diff --git a/src/PowerSimulations.jl b/src/PowerSimulations.jl index cd8544a63..3b3fe5246 100644 --- a/src/PowerSimulations.jl +++ b/src/PowerSimulations.jl @@ -323,6 +323,7 @@ export EmergencyUp export EmergencyDown export RawACE export ProductionCostExpression +export FuelConsumptionExpression export ActivePowerRangeExpressionLB export ActivePowerRangeExpressionUB diff --git a/src/core/device_model.jl b/src/core/device_model.jl index edc65bba8..764bcaf9b 100644 --- a/src/core/device_model.jl +++ b/src/core/device_model.jl @@ -49,7 +49,7 @@ mutable struct DeviceModel{D <: PSY.Device, B <: AbstractDeviceFormulation} use_slacks::Bool duals::Vector{DataType} services::Vector{ServiceModel} - time_series_names::Dict{Type{<:TimeSeriesParameter}, String} + time_series_names::Dict{Type{<:ParameterType}, String} attributes::Dict{String, Any} subsystem::Union{Nothing, String} diff --git a/src/core/expressions.jl b/src/core/expressions.jl index f5ad354a7..e1e8454eb 100644 --- a/src/core/expressions.jl +++ b/src/core/expressions.jl @@ -8,6 +8,7 @@ struct EmergencyUp <: ExpressionType end struct EmergencyDown <: ExpressionType end struct RawACE <: ExpressionType end struct ProductionCostExpression <: CostExpressions end +struct FuelConsumptionExpression <: ExpressionType end struct ActivePowerRangeExpressionLB <: RangeConstraintLBExpressions end struct ActivePowerRangeExpressionUB <: RangeConstraintUBExpressions end struct ComponentReserveUpBalanceExpression <: ExpressionType end @@ -16,6 +17,7 @@ struct InterfaceTotalFlow <: ExpressionType end struct PTDFBranchFlow <: ExpressionType end should_write_resulting_value(::Type{<:CostExpressions}) = true +should_write_resulting_value(::Type{FuelConsumptionExpression}) = true should_write_resulting_value(::Type{InterfaceTotalFlow}) = true should_write_resulting_value(::Type{RawACE}) = true should_write_resulting_value(::Type{ActivePowerBalance}) = true diff --git a/src/core/optimization_container.jl b/src/core/optimization_container.jl index b94438f9b..a0a549d9d 100644 --- a/src/core/optimization_container.jl +++ b/src/core/optimization_container.jl @@ -35,8 +35,12 @@ function get_objective_expression(v::ObjectiveFunction) else # JuMP doesn't support expression conversion from Affn to QuadExpressions if isa(v.invariant_terms, JuMP.GenericQuadExpr) - return JuMP.add_to_expression!(v.invariant_terms, v.variant_terms) + # Avoid mutation of invariant term + temp_expr = JuMP.QuadExpr() + JuMP.add_to_expression!(temp_expr, v.invariant_terms) + return JuMP.add_to_expression!(temp_expr, v.variant_terms) else + # This will mutate the variant terms, but these are reseted at each step. return JuMP.add_to_expression!(v.variant_terms, v.invariant_terms) end end @@ -1438,11 +1442,18 @@ function add_expression_container!( ::T, ::Type{U}, axs...; + expr_type = GAE, sparse = false, meta = IS.Optimization.CONTAINER_KEY_EMPTY_META, ) where {T <: ExpressionType, U <: Union{PSY.Component, PSY.System}} expr_key = ExpressionKey(T, U, meta) - return _add_expression_container!(container, expr_key, GAE, axs...; sparse = sparse) + return _add_expression_container!( + container, + expr_key, + expr_type, + axs...; + sparse = sparse, + ) end function add_expression_container!( diff --git a/src/core/parameters.jl b/src/core/parameters.jl index 2946dd68f..0a0a87ea7 100644 --- a/src/core/parameters.jl +++ b/src/core/parameters.jl @@ -47,7 +47,9 @@ function add_component_name!(attr::TimeSeriesAttributes, name::String, uuid::Str return end -_get_ts_uuid(attr::TimeSeriesAttributes, name) = attr.component_name_to_ts_uuid[name] +function _get_ts_uuid(attr::TimeSeriesAttributes, name) + return attr.component_name_to_ts_uuid[name] +end struct VariableValueAttributes{T <: OptimizationContainerKey} <: ParameterAttributes attribute_key::T @@ -303,6 +305,11 @@ Parameter to define cost function coefficient """ struct CostFunctionParameter <: ObjectiveFunctionParameter end +""" +Parameter to define fuel cost time series +""" +struct FuelCostParameter <: ObjectiveFunctionParameter end + abstract type AuxVariableValueParameter <: RightHandSideParameter end struct EventParameter <: ParameterType end diff --git a/src/devices_models/device_constructors/thermalgeneration_constructor.jl b/src/devices_models/device_constructors/thermalgeneration_constructor.jl index 3c6f50c09..dc9f70908 100644 --- a/src/devices_models/device_constructors/thermalgeneration_constructor.jl +++ b/src/devices_models/device_constructors/thermalgeneration_constructor.jl @@ -60,6 +60,10 @@ function construct_device!( add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -78,6 +82,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -95,6 +100,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return end @@ -182,6 +194,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -193,6 +208,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -210,6 +226,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -288,6 +311,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -307,6 +333,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -324,6 +351,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -408,6 +442,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -419,6 +456,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -436,6 +474,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -506,6 +551,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalStandardDispatch()) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -524,6 +573,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -541,6 +591,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -608,6 +665,10 @@ function construct_device!( initial_conditions!(container, devices, ThermalStandardDispatch()) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -618,6 +679,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -635,6 +697,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -693,6 +762,10 @@ function construct_device!( add_variables!(container, ActivePowerVariable, devices, D()) add_variables!(container, ReactivePowerVariable, devices, D()) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -711,6 +784,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -728,6 +802,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -789,6 +870,10 @@ function construct_device!( add_variables!(container, ActivePowerVariable, devices, D()) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_to_expression!( container, ActivePowerBalance, @@ -799,6 +884,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -816,6 +902,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + ActivePowerVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -890,6 +983,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -918,6 +1014,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -935,6 +1032,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -1040,6 +1144,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -1060,6 +1167,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1077,6 +1185,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -1181,6 +1296,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -1200,6 +1318,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1217,6 +1336,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -1305,6 +1431,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -1324,6 +1453,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1341,6 +1471,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return end @@ -1424,6 +1561,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -1443,6 +1583,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1460,6 +1601,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return @@ -1544,6 +1692,9 @@ function construct_device!( if haskey(get_time_series_names(model), ActivePowerTimeSeriesParameter) add_parameters!(container, ActivePowerTimeSeriesParameter, devices, model) end + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end add_to_expression!( container, @@ -1563,6 +1714,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1580,6 +1732,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) add_feedforward_arguments!(container, model, devices) return end @@ -1645,6 +1804,10 @@ function construct_device!( add_parameters!(container, OnStatusParameter, devices, model) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_feedforward_arguments!(container, model, devices) initial_conditions!(container, devices, ThermalCompactDispatch()) @@ -1659,6 +1822,7 @@ function construct_device!( ) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1692,6 +1856,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) return end @@ -1753,6 +1924,10 @@ function construct_device!( add_parameters!(container, OnStatusParameter, devices, model) + if haskey(get_time_series_names(model), FuelCostParameter) + add_parameters!(container, FuelCostParameter, devices, model) + end + add_feedforward_arguments!(container, model, devices) add_to_expression!( @@ -1776,6 +1951,7 @@ function construct_device!( initial_conditions!(container, devices, ThermalCompactDispatch()) add_expressions!(container, ProductionCostExpression, devices, model) + add_expressions!(container, FuelConsumptionExpression, devices, model) add_to_expression!( container, @@ -1793,6 +1969,13 @@ function construct_device!( model, network_model, ) + add_to_expression!( + container, + FuelConsumptionExpression, + PowerAboveMinimumVariable, + devices, + model, + ) return end diff --git a/src/devices_models/devices/common/add_to_expression.jl b/src/devices_models/devices/common/add_to_expression.jl index 0d9902e9b..ad8848ea5 100644 --- a/src/devices_models/devices/common/add_to_expression.jl +++ b/src/devices_models/devices/common/add_to_expression.jl @@ -26,6 +26,44 @@ function add_expressions!( return end +function add_expressions!( + container::OptimizationContainer, + ::Type{T}, + devices::U, + model::DeviceModel{D, W}, +) where { + T <: FuelConsumptionExpression, + U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + W <: AbstractDeviceFormulation, +} where {D <: PSY.Component} + time_steps = get_time_steps(container) + names = String[] + found_quad_fuel_functions = false + for d in devices + fuel_curve = PSY.get_variable(PSY.get_operation_cost(d)) + if fuel_curve isa PSY.FuelCurve + push!(names, PSY.get_name(d)) + if !found_quad_fuel_functions + found_quad_fuel_functions = + PSY.get_value_curve(fuel_curve) isa PSY.QuadraticCurve + end + end + end + + if !isempty(names) + expr_type = found_quad_fuel_functions ? JuMP.QuadExpr : GAE + add_expression_container!( + container, + T(), + D, + names, + time_steps; + expr_type = expr_type, + ) + end + return +end + function add_expressions!( container::OptimizationContainer, ::Type{T}, @@ -1503,7 +1541,7 @@ function add_to_expression!( cost_expression::Union{JuMP.AbstractJuMPScalar, Float64}, component::T, time_period::Int, -) where {S <: CostExpressions, T <: PSY.Component} +) where {S <: Union{CostExpressions, FuelConsumptionExpression}, T <: PSY.Component} if has_container_key(container, S, T) device_cost_expression = get_expression(container, S(), T) component_name = PSY.get_name(component) @@ -1533,6 +1571,142 @@ function add_to_expression!( return end +function add_to_expression!( + container::OptimizationContainer, + ::Type{T}, + ::Type{U}, + devices::IS.FlattenIteratorWrapper{V}, + model::DeviceModel{V, W}, +) where { + T <: FuelConsumptionExpression, + U <: ActivePowerVariable, + V <: PSY.ThermalGen, + W <: AbstractDeviceFormulation, +} + variable = get_variable(container, U(), V) + time_steps = get_time_steps(container) + base_power = get_base_power(container) + resolution = get_resolution(container) + dt = Dates.value(resolution) / MILLISECONDS_IN_HOUR + for d in devices + var_cost = PSY.get_variable(PSY.get_operation_cost(d)) + if !(var_cost isa PSY.FuelCurve) + continue + end + expression = get_expression(container, T(), V) + name = PSY.get_name(d) + device_base_power = PSY.get_base_power(d) + value_curve = PSY.get_value_curve(var_cost) + if value_curve isa PSY.LinearCurve + power_units = PSY.get_power_units(var_cost) + proportional_term = PSY.get_proportional_term(value_curve) + prop_term_per_unit = get_proportional_cost_per_system_unit( + proportional_term, + power_units, + base_power, + device_base_power, + ) + for t in time_steps + fuel_expr = variable[name, t] * prop_term_per_unit * dt + JuMP.add_to_expression!( + expression[name, t], + fuel_expr, + ) + end + elseif value_curve isa PSY.QuadraticCurve + power_units = PSY.get_power_units(var_cost) + proportional_term = PSY.get_proportional_term(value_curve) + quadratic_term = PSY.get_quadratic_term(value_curve) + prop_term_per_unit = get_proportional_cost_per_system_unit( + proportional_term, + power_units, + base_power, + device_base_power, + ) + quad_term_per_unit = get_quadratic_cost_per_system_unit( + quadratic_term, + power_units, + base_power, + device_base_power, + ) + for t in time_steps + fuel_expr = + ( + variable[name, t] .^ 2 * quad_term_per_unit + + variable[name, t] * prop_term_per_unit + ) * dt + JuMP.add_to_expression!( + expression[name, t], + fuel_expr, + ) + end + end + end +end + +function add_to_expression!( + container::OptimizationContainer, + ::Type{T}, + ::Type{U}, + devices::IS.FlattenIteratorWrapper{V}, + model::DeviceModel{V, W}, +) where { + T <: FuelConsumptionExpression, + U <: PowerAboveMinimumVariable, + V <: PSY.ThermalGen, + W <: AbstractDeviceFormulation, +} + variable = get_variable(container, U(), V) + time_steps = get_time_steps(container) + base_power = get_base_power(container) + resolution = get_resolution(container) + dt = Dates.value(resolution) / MILLISECONDS_IN_HOUR + for d in devices + var_cost = PSY.get_variable(PSY.get_operation_cost(d)) + if !(var_cost isa PSY.FuelCurve) + continue + end + expression = get_expression(container, T(), V) + name = PSY.get_name(d) + device_base_power = PSY.get_base_power(d) + value_curve = PSY.get_value_curve(var_cost) + P_min = PSY.get_active_power_limits(d).min + if value_curve isa PSY.LinearCurve + power_units = PSY.get_power_units(var_cost) + proportional_term = PSY.get_proportional_term(value_curve) + prop_term_per_unit = get_proportional_cost_per_system_unit( + proportional_term, + power_units, + base_power, + device_base_power, + ) + for t in time_steps + sos_status = _get_sos_value(container, W, d) + if sos_status == SOSStatusVariable.NO_VARIABLE + bin = 1.0 + elseif sos_status == SOSStatusVariable.PARAMETER + param = get_default_on_parameter(d) + bin = get_parameter(container, param, V).parameter_array[name, t] + elseif sos_status == SOSStatusVariable.VARIABLE + var = get_default_on_variable(d) + bin = get_variable(container, var, V)[name, t] + else + @assert false + end + fuel_expr = + variable[name, t] * prop_term_per_unit * dt + + P_min * bin * prop_term_per_unit * dt + JuMP.add_to_expression!( + expression[name, t], + fuel_expr, + ) + end + elseif value_curve isa PSY.QuadraticCurve + error("Quadratic Curves are not accepted with Compact Formulation: $W") + end + end +end + #= function add_to_expression!( container::OptimizationContainer, diff --git a/src/devices_models/devices/common/get_time_series.jl b/src/devices_models/devices/common/get_time_series.jl index 6aa8a8476..6e307fefc 100644 --- a/src/devices_models/devices/common/get_time_series.jl +++ b/src/devices_models/devices/common/get_time_series.jl @@ -28,9 +28,10 @@ function get_time_series( component::PSY.Component, forecast_name::String, ) + ts_type = get_default_time_series_type(container) return _get_time_series( container, component, - TimeSeriesAttributes(PSY.Deterministic, forecast_name), + TimeSeriesAttributes(ts_type, forecast_name), ) end diff --git a/src/devices_models/devices/common/objective_function/common.jl b/src/devices_models/devices/common/objective_function/common.jl index 064b68977..fc4806f3a 100644 --- a/src/devices_models/devices/common/objective_function/common.jl +++ b/src/devices_models/devices/common/objective_function/common.jl @@ -113,9 +113,9 @@ function add_proportional_cost!( multiplier = objective_function_multiplier(U(), V()) for d in devices op_cost_data = PSY.get_operation_cost(d) - cost_term = proportional_cost(op_cost_data, U(), d, V()) - iszero(cost_term) && continue for t in get_time_steps(container) + cost_term = proportional_cost(container, op_cost_data, U(), d, V(), t) + iszero(cost_term) && continue if !PSY.get_must_run(d) exp = _add_proportional_term!(container, U(), d, cost_term * multiplier, t) add_to_expression!(container, ProductionCostExpression, exp, d, t) @@ -270,19 +270,48 @@ end ################## Fuel Cost ##################### ################################################## -function _get_fuel_cost_value( +function get_fuel_cost_value( + container::OptimizationContainer, + component::T, + time_period::Int, + ::Val{true}, +) where {T <: PSY.Component} + parameter_array = get_parameter_array(container, FuelCostParameter(), T) + parameter_multiplier = + get_parameter_multiplier_array(container, FuelCostParameter(), T) + name = PSY.get_name(component) + return parameter_array[name, time_period] * parameter_multiplier[name, time_period] +end + +function get_fuel_cost_value( ::OptimizationContainer, - fuel_cost::Float64, + component::T, ::Int, -) - return fuel_cost + ::Val{false}, +) where {T <: PSY.Component} + return PSY.get_fuel_cost(component) end -function _get_fuel_cost_value( +function _add_time_varying_fuel_variable_cost!( container::OptimizationContainer, + ::T, + component::V, fuel_cost::IS.TimeSeriesKey, - time_period::Int, -) - error("Not implemented yet fuel cost") - return fuel_cost +) where {T <: VariableType, V <: PSY.Component} + parameter = get_parameter_array(container, FuelCostParameter(), V) + multiplier = get_parameter_multiplier_array(container, FuelCostParameter(), V) + expression = get_expression(container, FuelConsumptionExpression(), V) + name = PSY.get_name(component) + for t in get_time_steps(container) + cost_expr = expression[name, t] * parameter[name, t] * multiplier[name, t] + add_to_expression!( + container, + ProductionCostExpression, + cost_expr, + component, + t, + ) + add_to_objective_variant_expression!(container, cost_expr) + end + return end diff --git a/src/devices_models/devices/common/objective_function/linear_curve.jl b/src/devices_models/devices/common/objective_function/linear_curve.jl index 06d47c9b2..91e2a9eed 100644 --- a/src/devices_models/devices/common/objective_function/linear_curve.jl +++ b/src/devices_models/devices/common/objective_function/linear_curve.jl @@ -115,12 +115,12 @@ end function _add_fuel_linear_variable_cost!( container::OptimizationContainer, ::T, - component::PSY.Component, - fuel_curve::Float64, + component::V, + ::Float64, # already normalized in MMBTU/p.u. fuel_cost::IS.TimeSeriesKey, -) where {T <: VariableType} - error("Not implemented yet") - _add_linearcurve_variable_cost!(container, T(), component, fuel_curve) +) where {T <: VariableType, V <: PSY.Component} + _add_time_varying_fuel_variable_cost!(container, T(), component, fuel_cost) + return end """ diff --git a/src/devices_models/devices/common/objective_function/piecewise_linear.jl b/src/devices_models/devices/common/objective_function/piecewise_linear.jl index d173cd85d..aff4260c7 100644 --- a/src/devices_models/devices/common/objective_function/piecewise_linear.jl +++ b/src/devices_models/devices/common/objective_function/piecewise_linear.jl @@ -320,22 +320,17 @@ function _get_pwl_cost_expression( base_power, device_base_power, ) - fuel_cost = PSY.get_fuel_cost(cost_function) - fuel_cost_value = _get_fuel_cost_value( - container, - fuel_cost, - time_period, - ) # Multiplier is not necessary here. There is no negative cost for fuel curves. resolution = get_resolution(container) dt = Dates.value(resolution) / MILLISECONDS_IN_HOUR - return _get_pwl_cost_expression( + fuel_consumption_expression = _get_pwl_cost_expression( container, component, time_period, cost_data_normalized, - dt * fuel_cost_value, + dt, ) + return fuel_consumption_expression end ################################################## @@ -484,10 +479,7 @@ function _add_variable_cost_to_objective!( container::OptimizationContainer, ::T, component::PSY.Component, - cost_function::Union{ - PSY.CostCurve{PSY.PiecewisePointCurve}, - PSY.FuelCurve{PSY.PiecewisePointCurve}, - }, + cost_function::PSY.CostCurve{PSY.PiecewisePointCurve}, ::U, ) where {T <: VariableType, U <: AbstractDeviceFormulation} component_name = PSY.get_name(component) @@ -515,6 +507,66 @@ function _add_variable_cost_to_objective!( return end +""" +Creates piecewise linear cost function using a sum of variables and expression with sign and time step included. +# Arguments + - container::OptimizationContainer : the optimization_container model built in PowerSimulations + - var_key::VariableKey: The variable name + - component_name::String: The component_name of the variable container + - cost_function::PSY.CostCurve{PSY.PiecewisePointCurve}: container for piecewise linear cost +""" +function _add_variable_cost_to_objective!( + container::OptimizationContainer, + ::T, + component::PSY.Component, + cost_function::PSY.FuelCurve{PSY.PiecewisePointCurve}, + ::U, +) where {T <: VariableType, U <: AbstractDeviceFormulation} + component_name = PSY.get_name(component) + @debug "PWL Variable Cost" _group = LOG_GROUP_COST_FUNCTIONS component_name + # If array is full of tuples with zeros return 0.0 + value_curve = PSY.get_value_curve(cost_function) + cost_component = PSY.get_function_data(value_curve) + if all(iszero.((point -> point.y).(PSY.get_points(cost_component)))) # TODO I think this should have been first. before? + @debug "All cost terms for component $(component_name) are 0.0" _group = + LOG_GROUP_COST_FUNCTIONS + return + end + pwl_fuel_consumption_expressions = + _add_pwl_term!(container, component, cost_function, T(), U()) + + is_time_variant_ = is_time_variant(cost_function) + for t in get_time_steps(container) + fuel_cost_value = get_fuel_cost_value( + container, + component, + t, + Val{is_time_variant_}(), + ) + pwl_cost_expression = pwl_fuel_consumption_expressions[t] * fuel_cost_value + add_to_expression!( + container, + ProductionCostExpression, + pwl_cost_expression, + component, + t, + ) + add_to_expression!( + container, + FuelConsumptionExpression, + pwl_fuel_consumption_expressions[t], + component, + t, + ) + if is_time_variant_ + add_to_objective_variant_expression!(container, pwl_cost_expression) + else + add_to_objective_invariant_expression!(container, pwl_cost_expression) + end + end + return +end + ################################################## ###### CostCurve: PiecewiseIncrementalCurve ###### ######### and PiecewiseAverageCurve ############## diff --git a/src/devices_models/devices/common/objective_function/quadratic_curve.jl b/src/devices_models/devices/common/objective_function/quadratic_curve.jl index 686dc0d23..0bc31bbe5 100644 --- a/src/devices_models/devices/common/objective_function/quadratic_curve.jl +++ b/src/devices_models/devices/common/objective_function/quadratic_curve.jl @@ -212,14 +212,7 @@ function _add_fuel_quadratic_variable_cost!( quadratic_fuel_curve::Float64, fuel_cost::IS.TimeSeriesKey, ) where {T <: VariableType} - error("Not implemented yet") - _add_quadraticcurve_variable_cost!( - container, - T(), - component, - proportional_fuel_curve, - quadratic_fuel_curve, - ) + _add_time_varying_fuel_variable_cost!(container, T(), component, fuel_cost) end @doc raw""" diff --git a/src/devices_models/devices/thermal_generation.jl b/src/devices_models/devices/thermal_generation.jl index 536d54ead..cf31e2696 100644 --- a/src/devices_models/devices/thermal_generation.jl +++ b/src/devices_models/devices/thermal_generation.jl @@ -51,6 +51,7 @@ get_variable_binary(::Union{ColdStartVariable, WarmStartVariable, HotStartVariab ########################### Parameter related set functions ################################ get_multiplier_value(::ActivePowerTimeSeriesParameter, d::PSY.ThermalGen, ::AbstractThermalFormulation) = PSY.get_max_active_power(d) +get_multiplier_value(::FuelCostParameter, d::PSY.ThermalGen, ::AbstractThermalFormulation) = 1.0 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 @@ -76,8 +77,8 @@ initial_condition_variable(::InitialTimeDurationOff, d::PSY.ThermalGen, ::Abstra ########################Objective Function################################################## # TODO: Decide what is the cost for OnVariable, if fixed or constant term in variable -function proportional_cost(cost::PSY.ThermalGenerationCost, S::OnVariable, T::PSY.ThermalGen, U::AbstractThermalFormulation) - return onvar_cost(cost, S, T, U) + PSY.get_constant_term(PSY.get_vom_cost(PSY.get_variable(cost))) + PSY.get_fixed(cost) +function proportional_cost(container::OptimizationContainer, cost::PSY.ThermalGenerationCost, S::OnVariable, T::PSY.ThermalGen, U::AbstractThermalFormulation, t::Int) + return onvar_cost(container, cost, S, T, U, t) + PSY.get_constant_term(PSY.get_vom_cost(PSY.get_variable(cost))) + PSY.get_fixed(cost) end proportional_cost(cost::PSY.MarketBidCost, ::OnVariable, ::PSY.ThermalGen, ::AbstractThermalFormulation) = PSY.get_no_load_cost(cost) @@ -112,16 +113,16 @@ variable_cost(cost::PSY.OperationalCost, ::PowerAboveMinimumVariable, ::PSY.Ther """ Theoretical Cost at power output zero. Mathematically is the intercept with the y-axis """ -function onvar_cost(cost::PSY.ThermalGenerationCost, S::OnVariable, d::PSY.ThermalGen, U::AbstractThermalFormulation) - return _onvar_cost(PSY.get_variable(cost), d) +function onvar_cost(container::OptimizationContainer, cost::PSY.ThermalGenerationCost, S::OnVariable, d::PSY.ThermalGen, U::AbstractThermalFormulation, t::Int) + return _onvar_cost(container, PSY.get_variable(cost), d, t) end -function _onvar_cost(cost_function::PSY.CostCurve{PSY.PiecewisePointCurve}, d::PSY.ThermalGen) +function _onvar_cost(::OptimizationContainer, cost_function::PSY.CostCurve{PSY.PiecewisePointCurve}, d::PSY.ThermalGen, ::Int) # OnVariableCost is included in the Point itself for PiecewisePointCurve return 0.0 end -function _onvar_cost(cost_function::Union{PSY.CostCurve{PSY.LinearCurve}, PSY.CostCurve{PSY.QuadraticCurve}}, d::PSY.ThermalGen) +function _onvar_cost(::OptimizationContainer, cost_function::Union{PSY.CostCurve{PSY.LinearCurve}, PSY.CostCurve{PSY.QuadraticCurve}}, d::PSY.ThermalGen, ::Int) value_curve = PSY.get_value_curve(cost_function) cost_component = PSY.get_function_data(value_curve) # Always in \$/h @@ -129,31 +130,35 @@ function _onvar_cost(cost_function::Union{PSY.CostCurve{PSY.LinearCurve}, PSY.Co return constant_term end -function _onvar_cost(cost_function::PSY.CostCurve{PSY.PiecewiseIncrementalCurve}, d::PSY.ThermalGen) +function _onvar_cost(::OptimizationContainer, cost_function::PSY.CostCurve{PSY.PiecewiseIncrementalCurve}, d::PSY.ThermalGen, ::Int) # Input at min is used to transform to InputOutputCurve return 0.0 end -function _onvar_cost(cost_function::PSY.FuelCurve{PSY.PiecewisePointCurve}, d::PSY.ThermalGen) +function _onvar_cost(::OptimizationContainer, cost_function::PSY.FuelCurve{PSY.PiecewisePointCurve}, d::PSY.ThermalGen, ::Int) # OnVariableCost is included in the Point itself for PiecewisePointCurve return 0.0 end -function _onvar_cost(cost_function::PSY.FuelCurve{PSY.PiecewiseIncrementalCurve}, d::PSY.ThermalGen) +function _onvar_cost(::OptimizationContainer, cost_function::PSY.FuelCurve{PSY.PiecewiseIncrementalCurve}, d::PSY.ThermalGen, ::Int) # Input at min is used to transform to InputOutputCurve return 0.0 end -function _onvar_cost(cost_function::Union{PSY.FuelCurve{PSY.LinearCurve}, PSY.FuelCurve{PSY.QuadraticCurve}}, d::PSY.ThermalGen) +function _onvar_cost(container::OptimizationContainer, cost_function::Union{PSY.FuelCurve{PSY.LinearCurve}, PSY.FuelCurve{PSY.QuadraticCurve}}, d::T, t::Int) where {T <: PSY.ThermalGen} value_curve = PSY.get_value_curve(cost_function) cost_component = PSY.get_function_data(value_curve) - # In Unit/h (unit typically in ) + # In Unit/h constant_term = PSY.get_constant_term(cost_component) fuel_cost = PSY.get_fuel_cost(cost_function) if typeof(fuel_cost) <: Float64 return constant_term * fuel_cost else - error("Time series not implemented yet") + parameter_array = get_parameter_array(container, FuelCostParameter(), T) + parameter_multiplier = + get_parameter_multiplier_array(container, FuelCostParameter(), T) + name = PSY.get_name(d) + return constant_term * parameter_array[name, t] * parameter_multiplier[name, t] end end @@ -194,7 +199,9 @@ function get_default_time_series_names( ::Type{U}, ::Type{V}, ) where {U <: PSY.ThermalGen, V <: Union{FixedOutput, AbstractThermalFormulation}} - return Dict{Type{<:TimeSeriesParameter}, String}() + return Dict{Any, String}( + FuelCostParameter => "fuel_cost", + ) end function get_default_attributes( diff --git a/src/operation/decision_model.jl b/src/operation/decision_model.jl index 65612b171..831563a75 100644 --- a/src/operation/decision_model.jl +++ b/src/operation/decision_model.jl @@ -414,13 +414,15 @@ function reset!(model::DecisionModel{<:DefaultDecisionProblem}) if was_built_for_recurrent_solves set_execution_count!(model, 0) end + sys = get_system(model) + ts_type = get_deterministic_time_series_type(sys) IS.Optimization.set_container!( get_internal(model), OptimizationContainer( get_system(model), get_settings(model), nothing, - PSY.Deterministic, + ts_type, ), ) get_optimization_container(model).built_for_recurrent_solves = diff --git a/src/parameters/add_parameters.jl b/src/parameters/add_parameters.jl index 15ebf6e78..631a42fbc 100644 --- a/src/parameters/add_parameters.jl +++ b/src/parameters/add_parameters.jl @@ -34,6 +34,23 @@ function add_parameters!( return end +function add_parameters!( + container::OptimizationContainer, + ::Type{T}, + devices::U, + model::DeviceModel{D, W}, +) where { + T <: FuelCostParameter, + U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + W <: AbstractDeviceFormulation, +} where {D <: PSY.Component} + if get_rebuild_model(get_settings(container)) && has_container_key(container, T, D) + return + end + _add_parameters!(container, T(), devices, model) + return +end + function add_parameters!( container::OptimizationContainer, ::Type{T}, @@ -257,7 +274,55 @@ function _add_parameters!( U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, W <: AbstractDeviceFormulation, } where {D <: PSY.Component} - _add_time_series_parameters!(container, param, devices, model) + ts_type = get_default_time_series_type(container) + if !(ts_type <: Union{PSY.AbstractDeterministic, PSY.StaticTimeSeries}) + error( + "add_parameters! for ObjectiveFunctionParameter is not compatible with $ts_type", + ) + end + time_steps = get_time_steps(container) + ts_name = get_time_series_names(model)[T] + device_names = + [PSY.get_name(x) for x in devices if PSY.has_time_series(x, ts_type, ts_name)] + if isempty(device_names) + return + end + jump_model = get_jump_model(container) + + param_container = add_param_container!( + container, + param, + D, + ActivePowerVariable, + PSI.SOSStatusVariable.NO_VARIABLE, + false, + Float64, + device_names, + time_steps, + ) + + for device in devices + if !PSY.has_time_series(device, ts_type, ts_name) + continue + end + ts_vals = get_time_series_initial_values!(container, ts_type, device, ts_name) + name = PSY.get_name(device) + for step in time_steps + PSI.set_parameter!( + param_container, + jump_model, + ts_vals[step], + name, + step, + ) + PSI.set_multiplier!( + param_container, + get_multiplier_value(T(), device, W()), + name, + step, + ) + end + end return end diff --git a/src/parameters/update_cost_parameters.jl b/src/parameters/update_cost_parameters.jl index 600eca675..259141da6 100644 --- a/src/parameters/update_cost_parameters.jl +++ b/src/parameters/update_cost_parameters.jl @@ -14,31 +14,62 @@ function _update_parameter_values!( template = get_template(model) device_model = get_model(template, V) components = get_available_components(device_model, get_system(model)) - for component in components if _has_variable_cost_parameter(component) name = PSY.get_name(component) - ts_vector = PSY.get_variable_cost( - component, - PSY.get_operation_cost(component); - start_time = initial_forecast_time, - len = horizon, - ) - variable_cost_forecast_values = TimeSeries.values(ts_vector) - for (t, value) in enumerate(variable_cost_forecast_values) - if attributes.uses_compact_power - # TODO implement this - value, _ = _convert_variable_cost(value) - end - # TODO removed an apparently unused block of code here? - _set_param_value!(parameter_array, value, name, t) - update_variable_cost!( - container, - parameter_array, - parameter_multiplier, - attributes, + op_cost = PSY.get_operation_cost(component) + if op_cost isa PSY.MarketBidCost + ts_vector = PSY.get_variable_cost( component, - t, + PSY.get_operation_cost(component); + start_time = initial_forecast_time, + len = horizon, + ) + variable_cost_forecast_values = TimeSeries.values(ts_vector) + for (t, value) in enumerate(variable_cost_forecast_values) + if attributes.uses_compact_power + # TODO implement this + value, _ = _convert_variable_cost(value) + end + # TODO removed an apparently unused block of code here? + _set_param_value!(parameter_array, value, name, t) + update_variable_cost!( + container, + parameter_array, + parameter_multiplier, + attributes, + component, + t, + ) + end + elseif op_cost isa PSY.ThermalGenerationCost + fuel_curve = PSY.get_variable(op_cost) + ts_vector = PSY.get_fuel_cost( + component; + start_time = initial_forecast_time, + len = horizon, + ) + fuel_cost_forecast_values = TimeSeries.values(ts_vector) + for (t, value) in enumerate(fuel_cost_forecast_values) + # TODO: Is this compact power attribute being used? + if attributes.uses_compact_power + # TODO implement this + value, _ = _convert_variable_cost(value) + end + _set_param_value!(parameter_array, value, name, t) + update_variable_cost!( + container, + parameter_array, + parameter_multiplier, + attributes, + component, + fuel_curve, + t, + ) + end + else + error( + "Update Cost Function Parameter not implemented for $(typeof(op_cost))", ) end end @@ -50,6 +81,15 @@ _has_variable_cost_parameter(component::PSY.Component) = _has_variable_cost_parameter(PSY.get_operation_cost(component)) _has_variable_cost_parameter(::PSY.MarketBidCost) = true _has_variable_cost_parameter(::T) where {T <: PSY.OperationalCost} = false +function _has_variable_cost_parameter(cost::T) where {T <: PSY.ThermalGenerationCost} + var_cost = PSY.get_variable(cost) + if var_cost isa PSY.FuelCurve + if PSY.get_fuel_cost(var_cost) isa IS.TimeSeriesKey + return true + end + end + return false +end function _update_pwl_cost_expression( container::OptimizationContainer, @@ -123,3 +163,25 @@ function update_variable_cost!( set_expression!(container, ProductionCostExpression, gen_cost, component, time_period) return end + +function update_variable_cost!( + container::OptimizationContainer, + parameter_array::JuMPFloatArray, + parameter_multiplier::JuMPFloatArray, + ::CostFunctionAttributes{Float64}, + component::T, + fuel_curve::PSY.FuelCurve, + time_period::Int, +) where {T <: PSY.Component} + component_name = PSY.get_name(component) + fuel_cost = parameter_array[component_name, time_period] + if all(iszero.(last.(fuel_cost))) + return + end + mult_ = parameter_multiplier[component_name, time_period] + expression = get_expression(container, FuelConsumptionExpression(), T) + cost_expr = expression[component_name, time_period] * fuel_cost * mult_ + add_to_objective_variant_expression!(container, cost_expr) + set_expression!(container, ProductionCostExpression, cost_expr, component, time_period) + return +end diff --git a/src/utils/powersystems_utils.jl b/src/utils/powersystems_utils.jl index d49cebb5a..587786272 100644 --- a/src/utils/powersystems_utils.jl +++ b/src/utils/powersystems_utils.jl @@ -338,6 +338,10 @@ function _get_piecewise_incrementalcurve_per_system_unit( return PSY.PiecewiseStepData(x_coords_normalized, y_coords_normalized) end +function is_time_variant(cost_function::PSY.FuelCurve{PSY.PiecewisePointCurve}) + return isa(PSY.get_fuel_cost(cost_function), IS.TimeSeriesKey) +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) diff --git a/test/test_device_thermal_generation_constructors.jl b/test/test_device_thermal_generation_constructors.jl index 907edd597..868e0bc9f 100644 --- a/test/test_device_thermal_generation_constructors.jl +++ b/test/test_device_thermal_generation_constructors.jl @@ -967,3 +967,120 @@ end end psi_checkobjfun_test(model, GAEVF) end + +@testset "Thermal with fuel cost time series" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_re_fuel_cost") + + template = ProblemTemplate( + NetworkModel( + CopperPlatePowerModel; + duals = [CopperPlateBalanceConstraint], + ), + ) + + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, RenewableDispatch, RenewableFullDispatch) + + model = DecisionModel( + template, + sys; + name = "UC", + optimizer = HiGHS_optimizer, + system_to_file = false, + store_variable_names = true, + optimizer_solve_log_print = false, + ) + models = SimulationModels(; + decision_models = [ + model, + ], + ) + sequence = SimulationSequence(; + models = models, + feedforwards = Dict( + ), + ini_cond_chronology = InterProblemChronology(), + ) + + sim = Simulation(; + name = "compact_sim", + steps = 2, + models = models, + sequence = sequence, + initial_time = DateTime("2024-01-01T00:00:00"), + simulation_folder = mktempdir(), + ) + + build!(sim; console_level = Logging.Error, serialize = false) + moi_tests(model, 432, 0, 192, 120, 72, false) + execute!(sim; enable_progress_bar = true) + + sim_res = SimulationResults(sim) + res_uc = get_decision_problem_results(sim_res, "UC") + th_uc = read_realized_variable(res_uc, "ActivePowerVariable__ThermalStandard") + p_brighton = th_uc[!, "Brighton"] + p_solitude = th_uc[!, "Solitude"] + + @test sum(p_brighton[1:24]) < 50.0 # Barely used when expensive + @test sum(p_brighton[25:48]) > 5000.0 # Used a lot when cheap + @test sum(p_solitude[1:24]) > 5000.0 # Used a lot when cheap + @test sum(p_solitude[25:48]) < 50.0 # Barely used when expensive +end + +@testset "Thermal with fuel cost time series with Quadratic and PWL" begin + sys = PSB.build_system(PSITestSystems, "c_sys5_re_fuel_cost") + + template = ProblemTemplate( + NetworkModel( + CopperPlatePowerModel; + duals = [CopperPlateBalanceConstraint], + ), + ) + + solitude = get_component(ThermalStandard, sys, "Solitude") + op_cost = get_operation_cost(solitude) + ts = deepcopy(get_time_series(Deterministic, solitude, "fuel_cost")) + remove_time_series!(sys, Deterministic, solitude, "fuel_cost") + quad_curve = QuadraticCurve(0.05, 1.0, 0.0) + new_th_cost = ThermalGenerationCost(; + variable = FuelCurve(; + value_curve = quad_curve, + fuel_cost = 1.0, + ), + fixed = op_cost.fixed, + start_up = op_cost.start_up, + shut_down = op_cost.shut_down, + ) + + set_operation_cost!(solitude, new_th_cost) + add_time_series!( + sys, + solitude, + ts, + ) + + # There is no free MIQP solver, we need to use ThermalDisptchNoMin for testing + set_device_model!(template, ThermalStandard, ThermalDispatchNoMin) + set_device_model!(template, PowerLoad, StaticPowerLoad) + set_device_model!(template, RenewableDispatch, RenewableFullDispatch) + + model = DecisionModel( + template, + sys; + name = "UC", + optimizer = ipopt_optimizer, + system_to_file = false, + store_variable_names = true, + optimizer_solve_log_print = false, + ) + @test build!(model; output_dir = mktempdir(; cleanup = true)) == + PSI.ModelBuildStatus.BUILT + solve!(model) + moi_tests(model, 288, 0, 192, 120, 72, false) + container = PSI.get_optimization_container(model) + @test isa( + PSI.get_invariant_terms(PSI.get_objective_expression(container)), + JuMP.QuadExpr, + ) +end diff --git a/test/test_utils/solver_definitions.jl b/test/test_utils/solver_definitions.jl index 795b195bb..e5d4c2960 100644 --- a/test/test_utils/solver_definitions.jl +++ b/test/test_utils/solver_definitions.jl @@ -1,9 +1,7 @@ # Solvers using Ipopt -using GLPK using SCS using HiGHS -using Cbc ipopt_optimizer = JuMP.optimizer_with_attributes(Ipopt.Optimizer, "tol" => 1e-6, "print_level" => 0)