Skip to content

Commit

Permalink
Merge pull request #39 from NREL-Sienna/ps/reserves
Browse files Browse the repository at this point in the history
fix: Add `ReserveRange` formulation to `HydroPumpedStorage`
  • Loading branch information
jd-lara authored Dec 12, 2024
2 parents 2d9ef90 + 58db674 commit 5817270
Show file tree
Hide file tree
Showing 6 changed files with 281 additions and 9 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd"
[compat]
Dates = "1"
InfrastructureSystems = "2"
PowerSimulations = "^0.28"
PowerSimulations = "^0.29"
PowerSystems = "4"
julia = "^1.6"
JuMP = "1"
3 changes: 3 additions & 0 deletions src/core/expressions.jl
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
struct ReserveRangeExpressionLB <: PSI.RangeConstraintLBExpressions end
struct ReserveRangeExpressionUB <: PSI.RangeConstraintUBExpressions end

PSI.should_write_resulting_value(::Type{ReserveRangeExpressionUB}) = true
PSI.should_write_resulting_value(::Type{ReserveRangeExpressionLB}) = true
178 changes: 178 additions & 0 deletions src/hydro_generation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,94 @@ function PSI.get_min_max_limits(
return PSY.get_active_power_limits_pump(x)
end

######################## Energy Limits Constraints #############################

function _add_output_limit_constraints!(
container::PSI.OptimizationContainer,
::Type{PSI.OutputActivePowerVariableLimitsConstraint},
devices::IS.FlattenIteratorWrapper{V},
model::PSI.DeviceModel{V, W},
network_model::PSI.NetworkModel{X},
) where {
V <: PSY.HydroPumpedStorage,
W <: AbstractHydroReservoirFormulation,
X <: PM.AbstractPowerModel,
}
if !PSI.has_service_model(model)
PSI.add_constraints!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
PSI.ActivePowerOutVariable,
devices,
model,
network_model,
)
else
if PSI.get_attribute(model, "reservation")
array_lb = PSI.get_expression(
container,
ReserveRangeExpressionLB(),
PSY.HydroPumpedStorage,
)
PSI._add_reserve_lower_bound_range_constraints_impl!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
array_lb,
devices,
model,
)
array_ub = PSI.get_expression(
container,
ReserveRangeExpressionUB(),
PSY.HydroPumpedStorage,
)
PSI._add_reserve_upper_bound_range_constraints_impl!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
array_ub,
devices,
model,
)
else
array_lb = PSI.get_expression(
container,
ReserveRangeExpressionLB(),
PSY.HydroPumpedStorage,
)
PSI._add_lower_bound_range_constraints_impl!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
array_lb,
devices,
model,
)
array_ub = PSI.get_expression(
container,
ReserveRangeExpressionUB(),
PSY.HydroPumpedStorage,
)
PSI._add_upper_bound_range_constraints_impl!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
array_ub,
devices,
model,
)
end
end
end
function _add_output_limits_with_reserves!(
container::PSI.OptimizationContainer,
::Type{PSI.OutputActivePowerVariableLimitsConstraint},
devices::IS.FlattenIteratorWrapper{V},
model::PSI.DeviceModel{V, W},
network_model::PSI.NetworkModel{X},
) where {
V <: PSY.HydroPumpedStorage,
W <: AbstractHydroReservoirFormulation,
X <: PM.AbstractPowerModel,
} end

######################## Energy balance constraints ############################

"""
Expand Down Expand Up @@ -1082,3 +1170,93 @@ function PSI._get_initial_conditions_value(
PSI.LOG_GROUP_BUILD_INITIAL_CONDITIONS
return T(component, val)
end

function PSI.add_constraints!(
container::PSI.OptimizationContainer,
T::Type{<:ReserveRangeExpressionUB},
U::Type{<:PSI.ActivePowerReserveVariable},
devices::IS.FlattenIteratorWrapper{V},
model::PSI.DeviceModel{V, W},
::PSI.NetworkModel{X},
) where {V <: PSY.HydroGen, W <: PSI.AbstractDeviceFormulation, X <: PM.AbstractPowerModel}
PSI.add_range_constraints!(container, T, U, devices, model, X)
return
end

function PSI.add_to_expression!(
container::PSI.OptimizationContainer,
::Type{T},
::Type{U},
devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}},
model::PSI.DeviceModel{V, W},
network_model::PSI.NetworkModel{X},
) where {
T <: Union{ReserveRangeExpressionLB, ReserveRangeExpressionUB},
U <: PSI.VariableType,
V <: PSY.Device,
W <: PSI.AbstractDeviceFormulation,
X <: PM.AbstractPowerModel,
}
variable = PSI.get_variable(container, U(), V)
if !PSI.has_container_key(container, T, V)
PSI.add_expressions!(container, T, devices, model)
end
expression = PSI.get_expression(container, T(), V)
for d in devices, t in PSI.get_time_steps(container)
name = PSY.get_name(d)
PSI._add_to_jump_expression!(expression[name, t], variable[name, t], 1.0)
end
return
end

function PSI.add_to_expression!(
container::PSI.OptimizationContainer,
::Type{T},
::Type{U},
devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}},
model::PSI.ServiceModel{X, W},
) where {
T <: ReserveRangeExpressionUB,
U <: PSI.VariableType,
V <: PSY.HydroGen,
X <: PSY.Reserve{PSY.ReserveUp},
W <: PSI.AbstractReservesFormulation,
}
service_name = PSI.get_service_name(model)
variable = PSI.get_variable(container, U(), X, service_name)
if !PSI.has_container_key(container, T, V)
PSI.add_expressions!(container, T, devices, model)
end
expression = PSI.get_expression(container, T(), V)
for d in devices, t in PSI.get_time_steps(container)
name = PSY.get_name(d)
PSI._add_to_jump_expression!(expression[name, t], variable[name, t], 1.0)
end
return
end

function PSI.add_to_expression!(
container::PSI.OptimizationContainer,
::Type{T},
::Type{U},
devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}},
model::PSI.ServiceModel{X, W},
) where {
T <: ReserveRangeExpressionLB,
U <: PSI.VariableType,
V <: PSY.HydroGen,
X <: PSY.Reserve{PSY.ReserveDown},
W <: PSI.AbstractReservesFormulation,
}
service_name = PSI.get_service_name(model)
variable = PSI.get_variable(container, U(), X, service_name)
if !PSI.has_container_key(container, T, V)
PSI.add_expressions!(container, T, devices, model)
end
expression = PSI.get_expression(container, T(), V)
for d in devices, t in PSI.get_time_steps(container)
name = PSY.get_name(d)
PSI._add_to_jump_expression!(expression[name, t], variable[name, t], -1.0)
end
return
end
27 changes: 22 additions & 5 deletions src/hydrogeneration_constructor.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1332,9 +1332,26 @@ function PSI.construct_device!(
model,
network_model,
)

PSI.add_expressions!(container, ReserveRangeExpressionLB, devices, model)
PSI.add_expressions!(container, ReserveRangeExpressionUB, devices, model)
if PSI.has_service_model(model)
PSI.add_to_expression!(
container,
ReserveRangeExpressionUB,
PSI.ActivePowerOutVariable,
devices,
model,
network_model,
)
PSI.add_to_expression!(
container,
ReserveRangeExpressionLB,
PSI.ActivePowerOutVariable,
devices,
model,
network_model,
)
end
# PSI.add_range_constraints!(container, ReserveRangeExpressionLB, devices, model)
# PSI.add_range_constraints!(container, ReserveRangeExpressionUB, devices, model)

PSI.add_feedforward_arguments!(container, model, devices)
return
Expand All @@ -1349,14 +1366,14 @@ function PSI.construct_device!(
) where {H <: PSY.HydroPumpedStorage, S <: PM.AbstractActivePowerModel}
devices = PSI.get_available_components(model, sys)

PSI.add_constraints!(
_add_output_limit_constraints!(
container,
PSI.OutputActivePowerVariableLimitsConstraint,
PSI.ActivePowerOutVariable,
devices,
model,
network_model,
)

PSI.add_constraints!(
container,
PSI.InputActivePowerVariableLimitsConstraint,
Expand Down
22 changes: 20 additions & 2 deletions test/test_device_hydro_generation_constructors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ end
set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver)

@testset "HydroRoR ED model $(net)" begin
ED = DecisionModel(UnitCommitmentProblem, template, sys; optimizer=GLPK_optimizer)
ED = DecisionModel(UnitCommitmentProblem, template, sys; optimizer=HiGHS_optimizer)
@test build!(ED; output_dir=mktempdir(; cleanup=true)) == PSI.ModelBuildStatus.BUILT
psi_checksolve_test(ED, [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], 175521.0, 1000)
end
Expand Down Expand Up @@ -232,7 +232,7 @@ end
UnitCommitmentProblem,
template,
sys;
optimizer=GLPK_optimizer,
optimizer=HiGHS_optimizer,
)
@test build!(ED; output_dir=mktempdir(; cleanup=true)) ==
PSI.ModelBuildStatus.BUILT
Expand Down Expand Up @@ -546,3 +546,21 @@ end
# The value of this test needs to be revised
# moi_tests(model, 240, 0, 48, 96, 72, false)
end

@testset "Test Reserves from HydroPumpedStorage" begin
template = ProblemTemplate(CopperPlatePowerModel)
set_device_model!(template, PowerLoad, StaticPowerLoad)
set_device_model!(template, HydroPumpedStorage, HydroDispatchPumpedStorage)
set_service_model!(
template,
ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "Reserve7"),
)
set_service_model!(
template,
ServiceModel(VariableReserve{ReserveDown}, RangeReserve, "Reserve8"),
)

c_sys5_phes_ed = PSB.build_system(PSITestSystems, "c_sys5_phes_ed", add_reserves=true)
model = DecisionModel(template, c_sys5_phes_ed)
@test build!(model; output_dir=mktempdir(; cleanup=true)) == PSI.ModelBuildStatus.BUILT
end
58 changes: 57 additions & 1 deletion test/test_hydro_simulations.jl
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function test_2_stage_decision_models_with_feedforwards(in_memory)
c_sys5_hy_ed = PSB.build_system(PSITestSystems, "c_sys5_hy_ed")
models = SimulationModels(;
decision_models=[
DecisionModel(template_uc, c_sys5_hy_uc; name="UC", optimizer=GLPK_optimizer),
DecisionModel(template_uc, c_sys5_hy_uc; name="UC", optimizer=HiGHS_optimizer),
DecisionModel(template_ed, c_sys5_hy_ed; name="ED", optimizer=ipopt_optimizer),
],
)
Expand Down Expand Up @@ -181,3 +181,59 @@ end
test_2_stage_decision_models_with_feedforwards(in_memory)
end
end

@testset "HydroPumpedStorage simulation with Reserves" begin
output_dir = mktempdir(; cleanup=true)
sys = PSB.build_system(PSITestSystems, "c_sys5_phes_ed"; add_reserves=true)
res5 = only(get_components(VariableReserve{ReserveUp}, sys))
set_requirement!(res5, 0.1)

res6 = only(get_components(VariableReserve{ReserveDown}, sys))
set_requirement!(res6, 0.1)
template = ProblemTemplate(CopperPlatePowerModel)
set_device_model!(template, PowerLoad, StaticPowerLoad)
set_device_model!(
template,
DeviceModel(
HydroPumpedStorage,
HydroDispatchPumpedStorage;
attributes=Dict{String, Any}("reservation" => false),
),
)
set_device_model!(template, ThermalStandard, ThermalBasicUnitCommitment)
set_service_model!(template, ServiceModel(VariableReserve{ReserveUp}, RangeReserve))
set_service_model!(template, ServiceModel(VariableReserve{ReserveDown}, RangeReserve))

model = DecisionModel(
template,
sys,
name="ED",
optimizer=HiGHS_optimizer,
optimizer_solve_log_print=true,
store_variable_names=true,
)
@test build!(model, output_dir=output_dir) == PSI.ModelBuildStatus.BUILT
@test solve!(model; optimizer=HiGHS_optimizer, output_dir=output_dir) ==
IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED

set_device_model!(
template,
DeviceModel(
HydroPumpedStorage,
HydroDispatchPumpedStorage;
attributes=Dict{String, Any}("reservation" => true),
),
)

model = DecisionModel(
template,
sys,
name="ED",
optimizer=HiGHS_optimizer,
optimizer_solve_log_print=true,
store_variable_names=true,
)
@test build!(model, output_dir=output_dir) == PSI.ModelBuildStatus.BUILT
@test solve!(model; optimizer=HiGHS_optimizer, output_dir=output_dir) ==
IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED
end

0 comments on commit 5817270

Please sign in to comment.