Skip to content

Commit 5b38812

Browse files
committed
initial structure
1 parent a57d024 commit 5b38812

1 file changed

Lines changed: 73 additions & 26 deletions

File tree

src/core/energy_storage/electric_storage.jl

Lines changed: 73 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -172,10 +172,7 @@ end
172172
internal_efficiency_fraction::Float64 = 0.975
173173
inverter_efficiency_fraction::Float64 = 0.96
174174
rectifier_efficiency_fraction::Float64 = 0.96
175-
soc_min_fraction::Float64 = 0.2
176-
soc_min_applies_during_outages::Bool = false
177-
soc_init_fraction::Float64 = off_grid_flag ? 1.0 : 0.5
178-
can_grid_charge::Bool = off_grid_flag ? false : true
175+
can_grid_charge::Bool = off_grid_flag ? false : true # TODO: is this relevant for all dispatch strategies?
179176
installed_cost_per_kw::Real = 968.0 # Cost of power components (e.g., inverter and BOS)
180177
installed_cost_per_kwh::Real = 253.0 # Cost of energy components (e.g., battery pack)
181178
installed_cost_constant::Real = 222115.0 # "+c" constant cost that is added to total ElectricStorage installed costs if a battery is included. Accounts for costs not expected to scale with power or energy capacity.
@@ -196,15 +193,33 @@ end
196193
discharge_efficiency::Float64 = inverter_efficiency_fraction * internal_efficiency_fraction^0.5
197194
grid_charge_efficiency::Float64 = can_grid_charge ? charge_efficiency : 0.0
198195
model_degradation::Bool = false
199-
degradation::Dict = Dict()
200-
minimum_avg_soc_fraction::Float64 = 0.0
201-
optimize_soc_init_fraction::Bool = false # If true, soc_init_fraction will not apply. Model will optimize initial SOC and constrain initial SOC = final SOC.
196+
degradation::Dict = Dict()
202197
min_duration_hours::Real = 0.0 # Minimum amount of time storage can discharge at its rated power capacity
203198
max_duration_hours::Real = 100000.0 # Maximum amount of time storage can discharge at its rated power capacity (ratio of ElectricStorage size_kwh to size_kw)
199+
200+
# Dispatch-related inputs
201+
dispatch_strategy::String = "optimized" # can be one of ["optimized", "peak_shaving", "self_consumption", "backup", "custom_soc"] # Note: "daily_foresight_optimized" is available only via the REopt API
202+
# SOC inputs relevant if dispatch_strategy = "optimized", "peak_shaving", "self_consumption", or "backup" #TODO: confirm this.
203+
soc_min_fraction::Float64 = dispatch_strategy == "backup" ? 0.8 : 0.2
204+
soc_min_applies_during_outages::Bool = false
205+
soc_init_fraction::Float64 = off_grid_flag ? 1.0 : 0.5
206+
minimum_avg_soc_fraction::Float64 = 0.0
207+
optimize_soc_init_fraction::Bool = false # If true, soc_init_fraction will not apply. Model will optimize initial SOC and constrain initial SOC = final SOC.
208+
# SOC inputs relevant if dispatch_strategy = "custom_soc"
204209
fixed_soc_series_fraction::Union{Nothing, Array{<:Real,1}} = nothing # If provided, SOC (as fraction of total energy capacity) will not be optimized and will instead be fixed to the values provided here +- the absolute fixed_soc_series_fraction_tolerance (this buffer is to avoid infeasible solutions)
205210
fixed_soc_series_fraction_tolerance::Union{Nothing, Real} = !isnothing(fixed_soc_series_fraction) ? 0.05 : nothing # Absolute tolerance on fixed_soc_series_fraction to avoid infeasible solutions when fixed_soc_series_fraction is provided.
211+
206212
207-
```
213+
214+
!!! note "Dispatch Strategy Options"
215+
The following dispatch strategies are available via the `dispatch_strategy` input:
216+
- `optimized`: Storage dispatch is optimized to minimize the total lifecycle cost of energy for the site. The model has perfect foresight into loads and modeled variable generation potential over the entire year.
217+
- `peak_shaving`: Uses SAM's Peak Shaving dispatch heuristic. To use this option, users MUST specify BESS (and PV if included) sizing (by setting min and max values) # TODO: Xiang to update
218+
- `self_consumption`: To use this option, users MUST specify BESS (and PV if included) sizing (by setting min and max values) # TODO: Xiang to update
219+
- `backup`: Storage is reserved to meet load during grid outages by changing the default soc_min_fraction to 0.8.
220+
- `daily_foresight_optimized`: This option is only available via the REopt API (not available in REopt.jl)
221+
- `custom_soc`: User must provide a fixed_soc_series_fraction and can optionally tailor the fixed_soc_series_fraction_tolerance.
222+
208223
"""
209224
Base.@kwdef struct ElectricStorageDefaults
210225
off_grid_flag::Bool = false
@@ -215,10 +230,7 @@ Base.@kwdef struct ElectricStorageDefaults
215230
internal_efficiency_fraction::Float64 = 0.975
216231
inverter_efficiency_fraction::Float64 = 0.96
217232
rectifier_efficiency_fraction::Float64 = 0.96
218-
soc_min_fraction::Float64 = 0.2
219-
soc_min_applies_during_outages::Bool = false
220-
soc_init_fraction::Float64 = off_grid_flag ? 1.0 : 0.5
221-
can_grid_charge::Bool = off_grid_flag ? false : true
233+
can_grid_charge::Bool = off_grid_flag ? false : true # TODO: is this relevant for all dispatch strategies?
222234
installed_cost_per_kw::Real = 968.0
223235
installed_cost_per_kwh::Real = 253.0
224236
installed_cost_constant::Real = 222115.0
@@ -240,12 +252,16 @@ Base.@kwdef struct ElectricStorageDefaults
240252
grid_charge_efficiency::Float64 = can_grid_charge ? charge_efficiency : 0.0
241253
model_degradation::Bool = false
242254
degradation::Dict = Dict()
243-
minimum_avg_soc_fraction::Float64 = 0.0
244-
optimize_soc_init_fraction::Bool = false
245255
min_duration_hours::Real = 0.0
246256
max_duration_hours::Real = 100000.0
247-
fixed_soc_series_fraction::Union{Nothing, Array{<:Real,1}} = nothing
248-
fixed_soc_series_fraction_tolerance::Union{Nothing, Real} = !isnothing(fixed_soc_series_fraction) ? 0.05 : nothing
257+
dispatch_strategy::String = "optimized" # can be one of ["optimized", "peak_shaving", "self_consumption", "backup", "custom_soc"]
258+
soc_min_fraction::Float64 = dispatch_strategy == "backup" ? 0.8 : 0.2
259+
soc_min_applies_during_outages::Bool = false
260+
soc_init_fraction::Float64 = off_grid_flag ? 1.0 : 0.5
261+
minimum_avg_soc_fraction::Float64 = 0.0
262+
optimize_soc_init_fraction::Bool = false # If true, soc_init_fraction will not apply. Model will optimize initial SOC and constrain initial SOC = final SOC.
263+
fixed_soc_series_fraction::Union{Nothing, Array{<:Real,1}} = nothing # If provided, SOC (as fraction of total energy capacity) will not be optimized and will instead be fixed to the values provided here +- the absolute fixed_soc_series_fraction_tolerance (this buffer is to avoid infeasible solutions)
264+
fixed_soc_series_fraction_tolerance::Union{Nothing, Real} = !isnothing(fixed_soc_series_fraction) ? 0.05 : nothing # Absolute tolerance on fixed_soc_series_fraction to avoid infeasible solutions when fixed_soc_series_fraction is provided.
249265
end
250266

251267

@@ -263,9 +279,6 @@ struct ElectricStorage <: AbstractElectricStorage
263279
internal_efficiency_fraction::Float64
264280
inverter_efficiency_fraction::Float64
265281
rectifier_efficiency_fraction::Float64
266-
soc_min_fraction::Float64
267-
soc_min_applies_during_outages::Bool
268-
soc_init_fraction::Float64
269282
can_grid_charge::Bool
270283
installed_cost_per_kw::Real
271284
installed_cost_per_kwh::Real
@@ -291,13 +304,18 @@ struct ElectricStorage <: AbstractElectricStorage
291304
net_present_cost_cost_constant::Real
292305
model_degradation::Bool
293306
degradation::Degradation
294-
minimum_avg_soc_fraction::Float64
295-
optimize_soc_init_fraction::Bool
296307
min_duration_hours::Real
297308
max_duration_hours::Real
309+
dispatch_strategy::String
310+
soc_min_fraction::Float64
311+
soc_min_applies_during_outages::Bool
312+
soc_init_fraction::Float64
313+
minimum_avg_soc_fraction::Float64
314+
optimize_soc_init_fraction::Bool
298315
fixed_soc_series_fraction::Union{Nothing, Array{<:Real,1}}
299316
fixed_soc_series_fraction_tolerance::Union{Nothing, Real}
300317

318+
301319
function ElectricStorage(d::Dict, f::Financial, s::Site)
302320
set_sector_defaults!(d; struct_name="Storage", sector=s.sector, federal_procurement_type=s.federal_procurement_type)
303321
s = ElectricStorageDefaults(;d...)
@@ -314,6 +332,34 @@ struct ElectricStorage <: AbstractElectricStorage
314332
throw(@error("ElectricStorage min_duration_hours must be less than max_duration_hours."))
315333
end
316334

335+
# Dispatch validation
336+
valid_dispatch_strategies = ["optimized", "peak_shaving", "self_consumption", "backup", "custom_soc"]
337+
if !(s.dispatch_strategy in valid_dispatch_strategies)
338+
throw(@error("ElectricStorage dispatch_strategy must be one of the following: $(valid_dispatch_strategies)"))
339+
end
340+
if s.dispatch_strategy == "custom_soc" && isnothing(s.fixed_soc_series_fraction)
341+
throw(@error("ElectricStorage fixed_soc_series_fraction must be provided when dispatch_strategy is custom_soc."))
342+
end
343+
if s.dispatch_strategy != "custom_soc" && !isnothing(s.fixed_soc_series_fraction)
344+
@warn "Updating ElectricStorage dispatch_strategy to custom_soc since fixed_soc_series_fraction is provided."
345+
s.dispatch_strategy = "custom_soc"
346+
end
347+
requires_fixed_sizing = ["peak_shaving", "self_consumption"]
348+
if s.dispatch_strategy in requires_fixed_sizing && (s.min_kw != s.max_kw || s.min_kwh != s.max_kwh || s.max_kw == 0 || s.max_kwh == 0)
349+
throw(@error("ElectricStorage dispatch_strategy $(s.dispatch_strategy) requires fixed non-zero storage sizing. Please fix the sizing by setting min_kw=max_kw, and min_kwh=max_kwh."))
350+
end
351+
352+
# Call SAM for peak_shaving and self_consumption dispatch strategies
353+
if s.dispatch_strategy == "peak_shaving"
354+
@info "Using SAM Peak Shaving dispatch strategy for ElectricStorage."
355+
# Call SAM here?
356+
# fixed_soc_series_fraction = SAM output
357+
elseif s.dispatch_strategy == "self_consumption"
358+
@info "Using SAM Self Consumption dispatch strategy for ElectricStorage."
359+
# Call SAM here?
360+
# fixed_soc_series_fraction = SAM output
361+
end
362+
317363
# Copy SOC input in case we need to change them
318364
soc_init_fraction = s.soc_init_fraction
319365
soc_min_fraction = s.soc_min_fraction
@@ -414,9 +460,6 @@ struct ElectricStorage <: AbstractElectricStorage
414460
s.internal_efficiency_fraction,
415461
s.inverter_efficiency_fraction,
416462
s.rectifier_efficiency_fraction,
417-
soc_min_fraction,
418-
s.soc_min_applies_during_outages,
419-
soc_init_fraction,
420463
s.can_grid_charge,
421464
s.installed_cost_per_kw,
422465
s.installed_cost_per_kwh,
@@ -442,10 +485,14 @@ struct ElectricStorage <: AbstractElectricStorage
442485
net_present_cost_cost_constant,
443486
s.model_degradation,
444487
degr,
445-
minimum_avg_soc_fraction,
446-
optimize_soc_init_fraction,
447488
s.min_duration_hours,
448489
s.max_duration_hours,
490+
s.dispatch_strategy,
491+
soc_min_fraction,
492+
s.soc_min_applies_during_outages,
493+
soc_init_fraction,
494+
minimum_avg_soc_fraction,
495+
optimize_soc_init_fraction,
449496
s.fixed_soc_series_fraction,
450497
s.fixed_soc_series_fraction_tolerance
451498
)

0 commit comments

Comments
 (0)