Skip to content

Commit 9d50481

Browse files
authored
Merge pull request #55 from NREL/develop
v0.16.0 offgrid capability, bug fixes, name changes
2 parents 857b2a3 + 25f97fa commit 9d50481

72 files changed

Lines changed: 1343 additions & 728 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# REopt Changelog
22

3+
## v0.16.0
4+
Allows users to model "off-grid" systems as a year-long outage:
5+
- add flag to "turn on" off-grid modeling `Settings.off_grid_flag`
6+
- when `off_grid_flag` is "true", adjust default values in core/ `electric_storage`, `electric_load`, `financial`, `generator`, `pv`
7+
- add operating reserve requirement inputs, outputs, and constraints based on load and PV generation
8+
- add minimum load met percent input and constraint
9+
- add generator replacement year and cost (for off-grid and on-grid)
10+
- add off-grid additional annual costs (tax deductible) and upfront capital costs (depreciable via straight line depreciation)
11+
12+
Name changes:
13+
- consistently append `_before_tax` and `_after_tax` to results names
14+
- change all instances of `timestep` to `time_step` and `timesteps` to `time_steps`
15+
16+
Other changes:
17+
- report previously missing lcc breakdown components, all reported in `results/financial.jl`
18+
- change variable types from Float to Real to allow users to enter Ints (where applicable)
19+
- `year_one_coincident_peak_cost_after_tax` is now correctly multiplied by `(1 - p.s.financial.offtaker_tax_pct)`
20+
321
## v0.15.2
422
- bug fix for 15 & 30 minute electric, heating, and cooling loads
523
- bug fix for URDB fixed charges
@@ -55,7 +73,7 @@
5573
- add ElectricLoad.blended_doe_reference_names & blended_doe_reference_percents
5674
- add ElectricLoad.monthly_totals_kwh builtin profile scaling
5775
- add ElectricTariff inputs: `add_monthly_rates_to_urdb_rate`, `tou_energy_rates_per_kwh`,
58-
`add_tou_energy_rates_to_urdb_rate`, `coincident_peak_load_charge_per_kw`, `coincident_peak_load_active_timesteps`
76+
`add_tou_energy_rates_to_urdb_rate`, `coincident_peak_load_charge_per_kw`, `coincident_peak_load_active_time_steps`
5977
- handle multiple PV outputs
6078

6179
## v0.10.0
@@ -67,7 +85,7 @@
6785
- add option to run Business As Usual scenario in parallel with optimal scenario (default is `true`)
6886
- add incentives (and cost curves) to `Wind` and `Generator`
6987
- fixed bug in URDB fixed charges
70-
- renamed `outage_start(end)_timestep` to `outage_start(end)_time_step`
88+
- renamed `outage_start(end)_time_step` to `outage_start(end)_time_step`
7189

7290
## v0.9.0
7391
- `ElectricTariff.NEM` boolean is now determined by `ElectricUtility.net_metering_limit_kw` (true if limit > 0)
@@ -172,7 +190,7 @@
172190
#### bug fixes
173191
- allow non-integer `outage_probabilities`
174192
- correct `total_unserved_load` output
175-
- don't `add_min_hours_crit_ld_met_constraint` unless `min_resil_timesteps <= length(elecutil.outage_timesteps)`
193+
- don't `add_min_hours_crit_ld_met_constraint` unless `min_resil_time_steps <= length(elecutil.outage_time_steps)`
176194

177195
## v0.2.0
178196
#### Improvements
@@ -181,7 +199,7 @@
181199
- previously only had to pay to upgrade new capacity
182200
- implement ElectricLoad `loads_kw_is_net` and `critical_loads_kw_is_net`
183201
- add existing PV production to raw load profile if `true`
184-
- add `min_resil_timesteps` input and optional constraint for minimum timesteps that critical load must be met in every outage
202+
- add `min_resil_time_steps` input and optional constraint for minimum time_steps that critical load must be met in every outage
185203
#### bug fixes
186204
- enforce storage cannot grid charge
187205

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 <nick.laws@nrel.gov>"]
4-
version = "0.15.2"
4+
version = "0.16.0"
55

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

docs/src/developer/concept.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ The `max_kw` input value for any technology is considered to be the maximum _add
1818
The `min_kw` input value for any technology sets the lower bound on the capacity. If `min_kw` is non-zero then the model will be forced to choose at least that system size. The `min_kw` value is set equal to the `existing_kw` value in the Business As Usual scenario.
1919

2020
# Business As Usual Scenario
21-
In order to calculate the Net Present Value of the optimal solution, as well as other baseline metrics, one can optionally run the Business As Usual (BAU) scenario. The option for running the BAU scenario in addition to the optimal scenario is in the [Settings](@ref). The default value for `Settings.run_bau` is `true` so that when an array of `JuMP.Model`s is provided to `run_reopt` the BAU scenario is also run. For example:
21+
In order to calculate the Net Present Value of the optimal solution, as well as other baseline metrics, one can optionally run the Business As Usual (BAU) scenario. When an array of `JuMP.Model`s is provided to `run_reopt` the BAU scenario is also run. For example:
2222
```julia
2323
m1 = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0))
2424
m2 = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0))

docs/src/developer/inputs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ techs_by_exportbin = Dict(
5151
```
5252
A use-case example for the `techs_by_exportbin` map is defining the net metering benefit:
5353
```julia
54-
NEM_benefit = @expression(m, p.pwf_e * p.hours_per_timestep *
54+
NEM_benefit = @expression(m, p.pwf_e * p.hours_per_time_step *
5555
sum( sum(p.s.electric_tariff.export_rates[:NEM][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
5656
for t in p.techs_by_exportbin[:NEM]) for ts in p.time_steps)
5757
)

docs/src/mpc/inputs.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ Here is a more complex `MPCScenario`, which is used in [MPC Examples](@ref):
132132
"monthly_demand_rates": [10.0],
133133
"monthly_previous_peak_demands": [98.0],
134134
"tou_demand_rates": [0.0, 15.0],
135-
"tou_demand_timesteps": [
135+
"tou_demand_time_steps": [
136136
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
137137
[16, 17, 18, 19, 20, 21, 22, 23, 24]
138138
],

docs/src/reopt/examples.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,6 @@ results = run_reopt([m1,m2], "./scenarios/pv_storage.json")
2020
```
2121
With the [BAUScenario](@ref) results we can calculate the net present value of the optimal system.
2222

23-
!!! note
24-
The `Settings.run_bau` is `true` by default and so there is no need to change the `run_bau` value in general since it is ignored when only one `JuMP.Model` is passed to the `run_reopt` method. We include the `run_bau` option to align with the REopt API Settings.
25-
2623
## Advanced
2724

2825
### Modifying the mathematical model

src/REopt.jl

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,13 @@ using ArchGDAL
6464
using Roots: fzero # for IRR
6565
global hdl = nothing
6666

67-
const M3_TO_GAL = 264.172 # [gal/m^3]
68-
const GAL_DIESEL_TO_KWH = 40.7 # [kWh/gal_diesel]
67+
const EXISTING_BOILER_EFFICIENCY = 0.8
68+
69+
const GAL_PER_M3 = 264.172 # [gal/m^3]
70+
const KWH_PER_GAL_DIESEL = 40.7 # [kWh/gal_diesel]
71+
const KWH_PER_MMBTU = 293.07107 # [kWh/mmbtu]
72+
const KWH_THERMAL_PER_TONHOUR = 3.51685
73+
const TONNE_PER_LB = 1/2204.62 # [tonne/lb]
6974

7075
include("keys.jl")
7176
include("core/types.jl")
@@ -109,6 +114,7 @@ include("constraints/cost_curve_constraints.jl")
109114
include("constraints/production_incentive_constraints.jl")
110115
include("constraints/thermal_tech_constraints.jl")
111116
include("constraints/chp_constraints.jl")
117+
include("constraints/operating_reserve_constraints.jl")
112118
include("constraints/battery_degradation.jl")
113119

114120
include("mpc/structs.jl")

src/constraints/battery_degradation.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ end
4040

4141
function constrain_degradation_variables(m, p; b="ElectricStorage")
4242
days = 1:365*p.s.financial.analysis_years
43-
ts_per_day = 24 / p.hours_per_timestep
43+
ts_per_day = 24 / p.hours_per_time_step
4444
ts_per_year = ts_per_day * 365
4545
for d in days
4646
ts0 = Int((ts_per_day * (d - 1) + 1) % ts_per_year)
@@ -53,13 +53,13 @@ function constrain_degradation_variables(m, p; b="ElectricStorage")
5353
)
5454
@constraint(m,
5555
m[:Eplus_sum][d] ==
56-
p.hours_per_timestep * (
56+
p.hours_per_time_step * (
5757
sum(m[:dvProductionToStorage][b, t, ts] for t in p.techs.elec, ts in ts0:tsF)
5858
+ sum(m[:dvGridToStorage][b, ts] for ts in ts0:tsF)
5959
)
6060
)
6161
@constraint(m,
62-
m[:Eminus_sum][d] == p.hours_per_timestep * sum(m[:dvDischargeFromStorage][b, ts] for ts in ts0:tsF)
62+
m[:Eminus_sum][d] == p.hours_per_time_step * sum(m[:dvDischargeFromStorage][b, ts] for ts in ts0:tsF)
6363
)
6464
@constraint(m,
6565
m[:EFC][d] == (m[:Eplus_sum][d] + m[:Eminus_sum][d]) / 2
@@ -104,7 +104,7 @@ function add_degradation(m, p;
104104
constrain_degradation_variables(m, p, b=b)
105105

106106
@constraint(m, [d in 2:days[end]],
107-
SOH[d] == SOH[d-1] - p.hours_per_timestep * (
107+
SOH[d] == SOH[d-1] - p.hours_per_time_step * (
108108
p.s.storage.attr[b].degradation.calendar_fade_coefficient * time_exponent *
109109
m[:Eavg][d-1] * d^(time_exponent-1) +
110110
p.s.storage.attr[b].degradation.cycle_fade_coefficient * m[:EFC][d-1]

src/constraints/chp_constraints.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ function add_chp_fuel_burn_constraints(m, p; _n="")
3535
fuel_burn_intercept = fuel_burn_full_load - fuel_burn_slope * 1.0 # [kWt/kWe_rated]
3636

3737
# Fuel cost
38-
fuel_cost_per_kwh = p.s.chp.fuel_cost_per_mmbtu / MMBTU_TO_KWH
38+
fuel_cost_per_kwh = p.s.chp.fuel_cost_per_mmbtu / KWH_PER_MMBTU
3939
fuel_cost_series = per_hour_value_to_time_series(fuel_cost_per_kwh, p.s.settings.time_steps_per_hour,
4040
"CHP.fuel_cost_per_mmbtu")
4141
m[:TotalCHPFuelCosts] = @expression(m, p.pwf_fuel["CHP"] *
@@ -48,7 +48,7 @@ function add_chp_fuel_burn_constraints(m, p; _n="")
4848

4949
#Constraint (1c1): Total Fuel burn for CHP **with** y-intercept fuel burn and supplementary firing
5050
@constraint(m, CHPFuelBurnCon[t in p.techs.chp, ts in p.time_steps],
51-
m[Symbol("dvFuelUsage"*_n)][t,ts] == p.hours_per_timestep * (
51+
m[Symbol("dvFuelUsage"*_n)][t,ts] == p.hours_per_time_step * (
5252
m[Symbol("dvFuelBurnYIntercept"*_n)][t,ts] +
5353
p.production_factor[t,ts] * fuel_burn_slope * m[Symbol("dvRatedProduction"*_n)][t,ts] +
5454
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] / p.s.chp.supplementary_firing_efficiency
@@ -62,7 +62,7 @@ function add_chp_fuel_burn_constraints(m, p; _n="")
6262
else
6363
#Constraint (1c2): Total Fuel burn for CHP **without** y-intercept fuel burn
6464
@constraint(m, CHPFuelBurnConLinear[t in p.techs.chp, ts in p.time_steps],
65-
m[Symbol("dvFuelUsage"*_n)][t,ts] == p.hours_per_timestep * (
65+
m[Symbol("dvFuelUsage"*_n)][t,ts] == p.hours_per_time_step * (
6666
p.production_factor[t,ts] * fuel_burn_slope * m[Symbol("dvRatedProduction"*_n)][t,ts] +
6767
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] / p.s.chp.supplementary_firing_efficiency
6868
)
@@ -184,7 +184,7 @@ function add_chp_hourly_om_charges(m, p; _n="")
184184
)
185185

186186
m[:TotalHourlyCHPOMCosts] = @expression(m, p.third_party_factor * p.pwf_om *
187-
sum(m[Symbol(dv)][t, ts] * p.hours_per_timestep for t in p.techs.chp, ts in p.time_steps))
187+
sum(m[Symbol(dv)][t, ts] * p.hours_per_time_step for t in p.techs.chp, ts in p.time_steps))
188188
nothing
189189
end
190190

@@ -206,7 +206,7 @@ function add_chp_constraints(m, p; _n="")
206206
m[:TotalHourlyCHPOMCosts] = 0
207207
m[:TotalCHPFuelCosts] = 0
208208
m[:TotalCHPPerUnitProdOMCosts] = @expression(m, p.third_party_factor * p.pwf_om *
209-
sum(p.s.chp.om_cost_per_kwh * p.hours_per_timestep *
209+
sum(p.s.chp.om_cost_per_kwh * p.hours_per_time_step *
210210
m[:dvRatedProduction][t, ts] for t in p.techs.chp, ts in p.time_steps)
211211
)
212212

src/constraints/electric_utility_constraints.jl

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ function add_export_constraints(m, p; _n="")
4646

4747
if !isempty(NEM_techs)
4848
# Constraint (9c): Net metering only -- can't sell more than you purchase
49-
# hours_per_timestep is cancelled on both sides, but used for unit consistency (convert power to energy)
49+
# hours_per_time_step is cancelled on both sides, but used for unit consistency (convert power to energy)
5050
@constraint(m,
51-
p.hours_per_timestep * sum( m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
51+
p.hours_per_time_step * sum( m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
5252
for t in NEM_techs, ts in p.time_steps)
53-
<= p.hours_per_timestep * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier]
53+
<= p.hours_per_time_step * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier]
5454
for ts in p.time_steps, tier in 1:p.s.electric_tariff.n_energy_tiers)
5555
)
5656

@@ -60,12 +60,12 @@ function add_export_constraints(m, p; _n="")
6060
@constraint(m,
6161
sum(m[Symbol("dvSize"*_n)][t] for t in NEM_techs) <= p.s.electric_utility.interconnection_limit_kw
6262
)
63-
NEM_benefit = @expression(m, p.pwf_e * p.hours_per_timestep *
63+
NEM_benefit = @expression(m, p.pwf_e * p.hours_per_time_step *
6464
sum( sum(p.s.electric_tariff.export_rates[:NEM][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
6565
for t in p.techs_by_exportbin[:NEM]) for ts in p.time_steps)
6666
)
6767
if :EXC in p.s.electric_tariff.export_bins
68-
EXC_benefit = @expression(m, p.pwf_e * p.hours_per_timestep *
68+
EXC_benefit = @expression(m, p.pwf_e * p.hours_per_time_step *
6969
sum( sum(p.s.electric_tariff.export_rates[:EXC][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :EXC, ts]
7070
for t in p.techs_by_exportbin[:EXC]) for ts in p.time_steps)
7171
)
@@ -93,7 +93,7 @@ function add_export_constraints(m, p; _n="")
9393

9494
# binary choice for NEM benefit
9595
@constraint(m,
96-
binNEM => {NEM_benefit >= p.pwf_e * p.hours_per_timestep *
96+
binNEM => {NEM_benefit >= p.pwf_e * p.hours_per_time_step *
9797
sum( sum(p.s.electric_tariff.export_rates[:NEM][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :NEM, ts]
9898
for t in p.techs_by_exportbin[:NEM]) for ts in p.time_steps)
9999
}
@@ -104,7 +104,7 @@ function add_export_constraints(m, p; _n="")
104104
if :EXC in p.s.electric_tariff.export_bins
105105
EXC_benefit = @variable(m, lower_bound = max_bene)
106106
@constraint(m,
107-
binNEM => {EXC_benefit >= p.pwf_e * p.hours_per_timestep *
107+
binNEM => {EXC_benefit >= p.pwf_e * p.hours_per_time_step *
108108
sum( sum(p.s.electric_tariff.export_rates[:EXC][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :EXC, ts]
109109
for t in p.techs_by_exportbin[:EXC]) for ts in p.time_steps)
110110
}
@@ -118,7 +118,7 @@ function add_export_constraints(m, p; _n="")
118118

119119
if typeof(binNEM) <: Real # no need for wholesale binary
120120
binWHL = 1
121-
WHL_benefit = @expression(m, p.pwf_e * p.hours_per_timestep *
121+
WHL_benefit = @expression(m, p.pwf_e * p.hours_per_time_step *
122122
sum( sum(p.s.electric_tariff.export_rates[:WHL][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :WHL, ts]
123123
for t in p.techs_by_exportbin[:WHL]) for ts in p.time_steps)
124124
)
@@ -131,7 +131,7 @@ function add_export_constraints(m, p; _n="")
131131
@constraint(m, binNEM + binWHL == 1) # can either NEM or WHL export, not both
132132

133133
@constraint(m,
134-
binWHL => {WHL_benefit >= p.pwf_e * p.hours_per_timestep *
134+
binWHL => {WHL_benefit >= p.pwf_e * p.hours_per_time_step *
135135
sum( sum(p.s.electric_tariff.export_rates[:WHL][ts] * m[Symbol("dvProductionToGrid"*_n)][t, :WHL, ts]
136136
for t in p.techs_by_exportbin[:WHL]) for ts in p.time_steps)
137137
}
@@ -201,7 +201,7 @@ end
201201

202202
function add_tou_peak_constraint(m, p; _n="")
203203
## Constraint (12d): Ratchet peak demand is >= demand at each hour in the ratchet`
204-
@constraint(m, [r in p.ratchets, ts in p.s.electric_tariff.tou_demand_ratchet_timesteps[r]],
204+
@constraint(m, [r in p.ratchets, ts in p.s.electric_tariff.tou_demand_ratchet_time_steps[r]],
205205
sum(m[Symbol("dvPeakDemandTOU"*_n)][r, tier] for tier in 1:p.s.electric_tariff.n_tou_demand_tiers) >=
206206
sum(m[Symbol("dvGridPurchase"*_n)][ts, tier] for tier in 1:p.s.electric_tariff.n_energy_tiers)
207207
)
@@ -230,7 +230,7 @@ function add_tou_peak_constraint(m, p; _n="")
230230
)
231231

232232
# Ratchet peak demand is >= demand at each hour in the ratchet
233-
@constraint(m, [r in p.ratchets, ts in p.s.electric_tariff.tou_demand_ratchet_timesteps[r]],
233+
@constraint(m, [r in p.ratchets, ts in p.s.electric_tariff.tou_demand_ratchet_time_steps[r]],
234234
sum(m[Symbol("dvPeakDemandTOU"*_n)][r, tier] for tier in 1:ntiers)
235235
>= sum( m[Symbol("dvGridPurchase"*_n)][ts, tier] for tier in 1:p.s.electric_tariff.n_energy_tiers )
236236
)
@@ -275,7 +275,7 @@ function add_energy_tier_constraints(m, p; _n="")
275275
b = m[Symbol(dv)]
276276
##Constraint (10a): Usage limits by pricing tier, by month
277277
@constraint(m, [mth in p.months, tier in 1:p.s.electric_tariff.n_energy_tiers],
278-
p.hours_per_timestep * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier] for ts in p.s.electric_tariff.time_steps_monthly[mth] )
278+
p.hours_per_time_step * sum( m[Symbol("dvGridPurchase"*_n)][ts, tier] for ts in p.s.electric_tariff.time_steps_monthly[mth] )
279279
<= b[mth, tier] * p.s.electric_tariff.energy_tier_limits[tier]
280280
)
281281
##Constraint (10b): Ordering of pricing tiers
@@ -347,12 +347,12 @@ end
347347

348348

349349
function add_coincident_peak_charge_constraints(m, p; _n="")
350-
## Constraint (14a): in each coincident peak period, charged CP demand is the max of demand in all CP timesteps
350+
## Constraint (14a): in each coincident peak period, charged CP demand is the max of demand in all CP time_steps
351351
dv = "dvPeakDemandCP" * _n
352352
m[Symbol(dv)] = @variable(m, [p.s.electric_tariff.coincpeak_periods], lower_bound = 0, base_name = dv)
353353
@constraint(m,
354354
[prd in p.s.electric_tariff.coincpeak_periods,
355-
ts in p.s.electric_tariff.coincident_peak_load_active_timesteps[prd]],
355+
ts in p.s.electric_tariff.coincident_peak_load_active_time_steps[prd]],
356356
m[Symbol("dvPeakDemandCP"*_n)][prd] >= sum(m[Symbol("dvGridPurchase"*_n)][ts, tier]
357357
for tier in 1:p.s.electric_tariff.n_energy_tiers)
358358
)
@@ -369,7 +369,7 @@ function add_elec_utility_expressions(m, p; _n="")
369369
m[Symbol("TotalExportBenefit"*_n)] = 0
370370
end
371371

372-
m[Symbol("TotalEnergyChargesUtil"*_n)] = @expression(m, p.pwf_e * p.hours_per_timestep *
372+
m[Symbol("TotalEnergyChargesUtil"*_n)] = @expression(m, p.pwf_e * p.hours_per_time_step *
373373
sum( p.s.electric_tariff.energy_rates[ts, tier] * m[Symbol("dvGridPurchase"*_n)][ts, tier]
374374
for ts in p.time_steps, tier in 1:p.s.electric_tariff.n_energy_tiers)
375375
)

0 commit comments

Comments
 (0)