Skip to content

Commit bb85c6d

Browse files
authored
Merge pull request #596 from NatLabRockies/develop
v0.58.2 Hourly Generator Costs, Bug fixes in TES-to-turbine results
2 parents 2af3afe + 98b56ed commit bb85c6d

19 files changed

Lines changed: 109 additions & 58 deletions

CHANGELOG.md

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

28+
## v0.58.2
29+
### Added
30+
- **Generator** **om_cost_per_hr_per_kw_rated**: Generator non-fuel variable operations and maintenance costs in \$/hr/kw_rated (default of 0.0)
31+
32+
### Changed
33+
- Refactored some results expressions so that `value.` isn't called within them.
34+
35+
### Fixed
36+
- Fixed an error creating results for flows from hot TES to the steam turbine.
37+
- Fixed an bug preventing `include_cooling_in_chp_size` from being included in CHP inputs.
38+
2839
## v0.58.1
2940
### Fixed
3041
- Calculation of offgrid_microgrid_lcoe_dollars_per_kwh for sub-hourly runs.
@@ -38,14 +49,12 @@ Classify the change according to the following categories:
3849

3950
### Changed
4051
- Updated heating dispatch results by separating heat flows to absorption chiller from heating load served (formerly, these were aggregated).
52+
- **HotThermalStorage** and **HighTempThermalStorage** output **storage_to_turbine_series_mmbtu_per_hour** to **storage_to_steamturbine_series_mmbtu_per_hour**
4153

4254
### Fixed
4355
- Fixed a bug in which the CHP system requires a **DomesticHotWater** load.
4456
- Fixed a bug in which the storage to steam turbine flow was included in the thermal heating load served.
4557

46-
### Changed
47-
- **HotThermalStorage** and **HighTempThermalStorage** output **storage_to_turbine_series_mmbtu_per_hour** to **storage_to_steamturbine_series_mmbtu_per_hour**
48-
4958
## v0.57.0
5059
### Fixed
5160
- 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.58.1"
4+
version = "0.58.2"
55

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

src/constraints/generator_constraints.jl

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ function add_binGenIsOnInTS_constraints(m,p)
3636
end
3737
end
3838

39-
4039
function add_gen_can_run_constraints(m,p)
4140
if p.s.generator.only_runs_during_grid_outage
4241
for ts in p.time_steps_with_grid, t in p.techs.gen
@@ -58,6 +57,38 @@ function add_gen_rated_prod_constraint(m, p)
5857
)
5958
end
6059

60+
"""
61+
add_generator_hourly_om_charges(m, p)
62+
63+
- add decision variable "dvOMByHourBySizeGen" for the hourly Generator operations and maintenance costs
64+
- add the cost to TotalPerUnitHourOMCosts
65+
"""
66+
function add_generator_hourly_om_charges(m, p)
67+
dv = "dvOMByHourBySizeGen"
68+
m[Symbol(dv)] = @variable(m, [p.techs.gen, p.time_steps], base_name=dv, lower_bound=0)
69+
70+
#Constraint Generator-hourly-om-a: om per hour, per time step >= per_unit_size_cost * size for when on, >= zero when off
71+
@constraint(m, GeneratorHourlyOMBySizeA[t in p.techs.gen, ts in p.time_steps],
72+
p.s.generator.om_cost_per_hr_per_kw_rated * m[Symbol("dvSize")][t] -
73+
(p.s.generator.existing_kw + p.s.generator.max_kw) * p.s.generator.om_cost_per_hr_per_kw_rated * (1-m[Symbol("binGenIsOnInTS")][t,ts])
74+
<= m[Symbol("dvOMByHourBySizeGen")][t, ts]
75+
)
76+
#Constraint Generator-hourly-om-b: om per hour, per time step <= per_unit_size_cost * size for each hour
77+
@constraint(m, GeneratorHourlyOMBySizeB[t in p.techs.gen, ts in p.time_steps],
78+
p.s.generator.om_cost_per_hr_per_kw_rated * m[Symbol("dvSize")][t]
79+
>= m[Symbol("dvOMByHourBySizeGen")][t, ts]
80+
)
81+
#Constraint Generator-hourly-om-c: om per hour, per time step <= zero when off, <= per_unit_size_cost*max_size
82+
@constraint(m, GeneratorHourlyOMBySizeC[t in p.techs.gen, ts in p.time_steps],
83+
(p.s.generator.existing_kw + p.s.generator.max_kw) * p.s.generator.om_cost_per_hr_per_kw_rated * m[Symbol("binGenIsOnInTS")][t,ts]
84+
>= m[Symbol("dvOMByHourBySizeGen")][t, ts]
85+
)
86+
87+
m[:TotalHourlyGenOMCosts] = @expression(m, p.third_party_factor * p.pwf_om *
88+
sum(m[Symbol(dv)][t, ts] * p.hours_per_time_step for t in p.techs.gen, ts in p.time_steps))
89+
nothing
90+
end
91+
6192

6293
"""
6394
add_gen_constraints(m, p)
@@ -70,6 +101,11 @@ function add_gen_constraints(m, p)
70101
add_gen_can_run_constraints(m,p)
71102
add_gen_rated_prod_constraint(m,p)
72103

104+
m[:TotalHourlyGenOMCosts] = 0
105+
if p.s.generator.om_cost_per_hr_per_kw_rated > 1.0E-7
106+
add_generator_hourly_om_charges(m, p)
107+
end
108+
73109
m[:TotalGenPerUnitProdOMCosts] = @expression(m, p.third_party_factor * p.pwf_om *
74110
sum(p.s.generator.om_cost_per_kwh * p.hours_per_time_step *
75111
m[:dvRatedProduction][t, ts] for t in p.techs.gen, ts in p.time_steps)

src/core/chp.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ conflict_res_min_allowable_fraction_of_max = 0.25
4343
serve_absorption_chiller_only::Bool = false # If CHP produced heat either serves absorption chiller or sends it to waste; only applies to the months specified in months_serving_absorption_chiller_only if true
4444
months_serving_absorption_chiller_only::AbstractVector{Int64} = Int64[] # months in which CHP only sevres the absorption chiller, with 1=January and 12=December; only applied when serve_absorption_chiller_only = true
4545
follow_electrical_load::Bool = false # If CHP follows the electrical load by running at capacity or meeting the load only.
46-
include_cooling_in_chp_size::Bool = false # If true, includes cooling load (via absorption chiller) in the heuristic CHP sizing calculation along with heating loads. Defaults to true when AbsorptionChiller is present with CHP. Requires CoolingLoad to be specified.
4746
4847
macrs_option_years::Int = 5 # Notes: this value cannot be 0 if aiming to apply 100% bonus depreciation; default may change if Site.sector is not "commercial/industrial"
4948
macrs_bonus_fraction::Float64 = 1.0 #Note: default may change if Site.sector is not "commercial/industrial"

src/core/generator.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
installed_cost_per_kw::Real = off_grid_flag ? 880 : only_runs_during_grid_outage ? 650.0 : 800.0,
1010
om_cost_per_kw::Real = off_grid_flag ? 10.0 : 20.0,
1111
om_cost_per_kwh::Real = 0.0,
12+
om_cost_per_hr_per_kw_rated::Float64 = 0.0, # Generator non-fuel variable operations and maintenance costs in \$/hr/kw_rated
1213
fuel_cost_per_gallon::Real = 2.25,
1314
electric_efficiency_full_load::Real = 0.322,
1415
electric_efficiency_half_load::Real = electric_efficiency_full_load,
@@ -57,6 +58,7 @@ struct Generator <: AbstractGenerator
5758
installed_cost_per_kw
5859
om_cost_per_kw
5960
om_cost_per_kwh
61+
om_cost_per_hr_per_kw_rated
6062
fuel_cost_per_gallon
6163
electric_efficiency_full_load
6264
electric_efficiency_half_load
@@ -104,6 +106,7 @@ struct Generator <: AbstractGenerator
104106
installed_cost_per_kw::Real = off_grid_flag ? 880 : only_runs_during_grid_outage ? 650.0 : 800.0,
105107
om_cost_per_kw::Real= off_grid_flag ? 10.0 : 20.0,
106108
om_cost_per_kwh::Real = 0.0,
109+
om_cost_per_hr_per_kw_rated::Float64 = 0.0, # Generator non-fuel variable operations and maintenance costs in \$/hr/kw_rated
107110
fuel_cost_per_gallon::Real = 2.25,
108111
electric_efficiency_full_load::Real = 0.322,
109112
electric_efficiency_half_load::Real = electric_efficiency_full_load,
@@ -152,6 +155,7 @@ struct Generator <: AbstractGenerator
152155
installed_cost_per_kw,
153156
om_cost_per_kw,
154157
om_cost_per_kwh,
158+
om_cost_per_hr_per_kw_rated,
155159
fuel_cost_per_gallon,
156160
electric_efficiency_full_load,
157161
electric_efficiency_half_load,

src/core/reopt.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ function build_reopt!(m::JuMP.AbstractModel, p::REoptInputs)
274274
add_gen_constraints(m, p)
275275
m[:TotalPerUnitProdOMCosts] += m[:TotalGenPerUnitProdOMCosts]
276276
m[:TotalFuelCosts] += m[:TotalGenFuelCosts]
277+
m[:TotalPerUnitHourOMCosts] += m[:TotalHourlyGenOMCosts]
277278
end
278279

279280
if !isempty(p.techs.chp)

src/core/scenario.jl

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,7 +473,11 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
473473
avg_cooling_load_kw = nothing
474474
absorption_chiller_cop = nothing
475475
# User can override by explicitly setting include_cooling_in_chp_size = false
476-
include_cooling_in_size = get(d["CHP"], "include_cooling_in_chp_size", haskey(d, "AbsorptionChiller"))
476+
if "include_cooling_in_chp_size" in keys(d["CHP"])
477+
include_cooling_in_size = pop!(d["CHP"], "include_cooling_in_chp_size")
478+
else
479+
include_cooling_in_size = haskey(d, "AbsorptionChiller")
480+
end
477481

478482
if max_cooling_demand_kw > 0 && include_cooling_in_size
479483
# Use already-processed cooling_load object
@@ -498,7 +502,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
498502
sector = site.sector,
499503
federal_procurement_type = site.federal_procurement_type)
500504
else # Only if modeling CHP without heating_load and existing_boiler (for prime generator, electric-only)
501-
chp = CHP(d["CHP"],
505+
chp = CHP(d["CHP"];
502506
electric_load_series_kw = electric_load.loads_kw,
503507
avg_cooling_load_kw = avg_cooling_load_kw,
504508
absorption_chiller_cop = absorption_chiller_cop,

src/mpc/model.jl

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ function build_mpc!(m::JuMP.AbstractModel, p::MPCInputs)
153153

154154
m[:TotalFuelCosts] = 0.0
155155
m[:TotalPerUnitProdOMCosts] = 0.0
156+
m[:TotalPerUnitHourOMCosts] = 0.0
156157

157158
if !isempty(p.techs.gen)
158159
add_gen_constraints(m, p)
@@ -164,6 +165,7 @@ function build_mpc!(m::JuMP.AbstractModel, p::MPCInputs)
164165
sum(m[:dvFuelUsage][t,ts] * p.s.generator.fuel_cost_per_gallon for t in p.techs.gen, ts in p.time_steps)
165166
)
166167
m[:TotalFuelCosts] += m[:TotalGenFuelCosts]
168+
m[:TotalPerUnitHourOMCosts] += m[:TotalHourlyGenOMCosts]
167169
end
168170

169171
add_elec_utility_expressions(m, p)
@@ -203,7 +205,7 @@ function build_mpc!(m::JuMP.AbstractModel, p::MPCInputs)
203205
@expression(m, Costs,
204206

205207
# Variable O&M
206-
m[:TotalPerUnitProdOMCosts] +
208+
m[:TotalPerUnitProdOMCosts] + m[:TotalPerUnitHourOMCosts] +
207209

208210
# Total Generator Fuel Costs
209211
m[:TotalFuelCosts] +

src/mpc/structs.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,7 @@ function MPCGenerator(;
261261
only_runs_during_grid_outage::Bool = true,
262262
sells_energy_back_to_grid::Bool = false,
263263
om_cost_per_kwh::Real=0.0,
264+
om_cost_per_hr_per_kw_rated::Real=0.0,
264265
)
265266
```
266267
"""
@@ -276,6 +277,7 @@ struct MPCGenerator <: AbstractGenerator
276277
only_runs_during_grid_outage
277278
sells_energy_back_to_grid
278279
om_cost_per_kwh
280+
om_cost_per_hr_per_kw_rated
279281

280282
function MPCGenerator(;
281283
size_kw::Real,
@@ -288,6 +290,7 @@ struct MPCGenerator <: AbstractGenerator
288290
only_runs_during_grid_outage::Bool = true,
289291
sells_energy_back_to_grid::Bool = false,
290292
om_cost_per_kwh::Real=0.0,
293+
om_cost_per_hr_per_kw_rated::Real=0.0,
291294
)
292295

293296
max_kw = size_kw
@@ -304,6 +307,7 @@ struct MPCGenerator <: AbstractGenerator
304307
only_runs_during_grid_outage,
305308
sells_energy_back_to_grid,
306309
om_cost_per_kwh,
310+
om_cost_per_hr_per_kw_rated
307311
)
308312
end
309313
end

src/results/boiler.jl

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ function add_boiler_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="
5555
r["thermal_to_steamturbine_series_mmbtu_per_hour"] = round.(value.(NewBoilerToSteamTurbine), digits=3)
5656

5757
if "AbsorptionChiller" in p.techs.cooling
58-
@expression(m, NewBoilertoAbsorptionChillerKW[ts in p.time_steps], sum(value.(m[:dvHeatToAbsorptionChiller]["Boiler",q,ts] for q in p.heating_loads)))
59-
@expression(m, NewBoilertoAbsorptionChillerByQualityKW[q in p.heating_loads, ts in p.time_steps], sum(value.(m[:dvHeatToAbsorptionChiller]["Boiler",q,ts])))
58+
@expression(m, NewBoilertoAbsorptionChillerKW[ts in p.time_steps], sum(m[:dvHeatToAbsorptionChiller]["Boiler",q,ts] for q in p.heating_loads))
59+
@expression(m, NewBoilertoAbsorptionChillerByQualityKW[q in p.heating_loads, ts in p.time_steps], sum(m[:dvHeatToAbsorptionChiller]["Boiler",q,ts]))
6060
else
6161
@expression(m, NewBoilertoAbsorptionChillerKW[ts in p.time_steps], 0.0)
6262
@expression(m, NewBoilertoAbsorptionChillerByQualityKW[q in p.heating_loads, ts in p.time_steps], 0.0)

0 commit comments

Comments
 (0)