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

Add GHP to proforma metrics, add thermal BAU outputs #281

Merged
merged 16 commits into from
Oct 4, 2023
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
71 changes: 71 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,72 @@ 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)
Copy link
Collaborator

Choose a reason for hiding this comment

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

@rathod-b I updated this to handle Central GHP "WWHP" with the two different heat pump sizes. I just made new_kw == average of two sizes as a workaround even though they should be handled separately. Punt on that!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense!

total_kw = results[tech_name]["size_heat_pump_ton"]
existing_kw = :existing_kw in fieldnames(typeof(tech)) ? tech.existing_kw : 0
new_kw = total_kw - existing_kw
capital_cost = new_kw * tech.installed_cost_per_kw[2]

# 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
Loading