11# REopt®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE.
22
33
4- function add_degradation_variables (m, p)
4+ function add_degradation_variables (m, p, segments )
55 days = 1 : 365 * p. s. financial. analysis_years
66 @variable (m, Eavg[days] >= 0 )
7- @variable (m, Eplus_sum[days] >= 0 )
8- @variable (m, Eminus_sum[days] >= 0 )
7+ @variable (m, Eplus_sum[days, 1 : segments ] >= 0 ) # energy charged for each day for each segment level
8+ @variable (m, Eminus_sum[days, 1 : segments ] >= 0 ) # energy discharged for each day for each segment level
99 @variable (m, EFC[days] >= 0 )
1010 @variable (m, SOH[days])
11+ @variable (m, dvSegmentChargePower[p. time_steps, 1 : segments] >= 0 ) # charge power for each ts for each segment
12+ @variable (m, dvSegmentDischargePower[p. time_steps, 1 : segments] >= 0 ); # discharge power for each ts for each segment
1113end
1214
1315
1416function constrain_degradation_variables (m, p; b= " ElectricStorage" )
1517 days = 1 : 365 * p. s. financial. analysis_years
1618 ts_per_day = 24 / p. hours_per_time_step
1719 ts_per_year = ts_per_day * 365
20+ J = length (p. s. storage. attr[b]. degradation. cycle_fade_coefficient); # Number of segments
1821 ts0 = Dict ()
1922 tsF = Dict ()
2023 for d in days
@@ -24,21 +27,41 @@ function constrain_degradation_variables(m, p; b="ElectricStorage")
2427 tsF[d] = Int (ts_per_day * 365 )
2528 end
2629 end
30+
2731 @constraint (m, [d in days],
2832 m[:Eavg ][d] == 1 / ts_per_day * sum (m[:dvStoredEnergy ][b, ts] for ts in ts0[d]: tsF[d])
2933 )
34+
3035 @constraint (m, [d in days],
31- m[:Eplus_sum ][d] ==
32- p. hours_per_time_step * (
33- sum (m[:dvProductionToStorage ][b, t, ts] for t in p. techs. elec, ts in ts0[d]: tsF[d])
34- + sum (m[:dvGridToStorage ][b, ts] for ts in ts0[d]: tsF[d])
35- )
36+ m[:EFC ][d] == sum (m[:Eplus_sum ][d, j] + m[:Eminus_sum ][d, j] for j in 1 : J) / 2
3637 )
37- @constraint (m, [d in days],
38- m[:Eminus_sum ][d] == p. hours_per_time_step * sum (m[:dvDischargeFromStorage ][b, ts] for ts in ts0[d]: tsF[d])
38+
39+ # Power in equals power into storage from grid or local production
40+ @constraint (m, [ts in p. time_steps],
41+ sum (m[:dvSegmentChargePower ][ts, j] for j in 1 : J) == sum (
42+ m[:dvProductionToStorage ][b, t, ts] for t in p. techs. elec) + m[:dvGridToStorage ][b, ts]
3943 )
40- @constraint (m, [d in days],
41- m[:EFC ][d] == (m[:Eplus_sum ][d] + m[:Eminus_sum ][d]) / 2
44+
45+ # Power out equals power discharged from storage to any destination
46+ @constraint (m, [ts in p. time_steps],
47+ sum (m[:dvSegmentDischargePower ][ts, j] for j in 1 : J) == m[:dvDischargeFromStorage ][b, ts]);
48+
49+ # Balance charging with daily e_plus, here is only collect all power across the day, so don't need to times efficiency
50+ @constraint (m, [d in days, j in 1 : J], m[:Eplus_sum ][d, j] == sum (m[:dvSegmentChargePower ][ts0[d]: tsF[d], j])* p. hours_per_time_step)
51+ @constraint (m, [d in days, j in 1 : J], m[:Eminus_sum ][d, j] == sum (m[:dvSegmentDischargePower ][ts0[d]: tsF[d], j])* p. hours_per_time_step);
52+ # [az] we may want to adjust the notation to "ts, j for ts in ts0[d]:tsF[d] so it reads the same as the other constraints in REopt
53+
54+ # energy limit, replace SOC limitation
55+ @constraint (
56+ m,
57+ [ts in p. time_steps, j in 1 : J],
58+ m[:dvSegmentChargePower ][ts, j]* p. hours_per_time_step <= p. s. storage. attr[b]. degradation. cycle_fade_fraction[j]* m[:dvStorageEnergy ][b]
59+ )
60+
61+ @constraint (
62+ m,
63+ [ts in p. time_steps, j in 1 : J],
64+ m[:dvSegmentDischargePower ][ts, j]* p. hours_per_time_step <= p. s. storage. attr[b]. degradation. cycle_fade_fraction[j]* m[:dvStorageEnergy ][b]
4265 )
4366end
4467
@@ -54,7 +77,7 @@ function add_degradation(m, p; b="ElectricStorage")
5477 # Indices
5578 days = 1 : 365 * p. s. financial. analysis_years
5679 months = 1 : p. s. financial. analysis_years* 12
57-
80+ J = length (p . s . storage . attr[b] . degradation . cycle_fade_coefficient); # Number of segments
5881 strategy = p. s. storage. attr[b]. degradation. maintenance_strategy
5982
6083 if isempty (p. s. storage. attr[b]. degradation. maintenance_cost_per_kwh)
@@ -74,15 +97,15 @@ function add_degradation(m, p; b="ElectricStorage")
7497 throw (@error (" The degradation maintenance_cost_per_kwh must have a length of $(length (days)- 1 ) ." ))
7598 end
7699
77- add_degradation_variables (m, p)
100+ add_degradation_variables (m, p, J )
78101 constrain_degradation_variables (m, p, b= b)
79102
80103 @constraint (m, [d in 2 : days[end ]],
81104 m[:SOH ][d] == m[:SOH ][d- 1 ] - p. hours_per_time_step * (
82105 p. s. storage. attr[b]. degradation. calendar_fade_coefficient *
83106 p. s. storage. attr[b]. degradation. time_exponent *
84107 m[:Eavg ][d- 1 ] * d^ (p. s. storage. attr[b]. degradation. time_exponent- 1 ) +
85- p. s. storage. attr[b]. degradation. cycle_fade_coefficient * m[:EFC ][d- 1 ]
108+ sum ( p. s. storage. attr[b]. degradation. cycle_fade_coefficient[j] * m[:Eminus_sum ][d- 1 , j] for j in 1 : J)
86109 )
87110 )
88111 # NOTE SOH can be negative
@@ -170,7 +193,10 @@ function add_degradation(m, p; b="ElectricStorage")
170193 maint_cost = sum (p. s. storage. attr[b]. degradation. maintenance_cost_per_kwh[day* i] for i in 1 : batt_replace_count)
171194 replacement_costs[mth] = maint_cost
172195
173- residual_factor = 1 - (p. s. financial. analysis_years* 12 / mth - floor (p. s. financial. analysis_years* 12 / mth))
196+ # Calculate fraction of time remaining after analysis period ends where Batt will be healthy ("useful")
197+ # Multiply by 0.2 to scale residual to BESS SOH (considered healthy if SOH is between 80% and 100%)
198+ # Total BESS capacity residual is (0.8 + residual useful fraction) * BESS capacity
199+ residual_factor = 0.2 * (1 - (p. s. financial. analysis_years* 12 / mth - floor (p. s. financial. analysis_years* 12 / mth))) + 0.8
174200 residual_value = p. s. storage. attr[b]. degradation. maintenance_cost_per_kwh[end ]* residual_factor
175201 residual_values[mth] = residual_value
176202 end
@@ -182,7 +208,7 @@ function add_degradation(m, p; b="ElectricStorage")
182208 @expression (m, residual_value, sum (residual_values[mth] * m[:dvSOHChangeTimesEnergy ][mth] for mth in months))
183209
184210 elseif strategy == " augmentation"
185-
211+ @info " Augmentation BESS degradation costs. "
186212 @expression (m, degr_cost,
187213 sum (
188214 p. s. storage. attr[b]. degradation. maintenance_cost_per_kwh[d- 1 ] * (m[:SOH ][d- 1 ] - m[:SOH ][d])
0 commit comments