Changelog
All notable changes to PowerDiff.jl are recorded here. The format follows Keep a Changelog, and the project follows Semantic Versioning: while the version is below 1.0, a minor bump is a breaking release.
[Unreleased]
[0.2.0] - 2026-09-05
Everything on main since v0.1.0 ships here. v0.1.0 was registered against PowerIO 0.6, so the 0.7 through 0.11 work, the public KKT layout, the energized-branch angle gates and the pull request benchmarks have all been unreleased until now.
Tracks PowerIO 0.11 (powerio C ABI 7). The [compat] bound is now PowerIO = "0.11"; the binding gates its ABI handshake on equality, so it and its binaries move together and the bound cannot be relaxed.
PowerIO 0.11 replaces the accessor-and-JSON-payload layer with typed element tables, so this is a rewrite of the seam rather than a version bump. PowerDiff is a consumer of PowerIO: every electrical quantity PowerIO states is now read from it, and nothing that PowerIO computes is computed again here.
Changed
- Breaking:
parse_fileandparse_matpowerreturn aPowerIO.PioModule{PowerIO.BalancedNetwork}rather than a bare network. The module carries the reader's diagnostics, the source record and the history alongside the case, and it is whatPowerIO.emitwrites back out. Every network constructor accepts the module or the network inside it, soDCNetwork(net),ACNetwork(net),DCOPFProblem(net),ACOPFProblem(net),DCPowerFlowState(net)andcalc_demand_vector(net)are unchanged at the call site. - Breaking: a failure inside PowerIO reaches the caller as a
PowerIO.PowerIOErrorinstead of being flattened into anArgumentError. It carries the diagnosticcodeand the records behind it, both of which wrapping discarded. PowerDiff's own refusals stayArgumentError. - Breaking:
parse_matpower_structis removed. It was a compatibility alias forparse_matpowerwith no callers. - Breaking:
network_findingsis removed, one release after it was added and before it ever shipped. It existed because PowerIO 0.9 reached its findings only through a normalize pass PowerDiff had to own, deduplicate and label. In 0.11 they arem.diagnostics, a property of the moduleparse_filealready returns, and aDiagnosticis a record with acodeand aseverityrather than a line of text. Wrapping a property in an exported function is surface, not integration. - The series conductance and susceptance are read back from PowerIO's terminal admittance coefficients rather than derived from
randx, and each branch terminal's charging admittance is carried on its own side. PowerDiff previously summed the two sides, split the total evenly and discarded the charging conductance, which lost fidelity on every source that states the terminals separately. Nothing insrc/inverts a branch impedance any more. - The branch-by-bus incidence matrix is stated once and shared by both network types, which previously assembled one each.
- Out-of-service and isolated elements are selected out here rather than by PowerIO.
to_powerdatais unfiltered in 0.11: every row carries astatusand its source row number, which is exactly theIDMappingindex, so the two-pass reconciliation that used to recover those numbers is gone. Isolated (type == 4) buses and everything standing on them are dropped, as before. - A generator's cost model is read off the element rather than inferred from the shape of a converted row, so a piecewise linear cost is refused for what it is.
- The format vocabulary is PowerIO's rather than a copy of it: an unrecognized token goes to PowerIO, which answers with what the linked library actually reads, and a reader PowerIO gains is reachable without a PowerDiff release. PowerDiff's historical short spellings still resolve. A bare
jsonis still refused as ambiguous. The hand-maintained distribution-format blocklist is replaced by a check on what the source actually parsed to, which also covers time series, scenario sets and calculation instances. docs/powerio-integration.mdmoves todocs/src/and is published; it stated the ingest contract but sat outside the docs build, so it was never rendered.
Fixed
- A branch whose source states no thermal limit takes the synthesized limit whether the source spells that
0orInf. PowerIO 0.11 carries MATPOWER'srate_a == 0out asInf, which the previousrate_a > 0test accepted, so every unrated branch would have reached the solver with an unbounded flow. Stock IEEE 300 leaves all 411 branches unrated. - Generator reactive limits may be absent. PowerIO carries a bound the case does not state as
±Infrather than refusing the case, and stock case9241pegase leaves them off seven generators. PowerDiff leaves the bound off the solver model and its KKT complementarity row readsρ = 0, the multiplier of a constraint that is not there and the value a solver reports for a bound it was never given. Without this the upgrade would have turned a clean refusal into0 * Inf— aNaNin the residual and anInfin the Jacobian. The KKT sparsity pattern is unchanged.
Added
- A worked post-event transmission restoration example on IEEE 300, screening which outaged branches to repair first with a single
vjprather than a full sensitivity matrix, with its write-up. Contributed by @karenkji. - A test that the ingest still reproduces MATPOWER's network, comparing
ptdf_matrixagainstPowerModels.calc_basic_ptdf_matrixon three PGLib cases. It shares no code with the ingest and pins the topology, the branch susceptances and the reference bus together.
Release tooling
Register.ymlreplacesregister.yml, and validates instead of mutating. The version bump and the changelog section now land through an ordinary reviewed pull request, so CI runs on the exact tree that gets registered; the old workflow pushed that commit itself withGITHUB_TOKEN, which starts no workflow, making it the one commit onmainthat CI never saw. The replacement takes the commit to register, refuses anything that is not the tip ofmain, re-runs the tests on it, checks the version against General'sVersions.toml, refuses to trigger twice for the same commit, and offers a dry run.- TagBot generates the GitHub release notes from the merged pull requests since the last release, with housekeeping filtered out by label.
CHANGELOG.mdfollows Keep a Changelog and is rendered into the documentation byChangelog.jl.- Every workflow carries an explicit
permissions:block and a concurrency group, and every third-party action is pinned to a commit SHA. CI reports coverage. - The benchmark job skips pull requests from forks instead of failing on them: a fork gets a read-only token, so the step that posts the comment could not succeed.
RELEASING.mdandCONTRIBUTING.mdare new.
[0.1.0] - 2026-07-09
First release on the PowerIO 0.6.x data layer, and the first cut through the one-click register.yml release workflow.
- Migrated to PowerIO 0.6.x: the
[compat]bound is nowPowerIO = "0.6", and the deprecatedPowerIO.Networktype was replaced byPowerIO.BalancedNetwork(renamed upstream in PowerIO 0.3.0). The parsing/data contract is unchanged —PowerIO.to_powerdata's row schema is identical across the two versions, so the_network_dataadapter and everything downstream are unaffected. - Release tooling: TagBot now carries the
contents: writepermission it needs to publish GitHub releases (previously it produced a bare tag with no release object), and aregister.ymlworkflow performs the version bump and JuliaRegistrator trigger in one manual dispatch, drawing release notes from this changelog.