dss-extensions / DSS-Python

Native, "direct" Python bindings (interface) and misc tools for a custom implementation of OpenDSS (EPRI Distribution System Simulator). Based on CFFI, DSS C-API, aiming for full COM API-level compatibility on Windows, Linux and MacOS, while providing various extensions.
https://dss-extensions.org/DSS-Python/
BSD 3-Clause "New" or "Revised" License
60 stars 4 forks source link

Incorrect size of data from monitor after disable an element and solve #55

Closed metab0t closed 7 months ago

metab0t commented 1 year ago

I test the following code on a simple test case and I found that the data from monitor is not updated if I disable a InvControl and solve again.

DSS file saved as pvinverter.dss

Clear

New Circuit.C bus1=A pu=1.0 basekv=13.8 
~ Z0=[10, 10] Z1=[10, 10]

New XYCurve.Eff npts=4 xarray=[.1 .2 .4 1.0] yarray=[1 1 1 1]

New XYCurve.FatorPvsT npts=4 xarray=[0 25 75 100] yarray=[1 1 1 1] 

New Loadshape.Irrad npts=24 interval=1 
~ mult=[0 0 0 0 0 0 .1 .2 .3 .5 .8 .9 1.0 1.0 .99 .9 .7 .4 .1 0 0 0 0 0]

New Tshape.Temp npts=24 interval=1 
~ temp=[25 25 25 25 25 25 25 25 35 40 45 50 60 60 55 40 35 30 25 25 25 25 25 25]

New PVSystem.PV phases=3 bus1=A Pmpp=1000 kV=13.8 kVA=1000 conn=wye EffCurve=Eff 
~ P-TCurve=FatorPvsT %Pmpp=100 Temperature=25 irradiance=1  daily=Irrad Tdaily=Temp

New XYcurve.generic npts=3 yarray=[1 1 0] xarray=[1 1.02 1.1]

New InvControl.VoltWatt mode=VOLTWATT voltage_curvex_ref=rated 
~ voltwatt_curve=generic VoltwattYAxis=PMPPPU DeltaP_factor=0.45 activeP=0.00001

New Monitor.PV_currents1 element=PVSystem.PV terminal=1 mode=0
New Monitor.PV_currents2 element=PVSystem.PV terminal=1 mode=0
New Monitor.PV_currents3 element=PVSystem.PV terminal=1 mode=0

Set voltagebases=[13.8]
Calcvoltagebases

set casename=Daily_voltwatt_PMPPPU

Set maxcontroli=2000

Python file saved as test.py

# %%
import dss
dssengine = dss.DSS
dssengine.AllowChangeDir = False
dssc = dssengine.ActiveCircuit

from pathlib import Path

# %%
def print_monitor_info():
    for name in dssc.Monitors.AllNames:
        dssc.Monitors.Name = name
        print(f"Monitor: {name}, shape: {dssc.Monitors.AsMatrix().shape}, samplecount: {dssc.Monitors.SampleCount}")

# %%
model_path = Path(".") / "pvinverter.dss"

# %%
dssengine.Text.Command = "Clear"
dssengine.Text.Command = f"Redirect {str(model_path)}"

# %%
dssengine.Text.Command = "Set mode=daily time=(0, 0) stepsize=1h number=24"
dssc.Solution.Solve()
assert dssc.Solution.Converged

# %%
print_monitor_info()

# %%
dssengine.Text.Command = "Set mode=daily time=(0, 0) stepsize=1h number=60"
dssc.Solution.Solve()
assert dssc.Solution.Converged
dssc.Solution.Number

# %%
print_monitor_info()

# %%
dssc.Disable("InvControl.VoltWatt")
dssengine.Text.Command = "Set mode=daily time=(0, 0) stepsize=1h number=36"
dssc.Solution.Solve()
assert dssc.Solution.Converged
dssc.Solution.Number

# %%
print_monitor_info()

The output is:

Monitor: pv_currents1, shape: (24, 18), samplecount: 24
Monitor: pv_currents2, shape: (24, 18), samplecount: 24
Monitor: pv_currents3, shape: (24, 18), samplecount: 24
Monitor: pv_currents1, shape: (60, 18), samplecount: 60
Monitor: pv_currents2, shape: (60, 18), samplecount: 60
Monitor: pv_currents3, shape: (60, 18), samplecount: 60
Monitor: pv_currents1, shape: (36, 18), samplecount: 36
Monitor: pv_currents2, shape: (36, 18), samplecount: 36
Monitor: pv_currents3, shape: (60, 18), samplecount: 60

It is very strange that the size of data from three monitors are inconsistent in the last solution.

@PMeira Is this behavior a bug?

metab0t commented 1 year ago

When I add more monitors to observe the same variable, I notice that the last monitor is always inconsistent.

Monitor: pv_currents1, shape: (24, 18), samplecount: 24
Monitor: pv_currents2, shape: (24, 18), samplecount: 24
Monitor: pv_currents3, shape: (24, 18), samplecount: 24
Monitor: pv_currents4, shape: (24, 18), samplecount: 24
Monitor: pv_currents5, shape: (24, 18), samplecount: 24
Monitor: pv_currents6, shape: (24, 18), samplecount: 24
Monitor: pv_currents7, shape: (24, 18), samplecount: 24
Monitor: pv_currents8, shape: (24, 18), samplecount: 24
Monitor: pv_currents1, shape: (60, 18), samplecount: 60
Monitor: pv_currents2, shape: (60, 18), samplecount: 60
Monitor: pv_currents3, shape: (60, 18), samplecount: 60
Monitor: pv_currents4, shape: (60, 18), samplecount: 60
Monitor: pv_currents5, shape: (60, 18), samplecount: 60
Monitor: pv_currents6, shape: (60, 18), samplecount: 60
Monitor: pv_currents7, shape: (60, 18), samplecount: 60
Monitor: pv_currents8, shape: (60, 18), samplecount: 60
Monitor: pv_currents1, shape: (36, 18), samplecount: 36
Monitor: pv_currents2, shape: (36, 18), samplecount: 36
Monitor: pv_currents3, shape: (36, 18), samplecount: 36
Monitor: pv_currents4, shape: (36, 18), samplecount: 36
Monitor: pv_currents5, shape: (36, 18), samplecount: 36
Monitor: pv_currents6, shape: (36, 18), samplecount: 36
Monitor: pv_currents7, shape: (36, 18), samplecount: 36
Monitor: pv_currents8, shape: (60, 18), samplecount: 60
PMeira commented 1 year ago

It seems to be something related to the Disable function, it's actually disabling the monitor, not the InvControl. Investigating now; the behavior is consistent with the official OpenDSS, so it's not a bug we introduced here.

You can check right after the dssc.Disable("InvControl.VoltWatt"):

dssc.Disable("InvControl.VoltWatt")
elem = dssc.ActiveDSSElement
print(elem.Name, elem.Properties['enabled'].Val)

Sidenote: I see you already edited the post, but InvControl needs at least one Storage or PVSystem element, otherwise it will crash the official OpenDSS. On DSS-Extensions, it will just not do anything useful.

PMeira commented 1 year ago

Oh, and the sample count doesn't change when the monitor is disabled since it doesn't get any new sample and it is not cleared. You could force clear the monitors with a command reset monitors and the monitor should stay empty if disabled.

metab0t commented 1 year ago

I got it. The Disable function actually disables the last monitor instead of InvControl. So should it be considered a bug? How should the user disable the InvControl?

metab0t commented 1 year ago

The solution is to activate the element firstly 😢

dssc.SetActiveElement("InvControl.VoltWatt")
dssc.Disable("InvControl.VoltWatt")
PMeira commented 1 year ago

Found the issue. Funnily enough, I had accidentally fixed it in my current private some weeks ago.

For some context, there are two variables Name in the scope, and the Pascal compiler uses the wrong one (which is the circuit name). This (typically) results in the active element not being changed, thus disabling the latest active element, the monitor in your example.

We can fix that and prepare a new release soon, plus add some tests. Since this is not in the Python code, it will take a bit longer.

The solution is to activate the element firstly 😢

Yeah, that kinda defeats the purpose of the function. In Python, it may be preferable to use dssengine.Text.Command = 'InvControl.VoltWatt.enabled=n' for the time being (one function call instead of two).

From the code history, the Enable and Disable functions never worked properly in the official COM implementation. I can report that in the forum.

metab0t commented 1 year ago

Thank you for the timely reply and plan to fix!

PMeira commented 1 year ago

dssengine.Text.Command = 'InvControl.VoltWatt.enabled=n' for the time being (one function call instead of two).

Or maybe dssengine.Text.Command = 'disable InvControl.VoltWatt is more clear.

Thank you for the timely reply and plan to fix!

And thank you for reporting this, @metab0t

EDIT: Reported to the official forum at https://sourceforge.net/p/electricdss/discussion/experts/thread/6c00144a5d/

PMeira commented 7 months ago

Sorry, forgot to close this. The fix is out since the 0.15.0 beta. We should announce 0.15.1 later today (already on PyPI).