Skip to content

Commit c8db8a5

Browse files
authored
Merge pull request #387 from NREL/develop
v. 0.46.0
2 parents 1199d1c + 6c39421 commit c8db8a5

61 files changed

Lines changed: 11657 additions & 324 deletions

Some content is hidden

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

.github/workflows/CI.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ jobs:
1313
matrix:
1414
julia-version: ['1.8']
1515
julia-arch: [x64]
16-
os: [ubuntu-latest, windows-latest, macOS-11]
16+
# os: [ubuntu-latest, windows-latest, macOS-11]
17+
os: [windows-latest, macOS-11]
1718

1819
steps:
1920
- uses: actions/checkout@v2

CHANGELOG.md

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

26+
## v. 0.46.0
27+
### Added
28+
- In `src/core/absorption_chiller.jl` struct, added field **heating_load_input** to the AbsorptionChiller struct
29+
- Added new variables **dvHeatToStorage** and **dvHeatFromStorage** which are indexed on `p.heating_loads` and added reconciliation constraints so that **dvProductionToStorage** and **dvDischargeFromStorage** maintain their relationship to state of charge for Hot thermal energy storage.
30+
- In `src/constraints/thermal_tech_constraints.jl`, added function **no_existing_boiler_production** which prevents ExistingBoiler from producing heat in optimized (non-BAU) scenarios
31+
- for all heating techs and CHP, added fields **can_serve_space_heating**, **can_serve_dhw**, and **can_serve_process_heat** in core structs and added new results fields **thermal_to_dhw_load_series_mmbtu_per_hour**, **thermal_to_space_heating_load_series_mmbtu_per_hour**, and **thermal_to_process_heat_load_series_mmbtu_per_hour**
32+
- In `src/core/techs.jl`, added new sets **ghp_techs**, **cooling_techs**, **techs_can_serve_space_heating**, **techs_can_serve_dhw**, and **techs_can_serve_process_heat**
33+
- In `src/core/reopt_inputs.jl`, added new fields **heating_loads**, **heating_loads_kw**, **heating_loads_served_by_tes**, and **absorption_chillers_using_heating_load** to the REoptInputs and BAUInputs structs. in the math, new set `p.heating_loads` has index q (to represent "qualities" of heat).
34+
- In `src/core/heating_cooling_loads.jl`, added new struct **ProcessHeatLoad**
35+
- In `src/core/scenario.jl`, added new field **process_heat_load**
36+
- In `src/mpc/inputs.jl`, added new field **heating_loads**
37+
- In `src/core/existing_boiler.jl`, added field **retire_in_optimal** to the ExistingBoiler struct
38+
- Info to user including name of PV and/or temperature datasource used and distance from site location to datasource location
39+
- Warning to user if data is not from NSRDB or if data is more than 200 miles away
40+
- In `results/heating_cooling_load.jl`, added new fields **process_heat_thermal_load_series_mmbtu_per_hour**, **process_heat_boiler_fuel_load_series_mmbtu_per_hour**, **annual_calculated_process_heat_thermal_load_mmbtu**, and **annual_calculated_process_heat_boiler_fuel_load_mmbtu** to HeatingLoad results, with sum heating loads now including process heat
41+
### Changed
42+
- Change the way we determine which dataset to utilize in the PVWatts API call. Previously, we utilized defined lat-long bounds to determine if "nsrdb" or "intl" data should be used in PVWatts call. Now, we call the Solar Dataset Query API (v2) (https://developer.nrel.gov/docs/solar/data-query/v2/) to determine the dataset to use, and include "tmy3" as an option, as this is currently the best-available data for many locations in Alaska.
43+
- Refactored **dvThermalProduction** to be separated in **dvCoolingProduction** and **dvHeatingProduction** with **dvHeatingProduction** now indexed on `p.heating_loads`
44+
- Refactored heating load balance constraints so that a separate flow balance is reconciled for each heating load in `p.heating_loads`
45+
- Renamed **dvThermalProductionYIntercept** to **dvHeatingProductionYIntercept**
46+
- Divided **ThermalStorage** into **HotThermalStorage** and **ColdThermalStorage** as the former now has attributes related to the compatible heat loads as input or output.
47+
- Changed technologies included **dvProductionToWaste** to all heating techs. NOTE: this variable is forced to zero to allow steam turbine tests to pass, but I believe that waste heat should be allowed for the turbine. A TODO is in place to review this commit (a406cc5df6e4a27b56c92815c35d04815904e495).
48+
- Changed test values and tolerances for CHP Sizing test.
49+
- Updated test sets "Emissions and Renewable Energy Percent" and "Minimize Unserved Load" to decrease computing time.
50+
- Test for tiered TOU demand rates in `test/runtests.jl`
51+
- Updated `pop_year` and `income_year` used in call to EASIUR data (`get_EASIUR2005`) each to 2024, from 2020.
52+
- Updated usd conversion used for EASIUR health cost calcs from USD_2010_to_2020 = 1.246 to USD_2010_to_2024 = 1.432
53+
### Fixed
54+
- Added a constraint in `src/constraints/steam_turbine_constraints.jl` that allows for heat loads to reconcile when thermal storage is paired with a SteamTurbine.
55+
- Fixed a bug in which net-metering system size limits could be exceeded while still obtaining the net-metering benefit due to a large "big-M".
56+
- Fixed a reshape call in function `parse_urdb_tou_demand` that incorrectly assumed row major instead of column major ordering
57+
- Fixed a loop range in function `parse_urdb_tou_demand` that incorrectly started at 0 instead of 1
58+
- Added the missing tier index when accessing `p.s.electric_tariff.tou_demand_rates` in function `add_elec_utility_expressions`
59+
2660
## v0.45.0
2761
### Fixed
62+
- Fixed bug in call to `GhpGhx.jl` when sizing hybrid GHP using the fractional sizing method
2863
- Added `export_rate_beyond_net_metering_limit` to list of inputs to be converted to type Real, to avoid MethodError if type is vector of Any.
2964
- Fix blended CRB processing when one or more load types have zero annual energy
3065
- When calculating CHP fuel intercept and slope, use 1 for the HHV because CHP fuel measured in units of kWh, instead of using non-existent **CHP.fuel_higher_heating_value_kwh_per_gal**

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.45.0"
4+
version = "0.46.0"
55

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

data/absorption_chiller/absorption_chiller_defaults.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
20.0,
4040
18.0
4141
],
42-
"cop_thermal": 0.74
42+
"cop_thermal": 0.74,
43+
"heating_load_input": "DomesticHotWater"
4344
},
4445
"steam":{
4546
"installed_cost_per_ton": [
@@ -78,6 +79,7 @@
7879
23.0,
7980
20.0
8081
],
81-
"cop_thermal":1.42
82+
"cop_thermal":1.42,
83+
"heating_load_input": "DomesticHotWater"
8284
}
8385
}

docs/src/reopt/inputs.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,11 @@ REopt.DomesticHotWaterLoad
147147
REopt.SpaceHeatingLoad
148148
```
149149

150+
## ProcessHeatLoad
151+
```@docs
152+
REopt.ProcessHeatLoad
153+
```
154+
150155
## FlexibleHVAC
151156
```@docs
152157
REopt.FlexibleHVAC
@@ -166,3 +171,8 @@ REopt.GHP
166171
```@docs
167172
REopt.SteamTurbine
168173
```
174+
175+
## ElectricHeater
176+
```@docs
177+
REopt.ElectricHeater
178+
```

src/constraints/chp_constraints.jl

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,36 +47,37 @@ function add_chp_thermal_production_constraints(m, p; _n="")
4747
thermal_prod_slope = (thermal_prod_full_load - thermal_prod_half_load) / (1.0 - 0.5) # [kWt/kWe]
4848
thermal_prod_intercept = thermal_prod_full_load - thermal_prod_slope * 1.0 # [kWt/kWe_rated
4949

50-
# Conditionally add dvThermalProductionYIntercept if coefficient p.s.chpThermalProdIntercept is greater than ~zero
50+
51+
# Conditionally add dvHeatingProductionYIntercept if coefficient p.s.chpThermalProdIntercept is greater than ~zero
5152
if abs(thermal_prod_intercept) > 1.0E-7
52-
dv = "dvThermalProductionYIntercept"*_n
53+
dv = "dvHeatingProductionYIntercept"*_n
5354
m[Symbol(dv)] = @variable(m, [p.techs.chp, p.time_steps], base_name=dv)
5455

5556
#Constraint (2a-1): Upper Bounds on Thermal Production Y-Intercept
5657
@constraint(m, CHPYInt2a1Con[t in p.techs.chp, ts in p.time_steps],
57-
m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts] <= thermal_prod_intercept * m[Symbol("dvSize"*_n)][t]
58+
m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts] <= thermal_prod_intercept * m[Symbol("dvSize"*_n)][t]
5859
)
5960
# Constraint (2a-2): Upper Bounds on Thermal Production Y-Intercept
6061
@constraint(m, CHPYInt2a2Con[t in p.techs.chp, ts in p.time_steps],
61-
m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts] <= thermal_prod_intercept * p.s.chp.max_kw
62+
m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts] <= thermal_prod_intercept * p.s.chp.max_kw
6263
* m[Symbol("binCHPIsOnInTS"*_n)][t,ts]
6364
)
6465
#Constraint (2b): Lower Bounds on Thermal Production Y-Intercept
6566
@constraint(m, CHPYInt2bCon[t in p.techs.chp, ts in p.time_steps],
66-
m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts] >= thermal_prod_intercept * m[Symbol("dvSize"*_n)][t]
67+
m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts] >= thermal_prod_intercept * m[Symbol("dvSize"*_n)][t]
6768
- thermal_prod_intercept * p.s.chp.max_kw * (1 - m[Symbol("binCHPIsOnInTS"*_n)][t,ts])
6869
)
6970
# Constraint (2c): Thermal Production of CHP
7071
# Note: p.HotWaterAmbientFactor[t,ts] * p.HotWaterThermalFactor[t,ts] removed from this but present in math
7172
@constraint(m, CHPThermalProductionCon[t in p.techs.chp, ts in p.time_steps],
72-
m[Symbol("dvThermalProduction"*_n)][t,ts] ==
73+
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) ==
7374
thermal_prod_slope * p.production_factor[t,ts] * m[Symbol("dvRatedProduction"*_n)][t,ts]
74-
+ m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts] +
75+
+ m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts] +
7576
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts]
7677
)
7778
else
7879
@constraint(m, CHPThermalProductionConLinear[t in p.techs.chp, ts in p.time_steps],
79-
m[Symbol("dvThermalProduction"*_n)][t,ts] ==
80+
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) ==
8081
thermal_prod_slope * p.production_factor[t,ts] * m[Symbol("dvRatedProduction"*_n)][t,ts] +
8182
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts]
8283
)
@@ -99,7 +100,7 @@ function add_chp_supplementary_firing_constraints(m, p; _n="")
99100
# Constrain upper limit of dvSupplementaryThermalProduction, using auxiliary variable for (size * useSupplementaryFiring)
100101
@constraint(m, CHPSupplementaryFireCon[t in p.techs.chp, ts in p.time_steps],
101102
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] <=
102-
(p.s.chp.supplementary_firing_max_steam_ratio - 1.0) * p.production_factor[t,ts] * (thermal_prod_slope * m[Symbol("dvSupplementaryFiringSize"*_n)][t] + m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts])
103+
(p.s.chp.supplementary_firing_max_steam_ratio - 1.0) * p.production_factor[t,ts] * (thermal_prod_slope * m[Symbol("dvSupplementaryFiringSize"*_n)][t] + m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts])
103104
)
104105
if solver_is_compatible_with_indicator_constraints(p.s.settings.solver_name)
105106
# Constrain lower limit of 0 if CHP tech is off
@@ -110,7 +111,7 @@ function add_chp_supplementary_firing_constraints(m, p; _n="")
110111
#There's no upper bound specified for the CHP supplementary firing, so assume the entire heat load as a reasonable maximum that wouldn't be exceeded (but might not be the best possible value).
111112
max_supplementary_firing_size = maximum(p.s.dhw_load.loads_kw .+ p.s.space_heating_load.loads_kw)
112113
@constraint(m, NoCHPSupplementaryFireOffCon[t in p.techs.chp, ts in p.time_steps],
113-
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] <= (p.s.chp.supplementary_firing_max_steam_ratio - 1.0) * p.production_factor[t,ts] * (thermal_prod_slope * max_supplementary_firing_size + m[Symbol("dvThermalProductionYIntercept"*_n)][t,ts])
114+
m[Symbol("dvSupplementaryThermalProduction"*_n)][t,ts] <= (p.s.chp.supplementary_firing_max_steam_ratio - 1.0) * p.production_factor[t,ts] * (thermal_prod_slope * max_supplementary_firing_size + m[Symbol("dvHeatingProductionYIntercept"*_n)][t,ts])
114115
)
115116
end
116117
end

src/constraints/electric_utility_constraints.jl

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,21 @@ function add_export_constraints(m, p; _n="")
6565
!binNEM => {sum(m[Symbol("dvSize"*_n)][t] for t in NEM_techs) <= p.s.electric_utility.interconnection_limit_kw}
6666
)
6767
else
68+
#leverage max system sizes for interconnect limit size, alternate is max monthly fully-electrified load in kWh
69+
#assume electric heater with COP of 1 for conversion of heat to electricity
70+
max_interconnection_size = minimum([
71+
p.s.electric_utility.interconnection_limit_kw,
72+
sum(p.max_sizes[t] for t in NEM_techs),
73+
p.hours_per_time_step * maximum([sum((
74+
p.s.electric_load.loads_kw[ts] +
75+
p.s.cooling_load.loads_kw_thermal[ts]/p.cop["ExistingChiller"] +
76+
(p.s.space_heating_load.loads_kw[ts] + p.s.dhw_load.loads_kw[ts] + p.s.process_heat_load.loads_kw[ts])
77+
) for ts in p.s.electric_tariff.time_steps_monthly[m]) for m in p.months
78+
])
79+
])
80+
6881
@constraint(m,
69-
sum(m[Symbol("dvSize"*_n)][t] for t in NEM_techs) <= p.s.electric_utility.interconnection_limit_kw - (p.s.electric_utility.interconnection_limit_kw - p.s.electric_utility.net_metering_limit_kw)*binNEM
82+
sum(m[Symbol("dvSize"*_n)][t] for t in NEM_techs) <= max_interconnection_size - (max_interconnection_size - p.s.electric_utility.net_metering_limit_kw)*binNEM
7083
)
7184
end
7285

@@ -371,7 +384,7 @@ function add_elec_utility_expressions(m, p; _n="")
371384

372385
if !isempty(p.s.electric_tariff.tou_demand_rates)
373386
m[Symbol("DemandTOUCharges"*_n)] = @expression(m,
374-
p.pwf_e * sum( p.s.electric_tariff.tou_demand_rates[r] * m[Symbol("dvPeakDemandTOU"*_n)][r, tier]
387+
p.pwf_e * sum( p.s.electric_tariff.tou_demand_rates[r, tier] * m[Symbol("dvPeakDemandTOU"*_n)][r, tier]
375388
for r in p.ratchets, tier in 1:p.s.electric_tariff.n_tou_demand_tiers)
376389
)
377390
else

src/constraints/emissions_constraints.jl

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
# REopt®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE.
22

33
function add_emissions_constraints(m,p)
4-
if !isnothing(p.s.site.CO2_emissions_reduction_min_fraction)
5-
@constraint(m, MinEmissionsReductionCon,
6-
m[:Lifecycle_Emissions_Lbs_CO2] <=
7-
(1-p.s.site.CO2_emissions_reduction_min_fraction) * m[:Lifecycle_Emissions_Lbs_CO2_BAU]
8-
)
9-
end
10-
if !isnothing(p.s.site.CO2_emissions_reduction_max_fraction)
11-
@constraint(m, MaxEmissionsReductionCon,
12-
m[:Lifecycle_Emissions_Lbs_CO2] >=
13-
(1-p.s.site.CO2_emissions_reduction_max_fraction) * m[:Lifecycle_Emissions_Lbs_CO2_BAU]
14-
)
4+
if !isnothing(p.s.site.bau_emissions_lb_CO2_per_year)
5+
if !isnothing(p.s.site.CO2_emissions_reduction_min_fraction)
6+
@constraint(m, MinEmissionsReductionCon,
7+
m[:Lifecycle_Emissions_Lbs_CO2] <=
8+
(1-p.s.site.CO2_emissions_reduction_min_fraction) * m[:Lifecycle_Emissions_Lbs_CO2_BAU]
9+
)
10+
end
11+
if !isnothing(p.s.site.CO2_emissions_reduction_max_fraction)
12+
@constraint(m, MaxEmissionsReductionCon,
13+
m[:Lifecycle_Emissions_Lbs_CO2] >=
14+
(1-p.s.site.CO2_emissions_reduction_max_fraction) * m[:Lifecycle_Emissions_Lbs_CO2_BAU]
15+
)
16+
end
17+
else
18+
@warn "No emissions reduction constraints added, as BAU emissions have not been calculated."
1519
end
1620
end
1721

@@ -32,14 +36,19 @@ function add_yr1_emissions_calcs(m,p)
3236
yr1_emissions_offset_from_elec_exports_lbs_PM25 =
3337
calc_yr1_emissions_offset_from_elec_exports(m, p)
3438

35-
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_CO2] = m[:yr1_emissions_from_elec_grid_lbs_CO2] -
36-
yr1_emissions_offset_from_elec_exports_lbs_CO2
37-
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_NOx] = m[:yr1_emissions_from_elec_grid_lbs_NOx] -
38-
yr1_emissions_offset_from_elec_exports_lbs_NOx
39-
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_SO2] = m[:yr1_emissions_from_elec_grid_lbs_SO2] -
40-
yr1_emissions_offset_from_elec_exports_lbs_SO2
41-
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_PM25] = m[:yr1_emissions_from_elec_grid_lbs_PM25] -
42-
yr1_emissions_offset_from_elec_exports_lbs_PM25
39+
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_CO2] = (m[:yr1_emissions_from_elec_grid_lbs_CO2] -
40+
yr1_emissions_offset_from_elec_exports_lbs_CO2)
41+
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_NOx] = (m[:yr1_emissions_from_elec_grid_lbs_NOx] -
42+
yr1_emissions_offset_from_elec_exports_lbs_NOx)
43+
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_SO2] = (m[:yr1_emissions_from_elec_grid_lbs_SO2] -
44+
yr1_emissions_offset_from_elec_exports_lbs_SO2)
45+
m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_PM25] = (m[:yr1_emissions_from_elec_grid_lbs_PM25] -
46+
yr1_emissions_offset_from_elec_exports_lbs_PM25)
47+
48+
m[:EmissionsYr1_Total_LbsCO2] = m[:yr1_emissions_onsite_fuel_lbs_CO2] + m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_CO2]
49+
m[:EmissionsYr1_Total_LbsNOx] = m[:yr1_emissions_onsite_fuel_lbs_NOx] + m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_NOx]
50+
m[:EmissionsYr1_Total_LbsSO2] = m[:yr1_emissions_onsite_fuel_lbs_SO2] + m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_SO2]
51+
m[:EmissionsYr1_Total_LbsPM25] = m[:yr1_emissions_onsite_fuel_lbs_PM25] + m[:yr1_emissions_from_elec_grid_net_if_selected_lbs_PM25]
4352
nothing
4453
end
4554

0 commit comments

Comments
 (0)