Fix DiscreteShuntController controlled_bus reindexing - #3065
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #3065 +/- ##
========================================
Coverage 71.63% 71.64%
========================================
Files 355 355
Lines 39370 39372 +2
========================================
+ Hits 28204 28207 +3
+ Misses 11166 11165 -1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Thanks. I checked the remaining CI failures. The The The focused regression test passes locally: |
|
@Ehsanpoly , I understand your Idea, but I would not like to do it this way. Since your implementation modifies the inner data structure of all controller, if the have a bus attribute. You cannot per se say that this structure will always hold the bus mapping. In my opinion it would be better to create an interface / put a method in the controller parent object, which takes the job and can be overwritten by controller developer if needed. This way you make sure, not to get any side effects, which can be really hard to track. Therefore I will reject this PR. |
|
What type of controller operates directly on a bus? Only these controllers should store a bus reference. If a controller is for a shunt or trafo it should always look up the bus it's controlled element is at. And not store the reference. If this lookup has to be done often in a single timeseries execution it could be cached for the run, but on the next run it should be reevaluated. Since I am rather sure that most controllers are not "controlling" a bus, since there is not much to control there, I would rather suggest just to remove the stored variable all together and replace it with a property that activly gets the current bus from the networks data structure. |
|
@vogt31337 @KS-HTK Thank you for the feedback. I agree that my previous implementation was too broad because it modified controller internals generically from I will revise the PR by removing the generic controller update from |
|
Yeah Sounds better @Ehsanpoly, and we will introduce an Interface for Controller, so If your Controller depends in the voltage of a Bus for Exemplar IT will "Auto" Update. |
3b19994 to
e41917b
Compare
|
@vogt31337 I revised the PR according to your feedback. The generic controller update in I also updated the regression test to cover Thank you for your hints. |
| def controlled_bus(self, net): | ||
| if self.bus_index is not None: | ||
| return self.bus_index | ||
| return net.shunt.at[self.shunt_index, "bus"] |
There was a problem hiding this comment.
Will this function not have the same Issue?
So on its first call self.bus_index may be None, but on any subsequent call self.bus_index should never be None, as it is never unset.
I would remove the if and have only the net.shunt.at… return. this way it would resolve each time.
But this might be slow.
The other option is to have a property controlled_bus, using a setter, getter and deleter and adding a call to each controllers deleter at the end of a loadflow/timeseries. To only resolve the call once.
@vogt31337 What is you're opinion on this? Will having the two calls to this in the control_step be to costly for a timeseries? Or should we just use the full lookup in those two cases? (or add a local var in the control_step that saves it, reducing it to one lookup in each control step?)
There was a problem hiding this comment.
@KS-HTK Thank you, I agree. To avoid keeping any stale bus reference, I will simplify controlled_bus() so it always resolves the current bus from net.shunt.
To keep the lookup cost minimal, I will store the result in a local variable inside control_step() and is_converged() instead of calling the lookup repeatedly. I will leave the broader cached property / controller interface idea to a separate design change.
There was a problem hiding this comment.
Problem is you always do a lookup then. That's why I wanted to have an Interface with an overwritable function.
I think the Bus Array ist for vectorized Access to get Performance Out of it, If you want to Control 10.000 shunts at once.
There was a problem hiding this comment.
@vogt31337 Thank you, I agree with the performance concern.
Would you prefer that I revise this PR toward the controller-interface approach instead of the dynamic lookup approach?
My understanding is that the clean design would be:
- Add a default no-op bus-reindex hook to the controller base class, for example
reindex_buses(self, bus_lookup). - Let
reindex_buses(net, bus_lookup, ...)call this hook for controllers, instead of inspecting or mutating arbitrary controller attributes. - Override the hook only in
ShuntController, where the controller knows exactly which bus reference it owns. - Keep the cached
controlled_busfor runtime performance, but update it explicitly during bus reindexing.
This would avoid repeated pandas lookups in control_step() / is_converged(), while also avoiding the original broad side effect of mutating every controller with a controlled_bus attribute.
655a32b to
facf3e3
Compare
|
|
I updated the implementation according to your comment.
|



Fixes #2946.
This updates controller object bus references during bus reindexing. Previously,
create_continuous_bus_index()updated element tables such asnet.shunt.bus, butDiscreteShuntController.controlled_buscould remain stale.A regression test was added for
DiscreteShuntControllerafterdrop_buses()followed bycreate_continuous_bus_index().