Example #1
0
    def test_Wh_J(self):
        """Tests work / energy / heat content conversion"""

        e_Wh = 1.0
        e_J = 3600.0

        self.assertAlmostEqual(
            UnitConv(e_Wh).Wh_J(unit_in="Wh"), e_J, places=2
        )

        self.assertAlmostEqual(UnitConv(e_J).Wh_J(unit_in="J"), e_Wh, places=2)
Example #2
0
    def test_ft_m(self):
        """Tests length conversion"""

        length_sqft = 1.0
        length_m2 = 0.3048

        self.assertAlmostEqual(
            UnitConv(length_sqft).ft_m(unit_in="ft"), length_m2, places=2
        )

        self.assertAlmostEqual(
            UnitConv(length_m2).ft_m(unit_in="m"), length_sqft, places=2
        )
Example #3
0
    def test_sqft_m2(self):
        """Tests area conversion"""

        area_sqft = 1.0
        area_m2 = 0.3048 ** 2

        self.assertAlmostEqual(
            UnitConv(area_sqft).sqft_m2(unit_in="sqft"), area_m2, places=2
        )

        self.assertAlmostEqual(
            UnitConv(area_m2).sqft_m2(unit_in="m2"), area_sqft, places=2
        )
Example #4
0
    def test_hp_W(self):
        """Tests power conversion between m3 and gallon"""

        p_hp = 100.0
        p_kW = 74.57

        # test to m3
        self.assertAlmostEqual(
            UnitConv(p_hp, scale_out="kilo").hp_W(unit_in="hp"), p_kW, places=2
        )

        # test to hp
        self.assertAlmostEqual(
            UnitConv(p_kW, scale_in="kilo").hp_W(unit_in="W"), p_hp, places=2
        )
Example #5
0
    def test_m3_gal(self):
        """Tests volume conversion between m3 and gallon"""

        v_m3 = 0.3785412
        v_gal = 100.0

        # test to gal
        self.assertAlmostEqual(
            UnitConv(v_m3).m3_gal(unit_in="m3"), v_gal, places=2
        )

        # test to m3
        self.assertAlmostEqual(
            UnitConv(v_gal).m3_gal(unit_in="gal"), v_m3, places=2
        )
Example #6
0
    def test_m3perh_m3pers(self):
        """Tests volume flow conversion
        """

        flow_m3perh = 3600.
        flow_m3pers = 1.

        self.assertAlmostEqual(
            UnitConv(flow_m3perh).m3perh_m3pers(unit_in='m3perh'),
            flow_m3pers,
            places=2)

        self.assertAlmostEqual(
            UnitConv(flow_m3pers).m3perh_m3pers(unit_in='m3pers'),
            flow_m3perh,
            places=2)
Example #7
0
    def test_m3perh_m3pers(self):
        """Tests volume flow conversion"""

        flow_m3perh = 3600.0
        flow_m3pers = 1.0

        self.assertAlmostEqual(
            UnitConv(flow_m3perh).m3perh_m3pers(unit_in="m3perh"),
            flow_m3pers,
            places=2,
        )

        self.assertAlmostEqual(
            UnitConv(flow_m3pers).m3perh_m3pers(unit_in="m3pers"),
            flow_m3perh,
            places=2,
        )
Example #8
0
    def test_Btu_J(self):
        """Tests work / energy / heat content conversion between Btu and joule
        """

        e_MMBtu = 2.
        e_GJ = 2. * 1.055056

        # test to GJ
        self.assertAlmostEqual(UnitConv(e_MMBtu, scale_in='MM',
                                        scale_out='G').Btu_J(unit_in='Btu'),
                               e_GJ,
                               places=2)

        # test to MMBtu
        self.assertAlmostEqual(UnitConv(e_GJ, scale_in='G',
                                        scale_out='MM').Btu_J(unit_in='J'),
                               e_MMBtu,
                               places=2)
Example #9
0
    def test_degC_K(self):
        """Tests temperature conversion between degC and K
        """
        # say we're using water
        t_degC_freeze = 0.
        t_K_freeze = 273.15

        # test to F
        self.assertAlmostEqual(UnitConv(t_degC_freeze).degC_K(unit_in='degC'),
                               t_K_freeze,
                               places=2)

        t_degC_boil = 100.
        t_K_boil = 373.15

        # test from F
        self.assertAlmostEqual(UnitConv(t_K_boil).degC_K(unit_in='K'),
                               t_degC_boil,
                               places=2)
Example #10
0
    def test_therm_J(self):
        """Tests work / energy / heat content conversion between therm
        and joule
        """

        e_therm = 2.
        e_MJ = 211.

        # test to MJ
        self.assertAlmostEqual(UnitConv(
            e_therm, scale_out='M').therm_J(unit_in='therm'),
                               e_MJ,
                               places=2)

        # test to therm
        self.assertAlmostEqual(UnitConv(e_MJ,
                                        scale_in='M').therm_J(unit_in='J'),
                               e_therm,
                               places=2)
Example #11
0
    def test_degF_degC(self):
        """Tests temperature conversion between degF and degC
        """
        # say we're using water
        t_degC_freeze = 0.
        t_degF_freeze = 32.

        # test to F
        self.assertAlmostEqual(
            UnitConv(t_degC_freeze).degF_degC(unit_in='degC'),
            t_degF_freeze,
            places=2)

        t_degC_boil = 100.
        t_degF_boil = 212.

        # test from F
        self.assertAlmostEqual(UnitConv(t_degF_boil).degF_degC(unit_in='degF'),
                               t_degC_boil,
                               places=2)
Example #12
0
    def test_therm_J(self):
        """Tests work / energy / heat content conversion between therm
        and joule
        """

        e_therm = 2.0
        e_MJ = 211.0

        # test to MJ
        self.assertAlmostEqual(
            UnitConv(e_therm, scale_out="M").therm_J(unit_in="therm"),
            e_MJ,
            places=2,
        )

        # test to therm
        self.assertAlmostEqual(
            UnitConv(e_MJ, scale_in="M").therm_J(unit_in="J"),
            e_therm,
            places=2,
        )
Example #13
0
    def test_Btu_J(self):
        """Tests work / energy / heat content conversion between Btu and joule"""

        e_MMBtu = 2.0
        e_GJ = 2.0 * 1.055056

        # test to GJ
        self.assertAlmostEqual(
            UnitConv(e_MMBtu, scale_in="MM", scale_out="G").Btu_J(
                unit_in="Btu"
            ),
            e_GJ,
            places=2,
        )

        # test to MMBtu
        self.assertAlmostEqual(
            UnitConv(e_GJ, scale_in="G", scale_out="MM").Btu_J(unit_in="J"),
            e_MMBtu,
            places=2,
        )
Example #14
0
def create_plot(results, name, id):
    """
    plot_data = {
            'name': 'Empty Plot', \
            'id': '2',
            'index' : [0], \
            'series': {\
                'Q_hp' : {\
                    'data': [0], \
                    'name': 'Q_hp'}, \
                'Q_dem' : {\
                    'data': [0], \
                    'name': 'Q_dem'}, \
                }, \
            'totals': {\
                'Sol_Fra': {\
                    'name': 'Sol_Fra', \
                    'data': 0.5}, \
                'Q_hp' : {\
                    'data': 1000., \
                    'name': 'Q_hp'}, \
                'Q_dem' : {\
                    'data': 1000., \
                    'name': 'Q_dem'}, \
                }
            }
    """

    # Create totals data
    totals_data = {
        "Solar Fraction": round(results["sol_fra"]["annual"], 3) * 100
    }

    # Add the project totals to totals_data
    totals_data.update(results["proj_total"])

    for key, val in results["proj_total"].items():
        # Convert from Kelvin to Celsius
        if (("Temperature" in key) and ("Drop" not in key)
                and (key not in FILTER)):
            totals_data.update(
                {key: round(UnitConv(val).degC_K(unit_in="K"), 2)})

    totals = {"totals": totals_data}

    # Load timestep results
    ts_res = results["ts_res"]

    # Insert the time series data
    series = {"series": {}}
    for key, val in ts_res.items():
        # Convert from Kelvin to Celsius
        if ("Temperature" in key) and ("Drop" not in key):
            ts_res[key] = round(UnitConv(ts_res[key]).degC_K(unit_in="K"), 2)
        if key not in FILTER:
            series["series"][key] = {"name": key, "data": ts_res[key].tolist()}

    # Create a dictionary containing the graph data (id holds the config id)
    plot = {
        "name": name,
        "id": id,
        "index": json.dumps(hours2datetime(2018, len(ts_res.index))),
    }

    # Add the data key value pairs
    plot.update(series)
    plot.update(totals)

    return plot
Example #15
0
    def setUpClass(self):
        """Assigns values to test variables.
        """
        random_state = np.random.RandomState(123)

        self.plot_results = True
        # it will save under img on the test directory
        self.outpath = os.path.dirname(__file__)

        # get labels
        self.c = SwhLabels().set_hous_labels()
        self.s = SwhLabels().set_prod_labels()
        self.r = SwhLabels().set_res_labels()

        # generate weather data and annual hourly
        # water draw profile

        weather_db_path = os.path.join(os.getcwd(),
                                       'mswh/comm/mswh_system_input.db')

        db = Sql(weather_db_path)

        try:
            # read table names for all tables in a
            # {table name : sheet name} form
            inputs = db.tables2dict(close=True)
        except:
            msg = 'Failed to read input tables from {}.'
            log.error(msg.format(inpath))

        source_and_sink = SourceAndSink(input_dfs=inputs)

        # SF climate is 03, 16 is cold
        self.weather = source_and_sink.irradiation_and_water_main(
            '03', method='isotropic diffuse')

        # community scale household occupancies for 4 households
        occ_com = [4, 4, 3, 5]
        # individual scale household occupancy
        occ_ind = [4]

        # are people at home during the day in any of the households:
        # 'y' or 'n'
        at_home_com = ['n', 'n', 'n', 'n']
        at_home_ind = ['n']

        loads_com, peakload_com = SourceAndSink._make_example_loading_inputs(
            inputs,
            self.c,
            random_state,
            occupancy=occ_com,
            at_home=at_home_com)

        loads_indiv, peakload_indiv = SourceAndSink._make_example_loading_inputs(
            inputs,
            self.c,
            random_state,
            occupancy=occ_ind,
            at_home=at_home_ind)

        # scaled loads to match 300 L/day
        load_array_val = (loads_indiv['End-Use Load'][0] * (300 * 365 * .001) /
                          loads_indiv['End-Use Load'][0].sum())

        loads_indiv_val = pd.DataFrame(
            data=[[1, occ_ind[0], load_array_val]],
            columns=[self.c['id'], self.c['occ'], self.c['load_m3']])

        # performance parameters

        # solar thermal
        sol_the_sys_params = pd.DataFrame(
            data=[[self.s['the_sto'], self.s['f_upper_vol'], .5],
                  [self.s['the_sto'], self.s['ins_thi'], .085],
                  [self.s['the_sto'], self.s['spec_hea_con'], .04],
                  [self.s['the_sto'], self.s['t_tap_set'], 322.04],
                  [self.s['the_sto'], self.s['h_vs_r'], 6.],
                  [self.s['the_sto'], self.s['dt_appr'], 2.],
                  [self.s['the_sto'], self.s['t_max_tank'], 344.15],
                  [self.s['the_sto'], self.s['eta_coil'], .84],
                  [self.s['the_sto'], self.s['circ'], 0.],
                  [self.s['sol_col'], self.s['interc_hwb'], .753],
                  [self.s['sol_col'], self.s['slope_hwb'], -4.025],
                  [self.s['sol_pump'], self.s['eta_sol_pump'], .85],
                  [self.s['piping'], self.s['pipe_spec_hea_con'], .0175],
                  [self.s['piping'], self.s['pipe_ins_thick'], .008],
                  [self.s['piping'], self.s['flow_factor'], .8],
                  [self.s['piping'], self.s['dia_len_exp'],
                   0.43082708345352605],
                  [self.s['piping'], self.s['dia_len_sca'],
                   0.007911283766743384],
                  [self.s['piping'], self.s['discr_diam_m'],
                  '[0.0127, 0.01905, 0.0254, 0.03175, 0.0381,'\
                   '0.0508, 0.0635, 0.0762, 0.1016]'],
                  [self.s['piping'], self.s['circ'], False],
                  [self.s['piping'], self.s['long_br_len_fr'], 1.],
                  [self.s['dist_pump'], self.s['eta_dist_pump'], .85]],
            columns=[self.s['comp'], self.s['param'], self.s['param_value']])

        last_row_for_indiv = (
            sol_the_sys_params.shape[0] - 1 -
            sol_the_sys_params.loc[sol_the_sys_params[self.s['comp']] ==
                                   self.s['dist_pump'], :].shape[0])

        # conventional gas tank wh
        # ~R2.1, EL 1 from the rulemaking analysis
        gas_tank_wh_params = pd.DataFrame(
            data=[[self.s['gas_tank'], self.s['tank_re'], .78],
                  [self.s['gas_tank'], self.s['ins_thi'], .03],
                  [self.s['gas_tank'], self.s['spec_hea_con'], .081],
                  [self.s['gas_tank'], self.s['t_tap_set'], 322.04]],
            columns=[self.s['comp'], self.s['param'], self.s['param_value']])

        # gas tankless backup
        sol_the_inst_gas_bckp_params = pd.DataFrame(
            data=[[self.s['gas_burn'], self.s['comb_eff'], .85]],
            columns=[self.s['comp'], self.s['param'], self.s['param_value']])

        # sizing

        # assume 4 occupants
        # basecase gas tank WH: DOE sizing rule based on
        # peak hourly demand +/-2 gal, assuming +/- 0
        gas_tank_size_indiv = UnitConv(
            peakload_indiv.loc[0, self.c['max_load']]).m3_gal(unit_in='gal')

        gas_tank_wh_size = pd.DataFrame(
            data=[[self.s['gas_tank'], gas_tank_size_indiv]],
            columns=[self.s['comp'], self.s['cap']])

        # individual scale retrofit backup
        sol_the_gas_tank_bckp_size = pd.DataFrame(
            data=[[1, self.s['gas_tank'], gas_tank_size_indiv]],
            columns=[self.c['id'], self.s['comp'], self.s['cap']])

        # CSI sizing rules
        def demand_estimate(occ):
            if occ == 1:
                return 20.
            if occ == 2:
                return 35.
            else:
                return 35. + 10. * (occ - 2.)

        peakload_com[self.c['dem_estimate']] = \
            loads_com[self.c['occ']].apply(lambda x: demand_estimate(x))

        demand_estimate_ind = demand_estimate(occ_ind[0])

        demand_estimate_com = \
            peakload_com[self.c['dem_estimate']].sum()

        col_area_scaler = 1.2  # (CSI sizing rule: upper limit 1.25)
        tank_vol_scaler = 1.3  # (CSI sizing rule: lower limit 1.25)

        col_area_ind_sqft = demand_estimate_ind * col_area_scaler
        col_area_com_sqft = demand_estimate_com * col_area_scaler

        tank_vol_ind_gal = col_area_ind_sqft * tank_vol_scaler
        tank_vol_com_gal = col_area_com_sqft * tank_vol_scaler

        col_area_ind = UnitConv(col_area_ind_sqft).sqft_m2()
        col_area_com = UnitConv(col_area_com_sqft).sqft_m2()

        tank_vol_ind = UnitConv(tank_vol_ind_gal).m3_gal(unit_in='gal')
        tank_vol_com = UnitConv(tank_vol_com_gal).m3_gal(unit_in='gal')

        # piping
        pipe_m_per_hhld = 3.048
        detached_k = 6.
        attached_k = 3.

        # distribution pump
        dist_pump_size = 10.4376 * len(occ_com)**0.9277

        # solar pump
        com_solar_pump_size = 7.5101 * sum(occ_com)**0.5322
        indiv_solar_pump_size = 7.5101 * sum(occ_ind)**0.5322

        # individual
        sol_the_sys_sizes_indiv = pd.DataFrame(
            data=[[self.s['sol_col'], col_area_ind],
                  [self.s['the_sto'], tank_vol_ind],
                  [self.s['sol_pump'], indiv_solar_pump_size],
                  [self.s['piping'], pipe_m_per_hhld]],
            columns=[self.s['comp'], self.s['cap']])

        sol_the_inst_gas_bckp_size = pd.DataFrame(
            data=[[1, self.s['gas_burn'], 50972]],
            columns=[self.c['id'], self.s['comp'], self.s['cap']])

        # piping for single family attached
        sol_the_sys_sizes_com = pd.DataFrame(
            data=[[self.s['sol_col'], col_area_com],
                  [self.s['the_sto'], tank_vol_com],
                  [self.s['sol_pump'], com_solar_pump_size],
                  [self.s['dist_pump'], dist_pump_size],
                  [
                      self.s['piping'],
                      attached_k * pipe_m_per_hhld * len(occ_com)
                  ]],
            columns=[self.s['comp'], self.s['cap']])

        # gas tank backup
        sol_the_gas_tank_bckp_params = gas_tank_wh_params.copy()

        peakload_com['gas_tank_size_com'] = peakload_com[self.c[
            'max_load']].apply(lambda x: UnitConv(x).m3_gal(unit_in='gal'))

        peakload_com.index = peakload_com[self.c['id']]

        sol_the_gas_tank_bckp_sizes_com = pd.DataFrame(
            columns=[self.c['id'], self.s['comp'], self.s['cap']],
            index=peakload_com.index)

        for i in peakload_com.index:
            sol_the_gas_tank_bckp_sizes_com.loc[i, self.c['id']] = i
            sol_the_gas_tank_bckp_sizes_com.loc[
                i, self.s['comp']] = self.s['gas_tank']
            sol_the_gas_tank_bckp_sizes_com.loc[i, self.s['cap']] =\
                peakload_com.loc[i, 'gas_tank_size_com']

        sol_the_gas_tank_bckp_sizes_com.index = \
            range(sol_the_gas_tank_bckp_sizes_com.shape[0])

        sol_the_inst_gas_bckp_sizes_com = pd.DataFrame(
            columns=[self.c['id'], self.s['comp'], self.s['cap']],
            index=peakload_com.index)

        def gas_tankles_size_W(occupancy):
            size = 24875. * occupancy**0.5175
            return size

        for i in peakload_com.index:
            sol_the_inst_gas_bckp_sizes_com.loc[i, self.c['id']] = i
            sol_the_inst_gas_bckp_sizes_com.loc[
                i, self.s['comp']] = self.s['gas_burn']
            sol_the_inst_gas_bckp_sizes_com.loc[i,  self.s['cap']] = \
                gas_tankles_size_W(occ_com[i - 1])

        sol_the_inst_gas_bckp_sizes_com.index =\
            range(sol_the_inst_gas_bckp_sizes_com.shape[0])

        # instantiate systems

        # individual system
        self.sol_wh_indiv_new = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_indiv,
            backup_sizes=sol_the_inst_gas_bckp_size,
            weather=self.weather,
            loads=loads_indiv)

        msg = 'Set up the individual solar thermal system with tankless '\
              'backup test case.'
        log.info(msg)

        self.sol_wh_indiv_retr = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_gas_tank_bckp_params,
            sys_sizes=sol_the_sys_sizes_indiv,
            backup_sizes=sol_the_gas_tank_bckp_size,
            weather=self.weather,
            loads=loads_indiv)

        msg = 'Set up the individual solar thermal system with gas tank '\
              'backup test case.'
        log.info(msg)

        # community system
        self.sol_wh_com_new = System(
            sys_params=sol_the_sys_params,
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_com,
            backup_sizes=sol_the_inst_gas_bckp_sizes_com,
            weather=self.weather,
            loads=loads_com)

        msg = 'Set up the community solar thermal system with tankless '\
              'backup test case.'
        log.info(msg)

        self.sol_wh_com_retr = System(
            sys_params=sol_the_sys_params,
            backup_params=sol_the_gas_tank_bckp_params,
            sys_sizes=sol_the_sys_sizes_com,
            backup_sizes=sol_the_gas_tank_bckp_sizes_com,
            weather=self.weather,
            loads=loads_com)

        msg = 'Set up the community solar thermal system with gas tank '\
              'backup test case.'
        log.info(msg)

        # solar thermal validation

        # individual new with
        # tank and collector size from
        # https://secure.solar-rating.org/Certification/Ratings/#
        # RatingsReport.aspx?device=6926&units=METRICS
        # solar thermal retrofit solar fractions in the rating sheets are low
        # - for the same
        # size of collector and storage the new and retrofit should have the
        # same solar fraction
        # by definition https://en.wikipedia.org/wiki/Solar_savings_fraction

        # sizes based on several observed rated systems from OG-300
        # with an 80 gal tank and gas tankless backup
        sol_the_sys_sizes_val = pd.DataFrame(
            data=[[self.s['sol_col'], 3.9],
                  [self.s['the_sto'],
                   UnitConv(80.).m3_gal(unit_in='gal')],
                  [self.s['sol_pump'], indiv_solar_pump_size],
                  [self.s['piping'], 0.]],
            columns=[self.s['comp'], self.s['cap']])

        self.val_sol_wh = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_val,
            backup_sizes=sol_the_inst_gas_bckp_size,
            weather=self.weather,
            loads=loads_indiv_val)

        msg = 'Set up the individual solar thermal system validation '\
            'test case.'
        log.info(msg)

        self.conv_wh = System(sys_params=gas_tank_wh_params,
                              sys_sizes=gas_tank_wh_size,
                              weather=self.weather,
                              loads=loads_indiv)

        msg = 'Set up the conventional tank wh test case.'
        log.info(msg)

        # With baseline tank with a slightly higher RE and insulation set to
        # R12, as used for the solar tank
        gas_tank_wh_params_val = pd.DataFrame(
            data=[[self.s['gas_tank'], self.s['tank_re'], .82, '-'],
                  [self.s['gas_tank'], self.s['ins_thi'], .04, 'm'],
                  [self.s['gas_tank'], self.s['spec_hea_con'], .085, 'W/mK'],
                  [self.s['gas_tank'], self.s['t_tap_set'], 322.04, 'K']],
            columns=[
                self.s['comp'], self.s['param'], self.s['param_value'],
                self.s['param_unit']
            ])

        # with validation loads and validation parameters
        self.conv_wh_val = System(sys_params=gas_tank_wh_params_val,
                                  sys_sizes=gas_tank_wh_size,
                                  weather=self.weather,
                                  loads=loads_indiv_val)

        msg = 'Set up the conventional tank wh validation test case.'
        log.info(msg)

        # project level component parameter
        # dataframe for solar electric system
        # with an inst. gas backup
        sol_el_tank_params = pd.DataFrame(
            data=[[self.s['hp'], self.s['c1_cop'], 1.229E+00],
                  [self.s['hp'], self.s['c2_cop'], 5.549E-02],
                  [self.s['hp'], self.s['c3_cop'], 1.139E-04],
                  [self.s['hp'], self.s['c4_cop'], -1.128E-02],
                  [self.s['hp'], self.s['c5_cop'], -3.570E-06],
                  [self.s['hp'], self.s['c6_cop'], -7.234E-04],
                  [self.s['hp'], self.s['c1_heat_cap'], 7.055E-01],
                  [self.s['hp'], self.s['c2_heat_cap'], 3.945E-02],
                  [self.s['hp'], self.s['c3_heat_cap'], 1.433E-04],
                  [self.s['hp'], self.s['c4_heat_cap'], 2.768E-03],
                  [self.s['hp'], self.s['c5_heat_cap'], -1.069E-04],
                  [self.s['hp'], self.s['c6_heat_cap'], -2.494E-04],
                  [self.s['hp'], self.s['heat_cap_rated'], 2350.],
                  [self.s['hp'], self.s['cop_rated'], 2.43],
                  [self.s['pv'], self.s['eta_pv'], .16],
                  [self.s['pv'], self.s['f_act'], 1.],
                  [self.s['pv'], self.s['irrad_ref'], 1000.],
                  [self.s['inv'], self.s['eta_dc_ac'], .85],
                  [self.s['the_sto'], self.s['f_upper_vol'], .5],
                  [self.s['the_sto'], self.s['ins_thi'], .04],
                  [self.s['the_sto'], self.s['spec_hea_con'], .04],
                  [self.s['the_sto'], self.s['t_tap_set'], 322.04],
                  [self.s['the_sto'], self.s['h_vs_r'], 6.],
                  [self.s['the_sto'], self.s['dt_appr'], 2.],
                  [self.s['the_sto'], self.s['t_max_tank'], 344.15],
                  [self.s['piping'], self.s['pipe_spec_hea_con'], .0175],
                  [self.s['piping'], self.s['pipe_ins_thick'], .008],
                  [self.s['piping'], self.s['flow_factor'], .8],
                  [self.s['piping'], self.s['dia_len_exp'],
                   .43082708345352605],
                  [self.s['piping'], self.s['dia_len_sca'],
                   .007911283766743384],
                  [self.s['piping'], self.s['discr_diam_m'],
                  '[0.0127, 0.01905, 0.0254, 0.03175, 0.0381,'\
                   '0.0508, 0.0635, 0.0762, 0.1016]'],
                  [self.s['piping'], self.s['circ'], False],
                  [self.s['piping'], self.s['long_br_len_fr'], 1.]],
            columns=[self.s['comp'], self.s['param'], self.s['param_value']])

        # test sizing
        sol_el_tank_sizes_indiv = pd.DataFrame(
            data=[[self.s['hp'], 2350.], [self.s['pv'], 6.25],
                  [self.s['the_sto'], UnitConv(80).m3_gal()],
                  [self.s['dist_pump'], dist_pump_size],
                  [self.s['piping'], pipe_m_per_hhld]],
            columns=[self.s['comp'], self.s['cap']])

        # electric resistance backup parameters
        sol_el_tank_inst_gas_bckp_params = pd.DataFrame(
            data=[[self.s['el_res'], self.s['eta_el_res'], 1.0]],
            columns=[self.s['comp'], self.s['param'], self.s['param_value']])

        # electric resistance backup size
        sol_el_tank_inst_el_res_bckp_size = pd.DataFrame(
            data=[[1, self.s['el_res'], 6500.]],
            columns=[self.c['id'], self.s['comp'], self.s['cap']])

        self.hp_wh = System(sys_params=sol_el_tank_params,
                            backup_params=sol_el_tank_inst_gas_bckp_params,
                            sys_sizes=sol_el_tank_sizes_indiv,
                            backup_sizes=sol_el_tank_inst_el_res_bckp_size,
                            weather=self.weather,
                            loads=loads_indiv)
Example #16
0
    def irradiation_and_water_main(self,
                                   climate_zone,
                                   collector_tilt='latitude',
                                   tilt_standard_deviation=None,
                                   collector_azimuth=0.,
                                   azimuth_standard_deviation=None,
                                   location_ground_reflectance=0.16,
                                   solar_constant_Wm2=1367.,
                                   method='isotropic diffuse',
                                   weather_data_source='cec',
                                   single_row_with_arrays=False):
        """Calculates the hourly total incident radiation on a tilted surface
        for any climate zone in California. If weather data from the provided
        database are passed as `input_dfs`, the user can specify a single
        climate.

        Two separate methods are available for use, with all equations
        (along with the equation numbers provided in comments) as provided in
        J. A. Duffie and W. A. Beckman, Solar engineering of thermal
        processes, 3rd ed. Hoboken, N.J: Wiley, 2006.

        Parameters:

            climate_zone: string
                String of two digits to indicate the CEC climate zone
                being analyzed ('01' to '16').

            collector_azimuth: float, default: 0.
                The deviation of the projection on a horizontal
                plane of the normal to the collector surface from
                the local meridian, in degrees.  Allowable values
                are between +/- 180 degrees (inclusive).  0 degrees
                corresponds to due south, east is negative, and west
                is positive.  Default value is 0 degrees (due south).

            azimuth_standard_deviation: float, default: 'None'
                Final collector azimuth is a value drawn using a normal
                distribution around the collector_azimuth value
                with a azimuth_standard_deviation standard deviation.
                If set to 'None' the final collector azimuth
                equals collector_azimuth

            collector_tilt: float, default: 'latitude'
                The angle between the plane of the collector and the
                horizontal, in degrees.  Allowable values are between
                0 and 180 degrees (inclusive), and values greater than
                90 degrees mean that the surface has a downward-facing
                component. If a default flag is left unchanged, the code
                will assign latitude value to the tilt as a good
                approximation of a design collector or PV tilt.

            tilt_standard_deviation: float, default: 'None'
                Final collector tilt is a value drawn using a normal
                distribution around the collector_tilt value
                with a tilt_standard_deviation standard deviation.
                If set to 'None' the final collector tilt
                equals collector_tilt

            location_ground_reflectance: float, default: 0.16
                The degree of ground reflectance.  Allowable
                values are 0-1 (inclusive), with 0 meaning
                no reflectance and 1 meaning very high
                reflectance.  For reference, fresh snow has
                a high ground reflectance of ~ 0.7.  Default
                value is 0.16, which is the annual average surface
                albedo averaged across the 16 CEC climate zones.

            method: string, default: 'HDKR anisotropic sky'
                Calculation method to use for estimating the total irradiance
                on the tilted collector surface. See notes below. Default
                value is 'HDKR anisotropic sky.'

            solar_constant_Wm2: float, default: 1367.
                Energy from the sun per unit time received on a unit
                area of surface perpendicular to the direction of
                propagation of the radiation at mean earth-sun distance
                outside the atmosphere.  Default value is 1367 W/m^2.

            weather_data_source: string, default: 'cec'
                The type of weather data being used to analyze the
                climate zone for solar insolation.  Allowable values
                are 'cec' and 'tmy3.'  Default value is 'cec.'

            single_row_with_arrays : boolean
                A flag to reformat the resulting dataframe in a row
                of data where each resulting 8760 is stored as an
                array

        Returns:

            data: pd df
                  Weather data frame with appended columns:
                  'global_tilt_radiation_Wm2', 'water_main_t_F',
                  'water_main_t_C', 'dry_bulb_C', 'wet_bulb_C', 'Tilt',
                  'Azimuth']

        Notes:

            The user can select one of two methods to use for
            this calculation:

            1) 'isotropic diffuse':
                   This model was derived by Liu and Jordan (1963).
                   All diffuse radiation is assumed to be isotropic.
                   It is the simpler and more conservative model,
                   and it has been widely used.

            2) 'HDKR anisotropic sky':
                   This model combined methods from Hay and Davies (1980),
                   Klucher (1979), and Reindl, et al. (1990).
                   Diffuse radiation in this model is represented in two
                   parts: isotropic and circumsolar. The model also accounts
                   for horizon brightening.
                   This is also a simple model, but it has been found to be
                   slightly more accurate (and less conservative) than the
                   'isotropic diffuse' model.  For collectors tilted toward
                   the equator, this model is suggested.
        """
        # Read in CEC weather data
        # Ensure climate_zone is a string and has a leading '0,' if needed
        climate_zone = str(climate_zone)

        if len(climate_zone) == 1:
            climate_zone = '0' + climate_zone

        # There are only 16 climate zones in CA, so ensure a valid zone
        # is provided.
        try:
            climate_zone_int = int(climate_zone)
        except:
            msg = 'Climate zone value ({}) is not a number.  Please' \
            ' ensure the climate zone is a number from 1-16, represented' \
            ' as a string.'
            log.error(msg.format(climate_zone))

            raise ValueError

        if (climate_zone_int > 16) | (climate_zone_int < 0):
            msg = 'Climate zone in CA must be a number from 1-16.' \
                  ' Further available climate zone codes are: 0 '\
                  ' for Banja Luka, BIH.'
            log.error(msg)
            raise ValueError

        # draw azimuth value from a distribution if standard
        # deviation provided
        if azimuth_standard_deviation:
            azimuth_mean = collector_azimuth
            collector_azimuth = self.random_state.normal(
                azimuth_mean, azimuth_standard_deviation)

        # Ensure collector_azimuth is between -180 (due east) and
        # +180 (due west)
        if (collector_azimuth > 180.) | (collector_azimuth < -180.):
            msg = 'Collector azimuth angle must be a number between' \
            ' -180 degrees (due east) and +180 degrees (due west).'
            log.error(msg)
            raise ValueError

        # Ensure location_ground_reflectance is between 0 and 1.
        if ((location_ground_reflectance > 1.)
                | (location_ground_reflectance < 0.)):
            msg = 'The annual average location ground reflectance must' \
            ' be a number between 0. (no reflectance) and 1 (perfect' \
            ' reflectance).'
            log.error(msg)
            raise ValueError

        # Ensure the provided solar constant is reasonable
        if ((solar_constant_Wm2 > 1450.) | (solar_constant_Wm2 < 1300.)):
            msg = 'The accepted solar constant is near 1367. W/m^2.' \
            ' Please select a reasonable solar constant value that is' \
            ' between 1300. and 1450. W/m^2.'
            log.error(msg)
            raise ValueError

        # Ensure selected method type is valid
        if ((method != 'isotropic diffuse') &
            (method != 'HDKR anisotropic sky')):
            msg = 'This model only calculated results for two models:' \
            ' \'isotropic diffuse\' and \'HDKR anisotropic sky\'.'
            log.error(msg)
            raise ValueError

        # Read in header data to get latitude and longitude of given
        # climate zone
        if weather_data_source == 'cec':

            key = 'CTZ' + climate_zone + 'S13b'
            header = pd.DataFrame(data=self.data[key].iloc[:20, :2])
            header_data = pd.Series(data=header.iloc[:, 1].values,
                                    index=header.iloc[:, 0])
            # latitude in degrees, north = positive
            latitude = float(header_data['Latitude'])
            # longitude in degrees, west = positive
            longitude = abs(float(header_data['Longitude']))

        elif weather_data_source == 'tmy3':

            # Map CEC climate zone to proper tmy3 weather file
            climate_zone_to_tmy3 = {
                # California, USA
                '01': '725945',
                '02': '724957',
                '03': '724930',
                '04': '724945',
                '05': '723940',
                '06': '722970',
                '07': '722900',
                '08': '722976',
                '09': '722880',
                '10': '722869',
                '11': '725910',
                '12': '724830',
                '13': '723890',
                '14': '723820',
                '15': '722868',
                '16': '725845',
                # Banja Luka, BIH
                '00': '145420'
            }

            key = climate_zone_to_tmy3[climate_zone] + 'TY'
            # latitude in degrees, north = positive
            latitude = float(self.data[key].iloc[0, 4])
            # longitude in degrees, west = positive
            longitude = abs(float(self.data[key].iloc[0, 5]))

        # set the tilt to latitude if no custom tilt got provided
        if collector_tilt == 'latitude':
            collector_tilt = latitude

        # check tilt data type
        elif not isinstance(collector_tilt, float):
            msg = 'Collector tilt value ({}) is neither a float nor' \
            ' \'latitude\'. Please use an allowed value.'
            log.error(msg.format(collector_tilt))
            raise ValueError

        # draw tilt value from a distribution if standard deviation provided
        if tilt_standard_deviation:
            tilt_mean = collector_tilt
            collector_tilt = self.random_state.normal(tilt_mean,
                                                      tilt_standard_deviation)

        # Read in actual weather data for the given climate zone
        if weather_data_source == 'cec':

            key = 'CTZ' + climate_zone + 'S13b'
            solar_data = pd.DataFrame(data=self.data[key].iloc[26:, :].values,
                                      columns=self.data[key].iloc[25, :])

            solar_data.columns = [x.lower() for x in solar_data.columns]

            # deal with data formats as needed
            solar_data = solar_data.astype(float)
            solar_data[solar_data.columns[:3]] = \
                solar_data[solar_data.columns[:3]].astype(int)

            # Rename solar columns
            solar_data.rename(columns={
                'global horizontal radiation':
                'global_horizontal_radiation_Wm2',
                'direct normal radiation':
                'direct_normal_radiation_Wm2',
                'diffuse horiz radiation':
                'diffuse_horizontal_radiation_Wm2'
            },
                              inplace=True)
            # Convert solar units from Btu/hr/ft^2 to W/m^2
            solar_data.global_horizontal_radiation_Wm2 *= 3.15459075
            solar_data.direct_normal_radiation_Wm2 *= 3.15459075
            solar_data.diffuse_horizontal_radiation_Wm2 *= 3.15459075

            solar_data['climate_zone'] = climate_zone
            # The hour data from the CEC is for the end of the hour;
            # we're setting it to be the start of the hour
            solar_data.hour -= 1

        elif weather_data_source == 'tmy3':

            solar_data = self.data[key].iloc[2:, :]
            solar_data = solar_data.apply(pd.to_numeric, errors='ignore')
            solar_data.columns = self.data[key].iloc[1, :]

            solar_data.columns = [x.lower() for x in solar_data.columns]
            # Rename solar columns
            solar_data.rename(columns={
                'etr (w/m^2)': 'extraterrestrial_horizontal_radiation_Wm2',
                'etrn (w/m^2)': 'extraterrestrial_normal_radiation_Wm2',
                'ghi (w/m^2)': 'global_horizontal_radiation_Wm2',
                'dni (w/m^2)': 'direct_normal_radiation_Wm2',
                'dhi (w/m^2)': 'diffuse_horizontal_radiation_Wm2',
                'alb (unitless)': 'surface_albedo'
            },
                              inplace=True)

            solar_data['month'] = solar_data.apply(
                lambda x: int(x['date (mm/dd/yyyy)'][:2]), axis=1)
            solar_data['day'] = solar_data.apply(
                lambda x: int(x['date (mm/dd/yyyy)'][3:5]), axis=1)
            solar_data['hour'] = solar_data.apply(
                lambda x: int(x['time (hh:mm)'][:2]) - 1, axis=1)

            # The TMY3 data contain surface albedo values that can be used
            # Ensure missing values (coded as -9900) are replaced with the
            # average of the available data
            solar_data.surface_albedo = np.where(
                solar_data.surface_albedo == -9900.0, np.nan,
                solar_data.surface_albedo)

            solar_data.surface_albedo = np.where(
                np.isnan(solar_data.surface_albedo),
                solar_data.surface_albedo.mean(), solar_data.surface_albedo)
            location_ground_reflectance = solar_data.surface_albedo.values

        solar_data['day_number_of_year'] = solar_data.apply(
            lambda x: datetime.datetime(2018, x.month, x.day).timetuple(
            ).tm_yday,
            axis=1)

        solar_data = self._add_season_column(solar_data)

        # Calculate solar time:
        # Solar time - standard time [minutes]= 4 *
        #     (longitude_standard - longitude_location) + E
        #    where: longitude_standard = 15 * (PST-GMT),
        # and PST-GMT is always -8 hours
        # Calculate E (equation of time, in minutes)
        B = (solar_data.day_number_of_year - 1) * 360. / 365.  # Equation 1.4.2
        E_minutes = 229.2 * (0.000075 + (0.001868 * np.cos(B)) -
                             (0.032077 * np.sin(B)) -
                             (0.014615 * np.cos(2 * B)) -
                             (0.04089 * np.sin(2 * B)))  # Equation 1.5.3

        # REMEMBER: longitudes are in degrees West, meaning they should
        # both be positive here for California!
        minutes_to_add = (4. * ((15. * 8.) - longitude)) + E_minutes
        solar_time = solar_data.hour + minutes_to_add / 60.  # in hours

        # Calculate the hour angle
        # hour_angle = 15 degrees per hour away from solar noon (12),
        # with morning being negative
        hour_angle_start = 15. * (solar_time - 12.)
        hour_angle_end = 15. * (solar_time + 1. - 12.)

        # Calculate the declination angle for the day (declination_angle)
        declination_angle = (180. / np.pi) * (
            0.006918 - (0.399912 * np.cos(np.radians(B))) +
            (0.070257 * np.sin(np.radians(B))) -
            (0.006758 * np.cos(2 * np.radians(B))) +
            (0.000907 * np.sin(2 * np.radians(B))) -
            (0.002697 * np.cos(3 * np.radians(B))) +
            (0.001480 * np.sin(3 * np.radians(B))))  # Equation 1.6.1b

        # Calculate the ratio of beam radiation to that on a horizontal
        # surface for the collector, averaged over the hour of consideration
        # (to avoid mathematical issues that can arise for hours in which
        # sunrise or sunset occurs)
        R_b_a = (
            (((np.sin(np.radians(declination_angle)) * np.sin(
                np.radians(latitude)) * np.cos(np.radians(collector_tilt))) -
              (np.sin(np.radians(declination_angle)) * np.cos(
                  np.radians(latitude)) * np.sin(np.radians(collector_tilt)) *
               np.cos(np.radians(collector_azimuth)))) *
             np.radians(hour_angle_end - hour_angle_start)) +
            (((np.cos(np.radians(declination_angle)) * np.cos(
                np.radians(latitude)) * np.cos(np.radians(collector_tilt))) +
              (np.cos(np.radians(declination_angle)) * np.sin(
                  np.radians(latitude)) * np.sin(np.radians(collector_tilt)) *
               np.cos(np.radians(collector_azimuth)))) *
             (np.sin(np.radians(hour_angle_end)) -
              np.sin(np.radians(hour_angle_start)))) -
            (np.cos(np.radians(declination_angle)) *
             np.sin(np.radians(collector_tilt)) *
             np.sin(np.radians(collector_azimuth)) *
             (np.cos(np.radians(hour_angle_end)) -
              np.cos(np.radians(hour_angle_start)))))
        R_b_b = (((np.cos(np.radians(latitude)) *
                   np.cos(np.radians(declination_angle))) *
                  (np.sin(np.radians(hour_angle_end)) -
                   np.sin(np.radians(hour_angle_start)))) +
                 ((np.sin(np.radians(latitude)) *
                   np.sin(np.radians(declination_angle))) *
                  (np.radians(hour_angle_end - hour_angle_start))))
        R_b_ave = R_b_a / R_b_b  # Equation 2.14.6

        # Calculate horizontal radiation in absense of atmosphere
        # (Equation 1.10.4, [J/m^2])
        if weather_data_source == 'cec':
            extraterrestrial_horizontal_radiation_Jm2 = (
                12. * 3600. / np.pi * solar_constant_Wm2 *
                (1. +
                 0.033 * np.cos(360. * solar_data.day_number_of_year / 365.)) *
                ((np.cos(np.radians(latitude)) *
                  np.cos(np.radians(declination_angle)) *
                  (np.sin(np.radians(hour_angle_end)) -
                   np.sin(np.radians(hour_angle_start)))) +
                 (np.pi / 180. * (hour_angle_end - hour_angle_start) *
                  np.sin(np.radians(latitude)) *
                  np.sin(np.radians(declination_angle)))))
            # Convert to W/m^2 and ensure the values aren't less than 0.
            solar_data['extraterrestrial_horizontal_radiation_Wm2'] = (
                extraterrestrial_horizontal_radiation_Jm2 * 0.000277777778)
            solar_data.extraterrestrial_horizontal_radiation_Wm2 = np.where(
                solar_data.extraterrestrial_horizontal_radiation_Wm2 < 0., 0.,
                solar_data.extraterrestrial_horizontal_radiation_Wm2)

        # Calculate beam radiation on a horizontal plane
        solar_data['beam_horizontal_radiation_Wm2'] = (
            solar_data.global_horizontal_radiation_Wm2 -
            solar_data.diffuse_horizontal_radiation_Wm2)

        # Calculate total radiation on a tilted surface using the isotropic
        # diffuse model.
        if method == 'isotropic diffuse':
            solar_data['global_tilt_radiation_Wm2'] = (
                (solar_data.beam_horizontal_radiation_Wm2 * R_b_ave) +
                (solar_data.diffuse_horizontal_radiation_Wm2 *
                 ((1 + np.cos(np.radians(collector_tilt))) / 2.0)) +
                (solar_data.global_horizontal_radiation_Wm2 *
                 location_ground_reflectance *
                 ((1 - np.cos(np.radians(collector_tilt))) / 2.0)))
            # Equation 2.15.1
        elif method == 'HDKR anisotropic sky':
            # Calculate the anisotropy index
            anisotropy_index = (
                solar_data.beam_horizontal_radiation_Wm2 /
                solar_data.extraterrestrial_horizontal_radiation_Wm2)
            # Equation 2.16.3
            # Calculate the modulating factor, f
            f = (solar_data.beam_horizontal_radiation_Wm2 /
                 solar_data.global_horizontal_radiation_Wm2)**0.5
            # Equation 2.16.6
            solar_data['global_tilt_radiation_Wm2'] = (
                ((solar_data.beam_horizontal_radiation_Wm2 +
                  (solar_data.diffuse_horizontal_radiation_Wm2 *
                   anisotropy_index)) * R_b_ave) +
                (solar_data.diffuse_horizontal_radiation_Wm2 *
                 (1 - anisotropy_index) *
                 ((1 + np.cos(np.radians(collector_tilt))) / 2.0) *
                 (1 + (f * (np.sin(np.radians(collector_tilt / 2.0))**3)))) +
                (solar_data.global_horizontal_radiation_Wm2 *
                 location_ground_reflectance *
                 ((1 - np.cos(np.radians(collector_tilt))) / 2.0)))
            # Equation 2.16.7

        # You can't get negative energy collection
        # It's also probably unreasonable to expect > 2000 W/m^2
        # In comparisons with PVWatts results, when our model predicts
        # > 2000 W/m^2, it is due to a mathematical
        # anomaly where the actual result should be closer to 0.
        solar_data.global_tilt_radiation_Wm2 = np.where(
            (solar_data.global_tilt_radiation_Wm2 < 0.) |
            (solar_data.global_tilt_radiation_Wm2 >= 2000.), 0.,
            solar_data.global_tilt_radiation_Wm2)

        # To avoid NaNs and other weird values, set the result to 0 if global
        # and diffuse horizontal are both 0
        solar_data.global_tilt_radiation_Wm2 = np.where(
            (solar_data.global_horizontal_radiation_Wm2 == 0.) &
            (solar_data.diffuse_horizontal_radiation_Wm2 == 0.), 0.,
            solar_data.global_tilt_radiation_Wm2)

        # Read in water mains temperature data
        # We only need one hour's worth of data for each month and location
        # because the provided
        # temperatures are equal for each hour of the day.
        water_mains_data = \
            self.data['Appendix_54B_Schedules_WaterMain'].\
            iloc[3:, 0:3]

        water_mains_data.columns = [
            'climate_zone_water', 'month_abb', 'water_main_t_F'
        ]

        # Fill the climate zone forward
        # Only get water mains data for the climate zone we are analyzing
        # for all climate zones is CA
        if (climate_zone_int <= 16) and (climate_zone_int >= 1):
            climate_zone_water_main = climate_zone
        # for any additional climate zones of interest
        elif climate_zone == '00':
            climate_zone_water_main = '11'

        water_mains_data = water_mains_data.fillna(method='ffill')
        water_mains_data = water_mains_data.loc[
            water_mains_data.climate_zone_water == \
            'WaterMainCZ' + climate_zone_water_main]

        # Convert calendar abbreviation to calendar number
        water_mains_data['month_num'] = water_mains_data.apply(
            lambda x: list(calendar.month_abbr).index(x.month_abb), axis=1)

        # Drop unused columns and merge with the weather data
        data = pd.merge(left=solar_data,
                        right=water_mains_data,
                        how='left',
                        left_on='month',
                        right_on='month_num')

        # convert temperatures from F to C
        data['water_main_t_C'] = UnitConv(
            data['water_main_t_F']).degF_degC(unit_in='degF')
        if weather_data_source == 'cec':
            data['dry_bulb_C'] = UnitConv(
                data['dry bulb']).degF_degC(unit_in='degF')
            data['wet_bulb_C'] = UnitConv(
                data['wet bulb']).degF_degC(unit_in='degF')
        elif weather_data_source == 'tmy3':
            data['dry_bulb_C'] = data['dry-bulb (c)']
            # Derive wet bulb temperature from dry bulb temperature and
            # relative humidity
            data['wet_bulb_C'] = self._wet_bulb_approx(data['dry_bulb_C'],
                                                       data['rhum (%)'])

        # add collector tilt value
        data['Tilt'] = collector_tilt
        data['Azimuth'] = collector_azimuth

        data.drop(columns=['climate_zone_water', 'month_abb', 'month_num'],
                  inplace=True)

        if single_row_with_arrays:
            data = self._pack_timeseries(data)

        return data
Example #17
0
    def setUpClass(self):
        """Assigns values to test variables."""
        random_state = np.random.RandomState(123)

        self.plot_results = True
        # it will save under img on the test directory
        self.outpath = os.path.dirname(__file__)

        # get labels
        self.c = SwhLabels().set_hous_labels()
        self.s = SwhLabels().set_prod_labels()
        self.r = SwhLabels().set_res_labels()

        # generate weather data and annual hourly
        # water draw profile

        weather_db_path = os.path.join(os.getcwd(),
                                       "mswh/comm/mswh_system_input.db")

        db = Sql(weather_db_path)

        try:
            inputs = db.tables2dict(close=True)
        except:
            msg = "Failed to read inputs from {}."
            log.error(msg.format(weather_db_path))

        source_and_sink = SourceAndSink(input_dfs=inputs)

        # SF climate is 03, 16 is cold
        self.weather = source_and_sink.irradiation_and_water_main(
            "03", method="isotropic diffuse")

        # community scale household occupancies for 4 households
        occ_com = [4, 4, 3, 5]
        # individual scale household occupancy
        occ_ind = [4]

        # are people at home during the day in any of the households:
        # 'y' or 'n'
        at_home_com = ["n", "n", "n", "n"]
        at_home_ind = ["n"]

        loads_com, peakload_com = SourceAndSink._make_example_loading_inputs(
            inputs,
            self.c,
            random_state,
            occupancy=occ_com,
            at_home=at_home_com,
        )

        (
            loads_indiv,
            peakload_indiv,
        ) = SourceAndSink._make_example_loading_inputs(
            inputs,
            self.c,
            random_state,
            occupancy=occ_ind,
            at_home=at_home_ind,
        )

        # scaled loads to match 300 L/day
        load_array_val = (loads_indiv["End-Use Load"][0] *
                          (300 * 365 * 0.001) /
                          loads_indiv["End-Use Load"][0].sum())

        loads_indiv_val = pd.DataFrame(
            data=[[1, occ_ind[0], load_array_val]],
            columns=[self.c["id"], self.c["occ"], self.c["load_m3"]],
        )

        # performance parameters

        # solar thermal
        sol_the_sys_params = pd.DataFrame(
            data=[
                [self.s["the_sto"], self.s["f_upper_vol"], 0.5],
                [self.s["the_sto"], self.s["ins_thi"], 0.085],
                [self.s["the_sto"], self.s["spec_hea_con"], 0.04],
                [self.s["the_sto"], self.s["t_tap_set"], 322.04],
                [self.s["the_sto"], self.s["h_vs_r"], 6.0],
                [self.s["the_sto"], self.s["dt_appr"], 2.0],
                [self.s["the_sto"], self.s["t_max_tank"], 344.15],
                [self.s["the_sto"], self.s["eta_coil"], 0.84],
                [self.s["the_sto"], self.s["circ"], 0.0],
                [self.s["sol_col"], self.s["interc_hwb"], 0.753],
                [self.s["sol_col"], self.s["slope_hwb"], -4.025],
                [self.s["sol_pump"], self.s["eta_sol_pump"], 0.85],
                [self.s["piping"], self.s["pipe_spec_hea_con"], 0.0175],
                [self.s["piping"], self.s["pipe_ins_thick"], 0.008],
                [self.s["piping"], self.s["flow_factor"], 0.8],
                [self.s["piping"], self.s["dia_len_exp"], 0.43082708345352605],
                [
                    self.s["piping"],
                    self.s["dia_len_sca"],
                    0.007911283766743384,
                ],
                [
                    self.s["piping"],
                    self.s["discr_diam_m"],
                    "[0.0127, 0.01905, 0.0254, 0.03175, 0.0381,"
                    "0.0508, 0.0635, 0.0762, 0.1016]",
                ],
                [self.s["piping"], self.s["circ"], False],
                [self.s["piping"], self.s["long_br_len_fr"], 1.0],
                [self.s["dist_pump"], self.s["eta_dist_pump"], 0.85],
            ],
            columns=[self.s["comp"], self.s["param"], self.s["param_value"]],
        )

        last_row_for_indiv = (
            sol_the_sys_params.shape[0] - 1 -
            sol_the_sys_params.loc[sol_the_sys_params[self.s["comp"]] ==
                                   self.s["dist_pump"], :].shape[0])

        # conventional gas tank wh
        # ~R2.1, EL 1 from the rulemaking analysis
        gas_tank_wh_params = pd.DataFrame(
            data=[
                [self.s["gas_tank"], self.s["tank_re"], 0.78],
                [self.s["gas_tank"], self.s["ins_thi"], 0.03],
                [self.s["gas_tank"], self.s["spec_hea_con"], 0.081],
                [self.s["gas_tank"], self.s["t_tap_set"], 322.04],
            ],
            columns=[self.s["comp"], self.s["param"], self.s["param_value"]],
        )

        # gas tankless backup
        sol_the_inst_gas_bckp_params = pd.DataFrame(
            data=[[self.s["gas_burn"], self.s["comb_eff"], 0.85]],
            columns=[self.s["comp"], self.s["param"], self.s["param_value"]],
        )

        # sizing

        # assume 4 occupants
        # basecase gas tank WH: DOE sizing rule based on
        # peak hourly demand +/-2 gal, assuming +/- 0
        gas_tank_size_indiv = UnitConv(
            peakload_indiv.loc[0, self.c["max_load"]]).m3_gal(unit_in="gal")

        gas_tank_wh_size = pd.DataFrame(
            data=[[self.s["gas_tank"], gas_tank_size_indiv]],
            columns=[self.s["comp"], self.s["cap"]],
        )

        # individual scale retrofit backup
        sol_the_gas_tank_bckp_size = pd.DataFrame(
            data=[[1, self.s["gas_tank"], gas_tank_size_indiv]],
            columns=[self.c["id"], self.s["comp"], self.s["cap"]],
        )

        # CSI sizing rules
        def demand_estimate(occ):
            if occ == 1:
                return 20.0
            if occ == 2:
                return 35.0
            else:
                return 35.0 + 10.0 * (occ - 2.0)

        peakload_com[self.c["dem_estimate"]] = loads_com[self.c["occ"]].apply(
            lambda x: demand_estimate(x))

        demand_estimate_ind = demand_estimate(occ_ind[0])

        demand_estimate_com = peakload_com[self.c["dem_estimate"]].sum()

        col_area_scaler = 1.2  # (CSI sizing rule: upper limit 1.25)
        tank_vol_scaler = 1.3  # (CSI sizing rule: lower limit 1.25)

        col_area_ind_sqft = demand_estimate_ind * col_area_scaler
        col_area_com_sqft = demand_estimate_com * col_area_scaler

        tank_vol_ind_gal = col_area_ind_sqft * tank_vol_scaler
        tank_vol_com_gal = col_area_com_sqft * tank_vol_scaler

        col_area_ind = UnitConv(col_area_ind_sqft).sqft_m2()
        col_area_com = UnitConv(col_area_com_sqft).sqft_m2()

        tank_vol_ind = UnitConv(tank_vol_ind_gal).m3_gal(unit_in="gal")
        tank_vol_com = UnitConv(tank_vol_com_gal).m3_gal(unit_in="gal")

        # piping
        pipe_m_per_hhld = 3.048
        detached_k = 6.0
        attached_k = 3.0

        # distribution pump
        dist_pump_size = 10.4376 * len(occ_com)**0.9277

        # solar pump
        com_solar_pump_size = 7.5101 * sum(occ_com)**0.5322
        indiv_solar_pump_size = 7.5101 * sum(occ_ind)**0.5322

        # individual
        sol_the_sys_sizes_indiv = pd.DataFrame(
            data=[
                [self.s["sol_col"], col_area_ind],
                [self.s["the_sto"], tank_vol_ind],
                [self.s["sol_pump"], indiv_solar_pump_size],
                [self.s["piping"], pipe_m_per_hhld],
            ],
            columns=[self.s["comp"], self.s["cap"]],
        )

        sol_the_inst_gas_bckp_size = pd.DataFrame(
            data=[[1, self.s["gas_burn"], 50972]],
            columns=[self.c["id"], self.s["comp"], self.s["cap"]],
        )

        # piping for single family attached
        sol_the_sys_sizes_com = pd.DataFrame(
            data=[
                [self.s["sol_col"], col_area_com],
                [self.s["the_sto"], tank_vol_com],
                [self.s["sol_pump"], com_solar_pump_size],
                [self.s["dist_pump"], dist_pump_size],
                [
                    self.s["piping"],
                    attached_k * pipe_m_per_hhld * len(occ_com),
                ],
            ],
            columns=[self.s["comp"], self.s["cap"]],
        )

        # gas tank backup
        sol_the_gas_tank_bckp_params = gas_tank_wh_params.copy()

        peakload_com["gas_tank_size_com"] = peakload_com[self.c[
            "max_load"]].apply(lambda x: UnitConv(x).m3_gal(unit_in="gal"))

        peakload_com.index = peakload_com[self.c["id"]]

        sol_the_gas_tank_bckp_sizes_com = pd.DataFrame(
            columns=[self.c["id"], self.s["comp"], self.s["cap"]],
            index=peakload_com.index,
        )

        for i in peakload_com.index:
            sol_the_gas_tank_bckp_sizes_com.loc[i, self.c["id"]] = i
            sol_the_gas_tank_bckp_sizes_com.loc[
                i, self.s["comp"]] = self.s["gas_tank"]
            sol_the_gas_tank_bckp_sizes_com.loc[
                i, self.s["cap"]] = peakload_com.loc[i, "gas_tank_size_com"]

        sol_the_gas_tank_bckp_sizes_com.index = range(
            sol_the_gas_tank_bckp_sizes_com.shape[0])

        sol_the_inst_gas_bckp_sizes_com = pd.DataFrame(
            columns=[self.c["id"], self.s["comp"], self.s["cap"]],
            index=peakload_com.index,
        )

        def gas_tankles_size_W(occupancy):
            size = 24875.0 * occupancy**0.5175
            return size

        for i in peakload_com.index:
            sol_the_inst_gas_bckp_sizes_com.loc[i, self.c["id"]] = i
            sol_the_inst_gas_bckp_sizes_com.loc[
                i, self.s["comp"]] = self.s["gas_burn"]
            sol_the_inst_gas_bckp_sizes_com.loc[
                i, self.s["cap"]] = gas_tankles_size_W(occ_com[i - 1])

        sol_the_inst_gas_bckp_sizes_com.index = range(
            sol_the_inst_gas_bckp_sizes_com.shape[0])

        # instantiate systems

        # individual system
        self.sol_wh_indiv_new = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_indiv,
            backup_sizes=sol_the_inst_gas_bckp_size,
            weather=self.weather,
            loads=loads_indiv,
        )

        msg = ("Set up the individual solar thermal system with tankless "
               "backup test case.")
        log.info(msg)

        self.sol_wh_indiv_retr = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_gas_tank_bckp_params,
            sys_sizes=sol_the_sys_sizes_indiv,
            backup_sizes=sol_the_gas_tank_bckp_size,
            weather=self.weather,
            loads=loads_indiv,
        )

        msg = ("Set up the individual solar thermal system with gas tank "
               "backup test case.")
        log.info(msg)

        # community system
        self.sol_wh_com_new = System(
            sys_params=sol_the_sys_params,
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_com,
            backup_sizes=sol_the_inst_gas_bckp_sizes_com,
            weather=self.weather,
            loads=loads_com,
        )

        msg = ("Set up the community solar thermal system with tankless "
               "backup test case.")
        log.info(msg)

        self.sol_wh_com_retr = System(
            sys_params=sol_the_sys_params,
            backup_params=sol_the_gas_tank_bckp_params,
            sys_sizes=sol_the_sys_sizes_com,
            backup_sizes=sol_the_gas_tank_bckp_sizes_com,
            weather=self.weather,
            loads=loads_com,
        )

        msg = ("Set up the community solar thermal system with gas tank "
               "backup test case.")
        log.info(msg)

        # solar thermal validation

        # individual new with
        # tank and collector size from
        # https://secure.solar-rating.org/Certification/Ratings/#
        # RatingsReport.aspx?device=6926&units=METRICS
        # solar thermal retrofit solar fractions in the rating sheets are low
        # - for the same
        # size of collector and storage the new and retrofit should have the
        # same solar fraction
        # by definition https://en.wikipedia.org/wiki/Solar_savings_fraction

        # sizes based on several observed rated systems from OG-300
        # with an 80 gal tank and gas tankless backup
        sol_the_sys_sizes_val = pd.DataFrame(
            data=[
                [self.s["sol_col"], 3.9],
                [self.s["the_sto"],
                 UnitConv(80.0).m3_gal(unit_in="gal")],
                [self.s["sol_pump"], indiv_solar_pump_size],
                [self.s["piping"], 0.0],
            ],
            columns=[self.s["comp"], self.s["cap"]],
        )

        self.val_sol_wh = System(
            sys_params=sol_the_sys_params.loc[:last_row_for_indiv, :],
            backup_params=sol_the_inst_gas_bckp_params,
            sys_sizes=sol_the_sys_sizes_val,
            backup_sizes=sol_the_inst_gas_bckp_size,
            weather=self.weather,
            loads=loads_indiv_val,
        )

        msg = ("Set up the individual solar thermal system validation "
               "test case.")
        log.info(msg)

        self.conv_wh = System(
            sys_params=gas_tank_wh_params,
            sys_sizes=gas_tank_wh_size,
            weather=self.weather,
            loads=loads_indiv,
        )

        msg = "Set up the conventional tank wh test case."
        log.info(msg)

        # With baseline tank with a slightly higher RE and insulation set to
        # R12, as used for the solar tank
        gas_tank_wh_params_val = pd.DataFrame(
            data=[
                [self.s["gas_tank"], self.s["tank_re"], 0.82, "-"],
                [self.s["gas_tank"], self.s["ins_thi"], 0.04, "m"],
                [self.s["gas_tank"], self.s["spec_hea_con"], 0.085, "W/mK"],
                [self.s["gas_tank"], self.s["t_tap_set"], 322.04, "K"],
            ],
            columns=[
                self.s["comp"],
                self.s["param"],
                self.s["param_value"],
                self.s["param_unit"],
            ],
        )

        # with validation loads and validation parameters
        self.conv_wh_val = System(
            sys_params=gas_tank_wh_params_val,
            sys_sizes=gas_tank_wh_size,
            weather=self.weather,
            loads=loads_indiv_val,
        )

        msg = "Set up the conventional tank wh validation test case."
        log.info(msg)

        # project level component parameter
        # dataframe for solar electric system
        # with an inst. gas backup
        sol_el_tank_params = pd.DataFrame(
            data=[
                [self.s["hp"], self.s["c1_cop"], 1.229e00],
                [self.s["hp"], self.s["c2_cop"], 5.549e-02],
                [self.s["hp"], self.s["c3_cop"], 1.139e-04],
                [self.s["hp"], self.s["c4_cop"], -1.128e-02],
                [self.s["hp"], self.s["c5_cop"], -3.570e-06],
                [self.s["hp"], self.s["c6_cop"], -7.234e-04],
                [self.s["hp"], self.s["c1_heat_cap"], 7.055e-01],
                [self.s["hp"], self.s["c2_heat_cap"], 3.945e-02],
                [self.s["hp"], self.s["c3_heat_cap"], 1.433e-04],
                [self.s["hp"], self.s["c4_heat_cap"], 2.768e-03],
                [self.s["hp"], self.s["c5_heat_cap"], -1.069e-04],
                [self.s["hp"], self.s["c6_heat_cap"], -2.494e-04],
                [self.s["hp"], self.s["heat_cap_rated"], 2350.0],
                [self.s["hp"], self.s["cop_rated"], 2.43],
                [self.s["pv"], self.s["eta_pv"], 0.16],
                [self.s["pv"], self.s["f_act"], 1.0],
                [self.s["pv"], self.s["irrad_ref"], 1000.0],
                [self.s["inv"], self.s["eta_dc_ac"], 0.85],
                [self.s["the_sto"], self.s["f_upper_vol"], 0.5],
                [self.s["the_sto"], self.s["ins_thi"], 0.04],
                [self.s["the_sto"], self.s["spec_hea_con"], 0.04],
                [self.s["the_sto"], self.s["t_tap_set"], 322.04],
                [self.s["the_sto"], self.s["h_vs_r"], 6.0],
                [self.s["the_sto"], self.s["dt_appr"], 2.0],
                [self.s["the_sto"], self.s["t_max_tank"], 344.15],
                [self.s["piping"], self.s["pipe_spec_hea_con"], 0.0175],
                [self.s["piping"], self.s["pipe_ins_thick"], 0.008],
                [self.s["piping"], self.s["flow_factor"], 0.8],
                [self.s["piping"], self.s["dia_len_exp"], 0.43082708345352605],
                [
                    self.s["piping"],
                    self.s["dia_len_sca"],
                    0.007911283766743384,
                ],
                [
                    self.s["piping"],
                    self.s["discr_diam_m"],
                    "[0.0127, 0.01905, 0.0254, 0.03175, 0.0381,"
                    "0.0508, 0.0635, 0.0762, 0.1016]",
                ],
                [self.s["piping"], self.s["circ"], False],
                [self.s["piping"], self.s["long_br_len_fr"], 1.0],
            ],
            columns=[self.s["comp"], self.s["param"], self.s["param_value"]],
        )

        # test sizing
        sol_el_tank_sizes_indiv = pd.DataFrame(
            data=[
                [self.s["hp"], 2350.0],
                [self.s["pv"], 6.25],
                [self.s["the_sto"], UnitConv(80).m3_gal()],
                [self.s["dist_pump"], dist_pump_size],
                [self.s["piping"], pipe_m_per_hhld],
            ],
            columns=[self.s["comp"], self.s["cap"]],
        )

        # electric resistance backup parameters
        sol_el_tank_inst_gas_bckp_params = pd.DataFrame(
            data=[[self.s["el_res"], self.s["eta_el_res"], 1.0]],
            columns=[self.s["comp"], self.s["param"], self.s["param_value"]],
        )

        # electric resistance backup size
        sol_el_tank_inst_el_res_bckp_size = pd.DataFrame(
            data=[[1, self.s["el_res"], 6500.0]],
            columns=[self.c["id"], self.s["comp"], self.s["cap"]],
        )

        self.hp_wh = System(
            sys_params=sol_el_tank_params,
            backup_params=sol_el_tank_inst_gas_bckp_params,
            sys_sizes=sol_el_tank_sizes_indiv,
            backup_sizes=sol_el_tank_inst_el_res_bckp_size,
            weather=self.weather,
            loads=loads_indiv,
        )
Example #18
0
    def _make_example_loading_inputs(inputs,
                                     labels,
                                     random_state,
                                     occupancy=[4., 4., 4., 4.],
                                     at_home=['n', 'n', 'n', 'n']):
        """Creates example end-use load profile inputs using the example
        load database (sample of 128 households).

        Parameters:

            inputs: dict of dfs
                Inputs read from the input database

            labels: dist
                Consumer label map

            random_state: np.RandomState object

            occupancy: list
                List of household occupancies. Any occupancy
                above 6 is considered as an occupancy of 6
                for simplicity. If occupancies significantly larger
                than 6 are needed, please aggregate example loads
                in postprocessing

            at_home: list
                List of at home during the day info, 'y' or 'n'

        Returns:

            loading_input : df
                Contains household id, occupancy and load array input
                (see models.py for details)

            household_info: df
                Contains load id and maximum load in [gal] for
                sizing purposes.
        """
        # reformat the example end-use loads table
        loads_df = inputs[labels['exmp_loads']].copy()
        # drop hour column
        loads_df = loads_df.drop(labels['hour'], axis=1)
        # convert loads into gallons
        loads_df_m3 = loads_df.apply(
            lambda x: UnitConv(x).m3_gal(unit_in='gal'))
        single_row_loads = SourceAndSink._pack_timeseries(
            loads_df_m3, row_index=labels['load_m3'])
        example_cons = single_row_loads.transpose().reset_index()
        example_cons[labels['ld_id']] = inputs[
            labels['exmp_consload']].loc[:, labels['ld_id']]
        example_cons[labels['occ']] = inputs[
            labels['exmp_consload']].loc[:, labels['occ']]
        example_cons[labels['at_hm']] = inputs[
            labels['exmp_consload']].loc[:, labels['at_hm']]
        example_cons[labels['max_load']] = loads_df.max(axis=0).values

        example_cons = example_cons.drop(columns='index', axis=1)

        if len(occupancy) != len(at_home):
            mg = 'Occupancy and at home arrays should have the same length.'
            log.error(msg)
            raise Exception

        if (np.array(occupancy) > 6).any():
            occ_arr = np.array(occupancy)
            max_occ = occ_arr.max()
            occ_arr[occ_arr > 6] = 6.
            occupancy = list(occ_arr)
            msg = 'Any occupancy above 6 is considered as occupancy of 6.'\
            ' Maximum provided is {}. Consider aggregating loads if this is'\
            ' of concern.'
            log.warning(msg.format(max_occ))

        uniq_occupancy = set(occupancy)

        uniq_at_home = set(at_home)

        available_example_cons = example_cons.loc[
            (example_cons[labels['occ']].isin(uniq_occupancy))
            & (example_cons[labels['at_hm']].isin(uniq_at_home))]

        if available_example_cons.shape[0] == 0:
            msg = 'We don\'t have any households matching your inputs in'\
                  ' the database.'
            log.error(meg.format)
            raise Exception

        selected_load_ids = []

        for cons in range(len(occupancy)):

            occ = occupancy[cons]
            at_hm = at_home[cons]

            pick_from = available_example_cons.loc[
                (example_cons[labels['occ']].isin([occ]))
                & (example_cons[labels['at_hm']].isin([at_hm]))]

            load_id = random_state.choice(pick_from[labels['ld_id']].values,
                                          1)[0]

            while ((load_id in selected_load_ids)
                   and (len(selected_load_ids) < len(
                       pick_from[labels['ld_id']].unique()))):

                load_id = random_state.choice(
                    pick_from[labels['ld_id']].values, 1)[0]

            selected_load_ids.append(load_id)

            load_row = pick_from.loc[pick_from[labels['ld_id']] ==
                                     load_id, :].reset_index()

        # identify indexes drawn. Note that the Load ID is
        # the same value as the index

        indxs = available_example_cons.loc[available_example_cons[
            labels['ld_id']].isin(selected_load_ids)].index

        loading_input = available_example_cons.loc[indxs, :].reset_index()
        loading_input[labels['load_m3']] = \
            loading_input[labels['load_m3']].apply(lambda x: x.values)

        loading_input = loading_input.drop(columns='index', axis=1)

        # sort by drawn loads to have the the loading_inputs dataframe
        # rows be in order of the occupancy list. For example, first
        # number in the occupancy list corresponds the first row
        # in the loading_input dataframe.

        loading_input['Load ID'] = pd.CategoricalIndex(
            loading_input[labels['ld_id']],
            ordered=True,
            categories=selected_load_ids)

        loading_input = loading_input.sort_values(
            labels['ld_id']).reset_index()

        loading_input[labels['id']] = range(1, loading_input.shape[0] + 1)

        columns_expected_by_system_model = \
            [labels['id'], labels['occ'], labels['load_m3']]

        info_columns = [labels['id'], labels['ld_id'], labels['max_load']]

        household_info = loading_input.loc[:, info_columns]
        loading_input = loading_input.loc[:, columns_expected_by_system_model]

        return loading_input, household_info