From 7875129fa849975b872abafb7fa06df639508208 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 12:04:47 -0500 Subject: [PATCH 1/9] Add new settings LDSAdditionalConstraints --- docs/src/User_Guide/model_configuration.md | 3 +++ .../3_three_zones_w_co2_capture/settings/genx_settings.yml | 1 + src/configure_settings/configure_settings.jl | 1 + 3 files changed, 5 insertions(+) diff --git a/docs/src/User_Guide/model_configuration.md b/docs/src/User_Guide/model_configuration.md index 0bb8b1dc13..a1526b3eab 100644 --- a/docs/src/User_Guide/model_configuration.md +++ b/docs/src/User_Guide/model_configuration.md @@ -31,6 +31,9 @@ The following tables summarize the model settings parameters and their default/p |StorageVirtualDischarge | Flag to enable contributions that a storage device makes to the capacity reserve margin without generating power.| ||1 = activate the virtual discharge of storage resources.| ||0 = do not activate the virtual discharge of storage resources.| +|LDSAdditionalConstraints | Flag to activate additional constraints for long duration storage resources to prevent violation of SoC limits in non-representative periods.| +||1 = activate additional constraints.| +||0 = do not activate additional constraints.| |HourlyMatching| Constraint to match generation from clean sources with hourly consumption.| ||1 = Constraint is active.| ||0 = Constraint is not active.| diff --git a/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml b/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml index ac456b6fcb..27f3eef52f 100644 --- a/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml +++ b/example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml @@ -10,3 +10,4 @@ ParameterScale: 1 # Turn on parameter scaling wherein demand, capacity and power WriteShadowPrices: 1 # Write shadow prices of LP or relaxed MILP; 0 = not active; 1 = active UCommit: 2 # Unit committment of thermal power plants; 0 = not active; 1 = active using integer clestering; 2 = active using linearized clustering TimeDomainReduction: 1 # Time domain reduce (i.e. cluster) inputs based on Demand_data.csv, Generators_variability.csv, and Fuels_data.csv; 0 = not active (use input data as provided); 0 = active (cluster input data, or use data that has already been clustered) +LDSAdditionalConstraints: 1 # Activate additional constraints to prevent violation of SoC limits in non-representative periods; 0 = not active; 1 = active \ No newline at end of file diff --git a/src/configure_settings/configure_settings.jl b/src/configure_settings/configure_settings.jl index 01d4603179..cffd8b7127 100644 --- a/src/configure_settings/configure_settings.jl +++ b/src/configure_settings/configure_settings.jl @@ -8,6 +8,7 @@ function default_settings() "CapacityReserveMargin" => 0, "CO2Cap" => 0, "StorageLosses" => 1, + "LDSAdditionalConstraints" => 0, "VirtualChargeDischargeCost" => 1, # $/MWh "MinCapReq" => 0, "MaxCapReq" => 0, From 3614cbc4a5d42f8381132486c28a5bae3f2612d5 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 12:05:38 -0500 Subject: [PATCH 2/9] Add new setting to lds.jl --- .../storage/long_duration_storage.jl | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/model/resources/storage/long_duration_storage.jl b/src/model/resources/storage/long_duration_storage.jl index 05c2e56b1e..b3537d3443 100644 --- a/src/model/resources/storage/long_duration_storage.jl +++ b/src/model/resources/storage/long_duration_storage.jl @@ -133,11 +133,14 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) @variable(EP, vCAPRES_dsoc[y in STOR_LONG_DURATION, w = 1:REP_PERIOD]) end - # Maximum positive storage inventory change within subperiod - @variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0) + # Additional constraints to prevent violation of SoC limits in non-representative periods + if setup["LDSAdditionalConstraints"] == 1 + # Maximum positive storage inventory change within subperiod + @variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0) - # Maximum negative storage inventory change within subperiod - @variable(EP, vdSOC_maxNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD] <= 0) + # Maximum negative storage inventory change within subperiod + @variable(EP, vdSOC_maxNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD] <= 0) + end ### Constraints ### @@ -225,23 +228,26 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) vSOCw[y, r]>=vCAPRES_socw[y, r]) end - # Extract maximum storage level variation (positive) within subperiod - @constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + if setup["LDSAdditionalConstraints"] == 1 + # Extract maximum storage level variation (positive) within subperiod + @constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], vdSOC_maxPos[y,w] >= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) - # Extract maximum storage level variation (negative) within subperiod - @constraint(EP, cMaxSoCVarNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + # Extract maximum storage level variation (negative) within subperiod + @constraint(EP, cMaxSoCVarNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], vdSOC_maxNeg[y,w] <= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) - # Max storage content within each modeled period cannot exceed installed energy capacity - @constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], - (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - +vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y]) - - # Min storage content within each modeled period cannot be negative - @constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], - (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - +vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) + # Max storage content within each modeled period cannot exceed installed energy capacity + @constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], + (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y]) + + # Min storage content within each modeled period cannot be negative + @constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], + (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + +vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) + end end + From 73f319ff43b60981a0ea3b62ec46cf119e76a00e Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 12:06:31 -0500 Subject: [PATCH 3/9] Add new setting to hydro_lds --- src/model/generate_model.jl | 2 +- .../hydro/hydro_inter_period_linkage.jl | 57 ++++++++++--------- 2 files changed, 32 insertions(+), 27 deletions(-) diff --git a/src/model/generate_model.jl b/src/model/generate_model.jl index d047fcbe11..0067b0c553 100644 --- a/src/model/generate_model.jl +++ b/src/model/generate_model.jl @@ -171,7 +171,7 @@ function generate_model(setup::Dict, inputs::Dict, OPTIMIZER::MOI.OptimizerWithA # Model constraints, variables, expression related to reservoir hydropower resources with long duration storage if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"]) - hydro_inter_period_linkage!(EP, inputs) + hydro_inter_period_linkage!(EP, inputs, setup) end # Model constraints, variables, expression related to demand flexibility resources diff --git a/src/model/resources/hydro/hydro_inter_period_linkage.jl b/src/model/resources/hydro/hydro_inter_period_linkage.jl index e1ca04e975..275f6bde55 100644 --- a/src/model/resources/hydro/hydro_inter_period_linkage.jl +++ b/src/model/resources/hydro/hydro_inter_period_linkage.jl @@ -1,5 +1,5 @@ @doc raw""" - hydro_inter_period_linkage!(EP::Model, inputs::Dict) + hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) This function creates variables and constraints enabling modeling of long duration storage resources when modeling representative time periods. **Storage inventory balance at beginning of each representative period** @@ -80,7 +80,7 @@ Similarly, the minimum storage content is imposed to be positive in every period Additional details on this approach are available in [Parolin et al., 2024](https://doi.org/10.48550/arXiv.2409.19079). """ -function hydro_inter_period_linkage!(EP::Model, inputs::Dict) +function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) println("Long Duration Storage Module for Hydro Reservoir") gen = inputs["RESOURCES"] @@ -96,6 +96,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) MODELED_PERIODS_INDEX = 1:NPeriods REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX] + NON_REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .!= MODELED_PERIODS_INDEX] ### Variables ### @@ -108,11 +109,14 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) # Build up inventory can be positive or negative @variable(EP, vdSOC_HYDRO[y in STOR_HYDRO_LONG_DURATION, w = 1:REP_PERIOD]) - # Maximum positive storage inventory change within subperiod - @variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0) + # Additional constraints to prevent violation of SoC limits in non-representative periods + if setup["LDSAdditionalConstraints"] == 1 + # Maximum positive storage inventory change within subperiod + @variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0) - # Maximum negative storage inventory change within subperiod - @variable(EP, vdSOC_maxNeg_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] <= 0) + # Maximum negative storage inventory change within subperiod + @variable(EP, vdSOC_maxNeg_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] <= 0) + end ### Constraints ### @@ -155,26 +159,27 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict) vSOC_HYDROw[y,r]==EP[:vS_HYDRO][y, hours_per_subperiod * dfPeriodMap[r, :Rep_Period_Index]] - vdSOC_HYDRO[y, dfPeriodMap[r, :Rep_Period_Index]]) - # Extract maximum storage level variation (positive) within subperiod - @constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + if setup["LDSAdditionalConstraints"] == 1 + # Extract maximum storage level variation (positive) within subperiod + @constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], vdSOC_maxPos_HYDRO[y,w] >= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) - # Extract maximum storage level variation (negative) within subperiod - @constraint(EP, cMaxSoCVarNeg_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], - vdSOC_maxNeg_HYDRO[y,w] <= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) - - # Max storage content within each modeled period cannot exceed installed energy capacity - @constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], - vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] - +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] - +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y]) - - # Min storage content within each modeled period cannot be negative - @constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], - vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) - -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] - +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] - +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) - + # Extract maximum storage level variation (negative) within subperiod + @constraint(EP, cMaxSoCVarNeg_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], + vdSOC_maxNeg_HYDRO[y,w] <= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) + + # Max storage content within each modeled period cannot exceed installed energy capacity + @constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] + +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] + +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y]) + + # Min storage content within each modeled period cannot be negative + @constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) + -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] + +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] + +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) + end end From 601a2a078a95d46126c738347dd3d968e1b5bad0 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 12:06:49 -0500 Subject: [PATCH 4/9] Update tutorial 4 --- docs/src/Tutorials/Tutorial_4_model_generation.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/Tutorials/Tutorial_4_model_generation.md b/docs/src/Tutorials/Tutorial_4_model_generation.md index 4ef5f566a1..0d50bab766 100644 --- a/docs/src/Tutorials/Tutorial_4_model_generation.md +++ b/docs/src/Tutorials/Tutorial_4_model_generation.md @@ -474,7 +474,7 @@ end # Model constraints, variables, expression related to reservoir hydropower resources with long duration storage if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"]) - GenX.hydro_inter_period_linkage!(EP, inputs) + GenX.hydro_inter_period_linkage!(EP, inputs, setup) end # Model constraints, variables, expression related to demand flexibility resources From 8c34f72d15cd7272fc50cfe0343334eb5be03714 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 12:07:43 -0500 Subject: [PATCH 5/9] Add LDS flag to storage in example case for testing --- .../3_three_zones_w_co2_capture/resources/Storage.csv | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv b/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv index 238c5acd03..c2fcbd3628 100644 --- a/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv +++ b/example_systems/3_three_zones_w_co2_capture/resources/Storage.csv @@ -1,4 +1,4 @@ -Resource,Zone,Model,New_Build,Can_Retire,Existing_Cap_MW,Existing_Cap_MWh,Max_Cap_MW,Max_Cap_MWh,Min_Cap_MW,Min_Cap_MWh,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster -MA_battery,1,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,MA,0 -CT_battery,2,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,CT,0 -ME_battery,3,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,ME,0 \ No newline at end of file +Resource,Zone,LDS,Model,New_Build,Can_Retire,Existing_Cap_MW,Existing_Cap_MWh,Max_Cap_MW,Max_Cap_MWh,Min_Cap_MW,Min_Cap_MWh,Inv_Cost_per_MWyr,Inv_Cost_per_MWhyr,Fixed_OM_Cost_per_MWyr,Fixed_OM_Cost_per_MWhyr,Var_OM_Cost_per_MWh,Var_OM_Cost_per_MWh_In,Self_Disch,Eff_Up,Eff_Down,Min_Duration,Max_Duration,Reg_Max,Rsv_Max,Reg_Cost,Rsv_Cost,region,cluster +MA_battery,1,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,MA,0 +CT_battery,2,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,CT,0 +ME_battery,3,0,1,1,0,0,0,-1,-1,0,0,19584,22494,4895,5622,0.15,0.15,0,0.92,0.92,1,10,0,0,0,0,ME,0 \ No newline at end of file From 126f89ee6312ab7a9eb65008c7649444ac90a116 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 14:44:50 -0500 Subject: [PATCH 6/9] Include new LDS constr only for non-repr periods --- .../resources/hydro/hydro_inter_period_linkage.jl | 10 +++++----- src/model/resources/storage/long_duration_storage.jl | 9 +++++---- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/model/resources/hydro/hydro_inter_period_linkage.jl b/src/model/resources/hydro/hydro_inter_period_linkage.jl index 275f6bde55..19761d07ea 100644 --- a/src/model/resources/hydro/hydro_inter_period_linkage.jl +++ b/src/model/resources/hydro/hydro_inter_period_linkage.jl @@ -96,7 +96,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) MODELED_PERIODS_INDEX = 1:NPeriods REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX] - NON_REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .!= MODELED_PERIODS_INDEX] + NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX) ### Variables ### @@ -110,7 +110,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) @variable(EP, vdSOC_HYDRO[y in STOR_HYDRO_LONG_DURATION, w = 1:REP_PERIOD]) # Additional constraints to prevent violation of SoC limits in non-representative periods - if setup["LDSAdditionalConstraints"] == 1 + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) # Maximum positive storage inventory change within subperiod @variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0) @@ -159,7 +159,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) vSOC_HYDROw[y,r]==EP[:vS_HYDRO][y, hours_per_subperiod * dfPeriodMap[r, :Rep_Period_Index]] - vdSOC_HYDRO[y, dfPeriodMap[r, :Rep_Period_Index]]) - if setup["LDSAdditionalConstraints"] == 1 + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) # Extract maximum storage level variation (positive) within subperiod @constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], vdSOC_maxPos_HYDRO[y,w] >= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) @@ -169,14 +169,14 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict) vdSOC_maxNeg_HYDRO[y,w] <= EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+t] - EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1]) # Max storage content within each modeled period cannot exceed installed energy capacity - @constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX], vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] +vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y]) # Min storage content within each modeled period cannot be negative - @constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX], vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) -EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1] +inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y] diff --git a/src/model/resources/storage/long_duration_storage.jl b/src/model/resources/storage/long_duration_storage.jl index b3537d3443..189381f895 100644 --- a/src/model/resources/storage/long_duration_storage.jl +++ b/src/model/resources/storage/long_duration_storage.jl @@ -112,6 +112,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) MODELED_PERIODS_INDEX = 1:NPeriods REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX] + NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX) ### Variables ### @@ -134,7 +135,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) end # Additional constraints to prevent violation of SoC limits in non-representative periods - if setup["LDSAdditionalConstraints"] == 1 + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) # Maximum positive storage inventory change within subperiod @variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0) @@ -228,7 +229,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) vSOCw[y, r]>=vCAPRES_socw[y, r]) end - if setup["LDSAdditionalConstraints"] == 1 + if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX) # Extract maximum storage level variation (positive) within subperiod @constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod], vdSOC_maxPos[y,w] >= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) @@ -238,13 +239,13 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict) vdSOC_maxNeg[y,w] <= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1]) # Max storage content within each modeled period cannot exceed installed energy capacity - @constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX], (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) +vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y]) # Min storage content within each modeled period cannot be negative - @constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX], (1-self_discharge(gen[y]))*vSOCw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) +(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]) +vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0) From aae25cf34da9aa8d49eb60d48809a7623d17e1b2 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Tue, 3 Dec 2024 15:33:58 -0500 Subject: [PATCH 7/9] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b192f55b74..eff4a786a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ number of concurrent Gurobi uses is limited (#783). - Additional long-duration storage constraints to bound state of charge in non-representative periods (#781). - New version of `add_similar_to_expression!` to support arrays of `Number`s. (#798) +- New settings flag `LDSAdditionalConstraints` to provide flexibility in activating new long-duration storage constraints (#781). Can be set in the GenX settings file (PR #801). ### Changed - The `charge.csv` and `storage.csv` files now include only resources with From c0599d750c66e5d5251df432e61a489effc45405 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Thu, 5 Dec 2024 13:48:13 -0500 Subject: [PATCH 8/9] Change LDSAdditionalConstraints default value to true --- src/configure_settings/configure_settings.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/configure_settings/configure_settings.jl b/src/configure_settings/configure_settings.jl index cffd8b7127..9df418e334 100644 --- a/src/configure_settings/configure_settings.jl +++ b/src/configure_settings/configure_settings.jl @@ -8,7 +8,7 @@ function default_settings() "CapacityReserveMargin" => 0, "CO2Cap" => 0, "StorageLosses" => 1, - "LDSAdditionalConstraints" => 0, + "LDSAdditionalConstraints" => 1, "VirtualChargeDischargeCost" => 1, # $/MWh "MinCapReq" => 0, "MaxCapReq" => 0, From 028f3d70117d1f5fc3fbb31f443f7b949447dfb2 Mon Sep 17 00:00:00 2001 From: lbonaldo Date: Mon, 23 Dec 2024 09:15:51 -0500 Subject: [PATCH 9/9] Update dev version in Project.toml --- CHANGELOG.md | 4 +++- Project.toml | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eff4a786a0..4484607652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,9 @@ number of concurrent Gurobi uses is limited (#783). - Additional long-duration storage constraints to bound state of charge in non-representative periods (#781). - New version of `add_similar_to_expression!` to support arrays of `Number`s. (#798) -- New settings flag `LDSAdditionalConstraints` to provide flexibility in activating new long-duration storage constraints (#781). Can be set in the GenX settings file (PR #801). +- New settings flag `LDSAdditionalConstraints` to provide flexibility in +activating new long-duration storage constraints (#781). Can be set in the GenX +settings file (PR #801). ### Changed - The `charge.csv` and `storage.csv` files now include only resources with diff --git a/Project.toml b/Project.toml index 5b2952361c..ccbd22f3f9 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "GenX" uuid = "5d317b1e-30ec-4ed6-a8ce-8d2d88d7cfac" authors = ["Bonaldo, Luca", "Chakrabarti, Sambuddha", "Cheng, Fangwei", "Ding, Yifu", "Jenkins, Jesse D.", "Luo, Qian", "Macdonald, Ruaridh", "Mallapragada, Dharik", "Manocha, Aneesha", "Mantegna, Gabe ", "Morris, Jack", "Patankar, Neha", "Pecci, Filippo", "Schwartz, Aaron", "Schwartz, Jacob", "Schivley, Greg", "Sepulveda, Nestor", "Xu, Qingyu", "Zhou, Justin"] -version = "0.4.1-dev.19" +version = "0.4.1-dev.20" [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"