Port to PowerIO 0.11 - #76
Merged
Merged
Conversation
Contributor
Benchmark Results (Julia v1)Time benchmarks
Memory benchmarks
|
PowerIO carries a bound a case leaves unstated as an infinite one rather than refusing the case, which is how MATPOWER, PowerModels, pandapower and PyPSA all spell "no limit"; stock case9241pegase leaves the reactive limits off seven generators. The KKT layout is fixed, one complementarity row per bound, so an infinite bound can be neither dropped nor carried: `rho * (qg - qmin)` with an infinite `qmin` is `0 * Inf`, a NaN in the residual and an Inf in the Jacobian. The bound is left off the solver model instead, and its row reads `rho = 0` -- the multiplier of a constraint that is not there, and what a solver reports for a bound it was never given, so the solved duals and the residual agree by construction. The derivative of an absent bound is constant, so its fixed-regime sensitivity is zero and the sparsity pattern is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
PowerIO 0.11 replaces the accessor-and-JSON-payload layer with typed element
tables over C ABI 7, so the seam is rewritten rather than bumped. `parse_file`
returns a `PioModule{BalancedNetwork}`, which carries the reader's diagnostics,
the source record and the history alongside the case; every constructor accepts
the module or the network inside it.
PowerDiff now derives no electrical quantity PowerIO states. The series
conductance and susceptance are read back from `to_powerdata`'s terminal
admittance coefficients, and each branch terminal's charging admittance is carried
on its own side rather than summed and split evenly with the charging conductance
discarded. The incidence matrix is stated once and shared by both network types.
A generator's cost model is read off the element instead of inferred from the
shape of a converted row.
`to_powerdata` is unfiltered in 0.11: rows carry a status and their source row
number, which is the IDMapping index, so the two-pass reconciliation that used to
recover those numbers is gone. Isolated buses and everything standing on them are
selected out here, as PowerIO used to do.
A rating the source does not state now reaches PowerDiff as an infinite one where
it used to be zero, and both take the synthesized limit. Without that, every
unrated branch would reach the solver with an unbounded flow; stock IEEE 300
leaves all 411 branches unrated.
A PowerIO failure propagates as a PowerIOError rather than being flattened into an
ArgumentError, which discarded the diagnostic code and the records behind it. The
format allowlist is replaced by pass-through, and the distribution-token blocklist
by a check on what the source parsed to, which also covers series carriers and
calculation instances.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Round trips go through `emit` and read the reader back off `m.sources`, and a diagnostic is asserted as a record rather than as a line of text. New coverage for what the port can get wrong: a source stating no thermal limit dispatches identically whether it spells that zero or infinity, an isolated bus in the middle of the bus table takes its branch with it without shifting the survivors, terminal charging is carried per side, and only a balanced transmission network is modeled. The ingest is held to an oracle that shares no code with it: `ptdf_matrix` against `PowerModels.calc_basic_ptdf_matrix` on three PGLib cases, which pins the topology, the branch susceptances and the reference bus together. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The integration contract stated the ingest but sat outside `docs/src/`, so it was linked from the README and never built. It moves into the docs and is rewritten for 0.11: what PowerIO states, what PowerDiff adds on top, and why the parametric admittance kernel is not a duplicate of `calc_admittance_matrix`. The restoration example switches branches through `update_switching!` rather than writing `network.sw` directly, which the DCNetwork docstring does not sanction, and its write-up no longer pins a PowerIO version in prose. Its output is unchanged. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
samtalki
force-pushed
the
claude/powerdiff-powerio-migration-sulux7
branch
from
September 5, 2026 19:19
e831561 to
e8ae30f
Compare
This was referenced Sep 5, 2026
Open
Member
Author
|
Six gaps found while doing this are filed upstream rather than worked around here:
#139 is why this PR states its own incidence matrix rather than consuming |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Port to PowerIO 0.11 (powerio C ABI 7). This PR previously targeted 0.9; 0.11
replaces the accessor-and-JSON-payload layer with typed element tables, so the seam
is rewritten rather than bumped, and the four upstream issues the 0.9 version filed
(eigenergy/PowerIO.jl#111-#114) are all closed, resolved by 0.10 and 0.11.
The governing rule is that PowerDiff is a consumer of PowerIO. Every electrical
quantity PowerIO states is read from it, and nothing PowerIO computes is computed
again here. Where PowerIO is missing something, an issue is filed upstream rather
than worked around locally.
What PowerDiff stopped computing
b = -x/(r² + x²)inDCNetworkc8 - b_tooffto_powerdatag, b = r/z², -x/z²inACNetworkc7 - g_to,c8 - b_tobr_bsummed then split evenly,g_fr/g_toforced to zeroGenerator.cost.model, verbatimto_powerdata(net; filtered=false)passi, which is that numberNothing in
src/inverts a branch impedance any more. The charging change is afidelity gain: the old path averaged the two terminals and discarded the charging
conductance, which loses information on every source that states them separately.
What PowerDiff still computes, and why it is not a duplicate
admittance_matrix(net, sw)assemblesYfromg,b,tap,shiftand the fourterminal terms on every call.
PowerIO.calc_admittance_matrixtabulatesYfor onenetwork at its parsed values with a boolean in-service flag; PowerDiff needs
Yas afunction of parameters a caller perturbs, because
swis continuous in[0, 1]and(:vm, :g),(:va, :b)and eight more operand/parameter pairs differentiate throughgandb(src/sens/interface.jl:179-180,src/sens/topology_ac.jl:64-71computes∂Y/∂gand∂Y/∂bin closed form). Freezing PowerIO's coefficients into the networkwould leave those analytical derivatives comparing against a constant
Y.The same argument disposes of
calc_bus_susceptance_matrix,calc_branch_flow_matrix,calc_branch_flow_dcandcalc_bus_injection_dc: each is that object evaluated atboolean
in_service. The incidence matrix is stated locally becausecalc_incidence_matrixreturns a bare sparse matrix with no branch or bus index map,so relabeling it into PowerDiff's sorted-source-id space costs more than stating it.
The bug this upgrade would otherwise have shipped
PowerIO 0.11 reads
rate_athrough the bound check that permits infinities, soMATPOWER's
rate_a == 0now arrives asInf— andrate_a > 0is true forInf.Left alone, every unrated branch would have reached the solver with an unbounded
thermal limit. Stock IEEE 300 leaves all 411 branches unrated, so the restoration
example added in #77 would have stopped shedding load and failed its own assertion.
Both spellings now take the synthesized limit, with a test pinning that the
0and1e6cases dispatch identically.Also
parse_file/parse_matpowerreturn aPioModule{BalancedNetwork}. It carriesm.diagnostics(the reader's findings, as records with acodeand aseverity),m.sources[1].format, andm.history, and it is whatPowerIO.emitwrites backout. Every constructor accepts the module or the network inside it, so call sites
are unchanged.
PowerIOErrorinstead of being flattened intoArgumentError, which discarded the diagnostic code and the records behind it.type == 4) buses are selected out here, sinceto_powerdatano longerdrops them and
calc_incidence_matrixfilters on branch status only. Keeping themwould energize a bus MATPOWER declares out of service.
blocklist by a check on what the source actually parsed to — one rule that also
covers time series, scenario sets and calculation instances, with nothing here to
trail what PowerIO reads.
network_findingsis dropped before it ever shipped. It existed because 0.9reached findings only through a normalize pass PowerDiff had to own; in 0.11 they
are a property of the module
parse_filealready returns.parse_matpower_struct(an alias for an alias, no callers) is removed.docs/src/and is published. It was linkedfrom the README and never built.
update_switching!rather than writing
network.swdirectly. Its output is byte-identical.Verification
Run locally against PowerIO 0.11.0 / ABI 7 on Julia 1.12.5.
pglib_opf_case118_ieee.m— dispatch, angles, flows, shedding, limits,susceptances, ID maps, and the Frobenius/infinity norms and per-column norms of
eight sensitivity matrices — compared before and after. Worst absolute difference
2.3e-12; nothing outside
atol=1e-9, rtol=1e-8. The objective,sum(pg)andthe norms of injection, flow, angle and shedding agree to all 17 digits, and the
DCNetworkand ingest-table dumps are byte-identical.ptdf_matrixagainstcalc_basic_ptdf_matrixon case14, case30 and case118, agreeing to 1e-8. It sharesno code with the ingest and pins topology, branch susceptances and reference bus.
checkdocsand cross-references.Filed upstream rather than worked around
to_powerdatareturns busvain degrees while its owndocstring says angles are radians and the branch rows convert.
calc_*return bare arrays with no bus or branch index map, while theAC family returns
BusMappedMatrix; the in-service branch row selection is notstated in any of the eight docstrings.
to_powerdata,calc_admittance_matrixand the DC family.Yand theExaModels-shaped
c1..c8.to_powerdatadropscost.model, so a piecewise linear cost is not detectablefrom the row.
to_powerdatavalidates rows the caller will discard, so an out-of-servicegenerator with a cubic cost refuses the whole conversion.
🤖 Generated with Claude Code