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 16 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,15 @@ Classify the change according to the following categories:
### Deprecated
### Removed

## Develop 08-09-2024
## Develop 2024-09-07
### Added
- Added new inputs **om_cost_per_kw** and **om_cost_per_kwh** to `ElectricStorage` for modeling capacity-based O&M
- Added new input **soc_based_per_ts_self_discharge_fraction** to `ElectricStorage` for modeling SOC-based battery self-discharge
- Added new input **capacity_based_per_ts_self_discharge_fraction** to `ElectricStorage` for modeling capacity-based battery self-discharge
- Added new outputs **lifecycle_om_cost_after_tax** and **year_one_om_cost_before_tax** to `ElectricStorage`
- Added testsets **Electric Storage O&M** and **Electric Storage Self-Discharge** in `test/runtests.jl`

## v0.47.3
### Changed
- Improve the full test suite reporting with a verbose summary table, and update the structure to reflect long-term open-source solver usage
- Removed MacOS from the runner list and just run with Windows OS, since MacOS commonly freezes and gets cancelled. We have not seen Windows OS pass while other OS's fail. .
Expand Down
4 changes: 4 additions & 0 deletions src/constraints/storage_constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ 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].soc_based_per_ts_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts])
- (p.s.storage.attr[b].capacity_based_per_ts_self_discharge_fraction * m[Symbol("dvStorageEnergy"*_n)][b])
)

# Constraint (4h): state-of-charge for electrical storage - no grid
Expand All @@ -65,6 +67,8 @@ 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].soc_based_per_ts_self_discharge_fraction * m[Symbol("dvStoredEnergy"*_n)][b, ts])
- (p.s.storage.attr[b].capacity_based_per_ts_self_discharge_fraction * m[Symbol("dvStorageEnergy"*_n)][b])
)

# Constraint (4i)-1: Dispatch to electrical storage is no greater than power capacity
Expand Down
20 changes: 18 additions & 2 deletions src/core/energy_storage/electric_storage.jl
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,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 # Capacity-based O&M costs in \$/kW-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement.
om_cost_per_kwh::Real = 0.0 # Capacity-based O&M costs in \$/kWh-rated/year. When including battery replacement costs, the user should ensure that O&M costs do not account for augmentation/replacement.
inverter_replacement_year::Int = 10
battery_replacement_year::Int = 10
macrs_option_years::Int = 7
Expand All @@ -165,7 +167,9 @@ end
model_degradation::Bool = false
degradation::Dict = Dict()
minimum_avg_soc_fraction::Float64 = 0.0
```
capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on the rated kWh capacity of the sized storage system.
soc_based_per_ts_self_discharge_fraction::Float64 = 0.0 # Battery self-discharge as a fraction per timestep loss based on kWh stored in each timestep
```
"""
Base.@kwdef struct ElectricStorageDefaults
off_grid_flag::Bool = false
Expand All @@ -184,6 +188,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 @@ -198,6 +204,8 @@ Base.@kwdef struct ElectricStorageDefaults
model_degradation::Bool = false
degradation::Dict = Dict()
minimum_avg_soc_fraction::Float64 = 0.0
capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0
soc_based_per_ts_self_discharge_fraction::Float64 = 0.0
end


Expand All @@ -223,6 +231,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 @@ -239,6 +249,8 @@ struct ElectricStorage <: AbstractElectricStorage
model_degradation::Bool
degradation::Degradation
minimum_avg_soc_fraction::Float64
capacity_based_per_ts_self_discharge_fraction::Float64
soc_based_per_ts_self_discharge_fraction::Float64

function ElectricStorage(d::Dict, f::Financial)
s = ElectricStorageDefaults(;d...)
Expand Down Expand Up @@ -311,6 +323,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 @@ -326,7 +340,9 @@ struct ElectricStorage <: AbstractElectricStorage
net_present_cost_per_kwh,
s.model_degradation,
degr,
s.minimum_avg_soc_fraction
s.minimum_avg_soc_fraction,
s.capacity_based_per_ts_self_discharge_fraction,
s.soc_based_per_ts_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 @@ -392,9 +392,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
2 changes: 2 additions & 0 deletions src/mpc/structs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,8 @@ Base.@kwdef struct MPCElectricStorage <: AbstractElectricStorage
max_kw::Float64 = size_kw
max_kwh::Float64 = size_kwh
minimum_avg_soc_fraction::Float64 = 0.0
capacity_based_per_ts_self_discharge_fraction::Float64 = 0.0
soc_based_per_ts_self_discharge_fraction::Float64 = 0.0
end


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"*_n)][b] +
p.s.storage.attr[b].om_cost_per_kwh * m[Symbol("dvStorageEnergy"*_n)][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