GridOPTICS / GridPACK

https://www.gridpack.org/
43 stars 20 forks source link

Update data collection objects in dynamic simulation #214

Open bjpalmer opened 1 month ago

bjpalmer commented 1 month ago

Add functionality to update values in the data collection objects for buses and branches in dynamic simulation. These values can then be queried and extracted by other applications.

bjpalmer commented 1 month ago

I've started adding some code for generators. @jainmilan can you give me some variables that you want current values for?

tahmid-khan commented 3 weeks ago

In #176, I asked for a way to get the current value of voltage at each bus, not just generators and loads. Does/will this issue cover that ability?

bjpalmer commented 3 weeks ago

This will probably work for you, but it might be better to add some accessors to the the DSFullBus class so you can access the values directly. Using this method, you will need to update the data collections on all buses and branches (by calling a function in the factory class), then accessing the data collection object for each bus and finally getting the variable you want. We are developing this functionality to create a python interface so the dynamic simulation can work with another code, but it may not be the optimal way for all applications to get this data.

wperkins commented 2 weeks ago

This will probably work for you, but it might be better to add some accessors to the the DSFullBus [...]

This is on my todo list, along with Python methods. It should be done this week.

wperkins commented 2 weeks ago

This will probably work for you, but it might be better to add some accessors to the the DSFullBus [...]

This is on my todo list, along with Python methods. It should be done this week.

Both DS and HADREC applications now have an updateData() method, both C++ and Python.

wperkins commented 1 week ago

@bjpalmer, I'm investigating why CURRENT load data is not available after updateData(). It appears that there are two kinds of loads on the DS bus. One is counted by p_ndyn_load. These are governed by p_loadmodels[] and seem to update CURRENT values when asked (although, I have not run a case where p_ndyn_load > 0 on any bus). The other is counted by p_npowerflow_load. These seem to have their P and Q values stored in p_powerflowload_p and p_powerflowload_q, respectively. These load values are never saved to CURRENT data collection values, even if they change via scaling in the simulation.

I think DSFullBus::updateData() may need another loop over p_powerflowload_p and p_powerflowload_q where those values are put in the DataCollection. I got a little lost trying to figure out when what load applies, so I think you need to take a look.

Also, it appears that other simulation state queries only work on p_powerflowload_*, like DSFullBus::getLoadPower() and DSFullBus::getTotalLoadPower(). This seems inconsistent to me.

@jainmilan, I'm pretty sure this is why we get None when querying load power.

bjpalmer commented 1 week ago

We looked at this last week. It seemed to me that the generator objects were not getting created by the calculation and therefore nothing was getting copied to the data collections. The basis for this assumption was that no GENERATOR_MODEL variable was showing up in the data collections after adding a p_factory->dumpData() to the code immediately after calling updateData. We only ran for 1 timestep, but as far as I could tell, it didn't look like the code was setting up generators for the test case we were looking at. The GENERATOR_MODEL variable is used inside the generator factory to create the appropriate generator instance.

wperkins commented 1 week ago

I've been running the 9-bus case with dsf.py (in current repository), where updateData() is called at the end of the simulation. I get CURRENT generator values for that, so it must work with some kinds of generators.

However, I'm complaining about load values.

The network is queried as follows:

def network_analytics_dump(ds_app):
    nbus = ds_app.totalBuses()
    for bus in range(nbus):
        print(bus,
              ds_app.getBusInfoInt(bus, "BUS_NUMBER"),
              ds_app.getBusInfoString(bus, "BUS_NAME"),
              ds_app.getBusInfoInt(bus, "BUS_TYPE"),
              ds_app.numGenerators(bus),
              ds_app.numLoads(bus),
              ds_app.getBusInfoReal(bus, "BUS_VOLTAGE_MAG"))
        for g in range(ds_app.numGenerators(bus)):
            print(" gen: ", g,
                  ds_app.getBusInfoInt(bus, "GENERATOR_NUMBER", g),
                  ds_app.getBusInfoString(bus, "GENERATOR_ID", g),
                  ds_app.getBusInfoReal(bus, "GENERATOR_PG", g),
                  ds_app.getBusInfoReal(bus, "GENERATOR_QG", g),
                  ds_app.getBusInfoReal(bus, "GENERATOR_PG_CURRENT", g),
                  ds_app.getBusInfoReal(bus, "GENERATOR_QG_CURRENT", g),
                  )
        for l in range(ds_app.numLoads(bus)):
            print("load: ", l,
                  ds_app.getBusInfoInt(bus, "LOAD_NUMBER", l),
                  ds_app.getBusInfoString(bus, "LOAD_ID", l),
                  ds_app.getBusInfoReal(bus, "LOAD_PL", l),
                  ds_app.getBusInfoReal(bus, "LOAD_QL", l),
                  ds_app.getBusInfoReal(bus, "LOAD_PL_CURRENT", l),
                  ds_app.getBusInfoReal(bus, "LOAD_QL_CURRENT", l)
                  )

The results of this query are as follows:

0 1 'bus-1       ' 3 1 0 1.04
 gen:  0 None 1  0.669999999997691 0.7010759010981538 0.669999999997691 0.7010759010981538
1 2 'bus-2       ' 2 1 0 1.02533
 gen:  0 None 1  1.63 0.6028676952146661 1.63 0.6028676952146661
2 3 'bus-3       ' 2 1 0 1.02536
 gen:  0 None 1  0.85 0.4202344495139094 0.85 0.4202344495139094
3 4 'bus-4       ' 1 0 0 1.0018586298562873
4 5 'bus-5       ' 1 0 1 0.9816273801688302
load:  0 None 1  90.0 30.0 None None
5 6 'bus-6       ' 1 0 0 1.002520962766657
6 7 'bus-7       ' 1 0 1 0.9812684602469763
load:  0 None 1  100.0 35.0 None None
7 8 'bus-8       ' 1 0 0 0.9935621039389169
8 9 'bus-9       ' 1 0 1 0.9661929333427239
load:  0 None 1  125.0 50.0 None None

The lines starting with gen all have 2 P/Q values (inital and current). I don't this this would have happened if generator models were not set up.

bjpalmer commented 1 week ago

@jainmilan, were we looking at the same input problem that @wperkins is describing? If you dump out the data collections, do you see a LOAD_MODEL variable defined? I'm not completely sure about this, but I think load variables only get printed out if a dynamic load model is defined, so maybe the test you are running does not have one of these, in which case no LOAD_MODEL variable would be defined.

wperkins commented 1 week ago

So, there can be loads without a LOAD_MODEL? Shouldn't we be able to query those too?

bjpalmer commented 1 week ago

Those loads are static. We could query them, although I don't know if there is much point in doing it more than once. @jainmilan, @abhyshr, @yliu250, this is a domain question.

wperkins commented 1 week ago

Those loads are static. We could query them, although I don't know if there is much point in doing it more than once. @jainmilan, @abhyshr, @yliu250, this is a domain question.

I think it's an API consistency question.

@jainmilan needs to be able query all load P/Q's at arbitrary points in the simulation. He would first need to figure out what loads have a LOAD_MODEL and which don't and then query those loads differently (e.g., different field name), right? I think it would be more consistent, and simple, to have "static" load P/Q's duplicated in CURRENT data collection fields.

Also, are we sure that loads without a LOAD_MODEL (static) do not change during the simulation? DSFullBus::scaleLoadPower() and DSFullBus::resetPower() seem to modify p_powerflowload_*, but I don't know when or if those get called.

Also, based on my reading of DSFullBus::load() handling of load input, things could get pretty messed up if a bus has a mix of static and LOAD_MODEL loads.

bjpalmer commented 1 week ago

@jainmilan, @wperkins, @abhyshr, @yliu250, I modified the updateData function so that it will always produce a value.

https://github.com/GridOPTICS/GridPACK/blob/db1594df82218d27be0dfb6c89c27238c4793fdf/src/applications/modules/dynamic_simulation_full_y/dsf_components.cpp#L1291-L1322

jainmilan commented 2 days ago

@wperkins @abhyshr @yliu250 @bjpalmer I am trying to access the from and to p and q values using getBranchInfoReal but getting None. https://github.com/GridOPTICS/GridPACK/blob/14db5222474ffc54d287a0d6462ed329667cc0f9/python/src/dsf.py#L44-L56

bjpalmer commented 1 day ago

I don't think you are going to get anything using that format. The BRANCH_FROM/TO_P/Q_CURRENT variables are all indexed by a specific line within the branch object and it doesn't look like you are including the line index in your calls to getBranchRealInfo. You can verify that the line variables are stored in the data collection by using the dumpData() method.

bjpalmer commented 1 day ago

I restructured the code a bit so that the evaluation of the branch flows is in a separate function (evaluateBranchFlow) that is then called inside the branch updateData function before storing data in the data collection.