From a3d7c896ddbd92df4eb8a5daf10eba51e9ef498e Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Fri, 10 Apr 2026 16:30:39 -0400 Subject: [PATCH 1/8] added bin2 hydro formulation todo: compare ipopt and highs results --- src/PowerOperationsModels.jl | 1 + src/core/formulations.jl | 5 ++ .../hydro_generation.jl | 67 ++++++++++++++++++- .../hydrogeneration_constructor.jl | 4 +- test/test_device_hydro_constructors.jl | 60 +++++++++++++++++ 5 files changed, 134 insertions(+), 3 deletions(-) diff --git a/src/PowerOperationsModels.jl b/src/PowerOperationsModels.jl index e24ea91..4c28538 100644 --- a/src/PowerOperationsModels.jl +++ b/src/PowerOperationsModels.jl @@ -528,6 +528,7 @@ export HydroWaterFactorModel export HydroWaterModelReservoir export HydroTurbineBilinearDispatch export HydroTurbineWaterLinearDispatch +export HydroTurbineBin2BilinearDispatch export HydroEnergyModelReservoir export HydroTurbineEnergyDispatch export HydroTurbineEnergyCommitment diff --git a/src/core/formulations.jl b/src/core/formulations.jl index f212c1a..25700ad 100644 --- a/src/core/formulations.jl +++ b/src/core/formulations.jl @@ -321,6 +321,11 @@ Formulation type to add injection variables for a HydroTurbine connected to rese """ struct HydroTurbineBilinearDispatch <: AbstractHydroDispatchFormulation end +""" +Formulation type to add injection variables for a HydroTurbine connected to reservoirs using a bilinear model (with water flow variables) [`PowerSystems.HydroGen`](@extref). Uses a linearized approximation. +""" +struct HydroTurbineBin2BilinearDispatch <: AbstractHydroDispatchFormulation end + """ Formulation type to add injection variables for a HydroTurbine connected to reservoirs using a linear model [`PowerSystems.HydroGen`](@extref). The model assumes a shallow reservoir. The head for the conversion between water flow and power can be approximated as a linear function of the water flow on which the head elevation is always the intake elevation. diff --git a/src/static_injector_models/hydro_generation.jl b/src/static_injector_models/hydro_generation.jl index 9603d19..81dabf9 100644 --- a/src/static_injector_models/hydro_generation.jl +++ b/src/static_injector_models/hydro_generation.jl @@ -488,7 +488,7 @@ function add_variables!( T <: HydroTurbineFlowRateVariable, U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, W <: Union{Vector{E}, IS.FlattenIteratorWrapper{E}}, - X <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch}, + X <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, } where { D <: PSY.HydroTurbine, E <: PSY.HydroReservoir, @@ -1835,6 +1835,71 @@ function add_constraints!( return end +""" +This function define the relationship between turbined flow and power produced with a linear approximation for the bilinear product. +""" +function add_constraints!( + container::OptimizationContainer, + sys::PSY.System, + ::Type{TurbinePowerOutputConstraint}, + devices::IS.FlattenIteratorWrapper{V}, + model::DeviceModel{V, W}, + ::NetworkModel{X}, +) where { + V <: PSY.HydroTurbine, + W <: HydroTurbineBin2BilinearDispatch, + X <: PM.AbstractPowerModel, +} + time_steps = get_time_steps(container) + base_power = get_base_power(container) + names = PSY.get_name.(devices) + constraint = + add_constraints_container!( + container, + TurbinePowerOutputConstraint(), + V, + names, + time_steps, + ) + power = get_variable(container, ActivePowerVariable(), V) + flow = get_variable(container, HydroTurbineFlowRateVariable(), V) + head = get_variable(container, HydroReservoirHeadVariable(), PSY.HydroReservoir) + + hf_prod = IOM._add_bilinear_approx!( + IOM.Bin2Config(IOM.SolverSOS2QuadConfig(4)), + container, + V, + names, + time_steps, + head, + flow, + get_variable_lower_bound(HydroTurbineFlowRateVariable(), V, W), + get_variable_upper_bound(HydroTurbineFlowRateVariable(), V, W), + get_variable_lower_bound(HydroReservoirHeadVariable(), V, W), + get_variable_upper_bound(HydroReservoirHeadVariable(), V, W), + ) + + for d in devices + name = PSY.get_name(d) + conversion_factor = PSY.get_conversion_factor(d) + reservoirs = filter(PSY.get_available, PSY.get_connected_head_reservoirs(sys, d)) + powerhouse_elevation = PSY.get_powerhouse_elevation(d) + for t in time_steps + constraint[name, t] = JuMP.@constraint( + container.JuMPmodel, + power[name, t] == + GRAVITATIONAL_CONSTANT * WATER_DENSITY * conversion_factor * + sum( + hf_prod[PSY.get_name(res), t] - + powerhouse_elevation * flow[name, PSY.get_name(res), t] + for res in reservoirs + ) / (1e6 * base_power) + ) + end + end + return +end + ############################################################################ ############################### Expressions ################################ ############################################################################ diff --git a/src/static_injector_models/hydrogeneration_constructor.jl b/src/static_injector_models/hydrogeneration_constructor.jl index c27e5e2..b7ae1f2 100644 --- a/src/static_injector_models/hydrogeneration_constructor.jl +++ b/src/static_injector_models/hydrogeneration_constructor.jl @@ -1809,7 +1809,7 @@ function construct_device!( network_model::NetworkModel{S}, ) where { H <: PSY.HydroTurbine, - D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch}, + D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, S <: AbstractActivePowerModel, } devices = get_available_components(model, sys) @@ -1873,7 +1873,7 @@ function construct_device!( network_model::NetworkModel{S}, ) where { H <: PSY.HydroTurbine, - D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch}, + D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, S <: AbstractActivePowerModel, } devices = get_available_components(model, sys) diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index 1b56ca5..2bdcdf3 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -692,6 +692,66 @@ end ) end +@testset "Solve HydroWaterModelReservoir with bilinear approximations" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_head") + reservoir = only(get_components(HydroReservoir, c_sys5_hy)) + hydro_inflow_ts = get_time_series_array(Deterministic, reservoir, "inflow") + + template = OperationsProblemTemplate() + set_device_model!(template, HydroTurbine, HydroTurbineBin2BilinearDispatch) + set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + + model = DecisionModel( + template, + c_sys5_hy; + optimizer = HiGHS_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + + moi_tests(model, 288, 0, 168, 168, 72, false) + psi_checkobjfun_test(model, AffExpr) + + df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") + hydro_vol_df = + read_variables(outputs, [(HydroReservoirVolumeVariable, HydroReservoir)])["HydroReservoirVolumeVariable__HydroReservoir"] + hydro_head_df = + read_variables(outputs, [(HydroReservoirHeadVariable, HydroReservoir)])["HydroReservoirHeadVariable__HydroReservoir"] + hydro_spillage_df = + read_variables(outputs, [(WaterSpillageVariable, HydroReservoir)])["WaterSpillageVariable__HydroReservoir"] + hydro_inflow_df = + read_parameters(outputs, [(InflowTimeSeriesParameter, HydroReservoir)])["InflowTimeSeriesParameter__HydroReservoir"] + + total_inflow = sum(values(hydro_inflow_ts)) + total_outflow = sum(df_outflow[!, :value]) + total_spillage = sum(hydro_spillage_df[!, :value]) + + calculated_vf = + (hydro_vol_df[1, :value]) + + ((total_inflow - total_outflow - total_spillage) * 3600 * 1e-9) + + @test abs(calculated_vf - hydro_vol_df[end, :value]) <= 1e-4 + + psi_checksolve_test( + model, + [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED], + 210949.49, + 1000, + ) +end + @testset "Solve HydroWaterModelReservoir with Budget" begin sys = PSB.build_system( PSITestSystems, From 3294e395f0c8fc2270e32e2874b718dc131f0ad6 Mon Sep 17 00:00:00 2001 From: Luke Kiernan Date: Fri, 10 Apr 2026 15:30:04 -0600 Subject: [PATCH 2/8] Update POM to work with IOM main - Port serialize_problem from IOM (removed there in deduplicate-opt-container) - Adapt to _check_branch_network_compatibility signature change (3-arg to 2-arg) - Remove system_to_file kwarg from tests (removed from IOM Settings) - Trim deserialization test (deserialize_problem not yet ported) - Bump PNM compat to ^0.19 to match IOM - Point test env at local IOM Co-Authored-By: Claude Opus 4.6 (1M context) --- Project.toml | 3 +- src/PowerOperationsModels.jl | 1 + .../instantiate_network_model.jl | 33 ++++++++-- .../operation_model_serialization.jl | 66 +++++++++++++++++++ test/Project.toml | 1 - test/performance/performance_test.jl | 2 - test/test_device_branch_constructors.jl | 5 -- test/test_device_hydro_constructors.jl | 3 - test/test_device_load_constructors.jl | 3 - ..._device_thermal_generation_constructors.jl | 7 -- test/test_model_decision.jl | 20 +----- test/test_utils/run_simulation.jl | 3 - 12 files changed, 99 insertions(+), 48 deletions(-) create mode 100644 src/operation/operation_model_serialization.jl diff --git a/Project.toml b/Project.toml index 15c0a8f..87185af 100644 --- a/Project.toml +++ b/Project.toml @@ -28,7 +28,6 @@ PowerFlowsExt = "PowerFlows" [sources] InfrastructureSystems = {url = "https://github.com/NREL-Sienna/InfrastructureSystems.jl", rev = "IS4"} -InfrastructureOptimizationModels = {url = "https://github.com/NREL-Sienna/InfrastructureOptimizationModels.jl", rev = "main"} [compat] Dates = "1" @@ -37,7 +36,7 @@ InfrastructureOptimizationModels = "0.1" InfrastructureSystems = "3" InteractiveUtils = "1.11.0" JuMP = "^1.28" -PowerNetworkMatrices = "^0.18" +PowerNetworkMatrices = "^0.19" PowerSystems = "5.3" ProgressMeter = "1.11.0" TimerOutputs = "~0.5" diff --git a/src/PowerOperationsModels.jl b/src/PowerOperationsModels.jl index e24ea91..859a100 100644 --- a/src/PowerOperationsModels.jl +++ b/src/PowerOperationsModels.jl @@ -280,6 +280,7 @@ include("area_interchange.jl") # Operation lifecycle: build/solve/run include("operation/build_problem.jl") include("initial_conditions/initialization.jl") +include("operation/operation_model_serialization.jl") include("operation/decision_model.jl") include("operation/emulation_model.jl") diff --git a/src/network_models/instantiate_network_model.jl b/src/network_models/instantiate_network_model.jl index 5a881b1..eb15ae7 100644 --- a/src/network_models/instantiate_network_model.jl +++ b/src/network_models/instantiate_network_model.jl @@ -66,6 +66,29 @@ _assign_subnetworks_to_buses( ::PSY.System, ) where {T <: AbstractPowerModel} = nothing +function _get_unmodeled_branch_types( + branch_models::BranchModelContainer, + sys::PSY.System, +) + unmodeled = DataType[] + for d in PSY.get_existing_device_types(sys) + if d <: PSY.ACTransmission && !haskey(branch_models, Symbol(d)) + push!(unmodeled, d) + end + end + return unmodeled +end + +function _validate_network_and_branches( + model::NetworkModel, + branch_models::BranchModelContainer, + sys::PSY.System, +) + unmodeled = _get_unmodeled_branch_types(branch_models, sys) + IOM._check_branch_network_compatibility(model, unmodeled) + return +end + ################################################################################# # Generic fallback for AbstractPowerModel (Ybus-based models: ACP, ACR, etc.) ################################################################################# @@ -76,7 +99,7 @@ function IOM.instantiate_network_model!( number_of_steps::Int, sys::PSY.System, ) where {T <: AbstractPowerModel} - IOM._check_branch_network_compatibility(model, branch_models, sys) + _validate_network_and_branches(model, branch_models, sys) if isempty(model.subnetworks) model.subnetworks = PNM.find_subnetworks(sys) end @@ -127,7 +150,7 @@ function IOM.instantiate_network_model!( number_of_steps::Int, sys::PSY.System, ) - IOM._check_branch_network_compatibility(model, branch_models, sys) + _validate_network_and_branches(model, branch_models, sys) PNM.populate_branch_maps_by_type!(model.network_reduction) empty!(model.reduced_branch_tracker) IOM.set_number_of_steps!(model.reduced_branch_tracker, number_of_steps) @@ -144,7 +167,7 @@ function IOM.instantiate_network_model!( number_of_steps::Int, sys::PSY.System, ) - IOM._check_branch_network_compatibility(model, branch_models, sys) + _validate_network_and_branches(model, branch_models, sys) if isempty(model.subnetworks) model.subnetworks = PNM.find_subnetworks(sys) end @@ -168,7 +191,7 @@ function IOM.instantiate_network_model!( number_of_steps::Int, sys::PSY.System, ) - IOM._check_branch_network_compatibility(model, branch_models, sys) + _validate_network_and_branches(model, branch_models, sys) if IOM.get_PTDF_matrix(model) === nothing @info "PTDF Matrix not provided. Calculating using PowerNetworkMatrices.PTDF" if model.reduce_radial_branches && model.reduce_degree_two_branches @@ -258,7 +281,7 @@ function IOM.instantiate_network_model!( number_of_steps::Int, sys::PSY.System, ) - IOM._check_branch_network_compatibility(model, branch_models, sys) + _validate_network_and_branches(model, branch_models, sys) if IOM.get_PTDF_matrix(model) === nothing @info "PTDF Matrix not provided. Calculating using PowerNetworkMatrices.PTDF" if model.reduce_radial_branches && model.reduce_degree_two_branches diff --git a/src/operation/operation_model_serialization.jl b/src/operation/operation_model_serialization.jl new file mode 100644 index 0000000..e948491 --- /dev/null +++ b/src/operation/operation_model_serialization.jl @@ -0,0 +1,66 @@ +const _SERIALIZED_MODEL_FILENAME = "model.bin" + +struct OptimizerAttributes + name::String + version::String + attributes::Any +end + +function OptimizerAttributes( + model::IOM.OperationModel, + optimizer::IOM.MOI.OptimizerWithAttributes, +) + jump_model = IOM.get_jump_model(model) + name = JuMP.solver_name(jump_model) + # Note that this uses private field access to MOI.OptimizerWithAttributes because there + # is no public method available. + # This could break if MOI changes their implementation. + try + version = IOM.MOI.get(JuMP.backend(jump_model), IOM.MOI.SolverVersion()) + return OptimizerAttributes(name, version, optimizer.params) + catch + @debug "Solver Version not supported by the solver" + version = "MOI.SolverVersion not supported" + return OptimizerAttributes(name, version, optimizer.params) + end +end + +function _get_optimizer_attributes(model::IOM.OperationModel) + return IOM.get_optimizer(IOM.get_settings(model)).params +end + +struct ProblemSerializationWrapper + template::IOM.AbstractProblemTemplate + sys::Union{Nothing, String} + settings::IOM.Settings + model_type::DataType + name::String + optimizer::OptimizerAttributes +end + +function serialize_problem(model::IOM.OperationModel; optimizer = nothing) + # A PowerSystem cannot be serialized in this format because of how it stores + # time series data. Use its specialized serialization method instead. + sys = IOM.get_system(model) + sys_filename = + joinpath(IOM.get_output_dir(model), IOM.make_system_filename(sys)) + # Skip serialization if the system is already in the folder + !ispath(sys_filename) && PSY.to_json(sys, sys_filename) + + if optimizer === nothing + optimizer = IOM.get_optimizer(IOM.get_settings(model)) + @assert optimizer !== nothing "optimizer must be passed if it wasn't saved in Settings" + end + + obj = ProblemSerializationWrapper( + model.template, + sys_filename, + deepcopy(IOM.get_settings(model)), + typeof(model), + string(IOM.get_name(model)), + OptimizerAttributes(model, optimizer), + ) + bin_file_name = joinpath(IOM.get_output_dir(model), _SERIALIZED_MODEL_FILENAME) + Serialization.serialize(bin_file_name, obj) + @info "Serialized OperationModel to" bin_file_name +end diff --git a/test/Project.toml b/test/Project.toml index e676ec5..91d6928 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -32,7 +32,6 @@ TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [sources] -InfrastructureOptimizationModels = {rev = "main", url = "https://github.com/NREL-Sienna/InfrastructureOptimizationModels.jl"} InfrastructureSystems = {rev = "IS4", url = "https://github.com/NREL-Sienna/InfrastructureSystems.jl"} [compat] diff --git a/test/performance/performance_test.jl b/test/performance/performance_test.jl index 768a4a9..7b15857 100644 --- a/test/performance/performance_test.jl +++ b/test/performance/performance_test.jl @@ -89,7 +89,6 @@ try optimizer = optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.01, "log_to_console" => false), - system_to_file = false, initialize_model = true, optimizer_solve_log_print = false, direct_mode_optimizer = true, @@ -103,7 +102,6 @@ try optimizer = optimizer_with_attributes(HiGHS.Optimizer, "mip_rel_gap" => 0.01, "log_to_console" => false), - system_to_file = false, initialize_model = true, check_numerical_bounds = false, ) diff --git a/test/test_device_branch_constructors.jl b/test/test_device_branch_constructors.jl index 7d6fda7..6e9d3d5 100644 --- a/test/test_device_branch_constructors.jl +++ b/test/test_device_branch_constructors.jl @@ -272,7 +272,6 @@ end sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, ) build!(model; output_dir = mktempdir()) @@ -289,7 +288,6 @@ end sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, ) solve!(model; output_dir = mktempdir()) dcp_vars = @@ -346,7 +344,6 @@ end sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, store_variable_names = true, ) @@ -379,7 +376,6 @@ end sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, ) solve!(model; output_dir = mktempdir()) @@ -434,7 +430,6 @@ end sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, ) solve!(model_wl; output_dir = mktempdir()) diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index 1b56ca5..a04fdf6 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -597,7 +597,6 @@ end optimizer = ipopt_optimizer, optimizer_solve_log_print = true, store_variable_names = true, - system_to_file = true, horizon = Hour(24), ) @@ -724,7 +723,6 @@ end sys; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, store_variable_names = true, optimizer_solve_log_print = false, ) @@ -773,7 +771,6 @@ end sys; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, store_variable_names = true, optimizer_solve_log_print = false, ) diff --git a/test/test_device_load_constructors.jl b/test/test_device_load_constructors.jl index 1c2855a..38309fa 100644 --- a/test/test_device_load_constructors.jl +++ b/test/test_device_load_constructors.jl @@ -112,7 +112,6 @@ end c_sys5_il; name = "UC_fixed_market_bid_cost", optimizer = HiGHS_optimizer, - system_to_file = false, optimizer_solve_log_print = true) @test build!(model; output_dir = test_path) == IOM.ModelBuildStatus.BUILT @test solve!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED @@ -189,7 +188,6 @@ end name = "UC", store_variable_names = true, optimizer = solvers[ix], - system_to_file = false, ) @test build!(model; output_dir = mktempdir(; cleanup = true)) == @@ -234,7 +232,6 @@ end name = "UC", store_variable_names = true, optimizer = solvers[ix], - system_to_file = false, ) @test build!(model; output_dir = mktempdir(; cleanup = true)) == diff --git a/test/test_device_thermal_generation_constructors.jl b/test/test_device_thermal_generation_constructors.jl index 483f02d..87d828d 100644 --- a/test/test_device_thermal_generation_constructors.jl +++ b/test/test_device_thermal_generation_constructors.jl @@ -24,7 +24,6 @@ const TIME1 = DateTime("2024-01-01T00:00:00") sys; name = "UC_$(i)", optimizer = HiGHS_optimizer, - system_to_file = false, optimizer_solve_log_print = true, ) @test build!(model; output_dir = test_path) == IOM.ModelBuildStatus.BUILT @@ -59,7 +58,6 @@ const TIME1 = DateTime("2024-01-01T00:00:00") sys_no_startup; name = "UC_no_startup", optimizer = HiGHS_optimizer, - system_to_file = false, optimizer_solve_log_print = true, ) @test build!(model_no_startup; output_dir = test_path) == IOM.ModelBuildStatus.BUILT @@ -88,7 +86,6 @@ const TIME1 = DateTime("2024-01-01T00:00:00") sys_with_startup; name = "UC_with_startup", optimizer = HiGHS_optimizer, - system_to_file = false, optimizer_solve_log_print = true, ) @test build!(model_with_startup; output_dir = test_path) == @@ -138,7 +135,6 @@ end sys; name = "UC_$(i)", optimizer = HiGHS_optimizer, - system_to_file = false, ) @test build!(model; output_dir = test_path) == IOM.ModelBuildStatus.BUILT @test solve!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED @@ -1164,7 +1160,6 @@ end =# sys_5; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, store_variable_names = true, rebuild_model = rebuild, ) @@ -1260,7 +1255,6 @@ end sys; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = false, store_variable_names = true, optimizer_solve_log_print = false, ) @@ -1368,7 +1362,6 @@ end =# sys; name = "UC", optimizer = ipopt_optimizer, - system_to_file = false, store_variable_names = true, optimizer_solve_log_print = false, ) diff --git a/test/test_model_decision.jl b/test/test_model_decision.jl index 13652aa..127333b 100644 --- a/test/test_model_decision.jl +++ b/test/test_model_decision.jl @@ -311,6 +311,8 @@ end IOM.RunStatus.SUCCESSFULLY_FINALIZED end +# TODO: Deserialization constructor (DecisionModel(path, optimizer)) was removed from IOM. +# Port deserialize_problem to POM to re-enable this test. @testset "Test Serialization, deserialization and write optimizer problem" begin fpath = mktempdir(; cleanup = true) sys = PSB.build_system(PSITestSystems, "c_sys5_re") @@ -324,23 +326,7 @@ end file_list = sort!(collect(readdir(fpath))) model_name = IOM.get_name(model) @test IOM._JUMP_MODEL_FILENAME in file_list - @test IOM._SERIALIZED_MODEL_FILENAME in file_list - ED2 = DecisionModel(fpath, HiGHS_optimizer) - @test build!(ED2; output_dir = fpath) == IOM.ModelBuildStatus.BUILT - psi_checksolve_test(ED2, [MOI.OPTIMAL], 240000.0, 10000) - - path2 = mktempdir(; cleanup = true) - model_no_sys = - DecisionModel(template, sys; optimizer = HiGHS_optimizer, system_to_file = false) - - @test build!(model_no_sys; output_dir = path2) == IOM.ModelBuildStatus.BUILT - @test solve!(model_no_sys) == IOM.RunStatus.SUCCESSFULLY_FINALIZED - - file_list = sort!(collect(readdir(path2))) - @test !any(occursin.(r"\.h5$", file_list)) - ED3 = DecisionModel(path2, HiGHS_optimizer; system = sys) - build!(ED3; output_dir = path2) - psi_checksolve_test(ED3, [MOI.OPTIMAL], 240000.0, 10000) + @test POM._SERIALIZED_MODEL_FILENAME in file_list end @testset "Test NonSpinning reserve model" begin diff --git a/test/test_utils/run_simulation.jl b/test/test_utils/run_simulation.jl index 7d46ea5..0b4bda9 100644 --- a/test/test_utils/run_simulation.jl +++ b/test/test_utils/run_simulation.jl @@ -11,7 +11,6 @@ function run_simulation( file_path::String, export_path; in_memory = false, - system_to_file = true, uc_network_model = nothing, ed_network_model = nothing, ) @@ -45,14 +44,12 @@ function run_simulation( c_sys5_hy_uc; name = "UC", optimizer = HiGHS_optimizer, - system_to_file = system_to_file, ), DecisionModel( template_ed, c_sys5_hy_ed; name = "ED", optimizer = ipopt_optimizer, - system_to_file = system_to_file, ), ], ) From 698925f5bcf277ecc81df340169553f750249d72 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Mon, 13 Apr 2026 15:44:51 -0400 Subject: [PATCH 3/8] restore deserialization test; restore new pwl names; remove op model serialization (restored in IOM); --- src/PowerOperationsModels.jl | 8 +-- src/common_models/market_bid_overrides.jl | 16 ++--- src/core/problem_template.jl | 8 +-- src/energy_storage_models/storage_models.jl | 4 +- .../operation_model_serialization.jl | 66 ------------------- src/services_models/reserves.jl | 2 +- .../thermal_generation.jl | 6 +- test/test_model_decision.jl | 20 +++++- 8 files changed, 39 insertions(+), 91 deletions(-) delete mode 100644 src/operation/operation_model_serialization.jl diff --git a/src/PowerOperationsModels.jl b/src/PowerOperationsModels.jl index 1205b36..c9a0957 100644 --- a/src/PowerOperationsModels.jl +++ b/src/PowerOperationsModels.jl @@ -141,8 +141,8 @@ import InfrastructureOptimizationModels: _include_constant_min_gen_power_in_constraint, add_variable_cost_to_objective!, _vom_offer_direction, - _add_pwl_constraint!, - add_pwl_term!, + add_pwl_constraint_delta!, + add_pwl_term_delta!, get_output_offer_curves, # Internal utilities used by market bid overrides and proportional_cost is_time_variant, @@ -175,7 +175,7 @@ import InfrastructureOptimizationModels: set_service_model!, finalize_template!, make_empty_jump_model_with_settings, - _set_model! + set_model! using InfrastructureOptimizationModels # TODO: use explicit imports. @@ -280,7 +280,7 @@ include("area_interchange.jl") # Operation lifecycle: build/solve/run include("operation/build_problem.jl") include("initial_conditions/initialization.jl") -include("operation/operation_model_serialization.jl") +include("operation/template_validation.jl") include("operation/decision_model.jl") include("operation/emulation_model.jl") diff --git a/src/common_models/market_bid_overrides.jl b/src/common_models/market_bid_overrides.jl index 4de701b..10e6ea8 100644 --- a/src/common_models/market_bid_overrides.jl +++ b/src/common_models/market_bid_overrides.jl @@ -175,7 +175,7 @@ function add_variable_cost_to_objective!( ::ImportExportSourceModel, ) isnothing(get_output_offer_curves(cost_function)) && return - add_pwl_term!( + add_pwl_term_delta!( IncrementalOffer(), container, component, @@ -194,7 +194,7 @@ function add_variable_cost_to_objective!( ::ImportExportSourceModel, ) isnothing(get_input_offer_curves(cost_function)) && return - add_pwl_term!( + add_pwl_term_delta!( DecrementalOffer(), container, component, @@ -221,7 +221,7 @@ function add_variable_cost_to_objective!( if !(isnothing(get_output_offer_curves(cost_function))) error("Component $(component_name) is not allowed to participate as a supply.") end - add_pwl_term!( + add_pwl_term_delta!( DecrementalOffer(), container, component, @@ -241,7 +241,7 @@ _vom_offer_direction(::AbstractControllablePowerLoadFormulation) = DecrementalOf """ PWL block offer constraints for ORDC (ReserveDemandCurve). """ -function _add_pwl_constraint!( +function add_pwl_constraint_delta!( container::OptimizationContainer, component::T, ::U, @@ -273,7 +273,7 @@ end """ PWL cost terms for StepwiseCostReserve (AbstractServiceFormulation). """ -function add_pwl_term!( +function add_pwl_term_delta!( container::OptimizationContainer, component::T, cost_data::PSY.CostCurve{PSY.PiecewiseIncrementalCurve}, @@ -300,7 +300,7 @@ function add_pwl_term!( slopes = IS.get_y_coords(data) break_points = PSY.get_x_coords(data) for t in time_steps - pwl_vars = add_pwl_variables!( + pwl_vars = add_pwl_variables_delta!( container, PiecewiseLinearBlockIncrementalOffer, T, @@ -309,9 +309,9 @@ function add_pwl_term!( length(slopes); upper_bound = Inf, ) - add_pwl_constraint!(container, component, U(), break_points, pwl_vars, t) + add_pwl_constraint_delta!(container, component, U(), break_points, pwl_vars, t) pwl_cost_expressions[t] = - get_pwl_cost_expression(pwl_vars, slopes, multiplier * dt) + get_pwl_cost_expression_delta(pwl_vars, slopes, multiplier * dt) end return pwl_cost_expressions end diff --git a/src/core/problem_template.jl b/src/core/problem_template.jl index e230078..5bdfa61 100644 --- a/src/core/problem_template.jl +++ b/src/core/problem_template.jl @@ -140,7 +140,7 @@ function set_device_model!( template::OperationsProblemTemplate, model::DeviceModel{D}, ) where {D <: IS.InfrastructureSystemsComponent} - _set_model!(template.devices, model) + set_model!(template.devices, model) return end @@ -152,7 +152,7 @@ function set_device_model!( template::OperationsProblemTemplate, model::DeviceModel{D}, ) where {D <: PSY.Branch} - _set_model!(template.branches, model) + set_model!(template.branches, model) return end @@ -191,7 +191,7 @@ function set_service_model!( service_name::String, model::ServiceModel{T, <:AbstractServiceFormulation}, ) where {T <: PSY.Service} - _set_model!(template.services, (service_name, Symbol(T)), model) + set_model!(template.services, (service_name, Symbol(T)), model) return end @@ -199,7 +199,7 @@ function set_service_model!( template::OperationsProblemTemplate, model::ServiceModel{<:PSY.Service, <:AbstractServiceFormulation}, ) - _set_model!(template.services, model) + set_model!(template.services, model) return end diff --git a/src/energy_storage_models/storage_models.jl b/src/energy_storage_models/storage_models.jl index 8396fdc..1f3730d 100644 --- a/src/energy_storage_models/storage_models.jl +++ b/src/energy_storage_models/storage_models.jl @@ -1916,7 +1916,7 @@ function _add_variable_cost_to_objective!( @debug "Market Bid" _group = LOG_GROUP_COST_FUNCTIONS component_name incremental_cost_curves = PSY.get_incremental_offer_curves(cost_function) if !isnothing(incremental_cost_curves) - add_pwl_term!( + add_pwl_term_delta!( IncrementalOffer(), container, component, @@ -1942,7 +1942,7 @@ function _add_variable_cost_to_objective!( @debug "Market Bid" _group = LOG_GROUP_COST_FUNCTIONS component_name decremental_cost_curves = PSY.get_decremental_offer_curves(cost_function) if !isnothing(decremental_cost_curves) - add_pwl_term!( + add_pwl_term_delta!( DecrementalOffer(), container, component, diff --git a/src/operation/operation_model_serialization.jl b/src/operation/operation_model_serialization.jl deleted file mode 100644 index e948491..0000000 --- a/src/operation/operation_model_serialization.jl +++ /dev/null @@ -1,66 +0,0 @@ -const _SERIALIZED_MODEL_FILENAME = "model.bin" - -struct OptimizerAttributes - name::String - version::String - attributes::Any -end - -function OptimizerAttributes( - model::IOM.OperationModel, - optimizer::IOM.MOI.OptimizerWithAttributes, -) - jump_model = IOM.get_jump_model(model) - name = JuMP.solver_name(jump_model) - # Note that this uses private field access to MOI.OptimizerWithAttributes because there - # is no public method available. - # This could break if MOI changes their implementation. - try - version = IOM.MOI.get(JuMP.backend(jump_model), IOM.MOI.SolverVersion()) - return OptimizerAttributes(name, version, optimizer.params) - catch - @debug "Solver Version not supported by the solver" - version = "MOI.SolverVersion not supported" - return OptimizerAttributes(name, version, optimizer.params) - end -end - -function _get_optimizer_attributes(model::IOM.OperationModel) - return IOM.get_optimizer(IOM.get_settings(model)).params -end - -struct ProblemSerializationWrapper - template::IOM.AbstractProblemTemplate - sys::Union{Nothing, String} - settings::IOM.Settings - model_type::DataType - name::String - optimizer::OptimizerAttributes -end - -function serialize_problem(model::IOM.OperationModel; optimizer = nothing) - # A PowerSystem cannot be serialized in this format because of how it stores - # time series data. Use its specialized serialization method instead. - sys = IOM.get_system(model) - sys_filename = - joinpath(IOM.get_output_dir(model), IOM.make_system_filename(sys)) - # Skip serialization if the system is already in the folder - !ispath(sys_filename) && PSY.to_json(sys, sys_filename) - - if optimizer === nothing - optimizer = IOM.get_optimizer(IOM.get_settings(model)) - @assert optimizer !== nothing "optimizer must be passed if it wasn't saved in Settings" - end - - obj = ProblemSerializationWrapper( - model.template, - sys_filename, - deepcopy(IOM.get_settings(model)), - typeof(model), - string(IOM.get_name(model)), - OptimizerAttributes(model, optimizer), - ) - bin_file_name = joinpath(IOM.get_output_dir(model), _SERIALIZED_MODEL_FILENAME) - Serialization.serialize(bin_file_name, obj) - @info "Serialized OperationModel to" bin_file_name -end diff --git a/src/services_models/reserves.jl b/src/services_models/reserves.jl index 7eee109..a6b63d7 100644 --- a/src/services_models/reserves.jl +++ b/src/services_models/reserves.jl @@ -543,7 +543,7 @@ function _add_reserves_variable_cost_to_objective!( end pwl_cost_expressions = - add_pwl_term!(container, component, variable_cost, T(), U()) + add_pwl_term_delta!(container, component, variable_cost, T(), U()) for t in time_steps add_to_expression!( container, diff --git a/src/static_injector_models/thermal_generation.jl b/src/static_injector_models/thermal_generation.jl index 3e41292..0f7efd2 100644 --- a/src/static_injector_models/thermal_generation.jl +++ b/src/static_injector_models/thermal_generation.jl @@ -1559,7 +1559,7 @@ Add PWL cost terms for ThermalDispatchNoMin formulation. Rejects non-convex or negative-slope PWL data since ThermalDispatchNoMin cannot use SOS-2 formulations. """ -function IOM.add_pwl_term!( +function IOM.add_pwl_term_lambda!( container::IOM.OptimizationContainer, component::T, cost_function::Union{ @@ -1621,7 +1621,7 @@ function IOM.add_pwl_term!( temp_cost_function = IOM.create_temporary_cost_function_in_system_per_unit(cost_function, data) for t in time_steps - IOM.add_pwl_variables!(container, T, name, t, data) + IOM.add_pwl_variables_lambda!(container, T, name, t, data) power_var = IOM.get_variable(container, U(), T)[name, t] IOM._add_pwl_constraint_standard!( container, @@ -1632,7 +1632,7 @@ function IOM.add_pwl_term!( power_var, ) pwl_cost = - IOM.get_pwl_cost_expression( + IOM.get_pwl_cost_expression_lambda( container, component, t, diff --git a/test/test_model_decision.jl b/test/test_model_decision.jl index 127333b..13652aa 100644 --- a/test/test_model_decision.jl +++ b/test/test_model_decision.jl @@ -311,8 +311,6 @@ end IOM.RunStatus.SUCCESSFULLY_FINALIZED end -# TODO: Deserialization constructor (DecisionModel(path, optimizer)) was removed from IOM. -# Port deserialize_problem to POM to re-enable this test. @testset "Test Serialization, deserialization and write optimizer problem" begin fpath = mktempdir(; cleanup = true) sys = PSB.build_system(PSITestSystems, "c_sys5_re") @@ -326,7 +324,23 @@ end file_list = sort!(collect(readdir(fpath))) model_name = IOM.get_name(model) @test IOM._JUMP_MODEL_FILENAME in file_list - @test POM._SERIALIZED_MODEL_FILENAME in file_list + @test IOM._SERIALIZED_MODEL_FILENAME in file_list + ED2 = DecisionModel(fpath, HiGHS_optimizer) + @test build!(ED2; output_dir = fpath) == IOM.ModelBuildStatus.BUILT + psi_checksolve_test(ED2, [MOI.OPTIMAL], 240000.0, 10000) + + path2 = mktempdir(; cleanup = true) + model_no_sys = + DecisionModel(template, sys; optimizer = HiGHS_optimizer, system_to_file = false) + + @test build!(model_no_sys; output_dir = path2) == IOM.ModelBuildStatus.BUILT + @test solve!(model_no_sys) == IOM.RunStatus.SUCCESSFULLY_FINALIZED + + file_list = sort!(collect(readdir(path2))) + @test !any(occursin.(r"\.h5$", file_list)) + ED3 = DecisionModel(path2, HiGHS_optimizer; system = sys) + build!(ED3; output_dir = path2) + psi_checksolve_test(ED3, [MOI.OPTIMAL], 240000.0, 10000) end @testset "Test NonSpinning reserve model" begin From 72eac8af9eaacfe22f7677bda58360137be43eef Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Mon, 13 Apr 2026 16:49:09 -0400 Subject: [PATCH 4/8] remove problem serialization test --- test/test_model_decision.jl | 32 -------------------------------- 1 file changed, 32 deletions(-) diff --git a/test/test_model_decision.jl b/test/test_model_decision.jl index 13652aa..02344c6 100644 --- a/test/test_model_decision.jl +++ b/test/test_model_decision.jl @@ -311,38 +311,6 @@ end IOM.RunStatus.SUCCESSFULLY_FINALIZED end -@testset "Test Serialization, deserialization and write optimizer problem" begin - fpath = mktempdir(; cleanup = true) - sys = PSB.build_system(PSITestSystems, "c_sys5_re") - template = get_template_dispatch_with_network( - NetworkModel(CopperPlatePowerModel; duals = [CopperPlateBalanceConstraint]), - ) - model = DecisionModel(template, sys; optimizer = HiGHS_optimizer) - @test build!(model; output_dir = fpath) == IOM.ModelBuildStatus.BUILT - @test solve!(model) == IOM.RunStatus.SUCCESSFULLY_FINALIZED - - file_list = sort!(collect(readdir(fpath))) - model_name = IOM.get_name(model) - @test IOM._JUMP_MODEL_FILENAME in file_list - @test IOM._SERIALIZED_MODEL_FILENAME in file_list - ED2 = DecisionModel(fpath, HiGHS_optimizer) - @test build!(ED2; output_dir = fpath) == IOM.ModelBuildStatus.BUILT - psi_checksolve_test(ED2, [MOI.OPTIMAL], 240000.0, 10000) - - path2 = mktempdir(; cleanup = true) - model_no_sys = - DecisionModel(template, sys; optimizer = HiGHS_optimizer, system_to_file = false) - - @test build!(model_no_sys; output_dir = path2) == IOM.ModelBuildStatus.BUILT - @test solve!(model_no_sys) == IOM.RunStatus.SUCCESSFULLY_FINALIZED - - file_list = sort!(collect(readdir(path2))) - @test !any(occursin.(r"\.h5$", file_list)) - ED3 = DecisionModel(path2, HiGHS_optimizer; system = sys) - build!(ED3; output_dir = path2) - psi_checksolve_test(ED3, [MOI.OPTIMAL], 240000.0, 10000) -end - @testset "Test NonSpinning reserve model" begin c_sys5 = PSB.build_system(PSITestSystems, "c_sys5_uc_non_spin"; add_reserves = true) template = get_thermal_standard_uc_template() From fec38b0b128aa0b2844cf9dec542281f211738d3 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Wed, 15 Apr 2026 18:23:28 -0400 Subject: [PATCH 5/8] fixed flow*head product --- .../hydro_generation.jl | 45 +- test/test_device_hydro_constructors.jl | 1818 ++++++++--------- 2 files changed, 936 insertions(+), 927 deletions(-) diff --git a/src/static_injector_models/hydro_generation.jl b/src/static_injector_models/hydro_generation.jl index 81dabf9..29d2eae 100644 --- a/src/static_injector_models/hydro_generation.jl +++ b/src/static_injector_models/hydro_generation.jl @@ -1851,7 +1851,7 @@ function add_constraints!( X <: PM.AbstractPowerModel, } time_steps = get_time_steps(container) - base_power = get_base_power(container) + base_power = get_model_base_power(container) names = PSY.get_name.(devices) constraint = add_constraints_container!( @@ -1864,34 +1864,43 @@ function add_constraints!( power = get_variable(container, ActivePowerVariable(), V) flow = get_variable(container, HydroTurbineFlowRateVariable(), V) head = get_variable(container, HydroReservoirHeadVariable(), PSY.HydroReservoir) - - hf_prod = IOM._add_bilinear_approx!( - IOM.Bin2Config(IOM.SolverSOS2QuadConfig(4)), - container, - V, - names, - time_steps, - head, - flow, - get_variable_lower_bound(HydroTurbineFlowRateVariable(), V, W), - get_variable_upper_bound(HydroTurbineFlowRateVariable(), V, W), - get_variable_lower_bound(HydroReservoirHeadVariable(), V, W), - get_variable_upper_bound(HydroReservoirHeadVariable(), V, W), - ) - for d in devices name = PSY.get_name(d) conversion_factor = PSY.get_conversion_factor(d) reservoirs = filter(PSY.get_available, PSY.get_connected_head_reservoirs(sys, d)) powerhouse_elevation = PSY.get_powerhouse_elevation(d) + + fh_prod = IOM._add_bilinear_approx!( + IOM.Bin2Config(IOM.SolverSOS2QuadConfig(4)), + container, + V, + PSY.get_name.(reservoirs), + time_steps, + flow[name, :, :], + head, + [ + ( + min = get_variable_lower_bound(HydroTurbineFlowRateVariable(), d, W()), + max = get_variable_upper_bound(HydroTurbineFlowRateVariable(), d, W()) + ) for _=1:length(reservoirs) + ], + [ + ( + min = get_variable_lower_bound(HydroReservoirHeadVariable(), res, W()), + max = get_variable_upper_bound(HydroReservoirHeadVariable(), res, W()) + ) for res in reservoirs + ], + "$(get_name(d))_FlowHeadProduct" + ) + for t in time_steps constraint[name, t] = JuMP.@constraint( container.JuMPmodel, power[name, t] == GRAVITATIONAL_CONSTANT * WATER_DENSITY * conversion_factor * sum( - hf_prod[PSY.get_name(res), t] - - powerhouse_elevation * flow[name, PSY.get_name(res), t] + fh_prod[PSY.get_name(res), t] + - powerhouse_elevation * flow[name, PSY.get_name(res), t] for res in reservoirs ) / (1e6 * base_power) ) diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index 7e11257..cf56601 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -1,695 +1,695 @@ ######################################### #### RESERVOIR BUDGET DISPATCH TESTS #### ######################################### -@testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 120, 0, 25, 24, 24, false) - psi_checkobjfun_test(model, GAEVF) -end - -@testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - use_slacks = true, - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 192, 0, 49, 48, 24, false) - psi_checkobjfun_test(model, GAEVF) -end - -########################################### -#### RESERVOIR BUDGET COMMITMENT TESTS #### -########################################### - -@testset "Hydro DCPLossLess with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 144, 0, 25, 24, 24, true) - psi_checkobjfun_test(model, GAEVF) -end - -@testset "Hydro ACPPowerModel with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 168, 0, 49, 48, 24, true) - psi_checkobjfun_test(model, GAEVF) -end - -######################################### -#### RESERVOIR TARGET DISPATCH TESTS #### -######################################### - -@testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => true, - "hydro_budget" => false, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 120, 0, 24, 24, 25, false) - psi_checkobjfun_test(model, GAEVF) -end - -@testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => true, - "hydro_budget" => false, - ), - ) - - c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - - # No Parameters Testing - model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) - mock_construct_device!(model, turbine_model) - mock_construct_device!(model, reservoir_model) - moi_tests(model, 144, 0, 48, 48, 25, false) - psi_checkobjfun_test(model, GAEVF) -end - -######################################### -########### RUN OF RIVER TESTS ########## -######################################### - -@testset "Solving ED Hydro System using Dispatch Run of River" begin - sys = PSB.build_system(PSITestSystems, "c_sys5_hy") - networks = [ACPPowerModel, DCPPowerModel] - - test_outputs = - Dict{Any, Float64}(ACPPowerModel => 136581.41, DCPPowerModel => 135382.37) - - for net in networks - @testset "HydroRoR ED model $(net)" begin - template = get_thermal_dispatch_template_network(net) - set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver) - ED = DecisionModel( - EconomicDispatchProblem, - template, - sys; - optimizer = ipopt_optimizer, - ) - @test build!(ED; output_dir = mktempdir(; cleanup = true)) == - ModelBuildStatus.BUILT - psi_checksolve_test( - ED, - [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], - test_outputs[net], - 1000, - ) - end - end -end - -@testset "Solving ED Hydro System using Commitment Run of River" begin - sys = PSB.build_system(PSITestSystems, "c_sys5_hy") - net = CopperPlatePowerModel - - template = get_thermal_dispatch_template_network(net) - set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver) - - @testset "HydroRoR ED model $(net)" begin - ED = - DecisionModel(UnitCommitmentProblem, template, sys; optimizer = HiGHS_optimizer) - @test build!(ED; output_dir = mktempdir(; cleanup = true)) == - ModelBuildStatus.BUILT - psi_checksolve_test(ED, [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], 135382.38, 1000) - end -end - -@testset "Test Reserves from Hydro with RunOfRiver" begin - template = OperationsProblemTemplate(CopperPlatePowerModel) - set_device_model!(template, PowerLoad, StaticPowerLoad) - set_device_model!(template, HydroTurbine, HydroDispatchRunOfRiver) - set_service_model!( - template, - ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "Reserve5"), - ) - set_service_model!( - template, - ServiceModel(VariableReserve{ReserveDown}, RangeReserve, "Reserve6"), - ) - set_service_model!( - template, - ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve, "ORDC1"), - ) - - c_sys5_hyd = PSB.build_system( - PSITestSystems, - "c_sys5_hyd"; - add_reserves = true, - force_build = true, - ) - model = DecisionModel(template, c_sys5_hyd) - @test build!(model; output_dir = mktempdir(; cleanup = true)) == - ModelBuildStatus.BUILT - # The value of this test needs to be revised - # moi_tests(model, 240, 0, 48, 96, 72, false) -end - -######################################### -###### RESERVOIR SYSTEM TESTS ########### -######################################### - -@testset "Solving ED Hydro System using Dispatch with Reservoir" begin - sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - networks = [ACPPowerModel, DCPPowerModel] - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) - models = [ - DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => true, - "hydro_budget" => false, - ), - ), - DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ), - ] - test_outputs = Dict{Any, Float64}( - (ACPPowerModel, HydroEnergyModelReservoir, true) => 136296.0, - (DCPPowerModel, HydroEnergyModelReservoir, true) => 135404.0, - (ACPPowerModel, HydroEnergyModelReservoir, false) => 131305.0, - (DCPPowerModel, HydroEnergyModelReservoir, false) => 130479.0, - ) - - for net in networks - for reservoir_model in models - formulation = get_formulation(reservoir_model) - attrs = IOM.get_attributes(reservoir_model) - energy_target = get(attrs, "energy_target", false) - hydro_budget = get(attrs, "hydro_budget", false) - @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin - template = get_thermal_dispatch_template_network(net) - set_device_model!(template, turbine_model) - set_device_model!(template, reservoir_model) - - ED = DecisionModel( - EconomicDispatchProblem, - template, - sys; - optimizer = ipopt_optimizer, - ) - @test build!(ED; output_dir = mktempdir(; cleanup = true)) == - ModelBuildStatus.BUILT - psi_checksolve_test( - ED, - [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], - test_outputs[(net, formulation, energy_target)], - 1000, - ) - end - end - end -end - -@testset "Solving ED Hydro System using Commitment with Reservoir" begin - sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - net = DCPPowerModel - turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) - models = [ - DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => true, - "hydro_budget" => false, - ), - ), - DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ), - ] - test_outputs = Dict{Any, Float64}( - (DCPPowerModel, HydroEnergyModelReservoir, true) => 141061.0, - (DCPPowerModel, HydroEnergyModelReservoir, false) => 144109.0, - ) - - for reservoir_model in models - formulation = get_formulation(reservoir_model) - attrs = IOM.get_attributes(reservoir_model) - energy_target = get(attrs, "energy_target", false) - hydro_budget = get(attrs, "hydro_budget", false) - @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin - template = get_thermal_dispatch_template_network(net) - set_device_model!(template, turbine_model) - set_device_model!(template, reservoir_model) - - ED = DecisionModel( - EconomicDispatchProblem, - template, - sys; - optimizer = HiGHS_optimizer, - ) - @test build!(ED; output_dir = mktempdir(; cleanup = true)) == - ModelBuildStatus.BUILT - psi_checksolve_test( - ED, - [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], - test_outputs[(net, formulation, energy_target)], - 10000, #update to 10k to handle the difference in Mac and Ubuntu. Likely a HiGHS issue. - ) - end - end -end - -######################################################## -####### Hydro DISPATCH RUN OF RIVER BUDGET TEST ######## -######################################################## -@testset "Test Hydro Dispatch Run Of River Formulations " begin - device_model = DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; - use_slacks = true, attributes = Dict("hydro_budget_interval" => Hour(24))) - - sys = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) - hy = only(get_components(HydroDispatch, sys)) - max_power = get_max_active_power(hy) - resolution = Dates.Hour(1) - tstamp = range(DateTime("2024-01-01T00:00:00"); step = resolution, length = 48) - data = ones(length(tstamp)) / (get_base_power(sys) * max_power) - ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) - add_time_series!(sys, hy, ts) - transform_single_time_series!(sys, Hour(24), Hour(24)) - - model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, sys) - mock_construct_device!(model, device_model) - moi_tests(model, 48, 0, 50, 24, 0, false) - psi_checkobjfun_test(model, GAEVF) -end - -@testset "Solve Hydro Dispatch Run Of River" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) - - hydro_budget = 24 - eps = 1e-6 - - hy = only(get_components(HydroDispatch, c_sys5_hy)) - max_power = get_max_active_power(hy) - - tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) - data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) - ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) - add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) - #remove_time_series!(c_sys5_hy, Deterministic) - transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) - - template_uc = OperationsProblemTemplate() - set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) - set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) - set_device_model!(template_uc, PowerLoad, StaticPowerLoad) - set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) - set_device_model!( - template_uc, - DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; - attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), - ) - model = DecisionModel( - template_uc, - c_sys5_hy; - optimizer = HiGHS_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - outputs = OptimizationProblemOutputs(model) - df = read_variable(outputs, "ActivePowerVariable__HydroDispatch") - hydro_power_sum = - sum(df[!, :value]) - - @test abs(hydro_power_sum - hydro_budget) <= eps -end - -@testset "Make Hydro Dispatch Run Of River with Reserves" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_hy = PSB.build_system( - PSITestSystems, - "c_sys5_hy"; - add_single_time_series = true, - add_reserves = true, - ) - - # Fix reserve parameters - reg_up = only(get_components(VariableReserve{ReserveUp}, c_sys5_hy)) - reg_dn = only(get_components(VariableReserve{ReserveDown}, c_sys5_hy)) - set_deployed_fraction!(reg_up, 0.0) - set_deployed_fraction!(reg_dn, 0.0) - set_requirement!(reg_up, 0.01) - set_requirement!(reg_dn, 0.01) - - hydro_budget = 24 - eps = 1e-6 - - hy = only(get_components(HydroDispatch, c_sys5_hy)) - - # Update Service allocation - # Remove reg up from hydro, but leave reg dn - remove_service!(hy, reg_up) - - # Add reg up to thermals - for th in get_components(ThermalStandard, c_sys5_hy) - add_service!(th, reg_up, c_sys5_hy) - end - - max_power = get_max_active_power(hy) - tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) - data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) - ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) - add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) - - ## add extra hydro budget - hy_copy = HydroDispatch(; - name = "HydroDispatchCopy", - available = get_available(hy), - bus = get_bus(hy), - active_power = get_active_power(hy), - reactive_power = get_reactive_power(hy), - rating = get_rating(hy), - prime_mover_type = get_prime_mover_type(hy), - active_power_limits = get_active_power_limits(hy), - reactive_power_limits = get_reactive_power_limits(hy), - ramp_limits = get_ramp_limits(hy), - time_limits = get_time_limits(hy), - base_power = get_base_power(hy), - ) - add_component!(c_sys5_hy, hy_copy) - copy_time_series!(hy_copy, hy) - - transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) - - template_uc = OperationsProblemTemplate() - set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) - set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) - set_device_model!(template_uc, PowerLoad, StaticPowerLoad) - set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) - set_device_model!( - template_uc, - DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; - attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), - ) - set_service_model!(template_uc, VariableReserve{ReserveUp}, RangeReserve) - set_service_model!(template_uc, VariableReserve{ReserveDown}, RangeReserve) - model = DecisionModel( - template_uc, - c_sys5_hy; - optimizer = HiGHS_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -end - -################################################ -####### Hydro PUMP ENERGY DISPATCH TEST ######## -################################################ -@testset "Test Hydro Pump Energy Dispatch Formulations " begin - device_model = DeviceModel( - HydroPumpTurbine, - HydroPumpEnergyDispatch; - attributes = Dict{String, Any}( - "reservation" => true, - "energy_target" => true, - ), - ) - - c_sys5_bat = - PSB.build_system( - PSITestSystems, - "c_sys5_hydro_pump_energy"; - add_reserves = true, - add_single_time_series = true, - ) - - hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) - transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) - - model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, c_sys5_bat) - mock_construct_device!(model, device_model) - moi_tests(model, 72, 0, 48, 24, 0, true) - psi_checkobjfun_test(model, GAEVF) -end - -@testset "Test Hydro Pump Energy Dispatch Formulations 2" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_bat = - PSB.build_system( - PSITestSystems, - "c_sys5_hydro_pump_energy"; - add_reserves = true, - add_single_time_series = true, - ) - - hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) - - transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) - - template_uc = OperationsProblemTemplate() - set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) - set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) - set_device_model!(template_uc, PowerLoad, StaticPowerLoad) - set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) - set_device_model!( - template_uc, - DeviceModel( - HydroPumpTurbine, - HydroPumpEnergyDispatch; - attributes = Dict{String, Any}( - "reservation" => true, - "energy_target" => true, - ), - ), - ) - - model = DecisionModel( - template_uc, - c_sys5_bat; - optimizer = HiGHS_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -end - -######################################### -######## HydroBlock model Tests ######### -######################################### - -@testset "Test Hydro Block Optimization Formulation" begin - output_dir = mktempdir(; cleanup = true) - modeling_horizon = 3 * 24 * 1 - - sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - res = first(PSY.get_components(HydroReservoir, sys)) - - set_head_to_volume_factor!(res, LinearCurve(1.0)) - set_storage_level_limits!(res, (min = 4000, max = 6000)) - set_level_targets!(res, 0.9) - template_ed = OperationsProblemTemplate( - NetworkModel( - CopperPlatePowerModel; - ), - ) - - set_device_model!(template_ed, ThermalStandard, ThermalBasicDispatch) - set_device_model!(template_ed, PowerLoad, StaticPowerLoad) - set_device_model!(template_ed, HydroReservoir, HydroWaterFactorModel) - set_device_model!(template_ed, HydroTurbine, HydroWaterFactorModel) - - model = DecisionModel( - template_ed, - sys; - name = "ED", - optimizer = ipopt_optimizer, - optimizer_solve_log_print = true, - store_variable_names = true, - horizon = Hour(24), - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; optimizer = ipopt_optimizer, output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - outputs = OptimizationProblemOutputs(model) - power_load = read_parameter(outputs, "ActivePowerTimeSeriesParameter__PowerLoad") - reservoir_inflow = read_parameter(outputs, "InflowTimeSeriesParameter__HydroReservoir") - - water_spillage = read_variable(outputs, "WaterSpillageVariable__HydroReservoir") - thermal_power = read_variable(outputs, "ActivePowerVariable__ThermalStandard") - hydro_power = read_variable(outputs, "ActivePowerVariable__HydroTurbine") - - turbine_output = read_aux_variable(outputs, "HydroEnergyOutput__HydroTurbine") - reservoir_volume = - read_variable(outputs, "HydroReservoirVolumeVariable__HydroReservoir") - - var = read_variable( - outputs, - "HydroReservoirVolumeVariable__HydroReservoir"; - table_format = TableFormat.WIDE, - ) - - # check the second step is equal to the first step + dispatch -end - -################################################ -######## HydroWaterModelReservoir TEST ######### -################################################ - -@testset "Solve HydroWaterModelReservoir" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_head") - reservoir = only(get_components(HydroReservoir, c_sys5_hy)) - hydro_inflow_ts = get_time_series_array(Deterministic, reservoir, "inflow") - - template = OperationsProblemTemplate() - set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) - set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) - - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - - model = DecisionModel( - template, - c_sys5_hy; - optimizer = ipopt_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - outputs = OptimizationProblemOutputs(model) - - moi_tests(model, 288, 0, 168, 168, 72, false) - psi_checkobjfun_test(model, AffExpr) - - df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") - hydro_vol_df = - read_variables(outputs, [(HydroReservoirVolumeVariable, HydroReservoir)])["HydroReservoirVolumeVariable__HydroReservoir"] - hydro_head_df = - read_variables(outputs, [(HydroReservoirHeadVariable, HydroReservoir)])["HydroReservoirHeadVariable__HydroReservoir"] - hydro_spillage_df = - read_variables(outputs, [(WaterSpillageVariable, HydroReservoir)])["WaterSpillageVariable__HydroReservoir"] - hydro_inflow_df = - read_parameters(outputs, [(InflowTimeSeriesParameter, HydroReservoir)])["InflowTimeSeriesParameter__HydroReservoir"] - - total_inflow = sum(values(hydro_inflow_ts)) - total_outflow = sum(df_outflow[!, :value]) - total_spillage = sum(hydro_spillage_df[!, :value]) - - calculated_vf = - (hydro_vol_df[1, :value]) + - ((total_inflow - total_outflow - total_spillage) * 3600 * 1e-9) - - @test abs(calculated_vf - hydro_vol_df[end, :value]) <= 1e-4 - - psi_checksolve_test( - model, - [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED], - 210949.49, - 1000, - ) -end +# @testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 120, 0, 25, 24, 24, false) +# psi_checkobjfun_test(model, GAEVF) +# end + +# @testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# use_slacks = true, +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 192, 0, 49, 48, 24, false) +# psi_checkobjfun_test(model, GAEVF) +# end + +# ########################################### +# #### RESERVOIR BUDGET COMMITMENT TESTS #### +# ########################################### + +# @testset "Hydro DCPLossLess with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 144, 0, 25, 24, 24, true) +# psi_checkobjfun_test(model, GAEVF) +# end + +# @testset "Hydro ACPPowerModel with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 168, 0, 49, 48, 24, true) +# psi_checkobjfun_test(model, GAEVF) +# end + +# ######################################### +# #### RESERVOIR TARGET DISPATCH TESTS #### +# ######################################### + +# @testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => true, +# "hydro_budget" => false, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 120, 0, 24, 24, 25, false) +# psi_checkobjfun_test(model, GAEVF) +# end + +# @testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => true, +# "hydro_budget" => false, +# ), +# ) + +# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + +# # No Parameters Testing +# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) +# mock_construct_device!(model, turbine_model) +# mock_construct_device!(model, reservoir_model) +# moi_tests(model, 144, 0, 48, 48, 25, false) +# psi_checkobjfun_test(model, GAEVF) +# end + +# ######################################### +# ########### RUN OF RIVER TESTS ########## +# ######################################### + +# @testset "Solving ED Hydro System using Dispatch Run of River" begin +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy") +# networks = [ACPPowerModel, DCPPowerModel] + +# test_outputs = +# Dict{Any, Float64}(ACPPowerModel => 136581.41, DCPPowerModel => 135382.37) + +# for net in networks +# @testset "HydroRoR ED model $(net)" begin +# template = get_thermal_dispatch_template_network(net) +# set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver) +# ED = DecisionModel( +# EconomicDispatchProblem, +# template, +# sys; +# optimizer = ipopt_optimizer, +# ) +# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == +# ModelBuildStatus.BUILT +# psi_checksolve_test( +# ED, +# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], +# test_outputs[net], +# 1000, +# ) +# end +# end +# end + +# @testset "Solving ED Hydro System using Commitment Run of River" begin +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy") +# net = CopperPlatePowerModel + +# template = get_thermal_dispatch_template_network(net) +# set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver) + +# @testset "HydroRoR ED model $(net)" begin +# ED = +# DecisionModel(UnitCommitmentProblem, template, sys; optimizer = HiGHS_optimizer) +# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == +# ModelBuildStatus.BUILT +# psi_checksolve_test(ED, [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], 135382.38, 1000) +# end +# end + +# @testset "Test Reserves from Hydro with RunOfRiver" begin +# template = OperationsProblemTemplate(CopperPlatePowerModel) +# set_device_model!(template, PowerLoad, StaticPowerLoad) +# set_device_model!(template, HydroTurbine, HydroDispatchRunOfRiver) +# set_service_model!( +# template, +# ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "Reserve5"), +# ) +# set_service_model!( +# template, +# ServiceModel(VariableReserve{ReserveDown}, RangeReserve, "Reserve6"), +# ) +# set_service_model!( +# template, +# ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve, "ORDC1"), +# ) + +# c_sys5_hyd = PSB.build_system( +# PSITestSystems, +# "c_sys5_hyd"; +# add_reserves = true, +# force_build = true, +# ) +# model = DecisionModel(template, c_sys5_hyd) +# @test build!(model; output_dir = mktempdir(; cleanup = true)) == +# ModelBuildStatus.BUILT +# # The value of this test needs to be revised +# # moi_tests(model, 240, 0, 48, 96, 72, false) +# end + +# ######################################### +# ###### RESERVOIR SYSTEM TESTS ########### +# ######################################### + +# @testset "Solving ED Hydro System using Dispatch with Reservoir" begin +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") +# networks = [ACPPowerModel, DCPPowerModel] +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) +# models = [ +# DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => true, +# "hydro_budget" => false, +# ), +# ), +# DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ), +# ] +# test_outputs = Dict{Any, Float64}( +# (ACPPowerModel, HydroEnergyModelReservoir, true) => 136296.0, +# (DCPPowerModel, HydroEnergyModelReservoir, true) => 135404.0, +# (ACPPowerModel, HydroEnergyModelReservoir, false) => 131305.0, +# (DCPPowerModel, HydroEnergyModelReservoir, false) => 130479.0, +# ) + +# for net in networks +# for reservoir_model in models +# formulation = get_formulation(reservoir_model) +# attrs = IOM.get_attributes(reservoir_model) +# energy_target = get(attrs, "energy_target", false) +# hydro_budget = get(attrs, "hydro_budget", false) +# @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin +# template = get_thermal_dispatch_template_network(net) +# set_device_model!(template, turbine_model) +# set_device_model!(template, reservoir_model) + +# ED = DecisionModel( +# EconomicDispatchProblem, +# template, +# sys; +# optimizer = ipopt_optimizer, +# ) +# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == +# ModelBuildStatus.BUILT +# psi_checksolve_test( +# ED, +# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], +# test_outputs[(net, formulation, energy_target)], +# 1000, +# ) +# end +# end +# end +# end + +# @testset "Solving ED Hydro System using Commitment with Reservoir" begin +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") +# net = DCPPowerModel +# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) +# models = [ +# DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => true, +# "hydro_budget" => false, +# ), +# ), +# DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ), +# ] +# test_outputs = Dict{Any, Float64}( +# (DCPPowerModel, HydroEnergyModelReservoir, true) => 141061.0, +# (DCPPowerModel, HydroEnergyModelReservoir, false) => 144109.0, +# ) + +# for reservoir_model in models +# formulation = get_formulation(reservoir_model) +# attrs = IOM.get_attributes(reservoir_model) +# energy_target = get(attrs, "energy_target", false) +# hydro_budget = get(attrs, "hydro_budget", false) +# @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin +# template = get_thermal_dispatch_template_network(net) +# set_device_model!(template, turbine_model) +# set_device_model!(template, reservoir_model) + +# ED = DecisionModel( +# EconomicDispatchProblem, +# template, +# sys; +# optimizer = HiGHS_optimizer, +# ) +# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == +# ModelBuildStatus.BUILT +# psi_checksolve_test( +# ED, +# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], +# test_outputs[(net, formulation, energy_target)], +# 10000, #update to 10k to handle the difference in Mac and Ubuntu. Likely a HiGHS issue. +# ) +# end +# end +# end + +# ######################################################## +# ####### Hydro DISPATCH RUN OF RIVER BUDGET TEST ######## +# ######################################################## +# @testset "Test Hydro Dispatch Run Of River Formulations " begin +# device_model = DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; +# use_slacks = true, attributes = Dict("hydro_budget_interval" => Hour(24))) + +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) +# hy = only(get_components(HydroDispatch, sys)) +# max_power = get_max_active_power(hy) +# resolution = Dates.Hour(1) +# tstamp = range(DateTime("2024-01-01T00:00:00"); step = resolution, length = 48) +# data = ones(length(tstamp)) / (get_base_power(sys) * max_power) +# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) +# add_time_series!(sys, hy, ts) +# transform_single_time_series!(sys, Hour(24), Hour(24)) + +# model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, sys) +# mock_construct_device!(model, device_model) +# moi_tests(model, 48, 0, 50, 24, 0, false) +# psi_checkobjfun_test(model, GAEVF) +# end + +# @testset "Solve Hydro Dispatch Run Of River" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) + +# hydro_budget = 24 +# eps = 1e-6 + +# hy = only(get_components(HydroDispatch, c_sys5_hy)) +# max_power = get_max_active_power(hy) + +# tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) +# data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) +# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) +# add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) +# #remove_time_series!(c_sys5_hy, Deterministic) +# transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) + +# template_uc = OperationsProblemTemplate() +# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) +# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) +# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) +# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) +# set_device_model!( +# template_uc, +# DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; +# attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), +# ) +# model = DecisionModel( +# template_uc, +# c_sys5_hy; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# outputs = OptimizationProblemOutputs(model) +# df = read_variable(outputs, "ActivePowerVariable__HydroDispatch") +# hydro_power_sum = +# sum(df[!, :value]) + +# @test abs(hydro_power_sum - hydro_budget) <= eps +# end + +# @testset "Make Hydro Dispatch Run Of River with Reserves" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_hy = PSB.build_system( +# PSITestSystems, +# "c_sys5_hy"; +# add_single_time_series = true, +# add_reserves = true, +# ) + +# # Fix reserve parameters +# reg_up = only(get_components(VariableReserve{ReserveUp}, c_sys5_hy)) +# reg_dn = only(get_components(VariableReserve{ReserveDown}, c_sys5_hy)) +# set_deployed_fraction!(reg_up, 0.0) +# set_deployed_fraction!(reg_dn, 0.0) +# set_requirement!(reg_up, 0.01) +# set_requirement!(reg_dn, 0.01) + +# hydro_budget = 24 +# eps = 1e-6 + +# hy = only(get_components(HydroDispatch, c_sys5_hy)) + +# # Update Service allocation +# # Remove reg up from hydro, but leave reg dn +# remove_service!(hy, reg_up) + +# # Add reg up to thermals +# for th in get_components(ThermalStandard, c_sys5_hy) +# add_service!(th, reg_up, c_sys5_hy) +# end + +# max_power = get_max_active_power(hy) +# tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) +# data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) +# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) +# add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) + +# ## add extra hydro budget +# hy_copy = HydroDispatch(; +# name = "HydroDispatchCopy", +# available = get_available(hy), +# bus = get_bus(hy), +# active_power = get_active_power(hy), +# reactive_power = get_reactive_power(hy), +# rating = get_rating(hy), +# prime_mover_type = get_prime_mover_type(hy), +# active_power_limits = get_active_power_limits(hy), +# reactive_power_limits = get_reactive_power_limits(hy), +# ramp_limits = get_ramp_limits(hy), +# time_limits = get_time_limits(hy), +# base_power = get_base_power(hy), +# ) +# add_component!(c_sys5_hy, hy_copy) +# copy_time_series!(hy_copy, hy) + +# transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) + +# template_uc = OperationsProblemTemplate() +# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) +# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) +# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) +# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) +# set_device_model!( +# template_uc, +# DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; +# attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), +# ) +# set_service_model!(template_uc, VariableReserve{ReserveUp}, RangeReserve) +# set_service_model!(template_uc, VariableReserve{ReserveDown}, RangeReserve) +# model = DecisionModel( +# template_uc, +# c_sys5_hy; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +# end + +# ################################################ +# ####### Hydro PUMP ENERGY DISPATCH TEST ######## +# ################################################ +# @testset "Test Hydro Pump Energy Dispatch Formulations " begin +# device_model = DeviceModel( +# HydroPumpTurbine, +# HydroPumpEnergyDispatch; +# attributes = Dict{String, Any}( +# "reservation" => true, +# "energy_target" => true, +# ), +# ) + +# c_sys5_bat = +# PSB.build_system( +# PSITestSystems, +# "c_sys5_hydro_pump_energy"; +# add_reserves = true, +# add_single_time_series = true, +# ) + +# hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) +# transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) + +# model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, c_sys5_bat) +# mock_construct_device!(model, device_model) +# moi_tests(model, 72, 0, 48, 24, 0, true) +# psi_checkobjfun_test(model, GAEVF) +# end + +# @testset "Test Hydro Pump Energy Dispatch Formulations 2" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_bat = +# PSB.build_system( +# PSITestSystems, +# "c_sys5_hydro_pump_energy"; +# add_reserves = true, +# add_single_time_series = true, +# ) + +# hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) + +# transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) + +# template_uc = OperationsProblemTemplate() +# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) +# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) +# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) +# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) +# set_device_model!( +# template_uc, +# DeviceModel( +# HydroPumpTurbine, +# HydroPumpEnergyDispatch; +# attributes = Dict{String, Any}( +# "reservation" => true, +# "energy_target" => true, +# ), +# ), +# ) + +# model = DecisionModel( +# template_uc, +# c_sys5_bat; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +# end + +# ######################################### +# ######## HydroBlock model Tests ######### +# ######################################### + +# @testset "Test Hydro Block Optimization Formulation" begin +# output_dir = mktempdir(; cleanup = true) +# modeling_horizon = 3 * 24 * 1 + +# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") +# res = first(PSY.get_components(HydroReservoir, sys)) + +# set_head_to_volume_factor!(res, LinearCurve(1.0)) +# set_storage_level_limits!(res, (min = 4000, max = 6000)) +# set_level_targets!(res, 0.9) +# template_ed = OperationsProblemTemplate( +# NetworkModel( +# CopperPlatePowerModel; +# ), +# ) + +# set_device_model!(template_ed, ThermalStandard, ThermalBasicDispatch) +# set_device_model!(template_ed, PowerLoad, StaticPowerLoad) +# set_device_model!(template_ed, HydroReservoir, HydroWaterFactorModel) +# set_device_model!(template_ed, HydroTurbine, HydroWaterFactorModel) + +# model = DecisionModel( +# template_ed, +# sys; +# name = "ED", +# optimizer = ipopt_optimizer, +# optimizer_solve_log_print = true, +# store_variable_names = true, +# horizon = Hour(24), +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; optimizer = ipopt_optimizer, output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# outputs = OptimizationProblemOutputs(model) +# power_load = read_parameter(outputs, "ActivePowerTimeSeriesParameter__PowerLoad") +# reservoir_inflow = read_parameter(outputs, "InflowTimeSeriesParameter__HydroReservoir") + +# water_spillage = read_variable(outputs, "WaterSpillageVariable__HydroReservoir") +# thermal_power = read_variable(outputs, "ActivePowerVariable__ThermalStandard") +# hydro_power = read_variable(outputs, "ActivePowerVariable__HydroTurbine") + +# turbine_output = read_aux_variable(outputs, "HydroEnergyOutput__HydroTurbine") +# reservoir_volume = +# read_variable(outputs, "HydroReservoirVolumeVariable__HydroReservoir") + +# var = read_variable( +# outputs, +# "HydroReservoirVolumeVariable__HydroReservoir"; +# table_format = TableFormat.WIDE, +# ) + +# # check the second step is equal to the first step + dispatch +# end + +# ################################################ +# ######## HydroWaterModelReservoir TEST ######### +# ################################################ + +# @testset "Solve HydroWaterModelReservoir" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_head") +# reservoir = only(get_components(HydroReservoir, c_sys5_hy)) +# hydro_inflow_ts = get_time_series_array(Deterministic, reservoir, "inflow") + +# template = OperationsProblemTemplate() +# set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) +# set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) + +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) + +# model = DecisionModel( +# template, +# c_sys5_hy; +# optimizer = ipopt_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# outputs = OptimizationProblemOutputs(model) + +# moi_tests(model, 288, 0, 168, 168, 72, false) +# psi_checkobjfun_test(model, AffExpr) + +# df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") +# hydro_vol_df = +# read_variables(outputs, [(HydroReservoirVolumeVariable, HydroReservoir)])["HydroReservoirVolumeVariable__HydroReservoir"] +# hydro_head_df = +# read_variables(outputs, [(HydroReservoirHeadVariable, HydroReservoir)])["HydroReservoirHeadVariable__HydroReservoir"] +# hydro_spillage_df = +# read_variables(outputs, [(WaterSpillageVariable, HydroReservoir)])["WaterSpillageVariable__HydroReservoir"] +# hydro_inflow_df = +# read_parameters(outputs, [(InflowTimeSeriesParameter, HydroReservoir)])["InflowTimeSeriesParameter__HydroReservoir"] + +# total_inflow = sum(values(hydro_inflow_ts)) +# total_outflow = sum(df_outflow[!, :value]) +# total_spillage = sum(hydro_spillage_df[!, :value]) + +# calculated_vf = +# (hydro_vol_df[1, :value]) + +# ((total_inflow - total_outflow - total_spillage) * 3600 * 1e-9) + +# @test abs(calculated_vf - hydro_vol_df[end, :value]) <= 1e-4 + +# psi_checksolve_test( +# model, +# [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED], +# 210949.49, +# 1000, +# ) +# end @testset "Solve HydroWaterModelReservoir with bilinear approximations" begin output_dir = mktempdir(; cleanup = true) @@ -751,223 +751,223 @@ end ) end -@testset "Solve HydroWaterModelReservoir with Budget" begin - sys = PSB.build_system( - PSITestSystems, - "c_sys5_hy_turbine_head"; - force_build = true, - add_single_time_series = true, - ) - res = only(get_components(HydroReservoir, sys)) - inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") - tstamp = timestamp(inflow_array) - vals = values(inflow_array) - budget_array = TimeArray(tstamp, vals .* 0.5) - budget_ts = SingleTimeSeries("hydro_budget", budget_array) - add_time_series!(sys, res, budget_ts) - transform_single_time_series!(sys, Hour(24), Hour(24)) - - template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroWaterModelReservoir; - attributes = Dict("hydro_target" => false, "hydro_budget" => true), - ) - set_device_model!(template, reservoir_model) - - model = DecisionModel( - template, - sys; - name = "UC", - optimizer = HiGHS_optimizer, - store_variable_names = true, - optimizer_solve_log_print = false, - ) - @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT - @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - sol = OptimizationProblemOutputs(model) - flow = read_expression(sol, "TotalHydroFlowRateReservoirOutgoing__HydroReservoir")[ - !, - "value", - ] - @test sum(flow) <= sum(vals) / 4.0 -end - -@testset "Solve HydroWaterModelReservoir with Target" begin - sys = PSB.build_system( - PSITestSystems, - "c_sys5_hy_turbine_head"; - force_build = true, - add_single_time_series = true, - ) - res = only(get_components(HydroReservoir, sys)) - hydro_cost = HydroReservoirCost(1e5, 0.0, 0.0) - set_operation_cost!(res, hydro_cost) - inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") - tstamp = timestamp(inflow_array) - vals = values(inflow_array) - head_array = TimeArray(tstamp, 490.0 * ones(length(vals))) - target_ts = SingleTimeSeries("hydro_target", head_array) - add_time_series!(sys, res, target_ts) - transform_single_time_series!(sys, Hour(24), Hour(24)) - - template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) - reservoir_model = DeviceModel( - HydroReservoir, - HydroWaterModelReservoir; - attributes = Dict("hydro_target" => true, "hydro_budget" => false), - ) - set_device_model!(template, reservoir_model) - - model = DecisionModel( - template, - sys; - name = "UC", - optimizer = HiGHS_optimizer, - store_variable_names = true, - optimizer_solve_log_print = false, - ) - @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT - @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - sol = OptimizationProblemOutputs(model) - head = read_variable(sol, "HydroReservoirHeadVariable__HydroReservoir")[!, "value"] - @test head[24] >= 490 -end - -##################################################### -######## HydroWaterModelReservoir Cascading ######### -##################################################### - -@testset "Solve Cascading HydroWaterModelReservoir" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") - - template = OperationsProblemTemplate() - set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) - set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) - - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - - model = DecisionModel( - template, - c_sys5_hy; - optimizer = ipopt_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - outputs = OptimizationProblemOutputs(model) - - moi_tests(model, 504, 0, 216, 216, 120, false) - psi_checkobjfun_test(model, AffExpr) -end - -@testset "Solve Cascading HydroEnergyModelReservoir" begin - output_dir = mktempdir(; cleanup = true) - - c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") - - template = OperationsProblemTemplate() - set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) - set_device_model!(template, HydroReservoir, HydroEnergyModelReservoir) - - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - - model = DecisionModel( - template, - c_sys5_hy; - optimizer = HiGHS_optimizer, - store_variable_names = true, - ) - - @test build!(model; output_dir = output_dir) == - ModelBuildStatus.BUILT - - @test solve!(model; output_dir = output_dir) == - IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - outputs = OptimizationProblemOutputs(model) - - moi_tests(model, 360, 0, 168, 168, 72, false) - psi_checkobjfun_test(model, AffExpr) -end - -################################################################### -######## Energy HydroPump and Turbine in same Reservoir ######### -################################################################### - -@testset "Solve Energy model with both Turbine and Reservoir" begin - sys = build_hydro_with_both_pump_and_turbine() - template = OperationsProblemTemplate() - set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) - set_device_model!(template, HydroTurbine, HydroTurbineEnergyCommitment) - res_model = DeviceModel( - HydroReservoir, - HydroEnergyModelReservoir; - use_slacks = true, - attributes = Dict{String, Any}( - "energy_target" => false, - "hydro_budget" => true, - ), - ) - set_device_model!(template, res_model) - set_device_model!(template, HydroPumpTurbine, HydroPumpEnergyDispatch) - p_model = DeviceModel( - HydroPumpTurbine, - HydroPumpEnergyCommitment; - attributes = Dict{String, Any}( - "reservation" => true, - ), - ) - set_device_model!(template, p_model) - - set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) - set_device_model!(template, PowerLoad, StaticPowerLoad) - - model = DecisionModel( - template, - sys; - optimizer = HiGHS_optimizer, - store_variable_names = true, - calculate_conflict = true, - ) - - @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT - @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - - p_model = DeviceModel( - HydroPumpTurbine, - HydroPumpEnergyCommitment; - attributes = Dict{String, Any}( - "reservation" => false, - ), - ) - set_device_model!(template, p_model) - - model = DecisionModel( - template, - sys; - optimizer = HiGHS_optimizer, - store_variable_names = true, - calculate_conflict = true, - ) - - @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT - @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -end +# @testset "Solve HydroWaterModelReservoir with Budget" begin +# sys = PSB.build_system( +# PSITestSystems, +# "c_sys5_hy_turbine_head"; +# force_build = true, +# add_single_time_series = true, +# ) +# res = only(get_components(HydroReservoir, sys)) +# inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") +# tstamp = timestamp(inflow_array) +# vals = values(inflow_array) +# budget_array = TimeArray(tstamp, vals .* 0.5) +# budget_ts = SingleTimeSeries("hydro_budget", budget_array) +# add_time_series!(sys, res, budget_ts) +# transform_single_time_series!(sys, Hour(24), Hour(24)) + +# template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) +# set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroWaterModelReservoir; +# attributes = Dict("hydro_target" => false, "hydro_budget" => true), +# ) +# set_device_model!(template, reservoir_model) + +# model = DecisionModel( +# template, +# sys; +# name = "UC", +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# optimizer_solve_log_print = false, +# ) +# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT +# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# sol = OptimizationProblemOutputs(model) +# flow = read_expression(sol, "TotalHydroFlowRateReservoirOutgoing__HydroReservoir")[ +# !, +# "value", +# ] +# @test sum(flow) <= sum(vals) / 4.0 +# end + +# @testset "Solve HydroWaterModelReservoir with Target" begin +# sys = PSB.build_system( +# PSITestSystems, +# "c_sys5_hy_turbine_head"; +# force_build = true, +# add_single_time_series = true, +# ) +# res = only(get_components(HydroReservoir, sys)) +# hydro_cost = HydroReservoirCost(1e5, 0.0, 0.0) +# set_operation_cost!(res, hydro_cost) +# inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") +# tstamp = timestamp(inflow_array) +# vals = values(inflow_array) +# head_array = TimeArray(tstamp, 490.0 * ones(length(vals))) +# target_ts = SingleTimeSeries("hydro_target", head_array) +# add_time_series!(sys, res, target_ts) +# transform_single_time_series!(sys, Hour(24), Hour(24)) + +# template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) +# set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) +# reservoir_model = DeviceModel( +# HydroReservoir, +# HydroWaterModelReservoir; +# attributes = Dict("hydro_target" => true, "hydro_budget" => false), +# ) +# set_device_model!(template, reservoir_model) + +# model = DecisionModel( +# template, +# sys; +# name = "UC", +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# optimizer_solve_log_print = false, +# ) +# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT +# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# sol = OptimizationProblemOutputs(model) +# head = read_variable(sol, "HydroReservoirHeadVariable__HydroReservoir")[!, "value"] +# @test head[24] >= 490 +# end + +# ##################################################### +# ######## HydroWaterModelReservoir Cascading ######### +# ##################################################### + +# @testset "Solve Cascading HydroWaterModelReservoir" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") + +# template = OperationsProblemTemplate() +# set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) +# set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) + +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) + +# model = DecisionModel( +# template, +# c_sys5_hy; +# optimizer = ipopt_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# outputs = OptimizationProblemOutputs(model) + +# moi_tests(model, 504, 0, 216, 216, 120, false) +# psi_checkobjfun_test(model, AffExpr) +# end + +# @testset "Solve Cascading HydroEnergyModelReservoir" begin +# output_dir = mktempdir(; cleanup = true) + +# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") + +# template = OperationsProblemTemplate() +# set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) +# set_device_model!(template, HydroReservoir, HydroEnergyModelReservoir) + +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) + +# model = DecisionModel( +# template, +# c_sys5_hy; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# ) + +# @test build!(model; output_dir = output_dir) == +# ModelBuildStatus.BUILT + +# @test solve!(model; output_dir = output_dir) == +# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# outputs = OptimizationProblemOutputs(model) + +# moi_tests(model, 360, 0, 168, 168, 72, false) +# psi_checkobjfun_test(model, AffExpr) +# end + +# ################################################################### +# ######## Energy HydroPump and Turbine in same Reservoir ######### +# ################################################################### + +# @testset "Solve Energy model with both Turbine and Reservoir" begin +# sys = build_hydro_with_both_pump_and_turbine() +# template = OperationsProblemTemplate() +# set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) +# set_device_model!(template, HydroTurbine, HydroTurbineEnergyCommitment) +# res_model = DeviceModel( +# HydroReservoir, +# HydroEnergyModelReservoir; +# use_slacks = true, +# attributes = Dict{String, Any}( +# "energy_target" => false, +# "hydro_budget" => true, +# ), +# ) +# set_device_model!(template, res_model) +# set_device_model!(template, HydroPumpTurbine, HydroPumpEnergyDispatch) +# p_model = DeviceModel( +# HydroPumpTurbine, +# HydroPumpEnergyCommitment; +# attributes = Dict{String, Any}( +# "reservation" => true, +# ), +# ) +# set_device_model!(template, p_model) + +# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) +# set_device_model!(template, PowerLoad, StaticPowerLoad) + +# model = DecisionModel( +# template, +# sys; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# calculate_conflict = true, +# ) + +# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT +# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + +# p_model = DeviceModel( +# HydroPumpTurbine, +# HydroPumpEnergyCommitment; +# attributes = Dict{String, Any}( +# "reservation" => false, +# ), +# ) +# set_device_model!(template, p_model) + +# model = DecisionModel( +# template, +# sys; +# optimizer = HiGHS_optimizer, +# store_variable_names = true, +# calculate_conflict = true, +# ) + +# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT +# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +# end From 172b57d5fdc6641743eea985646620aec6ca3921 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Tue, 21 Apr 2026 17:09:44 -0400 Subject: [PATCH 6/8] uncomment tests --- test/test_device_hydro_constructors.jl | 1818 ++++++++++++------------ 1 file changed, 909 insertions(+), 909 deletions(-) diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index cf56601..7e11257 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -1,695 +1,695 @@ ######################################### #### RESERVOIR BUDGET DISPATCH TESTS #### ######################################### -# @testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 120, 0, 25, 24, 24, false) -# psi_checkobjfun_test(model, GAEVF) -# end - -# @testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# use_slacks = true, -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 192, 0, 49, 48, 24, false) -# psi_checkobjfun_test(model, GAEVF) -# end - -# ########################################### -# #### RESERVOIR BUDGET COMMITMENT TESTS #### -# ########################################### - -# @testset "Hydro DCPLossLess with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 144, 0, 25, 24, 24, true) -# psi_checkobjfun_test(model, GAEVF) -# end - -# @testset "Hydro ACPPowerModel with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 168, 0, 49, 48, 24, true) -# psi_checkobjfun_test(model, GAEVF) -# end - -# ######################################### -# #### RESERVOIR TARGET DISPATCH TESTS #### -# ######################################### - -# @testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => true, -# "hydro_budget" => false, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 120, 0, 24, 24, 25, false) -# psi_checkobjfun_test(model, GAEVF) -# end - -# @testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => true, -# "hydro_budget" => false, -# ), -# ) - -# c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") - -# # No Parameters Testing -# model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) -# mock_construct_device!(model, turbine_model) -# mock_construct_device!(model, reservoir_model) -# moi_tests(model, 144, 0, 48, 48, 25, false) -# psi_checkobjfun_test(model, GAEVF) -# end - -# ######################################### -# ########### RUN OF RIVER TESTS ########## -# ######################################### - -# @testset "Solving ED Hydro System using Dispatch Run of River" begin -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy") -# networks = [ACPPowerModel, DCPPowerModel] - -# test_outputs = -# Dict{Any, Float64}(ACPPowerModel => 136581.41, DCPPowerModel => 135382.37) - -# for net in networks -# @testset "HydroRoR ED model $(net)" begin -# template = get_thermal_dispatch_template_network(net) -# set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver) -# ED = DecisionModel( -# EconomicDispatchProblem, -# template, -# sys; -# optimizer = ipopt_optimizer, -# ) -# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == -# ModelBuildStatus.BUILT -# psi_checksolve_test( -# ED, -# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], -# test_outputs[net], -# 1000, -# ) -# end -# end -# end - -# @testset "Solving ED Hydro System using Commitment Run of River" begin -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy") -# net = CopperPlatePowerModel - -# template = get_thermal_dispatch_template_network(net) -# set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver) - -# @testset "HydroRoR ED model $(net)" begin -# ED = -# DecisionModel(UnitCommitmentProblem, template, sys; optimizer = HiGHS_optimizer) -# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == -# ModelBuildStatus.BUILT -# psi_checksolve_test(ED, [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], 135382.38, 1000) -# end -# end - -# @testset "Test Reserves from Hydro with RunOfRiver" begin -# template = OperationsProblemTemplate(CopperPlatePowerModel) -# set_device_model!(template, PowerLoad, StaticPowerLoad) -# set_device_model!(template, HydroTurbine, HydroDispatchRunOfRiver) -# set_service_model!( -# template, -# ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "Reserve5"), -# ) -# set_service_model!( -# template, -# ServiceModel(VariableReserve{ReserveDown}, RangeReserve, "Reserve6"), -# ) -# set_service_model!( -# template, -# ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve, "ORDC1"), -# ) - -# c_sys5_hyd = PSB.build_system( -# PSITestSystems, -# "c_sys5_hyd"; -# add_reserves = true, -# force_build = true, -# ) -# model = DecisionModel(template, c_sys5_hyd) -# @test build!(model; output_dir = mktempdir(; cleanup = true)) == -# ModelBuildStatus.BUILT -# # The value of this test needs to be revised -# # moi_tests(model, 240, 0, 48, 96, 72, false) -# end - -# ######################################### -# ###### RESERVOIR SYSTEM TESTS ########### -# ######################################### - -# @testset "Solving ED Hydro System using Dispatch with Reservoir" begin -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") -# networks = [ACPPowerModel, DCPPowerModel] -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) -# models = [ -# DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => true, -# "hydro_budget" => false, -# ), -# ), -# DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ), -# ] -# test_outputs = Dict{Any, Float64}( -# (ACPPowerModel, HydroEnergyModelReservoir, true) => 136296.0, -# (DCPPowerModel, HydroEnergyModelReservoir, true) => 135404.0, -# (ACPPowerModel, HydroEnergyModelReservoir, false) => 131305.0, -# (DCPPowerModel, HydroEnergyModelReservoir, false) => 130479.0, -# ) - -# for net in networks -# for reservoir_model in models -# formulation = get_formulation(reservoir_model) -# attrs = IOM.get_attributes(reservoir_model) -# energy_target = get(attrs, "energy_target", false) -# hydro_budget = get(attrs, "hydro_budget", false) -# @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin -# template = get_thermal_dispatch_template_network(net) -# set_device_model!(template, turbine_model) -# set_device_model!(template, reservoir_model) - -# ED = DecisionModel( -# EconomicDispatchProblem, -# template, -# sys; -# optimizer = ipopt_optimizer, -# ) -# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == -# ModelBuildStatus.BUILT -# psi_checksolve_test( -# ED, -# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], -# test_outputs[(net, formulation, energy_target)], -# 1000, -# ) -# end -# end -# end -# end - -# @testset "Solving ED Hydro System using Commitment with Reservoir" begin -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") -# net = DCPPowerModel -# turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) -# models = [ -# DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => true, -# "hydro_budget" => false, -# ), -# ), -# DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ), -# ] -# test_outputs = Dict{Any, Float64}( -# (DCPPowerModel, HydroEnergyModelReservoir, true) => 141061.0, -# (DCPPowerModel, HydroEnergyModelReservoir, false) => 144109.0, -# ) - -# for reservoir_model in models -# formulation = get_formulation(reservoir_model) -# attrs = IOM.get_attributes(reservoir_model) -# energy_target = get(attrs, "energy_target", false) -# hydro_budget = get(attrs, "hydro_budget", false) -# @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin -# template = get_thermal_dispatch_template_network(net) -# set_device_model!(template, turbine_model) -# set_device_model!(template, reservoir_model) - -# ED = DecisionModel( -# EconomicDispatchProblem, -# template, -# sys; -# optimizer = HiGHS_optimizer, -# ) -# @test build!(ED; output_dir = mktempdir(; cleanup = true)) == -# ModelBuildStatus.BUILT -# psi_checksolve_test( -# ED, -# [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], -# test_outputs[(net, formulation, energy_target)], -# 10000, #update to 10k to handle the difference in Mac and Ubuntu. Likely a HiGHS issue. -# ) -# end -# end -# end - -# ######################################################## -# ####### Hydro DISPATCH RUN OF RIVER BUDGET TEST ######## -# ######################################################## -# @testset "Test Hydro Dispatch Run Of River Formulations " begin -# device_model = DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; -# use_slacks = true, attributes = Dict("hydro_budget_interval" => Hour(24))) - -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) -# hy = only(get_components(HydroDispatch, sys)) -# max_power = get_max_active_power(hy) -# resolution = Dates.Hour(1) -# tstamp = range(DateTime("2024-01-01T00:00:00"); step = resolution, length = 48) -# data = ones(length(tstamp)) / (get_base_power(sys) * max_power) -# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) -# add_time_series!(sys, hy, ts) -# transform_single_time_series!(sys, Hour(24), Hour(24)) - -# model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, sys) -# mock_construct_device!(model, device_model) -# moi_tests(model, 48, 0, 50, 24, 0, false) -# psi_checkobjfun_test(model, GAEVF) -# end - -# @testset "Solve Hydro Dispatch Run Of River" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) - -# hydro_budget = 24 -# eps = 1e-6 - -# hy = only(get_components(HydroDispatch, c_sys5_hy)) -# max_power = get_max_active_power(hy) - -# tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) -# data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) -# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) -# add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) -# #remove_time_series!(c_sys5_hy, Deterministic) -# transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) - -# template_uc = OperationsProblemTemplate() -# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) -# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) -# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) -# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) -# set_device_model!( -# template_uc, -# DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; -# attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), -# ) -# model = DecisionModel( -# template_uc, -# c_sys5_hy; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# outputs = OptimizationProblemOutputs(model) -# df = read_variable(outputs, "ActivePowerVariable__HydroDispatch") -# hydro_power_sum = -# sum(df[!, :value]) - -# @test abs(hydro_power_sum - hydro_budget) <= eps -# end - -# @testset "Make Hydro Dispatch Run Of River with Reserves" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_hy = PSB.build_system( -# PSITestSystems, -# "c_sys5_hy"; -# add_single_time_series = true, -# add_reserves = true, -# ) - -# # Fix reserve parameters -# reg_up = only(get_components(VariableReserve{ReserveUp}, c_sys5_hy)) -# reg_dn = only(get_components(VariableReserve{ReserveDown}, c_sys5_hy)) -# set_deployed_fraction!(reg_up, 0.0) -# set_deployed_fraction!(reg_dn, 0.0) -# set_requirement!(reg_up, 0.01) -# set_requirement!(reg_dn, 0.01) - -# hydro_budget = 24 -# eps = 1e-6 - -# hy = only(get_components(HydroDispatch, c_sys5_hy)) - -# # Update Service allocation -# # Remove reg up from hydro, but leave reg dn -# remove_service!(hy, reg_up) - -# # Add reg up to thermals -# for th in get_components(ThermalStandard, c_sys5_hy) -# add_service!(th, reg_up, c_sys5_hy) -# end - -# max_power = get_max_active_power(hy) -# tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) -# data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) -# ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) -# add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) - -# ## add extra hydro budget -# hy_copy = HydroDispatch(; -# name = "HydroDispatchCopy", -# available = get_available(hy), -# bus = get_bus(hy), -# active_power = get_active_power(hy), -# reactive_power = get_reactive_power(hy), -# rating = get_rating(hy), -# prime_mover_type = get_prime_mover_type(hy), -# active_power_limits = get_active_power_limits(hy), -# reactive_power_limits = get_reactive_power_limits(hy), -# ramp_limits = get_ramp_limits(hy), -# time_limits = get_time_limits(hy), -# base_power = get_base_power(hy), -# ) -# add_component!(c_sys5_hy, hy_copy) -# copy_time_series!(hy_copy, hy) - -# transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) - -# template_uc = OperationsProblemTemplate() -# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) -# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) -# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) -# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) -# set_device_model!( -# template_uc, -# DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; -# attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), -# ) -# set_service_model!(template_uc, VariableReserve{ReserveUp}, RangeReserve) -# set_service_model!(template_uc, VariableReserve{ReserveDown}, RangeReserve) -# model = DecisionModel( -# template_uc, -# c_sys5_hy; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -# end - -# ################################################ -# ####### Hydro PUMP ENERGY DISPATCH TEST ######## -# ################################################ -# @testset "Test Hydro Pump Energy Dispatch Formulations " begin -# device_model = DeviceModel( -# HydroPumpTurbine, -# HydroPumpEnergyDispatch; -# attributes = Dict{String, Any}( -# "reservation" => true, -# "energy_target" => true, -# ), -# ) - -# c_sys5_bat = -# PSB.build_system( -# PSITestSystems, -# "c_sys5_hydro_pump_energy"; -# add_reserves = true, -# add_single_time_series = true, -# ) - -# hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) -# transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) - -# model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, c_sys5_bat) -# mock_construct_device!(model, device_model) -# moi_tests(model, 72, 0, 48, 24, 0, true) -# psi_checkobjfun_test(model, GAEVF) -# end - -# @testset "Test Hydro Pump Energy Dispatch Formulations 2" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_bat = -# PSB.build_system( -# PSITestSystems, -# "c_sys5_hydro_pump_energy"; -# add_reserves = true, -# add_single_time_series = true, -# ) - -# hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) - -# transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) - -# template_uc = OperationsProblemTemplate() -# set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) -# set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) -# set_device_model!(template_uc, PowerLoad, StaticPowerLoad) -# set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) -# set_device_model!( -# template_uc, -# DeviceModel( -# HydroPumpTurbine, -# HydroPumpEnergyDispatch; -# attributes = Dict{String, Any}( -# "reservation" => true, -# "energy_target" => true, -# ), -# ), -# ) - -# model = DecisionModel( -# template_uc, -# c_sys5_bat; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -# end - -# ######################################### -# ######## HydroBlock model Tests ######### -# ######################################### - -# @testset "Test Hydro Block Optimization Formulation" begin -# output_dir = mktempdir(; cleanup = true) -# modeling_horizon = 3 * 24 * 1 - -# sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") -# res = first(PSY.get_components(HydroReservoir, sys)) - -# set_head_to_volume_factor!(res, LinearCurve(1.0)) -# set_storage_level_limits!(res, (min = 4000, max = 6000)) -# set_level_targets!(res, 0.9) -# template_ed = OperationsProblemTemplate( -# NetworkModel( -# CopperPlatePowerModel; -# ), -# ) - -# set_device_model!(template_ed, ThermalStandard, ThermalBasicDispatch) -# set_device_model!(template_ed, PowerLoad, StaticPowerLoad) -# set_device_model!(template_ed, HydroReservoir, HydroWaterFactorModel) -# set_device_model!(template_ed, HydroTurbine, HydroWaterFactorModel) - -# model = DecisionModel( -# template_ed, -# sys; -# name = "ED", -# optimizer = ipopt_optimizer, -# optimizer_solve_log_print = true, -# store_variable_names = true, -# horizon = Hour(24), -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; optimizer = ipopt_optimizer, output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# outputs = OptimizationProblemOutputs(model) -# power_load = read_parameter(outputs, "ActivePowerTimeSeriesParameter__PowerLoad") -# reservoir_inflow = read_parameter(outputs, "InflowTimeSeriesParameter__HydroReservoir") - -# water_spillage = read_variable(outputs, "WaterSpillageVariable__HydroReservoir") -# thermal_power = read_variable(outputs, "ActivePowerVariable__ThermalStandard") -# hydro_power = read_variable(outputs, "ActivePowerVariable__HydroTurbine") - -# turbine_output = read_aux_variable(outputs, "HydroEnergyOutput__HydroTurbine") -# reservoir_volume = -# read_variable(outputs, "HydroReservoirVolumeVariable__HydroReservoir") - -# var = read_variable( -# outputs, -# "HydroReservoirVolumeVariable__HydroReservoir"; -# table_format = TableFormat.WIDE, -# ) - -# # check the second step is equal to the first step + dispatch -# end - -# ################################################ -# ######## HydroWaterModelReservoir TEST ######### -# ################################################ - -# @testset "Solve HydroWaterModelReservoir" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_head") -# reservoir = only(get_components(HydroReservoir, c_sys5_hy)) -# hydro_inflow_ts = get_time_series_array(Deterministic, reservoir, "inflow") - -# template = OperationsProblemTemplate() -# set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) -# set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) - -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) - -# model = DecisionModel( -# template, -# c_sys5_hy; -# optimizer = ipopt_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# outputs = OptimizationProblemOutputs(model) - -# moi_tests(model, 288, 0, 168, 168, 72, false) -# psi_checkobjfun_test(model, AffExpr) - -# df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") -# hydro_vol_df = -# read_variables(outputs, [(HydroReservoirVolumeVariable, HydroReservoir)])["HydroReservoirVolumeVariable__HydroReservoir"] -# hydro_head_df = -# read_variables(outputs, [(HydroReservoirHeadVariable, HydroReservoir)])["HydroReservoirHeadVariable__HydroReservoir"] -# hydro_spillage_df = -# read_variables(outputs, [(WaterSpillageVariable, HydroReservoir)])["WaterSpillageVariable__HydroReservoir"] -# hydro_inflow_df = -# read_parameters(outputs, [(InflowTimeSeriesParameter, HydroReservoir)])["InflowTimeSeriesParameter__HydroReservoir"] - -# total_inflow = sum(values(hydro_inflow_ts)) -# total_outflow = sum(df_outflow[!, :value]) -# total_spillage = sum(hydro_spillage_df[!, :value]) - -# calculated_vf = -# (hydro_vol_df[1, :value]) + -# ((total_inflow - total_outflow - total_spillage) * 3600 * 1e-9) - -# @test abs(calculated_vf - hydro_vol_df[end, :value]) <= 1e-4 - -# psi_checksolve_test( -# model, -# [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED], -# 210949.49, -# 1000, -# ) -# end +@testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 120, 0, 25, 24, 24, false) + psi_checkobjfun_test(model, GAEVF) +end + +@testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with budget) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + use_slacks = true, + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 192, 0, 49, 48, 24, false) + psi_checkobjfun_test(model, GAEVF) +end + +########################################### +#### RESERVOIR BUDGET COMMITMENT TESTS #### +########################################### + +@testset "Hydro DCPLossLess with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 144, 0, 25, 24, 24, true) + psi_checkobjfun_test(model, GAEVF) +end + +@testset "Hydro ACPPowerModel with HydroTurbineEnergyCommitment and HydroEnergyModelReservoir (with budget) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 168, 0, 49, 48, 24, true) + psi_checkobjfun_test(model, GAEVF) +end + +######################################### +#### RESERVOIR TARGET DISPATCH TESTS #### +######################################### + +@testset "Hydro DCPLossLess with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => true, + "hydro_budget" => false, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, DCPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 120, 0, 24, 24, 25, false) + psi_checkobjfun_test(model, GAEVF) +end + +@testset "Hydro ACPPowerModel with HydroTurbineEnergyDispatch and HydroEnergyModelReservoir (with target) Formulations" begin + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => true, + "hydro_budget" => false, + ), + ) + + c_sys5_hyd = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + + # No Parameters Testing + model = DecisionModel(MockOperationProblem, ACPPowerModel, c_sys5_hyd) + mock_construct_device!(model, turbine_model) + mock_construct_device!(model, reservoir_model) + moi_tests(model, 144, 0, 48, 48, 25, false) + psi_checkobjfun_test(model, GAEVF) +end + +######################################### +########### RUN OF RIVER TESTS ########## +######################################### + +@testset "Solving ED Hydro System using Dispatch Run of River" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_hy") + networks = [ACPPowerModel, DCPPowerModel] + + test_outputs = + Dict{Any, Float64}(ACPPowerModel => 136581.41, DCPPowerModel => 135382.37) + + for net in networks + @testset "HydroRoR ED model $(net)" begin + template = get_thermal_dispatch_template_network(net) + set_device_model!(template, HydroDispatch, HydroDispatchRunOfRiver) + ED = DecisionModel( + EconomicDispatchProblem, + template, + sys; + optimizer = ipopt_optimizer, + ) + @test build!(ED; output_dir = mktempdir(; cleanup = true)) == + ModelBuildStatus.BUILT + psi_checksolve_test( + ED, + [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], + test_outputs[net], + 1000, + ) + end + end +end + +@testset "Solving ED Hydro System using Commitment Run of River" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_hy") + net = CopperPlatePowerModel + + template = get_thermal_dispatch_template_network(net) + set_device_model!(template, HydroDispatch, HydroCommitmentRunOfRiver) + + @testset "HydroRoR ED model $(net)" begin + ED = + DecisionModel(UnitCommitmentProblem, template, sys; optimizer = HiGHS_optimizer) + @test build!(ED; output_dir = mktempdir(; cleanup = true)) == + ModelBuildStatus.BUILT + psi_checksolve_test(ED, [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], 135382.38, 1000) + end +end + +@testset "Test Reserves from Hydro with RunOfRiver" begin + template = OperationsProblemTemplate(CopperPlatePowerModel) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, HydroTurbine, HydroDispatchRunOfRiver) + set_service_model!( + template, + ServiceModel(VariableReserve{ReserveUp}, RangeReserve, "Reserve5"), + ) + set_service_model!( + template, + ServiceModel(VariableReserve{ReserveDown}, RangeReserve, "Reserve6"), + ) + set_service_model!( + template, + ServiceModel(ReserveDemandCurve{ReserveUp}, StepwiseCostReserve, "ORDC1"), + ) + + c_sys5_hyd = PSB.build_system( + PSITestSystems, + "c_sys5_hyd"; + add_reserves = true, + force_build = true, + ) + model = DecisionModel(template, c_sys5_hyd) + @test build!(model; output_dir = mktempdir(; cleanup = true)) == + ModelBuildStatus.BUILT + # The value of this test needs to be revised + # moi_tests(model, 240, 0, 48, 96, 72, false) +end + +######################################### +###### RESERVOIR SYSTEM TESTS ########### +######################################### + +@testset "Solving ED Hydro System using Dispatch with Reservoir" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + networks = [ACPPowerModel, DCPPowerModel] + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyDispatch) + models = [ + DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => true, + "hydro_budget" => false, + ), + ), + DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ), + ] + test_outputs = Dict{Any, Float64}( + (ACPPowerModel, HydroEnergyModelReservoir, true) => 136296.0, + (DCPPowerModel, HydroEnergyModelReservoir, true) => 135404.0, + (ACPPowerModel, HydroEnergyModelReservoir, false) => 131305.0, + (DCPPowerModel, HydroEnergyModelReservoir, false) => 130479.0, + ) + + for net in networks + for reservoir_model in models + formulation = get_formulation(reservoir_model) + attrs = IOM.get_attributes(reservoir_model) + energy_target = get(attrs, "energy_target", false) + hydro_budget = get(attrs, "hydro_budget", false) + @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin + template = get_thermal_dispatch_template_network(net) + set_device_model!(template, turbine_model) + set_device_model!(template, reservoir_model) + + ED = DecisionModel( + EconomicDispatchProblem, + template, + sys; + optimizer = ipopt_optimizer, + ) + @test build!(ED; output_dir = mktempdir(; cleanup = true)) == + ModelBuildStatus.BUILT + psi_checksolve_test( + ED, + [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], + test_outputs[(net, formulation, energy_target)], + 1000, + ) + end + end + end +end + +@testset "Solving ED Hydro System using Commitment with Reservoir" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + net = DCPPowerModel + turbine_model = DeviceModel(HydroTurbine, HydroTurbineEnergyCommitment) + models = [ + DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => true, + "hydro_budget" => false, + ), + ), + DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ), + ] + test_outputs = Dict{Any, Float64}( + (DCPPowerModel, HydroEnergyModelReservoir, true) => 141061.0, + (DCPPowerModel, HydroEnergyModelReservoir, false) => 144109.0, + ) + + for reservoir_model in models + formulation = get_formulation(reservoir_model) + attrs = IOM.get_attributes(reservoir_model) + energy_target = get(attrs, "energy_target", false) + hydro_budget = get(attrs, "hydro_budget", false) + @testset "$(formulation) with energy_target: $energy_target, and hydro_budget: $hydro_budget, ED model on $(net)" begin + template = get_thermal_dispatch_template_network(net) + set_device_model!(template, turbine_model) + set_device_model!(template, reservoir_model) + + ED = DecisionModel( + EconomicDispatchProblem, + template, + sys; + optimizer = HiGHS_optimizer, + ) + @test build!(ED; output_dir = mktempdir(; cleanup = true)) == + ModelBuildStatus.BUILT + psi_checksolve_test( + ED, + [MOI.OPTIMAL, MOI.LOCALLY_SOLVED], + test_outputs[(net, formulation, energy_target)], + 10000, #update to 10k to handle the difference in Mac and Ubuntu. Likely a HiGHS issue. + ) + end + end +end + +######################################################## +####### Hydro DISPATCH RUN OF RIVER BUDGET TEST ######## +######################################################## +@testset "Test Hydro Dispatch Run Of River Formulations " begin + device_model = DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; + use_slacks = true, attributes = Dict("hydro_budget_interval" => Hour(24))) + + sys = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) + hy = only(get_components(HydroDispatch, sys)) + max_power = get_max_active_power(hy) + resolution = Dates.Hour(1) + tstamp = range(DateTime("2024-01-01T00:00:00"); step = resolution, length = 48) + data = ones(length(tstamp)) / (get_base_power(sys) * max_power) + ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) + add_time_series!(sys, hy, ts) + transform_single_time_series!(sys, Hour(24), Hour(24)) + + model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, sys) + mock_construct_device!(model, device_model) + moi_tests(model, 48, 0, 50, 24, 0, false) + psi_checkobjfun_test(model, GAEVF) +end + +@testset "Solve Hydro Dispatch Run Of River" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy"; add_single_time_series = true) + + hydro_budget = 24 + eps = 1e-6 + + hy = only(get_components(HydroDispatch, c_sys5_hy)) + max_power = get_max_active_power(hy) + + tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) + data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) + ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) + add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) + #remove_time_series!(c_sys5_hy, Deterministic) + transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) + + template_uc = OperationsProblemTemplate() + set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) + set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) + set_device_model!(template_uc, PowerLoad, StaticPowerLoad) + set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) + set_device_model!( + template_uc, + DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; + attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), + ) + model = DecisionModel( + template_uc, + c_sys5_hy; + optimizer = HiGHS_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + df = read_variable(outputs, "ActivePowerVariable__HydroDispatch") + hydro_power_sum = + sum(df[!, :value]) + + @test abs(hydro_power_sum - hydro_budget) <= eps +end + +@testset "Make Hydro Dispatch Run Of River with Reserves" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system( + PSITestSystems, + "c_sys5_hy"; + add_single_time_series = true, + add_reserves = true, + ) + + # Fix reserve parameters + reg_up = only(get_components(VariableReserve{ReserveUp}, c_sys5_hy)) + reg_dn = only(get_components(VariableReserve{ReserveDown}, c_sys5_hy)) + set_deployed_fraction!(reg_up, 0.0) + set_deployed_fraction!(reg_dn, 0.0) + set_requirement!(reg_up, 0.01) + set_requirement!(reg_dn, 0.01) + + hydro_budget = 24 + eps = 1e-6 + + hy = only(get_components(HydroDispatch, c_sys5_hy)) + + # Update Service allocation + # Remove reg up from hydro, but leave reg dn + remove_service!(hy, reg_up) + + # Add reg up to thermals + for th in get_components(ThermalStandard, c_sys5_hy) + add_service!(th, reg_up, c_sys5_hy) + end + + max_power = get_max_active_power(hy) + tstamp = range(DateTime("2024-01-01T00:00:00"); step = Dates.Hour(1), length = 48) + data = ones(length(tstamp)) / (get_base_power(c_sys5_hy) * max_power) + ts = SingleTimeSeries("hydro_budget", TimeArray(tstamp, data)) + add_time_series!(c_sys5_hy, first(get_components(HydroDispatch, c_sys5_hy)), ts) + + ## add extra hydro budget + hy_copy = HydroDispatch(; + name = "HydroDispatchCopy", + available = get_available(hy), + bus = get_bus(hy), + active_power = get_active_power(hy), + reactive_power = get_reactive_power(hy), + rating = get_rating(hy), + prime_mover_type = get_prime_mover_type(hy), + active_power_limits = get_active_power_limits(hy), + reactive_power_limits = get_reactive_power_limits(hy), + ramp_limits = get_ramp_limits(hy), + time_limits = get_time_limits(hy), + base_power = get_base_power(hy), + ) + add_component!(c_sys5_hy, hy_copy) + copy_time_series!(hy_copy, hy) + + transform_single_time_series!(c_sys5_hy, Hour(24), Hour(24)) + + template_uc = OperationsProblemTemplate() + set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) + set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) + set_device_model!(template_uc, PowerLoad, StaticPowerLoad) + set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) + set_device_model!( + template_uc, + DeviceModel(HydroDispatch, HydroDispatchRunOfRiverBudget; + attributes = Dict("hydro_budget_interval" => Hour(hydro_budget))), + ) + set_service_model!(template_uc, VariableReserve{ReserveUp}, RangeReserve) + set_service_model!(template_uc, VariableReserve{ReserveDown}, RangeReserve) + model = DecisionModel( + template_uc, + c_sys5_hy; + optimizer = HiGHS_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +end + +################################################ +####### Hydro PUMP ENERGY DISPATCH TEST ######## +################################################ +@testset "Test Hydro Pump Energy Dispatch Formulations " begin + device_model = DeviceModel( + HydroPumpTurbine, + HydroPumpEnergyDispatch; + attributes = Dict{String, Any}( + "reservation" => true, + "energy_target" => true, + ), + ) + + c_sys5_bat = + PSB.build_system( + PSITestSystems, + "c_sys5_hydro_pump_energy"; + add_reserves = true, + add_single_time_series = true, + ) + + hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) + transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) + + model = DecisionModel(MockOperationProblem, CopperPlatePowerModel, c_sys5_bat) + mock_construct_device!(model, device_model) + moi_tests(model, 72, 0, 48, 24, 0, true) + psi_checkobjfun_test(model, GAEVF) +end + +@testset "Test Hydro Pump Energy Dispatch Formulations 2" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_bat = + PSB.build_system( + PSITestSystems, + "c_sys5_hydro_pump_energy"; + add_reserves = true, + add_single_time_series = true, + ) + + hy_pump = first(PSY.get_components(HydroPumpTurbine, c_sys5_bat)) + + transform_single_time_series!(c_sys5_bat, Hour(24), Hour(24)) + + template_uc = OperationsProblemTemplate() + set_device_model!(template_uc, ThermalStandard, ThermalBasicUnitCommitment) + set_device_model!(template_uc, RenewableDispatch, RenewableFullDispatch) + set_device_model!(template_uc, PowerLoad, StaticPowerLoad) + set_device_model!(template_uc, RenewableNonDispatch, FixedOutput) + set_device_model!( + template_uc, + DeviceModel( + HydroPumpTurbine, + HydroPumpEnergyDispatch; + attributes = Dict{String, Any}( + "reservation" => true, + "energy_target" => true, + ), + ), + ) + + model = DecisionModel( + template_uc, + c_sys5_bat; + optimizer = HiGHS_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +end + +######################################### +######## HydroBlock model Tests ######### +######################################### + +@testset "Test Hydro Block Optimization Formulation" begin + output_dir = mktempdir(; cleanup = true) + modeling_horizon = 3 * 24 * 1 + + sys = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_energy") + res = first(PSY.get_components(HydroReservoir, sys)) + + set_head_to_volume_factor!(res, LinearCurve(1.0)) + set_storage_level_limits!(res, (min = 4000, max = 6000)) + set_level_targets!(res, 0.9) + template_ed = OperationsProblemTemplate( + NetworkModel( + CopperPlatePowerModel; + ), + ) + + set_device_model!(template_ed, ThermalStandard, ThermalBasicDispatch) + set_device_model!(template_ed, PowerLoad, StaticPowerLoad) + set_device_model!(template_ed, HydroReservoir, HydroWaterFactorModel) + set_device_model!(template_ed, HydroTurbine, HydroWaterFactorModel) + + model = DecisionModel( + template_ed, + sys; + name = "ED", + optimizer = ipopt_optimizer, + optimizer_solve_log_print = true, + store_variable_names = true, + horizon = Hour(24), + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; optimizer = ipopt_optimizer, output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + power_load = read_parameter(outputs, "ActivePowerTimeSeriesParameter__PowerLoad") + reservoir_inflow = read_parameter(outputs, "InflowTimeSeriesParameter__HydroReservoir") + + water_spillage = read_variable(outputs, "WaterSpillageVariable__HydroReservoir") + thermal_power = read_variable(outputs, "ActivePowerVariable__ThermalStandard") + hydro_power = read_variable(outputs, "ActivePowerVariable__HydroTurbine") + + turbine_output = read_aux_variable(outputs, "HydroEnergyOutput__HydroTurbine") + reservoir_volume = + read_variable(outputs, "HydroReservoirVolumeVariable__HydroReservoir") + + var = read_variable( + outputs, + "HydroReservoirVolumeVariable__HydroReservoir"; + table_format = TableFormat.WIDE, + ) + + # check the second step is equal to the first step + dispatch +end + +################################################ +######## HydroWaterModelReservoir TEST ######### +################################################ + +@testset "Solve HydroWaterModelReservoir" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_turbine_head") + reservoir = only(get_components(HydroReservoir, c_sys5_hy)) + hydro_inflow_ts = get_time_series_array(Deterministic, reservoir, "inflow") + + template = OperationsProblemTemplate() + set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) + set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + + model = DecisionModel( + template, + c_sys5_hy; + optimizer = ipopt_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + + moi_tests(model, 288, 0, 168, 168, 72, false) + psi_checkobjfun_test(model, AffExpr) + + df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") + hydro_vol_df = + read_variables(outputs, [(HydroReservoirVolumeVariable, HydroReservoir)])["HydroReservoirVolumeVariable__HydroReservoir"] + hydro_head_df = + read_variables(outputs, [(HydroReservoirHeadVariable, HydroReservoir)])["HydroReservoirHeadVariable__HydroReservoir"] + hydro_spillage_df = + read_variables(outputs, [(WaterSpillageVariable, HydroReservoir)])["WaterSpillageVariable__HydroReservoir"] + hydro_inflow_df = + read_parameters(outputs, [(InflowTimeSeriesParameter, HydroReservoir)])["InflowTimeSeriesParameter__HydroReservoir"] + + total_inflow = sum(values(hydro_inflow_ts)) + total_outflow = sum(df_outflow[!, :value]) + total_spillage = sum(hydro_spillage_df[!, :value]) + + calculated_vf = + (hydro_vol_df[1, :value]) + + ((total_inflow - total_outflow - total_spillage) * 3600 * 1e-9) + + @test abs(calculated_vf - hydro_vol_df[end, :value]) <= 1e-4 + + psi_checksolve_test( + model, + [MOI.OPTIMAL, MOI.ALMOST_OPTIMAL, MOI.LOCALLY_SOLVED], + 210949.49, + 1000, + ) +end @testset "Solve HydroWaterModelReservoir with bilinear approximations" begin output_dir = mktempdir(; cleanup = true) @@ -751,223 +751,223 @@ ) end -# @testset "Solve HydroWaterModelReservoir with Budget" begin -# sys = PSB.build_system( -# PSITestSystems, -# "c_sys5_hy_turbine_head"; -# force_build = true, -# add_single_time_series = true, -# ) -# res = only(get_components(HydroReservoir, sys)) -# inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") -# tstamp = timestamp(inflow_array) -# vals = values(inflow_array) -# budget_array = TimeArray(tstamp, vals .* 0.5) -# budget_ts = SingleTimeSeries("hydro_budget", budget_array) -# add_time_series!(sys, res, budget_ts) -# transform_single_time_series!(sys, Hour(24), Hour(24)) - -# template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) -# set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroWaterModelReservoir; -# attributes = Dict("hydro_target" => false, "hydro_budget" => true), -# ) -# set_device_model!(template, reservoir_model) - -# model = DecisionModel( -# template, -# sys; -# name = "UC", -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# optimizer_solve_log_print = false, -# ) -# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT -# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# sol = OptimizationProblemOutputs(model) -# flow = read_expression(sol, "TotalHydroFlowRateReservoirOutgoing__HydroReservoir")[ -# !, -# "value", -# ] -# @test sum(flow) <= sum(vals) / 4.0 -# end - -# @testset "Solve HydroWaterModelReservoir with Target" begin -# sys = PSB.build_system( -# PSITestSystems, -# "c_sys5_hy_turbine_head"; -# force_build = true, -# add_single_time_series = true, -# ) -# res = only(get_components(HydroReservoir, sys)) -# hydro_cost = HydroReservoirCost(1e5, 0.0, 0.0) -# set_operation_cost!(res, hydro_cost) -# inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") -# tstamp = timestamp(inflow_array) -# vals = values(inflow_array) -# head_array = TimeArray(tstamp, 490.0 * ones(length(vals))) -# target_ts = SingleTimeSeries("hydro_target", head_array) -# add_time_series!(sys, res, target_ts) -# transform_single_time_series!(sys, Hour(24), Hour(24)) - -# template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) -# set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) -# reservoir_model = DeviceModel( -# HydroReservoir, -# HydroWaterModelReservoir; -# attributes = Dict("hydro_target" => true, "hydro_budget" => false), -# ) -# set_device_model!(template, reservoir_model) - -# model = DecisionModel( -# template, -# sys; -# name = "UC", -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# optimizer_solve_log_print = false, -# ) -# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT -# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# sol = OptimizationProblemOutputs(model) -# head = read_variable(sol, "HydroReservoirHeadVariable__HydroReservoir")[!, "value"] -# @test head[24] >= 490 -# end - -# ##################################################### -# ######## HydroWaterModelReservoir Cascading ######### -# ##################################################### - -# @testset "Solve Cascading HydroWaterModelReservoir" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") - -# template = OperationsProblemTemplate() -# set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) -# set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) - -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) - -# model = DecisionModel( -# template, -# c_sys5_hy; -# optimizer = ipopt_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# outputs = OptimizationProblemOutputs(model) - -# moi_tests(model, 504, 0, 216, 216, 120, false) -# psi_checkobjfun_test(model, AffExpr) -# end - -# @testset "Solve Cascading HydroEnergyModelReservoir" begin -# output_dir = mktempdir(; cleanup = true) - -# c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") - -# template = OperationsProblemTemplate() -# set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) -# set_device_model!(template, HydroReservoir, HydroEnergyModelReservoir) - -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) - -# model = DecisionModel( -# template, -# c_sys5_hy; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# ) - -# @test build!(model; output_dir = output_dir) == -# ModelBuildStatus.BUILT - -# @test solve!(model; output_dir = output_dir) == -# IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# outputs = OptimizationProblemOutputs(model) - -# moi_tests(model, 360, 0, 168, 168, 72, false) -# psi_checkobjfun_test(model, AffExpr) -# end - -# ################################################################### -# ######## Energy HydroPump and Turbine in same Reservoir ######### -# ################################################################### - -# @testset "Solve Energy model with both Turbine and Reservoir" begin -# sys = build_hydro_with_both_pump_and_turbine() -# template = OperationsProblemTemplate() -# set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) -# set_device_model!(template, HydroTurbine, HydroTurbineEnergyCommitment) -# res_model = DeviceModel( -# HydroReservoir, -# HydroEnergyModelReservoir; -# use_slacks = true, -# attributes = Dict{String, Any}( -# "energy_target" => false, -# "hydro_budget" => true, -# ), -# ) -# set_device_model!(template, res_model) -# set_device_model!(template, HydroPumpTurbine, HydroPumpEnergyDispatch) -# p_model = DeviceModel( -# HydroPumpTurbine, -# HydroPumpEnergyCommitment; -# attributes = Dict{String, Any}( -# "reservation" => true, -# ), -# ) -# set_device_model!(template, p_model) - -# set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) -# set_device_model!(template, PowerLoad, StaticPowerLoad) - -# model = DecisionModel( -# template, -# sys; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# calculate_conflict = true, -# ) - -# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT -# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED - -# p_model = DeviceModel( -# HydroPumpTurbine, -# HydroPumpEnergyCommitment; -# attributes = Dict{String, Any}( -# "reservation" => false, -# ), -# ) -# set_device_model!(template, p_model) - -# model = DecisionModel( -# template, -# sys; -# optimizer = HiGHS_optimizer, -# store_variable_names = true, -# calculate_conflict = true, -# ) - -# @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT -# @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED -# end +@testset "Solve HydroWaterModelReservoir with Budget" begin + sys = PSB.build_system( + PSITestSystems, + "c_sys5_hy_turbine_head"; + force_build = true, + add_single_time_series = true, + ) + res = only(get_components(HydroReservoir, sys)) + inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") + tstamp = timestamp(inflow_array) + vals = values(inflow_array) + budget_array = TimeArray(tstamp, vals .* 0.5) + budget_ts = SingleTimeSeries("hydro_budget", budget_array) + add_time_series!(sys, res, budget_ts) + transform_single_time_series!(sys, Hour(24), Hour(24)) + + template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroWaterModelReservoir; + attributes = Dict("hydro_target" => false, "hydro_budget" => true), + ) + set_device_model!(template, reservoir_model) + + model = DecisionModel( + template, + sys; + name = "UC", + optimizer = HiGHS_optimizer, + store_variable_names = true, + optimizer_solve_log_print = false, + ) + @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT + @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + sol = OptimizationProblemOutputs(model) + flow = read_expression(sol, "TotalHydroFlowRateReservoirOutgoing__HydroReservoir")[ + !, + "value", + ] + @test sum(flow) <= sum(vals) / 4.0 +end + +@testset "Solve HydroWaterModelReservoir with Target" begin + sys = PSB.build_system( + PSITestSystems, + "c_sys5_hy_turbine_head"; + force_build = true, + add_single_time_series = true, + ) + res = only(get_components(HydroReservoir, sys)) + hydro_cost = HydroReservoirCost(1e5, 0.0, 0.0) + set_operation_cost!(res, hydro_cost) + inflow_array = get_time_series_array(SingleTimeSeries, res, "inflow") + tstamp = timestamp(inflow_array) + vals = values(inflow_array) + head_array = TimeArray(tstamp, 490.0 * ones(length(vals))) + target_ts = SingleTimeSeries("hydro_target", head_array) + add_time_series!(sys, res, target_ts) + transform_single_time_series!(sys, Hour(24), Hour(24)) + + template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearDispatch) + reservoir_model = DeviceModel( + HydroReservoir, + HydroWaterModelReservoir; + attributes = Dict("hydro_target" => true, "hydro_budget" => false), + ) + set_device_model!(template, reservoir_model) + + model = DecisionModel( + template, + sys; + name = "UC", + optimizer = HiGHS_optimizer, + store_variable_names = true, + optimizer_solve_log_print = false, + ) + @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT + @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + sol = OptimizationProblemOutputs(model) + head = read_variable(sol, "HydroReservoirHeadVariable__HydroReservoir")[!, "value"] + @test head[24] >= 490 +end + +##################################################### +######## HydroWaterModelReservoir Cascading ######### +##################################################### + +@testset "Solve Cascading HydroWaterModelReservoir" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") + + template = OperationsProblemTemplate() + set_device_model!(template, HydroTurbine, HydroTurbineBilinearDispatch) + set_device_model!(template, HydroReservoir, HydroWaterModelReservoir) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + + model = DecisionModel( + template, + c_sys5_hy; + optimizer = ipopt_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + + moi_tests(model, 504, 0, 216, 216, 120, false) + psi_checkobjfun_test(model, AffExpr) +end + +@testset "Solve Cascading HydroEnergyModelReservoir" begin + output_dir = mktempdir(; cleanup = true) + + c_sys5_hy = PSB.build_system(PSITestSystems, "c_sys5_hy_cascading_turbine_head") + + template = OperationsProblemTemplate() + set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) + set_device_model!(template, HydroReservoir, HydroEnergyModelReservoir) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + + model = DecisionModel( + template, + c_sys5_hy; + optimizer = HiGHS_optimizer, + store_variable_names = true, + ) + + @test build!(model; output_dir = output_dir) == + ModelBuildStatus.BUILT + + @test solve!(model; output_dir = output_dir) == + IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + outputs = OptimizationProblemOutputs(model) + + moi_tests(model, 360, 0, 168, 168, 72, false) + psi_checkobjfun_test(model, AffExpr) +end + +################################################################### +######## Energy HydroPump and Turbine in same Reservoir ######### +################################################################### + +@testset "Solve Energy model with both Turbine and Reservoir" begin + sys = build_hydro_with_both_pump_and_turbine() + template = OperationsProblemTemplate() + set_device_model!(template, HydroTurbine, HydroTurbineEnergyDispatch) + set_device_model!(template, HydroTurbine, HydroTurbineEnergyCommitment) + res_model = DeviceModel( + HydroReservoir, + HydroEnergyModelReservoir; + use_slacks = true, + attributes = Dict{String, Any}( + "energy_target" => false, + "hydro_budget" => true, + ), + ) + set_device_model!(template, res_model) + set_device_model!(template, HydroPumpTurbine, HydroPumpEnergyDispatch) + p_model = DeviceModel( + HydroPumpTurbine, + HydroPumpEnergyCommitment; + attributes = Dict{String, Any}( + "reservation" => true, + ), + ) + set_device_model!(template, p_model) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + + model = DecisionModel( + template, + sys; + optimizer = HiGHS_optimizer, + store_variable_names = true, + calculate_conflict = true, + ) + + @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT + @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED + + p_model = DeviceModel( + HydroPumpTurbine, + HydroPumpEnergyCommitment; + attributes = Dict{String, Any}( + "reservation" => false, + ), + ) + set_device_model!(template, p_model) + + model = DecisionModel( + template, + sys; + optimizer = HiGHS_optimizer, + store_variable_names = true, + calculate_conflict = true, + ) + + @test build!(model; output_dir = mktempdir()) == ModelBuildStatus.BUILT + @test solve!(model) == IS.Simulation.RunStatus.SUCCESSFULLY_FINALIZED +end From 44e964aa3fd7abf4033edaa476d4aa499662ac61 Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Tue, 21 Apr 2026 17:44:53 -0400 Subject: [PATCH 7/8] fix type dispatch; removed constraint counting tests for approx --- src/static_injector_models/hydro_generation.jl | 16 ++++++++-------- test/test_device_hydro_constructors.jl | 1 - 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/static_injector_models/hydro_generation.jl b/src/static_injector_models/hydro_generation.jl index 70d5b4e..94be781 100644 --- a/src/static_injector_models/hydro_generation.jl +++ b/src/static_injector_models/hydro_generation.jl @@ -1823,14 +1823,14 @@ function add_constraints!( constraint = add_constraints_container!( container, - TurbinePowerOutputConstraint(), + TurbinePowerOutputConstraint, V, names, time_steps, ) - power = get_variable(container, ActivePowerVariable(), V) - flow = get_variable(container, HydroTurbineFlowRateVariable(), V) - head = get_variable(container, HydroReservoirHeadVariable(), PSY.HydroReservoir) + power = get_variable(container, ActivePowerVariable, V) + flow = get_variable(container, HydroTurbineFlowRateVariable, V) + head = get_variable(container, HydroReservoirHeadVariable, PSY.HydroReservoir) for d in devices name = PSY.get_name(d) conversion_factor = PSY.get_conversion_factor(d) @@ -1847,14 +1847,14 @@ function add_constraints!( head, [ ( - min = get_variable_lower_bound(HydroTurbineFlowRateVariable(), d, W()), - max = get_variable_upper_bound(HydroTurbineFlowRateVariable(), d, W()) + min = get_variable_lower_bound(HydroTurbineFlowRateVariable, d, W), + max = get_variable_upper_bound(HydroTurbineFlowRateVariable, d, W) ) for _=1:length(reservoirs) ], [ ( - min = get_variable_lower_bound(HydroReservoirHeadVariable(), res, W()), - max = get_variable_upper_bound(HydroReservoirHeadVariable(), res, W()) + min = get_variable_lower_bound(HydroReservoirHeadVariable, res, W), + max = get_variable_upper_bound(HydroReservoirHeadVariable, res, W) ) for res in reservoirs ], "$(get_name(d))_FlowHeadProduct" diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index 7e11257..78a44cf 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -720,7 +720,6 @@ end outputs = OptimizationProblemOutputs(model) - moi_tests(model, 288, 0, 168, 168, 72, false) psi_checkobjfun_test(model, AffExpr) df_outflow = read_expression(outputs, "TotalHydroFlowRateTurbineOutgoing__HydroTurbine") From 282a341e9c020a16963ad39357dd90f3a6d13d2e Mon Sep 17 00:00:00 2001 From: Anthony Costarelli Date: Tue, 21 Apr 2026 17:45:45 -0400 Subject: [PATCH 8/8] formatting --- src/static_injector_models/hydro_generation.jl | 17 +++++++++++------ .../hydrogeneration_constructor.jl | 12 ++++++++++-- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/static_injector_models/hydro_generation.jl b/src/static_injector_models/hydro_generation.jl index 94be781..a9ce838 100644 --- a/src/static_injector_models/hydro_generation.jl +++ b/src/static_injector_models/hydro_generation.jl @@ -487,7 +487,11 @@ function add_variables!( T <: HydroTurbineFlowRateVariable, U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, W <: Union{Vector{E}, IS.FlattenIteratorWrapper{E}}, - X <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, + X <: Union{ + HydroTurbineBilinearDispatch, + HydroTurbineWaterLinearDispatch, + HydroTurbineBin2BilinearDispatch, + }, } where { D <: PSY.HydroTurbine, E <: PSY.HydroReservoir, @@ -1848,16 +1852,16 @@ function add_constraints!( [ ( min = get_variable_lower_bound(HydroTurbineFlowRateVariable, d, W), - max = get_variable_upper_bound(HydroTurbineFlowRateVariable, d, W) - ) for _=1:length(reservoirs) + max = get_variable_upper_bound(HydroTurbineFlowRateVariable, d, W), + ) for _ in 1:length(reservoirs) ], [ ( min = get_variable_lower_bound(HydroReservoirHeadVariable, res, W), - max = get_variable_upper_bound(HydroReservoirHeadVariable, res, W) + max = get_variable_upper_bound(HydroReservoirHeadVariable, res, W), ) for res in reservoirs ], - "$(get_name(d))_FlowHeadProduct" + "$(get_name(d))_FlowHeadProduct", ) for t in time_steps @@ -1867,7 +1871,8 @@ function add_constraints!( GRAVITATIONAL_CONSTANT * WATER_DENSITY * conversion_factor * sum( fh_prod[PSY.get_name(res), t] - - powerhouse_elevation * flow[name, PSY.get_name(res), t] + - + powerhouse_elevation * flow[name, PSY.get_name(res), t] for res in reservoirs ) / (1e6 * base_power) ) diff --git a/src/static_injector_models/hydrogeneration_constructor.jl b/src/static_injector_models/hydrogeneration_constructor.jl index 1937456..d7180ad 100644 --- a/src/static_injector_models/hydrogeneration_constructor.jl +++ b/src/static_injector_models/hydrogeneration_constructor.jl @@ -1809,7 +1809,11 @@ function construct_device!( network_model::NetworkModel{S}, ) where { H <: PSY.HydroTurbine, - D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, + D <: Union{ + HydroTurbineBilinearDispatch, + HydroTurbineWaterLinearDispatch, + HydroTurbineBin2BilinearDispatch, + }, S <: AbstractActivePowerModel, } devices = get_available_components(model, sys) @@ -1873,7 +1877,11 @@ function construct_device!( network_model::NetworkModel{S}, ) where { H <: PSY.HydroTurbine, - D <: Union{HydroTurbineBilinearDispatch, HydroTurbineWaterLinearDispatch, HydroTurbineBin2BilinearDispatch}, + D <: Union{ + HydroTurbineBilinearDispatch, + HydroTurbineWaterLinearDispatch, + HydroTurbineBin2BilinearDispatch, + }, S <: AbstractActivePowerModel, } devices = get_available_components(model, sys)