diff --git a/src/common.jl b/src/common.jl index 9ea6a41..2402845 100644 --- a/src/common.jl +++ b/src/common.jl @@ -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}, @@ -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) diff --git a/src/post_processing.jl b/src/post_processing.jl index ebea6d8..86d3f22 100644 --- a/src/post_processing.jl +++ b/src/post_processing.jl @@ -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, @@ -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) @@ -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 @@ -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) @@ -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)" @@ -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) @@ -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 = @@ -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)" diff --git a/src/psse_export.jl b/src/psse_export.jl index 8ac47bd..e15ace5 100644 --- a/src/psse_export.jl +++ b/src/psse_export.jl @@ -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, ) @@ -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) diff --git a/test/test_utils/common.jl b/test/test_utils/common.jl index 153efa2..0cc0a15 100644 --- a/test/test_utils/common.jl +++ b/test/test_utils/common.jl @@ -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()