Skip to content

Support disconnected DC network islands#55

Merged
samtalki merged 13 commits into
mainfrom
ck/dc-island-support-followup
Jun 15, 2026
Merged

Support disconnected DC network islands#55
samtalki merged 13 commits into
mainfrom
ck/dc-island-support-followup

Conversation

@cameronkhanpour

Copy link
Copy Markdown
Collaborator

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

  • Add per-island DC reference-bus selection while preserving the configured ref_bus for its island.
  • Generalize DC OPF KKT dimensions, residuals, analytical Jacobians, JVPs, VJPs, LMP decomposition, DCPF factorization, and related sensitivity helpers to use one reference constraint per energized island.
  • Add topology caching and problem reference-count caching to keep lightweight KKT builders near the pre-feature allocation shape.
  • Document multi-island DC behavior, per-island energy components, and the non-smooth sensitivity boundary when switching changes split or merge islands.
  • Reject multi-island AcceleratedDCPowerFlows conversion clearly because APF exposes one slack bus.
  • Add focused multi-island tests for programmatic islands, isolated shedding, isolated buses, bridge-opening cases, KKT residuals, finite sensitivities, cache refresh, and bundled MATPOWER regressions.

Review Notes

This is intentionally draft because review on #54 raised design questions that are specific to this broader feature:

  • lazy mutable topology caching inside DCNetwork and the resulting thread-safety contract
  • _n_ref as a cached invariant of a built DCOPFProblem

Those are now isolated here instead of gating the issue #53 fix.

Validation

  • git diff --check ck/dcopf-solve...HEAD
  • clean temporary Julia environment: Pkg.test("PowerDiff")
  • clean temporary docs environment: SITE_BUILD=true include("docs/make.jl")

@cameronkhanpour cameronkhanpour marked this pull request as ready for review June 7, 2026 07:05
Base automatically changed from ck/dcopf-solve to main June 9, 2026 04:32
@samtalki samtalki force-pushed the ck/dc-island-support-followup branch from 490f41c to 7ee4b3c Compare June 15, 2026 19:02
@github-actions

github-actions Bot commented Jun 15, 2026

Copy link
Copy Markdown

Benchmark Results (Julia v1)

Time benchmarks
main 915caaa... main / 915caaa...
ac_opf/kkt_jacobian/case30.m 3.32 ± 0.041 ms 3.32 ± 0.042 ms 1 ± 0.018
ac_opf/kkt_param/case30.m/switching 0.105 ± 0.0094 ms 0.105 ± 0.0082 ms 0.992 ± 0.12
dc_opf/kkt_jacobian/case30.m/cost_linear 0.15 ± 0.001 μs 0.251 ± 0.01 μs 0.598 ± 0.024
dc_opf/kkt_jacobian/case30.m/cost_quadratic 0.141 ± 0.011 μs 0.16 ± 0.01 μs 0.881 ± 0.088
dc_opf/kkt_jacobian/case30.m/demand 0.271 ± 0.021 μs 0.391 ± 0.071 μs 0.693 ± 0.14
dc_opf/kkt_jacobian/case30.m/flowlimit 0.381 ± 0.039 μs 0.401 ± 0.049 μs 0.95 ± 0.15
dc_opf/kkt_jacobian/case30.m/full 15.4 ± 16 μs 15 ± 4.9 μs 1.02 ± 1.1
dc_opf/kkt_jacobian/case30.m/susceptance 0.0884 ± 0.0029 ms 0.0875 ± 0.0043 ms 1.01 ± 0.059
parser/case30.m 0.811 ± 0.03 ms 0.806 ± 0.037 ms 1.01 ± 0.06
time_to_load 1.78 ± 0.0026 s 1.78 ± 0.016 s 0.999 ± 0.009
Memory benchmarks
main 915caaa... main / 915caaa...
ac_opf/kkt_jacobian/case30.m 0.033 M allocs: 1.17 MB 0.033 M allocs: 1.17 MB 1
ac_opf/kkt_param/case30.m/switching 1.48 k allocs: 0.603 MB 1.48 k allocs: 0.603 MB 1
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.0819 MB 0.081 k allocs: 0.0824 MB 0.995
dc_opf/kkt_jacobian/case30.m/susceptance 2.28 k allocs: 0.291 MB 2.28 k allocs: 0.291 MB 1
parser/case30.m 16.9 k allocs: 0.508 MB 16.9 k allocs: 0.508 MB 1
time_to_load 0.149 k allocs: 11.1 kB 0.149 k allocs: 11.1 kB 1

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 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 DCNetwork and exposes reference_buses(net) to provide one deterministic reference bus per energized island (preserving the configured ref_bus for its island).
  • Generalizes DC OPF modeling + KKT dimensions/indices/residuals/Jacobians to use n_ref reference 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.

Comment thread ext/PowerDiffAPFExt.jl Outdated

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

@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

Comment thread docs/src/advanced.md
Comment thread src/sens/lmp.jl Outdated
Comment thread src/sens/lmp.jl Outdated
Comment thread src/sens/lmp.jl Outdated
Comment thread src/sens/susceptance.jl Outdated
Comment thread src/types/dc_network.jl
Comment thread src/types/dc_opf_problem.jl Outdated
Comment thread docs/src/math/dc-power-flow.md Outdated
Comment thread docs/src/math/dc-opf.md Outdated
- 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).
Comment thread src/sens/lmp.jl
Comment thread src/types/dc_network.jl
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'.

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

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 (and reference_buses) treats a branch as energized iff b[e] * sw[e] != 0. If a branch is de-energized by setting b[e] = 0 while sw[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 equivalent sw_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)

Comment thread src/sens/lmp.jl
Comment thread src/sens/lmp.jl Outdated
Comment thread docs/src/math/dc-opf.md Outdated

@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 @cameronkhanpour , and thanks @klamike

Comment thread src/prob/kkt_dc_opf.jl
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.
@samtalki samtalki merged commit 3a2e809 into main Jun 15, 2026
5 checks passed
@samtalki samtalki deleted the ck/dc-island-support-followup branch June 15, 2026 23:53
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