From 5ab8d8a4f8a4424eb8285f9318ad45a2bf56c290 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Wed, 11 Oct 2023 19:02:49 -0400 Subject: [PATCH 01/31] Refector reserves --- src/model/resources/hydro/hydro_res.jl | 56 ++---- src/model/resources/storage/storage_all.jl | 211 ++++++++------------- 2 files changed, 99 insertions(+), 168 deletions(-) diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index 8b86e5517a..bbd1b88645 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -182,48 +182,34 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = inputs["T"] # Number of time steps (hours) + T = 1:inputs["T"] # Number of time steps (hours) HYDRO_RES = inputs["HYDRO_RES"] + REG = inputs["REG"] + RSV = inputs["RSV"] - HYDRO_RES_REG_RSV = intersect(HYDRO_RES, inputs["REG"], inputs["RSV"]) # Set of reservoir hydro resources with both regulation and spinning reserves + HYDRO_RES_REG = intersect(HYDRO_RES, REG) # Set of reservoir hydro resources with regulation reserves + HYDRO_RES_RSV = intersect(HYDRO_RES, RSV) # Set of reservoir hydro resources with spinning reserves - HYDRO_RES_REG = intersect(HYDRO_RES, inputs["REG"]) # Set of reservoir hydro resources with regulation reserves - HYDRO_RES_RSV = intersect(HYDRO_RES, inputs["RSV"]) # Set of reservoir hydro resources with spinning reserves + vP = EP[:vP] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + eTotalCap = EP[:eTotalCap] - HYDRO_RES_REG_ONLY = setdiff(HYDRO_RES_REG, HYDRO_RES_RSV) # Set of reservoir hydro resources only with regulation reserves - HYDRO_RES_RSV_ONLY = setdiff(HYDRO_RES_RSV, HYDRO_RES_REG) # Set of reservoir hydro resources only with spinning reserves + # NOTE the load-bearing 1 * to create AffExpr and not VariableRef + max_up_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in T], 1 * vP[y, t]) + max_dn_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in T], 1 * vP[y, t]) - if !isempty(HYDRO_RES_REG_RSV) - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - cRegulation[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - cReserve[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - # Maximum discharging rate and contribution to reserves up must be less than power rating - cMaxReservesUp[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= EP[:eTotalCap][y] - # Maximum discharging rate and contribution to regulation down must be greater than zero - cMaxReservesDown[y in HYDRO_RES_REG_RSV, t in 1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0 - end) - end + S = HYDRO_RES_REG + add_similar_to_expression!(max_up_reserves_lhs[S, :], vREG[S, :]) + add_similar_to_expression!(max_dn_reserves_lhs[S, :], -vREG[S, :]) - if !isempty(HYDRO_RES_REG_ONLY) - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - cRegulation[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - # Maximum discharging rate and contribution to reserves up must be less than power rating - cMaxReservesUp[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= EP[:eTotalCap][y] - # Maximum discharging rate and contribution to regulation down must be greater than zero - cMaxReservesDown[y in HYDRO_RES_REG_ONLY, t in 1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0 - end) - end + S = HYDRO_RES_RSV + add_similar_to_expression!(max_up_reserves_lhs[S, :], vRSV[S, :]) - if !isempty(HYDRO_RES_RSV_ONLY) - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - cReserve[y in HYDRO_RES_RSV_ONLY, t in 1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - # Maximum discharging rate and contribution to reserves up must be less than power rating - cMaxReservesUp[y in HYDRO_RES_RSV_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= EP[:eTotalCap][y] - end) - end + @constraint(EP, [y in HYDRO_RES, t in T], max_up_reserves_lhs[y, t] <= eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES, t in T], max_dn_reserves_lhs[y, t] >= 0) + @constraint(EP, [y in HYDRO_RES_REG, t in T], vREG[y, t] <= dfGen[y,:Reg_Max]*eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_RSV, t in T], vRSV[y, t] <= dfGen[y,:Rsv_Max]*eTotalCap[y]) end diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index c9d69da1d1..723b9914f2 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -159,137 +159,82 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) CapacityReserveMargin = setup["CapacityReserveMargin"] STOR_ALL = inputs["STOR_ALL"] - - STOR_REG_RSV = intersect(STOR_ALL, inputs["REG"], inputs["RSV"]) # Set of storage resources with both REG and RSV reserves - - STOR_REG = intersect(STOR_ALL, inputs["REG"]) # Set of storage resources with REG reserves - STOR_RSV = intersect(STOR_ALL, inputs["RSV"]) # Set of storage resources with RSV reserves - - STOR_NO_RES = setdiff(STOR_ALL, STOR_REG, STOR_RSV) # Set of storage resources with no reserves - - STOR_REG_ONLY = setdiff(STOR_REG, STOR_RSV) # Set of storage resources only with REG reserves - STOR_RSV_ONLY = setdiff(STOR_RSV, STOR_REG) # Set of storage resources only with RSV reserves - - if !isempty(STOR_REG_RSV) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed discharge power capacity - [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] - [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] - - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_REG_RSV, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t]-EP[:vRSV_charge][y,t] >= 0 - - # Maximum discharging rate and contribution to reserves down must be greater than zero - # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG_discharge][y,t] >= 0 - - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - [y in STOR_REG_RSV, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - end) - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - else - @constraints(EP, begin - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - end - - end - if !isempty(STOR_REG_ONLY) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] - - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_REG_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t] >= 0 - - # Maximum discharging rate and contribution to reserves down must be greater than zero - # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] - EP[:vREG_discharge][y,t] >= 0 - - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - [y in STOR_REG_ONLY, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - end) - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - else - @constraints(EP, begin - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - end - - end - if !isempty(STOR_RSV_ONLY) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] - - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_RSV_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vRSV_charge][y,t] >= 0 - end) - - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - else - @constraints(EP, begin - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - end - end - if !isempty(STOR_NO_RES) - # Maximum discharging rate must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - else - @constraints(EP, begin - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t]/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] - end) - end - end + REG = inputs["REG"] + RSV = inputs["RSV"] + + STOR_REG = intersect(STOR_ALL, REG) # Set of storage resources with REG reserves + STOR_RSV = intersect(STOR_ALL, RSV) # Set of storage resources with RSV reserves + + vP = EP[:vP] + vS = EP[:vS] + vCHARGE = EP[:vCHARGE] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + vREG_charge = EP[:vREG_charge] + vRSV_charge = EP[:vRSV_charge] + vREG_discharge = EP[:vREG_discharge] + vRSV_discharge = EP[:vRSV_discharge] + vCAPRES_discharge = EP[:vCAPRES_discharge] + eTotalCap = EP[:eTotalCap] + eTotalCapEnergy = EP[:eTotalCapEnergy] + + eff_up(y) = dfGen[y, :Eff_Up] + eff_down(y) = dfGen[y, :Eff_Down] + + # Maximum storage contribution to reserves is a specified fraction of installed capacity + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) + + # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) + + # Maximum charging rate plus contribution to reserves up must be greater than zero + # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + + S = STOR_REG + add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) + + S = STOR_RSV + add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) + + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) + + # Maximum discharging rate and contribution to reserves down must be greater than zero + # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply + @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) + + # Maximum charging rate plus contribution to regulation down must be less than available storage capacity + @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) + # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, + # this constraint is set in functions below for each storage type + + # Maximum discharging rate and contribution to reserves up must be less than power rating + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + + S = STOR_REG + add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) + S = STOR_RSV + add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) + + S = STOR_ALL + if CapacityReserveMargin > 0 + add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) + end + + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) + + # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + S = STOR_REG + add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) + S = STOR_RSV + add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) + S = STOR_ALL + if CapacityReserveMargin > 0 + add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) + end + + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) end From 25a065932a6b6c38d2683f505ad038c5f525302d Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 15:30:57 -0400 Subject: [PATCH 02/31] Revert changes to storage --- src/model/resources/storage/storage_all.jl | 211 +++++++++++++-------- 1 file changed, 133 insertions(+), 78 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 723b9914f2..c9d69da1d1 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -159,82 +159,137 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) CapacityReserveMargin = setup["CapacityReserveMargin"] STOR_ALL = inputs["STOR_ALL"] - REG = inputs["REG"] - RSV = inputs["RSV"] - - STOR_REG = intersect(STOR_ALL, REG) # Set of storage resources with REG reserves - STOR_RSV = intersect(STOR_ALL, RSV) # Set of storage resources with RSV reserves - - vP = EP[:vP] - vS = EP[:vS] - vCHARGE = EP[:vCHARGE] - vREG = EP[:vREG] - vRSV = EP[:vRSV] - vREG_charge = EP[:vREG_charge] - vRSV_charge = EP[:vRSV_charge] - vREG_discharge = EP[:vREG_discharge] - vRSV_discharge = EP[:vRSV_discharge] - vCAPRES_discharge = EP[:vCAPRES_discharge] - eTotalCap = EP[:eTotalCap] - eTotalCapEnergy = EP[:eTotalCapEnergy] - - eff_up(y) = dfGen[y, :Eff_Up] - eff_down(y) = dfGen[y, :Eff_Down] - - # Maximum storage contribution to reserves is a specified fraction of installed capacity - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) - - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" - - S = STOR_REG - add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) - - S = STOR_RSV - add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) - - @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) - - # Maximum discharging rate and contribution to reserves down must be greater than zero - # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) - - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - - # Maximum discharging rate and contribution to reserves up must be less than power rating - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" - - S = STOR_REG - add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) - S = STOR_RSV - add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) - - S = STOR_ALL - if CapacityReserveMargin > 0 - add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) - end - - @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) - - # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" - S = STOR_REG - add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) - S = STOR_RSV - add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) - S = STOR_ALL - if CapacityReserveMargin > 0 - add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) - end - - @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) + + STOR_REG_RSV = intersect(STOR_ALL, inputs["REG"], inputs["RSV"]) # Set of storage resources with both REG and RSV reserves + + STOR_REG = intersect(STOR_ALL, inputs["REG"]) # Set of storage resources with REG reserves + STOR_RSV = intersect(STOR_ALL, inputs["RSV"]) # Set of storage resources with RSV reserves + + STOR_NO_RES = setdiff(STOR_ALL, STOR_REG, STOR_RSV) # Set of storage resources with no reserves + + STOR_REG_ONLY = setdiff(STOR_REG, STOR_RSV) # Set of storage resources only with REG reserves + STOR_RSV_ONLY = setdiff(STOR_RSV, STOR_REG) # Set of storage resources only with RSV reserves + + if !isempty(STOR_REG_RSV) + # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up + @constraints(EP, begin + # Maximum storage contribution to reserves is a specified fraction of installed discharge power capacity + [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] + [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] + + # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging + [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] + [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] + + # Maximum charging rate plus contribution to reserves up must be greater than zero + # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand + [y in STOR_REG_RSV, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t]-EP[:vRSV_charge][y,t] >= 0 + + # Maximum discharging rate and contribution to reserves down must be greater than zero + # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply + [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG_discharge][y,t] >= 0 + + # Maximum charging rate plus contribution to regulation down must be less than available storage capacity + [y in STOR_REG_RSV, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] + # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, + # this constraint is set in functions below for each storage type + end) + # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less + # wrapping from end of sample period to start of sample period for energy capacity constraint + if CapacityReserveMargin > 0 + @constraints(EP, begin + [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + else + @constraints(EP, begin + [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + end + + end + if !isempty(STOR_REG_ONLY) + # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up + @constraints(EP, begin + # Maximum storage contribution to reserves is a specified fraction of installed capacity + [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] + + # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging + [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] + + # Maximum charging rate plus contribution to reserves up must be greater than zero + # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand + [y in STOR_REG_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t] >= 0 + + # Maximum discharging rate and contribution to reserves down must be greater than zero + # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply + [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] - EP[:vREG_discharge][y,t] >= 0 + + # Maximum charging rate plus contribution to regulation down must be less than available storage capacity + [y in STOR_REG_ONLY, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] + # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, + # this constraint is set in functions below for each storage type + end) + # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less + # wrapping from end of sample period to start of sample period for energy capacity constraint + if CapacityReserveMargin > 0 + @constraints(EP, begin + [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + else + @constraints(EP, begin + [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + end + + end + if !isempty(STOR_RSV_ONLY) + # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up + @constraints(EP, begin + # Maximum storage contribution to reserves is a specified fraction of installed capacity + [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] + + # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging + [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] + + # Maximum charging rate plus contribution to reserves up must be greater than zero + # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand + [y in STOR_RSV_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vRSV_charge][y,t] >= 0 + end) + + # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, + # this constraint is set in functions below for each storage type + + # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less + # wrapping from end of sample period to start of sample period for energy capacity constraint + if CapacityReserveMargin > 0 + @constraints(EP, begin + [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + else + @constraints(EP, begin + [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + end + end + if !isempty(STOR_NO_RES) + # Maximum discharging rate must be less than power rating OR available stored energy in prior period, whichever is less + # wrapping from end of sample period to start of sample period for energy capacity constraint + if CapacityReserveMargin > 0 + @constraints(EP, begin + [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] + [y in STOR_NO_RES, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + else + @constraints(EP, begin + [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:eTotalCap][y] + [y in STOR_NO_RES, t=1:T], EP[:vP][y,t]/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + end) + end + end end From 4330877ae4103cb225f849c53f75264c4438c6bc Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:02:40 -0400 Subject: [PATCH 03/31] First set of constr --- src/model/resources/storage/storage_all.jl | 132 +++++++++++++++++---- 1 file changed, 112 insertions(+), 20 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index c9d69da1d1..1dadbe4062 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -170,17 +170,33 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) STOR_REG_ONLY = setdiff(STOR_REG, STOR_RSV) # Set of storage resources only with REG reserves STOR_RSV_ONLY = setdiff(STOR_RSV, STOR_REG) # Set of storage resources only with RSV reserves + vP = EP[:vP] + vS = EP[:vS] + vCHARGE = EP[:vCHARGE] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + vREG_charge = EP[:vREG_charge] + vRSV_charge = EP[:vRSV_charge] + vREG_discharge = EP[:vREG_discharge] + vRSV_discharge = EP[:vRSV_discharge] + vCAPRES_discharge = EP[:vCAPRES_discharge] + eTotalCap = EP[:eTotalCap] + eTotalCapEnergy = EP[:eTotalCapEnergy] + + eff_up(y) = dfGen[y, :Eff_Up] + eff_down(y) = dfGen[y, :Eff_Down] + + # Maximum storage contribution to reserves is a specified fraction of installed capacity + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) + + # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) + if !isempty(STOR_REG_RSV) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed discharge power capacity - [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_REG_RSV, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] - [y in STOR_REG_RSV, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] - # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand [y in STOR_REG_RSV, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t]-EP[:vRSV_charge][y,t] >= 0 @@ -212,12 +228,6 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(STOR_REG_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_REG_ONLY, t=1:T], EP[:vREG][y,t] == EP[:vREG_charge][y,t]+EP[:vREG_discharge][y,t] - # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand [y in STOR_REG_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t] >= 0 @@ -249,12 +259,6 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(STOR_RSV_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum storage contribution to reserves is a specified fraction of installed capacity - [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - - # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - [y in STOR_RSV_ONLY, t=1:T], EP[:vRSV][y,t] == EP[:vRSV_charge][y,t]+EP[:vRSV_discharge][y,t] - # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand [y in STOR_RSV_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vRSV_charge][y,t] >= 0 @@ -293,3 +297,91 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) end end end + +# function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) +# +# dfGen = inputs["dfGen"] +# T = inputs["T"] +# p = inputs["hours_per_subperiod"] +# CapacityReserveMargin = setup["CapacityReserveMargin"] +# +# STOR_ALL = inputs["STOR_ALL"] +# REG = inputs["REG"] +# RSV = inputs["RSV"] +# +# STOR_REG = intersect(STOR_ALL, REG) # Set of storage resources with REG reserves +# STOR_RSV = intersect(STOR_ALL, RSV) # Set of storage resources with RSV reserves +# +# vP = EP[:vP] +# vS = EP[:vS] +# vCHARGE = EP[:vCHARGE] +# vREG = EP[:vREG] +# vRSV = EP[:vRSV] +# vREG_charge = EP[:vREG_charge] +# vRSV_charge = EP[:vRSV_charge] +# vREG_discharge = EP[:vREG_discharge] +# vRSV_discharge = EP[:vRSV_discharge] +# vCAPRES_discharge = EP[:vCAPRES_discharge] +# eTotalCap = EP[:eTotalCap] +# eTotalCapEnergy = EP[:eTotalCapEnergy] +# +# eff_up(y) = dfGen[y, :Eff_Up] +# eff_down(y) = dfGen[y, :Eff_Down] +# +# # Maximum storage contribution to reserves is a specified fraction of installed capacity +# @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) +# @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) +# +# # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging +# @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) +# @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) +# +# # Maximum charging rate plus contribution to reserves up must be greater than zero +# # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand +# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" +# +# S = STOR_REG +# add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) +# +# S = STOR_RSV +# add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) +# +# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) +# +# # Maximum discharging rate and contribution to reserves down must be greater than zero +# # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply +# @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) +# +# # Maximum charging rate plus contribution to regulation down must be less than available storage capacity +# @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) +# # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, +# # this constraint is set in functions below for each storage type +# +# # Maximum discharging rate and contribution to reserves up must be less than power rating +# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" +# +# S = STOR_REG +# add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) +# S = STOR_RSV +# add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) +# +# S = STOR_ALL +# if CapacityReserveMargin > 0 +# add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) +# end +# +# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) +# +# # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period +# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" +# S = STOR_REG +# add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) +# S = STOR_RSV +# add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) +# S = STOR_ALL +# if CapacityReserveMargin > 0 +# add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) +# end +# +# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) +# end From f363eaa1a7683d4ac3e8f39dc484c0608a750618 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:06:03 -0400 Subject: [PATCH 04/31] Second move --- src/model/resources/storage/storage_all.jl | 26 ++++++++++------------ 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 1dadbe4062..5108028227 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -194,13 +194,21 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) + # Maximum charging rate plus contribution to reserves up must be greater than zero + # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + + S = STOR_REG + add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) + + S = STOR_RSV + add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) + + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) + if !isempty(STOR_REG_RSV) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_REG_RSV, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t]-EP[:vRSV_charge][y,t] >= 0 - # Maximum discharging rate and contribution to reserves down must be greater than zero # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG_discharge][y,t] >= 0 @@ -228,10 +236,6 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(STOR_REG_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_REG_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vREG_charge][y,t] >= 0 - # Maximum discharging rate and contribution to reserves down must be greater than zero # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] - EP[:vREG_discharge][y,t] >= 0 @@ -258,12 +262,6 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) end if !isempty(STOR_RSV_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to reserves up must be greater than zero - # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - [y in STOR_RSV_ONLY, t=1:T], EP[:vCHARGE][y,t]-EP[:vRSV_charge][y,t] >= 0 - end) - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type From fced3ddda84562aeae2370b097e384789b3a25ef Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:08:33 -0400 Subject: [PATCH 05/31] Thirdmove --- src/model/resources/storage/storage_all.jl | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 5108028227..4d7b510f21 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -206,13 +206,13 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) + # Maximum discharging rate and contribution to reserves down must be greater than zero + # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply + @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) + if !isempty(STOR_REG_RSV) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum discharging rate and contribution to reserves down must be greater than zero - # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG_discharge][y,t] >= 0 - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity [y in STOR_REG_RSV, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, @@ -236,10 +236,6 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if !isempty(STOR_REG_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up @constraints(EP, begin - # Maximum discharging rate and contribution to reserves down must be greater than zero - # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] - EP[:vREG_discharge][y,t] >= 0 - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity [y in STOR_REG_ONLY, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, From 5834b790a6c5e7faa0f48db7e06ccaf2374cd9ff Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:19:47 -0400 Subject: [PATCH 06/31] Fifth move --- src/model/resources/storage/storage_all.jl | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 4d7b510f21..35207e59db 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -210,14 +210,14 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) + # Maximum charging rate plus contribution to regulation down must be less than available storage capacity + @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) + # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, + # this constraint is set in functions below for each storage type + if !isempty(STOR_REG_RSV) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - [y in STOR_REG_RSV, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - end) + # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less # wrapping from end of sample period to start of sample period for energy capacity constraint if CapacityReserveMargin > 0 @@ -235,12 +235,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) end if !isempty(STOR_REG_ONLY) # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - [y in STOR_REG_ONLY, t=1:T], dfGen[y,:Eff_Up]*(EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]) <= EP[:eTotalCapEnergy][y]-EP[:vS][y, hoursbefore(p,t,1)] - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - end) + # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less # wrapping from end of sample period to start of sample period for energy capacity constraint if CapacityReserveMargin > 0 From f59cec447b8a0d2ebf1b770773f909fca62446fd Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:27:01 -0400 Subject: [PATCH 07/31] Fifth move Move x/p <= y to x <= p * y This should be a mathematically identical formulation yet gives different numerical results. --- src/model/resources/storage/storage_all.jl | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 35207e59db..2d2b1e775b 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -223,12 +223,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end @@ -241,12 +241,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end @@ -261,12 +261,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vRSV_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end end @@ -276,12 +276,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t])/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_NO_RES, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t]/dfGen[y,:Eff_Down] <= EP[:vS][y, hoursbefore(p,t,1)] + [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end end From 11c683aad63f19504f897aeeee9d3a4f2e858200 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 17:49:11 -0400 Subject: [PATCH 08/31] Sixth move --- src/model/resources/storage/storage_all.jl | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 2d2b1e775b..51d9820ddc 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -223,12 +223,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end @@ -241,12 +241,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vREG_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end @@ -261,12 +261,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], (EP[:vP][y,t]+EP[:vRSV_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) end end @@ -276,7 +276,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], (EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]) <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] + [y in STOR_NO_RES, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] end) else @constraints(EP, begin From 723578ec2deab50e4db104704bf6e347a61ff251 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 20:42:23 -0400 Subject: [PATCH 09/31] Final changes --- src/model/resources/storage/storage_all.jl | 203 +++------------------ 1 file changed, 24 insertions(+), 179 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 51d9820ddc..b733098e02 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -154,21 +154,14 @@ end function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) dfGen = inputs["dfGen"] - T = inputs["T"] + T = 1:inputs["T"] p = inputs["hours_per_subperiod"] CapacityReserveMargin = setup["CapacityReserveMargin"] STOR_ALL = inputs["STOR_ALL"] - STOR_REG_RSV = intersect(STOR_ALL, inputs["REG"], inputs["RSV"]) # Set of storage resources with both REG and RSV reserves - - STOR_REG = intersect(STOR_ALL, inputs["REG"]) # Set of storage resources with REG reserves - STOR_RSV = intersect(STOR_ALL, inputs["RSV"]) # Set of storage resources with RSV reserves - - STOR_NO_RES = setdiff(STOR_ALL, STOR_REG, STOR_RSV) # Set of storage resources with no reserves - - STOR_REG_ONLY = setdiff(STOR_REG, STOR_RSV) # Set of storage resources only with REG reserves - STOR_RSV_ONLY = setdiff(STOR_RSV, STOR_REG) # Set of storage resources only with RSV reserves + STOR_REG = intersect(STOR_ALL, inputs["REG"]) # Set of storage resources with REG reserves + STOR_RSV = intersect(STOR_ALL, inputs["RSV"]) # Set of storage resources with RSV reserves vP = EP[:vP] vS = EP[:vS] @@ -187,190 +180,42 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) eff_down(y) = dfGen[y, :Eff_Down] # Maximum storage contribution to reserves is a specified fraction of installed capacity - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_REG, t in T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) + @constraint(EP, [y in STOR_REG, t in T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) + @constraint(EP, [y in STOR_RSV, t in T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" - + expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" S = STOR_REG add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) - S = STOR_RSV add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) - - @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) + @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] >= 0) # Maximum discharging rate and contribution to reserves down must be greater than zero # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) + @constraint(EP, [y in STOR_REG, t in T], vP[y, t] - vREG_discharge[y, t] >= 0) # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) + @constraint(EP, [y in STOR_REG, t in T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type - if !isempty(STOR_REG_RSV) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - else - @constraints(EP, begin - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - end - - end - if !isempty(STOR_REG_ONLY) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vREG_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - else - @constraints(EP, begin - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t] + EP[:vREG_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_REG_ONLY, t=1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - end - - end - if !isempty(STOR_RSV_ONLY) - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, - # this constraint is set in functions below for each storage type - - # Maximum discharging rate and contribution to reserves up must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - else - @constraints(EP, begin - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_RSV_ONLY, t=1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - end - end - if !isempty(STOR_NO_RES) - # Maximum discharging rate must be less than power rating OR available stored energy in prior period, whichever is less - # wrapping from end of sample period to start of sample period for energy capacity constraint - if CapacityReserveMargin > 0 - @constraints(EP, begin - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] + EP[:vCAPRES_discharge][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t]+EP[:vCAPRES_discharge][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - else - @constraints(EP, begin - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:eTotalCap][y] - [y in STOR_NO_RES, t=1:T], EP[:vP][y,t] <= EP[:vS][y, hoursbefore(p,t,1)] * dfGen[y, :Eff_Down] - end) - end - end + # Maximum discharging rate and contribution to reserves up must be less than power rating + expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + S = STOR_REG + add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) + S = STOR_RSV + add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) + if CapacityReserveMargin > 0 + S = STOR_ALL + add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) + end + @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= eTotalCap[y]) + # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period + @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) end - -# function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) -# -# dfGen = inputs["dfGen"] -# T = inputs["T"] -# p = inputs["hours_per_subperiod"] -# CapacityReserveMargin = setup["CapacityReserveMargin"] -# -# STOR_ALL = inputs["STOR_ALL"] -# REG = inputs["REG"] -# RSV = inputs["RSV"] -# -# STOR_REG = intersect(STOR_ALL, REG) # Set of storage resources with REG reserves -# STOR_RSV = intersect(STOR_ALL, RSV) # Set of storage resources with RSV reserves -# -# vP = EP[:vP] -# vS = EP[:vS] -# vCHARGE = EP[:vCHARGE] -# vREG = EP[:vREG] -# vRSV = EP[:vRSV] -# vREG_charge = EP[:vREG_charge] -# vRSV_charge = EP[:vRSV_charge] -# vREG_discharge = EP[:vREG_discharge] -# vRSV_discharge = EP[:vRSV_discharge] -# vCAPRES_discharge = EP[:vCAPRES_discharge] -# eTotalCap = EP[:eTotalCap] -# eTotalCapEnergy = EP[:eTotalCapEnergy] -# -# eff_up(y) = dfGen[y, :Eff_Up] -# eff_down(y) = dfGen[y, :Eff_Down] -# -# # Maximum storage contribution to reserves is a specified fraction of installed capacity -# @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) -# @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) -# -# # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging -# @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) -# @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) -# -# # Maximum charging rate plus contribution to reserves up must be greater than zero -# # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand -# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" -# -# S = STOR_REG -# add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) -# -# S = STOR_RSV -# add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) -# -# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) -# -# # Maximum discharging rate and contribution to reserves down must be greater than zero -# # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply -# @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) -# -# # Maximum charging rate plus contribution to regulation down must be less than available storage capacity -# @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) -# # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, -# # this constraint is set in functions below for each storage type -# -# # Maximum discharging rate and contribution to reserves up must be less than power rating -# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" -# -# S = STOR_REG -# add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) -# S = STOR_RSV -# add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) -# -# S = STOR_ALL -# if CapacityReserveMargin > 0 -# add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) -# end -# -# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) -# -# # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period -# expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" -# S = STOR_REG -# add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) -# S = STOR_RSV -# add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) -# S = STOR_ALL -# if CapacityReserveMargin > 0 -# add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) -# end -# -# @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) -# end From b1ff0d4a0230b0f670d45533ec821e6ab416f23d Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 12 Oct 2023 20:54:06 -0400 Subject: [PATCH 10/31] Minor cleanup --- src/model/resources/storage/storage_all.jl | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index b733098e02..59aa2cb81f 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -190,10 +190,8 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" - S = STOR_REG - add_similar_to_expression!(expr[S, :], -vREG_charge[S, :]) - S = STOR_RSV - add_similar_to_expression!(expr[S, :], -vRSV_charge[S, :]) + add_similar_to_expression!(expr[STOR_REG, :], -vREG_charge[STOR_REG, :]) + add_similar_to_expression!(expr[STOR_RSV, :], -vRSV_charge[STOR_RSV, :]) @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] >= 0) # Maximum discharging rate and contribution to reserves down must be greater than zero @@ -205,16 +203,13 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type - # Maximum discharging rate and contribution to reserves up must be less than power rating expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" - S = STOR_REG - add_similar_to_expression!(expr[S, :], vREG_discharge[S, :]) - S = STOR_RSV - add_similar_to_expression!(expr[S, :], vRSV_discharge[S, :]) + add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) + add_similar_to_expression!(expr[STOR_RSV, :], vRSTOR_RSVV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 - S = STOR_ALL - add_similar_to_expression!(expr[S, :], vCAPRES_discharge[S, :]) + add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) end + # Maximum discharging rate and contribution to reserves up must be less than power rating @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= eTotalCap[y]) # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) From 6d5bcc737e0255e2447e987d05f2c76705c8e5ae Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 13 Oct 2023 10:10:21 -0400 Subject: [PATCH 11/31] Typo in previously-unused branch --- src/model/resources/storage/storage_all.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 59aa2cb81f..2a4027cf14 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -205,7 +205,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) - add_similar_to_expression!(expr[STOR_RSV, :], vRSTOR_RSVV_discharge[STOR_RSV, :]) + add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) end From 92e13943b78851de82f141d70b24330dce678608 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 13 Oct 2023 10:10:37 -0400 Subject: [PATCH 12/31] Refactor storage_symmetric --- .../resources/storage/storage_symmetric.jl | 143 +++++------------- 1 file changed, 40 insertions(+), 103 deletions(-) diff --git a/src/model/resources/storage/storage_symmetric.jl b/src/model/resources/storage/storage_symmetric.jl index 446e9891f7..7962e456bf 100644 --- a/src/model/resources/storage/storage_symmetric.jl +++ b/src/model/resources/storage/storage_symmetric.jl @@ -52,106 +52,43 @@ Sets up variables and constraints specific to storage resources with symmetric c """ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) - dfGen = inputs["dfGen"] - T = inputs["T"] - CapacityReserveMargin = setup["CapacityReserveMargin"] - - STOR_SYMMETRIC = inputs["STOR_SYMMETRIC"] - - STOR_SYM_REG_RSV = intersect(STOR_SYMMETRIC, inputs["REG"], inputs["RSV"]) # Set of symmetric storage resources with both REG and RSV reserves - - STOR_SYM_REG = intersect(STOR_SYMMETRIC, inputs["REG"]) # Set of symmetric storage resources with REG reserves - STOR_SYM_RSV = intersect(STOR_SYMMETRIC, inputs["RSV"]) # Set of symmetric storage resources with RSV reserves - - STOR_SYM_NO_RES = setdiff(STOR_SYMMETRIC, STOR_SYM_REG, STOR_SYM_RSV) # Set of symmetric storage resources with no reserves - - STOR_SYM_REG_ONLY = setdiff(STOR_SYM_REG, STOR_SYM_RSV) # Set of symmetric storage resources only with REG reserves - STOR_SYM_RSV_ONLY = setdiff(STOR_SYM_RSV, STOR_SYM_REG) # Set of symmetric storage resources only with RSV reserves - - if !isempty(STOR_SYM_REG_RSV) - if CapacityReserveMargin > 0 - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating - [y in STOR_SYM_REG_RSV, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_REG_RSV, t in 1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]+EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - end) - else - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating - [y in STOR_SYM_REG_RSV, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]<= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_REG_RSV, t in 1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vRSV_discharge][y,t]+EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]<= EP[:eTotalCap][y] - end) - end - end - - if !isempty(STOR_SYM_REG_ONLY) - if CapacityReserveMargin > 0 - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating - [y in STOR_SYM_REG_ONLY, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_REG_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - end) - else - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating - [y in STOR_SYM_REG_ONLY, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]<= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_REG_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vREG_discharge][y,t]+EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]<= EP[:eTotalCap][y] - end) - end - end - - if !isempty(STOR_SYM_RSV_ONLY) - if CapacityReserveMargin > 0 - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate must be less than symmetric power rating - [y in STOR_SYM_RSV_ONLY, t in 1:T], EP[:vCHARGE][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_RSV_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t]+EP[:vCHARGE][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - end) - else - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - @constraints(EP, begin - # Maximum charging rate must be less than symmetric power rating - [y in STOR_SYM_RSV_ONLY, t in 1:T], EP[:vCHARGE][y,t]<= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - [y in STOR_SYM_RSV_ONLY, t in 1:T], EP[:vP][y,t]+EP[:vRSV_discharge][y,t]+EP[:vCHARGE][y,t]<= EP[:eTotalCap][y] - end) - end - end - - if !isempty(STOR_SYM_NO_RES) - if CapacityReserveMargin > 0 - @constraints(EP, begin - # Maximum charging rate must be less than symmetric power rating - [y in STOR_SYM_NO_RES, t in 1:T], EP[:vCHARGE][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge cannot be greater than capacity - [y in STOR_SYM_NO_RES, t in 1:T], EP[:vP][y,t]+EP[:vCHARGE][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - end) - else - @constraints(EP, begin - # Maximum charging rate must be less than symmetric power rating - [y in STOR_SYM_NO_RES, t in 1:T], EP[:vCHARGE][y,t]<= EP[:eTotalCap][y] - - # Max simultaneous charge and discharge cannot be greater than capacity - [y in STOR_SYM_NO_RES, t in 1:T], EP[:vP][y,t]+EP[:vCHARGE][y,t]<= EP[:eTotalCap][y] - end) - end - end - -end \ No newline at end of file + T = 1:inputs["T"] + CapacityReserveMargin = setup["CapacityReserveMargin"] > 0 + + SYMMETRIC = inputs["STOR_SYMMETRIC"] + + REG = intersect(SYMMETRIC, inputs["REG"]) + RSV = intersect(SYMMETRIC, inputs["RSV"]) + + vP = EP[:vP] + vS = EP[:vS] + vCHARGE = EP[:vCHARGE] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + vREG_charge = EP[:vREG_charge] + vRSV_charge = EP[:vRSV_charge] + vREG_discharge = EP[:vREG_discharge] + vRSV_discharge = EP[:vRSV_discharge] + vCAPRES_charge = EP[:vCAPRES_charge] + vCAPRES_discharge = EP[:vCAPRES_discharge] + eTotalCap = EP[:eTotalCap] + + # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating + expr = @expression(EP, [y in SYMMETRIC, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[REG, :], vREG_charge[REG, :]) + if CapacityReserveMargin + add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_charge[SYMMETRIC, :]) + end + @constraint(EP, [y in SYMMETRIC, t in T], expr[y, t] <= eTotalCap[y]) + + # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity + expr = @expression(EP, [y in SYMMETRIC, t in T], vP[y, t] + vCHARGE[y, t]) + add_similar_to_expression!(expr[REG, :], vREG_charge[REG, :]) + add_similar_to_expression!(expr[REG, :], vREG_discharge[REG, :]) + add_similar_to_expression!(expr[RSV, :], vRSV_discharge[RSV, :]) + if CapacityReserveMargin + add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_charge[SYMMETRIC, :]) + add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_discharge[SYMMETRIC, :]) + end + @constraint(EP, [y in SYMMETRIC, t in T], expr[y, t] <= eTotalCap[y]) +end From 36155241938c150c267f0d7729e90945d9b580aa Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 13 Oct 2023 10:22:45 -0400 Subject: [PATCH 13/31] Remove unneeded lines --- src/model/resources/storage/storage_symmetric.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/model/resources/storage/storage_symmetric.jl b/src/model/resources/storage/storage_symmetric.jl index 7962e456bf..27135fa929 100644 --- a/src/model/resources/storage/storage_symmetric.jl +++ b/src/model/resources/storage/storage_symmetric.jl @@ -10,7 +10,6 @@ function storage_symmetric!(EP::Model, inputs::Dict, setup::Dict) println("Storage Resources with Symmetric Charge/Discharge Capacity Module") - dfGen = inputs["dfGen"] Reserves = setup["Reserves"] CapacityReserveMargin = setup["CapacityReserveMargin"] @@ -61,10 +60,7 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) RSV = intersect(SYMMETRIC, inputs["RSV"]) vP = EP[:vP] - vS = EP[:vS] vCHARGE = EP[:vCHARGE] - vREG = EP[:vREG] - vRSV = EP[:vRSV] vREG_charge = EP[:vREG_charge] vRSV_charge = EP[:vRSV_charge] vREG_discharge = EP[:vREG_discharge] From 158d9c5e0a51160f04ca61e7ae3af737afb7e22e Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 13 Oct 2023 10:43:22 -0400 Subject: [PATCH 14/31] Refactor storage_asymmetric --- .../resources/storage/storage_asymmetric.jl | 40 ++++++------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/src/model/resources/storage/storage_asymmetric.jl b/src/model/resources/storage/storage_asymmetric.jl index 3f4a4e6c3a..4149afec55 100644 --- a/src/model/resources/storage/storage_asymmetric.jl +++ b/src/model/resources/storage/storage_asymmetric.jl @@ -42,35 +42,21 @@ Sets up variables and constraints specific to storage resources with asymmetric """ function storage_asymmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) - dfGen = inputs["dfGen"] - T = inputs["T"] - CapacityReserveMargin = setup["CapacityReserveMargin"] + T = 1:inputs["T"] + CapacityReserveMargin = setup["CapacityReserveMargin"] > 0 STOR_ASYMMETRIC = inputs["STOR_ASYMMETRIC"] - STOR_ASYM_REG = intersect(STOR_ASYMMETRIC, inputs["REG"]) # Set of asymmetric storage resources with REG reserves - STOR_ASYM_NO_REG = setdiff(STOR_ASYMMETRIC, STOR_ASYM_REG) # Set of asymmetric storage resources without REG reserves - - if !isempty(STOR_ASYM_REG) - if CapacityReserveMargin > 0 - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - # Maximum charging rate plus contribution to regulation down must be less than charge power rating - @constraint(EP, [y in STOR_ASYM_REG, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCapCharge][y]) - else - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - # Maximum charging rate plus contribution to regulation down must be less than charge power rating - @constraint(EP, [y in STOR_ASYM_REG, t in 1:T], EP[:vCHARGE][y,t]+EP[:vREG_charge][y,t]<= EP[:eTotalCapCharge][y]) - end - else - if CapacityReserveMargin > 0 - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - # Maximum charging rate plus contribution to regulation down must be less than charge power rating - @constraint(EP, [y in STOR_ASYM_NO_REG, t in 1:T], EP[:vCHARGE][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCapCharge][y]) - else - # Storage units charging can charge faster to provide reserves down and charge slower to provide reserves up - # Maximum charging rate plus contribution to regulation down must be less than charge power rating - @constraint(EP, [y in STOR_ASYM_NO_REG, t in 1:T], EP[:vCHARGE][y,t]<= EP[:eTotalCapCharge][y]) - end - end + vCHARGE = EP[:vCHARGE] + vREG_charge = EP[:vREG_charge] + vCAPRES_charge = EP[:vCAPRES_charge] + eTotalCapCharge = EP[:eTotalCapCharge] + + expr = @expression(EP, [y in STOR_ASYMMETRIC, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[STOR_ASYM_REG, :], vREG_charge[STOR_ASYM_REG, :]) + if CapacityReserveMargin + add_similar_to_expression!(expr[STOR_ASYMMETRIC, :], vCAPRES_charge[STOR_ASYMMETRIC, :]) + end + @constraint(EP, [y in STOR_ASYMMETRIC, t in T], expr[y, t] <= eTotalCapCharge[y]) end From 68c617e43e966795f0edb4ddd4cb267d27fa3cc1 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 13 Oct 2023 18:02:12 -0400 Subject: [PATCH 15/31] Remove extra constraint --- src/model/resources/storage/storage_symmetric.jl | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/model/resources/storage/storage_symmetric.jl b/src/model/resources/storage/storage_symmetric.jl index 27135fa929..20509ad861 100644 --- a/src/model/resources/storage/storage_symmetric.jl +++ b/src/model/resources/storage/storage_symmetric.jl @@ -26,16 +26,12 @@ function storage_symmetric!(EP::Model, inputs::Dict, setup::Dict) if CapacityReserveMargin > 0 @constraints(EP, begin # Maximum charging rate (including virtual charging to move energy held in reserve back to available storage) must be less than symmetric power rating - [y in STOR_SYMMETRIC, t in 1:T], EP[:vCHARGE][y,t] + EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] - # Max simultaneous charge and discharge cannot be greater than capacity [y in STOR_SYMMETRIC, t in 1:T], EP[:vP][y,t]+EP[:vCHARGE][y,t]+EP[:vCAPRES_discharge][y,t]+EP[:vCAPRES_charge][y,t] <= EP[:eTotalCap][y] end) else @constraints(EP, begin # Maximum charging rate (including virtual charging to move energy held in reserve back to available storage) must be less than symmetric power rating - [y in STOR_SYMMETRIC, t in 1:T], EP[:vCHARGE][y,t] <= EP[:eTotalCap][y] - # Max simultaneous charge and discharge cannot be greater than capacity [y in STOR_SYMMETRIC, t in 1:T], EP[:vP][y,t]+EP[:vCHARGE][y,t] <= EP[:eTotalCap][y] end) @@ -70,13 +66,6 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) eTotalCap = EP[:eTotalCap] # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating - expr = @expression(EP, [y in SYMMETRIC, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" - add_similar_to_expression!(expr[REG, :], vREG_charge[REG, :]) - if CapacityReserveMargin - add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_charge[SYMMETRIC, :]) - end - @constraint(EP, [y in SYMMETRIC, t in T], expr[y, t] <= eTotalCap[y]) - # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity expr = @expression(EP, [y in SYMMETRIC, t in T], vP[y, t] + vCHARGE[y, t]) add_similar_to_expression!(expr[REG, :], vREG_charge[REG, :]) From 8c087123d350b87e045bbe5c35ef883c1bcd92f7 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Tue, 24 Oct 2023 17:21:26 -0400 Subject: [PATCH 16/31] Refactor thermal_commit reserves --- src/model/resources/thermal/thermal_commit.jl | 77 +++++-------------- 1 file changed, 21 insertions(+), 56 deletions(-) diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index dce4c29aa2..34e4d3ee0c 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -272,70 +272,35 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = inputs["T"] # Number of time steps (hours) + T = 1:inputs["T"] # Number of time steps (hours) THERM_COMMIT = inputs["THERM_COMMIT"] - THERM_COMMIT_REG_RSV = intersect(THERM_COMMIT, inputs["REG"], inputs["RSV"]) # Set of thermal resources with both regulation and spinning reserves - - THERM_COMMIT_REG = intersect(THERM_COMMIT, inputs["REG"]) # Set of thermal resources with regulation reserves - THERM_COMMIT_RSV = intersect(THERM_COMMIT, inputs["RSV"]) # Set of thermal resources with spinning reserves + REG = intersect(THERM_COMMIT, inputs["REG"]) # Set of thermal resources with regulation reserves + RSV = intersect(THERM_COMMIT, inputs["RSV"]) # Set of thermal resources with spinning reserves - THERM_COMMIT_NO_RES = setdiff(THERM_COMMIT, THERM_COMMIT_REG, THERM_COMMIT_RSV) # Set of thermal resources with no reserves + vP = EP[:vP] + vREG = EP[:vREG] + vRSV = EP[:vRSV] - THERM_COMMIT_REG_ONLY = setdiff(THERM_COMMIT_REG, THERM_COMMIT_RSV) # Set of thermal resources only with regulation reserves - THERM_COMMIT_RSV_ONLY = setdiff(THERM_COMMIT_RSV, THERM_COMMIT_REG) # Set of thermal resources only with spinning reserves - - if !isempty(THERM_COMMIT_REG_RSV) - @constraints(EP, begin - # Maximum regulation and reserve contributions - [y in THERM_COMMIT_REG_RSV, t=1:T], EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Reg_Max]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - [y in THERM_COMMIT_REG_RSV, t=1:T], EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Rsv_Max]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] + commit(y,t) = dfGen[y, :Cap_Size] * EP[:vCOMMIT][y,t] + min_power(y) = dfGen[y, :Min_Power] + max_power(y,t) = inputs["pP_Max"][y,t] - # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - [y in THERM_COMMIT_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= dfGen[y,:Min_Power]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power - [y in THERM_COMMIT_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - end) - end - - if !isempty(THERM_COMMIT_REG) - @constraints(EP, begin - # Maximum regulation and reserve contributions - [y in THERM_COMMIT_REG, t=1:T], EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Reg_Max]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - [y in THERM_COMMIT_REG, t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= dfGen[y,:Min_Power]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Maximum power generated per technology "y" at hour "t" and contribution to regulation must be < max power - [y in THERM_COMMIT_REG, t=1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - end) - end + # Maximum regulation and reserve contributions + @constraint(EP, [y in REG, t in T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * commit(y, t)) + @constraint(EP, [y in RSV, t in T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * commit(y, t)) - if !isempty(THERM_COMMIT_RSV) - @constraints(EP, begin - # Maximum regulation and reserve contributions - [y in THERM_COMMIT_RSV, t=1:T], EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Rsv_Max]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Minimum stable power generated per technology "y" at hour "t" must be > min power - [y in THERM_COMMIT_RSV, t=1:T], EP[:vP][y,t] >= dfGen[y,:Min_Power]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Maximum power generated per technology "y" at hour "t" and contribution to reserves up must be < max power - [y in THERM_COMMIT_RSV, t=1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - end) - end - - if !isempty(THERM_COMMIT_NO_RES) - @constraints(EP, begin - # Minimum stable power generated per technology "y" at hour "t" > Min power - [y in THERM_COMMIT_NO_RES, t=1:T], EP[:vP][y,t] >= dfGen[y,:Min_Power]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - - # Maximum power generated per technology "y" at hour "t" < Max power - [y in THERM_COMMIT_NO_RES, t=1:T], EP[:vP][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Cap_Size]*EP[:vCOMMIT][y,t] - end) - end + # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power + expr = @expression(EP, [y in THERM_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) + @constraint(EP, [y in THERM_COMMIT, t in T], expr[y, t] >= min_power(y) * commit(y, t)) + # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power + expr = @expression(EP, [y in THERM_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[REG, :], vREG[REG, :]) + add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) + @constraint(EP, [y in THERM_COMMIT, t in T], expr[y, t] <= max_power(y, t) * commit(y, t)) end @doc raw""" From 47dec42cc6dd912629476ba015b6d0dd1390d60c Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Tue, 24 Oct 2023 17:31:36 -0400 Subject: [PATCH 17/31] Remove note about duplication --- src/model/resources/thermal/thermal_commit.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index 34e4d3ee0c..ae817ee4b8 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -264,7 +264,6 @@ When modeling frequency regulation and spinning reserves contributions, thermal \end{aligned} ``` -Note there are multiple versions of these constraints in the code in order to avoid creation of unecessary constraints and decision variables for thermal units unable to provide regulation and/or reserves contributions due to input parameters (e.g. ```Reg_Max=0``` and/or ```RSV_Max=0```). """ function thermal_commit_reserves!(EP::Model, inputs::Dict) From 29b7322aa659b1c61ad14d1210a8a295ded7c7e5 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Tue, 24 Oct 2023 20:37:57 -0400 Subject: [PATCH 18/31] refactor therm_no_commit --- .../resources/thermal/thermal_no_commit.jl | 83 +++++-------------- 1 file changed, 22 insertions(+), 61 deletions(-) diff --git a/src/model/resources/thermal/thermal_no_commit.jl b/src/model/resources/thermal/thermal_no_commit.jl index 480e6e2380..06f08e3d63 100644 --- a/src/model/resources/thermal/thermal_no_commit.jl +++ b/src/model/resources/thermal/thermal_no_commit.jl @@ -140,72 +140,33 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = inputs["T"] # Number of time steps (hours) - - THERM_NO_COMMIT = setdiff(inputs["THERM_ALL"], inputs["COMMIT"]) - - # Constraints on contribution to regulation and reserves - # Thermal units without commitment constraints assumed to be quick-start units, so can contribute a specified fraction of their - # total installed capacity to reserves - - THERM_NO_COMMIT_REG_RSV = intersect(THERM_NO_COMMIT, inputs["REG"], inputs["RSV"]) # Set of thermal resources with both regulation and spinning reserves + T = 1:inputs["T"] # Number of time steps (hours) - THERM_NO_COMMIT_REG = intersect(THERM_NO_COMMIT, inputs["REG"]) # Set of thermal resources with regulation reserves - THERM_NO_COMMIT_RSV = intersect(THERM_NO_COMMIT, inputs["RSV"]) # Set of thermal resources with spinning reserves + THERM_NO_COMMIT = setdiff(inputs["THERM_ALL"], inputs["COMMIT"]) - THERM_NO_COMMIT_NO_RES = setdiff(THERM_NO_COMMIT, THERM_NO_COMMIT_REG, THERM_NO_COMMIT_RSV) # Set of thermal resources with no reserves + REG = intersect(THERM_NO_COMMIT, inputs["REG"]) # Set of thermal resources with regulation reserves + RSV = intersect(THERM_NO_COMMIT, inputs["RSV"]) # Set of thermal resources with spinning reserves - THERM_NO_COMMIT_REG_ONLY = setdiff(THERM_NO_COMMIT_REG, THERM_NO_COMMIT_RSV) # Set of thermal resources only with regulation reserves - THERM_NO_COMMIT_RSV_ONLY = setdiff(THERM_NO_COMMIT_RSV, THERM_NO_COMMIT_REG) # Set of thermal resources only with spinning reserves + vP = EP[:vP] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + eTotalCap = EP[:eTotalCap] - if !isempty(THERM_NO_COMMIT_REG_RSV) - @constraints(EP, begin - - [y in THERM_NO_COMMIT_REG_RSV, t=1:T], EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - [y in THERM_NO_COMMIT_REG_RSV, t=1:T], EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] + min_power(y) = dfGen[y, :Min_Power] + max_power(y,t) = inputs["pP_Max"][y,t] - # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation down must be > min power - [y in THERM_NO_COMMIT_REG_RSV, t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= dfGen[y,:Min_Power]*EP[:eTotalCap][y] + # Maximum regulation and reserve contributions + @constraint(EP, [y in REG, t in T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in RSV, t in T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * eTotalCap[y]) - # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves must be < max power - [y in THERM_NO_COMMIT_REG_RSV, t=1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] - end) - end - - if !isempty(THERM_NO_COMMIT_REG) - @constraints(EP, begin - - [y in THERM_NO_COMMIT_REG, t=1:T], EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Reg_Max]*EP[:eTotalCap][y] - - # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation down must be > min power - [y in THERM_NO_COMMIT_REG, t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= dfGen[y,:Min_Power]*EP[:eTotalCap][y] - - # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves must be < max power - [y in THERM_NO_COMMIT_REG, t=1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] - end) - end - - if !isempty(THERM_NO_COMMIT_RSV) - @constraints(EP, begin - - [y in THERM_NO_COMMIT_RSV, t=1:T], EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*dfGen[y,:Rsv_Max]*EP[:eTotalCap][y] - - # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation down must be > min power - [y in THERM_NO_COMMIT_RSV, t=1:T], EP[:vP][y,t] >= dfGen[y,:Min_Power]*EP[:eTotalCap][y] - - # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves must be < max power - [y in THERM_NO_COMMIT_RSV, t=1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] - end) - end - - if !isempty(THERM_NO_COMMIT_NO_RES) - @constraints(EP, begin - # Minimum stable power generated per technology "y" at hour "t" Min_Power - [y in THERM_NO_COMMIT_NO_RES, t=1:T], EP[:vP][y,t] >= dfGen[y,:Min_Power]*EP[:eTotalCap][y] - - # Maximum power generated per technology "y" at hour "t" - [y in THERM_NO_COMMIT_NO_RES, t=1:T], EP[:vP][y,t] <= inputs["pP_Max"][y,t]*EP[:eTotalCap][y] - end) - end + # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power + expr = @expression(EP, [y in THERM_NO_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) + @constraint(EP, [y in THERM_NO_COMMIT, t in T], expr[y, t] >= min_power(y) * eTotalCap[y]) + # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power + expr = @expression(EP, [y in THERM_NO_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + add_similar_to_expression!(expr[REG, :], vREG[REG, :]) + add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) + @constraint(EP, [y in THERM_NO_COMMIT, t in T], expr[y, t] <= max_power(y, t) * eTotalCap[y]) end From 048df4d6709902b085eaff906417bb231cf8ddfe Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 26 Oct 2023 13:23:04 -0400 Subject: [PATCH 19/31] Fix storage_all capres --- src/model/resources/storage/storage_all.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 2a4027cf14..fc2d271d7a 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -172,7 +172,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) vRSV_charge = EP[:vRSV_charge] vREG_discharge = EP[:vREG_discharge] vRSV_discharge = EP[:vRSV_discharge] - vCAPRES_discharge = EP[:vCAPRES_discharge] + eTotalCap = EP[:eTotalCap] eTotalCapEnergy = EP[:eTotalCapEnergy] @@ -207,7 +207,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 - add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) + add_similar_to_expression!(expr[STOR_ALL, :], EP[:vCAPRES_discharge][STOR_ALL, :]) end # Maximum discharging rate and contribution to reserves up must be less than power rating @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= eTotalCap[y]) From d150f50fd908c795da0638032872bad8322eaa93 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 26 Oct 2023 13:31:50 -0400 Subject: [PATCH 20/31] Fix capres access --- src/model/resources/storage/storage_all.jl | 3 ++- src/model/resources/storage/storage_asymmetric.jl | 2 +- src/model/resources/storage/storage_symmetric.jl | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index fc2d271d7a..e81f6fc8bc 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -207,7 +207,8 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 - add_similar_to_expression!(expr[STOR_ALL, :], EP[:vCAPRES_discharge][STOR_ALL, :]) + vCAPRES_discharge = EP[:vCAPRES_discharge] + add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) end # Maximum discharging rate and contribution to reserves up must be less than power rating @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= eTotalCap[y]) diff --git a/src/model/resources/storage/storage_asymmetric.jl b/src/model/resources/storage/storage_asymmetric.jl index 4149afec55..1623abb358 100644 --- a/src/model/resources/storage/storage_asymmetric.jl +++ b/src/model/resources/storage/storage_asymmetric.jl @@ -50,12 +50,12 @@ function storage_asymmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) vCHARGE = EP[:vCHARGE] vREG_charge = EP[:vREG_charge] - vCAPRES_charge = EP[:vCAPRES_charge] eTotalCapCharge = EP[:eTotalCapCharge] expr = @expression(EP, [y in STOR_ASYMMETRIC, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[STOR_ASYM_REG, :], vREG_charge[STOR_ASYM_REG, :]) if CapacityReserveMargin + vCAPRES_charge = EP[:vCAPRES_charge] add_similar_to_expression!(expr[STOR_ASYMMETRIC, :], vCAPRES_charge[STOR_ASYMMETRIC, :]) end @constraint(EP, [y in STOR_ASYMMETRIC, t in T], expr[y, t] <= eTotalCapCharge[y]) diff --git a/src/model/resources/storage/storage_symmetric.jl b/src/model/resources/storage/storage_symmetric.jl index 20509ad861..23bbdfac64 100644 --- a/src/model/resources/storage/storage_symmetric.jl +++ b/src/model/resources/storage/storage_symmetric.jl @@ -61,8 +61,6 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) vRSV_charge = EP[:vRSV_charge] vREG_discharge = EP[:vREG_discharge] vRSV_discharge = EP[:vRSV_discharge] - vCAPRES_charge = EP[:vCAPRES_charge] - vCAPRES_discharge = EP[:vCAPRES_discharge] eTotalCap = EP[:eTotalCap] # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating @@ -72,6 +70,8 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(expr[REG, :], vREG_discharge[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV_discharge[RSV, :]) if CapacityReserveMargin + vCAPRES_charge = EP[:vCAPRES_charge] + vCAPRES_discharge = EP[:vCAPRES_discharge] add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_charge[SYMMETRIC, :]) add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_discharge[SYMMETRIC, :]) end From 74bb719391c15a45a10ef3d360e9e7e5f590e2bb Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 9 Nov 2023 15:28:33 -0500 Subject: [PATCH 21/31] Make T an integer again, not UnitRange --- src/model/resources/hydro/hydro_res.jl | 14 +++++------ src/model/resources/storage/storage_all.jl | 24 +++++++++---------- .../resources/storage/storage_asymmetric.jl | 6 ++--- .../resources/storage/storage_symmetric.jl | 6 ++--- src/model/resources/thermal/thermal_commit.jl | 14 +++++------ .../resources/thermal/thermal_no_commit.jl | 14 +++++------ 6 files changed, 39 insertions(+), 39 deletions(-) diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index bbd1b88645..b642a533e6 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -182,7 +182,7 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = 1:inputs["T"] # Number of time steps (hours) + T = inputs["T"] # Number of time steps (hours) HYDRO_RES = inputs["HYDRO_RES"] REG = inputs["REG"] @@ -197,8 +197,8 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) eTotalCap = EP[:eTotalCap] # NOTE the load-bearing 1 * to create AffExpr and not VariableRef - max_up_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in T], 1 * vP[y, t]) - max_dn_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in T], 1 * vP[y, t]) + max_up_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in 1:T], 1 * vP[y, t]) + max_dn_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in 1:T], 1 * vP[y, t]) S = HYDRO_RES_REG add_similar_to_expression!(max_up_reserves_lhs[S, :], vREG[S, :]) @@ -207,9 +207,9 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) S = HYDRO_RES_RSV add_similar_to_expression!(max_up_reserves_lhs[S, :], vRSV[S, :]) - @constraint(EP, [y in HYDRO_RES, t in T], max_up_reserves_lhs[y, t] <= eTotalCap[y]) - @constraint(EP, [y in HYDRO_RES, t in T], max_dn_reserves_lhs[y, t] >= 0) + @constraint(EP, [y in HYDRO_RES, t in 1:T], max_up_reserves_lhs[y, t] <= eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES, t in 1:T], max_dn_reserves_lhs[y, t] >= 0) - @constraint(EP, [y in HYDRO_RES_REG, t in T], vREG[y, t] <= dfGen[y,:Reg_Max]*eTotalCap[y]) - @constraint(EP, [y in HYDRO_RES_RSV, t in T], vRSV[y, t] <= dfGen[y,:Rsv_Max]*eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max]*eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max]*eTotalCap[y]) end diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index e81f6fc8bc..914b4fdacd 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -154,7 +154,7 @@ end function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) dfGen = inputs["dfGen"] - T = 1:inputs["T"] + T = inputs["T"] p = inputs["hours_per_subperiod"] CapacityReserveMargin = setup["CapacityReserveMargin"] @@ -180,30 +180,30 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) eff_down(y) = dfGen[y, :Eff_Down] # Maximum storage contribution to reserves is a specified fraction of installed capacity - @constraint(EP, [y in STOR_REG, t in T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in STOR_RSV, t in T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging - @constraint(EP, [y in STOR_REG, t in T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) - @constraint(EP, [y in STOR_RSV, t in T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] == vRSV_charge[y, t] + vRSV_discharge[y, t]) # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[STOR_REG, :], -vREG_charge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], -vRSV_charge[STOR_RSV, :]) - @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] >= 0) + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) # Maximum discharging rate and contribution to reserves down must be greater than zero # Note: when discharging, reducing discharge rate is contributing to downwards regulation as it drops net supply - @constraint(EP, [y in STOR_REG, t in T], vP[y, t] - vREG_discharge[y, t] >= 0) + @constraint(EP, [y in STOR_REG, t in 1:T], vP[y, t] - vREG_discharge[y, t] >= 0) # Maximum charging rate plus contribution to regulation down must be less than available storage capacity - @constraint(EP, [y in STOR_REG, t in T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) + @constraint(EP, [y in STOR_REG, t in 1:T], eff_up(y)*(vCHARGE[y, t]+vREG_charge[y, t]) <= eTotalCapEnergy[y]-vS[y, hoursbefore(p,t,1)]) # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type - expr = @expression(EP, [y in STOR_ALL, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 @@ -211,7 +211,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) end # Maximum discharging rate and contribution to reserves up must be less than power rating - @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= eTotalCap[y]) + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= eTotalCap[y]) # Maximum discharging rate and contribution to reserves up must be less than available stored energy in prior period - @constraint(EP, [y in STOR_ALL, t in T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) + @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] <= vS[y, hoursbefore(p,t,1)] * eff_down(y)) end diff --git a/src/model/resources/storage/storage_asymmetric.jl b/src/model/resources/storage/storage_asymmetric.jl index 1623abb358..3293a1bb40 100644 --- a/src/model/resources/storage/storage_asymmetric.jl +++ b/src/model/resources/storage/storage_asymmetric.jl @@ -42,7 +42,7 @@ Sets up variables and constraints specific to storage resources with asymmetric """ function storage_asymmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) - T = 1:inputs["T"] + T = inputs["T"] CapacityReserveMargin = setup["CapacityReserveMargin"] > 0 STOR_ASYMMETRIC = inputs["STOR_ASYMMETRIC"] @@ -52,11 +52,11 @@ function storage_asymmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) vREG_charge = EP[:vREG_charge] eTotalCapCharge = EP[:eTotalCapCharge] - expr = @expression(EP, [y in STOR_ASYMMETRIC, t in T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in STOR_ASYMMETRIC, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[STOR_ASYM_REG, :], vREG_charge[STOR_ASYM_REG, :]) if CapacityReserveMargin vCAPRES_charge = EP[:vCAPRES_charge] add_similar_to_expression!(expr[STOR_ASYMMETRIC, :], vCAPRES_charge[STOR_ASYMMETRIC, :]) end - @constraint(EP, [y in STOR_ASYMMETRIC, t in T], expr[y, t] <= eTotalCapCharge[y]) + @constraint(EP, [y in STOR_ASYMMETRIC, t in 1:T], expr[y, t] <= eTotalCapCharge[y]) end diff --git a/src/model/resources/storage/storage_symmetric.jl b/src/model/resources/storage/storage_symmetric.jl index 23bbdfac64..569ff59498 100644 --- a/src/model/resources/storage/storage_symmetric.jl +++ b/src/model/resources/storage/storage_symmetric.jl @@ -47,7 +47,7 @@ Sets up variables and constraints specific to storage resources with symmetric c """ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) - T = 1:inputs["T"] + T = inputs["T"] CapacityReserveMargin = setup["CapacityReserveMargin"] > 0 SYMMETRIC = inputs["STOR_SYMMETRIC"] @@ -65,7 +65,7 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) # Maximum charging rate plus contribution to regulation down must be less than symmetric power rating # Max simultaneous charge and discharge rates cannot be greater than symmetric charge/discharge capacity - expr = @expression(EP, [y in SYMMETRIC, t in T], vP[y, t] + vCHARGE[y, t]) + expr = @expression(EP, [y in SYMMETRIC, t in 1:T], vP[y, t] + vCHARGE[y, t]) add_similar_to_expression!(expr[REG, :], vREG_charge[REG, :]) add_similar_to_expression!(expr[REG, :], vREG_discharge[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV_discharge[RSV, :]) @@ -75,5 +75,5 @@ function storage_symmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_charge[SYMMETRIC, :]) add_similar_to_expression!(expr[SYMMETRIC, :], vCAPRES_discharge[SYMMETRIC, :]) end - @constraint(EP, [y in SYMMETRIC, t in T], expr[y, t] <= eTotalCap[y]) + @constraint(EP, [y in SYMMETRIC, t in 1:T], expr[y, t] <= eTotalCap[y]) end diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index ae817ee4b8..5a0a7e144b 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -271,7 +271,7 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = 1:inputs["T"] # Number of time steps (hours) + T = inputs["T"] # Number of time steps (hours) THERM_COMMIT = inputs["THERM_COMMIT"] @@ -287,19 +287,19 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) max_power(y,t) = inputs["pP_Max"][y,t] # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * commit(y, t)) - @constraint(EP, [y in RSV, t in T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * commit(y, t)) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * commit(y, t)) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * commit(y, t)) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - expr = @expression(EP, [y in THERM_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in THERM_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) - @constraint(EP, [y in THERM_COMMIT, t in T], expr[y, t] >= min_power(y) * commit(y, t)) + @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * commit(y, t)) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power - expr = @expression(EP, [y in THERM_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in THERM_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[REG, :], vREG[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) - @constraint(EP, [y in THERM_COMMIT, t in T], expr[y, t] <= max_power(y, t) * commit(y, t)) + @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] <= max_power(y, t) * commit(y, t)) end @doc raw""" diff --git a/src/model/resources/thermal/thermal_no_commit.jl b/src/model/resources/thermal/thermal_no_commit.jl index 06f08e3d63..2bcf5b390d 100644 --- a/src/model/resources/thermal/thermal_no_commit.jl +++ b/src/model/resources/thermal/thermal_no_commit.jl @@ -140,7 +140,7 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) dfGen = inputs["dfGen"] - T = 1:inputs["T"] # Number of time steps (hours) + T = inputs["T"] # Number of time steps (hours) THERM_NO_COMMIT = setdiff(inputs["THERM_ALL"], inputs["COMMIT"]) @@ -156,17 +156,17 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) max_power(y,t) = inputs["pP_Max"][y,t] # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in RSV, t in T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * eTotalCap[y]) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * eTotalCap[y]) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - expr = @expression(EP, [y in THERM_NO_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in THERM_NO_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) - @constraint(EP, [y in THERM_NO_COMMIT, t in T], expr[y, t] >= min_power(y) * eTotalCap[y]) + @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * eTotalCap[y]) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power - expr = @expression(EP, [y in THERM_NO_COMMIT, t in T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = @expression(EP, [y in THERM_NO_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" add_similar_to_expression!(expr[REG, :], vREG[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) - @constraint(EP, [y in THERM_NO_COMMIT, t in T], expr[y, t] <= max_power(y, t) * eTotalCap[y]) + @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] <= max_power(y, t) * eTotalCap[y]) end From 0957306566a84db4c7f310d57dc335dac038d59f Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 9 Nov 2023 17:05:47 -0500 Subject: [PATCH 22/31] add expression creation function --- src/model/expression_manipulation.jl | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/model/expression_manipulation.jl b/src/model/expression_manipulation.jl index 20b9474ceb..5acbf035cd 100644 --- a/src/model/expression_manipulation.jl +++ b/src/model/expression_manipulation.jl @@ -47,6 +47,21 @@ function fill_with_const!(arr::Array{GenericAffExpr{C,T}, dims}, con::Real) wher return nothing end +###### ###### ###### ###### ###### ###### +# Create an expression from some indices of a 2D variable array +###### ###### ###### ###### ###### ###### + +function extract_time_series_to_expression(var::JuMP.Containers.DenseAxisArray{VariableRef, 2, Tuple{X, Base.OneTo{Int64}}, Y}, + set::AbstractVector{Int}) where {X, Y} + TIME_DIM = 2 + time_range = var.axes[TIME_DIM] + + aff_exprs = AffExpr.(0, var[set, :] .=> 1) + new_axes = (set, time_range) + expr = JuMP.Containers.DenseAxisArray(aff_exprs.data, new_axes...) + return expr +end + ###### ###### ###### ###### ###### ###### # Element-wise addition of one expression into another # Both arrays must have the same dimensions @@ -141,4 +156,4 @@ end function sum_expression(expr::AbstractArray{AbstractJuMPScalar, dims}) where {dims} return _sum_expression(expr) -end \ No newline at end of file +end From 3f9c6a88ee3e98af0e04195dfcbde9ce46f2f73c Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Thu, 9 Nov 2023 17:23:16 -0500 Subject: [PATCH 23/31] Use new expression manipulation function --- src/model/expression_manipulation.jl | 11 +++++++++++ src/model/resources/hydro/hydro_res.jl | 5 ++--- src/model/resources/storage/storage_all.jl | 4 ++-- src/model/resources/storage/storage_asymmetric.jl | 2 +- src/model/resources/thermal/thermal_commit.jl | 4 ++-- src/model/resources/thermal/thermal_no_commit.jl | 4 ++-- 6 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/model/expression_manipulation.jl b/src/model/expression_manipulation.jl index 5acbf035cd..72410f1cb2 100644 --- a/src/model/expression_manipulation.jl +++ b/src/model/expression_manipulation.jl @@ -50,6 +50,17 @@ end ###### ###### ###### ###### ###### ###### # Create an expression from some indices of a 2D variable array ###### ###### ###### ###### ###### ###### +# +function extract_time_series_to_expression(var::Matrix{VariableRef}, + set::AbstractVector{Int}) + TIME_DIM = 2 + time_range = 1:size(var)[TIME_DIM] + + aff_exprs_data = AffExpr.(0, var[set, :] .=> 1) + new_axes = (set, time_range) + expr = JuMP.Containers.DenseAxisArray(aff_exprs_data, new_axes...) + return expr +end function extract_time_series_to_expression(var::JuMP.Containers.DenseAxisArray{VariableRef, 2, Tuple{X, Base.OneTo{Int64}}, Y}, set::AbstractVector{Int}) where {X, Y} diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index b642a533e6..c367a6bdb2 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -196,9 +196,8 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) vRSV = EP[:vRSV] eTotalCap = EP[:eTotalCap] - # NOTE the load-bearing 1 * to create AffExpr and not VariableRef - max_up_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in 1:T], 1 * vP[y, t]) - max_dn_reserves_lhs = @expression(EP, [y in HYDRO_RES, t in 1:T], 1 * vP[y, t]) + max_up_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) + max_dn_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) S = HYDRO_RES_REG add_similar_to_expression!(max_up_reserves_lhs[S, :], vREG[S, :]) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 914b4fdacd..8b14ffb55e 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -189,7 +189,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Maximum charging rate plus contribution to reserves up must be greater than zero # Note: when charging, reducing charge rate is contributing to upwards reserve & regulation as it drops net demand - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vCHARGE, STOR_ALL) add_similar_to_expression!(expr[STOR_REG, :], -vREG_charge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], -vRSV_charge[STOR_RSV, :]) @constraint(EP, [y in STOR_ALL, t in 1:T], expr[y, t] >= 0) @@ -203,7 +203,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) # Note: maximum charge rate is also constrained by maximum charge power capacity, but as this differs by storage type, # this constraint is set in functions below for each storage type - expr = @expression(EP, [y in STOR_ALL, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vP, STOR_ALL) add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) if CapacityReserveMargin > 0 diff --git a/src/model/resources/storage/storage_asymmetric.jl b/src/model/resources/storage/storage_asymmetric.jl index 3293a1bb40..93e74ad025 100644 --- a/src/model/resources/storage/storage_asymmetric.jl +++ b/src/model/resources/storage/storage_asymmetric.jl @@ -52,7 +52,7 @@ function storage_asymmetric_reserves!(EP::Model, inputs::Dict, setup::Dict) vREG_charge = EP[:vREG_charge] eTotalCapCharge = EP[:eTotalCapCharge] - expr = @expression(EP, [y in STOR_ASYMMETRIC, t in 1:T], 1 * vCHARGE[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vCHARGE, STOR_ASYMMETRIC) add_similar_to_expression!(expr[STOR_ASYM_REG, :], vREG_charge[STOR_ASYM_REG, :]) if CapacityReserveMargin vCAPRES_charge = EP[:vCAPRES_charge] diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index 5a0a7e144b..d7ee2bcbd4 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -291,12 +291,12 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * commit(y, t)) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - expr = @expression(EP, [y in THERM_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vP, THERM_COMMIT) add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * commit(y, t)) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power - expr = @expression(EP, [y in THERM_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vP, THERM_COMMIT) add_similar_to_expression!(expr[REG, :], vREG[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) @constraint(EP, [y in THERM_COMMIT, t in 1:T], expr[y, t] <= max_power(y, t) * commit(y, t)) diff --git a/src/model/resources/thermal/thermal_no_commit.jl b/src/model/resources/thermal/thermal_no_commit.jl index 2bcf5b390d..c7701df06b 100644 --- a/src/model/resources/thermal/thermal_no_commit.jl +++ b/src/model/resources/thermal/thermal_no_commit.jl @@ -160,12 +160,12 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * eTotalCap[y]) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power - expr = @expression(EP, [y in THERM_NO_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vP, THERM_NO_COMMIT) add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] >= min_power(y) * eTotalCap[y]) # Maximum power generated per technology "y" at hour "t" and contribution to regulation and reserves up must be < max power - expr = @expression(EP, [y in THERM_NO_COMMIT, t in 1:T], 1 * vP[y, t]) # NOTE load-bearing "1 *" + expr = extract_time_series_to_expression(vP, THERM_NO_COMMIT) add_similar_to_expression!(expr[REG, :], vREG[REG, :]) add_similar_to_expression!(expr[RSV, :], vRSV[RSV, :]) @constraint(EP, [y in THERM_NO_COMMIT, t in 1:T], expr[y, t] <= max_power(y, t) * eTotalCap[y]) From 579abcd81e93de39c6907e4faa334da93dfd6446 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 10 Nov 2023 19:17:29 -0500 Subject: [PATCH 24/31] Update src/model/resources/storage/storage_all.jl Co-authored-by: Luca Bonaldo <39280783+lbonaldo@users.noreply.github.com> --- src/model/resources/storage/storage_all.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index 8b14ffb55e..d69940e8bb 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -206,7 +206,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) expr = extract_time_series_to_expression(vP, STOR_ALL) add_similar_to_expression!(expr[STOR_REG, :], vREG_discharge[STOR_REG, :]) add_similar_to_expression!(expr[STOR_RSV, :], vRSV_discharge[STOR_RSV, :]) - if CapacityReserveMargin > 0 + if CapacityReserveMargin vCAPRES_discharge = EP[:vCAPRES_discharge] add_similar_to_expression!(expr[STOR_ALL, :], vCAPRES_discharge[STOR_ALL, :]) end From a2a79462e4202f7d9cfe1aa00f5ee44408c15fa6 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Sat, 11 Nov 2023 13:51:51 -0500 Subject: [PATCH 25/31] Fix typo --- src/model/resources/storage/storage_all.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index d69940e8bb..ad1b20773b 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -156,7 +156,7 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) dfGen = inputs["dfGen"] T = inputs["T"] p = inputs["hours_per_subperiod"] - CapacityReserveMargin = setup["CapacityReserveMargin"] + CapacityReserveMargin = setup["CapacityReserveMargin"] > 1 STOR_ALL = inputs["STOR_ALL"] From 07b96bc48ff8a8765c55a5fedce057a92f303243 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 10 Nov 2023 19:13:19 -0500 Subject: [PATCH 26/31] Update note string --- src/model/expression_manipulation.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/model/expression_manipulation.jl b/src/model/expression_manipulation.jl index 72410f1cb2..16cfc0cc65 100644 --- a/src/model/expression_manipulation.jl +++ b/src/model/expression_manipulation.jl @@ -48,7 +48,8 @@ function fill_with_const!(arr::Array{GenericAffExpr{C,T}, dims}, con::Real) wher end ###### ###### ###### ###### ###### ###### -# Create an expression from some indices of a 2D variable array +# Create an expression from some first-dimension indices of a 2D variable array, +# where all of the 2nd-dimension indices are kept ###### ###### ###### ###### ###### ###### # function extract_time_series_to_expression(var::Matrix{VariableRef}, From 3d2946c19c002bc6b4f8c4aba2fbc00ae7a53fc0 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Fri, 10 Nov 2023 19:20:17 -0500 Subject: [PATCH 27/31] Abstract reg_max, rsv_max --- src/model/resources/hydro/hydro_res.jl | 6 ++++-- src/model/resources/storage/storage_all.jl | 6 ++++-- src/model/resources/thermal/thermal_commit.jl | 6 ++++-- src/model/resources/thermal/thermal_no_commit.jl | 6 ++++-- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index c367a6bdb2..37eddce2ee 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -195,6 +195,8 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) vREG = EP[:vREG] vRSV = EP[:vRSV] eTotalCap = EP[:eTotalCap] + reg_max(y) = dfGen[y, :Reg_Max] + rsv_max(y) = dfGen[y, :Rsv_Max] max_up_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) max_dn_reserves_lhs = extract_time_series_to_expression(vP, HYDRO_RES) @@ -209,6 +211,6 @@ function hydro_res_reserves!(EP::Model, inputs::Dict) @constraint(EP, [y in HYDRO_RES, t in 1:T], max_up_reserves_lhs[y, t] <= eTotalCap[y]) @constraint(EP, [y in HYDRO_RES, t in 1:T], max_dn_reserves_lhs[y, t] >= 0) - @constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max]*eTotalCap[y]) - @constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max]*eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_REG, t in 1:T], vREG[y, t] <= reg_max(y) * eTotalCap[y]) + @constraint(EP, [y in HYDRO_RES_RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * eTotalCap[y]) end diff --git a/src/model/resources/storage/storage_all.jl b/src/model/resources/storage/storage_all.jl index ad1b20773b..d0ff71ce1a 100644 --- a/src/model/resources/storage/storage_all.jl +++ b/src/model/resources/storage/storage_all.jl @@ -178,10 +178,12 @@ function storage_all_reserves!(EP::Model, inputs::Dict, setup::Dict) eff_up(y) = dfGen[y, :Eff_Up] eff_down(y) = dfGen[y, :Eff_Down] + reg_max(y) = dfGen[y, :Reg_Max] + rsv_max(y) = dfGen[y, :Rsv_Max] # Maximum storage contribution to reserves is a specified fraction of installed capacity - @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] <= reg_max(y) * eTotalCap[y]) + @constraint(EP, [y in STOR_RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * eTotalCap[y]) # Actual contribution to regulation and reserves is sum of auxilary variables for portions contributed during charging and discharging @constraint(EP, [y in STOR_REG, t in 1:T], vREG[y, t] == vREG_charge[y, t] + vREG_discharge[y, t]) diff --git a/src/model/resources/thermal/thermal_commit.jl b/src/model/resources/thermal/thermal_commit.jl index d7ee2bcbd4..47949bb557 100644 --- a/src/model/resources/thermal/thermal_commit.jl +++ b/src/model/resources/thermal/thermal_commit.jl @@ -285,10 +285,12 @@ function thermal_commit_reserves!(EP::Model, inputs::Dict) commit(y,t) = dfGen[y, :Cap_Size] * EP[:vCOMMIT][y,t] min_power(y) = dfGen[y, :Min_Power] max_power(y,t) = inputs["pP_Max"][y,t] + reg_max(y) = dfGen[y, :Reg_Max] + rsv_max(y) = dfGen[y, :Rsv_Max] # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * commit(y, t)) - @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * commit(y, t)) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(y) * commit(y, t)) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(y) * commit(y, t)) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power expr = extract_time_series_to_expression(vP, THERM_COMMIT) diff --git a/src/model/resources/thermal/thermal_no_commit.jl b/src/model/resources/thermal/thermal_no_commit.jl index c7701df06b..91fc176dd7 100644 --- a/src/model/resources/thermal/thermal_no_commit.jl +++ b/src/model/resources/thermal/thermal_no_commit.jl @@ -154,10 +154,12 @@ function thermal_no_commit_reserves!(EP::Model, inputs::Dict) min_power(y) = dfGen[y, :Min_Power] max_power(y,t) = inputs["pP_Max"][y,t] + reg_max(y) = dfGen[y, :Reg_Max] + rsv_max(y) = dfGen[y, :Rsv_Max] # Maximum regulation and reserve contributions - @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * dfGen[y,:Reg_Max] * eTotalCap[y]) - @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * dfGen[y,:Rsv_Max] * eTotalCap[y]) + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= max_power(y, t) * reg_max(y) * eTotalCap[y]) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= max_power(y, t) * rsv_max(y) * eTotalCap[y]) # Minimum stable power generated per technology "y" at hour "t" and contribution to regulation must be > min power expr = extract_time_series_to_expression(vP, THERM_NO_COMMIT) From 021c08c29160121f027f4c70e12f58e7426d8e1a Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Sat, 11 Nov 2023 12:43:54 -0500 Subject: [PATCH 28/31] add tests --- test/expression_manipulation_test.jl | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/expression_manipulation_test.jl b/test/expression_manipulation_test.jl index 1771f0d144..6a89138222 100644 --- a/test/expression_manipulation_test.jl +++ b/test/expression_manipulation_test.jl @@ -53,6 +53,27 @@ let @variable(EP, single_var >= 0) GenX.add_term_to_expression!(EP[:large_expr], single_var) @test EP[:large_expr][100] == test_var[100] + 22.0 + single_var + + # Test extracting some rows from a 2D matrix variable + columns = 10 + @variable(EP, var_matrix[row in 1:4, col in 1:columns]) + rows_to_extract = [3, 4] + expr = GenX.extract_time_series_to_expression(var_matrix, rows_to_extract) + @test size(expr) == (length(rows_to_extract), columns) + @test expr isa JuMP.Containers.DenseAxisArray + @test expr.axes[1] == rows_to_extract + @test expr.axes[2] == 1:columns + unregister(EP, :var_matrix) + + # Test extracting some rows from a 2D dense axis array variable + @variable(EP, var_denseaxisarray[row in [1, 3, 5, 11], col in 1:columns]) + rows_to_extract = [3, 5] + expr = GenX.extract_time_series_to_expression(var_denseaxisarray, rows_to_extract) + @test size(expr) == (length(rows_to_extract), columns) + @test expr isa JuMP.Containers.DenseAxisArray + @test expr.axes[1] == rows_to_extract + @test expr.axes[2] == 1:columns + unregister(EP, :var_denseaxisarray) end ###### ###### ###### ###### ###### ###### ###### From 5ee48ffe1aa1031c915f83d3541d3201977ac8a1 Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Sat, 11 Nov 2023 12:49:02 -0500 Subject: [PATCH 29/31] Add additional test --- test/expression_manipulation_test.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/test/expression_manipulation_test.jl b/test/expression_manipulation_test.jl index 6a89138222..334ddefcd0 100644 --- a/test/expression_manipulation_test.jl +++ b/test/expression_manipulation_test.jl @@ -61,6 +61,7 @@ let expr = GenX.extract_time_series_to_expression(var_matrix, rows_to_extract) @test size(expr) == (length(rows_to_extract), columns) @test expr isa JuMP.Containers.DenseAxisArray + @test eltype(expr.data) isa AffExpr @test expr.axes[1] == rows_to_extract @test expr.axes[2] == 1:columns unregister(EP, :var_matrix) From ef16c18ab5214d45ff92cc7648992f79c1f20e8b Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Sat, 11 Nov 2023 13:05:37 -0500 Subject: [PATCH 30/31] Fix test --- test/expression_manipulation_test.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/expression_manipulation_test.jl b/test/expression_manipulation_test.jl index 334ddefcd0..a83c234a7b 100644 --- a/test/expression_manipulation_test.jl +++ b/test/expression_manipulation_test.jl @@ -61,7 +61,7 @@ let expr = GenX.extract_time_series_to_expression(var_matrix, rows_to_extract) @test size(expr) == (length(rows_to_extract), columns) @test expr isa JuMP.Containers.DenseAxisArray - @test eltype(expr.data) isa AffExpr + @test eltype(expr.data) == AffExpr @test expr.axes[1] == rows_to_extract @test expr.axes[2] == 1:columns unregister(EP, :var_matrix) From 13be2ddd419ba4c4bda349aa4b3e95de31811eed Mon Sep 17 00:00:00 2001 From: Jacob Schwartz Date: Sat, 11 Nov 2023 13:41:32 -0500 Subject: [PATCH 31/31] Refactor VRE reserves and fix bug This fixes #573, where Reserves + Num_VRE_Bins > 1 would lead to the VRE_NO_POWER_OUT resources being able to contribute an infinite amount to the reserves. This fixes #574, where VRE power output constraints were being applied length(VRE) times to all VRE resources, when Reserves = 1. --- .../curtailable_variable_renewable.jl | 131 +++++++++--------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl b/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl index 5555e3f699..cdd43f1a33 100644 --- a/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl +++ b/src/model/resources/curtailable_variable_renewable/curtailable_variable_renewable.jl @@ -47,24 +47,24 @@ function curtailable_variable_renewable!(EP::Model, inputs::Dict, setup::Dict) add_similar_to_expression!(EP[:eCapResMarBalance], eCapResMarBalanceVRE) end - ### Constratints ### - # For resource for which we are modeling hourly power output - for y in VRE_POWER_OUT - # Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone. - # For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits. - VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID]) - - # Constraints on contribution to regulation and reserves - if Reserves == 1 - curtailable_variable_renewable_reserves!(EP, inputs) - else - # Maximum power generated per hour by renewable generators must be less than - # sum of product of hourly capacity factor for each bin times its the bin installed capacity - # Note: inequality constraint allows curtailment of output below maximum level. - @constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)) - end - - end + ### Constraints ### + if Reserves == 1 + # Constraints on power output and contribution to regulation and reserves + curtailable_variable_renewable_reserves!(EP, inputs) + remove_reserves_for_binned_vre_resources!(EP, inputs) + else + # For resource for which we are modeling hourly power output + for y in VRE_POWER_OUT + # Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone. + # For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits. + VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID]) + + # Maximum power generated per hour by renewable generators must be less than + # sum of product of hourly capacity factor for each bin times its the bin installed capacity + # Note: inequality constraint allows curtailment of output below maximum level. + @constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)) + end + end # Set power variables for all bins that are not being modeled for hourly output to be zero for y in VRE_NO_POWER_OUT @@ -102,60 +102,53 @@ The amount of frequency regulation and operating reserves procured in each time ``` """ function curtailable_variable_renewable_reserves!(EP::Model, inputs::Dict) - dfGen = inputs["dfGen"] T = inputs["T"] - VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], inputs["VRE"]) - - for y in VRE_POWER_OUT - # Define the set of generator indices corresponding to the different sites (or bins) of a particular VRE technology (E.g. wind or solar) in a particular zone. - # For example the wind resource in a particular region could be include three types of bins corresponding to different sites with unique interconnection, hourly capacity factor and maximim available capacity limits. - VRE_BINS = intersect(dfGen[dfGen[!,:R_ID].>=y,:R_ID], dfGen[dfGen[!,:R_ID].<=y+dfGen[y,:Num_VRE_Bins]-1,:R_ID]) - - if y in inputs["REG"] && y in inputs["RSV"] # Resource eligible for regulation and spinning reserves - @constraints(EP, begin - # For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour - [t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - [t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - - # Power generated and regulation reserve contributions down per hour must be greater than zero - [t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0 - - # Power generated and reserve contributions up per hour by renewable generators must be less than - # hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level. - [t=1:T], EP[:vP][y,t]+EP[:vREG][y,t]+EP[:vRSV][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - end) - elseif y in inputs["REG"] # Resource only eligible for regulation reserves - @constraints(EP, begin - # For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour - [t=1:T], EP[:vREG][y,t] <= dfGen[y,:Reg_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - - # Power generated and regulation reserve contributions down per hour must be greater than zero - [t=1:T], EP[:vP][y,t]-EP[:vREG][y,t] >= 0 - - # Power generated and reserve contributions up per hour by renewable generators must be less than - # hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level. - [t=1:T], EP[:vP][y,t]+EP[:vREG][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - end) - - elseif y in inputs["RSV"] # Resource only eligible for spinning reserves - only available in up, no down spinning reserves - - @constraints(EP, begin - # For VRE, reserve contributions must be less than the specified percentage of the capacity factor for the hour - [t=1:T], EP[:vRSV][y,t] <= dfGen[y,:Rsv_Max]*sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - - # Power generated and reserve contributions up per hour by renewable generators must be less than - # hourly capacity factor. Note: inequality constraint allows curtailment of output below maximum level. - [t=1:T], EP[:vP][y,t]+EP[:vRSV][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS) - end) - else # Resource not eligible for reserves - # Maximum power generated per hour by renewable generators must be less than - # sum of product of hourly capacity factor for each bin times its the bin installed capacity - # Note: inequality constraint allows curtailment of output below maximum level. - @constraint(EP, [t=1:T], EP[:vP][y,t] <= sum(inputs["pP_Max"][yy,t]*EP[:eTotalCap][yy] for yy in VRE_BINS)) - end - end + VRE = inputs["VRE"] + VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], VRE) + REG = intersect(VRE_POWER_OUT, inputs["REG"]) + RSV = intersect(VRE_POWER_OUT, inputs["RSV"]) + + eTotalCap = EP[:eTotalCap] + vP = EP[:vP] + vREG = EP[:vREG] + vRSV = EP[:vRSV] + hourly_capacity_factor(y, t) = inputs["pP_Max"][y, t] + reg_max(y) = dfGen[y, :Reg_Max] + rsv_max(y) = dfGen[y, :Rsv_Max] + + hourly_capacity(y, t) = hourly_capacity_factor(y, t) * eTotalCap[y] + resources_in_bin(y) = UnitRange(y, y + dfGen[y, :Num_VRE_Bins] - 1) + hourly_bin_capacity(y, t) = sum(hourly_capacity(yy, t) for yy in resources_in_bin(y)) + + @constraint(EP, [y in REG, t in 1:T], vREG[y, t] <= reg_max(y) * hourly_bin_capacity(y, t)) + @constraint(EP, [y in RSV, t in 1:T], vRSV[y, t] <= rsv_max(y) * hourly_bin_capacity(y, t)) + + expr = extract_time_series_to_expression(vP, VRE_POWER_OUT) + add_similar_to_expression!(expr[REG, :], -vREG[REG, :]) + @constraint(EP, [y in VRE_POWER_OUT, t in 1:T], expr[y, t] >= 0) + + expr = extract_time_series_to_expression(vP, VRE_POWER_OUT) + add_similar_to_expression!(expr[REG, :], +vREG[REG, :]) + add_similar_to_expression!(expr[RSV, :], +vRSV[RSV, :]) + @constraint(EP, [y in VRE_POWER_OUT, t in 1:T], expr[y, t] <= hourly_bin_capacity(y, t)) +end + +function remove_reserves_for_binned_vre_resources!(EP::Model, inputs::Dict) + dfGen = inputs["dfGen"] + + VRE = inputs["VRE"] + VRE_POWER_OUT = intersect(dfGen[dfGen.Num_VRE_Bins.>=1,:R_ID], VRE) + REG = inputs["REG"] + RSV = inputs["RSV"] + VRE_NO_POWER_OUT = setdiff(VRE, VRE_POWER_OUT) + for y in intersect(VRE_NO_POWER_OUT, REG) + fix.(EP[:vREG][y,:], 0.0, force=true) + end + for y in intersect(VRE_NO_POWER_OUT, RSV) + fix.(EP[:vRSV][y,:], 0.0, force=true) + end end