diff --git a/MACOS_INSTALL.md b/MACOS_INSTALL.md new file mode 100644 index 00000000..3efade44 --- /dev/null +++ b/MACOS_INSTALL.md @@ -0,0 +1,103 @@ +# Prepare your environment + +Download the latest version of python and Microsoft c++ redistributable + +* [Microsoft C++ redistributable](https://www.microsoft.com/en-ca/download/details.aspx?id=48145) +* [Python environment](https://www.python.org/downloads/) + +# Get the code + +1. First thing you will need is an editor for your source code, that's a personal choice, but we would like to recommend PyCharm community edition, an excellent open-source python editor. [PyCharm Community edition](https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=windows&code=PCC) + +2. Run the installer, and follow the installation instructions for PyCharm, you may change a few options, but the default ones should be fine. + +3. Open PyCharm and click on **"Get from Version Control"**. + +![pycharm welcome screen](docs/img_windows_install/img_0.png) + +You can find it also at VCS -> Get from Version Control... + +![pycharm get from version control](docs/img_windows_install/img_6.png) + + +4. Select Git as the version control, and set the URL to [libs repository](https://rs-loy-gitlab.concordia.ca/Guille/libs.git) as shown in the picture. +At the website, copy the URL from Clone -> Clone with HTTPS. + +![pycharm get from version control screen](docs/img_windows_install/img_1.png) + + +You may need to install Git, by clicking at ***Download and install***. +If that message does not appear is because you have it already installed in your computer. + +5. Click Clone to download CERC libs source code. You will end with a project like this: + + +![pycharm project screen](docs/img_windows_install/img_2.png) + +6. To create your working branch you need rights to edit that project. Please, talk to Guillermo (guillermo.gutierrezmorote@concordia.ca) +or Koa (kekoa.wells@concordia.ca) to get those rights. Once you have them, right-click on the project folder (libs) -> Git -> Repository -> Branches: + +![create new branch 1](docs/img_windows_install/img_9.png) + +And then + New Branch: + +![create new branch 2](docs/img_windows_install/img_10.png) + +Give a name to your branch and open the tab Git at the down-left corner. Right-click on your branch and push. + +![push new branch 1](docs/img_windows_install/img_11.png) + +![push new branch 2](docs/img_windows_install/img_12.png) + +Check that your branch appears in the Remote list: + +![check all set](docs/img_windows_install/img_13.png) + +If your branch is there, you are done with this part. + +# Configure PyCharm + +We use two spaces as a tab instead of standard [pep8](https://www.python.org/dev/peps/pep-0008/) four spaces indentation. +This option can be configured in PyCharm at the settings screen, as shown in the picture. + +![pycharm configuration screen](docs/img_windows_install/img_5.png) + + +# Start your project + +1. At our Git (https://rs-loy-gitlab.concordia.ca/), click on New project: + +![git new project screen](docs/img_windows_install/img_14.png) + +The create a black project with the desired name (remember to follow our ![Coding Style](PYGUIDE.md)). +Be sure that Initialize repository with a README is selected, and ideally, that the Visibility Level is Public. + +![git give a name](docs/img_windows_install/img_15.png) + +And finally, clone it following the same steps as for ![libs](WINDOWS_INSTALL.md#get-the-code) (steps 3 to 5). + +2. Go to project settings and add the libs project to your own, by clicking on Add Content Root: + +![pycharm new project screen](docs/img_windows_install/img_4.png) + +![pycharm add libs](docs/img_windows_install/img_7.png) + +3. Add your first file to your project and click on install requirements to automatically download all the dependencies (in blue at top-right corner). + +![pycharm add dependencies](docs/img_windows_install/img_8.png) + +4. When all the dependencies are satisfied, you are all set to start importing your first city model. + +Add the following code to your main.py + +```python + +from imports.geometry_factory import GeometryFactory + +city = GeometryFactory('citygml', 'myfile.gml').city +``` + +5. Always remember to push your own project changes as the last thing you do before ending your working day! +First, commit your changes by clicking on the green check at the top-right corner of Pycharm. Add a comment that explains briefly your changes. +Then, pull by clicking on the blue arrow to be sure that there are no conflicts between your version (local) and the remote one (gitlab). +Once the conflicts are solved and the merge in local is done, push the changes by clicking on the green arrow. diff --git a/WINDOWS_INSTALL.md b/WINDOWS_INSTALL.md index 7e7ef3ac..3efade44 100644 --- a/WINDOWS_INSTALL.md +++ b/WINDOWS_INSTALL.md @@ -1,11 +1,11 @@ -# Prepare your environment. +# Prepare your environment Download the latest version of python and Microsoft c++ redistributable * [Microsoft C++ redistributable](https://www.microsoft.com/en-ca/download/details.aspx?id=48145) * [Python environment](https://www.python.org/downloads/) -# Get the code. +# Get the code 1. First thing you will need is an editor for your source code, that's a personal choice, but we would like to recommend PyCharm community edition, an excellent open-source python editor. [PyCharm Community edition](https://www.jetbrains.com/pycharm/download/download-thanks.html?platform=windows&code=PCC) @@ -34,7 +34,8 @@ If that message does not appear is because you have it already installed in your ![pycharm project screen](docs/img_windows_install/img_2.png) -6. Create your working branch by right clicking on the project folder (libs) -> Git -> Repository -> Branches: +6. To create your working branch you need rights to edit that project. Please, talk to Guillermo (guillermo.gutierrezmorote@concordia.ca) +or Koa (kekoa.wells@concordia.ca) to get those rights. Once you have them, right-click on the project folder (libs) -> Git -> Repository -> Branches: ![create new branch 1](docs/img_windows_install/img_9.png) @@ -42,7 +43,7 @@ And then + New Branch: ![create new branch 2](docs/img_windows_install/img_10.png) -Give a name to your branch and open the tab Git at the down-left corner. Right click on your branch and push. +Give a name to your branch and open the tab Git at the down-left corner. Right-click on your branch and push. ![push new branch 1](docs/img_windows_install/img_11.png) @@ -54,7 +55,7 @@ Check that your branch appears in the Remote list: If your branch is there, you are done with this part. -# Configure PyCharm. +# Configure PyCharm We use two spaces as a tab instead of standard [pep8](https://www.python.org/dev/peps/pep-0008/) four spaces indentation. This option can be configured in PyCharm at the settings screen, as shown in the picture. @@ -62,7 +63,7 @@ This option can be configured in PyCharm at the settings screen, as shown in the ![pycharm configuration screen](docs/img_windows_install/img_5.png) -# Start your project. +# Start your project 1. At our Git (https://rs-loy-gitlab.concordia.ca/), click on New project: diff --git a/city_model_structure/building_demand/thermal_zone.py b/city_model_structure/building_demand/thermal_zone.py index 8aa4fb97..ce66223d 100644 --- a/city_model_structure/building_demand/thermal_zone.py +++ b/city_model_structure/building_demand/thermal_zone.py @@ -25,10 +25,10 @@ class ThermalZone: """ ThermalZone class """ - def __init__(self, thermal_boundaries, parent_internal_zone, volume, floor_area): + def __init__(self, thermal_boundaries, parent_internal_zone, volume, footprint_area): self._id = None self._parent_internal_zone = parent_internal_zone - self._floor_area = floor_area + self._footprint_area = footprint_area self._thermal_boundaries = thermal_boundaries self._additional_thermal_bridge_u_value = None self._effective_thermal_capacity = None @@ -38,6 +38,7 @@ class ThermalZone: self._volume = volume self._ordinate_number = None self._view_factors_matrix = None + self._total_floor_area = None self._usage = None self._not_detailed_source_mean_annual_internal_gains = None @@ -61,12 +62,12 @@ class ThermalZone: return self._id @property - def floor_area(self) -> float: + def footprint_area(self) -> float: """ - Get thermal zone floor area in m2 + Get thermal zone footprint area in m2 :return: float """ - return self._floor_area + return self._footprint_area @property def thermal_boundaries(self) -> List[ThermalBoundary]: @@ -682,3 +683,19 @@ class ThermalZone: :param value: ThermalControl """ self._thermal_control = value + + @property + def total_floor_area(self): + """ + Get the total floor area of this thermal zone + :return: float + """ + return self._total_floor_area + + @total_floor_area.setter + def total_floor_area(self, value): + """ + Set the total floor area of this thermal zone + :param value: float + """ + self._total_floor_area = value diff --git a/exports/formats/energy_ade.py b/exports/formats/energy_ade.py index 315bc131..8e618f4f 100644 --- a/exports/formats/energy_ade.py +++ b/exports/formats/energy_ade.py @@ -278,7 +278,7 @@ class EnergyAde: 'energy:type': 'grossFloorArea', 'energy:value': { '@uom': 'm2', - '#text': f'{thermal_zone.floor_area}' + '#text': f'{thermal_zone.footprint_area}' } } }, diff --git a/exports/formats/idf.py b/exports/formats/idf.py index c51b648e..147d73e0 100644 --- a/exports/formats/idf.py +++ b/exports/formats/idf.py @@ -7,6 +7,7 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord Soroush Samareh Abolhassani soroush.samarehabolhassani@mail.concordia.ca """ +from pathlib import Path from geomeppy import IDF import helpers.constants as cte @@ -24,6 +25,7 @@ class Idf: _ROUGHNESS = 'MediumRough' _HOURLY_SCHEDULE = 'SCHEDULE:DAY:HOURLY' _COMPACT_SCHEDULE = 'SCHEDULE:COMPACT' + _FILE_SCHEDULE = 'SCHEDULE:FILE' _ZONE = 'ZONE' _LIGHTS = 'LIGHTS' _PEOPLE = 'PEOPLE' @@ -47,13 +49,38 @@ class Idf: # todo: make an enum for all the usage types cte.RESIDENTIAL: 'residential_building' } - idf_type_limits = { cte.ON_OFF: 'on/off', cte.FRACTION: 'Fraction', cte.ANY_NUMBER: 'Any Number', - 'continuous': 'Continuous', - 'discrete': 'Discrete' + cte.CONTINUOUS: 'Continuous', + cte.DISCRETE: 'Discrete' + } + idf_day_types = { + cte.MONDAY: 'Monday', + cte.TUESDAY: 'Tuesday', + cte.WEDNESDAY: 'Wednesday', + cte.THURSDAY: 'Thursday', + cte.FRIDAY: 'Friday', + cte.SATURDAY: 'Saturday', + cte.SUNDAY: 'Sunday', + cte.HOLIDAY: 'Holidays', + cte.WINTER_DESIGN_DAY: 'WinterDesignDay', + cte.SUMMER_DESIGN_DAY: 'SummerDesignDay' + } + idf_schedule_types = { + 'compact': 'Compact', + cte.DAY: 'Day', + cte.WEEK: 'Week', + cte.YEAR: 'Year', + 'file': 'File' + } + idf_schedule_data_type = { + 'compact': 'Compact', + 'hourly': 'Hourly', + 'daily': 'Daily', + 'interval': 'Interval', + 'list': 'List', } def __init__(self, city, output_path, idf_file_path, idd_file_path, epw_file_path, export_type="Surfaces"): @@ -121,69 +148,56 @@ class Idf: Visible_Absorptance=layer.material.visible_absorptance ) - def _add_daily_schedule(self, usage, schedule): - _schedule = self._idf.newidfobject(self._COMPACT_SCHEDULE, Name=f'{schedule.type} schedules {usage}') - _val = schedule.values - _schedule.Schedule_Type_Limits_Name = self.idf_type_limits[schedule.data_type.lower()] - _schedule.Field_1 = "Through: 12/31" - _schedule.Field_2 = "For: AllDays" - _schedule.Field_3 = "Until: 01:00" - _schedule.Field_4 = _val[0] - _schedule.Field_5 = "Until: 02:00" - _schedule.Field_6 = _val[1] - _schedule.Field_7 = "Until: 03:00" - _schedule.Field_8 = _val[2] - _schedule.Field_9 = "Until: 04:00" - _schedule.Field_10 = _val[3] - _schedule.Field_11 = "Until: 05:00" - _schedule.Field_12 = _val[4] - _schedule.Field_13 = "Until: 06:00" - _schedule.Field_14 = _val[5] - _schedule.Field_15 = "Until: 07:00" - _schedule.Field_16 = _val[6] - _schedule.Field_17 = "Until: 08:00" - _schedule.Field_18 = _val[7] - _schedule.Field_19 = "Until: 09:00" - _schedule.Field_20 = _val[8] - _schedule.Field_21 = "Until: 10:00" - _schedule.Field_22 = _val[9] - _schedule.Field_23 = "Until: 11:00" - _schedule.Field_24 = _val[10] - _schedule.Field_25 = "Until: 12:00" - _schedule.Field_26 = _val[11] - _schedule.Field_27 = "Until: 13:00" - _schedule.Field_28 = _val[12] - _schedule.Field_29 = "Until: 14:00" - _schedule.Field_30 = _val[13] - _schedule.Field_31 = "Until: 15:00" - _schedule.Field_32 = _val[14] - _schedule.Field_33 = "Until: 16:00" - _schedule.Field_34 = _val[15] - _schedule.Field_35 = "Until: 17:00" - _schedule.Field_36 = _val[16] - _schedule.Field_37 = "Until: 18:00" - _schedule.Field_38 = _val[17] - _schedule.Field_39 = "Until: 19:00" - _schedule.Field_40 = _val[18] - _schedule.Field_41 = "Until: 20:00" - _schedule.Field_42 = _val[19] - _schedule.Field_43 = "Until: 21:00" - _schedule.Field_44 = _val[20] - _schedule.Field_45 = "Until: 22:00" - _schedule.Field_46 = _val[21] - _schedule.Field_47 = "Until: 23:00" - _schedule.Field_48 = _val[22] - _schedule.Field_49 = "Until: 24:00" - _schedule.Field_50 = _val[23] + def _add_standard_compact_hourly_schedule(self, usage, schedules): + _kwargs = {'Name': f'{schedules[0].type} schedules {usage}', + 'Schedule_Type_Limits_Name': self.idf_type_limits[schedules[0].data_type], + 'Field_1': 'Through: 12/31'} + for j, schedule in enumerate(schedules): + _val = schedule.values + _new_field = '' + for day_type in schedule.day_types: + _new_field += f' {self.idf_day_types[day_type]}' + _kwargs[f'Field_{j * 25 + 2}'] = f'For:{_new_field}' + for i in range(0, len(_val)): + _kwargs[f'Field_{j * 25 + 3 + i}'] = f'Until: {i + 1:02d}:00,{_val[i]}' + self._idf.newidfobject(self._COMPACT_SCHEDULE, **_kwargs) + + def _add_non_hourly_schedule(self, usage, schedules): + raise NotImplementedError + + def _write_schedules_file(self, usage, schedule): + file_name = str((Path(self._output_path) / f'{schedule.type} schedules {usage}.dat').resolve()) + with open(file_name, 'w') as file: + for value in schedule.values: + file.write(f'{str(value)},\n') + return file_name + + def _add_file_schedule(self, usage, schedule, file_name): + print(file_name) + _schedule = self._idf.newidfobject(self._FILE_SCHEDULE, Name=f'{schedule.type} schedules {usage}') + _schedule.Schedule_Type_Limits_Name = self.idf_type_limits[schedule.data_type] + _schedule.File_Name = file_name + _schedule.Column_Number = 1 + _schedule.Rows_to_Skip_at_Top = 0 + _schedule.Number_of_Hours_of_Data = 8760 + _schedule.Column_Separator = 'Comma' + _schedule.Interpolate_to_Timestep = 'No' + _schedule.Minutes_per_Item = 60 + + def _add_schedules(self, usage, new_schedules, schedule_from_file=False): + if schedule_from_file: + new_schedule = new_schedules[0] + for schedule in self._idf.idfobjects[self._FILE_SCHEDULE]: + if schedule.Name == f'{new_schedule.type} schedules {usage}': + return + file_name = self._write_schedules_file(usage, new_schedule) + return self._add_file_schedule(usage, new_schedule, file_name) + else: + for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]: + if schedule.Name == f'{new_schedules[0].type} schedules {usage}': + return + return self._add_standard_compact_hourly_schedule(usage, new_schedules) - def _add_schedule(self, usage, new_schedule): - for schedule in self._idf.idfobjects[self._HOURLY_SCHEDULE]: - if schedule.Name == f'{new_schedule.type} schedules {usage}': - return - if new_schedule.time_range == "day": - return self._add_daily_schedule(usage, new_schedule) - return - def _add_construction(self, thermal_boundary): for construction in self._idf.idfobjects[self._CONSTRUCTION]: if construction.Name == thermal_boundary.construction_name: @@ -199,18 +213,18 @@ class Idf: self._add_material(layer) layers = thermal_boundary.layers # The constructions should have at least one layer - _kwargs = {"Name": thermal_boundary.construction_name, "Outside_Layer": layers[0].material.name} + _kwargs = {'Name': thermal_boundary.construction_name, 'Outside_Layer': layers[0].material.name} for i in range(1, len(layers) - 1): _kwargs[f'Layer_{i + 1}'] = layers[1].material.name self._idf.newidfobject(self._CONSTRUCTION, **_kwargs) - def _add_zone(self, usage_zone, thermal_zone_volume): + def _add_zone(self, thermal_zone): for zone in self._idf.idfobjects['ZONE']: - if zone.Name == usage_zone.id: + if zone.Name == thermal_zone.id: return # todo: what do we need to define a zone in energy plus? - self._idf.newidfobject(self._ZONE, Name=usage_zone.id, Volume=thermal_zone_volume * usage_zone.percentage) - self._add_heating_system(usage_zone) + self._idf.newidfobject(self._ZONE, Name=thermal_zone.id, Volume=thermal_zone.volume) + self._add_heating_system(thermal_zone) def _add_thermostat(self, usage_zone): thermostat_name = f'Thermostat {usage_zone.usage}' @@ -222,32 +236,32 @@ class Idf: Constant_Heating_Setpoint=usage_zone.thermal_control.mean_heating_set_point, Constant_Cooling_Setpoint=usage_zone.thermal_control.mean_cooling_set_point) - def _add_heating_system(self, usage_zone): + def _add_heating_system(self, thermal_zone): for air_system in self._idf.idfobjects[self._IDEAL_LOAD_AIR_SYSTEM]: - if air_system.Zone_Name == usage_zone.id: + if air_system.Zone_Name == thermal_zone.id: return - thermostat = self._add_thermostat(usage_zone) + thermostat = self._add_thermostat(thermal_zone) self._idf.newidfobject(self._IDEAL_LOAD_AIR_SYSTEM, - Zone_Name=usage_zone.id, - System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {usage_zone.usage}', - Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {usage_zone.usage}', - Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {usage_zone.usage}', + Zone_Name=thermal_zone.id, + System_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', + Heating_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', + Cooling_Availability_Schedule_Name=f'HVAC AVAIL SCHEDULES {thermal_zone.usage}', Template_Thermostat_Name=thermostat.Name) - def _add_occupancy(self, usage_zone, area): - number_of_people = area * usage_zone.occupancy.occupancy_density - fraction_radiant = usage_zone.occupancy.sensible_radiative_internal_gain / \ - (usage_zone.occupancy.sensible_radiative_internal_gain + - usage_zone.occupancy.sensible_convective_internal_gain + - usage_zone.occupancy.latent_internal_gain) + def _add_occupancy(self, thermal_zone): + number_of_people = thermal_zone.occupancy.occupancy_density * thermal_zone.total_floor_area + fraction_radiant = thermal_zone.occupancy.sensible_radiative_internal_gain / \ + (thermal_zone.occupancy.sensible_radiative_internal_gain + + thermal_zone.occupancy.sensible_convective_internal_gain + + thermal_zone.occupancy.latent_internal_gain) self._idf.newidfobject(self._PEOPLE, - Name=f'{usage_zone.id}_occupancy', - Zone_or_ZoneList_Name=usage_zone.id, - Number_of_People_Schedule_Name=f'Occupancy schedules {usage_zone.usage}', + Name=f'{thermal_zone.id}_occupancy', + Zone_or_ZoneList_Name=thermal_zone.id, + Number_of_People_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}', Number_of_People_Calculation_Method="People", Number_of_People=number_of_people, Fraction_Radiant=fraction_radiant, - Activity_Level_Schedule_Name=f'Occupancy schedules {usage_zone.usage}' + Activity_Level_Schedule_Name=f'Occupancy schedules {thermal_zone.usage}' ) def _add_equipment(self, usage_zone): @@ -286,29 +300,17 @@ class Idf: for thermal_zone in internal_zone.thermal_zones: for thermal_boundary in thermal_zone.thermal_boundaries: self._add_construction(thermal_boundary) - for usage_zone in thermal_zone.usage_zones: - usage = usage_zone.usage - usage_zone_area = thermal_zone.floor_area * usage_zone.percentage + usage = thermal_zone.usage + # todo: infiltration can be written with two values (system on and system off) in E+? Or just as schedule? + # self._add_schedule(usage, "Infiltration") + self._add_schedules(usage, thermal_zone.lighting.schedules) + self._add_schedules(usage, thermal_zone.occupancy.occupancy_schedules, schedule_from_file=True) + self._add_schedules(usage, thermal_zone.thermal_control.hvac_availability_schedules) - # todo: infiltration can be written with two values (system on and system off) in E+? Or just as schedule? - # self._add_schedule(usage, "Infiltration") - for schedule in usage_zone.lighting.schedules: - for day_type in schedule.day_types: - if day_type == cte.MONDAY: - self._add_schedule(usage, schedule) - for schedule in usage_zone.occupancy.occupancy_schedules: - for day_type in schedule.day_types: - if day_type == cte.MONDAY: - self._add_schedule(usage, usage_zone.occupancy.occupancy_schedules) - for schedule in usage_zone.thermal_control.hvac_availability_schedules: - for day_type in schedule.day_types: - if day_type == cte.MONDAY: - self._add_schedule(usage, schedule) - - self._add_zone(usage_zone, thermal_zone.volume) - self._add_heating_system(usage_zone) + self._add_zone(thermal_zone) + self._add_heating_system(thermal_zone) # self._add_infiltration(usage_zone) - self._add_occupancy(usage_zone, usage_zone_area) + self._add_occupancy(thermal_zone) if self._export_type == "Surfaces": self._add_surfaces(building) @@ -359,10 +361,9 @@ class Idf: for thermal_zone in internal_zone.thermal_zones: for boundary in thermal_zone.thermal_boundaries: idf_surface_type = self.idf_surfaces[boundary.parent_surface.type] - for usage_zone in thermal_zone.usage_zones: - surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}', - Surface_Type=idf_surface_type, Zone_Name=usage_zone.id, - Construction_Name=boundary.construction_name) - coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, - self._city.lower_corner) - surface.setcoords(coordinates) + surface = self._idf.newidfobject(self._SURFACE, Name=f'{boundary.parent_surface.name}', + Surface_Type=idf_surface_type, Zone_Name=thermal_zone.id, + Construction_Name=boundary.construction_name) + coordinates = self._matrix_to_list(boundary.parent_surface.solid_polygon.coordinates, + self._city.lower_corner) + surface.setcoords(coordinates) diff --git a/helpers/constants.py b/helpers/constants.py index 9707ffd9..4976edd1 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -45,6 +45,9 @@ ON_OFF = 'on_off' TEMPERATURE = 'temperature' HUMIDITY = 'humidity' CONTROL_TYPE = 'control_type' +CONTINUOUS = 'continuous' +DISCRETE = 'discrete' +CONSTANT = 'constant' # surface types WALL = 'Wall' diff --git a/imports/construction/nrel_physics_interface.py b/imports/construction/nrel_physics_interface.py index f5133568..354b3176 100644 --- a/imports/construction/nrel_physics_interface.py +++ b/imports/construction/nrel_physics_interface.py @@ -57,10 +57,11 @@ class NrelPhysicsInterface: thermal_zone.view_factors_matrix = view_factors_matrix @staticmethod - def _create_storeys(building, archetype): + def _create_storeys(building, archetype, divide_in_storeys): building.average_storey_height = archetype.average_storey_height building.storeys_above_ground = 1 - thermal_zones = StoreysGeneration(building, building.internal_zones[0]).thermal_zones + thermal_zones = StoreysGeneration(building, building.internal_zones[0], + divide_in_storeys=divide_in_storeys).thermal_zones building.internal_zones[0].thermal_zones = thermal_zones def enrich_buildings(self): diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index 415ae740..124837ba 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -18,9 +18,10 @@ class UsPhysicsParameters(NrelPhysicsInterface): """ UsPhysicsParameters class """ - def __init__(self, city, base_path): + def __init__(self, city, base_path, divide_in_storeys=False): self._city = city self._path = base_path + self._divide_in_storeys = divide_in_storeys self._climate_zone = ConstructionHelper.city_to_nrel_climate_zone(city.name) super().__init__() @@ -41,10 +42,22 @@ class UsPhysicsParameters(NrelPhysicsInterface): # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned if len(building.internal_zones) == 1: if building.internal_zones[0].thermal_zones is None: - self._create_storeys(building, archetype) + self._create_storeys(building, archetype, self._divide_in_storeys) + if self._divide_in_storeys: + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.total_floor_area = thermal_zone.footprint_area + else: + number_of_storeys = int(float(building.eave_height) / float(building.average_storey_height)) + thermal_zone = building.internal_zones[0].thermal_zones[0] + thermal_zone.total_floor_area = thermal_zone.footprint_area * number_of_storeys + else: + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + thermal_zone.total_floor_area = thermal_zone.footprint_area - self._assign_values(building.internal_zones, archetype) for internal_zone in building.internal_zones: + self._assign_values(internal_zone.thermal_zones, archetype) for thermal_zone in internal_zone.thermal_zones: self._calculate_view_factors(thermal_zone) @@ -54,7 +67,7 @@ class UsPhysicsParameters(NrelPhysicsInterface): construction_period_limits = building_archetype.construction_period.split(' - ') if construction_period_limits[1] == 'PRESENT': construction_period_limits[1] = 3000 - if int(construction_period_limits[0]) <= year_of_construction < int(construction_period_limits[1]): + if int(construction_period_limits[0]) <= int(year_of_construction) < int(construction_period_limits[1]): if (str(function) == str(building_archetype.function)) and \ (climate_zone == str(building_archetype.climate_zone)): return building_archetype @@ -68,52 +81,50 @@ class UsPhysicsParameters(NrelPhysicsInterface): return construction_archetype return None - def _assign_values(self, internal_zones, archetype): - for internal_zone in internal_zones: - for thermal_zone in internal_zone.thermal_zones: - thermal_zone.additional_thermal_bridge_u_value = archetype.extra_loses_due_to_thermal_bridges - thermal_zone.effective_thermal_capacity = archetype.thermal_capacity - thermal_zone.indirectly_heated_area_ratio = archetype.indirect_heated_ratio - thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_for_ventilation_system_on - thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_for_ventilation_system_off - for thermal_boundary in thermal_zone.thermal_boundaries: - construction_archetype = self._search_construction_in_archetype(archetype, thermal_boundary.type) - print('wa', construction_archetype.window) - thermal_boundary.construction_name = construction_archetype.name - try: - thermal_boundary.window_ratio = construction_archetype.window_ratio - except ValueError: - # This is the normal operation way when the windows are defined in the geometry - continue - thermal_boundary.layers = [] - for layer_archetype in construction_archetype.layers: - layer = Layer() - layer.thickness = layer_archetype.thickness - material = Material() - archetype_material = layer_archetype.material - material.name = layer_archetype.name - material.no_mass = archetype_material.no_mass - if archetype_material.no_mass: - material.thermal_resistance = archetype_material.thermal_resistance - else: - material.density = archetype_material.density - material.conductivity = archetype_material.conductivity - material.specific_heat = archetype_material.specific_heat - material.solar_absorptance = archetype_material.solar_absorptance - material.thermal_absorptance = archetype_material.thermal_absorptance - material.visible_absorptance = archetype_material.visible_absorptance - layer.material = material - thermal_boundary.layers.append(layer) - # The agreement is that the layers are defined from outside to inside - external_layer = construction_archetype.layers[0] - thermal_boundary.outside_solar_absorptance = external_layer.material.solar_absorptance - thermal_boundary.outside_thermal_absorptance = external_layer.material.thermal_absorptance - thermal_boundary.outside_visible_absorptance = external_layer.material.visible_absorptance + def _assign_values(self, thermal_zones, archetype): + for thermal_zone in thermal_zones: + thermal_zone.additional_thermal_bridge_u_value = archetype.extra_loses_due_to_thermal_bridges + thermal_zone.effective_thermal_capacity = archetype.thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirect_heated_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_for_ventilation_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_for_ventilation_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_archetype = self._search_construction_in_archetype(archetype, thermal_boundary.type) + thermal_boundary.construction_name = construction_archetype.name + try: + thermal_boundary.window_ratio = construction_archetype.window_ratio + except ValueError: + # This is the normal operation way when the windows are defined in the geometry + continue + thermal_boundary.layers = [] + for layer_archetype in construction_archetype.layers: + layer = Layer() + layer.thickness = layer_archetype.thickness + material = Material() + archetype_material = layer_archetype.material + material.name = layer_archetype.name + material.no_mass = archetype_material.no_mass + if archetype_material.no_mass: + material.thermal_resistance = archetype_material.thermal_resistance + else: + material.density = archetype_material.density + material.conductivity = archetype_material.conductivity + material.specific_heat = archetype_material.specific_heat + material.solar_absorptance = archetype_material.solar_absorptance + material.thermal_absorptance = archetype_material.thermal_absorptance + material.visible_absorptance = archetype_material.visible_absorptance + layer.material = material + thermal_boundary.layers.append(layer) + # The agreement is that the layers are defined from outside to inside + external_layer = construction_archetype.layers[0] + thermal_boundary.outside_solar_absorptance = external_layer.material.solar_absorptance + thermal_boundary.outside_thermal_absorptance = external_layer.material.thermal_absorptance + thermal_boundary.outside_visible_absorptance = external_layer.material.visible_absorptance - for thermal_opening in thermal_boundary.thermal_openings: - if construction_archetype.window is not None: - window_archetype = construction_archetype.window - thermal_opening.construction_name = window_archetype.name - thermal_opening.frame_ratio = window_archetype.frame_ratio - thermal_opening.g_value = window_archetype.g_value - thermal_opening.overall_u_value = window_archetype.overall_u_value + for thermal_opening in thermal_boundary.thermal_openings: + if construction_archetype.window is not None: + window_archetype = construction_archetype.window + thermal_opening.construction_name = window_archetype.name + thermal_opening.frame_ratio = window_archetype.frame_ratio + thermal_opening.g_value = window_archetype.g_value + thermal_opening.overall_u_value = window_archetype.overall_u_value diff --git a/imports/customized_imports/helpers/sanam_customized_usage_helper.py b/imports/customized_imports/helpers/sanam_customized_usage_helper.py deleted file mode 100644 index a2de6577..00000000 --- a/imports/customized_imports/helpers/sanam_customized_usage_helper.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Sanam's customized importer Usage helper -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -import sys -import helpers.constants as cte - - -class SanamCustomizedUsageHelper: - """ - SanamCustomizedUsage class - """ - usage_to_customized = { - cte.RESIDENTIAL: 'residential', - cte.INDUSTRY: 'manufacturing', - cte.OFFICE_AND_ADMINISTRATION: 'office', - cte.HOTEL: 'hotel', - cte.HEALTH_CARE: 'health', - cte.RETAIL: 'retail', - cte.HALL: 'assembly', - cte.RESTAURANT: 'restaurant', - cte.EDUCATION: 'school' - } - customized_default_value = 'residential' - - @staticmethod - def customized_from_usage(usage): - """ - Get customized usage from the given internal usage key - :param usage: str - :return: str - """ - try: - return SanamCustomizedUsageHelper.usage_to_customized[usage] - except KeyError: - sys.stderr.write('Error: keyword not found. Returned default HfT usage "residential"\n') - return SanamCustomizedUsageHelper.customized_default_value diff --git a/imports/customized_imports/sanam_customized_usage_parameters.py b/imports/customized_imports/sanam_customized_usage_parameters.py deleted file mode 100644 index ffe401b6..00000000 --- a/imports/customized_imports/sanam_customized_usage_parameters.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -SanamCustomizedUsageParameters add two parameters to usage properties from ASHRAE -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" - -import sys -import xmltodict -import helpers.constants as cte -from imports.usage.helpers.usage_helper import UsageHelper -from city_model_structure.building_demand.occupancy import Occupancy -from city_model_structure.building_demand.usage_zone import UsageZone -from imports.geometry.helpers.geometry_helper import GeometryHelper - - -class SanamCustomizedUsageParameters: - """ - SanamCustomizedUsageParameters class - """ - def __init__(self, city, base_path): - file = 'ashrae_archetypes.xml' - path = str(base_path / file) - self._city = city - self._usage_archetypes = [] - with open(path) as xml: - self._archetypes = xmltodict.parse(xml.read(), force_list=('zoneUsageVariant', 'zoneUsageType')) - - def enrich_buildings(self): - """ - Returns the city with the usage parameters assigned to the buildings - :return: - """ - city = self._city - for building in city.buildings: - libs_usage = GeometryHelper().libs_usage_from_libs_function(building.function) - comnet_usage = UsageHelper().comnet_from_libs_usage(libs_usage) - archetype = self._search_archetype(comnet_usage) - if archetype is None: - sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' - f' {building.function}, that assigns building usage as ' - f'{libs_usage}\n') - return - - for internal_zone in building.internal_zones: - if internal_zone.area is None: - raise Exception('Internal zone area not defined, ACH cannot be calculated') - if internal_zone.volume is None: - raise Exception('Internal zone volume not defined, ACH cannot be calculated') - if internal_zone.area <= 0: - raise Exception('Internal zone area is zero, ACH cannot be calculated') - if internal_zone.volume <= 0: - raise Exception('Internal zone volume is zero, ACH cannot be calculated') - volume_per_area = internal_zone.volume / internal_zone.area - - usage_zone = UsageZone() - usage_zone.usage = libs_usage - self._assign_values(usage_zone, archetype, volume_per_area) - - def _search_archetype(self, libs_usage): - comnet_usage = UsageHelper.comnet_from_libs_usage(libs_usage) - for building_archetype in self._archetypes['buildingUsageLibrary']['zoneUsageType']: - if building_archetype['id'] == comnet_usage: - usage_archetype = self._parse_usage_type(self._archetypes) - return usage_archetype - return None - - @staticmethod - def _assign_values(usage_zone, archetype, volume_per_area): - usage_zone.usage = archetype.usage - if archetype.occupancy.occupancy_density is not None: - if usage_zone.occupancy is None: - _occupancy = Occupancy() - usage_zone.occupancy = _occupancy - usage_zone.occupancy.occupancy_density = archetype.occupancy.occupancy_density - archetype_mechanical_air_change = float(archetype.mechanical_air_change) \ - * float(archetype.occupancy.occupancy_density) \ - * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET ** 3 / volume_per_area - usage_zone.mechanical_air_change = archetype_mechanical_air_change - - @staticmethod - def _parse_usage_type(data): - usage_zone_archetype = UsageZone() - usage_zone_archetype.usage = data['id'] - usage_zone_archetype.mechanical_air_change = data['endUses']['ventilation']['minimumVentilationRate'][ - '#text'] - if 'occupancy' in data: - _occupancy = Occupancy() - _occupancy.occupancy_density = data['occupancy']['occupancyDensity']['#text'] - usage_zone_archetype.occupancy = _occupancy - return usage_zone_archetype diff --git a/imports/customized_imports_factory.py b/imports/customized_imports_factory.py deleted file mode 100644 index 24b55ec3..00000000 --- a/imports/customized_imports_factory.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -CustomizedImportsFactory is used to import any information using user customized formats -This factory can only be called after calling the construction factory. -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from pathlib import Path - - -class CustomizedImportsFactory: - """ - CustomizedImportsFactory class - """ - def __init__(self, importer_class, city, base_path=None): - if base_path is None: - base_path = Path(Path(__file__).parent.parent / 'data/customized_imports') - self._importer_class = importer_class - self._city = city - self._base_path = base_path - - def enrich(self): - """ - Returns the class that will enrich the city given - :return: Class - """ - importer = self._importer_class(self._city, self._base_path) - return importer.enrich_buildings() diff --git a/imports/usage/comnet_usage_parameters.py b/imports/usage/comnet_usage_parameters.py index e2663479..7dc9c4e9 100644 --- a/imports/usage/comnet_usage_parameters.py +++ b/imports/usage/comnet_usage_parameters.py @@ -128,7 +128,7 @@ class ComnetUsageParameters: elif schedule_day == day_types['saturday']: _schedule.day_types = [cte.SATURDAY] else: - _schedule.day_types = [cte.SUNDAY] + _schedule.day_types = [cte.SUNDAY, cte.HOLIDAY] _schedule.type = name _schedule.data_type = SchedulesHelper.data_type_from_comnet(data_type) if _schedule.data_type == cte.TEMPERATURE: diff --git a/unittests/test_construction_factory.py b/unittests/test_construction_factory.py index 55213ea8..1b9b533e 100644 --- a/unittests/test_construction_factory.py +++ b/unittests/test_construction_factory.py @@ -73,7 +73,7 @@ class TestConstructionFactory(TestCase): def _check_thermal_zones(self, internal_zone): for thermal_zone in internal_zone.thermal_zones: self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none') - self.assertIsNotNone(thermal_zone.floor_area, 'thermal_zone floor area is none') + self.assertIsNotNone(thermal_zone.footprint_area, 'thermal_zone floor area is none') self.assertTrue(len(thermal_zone.thermal_boundaries) > 0, 'thermal_zone thermal_boundaries not defined') self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, 'additional_thermal_bridge_u_value is none') self.assertIsNotNone(thermal_zone.effective_thermal_capacity, 'thermal_zone effective_thermal_capacity is none') diff --git a/unittests/test_customized_imports_factory.py b/unittests/test_customized_imports_factory.py deleted file mode 100644 index 127666a3..00000000 --- a/unittests/test_customized_imports_factory.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -TestCustomizedImportsFactory tests and validates the factory to import customized data -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Concordia CERC group -Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from pathlib import Path -from unittest import TestCase - -import helpers.constants as cte -from imports.geometry_factory import GeometryFactory -from imports.usage_factory import UsageFactory -from imports.customized_imports_factory import CustomizedImportsFactory -from imports.customized_imports.sanam_customized_usage_parameters import SanamCustomizedUsageParameters as scp - - -class TestCustomizedImportsFactory(TestCase): - """ - TestCustomizedImportsFactory TestCase - """ - def setUp(self) -> None: - """ - Configure test environment - :return: - """ - self._example_path = (Path(__file__).parent / 'tests_data').resolve() - - def _get_citygml(self, file): - file_path = (self._example_path / file).resolve() - _city = GeometryFactory('citygml', file_path).city - self.assertIsNotNone(_city, 'city is none') - UsageFactory('hft', _city).enrich() - - return _city - - def test_city_with_customized_data(self): - """ - Enrich the city with the usage information and verify it - :return: None - """ - - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - - CustomizedImportsFactory(scp, city).enrich() - for building in city.buildings: - self.assertIsNot(len(building.internal_zones), 0, 'no building internal_zones defined') - for internal_zone in building.internal_zones: - for usage_zone in internal_zone.usage_zones: - if usage_zone.usage != cte.RESIDENTIAL: - self.assertIsNotNone(usage_zone.mechanical_air_change, 'mechanical air change rate is none') - self.assertIsNotNone(usage_zone.occupancy.occupancy_density, 'occupancy density us none')