From ef1d8f20f60caac2d367ccdeed9b89af9a0fa1d4 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Sat, 15 Jun 2024 11:20:49 -0700 Subject: [PATCH 1/7] comment AGC test --- test/test_services_constructor.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_services_constructor.jl b/test/test_services_constructor.jl index 42839a3bde..68d3911002 100644 --- a/test/test_services_constructor.jl +++ b/test/test_services_constructor.jl @@ -203,6 +203,7 @@ end moi_tests(model, 504, 0, 120, 192, 24, false) end +#= @testset "Test AGC" begin c_sys5_reg = PSB.build_system(PSITestSystems, "c_sys5_reg") @test_throws ArgumentError template_agc_reserve_deployment(; dummy_arg = 0.0) @@ -215,6 +216,7 @@ end # These values might change as the AGC model is refined moi_tests(agc_problem, 696, 0, 480, 0, 384, false) end +=# @testset "Test GroupReserve from Thermal Dispatch" begin template = get_thermal_dispatch_template_network() From 759357c569659bfe4cbed99f05d69e23a1c01cad Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 17 Jun 2024 15:14:21 -0700 Subject: [PATCH 2/7] add meta for ORDC and comment AGC --- .../devices/common/add_to_expression.jl | 22 ++++++++++++++++++- src/services_models/services_constructor.jl | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/devices_models/devices/common/add_to_expression.jl b/src/devices_models/devices/common/add_to_expression.jl index b735f0d327..9fcd05a03c 100644 --- a/src/devices_models/devices/common/add_to_expression.jl +++ b/src/devices_models/devices/common/add_to_expression.jl @@ -39,7 +39,7 @@ function add_expressions!( } where {D <: PSY.Component} time_steps = get_time_steps(container) names = [PSY.get_name(d) for d in devices] - add_expression_container!(container, T(), D, names, time_steps) + add_expression_container!(container, T(), D, names, time_steps; meta = names[1]) return end @@ -1406,6 +1406,25 @@ function add_to_expression!( return end +function add_to_expression!( + container::OptimizationContainer, + ::Type{S}, + cost_expression::JuMP.AbstractJuMPScalar, + component::T, + time_period::Int, +) where {S <: CostExpressions, T <: PSY.ReserveDemandCurve} + if has_container_key(container, S, T, PSY.get_name(component)) + device_cost_expression = get_expression(container, S(), T, PSY.get_name(component)) + component_name = PSY.get_name(component) + JuMP.add_to_expression!( + device_cost_expression[component_name, time_period], + cost_expression, + ) + end + return +end + +#= function add_to_expression!( container::OptimizationContainer, ::Type{T}, @@ -1461,3 +1480,4 @@ function add_to_expression!( end return end +=# \ No newline at end of file diff --git a/src/services_models/services_constructor.jl b/src/services_models/services_constructor.jl index 1d2d89ea50..01b8d5ccd6 100644 --- a/src/services_models/services_constructor.jl +++ b/src/services_models/services_constructor.jl @@ -215,7 +215,7 @@ function construct_service!( name = get_service_name(model) service = PSY.get_component(SR, sys, name) contributing_devices = get_contributing_devices(model) - add_variable!(container, ServiceRequirementVariable(), [service], StepwiseCostReserve()) + add_variable!(container, ServiceRequirementVariable(), service, StepwiseCostReserve()) add_variables!( container, ActivePowerReserveVariable, From 917b9cbb9547c6e0d2624307278b237ceba809d5 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 17 Jun 2024 15:14:46 -0700 Subject: [PATCH 3/7] add model for ORDC --- .../common/objective_function/market_bid.jl | 109 +++++++++++++++++- .../objective_function/piecewise_linear.jl | 32 ----- src/services_models/reserves.jl | 78 ++++++++----- 3 files changed, 158 insertions(+), 61 deletions(-) diff --git a/src/devices_models/devices/common/objective_function/market_bid.jl b/src/devices_models/devices/common/objective_function/market_bid.jl index 65f68a28c6..e3625f98be 100644 --- a/src/devices_models/devices/common/objective_function/market_bid.jl +++ b/src/devices_models/devices/common/objective_function/market_bid.jl @@ -18,7 +18,7 @@ function _add_pwl_variables!( pwlvars[i] = var_container[(component_name, i, time_period)] = JuMP.@variable( get_jump_model(container), - base_name = "PieceWiseLinearBlockOffer_$(component_name)_supply_{pwl_$(i), $time_period}", + base_name = "PieceWiseLinearBlockOffer_$(component_name)_{pwl_$(i), $time_period}", lower_bound = 0.0, ) end @@ -80,6 +80,49 @@ function _add_pwl_constraint!( return end +""" +Implement the constraints for PWL Block Offer variables for ORDC. That is: + +```math +\\sum_{k\\in\\mathcal{K}} \\delta_{k,t} = p_t \\\\ +\\sum_{k\\in\\mathcal{K}} \\delta_{k,t} <= P_{k+1,t}^{max} - P_{k,t}^{max} +``` +""" +function _add_pwl_constraint!( + container::OptimizationContainer, + component::T, + ::U, + break_points::Vector{Float64}, + sos_status::SOSStatusVariable, + period::Int, +) where {T <: PSY.ReserveDemandCurve, U <: VariableType} + name = PSY.get_name(component) + variables = get_variable(container, U(), T, name) + const_container = lazy_container_addition!( + container, + PieceWiseLinearBlockOfferConstraint(), + T, + axes(variables)...; + meta = name, + ) + len_cost_data = length(break_points) - 1 + jump_model = get_jump_model(container) + pwl_vars = get_variable(container, PieceWiseLinearBlockOffer(), T) + const_container[name, period] = JuMP.@constraint( + jump_model, + variables[name, period] == + sum(pwl_vars[name, ix, period] for ix in 1:len_cost_data) + ) + + for ix in 1:len_cost_data + JuMP.@constraint( + jump_model, + pwl_vars[name, ix, period] <= break_points[ix + 1] - break_points[ix] + ) + end + return +end + ################################################## ################ PWL Expressions ################# ################################################## @@ -136,6 +179,29 @@ function _get_pwl_cost_expression( ) end +""" +Get cost expression for StepwiseCostReserve +""" +function _get_pwl_cost_expression( + container::OptimizationContainer, + component::T, + time_period::Int, + cost_data::PSY.PiecewiseStepData, + multiplier::Float64, +) where {T <: PSY.ReserveDemandCurve} + name = PSY.get_name(component) + pwl_var_container = get_variable(container, PieceWiseLinearBlockOffer(), T) + slopes = PSY.get_y_coords(cost_data) + ordc_cost = JuMP.AffExpr(0.0) + for i in 1:length(slopes) + JuMP.add_to_expression!( + ordc_cost, + slopes[i] * multiplier * pwl_var_container[(name, i, time_period)], + ) + end + return ordc_cost +end + #= # For Market Bid function _add_pwl_variables!( @@ -269,6 +335,47 @@ function _add_pwl_term!( return pwl_cost_expressions end + +################################################## +########## PWL for StepwiseCostReserve ########## +################################################## + +function _add_pwl_term!( + container::OptimizationContainer, + component::T, + cost_data::PSY.CostCurve{PSY.PiecewiseIncrementalCurve}, + ::U, + ::V, +) where {T <: PSY.Component, U <: VariableType, V <: AbstractServiceFormulation} + multiplier = objective_function_multiplier(U(), V()) + resolution = get_resolution(container) + dt = Dates.value(Dates.Second(resolution)) / SECONDS_IN_HOUR + base_power = get_base_power(container) + value_curve = PSY.get_value_curve(cost_data) + power_units = PSY.get_power_units(cost_data) + cost_component = PSY.get_function_data(value_curve) + device_base_power = PSY.get_base_power(component) + data = get_piecewise_incrementalcurve_per_system_unit( + cost_component, + power_units, + base_power, + device_base_power, + ) + name = PSY.get_name(component) + time_steps = get_time_steps(container) + pwl_cost_expressions = Vector{JuMP.AffExpr}(undef, time_steps[end]) + sos_val = _get_sos_value(container, V, component) + for t in time_steps + break_points = PSY.get_x_coords(data) + _add_pwl_variables!(container, T, name, t, data) + _add_pwl_constraint!(container, component, U(), break_points, sos_val, t) + pwl_cost = _get_pwl_cost_expression(container, component, t, data, multiplier * dt) + pwl_cost_expressions[t] = pwl_cost + end + return pwl_cost_expressions +end + + #= """ Add PWL cost terms for data coming from the MarketBidCost 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 d3407f21ef..37ae04b62d 100644 --- a/src/devices_models/devices/common/objective_function/piecewise_linear.jl +++ b/src/devices_models/devices/common/objective_function/piecewise_linear.jl @@ -311,38 +311,6 @@ function _get_pwl_cost_expression( ) end -################################################## -########## PWL for StepwiseCostReserve ########## -################################################## - -function _add_pwl_term!( - container::OptimizationContainer, - component::T, - cost_data::AbstractVector{PSY.PiecewiseStepData}, - ::U, - ::V, -) where {T <: PSY.Component, U <: VariableType, V <: AbstractServiceFormulation} - multiplier = objective_function_multiplier(U(), V()) - resolution = get_resolution(container) - dt = Dates.value(Dates.Second(resolution)) / SECONDS_IN_HOUR - base_power = get_base_power(container) - # Re-scale breakpoints by Basepower - name = PSY.get_name(component) - time_steps = get_time_steps(container) - pwl_cost_expressions = Vector{JuMP.AffExpr}(undef, time_steps[end]) - sos_val = _get_sos_value(container, V, component) - for t in time_steps - data = cost_data[t] - break_points = PSY.get_x_coords(data) ./ base_power - _add_pwl_variables!(container, T, name, t, data) - _add_pwl_constraint!(container, component, U(), break_points, sos_val, t) - _add_pwl_sos_constraint!(container, component, U(), break_points, sos_val, t) - pwl_cost = _get_pwl_cost_expression(container, component, t, data, multiplier * dt) - pwl_cost_expressions[t] = pwl_cost - end - return pwl_cost_expressions -end - ################################################## ######## CostCurve: PiecewisePointCurve ########## ################################################## diff --git a/src/services_models/reserves.jl b/src/services_models/reserves.jl index 90d8fc9789..113492096c 100644 --- a/src/services_models/reserves.jl +++ b/src/services_models/reserves.jl @@ -29,7 +29,7 @@ get_multiplier_value(::RequirementTimeSeriesParameter, d::PSY.ReserveNonSpinning get_parameter_multiplier(::VariableValueParameter, d::Type{<:PSY.AbstractReserve}, ::AbstractReservesFormulation) = 1.0 get_initial_parameter_value(::VariableValueParameter, d::Type{<:PSY.AbstractReserve}, ::AbstractReservesFormulation) = 0.0 -objective_function_multiplier(::ServiceRequirementVariable, ::StepwiseCostReserve) = 1.0 +objective_function_multiplier(::ServiceRequirementVariable, ::StepwiseCostReserve) = -1.0 sos_status(::PSY.ReserveDemandCurve, ::StepwiseCostReserve)=SOSStatusVariable.NO_VARIABLE uses_compact_power(::PSY.ReserveDemandCurve, ::StepwiseCostReserve)=false #! format: on @@ -87,6 +87,40 @@ function get_default_attributes( return Dict{String, Any}() end +""" +Add variables for ServiceRequirementVariable for StepWiseCostReserve +""" +function add_variable!( + container::OptimizationContainer, + variable_type::T, + service::D, + formulation, +) where { + T <: ServiceRequirementVariable, + D <: PSY.ReserveDemandCurve, +} + time_steps = get_time_steps(container) + service_name = PSY.get_name(service) + variable = add_variable_container!( + container, + variable_type, + D, + [service_name], + time_steps; + meta = service_name, + ) + + for t in time_steps + variable[service_name, t] = JuMP.@variable( + get_jump_model(container), + base_name = "$(T)_$(D)_$(service_name)_{$(service_name), $(t)}", + lower_bound = 0.0, + ) + end + + return +end + ################################## Reserve Requirement Constraint ########################## function add_constraints!( container::OptimizationContainer, @@ -276,7 +310,7 @@ function add_constraints!( ) reserve_variable = get_variable(container, ActivePowerReserveVariable(), SR, service_name) - requirement_variable = get_variable(container, ServiceRequirementVariable(), SR) + requirement_variable = get_variable(container, ServiceRequirementVariable(), SR, service_name) jump_model = get_jump_model(container) for t in time_steps constraint[service_name, t] = JuMP.@constraint( @@ -479,36 +513,24 @@ function _add_variable_cost_to_objective!( @debug "PWL Variable Cost" _group = LOG_GROUP_COST_FUNCTIONS component_name # If array is full of tuples with zeros return 0.0 time_steps = get_time_steps(container) - variable_cost_forecast = get_time_series(container, component, "variable_cost") - variable_cost_forecast_values = TimeSeries.values(variable_cost_forecast) - parameter_container = _get_cost_function_parameter_container( - container, - CostFunctionParameter(), - component, - T(), - U(), - eltype(variable_cost_forecast_values), - ) + variable_cost = PSY.get_variable(component) + if variable_cost isa Nothing + error("ReserveDemandCurve $(component.name) does not have cost data.") + elseif typeof(variable_cost) <: PSY.TimeSeriesKey + error("Timeseries curve for ReserveDemandCurve $(component.name) is not supported yet.") + end + pwl_cost_expressions = - _add_pwl_term!(container, component, variable_cost_forecast_values, T(), U()) - jump_model = get_jump_model(container) + _add_pwl_term!(container, component, variable_cost, T(), U()) for t in time_steps - set_multiplier!( - parameter_container, - # Using 1.0 here since we want to reuse the existing code that adds the mulitpler - # of base power times the time delta. - 1.0, - component_name, - t, - ) - set_parameter!( - parameter_container, - jump_model, - variable_cost_forecast_values[t], - component_name, + add_to_expression!( + container, + ProductionCostExpression, + pwl_cost_expressions[t], + component, t, ) - add_to_objective_variant_expression!(container, pwl_cost_expressions[t]) + add_to_objective_invariant_expression!(container, pwl_cost_expressions[t]) end return end From 86f3f062d615890e930a1567ee3b826d30764881 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 17 Jun 2024 15:15:09 -0700 Subject: [PATCH 4/7] formatter --- src/devices_models/devices/common/add_to_expression.jl | 2 +- .../devices/common/objective_function/market_bid.jl | 2 -- src/services_models/reserves.jl | 7 +++++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/devices_models/devices/common/add_to_expression.jl b/src/devices_models/devices/common/add_to_expression.jl index 9fcd05a03c..00cb36f80f 100644 --- a/src/devices_models/devices/common/add_to_expression.jl +++ b/src/devices_models/devices/common/add_to_expression.jl @@ -1480,4 +1480,4 @@ function add_to_expression!( end return end -=# \ No newline at end of file +=# diff --git a/src/devices_models/devices/common/objective_function/market_bid.jl b/src/devices_models/devices/common/objective_function/market_bid.jl index e3625f98be..b93b057fcf 100644 --- a/src/devices_models/devices/common/objective_function/market_bid.jl +++ b/src/devices_models/devices/common/objective_function/market_bid.jl @@ -335,7 +335,6 @@ function _add_pwl_term!( return pwl_cost_expressions end - ################################################## ########## PWL for StepwiseCostReserve ########## ################################################## @@ -375,7 +374,6 @@ function _add_pwl_term!( return pwl_cost_expressions end - #= """ Add PWL cost terms for data coming from the MarketBidCost diff --git a/src/services_models/reserves.jl b/src/services_models/reserves.jl index 113492096c..af42f52310 100644 --- a/src/services_models/reserves.jl +++ b/src/services_models/reserves.jl @@ -310,7 +310,8 @@ function add_constraints!( ) reserve_variable = get_variable(container, ActivePowerReserveVariable(), SR, service_name) - requirement_variable = get_variable(container, ServiceRequirementVariable(), SR, service_name) + requirement_variable = + get_variable(container, ServiceRequirementVariable(), SR, service_name) jump_model = get_jump_model(container) for t in time_steps constraint[service_name, t] = JuMP.@constraint( @@ -517,7 +518,9 @@ function _add_variable_cost_to_objective!( if variable_cost isa Nothing error("ReserveDemandCurve $(component.name) does not have cost data.") elseif typeof(variable_cost) <: PSY.TimeSeriesKey - error("Timeseries curve for ReserveDemandCurve $(component.name) is not supported yet.") + error( + "Timeseries curve for ReserveDemandCurve $(component.name) is not supported yet.", + ) end pwl_cost_expressions = From 51a4685ea6b45a93b8196471dc2f3de2ae230e90 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 17 Jun 2024 16:39:09 -0700 Subject: [PATCH 5/7] update ambiguity for ORDC --- .../devices/common/objective_function/market_bid.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/devices_models/devices/common/objective_function/market_bid.jl b/src/devices_models/devices/common/objective_function/market_bid.jl index b93b057fcf..d46025e62f 100644 --- a/src/devices_models/devices/common/objective_function/market_bid.jl +++ b/src/devices_models/devices/common/objective_function/market_bid.jl @@ -95,7 +95,7 @@ function _add_pwl_constraint!( break_points::Vector{Float64}, sos_status::SOSStatusVariable, period::Int, -) where {T <: PSY.ReserveDemandCurve, U <: VariableType} +) where {T <: PSY.ReserveDemandCurve, U <: ServiceRequirementVariable} name = PSY.get_name(component) variables = get_variable(container, U(), T, name) const_container = lazy_container_addition!( From 68af0983cfbe5eec6f58bdf461b5976bc2830d20 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 17 Jun 2024 16:39:23 -0700 Subject: [PATCH 6/7] update test numbers after ORDC update --- test/test_network_constructors.jl | 2 +- test/test_services_constructor.jl | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/test/test_network_constructors.jl b/test/test_network_constructors.jl index 330bd6a419..c0dfd41a56 100644 --- a/test/test_network_constructors.jl +++ b/test/test_network_constructors.jl @@ -794,7 +794,7 @@ end PSI.ModelBuildStatus.BUILT @test solve!(ps_model) == PSI.RunStatus.SUCCESSFULLY_FINALIZED - moi_tests(ps_model, 576, 0, 552, 552, 360, false) + moi_tests(ps_model, 576, 0, 576, 576, 360, false) opt_container = PSI.get_optimization_container(ps_model) copper_plate_constraints = diff --git a/test/test_services_constructor.jl b/test/test_services_constructor.jl index 68d3911002..46d5800833 100644 --- a/test/test_services_constructor.jl +++ b/test/test_services_constructor.jl @@ -21,7 +21,7 @@ model = DecisionModel(template, c_sys5_uc) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 648, 0, 120, 216, 72, false) + moi_tests(model, 624, 0, 216, 216, 48, false) reserve_variables = [ :ActivePowerReserveVariable__VariableReserve__ReserveUp__Reserve1 :ActivePowerReserveVariable__ReserveDemandCurve__ReserveUp__ORDC1 @@ -99,7 +99,7 @@ end model = DecisionModel(template, c_sys5_uc; optimizer = cbc_optimizer) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 1008, 0, 480, 216, 192, true) + moi_tests(model, 984, 0, 576, 216, 168, true) end @testset "Test Reserves from Thermal Standard UC with NonSpinningReserve" begin @@ -137,7 +137,7 @@ end model = DecisionModel(template, c_sys5_re) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 360, 0, 72, 120, 72, false) + moi_tests(model, 336, 0, 168, 120, 48, false) end @testset "Test Reserves from Hydro" begin @@ -161,7 +161,7 @@ end model = DecisionModel(template, c_sys5_hyd) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 240, 0, 48, 96, 72, false) + moi_tests(model, 216, 0, 144, 96, 48, false) end @testset "Test Reserves from with slack variables" begin @@ -260,7 +260,7 @@ end model = DecisionModel(template, c_sys5_uc) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 648, 0, 120, 240, 72, false) + moi_tests(model, 624, 0, 216, 240, 48, false) end @testset "Test GroupReserve Errors" begin @@ -368,7 +368,7 @@ end model = DecisionModel(template, c_sys5_uc) @test build!(model; output_dir = mktempdir(; cleanup = true)) == PSI.ModelBuildStatus.BUILT - moi_tests(model, 648, 0, 384, 216, 72, false) + moi_tests(model, 624, 0, 480, 216, 48, false) reserve_variables = [ :ActivePowerReserveVariable__VariableReserve__ReserveUp__Reserve1 :ActivePowerReserveVariable__ReserveDemandCurve__ReserveUp__ORDC1 From 4cca4b8fa3ddd383f5c4faa8ece8f40cf641270c Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Mon, 24 Jun 2024 10:52:46 -0700 Subject: [PATCH 7/7] make suggested change --- .../devices/common/add_to_expression.jl | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/devices_models/devices/common/add_to_expression.jl b/src/devices_models/devices/common/add_to_expression.jl index 00cb36f80f..1e66935103 100644 --- a/src/devices_models/devices/common/add_to_expression.jl +++ b/src/devices_models/devices/common/add_to_expression.jl @@ -38,8 +38,15 @@ function add_expressions!( W <: AbstractReservesFormulation, } where {D <: PSY.Component} time_steps = get_time_steps(container) - names = [PSY.get_name(d) for d in devices] - add_expression_container!(container, T(), D, names, time_steps; meta = names[1]) + @assert length(devices) == 1 + add_expression_container!( + container, + T(), + D, + PSY.get_name.(devices), + time_steps; + meta = PSY.get_name(first(devices)), + ) return end