Skip to content

Commit cccab41

Browse files
authored
Settings flag for new LDS constraints (#801)
1 parent 012a28d commit cccab41

File tree

10 files changed

+72
-52
lines changed

10 files changed

+72
-52
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ number of concurrent Gurobi uses is limited (#783).
1414
- Additional long-duration storage constraints to bound state of charge in
1515
non-representative periods (#781).
1616
- New version of `add_similar_to_expression!` to support arrays of `Number`s. (#798)
17+
- New settings flag `LDSAdditionalConstraints` to provide flexibility in
18+
activating new long-duration storage constraints (#781). Can be set in the GenX
19+
settings file (PR #801).
1720

1821
### Changed
1922
- The `charge.csv` and `storage.csv` files now include only resources with

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "GenX"
22
uuid = "5d317b1e-30ec-4ed6-a8ce-8d2d88d7cfac"
33
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"]
4-
version = "0.4.1-dev.19"
4+
version = "0.4.1-dev.20"
55

66
[deps]
77
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"

docs/src/Tutorials/Tutorial_4_model_generation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ end
474474

475475
# Model constraints, variables, expression related to reservoir hydropower resources with long duration storage
476476
if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"])
477-
GenX.hydro_inter_period_linkage!(EP, inputs)
477+
GenX.hydro_inter_period_linkage!(EP, inputs, setup)
478478
end
479479

480480
# Model constraints, variables, expression related to demand flexibility resources

docs/src/User_Guide/model_configuration.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ The following tables summarize the model settings parameters and their default/p
3131
|StorageVirtualDischarge | Flag to enable contributions that a storage device makes to the capacity reserve margin without generating power.|
3232
||1 = activate the virtual discharge of storage resources.|
3333
||0 = do not activate the virtual discharge of storage resources.|
34+
|LDSAdditionalConstraints | Flag to activate additional constraints for long duration storage resources to prevent violation of SoC limits in non-representative periods.|
35+
||1 = activate additional constraints.|
36+
||0 = do not activate additional constraints.|
3437
|HourlyMatching| Constraint to match generation from clean sources with hourly consumption.|
3538
||1 = Constraint is active.|
3639
||0 = Constraint is not active.|
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
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
2-
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
3-
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
4-
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
1+
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
2+
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
3+
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
4+
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

example_systems/3_three_zones_w_co2_capture/settings/genx_settings.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ ParameterScale: 1 # Turn on parameter scaling wherein demand, capacity and power
1010
WriteShadowPrices: 1 # Write shadow prices of LP or relaxed MILP; 0 = not active; 1 = active
1111
UCommit: 2 # Unit committment of thermal power plants; 0 = not active; 1 = active using integer clestering; 2 = active using linearized clustering
1212
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)
13+
LDSAdditionalConstraints: 1 # Activate additional constraints to prevent violation of SoC limits in non-representative periods; 0 = not active; 1 = active

src/configure_settings/configure_settings.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ function default_settings()
88
"CapacityReserveMargin" => 0,
99
"CO2Cap" => 0,
1010
"StorageLosses" => 1,
11+
"LDSAdditionalConstraints" => 1,
1112
"VirtualChargeDischargeCost" => 1, # $/MWh
1213
"MinCapReq" => 0,
1314
"MaxCapReq" => 0,

src/model/generate_model.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ function generate_model(setup::Dict, inputs::Dict, OPTIMIZER::MOI.OptimizerWithA
171171

172172
# Model constraints, variables, expression related to reservoir hydropower resources with long duration storage
173173
if inputs["REP_PERIOD"] > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"])
174-
hydro_inter_period_linkage!(EP, inputs)
174+
hydro_inter_period_linkage!(EP, inputs, setup)
175175
end
176176

177177
# Model constraints, variables, expression related to demand flexibility resources

src/model/resources/hydro/hydro_inter_period_linkage.jl

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
@doc raw"""
2-
hydro_inter_period_linkage!(EP::Model, inputs::Dict)
2+
hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict)
33
This function creates variables and constraints enabling modeling of long duration storage resources when modeling representative time periods.
44
55
**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
8080
8181
Additional details on this approach are available in [Parolin et al., 2024](https://doi.org/10.48550/arXiv.2409.19079).
8282
"""
83-
function hydro_inter_period_linkage!(EP::Model, inputs::Dict)
83+
function hydro_inter_period_linkage!(EP::Model, inputs::Dict, setup::Dict)
8484
println("Long Duration Storage Module for Hydro Reservoir")
8585

8686
gen = inputs["RESOURCES"]
@@ -96,6 +96,7 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict)
9696

9797
MODELED_PERIODS_INDEX = 1:NPeriods
9898
REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX]
99+
NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX)
99100

100101
### Variables ###
101102

@@ -108,11 +109,14 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict)
108109
# Build up inventory can be positive or negative
109110
@variable(EP, vdSOC_HYDRO[y in STOR_HYDRO_LONG_DURATION, w = 1:REP_PERIOD])
110111

111-
# Maximum positive storage inventory change within subperiod
112-
@variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0)
112+
# Additional constraints to prevent violation of SoC limits in non-representative periods
113+
if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX)
114+
# Maximum positive storage inventory change within subperiod
115+
@variable(EP, vdSOC_maxPos_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] >= 0)
113116

114-
# Maximum negative storage inventory change within subperiod
115-
@variable(EP, vdSOC_maxNeg_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] <= 0)
117+
# Maximum negative storage inventory change within subperiod
118+
@variable(EP, vdSOC_maxNeg_HYDRO[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD] <= 0)
119+
end
116120

117121
### Constraints ###
118122

@@ -155,26 +159,27 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict)
155159
vSOC_HYDROw[y,r]==EP[:vS_HYDRO][y, hours_per_subperiod * dfPeriodMap[r, :Rep_Period_Index]] -
156160
vdSOC_HYDRO[y, dfPeriodMap[r, :Rep_Period_Index]])
157161

158-
# Extract maximum storage level variation (positive) within subperiod
159-
@constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
162+
if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX)
163+
# Extract maximum storage level variation (positive) within subperiod
164+
@constraint(EP, cMaxSoCVarPos_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
160165
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])
161166

162-
# Extract maximum storage level variation (negative) within subperiod
163-
@constraint(EP, cMaxSoCVarNeg_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
164-
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])
165-
166-
# Max storage content within each modeled period cannot exceed installed energy capacity
167-
@constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
168-
vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
169-
-EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]
170-
+inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y]
171-
+vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y])
172-
173-
# Min storage content within each modeled period cannot be negative
174-
@constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
175-
vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
176-
-EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]
177-
+inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y]
178-
+vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0)
179-
167+
# Extract maximum storage level variation (negative) within subperiod
168+
@constraint(EP, cMaxSoCVarNeg_H[y in STOR_HYDRO_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
169+
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])
170+
171+
# Max storage content within each modeled period cannot exceed installed energy capacity
172+
@constraint(EP, cSoCLongDurationStorageMaxInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX],
173+
vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
174+
-EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]
175+
+inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y]
176+
+vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] <= hydro_energy_to_power_ratio(gen[y])*EP[:eTotalCap][y])
177+
178+
# Min storage content within each modeled period cannot be negative
179+
@constraint(EP, cSoCLongDurationStorageMinInt_H[y in STOR_HYDRO_LONG_DURATION, r in NON_REP_PERIODS_INDEX],
180+
vSOC_HYDROw[y,r]-(1/efficiency_down(gen[y])*EP[:vP][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
181+
-EP[:vSPILL][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]
182+
+inputs["pP_Max"][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1]*EP[:eTotalCap][y]
183+
+vdSOC_maxPos_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0)
184+
end
180185
end

src/model/resources/storage/long_duration_storage.jl

Lines changed: 26 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict)
112112

113113
MODELED_PERIODS_INDEX = 1:NPeriods
114114
REP_PERIODS_INDEX = MODELED_PERIODS_INDEX[dfPeriodMap[!, :Rep_Period] .== MODELED_PERIODS_INDEX]
115+
NON_REP_PERIODS_INDEX = setdiff(MODELED_PERIODS_INDEX, REP_PERIODS_INDEX)
115116

116117
### Variables ###
117118

@@ -133,11 +134,14 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict)
133134
@variable(EP, vCAPRES_dsoc[y in STOR_LONG_DURATION, w = 1:REP_PERIOD])
134135
end
135136

136-
# Maximum positive storage inventory change within subperiod
137-
@variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0)
137+
# Additional constraints to prevent violation of SoC limits in non-representative periods
138+
if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX)
139+
# Maximum positive storage inventory change within subperiod
140+
@variable(EP, vdSOC_maxPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD] >= 0)
138141

139-
# Maximum negative storage inventory change within subperiod
140-
@variable(EP, vdSOC_maxNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD] <= 0)
142+
# Maximum negative storage inventory change within subperiod
143+
@variable(EP, vdSOC_maxNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD] <= 0)
144+
end
141145

142146
### Constraints ###
143147

@@ -225,23 +229,26 @@ function long_duration_storage!(EP::Model, inputs::Dict, setup::Dict)
225229
vSOCw[y, r]>=vCAPRES_socw[y, r])
226230
end
227231

228-
# Extract maximum storage level variation (positive) within subperiod
229-
@constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
232+
if setup["LDSAdditionalConstraints"] == 1 && !isempty(NON_REP_PERIODS_INDEX)
233+
# Extract maximum storage level variation (positive) within subperiod
234+
@constraint(EP, cMaxSoCVarPos[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
230235
vdSOC_maxPos[y,w] >= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1])
231236

232-
# Extract maximum storage level variation (negative) within subperiod
233-
@constraint(EP, cMaxSoCVarNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
237+
# Extract maximum storage level variation (negative) within subperiod
238+
@constraint(EP, cMaxSoCVarNeg[y in STOR_LONG_DURATION, w=1:REP_PERIOD, t=2:hours_per_subperiod],
234239
vdSOC_maxNeg[y,w] <= EP[:vS][y,hours_per_subperiod*(w-1)+t] - EP[:vS][y,hours_per_subperiod*(w-1)+1])
235240

236-
# Max storage content within each modeled period cannot exceed installed energy capacity
237-
@constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX],
238-
(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])
239-
+(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
240-
+vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y])
241-
242-
# Min storage content within each modeled period cannot be negative
243-
@constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in MODELED_PERIODS_INDEX],
244-
(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])
245-
+(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
246-
+vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0)
241+
# Max storage content within each modeled period cannot exceed installed energy capacity
242+
@constraint(EP, cSoCLongDurationStorageMaxInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX],
243+
(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])
244+
+(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
245+
+vdSOC_maxPos[y,dfPeriodMap[r,:Rep_Period_Index]] <= EP[:eTotalCapEnergy][y])
246+
247+
# Min storage content within each modeled period cannot be negative
248+
@constraint(EP, cSoCLongDurationStorageMinInt[y in STOR_LONG_DURATION, r in NON_REP_PERIODS_INDEX],
249+
(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])
250+
+(efficiency_up(gen[y])*EP[:vCHARGE][y,hours_per_subperiod*(dfPeriodMap[r,:Rep_Period_Index]-1)+1])
251+
+vdSOC_maxNeg[y,dfPeriodMap[r,:Rep_Period_Index]] >= 0)
252+
end
247253
end
254+

0 commit comments

Comments
 (0)