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

Allow presized ghp/ghx #466

Draft
wants to merge 23 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ Classify the change according to the following categories:

## v0.48.2
### Added
- Add new optional parameter **max_ton** to GHP module to allow user to size GHP smaller than peak load
- Battery residual value if choosing replacement strategy for degradation
- Add new **ElectricStorage** parameters **max_duration_hours** and **min_duration_hours** to bound the energy duration of battery storage
### Changed
Expand Down
16 changes: 11 additions & 5 deletions src/core/ghp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ struct with outer constructor:
installed_cost_ghx_per_ft::Float64 = 14.0
installed_cost_building_hydronic_loop_per_sqft = 1.70
om_cost_per_sqft_year::Float64 = -0.51
building_sqft::Float64 # Required input
building_sqft::Float64 # Required input
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
cooling_efficiency_thermal_factor::Float64 = NaN # Default depends on building and location
ghpghx_response::Dict = Dict()
can_serve_dhw::Bool = false
max_ton::Real # Maximum heat pump capacity size. Default at a big number
max_number_of_boreholes::Real # Maximum GHX size
load_served_by_ghp::String # "scaled" or "nonpeak"

macrs_option_years::Int = 5
macrs_bonus_fraction::Float64 = 0.6
Expand Down Expand Up @@ -80,6 +83,9 @@ Base.@kwdef mutable struct GHP <: AbstractGHP
can_serve_space_heating::Bool = true
can_serve_process_heat::Bool = false
can_supply_steam_turbine::Bool = false
max_ton::Real = BIG_NUMBER
max_number_of_boreholes::Real = BIG_NUMBER
load_served_by_ghp::String = "nonpeak"

aux_heater_type::String = "electric"
is_ghx_hybrid::Bool = false
Expand Down Expand Up @@ -157,7 +163,7 @@ function GHP(response::Dict, d::Dict)
end
# incentives = IncentivesNoProdBased(**d_mod)

setup_installed_cost_curve!(ghp, response)
setup_installed_cost_curve!(d, ghp, response)

setup_om_cost!(ghp)

Expand All @@ -171,10 +177,10 @@ function GHP(response::Dict, d::Dict)
end

"""
setup_installed_cost_curve!(response::Dict, ghp::GHP)
setup_installed_cost_curve!(d::Dict, response::Dict, ghp::GHP)

"""
function setup_installed_cost_curve!(ghp::GHP, response::Dict)
function setup_installed_cost_curve!(d::Dict, ghp::GHP, response::Dict)
big_number = 1.0e10
# GHX and GHP sizing metrics for cost calculations
total_ghx_ft = response["outputs"]["number_of_boreholes"] * response["outputs"]["length_boreholes_ft"]
Expand Down
74 changes: 70 additions & 4 deletions src/core/scenario.jl
Original file line number Diff line number Diff line change
Expand Up @@ -475,7 +475,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
space_heating_thermal_load_reduction_with_ghp_kw = zeros(8760 * settings.time_steps_per_hour)
cooling_thermal_load_reduction_with_ghp_kw = zeros(8760 * settings.time_steps_per_hour)
eval_ghp = false
get_ghpghx_from_input = false
get_ghpghx_from_input = false
if haskey(d, "GHP") && haskey(d["GHP"],"building_sqft")
eval_ghp = true
if haskey(d["GHP"], "ghpghx_responses") && !isempty(d["GHP"]["ghpghx_responses"])
Expand Down Expand Up @@ -619,9 +619,77 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
# Call GhpGhx.jl to size GHP and GHX
@info "Starting GhpGhx.jl"
# Call GhpGhx.jl to size GHP and GHX
# If user provides udersized GHP, calculate load to send to GhpGhx.jl, and load to send to REopt for backup
heating_load_ton = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*1000000/12000
thermal_load_ton = heating_load_ton
if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []]
cooling_load_ton = ghpghx_inputs["cooling_thermal_load_ton"]
thermal_load_ton = heating_load_ton + cooling_load_ton
end
peak_thermal_load = maximum(thermal_load_ton)
if haskey(d["GHP"],"max_ton") && peak_thermal_load > d["GHP"]["max_ton"]
@info "User entered undersized GHP. Calculating load that can be served by user specified undersized GHP"
# When user specifies undersized GHP, calculate the load to be served by GHP and send the rest to REopt
# If user choose to scale down total load (load_served_by_ghp="scaled"), calculate the ratio of the udersized GHP size and peak load
if d["GHP"]["load_served_by_ghp"] == "scaled"
@info "GHP served scaled down of total thermal load"
peak_ratio = d["GHP"]["max_ton"]/peak_thermal_load
# Scale down the total load profile by the peak ratio and use this scaled down load to rerun GhpGhx.jl
ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio
if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []]
ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton*peak_ratio
end
elseif d["GHP"]["load_served_by_ghp"] == "nonpeak"
@info "GHP serves all thermal load below thermal peak load"
heating_load_mmbtu = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]
#ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"][ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] .>= d["GHP"]["max_ton"]*12000/1000000] .= d["GHP"]["max_ton"]*12000/1000000
heating_load_mmbtu[heating_load_mmbtu .>=d["GHP"]["max_ton"]*12000/1000000] .= d["GHP"]["max_ton"]*12000/1000000
ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = heating_load_mmbtu
if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []]
cooling_load_ton = ghpghx_inputs["cooling_thermal_load_ton"]
cooling_load_ton[cooling_load_ton .>=d["GHP"]["max_ton"]] .= d["GHP"]["max_ton"]
ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton
end

end
end
results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs)
# If max_number_of_boreholes is specified, check if number of boreholes sized by GhpGhx.jl greater than user-specified max_number_of_boreholes,
# and if max_number_of_boreholes is less, reduce thermal load served by GHP until max_number_of_boreholes = number of boreholses sized by GhpGhx.jl
if haskey(d["GHP"],"max_number_of_boreholes")
determine_number_of_boreholes = GhpGhx.get_results_for_reopt(results, inputs_params)
optimal_number_of_boreholes = determine_number_of_boreholes["number_of_boreholes"]
if optimal_number_of_boreholes > d["GHP"]["max_number_of_boreholes"]
@info "Max number of boreholes specified less than number of boreholes sized in GhpGhx.jl, reducing thermal load served by GHP further"
max_iter = 10
for iter = 1:max_iter
borehole_ratio = optimal_number_of_boreholes/d["GHP"]["max_number_of_boreholes"]
new_load_peak = maximum(heating_load_mmbtu)*borehole_ratio
heating_load_mmbtu[heating_load_mmbtu .>=new_load_peak] .= new_load_peak
ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = heating_load_mmbtu
if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []]
cooling_load_ton[cooling_load_ton .>=new_load_peak] .= new_load_peak
ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton
end
# Rerun GhpGhx.jl
results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs)
determine_number_of_boreholes = GhpGhx.get_results_for_reopt(results, inputs_params)
optimal_number_of_boreholes = determine_number_of_boreholes["number_of_boreholes"]
# Solution is found if the new optimal number of boreholes sized by GhpGhx.jl = user-specified max number of boreholes,
# Otherwise, continue solving until reaching max iteration
if -0.5 < optimal_number_of_boreholes-d["GHP"]["max_number_of_boreholes"] < 0.5
break
end
iter += 1
end

end
end


# Create a dictionary of the results data needed for REopt
ghpghx_results = GhpGhx.get_results_for_reopt(results, inputs_params)
# Return results from GhpGhx.jl without load scaling if user does not provide GHP size or if user entered GHP size is greater than GHP size output
@info "GhpGhx.jl model solved" #with status $(results["status"])."
catch e
@info e
Expand All @@ -639,9 +707,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false)
end
append!(ghp_option_list, [GHP(ghpghx_response, ghp_inputs_removed_ghpghx_params)])
# Print out ghpghx_response for loading into a future run without running GhpGhx.jl again
#open("scenarios/ghpghx_response.json","w") do f
# JSON.print(f, ghpghx_response)
#end
# open("scenarios/ghpghx_response.json","w") do f
end
# If ghpghx_responses is included in inputs, do NOT run GhpGhx.jl model and use already-run ghpghx result as input to REopt
elseif eval_ghp && get_ghpghx_from_input
Expand Down
5 changes: 5 additions & 0 deletions src/results/ghp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ function add_ghp_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dict; _n="")
# r["size_heat_pump_ton"] = 0.0
# r["size_wwhp_heating_pump_ton"] = 0.0
# r["size_wwhp_cooling_pump_ton"] = 0.0

# Set sizing factor = 1 if user inputs their own GHP size
if haskey(d, "GHP") && haskey(d["GHP"],"max_ton")
p.s.ghp_option_list[ghp_option_chosen].heatpump_capacity_sizing_factor_on_peak_load = 1.0
end
if ghp_option_chosen >= 1
r["ghpghx_chosen_outputs"] = p.s.ghp_option_list[ghp_option_chosen].ghpghx_response["outputs"]

Expand Down
Loading