Skip to content

[WIP] StaticBranches for transformers; fix susceptance in DCP; PTDF phase shift injection; some refactoring - #217

Draft
acostarelli wants to merge 5 commits into
mainfrom
ac/transformers
Draft

[WIP] StaticBranches for transformers; fix susceptance in DCP; PTDF phase shift injection; some refactoring#217
acostarelli wants to merge 5 commits into
mainfrom
ac/transformers

Conversation

@acostarelli

Copy link
Copy Markdown
Member

No description provided.

Anthony Costarelli and others added 5 commits July 31, 2026 14:00
PowerSystems psy6 replaced Transformer2W/TapTransformer/PhaseShiftingTransformer
with TwoWindingTransformer, and Transformer3W/PhaseShiftingTransformer3W with
ThreeWindingTransformer, moving all series data onto PSY.TransformerCircuit.
POM was parked mid-migration: the broken code was commented out rather than
ported, leaving three-winding transformers unsupported and nine test files
disabled.

Support StaticBranch, StaticBranchBounds and StaticBranchUnbounded for both
arities, with tap and phase-shift angle read as fixed data from each circuit.

Delete the tap/phase control formulations outright rather than porting them:
TapControl, VoltageControlTap and PhaseAngleControl, plus PhaseAngleControlLimit,
their slack specs and template-validation hooks, and the ~490 commented-out
three-winding lines in AC_branches.jl and branch_constructor.jl. TapRatioVariable
and PhaseShifterAngle go with them -- both are exported but have no producer or
consumer left, so they belong in the release notes as a breaking removal.

Fix three sites that assumed one arc per device and would have thrown on the new
types: _push_component_buses! and _get_area_from_to still called the deleted
get_primary_star_arc/get_secondary_star_arc/get_tertiary_star_arc, and LPACC's
device_by_name lookup would KeyError on a three-winding geometry (named
"X_winding_i" against a device-name key).

Emit one BranchGeometry per modeled arc instead of per device, via
_branch_elements/_element_name. A three-winding transformer expands into PNM's
per-circuit wrappers -- which carry the "X_winding_i" names PNM's reduction maps
already use -- so 2W, 3W and lines all flow through the same generic builders and
no three-winding special case is needed. Unavailable circuits are skipped, since
transformer availability is `any` over circuits and PNM gives a dead circuit no
arc.

Divide the DC susceptance by the tap: PNM's Ybus places the ideal ratio on the
from side, so the DC linearization is p = -(b/tap)*(dtheta - alpha). Worth
2-7% on c_sys14's transformers (tap 0.932/0.969/0.978); no line moves.

Handle Union{Nothing,Float64} circuit ratings. PSS/E RATE=0 parses to nothing and
means genuinely unlimited: such branches get no rate rows and unbounded flow and
current variables. A concrete 0.0 remains a loud data error. A branch rating time
series without a static base rating now errors instead of failing on arithmetic.

Align the AC from-side self terms to PNM's Ybus convention Y11 = y/tm^2 + y_fr,
in ACP, ACR, LPACC and IVR. POM followed PowerModels in folding the shunt inside
the ideal transformer as (g + g_fr)/tm^2, which agrees only at tm == 1 and
diverges exactly for an off-nominal tap with a magnetizing shunt -- the case this
port introduces. Verified term by term against PNM._pi_to_ybus. Since tap is now
fixed data, it is folded into the coefficients at construction, so nothing
divides inside the time loop.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
POM's native DCP Ohm's law derived its susceptance from the Ybus series
admittance as x/(r^2 + x^2), while POM's PTDF path takes it from PNM's
BA_Matrix, which uses the r-free 1/x. The two DC network models therefore
produced different flows for the same system: on c_sys14 the susceptance differs
by 5-122% across 15 of 16 lines, worst on Line14, where r/x = 1.1 gives 5.00
against 2.25.

Read b_dc from PNM.get_series_susceptance -- the same (1/x)/tap that BA_Matrix
assembles -- and the direct-arc shift from PNM.get_series_phase_shift. DC is
defined as the r-free linearization, so this is also the standard convention,
and it is what POM's own tap-control test asserted before that formulation was
removed.

The AC families are untouched and keep the r-corrected series admittance from
the full pi-model; that asymmetry is deliberate and now documented at
BranchGeometry rather than being an accident of which builder computed what.

This changes DC flows on every branch with non-negligible resistance, so DCP
golden numbers move. Split out from the transformer port so the two are
reviewable -- and revertible -- independently.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PNM's BA_Matrix and PTDF deliberately exclude a branch's phase-shift angle from
their susceptances and leave the correction to the consumer. POM never applied
it: alpha was consumed by every angle-based builder (DCP, ACP, ACR, LPACC, IVR)
and nowhere in the factor-matrix paths. That was latent while the only
alpha-carrying formulation was PhaseAngleControl, which is now deleted, but with
alpha read as fixed circuit data any phase-shifting transformer silently got
flows that ignored its shift.

Model a shifter as its shift-free branch plus the injection pair (+b*alpha at
from, -b*alpha at to), then take b*alpha back out of the shifter's own row
(f = b*dtheta - b*alpha). Since alpha is fixed data these are constants, so the
whole correction is a scalar offset per factor row.

Fold that offset into each row inside _make_flow_expressions! rather than writing
the injection pair into ActivePowerBalance. PTDF flows are built per device type,
in each branch DeviceModel's ModelConstructStage, so a transformer's injection
added to the balance during its own stage would never reach a branch type
constructed earlier -- correctness would depend on template ordering. Folding it
into the row has no ordering dependency, and ptdf_col is already in hand, so the
two bus lookups are free.

The same gap existed in the security-constrained path, where post-contingency
flows are a MODF row times nodal injections. Corrected there too, per outage:
_surviving_shift_injections drops a shifter the contingency removes, so a
post-contingency flow cannot carry a shift that is no longer there. That branch
is defensive today -- PNM's _assert_not_phase_shifting rejects contingencies on
phase-shifting components, so none can currently remove a shifter -- and is
documented as such.

Verified against DCP, which derives its law straight from PNM's Ybus: on c_sys14
with alpha in {0, 0.02, 0.05, -0.03} on Trans2, DCP and PTDF flows agree to 1.4e-8
while the flows themselves move materially with alpha (0.354 -> 0.327 -> 0.287 ->
0.393), so the agreement is not vacuous. Post-contingency flows likewise move with
alpha on a surviving shifter.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seven test files were disabled for the psy6 migration. Five come back; the two
PowerFlows ones stay disabled because PowerFlows@psy6 still does not precompile
against PowerNetworkMatrices@psy6.

Delete test_native_tapcontrol.jl and test_voltage_control_tap_models.jl (464
lines): they test TapControl / VoltageControlTap / PhaseAngleControl, which no
longer exist. The DC physics assertion from the first one is preserved in the
rewritten c_sys5 check described below.

Migrate the surviving files. Transformer2W and TapTransformer both map to
TwoWindingTransformer, so paired device models and constraint keys collapse to
one entry; Transformer3W maps to ThreeWindingTransformer. Transformer ratings
move from the parent to the circuit, since psy6 defines no parent-level
get_rating for either arity.

Add test_utils/transformer_test_utils.jl with add_three_winding_transformer!.
Every 3W fixture has to be hand-built: no PowerSystemCaseBuilder system carries a
ThreeWindingTransformer, a nonzero magnetizing_shunt, or a circuit base_power
different from the system base.

Rewrite the three-winding testset in test_native_dcp_acp_models around the
per-circuit path: winding names match PNM's "<name>_winding_i" keys, each
circuit's branch_admittance carries its own tap and shift, the geometry walk
yields one entry per circuit terminating at the star bus, and the DCP build
satisfies the DC law per circuit against the star-bus angle including a shifted
secondary. The unrated tertiary (PSS/E RATE=0 -> nothing) gets no rate row.

Re-baseline the c_sys5 DC ohm-law assertion. It called PSY.get_series_admittance,
which psy6 moved to PNM, and asserted the r-corrected susceptance. It now uses
PNM.get_series_susceptance to match the convention native DCP and PTDF share.

Restore the model-free branch_rating arity. Five call sites (variable-creation
bounds, the time-series multiplier) have no DeviceModel in scope, and the
DeviceModel-taking methods cannot serve them.

Rework the PNM-rating-aggregator invariant testset. It asserted
get_equivalent_rating == PSY.get_rating(branch, PSY.SU), which has no method for
a transformer. It now compares against POM's circuit-aware branch_rating and
additionally asserts every fixture transformer's circuit base_power equals the
system base -- the condition the agreement actually depends on, since PNM reads
the device-base leaf and a mixed-base transformer would diverge.

One case is parked as @test_broken: native DCP under radial + degree-two
reduction on c_sys14 builds but is rate-limit infeasible. Diagnosed as
independent of this port -- it fails identically with the pre-port susceptance
convention, inflating every rating to 100 pu makes it solve, unreduced c_sys14
solves, and ACP under the same reduction solves. The comment records that so it
is picked up deliberately rather than rediscovered.

Suite: 103806 passing, 1 broken. Note the two files that failed in an 8-job run
pass individually and at --jobs=6; the enabled suite is now 103.6k tests against
30.9k before, so solver contention is the cause, not a regression.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Ports transformer handling to the psy6 device model (TwoWindingTransformer / ThreeWindingTransformer circuits), refactors native branch construction and bounds to be reduction-aware, and aligns DC/PTDF physics by using PNM’s DC susceptance and explicit phase-shift injection corrections.

Changes:

  • Replace legacy transformer formulations (VoltageControlTap/TapControl/PhaseAngleControl) with static-branch modeling and circuit-based transformer support, including three-winding star-arc decomposition.
  • Refactor branch variable creation/bounding and rate-limit row construction to be network-reduction-aware and to correctly handle unrated (nothing) transformer circuits.
  • Fix/align DC flow physics across native DCP/PTDF/MODF by using PNM.get_series_susceptance and applying phase-shift injection offsets in PTDF/MODF expressions.

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/test_voltage_control_tap_models.jl Removed VoltageControlTap test coverage (feature removed/disabled).
test/test_utils/transformer_test_utils.jl Adds hand-built three-winding transformer fixtures and naming helpers for tests.
test/test_native_tapcontrol.jl Removes TapControl DCP tests (feature removed/disabled).
test/test_native_network_reductions.jl Updates transformer type naming and removes PhaseAngleControl reduction test.
test/test_native_lpacc_model.jl Updates validation expectations after removing VoltageControlTap from LPACC gating.
test/test_native_dcp_acp_models.jl Updates DCP physics check to use DC susceptance; adds three-winding circuit decomposition tests; marks a reduced DCP solve as broken.
test/test_device_branch_constructors.jl Updates constructors/tests to TwoWindingTransformer and ThreeWindingTransformer circuit APIs; adjusts rating/base-power invariants.
test/test_ac_transmission_security_constrained_models.jl Migrates SC tests to TwoWindingTransformer and psy6 three-winding naming.
test/runtests.jl Re-enables several previously-disabled test files by removing them from DISABLED_TESTS.
test/includes.jl Includes new transformer test utilities.
src/utils/psy_utils.jl Introduces _branch_elements / _element_name to model multi-arc three-winding transformers via PNM circuit wrappers.
src/PowerOperationsModels.jl Removes exports and includes for disabled transformer control formulations/variables.
src/operation/template_validation.jl Drops VoltageControlTap-specific LPACC gating and regulated-bus collision detection code.
src/network_models/network_constructor.jl Removes now-obsolete special-case comments/paths for Transformer3W DCP expression handling.
src/network_models/instantiate_network_model.jl Updates three-winding bus collection to iterate transformer circuits.
src/core/variables.jl Removes TapRatioVariable and PhaseShifterAngle variable types (disabled/removed formulations).
src/core/network_formulations.jl Minor comment adjustment around PTDF vs DCP model hierarchy.
src/core/formulations.jl Removes disabled TapControl/VoltageControlTap/PhaseAngleControl formulation stubs.
src/core/constraints.jl Generalizes FlowLimitConstraint docstring away from PhaseShiftingTransformer wording.
src/core/branch_slack_specs.jl Removes TapControl slack spec (disabled/removed formulation).
src/common_models/add_to_expression.jl Removes PhaseShiftingTransformer PTDF injection path; adjusts PTDF orientation sign handling after removing transformer3W_map branch.
src/common_models/add_parameters.jl Adds explicit error when a branch rating time series exists but static rating is nothing.
src/ac_transmission_models/voltage_control_tap_models.jl Deletes VoltageControlTap implementation.
src/ac_transmission_models/transformer_models.jl Deletes TapControl/PhaseAngleControl transformer models implementation.
src/ac_transmission_models/security_constrained_branch.jl Threads phase-shift injection corrections into MODF post-contingency flow expressions; updates slack variable creation API usage.
src/ac_transmission_models/branch_constructor.jl Refactors slack/flow variable creation to use device_model-aware add_variables! signatures and removes deferred branch_rate_bounds! calls.
src/ac_transmission_models/AC_branches.jl Major refactor: reduction-aware variable bounds via branch_variable_bounds, unrated-circuit behavior, DC susceptance/phase-shift alignment, PTDF/MODF phase-shift offsets, and multi-arc three-winding geometry handling.
docs/src/reference/formulation_library.md Updates documentation to reflect new variable-bounds approach (no branch_rate_bounds! post-pass).
Suppressed comments (3)

test/test_device_branch_constructors.jl:179

  • rate_limit2w is computed from tap_transformer instead of transformer, so Trans4’s rating is ignored and the assertions use the wrong bound.
    rate_limit2w = PSY.get_rating(PSY.get_circuit(tap_transformer), PSY.SU)

test/test_device_branch_constructors.jl:496

  • rate_limit2w is computed from tap_transformer instead of transformer, so Trans4’s rating is ignored and the PTDF/DC assertions use an incorrect bound.
    rate_limit2w = PSY.get_rating(PSY.get_circuit(tap_transformer), PSY.SU)

test/test_device_branch_constructors.jl:559

  • rate_limit2w is computed from tap_transformer instead of transformer, so Trans4’s rating is ignored and the AC assertions use an incorrect bound.
    rate_limit2w = PSY.get_rating(PSY.get_circuit(tap_transformer), PSY.SU)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

transformer = PSY.get_component(Transformer2W, system, "Trans4")
rate_limit2w = PSY.get_rating(tap_transformer, PSY.SU)
transformer = PSY.get_component(TwoWindingTransformer, system, "Trans4")
rate_limit2w = PSY.get_rating(PSY.get_circuit(tap_transformer), PSY.SU)
Comment thread src/core/constraints.jl
struct FeedforwardEnergyTargetConstraint <: ConstraintType end
"""
Struct to create the constraint that set the flow limits through a PhaseShiftingTransformer.
Struct to create the constraint that set the flow limits through a branch.
@github-actions

Copy link
Copy Markdown

Performance Results

Version Precompile Time
Main 3.066596035
This Branch 3.028898287
Version Build Time
Main-Build Time Precompile 89.180377777
Main-Build Time Postcompile 1.07940439
This Branch-Build Time Precompile 86.124023693
This Branch-Build Time Postcompile 1.313141355
Version Solve Time
Main-Solve Time Precompile 408.366203591
Main-Solve Time Postcompile 375.624096434
This Branch-Solve Time Precompile 2062.731382714
This Branch-Solve Time Postcompile 2035.156697115

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants