From 27a2ddab4b7d142943cce33ec97f868cf8a45bff Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 5 Mar 2024 21:03:44 -0700 Subject: [PATCH 01/20] bump get_available_components --- src/storage_constructor.jl | 8 ++++---- src/storage_models.jl | 5 ++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/storage_constructor.jl b/src/storage_constructor.jl index 7d1b7dc..5c9f4b1 100644 --- a/src/storage_constructor.jl +++ b/src/storage_constructor.jl @@ -219,7 +219,7 @@ function PSI.construct_device!( model::PSI.DeviceModel{St, D}, network_model::PSI.NetworkModel{S}, ) where {St <: PSY.Storage, D <: StorageDispatchWithReserves, S <: PM.AbstractPowerModel} - devices = PSI.get_available_components(St, sys) + devices = PSI.get_available_components(model, sys) _active_power_variables_and_expressions(container, devices, model, network_model) PSI.add_variables!(container, PSI.ReactivePowerVariable, devices, D()) @@ -252,7 +252,7 @@ function PSI.construct_device!( model::PSI.DeviceModel{St, D}, network_model::PSI.NetworkModel{S}, ) where {St <: PSY.Storage, D <: StorageDispatchWithReserves, S <: PM.AbstractPowerModel} - devices = PSI.get_available_components(St, sys) + devices = PSI.get_available_components(model, sys) _active_power_and_energy_bounds(container, devices, model, network_model) PSI.add_constraints!( @@ -318,7 +318,7 @@ function PSI.construct_device!( D <: StorageDispatchWithReserves, S <: PM.AbstractActivePowerModel, } - devices = PSI.get_available_components(St, sys) + devices = PSI.get_available_components(model, sys) _active_power_variables_and_expressions(container, devices, model, network_model) if PSI.get_attribute(model, "regularization") @@ -345,7 +345,7 @@ function PSI.construct_device!( D <: StorageDispatchWithReserves, S <: PM.AbstractActivePowerModel, } - devices = PSI.get_available_components(St, sys) + devices = PSI.get_available_components(model, sys) _active_power_and_energy_bounds(container, devices, model, network_model) # Energy Balanace limits diff --git a/src/storage_models.jl b/src/storage_models.jl index d4b3a4e..e34d1e2 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -1754,14 +1754,13 @@ function PSI.calculate_aux_variable_value!( ::PSI.AuxVarKey{StorageEnergyOutput, T}, system::PSY.System, ) where {T <: PSY.Storage} - devices = PSI.get_available_components(T, system) time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR p_variable_results = PSI.get_variable(container, PSI.ActivePowerOutVariable(), T) aux_variable_container = PSI.get_aux_variable(container, StorageEnergyOutput(), T) - for d in devices, t in time_steps - name = PSY.get_name(d) + device_names = axes(aux_variable_container, 1) + for name in device_names, t in time_steps aux_variable_container[name, t] = PSI.jump_value(p_variable_results[name, t]) * fraction_of_hour end From b117f262912ba84e8c50011d5763890495e0726e Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Fri, 15 Mar 2024 17:01:26 -0600 Subject: [PATCH 02/20] Use new `init_optimization_container!` signature --- test/test_utils/mock_operation_models.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_utils/mock_operation_models.jl b/test/test_utils/mock_operation_models.jl index c6b5bc7..9d996a6 100644 --- a/test/test_utils/mock_operation_models.jl +++ b/test/test_utils/mock_operation_models.jl @@ -105,7 +105,7 @@ function mock_construct_device!( PSI.finalize_template!(template, PSI.get_system(problem)) PSI.init_optimization_container!( PSI.get_optimization_container(problem), - PSI.get_network_formulation(template), + PSI.get_network_model(template), PSI.get_system(problem), ) PSI.get_network_model(template).subnetworks = @@ -202,7 +202,7 @@ function setup_ic_model_container!(model::DecisionModel) PSI.init_optimization_container!( PSI.get_optimization_container(model), - PSI.get_network_formulation(PSI.get_template(model)), + PSI.get_network_model(PSI.get_template(model)), PSI.get_system(model), ) From 6d2e683193bc87826492af3ece1995d41b9768b2 Mon Sep 17 00:00:00 2001 From: GabrielKS <23368820+GabrielKS@users.noreply.github.com> Date: Mon, 18 Mar 2024 11:47:10 -0600 Subject: [PATCH 03/20] Use new cost structs, test that --- src/storage_models.jl | 4 ++-- test/test_storage_simulation.jl | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index e34d1e2..850feca 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -90,8 +90,8 @@ function PSI.variable_cost( ::PSY.Storage, ::AbstractStorageFormulation, ) - max_val = max(REG_COST, cost.variable.cost[2] * REG_COST) - return PSY.VariableCost(max_val) + max_val = max(REG_COST, PSY.get_proportional_term(PSY.get_variable(cost)) * REG_COST) + return PSY.LinearFunctionData(max_val) end function PSI.get_default_time_series_names( diff --git a/test/test_storage_simulation.jl b/test/test_storage_simulation.jl index d4aab48..fdeeab5 100644 --- a/test/test_storage_simulation.jl +++ b/test/test_storage_simulation.jl @@ -184,3 +184,22 @@ end @test isapprox(param_ed[!, 2], p_out_bat[!, 2] / 100.0; atol=1e-4) end + +@testset "Test cost handling" begin + c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true) + template = get_thermal_dispatch_template_network() + storage_model = DeviceModel( + GenericBattery, + StorageDispatchWithReserves; + attributes=Dict( + "reservation" => false, + "cycling_limits" => false, + "energy_target" => false, + "complete_coverage" => false, + "regularization" => true, + ), + ) + set_device_model!(template, storage_model) + model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) + @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT +end From b793424a58a7a67561671d29f4a2a845ec748503 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 2 May 2024 11:13:47 -0600 Subject: [PATCH 04/20] update variable cost --- src/storage_models.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index 850feca..71c899d 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -63,14 +63,14 @@ PSI.objective_function_multiplier(::PSI.VariableType, ::AbstractStorageFormulati PSI.objective_function_multiplier(::StorageEnergySurplusVariable, ::AbstractStorageFormulation)=PSI.OBJECTIVE_FUNCTION_POSITIVE PSI.objective_function_multiplier(::StorageEnergyShortageVariable, ::AbstractStorageFormulation)=PSI.OBJECTIVE_FUNCTION_POSITIVE -PSI.proportional_cost(cost::PSY.StorageManagementCost, ::StorageEnergySurplusVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_surplus_cost(cost) -PSI.proportional_cost(cost::PSY.StorageManagementCost, ::StorageEnergyShortageVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_shortage_cost(cost) -PSI.proportional_cost(::PSY.StorageManagementCost, ::StorageChargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST -PSI.proportional_cost(::PSY.StorageManagementCost, ::StorageDischargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST +PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergySurplusVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_surplus_cost(cost) +PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergyShortageVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_shortage_cost(cost) +PSI.proportional_cost(::PSY.StorageCost, ::StorageChargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST +PSI.proportional_cost(::PSY.StorageCost, ::StorageDischargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST -PSI.variable_cost(cost::PSY.StorageManagementCost, ::PSI.ActivePowerOutVariable, ::PSY.Storage, ::AbstractStorageFormulation)=PSY.get_variable(cost) -PSI.variable_cost(cost::PSY.StorageManagementCost, ::PSI.ActivePowerInVariable, ::PSY.Storage, ::AbstractStorageFormulation)=PSY.get_variable(cost) +PSI.variable_cost(cost::PSY.StorageCost, ::PSI.ActivePowerOutVariable, ::PSY.Storage, ::AbstractStorageFormulation)=PSY.get_discharge_variable_cost(cost) +PSI.variable_cost(cost::PSY.StorageCost, ::PSI.ActivePowerInVariable, ::PSY.Storage, ::AbstractStorageFormulation)=PSY.get_charge_variable_cost(cost) ######################## Parameters ################################################## @@ -85,7 +85,7 @@ PSI.get_variable_lower_bound(::StorageRegularizationVariable, d::PSY.Storage, :: #! format: on function PSI.variable_cost( - cost::PSY.StorageManagementCost, + cost::PSY.StorageCost, ::StorageRegularizationVariable, ::PSY.Storage, ::AbstractStorageFormulation, From 30a67b5e30c737d565e05d9ce6caa1f425604bf6 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 2 May 2024 11:14:00 -0600 Subject: [PATCH 05/20] change storage function value --- test/test_utils/modify_systems.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_utils/modify_systems.jl b/test/test_utils/modify_systems.jl index 3365403..b0e59b5 100644 --- a/test/test_utils/modify_systems.jl +++ b/test/test_utils/modify_systems.jl @@ -33,7 +33,7 @@ function _build_battery( reactive_power=0.0, reactive_power_limits=nothing, base_power=100.0, - operation_cost=PSY.StorageManagementCost( + operation_cost=PSY.StorageCost( energy_shortage_cost=1000.0, energy_surplus_cost=1000.0, fixed=0.0, From 55f90f0e2b54eef16890096d452eb466bd7c4449 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 16 May 2024 16:42:53 -0600 Subject: [PATCH 06/20] rename storage --- docs/src/index.md | 2 +- docs/src/tutorials/single_stage_model.md | 6 +-- src/core/formulations.jl | 2 +- src/storage_models.jl | 42 ++++++++--------- test/test_storage_device_models.jl | 46 +++++++++---------- test/test_storage_simulation.jl | 30 ++++++------ test/test_utils/modify_systems.jl | 2 +- .../operations_problems_templates.jl | 4 +- 8 files changed, 67 insertions(+), 67 deletions(-) diff --git a/docs/src/index.md b/docs/src/index.md index 4dbaa6f..a283257 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -16,7 +16,7 @@ For example, the formulation `StorageDispatchWithReserves` can be parametrized a ```julia DeviceModel( - StorageType, # E.g. BatteryEMS or GenericStorage + StorageType, # E.g. EnergyReservoirStorage or GenericStorage StorageDispatchWithReserves; attributes=Dict( "reservation" => true, diff --git a/docs/src/tutorials/single_stage_model.md b/docs/src/tutorials/single_stage_model.md index 2c03c41..82cb007 100644 --- a/docs/src/tutorials/single_stage_model.md +++ b/docs/src/tutorials/single_stage_model.md @@ -17,7 +17,7 @@ using HiGHS # solver ## Data !!! note - + `PowerSystemCaseBuilder.jl` is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function `build_system`. For more details visit [PowerSystemCaseBuilder Documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/powersystembuilder/) @@ -33,7 +33,7 @@ set_available!(orcd, false) ``` ```@example op_problem -batt = get_component(BatteryEMS, c_sys5_bat, "Bat2") +batt = get_component(EnergyReservoirStorage, c_sys5_bat, "Bat2") operation_cost = get_operation_cost(batt) ``` @@ -48,7 +48,7 @@ set_device_model!(template_uc, Line, StaticBranch) ```@example op_problem storage_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict( "reservation" => true, diff --git a/src/core/formulations.jl b/src/core/formulations.jl index 9670a9a..d47d772 100644 --- a/src/core/formulations.jl +++ b/src/core/formulations.jl @@ -10,7 +10,7 @@ The formulation supports the following attributes. See Documentation for more de ```julia DeviceModel( - StorageType, # E.g. BatteryEMS or GenericStorage + StorageType, # E.g. EnergyReservoirStorage or GenericStorage StorageDispatchWithReserves; attributes=Dict( "reservation" => true, diff --git a/src/storage_models.jl b/src/storage_models.jl index 71c899d..bbf5099 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -63,10 +63,10 @@ PSI.objective_function_multiplier(::PSI.VariableType, ::AbstractStorageFormulati PSI.objective_function_multiplier(::StorageEnergySurplusVariable, ::AbstractStorageFormulation)=PSI.OBJECTIVE_FUNCTION_POSITIVE PSI.objective_function_multiplier(::StorageEnergyShortageVariable, ::AbstractStorageFormulation)=PSI.OBJECTIVE_FUNCTION_POSITIVE -PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergySurplusVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_surplus_cost(cost) -PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergyShortageVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=PSY.get_energy_shortage_cost(cost) -PSI.proportional_cost(::PSY.StorageCost, ::StorageChargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST -PSI.proportional_cost(::PSY.StorageCost, ::StorageDischargeCyclingSlackVariable, ::PSY.BatteryEMS, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST +PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergySurplusVariable, ::PSY.EnergyReservoirStorage, ::AbstractStorageFormulation)=PSY.get_energy_surplus_cost(cost) +PSI.proportional_cost(cost::PSY.StorageCost, ::StorageEnergyShortageVariable, ::PSY.EnergyReservoirStorage, ::AbstractStorageFormulation)=PSY.get_energy_shortage_cost(cost) +PSI.proportional_cost(::PSY.StorageCost, ::StorageChargeCyclingSlackVariable, ::PSY.EnergyReservoirStorage, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST +PSI.proportional_cost(::PSY.StorageCost, ::StorageDischargeCyclingSlackVariable, ::PSY.EnergyReservoirStorage, ::AbstractStorageFormulation)=CYCLE_VIOLATION_COST PSI.variable_cost(cost::PSY.StorageCost, ::PSI.ActivePowerOutVariable, ::PSY.Storage, ::AbstractStorageFormulation)=PSY.get_discharge_variable_cost(cost) @@ -102,7 +102,7 @@ function PSI.get_default_time_series_names( end function PSI.get_default_attributes( - ::Type{PSY.GenericBattery}, + ::Type{PSY.EnergyReservoirStorage}, ::Type{T}, ) where {T <: AbstractStorageFormulation} return Dict{String, Any}( @@ -115,7 +115,7 @@ function PSI.get_default_attributes( end function PSI.get_default_attributes( - ::Type{PSY.BatteryEMS}, + ::Type{PSY.EnergyReservoirStorage}, ::Type{T}, ) where {T <: AbstractStorageFormulation} return Dict{String, Any}( @@ -1241,8 +1241,8 @@ function PSI.add_constraints!( devices::IS.FlattenIteratorWrapper{V}, model::PSI.DeviceModel{V, StorageDispatchWithReserves}, network_model::PSI.NetworkModel{X}, -) where {V <: PSY.GenericBattery, X <: PM.AbstractPowerModel} - error("$V is not supported for $(PSY.GenericBattery). \ +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} + error("$V is not supported for $(PSY.EnergyReservoirStorage). \ Set the attribute energy_target to false in the device model") return end @@ -1253,8 +1253,8 @@ function PSI.add_constraints!( devices::IS.FlattenIteratorWrapper{V}, model::PSI.DeviceModel{V, StorageDispatchWithReserves}, network_model::PSI.NetworkModel{X}, -) where {V <: PSY.GenericBattery, X <: PM.AbstractPowerModel} - error("$V is not supported for $(PSY.GenericBattery). \ +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} + error("$V is not supported for $(PSY.EnergyReservoirStorage). \ Set the attribute energy_target to false in the device model") return end @@ -1265,7 +1265,7 @@ function PSI.add_constraints!( devices::IS.FlattenIteratorWrapper{V}, model::PSI.DeviceModel{V, StorageDispatchWithReserves}, network_model::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} energy_var = PSI.get_variable(container, PSI.EnergyVariable(), V) surplus_var = PSI.get_variable(container, StorageEnergySurplusVariable(), V) shortfall_var = PSI.get_variable(container, StorageEnergyShortageVariable(), V) @@ -1295,7 +1295,7 @@ function add_cycling_charge_without_reserves!( devices::IS.FlattenIteratorWrapper{V}, ::PSI.DeviceModel{V, StorageDispatchWithReserves}, ::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -1326,7 +1326,7 @@ function add_cycling_charge_with_reserves!( devices::IS.FlattenIteratorWrapper{V}, ::PSI.DeviceModel{V, StorageDispatchWithReserves}, ::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -1361,7 +1361,7 @@ function PSI.add_constraints!( devices::IS.FlattenIteratorWrapper{V}, model::PSI.DeviceModel{V, StorageDispatchWithReserves}, network_model::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} if PSI.has_service_model(model) add_cycling_charge_with_reserves!(container, devices, model, network_model) else @@ -1375,7 +1375,7 @@ function add_cycling_discharge_without_reserves!( devices::IS.FlattenIteratorWrapper{V}, ::PSI.DeviceModel{V, StorageDispatchWithReserves}, ::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -1407,7 +1407,7 @@ function add_cycling_discharge_with_reserves!( devices::IS.FlattenIteratorWrapper{V}, ::PSI.DeviceModel{V, StorageDispatchWithReserves}, ::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -1441,7 +1441,7 @@ function PSI.add_constraints!( devices::IS.FlattenIteratorWrapper{V}, model::PSI.DeviceModel{V, StorageDispatchWithReserves}, network_model::PSI.NetworkModel{X}, -) where {V <: PSY.BatteryEMS, X <: PM.AbstractPowerModel} +) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} if PSI.has_service_model(model) add_cycling_discharge_with_reserves!(container, devices, model, network_model) else @@ -1630,8 +1630,8 @@ end function PSI.objective_function!( container::PSI.OptimizationContainer, - devices::IS.FlattenIteratorWrapper{PSY.BatteryEMS}, - model::PSI.DeviceModel{PSY.BatteryEMS, T}, + devices::IS.FlattenIteratorWrapper{PSY.EnergyReservoirStorage}, + model::PSI.DeviceModel{PSY.EnergyReservoirStorage, T}, ::Type{V}, ) where {T <: AbstractStorageFormulation, V <: PM.AbstractPowerModel} PSI.add_variable_cost!(container, PSI.ActivePowerOutVariable(), devices, T()) @@ -1678,7 +1678,7 @@ function PSI.add_proportional_cost!( formulation::AbstractStorageFormulation, ) where { T <: Union{StorageChargeCyclingSlackVariable, StorageDischargeCyclingSlackVariable}, - U <: PSY.BatteryEMS, + U <: PSY.EnergyReservoirStorage, } variable = PSI.get_variable(container, T(), U) for d in devices @@ -1696,7 +1696,7 @@ function PSI.add_proportional_cost!( formulation::AbstractStorageFormulation, ) where { T <: Union{StorageEnergyShortageVariable, StorageEnergySurplusVariable}, - U <: PSY.BatteryEMS, + U <: PSY.EnergyReservoirStorage, } variable = PSI.get_variable(container, T(), U) for d in devices diff --git a/test/test_storage_device_models.jl b/test/test_storage_device_models.jl index d9964dd..a6ebc7d 100644 --- a/test/test_storage_device_models.jl +++ b/test/test_storage_device_models.jl @@ -1,6 +1,6 @@ @testset "Storage Basic Storage With DC - PF" begin device_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => false, @@ -19,7 +19,7 @@ end @testset "Storage Basic Storage With AC - PF" begin device_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => false, @@ -37,7 +37,7 @@ end end @testset "Storage with Reservation & DC - PF" begin - device_model = DeviceModel(GenericBattery, StorageDispatchWithReserves) + device_model = DeviceModel(EnergyReservoirStorage, StorageDispatchWithReserves) c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat") model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_bat) mock_construct_device!(model, device_model) @@ -46,7 +46,7 @@ end end @testset "Storage with Reservation & AC - PF" begin - device_model = DeviceModel(GenericBattery, StorageDispatchWithReserves) + device_model = DeviceModel(EnergyReservoirStorage, StorageDispatchWithReserves) c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat") model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_bat) mock_construct_device!(model, device_model) @@ -54,9 +54,9 @@ end psi_checkobjfun_test(model, GAEVF) end -@testset "BatteryEMS with EnergyTarget with DC - PF" begin +@testset "EnergyReservoirStorage with EnergyTarget with DC - PF" begin device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -73,7 +73,7 @@ end psi_checkobjfun_test(model, GAEVF) device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => false, @@ -89,9 +89,9 @@ end psi_checkobjfun_test(model, GAEVF) end -@testset "BatteryEMS with EnergyTarget With AC - PF" begin +@testset "EnergyReservoirStorage with EnergyTarget With AC - PF" begin device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -108,7 +108,7 @@ end psi_checkobjfun_test(model, GAEVF) device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => false, @@ -127,9 +127,9 @@ end ### Feedforward Test ### # TODO: Feedforward debugging -@testset "Test EnergyTargetFeedforward to GenericBattery with BookKeeping model" begin +@testset "Test EnergyTargetFeedforward to EnergyReservoirStorage with BookKeeping model" begin device_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -141,7 +141,7 @@ end ) ff_et = EnergyTargetFeedforward(; - component_type=GenericBattery, + component_type=EnergyReservoirStorage, source=EnergyVariable, affected_values=[EnergyVariable], target_period=12, @@ -155,9 +155,9 @@ end moi_tests(model, 122, 0, 72, 73, 24, true) end -@testset "Test EnergyTargetFeedforward to BatteryEMS with BookKeeping model" begin +@testset "Test EnergyTargetFeedforward to EnergyReservoirStorage with BookKeeping model" begin device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -169,7 +169,7 @@ end ) ff_et = EnergyTargetFeedforward(; - component_type=BatteryEMS, + component_type=EnergyReservoirStorage, source=EnergyVariable, affected_values=[EnergyVariable], target_period=12, @@ -184,11 +184,11 @@ end end #= -@testset "Test EnergyLimitFeedforward to GenericBattery with BookKeeping model" begin - device_model = DeviceModel(GenericBattery, BookKeeping) +@testset "Test EnergyLimitFeedforward to EnergyReservoirStorage with BookKeeping model" begin + device_model = DeviceModel(EnergyReservoirStorage, BookKeeping) ff_il = EnergyLimitFeedforward(; - component_type=GenericBattery, + component_type=EnergyReservoirStorage, source=ActivePowerOutVariable, affected_values=[ActivePowerOutVariable], number_of_periods=12, @@ -201,11 +201,11 @@ end moi_tests(model, 121, 0, 74, 72, 24, true) end -@testset "Test EnergyLimitFeedforward to GenericBattery with BatteryAncillaryServices model" begin - device_model = DeviceModel(GenericBattery, BatteryAncillaryServices) +@testset "Test EnergyLimitFeedforward to EnergyReservoirStorage with BatteryAncillaryServices model" begin + device_model = DeviceModel(EnergyReservoirStorage, BatteryAncillaryServices) ff_il = EnergyLimitFeedforward(; - component_type=GenericBattery, + component_type=EnergyReservoirStorage, source=ActivePowerOutVariable, affected_values=[ActivePowerOutVariable], number_of_periods=12, @@ -221,7 +221,7 @@ end # To Fix @testset "Test Reserves from Storage" begin template = get_thermal_dispatch_template_network(CopperPlatePowerModel) - set_device_model!(template, DeviceModel(GenericBattery, BatteryAncillaryServices)) + set_device_model!(template, DeviceModel(EnergyReservoirStorage, BatteryAncillaryServices)) set_device_model!(template, RenewableDispatch, FixedOutput) set_service_model!( template, diff --git a/test/test_storage_simulation.jl b/test/test_storage_simulation.jl index fdeeab5..f04b2c7 100644 --- a/test/test_storage_simulation.jl +++ b/test/test_storage_simulation.jl @@ -2,17 +2,17 @@ ######## Test with BookKeeping ######## template = get_thermal_dispatch_template_network() c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true) - set_device_model!(template, GenericBattery, StorageDispatchWithReserves) + set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT - check_energy_initial_conditions_values(model, GenericBattery) + check_energy_initial_conditions_values(model, EnergyReservoirStorage) @test solve!(model) == RunStatus.SUCCESSFUL ######## Test with EnergyTarget ######## template = get_thermal_dispatch_template_network() c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat_ems"; force_build=true) device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -25,7 +25,7 @@ set_device_model!(template, device_model) model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT - check_energy_initial_conditions_values(model, BatteryEMS) + check_energy_initial_conditions_values(model, EnergyReservoirStorage) @test solve!(model) == RunStatus.SUCCESSFUL end @@ -38,14 +38,14 @@ end add_single_time_series=true, force_build=true, ) - set_device_model!(template, GenericBattery, StorageDispatchWithReserves) + set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = EmulationModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; executions=10, output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), - GenericBattery, + EnergyReservoirStorage, ) for ic in ic_data name = PSY.get_name(ic.component) @@ -62,14 +62,14 @@ end add_single_time_series=true, force_build=true, ) - set_device_model!(template, GenericBattery, StorageDispatchWithReserves) + set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = EmulationModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; executions=10, output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), - GenericBattery, + EnergyReservoirStorage, ) for ic in ic_data name = PSY.get_name(ic.component) @@ -87,7 +87,7 @@ end force_build=true, ) device_model = DeviceModel( - BatteryEMS, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -104,7 +104,7 @@ end ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), - BatteryEMS, + EnergyReservoirStorage, ) for ic in ic_data name = PSY.get_name(ic.component) @@ -114,7 +114,7 @@ end @test run!(model) == RunStatus.SUCCESSFUL end -@testset "Simulation with 2-Stages EnergyLimitFeedforward with GenericBattery" begin +@testset "Simulation with 2-Stages EnergyLimitFeedforward with EnergyReservoirStorage" begin sys_uc = build_system(PSITestSystems, "c_sys5_bat") sys_ed = build_system(PSITestSystems, "c_sys5_bat") @@ -150,7 +150,7 @@ end affected_values=[ActivePowerVariable], ), EnergyLimitFeedforward(; - component_type=GenericBattery, + component_type=EnergyReservoirStorage, source=ActivePowerOutVariable, affected_values=[ActivePowerOutVariable], number_of_periods=12, @@ -177,10 +177,10 @@ end # Test UC Vars are equal to ED params res = SimulationResults(sim_cache) res_ed = res.decision_problem_results["ED"] - param_ed = read_realized_parameter(res_ed, "EnergyLimitParameter__GenericBattery") + param_ed = read_realized_parameter(res_ed, "EnergyLimitParameter__EnergyReservoirStorage") res_uc = res.decision_problem_results["UC"] - p_out_bat = read_realized_variable(res_uc, "ActivePowerOutVariable__GenericBattery") + p_out_bat = read_realized_variable(res_uc, "ActivePowerOutVariable__EnergyReservoirStorage") @test isapprox(param_ed[!, 2], p_out_bat[!, 2] / 100.0; atol=1e-4) end @@ -189,7 +189,7 @@ end c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true) template = get_thermal_dispatch_template_network() storage_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict( "reservation" => false, diff --git a/test/test_utils/modify_systems.jl b/test/test_utils/modify_systems.jl index b0e59b5..d9655c2 100644 --- a/test/test_utils/modify_systems.jl +++ b/test/test_utils/modify_systems.jl @@ -16,7 +16,7 @@ function _build_battery( efficiency_out, ) name = string(bus.number) * "_BATTERY" - device = BatteryEMS(; + device = EnergyReservoirStorage(; name=name, available=true, bus=bus, diff --git a/test/test_utils/operations_problems_templates.jl b/test/test_utils/operations_problems_templates.jl index 1b53ffd..0487723 100644 --- a/test/test_utils/operations_problems_templates.jl +++ b/test/test_utils/operations_problems_templates.jl @@ -80,7 +80,7 @@ function get_template_basic_uc_storage_simulation() set_device_model!(template, RenewableDispatch, RenewableFullDispatch) set_device_model!(template, PowerLoad, StaticPowerLoad) device_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, @@ -100,7 +100,7 @@ function get_template_dispatch_storage_simulation() set_device_model!(template, RenewableDispatch, RenewableFullDispatch) set_device_model!(template, PowerLoad, StaticPowerLoad) device_model = DeviceModel( - GenericBattery, + EnergyReservoirStorage, StorageDispatchWithReserves; attributes=Dict{String, Any}( "reservation" => true, From 1c7a37843664a080b3b5f9638a45e5b95018415a Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Sun, 19 May 2024 19:18:02 -0600 Subject: [PATCH 07/20] remove duplicate methods --- src/storage_models.jl | 37 ------------------------------------- 1 file changed, 37 deletions(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index bbf5099..6773b4d 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -114,19 +114,6 @@ function PSI.get_default_attributes( ) end -function PSI.get_default_attributes( - ::Type{PSY.EnergyReservoirStorage}, - ::Type{T}, -) where {T <: AbstractStorageFormulation} - return Dict{String, Any}( - "reservation" => true, - "cycling_limits" => false, - "energy_target" => false, - "complete_coverage" => false, - "regularization" => false, - ) -end - ######################## Make initial Conditions for a Model #################### PSI.get_initial_conditions_device_model( ::PSI.OperationModel, @@ -1235,30 +1222,6 @@ function PSI.add_constraints!( return end -function PSI.add_constraints!( - ::PSI.OptimizationContainer, - ::Type{StateofChargeTargetConstraint}, - devices::IS.FlattenIteratorWrapper{V}, - model::PSI.DeviceModel{V, StorageDispatchWithReserves}, - network_model::PSI.NetworkModel{X}, -) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} - error("$V is not supported for $(PSY.EnergyReservoirStorage). \ - Set the attribute energy_target to false in the device model") - return -end - -function PSI.add_constraints!( - ::PSI.OptimizationContainer, - ::Type{<:Union{StorageCyclingCharge, StorageCyclingDischarge}}, - devices::IS.FlattenIteratorWrapper{V}, - model::PSI.DeviceModel{V, StorageDispatchWithReserves}, - network_model::PSI.NetworkModel{X}, -) where {V <: PSY.EnergyReservoirStorage, X <: PM.AbstractPowerModel} - error("$V is not supported for $(PSY.EnergyReservoirStorage). \ - Set the attribute energy_target to false in the device model") - return -end - function PSI.add_constraints!( container::PSI.OptimizationContainer, ::Type{StateofChargeTargetConstraint}, From ec865c7b4011b99024dd6cba615367d8506b8099 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 28 May 2024 15:11:20 -0600 Subject: [PATCH 08/20] fix cost function for regularizer --- src/storage_models.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index 6773b4d..e797c77 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -90,8 +90,7 @@ function PSI.variable_cost( ::PSY.Storage, ::AbstractStorageFormulation, ) - max_val = max(REG_COST, PSY.get_proportional_term(PSY.get_variable(cost)) * REG_COST) - return PSY.LinearFunctionData(max_val) + return PSY.CostCurve(PSY.LinearCurve(REG_COST), PSY.UnitSystem.SYSTEM_BASE, 0.0) end function PSI.get_default_time_series_names( From df91345b821adeadce62fc7d0132ef381e6c62e6 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 13 Jun 2024 16:13:10 -0700 Subject: [PATCH 09/20] update storage creation --- test/test_utils/modify_systems.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/test_utils/modify_systems.jl b/test/test_utils/modify_systems.jl index d9655c2..1581e95 100644 --- a/test/test_utils/modify_systems.jl +++ b/test/test_utils/modify_systems.jl @@ -21,8 +21,10 @@ function _build_battery( available=true, bus=bus, prime_mover_type=PSY.PrimeMovers.BA, - initial_energy=0.2, - state_of_charge_limits=(min=energy_capacity * 0.0, max=energy_capacity), + storage_technology_type=PSY.StorageTech.OTHER_CHEM, + storage_capacity=energy_capacity, + storage_level_limits=(min=0.0, max=1.0), + initial_storage_capacity_level=0.2, rating=rating, active_power=rating, cycle_limits=1000.0, From 18bc8f67c482a67f659be78e93ad895cc6d224e2 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 13 Jun 2024 16:13:33 -0700 Subject: [PATCH 10/20] update container key in FF --- src/core/feedforward.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/feedforward.jl b/src/core/feedforward.jl index 6a87110..9a73b2d 100644 --- a/src/core/feedforward.jl +++ b/src/core/feedforward.jl @@ -12,7 +12,7 @@ struct EnergyTargetFeedforward <: PSI.AbstractAffectFeedforward affected_values::Vector{DataType}, target_period::Int, penalty_cost::Float64, - meta=PSI.CONTAINER_KEY_EMPTY_META, + meta=ISOPT.CONTAINER_KEY_EMPTY_META, ) where {T} values_vector = Vector{PSI.VariableKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) @@ -135,7 +135,7 @@ struct EnergyLimitFeedforward <: PSI.AbstractAffectFeedforward source::Type{T}, affected_values::Vector{DataType}, number_of_periods::Int, - meta=PSI.CONTAINER_KEY_EMPTY_META, + meta=ISOPT.CONTAINER_KEY_EMPTY_META, ) where {T} values_vector = Vector{PSI.VariableKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) From dbc7156b45e00cb21245259c6600f4f0f9a6317b Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 13 Jun 2024 16:13:46 -0700 Subject: [PATCH 11/20] add ISOPT --- src/StorageSystemsSimulations.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/src/StorageSystemsSimulations.jl b/src/StorageSystemsSimulations.jl index e882b20..2c054b2 100644 --- a/src/StorageSystemsSimulations.jl +++ b/src/StorageSystemsSimulations.jl @@ -55,6 +55,7 @@ const PSI = PowerSimulations const PSY = PowerSystems const PM = PSI.PM const IS = InfrastructureSystems +const ISOPT = InfrastructureSystems.Optimization using DocStringExtensions @template (FUNCTIONS, METHODS) = """ From b2e2140981c3f93de94f5ae0a95bb52b61c1c07f Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 13 Jun 2024 16:14:02 -0700 Subject: [PATCH 12/20] update bounds with new names --- src/storage_models.jl | 59 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 48 insertions(+), 11 deletions(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index e797c77..cb57c14 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -22,9 +22,9 @@ PSI.get_variable_multiplier(::PSI.ReactivePowerVariable, d::Type{<:PSY.Storage}, ############## EnergyVariable, Storage #################### PSI.get_variable_binary(::PSI.EnergyVariable, ::Type{<:PSY.Storage}, ::AbstractStorageFormulation) = false -PSI.get_variable_upper_bound(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_state_of_charge_limits(d).max -PSI.get_variable_lower_bound(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_state_of_charge_limits(d).min -PSI.get_variable_warm_start_value(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_initial_energy(d) +PSI.get_variable_upper_bound(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_storage_level_limits(d).max * PSY.get_storage_capacity(d) * PSY.get_conversion_factor(d) +PSI.get_variable_lower_bound(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_storage_level_limits(d).min * PSY.get_storage_capacity(d) * PSY.get_conversion_factor(d) +PSI.get_variable_warm_start_value(::PSI.EnergyVariable, d::PSY.Storage, ::AbstractStorageFormulation) = PSY.get_initial_storage_capacity_level(d) * PSY.get_storage_capacity(d) * PSY.get_conversion_factor(d) ############## ReservationVariable, Storage #################### PSI.get_variable_binary(::PSI.ReservationVariable, ::Type{<:PSY.Storage}, ::AbstractStorageFormulation) = true @@ -123,7 +123,10 @@ PSI.initial_condition_default( ::PSI.InitialEnergyLevel, d::PSY.Storage, ::AbstractStorageFormulation, -) = PSY.get_initial_energy(d) +) = + PSY.get_initial_storage_capacity_level(d) * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) PSI.initial_condition_variable( ::PSI.InitialEnergyLevel, d::PSY.Storage, @@ -286,7 +289,15 @@ function PSI.get_min_max_limits( ::Type{StateofChargeLimitsConstraint}, ::Type{<:AbstractStorageFormulation}, ) - return PSY.get_state_of_charge_limits(d) + min_max_limits = ( + min=PSY.get_storage_level_limits(d).min * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d), + max=PSY.get_storage_level_limits(d).max * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d), + ) + return min_max_limits end function PSI.add_constraints!( @@ -936,7 +947,14 @@ function PSI.add_constraints!( ci_name = PSY.get_name(storage) inv_efficiency = 1.0 / PSY.get_efficiency(storage).out eff_in = PSY.get_efficiency(storage).in - soc_limits = PSY.get_state_of_charge_limits(storage) + soc_limits = ( + min=PSY.get_storage_level_limits(storage).min * + PSY.get_storage_capacity(storage) * + PSY.get_conversion_factor(storage), + max=PSY.get_storage_level_limits(storage).max * + PSY.get_storage_capacity(storage) * + PSY.get_conversion_factor(storage), + ) for service in PSY.get_services(storage) sustained_time = PSY.get_sustained_time(service) num_periods = sustained_time / Dates.value(Dates.Second(resolution)) @@ -1078,7 +1096,14 @@ function PSI.add_constraints!( ci_name = PSY.get_name(storage) inv_efficiency = 1.0 / PSY.get_efficiency(storage).out eff_in = PSY.get_efficiency(storage).in - soc_limits = PSY.get_state_of_charge_limits(storage) + soc_limits = ( + min=PSY.get_storage_level_limits(storage).min * + PSY.get_storage_capacity(storage) * + PSY.get_conversion_factor(storage), + max=PSY.get_storage_level_limits(storage).max * + PSY.get_storage_capacity(storage) * + PSY.get_conversion_factor(storage), + ) expr_up_discharge = Set() expr_dn_charge = Set() for service in PSY.get_services(storage) @@ -1270,7 +1295,10 @@ function add_cycling_charge_without_reserves!( for d in devices name = PSY.get_name(d) - e_max = PSY.get_state_of_charge_limits(d).max + e_max = + PSY.get_storage_level_limits(d).max * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) cycle_count = PSY.get_cycle_limits(d) efficiency = PSY.get_efficiency(d) constraint[name] = JuMP.@constraint( @@ -1302,7 +1330,10 @@ function add_cycling_charge_with_reserves!( for d in devices name = PSY.get_name(d) - e_max = PSY.get_state_of_charge_limits(d).max + e_max = + PSY.get_storage_level_limits(d).max * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) cycle_count = PSY.get_cycle_limits(d) efficiency = PSY.get_efficiency(d) constraint[name] = JuMP.@constraint( @@ -1350,7 +1381,10 @@ function add_cycling_discharge_without_reserves!( for d in devices name = PSY.get_name(d) - e_max = PSY.get_state_of_charge_limits(d).max + e_max = + PSY.get_storage_level_limits(d).max * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) cycle_count = PSY.get_cycle_limits(d) efficiency = PSY.get_efficiency(d) constraint[name] = JuMP.@constraint( @@ -1383,7 +1417,10 @@ function add_cycling_discharge_with_reserves!( for d in devices name = PSY.get_name(d) - e_max = PSY.get_state_of_charge_limits(d).max + e_max = + PSY.get_storage_level_limits(d).max * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) cycle_count = PSY.get_cycle_limits(d) efficiency = PSY.get_efficiency(d) constraint[name] = JuMP.@constraint( From 8ce819fe53317672c0a82e5d44171b8a722d2464 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Thu, 13 Jun 2024 16:14:15 -0700 Subject: [PATCH 13/20] update tests --- test/test_storage_device_models.jl | 2 +- test/test_storage_simulation.jl | 53 ++++++++++++++---------- test/test_utils/mock_operation_models.jl | 6 +++ test/test_utils/model_checks.jl | 13 +++--- 4 files changed, 47 insertions(+), 27 deletions(-) diff --git a/test/test_storage_device_models.jl b/test/test_storage_device_models.jl index a6ebc7d..a92098d 100644 --- a/test/test_storage_device_models.jl +++ b/test/test_storage_device_models.jl @@ -127,7 +127,7 @@ end ### Feedforward Test ### # TODO: Feedforward debugging -@testset "Test EnergyTargetFeedforward to EnergyReservoirStorage with BookKeeping model" begin +@testset "Test EnergyTargetFeedforward to EnergyReservoirStorage with StorageDispatch model" begin device_model = DeviceModel( EnergyReservoirStorage, StorageDispatchWithReserves; diff --git a/test/test_storage_simulation.jl b/test/test_storage_simulation.jl index f04b2c7..7c7b6a4 100644 --- a/test/test_storage_simulation.jl +++ b/test/test_storage_simulation.jl @@ -4,9 +4,9 @@ c_sys5_bat = PSB.build_system(PSITestSystems, "c_sys5_bat"; force_build=true) set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) - @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT + @test build!(model; output_dir=mktempdir(; cleanup=true)) == PSI.ModelBuildStatus.BUILT check_energy_initial_conditions_values(model, EnergyReservoirStorage) - @test solve!(model) == RunStatus.SUCCESSFUL + @test solve!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with EnergyTarget ######## template = get_thermal_dispatch_template_network() @@ -24,9 +24,9 @@ ) set_device_model!(template, device_model) model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) - @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT + @test build!(model; output_dir=mktempdir(; cleanup=true)) == PSI.ModelBuildStatus.BUILT check_energy_initial_conditions_values(model, EnergyReservoirStorage) - @test solve!(model) == RunStatus.SUCCESSFUL + @test solve!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED end @testset "Emulation Model initial_conditions test for Storage" begin @@ -41,18 +41,21 @@ end set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = EmulationModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; executions=10, output_dir=mktempdir(; cleanup=true)) == - BuildStatus.BUILT + PSI.ModelBuildStatus.BUILT ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), EnergyReservoirStorage, ) for ic in ic_data - name = PSY.get_name(ic.component) + d = ic.component + name = PSY.get_name(d) e_var = PSI.jump_value(PSI.get_value(ic)) - @test PSY.get_initial_energy(ic.component) == e_var + @test PSY.get_initial_storage_capacity_level(d) * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) == e_var end - @test run!(model) == RunStatus.SUCCESSFUL + @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with BatteryAncillaryServices ######## template = get_thermal_dispatch_template_network() @@ -65,18 +68,21 @@ end set_device_model!(template, EnergyReservoirStorage, StorageDispatchWithReserves) model = EmulationModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; executions=10, output_dir=mktempdir(; cleanup=true)) == - BuildStatus.BUILT + PSI.ModelBuildStatus.BUILT ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), EnergyReservoirStorage, ) for ic in ic_data - name = PSY.get_name(ic.component) + d = ic.component + name = PSY.get_name(d) e_var = PSI.jump_value(PSI.get_value(ic)) - @test PSY.get_initial_energy(ic.component) == e_var + @test PSY.get_initial_storage_capacity_level(d) * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) == e_var end - @test run!(model) == RunStatus.SUCCESSFUL + @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED ######## Test with EnergyTarget ######## template = get_thermal_dispatch_template_network() @@ -100,18 +106,21 @@ end set_device_model!(template, device_model) model = EmulationModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) @test build!(model; executions=10, output_dir=mktempdir(; cleanup=true)) == - BuildStatus.BUILT + PSI.ModelBuildStatus.BUILT ic_data = PSI.get_initial_condition( PSI.get_optimization_container(model), InitialEnergyLevel(), EnergyReservoirStorage, ) for ic in ic_data - name = PSY.get_name(ic.component) + d = ic.component + name = PSY.get_name(d) e_var = PSI.jump_value(PSI.get_value(ic)) - @test PSY.get_initial_energy(ic.component) == e_var + @test PSY.get_initial_storage_capacity_level(d) * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) == e_var end - @test run!(model) == RunStatus.SUCCESSFUL + @test run!(model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED end @testset "Simulation with 2-Stages EnergyLimitFeedforward with EnergyReservoirStorage" begin @@ -169,18 +178,20 @@ end ) build_out = build!(sim_cache) - @test build_out == PSI.BuildStatus.BUILT + @test build_out == PSI.SimulationBuildStatus.BUILT execute_out = execute!(sim_cache) - @test execute_out == PSI.RunStatus.SUCCESSFUL + @test execute_out == PSI.RunStatus.SUCCESSFULLY_FINALIZED # Test UC Vars are equal to ED params res = SimulationResults(sim_cache) res_ed = res.decision_problem_results["ED"] - param_ed = read_realized_parameter(res_ed, "EnergyLimitParameter__EnergyReservoirStorage") + param_ed = + read_realized_parameter(res_ed, "EnergyLimitParameter__EnergyReservoirStorage") res_uc = res.decision_problem_results["UC"] - p_out_bat = read_realized_variable(res_uc, "ActivePowerOutVariable__EnergyReservoirStorage") + p_out_bat = + read_realized_variable(res_uc, "ActivePowerOutVariable__EnergyReservoirStorage") @test isapprox(param_ed[!, 2], p_out_bat[!, 2] / 100.0; atol=1e-4) end @@ -201,5 +212,5 @@ end ) set_device_model!(template, storage_model) model = DecisionModel(template, c_sys5_bat; optimizer=HiGHS_optimizer) - @test build!(model; output_dir=mktempdir(; cleanup=true)) == BuildStatus.BUILT + @test build!(model; output_dir=mktempdir(; cleanup=true)) == PSI.ModelBuildStatus.BUILT end diff --git a/test/test_utils/mock_operation_models.jl b/test/test_utils/mock_operation_models.jl index 9d996a6..063021f 100644 --- a/test/test_utils/mock_operation_models.jl +++ b/test/test_utils/mock_operation_models.jl @@ -103,6 +103,12 @@ function mock_construct_device!( set_device_model!(problem.template, model) template = PSI.get_template(problem) PSI.finalize_template!(template, PSI.get_system(problem)) + settings = PSI.get_settings(problem) + PSI.set_resolution!( + settings, + first(PSY.get_time_series_resolutions(PSI.get_system(problem))), + ) + PSI.set_horizon!(settings, PSY.get_forecast_horizon(PSI.get_system(problem))) PSI.init_optimization_container!( PSI.get_optimization_container(problem), PSI.get_network_model(template), diff --git a/test/test_utils/model_checks.jl b/test/test_utils/model_checks.jl index 23b2fcc..90adca5 100644 --- a/test/test_utils/model_checks.jl +++ b/test/test_utils/model_checks.jl @@ -90,7 +90,7 @@ function psi_checksolve_test(model::DecisionModel, status, expected_result, tol= @test isapprox(obj_value, expected_result, atol=tol) end -function psi_ptdf_lmps(res::ProblemResults, ptdf) +function psi_ptdf_lmps(res::OptimizationProblemResults, ptdf) cp_duals = read_dual(res, PSI.ConstraintKey(CopperPlateBalanceConstraint, PSY.System)) λ = Matrix{Float64}(cp_duals[:, propertynames(cp_duals) .!= :DateTime]) @@ -304,9 +304,12 @@ function check_energy_initial_conditions_values(model, ::Type{T}) where {T <: PS T, ) for ic in ic_data - name = PSY.get_name(ic.component) + d = ic.component + name = PSY.get_name(d) e_value = PSI.jump_value(PSI.get_value(ic)) - @test PSY.get_initial_energy(ic.component) == e_value + @test PSY.get_initial_storage_capacity_level(d) * + PSY.get_storage_capacity(d) * + PSY.get_conversion_factor(d) == e_value end end @@ -414,7 +417,7 @@ function check_initialization_constraint_count( ::S, ::Type{T}; filter_func=PSY.get_available, - meta=PSI.CONTAINER_KEY_EMPTY_META, + meta=ISOPT.CONTAINER_KEY_EMPTY_META, ) where {S <: PSI.ConstraintType, T <: PSY.Component} container = model.internal.ic_model_container no_component = length(PSY.get_components(filter_func, T, model.sys)) @@ -428,7 +431,7 @@ function check_constraint_count( ::S, ::Type{T}; filter_func=PSY.get_available, - meta=PSI.CONTAINER_KEY_EMPTY_META, + meta=ISOPT.CONTAINER_KEY_EMPTY_META, ) where {S <: PSI.ConstraintType, T <: PSY.Component} no_component = length(PSY.get_components(filter_func, T, model.sys)) time_steps = PSI.get_time_steps(PSI.get_optimization_container(model))[end] From 4866a14e05081446b034bf752eb35c12a052ed43 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 26 Jun 2024 09:49:50 -0600 Subject: [PATCH 14/20] bump versions --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index 3757555..d66a044 100644 --- a/Project.toml +++ b/Project.toml @@ -18,10 +18,10 @@ PowerSystems = "bcd98974-b02a-5e2f-9ee0-a103f5c450dd" Dates = "1" DataStructures = "~0.18" DocStringExtensions = "~0.8, ~0.9" -InfrastructureSystems = "1" +InfrastructureSystems = "2" JuMP = "1" LinearAlgebra = "1" MathOptInterface = "1" PowerSimulations = "^0.27" -PowerSystems = "3" +PowerSystems = "4" julia = "^1.6" From b32d7c56565add52b3bf2dc9676244aa77e8c945 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 28 Jun 2024 10:55:07 -0600 Subject: [PATCH 15/20] Update docs/src/tutorials/single_stage_model.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- docs/src/tutorials/single_stage_model.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/tutorials/single_stage_model.md b/docs/src/tutorials/single_stage_model.md index 82cb007..641bf6e 100644 --- a/docs/src/tutorials/single_stage_model.md +++ b/docs/src/tutorials/single_stage_model.md @@ -17,7 +17,7 @@ using HiGHS # solver ## Data !!! note - + `PowerSystemCaseBuilder.jl` is a helper library that makes it easier to reproduce examples in the documentation and tutorials. Normally you would pass your local files to create the system data instead of calling the function `build_system`. For more details visit [PowerSystemCaseBuilder Documentation](https://nrel-sienna.github.io/PowerSystems.jl/stable/tutorials/powersystembuilder/) From f27d815e875706623deed493adac0dd318057089 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 28 Jun 2024 10:56:52 -0600 Subject: [PATCH 16/20] Update Project.toml --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index d66a044..b7f1c9c 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "StorageSystemsSimulations" uuid = "e2f1a126-19d0-4674-9252-42b2384f8e3c" authors = ["Jose Daniel Lara, Rodrigo Henriquez-Auba, Sourabh Dalvi"] -version = "0.9.0" +version = "0.10.0" [deps] DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" @@ -22,6 +22,6 @@ InfrastructureSystems = "2" JuMP = "1" LinearAlgebra = "1" MathOptInterface = "1" -PowerSimulations = "^0.27" +PowerSimulations = "^0.28" PowerSystems = "4" julia = "^1.6" From 7017a258f2613c6eebc9c97905d95175cfc0a5e2 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 28 Jun 2024 10:57:17 -0600 Subject: [PATCH 17/20] Update index.md --- docs/src/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/index.md b/docs/src/index.md index a283257..a8a39d9 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -16,7 +16,7 @@ For example, the formulation `StorageDispatchWithReserves` can be parametrized a ```julia DeviceModel( - StorageType, # E.g. EnergyReservoirStorage or GenericStorage + StorageType, # E.g. EnergyReservoirStorage StorageDispatchWithReserves; attributes=Dict( "reservation" => true, From 509147b8c9c0a949ae04960b51399431af106925 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 28 Jun 2024 11:16:24 -0600 Subject: [PATCH 18/20] Update Project.toml --- docs/Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Project.toml b/docs/Project.toml index f6b0acf..3094652 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -13,4 +13,4 @@ StorageSystemsSimulations = "e2f1a126-19d0-4674-9252-42b2384f8e3c" [compat] Documenter = "1" -InfrastructureSystems = "1" +InfrastructureSystems = "2" From cd03095c7055a20be232d1a7f49275d668541421 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 28 Jun 2024 11:22:23 -0600 Subject: [PATCH 19/20] one more fix --- src/storage_models.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/storage_models.jl b/src/storage_models.jl index cb57c14..15da470 100644 --- a/src/storage_models.jl +++ b/src/storage_models.jl @@ -90,7 +90,7 @@ function PSI.variable_cost( ::PSY.Storage, ::AbstractStorageFormulation, ) - return PSY.CostCurve(PSY.LinearCurve(REG_COST), PSY.UnitSystem.SYSTEM_BASE, 0.0) + return PSY.CostCurve(PSY.LinearCurve(REG_COST), PSY.UnitSystem.SYSTEM_BASE) end function PSI.get_default_time_series_names( From 2974384e614da4b3beb59dc56d831459ab8716a0 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 28 Jun 2024 10:33:24 -0700 Subject: [PATCH 20/20] update docs --- .../StorageDispatchWithReserves.md | 12 ++++++------ docs/src/index.md | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/src/formulation_library/StorageDispatchWithReserves.md b/docs/src/formulation_library/StorageDispatchWithReserves.md index 942ba91..fa6b319 100644 --- a/docs/src/formulation_library/StorageDispatchWithReserves.md +++ b/docs/src/formulation_library/StorageDispatchWithReserves.md @@ -6,17 +6,17 @@ StorageDispatchWithReserves ## Attributes - - `"reservation"`: Forces the battery to operate exclusively on charge or discharge mode through the entire operation interval. We recommend setting this to false for models with relatively large resolutions (e.g., 1-Hr) since the storage can take simultaneous charge or discharge positions on average over the period. - - `"cycling_limits"`: This limits the battery's energy cycling. The calculation uses the total energy charge/discharge and the number of cycles. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageChargeCyclingSlackVariable`](@ref) and [`StorageDischargeCyclingSlackVariable`](@ref) are included in the model if `use_slacks` is set to `true`. - - `"energy_target"`: Set a target at the end of the model horizon for the state of charge. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageEnergyShortageVariable`](@ref) and [`StorageEnergySurplusVariable`](@ref) are included in the model if `use_slacks` is set to `true`. + - `"reservation"`: Forces the storage to operate exclusively on charge or discharge mode through the entire operation interval. We recommend setting this to false for models with relatively longer time resolutions (e.g., 1-Hr) since the storage can take simultaneous charge or discharge positions on average over the period. + - `"cycling_limits"`: This limits the storage's energy cycling. A single charging (discharging) cycle is fully charging (discharging) the storage once. The calculation uses the total energy charge/discharge and the number of cycles. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageChargeCyclingSlackVariable`](@ref) and [`StorageDischargeCyclingSlackVariable`](@ref) are included in the model if `use_slacks` is set to `true`. + - `"energy_target"`: Set a target at the end of the model horizon for the storage's state of charge. Currently, the formulation only supports a fixed value per operation period. Additional variables for [`StorageEnergyShortageVariable`](@ref) and [`StorageEnergySurplusVariable`](@ref) are included in the model if `use_slacks` is set to `true`. !!! warning - Combining the cycle limits and energy target attributes is not recommended. Both - attributes impose constraints on the energy; there is no guarantee that the constraints can be satisfied simultaneously. + Combining cycle limits and energy target attributes is not recommended. Both + attributes impose constraints on energy. There is no guarantee that the constraints can be satisfied simultaneously. - `"complete_coverage"`: This attribute implements constraints that require the battery to cover the sum of all the ancillary services it participates in simultaneously. It is equivalent to holding energy in case all the services get deployed simultaneously. This constraint is added to the constraints that cover each service independently and corresponds to a more conservative operation regime. - - `"regularization"`: This attribute smooths the charge/discharge profiles to avoid bang-bang solutions via a penalty on the absolute value of the intra-temporal variations of the charge and discharge power. The model can stall in models with large amounts of curtailment or long periods with negative or zero prices due to numerical degeneracy. The regularization term is scaled by the power limits to normalize the term and avoid additional penalties to larger storage units. + - `"regularization"`: This attribute smooths the charge/discharge profiles to avoid bang-bang solutions via a penalty on the absolute value of the intra-temporal variations of the charge and discharge power. Solving for optimal storage dispatch can stall in models with large amounts of curtailment or long periods with negative or zero prices due to numerical degeneracy. The regularization term is scaled by the storage device's power limits to normalize the term and avoid additional penalties to larger storage units. !!! danger diff --git a/docs/src/index.md b/docs/src/index.md index a8a39d9..047c6e2 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -7,9 +7,9 @@ CurrentModule = StorageSystemsSimulations ## Overview `StorageSimulations.jl` is a `PowerSimulations.jl` extension to support formulations and models -related to energ storage. +related to energy storage. -Operational Storage Model can have multiple combinations of different restrictions. For instance, +An Operational Storage Model can have multiple combinations of different restrictions. For instance, it might be relevant to a study to consider cycling limits or employ energy targets coming from a planning model. To manage all these variations `StorageSimulations.jl` heavily uses the `DeviceModel` attributes feature. For example, the formulation `StorageDispatchWithReserves` can be parametrized as follows: