From c1463b976fea09361cb4388d5cb11ff54dece0cc Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:55:59 -0700 Subject: [PATCH 01/33] add served expressions --- src/core/expressions.jl | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/expressions.jl b/src/core/expressions.jl index 9b4b5570..01cd54e4 100644 --- a/src/core/expressions.jl +++ b/src/core/expressions.jl @@ -1,7 +1,11 @@ ### Component Abstract Total Reserve Expressions ### abstract type ComponentReserveExpressionType <: PSI.ExpressionType end +abstract type ComponentServedReserveExpressionType <: PSI.ExpressionType end abstract type ComponentReserveUpExpressionType <: ComponentReserveExpressionType end abstract type ComponentReserveDownExpressionType <: ComponentReserveExpressionType end +abstract type ComponentServedReserveUpExpressionType <: ComponentServedReserveExpressionType end +abstract type ComponentServedReserveDownExpressionType <: + ComponentServedReserveExpressionType end ### Component Total Reserve Expressions ### struct ThermalReserveUpExpression <: ComponentReserveUpExpressionType end @@ -13,6 +17,16 @@ struct ChargeReserveDownExpression <: ComponentReserveDownExpressionType end struct DischargeReserveUpExpression <: ComponentReserveUpExpressionType end struct DischargeReserveDownExpression <: ComponentReserveDownExpressionType end +### Component Served Reserve Expressions ### +struct ThermalServedReserveUpExpression <: ComponentServedReserveUpExpressionType end +struct ThermalServedReserveDownExpression <: ComponentServedReserveDownExpressionType end +struct RenewableServedReserveUpExpression <: ComponentServedReserveUpExpressionType end +struct RenewableServedReserveDownExpression <: ComponentServedReserveDownExpressionType end +struct ChargeServedReserveUpExpression <: ComponentServedReserveUpExpressionType end +struct ChargeServedReserveDownExpression <: ComponentServedReserveDownExpressionType end +struct DischargeServedReserveUpExpression <: ComponentServedReserveUpExpressionType end +struct DischargeServedReserveDownExpression <: ComponentServedReserveDownExpressionType end + ### Reserve Balance Expression ### struct ComponentReserveBalanceExpression <: PSI.ExpressionType end abstract type TotalReserveUpExpression <: PSI.ExpressionType end From 0336b744a53df95f09a63aa2acb1c2d5f2065775 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:56:16 -0700 Subject: [PATCH 02/33] add served expressions functions --- src/add_to_expression.jl | 130 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/src/add_to_expression.jl b/src/add_to_expression.jl index 9df24f1f..cc0c0463 100644 --- a/src/add_to_expression.jl +++ b/src/add_to_expression.jl @@ -238,6 +238,71 @@ function PSI.add_to_expression!( return end +# Component Served Reserve Up +function add_to_expression_componentservedreserveup!( + container::PSI.OptimizationContainer, + ::Type{T}, + ::Type{U}, + devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}}, + ::W, + time_steps::UnitRange{Int64}, +) where { + T <: ComponentServedReserveUpExpressionType, + U <: PSI.VariableType, + V <: PSY.HybridSystem, + W <: AbstractHybridFormulation, +} + expression = PSI.get_expression(container, T(), V) + for d in devices + name = PSY.get_name(d) + services = PSY.get_services(d) + for service in services + # TODO: This could be improved without requiring to read services for each component independently + if isa(service, PSY.Reserve{PSY.ReserveDown}) + continue + end + variable = + PSI.get_variable(container, U(), typeof(service), PSY.get_name(service)) + mult = PSI.get_variable_multiplier(U, d, W(), service) + deployed_frac = PSY.get_deployed_fraction(service) + for t in time_steps + PSI._add_to_jump_expression!( + expression[name, t], + variable[name, t], + mult * deployed_frac, + ) + end + end + end + return +end + +function PSI.add_to_expression!( + container::PSI.OptimizationContainer, + expression::Type{T}, + variable::Type{U}, + devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}}, + ::PSI.DeviceModel{V, W}, + ::PSI.NetworkModel{X}, +) where { + T <: ComponentServedReserveUpExpressionType, + U <: PSI.VariableType, + V <: PSY.HybridSystem, + W <: HybridDispatchWithReserves, + X <: PM.AbstractPowerModel, +} + time_steps = PSI.get_time_steps(container) + add_to_expression_componentservedreserveup!( + container, + expression, + variable, + devices, + W(), + time_steps, + ) + return +end + # Component Reserve Down function add_to_expression_componentreservedown!( container::PSI.OptimizationContainer, @@ -298,6 +363,71 @@ function PSI.add_to_expression!( return end +# Component Served Reserve Down +function add_to_expression_componentservedreservedown!( + container::PSI.OptimizationContainer, + ::Type{T}, + ::Type{U}, + devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}}, + ::W, + time_steps::UnitRange{Int64}, +) where { + T <: ComponentServedReserveDownExpressionType, + U <: PSI.VariableType, + V <: PSY.HybridSystem, + W <: AbstractHybridFormulation, +} + expression = PSI.get_expression(container, T(), V) + for d in devices + name = PSY.get_name(d) + services = PSY.get_services(d) + for service in services + # TODO: This could be improved without requiring to read services for each component independently + if isa(service, PSY.Reserve{PSY.ReserveUp}) + continue + end + variable = + PSI.get_variable(container, U(), typeof(service), PSY.get_name(service)) + mult = PSI.get_variable_multiplier(U, d, W(), service) + deployed_frac = PSY.get_deployed_fraction(service) + for t in time_steps + PSI._add_to_jump_expression!( + expression[name, t], + variable[name, t], + mult * deployed_frac, + ) + end + end + end + return +end + +function PSI.add_to_expression!( + container::PSI.OptimizationContainer, + expression::Type{T}, + variable::Type{U}, + devices::Union{Vector{V}, IS.FlattenIteratorWrapper{V}}, + ::PSI.DeviceModel{V, W}, + ::PSI.NetworkModel{X}, +) where { + T <: ComponentServedReserveDownExpressionType, + U <: PSI.VariableType, + V <: PSY.HybridSystem, + W <: HybridDispatchWithReserves, + X <: PM.AbstractPowerModel, +} + time_steps = PSI.get_time_steps(container) + add_to_expression_componentservedreservedown!( + container, + expression, + variable, + devices, + W(), + time_steps, + ) + return +end + # Add Reserve Variable for each component to the Reserve Balance function add_to_expression_componentreservebalance!( container::PSI.OptimizationContainer, From 87c340ecdd7db5a07fb02c3f926564b512bec408 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:56:44 -0700 Subject: [PATCH 03/33] add served expressions to device constructor --- src/hybrid_system_constructor.jl | 192 ++++++++++++++++++++++++++++--- 1 file changed, 178 insertions(+), 14 deletions(-) diff --git a/src/hybrid_system_constructor.jl b/src/hybrid_system_constructor.jl index a76f004b..1710c412 100644 --- a/src/hybrid_system_constructor.jl +++ b/src/hybrid_system_constructor.jl @@ -64,6 +64,22 @@ function PSI.construct_device!( PSI.add_variables!(container, PSI.EnergyVariable, _hybrids_with_storage, D()) PSI.add_variables!(container, BatteryStatus, _hybrids_with_storage, D()) + if PSI.get_attribute(model, "cycling") + PSI.add_variables!( + container, + CumulativeCyclingCharge, + _hybrids_with_storage, + D(), + ) + + PSI.add_variables!( + container, + CumulativeCyclingDischarge, + _hybrids_with_storage, + D(), + ) + end + if PSI.get_attribute(model, "energy_target") PSI.add_variables!( container, @@ -194,20 +210,22 @@ function PSI.construct_device!( model, network_model, ) - PSI.add_constraints!( - container, - CyclingCharge, - _hybrids_with_storage, - model, - network_model, - ) - PSI.add_constraints!( - container, - CyclingDischarge, - _hybrids_with_storage, - model, - network_model, - ) + if PSI.get_attribute(model, "cycling") + PSI.add_constraints!( + container, + CyclingCharge, + _hybrids_with_storage, + model, + network_model, + ) + PSI.add_constraints!( + container, + CyclingDischarge, + _hybrids_with_storage, + model, + network_model, + ) + end if PSI.get_attribute(model, "energy_target") PSI.add_constraints!( container, @@ -498,6 +516,43 @@ function PSI.construct_device!( model, network_model, ) + + # Add served reserve variables and expressions + PSI.lazy_container_addition!( + container, + ThermalServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_thermal), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + ThermalServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_thermal), + time_steps, + ) + + # add to expression thermal up served reserve + PSI.add_to_expression!( + container, + ThermalServedReserveUpExpression, + ThermalReserveVariable, + _hybrids_with_thermal, + model, + network_model, + ) + + # add to expression thermal down served reserve + PSI.add_to_expression!( + container, + ThermalServedReserveDownExpression, + ThermalReserveVariable, + _hybrids_with_thermal, + model, + network_model, + ) end end @@ -551,6 +606,44 @@ function PSI.construct_device!( model, network_model, ) + + # Create renewable served up reserves + PSI.lazy_container_addition!( + container, + RenewableServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_renewable), + time_steps, + ) + + # Create renewable served down reserves + PSI.lazy_container_addition!( + container, + RenewableServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_renewable), + time_steps, + ) + + # add to expression renewable up reserve + PSI.add_to_expression!( + container, + RenewableServedReserveUpExpression, + RenewableReserveVariable, + _hybrids_with_renewable, + model, + network_model, + ) + + # add to expression renewable down reserve + PSI.add_to_expression!( + container, + RenewableServedReserveDownExpression, + RenewableReserveVariable, + _hybrids_with_renewable, + model, + network_model, + ) end end @@ -696,6 +789,77 @@ function PSI.construct_device!( model, network_model, ) + + ## Served ## + # Add served reserve variables and expressions for charging unit + PSI.lazy_container_addition!( + container, + ChargeServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + ChargeServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.add_to_expression!( + container, + ChargeServedReserveUpExpression, + ChargingReserveVariable, + _hybrids_with_storage, + model, + network_model, + ) + + PSI.add_to_expression!( + container, + ChargeServedReserveDownExpression, + ChargingReserveVariable, + _hybrids_with_storage, + model, + network_model, + ) + + # Add served reserve variables and expressions for discharging unit + PSI.lazy_container_addition!( + container, + DischargeServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + DischargeServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.add_to_expression!( + container, + DischargeServedReserveUpExpression, + DischargingReserveVariable, + _hybrids_with_storage, + model, + network_model, + ) + + PSI.add_to_expression!( + container, + DischargeServedReserveDownExpression, + DischargingReserveVariable, + _hybrids_with_renewable, + model, + network_model, + ) end PSI.initial_conditions!(container, _hybrids_with_storage, D()) end From 3647543578fb9461f02a7f7027a7fc370a163b5f Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:57:01 -0700 Subject: [PATCH 04/33] add cycling constraints using reserves --- src/add_constraints.jl | 250 ++++++++++++++++++++++++----------------- 1 file changed, 148 insertions(+), 102 deletions(-) diff --git a/src/add_constraints.jl b/src/add_constraints.jl index 1409cff0..493fabdf 100644 --- a/src/add_constraints.jl +++ b/src/add_constraints.jl @@ -914,6 +914,58 @@ function _add_constraints_cyclingcharge!( return end +function _add_constraints_cyclingcharge_withreserves!( + container::PSI.OptimizationContainer, + T::Type{<:CyclingCharge}, + devices::U, + ::W, +) where { + U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + W <: AbstractHybridFormulation, +} where {D <: PSY.HybridSystem} + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + charge_var = PSI.get_variable(container, BatteryCharge(), D) + ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), D) + ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), D) + con_cycling_ch = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * + fraction_of_hour * + sum( + charge_var[ci_name, :] + ch_served_reg_dn[ci_name, :] - + ch_served_reg_up[ci_name, :], + ) <= param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * + fraction_of_hour * + sum( + charge_var[ci_name, :] + ch_served_reg_dn[ci_name, :] - + ch_served_reg_up[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + end + end + return +end + function _add_constraints_cyclingcharge_decisionmodel!( container::PSI.OptimizationContainer, T::Type{<:CyclingCharge}, @@ -957,7 +1009,12 @@ function PSI.add_constraints!( W <: AbstractHybridFormulation, } where {D <: PSY.HybridSystem} if PSI.get_attribute(model, "cycling") - _add_constraints_cyclingcharge!(container, T, devices, W()) + has_services = PSI.has_service_model(model) + if !has_services + _add_constraints_cyclingcharge!(container, T, devices, W()) + else + _add_constraints_cyclingcharge_withreserves!(container, T, devices, W()) + end end return end @@ -1007,6 +1064,60 @@ function _add_constraints_cyclingdischarge!( return end +function _add_constraints_cyclingdischarge_withreserves!( + container::PSI.OptimizationContainer, + T::Type{<:CyclingDischarge}, + devices::U, + ::W, +) where { + U <: Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + W <: AbstractHybridFormulation, +} where {D <: PSY.HybridSystem} + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + discharge_var = PSI.get_variable(container, BatteryDischarge(), D) + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), D) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), D) + con_cycling_ds = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum( + discharge_var[ci_name, :] + ds_served_reg_up[ci_name, :] - + ds_served_reg_dn[ci_name, :], + ) <= param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum( + discharge_var[ci_name, :] + ds_served_reg_up[ci_name, :] - + ds_served_reg_dn[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + end + end + return +end + function _add_constraints_cyclingdischarge_decisionmodel!( container::PSI.OptimizationContainer, T::Type{<:CyclingDischarge}, @@ -1050,7 +1161,12 @@ function PSI.add_constraints!( W <: AbstractHybridFormulation, } where {D <: PSY.HybridSystem} if PSI.get_attribute(model, "cycling") - _add_constraints_cyclingdischarge!(container, T, devices, W()) + has_services = PSI.has_service_model(model) + if !has_services + _add_constraints_cyclingdischarge!(container, T, devices, W()) + else + _add_constraints_cyclingdischarge_withreserves!(container, T, devices, W()) + end end return end @@ -1105,6 +1221,8 @@ function PSI.add_constraints!( names = [PSY.get_name(x) for x in devices] time_steps = PSI.get_time_steps(container) reg_var = PSI.get_variable(container, ChargeRegularizationVariable(), V) + ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), V) + ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), V) powerin_var = PSI.get_variable(container, BatteryCharge(), V) has_services = PSI.has_service_model(model) @@ -1133,68 +1251,30 @@ function PSI.add_constraints!( end for device in devices ci_name = PSY.get_name(device) - deployed_r_up_ch = Set() - deployed_r_dn_ch = Set() - for service in services - service_name = PSY.get_name(service) - if typeof(service) <: PSY.VariableReserve{PSY.ReserveUp} - res_ch = PSI.get_variable( - container, - ChargingReserveVariable(), - typeof(service), - service_name, - ) - push!( - deployed_r_up_ch, - PSY.get_deployed_fraction(service) * res_ch[ci_name, :], - ) - elseif typeof(service) <: PSY.VariableReserve{PSY.ReserveDown} - res_ch = PSI.get_variable( - container, - ChargingReserveVariable(), - typeof(service), - service_name, - ) - push!( - deployed_r_dn_ch, - PSY.get_deployed_fraction(service) * res_ch[ci_name, :], - ) - end - end constraint_ub[ci_name, 1] = JuMP.@constraint(PSI.get_jump_model(container), reg_var[ci_name, 1] == 0) constraint_lb[ci_name, 1] = JuMP.@constraint(PSI.get_jump_model(container), reg_var[ci_name, 1] == 0) for t in time_steps[2:end] - total_reg_up_now = JuMP.AffExpr() - total_reg_dn_now = JuMP.AffExpr() - total_reg_up_before = JuMP.AffExpr() - total_reg_dn_before = JuMP.AffExpr() - for rup in deployed_r_up_ch - JuMP.add_to_expression!(total_reg_up_now, rup[t]) - JuMP.add_to_expression!(total_reg_up_before, rup[t - 1]) - end - for rdn in deployed_r_dn_ch - JuMP.add_to_expression!(total_reg_dn_now, rdn[t]) - JuMP.add_to_expression!(total_reg_dn_before, rdn[t - 1]) - end constraint_ub[ci_name, t] = JuMP.@constraint( PSI.get_jump_model(container), ( - powerin_var[ci_name, t - 1] - total_reg_up_before + - total_reg_dn_before - ) - - (powerin_var[ci_name, t] - total_reg_up_now + total_reg_dn_now) <= - reg_var[ci_name, t] + powerin_var[ci_name, t - 1] - ch_served_reg_up[ci_name, t - 1] + + ch_served_reg_dn[ci_name, t - 1] + ) - ( + powerin_var[ci_name, t] - ch_served_reg_up[ci_name, t] + + ch_served_reg_dn[ci_name, t] + ) <= reg_var[ci_name, t] ) constraint_lb[ci_name, t] = JuMP.@constraint( PSI.get_jump_model(container), ( - powerin_var[ci_name, t - 1] - total_reg_up_before + - total_reg_dn_before - ) - - (powerin_var[ci_name, t] - total_reg_up_now + total_reg_dn_now) >= - -reg_var[ci_name, t] + powerin_var[ci_name, t - 1] - ch_served_reg_up[ci_name, t - 1] + + ch_served_reg_dn[ci_name, t - 1] + ) - ( + powerin_var[ci_name, t] - ch_served_reg_up[ci_name, t] + + ch_served_reg_dn[ci_name, t] + ) >= -reg_var[ci_name, t] ) end end @@ -1237,6 +1317,10 @@ function PSI.add_constraints!( time_steps = PSI.get_time_steps(container) reg_var = PSI.get_variable(container, DischargeRegularizationVariable(), V) powerout_var = PSI.get_variable(container, BatteryDischarge(), V) + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), V) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), V) has_services = PSI.has_service_model(model) constraint_ub = PSI.add_constraints_container!( @@ -1264,68 +1348,30 @@ function PSI.add_constraints!( end for device in devices ci_name = PSY.get_name(device) - deployed_r_up_ds = Set() - deployed_r_dn_ds = Set() - for service in services - service_name = PSY.get_name(service) - if typeof(service) <: PSY.VariableReserve{PSY.ReserveUp} - res_ds = PSI.get_variable( - container, - DischargingReserveVariable(), - typeof(service), - service_name, - ) - push!( - deployed_r_up_ds, - PSY.get_deployed_fraction(service) * res_ds[ci_name, :], - ) - elseif typeof(service) <: PSY.VariableReserve{PSY.ReserveDown} - res_ds = PSI.get_variable( - container, - DischargingReserveVariable(), - typeof(service), - service_name, - ) - push!( - deployed_r_dn_ds, - PSY.get_deployed_fraction(service) * res_ds[ci_name, :], - ) - end - end constraint_ub[ci_name, 1] = JuMP.@constraint(PSI.get_jump_model(container), reg_var[ci_name, 1] == 0) constraint_lb[ci_name, 1] = JuMP.@constraint(PSI.get_jump_model(container), reg_var[ci_name, 1] == 0) for t in time_steps[2:end] - total_reg_up_now = JuMP.AffExpr() - total_reg_dn_now = JuMP.AffExpr() - total_reg_up_before = JuMP.AffExpr() - total_reg_dn_before = JuMP.AffExpr() - for rup in deployed_r_up_ds - JuMP.add_to_expression!(total_reg_up_now, rup[t]) - JuMP.add_to_expression!(total_reg_up_before, rup[t - 1]) - end - for rdn in deployed_r_dn_ds - JuMP.add_to_expression!(total_reg_dn_now, rdn[t]) - JuMP.add_to_expression!(total_reg_dn_before, rdn[t - 1]) - end constraint_ub[ci_name, t] = JuMP.@constraint( PSI.get_jump_model(container), ( - powerout_var[ci_name, t - 1] + total_reg_up_before - - total_reg_dn_before - ) - - (powerout_var[ci_name, t] + total_reg_up_now - total_reg_dn_now) <= - reg_var[ci_name, t] + powerout_var[ci_name, t - 1] + ds_served_reg_up[ci_name, t - 1] - + ds_served_reg_dn[ci_name, t - 1] + ) - ( + powerout_var[ci_name, t] + ds_served_reg_up[ci_name, t] - + ds_served_reg_dn[ci_name, t] + ) <= reg_var[ci_name, t] ) constraint_lb[ci_name, t] = JuMP.@constraint( PSI.get_jump_model(container), ( - powerout_var[ci_name, t - 1] + total_reg_up_before - - total_reg_dn_before - ) - - (powerout_var[ci_name, t] + total_reg_up_now - total_reg_dn_now) >= - -reg_var[ci_name, t] + powerout_var[ci_name, t - 1] + ds_served_reg_up[ci_name, t - 1] - + ds_served_reg_dn[ci_name, t - 1] + ) - ( + powerout_var[ci_name, t] + ds_served_reg_up[ci_name, t] - + ds_served_reg_dn[ci_name, t] + ) >= -reg_var[ci_name, t] ) end end From 5d8dd8aa732c7e3f3bb256402fab2fc943a094da Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:57:14 -0700 Subject: [PATCH 05/33] add aux variables for cycling using expressions --- src/add_aux_variables.jl | 54 +++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 2d9a90f3..75e4fb7c 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -9,16 +9,30 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR charge_var = PSI.get_variable(container, BatteryCharge(), T) + ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), T) + ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), T) aux_variable_container = PSI.get_aux_variable(container, CumulativeCyclingCharge(), T) - for d in devices, t in time_steps + for d in devices name = PSY.get_name(d) storage = PSY.get_storage(d) efficiency = PSY.get_efficiency(storage) - #TODO: add served fraction of reserves - aux_variable_container[name, t] = - efficiency.in * - fraction_of_hour * - sum(PSI.jump_value(charge_var[name, k]) for k in 1:t) + for t in time_steps + if !PSY.has_service(d, PSY.VariableReserve) + aux_variable_container[name, t] = + efficiency.in * + fraction_of_hour * + sum(PSI.jump_value(charge_var[name, k]) for k in 1:t) + else + aux_variable_container[name, t] = + efficiency.in * + fraction_of_hour * + (sum( + PSI.jump_value(charge_var[name, k]) + + PSI.jump_value(ch_served_reg_dn[name, k]) - + PSI.jump_value(ch_served_reg_up[name, k]) for k in 1:t + )) + end + end end return @@ -35,17 +49,33 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR discharge_var = PSI.get_variable(container, BatteryDischarge(), T) + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), T) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), T) aux_variable_container = PSI.get_aux_variable(container, CumulativeCyclingDischarge(), T) - for d in devices, t in time_steps + for d in devices name = PSY.get_name(d) storage = PSY.get_storage(d) efficiency = PSY.get_efficiency(storage) - #TODO: add served fraction of reserves - aux_variable_container[name, t] = - (1.0 / efficiency.out) * - fraction_of_hour * - sum(PSI.jump_value(discharge_var[name, k]) for k in 1:t) + for t in time_steps + if !PSY.has_service(d, PSY.VariableReserve) + aux_variable_container[name, t] = + (1.0 / efficiency.out) * + fraction_of_hour * + sum(PSI.jump_value(discharge_var[name, k]) for k in 1:t) + else + aux_variable_container[name, t] = + (1.0 / efficiency.out) * + fraction_of_hour * + (sum( + PSI.jump_value(discharge_var[name, k]) + + PSI.jump_value(ds_served_reg_up[name, k]) - + PSI.jump_value(ds_served_reg_dn[name, k]) for k in 1:t + )) + end + end end return From f9b4adeca6385577d15afe1b4628606228be73f7 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:57:26 -0700 Subject: [PATCH 06/33] update ff --- src/feedforwards.jl | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/src/feedforwards.jl b/src/feedforwards.jl index 9e7c1f6e..faa3a79a 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -3,7 +3,7 @@ struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward affected_values::Vector{<:PSI.OptimizationContainerKey} target_period::Int penalty_cost::Float64 - function CyclingLimitFeedforward(; + function CyclingChargeLimitFeedforward(; component_type::Type{<:PSY.Component}, source::Type{T}, affected_values::Vector{DataType}, @@ -18,7 +18,7 @@ struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward PSI.get_optimization_container_key(v(), component_type, meta) else error( - "ReservoirTargetFeedforward is only compatible with VariableType or ParamterType affected values", + "CyclingChargeLimitFeedforward is only compatible with VariableType or ParamterType affected values", ) end end @@ -35,6 +35,43 @@ PSI.get_default_parameter_type(::CyclingChargeLimitFeedforward, _) = PSI.get_optimization_container_key(ff::CyclingChargeLimitFeedforward) = ff.optimization_container_key +struct CyclingDischargeLimitFeedforward <: PSI.AbstractAffectFeedforward + optimization_container_key::PSI.OptimizationContainerKey + affected_values::Vector{<:PSI.OptimizationContainerKey} + target_period::Int + penalty_cost::Float64 + function CyclingDischargeLimitFeedforward(; + component_type::Type{<:PSY.Component}, + source::Type{T}, + affected_values::Vector{DataType}, + target_period::Int, + penalty_cost::Float64, + meta=PSI.CONTAINER_KEY_EMPTY_META, + ) where {T} + values_vector = Vector{PSI.VariableKey}(undef, length(affected_values)) + for (ix, v) in enumerate(affected_values) + if v <: PSI.VariableType + values_vector[ix] = + PSI.get_optimization_container_key(v(), component_type, meta) + else + error( + "CyclingDischargeLimitFeedforward is only compatible with VariableType or ParamterType affected values", + ) + end + end + new( + PSI.get_optimization_container_key(T(), component_type, meta), + values_vector, + penalty_cost, + ) + end +end + +PSI.get_default_parameter_type(::CyclingDischargeLimitFeedforward, _) = + CyclingDischargeLimitParameter +PSI.get_optimization_container_key(ff::CyclingDischargeLimitFeedforward) = + ff.optimization_container_key + function PSI.update_parameter_values!( model::PSI.DecisionModel, key::PSI.ParameterKey{T, U}, From d3e9fe8f19600d6bf423f464c9d15abd53a2ff27 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Wed, 1 May 2024 12:57:33 -0700 Subject: [PATCH 07/33] update centralized sim for testing --- scripts/centralized_paper/centralized_sim.jl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/centralized_paper/centralized_sim.jl b/scripts/centralized_paper/centralized_sim.jl index c3b66c38..ba6d4da1 100644 --- a/scripts/centralized_paper/centralized_sim.jl +++ b/scripts/centralized_paper/centralized_sim.jl @@ -126,7 +126,7 @@ set_device_model!( ##### Run DCP Simulation ###### ############################### -mipgap = 2.0e-2 +mipgap = 3.0e-2 model = DecisionModel( template_uc_copperplate, @@ -148,6 +148,9 @@ PSI.solve!(model) res = ProblemResults(model) +cyc_charge = read_aux_variable(res, "CumulativeCyclingCharge__HybridSystem") +cyc_discharge = read_aux_variable(res, "CumulativeCyclingDischarge__HybridSystem") + techs = ["STEAM", "CT", "CC", "WIND", "NUCLEAR", "PV", "RTPV", "HYDRO", "HYBRID"] tot_dict = Dict() for t in techs @@ -641,3 +644,15 @@ aux = cons[PowerSimulations.ConstraintKey{ for (k, v) in obj_jump.terms println(k, v) end + +exprs = model.internal.container.expressions +for k in keys(exprs) + println(k) +end + +exprs[PowerSimulations.ExpressionKey{ + HybridSystemsSimulations.DischargeServedReserveUpExpression, + HybridSystem, +}( + "", +)] From b302de4dc47eb47adfa618db01720a00b070cee8 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:00:03 -0700 Subject: [PATCH 08/33] update centralized sim --- scripts/centralized_paper/centralized_sim.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/centralized_paper/centralized_sim.jl b/scripts/centralized_paper/centralized_sim.jl index ba6d4da1..e2490c94 100644 --- a/scripts/centralized_paper/centralized_sim.jl +++ b/scripts/centralized_paper/centralized_sim.jl @@ -70,7 +70,7 @@ served_fraction_map = Dict( "Reg_Down" => 0.3, "Flex_Down" => 0.1, ) - +#= for sys in [sys_rts_da] services = get_components(VariableReserve, sys) hy_sys = first(get_components(HybridSystem, sys)) @@ -89,7 +89,7 @@ for sys in [sys_rts_da] end end end - +=# ############################### ###### Create Templates ####### ############################### @@ -110,8 +110,8 @@ set_device_model!( template_uc_copperplate, DeviceModel( PSY.HybridSystem, - HybridDispatchWithReserves; - #HybridEnergyOnlyDispatch; + #HybridDispatchWithReserves; + HybridEnergyOnlyDispatch; attributes=Dict{String, Any}( "reservation" => true, "storage_reservation" => true, From 156cf2fa5dc7bb4e54ac830067e0f513225fe955 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:00:17 -0700 Subject: [PATCH 09/33] remove ptdf from scripts --- scripts/cooptimizer_pipeline/step1_prices.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/cooptimizer_pipeline/step1_prices.jl b/scripts/cooptimizer_pipeline/step1_prices.jl index 6d8967bf..60c7b0e5 100644 --- a/scripts/cooptimizer_pipeline/step1_prices.jl +++ b/scripts/cooptimizer_pipeline/step1_prices.jl @@ -108,16 +108,16 @@ template_uc_copperplate = get_uc_copperplate_template(sys_rts_da) template_ed_copperplate = get_ed_copperplate_template(sys_rts_rt) # PTDF Bounded -template_uc_ptdf = get_uc_ptdf_template(sys_rts_da) -template_ed_ptdf = get_ed_ptdf_template(sys_rts_rt) +#template_uc_ptdf = get_uc_ptdf_template(sys_rts_da) +#template_ed_ptdf = get_ed_ptdf_template(sys_rts_rt) # PTDF Unbounded -template_uc_unbounded_ptdf = get_uc_ptdf_unbounded_template(sys_rts_da) -template_ed_unbounded_ptdf = get_ed_ptdf_unbounded_template(sys_rts_rt) +#template_uc_unbounded_ptdf = get_uc_ptdf_unbounded_template(sys_rts_da) +#template_ed_unbounded_ptdf = get_ed_ptdf_unbounded_template(sys_rts_rt) # DCP -template_uc_dcp = get_uc_dcp_template() -template_ed_dcp = get_ed_dcp_template() +#template_uc_dcp = get_uc_dcp_template() +#template_ed_dcp = get_ed_dcp_template() set_device_model!( template_uc_copperplate, From 52b2711768e80653aa3f99599779235d9490842c Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:03:19 -0700 Subject: [PATCH 10/33] update cooptimizer with feedforward --- .../cooptimizer_pipeline/step2_simulation.jl | 176 +++++++++++++++--- .../cooptimizer_decision_model.jl | 163 +++++++++++++++- 2 files changed, 307 insertions(+), 32 deletions(-) diff --git a/scripts/cooptimizer_pipeline/step2_simulation.jl b/scripts/cooptimizer_pipeline/step2_simulation.jl index f6b99335..54577359 100644 --- a/scripts/cooptimizer_pipeline/step2_simulation.jl +++ b/scripts/cooptimizer_pipeline/step2_simulation.jl @@ -2,6 +2,7 @@ include(joinpath(@__DIR__, "step1_prices.jl")) ########################################### ### Systems for DA and Merchant DA Bids ### ########################################### +# JuMP._TERM_LIMIT_FOR_PRINTING[] = 10000 sys_rts_da = build_system(PSISystems, "modified_RTS_GMLC_DA_sys_noForecast") sys_rts_merchant_da = build_system(PSISystems, "modified_RTS_GMLC_RT_sys_noForecast") sys_rts_merchant_rt = build_system(PSISystems, "modified_RTS_GMLC_RT_sys_noForecast") @@ -42,7 +43,9 @@ for sys in [sys_rts_da, sys_rts_merchant_da, sys_rts_merchant_rt] for service in services serv_name = get_name(service) serv_ext = get_ext(service) - serv_ext["served_fraction"] = served_fraction_map[serv_name] + serv_frac = served_fraction_map[serv_name] + serv_ext["served_fraction"] = serv_frac + set_deployed_fraction!(service, serv_frac) if contains(serv_name, "Spin_Up_R1") | contains(serv_name, "Spin_Up_R2") | contains(serv_name, "Flex") @@ -84,6 +87,22 @@ PSY.set_ext!(hy_sys_rt, sys_rts_merchant_rt.internal.ext) template_uc_copperplate = get_uc_copperplate_template(sys_rts_da) +set_device_model!( + template_uc_copperplate, + DeviceModel( + PSY.HybridSystem, + HybridDispatchWithReserves; + #HybridEnergyOnlyDispatch; + attributes=Dict{String, Any}( + "reservation" => true, + "storage_reservation" => true, + "energy_target" => true, + "cycling" => true, + "regularization" => true, + ), + ), +) + decision_optimizer_DA = DecisionModel( MerchantHybridCooptimizerCase, template_uc_copperplate, @@ -95,7 +114,7 @@ decision_optimizer_DA = DecisionModel( store_variable_names=true; name="MerchantHybridCooptimizer_DA", ) - +#build!(decision_optimizer_DA, output_dir = mktempdir()) #= build!(decision_optimizer_DA, output_dir = mktempdir()) solve!(decision_optimizer_DA) @@ -104,11 +123,21 @@ res = ProblemResults(decision_optimizer_DA) totbid_dn = res.aux_variable_values[PSI.AuxVarKey{HSS.TotalBidReserve, VariableReserve{ReserveDown}}("Reg_Down")] regdn_out = res.variable_values[PSI.VariableKey{BidReserveVariableOut, VariableReserve{ReserveDown}}("Reg_Down")] regdn_in = res.variable_values[PSI.VariableKey{BidReserveVariableIn, VariableReserve{ReserveDown}}("Reg_Down")] + +exprs = decision_optimizer_DA.internal.container.expressions +for k in keys(exprs) + println(k) +end + +exprs[PowerSimulations.ExpressionKey{HybridSystemsSimulations.DischargeServedReserveDownExpression, HybridSystem}("")] + =# # Set Hybrid in UC as FixedDA +template_uc_copperplate_UC = deepcopy(template_uc_copperplate) + set_device_model!( - template_uc_copperplate, + template_uc_copperplate_UC, DeviceModel( PSY.HybridSystem, HybridFixedDA; @@ -117,15 +146,15 @@ set_device_model!( ) set_service_model!( - template_uc_copperplate, + template_uc_copperplate_UC, ServiceModel(VariableReserve{ReserveUp}, RangeReserve, use_slacks=false), ) set_service_model!( - template_uc_copperplate, + template_uc_copperplate_UC, ServiceModel(VariableReserve{ReserveDown}, RangeReserve, use_slacks=false), ) -template_ed_copperplate = deepcopy(template_uc_copperplate) +template_ed_copperplate = deepcopy(template_uc_copperplate_UC) set_device_model!(template_ed_copperplate, ThermalStandard, ThermalBasicDispatch) set_device_model!(template_ed_copperplate, HydroDispatch, FixedOutput) #set_device_model!(template_ed, HydroEnergyReservoir, FixedOutput) @@ -137,15 +166,25 @@ end ####### Systems for RT Bids and Realized ED ########## ###################################################### rt_template = ProblemTemplate(CopperPlatePowerModel) + set_device_model!( rt_template, DeviceModel( PSY.HybridSystem, - HybridFixedDA; - attributes=Dict{String, Any}("cycling" => false), + HybridDispatchWithReserves; + attributes=Dict{String, Any}( + "reservation" => true, + "storage_reservation" => true, + "energy_target" => true, + "cycling" => false, + "regularization" => true, + ), ), ) +set_service_model!(rt_template, ServiceModel(VariableReserve{ReserveUp}, RangeReserve)) +set_service_model!(rt_template, ServiceModel(VariableReserve{ReserveDown}, RangeReserve)) + decision_optimizer_RT = DecisionModel( MerchantHybridCooptimizerCase, rt_template, @@ -164,7 +203,7 @@ models = SimulationModels( decision_models=[ decision_optimizer_DA, DecisionModel( - template_uc_copperplate, + template_uc_copperplate_UC, sys_rts_da; name="UC", optimizer=optimizer_with_attributes(Xpress.Optimizer, "MIPRELSTOP" => mipgap), @@ -208,11 +247,11 @@ sequence = SimulationSequence( source=EnergyDABidIn, affected_values=[ActivePowerInVariable], ), - FixValueFeedforward( - component_type=component_type = HybridSystem, - source=TotalReserve, - affected_values=[TotalReserve], - ), + #FixValueFeedforward( + # component_type=component_type = HybridSystem, + # source=TotalReserve, + # affected_values=[TotalReserve], + #), ], # This FF configuration allows the Hybrid to re-assign reserves internally # But it can't increase the reserve offer to the ED @@ -227,11 +266,26 @@ sequence = SimulationSequence( source=EnergyDABidIn, affected_values=[EnergyDABidIn], ), - FixValueFeedforward( + CyclingChargeLimitFeedforward( component_type=PSY.HybridSystem, - source=TotalReserve, - affected_values=[TotalReserve], + source=HSS.CumulativeCyclingCharge, + affected_values=[HSS.CyclingChargeLimitParameter], + target_period=1, + penalty_cost=0.0, ), + CyclingDischargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CumulativeCyclingDischarge, + affected_values=[HSS.CyclingDischargeLimitParameter], + target_period=1, + penalty_cost=0.0, + ), + #FixValueFeedforward( + # component_type=PSY.HybridSystem, + # source=TotalReserve, + # affected_values=[TotalReserve], + #), + ], "ED" => [ SemiContinuousFeedforward( @@ -261,11 +315,11 @@ sequence = SimulationSequence( affected_values=[ActivePowerReserveVariable], add_slacks=true, ), - FixValueFeedforward( - component_type=HybridSystem, - source=TotalReserve, - affected_values=[TotalReserve], - ), + #FixValueFeedforward( + # component_type=HybridSystem, + # source=TotalReserve, + # affected_values=[TotalReserve], + #), ], ), ini_cond_chronology=InterProblemChronology(), @@ -329,10 +383,62 @@ results_ed = get_decision_problem_results(results, "ED") p_soc_da = read_realized_variable(result_merch_DA, "EnergyVariable__HybridSystem") p_soc_rt = read_realized_variable(result_merch_RT, "EnergyVariable__HybridSystem") -p_tot_reserve = read_variable(result_merch_DA, "TotalReserve__HybridSystem") +#p_tot_reserve = read_variable(result_merch_DA, "TotalReserve__HybridSystem") + +day = DateTime("2020-10-03T00:00:00") + +aux_var_cycling = + read_aux_variable(result_merch_DA, "CumulativeCyclingCharge__HybridSystem") +first_day_aux_var = aux_var_cycling[day] + +charge_var = read_variable(result_merch_DA, "BatteryCharge__HybridSystem") +first_day_charge = charge_var[day] +dates = first_day_charge[!, 1] + +reg_up_charge_var = read_variable( + result_merch_DA, + "ChargingReserveVariable__VariableReserve__ReserveUp__Reg_Up", +) +first_day_regup_charge = reg_up_charge_var[day] + +reg_dn_charge_var = read_variable( + result_merch_DA, + "ChargingReserveVariable__VariableReserve__ReserveDown__Reg_Down", +) +first_day_regdn_charge = reg_dn_charge_var[day] + +dev_cumulative = similar(first_day_charge[!, 2]) + +efficiency = hy_sys_da.storage.efficiency +fraction_of_hour = 1 / 12 + +for ix in 1:length(dev_cumulative) + dev_cumulative[ix] = + efficiency.in * + fraction_of_hour * + sum( + first_day_charge[!, 2][k] + 0.3 * first_day_regdn_charge[!, 2][k] - + 0.3 * first_day_regup_charge[!, 2][k] for k in 1:ix + ) +end + +plot([ + scatter(x=dates, y=first_day_charge[!, 2], name="Charge Var"), + scatter(x=dates, y=first_day_aux_var[!, 2], name="Cumulative Aux Var Cycling"), + scatter(x=dates, y=first_day_regup_charge[!, 2], name="Reg Up Charge"), + scatter(x=dates, y=first_day_regdn_charge[!, 2], name="Reg Dn Charge"), + scatter( + x=dates, + y=first_day_charge[!, 2] + 0.3 * first_day_regdn_charge[!, 2] - + 0.3 * first_day_regup_charge[!, 2], + name="Effective Charge Var", + ), +]) plot(p_soc_da[!, 2]) +par = read_parameter(result_merch_DA, "CyclingDischargeLimitParameter__HybridSystem") + ## Prices Comparison prices_uc_centralized = prices_uc_dcp prices_ed_centralized = prices_ed_dcp @@ -625,3 +731,27 @@ p2 = plot([ scatter(x=dates_ed, y=-slackup_rdn, line_shape="hv", name="- SlackUp RegDown"), scatter(x=dates_ed, y=slackdn_rdn, line_shape="hv", name="SlackDown RegDown"), ]) + +# JuMP._TERM_LIMIT_FOR_PRINTING[] = 10000 +JuMP._TERM_LIMIT_FOR_PRINTING[] = 60 + +cons = decision_optimizer_DA.internal.container.constraints +for k in keys(cons) + println(k) +end + +cons[PowerSimulations.ConstraintKey{HybridSystemsSimulations.CyclingDischarge, HybridSystem}( + "", +)] + +exprs = decision_optimizer_DA.internal.container.expressions +for k in keys(exprs) + println(k) +end + +exprs[PowerSimulations.ExpressionKey{ + HybridSystemsSimulations.DischargeServedReserveUpExpression, + HybridSystem, +}( + "", +)] diff --git a/src/decision_models/cooptimizer_decision_model.jl b/src/decision_models/cooptimizer_decision_model.jl index c7c9b226..0dc322ba 100644 --- a/src/decision_models/cooptimizer_decision_model.jl +++ b/src/decision_models/cooptimizer_decision_model.jl @@ -65,6 +65,8 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim end device_model = PSI.get_model(PSI.get_template(decision_model), PSY.HybridSystem) + device_formulation = PSI.get_formulation(device_model) + network_model = PSI.get_network_model(PSI.get_template(decision_model)) ############################### ######## Variables ############ @@ -316,6 +318,42 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim MerchantModelWithReserves(), time_steps, ) + + # Create served renewable total up reserves + PSI.lazy_container_addition!( + container, + RenewableServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_renewable), + time_steps, + ) + + # Create served renewable total down reserves + PSI.lazy_container_addition!( + container, + RenewableServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_renewable), + time_steps, + ) + + add_to_expression_componentservedreserveup!( + container, + RenewableServedReserveUpExpression, + RenewableReserveVariable, + _hybrids_with_renewable, + MerchantModelWithReserves(), + time_steps, + ) + + add_to_expression_componentservedreservedown!( + container, + RenewableServedReserveDownExpression, + RenewableReserveVariable, + _hybrids_with_renewable, + MerchantModelWithReserves(), + time_steps, + ) end ## Load Variables and Expressions ## @@ -345,6 +383,8 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim DischargingReserveVariable, EnergyBatteryChargeBid, EnergyBatteryDischargeBid, + CumulativeCyclingCharge, + CumulativeCyclingDischarge, ] PSI.add_variables!( container, @@ -395,6 +435,41 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim time_steps, ) + # Add served reserve expressions for charging unit + PSI.lazy_container_addition!( + container, + ChargeServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + ChargeServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + add_to_expression_componentservedreserveup!( + container, + ChargeServedReserveUpExpression, + ChargingReserveVariable, + _hybrids_with_storage, + device_formulation(), + time_steps, + ) + + add_to_expression_componentservedreservedown!( + container, + ChargeServedReserveDownExpression, + ChargingReserveVariable, + _hybrids_with_storage, + device_formulation(), + time_steps, + ) + # Add reserve expressions for discharging unit PSI.lazy_container_addition!( container, @@ -429,6 +504,41 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim MerchantModelWithReserves(), time_steps, ) + + # Add served reserve expressions for discharging unit + PSI.lazy_container_addition!( + container, + DischargeServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + DischargeServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_storage), + time_steps, + ) + + add_to_expression_componentservedreserveup!( + container, + DischargeServedReserveUpExpression, + DischargingReserveVariable, + _hybrids_with_storage, + device_formulation(), + time_steps, + ) + add_to_expression_componentservedreservedown!( + container, + DischargeServedReserveDownExpression, + DischargingReserveVariable, + _hybrids_with_storage, + device_formulation(), + time_steps, + ) + # Regularization terms if PSI.get_attribute(device_model, "regularization") PSI.add_variables!( @@ -502,6 +612,40 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim MerchantModelWithReserves(), time_steps, ) + + # Add Served Expressions for Thermal + PSI.lazy_container_addition!( + container, + ThermalServedReserveUpExpression(), + T, + PSY.get_name.(_hybrids_with_thermal), + time_steps, + ) + + PSI.lazy_container_addition!( + container, + ThermalServedReserveDownExpression(), + T, + PSY.get_name.(_hybrids_with_thermal), + time_steps, + ) + + add_to_expression_componentservedreserveup!( + container, + ThermalServedReserveUpExpression, + ThermalReserveVariable, + _hybrids_with_thermal, + MerchantModelWithReserves(), + time_steps, + ) + add_to_expression_componentservedreservedown!( + container, + ThermalServedReserveDownExpression, + ThermalReserveVariable, + _hybrids_with_thermal, + MerchantModelWithReserves(), + time_steps, + ) end ############################### @@ -849,34 +993,34 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim MerchantModelWithReserves(), ) - # TODO: set-up cycling in the decision model - cycling = true - if cycling + if PSI.get_attribute(device_model, "cycling") #= PSI.add_parameters!( container, CyclingChargeLimitParameter, _hybrids_with_storage, - model, + device_model, ) PSI.add_parameters!( container, CyclingDischargeLimitParameter, _hybrids_with_storage, - model, + device_model, ) =# - _add_constraints_cyclingcharge_decisionmodel!( + PSI.add_constraints!( container, CyclingCharge, _hybrids_with_storage, - MerchantModelWithReserves(), + device_model, + network_model, ) - _add_constraints_cyclingdischarge_decisionmodel!( + PSI.add_constraints!( container, CyclingDischarge, _hybrids_with_storage, - MerchantModelWithReserves(), + device_model, + network_model, ) end @@ -980,6 +1124,7 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim ) PSI.add_feedforward_arguments!(container, device_model, hybrids) + PSI.add_feedforward_constraints!(container, device_model, hybrids) PSI.update_objective_function!(container) PSI.serialize_metadata!(container, PSI.get_output_dir(decision_model)) return From 3c57fbb4cd153c9edfa74d99e8f1bb90db930019 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:03:29 -0700 Subject: [PATCH 11/33] update script cosim --- scripts/cooptimizer_pipeline/step2_simulation.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/scripts/cooptimizer_pipeline/step2_simulation.jl b/scripts/cooptimizer_pipeline/step2_simulation.jl index 54577359..635b1f91 100644 --- a/scripts/cooptimizer_pipeline/step2_simulation.jl +++ b/scripts/cooptimizer_pipeline/step2_simulation.jl @@ -385,7 +385,7 @@ p_soc_rt = read_realized_variable(result_merch_RT, "EnergyVariable__HybridSystem #p_tot_reserve = read_variable(result_merch_DA, "TotalReserve__HybridSystem") -day = DateTime("2020-10-03T00:00:00") +day = DateTime("2020-10-04T00:00:00") aux_var_cycling = read_aux_variable(result_merch_DA, "CumulativeCyclingCharge__HybridSystem") @@ -435,6 +435,13 @@ plot([ ), ]) +## RT ## + +aux_var_cycling = + read_realized_aux_variable(result_merch_RT, "CumulativeCyclingCharge__HybridSystem") + +plot(aux_var_cycling[!, 2]) + plot(p_soc_da[!, 2]) par = read_parameter(result_merch_DA, "CyclingDischargeLimitParameter__HybridSystem") From 0404d827a7a7d628c94946f039651c2a4e86d86f Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:03:48 -0700 Subject: [PATCH 12/33] remove cooptimizer extra params --- src/decision_models/cooptimizer_decision_model.jl | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/decision_models/cooptimizer_decision_model.jl b/src/decision_models/cooptimizer_decision_model.jl index 0dc322ba..7e5b8408 100644 --- a/src/decision_models/cooptimizer_decision_model.jl +++ b/src/decision_models/cooptimizer_decision_model.jl @@ -994,20 +994,6 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim ) if PSI.get_attribute(device_model, "cycling") - #= - PSI.add_parameters!( - container, - CyclingChargeLimitParameter, - _hybrids_with_storage, - device_model, - ) - PSI.add_parameters!( - container, - CyclingDischargeLimitParameter, - _hybrids_with_storage, - device_model, - ) - =# PSI.add_constraints!( container, CyclingCharge, From cd6eb6b6d6d0f716994d02cdabdc97848ce196e6 Mon Sep 17 00:00:00 2001 From: rodrigomha Date: Fri, 3 May 2024 16:04:06 -0700 Subject: [PATCH 13/33] update FF models --- src/HybridSystemsSimulations.jl | 4 + src/add_aux_variables.jl | 14 +-- src/add_constraints.jl | 55 ++++++++--- src/core/constraints.jl | 7 ++ src/core/variables.jl | 1 + src/feedforwards.jl | 170 +++++++++++++++++++++++++++++++- 6 files changed, 226 insertions(+), 25 deletions(-) diff --git a/src/HybridSystemsSimulations.jl b/src/HybridSystemsSimulations.jl index e5ca2e24..b81b05c1 100644 --- a/src/HybridSystemsSimulations.jl +++ b/src/HybridSystemsSimulations.jl @@ -24,6 +24,10 @@ export TotalReserve # Auxiliary variables +# FeedForward +export CyclingChargeLimitFeedforward +export CyclingDischargeLimitFeedforward + # Constraints export OptConditionRenewablePower export OptConditionBatteryCharge diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 75e4fb7c..21afb6e0 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -9,8 +9,6 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR charge_var = PSI.get_variable(container, BatteryCharge(), T) - ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), T) - ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), T) aux_variable_container = PSI.get_aux_variable(container, CumulativeCyclingCharge(), T) for d in devices name = PSY.get_name(d) @@ -23,6 +21,10 @@ function PSI.calculate_aux_variable_value!( fraction_of_hour * sum(PSI.jump_value(charge_var[name, k]) for k in 1:t) else + ch_served_reg_up = + PSI.get_expression(container, ChargeServedReserveUpExpression(), T) + ch_served_reg_dn = + PSI.get_expression(container, ChargeServedReserveDownExpression(), T) aux_variable_container[name, t] = efficiency.in * fraction_of_hour * @@ -49,10 +51,6 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR discharge_var = PSI.get_variable(container, BatteryDischarge(), T) - ds_served_reg_up = - PSI.get_expression(container, DischargeServedReserveUpExpression(), T) - ds_served_reg_dn = - PSI.get_expression(container, DischargeServedReserveDownExpression(), T) aux_variable_container = PSI.get_aux_variable(container, CumulativeCyclingDischarge(), T) for d in devices @@ -66,6 +64,10 @@ function PSI.calculate_aux_variable_value!( fraction_of_hour * sum(PSI.jump_value(discharge_var[name, k]) for k in 1:t) else + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), T) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), T) aux_variable_container[name, t] = (1.0 / efficiency.out) * fraction_of_hour * diff --git a/src/add_constraints.jl b/src/add_constraints.jl index 493fabdf..fdb4356e 100644 --- a/src/add_constraints.jl +++ b/src/add_constraints.jl @@ -935,6 +935,20 @@ function _add_constraints_cyclingcharge_withreserves!( ci_name = PSY.get_name(device) storage = PSY.get_storage(device) efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * + fraction_of_hour * + sum( + charge_var[ci_name, :] + ch_served_reg_dn[ci_name, :] - + ch_served_reg_up[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + #= if PSI.built_for_recurrent_solves(container) param_value = PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] @@ -962,6 +976,7 @@ function _add_constraints_cyclingcharge_withreserves!( ) <= cycles_in_horizon * E_max ) end + =# end return end @@ -1087,6 +1102,20 @@ function _add_constraints_cyclingdischarge_withreserves!( ci_name = PSY.get_name(device) storage = PSY.get_storage(device) efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum( + discharge_var[ci_name, :] + ds_served_reg_up[ci_name, :] - + ds_served_reg_dn[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + #= if PSI.built_for_recurrent_solves(container) param_value = PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] @@ -1114,6 +1143,7 @@ function _add_constraints_cyclingdischarge_withreserves!( ) <= cycles_in_horizon * E_max ) end + =# end return end @@ -1221,11 +1251,10 @@ function PSI.add_constraints!( names = [PSY.get_name(x) for x in devices] time_steps = PSI.get_time_steps(container) reg_var = PSI.get_variable(container, ChargeRegularizationVariable(), V) - ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), V) - ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), V) powerin_var = PSI.get_variable(container, BatteryCharge(), V) has_services = PSI.has_service_model(model) - + if has_services + end constraint_ub = PSI.add_constraints_container!( container, ChargeRegularizationConstraint(), @@ -1245,10 +1274,10 @@ function PSI.add_constraints!( ) if has_services - services = Set() - for d in devices - union!(services, PSY.get_services(d)) - end + ch_served_reg_up = + PSI.get_expression(container, ChargeServedReserveUpExpression(), V) + ch_served_reg_dn = + PSI.get_expression(container, ChargeServedReserveDownExpression(), V) for device in devices ci_name = PSY.get_name(device) constraint_ub[ci_name, 1] = @@ -1317,10 +1346,6 @@ function PSI.add_constraints!( time_steps = PSI.get_time_steps(container) reg_var = PSI.get_variable(container, DischargeRegularizationVariable(), V) powerout_var = PSI.get_variable(container, BatteryDischarge(), V) - ds_served_reg_up = - PSI.get_expression(container, DischargeServedReserveUpExpression(), V) - ds_served_reg_dn = - PSI.get_expression(container, DischargeServedReserveDownExpression(), V) has_services = PSI.has_service_model(model) constraint_ub = PSI.add_constraints_container!( @@ -1342,10 +1367,10 @@ function PSI.add_constraints!( ) if has_services - services = Set() - for d in devices - union!(services, PSY.get_services(d)) - end + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), V) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), V) for device in devices ci_name = PSY.get_name(device) constraint_ub[ci_name, 1] = diff --git a/src/core/constraints.jl b/src/core/constraints.jl index 5e296b15..11b38b1b 100644 --- a/src/core/constraints.jl +++ b/src/core/constraints.jl @@ -68,6 +68,13 @@ struct DischargeRegularizationConstraint <: PSI.ConstraintType end struct StateofChargeTargetConstraint <: PSI.ConstraintType end struct RenewableActivePowerLimitConstraint <: PSI.ConstraintType end +################### +### Feedforwards ### +################### + +struct FeedForwardCyclingChargeConstraint <: PSI.ConstraintType end +struct FeedForwardCyclingDischargeConstraint <: PSI.ConstraintType end + ############################################## ### Dual Optimality Conditions Constraints ### ############################################## diff --git a/src/core/variables.jl b/src/core/variables.jl index 63001686..bdc9d8f8 100644 --- a/src/core/variables.jl +++ b/src/core/variables.jl @@ -142,3 +142,4 @@ struct ComplementarySlackVarCyclingDischarge <: MerchantModelComplementarySlackV # implement below #PSI.convert_result_to_natural_units() = false +PSI.should_write_resulting_value(::Type{TotalReserve}) = false diff --git a/src/feedforwards.jl b/src/feedforwards.jl index faa3a79a..89937f39 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -11,9 +11,9 @@ struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward penalty_cost::Float64, meta=PSI.CONTAINER_KEY_EMPTY_META, ) where {T} - values_vector = Vector{PSI.VariableKey}(undef, length(affected_values)) + values_vector = Vector{PSI.ParameterKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) - if v <: PSI.VariableType + if v <: PSI.ParameterType values_vector[ix] = PSI.get_optimization_container_key(v(), component_type, meta) else @@ -48,9 +48,9 @@ struct CyclingDischargeLimitFeedforward <: PSI.AbstractAffectFeedforward penalty_cost::Float64, meta=PSI.CONTAINER_KEY_EMPTY_META, ) where {T} - values_vector = Vector{PSI.VariableKey}(undef, length(affected_values)) + values_vector = Vector{PSI.ParameterKey}(undef, length(affected_values)) for (ix, v) in enumerate(affected_values) - if v <: PSI.VariableType + if v <: PSI.ParameterType values_vector[ix] = PSI.get_optimization_container_key(v(), component_type, meta) else @@ -72,6 +72,168 @@ PSI.get_default_parameter_type(::CyclingDischargeLimitFeedforward, _) = PSI.get_optimization_container_key(ff::CyclingDischargeLimitFeedforward) = ff.optimization_container_key +#= +function PSI.add_feedforward_arguments!( + container::PSI.OptimizationContainer, + model::PSI.DeviceModel, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, +) where {D <: PSY.HybridSystem} + for ff in PSI.get_feedforwards(model) + PSI._add_feedforward_arguments!(container, model, devices, ff) + end + return +end +=# + +function PSI._add_feedforward_arguments!( + container::PSI.OptimizationContainer, + model::PSI.DeviceModel, + devices::Vector{D}, + ff::U, +) where { + D <: PSY.HybridSystem, + U <: Union{CyclingChargeLimitFeedforward, CyclingDischargeLimitFeedforward}, +} + parameter_type = PSI.get_default_parameter_type(ff, D) + PSI.add_parameters!(container, parameter_type, devices, model) + return +end + +function PSI.add_feedforward_constraints!( + container::PSI.OptimizationContainer, + model::PSI.DeviceModel, + devices::Vector{V}, +) where {V <: PSY.HybridSystem} + for ff in PSI.get_feedforwards(model) + PSI.add_feedforward_constraints!(container, model, devices, ff) + end + return +end + +function PSI.add_feedforward_constraints!( + container::PSI.OptimizationContainer, + device_model::PSI.DeviceModel, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + ff::CyclingChargeLimitFeedforward, +) where {D <: PSY.HybridSystem} + if PSI.get_attribute(device_model, "cycling") + throw( + IS.ConflictingInputsError( + "Cycling Attribute not allowed with Cycling Limit Feedforwards", + ), + ) + end + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + charge_var = PSI.get_variable(container, BatteryCharge(), D) + ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), D) + ch_served_reg_dn = PSI.get_expression(container, ChargeServedReserveDownExpression(), D) + T = FeedForwardCyclingChargeConstraint + con_cycling_ch = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * + fraction_of_hour * + sum( + charge_var[ci_name, :] + ch_served_reg_dn[ci_name, :] - + ch_served_reg_up[ci_name, :], + ) <= param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * + fraction_of_hour * + sum( + charge_var[ci_name, :] + ch_served_reg_dn[ci_name, :] - + ch_served_reg_up[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + end + end + return +end + +function PSI.add_feedforward_constraints!( + container::PSI.OptimizationContainer, + device_model::PSI.DeviceModel, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + ff::CyclingDischargeLimitFeedforward, +) where {D <: PSY.HybridSystem} + if PSI.get_attribute(device_model, "cycling") + throw( + IS.ConflictingInputsError( + "Cycling Attribute not allowed with Cycling Limit Feedforwards", + ), + ) + end + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + discharge_var = PSI.get_variable(container, BatteryDischarge(), D) + ds_served_reg_up = + PSI.get_expression(container, DischargeServedReserveUpExpression(), D) + ds_served_reg_dn = + PSI.get_expression(container, DischargeServedReserveDownExpression(), D) + T = FeedForwardCyclingDischargeConstraint + con_cycling_ds = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum( + discharge_var[ci_name, :] + ds_served_reg_up[ci_name, :] - + ds_served_reg_dn[ci_name, :], + ) <= param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum( + discharge_var[ci_name, :] + ds_served_reg_up[ci_name, :] - + ds_served_reg_dn[ci_name, :], + ) <= cycles_in_horizon * E_max + ) + end + end + return +end + function PSI.update_parameter_values!( model::PSI.DecisionModel, key::PSI.ParameterKey{T, U}, From 79f85af3957e5bbef8b8e2365f2cc0386d2dac83 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 7 May 2024 16:52:04 -0600 Subject: [PATCH 14/33] change var names --- src/core/aux_variables.jl | 4 ++-- src/decision_models/cooptimizer_decision_model.jl | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/aux_variables.jl b/src/core/aux_variables.jl index 3a36ba94..1f8c49ee 100644 --- a/src/core/aux_variables.jl +++ b/src/core/aux_variables.jl @@ -1,2 +1,2 @@ -struct CumulativeCyclingCharge <: PSI.AuxVariableType end -struct CumulativeCyclingDischarge <: PSI.AuxVariableType end +struct CyclingChargeUsage <: PSI.AuxVariableType end +struct CyclingDischargeUsage <: PSI.AuxVariableType end diff --git a/src/decision_models/cooptimizer_decision_model.jl b/src/decision_models/cooptimizer_decision_model.jl index 7e5b8408..7e317ad8 100644 --- a/src/decision_models/cooptimizer_decision_model.jl +++ b/src/decision_models/cooptimizer_decision_model.jl @@ -383,8 +383,8 @@ function PSI.build_impl!(decision_model::PSI.DecisionModel{MerchantHybridCooptim DischargingReserveVariable, EnergyBatteryChargeBid, EnergyBatteryDischargeBid, - CumulativeCyclingCharge, - CumulativeCyclingDischarge, + CyclingChargeUsage, + CyclingDischargeUsage, ] PSI.add_variables!( container, From b5e6e1898c78ef18c14bb45c816efe0ca9403c5f Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 7 May 2024 16:52:18 -0600 Subject: [PATCH 15/33] update models --- src/hybrid_system_constructor.jl | 78 ++++++++++++++-------------- src/hybrid_system_decision_models.jl | 2 +- src/hybrid_system_device_models.jl | 7 +++ 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/src/hybrid_system_constructor.jl b/src/hybrid_system_constructor.jl index 1710c412..67850ae1 100644 --- a/src/hybrid_system_constructor.jl +++ b/src/hybrid_system_constructor.jl @@ -33,8 +33,6 @@ function PSI.construct_device!( network_model, ) - PSI.add_feedforward_arguments!(container, model, devices) - if PSI.has_service_model(model) error("Services are not supported by $D") end @@ -64,21 +62,19 @@ function PSI.construct_device!( PSI.add_variables!(container, PSI.EnergyVariable, _hybrids_with_storage, D()) PSI.add_variables!(container, BatteryStatus, _hybrids_with_storage, D()) - if PSI.get_attribute(model, "cycling") - PSI.add_variables!( - container, - CumulativeCyclingCharge, - _hybrids_with_storage, - D(), - ) + PSI.add_variables!( + container, + CyclingChargeUsage, + _hybrids_with_storage, + D(), + ) - PSI.add_variables!( - container, - CumulativeCyclingDischarge, - _hybrids_with_storage, - D(), - ) - end + PSI.add_variables!( + container, + CyclingDischargeUsage, + _hybrids_with_storage, + D(), + ) if PSI.get_attribute(model, "energy_target") PSI.add_variables!( @@ -116,6 +112,8 @@ function PSI.construct_device!( PSI.add_parameters!(container, ElectricLoadTimeSeries, _hybrids_with_loads, model) end + PSI.add_feedforward_arguments!(container, model, devices) + ### Objective Function ### PSI.objective_function!(container, devices, model, network_model) return @@ -305,8 +303,6 @@ function PSI.construct_device!( network_model, ) - PSI.add_feedforward_arguments!(container, model, devices) - ### Add Component Variables ### _hybrids_with_thermal = [d for d in devices if PSY.get_thermal_unit(d) !== nothing] @@ -670,40 +666,42 @@ function PSI.construct_device!( ) end - if PSI.get_attribute(model, "cycling") - PSI.add_variables!( + PSI.add_variables!( + container, + CyclingChargeUsage, + _hybrids_with_storage, + D(), + ) + PSI.add_variables!( + container, + CyclingDischargeUsage, + _hybrids_with_storage, + D(), + ) + #= + if PSI.built_for_recurrent_solves(container) + PSI.add_parameters!( container, - CumulativeCyclingCharge, + CyclingChargeLimitParameter, _hybrids_with_storage, - D(), + model, ) - PSI.add_variables!( + PSI.add_parameters!( container, - CumulativeCyclingDischarge, + CyclingDischargeLimitParameter, _hybrids_with_storage, - D(), + model, ) - if PSI.built_for_recurrent_solves(container) - PSI.add_parameters!( - container, - CyclingChargeLimitParameter, - _hybrids_with_storage, - model, - ) - PSI.add_parameters!( - container, - CyclingDischargeLimitParameter, - _hybrids_with_storage, - model, - ) - end end + =# if PSI.get_attribute(model, "regularization") PSI.add_variables!(container, ChargeRegularizationVariable, devices, D()) PSI.add_variables!(container, DischargeRegularizationVariable, devices, D()) end + PSI.add_feedforward_arguments!(container, model, collect(devices)) + # Add reserve variables and expressions for storage unit if PSI.has_service_model(model) # Reserve Variables @@ -1094,6 +1092,8 @@ function PSI.construct_device!( PSI.add_constraints!(container, ReserveBalance, devices, model, network_model) end + + PSI.add_feedforward_constraints!(container, model, devices) return end diff --git a/src/hybrid_system_decision_models.jl b/src/hybrid_system_decision_models.jl index 3541efc7..d04bcd85 100644 --- a/src/hybrid_system_decision_models.jl +++ b/src/hybrid_system_decision_models.jl @@ -452,7 +452,7 @@ end function PSI.add_feedforward_arguments!( container::PSI.OptimizationContainer, - model::PSI.DeviceModel, + model::PSI.DeviceModel{V, <:AbstractHybridFormulation}, devices::Vector{V}, ) where {V <: PSY.HybridSystem} for ff in PSI.get_feedforwards(model) diff --git a/src/hybrid_system_device_models.jl b/src/hybrid_system_device_models.jl index af94717a..f42fceb5 100644 --- a/src/hybrid_system_device_models.jl +++ b/src/hybrid_system_device_models.jl @@ -18,6 +18,7 @@ function PSI.get_default_attributes( "reservation" => true, "storage_reservation" => true, "energy_target" => false, + "regularization" => true, ) end @@ -380,6 +381,12 @@ PSI.get_parameter_multiplier( ::Union{HybridFixedDA, HybridEnergyOnlyDispatch, HybridDispatchWithReserves}, ) = 1.0 +PSI.get_parameter_multiplier( + ::Union{CyclingDischargeLimitParameter, CyclingChargeLimitParameter}, + ::PSY.HybridSystem, + ::Union{HybridFixedDA, HybridEnergyOnlyDispatch, HybridDispatchWithReserves}, +) = 1.0 + PSI.get_initial_parameter_value( ::Union{CyclingDischargeLimitParameter, CyclingChargeLimitParameter}, d::PSY.HybridSystem, From 64795e27d0c60d3b9c24cc01da4634bdcd52b05e Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 7 May 2024 16:52:56 -0600 Subject: [PATCH 16/33] rename feedforward uses --- src/add_aux_variables.jl | 33 +++++++++++++--------------- src/add_parameters.jl | 4 ++-- src/feedforwards.jl | 47 ++++++++++++++++++---------------------- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 21afb6e0..5de85ac6 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -1,6 +1,6 @@ function PSI.calculate_aux_variable_value!( container::PSI.OptimizationContainer, - ::PSI.AuxVarKey{CumulativeCyclingCharge, T}, + ::PSI.AuxVarKey{CyclingChargeUsage, T}, system::PSY.System, ) where {T <: PSY.HybridSystem} devices_hybrids = PSI.get_available_components(T, system) @@ -9,7 +9,7 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR charge_var = PSI.get_variable(container, BatteryCharge(), T) - aux_variable_container = PSI.get_aux_variable(container, CumulativeCyclingCharge(), T) + aux_variable_container = PSI.get_aux_variable(container, CyclingChargeUsage(), T) for d in devices name = PSY.get_name(d) storage = PSY.get_storage(d) @@ -19,7 +19,7 @@ function PSI.calculate_aux_variable_value!( aux_variable_container[name, t] = efficiency.in * fraction_of_hour * - sum(PSI.jump_value(charge_var[name, k]) for k in 1:t) + PSI.jump_value(charge_var[name, t]) else ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), T) @@ -27,12 +27,11 @@ function PSI.calculate_aux_variable_value!( PSI.get_expression(container, ChargeServedReserveDownExpression(), T) aux_variable_container[name, t] = efficiency.in * - fraction_of_hour * - (sum( - PSI.jump_value(charge_var[name, k]) + - PSI.jump_value(ch_served_reg_dn[name, k]) - - PSI.jump_value(ch_served_reg_up[name, k]) for k in 1:t - )) + fraction_of_hour *( + PSI.jump_value(charge_var[name, t]) + + PSI.jump_value(ch_served_reg_dn[name, t]) - + PSI.jump_value(ch_served_reg_up[name, t]) + ) end end end @@ -42,7 +41,7 @@ end function PSI.calculate_aux_variable_value!( container::PSI.OptimizationContainer, - ::PSI.AuxVarKey{CumulativeCyclingDischarge, T}, + ::PSI.AuxVarKey{CyclingDischargeUsage, T}, system::PSY.System, ) where {T <: PSY.HybridSystem} devices_hybrids = PSI.get_available_components(T, system) @@ -52,7 +51,7 @@ function PSI.calculate_aux_variable_value!( fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR discharge_var = PSI.get_variable(container, BatteryDischarge(), T) aux_variable_container = - PSI.get_aux_variable(container, CumulativeCyclingDischarge(), T) + PSI.get_aux_variable(container, CyclingDischargeUsage(), T) for d in devices name = PSY.get_name(d) storage = PSY.get_storage(d) @@ -62,7 +61,7 @@ function PSI.calculate_aux_variable_value!( aux_variable_container[name, t] = (1.0 / efficiency.out) * fraction_of_hour * - sum(PSI.jump_value(discharge_var[name, k]) for k in 1:t) + PSI.jump_value(discharge_var[name, t]) else ds_served_reg_up = PSI.get_expression(container, DischargeServedReserveUpExpression(), T) @@ -70,12 +69,10 @@ function PSI.calculate_aux_variable_value!( PSI.get_expression(container, DischargeServedReserveDownExpression(), T) aux_variable_container[name, t] = (1.0 / efficiency.out) * - fraction_of_hour * - (sum( - PSI.jump_value(discharge_var[name, k]) + - PSI.jump_value(ds_served_reg_up[name, k]) - - PSI.jump_value(ds_served_reg_dn[name, k]) for k in 1:t - )) + fraction_of_hour *( + PSI.jump_value(discharge_var[name, t]) + + PSI.jump_value(ds_served_reg_up[name, t]) - + PSI.jump_value(ds_served_reg_dn[name, t])) end end end diff --git a/src/add_parameters.jl b/src/add_parameters.jl index 156bb093..cb03417a 100644 --- a/src/add_parameters.jl +++ b/src/add_parameters.jl @@ -436,9 +436,9 @@ function PSI.add_parameters!( fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR mult = fraction_of_hour * length(time_steps) / HOURS_IN_DAY if T <: CyclingDischargeLimitParameter - key = PSI.AuxVarKey{CumulativeCyclingDischarge, PSY.HybridSystem}("") + key = PSI.AuxVarKey{CyclingDischargeUsage, PSY.HybridSystem}("") else - key = PSI.AuxVarKey{CumulativeCyclingCharge, PSY.HybridSystem}("") + key = PSI.AuxVarKey{CyclingChargeUsage, PSY.HybridSystem}("") end parameter_container = PSI.add_param_container!(container, T(), D, key, names) jump_model = PSI.get_jump_model(container) diff --git a/src/feedforwards.jl b/src/feedforwards.jl index 89937f39..da425737 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -1,13 +1,11 @@ struct CyclingChargeLimitFeedforward <: PSI.AbstractAffectFeedforward optimization_container_key::PSI.OptimizationContainerKey affected_values::Vector{<:PSI.OptimizationContainerKey} - target_period::Int penalty_cost::Float64 function CyclingChargeLimitFeedforward(; component_type::Type{<:PSY.Component}, source::Type{T}, affected_values::Vector{DataType}, - target_period::Int, penalty_cost::Float64, meta=PSI.CONTAINER_KEY_EMPTY_META, ) where {T} @@ -38,13 +36,11 @@ PSI.get_optimization_container_key(ff::CyclingChargeLimitFeedforward) = struct CyclingDischargeLimitFeedforward <: PSI.AbstractAffectFeedforward optimization_container_key::PSI.OptimizationContainerKey affected_values::Vector{<:PSI.OptimizationContainerKey} - target_period::Int penalty_cost::Float64 function CyclingDischargeLimitFeedforward(; component_type::Type{<:PSY.Component}, source::Type{T}, affected_values::Vector{DataType}, - target_period::Int, penalty_cost::Float64, meta=PSI.CONTAINER_KEY_EMPTY_META, ) where {T} @@ -87,15 +83,22 @@ end function PSI._add_feedforward_arguments!( container::PSI.OptimizationContainer, - model::PSI.DeviceModel, + device_model::PSI.DeviceModel, devices::Vector{D}, ff::U, ) where { D <: PSY.HybridSystem, U <: Union{CyclingChargeLimitFeedforward, CyclingDischargeLimitFeedforward}, } + if PSI.get_attribute(device_model, "cycling") + throw( + IS.ConflictingInputsError( + "Cycling Attribute not allowed with $U Feedforwards", + ), + ) + end parameter_type = PSI.get_default_parameter_type(ff, D) - PSI.add_parameters!(container, parameter_type, devices, model) + PSI.add_parameters!(container, parameter_type, devices, device_model) return end @@ -116,13 +119,6 @@ function PSI.add_feedforward_constraints!( devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, ff::CyclingChargeLimitFeedforward, ) where {D <: PSY.HybridSystem} - if PSI.get_attribute(device_model, "cycling") - throw( - IS.ConflictingInputsError( - "Cycling Attribute not allowed with Cycling Limit Feedforwards", - ), - ) - end time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -177,13 +173,6 @@ function PSI.add_feedforward_constraints!( devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, ff::CyclingDischargeLimitFeedforward, ) where {D <: PSY.HybridSystem} - if PSI.get_attribute(device_model, "cycling") - throw( - IS.ConflictingInputsError( - "Cycling Attribute not allowed with Cycling Limit Feedforwards", - ), - ) - end time_steps = PSI.get_time_steps(container) resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR @@ -252,18 +241,24 @@ function PSI.update_parameter_values!( current_time = PSI.get_current_time(model) state_values = PSI.get_dataset_values(input, PSI.get_attribute_key(parameter_attributes)) + @error PSI.get_name(model) + @error state_values component_names = axes(parameter_array)[1] - resolution = PSI.get_resolution(model) - #TODO: This should be Horizon Time Steps not Interval - interval_time_steps = - Int(PSI.get_interval(model.internal.store_parameters) / resolution) + model_resolution = PSI.get_resolution(optimization_container) state_data = PSI.get_dataset(input, PSI.get_attribute_key(parameter_attributes)) state_timestamps = state_data.timestamps - state_data_index = PSI.find_timestamp_index(state_timestamps, current_time) + end_of_horizon_time = current_time + (PSI.get_time_steps(optimization_container)[end] - 1)*model_resolution + state_data_index_start = PSI.find_timestamp_index(state_timestamps, current_time) + state_data_index_end = PSI.find_timestamp_index(state_timestamps, end_of_horizon_time) + @error state_data_index_start + @error end_of_horizon_time + @error state_data_index_end for name in component_names + param_value = max.(state_values[name, state_data_index_start:state_data_index_end], 1e-3) + @error param_value PSI.fix_parameter_value( parameter_array[name], - state_values[name, state_data_index + interval_time_steps - 1], + sum(param_value)/12, ) end From d580fbd1c7b165cdb2135a89f2c0cd75b0730bf2 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 7 May 2024 16:53:06 -0600 Subject: [PATCH 17/33] update scripts --- scripts/centralized_paper/centralized_sim.jl | 4 +- scripts/cooptimizer_pipeline/step1_prices.jl | 37 ++++++++++++++++--- .../cooptimizer_pipeline/step2_simulation.jl | 10 ++--- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/scripts/centralized_paper/centralized_sim.jl b/scripts/centralized_paper/centralized_sim.jl index e2490c94..606ca5cb 100644 --- a/scripts/centralized_paper/centralized_sim.jl +++ b/scripts/centralized_paper/centralized_sim.jl @@ -148,8 +148,8 @@ PSI.solve!(model) res = ProblemResults(model) -cyc_charge = read_aux_variable(res, "CumulativeCyclingCharge__HybridSystem") -cyc_discharge = read_aux_variable(res, "CumulativeCyclingDischarge__HybridSystem") +cyc_charge = read_aux_variable(res, "CyclingChargeUsage__HybridSystem") +cyc_discharge = read_aux_variable(res, "CyclingDischargeUsage__HybridSystem") techs = ["STEAM", "CT", "CC", "WIND", "NUCLEAR", "PV", "RTPV", "HYDRO", "HYBRID"] tot_dict = Dict() diff --git a/scripts/cooptimizer_pipeline/step1_prices.jl b/scripts/cooptimizer_pipeline/step1_prices.jl index 60c7b0e5..32882308 100644 --- a/scripts/cooptimizer_pipeline/step1_prices.jl +++ b/scripts/cooptimizer_pipeline/step1_prices.jl @@ -127,7 +127,7 @@ set_device_model!( attributes=Dict{String, Any}( "reservation" => true, "storage_reservation" => true, - "energy_target" => true, + "energy_target" => false, "cycling" => true, ), ), @@ -141,8 +141,8 @@ set_device_model!( attributes=Dict{String, Any}( "reservation" => true, "storage_reservation" => true, - "energy_target" => true, - "cycling" => true, + "energy_target" => false, + "cycling" => false, ), ), ) @@ -205,11 +205,25 @@ sequence = SimulationSequence( component_type=VariableReserve{ReserveUp}, source=ActivePowerReserveVariable, affected_values=[ActivePowerReserveVariable], + add_slacks = true, ), LowerBoundFeedforward( component_type=VariableReserve{ReserveDown}, source=ActivePowerReserveVariable, affected_values=[ActivePowerReserveVariable], + add_slacks = true, + ), + CyclingChargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingChargeUsage, + affected_values=[HSS.CyclingChargeLimitParameter], + penalty_cost=0.0, + ), + CyclingDischargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingDischargeUsage, + affected_values=[HSS.CyclingDischargeLimitParameter], + penalty_cost=0.0, ), ], ), @@ -251,9 +265,20 @@ results_ed_dcp = get_decision_problem_results(results_dcp, "ED") results_uc_dcp = get_decision_problem_results(results_dcp, "UC") aux_var = - read_realized_variable(results_uc_dcp, "CumulativeCyclingDischarge__HybridSystem")[!, 2] -param_cycl = - read_parameter(results_ed_dcp, "CyclingDischargeLimitParameter__HybridSystem")[!, 2] + read_realized_variable(results_uc_dcp, "CyclingDischargeUsage__HybridSystem") +discharge_var = read_realized_variable(results_uc_dcp, "BatteryDischarge__HybridSystem")[!, 2] +reserve_up_ds_var = read_realized_variable(results_uc_dcp, "DischargingReserveVariable__VariableReserve__ReserveUp__Reg_Up") + +p_ds = read_realized_variable(results_ed_dcp, "BatteryDischarge__HybridSystem") +p_rd = read_realized_variable(results_ed_dcp, "CyclingDischargeUsage__HybridSystem") + +cum_p_rd = [sum(p_rd[!,2][1 + 12(k - 1):12 + 12(k - 1)]) for k in 1:48] + +param_cycl_ = + read_parameter(results_ed_dcp, "CyclingDischargeLimitParameter__HybridSystem") +param_cycl_uc = [v[!, 1][1] for v in values(param_cycl_)] + +param_cycl_ed = [v[!, 1][1] for v in values(param_ed)] p_re_param_uc = read_realized_parameter(results_uc_dcp, "RenewablePowerTimeSeries__HybridSystem")[!, 2] diff --git a/scripts/cooptimizer_pipeline/step2_simulation.jl b/scripts/cooptimizer_pipeline/step2_simulation.jl index 635b1f91..f0ad49ac 100644 --- a/scripts/cooptimizer_pipeline/step2_simulation.jl +++ b/scripts/cooptimizer_pipeline/step2_simulation.jl @@ -268,16 +268,14 @@ sequence = SimulationSequence( ), CyclingChargeLimitFeedforward( component_type=PSY.HybridSystem, - source=HSS.CumulativeCyclingCharge, + source=HSS.CyclingChargeUsage, affected_values=[HSS.CyclingChargeLimitParameter], - target_period=1, penalty_cost=0.0, ), CyclingDischargeLimitFeedforward( component_type=PSY.HybridSystem, - source=HSS.CumulativeCyclingDischarge, + source=HSS.CyclingDischargeUsage, affected_values=[HSS.CyclingDischargeLimitParameter], - target_period=1, penalty_cost=0.0, ), #FixValueFeedforward( @@ -388,7 +386,7 @@ p_soc_rt = read_realized_variable(result_merch_RT, "EnergyVariable__HybridSystem day = DateTime("2020-10-04T00:00:00") aux_var_cycling = - read_aux_variable(result_merch_DA, "CumulativeCyclingCharge__HybridSystem") + read_aux_variable(result_merch_DA, "CyclingChargeUsage__HybridSystem") first_day_aux_var = aux_var_cycling[day] charge_var = read_variable(result_merch_DA, "BatteryCharge__HybridSystem") @@ -438,7 +436,7 @@ plot([ ## RT ## aux_var_cycling = - read_realized_aux_variable(result_merch_RT, "CumulativeCyclingCharge__HybridSystem") + read_realized_aux_variable(result_merch_RT, "CyclingChargeUsage__HybridSystem") plot(aux_var_cycling[!, 2]) From e6cd078eb34bdee7fd41012526f2ad9b1a5ab047 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 7 May 2024 22:30:09 -0600 Subject: [PATCH 18/33] formatter --- scripts/cooptimizer_pipeline/step1_prices.jl | 20 +++++++------ .../cooptimizer_pipeline/step2_simulation.jl | 3 +- src/add_aux_variables.jl | 16 +++++------ src/feedforwards.jl | 16 +++++------ src/hybrid_system_constructor.jl | 28 +++---------------- 5 files changed, 31 insertions(+), 52 deletions(-) diff --git a/scripts/cooptimizer_pipeline/step1_prices.jl b/scripts/cooptimizer_pipeline/step1_prices.jl index 32882308..ef9a075b 100644 --- a/scripts/cooptimizer_pipeline/step1_prices.jl +++ b/scripts/cooptimizer_pipeline/step1_prices.jl @@ -205,13 +205,13 @@ sequence = SimulationSequence( component_type=VariableReserve{ReserveUp}, source=ActivePowerReserveVariable, affected_values=[ActivePowerReserveVariable], - add_slacks = true, + add_slacks=true, ), LowerBoundFeedforward( component_type=VariableReserve{ReserveDown}, source=ActivePowerReserveVariable, affected_values=[ActivePowerReserveVariable], - add_slacks = true, + add_slacks=true, ), CyclingChargeLimitFeedforward( component_type=PSY.HybridSystem, @@ -264,18 +264,20 @@ results_dcp = SimulationResults(sim_dcp; ignore_status=true) results_ed_dcp = get_decision_problem_results(results_dcp, "ED") results_uc_dcp = get_decision_problem_results(results_dcp, "UC") -aux_var = - read_realized_variable(results_uc_dcp, "CyclingDischargeUsage__HybridSystem") -discharge_var = read_realized_variable(results_uc_dcp, "BatteryDischarge__HybridSystem")[!, 2] -reserve_up_ds_var = read_realized_variable(results_uc_dcp, "DischargingReserveVariable__VariableReserve__ReserveUp__Reg_Up") +aux_var = read_realized_variable(results_uc_dcp, "CyclingDischargeUsage__HybridSystem") +discharge_var = + read_realized_variable(results_uc_dcp, "BatteryDischarge__HybridSystem")[!, 2] +reserve_up_ds_var = read_realized_variable( + results_uc_dcp, + "DischargingReserveVariable__VariableReserve__ReserveUp__Reg_Up", +) p_ds = read_realized_variable(results_ed_dcp, "BatteryDischarge__HybridSystem") p_rd = read_realized_variable(results_ed_dcp, "CyclingDischargeUsage__HybridSystem") -cum_p_rd = [sum(p_rd[!,2][1 + 12(k - 1):12 + 12(k - 1)]) for k in 1:48] +cum_p_rd = [sum(p_rd[!, 2][(1 + 12(k - 1)):(12 + 12(k - 1))]) for k in 1:48] -param_cycl_ = - read_parameter(results_ed_dcp, "CyclingDischargeLimitParameter__HybridSystem") +param_cycl_ = read_parameter(results_ed_dcp, "CyclingDischargeLimitParameter__HybridSystem") param_cycl_uc = [v[!, 1][1] for v in values(param_cycl_)] param_cycl_ed = [v[!, 1][1] for v in values(param_ed)] diff --git a/scripts/cooptimizer_pipeline/step2_simulation.jl b/scripts/cooptimizer_pipeline/step2_simulation.jl index f0ad49ac..f259df69 100644 --- a/scripts/cooptimizer_pipeline/step2_simulation.jl +++ b/scripts/cooptimizer_pipeline/step2_simulation.jl @@ -385,8 +385,7 @@ p_soc_rt = read_realized_variable(result_merch_RT, "EnergyVariable__HybridSystem day = DateTime("2020-10-04T00:00:00") -aux_var_cycling = - read_aux_variable(result_merch_DA, "CyclingChargeUsage__HybridSystem") +aux_var_cycling = read_aux_variable(result_merch_DA, "CyclingChargeUsage__HybridSystem") first_day_aux_var = aux_var_cycling[day] charge_var = read_variable(result_merch_DA, "BatteryCharge__HybridSystem") diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 5de85ac6..1e02504e 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -17,9 +17,7 @@ function PSI.calculate_aux_variable_value!( for t in time_steps if !PSY.has_service(d, PSY.VariableReserve) aux_variable_container[name, t] = - efficiency.in * - fraction_of_hour * - PSI.jump_value(charge_var[name, t]) + efficiency.in * fraction_of_hour * PSI.jump_value(charge_var[name, t]) else ch_served_reg_up = PSI.get_expression(container, ChargeServedReserveUpExpression(), T) @@ -27,7 +25,8 @@ function PSI.calculate_aux_variable_value!( PSI.get_expression(container, ChargeServedReserveDownExpression(), T) aux_variable_container[name, t] = efficiency.in * - fraction_of_hour *( + fraction_of_hour * + ( PSI.jump_value(charge_var[name, t]) + PSI.jump_value(ch_served_reg_dn[name, t]) - PSI.jump_value(ch_served_reg_up[name, t]) @@ -50,8 +49,7 @@ function PSI.calculate_aux_variable_value!( resolution = PSI.get_resolution(container) fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR discharge_var = PSI.get_variable(container, BatteryDischarge(), T) - aux_variable_container = - PSI.get_aux_variable(container, CyclingDischargeUsage(), T) + aux_variable_container = PSI.get_aux_variable(container, CyclingDischargeUsage(), T) for d in devices name = PSY.get_name(d) storage = PSY.get_storage(d) @@ -69,10 +67,12 @@ function PSI.calculate_aux_variable_value!( PSI.get_expression(container, DischargeServedReserveDownExpression(), T) aux_variable_container[name, t] = (1.0 / efficiency.out) * - fraction_of_hour *( + fraction_of_hour * + ( PSI.jump_value(discharge_var[name, t]) + PSI.jump_value(ds_served_reg_up[name, t]) - - PSI.jump_value(ds_served_reg_dn[name, t])) + PSI.jump_value(ds_served_reg_dn[name, t]) + ) end end end diff --git a/src/feedforwards.jl b/src/feedforwards.jl index da425737..5f686aae 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -92,9 +92,7 @@ function PSI._add_feedforward_arguments!( } if PSI.get_attribute(device_model, "cycling") throw( - IS.ConflictingInputsError( - "Cycling Attribute not allowed with $U Feedforwards", - ), + IS.ConflictingInputsError("Cycling Attribute not allowed with $U Feedforwards"), ) end parameter_type = PSI.get_default_parameter_type(ff, D) @@ -247,19 +245,19 @@ function PSI.update_parameter_values!( model_resolution = PSI.get_resolution(optimization_container) state_data = PSI.get_dataset(input, PSI.get_attribute_key(parameter_attributes)) state_timestamps = state_data.timestamps - end_of_horizon_time = current_time + (PSI.get_time_steps(optimization_container)[end] - 1)*model_resolution + end_of_horizon_time = + current_time + + (PSI.get_time_steps(optimization_container)[end] - 1) * model_resolution state_data_index_start = PSI.find_timestamp_index(state_timestamps, current_time) state_data_index_end = PSI.find_timestamp_index(state_timestamps, end_of_horizon_time) @error state_data_index_start @error end_of_horizon_time @error state_data_index_end for name in component_names - param_value = max.(state_values[name, state_data_index_start:state_data_index_end], 1e-3) + param_value = + max.(state_values[name, state_data_index_start:state_data_index_end], 1e-3) @error param_value - PSI.fix_parameter_value( - parameter_array[name], - sum(param_value)/12, - ) + PSI.fix_parameter_value(parameter_array[name], sum(param_value) / 12) end IS.@record :execution PSI.ParameterUpdateEvent( diff --git a/src/hybrid_system_constructor.jl b/src/hybrid_system_constructor.jl index 67850ae1..dbb489d0 100644 --- a/src/hybrid_system_constructor.jl +++ b/src/hybrid_system_constructor.jl @@ -62,19 +62,9 @@ function PSI.construct_device!( PSI.add_variables!(container, PSI.EnergyVariable, _hybrids_with_storage, D()) PSI.add_variables!(container, BatteryStatus, _hybrids_with_storage, D()) - PSI.add_variables!( - container, - CyclingChargeUsage, - _hybrids_with_storage, - D(), - ) + PSI.add_variables!(container, CyclingChargeUsage, _hybrids_with_storage, D()) - PSI.add_variables!( - container, - CyclingDischargeUsage, - _hybrids_with_storage, - D(), - ) + PSI.add_variables!(container, CyclingDischargeUsage, _hybrids_with_storage, D()) if PSI.get_attribute(model, "energy_target") PSI.add_variables!( @@ -666,18 +656,8 @@ function PSI.construct_device!( ) end - PSI.add_variables!( - container, - CyclingChargeUsage, - _hybrids_with_storage, - D(), - ) - PSI.add_variables!( - container, - CyclingDischargeUsage, - _hybrids_with_storage, - D(), - ) + PSI.add_variables!(container, CyclingChargeUsage, _hybrids_with_storage, D()) + PSI.add_variables!(container, CyclingDischargeUsage, _hybrids_with_storage, D()) #= if PSI.built_for_recurrent_solves(container) PSI.add_parameters!( From bf535b1f7216422fdcf84c909f88d202a28a4830 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 8 May 2024 13:47:18 -0600 Subject: [PATCH 19/33] use correct PTDF model definition --- test/test_device_hybrid_generation_constructors.jl | 2 +- test/test_utils/additional_templates.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_device_hybrid_generation_constructors.jl b/test/test_device_hybrid_generation_constructors.jl index 15949bbe..2f23c88e 100644 --- a/test/test_device_hybrid_generation_constructors.jl +++ b/test/test_device_hybrid_generation_constructors.jl @@ -23,7 +23,7 @@ end sys = PSB.build_system(PSITestSystems, "c_sys5_hybrid") # No Parameters Testing - model = DecisionModel(MockOperationProblem, StandardPTDFModel, sys) + model = DecisionModel(MockOperationProblem, PTDFPowerModel, sys) mock_construct_device!(model, device_model) moi_tests(model, 816, 0, 720, 192, 192, true) psi_checkobjfun_test(model, GAEVF) diff --git a/test/test_utils/additional_templates.jl b/test/test_utils/additional_templates.jl index 8c1ad850..e07709b9 100644 --- a/test/test_utils/additional_templates.jl +++ b/test/test_utils/additional_templates.jl @@ -75,7 +75,7 @@ end function get_uc_ptdf_template(sys_rts_da) template_uc = ProblemTemplate( NetworkModel( - StandardPTDFModel, + PTDFPowerModel, use_slacks=true, PTDF_matrix=PTDF(sys_rts_da), duals=[CopperPlateBalanceConstraint], From 5931870414adae65fbc18f972ebddf2c6f21f7b9 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 8 May 2024 13:47:53 -0600 Subject: [PATCH 20/33] add missing methods for cycling --- src/add_aux_variables.jl | 85 ++++++++++++++++++++++++++++ src/feedforwards.jl | 10 +--- src/hybrid_system_decision_models.jl | 1 - 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 1e02504e..8541ba6c 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -79,3 +79,88 @@ function PSI.calculate_aux_variable_value!( return end + +function PSI.update_system_state!( + state::PSI.DatasetContainer{PSI.InMemoryDataset}, + key::PSI.AuxVarKey{T, PSY.HybridSystem}, + decision_state::PSI.DatasetContainer{PSI.InMemoryDataset}, + simulation_time::Dates.DateTime, +) where {T <: Union{CyclingDischargeUsage, CyclingChargeUsage}} + decision_dataset = PSI.get_dataset(decision_state, key) + # Gets the timestamp of the value used for the update, which might not match exactly the + # simulation time since the value might have not been updated yet + + ts = PSI.get_value_timestamp(decision_dataset, simulation_time) + system_dataset = PSI.get_dataset(state, key) + system_state_resolution = PSI.get_data_resolution(system_dataset) + decision_state_resolution = PSI.get_data_resolution(decision_dataset) + + decision_state_value = PSI.get_dataset_value(decision_dataset, simulation_time) + + if ts == PSI.get_update_timestamp(system_dataset) + # Uncomment for debugging + #@warn "Skipped overwriting data with the same timestamp \\ + # key: $(encode_key_as_string(key)), $(simulation_time), $ts" + return + end + + if PSI.get_update_timestamp(system_dataset) > ts + error("Trying to update with past data a future state timestamp \\ + key: $(PSI.encode_key_as_string(key)), $(simulation_time), $ts") + end + + # Writes the timestamp of the value used for the update + PSI.set_update_timestamp!(system_dataset, ts) + # Keep coordination between fields. System state is an array of size 1 + system_dataset.timestamps[1] = ts + time_ratio = (decision_state_resolution / system_state_resolution) + # Don't use set_dataset_values!(state, key, 1, decision_state_value). + # For the time variables we need to grab the values to avoid mutation of the + # dataframe row + PSI.set_value!(system_dataset, values(decision_state_value) .* time_ratio, 1) + # This value shouldn't be other than one and after one execution is no-op. + PSI.set_last_recorded_row!(system_dataset, 1) + return +end + +function PSI.update_decision_state!( + state::PSI.SimulationState, + key::PSI.AuxVarKey{T, PSY.HybridSystem}, + store_data::JuMP.Containers.DenseAxisArray{Float64, 2}, + simulation_time::Dates.DateTime, + model_params::PSI.ModelStoreParams, +) where {T <: Union{CyclingDischargeUsage, CyclingChargeUsage}} + + state_data = PSI.get_decision_state_data(state, key) + column_names = PSI.get_column_names(key, state_data)[1] + model_resolution = PSI.get_resolution(model_params) + state_resolution = PSI.get_data_resolution(state_data) + resolution_ratio = model_resolution ÷ state_resolution + state_timestamps = state_data.timestamps + IS.@assert_op resolution_ratio >= 1 + if simulation_time > PSI.get_end_of_step_timestamp(state_data) + state_data_index = 1 + state_data.timestamps[:] .= + range( + simulation_time; + step = state_resolution, + length = PSI.get_num_rows(state_data), + ) + else + state_data_index = PSI.find_timestamp_index(state_timestamps, simulation_time) + end + + offset = resolution_ratio - 1 + result_time_index = axes(store_data)[2] + PSI.set_update_timestamp!(state_data, simulation_time) + for t in result_time_index + state_range = state_data_index:(state_data_index + offset) + for name in column_names, i in state_range + # TODO: We could also interpolate here + state_data.values[name, i] = max(0.0, store_data[name, t] / resolution_ratio) + end + PSI.set_last_recorded_row!(state_data, state_range[end]) + state_data_index += resolution_ratio + end + return +end diff --git a/src/feedforwards.jl b/src/feedforwards.jl index 5f686aae..26882ba9 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -239,8 +239,6 @@ function PSI.update_parameter_values!( current_time = PSI.get_current_time(model) state_values = PSI.get_dataset_values(input, PSI.get_attribute_key(parameter_attributes)) - @error PSI.get_name(model) - @error state_values component_names = axes(parameter_array)[1] model_resolution = PSI.get_resolution(optimization_container) state_data = PSI.get_dataset(input, PSI.get_attribute_key(parameter_attributes)) @@ -250,14 +248,10 @@ function PSI.update_parameter_values!( (PSI.get_time_steps(optimization_container)[end] - 1) * model_resolution state_data_index_start = PSI.find_timestamp_index(state_timestamps, current_time) state_data_index_end = PSI.find_timestamp_index(state_timestamps, end_of_horizon_time) - @error state_data_index_start - @error end_of_horizon_time - @error state_data_index_end for name in component_names param_value = - max.(state_values[name, state_data_index_start:state_data_index_end], 1e-3) - @error param_value - PSI.fix_parameter_value(parameter_array[name], sum(param_value) / 12) + max.(state_values[name, state_data_index_start:state_data_index_end], 1e-6) + PSI.fix_parameter_value(parameter_array[name], sum(param_value)) end IS.@record :execution PSI.ParameterUpdateEvent( diff --git a/src/hybrid_system_decision_models.jl b/src/hybrid_system_decision_models.jl index d04bcd85..b2ace3d3 100644 --- a/src/hybrid_system_decision_models.jl +++ b/src/hybrid_system_decision_models.jl @@ -387,7 +387,6 @@ function PSI._update_parameter_values!( # Pass indices in this way since JuMP DenseAxisArray don't support view() state_value = state_values[name, service_name, state_data_index] if !isfinite(state_value) - @error model.name error( "The value for the system state used in $(PSI.encode_key_as_string(PSI.get_attribute_key(attributes))) is not a finite value $(state_value) \ This is commonly caused by referencing a state value at a time when such decision hasn't been made. \ From 2ed0c6f4362823fd8669d64f95ab506dabaeec7c Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 8 May 2024 13:48:06 -0600 Subject: [PATCH 21/33] correct configuratio --- scripts/cooptimizer_pipeline/step1_prices.jl | 10 +++++++++- scripts/flexpower_rts/centralized_script.jl | 18 +++++++++++++++--- scripts/get_templates.jl | 2 +- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/scripts/cooptimizer_pipeline/step1_prices.jl b/scripts/cooptimizer_pipeline/step1_prices.jl index ef9a075b..7df03ba7 100644 --- a/scripts/cooptimizer_pipeline/step1_prices.jl +++ b/scripts/cooptimizer_pipeline/step1_prices.jl @@ -261,6 +261,7 @@ cons[PowerSimulations.ConstraintKey{HybridSystemsSimulations.StateofChargeTarget execute_status = execute!(sim_dcp; enable_progress_bar=true); results_dcp = SimulationResults(sim_dcp; ignore_status=true) + results_ed_dcp = get_decision_problem_results(results_dcp, "ED") results_uc_dcp = get_decision_problem_results(results_dcp, "UC") @@ -279,7 +280,14 @@ cum_p_rd = [sum(p_rd[!, 2][(1 + 12(k - 1)):(12 + 12(k - 1))]) for k in 1:48] param_cycl_ = read_parameter(results_ed_dcp, "CyclingDischargeLimitParameter__HybridSystem") param_cycl_uc = [v[!, 1][1] for v in values(param_cycl_)] - +#= +plot([ + scatter(x = aux_var[!,1], y = aux_var[!, 2], line_shape = "hv"), + scatter(x = aux_var[!,1], y = param_cycl_uc, line_shape = "hv"), + scatter(x = aux_var[!, 1], y = cum_p_rd, line_shape = "hv") + ] + ) +=# param_cycl_ed = [v[!, 1][1] for v in values(param_ed)] p_re_param_uc = diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index 4db1758d..51775643 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -132,8 +132,8 @@ set_device_model!( attributes=Dict{String, Any}( "reservation" => true, "storage_reservation" => true, - "energy_target" => true, - "cycling" => false, + "energy_target" => false, + "cycling" => true, ), ), ) @@ -146,7 +146,7 @@ set_device_model!( attributes=Dict{String, Any}( "reservation" => true, "storage_reservation" => true, - "energy_target" => true, + "energy_target" => false, "cycling" => false, ), ), @@ -228,6 +228,18 @@ sequence = SimulationSequence( affected_values=[ActivePowerReserveVariable], add_slacks=true, ), + CyclingChargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingChargeUsage, + affected_values=[HSS.CyclingChargeLimitParameter], + penalty_cost=0.0, + ), + CyclingDischargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingDischargeUsage, + affected_values=[HSS.CyclingDischargeLimitParameter], + penalty_cost=0.0, + ), ], ), ini_cond_chronology=InterProblemChronology(), diff --git a/scripts/get_templates.jl b/scripts/get_templates.jl index 1a5557f6..066a76d6 100644 --- a/scripts/get_templates.jl +++ b/scripts/get_templates.jl @@ -77,7 +77,7 @@ end function get_uc_ptdf_template(sys_rts_da) template_uc = ProblemTemplate( NetworkModel( - StandardPTDFModel, + PTDFPowerModel, use_slacks=true, PTDF_matrix=PTDF(sys_rts_da), duals=[CopperPlateBalanceConstraint], From 7676a356e8a710f87962c8e3943513aaf91330df Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 8 May 2024 13:59:53 -0600 Subject: [PATCH 22/33] add regularizatio --- scripts/flexpower_rts/centralized_script.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index 51775643..e2007931 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -134,6 +134,7 @@ set_device_model!( "storage_reservation" => true, "energy_target" => false, "cycling" => true, + "regularization" => true, ), ), ) @@ -148,6 +149,7 @@ set_device_model!( "storage_reservation" => true, "energy_target" => false, "cycling" => false, + "regularization" => true, ), ), ) From f25a0cdc2367ba9f1b9c6339d24fde64546f52eb Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Wed, 8 May 2024 14:56:37 -0600 Subject: [PATCH 23/33] formatter --- src/add_aux_variables.jl | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/add_aux_variables.jl b/src/add_aux_variables.jl index 8541ba6c..99f7838f 100644 --- a/src/add_aux_variables.jl +++ b/src/add_aux_variables.jl @@ -130,7 +130,6 @@ function PSI.update_decision_state!( simulation_time::Dates.DateTime, model_params::PSI.ModelStoreParams, ) where {T <: Union{CyclingDischargeUsage, CyclingChargeUsage}} - state_data = PSI.get_decision_state_data(state, key) column_names = PSI.get_column_names(key, state_data)[1] model_resolution = PSI.get_resolution(model_params) @@ -140,12 +139,11 @@ function PSI.update_decision_state!( IS.@assert_op resolution_ratio >= 1 if simulation_time > PSI.get_end_of_step_timestamp(state_data) state_data_index = 1 - state_data.timestamps[:] .= - range( - simulation_time; - step = state_resolution, - length = PSI.get_num_rows(state_data), - ) + state_data.timestamps[:] .= range( + simulation_time; + step=state_resolution, + length=PSI.get_num_rows(state_data), + ) else state_data_index = PSI.find_timestamp_index(state_timestamps, simulation_time) end From 83ba1be0ddc5ad0f7463ab4bc60169e131247ea2 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 9 May 2024 11:59:21 -0600 Subject: [PATCH 24/33] update expression writing --- src/core/expressions.jl | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/expressions.jl b/src/core/expressions.jl index 01cd54e4..47318cb8 100644 --- a/src/core/expressions.jl +++ b/src/core/expressions.jl @@ -42,3 +42,5 @@ struct ServedReserveInDownExpression <: ServedReserveDownExpression end struct ServedReserveOutUpExpression <: ServedReserveUpExpression end struct ServedReserveOutDownExpression <: ServedReserveDownExpression end struct AssetPowerBalance <: PSI.ExpressionType end + +PSI.should_write_resulting_value(::Type{<:ComponentServedReserveExpressionType}) = true From 640bd07e1358d46a06188a375b94e9e149414ec5 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 9 May 2024 12:01:18 -0600 Subject: [PATCH 25/33] update script --- scripts/flexpower_rts/centralized_script.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index e2007931..019486ad 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -62,7 +62,7 @@ transform_single_time_series!(sys_rts_da, horizon_DA, interval_DA) #interval_RT = Minute(5) #horizon_RT = 24 interval_RT = Hour(1) -horizon_RT = 12 * 24 +horizon_RT = 12 transform_single_time_series!(sys_rts_rt, horizon_RT, interval_RT) served_fraction_map = Dict( @@ -134,7 +134,7 @@ set_device_model!( "storage_reservation" => true, "energy_target" => false, "cycling" => true, - "regularization" => true, + "regularization" => false, ), ), ) @@ -161,7 +161,7 @@ set_device_model!( mipgap = 0.01 if isempty(ARGS) starttime = DateTime("2020-07-10T00:00:00") - num_steps = 10 + num_steps = 4 else starttime = DateTime(ARGS[2]) num_steps = parse(Int, ARGS[3]) From 39238fb8c0b4aa26d0b9b3739163e25a6e0ed13b Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Thu, 9 May 2024 12:07:15 -0600 Subject: [PATCH 26/33] update script --- scripts/flexpower_rts/cooptimizer_script.jl | 87 ++++++++++++++++++--- 1 file changed, 75 insertions(+), 12 deletions(-) diff --git a/scripts/flexpower_rts/cooptimizer_script.jl b/scripts/flexpower_rts/cooptimizer_script.jl index e85b5c9b..416a6945 100644 --- a/scripts/flexpower_rts/cooptimizer_script.jl +++ b/scripts/flexpower_rts/cooptimizer_script.jl @@ -57,7 +57,7 @@ transform_single_time_series!(sys_rts_da, horizon_DA, interval_DA) #interval_RT = Minute(5) #horizon_RT = 24 interval_RT = Hour(1) -horizon_RT = 12 * 24 +horizon_RT = 12 #transform_single_time_series!(sys_rts_rt, horizon_RT, interval_RT) ################################### @@ -95,7 +95,7 @@ end mipgap = 0.001 if isempty(ARGS) starttime = DateTime("2020-07-10T00:00:00") - num_steps = 7 + num_steps = 2 else starttime = DateTime(ARGS[2]) num_steps = parse(Int, ARGS[3]) @@ -104,7 +104,7 @@ end results_folder = joinpath( @__DIR__, "../..", - "centralized_sim_HybridDispatchWithReserves_2020-07-10T00:00:00-2", + "centralized_sim_HybridDispatchWithReserves_2020-07-10T00:00:00", ) results_dcp = SimulationResults(results_folder; ignore_status=true) @@ -188,12 +188,31 @@ end ######### Load Templates ########## ################################### -template_uc_copperplate = get_uc_copperplate_template(sys_rts_da) -template_ed_copperplate = get_ed_copperplate_template(sys_rts_merchant_rt) +###################################################### +####### Template for DA Bids ########## +###################################################### + +template_merchant_da = get_uc_copperplate_template(sys_rts_da) + +set_device_model!( + template_merchant_da, + DeviceModel( + PSY.HybridSystem, + HybridDispatchWithReserves; + #HybridEnergyOnlyDispatch; + attributes=Dict{String, Any}( + "reservation" => true, + "storage_reservation" => true, + "energy_target" => true, + "cycling" => true, + "regularization" => true, + ), + ), +) decision_optimizer_DA = DecisionModel( formulation, - template_uc_copperplate, + template_merchant_da, sys_rts_merchant_da, optimizer=optimizer_with_attributes( Xpress.Optimizer, @@ -211,7 +230,10 @@ decision_optimizer_DA = DecisionModel( name=name = "$(formulation)_DA", ) -# Set Hybrid in UC as FixedDA +###################################################### +####### Template for DA Market Clearing ########## +###################################################### +template_uc_copperplate = get_uc_copperplate_template(sys_rts_da) set_device_model!( template_uc_copperplate, DeviceModel( @@ -222,20 +244,28 @@ set_device_model!( ) ###################################################### -####### Systems for RT Bids and Realized ED ########## +####### Template for RT Bids ########## ###################################################### +template_merchant_rt = get_uc_copperplate_template(sys_rts_da) + set_device_model!( - template_ed_copperplate, + template_merchant_rt, DeviceModel( PSY.HybridSystem, - HybridFixedDA; - attributes=Dict{String, Any}("cycling" => false), + HybridDispatchWithReserves; + attributes=Dict{String, Any}( + "reservation" => true, + "storage_reservation" => true, + "energy_target" => true, + "cycling" => false, + "regularization" => true, + ), ), ) decision_optimizer_RT = DecisionModel( formulation, - template_ed_copperplate, + template_merchant_rt, sys_rts_merchant_rt, optimizer=optimizer_with_attributes( Xpress.Optimizer, @@ -255,6 +285,27 @@ decision_optimizer_RT = DecisionModel( decision_optimizer_RT.ext = Dict{String, Any}("RT" => true) +###################################################### +####### Template for RT Market Clearing ########## +###################################################### + +template_ed_copperplate = get_uc_copperplate_template(sys_rts_da) +set_device_model!(template_ed_copperplate, ThermalStandard, ThermalBasicDispatch) +set_device_model!(template_ed_copperplate, HydroDispatch, FixedOutput) +#set_device_model!(template_ed, HydroEnergyReservoir, FixedOutput) +for s in values(template_ed_copperplate.services) + s.use_slacks = true +end + +set_device_model!( + template_ed_copperplate, + DeviceModel( + PSY.HybridSystem, + HybridFixedDA; + attributes=Dict{String, Any}("cycling" => false), + ), +) + models = SimulationModels( decision_models=[ decision_optimizer_DA, @@ -326,6 +377,18 @@ sequence = SimulationSequence( source=EnergyDABidIn, affected_values=[EnergyDABidIn], ), + CyclingChargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingChargeUsage, + affected_values=[HSS.CyclingChargeLimitParameter], + penalty_cost=0.0, + ), + CyclingDischargeLimitFeedforward( + component_type=PSY.HybridSystem, + source=HSS.CyclingDischargeUsage, + affected_values=[HSS.CyclingDischargeLimitParameter], + penalty_cost=0.0, + ), ], "ED" => [ SemiContinuousFeedforward( From 72eec12748858005a23b918124916c67f9cf0ee4 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 10 May 2024 12:55:55 -0600 Subject: [PATCH 27/33] update script --- scripts/flexpower_rts/centralized_script.jl | 58 ++++++++++++--------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index 019486ad..b94236a5 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -17,13 +17,38 @@ const PSB = PowerSystemCaseBuilder const HSS = HybridSystemsSimulations # Load Optimization and Useful Packages -using Xpress using JuMP using Logging using Dates using CSV using TimeSeries +@static if haskey(ENV, "NREL_CLUSTER") + using Gurobi + mipgap = 0.001 + optimizer = optimizer_with_attributes( + Gurobi.Optimizer, + "Threads" => (length(Sys.cpu_info()) ÷ 2) - 1, + "MIPGap" => mipgap, + "TimeLimit" => 3000 + ) +else + using Xpress + mipgap = 0.01 + optimizer = optimizer_with_attributes( + Xpress.Optimizer, + "MAXTIME" => 3000, # Stop after 50 Minutes + "THREADS" => length(Sys.cpu_info()) ÷ 2, + "MIPRELSTOP" => mipgap, + ) +end + +if isempty(ARGS) + push!(ARGS, "use_services") + push!(ARGS, "2020-07-10T00:00:00") + push!(ARGS, "4") +end + ############################### ######## Load Scripts ######### ############################### @@ -75,14 +100,10 @@ served_fraction_map = Dict( "Flex_Down" => 0.1, ) -if isempty(ARGS) +if ARGS[1] == "use_services" formulation = HybridDispatchWithReserves else - if ARGS[1] == "use_services" - formulation = HybridDispatchWithReserves - else - formulation = HybridEnergyOnlyDispatch - end + formulation = HybridEnergyOnlyDispatch end for sys in [sys_rts_da, sys_rts_rt] @@ -158,14 +179,8 @@ set_device_model!( ###### Simulation Params ###### ############################### -mipgap = 0.01 -if isempty(ARGS) - starttime = DateTime("2020-07-10T00:00:00") - num_steps = 4 -else - starttime = DateTime(ARGS[2]) - num_steps = parse(Int, ARGS[3]) -end +starttime = DateTime(ARGS[2]) +num_steps = parse(Int, ARGS[3]) models = SimulationModels( decision_models=[ @@ -173,12 +188,7 @@ models = SimulationModels( template_uc_copperplate, sys_rts_da; name="UC", - optimizer=optimizer_with_attributes( - Xpress.Optimizer, - "MAXTIME" => 3000, # Stop after 50 Minutes - "THREADS" => length(Sys.cpu_info()) ÷ 2, - "MIPRELSTOP" => mipgap, - ), + optimizer=optimizer, system_to_file=false, initialize_model=true, optimizer_solve_log_print=false, @@ -191,11 +201,7 @@ models = SimulationModels( template_ed_copperplate, sys_rts_rt; name="ED", - optimizer=optimizer_with_attributes( - Xpress.Optimizer, - "THREADS" => length(Sys.cpu_info()) ÷ 2, - "MIPRELSTOP" => mipgap, - ), + optimizer=optimizer, system_to_file=false, initialize_model=true, optimizer_solve_log_print=false, From 996005db78ca03498b8eae260964e33689f2b1f0 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 10 May 2024 12:56:03 -0600 Subject: [PATCH 28/33] add checks for attributes --- src/hybrid_system_constructor.jl | 47 ++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/hybrid_system_constructor.jl b/src/hybrid_system_constructor.jl index dbb489d0..09c8a958 100644 --- a/src/hybrid_system_constructor.jl +++ b/src/hybrid_system_constructor.jl @@ -81,6 +81,23 @@ function PSI.construct_device!( ) end + if PSI.get_attribute(model, "cycling") + if PSI.built_for_recurrent_solves(container) + PSI.add_parameters!( + container, + CyclingChargeLimitParameter, + _hybrids_with_storage, + model, + ) + PSI.add_parameters!( + container, + CyclingDischargeLimitParameter, + _hybrids_with_storage, + model, + ) + end + end + if PSI.get_attribute(model, "regularization") PSI.add_variables!(container, ChargeRegularizationVariable, devices, D()) PSI.add_variables!(container, DischargeRegularizationVariable, devices, D()) @@ -658,22 +675,22 @@ function PSI.construct_device!( PSI.add_variables!(container, CyclingChargeUsage, _hybrids_with_storage, D()) PSI.add_variables!(container, CyclingDischargeUsage, _hybrids_with_storage, D()) - #= - if PSI.built_for_recurrent_solves(container) - PSI.add_parameters!( - container, - CyclingChargeLimitParameter, - _hybrids_with_storage, - model, - ) - PSI.add_parameters!( - container, - CyclingDischargeLimitParameter, - _hybrids_with_storage, - model, - ) + if PSI.get_attribute(model, "cycling") + if PSI.built_for_recurrent_solves(container) + PSI.add_parameters!( + container, + CyclingChargeLimitParameter, + _hybrids_with_storage, + model, + ) + PSI.add_parameters!( + container, + CyclingDischargeLimitParameter, + _hybrids_with_storage, + model, + ) + end end - =# if PSI.get_attribute(model, "regularization") PSI.add_variables!(container, ChargeRegularizationVariable, devices, D()) From 0ba878b0e34e45534a06a12e09853661bc84fdf1 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Fri, 10 May 2024 13:10:42 -0600 Subject: [PATCH 29/33] formatter --- scripts/flexpower_rts/centralized_script.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index b94236a5..7d24394f 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -30,17 +30,17 @@ using TimeSeries Gurobi.Optimizer, "Threads" => (length(Sys.cpu_info()) ÷ 2) - 1, "MIPGap" => mipgap, - "TimeLimit" => 3000 + "TimeLimit" => 3000, ) else using Xpress mipgap = 0.01 optimizer = optimizer_with_attributes( - Xpress.Optimizer, - "MAXTIME" => 3000, # Stop after 50 Minutes - "THREADS" => length(Sys.cpu_info()) ÷ 2, - "MIPRELSTOP" => mipgap, - ) + Xpress.Optimizer, + "MAXTIME" => 3000, # Stop after 50 Minutes + "THREADS" => length(Sys.cpu_info()) ÷ 2, + "MIPRELSTOP" => mipgap, + ) end if isempty(ARGS) From 4cb5654f234699428748e69ee95591f036fb6a60 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 14 May 2024 13:09:58 -0600 Subject: [PATCH 30/33] update scripts --- scripts/flexpower_rts/centralized_script.jl | 3 +- scripts/flexpower_rts/cooptimizer_script.jl | 52 +++++++++++++-------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/scripts/flexpower_rts/centralized_script.jl b/scripts/flexpower_rts/centralized_script.jl index 7d24394f..92fdb576 100644 --- a/scripts/flexpower_rts/centralized_script.jl +++ b/scripts/flexpower_rts/centralized_script.jl @@ -1,3 +1,4 @@ +using Revise using Pkg Pkg.activate("test") Pkg.instantiate() @@ -34,7 +35,7 @@ using TimeSeries ) else using Xpress - mipgap = 0.01 + mipgap = 0.03 optimizer = optimizer_with_attributes( Xpress.Optimizer, "MAXTIME" => 3000, # Stop after 50 Minutes diff --git a/scripts/flexpower_rts/cooptimizer_script.jl b/scripts/flexpower_rts/cooptimizer_script.jl index 416a6945..912c76a9 100644 --- a/scripts/flexpower_rts/cooptimizer_script.jl +++ b/scripts/flexpower_rts/cooptimizer_script.jl @@ -17,13 +17,38 @@ const PSB = PowerSystemCaseBuilder const HSS = HybridSystemsSimulations # Load Optimization and Useful Packages -using Xpress using JuMP using Logging using Dates using CSV using TimeSeries +@static if haskey(ENV, "NREL_CLUSTER") + using Gurobi + mipgap = 0.001 + optimizer = optimizer_with_attributes( + Gurobi.Optimizer, + "Threads" => (length(Sys.cpu_info()) ÷ 2) - 1, + "MIPGap" => mipgap, + "TimeLimit" => 3000, + ) +else + using Xpress + mipgap = 0.03 + optimizer = optimizer_with_attributes( + Xpress.Optimizer, + "MAXTIME" => 3000, # Stop after 50 Minutes + "THREADS" => length(Sys.cpu_info()) ÷ 2, + "MIPRELSTOP" => mipgap, + ) +end + +if isempty(ARGS) + push!(ARGS, "use_services") + push!(ARGS, "2020-07-10T00:00:00") + push!(ARGS, "2") +end + ### Run read centralized results first ### include("../get_templates.jl") include("../modify_systems.jl") @@ -79,32 +104,19 @@ transform_single_time_series!(sys_rts_da, horizon_DA, interval_DA) transform_single_time_series!(sys_rts_merchant_da, horizon_DA * 12, interval_DA) transform_single_time_series!(sys_rts_merchant_rt, horizon_RT, interval_RT) -if isempty(ARGS) +if ARGS[1] == "use_services" results_prices = "HybridDispatchWithReserves" formulation = MerchantHybridCooptimizerCase else - if ARGS[1] == "use_services" - results_prices = "HybridDispatchWithReserves" - formulation = MerchantHybridCooptimizerCase - else - results_prices = "HybridEnergyOnlyDispatch" - formulation = MerchantHybridEnergyCase - end + results_prices = "HybridEnergyOnlyDispatch" + formulation = MerchantHybridEnergyCase end -mipgap = 0.001 -if isempty(ARGS) - starttime = DateTime("2020-07-10T00:00:00") - num_steps = 2 -else - starttime = DateTime(ARGS[2]) - num_steps = parse(Int, ARGS[3]) -end +starttime = DateTime(ARGS[2]) +num_steps = parse(Int, ARGS[3]) results_folder = joinpath( - @__DIR__, - "../..", - "centralized_sim_HybridDispatchWithReserves_2020-07-10T00:00:00", + "/Users/jlara/.julia/dev/HybridSystemsSimulations/centralized_sim_HybridDispatchWithReserves_2020-07-10T00:00:00", ) results_dcp = SimulationResults(results_folder; ignore_status=true) From 7ae10bfea99c80532756103aed044334bb536070 Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 14 May 2024 13:10:09 -0600 Subject: [PATCH 31/33] fixes for ffs --- src/add_constraints.jl | 78 ++++++++++++++++++++---------------------- src/add_parameters.jl | 2 +- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/add_constraints.jl b/src/add_constraints.jl index fdb4356e..b4609869 100644 --- a/src/add_constraints.jl +++ b/src/add_constraints.jl @@ -891,25 +891,25 @@ function _add_constraints_cyclingcharge!( ci_name = PSY.get_name(device) storage = PSY.get_storage(device) efficiency = PSY.get_efficiency(storage) - if PSI.built_for_recurrent_solves(container) - param_value = - PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] - con_cycling_ch[ci_name] = JuMP.@constraint( - PSI.get_jump_model(container), - efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= - param_value - ) - else - E_max = PSY.get_state_of_charge_limits(storage).max - cycles_per_day = PSY.get_cycle_limits(storage) - cycles_in_horizon = - cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY - con_cycling_ch[ci_name] = JuMP.@constraint( - PSI.get_jump_model(container), - efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= - cycles_in_horizon * E_max - ) - end + #if PSI.built_for_recurrent_solves(container) + # param_value = + # PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] + # con_cycling_ch[ci_name] = JuMP.@constraint( + # PSI.get_jump_model(container), + # efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= + # param_value + # ) + #else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= + cycles_in_horizon * E_max + ) + #end end return end @@ -1054,27 +1054,25 @@ function _add_constraints_cyclingdischarge!( ci_name = PSY.get_name(device) storage = PSY.get_storage(device) efficiency = PSY.get_efficiency(storage) - if PSI.built_for_recurrent_solves(container) - param_value = - PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] - con_cycling_ds[ci_name] = JuMP.@constraint( - PSI.get_jump_model(container), - (1.0 / efficiency.out) * - fraction_of_hour * - sum(discharge_var[ci_name, :]) <= param_value - ) - else - E_max = PSY.get_state_of_charge_limits(storage).max - cycles_per_day = PSY.get_cycle_limits(storage) - cycles_in_horizon = - cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY - con_cycling_ds[ci_name] = JuMP.@constraint( - PSI.get_jump_model(container), - (1.0 / efficiency.out) * - fraction_of_hour * - sum(discharge_var[ci_name, :]) <= cycles_in_horizon * E_max - ) - end + #if PSI.built_for_recurrent_solves(container) + # param_value = + # PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] + # con_cycling_ds[ci_name] = JuMP.@constraint( + # PSI.get_jump_model(container), + # (1.0 / efficiency.out) * + # fraction_of_hour * + # sum(discharge_var[ci_name, :]) <= param_value + # ) + #else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * fraction_of_hour * sum(discharge_var[ci_name, :]) <= cycles_in_horizon * E_max + ) + #end end return end diff --git a/src/add_parameters.jl b/src/add_parameters.jl index cb03417a..00ba2669 100644 --- a/src/add_parameters.jl +++ b/src/add_parameters.jl @@ -419,7 +419,7 @@ end ################### Cycling Battery Parameters #################### ################################################################### -function PSI.add_parameters!( +function PSI._add_parameters!( container::PSI.OptimizationContainer, ::Type{T}, devices::V, From ad2d61a2910e14c92559e27b8c7ffe6ccdf30a9c Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 14 May 2024 13:10:22 -0600 Subject: [PATCH 32/33] add ff for models without reserves --- src/feedforwards.jl | 109 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 4 deletions(-) diff --git a/src/feedforwards.jl b/src/feedforwards.jl index 26882ba9..01bc25b8 100644 --- a/src/feedforwards.jl +++ b/src/feedforwards.jl @@ -68,7 +68,6 @@ PSI.get_default_parameter_type(::CyclingDischargeLimitFeedforward, _) = PSI.get_optimization_container_key(ff::CyclingDischargeLimitFeedforward) = ff.optimization_container_key -#= function PSI.add_feedforward_arguments!( container::PSI.OptimizationContainer, model::PSI.DeviceModel, @@ -79,12 +78,13 @@ function PSI.add_feedforward_arguments!( end return end -=# + +# function _add_cycling_feedfo function PSI._add_feedforward_arguments!( container::PSI.OptimizationContainer, device_model::PSI.DeviceModel, - devices::Vector{D}, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, ff::U, ) where { D <: PSY.HybridSystem, @@ -96,7 +96,18 @@ function PSI._add_feedforward_arguments!( ) end parameter_type = PSI.get_default_parameter_type(ff, D) - PSI.add_parameters!(container, parameter_type, devices, device_model) + PSI._add_parameters!(container, parameter_type, devices, device_model) + return +end + +function PSI._add_feedforward_arguments!( + container::PSI.OptimizationContainer, + model::PSI.DeviceModel, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + ff::PSI.AbstractAffectFeedforward, +) where {D <: PSY.HybridSystem} + parameter_type = PSI.get_default_parameter_type(ff, D) + PSI.add_parameters!(container, parameter_type, ff, model, devices) return end @@ -165,6 +176,50 @@ function PSI.add_feedforward_constraints!( return end +function PSI.add_feedforward_constraints!( + container::PSI.OptimizationContainer, + device_model::PSI.DeviceModel{D, HybridEnergyOnlyDispatch}, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + ff::CyclingChargeLimitFeedforward, +) where {D <: PSY.HybridSystem} + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + charge_var = PSI.get_variable(container, BatteryCharge(), D) + T = FeedForwardCyclingChargeConstraint + con_cycling_ch = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingChargeLimitParameter(), D)[ci_name] + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= + param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ch[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + efficiency.in * fraction_of_hour * sum(charge_var[ci_name, :]) <= + cycles_in_horizon * E_max + ) + end + end + return +end + function PSI.add_feedforward_constraints!( container::PSI.OptimizationContainer, device_model::PSI.DeviceModel, @@ -221,6 +276,52 @@ function PSI.add_feedforward_constraints!( return end +function PSI.add_feedforward_constraints!( + container::PSI.OptimizationContainer, + device_model::PSI.DeviceModel{D, HybridEnergyOnlyDispatch}, + devices::Union{Vector{D}, IS.FlattenIteratorWrapper{D}}, + ff::CyclingDischargeLimitFeedforward, +) where {D <: PSY.HybridSystem} + time_steps = PSI.get_time_steps(container) + resolution = PSI.get_resolution(container) + fraction_of_hour = Dates.value(Dates.Minute(resolution)) / PSI.MINUTES_IN_HOUR + names = [PSY.get_name(d) for d in devices] + discharge_var = PSI.get_variable(container, BatteryDischarge(), D) + T = FeedForwardCyclingDischargeConstraint + con_cycling_ds = PSI.add_constraints_container!(container, T(), D, names) + for device in devices + ci_name = PSY.get_name(device) + storage = PSY.get_storage(device) + efficiency = PSY.get_efficiency(storage) + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + if PSI.built_for_recurrent_solves(container) + param_value = + PSI.get_parameter_array(container, CyclingDischargeLimitParameter(), D)[ci_name] + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum(discharge_var[ci_name, :]) <= param_value + ) + else + E_max = PSY.get_state_of_charge_limits(storage).max + cycles_per_day = PSY.get_cycle_limits(storage) + cycles_in_horizon = + cycles_per_day * fraction_of_hour * length(time_steps) / HOURS_IN_DAY + con_cycling_ds[ci_name] = JuMP.@constraint( + PSI.get_jump_model(container), + (1.0 / efficiency.out) * + fraction_of_hour * + sum(discharge_var[ci_name, :]) <= cycles_in_horizon * E_max + ) + end + end + return +end + function PSI.update_parameter_values!( model::PSI.DecisionModel, key::PSI.ParameterKey{T, U}, From bc572a9a0285c52ea371867d47da6b648e9c893f Mon Sep 17 00:00:00 2001 From: Jose Daniel Lara Date: Tue, 14 May 2024 13:10:43 -0600 Subject: [PATCH 33/33] move code around --- src/HybridSystemsSimulations.jl | 4 ++-- src/core/parameters.jl | 3 --- src/hybrid_system_constructor.jl | 5 +++++ src/hybrid_system_decision_models.jl | 22 ---------------------- 4 files changed, 7 insertions(+), 27 deletions(-) diff --git a/src/HybridSystemsSimulations.jl b/src/HybridSystemsSimulations.jl index b81b05c1..b18cd45c 100644 --- a/src/HybridSystemsSimulations.jl +++ b/src/HybridSystemsSimulations.jl @@ -55,8 +55,8 @@ export StrongDualityCut export DayAheadEnergyPrice export RealTimeEnergyPrice export AncillaryServicePrice -export ChargeCycleLimit -export DischargeCycleLimit +export CyclingDischargeLimitParameter +export CyclingChargeLimitParameter import MathOptInterface import PowerSimulations diff --git a/src/core/parameters.jl b/src/core/parameters.jl index 89aa0b45..63b0b4eb 100644 --- a/src/core/parameters.jl +++ b/src/core/parameters.jl @@ -9,9 +9,6 @@ struct DayAheadEnergyPrice <: PSI.ObjectiveFunctionParameter end struct RealTimeEnergyPrice <: PSI.ObjectiveFunctionParameter end struct AncillaryServicePrice <: PSI.ObjectiveFunctionParameter end -struct ChargeCycleLimit <: PSI.RightHandSideParameter end -struct DischargeCycleLimit <: PSI.RightHandSideParameter end - struct EnergyTargetParameter <: PSI.VariableValueParameter end struct CyclingChargeLimitParameter <: PSI.VariableValueParameter end struct CyclingDischargeLimitParameter <: PSI.VariableValueParameter end diff --git a/src/hybrid_system_constructor.jl b/src/hybrid_system_constructor.jl index 09c8a958..340678ed 100644 --- a/src/hybrid_system_constructor.jl +++ b/src/hybrid_system_constructor.jl @@ -82,6 +82,7 @@ function PSI.construct_device!( end if PSI.get_attribute(model, "cycling") + #= if PSI.built_for_recurrent_solves(container) PSI.add_parameters!( container, @@ -96,6 +97,7 @@ function PSI.construct_device!( model, ) end + =# end if PSI.get_attribute(model, "regularization") @@ -270,6 +272,7 @@ function PSI.construct_device!( ) end + PSI.add_feedforward_constraints!(container, model, devices) return end @@ -676,6 +679,7 @@ function PSI.construct_device!( PSI.add_variables!(container, CyclingChargeUsage, _hybrids_with_storage, D()) PSI.add_variables!(container, CyclingDischargeUsage, _hybrids_with_storage, D()) if PSI.get_attribute(model, "cycling") + #= if PSI.built_for_recurrent_solves(container) PSI.add_parameters!( container, @@ -690,6 +694,7 @@ function PSI.construct_device!( model, ) end + =# end if PSI.get_attribute(model, "regularization") diff --git a/src/hybrid_system_decision_models.jl b/src/hybrid_system_decision_models.jl index b2ace3d3..1a63296f 100644 --- a/src/hybrid_system_decision_models.jl +++ b/src/hybrid_system_decision_models.jl @@ -460,25 +460,3 @@ function PSI.add_feedforward_arguments!( end return end - -function PSI._add_feedforward_arguments!( - container::PSI.OptimizationContainer, - model::PSI.DeviceModel, - devices::Vector{T}, - ff::PSI.AbstractAffectFeedforward, -) where {T <: PSY.HybridSystem} - parameter_type = PSI.get_default_parameter_type(ff, T) - PSI.add_parameters!(container, parameter_type, ff, model, devices) - return -end - -function PSI._add_feedforward_arguments!( - container::PSI.OptimizationContainer, - model::PSI.DeviceModel, - devices::Vector{T}, - ff::PSI.FixValueFeedforward, -) where {T <: PSY.HybridSystem} - parameter_type = PSI.get_default_parameter_type(ff, T) - PSI.add_parameters!(container, parameter_type, ff, model, devices) - return -end