Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
135 changes: 111 additions & 24 deletions src/Ybus.jl
Original file line number Diff line number Diff line change
Expand Up @@ -820,6 +820,114 @@ function _get_filtered_components(
return components
end

"""
Apply reductions that always run during Ybus construction, before any user-specified
`network_reductions`. Currently this performs two steps:

- Merge closed zero-impedance `DiscreteControlledACBranch` components by collapsing
their endpoint buses (mutates `nr.bus_reduction_map` and `nr.reverse_bus_search_map`).
- Identify any other two-terminal branches whose endpoints both resolve to the same
surviving bus after the merge, and exclude them so they cannot produce self-loop
rows in the incidence matrix downstream.

Returns `(breaker_switches, collapsed_branch_names)`:

- `breaker_switches`: closed breakers with non-trivial impedance that still need to
be modeled as Y-bus branches.
- `collapsed_branch_names`: names of non-breaker two-terminal branches to be excluded
from `_get_ybus_two_terminal_ac_branches`.
"""
function _apply_implicit_reductions!(sys::PSY.System, nr::NetworkReductionData)
breaker_switches = _merge_zero_impedance_breakers!(sys, nr)
collapsed_branch_names = _eliminate_collapsed_branches!(sys, breaker_switches, nr)
return breaker_switches, collapsed_branch_names
end

"""
Merge closed `DiscreteControlledACBranch` components with near-zero impedance by
collapsing their endpoints in the network reduction maps. Returns the closed breakers
with non-trivial impedance that must still be modeled as Y-bus branches.
"""
function _merge_zero_impedance_breakers!(sys::PSY.System, nr::NetworkReductionData)
breaker_switches = Vector{PSY.DiscreteControlledACBranch}()
reverse_bus_search_map = get_reverse_bus_search_map(nr)
bus_reduction_map = get_bus_reduction_map(nr)
for br in PSY.get_components(PSY.DiscreteControlledACBranch, sys)
PSY.get_available(br) || continue
PSY.get_branch_status(br) == PSY.DiscreteControlledBranchStatus.CLOSED ||
continue
r = PSY.get_r(br)
x = PSY.get_x(br)
if r == 0.0 && x < ZERO_IMPEDANCE_LINE_REACTANCE_THRESHOLD
from_bus_number = PSY.get_number(PSY.get_from(PSY.get_arc(br)))
to_bus_number = PSY.get_number(PSY.get_to(PSY.get_arc(br)))
_update_bus_maps!(
reverse_bus_search_map,
bus_reduction_map,
to_bus_number,
from_bus_number,
)
push!(nr.removed_arcs, (from_bus_number, to_bus_number))
else
push!(breaker_switches, br)
end
end
return breaker_switches
end

"""
Identify two-terminal branches whose endpoints both resolve to the same surviving
bus after the zero-impedance breaker merge. Such branches would produce self-loop
rows in the incidence matrix and corrupt downstream reduction algorithms.

Filters collapsed entries out of `breaker_switches` in place. Returns a set of names
for non-breaker branches that the caller must exclude from
`_get_ybus_two_terminal_ac_branches`.
"""
function _eliminate_collapsed_branches!(
sys::PSY.System,
breaker_switches::Vector{PSY.DiscreteControlledACBranch},
nr::NetworkReductionData,
)
collapsed = Set{String}()
reverse_bus_search_map = get_reverse_bus_search_map(nr)
isempty(reverse_bus_search_map) && return collapsed
for br in PSY.get_components(PSY.ACTransmission, sys)
PSY.get_available(br) || continue
br isa PSY.ThreeWindingTransformer && continue
br isa PSY.DiscreteControlledACBranch && continue
if _record_if_collapsed!(br, nr, reverse_bus_search_map)
push!(collapsed, PSY.get_name(br))
end
end
filter!(
br -> !_record_if_collapsed!(br, nr, reverse_bus_search_map),
breaker_switches,
)
return collapsed
end

# Returns `true` if both endpoints of `br` resolve to the same surviving bus, in
# which case the arc is added to `nr.removed_arcs`. Used by
# `_eliminate_collapsed_branches!` to flag and audit branches that the implicit
# breaker merge has turned into self-loops.
function _record_if_collapsed!(
br::PSY.ACTransmission,
nr::NetworkReductionData,
reverse_bus_search_map::Dict{Int, Int},
)
arc = PSY.get_arc(br)
fr_no = PSY.get_number(PSY.get_from(arc))
to_no = PSY.get_number(PSY.get_to(arc))
fr_resolved = get(reverse_bus_search_map, fr_no, fr_no)
to_resolved = get(reverse_bus_search_map, to_no, to_no)
fr_resolved == to_resolved || return false
push!(nr.removed_arcs, (fr_no, to_no))
@debug "Implicit reduction: branch $(PSY.get_name(br)) excluded; endpoints " *
"$fr_no and $to_no both collapse to bus $fr_resolved."
return true
end

"""
Ybus(
sys::PSY.System;
Expand Down Expand Up @@ -907,29 +1015,7 @@ function Ybus(
end
end

#Building map for removed Breaker/Switches
breaker_switches = Vector{PSY.DiscreteControlledACBranch}()
for br in PSY.get_components(PSY.DiscreteControlledACBranch, sys)
!PSY.get_available(br) && continue
r = PSY.get_r(br)
x = PSY.get_x(br)
status = PSY.get_branch_status(br)
if status == PSY.DiscreteControlledBranchStatus.CLOSED
if r == 0.0 && x < ZERO_IMPEDANCE_LINE_REACTANCE_THRESHOLD
from_bus_number = PSY.get_number(PSY.get_from(PSY.get_arc(br)))
to_bus_number = PSY.get_number(PSY.get_to(PSY.get_arc(br)))
_update_bus_maps!(
reverse_bus_search_map,
bus_reduction_map,
to_bus_number,
from_bus_number,
)
push!(nr.removed_arcs, (from_bus_number, to_bus_number))
else
push!(breaker_switches, br)
end
end
end
breaker_switches, collapsed_branch_names = _apply_implicit_reductions!(sys, nr)

bus_ax = sort!(collect(keys(bus_reduction_map)))
axes = (bus_ax, bus_ax)
Expand All @@ -940,7 +1026,8 @@ function Ybus(
bus_lookup[b] = ix
end
adj = SparseArrays.spdiagm(ones(Int8, busnumber))
branches = _get_ybus_two_terminal_ac_branches(sys)
branches =
_get_ybus_two_terminal_ac_branches(sys; skip_names = collapsed_branch_names)
append!(branches.breaker_switches, breaker_switches)
transformer_3W =
_get_filtered_components(PSY.ThreeWindingTransformer, sys, PSY.get_available)
Expand Down
38 changes: 28 additions & 10 deletions src/YbusACBranches.jl
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,23 @@ end

function _populate_ybus_branch_vector!(
vec::Vector{T},
sys::PSY.System,
sys::PSY.System;
skip_names::Set{String} = Set{String}(),
) where {T <: PSY.ACTransmission}
iter = PSY.get_components(T, sys)
sizehint!(vec, length(iter))
for br in iter
PSY.get_available(br) && push!(vec, br)
PSY.get_available(br) || continue
PSY.get_name(br) in skip_names && continue
push!(vec, br)
end
return
end

function _get_ybus_two_terminal_ac_branches(sys::PSY.System)::YbusACBranches
function _get_ybus_two_terminal_ac_branches(
sys::PSY.System;
skip_names::Set{String} = Set{String}(),
)::YbusACBranches
branches = YbusACBranches(
Vector{PSY.Line}(),
Vector{PSY.MonitoredLine}(),
Expand All @@ -43,13 +49,25 @@ function _get_ybus_two_terminal_ac_branches(sys::PSY.System)::YbusACBranches
Vector{PSY.DynamicBranch}(),
Vector{PSY.DiscreteControlledACBranch}(),
)
_populate_ybus_branch_vector!(branches.lines, sys)
_populate_ybus_branch_vector!(branches.monitored_lines, sys)
_populate_ybus_branch_vector!(branches.generic_arc_impedances, sys)
_populate_ybus_branch_vector!(branches.tap_transformers, sys)
_populate_ybus_branch_vector!(branches.phase_shifting_transformers, sys)
_populate_ybus_branch_vector!(branches.transformer2w, sys)
_populate_ybus_branch_vector!(branches.dynamic_branches, sys)
_populate_ybus_branch_vector!(branches.lines, sys; skip_names = skip_names)
_populate_ybus_branch_vector!(branches.monitored_lines, sys; skip_names = skip_names)
_populate_ybus_branch_vector!(
branches.generic_arc_impedances,
sys;
skip_names = skip_names,
)
_populate_ybus_branch_vector!(branches.tap_transformers, sys; skip_names = skip_names)
_populate_ybus_branch_vector!(
branches.phase_shifting_transformers,
sys;
skip_names = skip_names,
)
_populate_ybus_branch_vector!(branches.transformer2w, sys; skip_names = skip_names)
_populate_ybus_branch_vector!(
branches.dynamic_branches,
sys;
skip_names = skip_names,
)
return branches
end

Expand Down
16 changes: 15 additions & 1 deletion src/radial_reduction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ by that branch.
function _build_row_to_cols(A::SparseArrays.SparseMatrixCSC{Int8, Int}, buscount::Int)
n_rows = size(A, 1)
row_first_col = zeros(Int, n_rows)
row_to_cols = Vector{Tuple{Int, Int}}(undef, n_rows)
# Initialize with a (0, 0) sentinel so any row with fewer than two stored
# entries can be detected after the loop. Without this, such rows leave
# `undef` slots that silently propagate garbage memory downstream.
row_to_cols = fill((0, 0), n_rows)
Arowval = SparseArrays.rowvals(A)
for col in 1:buscount
for k in SparseArrays.nzrange(A, col)
Expand All @@ -48,6 +51,17 @@ function _build_row_to_cols(A::SparseArrays.SparseMatrixCSC{Int8, Int}, buscount
end
end
end
for r in 1:n_rows
if row_to_cols[r] == (0, 0)
n_found = row_first_col[r] == 0 ? 0 : 1
error(
"Incidence-matrix row $r has $n_found stored entr$(n_found == 1 ? "y" : "ies") " *
"but exactly 2 are required (one per branch endpoint). This usually " *
"indicates a self-loop arc or a network reduction that left a " *
"collapsed arc in the arc axis.",
)
end
end
return row_to_cols
end

Expand Down
Loading