Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a8fac4a
fix: Enhance get_equivalent_rating function with keyword arguments fo…
jarry7 May 6, 2026
5285f0c
test: Enhance equivalent rating tests for BranchesParallel with addit…
jarry7 May 6, 2026
4edce49
test: verify SSH commit signing
jarry7 May 6, 2026
dcb9357
test: verify SSH commit signing 2
jarry7 May 6, 2026
2b22524
Update test/test_equivalent_getters.jl
jarry7 May 6, 2026
3e9f289
doc: clarify weighting explanation in equivalent rating calculation (…
jarry7 May 7, 2026
5ccdba1
feat: add error handling for empty parallel branch group in equivalen…
jarry7 May 7, 2026
4af223f
feat: enhance equivalent rating calculation with finite checks for ad…
jarry7 May 7, 2026
fef3297
feat: add error handling for empty parallel branch group in get_equiv…
jarry7 May 7, 2026
871c0ff
formatting
jarry7 May 7, 2026
8d62878
formatting
jarry7 May 7, 2026
b51595e
formatting
jarry7 May 7, 2026
bfb0e0e
formatting
jarry7 May 7, 2026
87328ac
formatting
jarry7 May 7, 2026
c2ca2ba
formatting
jarry7 May 7, 2026
429a1c1
Update comment for line1 => line2
jarry7 May 7, 2026
a590907
Additional tests for empty bp instances
jarry7 May 7, 2026
7459fa0
Revert silly tests suggested by copilot
jarry7 May 7, 2026
6a8a607
Add validation for non-empty branches in BranchesParallel constructor…
jarry7 May 7, 2026
c8fad15
Add rating aggregation and flow weighting methods for parallel branch…
jarry7 May 7, 2026
470432a
Add exports for rating methods and equivalent rating functions (to su…
jarry7 May 7, 2026
2b9cc07
Refactor get_equivalent_rating function to simplify signature (use de…
jarry7 May 7, 2026
bb4c771
Refactor equivalent rating tests for consistency in method usage
jarry7 May 7, 2026
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
113 changes: 109 additions & 4 deletions src/BranchesParallel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ mutable struct BranchesParallel{T <: PSY.ACTransmission} <: PSY.ACTransmission
end

function BranchesParallel(branches::Vector{T}) where {T <: PSY.ACTransmission}
isempty(branches) &&
throw(ArgumentError("BranchesParallel requires at least one branch."))
BranchesParallel(branches, nothing)
end
# Constructor for the mixed types
function BranchesParallel(branches::Vector{PSY.ACTransmission})
isempty(branches) &&
throw(ArgumentError("BranchesParallel requires at least one branch."))
return BranchesParallel{PSY.ACTransmission}(branches, nothing)
end

Expand Down Expand Up @@ -79,16 +83,117 @@ end
"""
get_equivalent_rating(bp::BranchesParallel)

Calculate the total rating for branches in parallel.
For parallel circuits, the rating is the sum of individual ratings divided by the number of circuits.
This provides a conservative estimate that accounts for potential overestimation of total capacity.
Calculate the equivalent rating for a group of parallel branches using the default strategy:
sum with admittance-weighted flow distribution.

Equivalent to `get_equivalent_rating(bp, SumRating(), AdmittanceWeighted())`.

See also:
[`get_equivalent_rating(::BranchesParallel, ::SumRating, ::AdmittanceWeighted)`](@ref),
[`get_equivalent_rating(::BranchesParallel, ::AverageRating, ::AdmittanceWeighted)`](@ref),
[`get_equivalent_rating(::BranchesParallel, ::SumRating, ::ArithmeticWeighting)`](@ref),
[`get_equivalent_rating(::BranchesParallel, ::AverageRating, ::ArithmeticWeighting)`](@ref).
"""
function get_equivalent_rating(bp::BranchesParallel)
# Sum of ratings divided by number of circuits
return get_equivalent_rating(bp, SumRating(), AdmittanceWeighted())
end

"""
get_equivalent_rating(bp::BranchesParallel, ::SumRating, ::AdmittanceWeighted)

Calculate the equivalent rating using the sum with admittance-weighted flow distribution.

Each branch carries a fraction of total flow proportional to its series susceptance
``f_i = b_i / \\sum_k b_k``. The total capacity is limited by the first branch
to reach its thermal limit:

``S_{\\max} = \\min_i \\left( \\frac{S_{\\text{limit},i}}{f_i} \\right)``
"""
function get_equivalent_rating(
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.

Why is the dispatch on a symbol and not different methods. This approach break encapsulation

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Completely revised approach.

Commits:

bp::BranchesParallel,
::SumRating,
::AdmittanceWeighted,
)
multipliers = _admittance_multipliers(bp)
if any(iszero, values(multipliers)) || any(!isfinite, values(multipliers))
throw(
ArgumentError(
"Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.",
),
)
end
# Total interface capacity limited by the first branch to reach its thermal limit.
return minimum(
get_equivalent_rating(br) / multipliers[PSY.get_name(br)] for br in bp.branches
)
end

"""
get_equivalent_rating(bp::BranchesParallel, ::AverageRating, ::AdmittanceWeighted)

Calculate the susceptance-weighted average of individual branch ratings.

Each branch carries a fraction of total flow proportional to its series susceptance
``f_i = b_i / \\sum_k b_k``. Returns ``\\sum_i f_i \\cdot S_{\\text{limit},i}``.
"""
function get_equivalent_rating(
bp::BranchesParallel,
::AverageRating,
::AdmittanceWeighted,
)
multipliers = _admittance_multipliers(bp)
if any(!isfinite, values(multipliers))
throw(
ArgumentError(
"Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.",
),
)
end
# Susceptance-weighted average of individual ratings.
return sum(
multipliers[PSY.get_name(br)] * get_equivalent_rating(br) for br in bp.branches
)
end

"""
get_equivalent_rating(bp::BranchesParallel, ::SumRating, ::ArithmeticWeighting)

Calculate the equivalent rating as the simple sum of individual branch ratings.

All branches are treated as carrying equal fractions of total flow.
"""
function get_equivalent_rating(
bp::BranchesParallel,
::SumRating,
::ArithmeticWeighting,
)
return sum(get_equivalent_rating(branch) for branch in bp.branches)
end

"""
get_equivalent_rating(bp::BranchesParallel, ::AverageRating, ::ArithmeticWeighting)

Calculate the equivalent rating as the arithmetic mean of individual branch ratings.

All branches are treated as carrying equal fractions of total flow.
"""
function get_equivalent_rating(
bp::BranchesParallel,
::AverageRating,
::ArithmeticWeighting,
)
return sum(get_equivalent_rating(branch) for branch in bp.branches) /
length(bp.branches)
end

# Internal helper: compute per-branch admittance multipliers for a parallel group.
function _admittance_multipliers(bp::BranchesParallel)
return Dict(
PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br))
for br in bp.branches
)
end

"""
get_equivalent_emergency_rating(bp::BranchesParallel)

Expand Down
8 changes: 8 additions & 0 deletions src/PowerNetworkMatrices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export DC_vPTDF_Matrix
export DC_BA_Matrix
export AC_Ybus_Matrix
export YBUS_ELTYPE
export BranchesParallel
export RatingMethod
export SumRating
export AverageRating
export RatingWeighting
export AdmittanceWeighted
export ArithmeticWeighting
export get_equivalent_rating

export apply_woodbury_correction
export clear_all_caches!
Expand Down
46 changes: 46 additions & 0 deletions src/definitions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,49 @@ const SUPPORTED_LINEAR_SOLVERS = ("KLU", "MKLPardiso", "AppleAccelerate", "Dense
s == "AppleAccelerate" && return AppleAccelerateSolver()
error("Unsupported linear solver: $s. Supported: $SUPPORTED_LINEAR_SOLVERS")
end

"""
Abstract supertype for rating aggregation strategies applied to groups of parallel branches.

See also: [`SumRating`](@ref), [`AverageRating`](@ref).
"""
abstract type RatingMethod end

"""
SumRating()

Rating aggregation strategy for parallel branches: returns the sum
of individual branch capacities.
"""
struct SumRating <: RatingMethod end

"""
AverageRating()

Rating aggregation strategy for parallel branches: returns the arithmetic mean
of individual branch ratings.
"""
struct AverageRating <: RatingMethod end

"""
Abstract supertype for flow weighting schemes applied to groups of parallel branches.

See also: [`AdmittanceWeighted`](@ref), [`ArithmeticWeighting`](@ref).
"""
abstract type RatingWeighting end

"""
AdmittanceWeighted()

Flow weighting scheme for parallel branches: each branch carries a fraction of total potential
flow proportional to series susceptance, reflecting physical behaviour of parallel circuits.
"""
struct AdmittanceWeighted <: RatingWeighting end

"""
ArithmeticWeighting()

Flow weighting scheme for parallel branches: each branch is treated as carrying an equal
fraction of total potential flow (uniform weighting).
"""
struct ArithmeticWeighting <: RatingWeighting end
32 changes: 26 additions & 6 deletions test/test_equivalent_getters.jl
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,30 @@
)
# Create BranchesParallel
bp = PNM.BranchesParallel([line1, line2])
# Test get_rating: Rating = (Rating1 + Rating2) / n = (100.0 + 150.0) / 2 = 125.0
rating_eq = PNM.get_equivalent_rating(bp)
@test rating_eq ≈ 125.0 atol = 1e-6

# Default (SumRating + AdmittanceWeighted)
# b1 = x1/(r1²+x1²) = 0.2/0.05 = 4.0, b2 = 0.4/0.20 = 2.0, b_total = 6.0
# f1 = 2/3, f2 = 1/3
# min(100/(2/3), 150/(1/3)) = min(150, 450) = 150.0
@test PNM.get_equivalent_rating(
bp,
) ≈ 150.0 atol = 1e-6

# AverageRating + AdmittanceWeighted
# (2/3)*100 + (1/3)*150 = 350/3 ≈ 116.667
@test PNM.get_equivalent_rating(
bp, PNM.AverageRating(), PNM.AdmittanceWeighted(),
) ≈ 350.0 / 3.0 atol = 1e-6

# SumRating + ArithmeticWeighting
@test PNM.get_equivalent_rating(
bp, PNM.SumRating(), PNM.ArithmeticWeighting(),
) ≈ 250.0 atol = 1e-6

# AverageRating + ArithmeticWeighting
@test PNM.get_equivalent_rating(
bp, PNM.AverageRating(), PNM.ArithmeticWeighting(),
) ≈ 125.0 atol = 1e-6

Comment thread
jarry7 marked this conversation as resolved.
emergency_rating_eq = PNM.get_equivalent_emergency_rating(bp)
@test emergency_rating_eq ≈ 250.0 atol = 1e-6
Expand All @@ -53,13 +74,12 @@
emergency_rating_eq = PNM.get_equivalent_emergency_rating(bs)
@test emergency_rating_eq ≈ 100.0 atol = 1e-6

#Add test parrallel circuit + line1
# Add test parallel circuit + line2
bs = PNM.BranchesSeries()
PNM.add_branch!(bs, bp, :FromTo)
PNM.add_branch!(bs, line2, :FromTo)
# Test get_rating: Rating = minimum rating for series branches (weakest link)
rating_eq = PNM.get_equivalent_rating(bs)
@test rating_eq ≈ 125.0 atol = 1e-6
@test rating_eq ≈ 150.0 atol = 1e-6

emergency_rating_eq = PNM.get_equivalent_emergency_rating(bs)
@test emergency_rating_eq ≈ 150.0 atol = 1e-6
Expand Down
Loading