Skip to content

Commit

Permalink
Fixed issue #572 by allowing hydro reservoirs to operate as long dura…
Browse files Browse the repository at this point in the history
…tion storage resources
  • Loading branch information
filippopecci committed Feb 14, 2024
1 parent 5d3c8e4 commit e6105f8
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 9 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix factor of 0.5 when writing out transmission losses. (#480)
- Fix summation error when a set of hours is empty (in thermal_commit.jl).
- Fix access to eELOSSByZone expr before initialization (#541)
- Fix modeling of hydro reservoir with long duration storage (#572).

### Changed

Expand Down
1 change: 1 addition & 0 deletions src/load_inputs/load_generators_data.jl
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function load_generators_data!(setup::Dict, path::AbstractString, inputs_gen::Di

# Set of storage resources with long duration storage capabilitites
inputs_gen["STOR_HYDRO_LONG_DURATION"] = gen_in[(gen_in.LDS.==1) .& (gen_in.HYDRO.==1),:R_ID]
inputs_gen["STOR_HYDRO_SHORT_DURATION"] = gen_in[(gen_in.LDS.==0) .& (gen_in.HYDRO.==1),:R_ID]
inputs_gen["STOR_LONG_DURATION"] = gen_in[(gen_in.LDS.==1) .& (gen_in.STOR.>=1),:R_ID]
inputs_gen["STOR_SHORT_DURATION"] = gen_in[(gen_in.LDS.==0) .& (gen_in.STOR.>=1),:R_ID]

Expand Down
11 changes: 4 additions & 7 deletions src/model/resources/hydro/hydro_inter_period_linkage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict)

STOR_HYDRO_LONG_DURATION = inputs["STOR_HYDRO_LONG_DURATION"]

START_SUBPERIODS = inputs["START_SUBPERIODS"]

hours_per_subperiod = inputs["hours_per_subperiod"] #total number of hours per subperiod

dfPeriodMap = inputs["Period_Map"] # Dataframe that maps modeled periods to representative periods
Expand All @@ -81,21 +79,20 @@ function hydro_inter_period_linkage!(EP::Model, inputs::Dict)
# Modified initial state of storage for long-duration storage - initialize wth value carried over from last period
# Alternative to cSoCBalStart constraint which is included when not modeling operations wrapping and long duration storage
# Note: tw_min = hours_per_subperiod*(w-1)+1; tw_max = hours_per_subperiod*w
@constraint(EP, cSoCBalLongDurationStorageStart_H[w=1:REP_PERIOD, y in STOR_HYDRO_LONG_DURATION],
@constraint(EP, cHydroReservoirLongDurationStorageStart[w=1:REP_PERIOD, y in STOR_HYDRO_LONG_DURATION],
EP[:vS_HYDRO][y,hours_per_subperiod*(w-1)+1] == (EP[:vS_HYDRO][y,hours_per_subperiod*w]-vdSOC_HYDRO[y,w])-(1/dfGen[y,:Eff_Down]*EP[:vP][y,hours_per_subperiod*(w-1)+1])-EP[:vSPILL][y,hours_per_subperiod*(w-1)+1]+inputs["pP_Max"][y,hours_per_subperiod*(w-1)+1]*EP[:eTotalCap][y])

# Storage at beginning of period w = storage at beginning of period w-1 + storage built up in period w (after n representative periods)
## Multiply storage build up term from prior period with corresponding weight
@constraint(EP, cSoCBalLongDurationStorage_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
@constraint(EP, cHydroReservoirLongDurationStorage[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
vSOC_HYDROw[y, mod1(r+1, NPeriods)] == vSOC_HYDROw[y,r] + vdSOC_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]])

# Storage at beginning of each modeled period cannot exceed installed energy capacity
@constraint(EP, cSoCBalLongDurationStorageUpper_H[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
@constraint(EP, cHydroReservoirLongDurationStorageUpper[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX],
vSOC_HYDROw[y,r] <= dfGen[y,:Hydro_Energy_to_Power_Ratio]*EP[:eTotalCap][y])

# Initial storage level for representative periods must also adhere to sub-period storage inventory balance
# Initial storage = Final storage - change in storage inventory across representative period
@constraint(EP, cSoCBalLongDurationStorageSub_H[y in STOR_HYDRO_LONG_DURATION, r in REP_PERIODS_INDEX],
@constraint(EP, cHydroReservoirLongDurationStorageSub[y in STOR_HYDRO_LONG_DURATION, r in REP_PERIODS_INDEX],
vSOC_HYDROw[y,r] == EP[:vS_HYDRO][y,hours_per_subperiod*dfPeriodMap[r,:Rep_Period_Index]] - vdSOC_HYDRO[y,dfPeriodMap[r,:Rep_Period_Index]])


Expand Down
17 changes: 15 additions & 2 deletions src/model/resources/hydro/hydro_res.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,12 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict)
HYDRO_RES = inputs["HYDRO_RES"] # Set of all reservoir hydro resources, used for common constraints
HYDRO_RES_KNOWN_CAP = inputs["HYDRO_RES_KNOWN_CAP"] # Reservoir hydro resources modeled with unknown reservoir energy capacity

STOR_HYDRO_SHORT_DURATION = inputs["STOR_HYDRO_SHORT_DURATION"]
representative_periods = inputs["REP_PERIOD"]

START_SUBPERIODS = inputs["START_SUBPERIODS"]
INTERIOR_SUBPERIODS = inputs["INTERIOR_SUBPERIODS"]

# These variables are used in the ramp-up and ramp-down expressions
reserves_term = @expression(EP, [y in HYDRO_RES, t in 1:T], 0)
regulation_term = @expression(EP, [y in HYDRO_RES, t in 1:T], 0)
Expand Down Expand Up @@ -107,6 +113,14 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict)

### Constratints ###

if representative_periods > 1 && !isempty(inputs["STOR_HYDRO_LONG_DURATION"])
CONSTRAINTSET = STOR_HYDRO_SHORT_DURATION
else
CONSTRAINTSET = HYDRO_RES
end

@constraint(EP, cHydroReservoirStart[y in CONSTRAINTSET,t in START_SUBPERIODS], EP[:vS_HYDRO][y,t] == EP[:vS_HYDRO][y, hoursbefore(p,t,1)]- (1/dfGen[y,:Eff_Down]*EP[:vP][y,t]) - vSPILL[y,t] + inputs["pP_Max"][y,t]*EP[:eTotalCap][y])

### Constraints commmon to all reservoir hydro (y in set HYDRO_RES) ###
@constraints(EP, begin
### NOTE: time coupling constraints in this block do not apply to first hour in each sample period;
Expand All @@ -115,8 +129,7 @@ function hydro_res!(EP::Model, inputs::Dict, setup::Dict)
# DEV NOTE: Last inputs["pP_Max"][y,t] term above is inflows; currently part of capacity factors inputs in Generators_variability.csv but should be moved to its own Hydro_inflows.csv input in future.

# Constraints for reservoir hydro
cHydroReservoir[y in HYDRO_RES, t in 1:T], EP[:vS_HYDRO][y,t] == (EP[:vS_HYDRO][y, hoursbefore(p,t,1)]
- (1/dfGen[y,:Eff_Down]*EP[:vP][y,t]) - vSPILL[y,t] + inputs["pP_Max"][y,t]*EP[:eTotalCap][y])
cHydroReservoirInterior[y in HYDRO_RES, t in INTERIOR_SUBPERIODS], EP[:vS_HYDRO][y,t] == (EP[:vS_HYDRO][y, hoursbefore(p,t,1)]- (1/dfGen[y,:Eff_Down]*EP[:vP][y,t]) - vSPILL[y,t] + inputs["pP_Max"][y,t]*EP[:eTotalCap][y])

# Maximum ramp up and down
cRampUp[y in HYDRO_RES, t in 1:T], EP[:vP][y,t] + regulation_term[y,t] + reserves_term[y,t] - EP[:vP][y, hoursbefore(p,t,1)] <= dfGen[y,:Ramp_Up_Percentage]*EP[:eTotalCap][y]
Expand Down

0 comments on commit e6105f8

Please sign in to comment.