Skip to content

Use PowerIO parser layer and ExaModels backend#48

Merged
samtalki merged 18 commits into
mainfrom
mk/backends
Jun 15, 2026
Merged

Use PowerIO parser layer and ExaModels backend#48
samtalki merged 18 commits into
mainfrom
mk/backends

Conversation

@klamike

@klamike klamike commented Apr 24, 2026

Copy link
Copy Markdown
Collaborator

note: @klamike merely drafted this PR. @cameronkhanpour and @samtalki deserve the credit!

Summary

This branch removes PowerDiff's parser backend split and makes PowerIO the single parser and data layer:

  • PowerDiff.parse_file(path) calls PowerIO.parse_file(path)
  • PowerDiff.parse_file(io; filetype="m") reads text and calls PowerIO.parse_str(text, "matpower")
  • PowerDiff keeps its own normalization into ParsedCase
  • the old native parser implementation is removed from the public path
  • parser path and IO parity tests now cover the PowerIO route
  • the branch is current with origin/main

The branch still carries the ExaModels backend work already reviewed in this PR. The parser changes depend on eigenergy/powerio#69 and eigenergy/PowerIO.jl#15.

Validation

  • Parser parity smoke against a local PowerIO C ABI build: MATPOWER parser semantics, PowerIO path and IO parity, typed AC pi model, and status filtering all passed.
  • Before merging this branch, make eigenergy/powerio and eigenergy/PowerIO.jl public, or configure POWERIO_JL_REPO_TOKEN with read access so CI can instantiate PowerIO.jl.

Base automatically changed from mk/ac to main May 30, 2026 19:09
@github-actions

github-actions Bot commented May 30, 2026

Copy link
Copy Markdown

Benchmark Results (Julia v1)

Time benchmarks
main 0451f75... main / 0451f75...
ac_opf/kkt_jacobian/case30.m 3.66 ± 0.13 ms 3.35 ± 0.084 ms 1.1 ± 0.048
ac_opf/kkt_param/case30.m/switching 0.113 ± 0.0096 ms 0.115 ± 0.0094 ms 0.978 ± 0.12
dc_opf/kkt_jacobian/case30.m/cost_linear 0.151 ± 0.02 μs 0.151 ± 0.01 μs 1 ± 0.15
dc_opf/kkt_jacobian/case30.m/cost_quadratic 0.141 ± 0.01 μs 0.16 ± 0.03 μs 0.881 ± 0.18
dc_opf/kkt_jacobian/case30.m/demand 0.281 ± 0.039 μs 0.28 ± 0.071 μs 1 ± 0.29
dc_opf/kkt_jacobian/case30.m/flowlimit 0.391 ± 0.08 μs 0.4 ± 0.05 μs 0.978 ± 0.23
dc_opf/kkt_jacobian/case30.m/full 13.6 ± 7.5 μs 18.7 ± 16 μs 0.728 ± 0.74
dc_opf/kkt_jacobian/case30.m/susceptance 0.089 ± 0.0044 ms 0.0922 ± 0.0059 ms 0.965 ± 0.078
parser/case30.m 3.77 ± 0.18 ms 0.836 ± 0.11 ms 4.51 ± 0.64
time_to_load 1.86 ± 0.028 s 2.02 ± 0.033 s 0.92 ± 0.021
Memory benchmarks
main 0451f75... main / 0451f75...
ac_opf/kkt_jacobian/case30.m 0.0339 M allocs: 1.18 MB 0.033 M allocs: 1.17 MB 1.01
ac_opf/kkt_param/case30.m/switching 1.49 k allocs: 0.602 MB 1.48 k allocs: 0.603 MB 0.998
dc_opf/kkt_jacobian/case30.m/cost_linear 6 allocs: 0.328 kB 6 allocs: 0.328 kB 1
dc_opf/kkt_jacobian/case30.m/cost_quadratic 6 allocs: 0.328 kB 6 allocs: 0.328 kB 1
dc_opf/kkt_jacobian/case30.m/demand 6 allocs: 1.42 kB 6 allocs: 1.42 kB 1
dc_opf/kkt_jacobian/case30.m/flowlimit 6 allocs: 1.89 kB 6 allocs: 1.89 kB 1
dc_opf/kkt_jacobian/case30.m/full 0.079 k allocs: 0.082 MB 0.079 k allocs: 0.0819 MB 1
dc_opf/kkt_jacobian/case30.m/susceptance 2.28 k allocs: 0.292 MB 2.28 k allocs: 0.291 MB 1.01
parser/case30.m 0.0465 M allocs: 1.82 MB 16.9 k allocs: 0.508 MB 3.59
time_to_load 0.149 k allocs: 11.1 kB 0.149 k allocs: 11.1 kB 1

@cameronkhanpour

cameronkhanpour commented May 30, 2026

Copy link
Copy Markdown
Collaborator

Rebase completion note

PR #48 has now been rebased onto current main, which already includes the merged PR #42 AC KKT work and PR #50 benchmark workflow.

Implemented scope

  • remove PowerModels from runtime dependencies and expose a typed MATPOWER v2 .m parser API
  • construct AC pi-model admittance and branch sensitivities locally, including transformers, phase shifts, switching states, and parallel lines
  • keep backend=:jump as the default AC OPF path and support opt-in CPU backend=:exa through ExaModels and NLPModelsIpopt
  • update docs, examples, parser coverage, Exa parity coverage, and benchmarks

Breaking changes

  • PowerModels dictionary constructors and wrappers are intentionally removed from the supported core API
  • MATPOWER v2 .m files are the supported parsed input format
  • backend=:exa rejects custom JuMP optimizer arguments rather than silently ignoring them

Validation

  • passed the full PowerDiff Pkg.test suite in a fresh temporary Julia environment
  • passed the Documenter build
  • passed the benchmark script smoke check with parser, dc_opf, and ac_opf groups
  • verified a PM-free import and the interactive REPL example
  • GitHub Actions build, test, and benchmark checks pass

Manual case1354_pegase comparison

  • warmed parser median: 0.0147 s on this branch vs 0.1373 s on main (~9.3x faster)
  • parser minimum allocated bytes: 14.2 MB vs 99.8 MB
  • warmed AC KKT assembly median: 0.113 s on this branch vs 0.104 s on main, with slightly fewer allocated bytes on this branch

The KKT result is near parity because main already includes PR #42's sparse AC KKT assembly optimization. This PR's measured performance improvement is the typed parser path and removal of the runtime PowerModels dependency.

Deferred

  • CUDA/GPU execution
  • broader parser support for non-MATPOWER formats and unsupported electrical extension tables

@cameronkhanpour cameronkhanpour marked this pull request as ready for review May 30, 2026 23:49
@cameronkhanpour cameronkhanpour mentioned this pull request May 31, 2026
6 tasks
@cameronkhanpour cameronkhanpour removed their request for review May 31, 2026 06:15

@samtalki samtalki left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, this is a good direction. Dropping the PowerModels runtime dep and getting a ~15x faster parser is a real win, and the Exa parity tests plus the finite difference coverage for transformers, phase shifts, and parallel lines are thorough. A few things before it goes in:

Blocker

  • docs/Project.toml has a hardcoded C:\Users\Bijan\... path in [sources]. That breaks the docs build for everyone except that one machine (it builds locally only because the path exists there). Put it back to {path = ".."}. The SPDX header also got dropped from that file, worth restoring.

Should fix

  • The rejected Dict constructors keep their entire old body after the throw. ACNetwork(::Dict) and ACPowerFlowState(::Dict) still call _prepare_network_data (deleted) and carry an am = error("unreachable dictionary constructor body") line. It's dead and references symbols that no longer exist. Collapse them down to just the throw, like you already did for ACOPFProblem(::Dict).
  • parse_file ends with resolved isa String ? parse_matpower(resolved; validate) : parse_matpower(resolved; validate) — both branches are identical, so the ternary does nothing. Drop it.

Question

  • DC OPF lost the tightened Ipopt tolerances (tol=1e-10, acceptable_tol=1e-8) in _rebuild_jump_model!, and the thresholds in test_angle_diff_duals.jl got loosened from 1e-8 to 1e-6 to match. The old comment said the tight tol was needed for accurate dual recovery in sensitivity analysis. Was dropping it intentional? _is_ipopt_optimizer is now unused as a result. If it's deliberate it's fine, just want it to be a choice rather than an accident.

Minor: thermal_lcon/thermal_ucon in _build_examodel get reused for the angle constraints too, so the name is a little misleading; and the angmin_scaled/angmax_scaled arrays in the exa records named tuple look unused (the constraints read b.angmin_scaled per branch).

Otherwise this looks solid.

@cameronkhanpour

Copy link
Copy Markdown
Collaborator

Fixed and pushed PR #48 review follow-up in commit aa5eee5.

Changes made:

  • Restored docs/Project.toml to PowerDiff = {path = ".."} and restored the SPDX header.
  • Collapsed ACNetwork(::Dict) and ACPowerFlowState(::Dict) to just the migration throw.
  • Removed the redundant parse_file ternary.
  • Restored DC Ipopt tolerances in _rebuild_jump_model!: tol=1e-10, acceptable_tol=1e-8, max_cpu_time=30.0.
  • Restored the two test_angle_diff_duals.jl FD gates from 1e-6 back to 1e-8.
  • Renamed Exa shared nonpositive bounds from thermal_lcon/ucon to nonpositive_lcon/ucon and removed unused angmin_scaled / angmax_scaled arrays from the records tuple.

The loosening on the tolerences was not needed. It was an accidental side effect of dropping the tight Ipopt settings I made during local testings.

- 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>

@samtalki samtalki left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pushed a few quality fixes on top (06a5bde): gated the :acceptable solve warning behind silence() and aligned the AC/Exa solve-status messages with the DC backend; fixed calc_demand_vector to index by the sorted IDMapping (loads misaligned on unsorted bus IDs) plus a regression test; removed the dead i_max field and _branch_data_dict; refreshed the stale PowerModels docstrings. Suite stays green.

Left the parser internals untouched. There are some issues I found. I have a durable solution coming soon and will update this PR ASAP.

@samtalki samtalki changed the title Remove PowerModels dependency, add ExaModels backend Use PowerIO parser layer and ExaModels backend Jun 9, 2026
samtalki and others added 3 commits June 9, 2026 14:27
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>
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

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>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR consolidates PowerDiff’s parsing/data ingestion onto PowerIO (removing the prior parser-backend split and PowerModels-dict construction path) and adds an AC OPF backend implemented with ExaModels + NLPModelsIpopt alongside the existing JuMP backend.

Changes:

  • Replace PowerModels-dict-based parsing/constructors with PowerDiff.parse_file / PowerIO.Network + typed normalization via _network_data.
  • Introduce ExaModels-based AC OPF backend (backend=:exa) with parity tests against the JuMP backend.
  • Update tests/docs/examples to use the new typed/network-table path, and add a PGLib artifact helper (get_path(:pglib)) plus parser parity tests.

Reviewed changes

Copilot reviewed 49 out of 50 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
test/unified/test_sensitivity_verification.jl Switch unified sensitivity verification to PowerDiff.parse_file and load_ac_pf_state.
test/unified/test_interface.jl Update unified interface tests for typed network tables and new AC PF state construction.
test/test_update_switching.jl Update switching tests to mutate typed network fields instead of dicts.
test/test_sensitivity_coverage.jl Update sensitivity coverage tests to use new parsing + AC PF state loader.
test/test_sensitivity_column.jl Update column sensitivity tests to use load_ac_pf_state and typed parsing helpers.
test/test_parser_parity.jl New tests asserting PowerIO parser semantics + path/IO parity and typed pi-model checks.
test/test_parameter_transforms.jl Update parameter transform tests to use load_ac_pf_state.
test/test_nonbasic.jl Refactor non-basic tests to operate on PowerDiff network tables and updated IDMapping.
test/test_kkt_vjp_jvp.jl Update KKT VJP/JVP tests to use typed parsing and demand helper.
test/test_jvp_vjp.jl Update JVP/VJP tests to use typed parsing and shared basic-case helper.
test/test_dc_opf_verification.jl Update DC OPF verification perturbations to mutate typed cost coefficients.
test/test_apf_integration.jl Update APF integration to parse via PowerDiff.
test/test_ac_topology_sens.jl Update AC topology FD checks to use pi-model branch current/power helpers and typed PF loader.
test/test_ac_opf_sens.jl Update AC OPF sensitivity tests to typed parsing and typed-network mutations.
test/test_ac_opf_exa_backend.jl New parity/regression tests for ExaModels backend solving + sensitivities.
test/test_ac_opf_all_sens.jl Update all-sensitivities tests to perturb typed-network parameters and cache behavior.
test/runtests.jl Adjust core test harness to typed parsing and include new parser/exa backend tests.
test/common.jl Redefine loaders around PowerDiff typed parsing; add programmatic pd_* helpers + load_ac_pf_state.
src/types/show.jl Add model-status display support for ExaModels models.
src/types/id_mapping.jl Simplify IDMapping to bus/branch/gen only and add NamedTuple constructor.
src/types/dc_opf_problem.jl Update DCOPFProblem constructors to accept PowerIO.Network / network tables; parametrize optimizer type.
src/types/dc_network.jl Implement PowerIO-based MATPOWER parsing + normalization and typed-demand/gen caches on DCNetwork.
src/types/ac_opf_problem.jl Add backend abstraction; build JuMP or ExaModels models from shared typed constants and cached KKT constants.
src/types/ac_network.jl Replace dict-based construction with typed network tables; implement pi-model admittance/current/flow helpers.
src/sens/voltage.jl Remove dict wrapper entrypoint with migration hint.
src/sens/vjp_jvp.jl Require cached KKT constants via _require_kkt_constants.
src/sens/topology_ac.jl Update topology RHS construction for pi-model/tap/shift-aware derivatives.
src/sens/interface.jl Make branch current/flow sensitivity paths work with either ACNetwork or branch_data.
src/sens/current.jl Refactor current/flow sensitivity assembly to use shared state helpers (net or branch_data).
src/prob/kkt_dc_opf.jl Use solution’s stored reference-bus dual in flattening.
src/prob/kkt_ac_opf.jl Remove ref-dict dependency; use cached typed constants for flows/residuals/parameter extraction.
src/prob/dc_opf.jl Capture/store reference-bus dual and gate warning emission on silence().
src/prob/ac_opf_solve.jl Add Exa backend solve path via NLPModelsIpopt and unify switching rebuild logic.
src/PowerDiff.jl Add PowerIO/ExaModels/NLPModelsIpopt deps, include artifacts helper, export new parsing API.
src/artifacts.jl New get_path(:pglib) artifact resolver.
README.md Update quick start/dependencies for PowerIO parsing and ExaModels backend.
Project.toml Add dependencies/compat entries for PowerIO, ExaModels, NLPModelsIpopt, LazyArtifacts; move PowerModels to test extras.
experiments/ipp_market_planning.jl Update experiment to use typed parsing/networks and typed parameter mutation.
examples/interactive_repl.jl Switch example to artifact-backed PGLib case and typed parsing.
examples/apf_integration.jl Update example to typed parsing and demonstrate typed limit scaling.
docs/src/math/ac-opf.md Update documentation to reflect analytical reduced-space KKT/Jacobian approach.
docs/src/index.md Update docs examples to use PowerDiff parsing (no PowerModels import).
docs/src/getting-started.md Update getting started guide for MATPOWER-only parsing and external AC PF voltages.
docs/src/api.md Document new MATPOWER parser API (parse_file, parse_matpower, get_path).
docs/src/advanced.md Update advanced docs for new construction patterns and Exa backend mention.
docs/Project.toml Remove PowerModels from docs environment deps.
docs/powerio-integration.md New document describing PowerIO contract and PowerDiff normalization responsibilities.
benchmark/benchmarks.jl Update benchmarks to accommodate new parsing/constructors (currently needs a fix per review comments).
Artifacts.toml Add PGLib OPF artifact for bundled case library.
.github/workflows/Benchmark.yml Ensure PowerModels is available for benchmark job environment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread benchmark/benchmarks.jl Outdated
Comment thread benchmark/benchmarks.jl Outdated
Comment thread src/prob/kkt_dc_opf.jl

@cameronkhanpour cameronkhanpour left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — PowerIO parser layer + "drop the ParsedCase layer" (#60)

Overall this is a clean consolidation. _network_data is a tidy single adapter, the HVDC/storage guards correctly read the raw (pre-filter) network so out-of-service records are still rejected (a real subtlety — to_powerdata's filtered output would have silently accepted a file that declared disabled storage/dcline), and the IO/path parity test plus the byte-identical field-dump validation (including the non-basic-id case) give good confidence that the re-keying from to_powerdata's dense indices back to source bus ids is correct. Dropping ~340 lines of native parser for one adapter is the right direction.

A few things before merge.

Blocking

1. benchmark/benchmarks.jl is broken by the ParsedCase removal. (Copilot flagged lines 20 and 38 — both valid.) isdefined(PowerDiff, :ParsedCase) is now always false, so _load_benchmark_case falls into the PM.make_basic_network(PM.parse_file(...)) branch and returns a PowerModels Dict. Line 27 then calls DCOPFProblem(net_data). Unlike the AC path, there is no DCOPFProblem(::Dict) method at all, so this is a raw MethodError, not even the friendly "dictionary constructors were removed" hint. Same for the @benchmarkable parser block (lines 33–39). Replace both branches with PowerDiff.parse_file($case_path); if you still want a PowerModels-parse baseline, add it as a separately-named benchmark instead of gating on the removed symbol.

Consistency

2. Dict-rejection is asymmetric between AC and DC. ACOPFProblem(::Dict), ACNetwork(::Dict), and ACPowerFlowState(::Dict) all throw the migration hint, but DCOPFProblem / DCNetwork have no ::Dict method — a Dict gets a bare MethodError. This is the root cause of how Blocking comment 1 surfaces. Adding matching ::Dict rejection methods on the DC side would make the removed-API story uniform and turn Blocking comment 1 into a clear error if it's ever reintroduced.

Worth confirming

3. _poly_cost assumes g.c is always length 3. src/types/dc_network.jl:303 indexes g.c[1], g.c[2], g.c[3] unconditionally for polynomial costs, relying on to_powerdata right-padding every model-2 gencost to exactly (cq, cl, cc). If a polynomial cost with ncost < 3 (purely linear, or a quadratic whose leading coeff collapsed) ever returns g.c shorter than 3, this is a BoundsError. The inline case and all pglib cases are quadratic (ncost=3), so the tests wouldn't catch it. Either confirm the to_powerdata contract guarantees length-3, or guard/pad before indexing.

Minor

4. AC branch shunt handling centralizes a MATPOWER-only assumption. _branch_row collapses b_fr + b_to into a single br_b; ACNetwork then re-splits it evenly (br_b/2 each) and leaves g_fr/g_to at zero — PowerIO's branch g_fr/g_to are never read. Exact for MATPOWER (symmetric line charging, zero charging conductance) and matches prior field values, but it's now a silent assumption baked into the adapter. Worth a one-line comment, or thread b_fr/b_to/g_fr/g_to through if non-MATPOWER fidelity is ever wanted.

5. Stale doc comments. Same class as Copilot's kkt_dc_opf.jl flatten_variables note (the "eta … set to 0" docstring is stale — it now packs sol.eta_ref). In test/common.jl, the header comment (~line 23) and the load_test_case docstring (~line 69) still say "PowerDiff's typed representation" / "typed parser", but parse_file now returns a PowerIO.Network.

Copilot's three inline findings all look valid; Blocking comment 1 above just expands on the two benchmark ones with the DC-specific MethodError detail.

- 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>
@samtalki

samtalki commented Jun 15, 2026

Copy link
Copy Markdown
Member

@cameronkhanpour all addressed in 9ba0ede:

  • Blocking item 1 (benchmark/benchmarks.jl): both ParsedCase branches now call PowerDiff.parse_file directly. Added DCNetwork/DCOPFProblem/DCPowerFlowState(::Dict) rejection methods so the DC side returns the migration hint instead of a bare MethodError (your item 2).
  • item 3 (_poly_cost length-3): model-2 gencosts with fewer than 3 terms are now zero-padded instead of indexing g.c[3].
  • item 4 (branch shunt): documented the MATPOWER symmetric b/2 + g_fr = g_to = 0 assumption in the adapter.
  • ** item 5 (stale docs)**: flatten_variables eta docstring and test/common.jl (header + load_test_case) updated to reflect PowerIO.Network.

Full Pkg.test suite stays green. Two items left for a follow-up:

  • Generator/branch row_to_id still carry dense file-order indices (not source mpc rows). I documented the contract in _network_data rather than flip a tested behavior; happy to make them source-true via g.i/br.i if you want that.
  • test/smoke_rts_gmlc.jl is incompatible with the new parser — RTS_GMLC carries dcline records that _network_data rejects: so it needs a separate rework rather than a mechanical migration. test/mwe_unified.jl is migrated and runs.

@cameronkhanpour

Copy link
Copy Markdown
Collaborator

your latest commit is failing the benchmark check because benchmark.jl still uses parse_file() function that was removed from the previous commits when we migrated from our own parser to PowerIO.

@samtalki samtalki merged commit 1f8051e into main Jun 15, 2026
5 checks passed
@samtalki samtalki deleted the mk/backends branch June 15, 2026 18:46
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.

4 participants