Skip to content

Commit ac70848

Browse files
authored
Merge pull request #419 from NREL/develop
v0.47.2
2 parents 4bd24d5 + 4ba2d47 commit ac70848

8 files changed

+1410
-120
lines changed

CHANGELOG.md

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,15 @@ Classify the change according to the following categories:
2323
### Deprecated
2424
### Removed
2525

26+
## v0.47.2
27+
### Fixed
28+
- Increased the big-M bound on maximum net metering benefit to prevent artificially low export benefits.
29+
- Fixed a bug in which tier limits did not load correctly when the number of tiers vary by period in the inputs.
30+
- Set a limit for demand and energy tier maxes to avoid errors returned by HiGHS due to numerical limits.
31+
- Index utility rate demand and energy tier limits on month and/or ratchet in addition to tier. This allows for the inclusion of multi-tiered energy and demand rates in which the rates may vary by month or ratchet, whereas previously only the maximum tier limit was used.
32+
### Added
33+
- Added thermal efficiency as input to chp defaults function.
34+
2635
## v0.47.1
2736
### Fixed
2837
- Type issue with `CoolingLoad` monthly energy input
@@ -178,7 +187,7 @@ Classify the change according to the following categories:
178187
## v0.37.5
179188
### Fixed
180189
- Fixed AVERT emissions profiles for NOx. Were previously the same as the SO2 profiles. AVERT emissions profiles are currently generated from AVERT v3.2 https://www.epa.gov/avert/download-avert. See REopt User Manual for more information.
181-
- Fix setting of equal demand tiers in scrub_urdb_demand_tiers!, which was previously causing an error.
190+
- Fix setting of equal demand tiers in `scrub_urdb_demand_tiers`, now renamed `scrub_urdb_tiers`.
182191
- When calling REopt.jl from a python environment using PyJulia and PyCall, some urdb_response fields get converted from a list-of-lists to a matrix type, when REopt.jl expects an array type. This fix adds checks on the type for two urdb_response fields and converts them to an array if needed.
183192
- Update the outages dispatch results to align with CHP availability during outages
184193

Project.toml

+1-1
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 <[email protected]>", "Bill Becker <[email protected]>", "Bhavesh Rathod <[email protected]>", "Alex Zolan <[email protected]>", "Amanda Farthing <[email protected]>"]
4-
version = "0.47.1"
4+
version = "0.47.2"
55

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

src/constraints/electric_utility_constraints.jl

+8-8
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ function add_export_constraints(m, p; _n="")
5252
@warn "Adding binary variable for net metering choice. Some solvers are slow with binaries."
5353

5454
# Good to bound the benefit - we use max_bene as a lower bound because the benefit is treated as a negative cost
55-
max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:NEM])])*10
55+
max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:NEM])])*p.pwf_e*p.hours_per_time_step*10
5656
NEM_benefit = @variable(m, lower_bound = max_bene)
5757

5858

@@ -135,7 +135,7 @@ function add_export_constraints(m, p; _n="")
135135
else
136136
binWHL = @variable(m, binary = true)
137137
@warn "Adding binary variable for wholesale export choice. Some solvers are slow with binaries."
138-
max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:WHL])])*10
138+
max_bene = sum([ld*rate for (ld,rate) in zip(p.s.electric_load.loads_kw, p.s.electric_tariff.export_rates[:WHL])])*p.pwf_e*p.hours_per_time_step*100
139139
WHL_benefit = @variable(m, lower_bound = max_bene)
140140

141141
@constraint(m, binNEM + binWHL == 1) # can either NEM or WHL export, not both
@@ -200,7 +200,7 @@ function add_monthly_peak_constraint(m, p; _n="")
200200
b = m[Symbol(dv)]
201201
# Upper bound on peak electrical power demand by month, tier; if tier is selected (0 o.w.)
202202
@constraint(m, [mth in p.months, tier in 1:ntiers],
203-
m[Symbol("dvPeakDemandMonth"*_n)][mth, tier] <= p.s.electric_tariff.monthly_demand_tier_limits[tier] *
203+
m[Symbol("dvPeakDemandMonth"*_n)][mth, tier] <= p.s.electric_tariff.monthly_demand_tier_limits[mth, tier] *
204204
b[mth, tier]
205205
)
206206

@@ -209,7 +209,7 @@ function add_monthly_peak_constraint(m, p; _n="")
209209

210210
# One monthly peak electrical power demand tier must be full before next one is active
211211
@constraint(m, [mth in p.months, tier in 2:ntiers],
212-
b[mth, tier] * p.s.electric_tariff.monthly_demand_tier_limits[tier-1] <=
212+
b[mth, tier] * p.s.electric_tariff.monthly_demand_tier_limits[mth, tier-1] <=
213213
m[Symbol("dvPeakDemandMonth"*_n)][mth, tier-1]
214214
)
215215
# TODO implement NewMaxDemandMonthsInTier, which adds mth index to monthly_demand_tier_limits
@@ -233,7 +233,7 @@ function add_tou_peak_constraint(m, p; _n="")
233233

234234
# Upper bound on peak electrical power demand by tier, by ratchet, if tier is selected (0 o.w.)
235235
@constraint(m, [r in p.ratchets, tier in 1:ntiers],
236-
m[Symbol("dvPeakDemandTOU"*_n)][r, tier] <= p.s.electric_tariff.tou_demand_tier_limits[tier] * b[r, tier]
236+
m[Symbol("dvPeakDemandTOU"*_n)][r, tier] <= p.s.electric_tariff.tou_demand_tier_limits[r, tier] * b[r, tier]
237237
)
238238

239239
# Ratchet peak electrical power ratchet tier ordering
@@ -243,7 +243,7 @@ function add_tou_peak_constraint(m, p; _n="")
243243

244244
# One ratchet peak electrical power demand tier must be full before next one is active
245245
@constraint(m, [r in p.ratchets, tier in 2:ntiers],
246-
b[r, tier] * p.s.electric_tariff.tou_demand_tier_limits[tier-1]
246+
b[r, tier] * p.s.electric_tariff.tou_demand_tier_limits[r, tier-1]
247247
<= m[Symbol("dvPeakDemandTOU"*_n)][r, tier-1]
248248
)
249249
end
@@ -299,15 +299,15 @@ function add_energy_tier_constraints(m, p; _n="")
299299
##Constraint (10a): Usage limits by pricing tier, by month
300300
@constraint(m, [mth in p.months, tier in 1:p.s.electric_tariff.n_energy_tiers],
301301
p.hours_per_time_step * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier] for ts in p.s.electric_tariff.time_steps_monthly[mth] )
302-
<= b[mth, tier] * p.s.electric_tariff.energy_tier_limits[tier]
302+
<= b[mth, tier] * p.s.electric_tariff.energy_tier_limits[mth, tier]
303303
)
304304
##Constraint (10b): Ordering of pricing tiers
305305
@constraint(m, [mth in p.months, tier in 2:p.s.electric_tariff.n_energy_tiers],
306306
b[mth, tier] - b[mth, tier-1] <= 0
307307
)
308308
## Constraint (10c): One tier must be full before any usage in next tier
309309
@constraint(m, [mth in p.months, tier in 2:p.s.electric_tariff.n_energy_tiers],
310-
b[mth, tier] * p.s.electric_tariff.energy_tier_limits[tier-1] -
310+
b[mth, tier] * p.s.electric_tariff.energy_tier_limits[mth, tier-1] -
311311
sum( m[Symbol("dvGridPurchase"*_n)][ts, tier-1] for ts in p.s.electric_tariff.time_steps_monthly[mth])
312312
<= 0
313313
)

src/core/chp.jl

+18-8
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,8 @@ function CHP(d::Dict;
221221
boiler_efficiency=eff,
222222
avg_electric_load_kw=avg_electric_load_kw,
223223
max_electric_load_kw=max_electric_load_kw,
224-
is_electric_only=chp.is_electric_only)
224+
is_electric_only=chp.is_electric_only,
225+
thermal_efficiency=chp.thermal_efficiency_full_load)
225226
defaults = chp_defaults_response["default_inputs"]
226227
for (k, v) in custom_chp_inputs
227228
if k in [:installed_cost_per_kw, :tech_sizes_for_cost_curve]
@@ -336,7 +337,8 @@ end
336337
boiler_efficiency::Union{Float64, Nothing}=nothing,
337338
avg_electric_load_kw::Union{Float64, Nothing}=nothing,
338339
max_electric_load_kw::Union{Float64, Nothing}=nothing,
339-
is_electric_only::Bool=false)
340+
is_electric_only::Bool=false,
341+
thermal_efficiency::Float64=NaN)
340342
341343
Depending on the set of inputs, different sets of outputs are determine in addition to all CHP cost and performance parameter defaults:
342344
1. Inputs: hot_water_or_steam and avg_boiler_fuel_load_mmbtu_per_hour
@@ -371,7 +373,8 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri
371373
boiler_efficiency::Union{Float64, Nothing}=nothing,
372374
avg_electric_load_kw::Union{Float64, Nothing}=nothing,
373375
max_electric_load_kw::Union{Float64, Nothing}=nothing,
374-
is_electric_only::Union{Bool, Nothing}=nothing)
376+
is_electric_only::Union{Bool, Nothing}=nothing,
377+
thermal_efficiency::Float64=NaN)
375378

376379
prime_mover_defaults_all = JSON.parsefile(joinpath(@__DIR__, "..", "..", "data", "chp", "chp_defaults.json"))
377380
avg_boiler_fuel_load_under_recip_over_ct = Dict([("hot_water", 27.0), ("steam", 7.0)]) # [MMBtu/hr] Based on external calcs for size versus production by prime_mover type
@@ -434,7 +437,7 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri
434437
boiler_effic = boiler_efficiency
435438
end
436439
chp_elec_size_heuristic_kw = get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour,
437-
prime_mover, size_class_calc, hot_water_or_steam, boiler_effic)
440+
prime_mover, size_class_calc, hot_water_or_steam, boiler_effic, thermal_efficiency)
438441
chp_max_size_kw = 2 * chp_elec_size_heuristic_kw
439442
# If available, calculate heuristic CHP size based on average electric load, and max size based on peak electric load
440443
elseif !isnothing(avg_electric_load_kw) && !isnothing(max_electric_load_kw)
@@ -482,7 +485,7 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri
482485
while !(size_class in size_class_last)
483486
append!(size_class_last, size_class)
484487
chp_elec_size_heuristic_kw = get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour,
485-
prime_mover, size_class, hot_water_or_steam, boiler_effic)
488+
prime_mover, size_class, hot_water_or_steam, boiler_effic, thermal_efficiency)
486489
chp_max_size_kw = 2 * chp_elec_size_heuristic_kw
487490
size_class = get_size_class_from_size(chp_elec_size_heuristic_kw, class_bounds, n_classes)
488491
end
@@ -515,11 +518,18 @@ function get_chp_defaults_prime_mover_size_class(;hot_water_or_steam::Union{Stri
515518
end
516519

517520
function get_heuristic_chp_size_kw(prime_mover_defaults_all, avg_boiler_fuel_load_mmbtu_per_hour,
518-
prime_mover, size_class, hot_water_or_steam, boiler_effic)
519-
therm_effic = prime_mover_defaults_all[prime_mover]["thermal_efficiency_full_load"][hot_water_or_steam][size_class+1]
521+
prime_mover, size_class, hot_water_or_steam, boiler_effic, thermal_efficiency=NaN)
522+
if isnan(thermal_efficiency)
523+
therm_effic = prime_mover_defaults_all[prime_mover]["thermal_efficiency_full_load"][hot_water_or_steam][size_class+1]
524+
else
525+
therm_effic = thermal_efficiency
526+
end
520527
if therm_effic == 0.0
528+
if prime_mover == "micro_turbine" && isnothing(size_class)
529+
size_class = "any"
530+
end
521531
throw(@error("Error trying to calculate heuristic CHP size based on average thermal load because the
522-
thermal efficiency of prime mover $prime_mover for generating $hot_water_or_steam is 0.0"))
532+
thermal efficiency of prime_mover $prime_mover (size_class $size_class) for generating $hot_water_or_steam is 0.0"))
523533
end
524534
elec_effic = prime_mover_defaults_all[prime_mover]["electric_efficiency_full_load"][size_class+1]
525535
avg_heating_thermal_load_mmbtu_per_hr = avg_boiler_fuel_load_mmbtu_per_hour * boiler_effic

src/core/electric_tariff.jl

+8-8
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,17 @@
88
"""
99
struct ElectricTariff
1010
energy_rates::AbstractArray{Float64, 2} # gets a second dim with tiers
11-
energy_tier_limits::AbstractArray{Float64,1}
11+
energy_tier_limits::AbstractArray{Float64,2} # month X tier
1212
n_energy_tiers::Int
1313

1414
monthly_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers
1515
time_steps_monthly::AbstractArray{AbstractArray{Int64,1},1} # length = 0 or 12
16-
monthly_demand_tier_limits::AbstractArray{Float64,1}
16+
monthly_demand_tier_limits::AbstractArray{Float64,2} # month X tier
1717
n_monthly_demand_tiers::Int
1818

1919
tou_demand_rates::AbstractArray{Float64, 2} # gets a second dim with tiers
2020
tou_demand_ratchet_time_steps::AbstractArray{AbstractArray{Int64,1},1} # length = n_tou_demand_ratchets
21-
tou_demand_tier_limits::AbstractArray{Float64,1}
21+
tou_demand_tier_limits::AbstractArray{Float64,2} # ratchet X tier
2222
n_tou_demand_tiers::Int
2323

2424
demand_lookback_months::AbstractArray{Int,1}
@@ -42,7 +42,7 @@ end
4242
`ElectricTariff` is a required REopt input for on-grid scenarios only (it cannot be supplied when `Settings.off_grid_flag` is true) with the following keys and default values:
4343
```julia
4444
urdb_label::String="",
45-
urdb_response::Dict=Dict(),
45+
urdb_response::Dict=Dict(), # Response JSON for URDB rates. Note: if creating your own urdb_response, ensure periods are zero-indexed.
4646
urdb_utility_name::String="",
4747
urdb_rate_name::String="",
4848
wholesale_rate::T1=nothing, # Price of electricity sold back to the grid in absence of net metering. Can be a scalar value, which applies for all-time, or an array with time-sensitive values. If an array is input then it must have a length of 8760, 17520, or 35040. The inputed array values are up/down-sampled using mean values to match the Settings.time_steps_per_hour.
@@ -120,11 +120,11 @@ function ElectricTariff(;
120120
# TODO remove_tiers for multinode models
121121
nem_rate = Float64[]
122122

123-
energy_tier_limits = Float64[]
123+
energy_tier_limits = Array{Float64,2}(undef, 0, 0)
124124
n_energy_tiers = 1
125-
monthly_demand_tier_limits = Float64[]
125+
monthly_demand_tier_limits = Array{Float64,2}(undef, 0, 0)
126126
n_monthly_demand_tiers = 1
127-
tou_demand_tier_limits = Float64[]
127+
tou_demand_tier_limits = Array{Float64,2}(undef, 0, 0)
128128
n_tou_demand_tiers = 1
129129
time_steps_monthly = get_monthly_time_steps(year, time_steps_per_hour=time_steps_per_hour)
130130

@@ -241,7 +241,7 @@ function ElectricTariff(;
241241
if remove_tiers
242242
energy_rates, monthly_demand_rates, tou_demand_rates = remove_tiers_from_urdb_rate(u)
243243
energy_tier_limits, monthly_demand_tier_limits, tou_demand_tier_limits =
244-
Float64[], Float64[], Float64[]
244+
Array{Float64,2}(undef, 0, 0), Array{Float64,2}(undef, 0, 0), Array{Float64,2}(undef, 0, 0)
245245
n_energy_tiers, n_monthly_demand_tiers, n_tou_demand_tiers = 1, 1, 1
246246
end
247247

0 commit comments

Comments
 (0)