1- mutable struct BranchesParallel{T <: PSY.ACTransmission } <: PSY.ACTransmission
1+ abstract type AbstractBranchesParallel <: PSY.ACTransmission end
2+
3+ mutable struct BranchesParallel{T <: PSY.ACTransmission } <: AbstractBranchesParallel
24 branches:: Vector{T}
35 equivalent_ybus:: Union{Matrix{YBUS_ELTYPE}, Nothing}
6+
7+ function BranchesParallel {T} (
8+ branches:: Vector{T} ,
9+ equivalent_ybus:: Union{Matrix{YBUS_ELTYPE}, Nothing} ,
10+ ) where {T <: PSY.ACTransmission }
11+ if ! isconcretetype (T)
12+ error (
13+ " BranchesParallel{T} requires a concrete branch type T. " *
14+ " Use MixedBranchesParallel for groups with mixed branch types. Got T=$T ." ,
15+ )
16+ end
17+ return new {T} (branches, equivalent_ybus)
18+ end
419end
520
621function BranchesParallel (branches:: Vector{T} ) where {T <: PSY.ACTransmission }
7- BranchesParallel (branches, nothing )
22+ return BranchesParallel {T} (branches, nothing )
823end
9- # Constructor for the mixed types
10- function BranchesParallel (branches:: Vector{PSY.ACTransmission} )
11- return BranchesParallel {PSY.ACTransmission} (branches, nothing )
24+
25+ mutable struct MixedBranchesParallel <: AbstractBranchesParallel
26+ branches:: Vector{PSY.ACTransmission}
27+ equivalent_ybus:: Union{Matrix{YBUS_ELTYPE}, Nothing}
28+ end
29+
30+ function MixedBranchesParallel (branches:: Vector{<:PSY.ACTransmission} )
31+ return MixedBranchesParallel (Vector {PSY.ACTransmission} (branches), nothing )
1232end
1333
1434function add_branch! (bp:: BranchesParallel{T} , branch:: T ) where {T <: PSY.ACTransmission }
1535 push! (bp. branches, branch)
1636end
1737
18- get_branch_type (:: BranchesParallel{T} ) where {T <: PSY.ACTransmission } = T
38+ function add_branch! (mbp:: MixedBranchesParallel , branch:: PSY.ACTransmission )
39+ push! (mbp. branches, branch)
40+ end
1941
20- function get_name (bp:: BranchesParallel{T} ) where {T <: PSY.ACTransmission }
42+ function get_name (bp:: AbstractBranchesParallel )
2143 base_string = _longest_starting_substring (PSY. get_name .(bp. branches)... )
2244 if isempty (base_string)
2345 base_string = join (PSY. get_name .(bp. branches), " _" ) * " _"
@@ -44,7 +66,7 @@ function _longest_starting_substring(branch_names...)
4466end
4567
4668function compute_parallel_multiplier (
47- parallel_branch_set:: BranchesParallel ,
69+ parallel_branch_set:: AbstractBranchesParallel ,
4870 branch_name:: String ,
4971)
5072 b_total = 0.0
@@ -58,45 +80,82 @@ function compute_parallel_multiplier(
5880 return b_branch / b_total
5981end
6082
61- function get_series_susceptance (segment:: BranchesParallel )
83+ function get_series_susceptance (segment:: AbstractBranchesParallel )
6284 return sum (get_series_susceptance (branch) for branch in segment. branches)
6385end
6486
65- function get_equivalent_physical_branch_parameters (bp:: BranchesParallel )
87+ function get_equivalent_physical_branch_parameters (bp:: AbstractBranchesParallel )
6688 if isnothing (bp. equivalent_ybus)
6789 populate_equivalent_ybus! (bp)
6890 end
6991 equivalent_ybus = bp. equivalent_ybus
7092 return _get_equivalent_physical_branch_parameters (equivalent_ybus)
7193end
7294
73- function populate_equivalent_ybus! (bp:: BranchesParallel )
95+ function populate_equivalent_ybus! (bp:: AbstractBranchesParallel )
7496 Y11, Y12, Y21, Y22 = ybus_branch_entries (bp)
7597 bp. equivalent_ybus = YBUS_ELTYPE[Y11 Y12; Y21 Y22]
7698 return
7799end
78100
79101"""
80- get_equivalent_rating (bp::BranchesParallel )
102+ get_sum_of_max_rating (bp::AbstractBranchesParallel )
81103
82- Calculate the total rating for branches in parallel.
83- For parallel circuits, the rating is the sum of individual ratings divided by the number of circuits.
84- This provides a conservative estimate that accounts for potential overestimation of total capacity.
104+ Sum of the individual branch ratings, treating each circuit as independently loadable
105+ up to its own thermal limit. This is the least conservative aggregate and assumes
106+ unconstrained flow steering across the parallel group.
107+ """
108+ function get_sum_of_max_rating (bp:: AbstractBranchesParallel )
109+ return sum (get_equivalent_rating (branch) for branch in bp. branches)
110+ end
111+
112+ """
113+ get_single_element_contingency_rating(bp::AbstractBranchesParallel)
114+
115+ N-1 rating for the parallel group: the surviving capacity after the largest-rated
116+ circuit trips, ``\\ sum_i S_i - \\ max_i S_i``. For a group of one branch this is zero.
85117"""
86- function get_equivalent_rating (bp:: BranchesParallel )
87- # Sum of ratings divided by number of circuits
88- return sum (get_equivalent_rating (branch) for branch in bp. branches) /
89- length (bp. branches)
118+ function get_single_element_contingency_rating (bp:: AbstractBranchesParallel )
119+ ratings = get_equivalent_rating .(bp. branches)
120+ return sum (ratings) - maximum (ratings)
90121end
91122
92123"""
93- get_equivalent_emergency_rating(bp::BranchesParallel)
124+ get_impedance_averaged_rating(bp::AbstractBranchesParallel)
125+
126+ Susceptance-weighted average of individual branch ratings,
127+ ``\\ sum_i f_i \\ cdot S_i`` with ``f_i = b_i / \\ sum_k b_k``. Reflects how DC flow
128+ physically splits across a parallel group. Throws `ArgumentError` if the total
129+ series susceptance is zero or non-finite.
130+ """
131+ function get_impedance_averaged_rating (bp:: AbstractBranchesParallel )
132+ b_total = sum (PSY. get_series_susceptance, bp. branches)
133+ if ! isfinite (b_total) || iszero (b_total)
134+ throw (
135+ ArgumentError (
136+ " Cannot compute impedance-averaged rating: total series susceptance across the parallel group must be finite and non-zero." ,
137+ ),
138+ )
139+ end
140+ return sum (
141+ PSY. get_series_susceptance (br) / b_total * get_equivalent_rating (br)
142+ for br in bp. branches
143+ )
144+ end
145+
146+ # Series-chain rating contribution for a parallel block: dispatch arm for
147+ # `get_equivalent_rating(::BranchesSeries)` defined in BranchesSeries.jl.
148+ _series_member_rating (bp:: AbstractBranchesParallel ) =
149+ get_single_element_contingency_rating (bp)
150+
151+ """
152+ get_equivalent_emergency_rating(bp::AbstractBranchesParallel)
94153
95154Calculate the total emergency rating for branches in parallel.
96155For parallel circuits, the emergency rating is the sum of individual emergency ratings divided by the number of circuits.
97156This provides a conservative estimate that accounts for potential overestimation of total capacity.
98157"""
99- function get_equivalent_emergency_rating (bp:: BranchesParallel )
158+ function get_equivalent_emergency_rating (bp:: AbstractBranchesParallel )
100159 equivalent_rating = 0.0
101160 for branch in bp. branches
102161 rating_b = get_equivalent_emergency_rating (branch)
@@ -106,36 +165,38 @@ function get_equivalent_emergency_rating(bp::BranchesParallel)
106165end
107166
108167"""
109- get_equivalent_available(bp::BranchesParallel )
168+ get_equivalent_available(bp::AbstractBranchesParallel )
110169
111170Get the availability status for parallel branches.
112171All branches in parallel must be available for the parallel circuit to be available.
113172"""
114- function get_equivalent_available (bp:: BranchesParallel )
173+ function get_equivalent_available (bp:: AbstractBranchesParallel )
115174 # All branches must be available
116175 return all (PSY. get_available (branch) for branch in bp. branches)
117176end
118177
178+ PSY. get_available (bp:: AbstractBranchesParallel ) = get_equivalent_available (bp)
179+
119180"""
120- get_equivalent_α(bp::BranchesParallel )
181+ get_equivalent_α(bp::AbstractBranchesParallel )
121182
122183Get the phase angle shift for parallel branches.
123184Returns the average phase angle shift across all parallel branches.
124185Returns 0.0 if branches don't support phase angle shift (e.g., lines).
125186"""
126- function get_equivalent_α (bp:: BranchesParallel )
187+ function get_equivalent_α (bp:: AbstractBranchesParallel )
127188 # Need to check the PS books
128189end
129190
130- function Base. iterate (bp:: BranchesParallel )
191+ function Base. iterate (bp:: AbstractBranchesParallel )
131192 return iterate (bp. branches)
132193end
133194
134- function Base. iterate (bp:: BranchesParallel , state)
195+ function Base. iterate (bp:: AbstractBranchesParallel , state)
135196 return iterate (bp. branches, state)
136197end
137198
138- function Base. length (bp:: BranchesParallel )
199+ function Base. length (bp:: AbstractBranchesParallel )
139200 return length (bp. branches)
140201end
141202
@@ -144,33 +205,32 @@ function add_to_map(
144205 filters:: Dict ,
145206) where {T <: PSY.ACTransmission }
146207 isempty (filters) && return true
147- if isabstracttype (T)
148- @warn " Parallel circuit contains mixed branch types, filters might be applied to more components than intended. Use Logging.Debug for additional information."
149- @debug " Parallel circuit branch types: $(typeof .(double_circuit. branches)) "
150- @debug " Parallel circuit branch names: $(PSY. get_name .(double_circuit. branches)) "
151- for branch in double_circuit. branches
152- filter = get (filters, typeof (branch), x -> true )
153- if ! filter (branch)
154- return false
155- end
156- end
208+ if ! haskey (filters, T)
157209 return true
158210 end
211+ return any (filters[T](device) for device in double_circuit)
212+ end
159213
160- if ! haskey (filters, T)
161- return true
162- else
163- return any ([filters[T](device) for device in double_circuit])
214+ function add_to_map (double_circuit:: MixedBranchesParallel , filters:: Dict )
215+ isempty (filters) && return true
216+ @warn " Parallel circuit contains mixed branch types, filters might be applied to more components than intended. Use Logging.Debug for additional information."
217+ @debug " Parallel circuit branch types: $(typeof .(double_circuit. branches)) "
218+ @debug " Parallel circuit branch names: $(PSY. get_name .(double_circuit. branches)) "
219+ for branch in double_circuit. branches
220+ filter = get (filters, typeof (branch), x -> true )
221+ if ! filter (branch)
222+ return false
223+ end
164224 end
165- error ( " Invalid condition reached in add_to_map for BranchesParallel " )
225+ return true
166226end
167227
168- function Base.:(== )(a:: BranchesParallel , b:: BranchesParallel )
228+ function Base.:(== )(a:: AbstractBranchesParallel , b:: AbstractBranchesParallel )
169229 return a. branches == b. branches
170230end
171231
172- function Base. show (io:: IO , x:: MIME{Symbol("text/plain")} , y:: BranchesParallel )
232+ function Base. show (io:: IO , x:: MIME{Symbol("text/plain")} , y:: AbstractBranchesParallel )
173233 show (io, x, y. branches)
174234end
175235
176- is_a_reduction (:: BranchesParallel ) = true
236+ is_a_reduction (:: AbstractBranchesParallel ) = true
0 commit comments