handle zero impedance branches within reductions structure#307
handle zero impedance branches within reductions structure#307m-bossart wants to merge 9 commits into
Conversation
Performance ResultsPrecompile Time
Execution Time
|
| """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) |
There was a problem hiding this comment.
Shouldn't we just eliminate the branch ? According to PSSe doc this is equivalent to the switch and the two nodes are the same
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
ZeroImpedanceBranchReductionand applies it automatically as the first reduction inYbus(...). - Updates Ybus/arc-admittance reduction application to support bus merges (
merged_bus_pairs) including arc-axis relabeling and duplicate/self-loop handling. - Adjusts how
DiscreteControlledACBranchcomponents 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.
| (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 |
| # 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 |
|
@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. |
| return bus_numbers_to_remove | ||
| end | ||
|
|
||
| function _remap_arc_keys_batch!( |
There was a problem hiding this comment.
There should be at least a @debug call somewhere here to avoid making a silent overwrite
| 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) |
| @@ -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 | |||
There was a problem hiding this comment.
This is unused now. Remove if its no longer applied elsewhere in the code
| 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 |
There was a problem hiding this comment.
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
| data[i, :] += data[j, :] | ||
| data[:, i] += data[:, j] | ||
| adjacency_data[i, :] += adjacency_data[j, :] | ||
| adjacency_data[:, i] += adjacency_data[:, j] |
There was a problem hiding this comment.
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
| return _apply_reduction(ybus, network_reduction_data) | ||
| end | ||
|
|
||
| function _resolve_arc_admittance( |
jd-lara
left a comment
There was a problem hiding this comment.
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])], |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| 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])], |
There was a problem hiding this comment.
[JuliaFormatter] reported by reviewdog 🐶
| network_reductions = NetworkReduction[RadialReduction(; irreducible_buses = [112, 113])], | |
| network_reductions = NetworkReduction[RadialReduction(; | |
| irreducible_buses = [112, 113], | |
| )], |
Addresses #299 and #301