Skip to content

Consume PowerIO directly; drop the ParsedCase layer#60

Merged
samtalki merged 7 commits into
mk/backendsfrom
powerio-consume-direct
Jun 15, 2026
Merged

Consume PowerIO directly; drop the ParsedCase layer#60
samtalki merged 7 commits into
mk/backendsfrom
powerio-consume-direct

Conversation

@samtalki

Copy link
Copy Markdown
Member

What

Stacks on mk/backends (#48). PowerIO.to_powerdata already returns normalized, per-unit, filtered, slack-inferred, cost-rescaled network data — which parser.jl reimplemented as ParsedCase. This builds DCNetwork/ACNetwork straight from a PowerIO.Network and deletes the duplicated layer (net −191 lines).

What stays in PowerDiff is only OPF-solver modeling, not parsing:

  • polynomial cost interpretation (reject PWL / higher-than-quadratic)
  • a finite flow-limit fallback when rate_a == 0
  • default ±60° angle-difference bounds (the MATPOWER/PowerModels convention)
  • rejection of storage / HVDC records PowerDiff does not model

Key changes

  • parse_file/parse_matpower return a PowerIO.Network. _network_data(net) builds the network tables.
  • DCNetwork/ACNetwork/DCOPFProblem/ACOPFProblem/calc_demand_vector accept a PowerIO.Network or the network-tables NamedTuple. ParsedCase/ParsedBus/… and the old normalization helpers are removed.
  • IDMapping drops per-load/shunt ids (loads/shunts are aggregated per bus by to_powerdata).
  • Generator costs are read from PowerIO's raw records, because to_powerdata mangles costs with ncost>3.
  • Finalize PowerIO as a registered dependency: drop the [sources] git pin, set [compat] PowerIO = "0.1".
  • Tests and the IPP experiment migrated off ParsedCase; examples already used the parse_file → constructor path.

Supersedes #59 (the PowerIO hard-dependency declaration), which is folded in here; credited to @cameronkhanpour via the commit co-author trailer.

PowerIO issues found (to file upstream)

to_powerdata is not fully drop-in; PowerDiff works around these, but they are real upstream gaps:

  1. Costs with ncost>3 are mangled — it keeps the 3 highest-degree coefficients with the wrong exponents and drops the rest, so a quadratic padded to ncost=5 (e.g. MATPOWER case14) loses its linear term. Worked around by reading raw cost coefficients from PowerIO.generators.
  2. No reference bus is inferred when the source marks noneto_powerdata only reassigns the slack when an explicit REF was demoted. PowerDiff promotes the largest generator's bus.
  3. Non-finite/malformed fields crash with Float64(::Nothing) instead of a clean error — wrapped as an ArgumentError.

Verification (zero regressions)

  • Full Pkg.test() passes against registered PowerIO 0.1.2 (parser path/IO parity ran, 541/541; MATPOWER semantics 29/29; non-basic 183/183).
  • DCNetwork/ACNetwork fields are byte-identical to the pre-refactor ParsedCase path for pglib case5/14/30 (captured baseline diff).
  • examples/interactive_repl.jl runs end-to-end; the IPP experiment is converted to the new API and parses.

🤖 Generated with Claude Code

samtalki and others added 2 commits June 14, 2026 18:54
PowerIO.to_powerdata already returns normalized, per-unit, filtered,
slack-inferred, cost-rescaled network data, which parser.jl reimplemented as
ParsedCase. Build DCNetwork/ACNetwork straight from a PowerIO.Network instead,
keeping only PowerDiff's OPF modeling: polynomial cost interpretation, a finite
flow-limit fallback when rate_a is 0, default angle-difference bounds, and
rejection of storage/HVDC records PowerDiff does not model.

- parser.jl: parse_file/parse_matpower return a PowerIO.Network; _network_data
  builds the network tables; remove ParsedCase/ParsedBus/... and the old
  normalization helpers.
- DCNetwork/ACNetwork/DCOPFProblem/ACOPFProblem/calc_demand_vector take a
  PowerIO.Network or the network-tables NamedTuple; drop the ParsedCase methods.
- IDMapping no longer tracks per-load/shunt ids (loads and shunts are aggregated
  per bus by to_powerdata).
- Read generator costs from PowerIO's raw records: to_powerdata mangles costs
  declared with ncost>3 (e.g. MATPOWER case14, a quadratic padded to ncost=5).
- Finalize PowerIO as a registered dependency: drop the [sources] git pin and
  set [compat] PowerIO = "0.1".
- Migrate the test suite and the IPP experiment off ParsedCase; examples already
  used the parse_file -> network constructor path.

Co-authored-by: Cameron Khanpour <99142483+cameronkhanpour@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PowerIO is registered and public, so CI no longer needs the private-repo probe.

- CI.yml / Documentation.yml / Benchmark.yml: drop the POWERIO_JL_TOKEN env, the
  PowerIO access probe/skip/configure steps, the availability gates, and
  JULIA_PKG_USE_CLI_GIT. The test and build (docs) job names are preserved.
- docs: drop the removed ParsedCase/Parsed* @docs entries from the API reference
  and rewrite the PowerIO integration page for the direct to_powerdata path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@samtalki

Copy link
Copy Markdown
Member Author

Filed the three to_powerdata gaps PowerDiff works around as eigenergy/PowerIO.jl#33 (costs with ncost>3, no reference inferred when none marked, crash on non-finite fields). Once fixed upstream, the raw-cost read and the reference/error workarounds in _network_data can be dropped.

get_path resolves PowerDiff's bundled PGLib artifact — a data-library concern, not
parsing. Pulling it (and the LazyArtifacts dependency it needs for `artifact"..."`)
into src/artifacts.jl leaves parser.jl as just the PowerIO entry points and the
network-data adapter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
samtalki and others added 4 commits June 14, 2026 23:39
PowerIO 0.1.3 makes to_powerdata a complete data layer: source bus ids on
bus_i, an inferred reference (type == 3), and correct polynomial costs
(ncost > 3 no longer mangled, so a quadratic padded to ncost=5 keeps its
linear term). Bump compat to 0.1.3 and remove the three workarounds
_network_data carried for the old gaps:

- read gen cost straight from to_powerdata's rows (model_poly/n/c, already
  per-unit and leading-zero collapsed) instead of re-reading raw costs from
  PowerIO.generators and rescaling; drop the _cost_tuple helper
- drop the biggest-pmax reference promotion; the reference now comes from
  to_powerdata (type == 3)
- drop the try/catch around to_powerdata, which now throws ArgumentError on
  malformed input itself

Kept as consumer-side solver prep (PowerIO leaves these to the caller): the
rate_a == 0 finite-limit fallback, the +/-60 deg default angle bounds,
rejection of storage/HVDC and PWL/higher-than-quadratic costs, and the dense
gen.bus/f_bus/t_bus -> source id mapping.

Rename src/parser.jl to src/network_data.jl (it builds network tables, it is
not a parser) and move `using PowerIO` to the top of PowerDiff.jl.

DCNetwork/ACNetwork field values are unchanged: a before/after field dump over
pglib case5/14/30 and a non-basic-id case (ids 1,2,3,4,10) is identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The MATPOWER parse wrappers (parse_file/parse_matpower), the _network_data
adapter, and its solver-prep helpers (_poly_cost, _fallback_rate_a,
_normalize_angle_bounds) now live in types/dc_network.jl rather than a separate
file. dc_network.jl is included before ac_network.jl, so ACNetwork and the OPF
problem constructors reuse the shared _network_data. This removes the standalone
src/network_data.jl, which was just the old parser.jl renamed.

Pure relocation: DCNetwork/ACNetwork field values are unchanged (before/after
field dump over pglib case5/14/30 and a non-basic-id case is identical), the
test suite passes, and docs build with parse_* docstrings resolving from
dc_network.jl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Correctness:
- _poly_cost: accept generators with no gencost row (gencost is optional in
  MATPOWER). PowerIO returns model_poly=false, n=0 for them; treat as cost-free
  instead of throwing, which had broken even power-flow-only construction. PWL
  (model_poly=false, n>0) is still rejected, and to_powerdata rejects
  higher-than-quadratic itself.

Consistency:
- reject storage from the raw network (PowerIO.storage(net)) like HVDC, so both
  guards see out-of-service records; to_powerdata's filtered pd.storage dropped
  them, silently accepting a file that declared disabled storage.

Efficiency / clarity:
- _poly_cost reads to_powerdata's right-aligned (cq, cl, cc) directly instead of
  collect/slice/popfirst per generator.
- build the branch table with a concrete-eltype comprehension + _branch_row
  helper instead of an abstract Vector{NamedTuple} + push!.
- drop the duplicate per-bus vmax array; _branch_row indexes the buses table.
- parse_matpower_struct no longer advertises kwargs... it cannot forward.

Docs:
- fix stale claims that parse_file returns "PowerDiff's typed representation"
  (it returns a PowerIO.Network) in README, getting-started, index, advanced.
- powerio-integration.md: costs come from to_powerdata, not raw records, and the
  reference bus comes from to_powerdata, not a largest-generator promotion.

No behavior change for valid inputs: DCNetwork/ACNetwork field values are
identical (before/after field dump over pglib case5/14/30 and a non-basic-id
case), and the test suite passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Dropping the ParsedCase layer folded shunts into per-bus gs/bs (which the network
constructors consume) but removed the separate data.shunt records. Add a `shunt`
field back to the _network_data tables: one (; index, shunt_bus, gs, bs) record per
bus with a nonzero shunt admittance, derived from the per-bus values to_powerdata
already aggregates (no raw re-read). DCNetwork/ACNetwork are unchanged (field dump
byte-identical); the inline parser test asserts the restored shunt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@samtalki samtalki merged commit 242aa32 into mk/backends Jun 15, 2026
4 checks passed
@samtalki samtalki deleted the powerio-consume-direct branch June 15, 2026 06:03
samtalki added a commit that referenced this pull request Jun 15, 2026
* init

(cherry picked from commit df6c11d)

* restore

* Finish Exa backend construction and parity coverage

* Adapt Exa backend to the new ExaModels API

* Complete typed MATPOWER backends

* Added ExaModels.jl Dependency Item to README

* Address PR 48 review feedback

* Quality fixes: solve-status warnings, demand indexing, dead code, docs

- Gate the AC/Exa `:acceptable` solve warning on `silence()` and state the
  sensitivity consequence; mirror the DC backend's status messages and add
  an `:unbounded` branch.
- Fix `calc_demand_vector(::ParsedCase)` to index by the sorted `IDMapping`
  instead of file order, so loads align when bus IDs are unsorted; reuse the
  caller's `id_map` and add a regression test.
- Remove the unused `ACNetwork.i_max` field and the dead `_branch_data_dict`.
- Refresh stale PowerModels docstrings (KKT flow equations, `branch_data`,
  `ACNetwork(Y)`) and document `DCOPFSolution.eta_ref`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Use PowerIO as the parser layer

* Handle private PowerIO.jl CI dependency

* Add PowerIO compat bound and list it in README dependencies

PowerIO = "0.0.1" rides alongside the [sources] pin so the bound is already
right when the pin is dropped after registration. README dependency list now
names the parser layer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Track PowerIO.jl main and widen compat to 0.1

The release-prep branch merged; main now carries PowerIO.jl 0.1.x with the
powerio v0.2.1 binary artifact.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Consume PowerIO directly; drop the ParsedCase layer (#60)

* Consume PowerIO directly; drop the ParsedCase layer

PowerIO.to_powerdata already returns normalized, per-unit, filtered,
slack-inferred, cost-rescaled network data, which parser.jl reimplemented as
ParsedCase. Build DCNetwork/ACNetwork straight from a PowerIO.Network instead,
keeping only PowerDiff's OPF modeling: polynomial cost interpretation, a finite
flow-limit fallback when rate_a is 0, default angle-difference bounds, and
rejection of storage/HVDC records PowerDiff does not model.

- parser.jl: parse_file/parse_matpower return a PowerIO.Network; _network_data
  builds the network tables; remove ParsedCase/ParsedBus/... and the old
  normalization helpers.
- DCNetwork/ACNetwork/DCOPFProblem/ACOPFProblem/calc_demand_vector take a
  PowerIO.Network or the network-tables NamedTuple; drop the ParsedCase methods.
- IDMapping no longer tracks per-load/shunt ids (loads and shunts are aggregated
  per bus by to_powerdata).
- Read generator costs from PowerIO's raw records: to_powerdata mangles costs
  declared with ncost>3 (e.g. MATPOWER case14, a quadratic padded to ncost=5).
- Finalize PowerIO as a registered dependency: drop the [sources] git pin and
  set [compat] PowerIO = "0.1".
- Migrate the test suite and the IPP experiment off ParsedCase; examples already
  used the parse_file -> network constructor path.

Co-authored-by: Cameron Khanpour <99142483+cameronkhanpour@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Resolve PowerIO from the registry in CI; refresh docs

PowerIO is registered and public, so CI no longer needs the private-repo probe.

- CI.yml / Documentation.yml / Benchmark.yml: drop the POWERIO_JL_TOKEN env, the
  PowerIO access probe/skip/configure steps, the availability gates, and
  JULIA_PKG_USE_CLI_GIT. The test and build (docs) job names are preserved.
- docs: drop the removed ParsedCase/Parsed* @docs entries from the API reference
  and rewrite the PowerIO integration page for the direct to_powerdata path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Move get_path (and LazyArtifacts) out of parser.jl

get_path resolves PowerDiff's bundled PGLib artifact — a data-library concern, not
parsing. Pulling it (and the LazyArtifacts dependency it needs for `artifact"..."`)
into src/artifacts.jl leaves parser.jl as just the PowerIO entry points and the
network-data adapter.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Drop PowerIO workarounds; consume to_powerdata directly on 0.1.3

PowerIO 0.1.3 makes to_powerdata a complete data layer: source bus ids on
bus_i, an inferred reference (type == 3), and correct polynomial costs
(ncost > 3 no longer mangled, so a quadratic padded to ncost=5 keeps its
linear term). Bump compat to 0.1.3 and remove the three workarounds
_network_data carried for the old gaps:

- read gen cost straight from to_powerdata's rows (model_poly/n/c, already
  per-unit and leading-zero collapsed) instead of re-reading raw costs from
  PowerIO.generators and rescaling; drop the _cost_tuple helper
- drop the biggest-pmax reference promotion; the reference now comes from
  to_powerdata (type == 3)
- drop the try/catch around to_powerdata, which now throws ArgumentError on
  malformed input itself

Kept as consumer-side solver prep (PowerIO leaves these to the caller): the
rate_a == 0 finite-limit fallback, the +/-60 deg default angle bounds,
rejection of storage/HVDC and PWL/higher-than-quadratic costs, and the dense
gen.bus/f_bus/t_bus -> source id mapping.

Rename src/parser.jl to src/network_data.jl (it builds network tables, it is
not a parser) and move `using PowerIO` to the top of PowerDiff.jl.

DCNetwork/ACNetwork field values are unchanged: a before/after field dump over
pglib case5/14/30 and a non-basic-id case (ids 1,2,3,4,10) is identical.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fold network_data.jl into the DCNetwork construction path

The MATPOWER parse wrappers (parse_file/parse_matpower), the _network_data
adapter, and its solver-prep helpers (_poly_cost, _fallback_rate_a,
_normalize_angle_bounds) now live in types/dc_network.jl rather than a separate
file. dc_network.jl is included before ac_network.jl, so ACNetwork and the OPF
problem constructors reuse the shared _network_data. This removes the standalone
src/network_data.jl, which was just the old parser.jl renamed.

Pure relocation: DCNetwork/ACNetwork field values are unchanged (before/after
field dump over pglib case5/14/30 and a non-basic-id case is identical), the
test suite passes, and docs build with parse_* docstrings resolving from
dc_network.jl.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address code-review findings on the PowerIO adapter

Correctness:
- _poly_cost: accept generators with no gencost row (gencost is optional in
  MATPOWER). PowerIO returns model_poly=false, n=0 for them; treat as cost-free
  instead of throwing, which had broken even power-flow-only construction. PWL
  (model_poly=false, n>0) is still rejected, and to_powerdata rejects
  higher-than-quadratic itself.

Consistency:
- reject storage from the raw network (PowerIO.storage(net)) like HVDC, so both
  guards see out-of-service records; to_powerdata's filtered pd.storage dropped
  them, silently accepting a file that declared disabled storage.

Efficiency / clarity:
- _poly_cost reads to_powerdata's right-aligned (cq, cl, cc) directly instead of
  collect/slice/popfirst per generator.
- build the branch table with a concrete-eltype comprehension + _branch_row
  helper instead of an abstract Vector{NamedTuple} + push!.
- drop the duplicate per-bus vmax array; _branch_row indexes the buses table.
- parse_matpower_struct no longer advertises kwargs... it cannot forward.

Docs:
- fix stale claims that parse_file returns "PowerDiff's typed representation"
  (it returns a PowerIO.Network) in README, getting-started, index, advanced.
- powerio-integration.md: costs come from to_powerdata, not raw records, and the
  reference bus comes from to_powerdata, not a largest-generator promotion.

No behavior change for valid inputs: DCNetwork/ACNetwork field values are
identical (before/after field dump over pglib case5/14/30 and a non-basic-id
case), and the test suite passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Re-expose shunts as a data.shunt table

Dropping the ParsedCase layer folded shunts into per-bus gs/bs (which the network
constructors consume) but removed the separate data.shunt records. Add a `shunt`
field back to the _network_data tables: one (; index, shunt_bus, gs, bs) record per
bus with a nonzero shunt admittance, derived from the per-bus values to_powerdata
already aggregates (no raw re-read). DCNetwork/ACNetwork are unchanged (field dump
byte-identical); the inline parser test asserts the restored shunt.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Cameron Khanpour <99142483+cameronkhanpour@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Address PR #48 review (Copilot + cameronkhanpour)

- benchmark/benchmarks.jl: drop the removed `isdefined(:ParsedCase)` branches and
  parse with `PowerDiff.parse_file` directly. ParsedCase no longer exists, so the
  old fallback resolved to `PM.make_basic_network(...)` and `DCOPFProblem(::Dict)`
  raised a bare MethodError.
- Add DCNetwork/DCOPFProblem/DCPowerFlowState(::Dict) rejection methods so the DC
  side gives the same migration hint as the AC side instead of a MethodError.
- _poly_cost: zero-pad model-2 gencosts with fewer than 3 terms instead of indexing
  g.c[3] (BoundsError guard).
- Guard marginal_cost_ub against generator-free networks.
- Warn on reference-bus fallback when no type-3 bus is present (AC + DC).
- Refresh stale docstrings: flatten_variables eta now packs sol.eta_ref; test/common.jl
  reflects PowerIO.Network. Document the symmetric branch-shunt and the gen/branch
  dense-index ID contracts.
- Migrate test/mwe_unified.jl off the removed dictionary API.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Support PowerIO parser formats

* Handle RTS smoke cost conversion

* Honor explicit AC slack override

---------

Co-authored-by: ckhanpour3 <ckhanpour3@gatech.edu>
Co-authored-by: samtalki <10187005+samtalki@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Cameron Khanpour <99142483+cameronkhanpour@users.noreply.github.com>
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.

1 participant