Skip to content

feat(converter): import OpenDSS RegControl as a tap controller - #3074

Open
Houmgaor wants to merge 7 commits into
e2nIEE:developfrom
Houmgaor:opendss-regcontrol-import
Open

feat(converter): import OpenDSS RegControl as a tap controller#3074
Houmgaor wants to merge 7 commits into
e2nIEE:developfrom
Houmgaor:opendss-regcontrol-import

Conversation

@Houmgaor

@Houmgaor Houmgaor commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary

from_opendss previously imported a transformer at its 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. 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:

  • Populates tap_side/tap_neutral/tap_min/tap_max/tap_step_percent/tap_pos from OpenDSS's MinTap/MaxTap/NumTaps/Tap() instead of folding the ratio into vn_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 whether MinTap/MaxTap are symmetric about 1.0, so tap_neutral is derived rather than assumed to be 0 -- verified against RegControls.TapNumber(), since it isn't documented.)
  • Adds 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 (mind the line-to-neutral vs. line-to-line sqrt(3) factor).
  • Warns explicitly, rather than guessing, when a RegControl uses 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 DiscreteTapControl vs. 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_controllers defaults to False so a plain from_opendss(...) + runpp(net) (the common case, never touching net.controller) is byte-for-byte unaffected by this PR. Would you prefer it default to True instead? I erred on the conservative side because (a) it's a behavior change for anyone who already runs pandapower.control.run_control on an imported net, not just runpp, 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)
  • New tests cover: tap fields populated instead of baked into vn; import_controllers=False produces zero controllers; voltage round-trip against OpenDSS still holds to the pre-existing tight tolerance; the DiscreteTapControl is created with independently-hand-computed vm_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 invalid RegControl.TapWinding warns instead of being silently ignored.

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.
@Houmgaor
Houmgaor force-pushed the opendss-regcontrol-import branch from 438a546 to faa29e2 Compare July 15, 2026 09:29
@codecov

codecov Bot commented Jul 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.19%. Comparing base (6184290) to head (4fe5cd5).
⚠️ Report is 9 commits behind head on develop.

Files with missing lines Patch % Lines
pandapower/converter/opendss/from_dss.py 93.33% 10 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Houmgaor

Copy link
Copy Markdown
Contributor Author

Regarding coverage: OpenDSS is not in the dev dependencies, so coverage is counted as 0% as the path is never exercised. Let me know if you are fine with adding OpenDSS as a dev dependency.

@vogt31337

Copy link
Copy Markdown
Contributor

Regarding coverage: OpenDSS is not in the dev dependencies, so coverage is counted as 0% as the path is never exercised. Let me know if you are fine with adding OpenDSS as a dev dependency.

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?

@KS-HTK

KS-HTK commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator

opendss will be added back to the all dependency group, this is then part of the dev dependency group. Coverage should show up in the future.

@Houmgaor

Copy link
Copy Markdown
Contributor Author

@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.

@vogt31337

Copy link
Copy Markdown
Contributor

@Houmgaor , yes you can Open Up a new PR and add opendss Back to the dev section in the pyproject.toml.
You can also try IT locally, IT Crashes immediatiely l, when you import opendss. I also opened an issue Up with them: dss-extensions/OpenDSSDirect.py#148

Maybe you can Figuren Out what ist wrong.

@vogt31337

Copy link
Copy Markdown
Contributor

@Houmgaor , can you explain more about the Controller you are trying to convert? What does the ldc actually do?
Maybe the Station Controller ist comparable?

@Houmgaor

Copy link
Copy Markdown
Contributor Author

It's the LDC feature of an OpenDSS RegControl: the relay holds a phasor-estimated downstream voltage, V = V_terminal + I·(R+jX), instead of its own terminal voltage. R/X are ohmic, PT/CT-secondary-referred, no real bus at the remote point.

Implemented as LineDropCompensationTapControl(DiscreteTapControl), overriding just _get_controlled_vm_pu to build that compensated phasor from res_bus vm/va, vn_kv, and the trafo's own p/q flow; falls back to plain DiscreteTapControl behavior when r_ohm=x_ohm=0.

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 DiscreteTapControl side rather than the BinarySearchControl/DroopControl side.

KS-HTK and others added 5 commits July 20, 2026 08:43
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).
@sonarqubecloud

sonarqubecloud Bot commented Aug 8, 2026

Copy link
Copy Markdown

@Houmgaor

Houmgaor commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants