From 215805ccd88b20339dec73ffc2ed520e88d57828 Mon Sep 17 00:00:00 2001 From: An Pham Date: Sun, 22 Dec 2024 20:39:38 -0700 Subject: [PATCH 01/10] added max_ton to GHP --- src/core/ghp.jl | 2 ++ src/core/scenario.jl | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/core/ghp.jl b/src/core/ghp.jl index 949a6a1f7..a6df398a4 100644 --- a/src/core/ghp.jl +++ b/src/core/ghp.jl @@ -24,6 +24,7 @@ struct with outer constructor: cooling_efficiency_thermal_factor::Float64 = NaN # Default depends on building and location ghpghx_response::Dict = Dict() can_serve_dhw::Bool = false + max_ton::Real # Maxium heat pump capacity size. Default at a big number macrs_option_years::Int = 5 macrs_bonus_fraction::Float64 = 0.6 @@ -80,6 +81,7 @@ 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 aux_heater_type::String = "electric" is_ghx_hybrid::Bool = false diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 621781f53..41f84168c 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -603,7 +603,22 @@ function Scenario(d::Dict; flex_hvac_from_json=false) results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) # Create a dictionary of the results data needed for REopt ghpghx_results = GhpGhx.get_results_for_reopt(results, inputs_params) - @info "GhpGhx.jl model solved" #with status $(results["status"])." + # Return results from GhpGhx.jl if user does not provide GHP size or if user entered GHP size is greater than GHP size output + if (!haskey(d["GHP"],"max_ton")) || (haskey(d["GHP"],"max_ton") & ghpghx_results["peak_combined_heatpump_thermal_ton"] <= d["GHP"]["max_ton"]) + @info "GhpGhx.jl model solved" #with status $(results["status"])." + # If user provides udersized GHP, calculate load to send to GhpGhx.jl, and load to send to REopt for backup, and resolve GhpGhx.jl + elseif haskey(d["GHP"],"max_ton") && ghpghx_results["peak_combined_heatpump_thermal_ton"] > 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 ratio of the udersized GHP size and peak load + peak_ratio = d["GHP"]["max_ton"]/ghpghx_results["peak_combined_heatpump_thermal_ton"] + # 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 + @info "Resolving GhpGhx.jl with user specified undersized GHP" + # Call GhpGhx.jl to size GHP and GHX + results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) + # Create a dictionary of the results data needed for REopt + ghpghx_results = GhpGhx.get_results_for_reopt(results, inputs_params) + end catch e @info e throw(@error("The GhpGhx package was not added (add https://github.com/NREL/GhpGhx.jl) or From 58896fcdef9388101b285d8e5c03268919891892 Mon Sep 17 00:00:00 2001 From: An Pham Date: Sun, 22 Dec 2024 21:09:05 -0700 Subject: [PATCH 02/10] Update scenario.jl --- src/core/scenario.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 41f84168c..295cf03e3 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -604,7 +604,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false) # 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 if user does not provide GHP size or if user entered GHP size is greater than GHP size output - if (!haskey(d["GHP"],"max_ton")) || (haskey(d["GHP"],"max_ton") & ghpghx_results["peak_combined_heatpump_thermal_ton"] <= d["GHP"]["max_ton"]) + if (!haskey(d["GHP"],"max_ton")) || (haskey(d["GHP"],"max_ton") && ghpghx_results["peak_combined_heatpump_thermal_ton"] <= d["GHP"]["max_ton"]) @info "GhpGhx.jl model solved" #with status $(results["status"])." # If user provides udersized GHP, calculate load to send to GhpGhx.jl, and load to send to REopt for backup, and resolve GhpGhx.jl elseif haskey(d["GHP"],"max_ton") && ghpghx_results["peak_combined_heatpump_thermal_ton"] > d["GHP"]["max_ton"] From 5fc72e1cb4fdf7e00056260567d4e9c5b21efc5c Mon Sep 17 00:00:00 2001 From: An Pham Date: Sun, 22 Dec 2024 22:03:28 -0700 Subject: [PATCH 03/10] set capacity_sizing_factor= 1.0 when user provides GHP size --- src/core/ghp.jl | 6 +++++- src/core/scenario.jl | 9 +++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/core/ghp.jl b/src/core/ghp.jl index a6df398a4..8f0eaef71 100644 --- a/src/core/ghp.jl +++ b/src/core/ghp.jl @@ -215,7 +215,11 @@ function setup_installed_cost_curve!(ghp::GHP, response::Dict) # the initial slope is based on the heat pump size (e.g. $/ton) of the cost curve for # building a rebate-based cost curve if there are less-than big_number maximum incentives ghp.tech_sizes_for_cost_curve = [0.0, big_number] - + + if haskey(d["GHP"],"max_ton") + ghp.heatpump_capacity_sizing_factor_on_peak_load = 1.0 + end + if ghp.heat_pump_configuration == "WSHP" # Use this with the cost curve to determine absolute cost ghp.heatpump_capacity_ton = heatpump_peak_ton * ghp.heatpump_capacity_sizing_factor_on_peak_load diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 295cf03e3..a1279f2fa 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -612,8 +612,13 @@ function Scenario(d::Dict; flex_hvac_from_json=false) # When user specifies undersized GHP, calculate the ratio of the udersized GHP size and peak load peak_ratio = d["GHP"]["max_ton"]/ghpghx_results["peak_combined_heatpump_thermal_ton"] # 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 - @info "Resolving GhpGhx.jl with user specified undersized GHP" + if get(ghpghx_inputs, "heating_thermal_load_mmbtu_per_hr", []) in [nothing, []] + ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio + end + if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []] + ghpghx_inputs["cooling_thermal_load_ton"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio + end + @info "Restarting GhpGhx.jl with user specified undersized GHP" # Call GhpGhx.jl to size GHP and GHX results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) # Create a dictionary of the results data needed for REopt From c7087c9bb32d3e2dacb4f5933121c328f8a3a72b Mon Sep 17 00:00:00 2001 From: An Pham Date: Sun, 22 Dec 2024 23:26:28 -0700 Subject: [PATCH 04/10] Update ghp.jl --- src/core/ghp.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/ghp.jl b/src/core/ghp.jl index 8f0eaef71..39a76746e 100644 --- a/src/core/ghp.jl +++ b/src/core/ghp.jl @@ -216,10 +216,10 @@ function setup_installed_cost_curve!(ghp::GHP, response::Dict) # building a rebate-based cost curve if there are less-than big_number maximum incentives ghp.tech_sizes_for_cost_curve = [0.0, big_number] - if haskey(d["GHP"],"max_ton") - ghp.heatpump_capacity_sizing_factor_on_peak_load = 1.0 - end - +# if haskey(ghp,"max_ton") +# ghp.heatpump_capacity_sizing_factor_on_peak_load = 1.0 +# end + if ghp.heat_pump_configuration == "WSHP" # Use this with the cost curve to determine absolute cost ghp.heatpump_capacity_ton = heatpump_peak_ton * ghp.heatpump_capacity_sizing_factor_on_peak_load From a34571ed3aece223a12c8ff594cfa454aae88e62 Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 23 Dec 2024 19:20:33 -0700 Subject: [PATCH 05/10] only input scaled down load to GhpGhx.jl if presized GHP --- src/core/scenario.jl | 52 +++++++++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index a1279f2fa..669b1d371 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -456,7 +456,8 @@ 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 + #existing_boiler = nothing if haskey(d, "GHP") && haskey(d["GHP"],"building_sqft") eval_ghp = true if haskey(d["GHP"], "ghpghx_responses") && !isempty(d["GHP"]["ghpghx_responses"]) @@ -600,30 +601,36 @@ 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 - results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) - # 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 if user does not provide GHP size or if user entered GHP size is greater than GHP size output - if (!haskey(d["GHP"],"max_ton")) || (haskey(d["GHP"],"max_ton") && ghpghx_results["peak_combined_heatpump_thermal_ton"] <= d["GHP"]["max_ton"]) - @info "GhpGhx.jl model solved" #with status $(results["status"])." - # If user provides udersized GHP, calculate load to send to GhpGhx.jl, and load to send to REopt for backup, and resolve GhpGhx.jl - elseif haskey(d["GHP"],"max_ton") && ghpghx_results["peak_combined_heatpump_thermal_ton"] > d["GHP"]["max_ton"] + # If user provides udersized GHP, calculate load to send to GhpGhx.jl, and load to send to REopt for backup + heating_load_mmbtu = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] + heating_load_ton = heating_load_mmbtu*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 ratio of the udersized GHP size and peak load - peak_ratio = d["GHP"]["max_ton"]/ghpghx_results["peak_combined_heatpump_thermal_ton"] + 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 - if get(ghpghx_inputs, "heating_thermal_load_mmbtu_per_hr", []) in [nothing, []] - ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio - end + ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = heating_load_mmbtu*peak_ratio + remaining_heating_thermal_load_mmbtu_per_hr = heating_load_mmbtu*(1-peak_ratio) if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []] - ghpghx_inputs["cooling_thermal_load_ton"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio + ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton*peak_ratio + remaining_cooling_thermal_load_ton = cooling_load_ton*(1-peak_ratio) end - @info "Restarting GhpGhx.jl with user specified undersized GHP" - # Call GhpGhx.jl to size GHP and GHX - results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) - # Create a dictionary of the results data needed for REopt - ghpghx_results = GhpGhx.get_results_for_reopt(results, inputs_params) end + results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) + # 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"])." + # Existing boiler and/or chiller are added to serve remaining thermal load not served by the undersized GHP + #if get(ghpghx_inputs, "heating_thermal_load_mmbtu_per_hr", []) in [nothing, []] + # existing_boiler_inputs = Dict{Symbol, Any}() + #end catch e @info e throw(@error("The GhpGhx package was not added (add https://github.com/NREL/GhpGhx.jl) or @@ -640,9 +647,10 @@ 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 + open("/Users/apham/Documents/Projects/REopt_Projects/FY25/GHP Development/Test_Model_Presized_GHP/data/ghpghx_response.json","w") do f + JSON.print(f, ghpghx_response) + end 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 From 9c7979898101f804d6138858addbb3a6d539d8e5 Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 23 Dec 2024 21:40:17 -0700 Subject: [PATCH 06/10] Update scenario.jl --- src/core/scenario.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 669b1d371..953c026af 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -616,10 +616,11 @@ function Scenario(d::Dict; flex_hvac_from_json=false) 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"] = heating_load_mmbtu*peak_ratio - remaining_heating_thermal_load_mmbtu_per_hr = heating_load_mmbtu*(1-peak_ratio) + remaining_heating_thermal_load_mmbtu_per_hr = heating_load_mmbtu*(1-peak_ratio) if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []] ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton*peak_ratio remaining_cooling_thermal_load_ton = cooling_load_ton*(1-peak_ratio) + #cooling_thermal_load_reduction_with_ghp_kw = cooling_load.ghpghx_inputs["cooling_thermal_load_ton"] * KWH_THERMAL_PER_TONHOUR end end results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) From 2abbf909b91cc67c08b45135e15d738899321b14 Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 23 Dec 2024 21:52:17 -0700 Subject: [PATCH 07/10] clean up --- src/core/scenario.jl | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index 953c026af..fbc2cc918 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -602,8 +602,7 @@ function Scenario(d::Dict; flex_hvac_from_json=false) @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_mmbtu = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] - heating_load_ton = heating_load_mmbtu*1000000/12000 + 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"] @@ -615,12 +614,11 @@ function Scenario(d::Dict; flex_hvac_from_json=false) # When user specifies undersized GHP, calculate the ratio of the udersized GHP size and peak 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"] = heating_load_mmbtu*peak_ratio - remaining_heating_thermal_load_mmbtu_per_hr = heating_load_mmbtu*(1-peak_ratio) + ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"] = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*peak_ratio + #remaining_heating_thermal_load_mmbtu_per_hr = ghpghx_inputs["heating_thermal_load_mmbtu_per_hr"]*(1-peak_ratio) if get(ghpghx_inputs, "cooling_thermal_load_ton", []) in [nothing, []] ghpghx_inputs["cooling_thermal_load_ton"] = cooling_load_ton*peak_ratio - remaining_cooling_thermal_load_ton = cooling_load_ton*(1-peak_ratio) - #cooling_thermal_load_reduction_with_ghp_kw = cooling_load.ghpghx_inputs["cooling_thermal_load_ton"] * KWH_THERMAL_PER_TONHOUR + #remaining_cooling_thermal_load_ton = cooling_load_ton*(1-peak_ratio) end end results, inputs_params = GhpGhx.ghp_model(ghpghx_inputs) From 137bc76ed156e879cf0715e130c284d525e2c7e3 Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 30 Dec 2024 19:50:57 -0700 Subject: [PATCH 08/10] set sizing factor = 1 if user inputs their own GHP size --- src/core/ghp.jl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/core/ghp.jl b/src/core/ghp.jl index 39a76746e..1661ddab4 100644 --- a/src/core/ghp.jl +++ b/src/core/ghp.jl @@ -159,7 +159,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) @@ -173,10 +173,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"] @@ -216,9 +216,10 @@ function setup_installed_cost_curve!(ghp::GHP, response::Dict) # building a rebate-based cost curve if there are less-than big_number maximum incentives ghp.tech_sizes_for_cost_curve = [0.0, big_number] -# if haskey(ghp,"max_ton") -# ghp.heatpump_capacity_sizing_factor_on_peak_load = 1.0 -# end + # Set sizing factor = 1 if user inputs their own GHP size + if haskey(d, "GHP") && haskey(d["GHP"],"max_ton") + ghp.heatpump_capacity_sizing_factor_on_peak_load = 1.0 + end if ghp.heat_pump_configuration == "WSHP" # Use this with the cost curve to determine absolute cost From ef4af55fee84c92fab5da80191402b89928729f8 Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 30 Dec 2024 20:01:11 -0700 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe7b6287..7c8fd9fe6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,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 From fa6f434a1a96a143cdfd6634f76c8f29015926af Mon Sep 17 00:00:00 2001 From: An Pham Date: Mon, 30 Dec 2024 20:06:41 -0700 Subject: [PATCH 10/10] clean up --- src/core/scenario.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/scenario.jl b/src/core/scenario.jl index fbc2cc918..a829834f2 100644 --- a/src/core/scenario.jl +++ b/src/core/scenario.jl @@ -457,7 +457,6 @@ function Scenario(d::Dict; flex_hvac_from_json=false) cooling_thermal_load_reduction_with_ghp_kw = zeros(8760 * settings.time_steps_per_hour) eval_ghp = false get_ghpghx_from_input = false - #existing_boiler = nothing if haskey(d, "GHP") && haskey(d["GHP"],"building_sqft") eval_ghp = true if haskey(d["GHP"], "ghpghx_responses") && !isempty(d["GHP"]["ghpghx_responses"])