multi ref bus - #340
Conversation
v0.24.3 fixed get_ref_bus_position only for Ybus, leaving PTDF, BA_Matrix, IncidenceMatrix, AdjacencyMatrix and VirtualPTDF on the bare `get_bus_lookup(M)[x]` lookup. Those still threw a raw KeyError whenever a reduction merged away the bus that assign_reference_buses! picked as an island's representative. Hoist _resolve_ref_bus_position into a single get_ref_bus_position(M::PowerNetworkMatrix) and drop the six per-type copies. ABA_Matrix keeps its own method (it stores ref_bus_position directly). LODF and VirtualLODF keep their per-type definitions: they are arc-indexed and have no get_bus_lookup, so they throw on any call regardless of reduction. That is pre-existing and left alone here.
"Small island corner cases" built PTDF/LODF/VirtualPTDF/VirtualLODF three ways (plain, single-bus island, radial-reduced) and asserted bitwise equality. The paths are electrically equivalent but leave different `valid_ix` orderings, so the factorization sums the same terms in a different order and results drift by a few ULP. Any change touching reference bus positions reshuffled which entries mismatched, making the testset flaky -- it fails on main today. Use atol = 1e-10, matching the tolerance already used elsewhere for the analogous ptdf_rad/vptdf_rad comparison. That is ~6 orders above the round-off noise and ~9 below the O(0.1) error a genuinely wrong reference bus produces. An absolute tolerance is required for the near-zero entries, where the values differ by 1e-17 against an exact 0.0. Split each three-way chain into two pairwise assertions so failures report the offending values instead of collapsing into one a == b == c.
get_ref_bus_position re-implemented the reduction-aware bus resolution that get_bus_index (common.jl) already provides, so call that instead and drop _resolve_ref_bus_position. This trades a descriptive error for the bare KeyError get_bus_index raises, on a path only reachable when a reference bus resolves to neither a surviving bus nor a reduction-mapped one. In the test fixture, _mk_star_jumper! and _mk_star_normal_line! each inlined the full Line construction that _add_test_line! already provides and differed only in x; collapse both onto one builder. Also drop a comment that speculated about a future design decision rather than documenting a why.
Performance ResultsPrecompile Time
Execution TimeCells show median (min–max) over 5 samples; delta compares medians.
|
m-bossart
left a comment
There was a problem hiding this comment.
Does it still make sense to have a function get_ref_bus that doesn't consider multiple ref buses in the same sychronous subsystem?
_make_subnetwork_axes replaced a reference bus that a reduction removed with
pop!(axis_1), which had two problems. The pick was an arbitrary island member,
never excluded from bus_numbers_to_remove, so subnetwork_axes could end up keyed
by a bus the same reduction pass deleted -- reproduced on a ZIBR star where the
key became bus 14 while bus_lookup held only {10, 20, 21}. And pop! deleted the
chosen bus from the island's own bus list, so the reference bus was missing from
its own subnetwork axis even when it did survive.
The key was only usable because get_ref_bus_position resolves through
reverse_bus_search_map at lookup time. Establish the invariant at the source
instead: pick the bus the old representative was merged into, so the reference
bus stays electrically the same one that resolution already selected, and PTDF
numerics do not move. A removal that merges nothing has no reverse map entry and
falls back to an order-independent pick. An island that loses every bus is left
for the existing empty-subnetwork sweep rather than re-keyed to another dead bus.
get_ref_bus_position keeps its get_bus_index resolution; it is now redundant for
this path but still covers any reduction that leaves a stale key.
Three testsets asserted that AutoTolerance is a no-op by comparing a matrix built with it against one built at eps() using exact equality. Two separate builds are not bitwise reproducible: with the AppleAccelerate backend active, measuring 20 build pairs shows one mismatch at 1 ULP (1.11e-16), while 20 repeated identical builds show none. So each of these assertions fails roughly 1 run in 20; they had simply been getting lucky. Compare with atol = 1e-10, which still proves the no-op -- genuinely sparsifying at these data_precision values would zero entries, moving them by their full magnitude (>=1e-3 in these systems). Same fix already applied once in this file for the sparsified VirtualPTDF row comparison (04b9901). The remaining exact comparison, apply_cutoff on a literal row, is a deterministic in-memory operation and stays exact.
Good question, my inclination is that we should make the behavior more concrete and if there are multiple Swing buses in a case, only one is a ref and the rest are slacks and the model for slacks -> fixed V and \theta and all refs behave like slacks but we need to validate that with PSSe |
Doesn't this have to be true given PSSE only has PV,PQ, Swing,Disconnected bus types. They they can't distinguish between refs/slacks with just these designations. |
m-bossart
left a comment
There was a problem hiding this comment.
I'm ok with these changes if we want to save the larger discussion about the semantics of ref/slack/swing for another change
These are other fixes for the multiref