Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
235bf51
initial implementation
rathod-b Mar 21, 2025
84a844a
Make cycle fade segmented
rathod-b Apr 16, 2025
1667f74
add degradation field cycle_fade_fraction
zolanaj May 9, 2025
394375a
use cycle_fade_fraction when constraining dvSegmentChargePowerdvSegme…
zolanaj May 9, 2025
fdc44e9
rm extra constraints on Eplus_sum, Eminus_sum
zolanaj May 9, 2025
6fd4ee4
update docstrings in degradation model and add check for length of cy…
zolanaj May 9, 2025
3237d6d
Merge branch 'develop' into segmented_cycle_degr
rathod-b May 12, 2025
685665d
Update runtests.jl
rathod-b May 13, 2025
7c1851c
Merge branch 'develop' into segmented_cycle_degr
rathod-b May 20, 2025
70b573b
update failing test, add changelog
rathod-b May 22, 2025
004828f
Merge branch 'develop' into segmented_cycle_degr
adfarth Jun 3, 2025
b70075d
add residual_value to results help text
adfarth Jun 3, 2025
e8d59d0
round soh timeseries
adfarth Jun 4, 2025
aa06ce5
update default degr params
rathod-b Jun 5, 2025
3cd0a81
Update runtests.jl
rathod-b Jun 10, 2025
f1a8703
Move away from ArchGDAL
rathod-b Jun 17, 2025
be10571
replace ArchGDAL with GMT
rathod-b Jun 18, 2025
0068b4b
Revert "Move away from ArchGDAL"
rathod-b Jun 23, 2025
25dc92d
Revert "replace ArchGDAL with GMT"
rathod-b Jun 23, 2025
167a264
Update residual factor of a battery
rathod-b Jun 23, 2025
1ab1084
Update CHANGELOG.md
rathod-b Jun 23, 2025
a2fa7a5
update local tests for degradation model
zolanaj Jun 25, 2025
b61cd99
Merge branch 'develop' into segmented_cycle_degr
zolanaj Jun 25, 2025
aa5900c
Update electric_storage.jl
rathod-b Jun 25, 2025
fdfa4b9
Update battery OM costs when using degradation
rathod-b Jul 1, 2025
69d55da
adjust battery om calculations
rathod-b Jul 2, 2025
d00fa27
Update electric_storage.jl
rathod-b Jul 2, 2025
4a8b85f
test value update for degradation model with new cost functions
zolanaj Jul 2, 2025
3c50f50
Update documentation
rathod-b Jul 2, 2025
8273f69
Merge branch 'develop' into segmented_cycle_degr
rathod-b Jul 2, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 46 additions & 10 deletions src/constraints/battery_degradation.jl
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
# REopt®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE.


function add_degradation_variables(m, p)
function add_degradation_variables(m, p, segments)
days = 1:365*p.s.financial.analysis_years
@variable(m, Eavg[days] >= 0)
@variable(m, Eplus_sum[days] >= 0)
@variable(m, Eminus_sum[days] >= 0)
@variable(m, Eplus_sum[days, 1:segments] >= 0) # energy charged for each day for each segment level
@variable(m, Eminus_sum[days, 1:segments] >= 0) # energy discharged for each day for each segment level
@variable(m, EFC[days] >= 0)
@variable(m, SOH[days])
@variable(m, dvSegmentChargePower[p.time_steps, 1:segments] >= 0) # charge power for each ts for each segment
@variable(m, dvSegmentDischargePower[p.time_steps, 1:segments] >= 0); # discharge power for each ts for each segment
end


function constrain_degradation_variables(m, p; b="ElectricStorage")
days = 1:365*p.s.financial.analysis_years
ts_per_day = 24 / p.hours_per_time_step
ts_per_year = ts_per_day * 365
J = length(p.s.storage.attr[b].degradation.cycle_fade_coefficient); # Number of segments
ts0 = Dict()
tsF = Dict()
for d in days
Expand All @@ -24,21 +27,54 @@ function constrain_degradation_variables(m, p; b="ElectricStorage")
tsF[d] = Int(ts_per_day * 365)
end
end

@constraint(m, [d in days],
m[:Eavg][d] == 1/ts_per_day * sum(m[:dvStoredEnergy][b, ts] for ts in ts0[d]:tsF[d])
)

@constraint(m, [d in days],
m[:Eplus_sum][d] ==
sum(m[:Eplus_sum][d, j] for j in 1:J) ==
p.hours_per_time_step * (
sum(m[:dvProductionToStorage][b, t, ts] for t in p.techs.elec, ts in ts0[d]:tsF[d])
+ sum(m[:dvGridToStorage][b, ts] for ts in ts0[d]:tsF[d])
)
)

@constraint(m, [d in days],
m[:Eminus_sum][d] == p.hours_per_time_step * sum(m[:dvDischargeFromStorage][b, ts] for ts in ts0[d]:tsF[d])
sum(m[:Eminus_sum][d, j] for j in 1:J) ==
p.hours_per_time_step * sum(m[:dvDischargeFromStorage][b, ts] for ts in ts0[d]:tsF[d])
)

@constraint(m, [d in days],
m[:EFC][d] == (m[:Eplus_sum][d] + m[:Eminus_sum][d]) / 2
m[:EFC][d] == sum(m[:Eplus_sum][d, j] + m[:Eminus_sum][d, j] for j in 1:J) / 2
)

# Power in equals power into storage from grid or local production
@constraint(m, [ts in p.time_steps],
sum(m[:dvSegmentChargePower][ts, j] for j in 1:J) == sum(
m[:dvProductionToStorage][b, t, ts] for t in p.techs.elec) + m[:dvGridToStorage][b, ts]
)

# Power out equals power discharged from storage to any destination
@constraint(m, [ts in p.time_steps],
sum(m[:dvSegmentDischargePower][ts, j] for j in 1:J) == m[:dvDischargeFromStorage][b, ts]);

# Balance charging with daily e_plus, here is only collect all power across the day, so don't need to times efficiency
@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)
@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);
#[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

# energy limit, replace SOC limitation
@constraint(
m,
[ts in p.time_steps, j in 1:J],
m[:dvSegmentChargePower][ts, j]*p.hours_per_time_step <= p.s.storage.attr[b].degradation.cycle_fade_coefficient[j]*m[:dvStorageEnergy][b]
)

@constraint(
m,
[ts in p.time_steps, j in 1:J],
m[:dvSegmentDischargePower][ts, j]*p.hours_per_time_step <= p.s.storage.attr[b].degradation.cycle_fade_coefficient[j]*m[:dvStorageEnergy][b]
)
end

Expand All @@ -54,7 +90,7 @@ function add_degradation(m, p; b="ElectricStorage")
# Indices
days = 1:365*p.s.financial.analysis_years
months = 1:p.s.financial.analysis_years*12

J = length(p.s.storage.attr[b].degradation.cycle_fade_coefficient); # Number of segments
strategy = p.s.storage.attr[b].degradation.maintenance_strategy

if isempty(p.s.storage.attr[b].degradation.maintenance_cost_per_kwh)
Expand All @@ -74,15 +110,15 @@ function add_degradation(m, p; b="ElectricStorage")
throw(@error("The degradation maintenance_cost_per_kwh must have a length of $(length(days)-1)."))
end

add_degradation_variables(m, p)
add_degradation_variables(m, p, J)
constrain_degradation_variables(m, p, b=b)
Comment thread
zolanaj marked this conversation as resolved.

@constraint(m, [d in 2:days[end]],
m[:SOH][d] == m[:SOH][d-1] - p.hours_per_time_step * (
p.s.storage.attr[b].degradation.calendar_fade_coefficient *
p.s.storage.attr[b].degradation.time_exponent *
m[:Eavg][d-1] * d^(p.s.storage.attr[b].degradation.time_exponent-1) +
p.s.storage.attr[b].degradation.cycle_fade_coefficient * m[:EFC][d-1]
sum(p.s.storage.attr[b].degradation.cycle_fade_coefficient[j] * m[:Eminus_sum][d-1, j] for j in 1:J)
)
)
# NOTE SOH can be negative
Expand Down Expand Up @@ -182,7 +218,7 @@ function add_degradation(m, p; b="ElectricStorage")
@expression(m, residual_value, sum(residual_values[mth] * m[:dvSOHChangeTimesEnergy][mth] for mth in months))

elseif strategy == "augmentation"

@info "Augmentation BESS degradation costs."
@expression(m, degr_cost,
sum(
p.s.storage.attr[b].degradation.maintenance_cost_per_kwh[d-1] * (m[:SOH][d-1] - m[:SOH][d])
Expand Down
6 changes: 5 additions & 1 deletion src/core/energy_storage/electric_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ Note that not all of the above inputs are necessary. When not providing `calenda
"""
Base.@kwdef mutable struct Degradation
calendar_fade_coefficient::Real = 2.55E-03
cycle_fade_coefficient::Real = 9.83E-05
cycle_fade_coefficient::Vector{<:Real} = [9.83E-05]
time_exponent::Real = 0.42
installed_cost_per_kwh_declination_rate::Real = 0.05
maintenance_strategy::String = "augmentation" # one of ["augmentation", "replacement"]
Expand Down Expand Up @@ -325,6 +325,10 @@ struct ElectricStorage <: AbstractElectricStorage

if haskey(d, :degradation)
degr = Degradation(;dictkeys_tosymbols(d[:degradation])...)

if length(degr.cycle_fade_coefficient) > 1
@info "Modeling segmented cycle fade battery degradation costing"
end
else
degr = Degradation()
end
Expand Down
Loading