diff --git a/CHANGELOG.md b/CHANGELOG.md index 37fc5999ae..3cc4dbe548 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,6 @@ 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 diff --git a/src/model/resources/hydro/hydro_inter_period_linkage.jl b/src/model/resources/hydro/hydro_inter_period_linkage.jl index 1963b3f1be..8e98eb9538 100644 --- a/src/model/resources/hydro/hydro_inter_period_linkage.jl +++ b/src/model/resources/hydro/hydro_inter_period_linkage.jl @@ -54,6 +54,8 @@ 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 @@ -79,20 +81,21 @@ 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, cHydroReservoirLongDurationStorageStart[w=1:REP_PERIOD, y in STOR_HYDRO_LONG_DURATION], + @constraint(EP, cSoCBalLongDurationStorageStart_H[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, cHydroReservoirLongDurationStorage[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCBalLongDurationStorage_H[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, cHydroReservoirLongDurationStorageUpper[y in STOR_HYDRO_LONG_DURATION, r in MODELED_PERIODS_INDEX], + @constraint(EP, cSoCBalLongDurationStorageUpper_H[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, cHydroReservoirLongDurationStorageSub[y in STOR_HYDRO_LONG_DURATION, r in REP_PERIODS_INDEX], + @constraint(EP, cSoCBalLongDurationStorageSub_H[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]]) diff --git a/src/model/resources/hydro/hydro_res.jl b/src/model/resources/hydro/hydro_res.jl index 2c56c88b69..9dc20a0194 100644 --- a/src/model/resources/hydro/hydro_res.jl +++ b/src/model/resources/hydro/hydro_res.jl @@ -70,12 +70,6 @@ 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) @@ -113,14 +107,6 @@ 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; @@ -129,7 +115,8 @@ 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 - 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]) + 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]) # 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]