diff --git a/src/PowerSimulations.jl b/src/PowerSimulations.jl index 0a36addcd9..cd8544a63c 100644 --- a/src/PowerSimulations.jl +++ b/src/PowerSimulations.jl @@ -556,7 +556,7 @@ include("devices_models/devices/common/duration_constraints.jl") include("devices_models/devices/common/get_time_series.jl") # Device Modeling components -include("devices_models/devices/interfaces.jl") +include("devices_models/devices/default_interface_methods.jl") include("devices_models/devices/common/add_to_expression.jl") include("devices_models/devices/common/set_expression.jl") include("devices_models/devices/renewable_generation.jl") diff --git a/src/devices_models/device_constructors/thermalgeneration_constructor.jl b/src/devices_models/device_constructors/thermalgeneration_constructor.jl index 5225586c43..3c6f50c09c 100644 --- a/src/devices_models/device_constructors/thermalgeneration_constructor.jl +++ b/src/devices_models/device_constructors/thermalgeneration_constructor.jl @@ -56,6 +56,10 @@ function construct_device!( initial_conditions!(container, devices, D()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -135,6 +139,17 @@ function construct_device!( add_constraints!(container, RampConstraint, devices, model, network_model) add_constraints!(container, DurationConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end + add_feedforward_constraints!(container, model, devices) objective_function!(container, devices, model, get_network_formulation(network_model)) @@ -164,6 +179,10 @@ function construct_device!( initial_conditions!(container, devices, D()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -227,6 +246,16 @@ function construct_device!( add_constraints!(container, CommitmentConstraint, devices, model, network_model) add_constraints!(container, RampConstraint, devices, model, network_model) add_constraints!(container, DurationConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -256,6 +285,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalBasicUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -335,6 +368,17 @@ function construct_device!( ) add_constraints!(container, CommitmentConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end + add_feedforward_constraints!(container, model, devices) objective_function!(container, devices, model, get_network_formulation(network_model)) @@ -361,6 +405,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalBasicUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -423,6 +471,16 @@ function construct_device!( ) add_constraints!(container, CommitmentConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -829,6 +887,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalMultiStartUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -931,6 +993,16 @@ function construct_device!( network_model, ) add_constraints!(container, ActiveRangeICConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -965,6 +1037,10 @@ function construct_device!( add_variables!(container, TimeDurationOff, devices, ThermalMultiStartUnitCommitment()) add_variables!(container, PowerOutput, devices, ThermalMultiStartUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -1053,6 +1129,16 @@ function construct_device!( network_model, ) add_constraints!(container, ActiveRangeICConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -1092,6 +1178,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalCompactUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -1169,6 +1259,16 @@ function construct_device!( add_constraints!(container, CommitmentConstraint, devices, model, network_model) add_constraints!(container, RampConstraint, devices, model, network_model) add_constraints!(container, DurationConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -1202,6 +1302,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalCompactUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -1270,6 +1374,16 @@ function construct_device!( add_constraints!(container, CommitmentConstraint, devices, model, network_model) add_constraints!(container, RampConstraint, devices, model, network_model) add_constraints!(container, DurationConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -1307,6 +1421,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalBasicCompactUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -1382,6 +1500,16 @@ function construct_device!( network_model, ) add_constraints!(container, CommitmentConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) @@ -1413,6 +1541,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalBasicCompactUnitCommitment()) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -1479,6 +1611,16 @@ function construct_device!( ) add_constraints!(container, CommitmentConstraint, devices, model, network_model) + if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) + add_constraints!( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + ActivePowerRangeExpressionUB, + devices, + model, + network_model, + ) + end add_feedforward_constraints!(container, model, devices) diff --git a/src/devices_models/devices/common/objective_function/common.jl b/src/devices_models/devices/common/objective_function/common.jl index c02b55a51f..064b689776 100644 --- a/src/devices_models/devices/common/objective_function/common.jl +++ b/src/devices_models/devices/common/objective_function/common.jl @@ -88,8 +88,14 @@ function _add_vom_cost_to_objective!( ) for t in get_time_steps(container) exp = - _add_proportional_term!(container, T(), d, cost_term_normalized * multiplier, t) - add_to_expression!(container, ProductionCostExpression, exp, d, t) + _add_proportional_term!( + container, + T(), + component, + cost_term_normalized * multiplier, + t, + ) + add_to_expression!(container, ProductionCostExpression, exp, component, t) end return end diff --git a/src/devices_models/devices/common/range_constraint.jl b/src/devices_models/devices/common/range_constraint.jl index 0a7c4b804c..ebb27d4538 100644 --- a/src/devices_models/devices/common/range_constraint.jl +++ b/src/devices_models/devices/common/range_constraint.jl @@ -705,7 +705,12 @@ function lower_bound_range_with_parameter!( mult = get_multiplier_array(param_container) jump_model = get_jump_model(container) time_steps = axes(constraint_container)[2] + ts_name = get_time_series_names(model)[P] + ts_type = get_default_time_series_type(container) for device in devices + if !(PSY.has_time_series(device, ts_type, ts_name)) + continue + end name = PSY.get_name(device) param = get_parameter_column_refs(param_container, name) for t in time_steps @@ -730,8 +735,13 @@ function _add_parameterized_lower_bound_range_constraints_impl!( W <: AbstractDeviceFormulation, } time_steps = get_time_steps(container) - names = [PSY.get_name(d) for d in devices] - + ts_name = get_time_series_names(model)[U] + ts_type = get_default_time_series_type(container) + names = [PSY.get_name(d) for d in devices if PSY.has_time_series(d, ts_type, ts_name)] + if isempty(names) + @debug "There are no $V devices with time series data" + return + end constraint = add_constraints_container!(container, T(), V, names, time_steps; meta = "lb") @@ -834,14 +844,19 @@ function upper_bound_range_with_parameter!( lhs_array, param::P, devices::IS.FlattenIteratorWrapper{V}, - ::DeviceModel{V, W}, + model::DeviceModel{V, W}, ) where {P <: TimeSeriesParameter, V <: PSY.Component, W <: AbstractDeviceFormulation} param_container = get_parameter(container, param, V) mult = get_multiplier_array(param_container) jump_model = get_jump_model(container) time_steps = axes(constraint_container)[2] + ts_name = get_time_series_names(model)[P] + ts_type = get_default_time_series_type(container) for device in devices name = PSY.get_name(device) + if !(PSY.has_time_series(device, ts_type, ts_name)) + continue + end param = get_parameter_column_refs(param_container, name) for t in time_steps constraint_container[name, t] = @@ -865,7 +880,13 @@ function _add_parameterized_upper_bound_range_constraints_impl!( W <: AbstractDeviceFormulation, } time_steps = get_time_steps(container) - names = [PSY.get_name(d) for d in devices] + ts_name = get_time_series_names(model)[P] + ts_type = get_default_time_series_type(container) + names = [PSY.get_name(d) for d in devices if PSY.has_time_series(d, ts_type, ts_name)] + if isempty(names) + @debug "There are no $V devices with time series data $ts_type, $ts_name" + return + end constraint = add_constraints_container!(container, T(), V, names, time_steps; meta = "ub") diff --git a/src/devices_models/devices/interfaces.jl b/src/devices_models/devices/default_interface_methods.jl similarity index 94% rename from src/devices_models/devices/interfaces.jl rename to src/devices_models/devices/default_interface_methods.jl index 0fd71e6243..5391344ae7 100644 --- a/src/devices_models/devices/interfaces.jl +++ b/src/devices_models/devices/default_interface_methods.jl @@ -11,7 +11,7 @@ get_variable_lower_bound(_, ::PSY.Component, __) = nothing get_variable_upper_bound(_, ::PSY.Component, __) = nothing get_multiplier_value(x, y::PSY.Component, z) = - error("Unable to get parameter $x for device $y for formulation $z") + error("Unable to get parameter $x for device $(IS.summary(y)) for formulation $z") get_expression_type_for_reserve(_, y::Type{<:PSY.Component}, z) = error("`get_expression_type_for_reserve` must be implemented for $y and $z") diff --git a/src/devices_models/devices/thermal_generation.jl b/src/devices_models/devices/thermal_generation.jl index 66809d18dc..536d54eadf 100644 --- a/src/devices_models/devices/thermal_generation.jl +++ b/src/devices_models/devices/thermal_generation.jl @@ -50,6 +50,7 @@ get_variable_upper_bound(::StartVariable, d::PSY.ThermalGen, ::AbstractThermalFo get_variable_binary(::Union{ColdStartVariable, WarmStartVariable, HotStartVariable}, ::Type{PSY.ThermalMultiStart}, ::AbstractThermalFormulation) = true ########################### Parameter related set functions ################################ +get_multiplier_value(::ActivePowerTimeSeriesParameter, d::PSY.ThermalGen, ::AbstractThermalFormulation) = PSY.get_max_active_power(d) get_parameter_multiplier(::VariableValueParameter, d::PSY.ThermalGen, ::AbstractThermalFormulation) = 1.0 get_initial_parameter_value(::VariableValueParameter, d::PSY.ThermalGen, ::AbstractThermalFormulation) = 1.0 get_expression_multiplier(::OnStatusParameter, ::ActivePowerRangeExpressionUB, d::PSY.ThermalGen, ::AbstractThermalFormulation) = PSY.get_active_power_limits(d).max @@ -442,6 +443,30 @@ function _get_data_for_range_ic( return ini_conds end +function add_constraints!( + container::OptimizationContainer, + ::Type{ActivePowerVariableTimeSeriesLimitsConstraint}, + U::Type{<:Union{ActivePowerVariable, ActivePowerRangeExpressionUB}}, + devices::IS.FlattenIteratorWrapper{V}, + model::DeviceModel{V, W}, + ::NetworkModel{X}, +) where { + V <: PSY.ThermalGen, + W <: AbstractThermalUnitCommitment, + X <: PM.AbstractPowerModel, +} + add_parameterized_upper_bound_range_constraints( + container, + ActivePowerVariableTimeSeriesLimitsConstraint, + U, + ActivePowerTimeSeriesParameter, + devices, + model, + X, + ) + return +end + """ This function adds range constraint for the first time period. Constraint (10) from PGLIB formulation """ diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index 785742ac72..65612b171f 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -74,8 +74,9 @@ function DecisionModel{M}( elseif name isa String name = Symbol(name) end + ts_type = get_deterministic_time_series_type(sys) internal = IS.Optimization.ModelInternal( - OptimizationContainer(sys, settings, jump_model, PSY.Deterministic), + OptimizationContainer(sys, settings, jump_model, ts_type), ) template_ = deepcopy(template) diff --git a/src/parameters/add_parameters.jl b/src/parameters/add_parameters.jl index 76f1ef2095..15ebf6e781 100644 --- a/src/parameters/add_parameters.jl +++ b/src/parameters/add_parameters.jl @@ -197,6 +197,10 @@ function _add_time_series_parameters!( device_names = String[] initial_values = Dict{String, AbstractArray}() for device in devices + if !PSY.has_time_series(device, ts_type, ts_name) + @debug "skipped time series for $D, $(PSY.get_name(device))" + continue + end push!(device_names, PSY.get_name(device)) ts_uuid = string(IS.get_time_series_uuid(ts_type, device, ts_name)) if !(ts_uuid in keys(initial_values)) @@ -226,6 +230,9 @@ function _add_time_series_parameters!( end for device in devices + if !PSY.has_time_series(device, ts_type, ts_name) + continue + end name = PSY.get_name(device) multiplier = get_multiplier_value(T(), device, W()) for step in time_steps diff --git a/src/parameters/update_container_parameter_values.jl b/src/parameters/update_container_parameter_values.jl index d8a52b3575..f2e70b1d67 100644 --- a/src/parameters/update_container_parameter_values.jl +++ b/src/parameters/update_container_parameter_values.jl @@ -50,6 +50,9 @@ function _update_parameter_values!( components = get_available_components(device_model, get_system(model)) ts_uuids = Set{String}() for component in components + if !PSY.has_time_series(component, U, ts_name) + continue + end ts_uuid = _get_ts_uuid(attributes, PSY.get_name(component)) if !(ts_uuid in ts_uuids) ts_vector = get_time_series_values!( diff --git a/src/services_models/services_constructor.jl b/src/services_models/services_constructor.jl index 6deb80d300..f547d622b3 100644 --- a/src/services_models/services_constructor.jl +++ b/src/services_models/services_constructor.jl @@ -534,9 +534,9 @@ function construct_service!( network_model::NetworkModel{<:PM.AbstractPowerModel}, ) where {T <: PSY.TransmissionInterface} interfaces = get_available_components(model, sys) + interface = PSY.get_component(T, sys, get_service_name(model)) if get_use_slacks(model) # Adding the slacks can be done in a cleaner fashion - interface = PSY.get_component(T, sys, get_service_name(model)) @assert PSY.get_available(interface) transmission_interface_slacks!(container, interface) end @@ -548,7 +548,8 @@ function construct_service!( PSY.get_name.(interfaces), get_time_steps(container), ) - #add_feedforward_arguments!(container, model, service) + # TODO:broken + # add_feedforward_arguments!(container, model, interface) return end diff --git a/src/utils/powersystems_utils.jl b/src/utils/powersystems_utils.jl index 0d846a9e05..d49cebb5a6 100644 --- a/src/utils/powersystems_utils.jl +++ b/src/utils/powersystems_utils.jl @@ -337,3 +337,22 @@ function _get_piecewise_incrementalcurve_per_system_unit( y_coords_normalized = y_coords .* system_base_power return PSY.PiecewiseStepData(x_coords_normalized, y_coords_normalized) end + +function get_deterministic_time_series_type(sys::PSY.System) + time_series_types = IS.get_time_series_counts_by_type(sys.data) + existing_types = Set(d["type"] for d in time_series_types) + if Set(["Deterministic", "DeterministicSingleTimeSeries"]) ∈ existing_types + error( + "The System contains a combination of forecast data and transformed time series data. Currently this is not supported.", + ) + end + if "Deterministic" ∈ existing_types + return PSY.Deterministic + elseif "DeterministicSingleTimeSeries" ∈ existing_types + return PSY.DeterministicSingleTimeSeries + else + error( + "The System does not contain any forecast data or transformed time series data.", + ) + end +end diff --git a/test/test_device_thermal_generation_constructors.jl b/test/test_device_thermal_generation_constructors.jl index 8525b3e5e7..907edd597f 100644 --- a/test/test_device_thermal_generation_constructors.jl +++ b/test/test_device_thermal_generation_constructors.jl @@ -921,3 +921,49 @@ end end end end + +@testset "Thermal with max_active_power time series" begin + device_model = DeviceModel( + ThermalStandard, + ThermalStandardUnitCommitment; + time_series_names = Dict(ActivePowerTimeSeriesParameter => "max_active_power")) + c_sys5 = PSB.build_system(PSITestSystems, "c_sys5") + + derate_data = SortedDict{Dates.DateTime, TimeSeries.TimeArray}() + data_ts = collect( + DateTime("1/1/2024 0:00:00", "d/m/y H:M:S"):Hour(1):DateTime( + "1/1/2024 23:00:00", + "d/m/y H:M:S", + ), + ) + for t in 1:2 + ini_time = data_ts[1] + Day(t - 1) + derate_data[ini_time] = + TimeArray(data_ts + Day(t - 1), fill!(Vector{Float64}(undef, 24), 0.8)) + end + solitude = get_component(ThermalStandard, c_sys5, "Solitude") + PSY.add_time_series!( + c_sys5, + solitude, + PSY.Deterministic("max_active_power", derate_data), + ) + + model = DecisionModel( + MockOperationProblem, + DCPPowerModel, + c_sys5) + + mock_construct_device!(model, device_model) + moi_tests(model, 480, 0, 504, 120, 120, true) + key = PSI.ConstraintKey( + ActivePowerVariableTimeSeriesLimitsConstraint, + ThermalStandard, + "ub", + ) + constraint = PSI.get_constraint(PSI.get_optimization_container(model), key) + ub_value = get_max_active_power(solitude) * 0.8 + for ix in eachindex(constraint) + @test JuMP.normalized_rhs(constraint[ix]) == ub_value + end + psi_checkobjfun_test(model, GAEVF) +end