From 0b6e2ba8c409755702d7aa398a5632492bd618bc Mon Sep 17 00:00:00 2001 From: Jared Langevin Date: Fri, 24 Jan 2025 10:23:28 -0500 Subject: [PATCH] Fix issues from v1 rebase Update fuel split calculations in ecm_prep to correctly reflect changes to account for dual fuel measures (commit 130c2d1). Remove superfluous updating of efficient-captured fuel splits data in ecm_prep, which effectively repeated those calculations multiple times. Reapply exogenous heating rates to secondary heating as well in ecm_prep. In run, reintroduce stock cost reporting that was erroneously removed, and add the more robust handling of zero/blank fuel split adjustments from commit c8a21e6. --- scout/ecm_prep.py | 49 +++++----------- scout/run.py | 141 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 137 insertions(+), 53 deletions(-) diff --git a/scout/ecm_prep.py b/scout/ecm_prep.py index ebe099d0..9e9d6d9f 100644 --- a/scout/ecm_prep.py +++ b/scout/ecm_prep.py @@ -4216,8 +4216,8 @@ def fill_mkts(self, msegs, msegs_cpl, convert_data, tsv_data_init, opts, no_stk_mseg = "" # Flag whether exogenous HP rates apply to current mseg. - # Assume only heating, water heating, and cooking end uses are - # covered by these exogenous rates (no secondary heating); do + # Assume only heating, secondary heating, water heating, and cooking + # end uses are covered by these exogenous rates; do # not apply rates to wood stoves; and ensure that these rates # are only assessed for equipment microsegments (e.g., they do # not apply to envelope component heating energy msegs); are @@ -4230,7 +4230,7 @@ def fill_mkts(self, msegs, msegs_cpl, convert_data, tsv_data_init, opts, ("stove (wood)" not in self.technology["primary"]) and \ (mskeys[-2] is None or "HP" not in mskeys[-2]) and (any([ x in mskeys for x in [ - "heating", "water heating", "cooking"]]) or ( + "heating", "secondary heating", "water heating", "cooking"]]) or ( self.linked_htcl_tover and self.linked_htcl_tover_anchor_eu == "heating")): hp_rate_flag = True @@ -12015,12 +12015,13 @@ def add_sectshape_energy(self, cm, sect_shape_init, add_energy, # Add energy data for current mseg, subtracting out any efficient- # case energy that remains with fossil fuel (not applicable to # sector shapes) - ((add_energy[s][yr] * (1 - (fs_eff_splt_energy[0][yr] / - fs_eff_splt_energy[1][yr])) + ((add_energy[s][yr] * (1 - ( + (fs_eff_splt_energy[0][yr] + fs_eff_splt_energy[1][yr]) / + fs_eff_splt_energy[2][yr])) # Pull in fuel split as needed if (fs_eff_splt_energy and s == "efficient" and "electricity" not in cm - and fs_eff_splt_energy[1][yr] != 0) + and fs_eff_splt_energy[2][yr] != 0) else add_energy[s][yr]) # Only add energy data to sector shapes if data concerns # electricity mseg or fuel switching from fossil is occurring and @@ -13458,7 +13459,8 @@ def find_adj_out_break_cats( out_cz][out_bldg][out_eu][out_fuel_gain] = { yr: mseg_out_break_adj[k]["efficient-captured"][ out_cz][out_bldg][out_eu][out_fuel_gain][yr] - ( - eff_capt_orig[yr] - eff_capt_adj[yr]) + eff_capt_orig[yr] - eff_capt_adj[yr]) * ( + 1 - fs_eff_splt_var_capt[yr]) for yr in self.handyvars.aeo_years} # Update efficient captured for envelope portion of pkg. if # this is being tracked @@ -13480,24 +13482,16 @@ def find_adj_out_break_cats( eff_orig[yr] - eff_adj[yr]) * ( 1 - fs_eff_splt_var[yr])) for yr in self.handyvars.aeo_years} - # Measure-captured efficient energy for switched to fuel - # (if reported) - if eff_capt: - mseg_out_break_adj[k]["efficient-captured"][ - out_cz][out_bldg][out_eu][out_fuel_gain] = { - yr: mseg_out_break_adj[k]["efficient-captured"][ - out_cz][out_bldg][out_eu][out_fuel_gain][yr] - ( - eff_capt_orig[yr] - eff_capt_adj[yr]) - for yr in self.handyvars.aeo_years} # Energy costs if all([x for x in [tot_base_orig_ecost, tot_eff_orig_ecost, tot_save_orig_ecost]]): # Pull the fraction of efficient-case energy cost that remains # w/ the orig. fuel in each year for the contrib. measure/mseg fs_eff_splt_cost = { - yr: (fs_eff_splt["cost"][0][yr] / - fs_eff_splt["cost"][1][yr]) if - fs_eff_splt["cost"][1][yr] != 0 else 1 + yr: ((fs_eff_splt["cost"][0][yr] + + fs_eff_splt["cost"][1][yr]) / + fs_eff_splt["cost"][2][yr]) if + fs_eff_splt["cost"][2][yr] != 0 else 1 for yr in self.handyvars.aeo_years} # Energy cost; original fuel @@ -13593,15 +13587,6 @@ def find_adj_out_break_cats( out_cz][out_bldg][out_eu][out_fuel_save][yr] - ( save_orig[yr] - (base_adj[yr] - eff_adj[yr])) for yr in self.handyvars.aeo_years} - # Measure-captured efficient energy (if reported) - if eff_capt: - # Remove adjusted efficient - mseg_out_break_adj[k]["efficient-captured"][ - out_cz][out_bldg][out_eu][out_fuel_save] = { - yr: mseg_out_break_adj[k]["efficient-captured"][ - out_cz][out_bldg][out_eu][out_fuel_save][yr] - ( - eff_capt_orig[yr] - eff_capt_adj[yr]) for - yr in self.handyvars.aeo_years} # Energy costs if all([x for x in [tot_base_orig_ecost, tot_eff_orig_ecost, @@ -13671,14 +13656,6 @@ def find_adj_out_break_cats( out_cz][out_bldg][out_eu][yr] - ( save_orig[yr] - (base_adj[yr] - eff_adj[yr])) for yr in self.handyvars.aeo_years} - # Measure-captured efficient energy (if reported) - if eff_capt: - mseg_out_break_adj[k][ - "efficient-captured"][out_cz][out_bldg][out_eu] = { - yr: mseg_out_break_adj[k]["efficient-captured"][ - out_cz][out_bldg][out_eu][yr] - ( - eff_capt_orig[yr] - eff_capt_adj[yr]) for - yr in self.handyvars.aeo_years} # Energy costs if all([x for x in [tot_base_orig_ecost, tot_eff_orig_ecost, diff --git a/scout/run.py b/scout/run.py index e6ba6d3d..74cf25e5 100644 --- a/scout/run.py +++ b/scout/run.py @@ -2640,16 +2640,26 @@ def htcl_adj(self, measures_htcl_adj, adopt_scheme, htcl_adj_data): "total"][var_sub][yr]) * ( 1 - adj_frac_t) * fs_eff_splt_var else: - # Handle efficient-captured energy case for - # fuel switching, where no base fuel data will - # be reported (go to next variable in loop) + # Handle case where no base fuel data is reported, which is + # conceivable for fuel switching (go to next variable in loop) try: - adj_out_break[ - "base fuel"][var][var_sub][yr] = \ - adj_out_break["base fuel"][var][ - var_sub][yr] - ( - adj[var]["total"][var_sub][yr]) * ( - 1 - adj_frac_t) * fs_eff_splt_var + # Ensure baseline result is not already zero before + # adjusting; if zero, no further adjustment required + if (not isinstance( + adj_out_break["base fuel"][var][var_sub][yr], numpy.ndarray) + and adj_out_break["base fuel"][var][var_sub][yr] != 0) or ( + isinstance( + adj_out_break["base fuel"][var][var_sub][yr], + numpy.ndarray) and all(adj_out_break[ + "base fuel"][var][var_sub][yr]) != 0): + adj_out_break[ + "base fuel"][var][var_sub][yr] = \ + adj_out_break["base fuel"][var][ + var_sub][yr] - ( + adj[var]["total"][var_sub][yr]) * ( + 1 - adj_frac_t) * fs_eff_splt_var + else: + continue except KeyError: continue @@ -3862,15 +3872,23 @@ def compete_adj( adj_key = "measure" else: adj_key = var_sub - # Handle efficient-captured energy case for fuel switching, - # where no base fuel data will be reported (skip to next - # variable) + # Handle case where no base fuel data is reported, which is conceivable + # for fuel switching (go to next variable in loop) try: - adj_out_break["base fuel"][var][var_sub][yr] = \ - adj_out_break["base fuel"][var][ - var_sub][yr] - ( - adj[var]["total"][adj_key][yr]) * ( - 1 - adj_t) * fs_eff_splt_var + # Ensure baseline result is not already zero before adjusting; if zero, no + # further adjustment required + if (not isinstance( + adj_out_break["base fuel"][var][var_sub][yr], numpy.ndarray) + and adj_out_break["base fuel"][var][var_sub][yr] != 0) or ( + isinstance(adj_out_break["base fuel"][var][var_sub][yr], numpy.ndarray) + and all(adj_out_break["base fuel"][var][var_sub][yr]) != 0): + adj_out_break["base fuel"][var][var_sub][yr] = \ + adj_out_break["base fuel"][var][ + var_sub][yr] - ( + adj[var]["total"][adj_key][yr]) * ( + 1 - adj_t) * fs_eff_splt_var + else: + continue except KeyError: continue @@ -4995,6 +5013,95 @@ def finalize_outputs( # the dict with these is calculated above self.output_ecms[m.name]["Markets and Savings (by Category)"][ adopt_scheme]["Stock Penetration (%)"] = frac_mkt_stk + # If a user desires stock data as an output, calculate and report + # these data for the baseline and measure cases + if self.opts.report_stk is True: + # Set baseline and measure stock keys, including units that + # are calculated above + base_stk_uc_key, base_stk_c_key, meas_stk_key = [ + x + stk_units for x in [ + "Baseline Stock (Uncompeted)", + "Baseline Stock (Competed)", + "Measure Stock (Competed)"]] + # Report baseline stock and stock cost figures (all baseline + # stock/stock cost that the measure could affect/capture); + # report both uncompeted/competed versions + stk_base_uc = {yr: m.markets[adopt_scheme][ + "uncompeted"]["master_mseg"]["stock"][ + "total"]["all"][yr] for yr in focus_yrs} + stk_base_c = { + yr: mkts["stock"]["total"]["all"][yr] for yr in focus_yrs} + stk_cost_base = {yr: mkts["cost"]["stock"]["total"][ + "baseline"][yr] for yr in focus_yrs} + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][base_stk_uc_key] = stk_base_uc + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][base_stk_c_key] = stk_base_c + # Report measure stock and stock cost figures + stk_meas, stk_cost_meas = [{yr: mkts["stock"]["total"][ + "measure"][yr] for yr in focus_yrs}, + {yr: mkts["cost"]["stock"]["total"][ + "efficient"][yr] for yr in focus_yrs}] + # Calculate average and low/high measure stock and stock + # cost figures + stk_meas_avg, stk_cost_meas_avg = [{ + k: numpy.mean(v) for k, v in sv.items()} for sv in [ + stk_meas, stk_cost_meas]] + stk_meas_low, stk_cost_meas_low = [{ + k: numpy.percentile(v, 5) for k, v in sv.items()} for sv + in [stk_meas, stk_cost_meas]] + stk_meas_high, stk_cost_meas_high = [{ + k: numpy.percentile(v, 95) for k, v in stk_meas.items()} + for sv in [stk_meas, stk_cost_meas]] + # Set the average measure stock output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][meas_stk_key] = stk_meas_avg + # Set the average measure stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_tot] = stk_cost_meas_avg + # Set the average measure incremental stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_inc] = { + yr: (stk_cost_meas_avg[yr] - stk_cost_base[yr]) + for yr in focus_yrs} + # Update stock cost data across all ECMs with data for + # current ECM + for yr in focus_yrs: + # Add to total stock cost data (first element of list) + stk_cost_all_ecms[0][yr] += stk_cost_meas_avg[yr] + # Add to inc. stock cost data (second element of list) + stk_cost_all_ecms[1][yr] += ( + stk_cost_meas_avg[yr] - stk_cost_base[yr]) + # Set low/high measure stock outputs (as applicable) + if stk_meas_avg != stk_meas_low: + meas_stk_key_low, stk_cost_key_tot_low, \ + stk_cost_key_inc_low = [x + " (low)" for x in [ + meas_stk_key, stk_cost_key_tot, stk_cost_key_inc]] + meas_stk_key_high, stk_cost_key_tot_high, \ + stk_cost_key_inc_high = [x + " (high)" for x in [ + meas_stk_key, stk_cost_key_tot, stk_cost_key_inc]] + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][meas_stk_key_low] = stk_meas_low + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][meas_stk_key_high] = stk_meas_high + # Set the low measure stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_tot_low] = \ + stk_meas_low + # Set the low measure incremental stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_inc_low] = { + yr: (stk_cost_meas_low[yr] - stk_cost_base[yr]) + for yr in focus_yrs} + # Set the high measure stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_tot_high] = \ + stk_cost_meas_high + # Set the high measure incremental stock cost output + self.output_ecms[m.name]["Markets and Savings (Overall)"][ + adopt_scheme][stk_cost_key_inc_high] = { + yr: (stk_cost_meas_high[yr] - stk_meas_high[yr]) + for yr in focus_yrs} # If a user desires output compatible with GCAM format, assess # impacts of measures on GCAM baselines stock/energy segments.