Skip to content

Commit

Permalink
Implement power flow-specific (re)active power limits proxies (#79)
Browse files Browse the repository at this point in the history
Fixes #39
  • Loading branch information
GabrielKS authored Jan 16, 2025
1 parent 7582e6d commit a080b9b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 30 deletions.
34 changes: 33 additions & 1 deletion src/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,38 @@ function get_total_q(l::PSY.StandardLoad)
PSY.get_impedance_reactive_power(l)
end

"""
Return the reactive power limits that should be used in power flow calculations and PSS/E
exports. Redirects to `PSY.get_reactive_power_limits` in all but special cases.
"""
get_reactive_power_limits_for_power_flow(gen::PSY.Device) =
PSY.get_reactive_power_limits(gen)

function get_reactive_power_limits_for_power_flow(gen::PSY.RenewableNonDispatch)
val = PSY.get_reactive_power(gen)
return (min = val, max = val)
end

"""
Return the active power limits that should be used in power flow calculations and PSS/E
exports. Redirects to `PSY.get_active_power_limits` in all but special cases.
"""
get_active_power_limits_for_power_flow(gen::PSY.Device) = PSY.get_active_power_limits(gen)

get_active_power_limits_for_power_flow(::PSY.Source) = (min = -Inf, max = Inf)

function get_active_power_limits_for_power_flow(gen::PSY.RenewableNonDispatch)
val = PSY.get_active_power(gen)
return (min = val, max = val)
end

get_active_power_limits_for_power_flow(gen::PSY.RenewableDispatch) =
(min = 0.0, max = PSY.get_rating(gen))

# TODO verify whether this is the correct behavior for Storage, (a) for redistribution and (b) for exporting
get_active_power_limits_for_power_flow(gen::PSY.Storage) =
(min = 0.0, max = PSY.get_output_active_power_limits(gen).max)

function _get_injections!(
bus_activepower_injection::Vector{Float64},
bus_reactivepower_injection::Vector{Float64},
Expand Down Expand Up @@ -58,7 +90,7 @@ function _get_reactive_power_bound!(
!PSY.get_available(source) && continue
bus = PSY.get_bus(source)
bus_ix = bus_lookup[PSY.get_number(bus)]
reactive_power_limits = PSY.get_reactive_power_limits(source)
reactive_power_limits = get_reactive_power_limits_for_power_flow(source)
if reactive_power_limits !== nothing
bus_reactivepower_bounds[bus_ix][1] += min(0, reactive_power_limits.min)
bus_reactivepower_bounds[bus_ix][2] += max(0, reactive_power_limits.max)
Expand Down
30 changes: 9 additions & 21 deletions src/post_processing.jl
Original file line number Diff line number Diff line change
Expand Up @@ -158,18 +158,6 @@ function _get_fixed_admittance_power(
return active_power, reactive_power
end

function _get_limits_for_power_distribution(gen::PSY.StaticInjection)
return PSY.get_active_power_limits(gen)
end

function _get_limits_for_power_distribution(gen::PSY.RenewableDispatch)
return (min = 0.0, max = PSY.get_max_active_power(gen))
end

function _get_limits_for_power_distribution(gen::PSY.Storage)
return (min = 0.0, max = PSY.get_output_active_power_limits(gen).max)
end

function _power_redistribution_ref(
sys::PSY.System,
P_gen::Float64,
Expand Down Expand Up @@ -204,16 +192,16 @@ function _power_redistribution_ref(
return
elseif length(devices_) > 1
devices =
sort(collect(devices_); by = x -> _get_limits_for_power_distribution(x).max)
sort(collect(devices_); by = x -> get_active_power_limits_for_power_flow(x).max)
else
error("No devices in bus $(PSY.get_name(bus))")
end

sum_basepower = sum([g.max for g in _get_limits_for_power_distribution.(devices)])
sum_basepower = sum([g.max for g in get_active_power_limits_for_power_flow.(devices)])
p_residual = P_gen
units_at_limit = Vector{Int}()
for (ix, d) in enumerate(devices)
p_limits = _get_limits_for_power_distribution(d)
p_limits = get_active_power_limits_for_power_flow(d)
part_factor = p_limits.max / sum_basepower
p_frac = P_gen * part_factor
p_set_point = clamp(p_frac, p_limits.min, p_limits.max)
Expand All @@ -229,7 +217,7 @@ function _power_redistribution_ref(
if !isapprox(p_residual, 0.0; atol = ISAPPROX_ZERO_TOLERANCE)
@debug "Ref Bus voltage residual $p_residual"
removed_power = sum([
g.max for g in _get_limits_for_power_distribution.(devices[units_at_limit])
g.max for g in get_active_power_limits_for_power_flow.(devices[units_at_limit])
])
reallocated_p = 0.0
it = 0
Expand All @@ -240,7 +228,7 @@ function _power_redistribution_ref(
end
for (ix, d) in enumerate(devices)
ix units_at_limit && continue
p_limits = PSY.get_active_power_limits(d)
p_limits = get_active_power_limits_for_power_flow(d)
part_factor = p_limits.max / (sum_basepower - removed_power)
p_frac = p_residual * part_factor
current_p = PSY.get_active_power(d)
Expand Down Expand Up @@ -270,7 +258,7 @@ function _power_redistribution_ref(
@debug "Remaining residual $q_residual, $(PSY.get_name(bus))"
p_set_point = PSY.get_active_power(device) + p_residual
PSY.set_active_power!(device, p_set_point)
p_limits = PSY.get_reactive_power_limits(device)
p_limits = get_reactive_power_limits_for_power_flow(device) # TODO should this be active_power_limits? It was reactive in the existing codebase
if (p_set_point >= p_limits.max + BOUNDS_TOLERANCE) ||
(p_set_point <= p_limits.min - BOUNDS_TOLERANCE)
@error "Unit $(PSY.get_name(device)) P=$(p_set_point) above limits. P_max = $(p_limits.max) P_min = $(p_limits.min)"
Expand Down Expand Up @@ -332,7 +320,7 @@ function _reactive_power_redistribution_pv(
units_at_limit = Vector{Int}()

for (ix, d) in enumerate(devices)
q_limits = PSY.get_reactive_power_limits(d)
q_limits = get_reactive_power_limits_for_power_flow(d)
if isapprox(q_limits.max, 0.0; atol = BOUNDS_TOLERANCE) &&
isapprox(q_limits.min, 0.0; atol = BOUNDS_TOLERANCE)
push!(units_at_limit, ix)
Expand Down Expand Up @@ -377,7 +365,7 @@ function _reactive_power_redistribution_pv(
reallocated_q = 0.0
for (ix, d) in enumerate(devices)
ix units_at_limit && continue
q_limits = PSY.get_reactive_power_limits(d)
q_limits = get_reactive_power_limits_for_power_flow(d)

if removed_power < total_active_power
fraction =
Expand Down Expand Up @@ -426,7 +414,7 @@ function _reactive_power_redistribution_pv(
@debug "Remaining residual $q_residual, $(PSY.get_name(bus))"
q_set_point = PSY.get_reactive_power(device) + q_residual
PSY.set_reactive_power!(device, q_set_point)
q_limits = PSY.get_reactive_power_limits(device)
q_limits = get_reactive_power_limits_for_power_flow(device)
if (q_set_point >= q_limits.max + BOUNDS_TOLERANCE) ||
(q_set_point <= q_limits.min - BOUNDS_TOLERANCE)
@error "Unit $(PSY.get_name(device)) Q=$(q_set_point) above limits. Q_max = $(q_limits.max) Q_min = $(q_limits.min)"
Expand Down
6 changes: 4 additions & 2 deletions src/psse_export.jl
Original file line number Diff line number Diff line change
Expand Up @@ -714,7 +714,7 @@ function write_to_buffers!(
# TODO approximate a QT for generators that don't have it set
# (this is needed to run power flows also)
reactive_power_limits = with_units_base(
() -> PSY.get_reactive_power_limits(generator),
() -> get_reactive_power_limits_for_power_flow(generator),
exporter.system,
PSY.UnitSystem.NATURAL_UNITS,
)
Expand All @@ -733,12 +733,14 @@ function write_to_buffers!(
# TODO maybe have a better default here
active_power_limits =
with_units_base(
() -> PSY.get_active_power_limits(generator),
() -> get_active_power_limits_for_power_flow(generator),
exporter.system,
PSY.UnitSystem.NATURAL_UNITS,
)
PT = active_power_limits.max
isfinite(PT) || (PT = PSSE_DEFAULT)
PB = active_power_limits.min
isfinite(PB) || (PB = PSSE_DEFAULT)
WMOD = get(PSY.get_ext(generator), "WMOD", PSSE_DEFAULT)
WPF = get(PSY.get_ext(generator), "WPF", PSSE_DEFAULT)

Expand Down
6 changes: 0 additions & 6 deletions test/test_utils/common.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ powerflow_match_fn(
isapprox(a, b; atol = POWERFLOW_COMPARISON_TOLERANCE) || IS.isequivalent(a, b)
powerflow_match_fn(a, b) = IS.isequivalent(a, b)

# TODO temporary hacks, see https://github.com/NREL-Sienna/PowerFlows.jl/issues/39
PowerSystems.get_reactive_power_limits(::RenewableNonDispatch) = (min = 0.0, max = 0.0)
PowerSystems.get_active_power_limits(
::Union{RenewableDispatch, RenewableNonDispatch, Source},
) = (min = 0.0, max = 0.0)

# TODO another temporary hack
"Create a version of the RTS_GMLC system that plays nice with the current implementation of AC power flow"
function create_pf_friendly_rts_gmlc()
Expand Down

0 comments on commit a080b9b

Please sign in to comment.