<jupyter_start><jupyter_text>Notebook to Analyze Utility System Impacts of Heat Pumps<jupyter_code>import sys
import math
import numpy as np
import pandas as pd

# import matplotlib pyplot commands
from matplotlib.pyplot import *
 
# Show Plots in the Notebook
%matplotlib inline
 
# 'style' the plot like fivethirtyeight.com website
style.use('bmh')

#---- Auto-reload of modules
%load_ext autoreload
%autoreload 2

#---- Altair imports 
import altair as alt
alt.data_transformers.enable('json');   # keeps Huge data out of Notebook
# Bring the heat pump calculator into the Python Path.
# This assumes that the heat-pump-calc project is present and on
# the same directory level as this heat-pump-study project.  Both
# are avaiable from GitHub: https://github.com/alanmitchell.
sys.path.insert(0, '../../heat-pump-calc')
import heatpump.library as lib
import heatpump.hp_model as hpmod
# Need to model a city that has some low temperatures, so that the curve of 
# electrical load will be fully mapped out across the temperature range.
lib.df_city.query('Name == "Talkeetna"')
lib.df_heatpumps.columns
lib.df_heatpumps.query('brand == "Fujitsu"')
# Base Case Inputs to the Heat Pump Calcuator.
in_base = dict(
    city_id=279,      # 279 = Talkeetna, 51 = Kotzebue
    utility=lib.util_from_id(77),
    pce_limit=500,
    co2_lbs_per_kwh=1.5,
    exist_heat_fuel_id=4,
    exist_unit_fuel_cost=3.09,
    exist_fuel_use=None,
    exist_heat_effic=0.8,
    exist_kwh_per_mmbtu=6.25,
    elec_use_jan=500,
    elec_use_may=350,
    includes_dhw=False,
    includes_dryer=False,
    includes_cooking=False,
    occupant_count=3,
    off_months_chks=[],
    garage_stall_count=1,
    garage_heated_by_hp=False,
    indoor_heat_setpoint=70,
    capital_cost=6000,
    rebate_dol=0,
    pct_financed=0,
    loan_term=10,
    loan_interest=0.05,
    hp_life=14,
    op_cost_chg=0,
    discount_rate=0.05,
    inflation_rate=0.02,
    fuel_esc_rate=0.03,
    elec_esc_rate=0.02,
    sales_tax=0.06,
    bldg_floor_area=1900,
    insul_level=2,
    pct_exposed_to_hp=0.46,
    doors_open_to_adjacent=True,
    bedroom_temp_tolerance='med',
    hp_model_id=-1,
    low_temp_cutoff=-5.0,
)
mod = hpmod.HP_model(**in_base)
mod.run()
mod.summary
mod.df_hourly.columns
df_running = mod.df_hourly.query('hp_kwh != 0')
df_running.plot(x='db_temp', y='hp_kwh', kind='scatter', marker='.')
# These curves extend below the Low Temp Cutoff, because the algorithm
# requires that 5 hours of the day are below the low temp cutoff before
# the heat pump is not run.
def load_from_df(df_hourly, low_temp_cutoff):
    """Returns a Pandas series, indexed on temperature bins, that shows the
    heat pump load, averaged into and indexed on outdoor temperature bins.
    The 2 degree F temperature bins span from -10 F to 70 F.  If there is no 
    heat pump electrical use in a bin, it is filled with 0.  Load is expressed in
    kW.
    'df_hourly' is the hourly results DataFrame from the Heat Pump Model.
    'low_temp_cutoff' is the lowest outdoor temperature to include in the load
    profile.
    """
    df_fil = df_hourly.query('db_temp >= @low_temp_cutoff and hp_kwh > 0').copy()
    df_fil['temp_bin'] = df_fil['db_temp'].map(lambda t: math.floor(t/2.0)*2.0 + 1.0)
    loads = df_fil.groupby('temp_bin').mean()['hp_kwh']

    # Make an index spanning the full range
    t_range = np.arange(-19.0, 70.0, 2.0)
    
    return loads.reindex(t_range, fill_value=0.0)
    
load_from_df(mod.df_hourly, -5.0).plot()
# Develop a series to multiply by that ramps load down from the 
# situation where all the heatpumps are on at +11 F and above to
# none on below -5 F,
# which approximates what happens if consumers shut off heat pumps somewhere between +11
# and -5 F
ser_shutoff = pd.Series(1.0, np.arange(-19, 70, 2.0))    # start with all 1s
ser_shutoff.loc[:-7.0] = 0.0      # All off at -7.0 F and below
# ramp the number of units on from -5 F up to +9 F
for ix in np.arange(-5.0, 11.0, 2.0):
    ser_shutoff.loc[ix] = (ix - -7.0)/2.0 * 1./9.
ser_shutoff.head(20)
load_from_df(mod.df_hourly, -5.0) * ser_shutoff
(load_from_df(mod.df_hourly, -5.0) * ser_shutoff).plot()
def run_and_combine_cases(case_list):
    """Runs the input cases in case_list, determines load profile, and then
    combines the cases together given the weightings in case_list.  case_list
    format is [
        (input_set1, weight1),
        (input_set2, weight2),
        etc.
    ]
    """
    loads_final = None
    for inputs, weight in case_list:
        mod = hpmod.HP_model(**inputs)
        mod.run()
        loads = load_from_df(mod.df_hourly, -5.0) * ser_shutoff
        if loads_final is not None:
            loads_final += loads * weight
        else:
            loads_final = loads * weight
            
    return loads_final<jupyter_output><empty_output><jupyter_text>Areas of State where Average Home size is 1,900 square feet The Heat Pump IDs in the following input cases could be different in future versions of the Heat Pump Library, as the ID's are positional.  Update the IDs if running this with a version of the library later than that present on December 17, 2018.<jupyter_code># 2x6, 1900 ft2, single-head
in_case1 = in_base.copy()
in_case1.update(dict(
    bldg_floor_area=1900,
    insul_level=2,          # 2 x 6
    hp_model_id=602,        # Fujitsu 15RLS3
))

# 2x4, 1900 ft2, single-head
in_case2 = in_case1.copy()
in_case2.update(dict(
    insul_level=1,          # 2 x 6
))

# 2x6, 1900 ft2, 2-head (Mitsubishi Electric PUMY-P36NKMU1)
in_case3 = in_case1.copy()
in_case3.update(dict(
    hp_model_id=1142,        # Above Mitsubishi
    pct_exposed_to_hp=0.66,  # Fraction of floor area exposed to indoor units    
))
load_1900 = run_and_combine_cases([
    (in_case1, 0.85 * 0.65),
    (in_case2, 0.85 * 0.35),
    (in_case3, 0.15)
])
load_1900.plot()<jupyter_output><empty_output><jupyter_text>Areas of the State with an Average Home Size of 1,200 square feet<jupyter_code># 2x6, 1200 ft2, single-head
in_case1 = in_base.copy()
in_case1.update(dict(
    bldg_floor_area=1200,
    insul_level=2,          # 2 x 6
    hp_model_id=601,        # Fujitsu 12RLS3
))

# better than 2 x 6, 1200 ft2, single-head
in_case2 = in_case1.copy()
in_case2.update(dict(
    insul_level=3,          # better than 2 x 6
))

load_1200 = run_and_combine_cases([
    (in_case1, 0.65),
    (in_case2, 0.35),
])
load_1200.plot()<jupyter_output><empty_output><jupyter_text>Areas of the State with an Average Home Size of 950 square feet<jupyter_code># 2x6, 950 ft2, single-head
in_case1 = in_base.copy()
in_case1.update(dict(
    bldg_floor_area=950,
    insul_level=2,          # 2 x 6
    hp_model_id=601,        # Fujitsu 12RLS3
))

# better than 2 x 6, 950 ft2, single-head
in_case2 = in_case1.copy()
in_case2.update(dict(
    insul_level=3,          # better than 2 x 6
))

load_950 = run_and_combine_cases([
    (in_case1, 0.6),
    (in_case2, 0.4),
])
load_950.plot()<jupyter_output><empty_output><jupyter_text>Plot All Home Sizes Together<jupyter_code>rcParams['figure.figsize']= (9, 6)   # set Chart Size
rcParams['font.size'] = 12.5            # set Font size in Chart
df_plot = pd.concat([load_1900, load_1200, load_950], axis=1)
df_plot.columns = ['1900 ft2', '1200 ft2', '950 ft2']
df_plot.plot()
xlabel('Outdoor Temperature, °F')
ylabel('Electrical Load per Heat Pump, kW')
legend(title='Average Home Size')
tight_layout()
savefig('images/hp_load.png')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10606669345118522, 'fraction_numerical': 0.05048881746350609, 'mean_word_length': 3.6013555144793594, 'pattern_counts': {'":': 0, '<': 21, '<?xml version=': 0, '>': 23, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '5', 'dir': 'jupyter-structured-clean-dedup', 'id': '291663', 'license': 'MIT', 'n_tokens_mistral': 3023, 'n_tokens_neox': 2758, 'n_words': 752, 'path': 'general/utility_impacts.ipynb', 'repo_name': 'alanmitchell/heat-pump-study', 'size': '7467'}