Skip to content
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

Open
wants to merge 75 commits into
base: develop
Choose a base branch
from
Open

Added ASHP (for space heating & cooling) #372

wants to merge 75 commits into from

Conversation

atpham88
Copy link
Collaborator

@atpham88 atpham88 commented Apr 8, 2024

Notes

  • This PR adds space heating ASHP to REopt
  • Electric Heater's heating_cop is replaced with backup_heating_cop

@atpham88 atpham88 requested a review from zolanaj April 8, 2024 16:05
@zolanaj
Copy link
Collaborator

zolanaj commented Apr 29, 2024

@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.

Copy link
Collaborator

@zolanaj zolanaj left a 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.

Comment on lines +63 to +64
heating_cop::Dict{String, Array{<:Real, 1}} # (techs.ashp)
cooling_cop::Dict{String, Array{<:Real, 1}} # (techs.ashp)
Copy link
Collaborator

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.

Comment on lines 1737 to 1771
@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

Copy link
Collaborator

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.)

Copy link
Collaborator

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.

Copy link
Collaborator

@Bill-Becker Bill-Becker left a 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,
Copy link
Collaborator

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.

Copy link
Collaborator Author

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]
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed here 032e0d6


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)
Copy link
Collaborator

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?

Copy link
Collaborator

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
Copy link
Collaborator

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".

Copy link
Collaborator

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.

Copy link
Collaborator

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.)

@@ -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")
Copy link
Collaborator

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?!

Copy link
Collaborator Author

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.

Comment on lines 1737 to 1771
@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

Copy link
Collaborator

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.

@zolanaj zolanaj changed the base branch from add-process-heat-load to develop May 14, 2024 03:20
@atpham88 atpham88 changed the title Added ASHP (for space heating) Added ASHP (for space heating & cooling) May 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants