Skip to content

Export and consume remote and shared voltage control - #461

Draft
m-bossart wants to merge 14 commits into
psy6from
mb/remote-control
Draft

m-bossart wants to merge 14 commits into
psy6from
mb/remote-control

Conversation

@m-bossart

@m-bossart m-bossart commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Remote and shared voltage control

Exports the remote and shared voltage control data to PSS/E and reads it in the solver.

What changes

  • The PSS/E writer carries IREG, VS and RMPCT on generators and the signed CONT with CR/CX on transformer windings (0 for a three-winding circuit regulating its star bus).
  • It also writes ICR/ICI and IFR/ITR/IDR on two-terminal DC lines, REMOT/RMPCT on VSC converters, FCREG/REMOT on FACTS devices and SWREG/SWREM on switched shunts.
  • RMPCT is each member's percent share of its ReactivePowerSharing group, and 100 for a device that regulates alone.
  • Discrete control, FACTS injections and the FACTS write-back read the regulated bus and reactive power through the unit-aware accessors.
  • update_exporter! keeps supplemental attributes, so a second export keeps sharing groups and impedance correction tables.
  • The exporter warns when a DC-voltage-controlling VSC converter has no DC voltage reference.
  • Round-trip testsets export PowerSystems-built and parser-built cases in v33 and v35, re-import them and compare the control fields.

Base

This branch sits on jd/psy6_correctness, which psy6 is taking in parts (#458 so far).
Only the last six commits belong to this PR, from dc8e32f (Export and consume remote and shared voltage control) on.
It will be rebased onto psy6 once the remaining parts merge.

Verification

Full suite: 48345 pass, 0 failures (2026-09-24, before the PowerSystems and PowerFlowFileParser rebase).

Stack

Merge in this order.
The Julia branches pin each other's mb/remote-control branches in [sources], and each pin moves back to main or psy6 as its dependency merges.

Depends on: Sienna-Platform/PowerSystems.jl#1819, Sienna-Platform/PowerFlowFileParser.jl#66, Sienna-Platform/PowerSystemCaseBuilder.jl#232, Sienna-Platform/PowerOpenAPIModels#28.
This branch also pins PowerNetworkMatrices jd/psy6_correctness.

🤖 Generated with Claude Code

jd-lara and others added 14 commits September 23, 2026 07:46
Pairs PowerFlows with PowerNetworkMatrices `jd/psy6_correctness`. No source change is
needed: the two nr-less overloads that branch had removed -- `branch_admittance(b)` and
`ybus_branch_entries(br)` -- are restored there, and the one PowerFlows call site for
either, `_build_controlled_device_set`'s `PNM.branch_admittance(branch)` in
src/discrete_control/control_metadata.jl, resolves again.

`ybus_branch_entries` had already moved to the nr form here in 37f7ac2, for the reason
that commit gives: a flow recomputed from solved voltages must be compared against the
admittance the Ybus was actually stamped with. That stays as it is. The restored overload
is for callers with no reduction in hand, which that one is not.
The root and test envs moved with `Track PNM's correctness branch`; docs/Project.toml kept
its own `[sources]` at PNM psy6, so the docs gate on this branch was building against the
other branch's PNM. `[sources]` is not inherited from a dev'd dependency, which is why this
file repeats the pins at all and why it has to be kept in step by hand.

Docs build against PNM jd/psy6_correctness: exit 0, no errors.
… reduce

The nested-reduction @test_broken indexed the `:ok` Symbol, so it threw and
recorded Broken regardless of behavior; the expanders were already fixed in
#437. Assert branch names, flows against an unreduced solve (AC, DC, PTDF)
and interior-bus write-back instead.

The AC reduce-then-solve testsets built `pf` without `network_reductions`, so
they compared unreduced vs unreduced. Build the reductions into `pf`, guard
both helpers against a pf that does not carry the reductions it claims, and
drop the dead commented-out Ward block. Radial stays convergence-only: PNM's
radial reduction is exact only under the DC approximation.
Si is a whole-block status, but the exporter wrote `number_engaged` into it. Since
PowerFlowFileParser.jl#59 that holds step counts (an in-service block engages all Ni
steps), so blocks came out as `S2 = 2`, `S3 = 3`, which is not valid PSS/E. A partly
engaged block cannot be spelled in the blocks at all; BINIT still carries the exact total.

Pin PSCB to lk/switched-shunt-solved-case (PowerSystemCaseBuilder.jl#227) so the case25
round-trip compares `nothing` against `nothing` for discretely switched shunts instead
of PSCB's 0.0 default. Revert the pin to psy6 once #227 merges.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Applies the verified PF-side findings of the 2026-09-22 psy6 review: PSS/E export
(LCC SETVL in MW, droop VSC DCSET sign, RATE/SBASE, solution records), discrete-control
sensitivity and batched refresh, polar/rect/mixed/FD NR cache reuse, DC loss factors and
backend resolution, post-processing and redistribution, LCC/VSC loss handling, typed
SolutionParameters replacing the solver_settings Dict, and concretely typed kernels.

rect_finalize_bus_injections! adds back only the constant-power (REF) or non-impedance
(PV) withdrawal, since the residual nets constant current and Y_bus_eff holds constant Z,
plus the HVDC term. FD's single-π test now uses PNM's arc-keyed
has_single_pi_equivalent instead of a ComplexF32 re-check with its own tolerance.

Tests run one worker per file under ParallelTestRunner with a shared test/includes.jl.

The case16/case25 PSS/E round-trips still fail on LCC active_power_flow: PSB-built
systems store transfer_setpoint per-unit although PSY documents MW (upstream).
PSY stores a power-mode transfer_setpoint per-unit on the LCC's base. The solver divided that
stored value by the system base again, scheduling LCCs on PSS/E-built systems at 1/100 of
their setpoint, and the export wrote the per-unit value as SETVL. Both now go through
`_lcc_transfer_setpoint(lcc, units)`, which wraps `PSY.get_value(..., Val(:mw), units)`; the
export asks for MW. Current-mode setpoints are amperes and pass through unchanged.

Hand-built test LCCs now set per-unit values, and the test env pins PSB to jd/pf_units_fix
(PowerSystemCaseBuilder.jl#229), which stores the setpoint per-unit on its legacy path too.
Replaces PowerFlows' local _lcc_transfer_setpoint with PSY.get_transfer_setpoint(lcc, units)
from PowerSystems.jl#1815. Requires #1815 on PSY psy6.
Residuals, Jacobians and HomotopyHessian no longer store data; it is
passed explicitly (R(data, x, t), J(data, t),
ACPowerFlowJacobian(data, residual, t)). data.polar_nr_cache held a
PolarNRCache whose residual and Jacobian pointed back at data. The D
type parameter is dropped from those types and from PolarNRCache, and a
testset guards against the field returning.

The reduced-power-flow helpers now check the reductions data actually
applied, as a set, instead of comparing pf's reductions to themselves.

PowerSystems psy6 requires input_basis on unit-bearing keyword
constructors; pass input_basis = PSY.CU at existing call sites. Pin
PowerSystemCaseBuilder to psy6, which now carries jd/pf_units_fix.
The PSS/E writer carries IREG, VS and RMPCT on generators, the signed CONT
with CR/CX on transformer windings (0 for a three-winding circuit regulating
its star bus), ICR/ICI and IFR/ITR/IDR on two-terminal DC lines, REMOT/RMPCT
on VSC converters, FCREG/REMOT on FACTS devices and SWREG/SWREM on switched
shunts, reading the new PowerSystems fields and ReactivePowerSharing groups;
RMPCT is each member's share of its group in percent, 100 for a device that
regulates alone. Discrete control, FACTS injections and the FACTS write-back
read the regulated bus and reactive power through the units-aware accessors.

Two round-trip testsets export PowerSystems-built and parser-built cases on
both raw versions, re-import them and compare the control fields against the
original records. The rest of the changes bring the suite up to date with
the psy6 line: input_basis on every unit-bearing test constructor and on the
exporter's synthetic HVDC generators, system_from_openapi in place of the
retired make_system, regulated buses on the helper transformers, switching
device ratings written in MVA, the VSC RDC rebuilt on the rated DC voltage
and RCOMP in ohm, a capped finite-difference probe step, and the LCC parity
tolerance the corrected line resistances need.
update_exporter! copied the system with the fast deep copy's default, which skips
supplemental attributes, so a second export after an update lost every sharing
group and impedance correction table. The copy now keeps them, and a testset
re-imports an export made after update_exporter! and checks the shares survive.

The droop VSC testset gives its DC-voltage-controlling converter a real DC
voltage reference; a 0 kV schedule is not a record the parser accepts.
RATE1 is in MVA like every other PSS/E rating; the parser copies it verbatim as
MVA and PowerSystems divides by the base on import.
A DC-voltage-controlling converter whose rated_dc_voltage and setpoint are both
zero writes DCSET as 0 kV, a record PSS/E cannot solve and the parser rejects;
the exporter now says so, naming the line and converter.

This branch has not been deployed

No deployments
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.

3 participants