From 165d38b8a9caae6b59a04004e995dcfbfd89a7ec Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 24 Dec 2025 14:24:16 -0700 Subject: [PATCH 1/6] add electrified load series outputs --- src/results/electric_load.jl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index 457ef4abe..f9bdadf59 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -24,10 +24,27 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic r = Dict{String, Any}() - r["load_series_kw"] = p.s.electric_load.loads_kw + r["bau_load_series_kw"] = p.s.electric_load.loads_kw r["critical_load_series_kw"] = p.s.electric_load.critical_loads_kw - r["annual_calculated_kwh"] = round( - sum(r["load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 + + r["bau_existing_chiller_electric_load_series_kw"] = p.s.cooling_load.loads_kw_thermal ./ p.cooling_cop["ExistingChiller"] + r["chiller_load_kw"] = [ + sum(value(m[Symbol("dvCoolingProduction"*_n)][t, ts]) / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling, p.techs.ghp); init=0.0) + for ts in p.time_steps + ] + r["electric_heater_load_kw"] = [ + sum(value(m[Symbol("dvHeatingProduction"*_n)][t, q, ts]) / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater; init=0.0) + for ts in p.time_steps + ] + r["ghp_load_kw"] = [ + sum(p.ghp_electric_consumption_kw[g,ts] * value(m[Symbol("binGHP"*_n)][g]) for g in p.ghp_options; init=0.0) + for ts in p.time_steps + ] + # TODO: if we change how we report this here, should we consider changing the critical load calculation throughout? + r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_electric_load_series_kw"] .+ r["chiller_load_kw"] .+ r["electric_heater_load_kw"] .+ r["ghp_load_kw"] + + r["bau_annual_calculated_kwh"] = round( + sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 ) load_dict = get_load_metrics( From 0c87f1d80b02a3b00a1b3eebdd15f7a66f756f46 Mon Sep 17 00:00:00 2001 From: adfarth Date: Wed, 24 Dec 2025 14:43:08 -0700 Subject: [PATCH 2/6] Update electric_load.jl --- src/results/electric_load.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index f9bdadf59..2bb9d291d 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -24,24 +24,24 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic r = Dict{String, Any}() - r["bau_load_series_kw"] = p.s.electric_load.loads_kw + r["bau_load_series_kw"] = p.s.electric_load.loads_kw # includes bau_existing_chiller_electric_load_series_kw r["critical_load_series_kw"] = p.s.electric_load.critical_loads_kw r["bau_existing_chiller_electric_load_series_kw"] = p.s.cooling_load.loads_kw_thermal ./ p.cooling_cop["ExistingChiller"] - r["chiller_load_kw"] = [ + r["chiller_load_series_kw"] = [ sum(value(m[Symbol("dvCoolingProduction"*_n)][t, ts]) / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling, p.techs.ghp); init=0.0) for ts in p.time_steps ] - r["electric_heater_load_kw"] = [ + r["electric_heater_load_series_kw"] = [ sum(value(m[Symbol("dvHeatingProduction"*_n)][t, q, ts]) / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater; init=0.0) for ts in p.time_steps ] - r["ghp_load_kw"] = [ + r["ghp_load_series_kw"] = [ sum(p.ghp_electric_consumption_kw[g,ts] * value(m[Symbol("binGHP"*_n)][g]) for g in p.ghp_options; init=0.0) for ts in p.time_steps ] # TODO: if we change how we report this here, should we consider changing the critical load calculation throughout? - r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_electric_load_series_kw"] .+ r["chiller_load_kw"] .+ r["electric_heater_load_kw"] .+ r["ghp_load_kw"] + r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_electric_load_series_kw"] .+ r["chiller_load_series_kw"] .+ r["electric_heater_load_series_kw"] .+ r["ghp_load_series_kw"] r["bau_annual_calculated_kwh"] = round( sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 From 3fef75a7e1edf7bebc1218a076ca7b04a85bbb9a Mon Sep 17 00:00:00 2001 From: adfarth Date: Tue, 30 Dec 2025 08:37:10 -0700 Subject: [PATCH 3/6] add annual_calculated_kwh --- src/results/electric_load.jl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index 2bb9d291d..28e2042f3 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -47,6 +47,10 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 ) + r["annual_calculated_kwh"] = round( + sum(r["load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 + ) + load_dict = get_load_metrics( r["load_series_kw"]; time_steps_per_hour=p.s.settings.time_steps_per_hour, From 3f55f26a8b195be07b13daf208e861abb82c8b6c Mon Sep 17 00:00:00 2001 From: adfarth Date: Tue, 30 Dec 2025 09:50:54 -0700 Subject: [PATCH 4/6] add notes, mostly --- src/constraints/load_balance.jl | 8 ++++++++ src/results/electric_load.jl | 22 ++++++++++------------ 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/constraints/load_balance.jl b/src/constraints/load_balance.jl index 057330e55..48ccaa5ab 100644 --- a/src/constraints/load_balance.jl +++ b/src/constraints/load_balance.jl @@ -50,6 +50,14 @@ function add_elec_load_balance_constraints(m, p; _n="") + m[Symbol("dvCurtail"*_n)][t, ts] for t in p.techs.elec) + p.s.electric_load.critical_loads_kw[ts] ) + """ + Why not do the following, unless a custom critical load series is uploaded?: + + sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling,p.techs.ghp)) * critical_load_fraction + + sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater) * critical_load_fraction + + p.s.electric_load.loads_kw[ts] * critical_load_fraction + - p.s.cooling_load.loads_kw_thermal[ts] / p.cooling_cop["ExistingChiller"][ts] * critical_load_fraction + + sum(p.ghp_electric_consumption_kw[g,ts] * m[Symbol("binGHP"*_n)][g] for g in p.ghp_options) * critical_load_fraction + """ else # load balancing constraint for off-grid runs @constraint(m, [ts in p.time_steps_without_grid], sum(p.production_factor[t,ts] * p.levelization_factor[t] * m[Symbol("dvRatedProduction"*_n)][t,ts] for t in p.techs.elec) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index 28e2042f3..fb0a74315 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -1,7 +1,7 @@ # REoptĀ®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE. """ `ElectricLoad` results keys: -- `load_series_kw` # vector of BAU site load in every time step. Does not include electric load for any new heating or cooling techs. +- `bau_load_series_kw` # vector of BAU site load in every time step. Does not include electric load for any new heating or cooling techs. Does not adjust for critical load during - `critical_load_series_kw` # vector of site critical load in every time step - `annual_calculated_kwh` # sum of the `load_series_kw`. Does not include electric load for any new heating or cooling techs. - `annual_electric_load_with_thermal_conversions_kwh` # Total end-use electrical load, including electrified heating and cooling end-use load @@ -24,10 +24,15 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic r = Dict{String, Any}() + # BAU Loads r["bau_load_series_kw"] = p.s.electric_load.loads_kw # includes bau_existing_chiller_electric_load_series_kw r["critical_load_series_kw"] = p.s.electric_load.critical_loads_kw + r["bau_existing_chiller_load_series_kw"] = p.s.cooling_load.loads_kw_thermal ./ p.cooling_cop["ExistingChiller"] + r["bau_annual_calculated_kwh"] = round(sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2) - r["bau_existing_chiller_electric_load_series_kw"] = p.s.cooling_load.loads_kw_thermal ./ p.cooling_cop["ExistingChiller"] + # Optimized loads + # dvCoolingProduction, dvHeatingProduction are not constrained in time_steps_without_grid + # ghp_electric_consumption_kw is not relevant (?) in time_steps_without_grid r["chiller_load_series_kw"] = [ sum(value(m[Symbol("dvCoolingProduction"*_n)][t, ts]) / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling, p.techs.ghp); init=0.0) for ts in p.time_steps @@ -40,16 +45,9 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic sum(p.ghp_electric_consumption_kw[g,ts] * value(m[Symbol("binGHP"*_n)][g]) for g in p.ghp_options; init=0.0) for ts in p.time_steps ] - # TODO: if we change how we report this here, should we consider changing the critical load calculation throughout? - r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_electric_load_series_kw"] .+ r["chiller_load_series_kw"] .+ r["electric_heater_load_series_kw"] .+ r["ghp_load_series_kw"] - - r["bau_annual_calculated_kwh"] = round( - sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 - ) - - r["annual_calculated_kwh"] = round( - sum(r["load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2 - ) + # TODO: splice critical load series into bau loads during time_steps_without_grid? + r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_load_series_kw"] .+ r["chiller_load_series_kw"] .+ r["electric_heater_load_series_kw"] .+ r["ghp_load_series_kw"] + r["annual_calculated_kwh"] = round(sum(r["load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2) load_dict = get_load_metrics( r["load_series_kw"]; From 86175fd0ce86997de535da315bc2a080b9e88ce7 Mon Sep 17 00:00:00 2001 From: adfarth Date: Tue, 30 Dec 2025 10:22:00 -0700 Subject: [PATCH 5/6] updt markdown --- src/results/electric_load.jl | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index fb0a74315..5bb2ca7d7 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -1,9 +1,15 @@ # REoptĀ®, Copyright (c) Alliance for Sustainable Energy, LLC. See also https://github.com/NREL/REopt.jl/blob/master/LICENSE. """ `ElectricLoad` results keys: -- `bau_load_series_kw` # vector of BAU site load in every time step. Does not include electric load for any new heating or cooling techs. Does not adjust for critical load during +- `bau_load_series_kw` # vector of BAU site load in every time step. Does not include electric load for any new heating or cooling techs. Does not adjust for critical load during modeled outages. +- `bau_existing_chiller_load_series_kw` # vector of BAU existing chiller electric load in every time step +- `chiller_load_series_kw` # vector of optimized chiller electric load in every time step +- `electric_heater_load_series_kw` # vector of optimized electric heater load in every time step +- `ghp_load_series_kw` # vector of optimized GHP electric load in every time step +- `load_series_kw` # vector of site electric load in the optimized case in every time step - `critical_load_series_kw` # vector of site critical load in every time step -- `annual_calculated_kwh` # sum of the `load_series_kw`. Does not include electric load for any new heating or cooling techs. +- `bau_annual_calculated_kwh` # sum of `bau_load_series_kw`. Does not include electric load for any new heating or cooling techs. +- `annual_calculated_kwh` # sum of `load_series_kw` - `annual_electric_load_with_thermal_conversions_kwh` # Total end-use electrical load, including electrified heating and cooling end-use load - `offgrid_load_met_series_kw` # vector of electric load met by generation techs, for off-grid scenarios only - `offgrid_load_met_fraction` # percentage of total electric load met on an annual basis, for off-grid scenarios only From 7a145a61e899d77ca026854896c25d12d5b40ece Mon Sep 17 00:00:00 2001 From: adfarth Date: Fri, 2 Jan 2026 10:31:37 -0700 Subject: [PATCH 6/6] notes --- src/constraints/load_balance.jl | 2 +- src/results/electric_load.jl | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/constraints/load_balance.jl b/src/constraints/load_balance.jl index 48ccaa5ab..74121d500 100644 --- a/src/constraints/load_balance.jl +++ b/src/constraints/load_balance.jl @@ -51,7 +51,7 @@ function add_elec_load_balance_constraints(m, p; _n="") + p.s.electric_load.critical_loads_kw[ts] ) """ - Why not do the following, unless a custom critical load series is uploaded?: + TODO: Do the following, unless a custom critical load series is uploaded?: + sum(m[Symbol("dvCoolingProduction"*_n)][t, ts] / p.cooling_cop[t][ts] for t in setdiff(p.techs.cooling,p.techs.ghp)) * critical_load_fraction + sum(m[Symbol("dvHeatingProduction"*_n)][t, q, ts] / p.heating_cop[t][ts] for q in p.heating_loads, t in p.techs.electric_heater) * critical_load_fraction + p.s.electric_load.loads_kw[ts] * critical_load_fraction diff --git a/src/results/electric_load.jl b/src/results/electric_load.jl index 5bb2ca7d7..804633772 100644 --- a/src/results/electric_load.jl +++ b/src/results/electric_load.jl @@ -31,11 +31,13 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic r = Dict{String, Any}() # BAU Loads + # TODO: bau load series should have critical load spliced in during time_steps_without_grid r["bau_load_series_kw"] = p.s.electric_load.loads_kw # includes bau_existing_chiller_electric_load_series_kw - r["critical_load_series_kw"] = p.s.electric_load.critical_loads_kw r["bau_existing_chiller_load_series_kw"] = p.s.cooling_load.loads_kw_thermal ./ p.cooling_cop["ExistingChiller"] r["bau_annual_calculated_kwh"] = round(sum(r["bau_load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2) + r["critical_load_series_kw"] = p.s.electric_load.critical_loads_kw + # Optimized loads # dvCoolingProduction, dvHeatingProduction are not constrained in time_steps_without_grid # ghp_electric_consumption_kw is not relevant (?) in time_steps_without_grid @@ -52,6 +54,7 @@ function add_electric_load_results(m::JuMP.AbstractModel, p::REoptInputs, d::Dic for ts in p.time_steps ] # TODO: splice critical load series into bau loads during time_steps_without_grid? + # TODO: would need to update heating and cooling load reporting too r["load_series_kw"] = r["bau_load_series_kw"] .- r["bau_existing_chiller_load_series_kw"] .+ r["chiller_load_series_kw"] .+ r["electric_heater_load_series_kw"] .+ r["ghp_load_series_kw"] r["annual_calculated_kwh"] = round(sum(r["load_series_kw"]) / p.s.settings.time_steps_per_hour, digits=2)