Skip to content

Commit 7503814

Browse files
authored
Merge pull request #586 from NatLabRockies/develop
v0.58.0 CHP load following
2 parents da0de8b + ad46000 commit 7503814

24 files changed

Lines changed: 925 additions & 133 deletions

CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ Classify the change according to the following categories:
2525
### Deprecated
2626
### Removed
2727

28+
## v0.58.0
29+
### Added
30+
- New optional attributes for **CHP** object **CHP.serve_absorption_chiller_only**, **CHP.months_serving_absorption_chiller_only**, and **CHP.follow_electrical_load**, which impose constraints on CHP operations if selected. The default is set to `false` for both attributes.
31+
- New result **thermal_to_absorption_chiller_series_mmbtu_per_hour** added to heating technologies and new result **storage_to_absorption_chiller_series_mmbtu_per_hour** for hot thermal storage technologies. This result is included as a part of the thermal site loads served, i.e., the adding this result does not change the existing results.
32+
- Added results fields to **HighTempThermalStorage** to match those of **HotThermalStorage**.
33+
34+
### Changed
35+
- Updated heating dispatch results by separating heat flows to absorption chiller from heating load served (formerly, these were aggregated).
36+
37+
### Fixed
38+
- Fixed a bug in which the CHP system requires a **DomesticHotWater** load.
39+
- Fixed a bug in which the storage to steam turbine flow was included in the thermal heating load served.
40+
41+
### Changed
42+
- **HotThermalStorage** and **HighTempThermalStorage** output **storage_to_turbine_series_mmbtu_per_hour** to **storage_to_steamturbine_series_mmbtu_per_hour**
43+
2844
## v0.57.0
2945
### Fixed
3046
- Include boiler emissions in emissions calculations

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@nlr.gov>", "Bill Becker <william.becker@nlr.gov>", "Bhavesh Rathod <bhavesh.rathod@nlr.gov>", "Alex Zolan <alexander.zolan@nlr.gov>", "Amanda Farthing <amanda.farthing@nlr.gov>", "Xiangkun Li <xiangkun.li@nlr.gov>", "An Pham <an.pham@nlr.gov>", "Byron Pullutasig <byron.pullatasig@nlr.gov>"]
4-
version = "0.57.0"
4+
version = "0.58.0"
55

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

src/constraints/chp_constraints.jl

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,68 @@ function add_chp_hourly_om_charges(m, p; _n="")
167167
nothing
168168
end
169169

170+
"""
171+
add_chp_to_absorption_chiller_only_constraints(m, p; _n="")
172+
173+
Used in the function add_chp_constraints to add constraints that restrict heat output of CHP to only serve absorption chiller
174+
load or send to waste in dispatch.
175+
"""
176+
function add_chp_to_absorption_chiller_only_constraints(m, p; _n="")
177+
monthly_timesteps = get_monthly_time_steps(p.s.electric_load.year; time_steps_per_hour=p.s.settings.time_steps_per_hour)
178+
for mth in p.s.chp.months_serving_absorption_chiller_only
179+
@constraint(m, [t in p.techs.chp, q in [p.s.absorption_chiller.heating_load_input], ts in monthly_timesteps[mth]],
180+
m[Symbol("dvProductionToWaste"*_n)]["CHP",q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)]["CHP",q,ts] == m[Symbol("dvHeatingProduction"*_n)]["CHP",q,ts]
181+
)
182+
# During restricted months, CHP cannot serve other heating loads (space heating, DHW)
183+
for q in setdiff(p.heating_loads,[p.s.absorption_chiller.heating_load_input])
184+
for ts in monthly_timesteps[mth]
185+
fix(m[Symbol("dvHeatToAbsorptionChiller"*_n)]["CHP",q,ts], 0.0, force=true)
186+
fix(m[Symbol("dvHeatingProduction"*_n)]["CHP",q,ts], 0.0, force=true)
187+
fix(m[Symbol("dvProductionToWaste"*_n)]["CHP",q,ts], 0.0, force=true)
188+
end
189+
end
190+
end
191+
end
192+
193+
194+
"""
195+
add_chp_electrical_load_following_constraints(m, p; _n="")
196+
197+
Used in function add_chp_constraints to add constraints that restrict output of CHP to serve full electrical load or
198+
run at capacity otherwise in dispatch (without regard to heat flows).
199+
"""
200+
function add_chp_electrical_load_following_constraints(m, p; _n="")
201+
dv = "binCHPSizeExceedsElectricLoad"*_n
202+
m[Symbol(dv)] = @variable(m, [p.time_steps], binary=true, base_name=dv)
203+
dv = "dvCHPSizeTimesExcess"*_n
204+
m[Symbol(dv)] = @variable(m, [p.time_steps], lower_bound=0, base_name=dv)
205+
# binary variable enforcement for size >= load
206+
max_diff_size_bigM = 2*max(p.max_sizes["CHP"], maximum(p.s.electric_load.loads_kw) #+ sum(p.heating_loads_kw[q][ts] for q in p.heating_loads)) #exclude heating electrification but include elec cooling?
207+
)
208+
@constraint(m, [ts in p.time_steps],
209+
m[Symbol("binCHPSizeExceedsElectricLoad"*_n)][ts] >= (m[Symbol("dvSize"*_n)]["CHP"] - p.s.electric_load.loads_kw[ts]) / max_diff_size_bigM
210+
)
211+
@constraint(m, [ts in p.time_steps],
212+
m[Symbol("binCHPSizeExceedsElectricLoad"*_n)][ts] <= 1 - (p.s.electric_load.loads_kw[ts] - m[Symbol("dvSize"*_n)]["CHP"]) / max_diff_size_bigM
213+
)
214+
# set dvCHPSizeTimesExcess = binCHPSizeExceedsElectricLoad * dvSize
215+
# big-M is min CF times heat load
216+
217+
@constraint(m, [ts in p.time_steps],
218+
m[Symbol("dvCHPSizeTimesExcess"*_n)][ts] >= p.production_factor["CHP",ts]*m[Symbol("dvSize"*_n)]["CHP"] - max_diff_size_bigM * (1-m[Symbol("binCHPSizeExceedsElectricLoad"*_n)][ts])
219+
)
220+
@constraint(m, [ts in p.time_steps],
221+
m[Symbol("dvCHPSizeTimesExcess"*_n)][ts] <= p.production_factor["CHP",ts]*m[Symbol("dvSize"*_n)]["CHP"]
222+
)
223+
@constraint(m, [ts in p.time_steps],
224+
m[Symbol("dvCHPSizeTimesExcess"*_n)][ts] <= max_diff_size_bigM * m[Symbol("binCHPSizeExceedsElectricLoad"*_n)][ts]
225+
)
226+
#Enforce dispatch: output = system size - (overage)
227+
@constraint(m, [ts in p.time_steps],
228+
m[Symbol("dvRatedProduction"*_n)]["CHP",ts] >= p.production_factor["CHP",ts]*m[Symbol("dvSize"*_n)]["CHP"] - m[Symbol("dvCHPSizeTimesExcess"*_n)][ts] + p.s.electric_load.loads_kw[ts] * m[Symbol("binCHPSizeExceedsElectricLoad"*_n)][ts]
229+
)
230+
end
231+
170232

171233
"""
172234
add_chp_constraints(m, p; _n="")
@@ -208,4 +270,13 @@ function add_chp_constraints(m, p; _n="")
208270
end
209271
end
210272
end
273+
274+
if !isempty(p.techs.absorption_chiller) && p.s.chp.serve_absorption_chiller_only
275+
add_chp_to_absorption_chiller_only_constraints(m, p; _n=_n)
276+
end
277+
278+
if p.s.chp.follow_electrical_load
279+
add_chp_electrical_load_following_constraints(m, p; _n=_n)
280+
end
211281
end
282+

src/constraints/load_balance.jl

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,10 @@ function add_thermal_load_constraints(m, p; _n="")
125125
p.heating_loads_kw[q][ts]
126126
+ sum(m[Symbol("dvProductionToWaste"*_n)][t,q,ts] for t in union(p.techs.heating, p.techs.chp))
127127
+ sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot, t in union(p.techs.heating, p.techs.chp))
128-
+ sum(m[Symbol("dvCoolingProduction"*_n)][t,ts] / p.thermal_cop[t] for t in p.absorption_chillers_using_heating_load[q])
128+
+ sum(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] for t in union(p.techs.heating, p.techs.chp))
129129
+ sum(m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] for t in p.techs.can_supply_steam_turbine)
130130
+ sum(m[Symbol("dvHeatFromStorageToTurbine"*_n)][b,q,ts] for b in p.s.storage.types.hot)
131+
+ sum(m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts] for b in p.s.storage.types.hot)
131132
)
132133
else
133134
@constraint(m, HeatLoadBalanceCon[q in p.heating_loads, ts in p.time_steps_with_grid],
@@ -137,14 +138,14 @@ function add_thermal_load_constraints(m, p; _n="")
137138
p.heating_loads_kw[q][ts]
138139
+ sum(m[Symbol("dvProductionToWaste"*_n)][t,q,ts] for t in union(p.techs.heating, p.techs.chp))
139140
+ sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot, t in union(p.techs.heating, p.techs.chp))
140-
+ sum(m[Symbol("dvCoolingProduction"*_n)][t,ts] / p.thermal_cop[t] for t in p.absorption_chillers_using_heating_load[q])
141+
+ sum(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] for t in union(p.techs.heating, p.techs.chp))
142+
+ sum(m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts] for b in p.s.storage.types.hot)
141143
)
142144
end
143145

144146
end
145147

146148
if !isempty(p.techs.cooling)
147-
148149
##Constraint (5a): Cold thermal loads
149150
@constraint(m, [ts in p.time_steps_with_grid],
150151
sum(m[Symbol("dvCoolingProduction"*_n)][t,ts] for t in p.techs.cooling)
@@ -155,4 +156,39 @@ function add_thermal_load_constraints(m, p; _n="")
155156
)
156157
end
157158
end
159+
end
160+
161+
function add_absorption_chiller_load_constraints(m,p;_n="")
162+
if !isempty(p.techs.absorption_chiller)
163+
for q in p.heating_loads
164+
if !isempty(p.absorption_chillers_using_heating_load[q])
165+
@constraint(m, abschlcon[ts in p.time_steps_with_grid],
166+
sum(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] for t in union(p.techs.heating, p.techs.chp))
167+
==
168+
sum(m[Symbol("dvCoolingProduction"*_n)][t,ts] / p.thermal_cop[t] for t in p.absorption_chillers_using_heating_load[q])
169+
)
170+
else
171+
for t in union(p.techs.heating, p.techs.chp)
172+
for ts in p.time_steps
173+
fix(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts], 0.0, force=true)
174+
end
175+
end
176+
end
177+
end
178+
for t in intersect(p.techs.heating,["GHP","ASHPWaterHeater","ASHPSpaceHeater"])
179+
for q in p.heating_loads
180+
for ts in p.time_steps
181+
fix(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts], 0.0, force=true)
182+
end
183+
end
184+
end
185+
else
186+
for t in union(p.techs.heating, p.techs.chp)
187+
for q in p.heating_loads
188+
for ts in p.time_steps
189+
fix(m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts], 0.0, force=true)
190+
end
191+
end
192+
end
193+
end
158194
end

src/constraints/steam_turbine_constraints.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ function steam_turbine_thermal_input(m, p; _n="")
55
# This constraint is already included in storage_constraints.jl if HotThermalStorage and SteamTurbine are considered that also includes dvProductionToStorage["HotThermalStorage"] in LHS
66
if isempty(p.s.storage.types.hot)
77
@constraint(m, SupplySteamTurbineProductionLimit[t in p.techs.can_supply_steam_turbine, q in p.heating_loads, ts in p.time_steps],
8-
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
8+
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
99
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
1010
)
1111
else
1212
@constraint(m, SupplySteamTurbineProductionLimit[t in p.techs.can_supply_steam_turbine, q in p.heating_loads, ts in p.time_steps],
13-
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
13+
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
1414
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
1515
)
1616
end
@@ -19,7 +19,7 @@ end
1919
function steam_turbine_production_constraints(m, p; _n="")
2020
# Constraint Steam Turbine Thermal Production
2121
@constraint(m, SteamTurbineThermalProductionCon[t in p.techs.steam_turbine, ts in p.time_steps],
22-
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] for q in p.heating_loads) == p.s.steam_turbine.thermal_produced_to_thermal_consumed_ratio * (
22+
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] for q in p.heating_loads) == p.s.steam_turbine.thermal_produced_to_thermal_consumed_ratio * (
2323
sum(m[Symbol("dvThermalToSteamTurbine"*_n)][tst,q,ts] for tst in p.techs.can_supply_steam_turbine, q in p.heating_loads) +
2424
sum(m[Symbol("dvHeatFromStorageToTurbine"*_n)][b,q,ts] for b in p.s.storage.types.hot, q in p.heating_loads)
2525
)

src/constraints/storage_constraints.jl

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function add_hot_thermal_storage_dispatch_constraints(m, p, b; _n="")
152152
m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] <= m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
153153
)
154154
@constraint(m, [q in p.heating_loads, ts in p.time_steps],
155-
m[Symbol("dvHeatFromStorageToTurbine"*_n)][b,q,ts] <= m[Symbol("dvHeatFromStorage"*_n)][b,q,ts]
155+
m[Symbol("dvHeatFromStorageToTurbine"*_n)][b,q,ts] + m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts] <= m[Symbol("dvHeatFromStorage"*_n)][b,q,ts]
156156
)
157157
if !p.s.storage.attr[b].can_supply_steam_turbine
158158
for q in p.heating_loads
@@ -170,6 +170,23 @@ function add_hot_thermal_storage_dispatch_constraints(m, p, b; _n="")
170170
end
171171
end
172172
end
173+
else
174+
if !isempty(p.techs.absorption_chiller)
175+
@constraint(m, [q in [p.s.absorption_chiller.heating_load_input], ts in p.time_steps],
176+
m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts] <= m[Symbol("dvHeatFromStorage"*_n)][b,q,ts]
177+
)
178+
for q in setdiff(p.heating_loads,[p.s.absorption_chiller.heating_load_input])
179+
for ts in p.time_steps
180+
fix(m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts], 0.0, force=true)
181+
end
182+
end
183+
else
184+
for q in p.heating_loads
185+
for ts in p.time_steps
186+
fix(m[Symbol("dvHeatFromStorageToAbsorptionChiller"*_n)][b,q,ts], 0.0, force=true)
187+
end
188+
end
189+
end
173190
end
174191

175192
# Constraint (4j)-1: Reconcile state-of-charge for (hot) thermal storage

src/constraints/thermal_tech_constraints.jl

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,18 @@ function add_heating_tech_constraints(m, p; _n="")
2828
if !isempty(p.techs.steam_turbine)
2929
if !isempty(p.s.storage.types.hot)
3030
@constraint(m, [t in p.techs.can_supply_steam_turbine, q in p.heating_loads, ts in p.time_steps],
31-
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
31+
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
3232
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
3333
)
3434
if !isempty(setdiff(union(p.techs.heating, p.techs.chp),p.techs.can_supply_steam_turbine))
3535
@constraint(m, [t in setdiff(union(p.techs.heating,p.techs.chp),p.techs.can_supply_steam_turbine), q in p.heating_loads, ts in p.time_steps],
36-
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
36+
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
3737
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
3838
)
3939
end
4040
else
4141
@constraint(m, [t in p.techs.can_supply_steam_turbine, q in p.heating_loads, ts in p.time_steps],
42-
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
42+
m[Symbol("dvThermalToSteamTurbine"*_n)][t,q,ts] + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
4343
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
4444
)
4545
if !isempty(setdiff(union(p.techs.heating, p.techs.chp),p.techs.can_supply_steam_turbine))
@@ -51,7 +51,7 @@ function add_heating_tech_constraints(m, p; _n="")
5151
else
5252
if !isempty(p.s.storage.types.hot)
5353
@constraint(m, [t in union(p.techs.heating, p.techs.chp), q in p.heating_loads, ts in p.time_steps],
54-
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] <=
54+
sum(m[Symbol("dvHeatToStorage"*_n)][b,t,q,ts] for b in p.s.storage.types.hot) + m[Symbol("dvProductionToWaste"*_n)][t,q,ts] + m[Symbol("dvHeatToAbsorptionChiller"*_n)][t,q,ts] <=
5555
m[Symbol("dvHeatingProduction"*_n)][t,q,ts]
5656
)
5757
else

0 commit comments

Comments
 (0)