diff --git a/Project.toml b/Project.toml index 6d6c548..6604d25 100644 --- a/Project.toml +++ b/Project.toml @@ -20,6 +20,9 @@ Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" TimerOutputs = "a759f4b9-e2f1-59dc-863e-4aeb61b1ea8f" +[sources] +InfrastructureSystems = {url = "https://github.com/NREL-Sienna/InfrastructureSystems.jl", rev = "IS4"} + [weakdeps] PowerFlows = "94fada2c-0ca5-4b90-a1fb-4bc5b59ccfc7" @@ -29,7 +32,6 @@ PowerFlowsExt = "PowerFlows" [compat] Dates = "1" DocStringExtensions = "~0.8, ~0.9" -InfrastructureOptimizationModels = "0.1" InfrastructureSystems = "3" InteractiveUtils = "1.11.0" JuMP = "^1.28" diff --git a/src/PowerOperationsModels.jl b/src/PowerOperationsModels.jl index 5a7967e..a855afa 100644 --- a/src/PowerOperationsModels.jl +++ b/src/PowerOperationsModels.jl @@ -528,6 +528,7 @@ export HydroWaterFactorModel export HydroWaterModelReservoir export HydroTurbineBilinearDispatch export HydroTurbineWaterLinearDispatch +export HydroTurbineWaterLinearCommitment export HydroEnergyModelReservoir export HydroTurbineEnergyDispatch export HydroTurbineEnergyCommitment diff --git a/src/common_models/add_parameters.jl b/src/common_models/add_parameters.jl index e3387bc..77b0963 100644 --- a/src/common_models/add_parameters.jl +++ b/src/common_models/add_parameters.jl @@ -104,7 +104,7 @@ function _check_dynamic_branch_rating_ts( end end - multiplier = get_multiplier_value(T, device, W) + multiplier = get_multiplier_value(T(), device, W()) if !all(x -> x >= rating, multiplier * ts) @warn "There are values of Parameter $T associated with $(typeof(device)) '$(PSY.get_name(device))' lower than the device static rating $(rating)." end diff --git a/src/core/formulations.jl b/src/core/formulations.jl index f212c1a..a23cc5b 100644 --- a/src/core/formulations.jl +++ b/src/core/formulations.jl @@ -327,6 +327,12 @@ The model assumes a shallow reservoir. The head for the conversion between water """ struct HydroTurbineWaterLinearDispatch <: AbstractHydroDispatchFormulation end +""" +Formulation type to add injection and commitment variables for a [`PowerSystems.HydroTurbine`](@extref) connected to reservoirs using a linear model with a binary [`PowerSimulations.OnVariable`](@extref) to decide if the turbine is on or not. +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. +""" +struct HydroTurbineWaterLinearCommitment <: AbstractHydroUnitCommitment end + """ Formulation type to add injection variables for a [`PowerSystems.HydroTurbine`](@extref) only using energy variables (no water flow variables) """ diff --git a/src/static_injector_models/hydro_generation.jl b/src/static_injector_models/hydro_generation.jl index 31886f7..164f4a8 100644 --- a/src/static_injector_models/hydro_generation.jl +++ b/src/static_injector_models/hydro_generation.jl @@ -15,6 +15,38 @@ function _add_proportional_term!( return lin_cost end +function _add_proportional_term_variant!( + container::OptimizationContainer, + ::Type{T}, + component::U, + linear_term::Float64, + time_period::Int, +) where {T <: VariableType, U <: PSY.Component} + lin_cost = _add_proportional_term_helper( + container, T(), component, linear_term, time_period) + add_to_objective_variant_expression!(container, lin_cost) + return lin_cost +end + +_add_proportional_term_maybe_variant!( + ::Val{false}, + container::OptimizationContainer, + ::Type{T}, + component::U, + linear_term::Float64, + time_period::Int, +) where {T <: VariableType, U <: PSY.Component} = + _add_proportional_term!(container, T, component, linear_term, time_period) +_add_proportional_term_maybe_variant!( + ::Val{true}, + container::OptimizationContainer, + ::Type{T}, + component::U, + linear_term::Float64, + time_period::Int, +) where {T <: VariableType, U <: PSY.Component} = + _add_proportional_term_variant!(container, T, component, linear_term, time_period) + # These methods are defined in PowerSimulations requires_initialization(::AbstractHydroReservoirFormulation) = false requires_initialization(::AbstractHydroUnitCommitment) = true @@ -437,6 +469,13 @@ function get_default_attributes( return Dict{String, Any}("head_fraction_usage" => 0.0) end +function get_default_attributes( + ::Type{T}, + ::Type{D}, +) where {T <: PSY.HydroTurbine, D <: HydroTurbineWaterLinearCommitment} + return Dict{String, Any}("head_fraction_usage" => 0.0) +end + function get_default_attributes( ::Type{PSY.HydroReservoir}, ::Type{HydroWaterFactorModel}, @@ -487,7 +526,11 @@ 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, + HydroTurbineWaterLinearCommitment, + }, } where { D <: PSY.HydroTurbine, E <: PSY.HydroReservoir, @@ -660,6 +703,25 @@ function add_constraints!( return end +""" +Add semicontinuous range constraints for [`HydroTurbineWaterLinearCommitment`](@ref) formulation +""" +function add_constraints!( + container::OptimizationContainer, + T::Type{ActivePowerVariableLimitsConstraint}, + U::Type{<:Union{VariableType, ExpressionType}}, + devices::IS.FlattenIteratorWrapper{V}, + model::DeviceModel{V, W}, + ::NetworkModel{X}, +) where { + V <: PSY.HydroTurbine, + W <: HydroTurbineWaterLinearCommitment, + X <: PM.AbstractPowerModel, +} + add_semicontinuous_range_constraints!(container, T, U, devices, model, X) + return +end + """ Min and max reactive Power Variable limits """ @@ -1761,7 +1823,7 @@ function add_constraints!( ::NetworkModel{X}, ) where { V <: PSY.HydroTurbine, - W <: HydroTurbineWaterLinearDispatch, + W <: Union{HydroTurbineWaterLinearDispatch, HydroTurbineWaterLinearCommitment}, X <: AbstractPowerModel, } time_steps = get_time_steps(container) diff --git a/src/static_injector_models/hydrogeneration_constructor.jl b/src/static_injector_models/hydrogeneration_constructor.jl index d64742d..53e33d2 100644 --- a/src/static_injector_models/hydrogeneration_constructor.jl +++ b/src/static_injector_models/hydrogeneration_constructor.jl @@ -1940,6 +1940,132 @@ function construct_device!( return end +########################################################## +########### HydroTurbineWaterLinearCommitment ############ +########################################################## + +""" +Construct model for [`PowerSystems.HydroTurbine`](@extref) with [`HydroTurbineWaterLinearCommitment`](@ref) Formulation +with only Active Power. +""" +function construct_device!( + container::OptimizationContainer, + sys::PSY.System, + ::ArgumentConstructStage, + model::DeviceModel{H, D}, + network_model::NetworkModel{S}, +) where { + H <: PSY.HydroTurbine, + D <: HydroTurbineWaterLinearCommitment, + S <: PM.AbstractActivePowerModel, +} + devices = get_available_components(model, sys) + reservoirs = get_available_reservoirs(sys) + + add_variables!( + container, + HydroTurbineFlowRateVariable, + devices, + reservoirs, + D, + ) + + add_variables!(container, ActivePowerVariable, devices, D) + add_variables!(container, OnVariable, devices, D) + + add_to_expression!( + container, + ActivePowerBalance, + ActivePowerVariable, + devices, + model, + network_model, + ) + + add_to_expression!( + container, + ActivePowerRangeExpressionLB, + ActivePowerVariable, + devices, + model, + network_model, + ) + add_to_expression!( + container, + ActivePowerRangeExpressionUB, + ActivePowerVariable, + devices, + model, + network_model, + ) + + process_market_bid_parameters!(container, devices, model) + if has_service_model(model) + error("$D does not support service models yet") + end + + add_expressions!(container, ProductionCostExpression, devices, model) + + add_feedforward_arguments!(container, model, devices) + add_event_arguments!(container, devices, model, network_model) + return +end + +function construct_device!( + container::OptimizationContainer, + sys::PSY.System, + ::ModelConstructStage, + model::DeviceModel{H, D}, + network_model::NetworkModel{S}, +) where { + H <: PSY.HydroTurbine, + D <: HydroTurbineWaterLinearCommitment, + S <: PM.AbstractActivePowerModel, +} + devices = get_available_components(model, sys) + + add_expressions!( + container, + sys, + TotalHydroFlowRateTurbineOutgoing, + devices, + model, + ) + + add_constraints!( + container, + ActivePowerVariableLimitsConstraint, + ActivePowerRangeExpressionLB, + devices, + model, + network_model, + ) + add_constraints!( + container, + ActivePowerVariableLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + + add_constraints!( + container, + sys, + TurbinePowerOutputConstraint, + devices, + model, + network_model, + ) + + add_feedforward_constraints!(container, model, devices) + + objective_function!(container, devices, model, S) + add_event_constraints!(container, devices, model, network_model) + add_constraint_dual!(container, sys, model) + return +end + ########################################################## ########### Hydro Pump Turbine Models #################### ########################################################## diff --git a/test/test_device_hydro_constructors.jl b/test/test_device_hydro_constructors.jl index a04fdf6..1915b83 100644 --- a/test/test_device_hydro_constructors.jl +++ b/test/test_device_hydro_constructors.jl @@ -782,6 +782,58 @@ end @test head[24] >= 490 end +@testset "Solve HydroWaterModelReservoir with Budget and Commitment" 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)) + turb = first(get_components(HydroTurbine, sys)) + set_active_power_limits!(turb, (min = 0.1, max = 5.2)) + set_operation_cost!(turb, HydroGenerationCost( + CostCurve(LinearCurve(1.0)), + 2.0, + )) + + template = OperationsProblemTemplate(NetworkModel(CopperPlatePowerModel)) + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, HydroTurbine, HydroTurbineWaterLinearCommitment) + 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 + ##################################################### ######## HydroWaterModelReservoir Cascading ######### #####################################################