diff --git a/bpeng/simulation/base.py b/bpeng/simulation/base.py index eeea9435f669bc0fb63c568882cc2d087d9fd7e1..206c190c5db87c0ca831617d24cc4f9e3e4bdba7 100644 --- a/bpeng/simulation/base.py +++ b/bpeng/simulation/base.py @@ -11,7 +11,7 @@ from enum import Enum import numpy as np import pandas as pd -import shapely +from shapely.geometry import Polygon from oplus import IDF from .building_hvac_oplus import (add_designspec_airdist, add_sch_compact, add_sch_constant, add_sch_typelimits, @@ -131,7 +131,7 @@ class BaseSim: Column name in building_shapefile DataFrame corresponding to building construction year """ - # Check if important columns exist in building shapefile + # Assert if columns taht will be used exist in building shapefile assert buildingid_colname.value in building_shapefile, "Missing column {} in building shapefile"\ .format(buildingid_colname) assert cnstrct_yr_colname.value in building_shapefile, "Missing column {} in building shapefile"\ @@ -142,17 +142,24 @@ class BaseSim: .format(build_height_colname) assert num_floors_colname.value in building_shapefile, "Missing column {} in building shapefile"\ .format(num_floors_colname) - - # Convert columns to float and Check data type of geometry + # Convert columns to float col_float = [buildingid_colname.value, cnstrct_yr_colname.value, build_height_colname.value, num_floors_colname.value] building_shapefile[col_float] = building_shapefile[col_float].astype(float) + # Assert data type of geometry geom_type1 = building_shapefile[geometry_colname.value].apply(type).value_counts().index[0] - assert geom_type1 == shapely.geometry.polygon.Polygon, 'Geometry in building shapefile is not ' \ - 'shapely.geometry.polygon.Polygon type.' + assert geom_type1 == Polygon, 'Geometry in building shapefile is not ' \ + 'shapely.geometry.Polygon type.' + # Assert if height inputs is more than 5 ft + assert (building_shapefile[build_height_colname.value] > 5).all(), "One or more building height is zero" + # Assert building with area under 50 sqm + long_m, lat_m = coord_to_meters() + areas = building_shapefile[geometry_colname.value].apply(lambda x: x.area * long_m * lat_m) + assert (areas > 50).all(), "One or more building has area below 50 sqm" # Perform same checks on shading building shapefile if building_shading_shapefile is not None: + # Check if important columns exist in shading building shapefile assert buildingid_colname.value in building_shading_shapefile, "Missing column {} in building shapefile"\ .format(buildingid_colname) assert cnstrct_yr_colname.value in building_shading_shapefile, "Missing column {} in building shapefile"\ @@ -163,13 +170,23 @@ class BaseSim: .format(build_height_colname) assert num_floors_colname.value in building_shading_shapefile, "Missing column {} in building shapefile"\ .format(num_floors_colname) + # Convert columns to float building_shading_shapefile[col_float] = building_shading_shapefile[col_float].astype(float) - # NOTE: Function fix_dup_id automatically solves duplicate BIN from NYC building footprints shapefile + # Function fix_dup_id automatically solves duplicate BIN from NYC building footprints shapefile building_shading_shapefile = fix_dup_id(building_shading_shapefile, buildid_colname=buildingid_colname.value) + # Check data type of geometry geom_type2 = building_shading_shapefile[geometry_colname.value].apply(type).value_counts().index[0] - assert geom_type2 == shapely.geometry.polygon.Polygon, 'Geometry in shading building shapefile is not ' \ - 'shapely.geometry.polygon.Polygon type.' + assert geom_type2 == Polygon, 'Geometry in shading building shapefile is not ' \ + 'shapely.geometry.Polygon type.' + # Remove shading building with height below 5 ft + building_shading_shapefile = building_shading_shapefile[building_shading_shapefile[build_height_colname + .value] > 5].copy() + # Remove shading building with area under 50 sqm + areas = building_shading_shapefile[geometry_colname.value].apply(lambda x: x.area * long_m * lat_m) + area_too_small_index = areas.index[areas < 50] + if area_too_small_index.tolist(): + building_shading_shapefile = building_shading_shapefile.drop(area_too_small_index) # Load starting idf file dir_path = os.path.dirname(os.path.abspath(__file__)) diff --git a/bpeng/simulation/extract_output.py b/bpeng/simulation/extract_output.py index 7ee0ac0d1e427588f25b5c14b709185ab3dccb25..118a8ab268a4a59f3e246ec179909d8b245a0c71 100644 --- a/bpeng/simulation/extract_output.py +++ b/bpeng/simulation/extract_output.py @@ -6,12 +6,12 @@ import pandas as pd # pylint: disable=too-many-locals,invalid-name # Conversion factors -J_to_kWh = 1/3600*1/1000 -therms_to_kWh = 29.3001 -J_to_therms = J_to_kWh/therms_to_kWh -J_to_mmBTU = J_to_therms*0.1 -W_to_MBH = 0.003412141633128 -CMS_to_CFM = 2118.87997276 +J_TO_KWH = 1/3600*1/1000 +THERMS_TO_KWH = 29.3001 +J_TO_THERMS = J_TO_KWH/THERMS_TO_KWH +J_TO_MMBTU = J_TO_THERMS*0.1 +W_TO_MBH = 0.003412141633128 +CMS_TO_CFM = 2118.87997276 def epxml_zcls_to_df(xml_file_path, bin_num, output_details='TotalOnly'): @@ -67,12 +67,12 @@ def epxml_zcls_to_df(xml_file_path, bin_num, output_details='TotalOnly'): tot_cool_name = '{} Total Cooling'.format(str(bin_num)) perc_cool_name = '{} Perc Cooling'.format(str(bin_num)) - df_out.loc[tot_heat_name] = df_out.loc[df_out['Load Type'] == 'Heating', cols].sum()*W_to_MBH + df_out.loc[tot_heat_name] = df_out.loc[df_out['Load Type'] == 'Heating', cols].sum()*W_TO_MBH df_out.loc[perc_heat_name] = df_out.loc[tot_heat_name, cols]/df_out.loc[tot_heat_name, cols].sum() df_out.loc[[tot_heat_name, perc_heat_name], 'Load Type'] = 'Heating' df_out.loc[[tot_heat_name, perc_heat_name], 'units'] = ['MBH', '%'] - df_out.loc[tot_cool_name] = df_out.loc[df_out['Load Type'] == 'Cooling', cols].sum()*W_to_MBH + df_out.loc[tot_cool_name] = df_out.loc[df_out['Load Type'] == 'Cooling', cols].sum()*W_TO_MBH df_out.loc[perc_cool_name] = df_out.loc[tot_cool_name, cols]/df_out.loc[tot_cool_name, cols].sum() df_out.loc[[tot_cool_name, perc_cool_name], 'Load Type'] = 'Cooling' df_out.loc[[tot_cool_name, perc_cool_name], 'units'] = ['MBH', '%'] @@ -117,13 +117,13 @@ def heat_cool_dist_from_eso(eso_file_path, bin_num, time_step="Hourly"): # Select heating energy and save in 2 dataframe: hourly and monthly col_heat = list(map(lambda col: col.find("Heating Energy") != -1, df_eso.columns)) - out_heat_energy_hourly = df_eso[df_eso.columns[col_heat]].sum(axis=1) * J_to_mmBTU * 1000 - out_heat_energy_monthly = df_eso[df_eso.columns[col_heat]].sum(axis=1).sum(level="month", axis=0) * J_to_mmBTU + out_heat_energy_hourly = df_eso[df_eso.columns[col_heat]].sum(axis=1) * J_TO_MMBTU * 1000 + out_heat_energy_monthly = df_eso[df_eso.columns[col_heat]].sum(axis=1).sum(level="month", axis=0) * J_TO_MMBTU # Select cooling energy and save in 2 dataframe: hourly and monthly col_cool = list(map(lambda col: col.find("Cooling Energy") != -1, df_eso.columns)) - out_cool_energy_hourly = df_eso[df_eso.columns[col_cool]].sum(axis=1) * J_to_mmBTU * 1000 - out_cool_energy_monthly = df_eso[df_eso.columns[col_cool]].sum(axis=1).sum(level="month", axis=0) * J_to_mmBTU + out_cool_energy_hourly = df_eso[df_eso.columns[col_cool]].sum(axis=1) * J_TO_MMBTU * 1000 + out_cool_energy_monthly = df_eso[df_eso.columns[col_cool]].sum(axis=1).sum(level="month", axis=0) * J_TO_MMBTU if (time_step == "Hourly") or (time_step is None): df_out = pd.concat([out_heat_energy_hourly, out_cool_energy_hourly], axis=1) @@ -172,8 +172,8 @@ def hvac_sizing_from_eio(eio_file_path, bin_num, output_type='TotalSizing'): zone_sizing["Calc Des Load {W}"] = zone_sizing["Calc Des Load {W}"].astype(float) zone_sizing["User Des Load {W}"] = zone_sizing["User Des Load {W}"].astype(float) - zone_sizing["Calc Des Load {MBH}"] = zone_sizing["Calc Des Load {W}"]*W_to_MBH - zone_sizing["User Des Load {MBH}"] = zone_sizing["User Des Load {W}"]*W_to_MBH + zone_sizing["Calc Des Load {MBH}"] = zone_sizing["Calc Des Load {W}"]*W_TO_MBH + zone_sizing["User Des Load {MBH}"] = zone_sizing["User Des Load {W}"]*W_TO_MBH if output_type == 'TotalSizing': total_heat_load = zone_sizing['Calc Des Load {MBH}'][zone_sizing['Load Type'] == 'Heating'].sum() diff --git a/bpeng/simulation/geometry.py b/bpeng/simulation/geometry.py index 24102de099dff43e7272eae105ecd42f2a419962..f2c765908632723654043eb3aa5e3835ee0491fa 100644 --- a/bpeng/simulation/geometry.py +++ b/bpeng/simulation/geometry.py @@ -10,7 +10,7 @@ import math import numpy as np import pandas as pd -import shapely +from shapely.geometry import Point, Polygon from geopy.distance import vincenty @@ -275,7 +275,7 @@ def string_to_pointlist(point_string): """ if point_string is not None: point_string = point_string.split(' ') - return shapely.geometry.Point(float(point_string[0]), float(point_string[1])) + return Point(float(point_string[0]), float(point_string[1])) def string_to_polygon(polygon_string): @@ -289,9 +289,9 @@ def string_to_polygon(polygon_string): shapely.Polygon: shapely polygon geometry """ - polygon_string = polygon_string[polygon_string.find("-"):polygon_string.find(")")].split(',') + polygon_string = polygon_string[polygon_string.find("-"):polygon_string.find(")")].split(', ') tup = [] for i in polygon_string: tup.append(string_to_pointlist(i)) - poly = shapely.geometry.Polygon([[p.x, p.y] for p in tup]) + poly = Polygon([[p.x, p.y] for p in tup]) return poly diff --git a/tests/simulation/test_midrise_idf.py b/tests/simulation/test_midrise_idf.py new file mode 100644 index 0000000000000000000000000000000000000000..414af6d90f2f4349d9fe5b3dc430ac7befd80546 --- /dev/null +++ b/tests/simulation/test_midrise_idf.py @@ -0,0 +1,33 @@ +import pandas as pd + +from tests.simulation.test_shapefiles import TestShpMidRise +from bpeng.simulation import MidRiseApart, geometry + + +class TestMidRiseApart: + midrise_idf = None + test_shp = pd.DataFrame(TestShpMidRise.VALUES1.value, columns=TestShpMidRise.COLNAMES.value) + test_shading_shp1 = pd.DataFrame(TestShpMidRise.VALUES2.value, columns=TestShpMidRise.COLNAMES.value) + test_shading_shp2 = pd.DataFrame(TestShpMidRise.VALUES3.value, columns=TestShpMidRise.COLNAMES.value) + test_shading_shp3 = pd.DataFrame(TestShpMidRise.VALUES4.value, columns=TestShpMidRise.COLNAMES.value) + test_shading_shp = pd.concat([test_shading_shp1, test_shading_shp2, test_shading_shp3], ignore_index=True) + test_shp.geometry = test_shp.geometry.apply(geometry.string_to_polygon) + test_shading_shp.geometry = test_shading_shp.geometry.apply(geometry.string_to_polygon) + + def setup_class(self): + self.midrise_idf = MidRiseApart(building_shapefile=self.test_shp, + building_shading_shapefile=self.test_shading_shp, + building_name='test_building') + + def test_init(self): + assert self.midrise_idf.build_type == 'MidRiseApart' + assert self.midrise_idf.building_name == 'test_building' + assert self.midrise_idf.building_id == [1051959.0] + + def test_idf_creation(self): + self.midrise_idf.idf_creation(add_shading_analysis=True, window_to_wall_ratio=0.16, + infiltration_value='Automatic', add_people=True, people_value='Automatic', + add_equipment=True, equipment_value='Automatic', add_light=True, + light_value='Automatic', solar_dist_method="FullExterior", output_table=False) + bsd = self.midrise_idf.idf("BuildingSurface:Detailed").filter('name', 'Wall 1051959.0 floor1 1').one + assert bsd['Construction Name']['Name'] == 'Steel Frame Res Ext Wall_stucco_pre1980' diff --git a/tests/simulation/test_shapefiles.py b/tests/simulation/test_shapefiles.py new file mode 100644 index 0000000000000000000000000000000000000000..d5a28fc599dc63b886ddbb00e6222166e9484b07 --- /dev/null +++ b/tests/simulation/test_shapefiles.py @@ -0,0 +1,43 @@ +from enum import Enum + + +class TestShpMidRise(Enum): + COLNAMES = ['bbl', + 'bin', + 'cnstrct_yr', + 'geometry', + 'groundelev', + 'heightroof', + 'num_floors', + 'shape_area', + 'shape_len'] + + # building geometry 1 + GEOMETRY1 = 'POLYGON ((-73.94641126946065 40.79089312357564, -73.94652458117535 40.79073642966144, ' \ + '-73.9466023711673 40.79076892427322, -73.94650177155994 40.79090804098926, ' \ + '-73.94647668522865 40.790897561923, -73.94646397442928 40.79091513919471, ' \ + '-73.94641126946065 40.79089312357564))' + + VALUES1 = [[1016320026, 1051959.0, 1910.0, GEOMETRY1, 10.0, 15.0364937268, 4.0, 1543.26117443, 179.44512646599998]] + + # shading building geometry 2 + GEOMETRY2 = 'POLYGON ((-73.94546926801122 40.79076781832001, -73.9455561859173 40.79064762546366, ' \ + '-73.9456969441075 40.79070642342177, -73.94587247261181 40.79077974460716, ' \ + '-73.94578555510427 40.79089993769955, -73.94546926801122 40.79076781832001))' + + VALUES2 = [[1016320037, 1051968.0, 1958.0, GEOMETRY2, 10.0, 18.47014921, 1.0, 4993.71774248, 299.811633249]] + + # shading building geometry 3 (with duplicate bin) + GEOMETRY3 = 'POLYGON ((-73.94645658458069 40.79132475276936, -73.9464908028793 40.79127743384843, ' \ + '-73.94655181519008 40.79119306274159, -73.94657866764616 40.79120427921903, ' \ + '-73.94659165741159 40.79120970445152, -73.94658277311777 40.79122198946571, ' \ + '-73.94649642685717 40.79134139451187, -73.94645658458069 40.79132475276936))' + + VALUES3 = [[1016320150, 1000000.0, 1900.0, GEOMETRY3, 11.0, 42.96, 3.0, 689.202639689, 134.673569074]] + + # shading building geometry 4 (with duplicate bin) + GEOMETRY4 = 'POLYGON ((-73.94564604994339 40.79052335826294, -73.94569347039969 40.79045778364416, ' \ + '-73.94592137466176 40.79055298367584, -73.94587395554622 40.7906185592885, ' \ + '-73.94564604994339 40.79052335826294))' + + VALUES4 = [[1016320034, 1000000.0, 1910.0, GEOMETRY4, 10.0, 46.6717958, 4.0, 1963.1642707, 198.546474432]]