Skip to content

Commit

Permalink
Merge pull request #281 from NREL/ghp_results_updates
Browse files Browse the repository at this point in the history
Add GHP to proforma metrics, add thermal BAU outputs
  • Loading branch information
Bill-Becker authored Oct 4, 2023
2 parents 202f3bb + 5822077 commit 86279b4
Show file tree
Hide file tree
Showing 8 changed files with 190,674 additions and 76,478 deletions.
2 changes: 1 addition & 1 deletion src/core/ghp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ Base.@kwdef mutable struct GHP <: AbstractGHP
space_heating_efficiency_thermal_factor::Float64 = NaN # Default depends on building and location
cooling_efficiency_thermal_factor::Float64 = NaN # Default depends on building and location
ghpghx_response::Dict = Dict()
can_serve_dhw::Bool = false
can_serve_dhw::Bool = false # If this default changes, must change conditional in scenario.jl for sending loads to GhpGhx.jl

aux_heater_type::String = "electric"
is_ghx_hybrid::Bool = false
Expand Down
8 changes: 5 additions & 3 deletions src/core/scenario.jl
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
d["GHP"]["ghpghx_inputs"][i]["ambient_temperature_f"] = ambient_temp_degF
# Only SpaceHeating portion of Heating Load gets served by GHP, unless allowed by can_serve_dhw
if get(ghpghx_inputs, "heating_thermal_load_mmbtu_per_hr", []) in [nothing, []]
if haskey(d["GHP"], "can_serve_dhw") && d["GHP"]["can_serve_dhw"]
if get(d["GHP"], "can_serve_dhw", false) # This is assuming the default stays false
ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = (space_heating_load.loads_kw + dhw_load.loads_kw - space_heating_thermal_load_reduction_with_ghp_kw) / KWH_PER_MMBTU
else
ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = (space_heating_load.loads_kw - space_heating_thermal_load_reduction_with_ghp_kw) / KWH_PER_MMBTU
Expand Down Expand Up @@ -576,8 +576,10 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
pop!(ghp_inputs_removed_ghpghx_responses, "ghpghx_inputs")
end
for ghpghx_response in get(d["GHP"], "ghpghx_responses", [])
if get(ghpghx_response["inputs"], "hybrid_ghx_sizing_method", nothing) in ["Automatic", "Fractional"]
ghp_inputs_removed_ghpghx_responses["is_ghx_hybrid"] = true
if haskey(ghpghx_response, "inputs")
if get(ghpghx_response["inputs"], "hybrid_ghx_sizing_method", nothing) in ["Automatic", "Fractional"]
ghp_inputs_removed_ghpghx_responses["is_ghx_hybrid"] = true
end
end
append!(ghp_option_list, [GHP(ghpghx_response, ghp_inputs_removed_ghpghx_responses)])
end
Expand Down
21 changes: 18 additions & 3 deletions src/results/financial.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ function add_financial_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _
if !(Symbol("GHPOMCosts"*_n) in keys(m.obj_dict)) # CHP not currently included in multi-node modeling
m[Symbol("GHPOMCosts"*_n)] = 0.0
end
if !(Symbol("GHPCapCosts"*_n) in keys(m.obj_dict)) # HHP not currently included in multi-node modeling
if !(Symbol("GHPCapCosts"*_n) in keys(m.obj_dict)) # GHP not currently included in multi-node modeling
m[Symbol("GHPCapCosts"*_n)] = 0.0
end

Expand Down Expand Up @@ -92,9 +92,10 @@ function add_financial_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _
r["year_one_om_costs_before_tax"] = r["lifecycle_om_costs_before_tax"] / (p.pwf_om * p.third_party_factor)
r["year_one_om_costs_after_tax"] = r["lifecycle_om_costs_after_tax"] / (p.pwf_om * p.third_party_factor)

r["lifecycle_capital_costs_plus_om_after_tax"] = value(m[Symbol("TotalTechCapCosts"*_n)] + m[Symbol("TotalStorageCapCosts"*_n)] + m[Symbol("GHPCapCosts"*_n)]) +
r["lifecycle_om_costs_after_tax"]
r["lifecycle_capital_costs_plus_om_after_tax"] = value(m[Symbol("TotalTechCapCosts"*_n)] + m[Symbol("TotalStorageCapCosts"*_n)] + m[Symbol("GHPCapCosts"*_n)]) + r["lifecycle_om_costs_after_tax"]

r["lifecycle_capital_costs"] = value(m[Symbol("TotalTechCapCosts"*_n)] + m[Symbol("TotalStorageCapCosts"*_n)] + m[Symbol("GHPCapCosts"*_n)])

r["initial_capital_costs"] = initial_capex(m, p; _n=_n)
future_replacement_cost, present_replacement_cost = replacement_costs_future_and_present(m, p; _n=_n)
r["initial_capital_costs_after_incentives"] = r["lifecycle_capital_costs"] / p.third_party_factor - present_replacement_cost
Expand Down Expand Up @@ -191,6 +192,20 @@ function initial_capex(m::JuMP.AbstractModel, p::REoptInputs; _n="")

# TODO thermal tech costs

if !isempty(p.s.ghp_option_list)

for option in enumerate(p.s.ghp_option_list)

if option[2].heat_pump_configuration == "WSHP"
initial_capex += option[2].installed_cost_per_kw[2]*option[2].heatpump_capacity_ton*value(m[Symbol("binGHP"*_n)][option[1]])
elseif option[2].heat_pump_configuration == "WWHP"
initial_capex += (option[2].wwhp_heating_pump_installed_cost_curve[2]*option[2].wwhp_heating_pump_capacity_ton + option[2].wwhp_cooling_pump_installed_cost_curve[2]*option[2].wwhp_cooling_pump_capacity_ton)*value(m[Symbol("binGHP"*_n)][option[1]])
else
@warn "Unknown heat pump configuration provided, excluding GHP costs from initial capital costs."
end
end
end

return initial_capex
end

Expand Down
77 changes: 77 additions & 0 deletions src/results/proforma.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,11 @@ function proforma_results(p::REoptInputs, d::Dict)
m.om_series_bau += escalate_om(annual_om_bau)
end

# calculate GHP incentives, and depreciation
if "GHP" in keys(d) && d["GHP"]["ghp_option_chosen"] > 0
update_ghp_metrics(m, p, p.s.ghp_option_list[d["GHP"]["ghp_option_chosen"]], "GHP", d, third_party)
end

# Optimal Case calculations
electricity_bill_series = escalate_elec(d["ElectricTariff"]["year_one_bill_before_tax"])
export_credit_series = escalate_elec(d["ElectricTariff"]["year_one_export_benefit_before_tax"])
Expand Down Expand Up @@ -359,6 +364,78 @@ function update_metrics(m::Metrics, p::REoptInputs, tech::AbstractTech, tech_nam
nothing
end

function update_ghp_metrics(m::REopt.Metrics, p::REoptInputs, tech::REopt.AbstractTech, tech_name::String, results::Dict, third_party::Bool)
if tech.heat_pump_configuration == "WWHP"
total_heating_kw = results[tech_name]["size_wwhp_heating_pump_ton"]
total_cooling_kw = results[tech_name]["size_wwhp_cooling_pump_ton"]
new_kw = (total_heating_kw + total_cooling_kw) / 2.0 # WIP workaround, not ideal
capital_cost = total_heating_kw * tech.wwhp_heating_pump_installed_cost_curve[2] +
total_cooling_kw * tech.wwhp_cooling_pump_installed_cost_curve[2]
else
new_kw = results[tech_name]["size_heat_pump_ton"]
capital_cost = new_kw * tech.installed_cost_per_kw[2]
end

# building specific OM costs
annual_om = -1 * tech.building_sqft*tech.om_cost_per_sqft_year

years = p.s.financial.analysis_years
escalate_om(val) = [val * (1 + p.s.financial.om_cost_escalation_rate_fraction)^yr for yr in 1:years]
m.om_series += escalate_om(annual_om)
m.om_series_bau += escalate_om(0)

# incentive calculations, in the spreadsheet utility incentives are applied first
utility_ibi = minimum([capital_cost * tech.utility_ibi_fraction, tech.utility_ibi_max])
utility_cbi = minimum([new_kw * tech.utility_rebate_per_kw, tech.utility_rebate_max])
state_ibi = minimum([(capital_cost - utility_ibi - utility_cbi) * tech.state_ibi_fraction, tech.state_ibi_max])
state_cbi = minimum([new_kw * tech.state_rebate_per_kw, tech.state_rebate_max])
federal_cbi = new_kw * tech.federal_rebate_per_kw
ibi = utility_ibi + state_ibi
cbi = utility_cbi + federal_cbi + state_cbi
m.total_ibi_and_cbi += ibi + cbi

# Production-based incentives
pbi_series = Float64[]
pbi_series_bau = Float64[]
# existing_energy_bau = third_party ? get(results[tech_name], "year_one_energy_produced_kwh_bau", 0) : 0
# year_one_energy = "year_one_energy_produced_kwh" in keys(results[tech_name]) ? results[tech_name]["year_one_energy_produced_kwh"] : results[tech_name]["annual_energy_produced_kwh"]
for yr in range(0, stop=years-1)
push!(pbi_series, 0.0)
push!(pbi_series_bau, 0.0)
end
m.total_pbi += pbi_series
m.total_pbi_bau += pbi_series_bau

# Federal ITC
# NOTE: bug in v1 has the ITC within the `if tech.macrs_option_years in [5 ,7]` block.
# NOTE: bug in v1 reduces the federal_itc_basis with the federal_cbi, which is incorrect
federal_itc_basis = capital_cost - state_ibi - utility_ibi - state_cbi - utility_cbi
federal_itc_amount = tech.federal_itc_fraction * federal_itc_basis
m.federal_itc += federal_itc_amount

# Depreciation
if tech.macrs_option_years in [5 ,7]
schedule = []
if tech.macrs_option_years == 5
schedule = p.s.financial.macrs_five_year
elseif tech.macrs_option_years == 7
schedule = p.s.financial.macrs_seven_year
end

macrs_bonus_basis = federal_itc_basis - federal_itc_basis * tech.federal_itc_fraction * tech.macrs_itc_reduction
macrs_basis = macrs_bonus_basis * (1 - tech.macrs_bonus_fraction)

depreciation_schedule = zeros(years)
for (i, r) in enumerate(schedule)
if i < length(depreciation_schedule)
depreciation_schedule[i] = macrs_basis * r
end
end
depreciation_schedule[1] += (tech.macrs_bonus_fraction * macrs_bonus_basis)
m.total_depreciation += depreciation_schedule
end
nothing
end

function npv(cashflows::AbstractArray{<:Real, 1}, rate::Real)
years = collect(0:length(cashflows)-1)
Expand Down
6 changes: 6 additions & 0 deletions src/results/results.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ function combine_results(p::REoptInputs, bau::Dict, opt::Dict, bau_scenario::BAU
("Financial", "lifecycle_om_costs_before_tax"),
("Financial", "lifecycle_om_costs_after_tax"),
("Financial", "year_one_om_costs_before_tax"),
("Financial", "lifecycle_fuel_costs_after_tax"),
("ElectricTariff", "year_one_energy_cost_before_tax"),
("ElectricTariff", "year_one_demand_cost_before_tax"),
("ElectricTariff", "year_one_fixed_cost_before_tax"),
Expand Down Expand Up @@ -152,6 +153,11 @@ function combine_results(p::REoptInputs, bau::Dict, opt::Dict, bau_scenario::BAU
("Generator", "year_one_fixed_om_cost_before_tax"),
("FlexibleHVAC", "temperatures_degC_node_by_time"),
("ExistingBoiler", "lifecycle_fuel_cost_after_tax"),
("ExistingBoiler", "year_one_fuel_cost_before_tax"),
("ExistingBoiler", "annual_thermal_production_mmbtu"),
("ExistingBoiler", "annual_fuel_consumption_mmbtu"),
("ExistingChiller", "annual_thermal_production_tonhour"),
("ExistingChiller", "annual_electric_consumption_kwh"),
("Site", "annual_renewable_electricity_kwh"),
("Site", "renewable_electricity_fraction"),
("Site", "total_renewable_energy_fraction"),
Expand Down
Loading

0 comments on commit 86279b4

Please sign in to comment.