Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement power flow-specific (re)active power limits proxies #79

Merged
merged 1 commit into from
Jan 16, 2025
Merged
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
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
Loading