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
8 changes: 7 additions & 1 deletion src/AdjacencyMatrix.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ function get_reduction(
get_user_irreducible_buses(get_reductions(network_reduction_data))
validate_buses(A, user_irreducible)
working_set = Set{Int}(user_irreducible)
union!(working_set, _system_derived_irreducible_buses(sys))
union!(
working_set,
_system_derived_irreducible_buses(
sys,
get_reduce_reactive_power_injectors(reduction),
),
)

exempt_bus_positions =
Set(get_irreducible_indices(A, collect(working_set)))
Expand Down
7 changes: 6 additions & 1 deletion src/degree_two_reduction.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ and `TransmissionInterface` endpoints) on top of any user set passed via
`Ybus(sys; irreducible_buses=...)`.

# Fields
- `reduce_reactive_power_injectors::Bool = true`
- `reduce_reactive_power_injectors::Bool = true`: when `true`, buses whose only
injectors support reactive power (e.g. a `SynchronousCondenser`, or a purely
susceptive `FixedAdmittance`) are treated as reduction candidates. When `false`,
such reactive-only injector hosts are kept. Buses hosting an active-power
injector are always kept. Capability is read from the PowerSystems
`supports_active_power` / `supports_reactive_power` traits.
"""
@kwdef struct DegreeTwoReduction <: NetworkReduction
reduce_reactive_power_injectors::Bool = true
Expand Down
18 changes: 16 additions & 2 deletions src/reduction_helpers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,30 @@ function _add_arc_buses!(buses::Set{Int}, br::PSY.ThreeWindingTransformer)
end

"""
_system_derived_irreducible_buses(sys) -> Set{Int}
_system_derived_irreducible_buses(sys, reduce_reactive_power_injectors) -> Set{Int}

Fresh `Set{Int}` of buses the system requires kept: `StaticInjection` hosts,
`TwoTerminalHVDC` endpoints, cross-area `ACTransmission` endpoints (when any
`AreaInterchange` exists), and branch-typed `TransmissionInterface` contributors.

A `StaticInjection` host is kept when the injector supports active power. When
`reduce_reactive_power_injectors` is `false`, reactive-power injectors also keep
their host bus; when `true` (the default), buses whose injectors only support
reactive power become reduction candidates. The non-injection protections are
unconditional.
"""
function _system_derived_irreducible_buses(sys::PSY.System)
function _system_derived_irreducible_buses(
sys::PSY.System,
reduce_reactive_power_injectors::Bool,
)
buses = Set{Int}()
for c in PSY.get_components(PSY.StaticInjection, sys)
PSY.get_available(c) || continue
# An injector keeps its bus if it supports active power, or (when reactive
# injectors are not being reduced) reactive power.
forces_keep = PSY.supports_active_power(c) ||
(!reduce_reactive_power_injectors && PSY.supports_reactive_power(c))
Comment on lines +77 to +78
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
forces_keep = PSY.supports_active_power(c) ||
(!reduce_reactive_power_injectors && PSY.supports_reactive_power(c))
forces_keep =
PSY.supports_active_power(c) ||
(!reduce_reactive_power_injectors && PSY.supports_reactive_power(c))

Comment on lines +77 to +78
forces_keep || continue
bus = PSY.get_bus(c)
PSY.get_available(bus) || continue
push!(buses, PSY.get_number(bus))
Expand Down
102 changes: 102 additions & 0 deletions test/test_degree_two_reactive_injectors.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# Build a standalone 4-bus chain (1-2-3-4). Buses 1 and 4 host active-power
# injectors; bus 2 hosts only a reactive SynchronousCondenser; bus 3 is bare.
# This isolates the reactive-only gating decision in the degree-2 reduction.
function _build_reactive_only_degree2_system()
sys = PSY.System(100.0)
buses = ACBus[]
for i in 1:4
if i == 1
bustype = ACBusTypes.REF
else
bustype = ACBusTypes.PV
end
bus = ACBus(;
number = i,
name = "Bus $i",
available = true,
bustype = bustype,
angle = 0.0,
magnitude = 1.0,
voltage_limits = (min = 0.9, max = 1.05),
base_voltage = 138.0,
)
add_component!(sys, bus)
push!(buses, bus)
end
for (i, (b_from, b_to)) in enumerate([(1, 2), (2, 3), (3, 4)])
line = Line(;
name = "Line $i",
available = true,
active_power_flow = 0.0,
reactive_power_flow = 0.0,
arc = Arc(; from = buses[b_from], to = buses[b_to]),
r = 0.01,
x = 0.1,
b = (from = 0.0, to = 0.0),
rating = 2.0,
angle_limits = (min = -1.5, max = 1.5),
)
add_component!(sys, line)
end
for (bus, name) in [(buses[1], "Gen 1"), (buses[4], "Gen 4")]
gen = ThermalStandard(;
name = name,
available = true,
status = true,
bus = bus,
active_power = 0.0,
reactive_power = 0.0,
rating = 1.0,
active_power_limits = (min = 0.0, max = 1.0),
reactive_power_limits = nothing,
ramp_limits = (up = 1.0, down = 1.0),
operation_cost = ThermalGenerationCost(nothing),
base_power = 100.0,
time_limits = (up = 1.0, down = 1.0),
must_run = false,
prime_mover_type = PrimeMovers.CC,
fuel = ThermalFuels.NATURAL_GAS,
)
add_component!(sys, gen)
end
condenser = SynchronousCondenser(;
name = "Cond 2",
available = true,
bus = buses[2],
reactive_power = 0.0,
rating = 1.0,
reactive_power_limits = (min = -1.0, max = 1.0),
base_power = 100.0,
)
add_component!(sys, condenser)
return sys
end

@testset "Degree-2 reactive-injector gating: irreducible-bus set" begin
sys = _build_reactive_only_degree2_system()

kept_reduce = PNM._system_derived_irreducible_buses(sys, true)
@test 1 in kept_reduce
@test 4 in kept_reduce
@test !(2 in kept_reduce) # condenser-only bus dropped when reducing reactive
@test !(3 in kept_reduce) # bare bus never kept

kept_keep = PNM._system_derived_irreducible_buses(sys, false)
@test 1 in kept_keep
@test 4 in kept_keep
@test 2 in kept_keep # condenser bus retained when keeping reactive
@test !(3 in kept_keep)
end

@testset "Degree-2 reactive-injector gating: get_reduction wiring" begin
sys = _build_reactive_only_degree2_system()
adj = AdjacencyMatrix(sys)

reduced = PNM.get_reduction(adj, sys, DegreeTwoReduction()) # default reduce = true
@test !(2 in reduced.irreducible_buses) # condenser bus is a reduction candidate
@test 2 in reduced.removed_buses # and is actually folded out of the chain

kept = PNM.get_reduction(adj, sys, DegreeTwoReduction(; reduce_reactive_power_injectors = false))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

[JuliaFormatter] reported by reviewdog 🐶

Suggested change
kept = PNM.get_reduction(adj, sys, DegreeTwoReduction(; reduce_reactive_power_injectors = false))
kept = PNM.get_reduction(
adj,
sys,
DegreeTwoReduction(; reduce_reactive_power_injectors = false),
)

@test 2 in kept.irreducible_buses # condenser bus retained
@test !(2 in kept.removed_buses) # and not folded
end
Loading