-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added ASHP (for space heating & cooling) #372
base: develop
Are you sure you want to change the base?
Conversation
@Bill-Becker tagging you for a second set of eyes on this PR; once testing improvements have been merged, so will add-process-heat-load, and then the target ranch will be moved to develop. I can then work on the version update. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for getting this together @atpham88! My edits were focused on allowing the ASHP system to cool as well, and then some additional things beyond that to get all tests passing. I'm passing to @Bill-Becker for a second set of eyes now.
heating_cop::Dict{String, Array{<:Real, 1}} # (techs.ashp) | ||
cooling_cop::Dict{String, Array{<:Real, 1}} # (techs.ashp) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a note that the heating_cop and cooling_cop are modeled inputs that are a time series. They are separated because ASHP is both a heating and cooling tech and, unlike GHP, does not have preprocessed production.
test/test_with_xpress.jl
Outdated
@testset "ASHP" begin | ||
d = JSON.parsefile("./scenarios/ashp.json") | ||
d["SpaceHeatingLoad"]["annual_mmbtu"] = 0.5 * 8760 | ||
d["DomesticHotWaterLoad"]["annual_mmbtu"] = 0.5 * 8760 | ||
s = Scenario(d) | ||
p = REoptInputs(s) | ||
m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) | ||
results = run_reopt(m, p) | ||
|
||
#first run: Boiler produces the required heat instead of ASHP - ASHP is not purchased here | ||
@test results["ASHP"]["size_mmbtu_per_hour"] ≈ 0.0 atol=0.1 | ||
@test results["ASHP"]["annual_thermal_production_mmbtu"] ≈ 0.0 atol=0.1 | ||
@test results["ASHP"]["annual_electric_consumption_kwh"] ≈ 0.0 atol=0.1 | ||
@test results["ElectricUtility"]["annual_energy_supplied_kwh"] ≈ 87600.0 atol=0.1 | ||
|
||
d["ExistingBoiler"]["fuel_cost_per_mmbtu"] = 100 | ||
d["ASHP"]["installed_cost_per_mmbtu_per_hour"] = 1.0 | ||
d["ElectricTariff"]["monthly_energy_rates"] = [0,0,0,0,0,0,0,0,0,0,0,0] | ||
s = Scenario(d) | ||
p = REoptInputs(s) | ||
m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) | ||
results = run_reopt(m, p) | ||
|
||
annual_thermal_prod = 0.8 * 8760 #80% efficient boiler --> 0.8 MMBTU of heat load per hour | ||
annual_ashp_consumption = annual_thermal_prod * REopt.KWH_PER_MMBTU #1.0 COP | ||
annual_energy_supplied = 87600 + annual_ashp_consumption | ||
|
||
#Second run: ASHP produces the required heat with free electricity | ||
@test results["ASHP"]["size_mmbtu_per_hour"] ≈ 0.8 atol=0.1 | ||
@test results["ASHP"]["annual_thermal_production_mmbtu"] ≈ annual_thermal_prod rtol=1e-4 | ||
@test results["ASHP"]["annual_electric_consumption_kwh"] ≈ annual_ashp_consumption rtol=1e-4 | ||
@test results["ElectricUtility"]["annual_energy_supplied_kwh"] ≈ annual_energy_supplied rtol=1e-4 | ||
|
||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just confirming - we're going to excise this test file, right? (I can do that in a separate PR, removing the test_with_xpress.jl
file and the testset header for imported xpress tests. I didn't want to do that for the testing speed improvements PR because I wanted the test adjustments to be clear to the reviewer.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should be doing all tests in HiGHS, and we don't need to add to test_with_xpress.jl.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks Alex and An, looks good! I have one units change request and a few other comments.
src/core/ashp.jl
Outdated
""" | ||
function ASHP(; | ||
min_mmbtu_per_hour::Real = 0.0, | ||
max_mmbtu_per_hour::Real = BIG_NUMBER, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should use ton
instead of mmbtu_per_hour
for heat pump thermal production. That's how they typically state the capacity in the industry. So this should apply to min, max, installed cost, O&M cost, and any incentives which I see we don't have a generic $/ton or percent-based incentive currently.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I converted mmbtu to ton for ASHP capacity here 032e0d6
- `size_mmbtu_per_hour` # Thermal production capacity size of the ASHP [MMBtu/hr] | ||
- `electric_consumption_series_kw` # Fuel consumption series [kW] | ||
- `annual_electric_consumption_kwh` # Fuel consumed in a year [kWh] | ||
- `thermal_production_series_mmbtu_per_hour` # Thermal energy production series [MMBtu/hr] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Related but counter to changing the sizing capacity/basis to ton, I'm not opposed to reporting heating load production as mmbtu/hr because that's how we have other heating results. Let's just make sure we're using the same naming convention for thermal/heating/cooling results as other heating and cooling techs, like CHP and GHP. I think this is consistent, and we're differentiating between heating production and cooling production based on the units.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Addressed here 032e0d6
src/results/ashp.jl
Outdated
|
||
function add_ashp_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="") | ||
r = Dict{String, Any}() | ||
r["size_mmbtu_per_hour"] = round(value(m[Symbol("dvSize"*_n)]["ASHP"]) / KWH_PER_MMBTU, digits=3) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this size based on the peak coincident heating and cooling thermal production?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes - see the constraint here:
function add_ashp_heating_cooling_constraints(m, p; _n="")
@constraint(m, [t in setdiff(intersect(p.techs.cooling, p.techs.heating), p.techs.ghp), ts in p.time_steps],
sum(m[Symbol("dvHeatingProduction"*_n)][t,q,ts] for q in p.heating_loads) + m[Symbol("dvCoolingProduction"*_n)][t,ts] <= m[Symbol("dvSize"*_n)][t]
)
end
@@ -2297,6 +2297,56 @@ else # run HiGHS tests | |||
|
|||
end | |||
|
|||
@testset "ASHP" begin | |||
#Case 1: Boiler produces the required heat instead of ASHP - ASHP is not purchased |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we have an equivalent way to "force in space heating ASHP", like we do for GHP, right now? I think the new ExistingBoiler.retire_in_optimal
would "force in space heating and DHW ASHP", but we don't have a way to just force in "DHW ASHP".
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it would be setting the min size, but in order to do that accurately you'd have to know what the peak heating or coincident peak heating + cooling thermal load.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would the force-in function set set all other heating production variables to zero, i.e., would we allow only ASHP to be the source of a given heating load? I ask because we'd also probably exclude other possible techs like the CHP from providing heat as well. (And, if we do this, does that mean no storage as well? CHP would serve load using storage as a middleman otherwise.)
test/test_with_cplex.jl
Outdated
@@ -163,6 +163,45 @@ end | |||
@test r["ElectricTariff"]["year_one_demand_cost_before_tax"] ≈ expected_demand_cost | |||
end | |||
|
|||
@testset "ASHP" begin | |||
d = JSON.parsefile("./scenarios/ashp.json") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is someone currently testing with CPLEX?!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Deleted this. I performed this test for my own only but accidentally pushed it.
test/test_with_xpress.jl
Outdated
@testset "ASHP" begin | ||
d = JSON.parsefile("./scenarios/ashp.json") | ||
d["SpaceHeatingLoad"]["annual_mmbtu"] = 0.5 * 8760 | ||
d["DomesticHotWaterLoad"]["annual_mmbtu"] = 0.5 * 8760 | ||
s = Scenario(d) | ||
p = REoptInputs(s) | ||
m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) | ||
results = run_reopt(m, p) | ||
|
||
#first run: Boiler produces the required heat instead of ASHP - ASHP is not purchased here | ||
@test results["ASHP"]["size_mmbtu_per_hour"] ≈ 0.0 atol=0.1 | ||
@test results["ASHP"]["annual_thermal_production_mmbtu"] ≈ 0.0 atol=0.1 | ||
@test results["ASHP"]["annual_electric_consumption_kwh"] ≈ 0.0 atol=0.1 | ||
@test results["ElectricUtility"]["annual_energy_supplied_kwh"] ≈ 87600.0 atol=0.1 | ||
|
||
d["ExistingBoiler"]["fuel_cost_per_mmbtu"] = 100 | ||
d["ASHP"]["installed_cost_per_mmbtu_per_hour"] = 1.0 | ||
d["ElectricTariff"]["monthly_energy_rates"] = [0,0,0,0,0,0,0,0,0,0,0,0] | ||
s = Scenario(d) | ||
p = REoptInputs(s) | ||
m = Model(optimizer_with_attributes(Xpress.Optimizer, "OUTPUTLOG" => 0)) | ||
results = run_reopt(m, p) | ||
|
||
annual_thermal_prod = 0.8 * 8760 #80% efficient boiler --> 0.8 MMBTU of heat load per hour | ||
annual_ashp_consumption = annual_thermal_prod * REopt.KWH_PER_MMBTU #1.0 COP | ||
annual_energy_supplied = 87600 + annual_ashp_consumption | ||
|
||
#Second run: ASHP produces the required heat with free electricity | ||
@test results["ASHP"]["size_mmbtu_per_hour"] ≈ 0.8 atol=0.1 | ||
@test results["ASHP"]["annual_thermal_production_mmbtu"] ≈ annual_thermal_prod rtol=1e-4 | ||
@test results["ASHP"]["annual_electric_consumption_kwh"] ≈ annual_ashp_consumption rtol=1e-4 | ||
@test results["ElectricUtility"]["annual_energy_supplied_kwh"] ≈ annual_energy_supplied rtol=1e-4 | ||
|
||
end | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, we should be doing all tests in HiGHS, and we don't need to add to test_with_xpress.jl.
cc @atpham88 Co-Authored-By: An Pham <An.Pham@nrel.gov>
Notes
heating_cop
is replaced withbackup_heating_cop