Support disconnected DC network islands#55
Conversation
490f41c to
7ee4b3c
Compare
Benchmark Results (Julia v1)Time benchmarks
Memory benchmarks
|
There was a problem hiding this comment.
Pull request overview
This PR adds first-class support for disconnected (multi-island) DC network topologies across the DC power flow + DC OPF stack, including per-island reference bus selection, KKT/sensitivity generalization to multiple reference constraints, caching adjustments to keep KKT builders lightweight, targeted documentation updates, and expanded test coverage (including APF interop behavior).
Changes:
- Introduces energized-island topology caching in
DCNetworkand exposesreference_buses(net)to provide one deterministic reference bus per energized island (preserving the configuredref_busfor its island). - Generalizes DC OPF modeling + KKT dimensions/indices/residuals/Jacobians to use
n_refreference constraints (one per energized island), with cache invalidation updates to handle topology-driven dimension changes. - Adds multi-island regression tests, updates docs to describe multi-island behavior and nonsmooth sensitivity boundaries, and rejects APF conversion for disconnected cases.
Reviewed changes
Copilot reviewed 20 out of 20 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/test_dc_islands.jl | Adds focused multi-island DC tests (islands, isolated buses, bridge opening, cache refresh, regressions). |
| test/test_apf_integration.jl | Adds test asserting APF conversion rejects multi-island networks. |
| test/runtests.jl | Registers the new DC islands test file in the test suite. |
| src/types/show.jl | Improves printing for multi-island reference buses and empty flow vectors. |
| src/types/dc_opf_problem.jl | Builds one reference constraint per energized island; caches _n_ref; clears work on topology invalidation. |
| src/types/dc_network.jl | Adds energized-topology cache, reference bus selection per island, and reduced B factorization for multi-island cases. |
| src/sens/topology.jl | Updates sensitivity documentation to match per-island reference elimination. |
| src/sens/susceptance.jl | Switches KKT layout sizing to the new multi-ref layout helper. |
| src/sens/lmp.jl | Updates LMP decomposition semantics to be uniform per island and uses non-ref set for multiple refs. |
| src/sens/flowlimit.jl | Updates KKT layout sizing to the new multi-ref layout helper. |
| src/sens/cost.jl | Updates KKT layout sizing to the new multi-ref layout helper. |
| src/prob/kkt_dc_opf.jl | Generalizes KKT dims/indices/residual/Jacobian construction to n_ref reference constraints. |
| src/prob/dc_opf.jl | Extracts a vector of reference-constraint duals (eta_ref) aligned with reference buses. |
| src/PowerDiff.jl | Exports reference_buses. |
| ext/PowerDiffAPFExt.jl | Rejects APF conversion when multiple energized islands exist; uses effective reference bus. |
| docs/src/math/dc-power-flow.md | Documents per-island reference elimination and topology cache/thread-safety contract. |
| docs/src/math/dc-opf.md | Documents multi-island reference constraints, KKT dimension changes, and nonsmooth topology boundaries. |
| docs/src/getting-started.md | Updates DC power flow description for per-island reference elimination. |
| docs/src/api.md | Adds reference_buses to the API list. |
| docs/src/advanced.md | Documents reference_buses and topology caching/thread-safety + rebuild requirements. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
samtalki
left a comment
There was a problem hiding this comment.
@cameronkhanpour made an integration pass and reviewed the result. Apologies in advance if my review comments point to anything that was my doing.
I fixed:
- stale island tests that still used removed DCNetwork(::Dict) paths
- unsupported dcline fixture handling in those tests
- empty range display crashes for zero branch DC networks
- related docs/comment wording cleanup
- Reject non-binary switching states in to_apf_network so PowerDiff's energized-island count (b*sw != 0) cannot disagree with APF's on/off branch binarization (sw > 0.5); add a regression test. - Annotate the DCOPFProblem placeholder constructor call so the positional arguments (empty variable/constraint fields, normalized demand, initial _n_ref, optimizer, silent) are self-explanatory. - Make the documented theta-stationarity consistent with the implementation: define E_ref as the n x n_ref reference-selection matrix and include the Diag(sw) gate on the angle-difference dual term across lmp.jl, susceptance.jl, kkt_dc_opf.jl, and dc-opf.md; carry the same gate in calc_congestion_component (a no-op for energized branches where sw == 1). - Clarify that the per-island reference choice is deterministic (configured ref_bus for its island, lowest sequential index otherwise) and that the energy component is exactly uniform per island only in the unregularized limit, in lmp.jl and the math/advanced docs. - Complete the DCNetwork field table in advanced.md (demand, pg_init, topology_cache).
Locks in the fix from the prior commit: calc_congestion_component must gate the angle-difference dual term by sw to match the KKT theta-stationarity A' Diag(sw) (gamma_ub - gamma_lb). The test uses a fractionally switched branch with a binding angle limit and asserts the gated result differs from the old ungated form, which it does not for fully closed networks (sw == 1).
Explain that the cache's from_bus/to_bus are the branch endpoints already encoded by the incidence matrix A, materialized as dense Int vectors so the connectivity refresh reads them in O(1). Annotate _refresh_topology_cache!: union-find over energized branches builds the island partition, then one reference bus is chosen per island (lowest index, except the configured ref_bus which is forced for its own island).
Replace the hard rejection of non-binary sw with an allow_fractional kwarg (default false). Fractional switching states are the basis of switching sensitivity, so rejecting them outright was too strict; the only constraint is that APF cannot represent partial switching. With allow_fractional=true the network is converted under APF's own sw > 0.5 binarization. Base the single-island requirement on that same binarization rule (closed and nonzero susceptance) rather than PD's b*sw != 0 energized check, so the slack-bus assumption matches the topology APF builds even for fractional sw. For binary sw the two rules coincide, so the default path is unchanged. Add a union-find connectivity helper and tests for both the opt-in conversion and the still-rejected disconnected-under-binarization case.
Corrected wording in the description of 'topology_cache'.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 21 changed files in this pull request and generated 3 comments.
Comments suppressed due to low confidence (1)
src/types/dc_opf_problem.jl:274
- Phase angle difference constraints are currently gated only by
sw, but the new energized-island logic (andreference_buses) treats a branch as energized iffb[e] * sw[e] != 0. If a branch is de-energized by settingb[e] = 0whilesw[e] == 1, it will correctly split islands / add a reference bus, but these angle-difference constraints will still couple the endpoint angles as if the branch were present. This makes the OPF feasible set inconsistent with the energized-topology definition used elsewhere (and with the comment “Open lines should not constrain angle differences”). Consider gating angle-difference constraints by the same energized predicate (or an equivalentsw_eff), and then updating the KKT residual/Jacobian and LMP congestion decomposition to match.
# Open lines should not constrain angle differences.
phase_diff_lb = @constraint(model, network.sw .* (network.A * va) .>= network.sw .* network.angmin)
phase_diff_ub = @constraint(model, network.sw .* (network.A * va) .<= network.sw .* network.angmax)
samtalki
left a comment
There was a problem hiding this comment.
Nice work @cameronkhanpour , and thanks @klamike
Address Copilot review: the file-header decomposition comment in lmp.jl, the inline comment in calc_congestion_component, and the LMP decomposition equation in dc-opf.md still showed the angle-difference dual term ungated. Add the Diag(sw) gate so they match the implementation and the theta-stationarity, and reword the inline note to cover fully closed, fractional, and open branches rather than implying energized always means sw == 1.
Summary
Stacked follow-up to #54.
This moves the disconnected-DC-topology work that was previously bundled into #54 into its own PR, so #54 can remain focused on the negative net demand fix for issue #53.
Changes
ref_busfor its island.AcceleratedDCPowerFlowsconversion clearly because APF exposes one slack bus.Review Notes
This is intentionally draft because review on #54 raised design questions that are specific to this broader feature:
DCNetworkand the resulting thread-safety contract_n_refas a cached invariant of a builtDCOPFProblemThose are now isolated here instead of gating the issue #53 fix.
Validation
git diff --check ck/dcopf-solve...HEADPkg.test("PowerDiff")SITE_BUILD=true include("docs/make.jl")