Skip to content

Psy6 - #346

Draft
jd-lara wants to merge 167 commits into
mainfrom
psy6
Draft

Psy6#346
jd-lara wants to merge 167 commits into
mainfrom
psy6

Conversation

@jd-lara

@jd-lara jd-lara commented Aug 17, 2026

Copy link
Copy Markdown
Member

opening to trigger CI/CD

luke-kiernan and others added 30 commits March 30, 2026 15:50
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the placeholder `Float64` getter target with explicit unit systems:
`SU` (system base) for impedances, admittances, and shunts used in matrix
construction, and `DU` (device base) for equivalent ratings, which must work
on detached branches. Thread `units::IS.AbstractUnitSystem` through the
`get_series_susceptance` accessors (PSY now requires it).

Pin InfrastructureSystems/PowerSystems (and test deps PowerSystemCaseBuilder/
PowerFlowFileParser) to their units-aware branches via `[sources]` so CI can
resolve the as-yet-unreleased upstream changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Re-point [sources] from the units feature branches to the next-release
branches now that they are merged upstream:
  - InfrastructureSystems: lk/units-domain-agnostic-is4 -> IS4
  - PowerSystems:          lk/units-fold-psu            -> psy6
  - PowerSystemCaseBuilder lk/units-table-driven        -> psy6
  - PowerFlowFileParser:   fork lk/units-fixes          -> NREL-Sienna main
    (the fix is merged to main but predates the only release, v0.1.0)

Fix two breakages surfaced by the bump to psy6:
  - virtual_lodf_calculations.jl: pass PSY.SU to get_series_susceptance
    in _extract_branch_susceptances_by_arc (missed call sites; the
    accessor now requires an explicit unit system).
  - test_arc_types_and_reductions.jl: attach the MixedBranchesParallel
    test's Line/TapTransformer to a System so the system-base getters in
    ybus_branch_entries resolve (psy6 now throws on detached components).
  - test_connectivity.jl: relax the VirtualPTDF/VirtualLODF cross-method
    equality checks from == to isapprox; the on-demand paths now differ
    by ULPs on the IS4/psy6 stack.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Finish threading the unit-aware PSY getter API through the subsystems the
original migration missed, surfaced by the bump to the psy6/IS4 release
branches:

- connectivity_checks.jl: reimplement find_connected_components with PNM's
  own union-find instead of PowerModels.calc_connected_components, which
  PowerSystems no longer re-exports (PowerModels dependency dropped).
- common.jl, network_modification.jl: pass PSY.SU to get_series_susceptance
  and get_impedance_active/reactive_power in the contingency/MODF delta
  computations (all operate on system-base, system-attached components).
- Ybus.jl, BranchesSeries.jl: the synthetic ward-equivalent
  GenericArcImpedance is detached by design and stores system-base impedance;
  read it back with device base (DU, identity) since system base would need a
  system base power a detached component cannot resolve.
- BranchesParallel.jl: document why get_impedance_averaged_rating uses system
  base (consistent weighting across the group; requires attached branches).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- test_connectivity.jl: compare the dense PTDF/LODF cross-method checks with
  isapprox + an absolute tolerance; the dense and on-demand paths drift by a
  few ULPs run-to-run on the IS4/psy6 stack and many entries are physically
  zero (relative tolerance is meaningless there).
- test_equivalent_getters.jl: attach the parallel branches to the system
  (skip_validation) so the system-base susceptance weighting can resolve.
- test_modf_lodf_reductions.jl: clone transformers via deepcopy + fresh
  identity instead of re-reading each field, and pass PSY.SU to
  get_series_susceptance on attached branches.
- test_ward_reduction.jl: read the detached ward-equivalent's susceptance with
  device base (DU).
- test_ybus_reductions.jl: setters now require unitful values (val * PSY.SU).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
VirtualPTDF, VirtualMODF, and VirtualLODF each rebuilt and stored the same
DC factorization (K/BA/A/ABA) and topology, duplicated the Woodbury outer
wrappers, and recomputed the same PTDF_A_diag / branch_susceptances_by_arc.

Introduce a private, unexported VirtualFactorCore that owns the factorization,
topology, solve scratch, solver lock, and the lazily-computed shared
PTDF_A_diag / branch_susceptances_by_arc. The three virtual matrices become
thin wrappers holding a `core` and forwarding to it via accessors and a
`getproperty` shim, so all existing public APIs (getindex, accessors,
get_post_modification_ptdf_row, ...) keep working unchanged.

A single core can now be shared across wrappers so the ABA matrix is
factorized once:
  vptdf = VirtualPTDF(Ybus(sys))
  vmodf = VirtualMODF(vptdf, sys)
  vlodf = VirtualLODF(vptdf)

- New src/virtual_factor_helpers.jl relocates _create_factorization,
  _solve_factorization, _extract_arc_susceptances,
  _extract_branch_susceptances_by_arc, _get_PTDF_A_diag so they precede the core.
- New src/virtual_factor_core.jl defines the struct, accessors, lazy getters,
  and the Ybus constructor.
- Woodbury outer wrappers collapse to one method on the core; the wrappers
  forward to it.
- dist_slack stays a PTDF/LODF wrapper concern (unused by MODF), never on core.
- Added test/test_virtual_shared_core.jl asserting one shared factorization and
  numerical equivalence vs independently built objects.
Confine getfield to the field-getter definitions and the getproperty
hooks. Introduce get_core (and get_cache/get_dist_slack/... companions)
on VirtualPTDF/VirtualMODF/VirtualLODF and read wrapper fields through
these accessors everywhere else, including the shared-core tests.
…truct-Og8R3

Extract shared VirtualFactorCore from virtual matrices
# Conflicts:
#	src/virtual_lodf_calculations.jl
Units migration: adopt unitful PowerSystems getter API
Introduce `populate_cache` for VirtualPTDF, VirtualLODF, and VirtualMODF so
callers can pre-warm row caches over an iterable of components/contingencies
using the solver backends' batched multi-RHS `solve_sparse!` instead of one
single-RHS solve per row. This amortizes the ABA factorization solves that
dominate optimization-problem builds on large systems.

- VirtualPTDF / VirtualLODF: gather requested arc rows, build one sparse RHS
  of the corresponding BA columns, solve in a single batched call, then scatter
  (PTDF) or apply the LODF map (A*X .* inv_PTDF_A_diag) across all columns at
  once. Accepts integer indices, arc bus-pair tuples, or branch-name strings.
- VirtualMODF: batch the pre-contingency BA-column solves over the union of all
  monitored arcs and every contingency's modified arcs. The monitored-arc solve
  is contingency-independent, so it is computed once and reused; Woodbury
  factors per contingency are assembled from the precomputed solves with no
  further linear solves. Monitored set is supplied by the user.
- RowCache: add set_persistent_row!/pin_row! so populated rows are pinned and
  never evicted by later lazy lookups, plus warn_if_over_capacity.
- Backend dispatch routes to KLU's solve_sparse! or AccelerateWrapper's, with a
  column-by-column fallback for other factorization backends.
- Export populate_cache; add tests covering PTDF/LODF/MODF equivalence with the
  lazy getindex path, pinning, identifier resolution, and error handling.

https://claude.ai/code/session_011TFwa4Hsdse4XYEeuHmYro
…property

Merge of origin/psy6 moved the virtual matrices' shared state into
VirtualFactorCore. Rework populate_cache so every wrapper field is read through
its proper getter (get_core, get_cache, get_cache_lock, get_dist_slack,
get_dist_slack_normalized, get_inv_PTDF_A_diag, get_row_caches,
get_woodbury_cache, get_max_cache_size_bytes, get_contingency_cache,
get_arc_lookup, get_tol, get_ref_bus_position) instead of relying on the
wrappers' getproperty forwarding. Core container fields are accessed directly
on the VirtualFactorCore, matching _compute_ptdf_row/_compute_lodf_row and the
Woodbury kernel.

The batched multi-RHS solve and the from-precomputed-solve Woodbury assembly
are unchanged and remain byte-for-byte consistent with the post-merge
woodbury_kernel.jl. Tests read cache/core/contingency state through getters too.
The redesigned backend routes array getindex through to_index, which resolves
the row against the arc-tuple lookup, so vptdf[i, :] with an integer row no
longer works (integer+integer and arc-tuple+colon do). Rework the PTDF/LODF
tests to index rows by arc tuple, and cover integer-keyed population by
inspecting the row cache directly. VirtualMODF keeps its integer
getindex(::Int, contingency) method, so those tests are unchanged in spirit.

Also apply the JuliaFormatter wrapping flagged by reviewdog on
src/populate_cache.jl (generic _solve_multi_rhs!, the mods comprehension, and
the _woodbury_factors_from_base call) and restructure the solver-looped
testsets to a begin/for form that stays within the line-length margin.
…back

- VirtualMODF populate_cache resolves monitored arc tuples through
  _monitored_arc_index, so an arc eliminated by network reduction yields the
  descriptive VirtualMODF error instead of a raw KeyError.
- VirtualPTDF/VirtualLODF populate_cache build each stored row (scatter,
  dist-slack, sparsify) outside cache_lock and take the lock only for the
  double-check + insert, matching the cached_row_lookup pattern and shortening
  the critical section.
- The generic _solve_multi_rhs! fallback now checks `applicable` and raises a
  clear, actionable error when a backend implements neither solve_sparse! nor
  _solve_factorization, instead of surfacing a raw MethodError.
- Add shared RowCacheValue alias for the cached-row value type.
Replace the remaining `vmodf.arc_susceptances` field reads in the
populate_cache tests with the `_get_arc_susceptances` accessor, so no code in
this change reaches struct fields through the wrappers' getproperty forwarding.
Add a test that backs a VirtualPTDF, VirtualLODF, and VirtualMODF with one
shared VirtualFactorCore (the common ABA factorization) and runs populate_cache
on each. Asserts the populated rows match independently-built matrices and that
populate_cache reuses the single shared factorization object (get_core(...).K
=== core.K) rather than rebuilding it — confirming the feature is built on the
common factorization backend introduced by the psy6 redesign.
- Generic _solve_multi_rhs! fallback now uses the value returned by
  _solve_factorization instead of assuming an in-place solve, so a backend that
  returns a fresh vector is handled correctly.
- VirtualMODF populate_cache docstring no longer claims branch-name strings are
  valid monitored identifiers; _resolve_monitored_index accepts integer indices
  and bus-pair tuples (matching the MODF getindex API).
- Resolve contingencies/monitored arcs inside core.solver_lock so the
  contingency_cache reads (UUID/Outage inputs) match getindex's locking and
  cannot race with clear_all_caches!.
…e-KFeWX

Add populate_cache for batched multi-RHS warming of virtual matrices
…ance, three_winding_arcs)

Promote the π-model admittance helpers that lived inline in PowerSimulations.jl's
PowerModels translator (get_branch_to_pm) and were later copied into
PowerOperationsModels.jl's native DCP/ACP builders as private `_*` helpers, into a
single public, reduction-aware home in PNM:

- branch_admittance(b) for lines / 2-winding transformers, and
  branch_admittance(segment, nr) for reduction-aggregated arcs
- reduced_arc_admittance(nr, from_no, to_no)
- winding_admittance(w) and three_winding_arcs(d) for 3-winding transformers
- branch_flow_limits(branch)

New file src/BranchAdmittance.jl (included after common.jl); exports added. Tests in
test/test_branch_admittance.jl ported from POM's native model tests plus a
three_winding_arcs decomposition check.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
jd-lara and others added 30 commits September 3, 2026 15:13
…guard

Let reverse indexing defer to the forward filter verdict
A branch with r == x == 0 has series susceptance 1/x == Inf. Ybus assembly
substitutes the reduction's minimum retained impedance for such a branch, but
the DC scalar accessors read the stored reactance, so the two sides disagreed.

compute_parallel_multiplier and get_series_phase_shift divided Inf by Inf and
returned NaN. BA_Matrix's phase-shifter arm read the component, got Inf, and
its non-finite fallback turned that into zero DC coupling for the arc, while
the same matrix's symmetric-branch arm read Ybus and got the substituted value.

_finite_series_susceptance guards the accessor and substitutes only when the
result is non-finite, so every non-degenerate branch keeps one source of truth.
Move every get_series_susceptance body into _series_susceptance_raw and
make get_series_susceptance delegate. _finite_series_susceptance now
calls the raw layer directly instead of the public accessor, so a
future change making get_series_susceptance reject non-finite values
won't affect the zero-impedance substitution path.
Chain equivalents now sum member reactances directly instead of inverting
each member's susceptance, so a zero-impedance segment contributes exactly
0.0 reactance rather than a transient Inf that the old reciprocal-sum had
to absorb.
network_modification.jl read a branch's raw series susceptance, which is
Inf for r==x==0, while BA-derived delta_b already used the substituted
epsilon. The mismatch misclassified a full outage as partial and scaled
the Pi-model delta by delta_b / Inf, leaving the Ybus unchanged. Switch
every call site to _finite_series_susceptance(segment, nr), and thread nr
into _direct_arc_ybus_delta so it can also pass min_x_eps into
ybus_branch_entries, whose Pi-model entries were themselves non-finite.
…actor weights

_extract_branch_susceptances_by_arc collects per-member susceptances that are
normalized downstream, so an Inf member produces NaN for the whole arc. Replace
get_series_susceptance reads with _finite_series_susceptance, which substitutes
the reduction's minimum retained impedance only when the raw value is non-finite.
_segment_susceptance_after_outage and _compute_series_outage_delta_b read
raw series susceptance, which is Inf for r == x == 0 branches, so their
outage deltas disagreed with the epsilon-substituted matrices. Both now
take nr and call _finite_series_susceptance instead.
…on sweep

BA_Matrix now substitutes the zero-impedance epsilon for a series-mapped
arc's susceptance instead of reading 1/x raw, matching every other site
converted for this bug class. Adds a property-based test verifying
component-derived susceptance agrees with the Ybus-derived value across
fixtures, guarding the whole class of Inf/NaN divergence rather than one
instance.
…ituting accessor

get_series_susceptance now throws for a branch with r == x == 0 instead of
silently returning Inf, across all six delegating methods (ACTransmission,
TwoWindingTransformer, TransformerCircuit, ThreeWindingTransformerCircuit,
AbstractBranchesParallel, BranchesSeries). get_effective_series_susceptance
is exported as the accessor consumers should use when they need the value
Ybus/BA assembly actually uses for such a branch.
get_impedance_averaged_rating read the asserting accessor, so a group holding a
zero-impedance member raised ErrorException rather than the ArgumentError its
docstring promised, and the non-finite guard below it was unreachable. It now
substitutes like the sibling multiplier does; a share is a ratio, so the constant
cancels.

_direct_arc_ybus_delta's three-winding arm built its Pi-model entries with the
default epsilon while the generic arm used the reduction's configured value, so a
non-default minimum_retained_impedance made a winding outage disagree with the
matrix it modifies.

get_effective_series_susceptance substitutes whenever the stored reactance is zero,
while Ybus assembly substitutes only when resistance and reactance are both zero, so
a purely resistive branch is outside the agreement its docstring claimed.

Comments and docstrings that named get_series_susceptance now name the substituting
accessor the code actually calls. The BA_Matrix ybus-constructor docstring was
detached by an intervening comment and bound nowhere. The susceptance property sweep
counts its checks so it cannot pass with none, and the series chain now exercises a
tapped leg with non-zero reactance, where a reciprocal slip would otherwise hide.
`_finite_series_susceptance`, `get_effective_series_susceptance`, and the error
helpers took an untyped segment. Segments carrying a series impedance span two
unrelated supertypes, since `PSY.TransformerCircuit` is a `DeviceParameter` rather
than an `ACTransmission`, so `SeriesSegment` names that union and the signatures
use it.

Comments that restated the code they sit above are gone; the rest keep the fact
that is not visible from the call and drop the surrounding explanation.
InfrastructureSystems#634 and PowerSystems#1791 renamed the per-unit
marker `DU`/`DeviceBaseUnit` to `CU`/`ComponentBaseUnit`, with no shims.
26 sites: the rating/impedance/flow-limit getters in BranchesSeries.jl,
BranchAdmittance.jl and ThreeWindingTransformerCircuit.jl, plus the tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP6eB1zx4eyE3hd7tpvSue
A bare TransformerCircuit never reaches these accessors as a segment. A
two-winding transformer holds one circuit and one arc, so the transformer is
the branch; a three-winding transformer spans three arcs and enters wrapped in
ThreeWindingTransformerCircuit, which subtypes ACTransmission. Every bare
circuit in the package is an internal delegation target reached from the
two-winding arm.

The public circuit method on get_series_susceptance was the only thing forcing
the accessors to straddle ACTransmission and DeviceParameter, so it is gone and
the SeriesSegment union with it. _series_susceptance_raw keeps its circuit
method, which is where the tap-divided arithmetic lives.
Same shape as the susceptance accessors: a bare TransformerCircuit reaches the
public accessor through its transformer at two windings, or through
ThreeWindingTransformerCircuit at three, and never arrives as a segment of its
own. The circuit method moves to _circuit_phase_shift, leaving the public
accessor uniformly on ACTransmission.
The substituted reactance is still divided by the tap, which is what keeps the
component value equal to what BA derives from Ybus: for a symmetric arc
imag(1/Y_ft) is x * tap, the reciprocal of the tap-divided susceptance. Existing
tests covered tap and zero impedance separately and nothing held them together,
so dropping the tap would have moved the value 5% at tap = 1.05 with a green
suite. BA_Matrix is the oracle rather than a restated constant.
Follow the DeviceBaseUnit -> ComponentBaseUnit rename
Test fixtures pass OperationalStates.ONLINE instead of a Bool and drop must_run; the
device-base marker DU is CU everywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QDkZStcFzebhh9XFpPKRSo
The OpenAPI model packages pinned `jd/openapi_deps_update`, which predates the
0.1.0 release, its precompilation fix and the Julia 1.13 work; point them at
main. InfrastructureSystems moves from the jd/openapi-1x-suite branch to IS4,
where it merged.

Restore the CI Julia matrices to '1' now that the 1.13 fix has landed.
Julia 1.13 changed collection iteration order, and the arc axis for one system
remaps completely across versions (index 9 was (7,8) on 1.12, (6,13) on 1.13),
so anything selecting by position picked a different object and the suite failed.

- `all([6, 1] .∈ keys(...))` broadcast over both sides, pairing element-wise;
  it only ever passed because the KeySet happened to iterate in that order.
  `issubset` says what was meant.
- Both 3W transformers in the PST fixture are phase-shifting, so `first` picked
  whichever came first; name the one whose taps the testset asserts.
- The Woodbury contingency arcs were fixed indices; name them by endpoints.
- The zero-impedance cluster collapses to one bus, but which member survives is
  order-dependent, so assert that exactly one is left rather than naming it.

Also iterate a snapshot of the keys in `assign_reference_buses!`: it `pop!`s from
the Dict it is iterating, which is undefined and can skip entries outright.
PowerSystems drops the deprecated TwoTerminalHVDCLine constructors. These
four call sites are a straight rename: the kwargs and the loss default are
identical on the generic type.
Pin co-developed dependencies to the OpenAPI.jl 1.x branches
The openapi-1x branches are merged across the line, so the branch pins
now track psy6 directly.
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.

5 participants