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)
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 )
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 )
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 )
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 )
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)
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, )
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)
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)
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)
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)
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, )
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, )
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
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)
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
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, )
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