Skip to content

Commit c9a51c6

Browse files
authored
Merge pull request #232 from NREL/develop
Develop (improve BESS modeling in ERP)
2 parents c2a0895 + 801c81a commit c9a51c6

File tree

4 files changed

+116
-46
lines changed

4 files changed

+116
-46
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ Classify the change according to the following categories:
2323
### Deprecated
2424
### Removed
2525

26+
## v0.32.3
27+
### Fixed
28+
- Calculate **num_battery_bins** default in `backup_reliability.jl` based on battery duration to prevent significant discretization error (and add test)
29+
- Account for battery (dis)charge efficiency after capping power in/out in `battery_bin_shift()`
30+
- Remove _try_ _catch_ in `backup_reliability(d::Dict, p::REoptInputs, r::Dict)` so can see where error was thrown
31+
2632
## v0.32.2
2733
### Fixed
2834
- Fixed bug in multiple PVs pv_to_location dictionary creation.

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "REopt"
22
uuid = "d36ad4e8-d74a-4f7a-ace1-eaea049febf6"
33
authors = ["Nick Laws", "Hallie Dunham <hallie.dunham@nrel.gov>", "Bill Becker <william.becker@nrel.gov>", "Bhavesh Rathod <bhavesh.rathod@nrel.gov>", "Alex Zolan <alexander.aolan@nrel.gov>", "Amanda Farthing <amanda.farthing@nrel.gov>"]
4-
version = "0.32.2"
4+
version = "0.32.3"
55

66
[deps]
77
ArchGDAL = "c9ce4bd3-c3d5-55b8-8973-c0e20141b8c3"

src/outagesim/backup_reliability.jl

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -418,12 +418,13 @@ function battery_bin_shift(excess_generation_kw::Vector{<:Real}, bin_size::Real,
418418
#Lose energy charging battery and use more energy discharging battery
419419
#Need to shift battery up by less and down by more.
420420

421-
#positive excess generation
422-
excess_generation_kw[excess_generation_kw .> 0] = excess_generation_kw[excess_generation_kw .> 0] .* battery_charge_efficiency
423-
excess_generation_kw[excess_generation_kw .< 0] = excess_generation_kw[excess_generation_kw .< 0] ./ battery_discharge_efficiency
424421
#Battery cannot charge or discharge more than its capacity
425422
excess_generation_kw[excess_generation_kw .> battery_size_kw] .= battery_size_kw
426423
excess_generation_kw[excess_generation_kw .< -battery_size_kw] .= -battery_size_kw
424+
#Account for (dis)charge efficiency
425+
excess_generation_kw[excess_generation_kw .> 0] = excess_generation_kw[excess_generation_kw .> 0] .* battery_charge_efficiency
426+
excess_generation_kw[excess_generation_kw .< 0] = excess_generation_kw[excess_generation_kw .< 0] ./ battery_discharge_efficiency
427+
427428
shift = round.(excess_generation_kw ./ bin_size)
428429
return shift
429430
end
@@ -706,7 +707,7 @@ function survival_with_battery(;
706707
bin_size = battery_size_kwh / (num_battery_bins-1)
707708

708709
#bin initial battery
709-
starting_battery_bins = bin_battery_charge(battery_starting_soc_kwh, num_battery_bins, battery_size_kwh)
710+
starting_battery_bins = bin_battery_charge(battery_starting_soc_kwh, num_battery_bins, battery_size_kwh)
710711
#For easier indice reading
711712
M = num_battery_bins
712713
if length(num_generators) == 1
@@ -760,7 +761,7 @@ function survival_with_battery_single_start_time(
760761
bin_size::Real,
761762
marginal_survival::Bool,
762763
time_steps_per_hour::Real)::Vector{Float64}
763-
764+
764765
gen_battery_prob_matrix_array = [zeros(M, N), zeros(M, N)]
765766
gen_battery_prob_matrix_array[1][starting_battery_bins[t], :] = starting_gens
766767
gen_battery_prob_matrix_array[2][starting_battery_bins[t], :] = starting_gens
@@ -815,7 +816,7 @@ Return a dictionary of inputs required for backup reliability calculations.
815816
-generator_mean_time_to_failure::Real = 1100 Average number of time steps between a generator's failures. 1/(failure to run probability).
816817
-num_generators::Int = 1 Number of generators. Will be determined by code if set to 0 and gen capacity > 0.1
817818
-generator_size_kw::Real = 0.0 Backup generator capacity. Will be determined by REopt optimization if set less than 0.1
818-
-num_battery_bins::Int = 101 Internal value for discretely modeling battery state of charge
819+
-num_battery_bins::Int Number of bins for discretely modeling battery state of charge
819820
-max_outage_duration::Int = 96 Maximum outage time step modeled
820821
-microgrid_only::Bool = false Boolean to specify if only microgrid upgraded technologies run during grid outage
821822
-battery_minimum_soc_fraction::Real = 0.0 The minimum battery state of charge (represented as a fraction) allowed during outages.
@@ -933,7 +934,7 @@ Return a dictionary of inputs required for backup reliability calculations.
933934
-generator_mean_time_to_failure::Real = 1100 Average number of time steps between a generator's failures. 1/(failure to run probability).
934935
-num_generators::Int = 1 Number of generators. Will be determined by code if set to 0 and gen capacity > 0.1
935936
-generator_size_kw::Real = 0.0 Backup generator capacity. Will be determined by REopt optimization if set less than 0.1
936-
-num_battery_bins::Int = 101 Internal value for discretely modeling battery state of charge
937+
-num_battery_bins::Int Number of bins for discretely modeling battery state of charge
937938
-max_outage_duration::Int = 96 Maximum outage duration modeled
938939
-fuel_limit:Union{Real, Vector{<:Real}} = 1e9 Amount of fuel available, either by generator type or per generator, depending on fuel_limit_is_per_generator. Change generator_fuel_burn_rate_per_kwh for different fuel efficiencies. Fuel units should be consistent with generator_fuel_intercept_per_hr and generator_fuel_burn_rate_per_kwh.
939940
-generator_fuel_intercept_per_hr::Union{Real, Vector{<:Real}} = 0.0 Amount of fuel burned each time step while idling. Fuel units should be consistent with fuel_limit and generator_fuel_burn_rate_per_kwh.
@@ -1051,7 +1052,7 @@ Return an array of backup reliability calculations. Inputs can be unpacked from
10511052
-generator_mean_time_to_failure::Union{Real, Vector{<:Real}} = 1100 Average number of time steps between a generator's failures. 1/(failure to run probability).
10521053
-num_generators::Union{Int, Vector{Int}} = 1 Number of generators
10531054
-generator_size_kw::Union{Real, Vector{<:Real}} = 0.0 Backup generator capacity
1054-
-num_battery_bins::Int = 101 Internal value for modeling battery
1055+
-num_battery_bins::Int = num_battery_bins_default(battery_size_kw,battery_size_kwh) Number of bins for discretely modeling battery state of charge
10551056
-max_outage_duration::Int = 96 Maximum outage duration modeled
10561057
-battery_size_kw::Real = 0.0 Battery kW of power capacity
10571058
-battery_size_kwh::Real = 0.0 Battery kWh of energy capacity
@@ -1068,15 +1069,15 @@ function backup_reliability_single_run(;
10681069
generator_mean_time_to_failure::Union{Real, Vector{<:Real}} = 1100,
10691070
num_generators::Union{Int, Vector{Int}} = 1,
10701071
generator_size_kw::Union{Real, Vector{<:Real}} = 0.0,
1071-
num_battery_bins::Int = 101,
10721072
max_outage_duration::Int = 96,
10731073
battery_size_kw::Real = 0.0,
10741074
battery_size_kwh::Real = 0.0,
1075+
num_battery_bins::Int = num_battery_bins_default(battery_size_kw,battery_size_kwh),
10751076
battery_charge_efficiency::Real = 0.948,
10761077
battery_discharge_efficiency::Real = 0.948,
10771078
time_steps_per_hour::Real = 1,
10781079
kwargs...)::Matrix
1079-
1080+
10801081
#No reliability calculations if no outage duration
10811082
if max_outage_duration == 0
10821083
return []
@@ -1434,22 +1435,17 @@ Possible keys in r:
14341435
-generator_mean_time_to_failure::Real = 1100 Average number of time steps between a generator's failures. 1/(failure to run probability).
14351436
-num_generators::Int = 1 Number of generators. Will be determined by code if set to 0 and gen capacity > 0.1
14361437
-generator_size_kw::Real = 0.0 Backup generator capacity. Will be determined by REopt optimization if set less than 0.1
1437-
-num_battery_bins::Int = 101 Internal value for discretely modeling battery state of charge
1438+
-num_battery_bins::Int = depends on battery sizing Number of bins for discretely modeling battery state of charge
14381439
-battery_operational_availability::Real = 0.97 Likelihood battery will be available at start of outage
14391440
-pv_operational_availability::Real = 0.98 Likelihood PV will be available at start of outage
14401441
-max_outage_duration::Int = 96 Maximum outage duration modeled
14411442
-microgrid_only::Bool = false Determines how generator, PV, and battery act during islanded mode
14421443
14431444
"""
14441445
function backup_reliability(d::Dict, p::REoptInputs, r::Dict)
1445-
try
1446-
reliability_inputs = backup_reliability_reopt_inputs(d=d, p=p, r=r)
1447-
cumulative_results, fuel_survival, fuel_used = return_backup_reliability(; reliability_inputs... )
1448-
process_reliability_results(cumulative_results, fuel_survival, fuel_used)
1449-
catch e
1450-
@info e
1451-
return Dict()
1452-
end
1446+
reliability_inputs = backup_reliability_reopt_inputs(d=d, p=p, r=r)
1447+
cumulative_results, fuel_survival, fuel_used = return_backup_reliability(; reliability_inputs... )
1448+
process_reliability_results(cumulative_results, fuel_survival, fuel_used)
14531449
end
14541450

14551451

@@ -1476,7 +1472,7 @@ Possible keys in r:
14761472
-generator_mean_time_to_failure::Real = 1100 Average number of time steps between a generator's failures. 1/(failure to run probability).
14771473
-num_generators::Int = 1 Number of generators. Will be determined by code if set to 0 and gen capacity > 0.1
14781474
-generator_size_kw::Real = 0.0 Backup generator capacity. Will be determined by REopt optimization if set less than 0.1
1479-
-num_battery_bins::Int = 101 Internal value for discretely modeling battery state of charge
1475+
-num_battery_bins::Int = num_battery_bins_default(r[:battery_size_kw],r[:battery_size_kwh]) Number of bins for discretely modeling battery state of charge
14801476
-max_outage_duration::Int = 96 Maximum outage duration modeled
14811477
14821478
"""
@@ -1485,3 +1481,14 @@ function backup_reliability(r::Dict)
14851481
cumulative_results, fuel_survival, fuel_used = return_backup_reliability(; reliability_inputs... )
14861482
process_reliability_results(cumulative_results, fuel_survival, fuel_used)
14871483
end
1484+
1485+
1486+
function num_battery_bins_default(size_kw::Real, size_kwh::Real)::Int
1487+
if size_kw == 0
1488+
return 1
1489+
else
1490+
duration = size_kwh / size_kw
1491+
return Int(duration * 20)
1492+
end
1493+
end
1494+

test/runtests.jl

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -243,32 +243,90 @@ else # run HiGHS tests
243243

244244
@testset "Backup Generator Reliability" begin
245245

246-
#test ensure `backup_reliability()` consistent with `simulate_outages()`
247-
reopt_inputs = JSON.parsefile("./scenarios/backup_reliability_reopt_inputs.json")
248-
reopt_inputs["ElectricLoad"]["annual_kwh"] = 4*reopt_inputs["ElectricLoad"]["annual_kwh"]
249-
p = REoptInputs(reopt_inputs)
250-
model = Model(optimizer_with_attributes(HiGHS.Optimizer,
251-
"output_flag" => false, "log_to_console" => false)
252-
)
253-
results = run_reopt(model, p)
254-
simresults = simulate_outages(results, p)
255-
reliability_inputs = Dict(
256-
"max_outage_duration" => 48,
257-
"generator_operational_availability" => 1.0,
258-
"generator_failure_to_start" => 0.0,
259-
"generator_mean_time_to_failure" => 10000000000,
260-
"fuel_limit" => 1000000000,
261-
"num_battery_bins" => 100,
262-
"battery_operational_availability" => 1.0,
263-
"battery_minimum_soc_fraction" => 0.0,
264-
"pv_operational_availability" => 1.0,
265-
)
266-
reliability_results = backup_reliability(results, p, reliability_inputs)
267-
for i = 1:min(length(simresults["probs_of_surviving"]), reliability_inputs["max_outage_duration"])
268-
@test simresults["probs_of_surviving"][i] reliability_results["mean_cumulative_survival_by_duration"][i] atol=0.001
246+
@testset "Compare backup_reliability and simulate_outages" begin
247+
# Tests ensure `backup_reliability()` consistent with `simulate_outages()`
248+
# First, just battery
249+
reopt_inputs = Dict(
250+
"Site" => Dict(
251+
"longitude" => -106.42077256104001,
252+
"latitude" => 31.810468380036337
253+
),
254+
"ElectricStorage" => Dict(
255+
"min_kw" => 4000,
256+
"max_kw" => 4000,
257+
"min_kwh" => 400000,
258+
"max_kwh" => 400000,
259+
"soc_min_fraction" => 0.8,
260+
"soc_init_fraction" => 0.9
261+
),
262+
"ElectricLoad" => Dict(
263+
"doe_reference_name" => "FlatLoad",
264+
"annual_kwh" => 175200000.0,
265+
"critical_load_fraction" => 0.2
266+
),
267+
"ElectricTariff" => Dict(
268+
"urdb_label" => "5ed6c1a15457a3367add15ae"
269+
),
270+
)
271+
p = REoptInputs(reopt_inputs)
272+
model = Model(optimizer_with_attributes(HiGHS.Optimizer,
273+
"output_flag" => false, "log_to_console" => false)
274+
)
275+
results = run_reopt(model, p)
276+
simresults = simulate_outages(results, p)
277+
278+
reliability_inputs = Dict(
279+
"generator_size_kw" => 0,
280+
"max_outage_duration" => 100,
281+
"generator_operational_availability" => 1.0,
282+
"generator_failure_to_start" => 0.0,
283+
"generator_mean_time_to_failure" => 10000000000,
284+
"fuel_limit" => 0,
285+
"battery_size_kw" => 4000,
286+
"battery_size_kwh" => 400000,
287+
"battery_charge_efficiency" => 1,
288+
"battery_discharge_efficiency" => 1,
289+
"battery_operational_availability" => 1.0,
290+
"battery_minimum_soc_fraction" => 0.0,
291+
"battery_starting_soc_series_fraction" => results["ElectricStorage"]["soc_series_fraction"],
292+
"pv_operational_availability" => 1.0,
293+
"critical_loads_kw" => results["ElectricLoad"]["critical_load_series_kw"]#4000*ones(8760)#p.s.electric_load.critical_loads_kw
294+
)
295+
reliability_results = backup_reliability(reliability_inputs)
296+
297+
#TODO: resolve bug where unlimted fuel markov portion of results goes to zero 1 timestep early
298+
for i = 1:99#min(length(simresults["probs_of_surviving"]), reliability_inputs["max_outage_duration"])
299+
@test simresults["probs_of_surviving"][i] reliability_results["mean_cumulative_survival_by_duration"][i] atol=0.01
300+
@test simresults["probs_of_surviving"][i] reliability_results["unlimited_fuel_mean_cumulative_survival_by_duration"][i] atol=0.01
301+
@test simresults["probs_of_surviving"][i] reliability_results["mean_fuel_survival_by_duration"][i] atol=0.01
302+
end
303+
304+
# Second, gen, PV, battery
305+
reopt_inputs = JSON.parsefile("./scenarios/backup_reliability_reopt_inputs.json")
306+
reopt_inputs["ElectricLoad"]["annual_kwh"] = 4*reopt_inputs["ElectricLoad"]["annual_kwh"]
307+
p = REoptInputs(reopt_inputs)
308+
model = Model(optimizer_with_attributes(HiGHS.Optimizer,
309+
"output_flag" => false, "log_to_console" => false)
310+
)
311+
results = run_reopt(model, p)
312+
simresults = simulate_outages(results, p)
313+
reliability_inputs = Dict(
314+
"max_outage_duration" => 48,
315+
"generator_operational_availability" => 1.0,
316+
"generator_failure_to_start" => 0.0,
317+
"generator_mean_time_to_failure" => 10000000000,
318+
"fuel_limit" => 1000000000,
319+
"battery_operational_availability" => 1.0,
320+
"battery_minimum_soc_fraction" => 0.0,
321+
"pv_operational_availability" => 1.0,
322+
)
323+
reliability_results = backup_reliability(results, p, reliability_inputs)
324+
for i = 1:min(length(simresults["probs_of_surviving"]), reliability_inputs["max_outage_duration"])
325+
@test simresults["probs_of_surviving"][i] reliability_results["mean_cumulative_survival_by_duration"][i] atol=0.001
326+
end
269327
end
270328

271-
#test survival with no generator decreasing and same as with generator but no fuel
329+
# Test survival with no generator decreasing and same as with generator but no fuel
272330
reliability_inputs = Dict(
273331
"critical_loads_kw" => 200 .* (2 .+ sin.(collect(1:8760)*2*pi/24)),
274332
"num_generators" => 0,
@@ -314,7 +372,6 @@ else # run HiGHS tests
314372
"battery_minimum_soc_fraction" => 0.5)
315373

316374

317-
318375
#Given outage starts in time period 1
319376
#____________________________________
320377
#Outage hour 1:

0 commit comments

Comments
 (0)