From a8fac4a09eb98d0dcfc5ebab4cf28e90c369acd2 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 16:45:18 -0600 Subject: [PATCH 01/23] fix: Enhance get_equivalent_rating function with keyword arguments for method and weighting options (default: method = :sum, weighting = :admittance_weighted) --- src/BranchesParallel.jl | 93 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 8 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 342a12c5c..dd9d316e9 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -77,16 +77,93 @@ function populate_equivalent_ybus!(bp::BranchesParallel) end """ - get_equivalent_rating(bp::BranchesParallel) + get_equivalent_rating( + bp::BranchesParallel; + method::Symbol = :sum, + weighting::Symbol = :admittance_weighted + ) -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. + +Two orthogonal keyword arguments control the calculation: + +## `weighting` — how flow is distributed across parallel branches + +- `:admittance_weighted` (default): Each branch carries a fraction of the total flow + proportional to its series susceptance ``f_i = b_i / \\sum_k b_k`` + (see [`compute_parallel_multiplier`](@ref)). Reflects actual DC power-flow physics. + +- `:arithmetic`: All branches are treated as carrying equal fractions of total flow + (uniform weighting). + +## `method` — how individual branch limits are aggregated + +- `:sum` (default): For `:admittance_weighted`, returns the total interface capacity + limited by the first branch to reach its thermal limit (bottleneck formula): + + ``S_{\\max} = \\min_i \\left( \\frac{S_{\\text{limit},i}}{f_i} \\right)`` + + For `:arithmetic`, returns the simple sum of individual ratings. + +- `:average`: For `:admittance_weighted`, returns the susceptance-weighted average + of individual ratings ``\\sum_i f_i \\cdot S_{\\text{limit},i}``. + For `:arithmetic`, returns the arithmetic mean of individual ratings. + +# Arguments +- `bp::BranchesParallel`: The parallel branch group. + +# Keywords +- `method::Symbol = :sum`: Aggregation method. Valid values: `:sum`, `:average`. +- `weighting::Symbol = :admittance_weighted`: Flow weighting scheme. Valid values: + `:admittance_weighted`, `:arithmetic`. """ -function get_equivalent_rating(bp::BranchesParallel) - # Sum of ratings divided by number of circuits - return sum(get_equivalent_rating(branch) for branch in bp.branches) / - length(bp.branches) +function get_equivalent_rating( + bp::BranchesParallel; + method::Symbol = :sum, + weighting::Symbol = :admittance_weighted, +) + if weighting === :admittance_weighted + if method === :sum + # Total interface capacity limited by the first branch to reach its thermal limit. + return minimum( + get_equivalent_rating(br) / + compute_parallel_multiplier(bp, PSY.get_name(br)) + for br in bp.branches + ) + elseif method === :average + # Susceptance-weighted average of individual ratings. + return sum( + compute_parallel_multiplier(bp, PSY.get_name(br)) * + get_equivalent_rating(br) + for br in bp.branches + ) + else + throw( + ArgumentError( + "Unknown method: $(method). Valid options are :sum, :average.", + ), + ) + end + elseif weighting === :arithmetic + if method === :sum + return sum(get_equivalent_rating(branch) for branch in bp.branches) + elseif method === :average + return sum(get_equivalent_rating(branch) for branch in bp.branches) / + length(bp.branches) + else + throw( + ArgumentError( + "Unknown method: $(method). Valid options are :sum, :average.", + ), + ) + end + else + throw( + ArgumentError( + "Unknown weighting: $(weighting). Valid options are :admittance_weighted, :arithmetic.", + ), + ) + end end """ From 5285f0cf798741d931450cffa9f4f370e91abd1b Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 16:45:37 -0600 Subject: [PATCH 02/23] test: Enhance equivalent rating tests for BranchesParallel with additional methods and error handling --- test/test_equivalent_getters.jl | 31 +++++++++++++++++++++++++------ 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index a2f615772..0f2bbaa77 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -36,9 +36,29 @@ ) # 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 (method=:sum, weighting=:admittance_weighted) + # 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 + + # method=:average, weighting=:admittance_weighted: susceptance-weighted average + # (2/3)*100 + (1/3)*150 = 350/3 ≈ 116.667 + @test PNM.get_equivalent_rating(bp; method = :average, weighting = :admittance_weighted) ≈ + 350.0 / 3.0 atol = 1e-6 + + # method=:sum, weighting=:arithmetic: simple sum + @test PNM.get_equivalent_rating(bp; method = :sum, weighting = :arithmetic) ≈ 250.0 atol = + 1e-6 + + # method=:average, weighting=:arithmetic: arithmetic mean + @test PNM.get_equivalent_rating(bp; method = :average, weighting = :arithmetic) ≈ 125.0 atol = + 1e-6 + + # invalid kwarg values → ArgumentError + @test_throws ArgumentError PNM.get_equivalent_rating(bp; method = :invalid) + @test_throws ArgumentError PNM.get_equivalent_rating(bp; weighting = :invalid) emergency_rating_eq = PNM.get_equivalent_emergency_rating(bp) @test emergency_rating_eq ≈ 250.0 atol = 1e-6 @@ -53,13 +73,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 + line1 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 From 4edce49353a3de2eec57b610651398b62439e0a3 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 17:37:59 -0600 Subject: [PATCH 03/23] test: verify SSH commit signing From dcb935789dda49602d258e60ba8d66b396f50b3e Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 17:38:57 -0600 Subject: [PATCH 04/23] test: verify SSH commit signing 2 From 2b225246e4197b8af098041954d2fd89152418e3 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 17:41:20 -0600 Subject: [PATCH 05/23] Update test/test_equivalent_getters.jl Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- test/test_equivalent_getters.jl | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index 0f2bbaa77..ea960d293 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -45,7 +45,11 @@ # method=:average, weighting=:admittance_weighted: susceptance-weighted average # (2/3)*100 + (1/3)*150 = 350/3 ≈ 116.667 - @test PNM.get_equivalent_rating(bp; method = :average, weighting = :admittance_weighted) ≈ + @test PNM.get_equivalent_rating( + bp; + method = :average, + weighting = :admittance_weighted, + ) ≈ 350.0 / 3.0 atol = 1e-6 # method=:sum, weighting=:arithmetic: simple sum From 3e9f289e234cce0ceda6371cb8a0db85d1ccb278 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:15:25 -0600 Subject: [PATCH 06/23] doc: clarify weighting explanation in equivalent rating calculation (remove unintended broken cross-reference) --- src/BranchesParallel.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index dd9d316e9..bc5d32550 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -90,8 +90,10 @@ Two orthogonal keyword arguments control the calculation: ## `weighting` — how flow is distributed across parallel branches - `:admittance_weighted` (default): Each branch carries a fraction of the total flow - proportional to its series susceptance ``f_i = b_i / \\sum_k b_k`` - (see [`compute_parallel_multiplier`](@ref)). Reflects actual DC power-flow physics. + proportional to its series susceptance ``f_i = b_i / \\sum_k b_k``, + where ``b_i`` is the series susceptance of branch i. + This reflects the physical behavior of parallel circuits, where flow + distributes according to branch admittances. - `:arithmetic`: All branches are treated as carrying equal fractions of total flow (uniform weighting). From 5ccdba1f3e700111b8ed5f85e6403f5cf49a7d5c Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:16:00 -0600 Subject: [PATCH 07/23] feat: add error handling for empty parallel branch group in equivalent rating calculation --- src/BranchesParallel.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index bc5d32550..20ca31d84 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -124,6 +124,14 @@ function get_equivalent_rating( method::Symbol = :sum, weighting::Symbol = :admittance_weighted, ) + if isempty(bp.branches) + throw( + ArgumentError( + "Cannot compute equivalent rating for an empty parallel branch group.", + ), + ) + end + if weighting === :admittance_weighted if method === :sum # Total interface capacity limited by the first branch to reach its thermal limit. From 4af223f2de5e65a898eea5c0aa98fad3c37ace69 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:16:42 -0600 Subject: [PATCH 08/23] feat: enhance equivalent rating calculation with finite checks for admittance-weighted method and pre-calculate multipliers for re-use --- src/BranchesParallel.jl | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 20ca31d84..bfbab639f 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -133,17 +133,38 @@ function get_equivalent_rating( end if weighting === :admittance_weighted + + multipliers = Dict( + PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for + br in bp.branches + ) + + if any(!isfinite(multiplier) for multiplier in values(multipliers)) + throw( + ArgumentError( + "Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.", + ), + ) + end + if method === :sum + if any(iszero(multiplier) for multiplier in values(multipliers)) + throw( + ArgumentError( + "Cannot compute admittance-weighted equivalent rating with method :sum: 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) / - compute_parallel_multiplier(bp, PSY.get_name(br)) - for br in bp.branches + get_equivalent_rating(br) / + multipliers[PSY.get_name(br)] for br in bp.branches ) elseif method === :average # Susceptance-weighted average of individual ratings. return sum( - compute_parallel_multiplier(bp, PSY.get_name(br)) * + multipliers[PSY.get_name(br)] * get_equivalent_rating(br) for br in bp.branches ) From fef329720dc25cbf70430d33710da920b9f05ef7 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:17:26 -0600 Subject: [PATCH 09/23] feat: add error handling for empty parallel branch group in get_equivalent_emergency_rating and get_equivalent_available --- src/BranchesParallel.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index bfbab639f..4ba76c5e0 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -205,6 +205,13 @@ For parallel circuits, the emergency rating is the sum of individual emergency r This provides a conservative estimate that accounts for potential overestimation of total capacity. """ function get_equivalent_emergency_rating(bp::BranchesParallel) + if isempty(bp.branches) + throw( + ArgumentError( + "Cannot compute equivalent emergency rating for an empty parallel branch group.", + ), + ) + end equivalent_rating = 0.0 for branch in bp.branches rating_b = get_equivalent_emergency_rating(branch) @@ -220,6 +227,13 @@ Get the availability status for parallel branches. All branches in parallel must be available for the parallel circuit to be available. """ function get_equivalent_available(bp::BranchesParallel) + if isempty(bp.branches) + throw( + ArgumentError( + "Cannot compute equivalent availability for an empty parallel branch group.", + ), + ) + end # All branches must be available return all(PSY.get_available(branch) for branch in bp.branches) end From 871c0ff212a2605f7425912ed9354c4be618d628 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:20:32 -0600 Subject: [PATCH 10/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 4ba76c5e0..6638c434f 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -124,15 +124,13 @@ function get_equivalent_rating( method::Symbol = :sum, weighting::Symbol = :admittance_weighted, ) - if isempty(bp.branches) - throw( - ArgumentError( - "Cannot compute equivalent rating for an empty parallel branch group.", - ), - ) - end - - if weighting === :admittance_weighted + if isempty(bp.branches) + throw( + ArgumentError( + "Cannot compute equivalent rating for an empty parallel branch group.", + ), + ) + end multipliers = Dict( PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for From 8d628782a80fa89474579bf3cba8ac5d66a9db2e Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:22:03 -0600 Subject: [PATCH 11/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 6638c434f..3e34c3b97 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -132,6 +132,7 @@ function get_equivalent_rating( ) end + if weighting === :admittance_weighted multipliers = Dict( PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for br in bp.branches From b51595ee9eebcd7f01256b2a94ee4d446229db70 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:22:21 -0600 Subject: [PATCH 12/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 3e34c3b97..2900a4426 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -134,8 +134,8 @@ function get_equivalent_rating( if weighting === :admittance_weighted multipliers = Dict( - PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for - br in bp.branches + PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for + br in bp.branches ) if any(!isfinite(multiplier) for multiplier in values(multipliers)) From bfb0e0e2400ad6d89897a04ab4ef0f600a4c58f1 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:22:41 -0600 Subject: [PATCH 13/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 2900a4426..5166425ad 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -138,13 +138,13 @@ function get_equivalent_rating( br in bp.branches ) - if any(!isfinite(multiplier) for multiplier in values(multipliers)) - throw( - ArgumentError( - "Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.", - ), - ) - end + if any(!isfinite(multiplier) for multiplier in values(multipliers)) + throw( + ArgumentError( + "Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.", + ), + ) + end if method === :sum if any(iszero(multiplier) for multiplier in values(multipliers)) From 87328acc8dcdae435b4fcdaf5ff23770f19fa689 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:23:24 -0600 Subject: [PATCH 14/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 5166425ad..1d25c2faa 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -147,13 +147,13 @@ function get_equivalent_rating( end if method === :sum - if any(iszero(multiplier) for multiplier in values(multipliers)) - throw( - ArgumentError( - "Cannot compute admittance-weighted equivalent rating with method :sum: total series susceptance across the parallel group must be finite and non-zero.", - ), - ) - end + if any(iszero(multiplier) for multiplier in values(multipliers)) + throw( + ArgumentError( + "Cannot compute admittance-weighted equivalent rating with method :sum: 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( From c2ca2ba86745caba1e1630e19125484062eca752 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:23:51 -0600 Subject: [PATCH 15/23] formatting Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- src/BranchesParallel.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 1d25c2faa..326e0689f 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -157,7 +157,7 @@ function get_equivalent_rating( # Total interface capacity limited by the first branch to reach its thermal limit. return minimum( - get_equivalent_rating(br) / + get_equivalent_rating(br) / multipliers[PSY.get_name(br)] for br in bp.branches ) elseif method === :average From 429a1c19875158c2ed10b2b9d0eef5cb09635774 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:26:56 -0600 Subject: [PATCH 16/23] Update comment for line1 => line2 Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- test/test_equivalent_getters.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index ea960d293..557d7004d 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -77,7 +77,7 @@ emergency_rating_eq = PNM.get_equivalent_emergency_rating(bs) @test emergency_rating_eq ≈ 100.0 atol = 1e-6 - #Add test parallel circuit + line1 + # Add test parallel circuit + line2 bs = PNM.BranchesSeries() PNM.add_branch!(bs, bp, :FromTo) PNM.add_branch!(bs, line2, :FromTo) From a590907930b1685e62063dd7455cc5d0ae758398 Mon Sep 17 00:00:00 2001 From: Jarrad Wright <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:28:07 -0600 Subject: [PATCH 17/23] Additional tests for empty bp instances Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- test/test_equivalent_getters.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index 557d7004d..faf3031b7 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -64,6 +64,11 @@ @test_throws ArgumentError PNM.get_equivalent_rating(bp; method = :invalid) @test_throws ArgumentError PNM.get_equivalent_rating(bp; weighting = :invalid) + empty_bp = PNM.BranchesParallel() + @test_throws ArgumentError PNM.get_equivalent_rating(empty_bp) + @test_throws ArgumentError PNM.get_equivalent_emergency_rating(empty_bp) + @test_throws ArgumentError PSY.get_available(empty_bp) + emergency_rating_eq = PNM.get_equivalent_emergency_rating(bp) @test emergency_rating_eq ≈ 250.0 atol = 1e-6 From 7459fa0c017056a95a3b54c6a4903ee15ebead6e Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Wed, 6 May 2026 18:46:31 -0600 Subject: [PATCH 18/23] Revert silly tests suggested by copilot --- test/test_equivalent_getters.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index faf3031b7..557d7004d 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -64,11 +64,6 @@ @test_throws ArgumentError PNM.get_equivalent_rating(bp; method = :invalid) @test_throws ArgumentError PNM.get_equivalent_rating(bp; weighting = :invalid) - empty_bp = PNM.BranchesParallel() - @test_throws ArgumentError PNM.get_equivalent_rating(empty_bp) - @test_throws ArgumentError PNM.get_equivalent_emergency_rating(empty_bp) - @test_throws ArgumentError PSY.get_available(empty_bp) - emergency_rating_eq = PNM.get_equivalent_emergency_rating(bp) @test emergency_rating_eq ≈ 250.0 atol = 1e-6 From 6a8a607583e22114ffeada2c6c23f66c6e378d16 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Thu, 7 May 2026 12:22:23 -0600 Subject: [PATCH 19/23] Add validation for non-empty branches in BranchesParallel constructors and methods (remove guards) --- src/BranchesParallel.jl | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 326e0689f..70e61482a 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -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 @@ -127,7 +131,7 @@ function get_equivalent_rating( if isempty(bp.branches) throw( ArgumentError( - "Cannot compute equivalent rating for an empty parallel branch group.", + "Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.", ), ) end @@ -204,13 +208,6 @@ For parallel circuits, the emergency rating is the sum of individual emergency r This provides a conservative estimate that accounts for potential overestimation of total capacity. """ function get_equivalent_emergency_rating(bp::BranchesParallel) - if isempty(bp.branches) - throw( - ArgumentError( - "Cannot compute equivalent emergency rating for an empty parallel branch group.", - ), - ) - end equivalent_rating = 0.0 for branch in bp.branches rating_b = get_equivalent_emergency_rating(branch) @@ -226,13 +223,6 @@ Get the availability status for parallel branches. All branches in parallel must be available for the parallel circuit to be available. """ function get_equivalent_available(bp::BranchesParallel) - if isempty(bp.branches) - throw( - ArgumentError( - "Cannot compute equivalent availability for an empty parallel branch group.", - ), - ) - end # All branches must be available return all(PSY.get_available(branch) for branch in bp.branches) end From c8fad150e4b71a1fb722a5d9801f1b77afae8a7d Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Thu, 7 May 2026 12:23:29 -0600 Subject: [PATCH 20/23] Add rating aggregation and flow weighting methods for parallel branches (to avoid breaking encapsulation) --- src/definitions.jl | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/definitions.jl b/src/definitions.jl index 7b1bc991a..b51720f28 100644 --- a/src/definitions.jl +++ b/src/definitions.jl @@ -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 From 470432a54506128e434cc4bec1316db86c1dadc3 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Thu, 7 May 2026 12:24:56 -0600 Subject: [PATCH 21/23] Add exports for rating methods and equivalent rating functions (to support docs) --- src/PowerNetworkMatrices.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/PowerNetworkMatrices.jl b/src/PowerNetworkMatrices.jl index 13bc15e94..5474c95eb 100644 --- a/src/PowerNetworkMatrices.jl +++ b/src/PowerNetworkMatrices.jl @@ -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! From 2b9cc07b4ce0730c1e2012b442a4045a9689cc81 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Thu, 7 May 2026 12:39:10 -0600 Subject: [PATCH 22/23] Refactor get_equivalent_rating function to simplify signature (use definitions vs Symbols) and improve documentation --- src/BranchesParallel.jl | 180 +++++++++++++++++++--------------------- 1 file changed, 87 insertions(+), 93 deletions(-) diff --git a/src/BranchesParallel.jl b/src/BranchesParallel.jl index 70e61482a..9086f773a 100644 --- a/src/BranchesParallel.jl +++ b/src/BranchesParallel.jl @@ -81,123 +81,117 @@ function populate_equivalent_ybus!(bp::BranchesParallel) end """ - get_equivalent_rating( - bp::BranchesParallel; - method::Symbol = :sum, - weighting::Symbol = :admittance_weighted - ) - -Calculate the equivalent rating for a group of parallel branches. - -Two orthogonal keyword arguments control the calculation: - -## `weighting` — how flow is distributed across parallel branches - -- `:admittance_weighted` (default): Each branch carries a fraction of the total flow - proportional to its series susceptance ``f_i = b_i / \\sum_k b_k``, - where ``b_i`` is the series susceptance of branch i. - This reflects the physical behavior of parallel circuits, where flow - distributes according to branch admittances. + get_equivalent_rating(bp::BranchesParallel) -- `:arithmetic`: All branches are treated as carrying equal fractions of total flow - (uniform weighting). +Calculate the equivalent rating for a group of parallel branches using the default strategy: +sum with admittance-weighted flow distribution. -## `method` — how individual branch limits are aggregated +Equivalent to `get_equivalent_rating(bp, SumRating(), AdmittanceWeighted())`. -- `:sum` (default): For `:admittance_weighted`, returns the total interface capacity - limited by the first branch to reach its thermal limit (bottleneck formula): - - ``S_{\\max} = \\min_i \\left( \\frac{S_{\\text{limit},i}}{f_i} \\right)`` +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) + return get_equivalent_rating(bp, SumRating(), AdmittanceWeighted()) +end - For `:arithmetic`, returns the simple sum of individual ratings. +""" + get_equivalent_rating(bp::BranchesParallel, ::SumRating, ::AdmittanceWeighted) -- `:average`: For `:admittance_weighted`, returns the susceptance-weighted average - of individual ratings ``\\sum_i f_i \\cdot S_{\\text{limit},i}``. - For `:arithmetic`, returns the arithmetic mean of individual ratings. +Calculate the equivalent rating using the sum with admittance-weighted flow distribution. -# Arguments -- `bp::BranchesParallel`: The parallel branch group. +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: -# Keywords -- `method::Symbol = :sum`: Aggregation method. Valid values: `:sum`, `:average`. -- `weighting::Symbol = :admittance_weighted`: Flow weighting scheme. Valid values: - `:admittance_weighted`, `:arithmetic`. +``S_{\\max} = \\min_i \\left( \\frac{S_{\\text{limit},i}}{f_i} \\right)`` """ function get_equivalent_rating( - bp::BranchesParallel; - method::Symbol = :sum, - weighting::Symbol = :admittance_weighted, + bp::BranchesParallel, + ::SumRating, + ::AdmittanceWeighted, ) - if isempty(bp.branches) + 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 - if weighting === :admittance_weighted - multipliers = Dict( - PSY.get_name(br) => compute_parallel_multiplier(bp, PSY.get_name(br)) for - br in bp.branches - ) - - if any(!isfinite(multiplier) for multiplier in values(multipliers)) - throw( - ArgumentError( - "Cannot compute admittance-weighted equivalent rating: total series susceptance across the parallel group must be finite and non-zero.", - ), - ) - end +""" + get_equivalent_rating(bp::BranchesParallel, ::AverageRating, ::AdmittanceWeighted) - if method === :sum - if any(iszero(multiplier) for multiplier in values(multipliers)) - throw( - ArgumentError( - "Cannot compute admittance-weighted equivalent rating with method :sum: total series susceptance across the parallel group must be finite and non-zero.", - ), - ) - end +Calculate the susceptance-weighted average of individual branch ratings. - # 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 - ) - elseif method === :average - # Susceptance-weighted average of individual ratings. - return sum( - multipliers[PSY.get_name(br)] * - get_equivalent_rating(br) - for br in bp.branches - ) - else - throw( - ArgumentError( - "Unknown method: $(method). Valid options are :sum, :average.", - ), - ) - end - elseif weighting === :arithmetic - if method === :sum - return sum(get_equivalent_rating(branch) for branch in bp.branches) - elseif method === :average - return sum(get_equivalent_rating(branch) for branch in bp.branches) / - length(bp.branches) - else - throw( - ArgumentError( - "Unknown method: $(method). Valid options are :sum, :average.", - ), - ) - end - else +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( - "Unknown weighting: $(weighting). Valid options are :admittance_weighted, :arithmetic.", + "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 """ From bb4c7711524c436b45de9fd75a0cccc369db9977 Mon Sep 17 00:00:00 2001 From: jarry7 <27745389+jarry7@users.noreply.github.com> Date: Thu, 7 May 2026 12:39:29 -0600 Subject: [PATCH 23/23] Refactor equivalent rating tests for consistency in method usage --- test/test_equivalent_getters.jl | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/test/test_equivalent_getters.jl b/test/test_equivalent_getters.jl index 557d7004d..488406338 100644 --- a/test/test_equivalent_getters.jl +++ b/test/test_equivalent_getters.jl @@ -37,32 +37,29 @@ # Create BranchesParallel bp = PNM.BranchesParallel([line1, line2]) - # Default (method=:sum, weighting=:admittance_weighted) + # 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 + @test PNM.get_equivalent_rating( + bp, + ) ≈ 150.0 atol = 1e-6 - # method=:average, weighting=:admittance_weighted: susceptance-weighted average + # AverageRating + AdmittanceWeighted # (2/3)*100 + (1/3)*150 = 350/3 ≈ 116.667 @test PNM.get_equivalent_rating( - bp; - method = :average, - weighting = :admittance_weighted, - ) ≈ - 350.0 / 3.0 atol = 1e-6 - - # method=:sum, weighting=:arithmetic: simple sum - @test PNM.get_equivalent_rating(bp; method = :sum, weighting = :arithmetic) ≈ 250.0 atol = - 1e-6 + bp, PNM.AverageRating(), PNM.AdmittanceWeighted(), + ) ≈ 350.0 / 3.0 atol = 1e-6 - # method=:average, weighting=:arithmetic: arithmetic mean - @test PNM.get_equivalent_rating(bp; method = :average, weighting = :arithmetic) ≈ 125.0 atol = - 1e-6 + # SumRating + ArithmeticWeighting + @test PNM.get_equivalent_rating( + bp, PNM.SumRating(), PNM.ArithmeticWeighting(), + ) ≈ 250.0 atol = 1e-6 - # invalid kwarg values → ArgumentError - @test_throws ArgumentError PNM.get_equivalent_rating(bp; method = :invalid) - @test_throws ArgumentError PNM.get_equivalent_rating(bp; weighting = :invalid) + # AverageRating + ArithmeticWeighting + @test PNM.get_equivalent_rating( + bp, PNM.AverageRating(), PNM.ArithmeticWeighting(), + ) ≈ 125.0 atol = 1e-6 emergency_rating_eq = PNM.get_equivalent_emergency_rating(bp) @test emergency_rating_eq ≈ 250.0 atol = 1e-6