feat(converter): import OpenDSS RegControl as a tap controller - #3074
feat(converter): import OpenDSS RegControl as a tap controller#3074Houmgaor wants to merge 7 commits into
Conversation
Transformers were previously imported at their solved OpenDSS tap ratio, baked directly into vn_hv_kv/vn_lv_kv. That freezes any on-load tap changer or RegControl at the operating point OpenDSS happened to solve, so it can never respond during a pandapower power flow -- understating, for example, the hosting capacity headroom a real regulator would create by tapping down as PV export raises voltage. Populate tap_side/tap_neutral/tap_min/tap_max/tap_step_percent/tap_pos from OpenDSS's MinTap/MaxTap/NumTaps instead, so the tap has something to actuate. This always happens and does not change solved voltages (verified against the existing no-regulator feeders to the same tight tolerance); OpenDSS's tap-position axis turns out to be centered on zero independent of whether MinTap/MaxTap are symmetric about 1.0, so tap_neutral is derived rather than assumed to be 0. Add an opt-in import_controllers flag (default False) that additionally imports each RegControl as a DiscreteTapControl, converting vreg/band/ ptratio to per-unit against the monitored bus. Line-drop compensation, reverse-mode regulation, and time delays are not modeled and are reported as warnings rather than guessed at; a RegControl monitoring a bus other than its own tapped winding's terminal is skipped rather than silently regulating the wrong bus.
438a546 to
faa29e2
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## develop #3074 +/- ##
===========================================
+ Coverage 72.12% 72.19% +0.07%
===========================================
Files 355 355
Lines 39378 39517 +139
===========================================
+ Hits 28400 28530 +130
- Misses 10978 10987 +9 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
Regarding coverage: |
I tried putting it in, but it fails the test pipeline. For some reason OpenDSS binding kills the newest version of pytest and since I wanted to get the release done, it was the easiest way to fix the pipeline. @Houmgaor , maybe you can take a look into why the pipeline fails? |
|
opendss will be added back to the |
|
@KS-HTK I'll leave this part to you then. @vogt31337 if you are fine with the idea, I can try a fix to the CI pipelines, unrelated to OpenDSS. It is annoying to never see them pass. |
|
@Houmgaor , yes you can Open Up a new PR and add opendss Back to the dev section in the pyproject.toml. Maybe you can Figuren Out what ist wrong. |
|
@Houmgaor , can you explain more about the Controller you are trying to convert? What does the ldc actually do? |
|
It's the LDC feature of an OpenDSS Implemented as It is not a Station Controller case. No remote bus to converge on, no iterative injection control. It's local, one-shot, tap-changer logic, so it sits on the |
Split _pick_tap_fields and _add_reg_controls into focused helpers to bring their cognitive complexity under SonarCloud's threshold (29 -> ~9 and 25 -> ~4), drop two redundant int() casts around round(), and build the tap kwargs as a dict literal (ruff RUF046, C408 / sonar S7498). Also restore the pydocstyle style the analyser actually enforces: no blank line before a class docstring (D211) and the summary on the first line (D212). Behaviour is unchanged; the converter suite passes unchanged.
Codacy has both D212 and D213 enabled, which no multi-line docstring can satisfy, so follow the codebase instead: summary on the second line, as in 677 of pandapower's 818 multi-line docstrings (59 of 74 in converter/). The D211 fix from the previous commit stands, since D203 is not enabled.
It was the only summary line in the file past 100 characters, and the only one Codacy still reported (as D212).
|
|
@vogt31337 OpenDSS testing is now merged (#3075), I updated this PR accordingly. Ready to merge on my side, let me know if you need any change. Better to do it sooner than later, some DSS networks currently return wrong values as the code stands. |



Summary
from_opendsspreviously imported a transformer at its solved OpenDSS tap ratio, baked directly intovn_hv_kv/vn_lv_kv. That freezes any on-load tap changer orRegControlat the operating point OpenDSS happened to solve, so it can never respond during a pandapower power flow. On a feeder with an LTC, that understates the voltage headroom a real regulator would create (e.g. by tapping down as PV export raises voltage), which is exactly the case hosting-capacity studies care about.This PR:
tap_side/tap_neutral/tap_min/tap_max/tap_step_percent/tap_posfrom OpenDSS'sMinTap/MaxTap/NumTaps/Tap()instead of folding the ratio intovn_hv_kv/vn_lv_kv, so the tap has something to actuate. This always happens and does not change solved voltages -- verified against the existing no-regulator feeders to the same tight tolerance as before. (One non-obvious finding along the way: OpenDSS's tap-position axis is centered on zero independent of whetherMinTap/MaxTapare symmetric about 1.0, sotap_neutralis derived rather than assumed to be 0 -- verified againstRegControls.TapNumber(), since it isn't documented.)import_controllersflag (defaultFalse) that additionally imports eachRegControlas aDiscreteTapControl, convertingvreg/band/ptratioto per-unit against the monitored bus (mind the line-to-neutral vs. line-to-line sqrt(3) factor).RegControluses line-drop compensation or reverse mode (neither is modeled), or monitors a bus other than its own tapped winding's terminal (in which case the controller is skipped rather than silently regulating the wrong bus).Line-drop compensation is deliberately out of scope for this PR -- nothing in pandapower's controller framework supports it today, and it's a bigger design decision (extend
DiscreteTapControlvs. a new controller class) than this diff should bundle. A regulator with LDC ignored is still far better than a frozen tap, provided the user is told, which the warning does.Question for maintainers
import_controllersdefaults toFalseso a plainfrom_opendss(...)+runpp(net)(the common case, never touchingnet.controller) is byte-for-byte unaffected by this PR. Would you prefer it default toTrueinstead? I erred on the conservative side because (a) it's a behavior change for anyone who already runspandapower.control.run_controlon an imported net, not justrunpp, and (b) the unsupported cases (LDC, reverse mode) are common on real regulators, not edge cases, so I didn't want a default that silently swaps in a simplified regulator model.Test plan
pytest pandapower/test/converter/test_from_opendss.py-- 24 passed (15 pre-existing + 9 new)import_controllers=Falseproduces zero controllers; voltage round-trip against OpenDSS still holds to the pre-existing tight tolerance; theDiscreteTapControlis created with independently-hand-computedvm_lower_pu/vm_upper_pu; adding PV export makes the controller tap down relative to baseline (the core point of the feature); LDC/reverse-mode settings warn without failing silently; a remote monitored bus skips the controller with a warning; a degenerate OpenDSS tap range (NumTaps<=0) falls back to the old baked-in-ratio behavior instead of dropping it; an invalidRegControl.TapWindingwarns instead of being silently ignored.