Skip to content

handle zero impedance branches within reductions structure#307

Open
m-bossart wants to merge 9 commits into
mainfrom
mb/zero-impedance-handling
Open

handle zero impedance branches within reductions structure#307
m-bossart wants to merge 9 commits into
mainfrom
mb/zero-impedance-handling

Conversation

@m-bossart
Copy link
Copy Markdown
Contributor

@m-bossart m-bossart commented May 20, 2026

Addresses #299 and #301

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 20, 2026

Performance Results

Precompile Time

Main This Branch Delta
2.089 s 2.089 s -0.0%

Execution Time

Test Main This Branch Delta
matpower_ACTIVSg2000_sys-Build PTDF First 1.604 s 1.859 s +15.9%
matpower_ACTIVSg2000_sys-Build PTDF Second 175.4 ms 109.8 ms -37.4%
matpower_ACTIVSg2000_sys-Build Ybus First 13.5 ms 13.3 ms -1.5%
matpower_ACTIVSg2000_sys-Build Ybus Second 387.4 ms 12.8 ms -96.7%
matpower_ACTIVSg2000_sys-Build LODF First 157.3 ms 667.0 ms +324.0%
matpower_ACTIVSg2000_sys-Build LODF Second 170.6 ms 160.3 ms -6.1%
matpower_ACTIVSg2000_sys-Build VirtualMODF First 3.118 s 2.657 s -14.8%
matpower_ACTIVSg2000_sys-Build VirtualMODF Second 65.7 ms 499.1 ms +659.6%
matpower_ACTIVSg2000_sys-VirtualMODF Query 10 rows 320.7 ms 329.2 ms +2.6%
matpower_ACTIVSg2000_sys-Radial network reduction First 411.0 ms 559.0 ms +36.0%
matpower_ACTIVSg2000_sys-Radial network reduction Second 0.6 ms 0.7 ms +4.7%
matpower_ACTIVSg2000_sys-Degree two network reduction First 1.548 s 1.724 s +11.4%
matpower_ACTIVSg2000_sys-Degree two network reduction Second 1.0 ms 0.9 ms -8.0%
Base_Eastern_Interconnect_515GW-Build Ybus First 4.32 s 4.152 s -3.9%
Base_Eastern_Interconnect_515GW-Build Ybus Second 4.152 s 4.211 s +1.4%
Base_Eastern_Interconnect_515GW-Radial network reduction First 44.1 ms 133.5 ms +203.0%
Base_Eastern_Interconnect_515GW-Radial network reduction Second 44.2 ms 35.3 ms -20.2%
Base_Eastern_Interconnect_515GW-Degree two network reduction First 353.0 ms 341.2 ms -3.3%
Base_Eastern_Interconnect_515GW-Degree two network reduction Second 43.0 ms 38.7 ms -10.1%

@m-bossart m-bossart marked this pull request as ready for review May 21, 2026 00:01
@jd-lara jd-lara requested review from Copilot and jd-lara May 21, 2026 00:28
Comment thread src/Ybus.jl
"""Ybus entries for a `Line` or a `DiscreteControlledACBranch`."""
function ybus_branch_entries(br::PSY.ACTransmission)
Y_l = (1 / (PSY.get_r(br) + PSY.get_x(br) * 1im))
r = PSY.get_r(br)
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.

Shouldn't we just eliminate the branch ? According to PSSe doc this is equivalent to the switch and the two nodes are the same

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

If you have a branch with shunt components we want to retain those contributions to the ybus. The new strategy implemented in this PR is to add all of the branches (even the ones with close to zero impedance) and then update the ybus by "merging" those two nodes based on the off-diagonal ybus entries. So in effect those two nodes are the same, it just happens separate from the ybus build (like the other reductions)

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an internal, mandatory “zero-impedance branch” reduction step to Ybus construction to merge electrically equivalent buses connected by (near) zero-impedance non-transformer branches, and updates reduction plumbing so merged buses propagate correctly into arc-admittance matrices and branch maps.

Changes:

  • Introduces ZeroImpedanceBranchReduction and applies it automatically as the first reduction in Ybus(...).
  • Updates Ybus/arc-admittance reduction application to support bus merges (merged_bus_pairs) including arc-axis relabeling and duplicate/self-loop handling.
  • Adjusts how DiscreteControlledACBranch components are collected for Ybus assembly (only available + CLOSED).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/zero_impedance_branch_reduction.jl New reduction pass that detects candidate “zero-impedance” arcs from Ybus off-diagonals and emits bus-merge + removed-arc data.
src/YbusACBranches.jl Adds specialized branch collection for DiscreteControlledACBranch (available + CLOSED) and includes breaker/switches in the assembled branch set.
src/Ybus.jl Applies the new reduction as mandatory first step; adds merged-bus handling for Ybus + arc-admittance matrices; remaps branch maps after merges; fixes dict-mutation during subnetwork-axis updates.
src/NetworkReductionData.jl Adds merged_bus_pairs to reduction data model.
src/definitions.jl Adds constants for susceptance threshold and epsilon reactance used by the zero-impedance handling.
src/PowerNetworkMatrices.jl Includes the new reduction source file in the module.

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

Comment thread src/Ybus.jl Outdated
(new_from == arc[1] && new_to == arc[2]) && continue
val = pop!(map, arc)
new_arc = (new_from, new_to)
new_arc[1] != new_arc[2] && (map[new_arc] = val)
# Use chain-resolved surviving bus so _merge_ybus_buses! processes correctly for chains
nr.merged_bus_pairs[to_no] = nr.reverse_bus_search_map[to_no]
push!(nr.removed_arcs, arc_key)
end
Comment thread src/zero_impedance_branch_reduction.jl
Comment thread src/Ybus.jl Outdated
Comment on lines 1022 to 1026
# Always apply zero-impedance branch reduction first (mandatory, internal).
ybus = build_reduced_ybus(ybus, sys, ZeroImpedanceBranchReduction())
for nr in network_reductions
ybus = build_reduced_ybus(ybus, sys, nr)
end
@m-bossart
Copy link
Copy Markdown
Contributor Author

@jd-lara There are some comments from co-pilot that I need to address, including better testing. But I'm interested in feedback on the overall approach of just adding all of the components and then looking at the Ybus to determine which branches qualify for the zero-impedance reduction. The benefit of this is not having to worry about wacky combinations of parallel devices in the data causing issues.

Comment thread src/Ybus.jl Outdated
return bus_numbers_to_remove
end

function _remap_arc_keys_batch!(
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.

There should be at least a @debug call somewhere here to avoid making a silent overwrite

Comment thread src/zero_impedance_branch_reduction.jl Outdated
transformer_arcs = Set{Tuple{Int, Int}}()
union!(transformer_arcs, keys(nrd.transformer3W_map))
for (arc, branch) in nrd.direct_branch_map
branch isa PSY.TwoWindingTransformer && push!(transformer_arcs, arc)
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.

Use dispatch here not a isa

Comment thread src/definitions.jl Outdated
@@ -6,6 +6,8 @@ const GiB = MiB * KiB
const MAX_CACHE_SIZE_MiB = 100
const ROW_PERSISTENT_CACHE_WARN = 1 * GiB
const ZERO_IMPEDANCE_LINE_REACTANCE_THRESHOLD = 1e-3
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.

This is unused now. Remove if its no longer applied elsewhere in the code

Comment thread src/zero_impedance_branch_reduction.jl Outdated
nz_rows, nz_cols, nz_vals = SparseArrays.findnz(get_data(ybus))
for (row_ix, col_ix, v) in zip(nz_rows, nz_cols, nz_vals)
row_ix >= col_ix && continue
real(v) == 0.0 || continue
Copy link
Copy Markdown
Member

@jd-lara jd-lara May 21, 2026

Choose a reason for hiding this comment

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

This entries will be Float32 so you will want to use a iszero(real(v)) to avoid memory costs on conversion.

This might apply in other places

Comment thread src/Ybus.jl Outdated
Comment on lines +1337 to +1340
data[i, :] += data[j, :]
data[:, i] += data[:, j]
adjacency_data[i, :] += adjacency_data[j, :]
adjacency_data[:, i] += adjacency_data[:, j]
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.

All of this block is allocating. Each data[i, :] += something has 2 SparseVector materialization and triggers a full traverseasl of the colptr in the sparse matrix.

You need dedicated accumulators.

If a system has 1000s of zero impedance objects this loop becomes costly

Comment thread src/Ybus.jl
return _apply_reduction(ybus, network_reduction_data)
end

function _resolve_arc_admittance(
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.

Needs to be type signed

Copy link
Copy Markdown
Member

@jd-lara jd-lara left a comment

Choose a reason for hiding this comment

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

The biggest comments is to avoid == 0.0 and use sizer and the refactoring of the accumulators. Those can be re-written as

function _accumulate_csc_row_into!(
      M::SparseArrays.SparseMatrixCSC,
      i::Int,
      j::Int,
  )
      rows = SparseArrays.rowvals(M)
      vals = SparseArrays.nonzeros(M)
      for col in 1:size(M, 2)
          v_j = zero(eltype(M))
          for k in SparseArrays.nzrange(M, col)
              rows[k] == j && (v_j = vals[k])
          end
          iszero(v_j) && continue
          found = false
          for k in SparseArrays.nzrange(M, col)
              if rows[k] == i
                  vals[k] += v_j
                  found = true
                  break
              end
          end
          found || (M[i, col] += v_j)
      end
      return
  end

  function _accumulate_csc_col_into!(
      M::SparseArrays.SparseMatrixCSC,
      i::Int,
      j::Int,
  )
      rows = SparseArrays.rowvals(M)
      vals = SparseArrays.nonzeros(M)
      j_range = SparseArrays.nzrange(M, j)
      i_range = SparseArrays.nzrange(M, i)
      for k_j in j_range
          r = rows[k_j]
          v_j = vals[k_j]
          iszero(v_j) && continue
          found = false
          for k_i in i_range
              if rows[k_i] == r
                  vals[k_i] += v_j
                  found = true
                  break
              end
          end
          found || (M[r, i] += v_j)
      end
      return
  end

  function _merge_ybus_buses!(
      data::SparseArrays.SparseMatrixCSC{YBUS_ELTYPE, Int},
      adjacency_data::SparseArrays.SparseMatrixCSC{Int8, Int},
      bus_lookup::Dict{Int, Int},
      merged_bus_pairs::Dict{Int, Int},
  )
      for (removed_bus, surviving_bus) in merged_bus_pairs
          i = bus_lookup[surviving_bus]
          j = bus_lookup[removed_bus]
          _accumulate_csc_row_into!(data, i, j)
          _accumulate_csc_col_into!(data, i, j)
          _accumulate_csc_row_into!(adjacency_data, i, j)
          _accumulate_csc_col_into!(adjacency_data, i, j)
      end
      return
  end

# Mark both sides of the (112, 113) arc irreducible: merge should be skipped.
@test_logs (:warn, r"irreducible buses (112 and 113|113 and 112)") Ybus(
sys;
network_reductions = NetworkReduction[RadialReduction(; irreducible_buses = [112, 113])],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
network_reductions = NetworkReduction[RadialReduction(; irreducible_buses = [112, 113])],
network_reductions = NetworkReduction[RadialReduction(;
irreducible_buses = [112, 113],
)],

)
ybus_skip = Ybus(
sys;
network_reductions = NetworkReduction[RadialReduction(; irreducible_buses = [112, 113])],
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
network_reductions = NetworkReduction[RadialReduction(; irreducible_buses = [112, 113])],
network_reductions = NetworkReduction[RadialReduction(;
irreducible_buses = [112, 113],
)],

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.

3 participants