Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add battery O&M and self-discharge #354

Open
wants to merge 19 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions src/constraints/storage_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="")
+ p.s.storage.attr[b].grid_charge_efficiency * m[Symbol("dvGridToStorage"*_n)][b, ts]
- m[Symbol("dvDischargeFromStorage"*_n)][b,ts] / p.s.storage.attr[b].discharge_efficiency
)
- ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts])
)

# Constraint (4h): state-of-charge for electrical storage - no grid
Expand All @@ -65,6 +66,7 @@ function add_elec_storage_dispatch_constraints(m, p, b; _n="")
sum(p.s.storage.attr[b].charge_efficiency * m[Symbol("dvProductionToStorage"*_n)][b,t,ts] for t in p.techs.elec)
- m[Symbol("dvDischargeFromStorage"*_n)][b, ts] / p.s.storage.attr[b].discharge_efficiency
)
- ((p.s.storage.attr[b].daily_self_discharge_fraction/24/p.hours_per_time_step) * m[Symbol("dvStoredEnergy"*_n)][b, ts])
)

# Constraint (4i)-1: Dispatch to electrical storage is no greater than power capacity
Expand Down
14 changes: 13 additions & 1 deletion src/core/energy_storage/electric_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ end
installed_cost_per_kwh::Real = 455.0
replace_cost_per_kw::Real = 715.0
replace_cost_per_kwh::Real = 318.0
om_cost_per_kw::Real = 0.0
om_cost_per_kwh::Real = 0.0
inverter_replacement_year::Int = 10
battery_replacement_year::Int = 10
macrs_option_years::Int = 7
Expand All @@ -164,6 +166,7 @@ end
model_degradation::Bool = false
degradation::Dict = Dict()
minimum_avg_soc_fraction::Float64 = 0.0
daily_self_discharge_fraction::Float64 = 0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lixiangk1 could you add a little explanatory text here for this input?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey! Did you mean add notes in the code to better define this term or just explain it in the pull request?

The input is a loss term for battery self-discharge, entered as a daily fraction for how much of the stored kWh is leaked from the battery (e.g., 3% of the the kWh stored in the battery is self-discharged per day). This is then modeled as a per timestep loss term where eg. 0.03/24/timesteps_per_hour * dvStoredEnergy is subtracted from dvStoredEnergy at every timestep.

Copy link
Collaborator

@adfarth adfarth Mar 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean adding a comment "# explanation" in the code here so that it appears in the REopt.jl documentation (https://nrel.github.io/REopt.jl/dev/reopt/inputs/#ElectricStorage). We haven't really done this for ElectricStorage inputs but for all of the other techs we've been including some help text around the inputs. If you could do the same for the O&M costs that would be great! Maybe with a note about not double-counting replacement and O&M costs.

```
"""
Base.@kwdef struct ElectricStorageDefaults
Expand All @@ -182,6 +185,8 @@ Base.@kwdef struct ElectricStorageDefaults
installed_cost_per_kwh::Real = 455.0
replace_cost_per_kw::Real = 715.0
replace_cost_per_kwh::Real = 318.0
om_cost_per_kw::Real = 0.0
om_cost_per_kwh::Real = 0.0
inverter_replacement_year::Int = 10
battery_replacement_year::Int = 10
macrs_option_years::Int = 7
Expand All @@ -196,6 +201,7 @@ Base.@kwdef struct ElectricStorageDefaults
model_degradation::Bool = false
degradation::Dict = Dict()
minimum_avg_soc_fraction::Float64 = 0.0
daily_self_discharge_fraction::Float64 = 0.0
end


Expand All @@ -220,6 +226,8 @@ struct ElectricStorage <: AbstractElectricStorage
installed_cost_per_kwh::Real
replace_cost_per_kw::Real
replace_cost_per_kwh::Real
om_cost_per_kw::Real
om_cost_per_kwh::Real
inverter_replacement_year::Int
battery_replacement_year::Int
macrs_option_years::Int
Expand All @@ -236,6 +244,7 @@ struct ElectricStorage <: AbstractElectricStorage
model_degradation::Bool
degradation::Degradation
minimum_avg_soc_fraction::Float64
daily_self_discharge_fraction::Float64

function ElectricStorage(d::Dict, f::Financial)
s = ElectricStorageDefaults(;d...)
Expand Down Expand Up @@ -307,6 +316,8 @@ struct ElectricStorage <: AbstractElectricStorage
s.installed_cost_per_kwh,
replace_cost_per_kw,
replace_cost_per_kwh,
s.om_cost_per_kw,
s.om_cost_per_kwh,
s.inverter_replacement_year,
s.battery_replacement_year,
s.macrs_option_years,
Expand All @@ -322,7 +333,8 @@ struct ElectricStorage <: AbstractElectricStorage
net_present_cost_per_kwh,
s.model_degradation,
degr,
s.minimum_avg_soc_fraction
s.minimum_avg_soc_fraction,
s.daily_self_discharge_fraction
)
end
end
8 changes: 5 additions & 3 deletions src/core/reopt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -363,9 +363,11 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs)
sum( p.s.storage.attr[b].net_present_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.all )
))

@expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om *
sum( p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all )
)
@expression(m, TotalPerUnitSizeOMCosts, p.third_party_factor * p.pwf_om * (
sum(p.om_cost_per_kw[t] * m[:dvSize][t] for t in p.techs.all) +
sum(p.s.storage.attr[b].om_cost_per_kw * m[:dvStoragePower][b] for b in p.s.storage.types.elec) +
sum(p.s.storage.attr[b].om_cost_per_kwh * m[:dvStorageEnergy][b] for b in p.s.storage.types.elec)
))

add_elec_utility_expressions(m, p)

Expand Down
6 changes: 6 additions & 0 deletions src/results/electric_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ function add_electric_storage_results(m::JuMP.AbstractModel, p::REoptInputs, d::
r["initial_capital_cost"] = r["size_kwh"] * p.s.storage.attr[b].installed_cost_per_kwh +
r["size_kw"] * p.s.storage.attr[b].installed_cost_per_kw

StoragePerUnitOMCosts = p.third_party_factor * p.pwf_om * (p.s.storage.attr[b].om_cost_per_kw * m[Symbol("dvStoragePower")][b] +
p.s.storage.attr[b].om_cost_per_kwh * m[Symbol("dvStorageEnergy")][b])

r["lifecycle_om_cost_after_tax"] = round(value(StoragePerUnitOMCosts) * (1 - p.s.financial.owner_tax_rate_fraction), digits=0)
r["year_one_om_cost_before_tax"] = round(value(StoragePerUnitOMCosts) / (p.pwf_om * p.third_party_factor), digits=0)

if p.s.storage.attr[b].model_degradation
r["state_of_health"] = value.(m[:SOH]).data / value.(m[:dvStorageEnergy])["ElectricStorage"];
r["maintenance_cost"] = value(m[:degr_cost])
Expand Down
Loading
Loading