Skip to content
Open
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
206 changes: 206 additions & 0 deletions src/BranchAdmittance.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
# ── Branch π-model admittance ────────────────────────────────────────────────
# Compute a branch's π-model admittance as a
# `(g, b, g_fr, b_fr, g_to, b_to, tap, shift)` NamedTuple following the PowerModels
# convention: `g + im*b == 1/(r + im*x)` is the series admittance, `*_fr`/`*_to` are the
# from/to shunts, and `tap`/`shift` are the transformer ratio / phase shift (radians).
#
# This logic historically lived inline in PowerSimulations.jl's PowerModels translator
# (`get_branch_to_pm`).

# Transformer ratio / phase-shift accessors. Default to a unit, shift-free branch.
_branch_tap(::PSY.ACTransmission) = 1.0
_branch_tap(b::PSY.TapTransformer) = PSY.get_tap(b)
_branch_tap(b::PSY.PhaseShiftingTransformer) = PSY.get_tap(b)
_branch_shift(::PSY.ACTransmission) = 0.0
_branch_shift(b::PSY.PhaseShiftingTransformer) = PSY.get_α(b)

"""
branch_admittance(b::PSY.ACTransmission) -> NamedTuple

π-model admittance `(g, b, g_fr, b_fr, g_to, b_to, tap, shift)` for a line or symmetric
branch, where `g + im*b == 1 / (r + im*x)` is the series admittance and the line charging
susceptance is split between the from/to shunts.
"""
function branch_admittance(b::PSY.ACTransmission)
ys = 1.0 / (PSY.get_r(b, PSY.SU) + PSY.get_x(b, PSY.SU) * im)
b_sh = PSY.get_b(b, PSY.SU)
return (
g = real(ys), b = imag(ys),
g_fr = 0.0, b_fr = b_sh.from, g_to = 0.0, b_to = b_sh.to,
tap = 1.0, shift = 0.0,
)
end

"""
branch_admittance(b::Union{PSY.Transformer2W, PSY.TapTransformer, PSY.PhaseShiftingTransformer}) -> NamedTuple

π-model admittance for a two-winding transformer. The magnetizing shunt is allocated to the
primary (from) side only; `tap`/`shift` carry the transformer ratio and phase shift.
"""
function branch_admittance(
b::Union{PSY.Transformer2W, PSY.TapTransformer, PSY.PhaseShiftingTransformer},
)
ys = 1.0 / (PSY.get_r(b, PSY.SU) + PSY.get_x(b, PSY.SU) * im)
yt = PSY.get_primary_shunt(b, PSY.SU)
return (
g = real(ys), b = imag(ys),
g_fr = real(yt), b_fr = imag(yt), g_to = 0.0, b_to = 0.0,
tap = _branch_tap(b), shift = _branch_shift(b),
)
end

"""
branch_admittance(segment, nr::NetworkReductionData) -> NamedTuple

π-model admittance for a reduction-aggregated arc (a `BranchesSeries` chain or
`BranchesParallel` group), built from PNM's reduction-aware equivalent physical branch
parameters. Series/parallel equivalents of lines carry `tap == 1`.
"""
function branch_admittance(segment, nr::NetworkReductionData)
eb = get_equivalent_physical_branch_parameters(segment, nr)
ys = 1.0 / (get_equivalent_r(eb) + get_equivalent_x(eb) * im)
return (
g = real(ys), b = imag(ys),
g_fr = get_equivalent_g_from(eb), b_fr = get_equivalent_b_from(eb),
g_to = get_equivalent_g_to(eb), b_to = get_equivalent_b_to(eb),
tap = get_equivalent_tap(eb), shift = get_equivalent_shift(eb),
)
end

# Flip a π-admittance tuple to the opposite orientation (from<->to). Reduced equivalents may
# be keyed by an arc whose orientation is reversed vs. the surviving branch's retained
# from->to; reorient so coefficients match (from_bus, to_bus). g/b are symmetric; from/to
# shunts swap; phase shift negates. Reduced line equivalents have tap == 1.
function _reverse_admittance(adm)
@assert adm.tap == 1.0 "Cannot reorient a reduced arc with a non-unit tap ($(adm.tap))."
return (
g = adm.g,
b = adm.b,
g_fr = adm.g_to,
b_fr = adm.b_to,
g_to = adm.g_fr,
b_to = adm.b_fr,
tap = adm.tap,
shift = -adm.shift,
)
end

"""
reduced_arc_admittance(nr::NetworkReductionData, from_no::Int, to_no::Int) -> Union{NamedTuple, Nothing}

Reduction-aware admittance for the retained arc `from_no -> to_no`. Returns the series/parallel
equivalent π-tuple (oriented from->to) when the arc was aggregated by a network reduction, or
`nothing` when the arc is direct (the caller falls back to the branch's own
[`branch_admittance`](@ref)).
"""
function reduced_arc_admittance(nr::NetworkReductionData, from_no::Int, to_no::Int)
series_map = get_series_branch_map(nr)
parallel_map = get_parallel_branch_map(nr)
arc = (from_no, to_no)
rev = (to_no, from_no)
if haskey(series_map, arc)
return branch_admittance(series_map[arc], nr)
elseif haskey(series_map, rev)
return _reverse_admittance(branch_admittance(series_map[rev], nr))
elseif haskey(parallel_map, arc)
return branch_admittance(parallel_map[arc], nr)
elseif haskey(parallel_map, rev)
return _reverse_admittance(branch_admittance(parallel_map[rev], nr))
end
return nothing
end

# ── Three-winding transformer admittance ─────────────────────────────────────

"""
three_winding_arcs(d::PSY.Transformer3W) -> Vector{<:NamedTuple}

Decompose a `Transformer3W` into its three wye-model windings via
[`ThreeWindingTransformerWinding`](@ref), returning per-winding data: a naming `suffix`, the
star-point `arc` (for reduction-aware bus mapping), the winding `rating`, and the `winding`
object itself (for [`winding_admittance`](@ref)).
"""
function three_winding_arcs(d::PSY.Transformer3W)
star_arcs = (
PSY.get_primary_star_arc(d),
PSY.get_secondary_star_arc(d),
PSY.get_tertiary_star_arc(d),
)
out = NamedTuple[]
for i in 1:3
w = ThreeWindingTransformerWinding(d, i)
push!(
out,
(
suffix = "winding_$i",
arc = star_arcs[i],
rating = get_equivalent_rating(w),
winding = w,
),
)
end
return out
end

# No phase shift for windings; only the (optional) winding tap matters for the π-model.
_winding_tap(::ThreeWindingTransformerWinding) = 1.0
_winding_tap(w::ThreeWindingTransformerWinding{PSY.PhaseShiftingTransformer3W}) =
get_equivalent_tap(w)

"""
winding_admittance(w::ThreeWindingTransformerWinding) -> NamedTuple

Per-winding π-model admittance `(g, b, g_fr, b_fr, g_to, b_to, tap)` for one winding of a
three-winding transformer (shunt split from/to, no phase shift).
"""
function winding_admittance(w::ThreeWindingTransformerWinding)
ys = 1.0 / (get_equivalent_r(w) + get_equivalent_x(w) * im)
b_sh = get_equivalent_b(w)
return (
g = real(ys), b = imag(ys),
g_fr = 0.0, b_fr = b_sh.from, g_to = 0.0, b_to = b_sh.to,
tap = _winding_tap(w),
)
end

# ── Branch flow limits ───────────────────────────────────────────────────────

"""
branch_flow_limits(branch) -> NamedTuple

Directional flow limits in MVA (device units, `PSY.DU`): `(from_to::Float64, to_from::Float64)`. For symmetric
branches both fields equal the branch rating; `MonitoredLine` carries asymmetric limits.
"""
function branch_flow_limits end

function branch_flow_limits(b::PSY.MonitoredLine)
fl = PSY.get_flow_limits(b, PSY.DU)
return (from_to = fl.from_to, to_from = fl.to_from)
end
Comment thread
Copilot marked this conversation as resolved.

function branch_flow_limits(
b::Union{
PSY.Line,
PSY.Transformer2W,
PSY.TapTransformer,
PSY.PhaseShiftingTransformer,
},
)
r = PSY.get_rating(b, PSY.DU)
return (from_to = r, to_from = r)
end

function branch_flow_limits(b::BranchesParallel)
r = get_equivalent_rating(b)
return (from_to = r, to_from = r)
end

function branch_flow_limits(b::BranchesSeries)
r = get_equivalent_rating(b)
return (from_to = r, to_from = r)
end

function branch_flow_limits(w::ThreeWindingTransformerWinding)
r = get_equivalent_rating(w)
return (from_to = r, to_from = r)
end
6 changes: 6 additions & 0 deletions src/PowerNetworkMatrices.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ export YBUS_ELTYPE
export get_sum_of_max_rating
export get_single_element_contingency_rating
export get_impedance_averaged_rating
export branch_admittance
export reduced_arc_admittance
export winding_admittance
export three_winding_arcs
export branch_flow_limits

export apply_woodbury_correction
export clear_all_caches!
Expand Down Expand Up @@ -137,6 +142,7 @@ include("AdjacencyMatrix.jl")
include("connectivity_checks.jl")
include("subnetworks.jl")
include("common.jl")
include("BranchAdmittance.jl")
include("auto_tolerance.jl")
include("BA_ABA_matrices.jl")
include("virtual_factor_helpers.jl")
Expand Down
Loading
Loading