From 9b3241e1d768552eab9d4c9850ddba229b466d7e Mon Sep 17 00:00:00 2001 From: guille Date: Tue, 17 May 2022 22:22:40 -0400 Subject: [PATCH] partial parser for data sources --- catalog_factories/usage/comnet_catalog.py | 292 +++++++++++++++++++++- catalog_factories/usage/usage_helper.py | 126 ++++++++++ unittests/test_usage_catalog.py | 15 ++ 3 files changed, 432 insertions(+), 1 deletion(-) create mode 100644 catalog_factories/usage/usage_helper.py create mode 100644 unittests/test_usage_catalog.py diff --git a/catalog_factories/usage/comnet_catalog.py b/catalog_factories/usage/comnet_catalog.py index 92447200..5fafaa5c 100644 --- a/catalog_factories/usage/comnet_catalog.py +++ b/catalog_factories/usage/comnet_catalog.py @@ -4,14 +4,304 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2022 Concordia CERC group Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ +from typing import Dict +import numpy +import pandas as pd from catalog_factories.catalog import Catalog from catalog_factories.data_models.usages.content import Content +from catalog_factories.data_models.usages.lighting import Lighting +from catalog_factories.usage.usage_helper import UsageHelper +from helpers.configuration_helper import ConfigurationHelper as ch +import helpers.constants as cte class ComnetCatalog(Catalog): def __init__(self, path): - pass + self._comnet_archetypes_path = str(path / 'comnet_archetypes.xlsx') + self._comnet_schedules_path = str(path / 'comnet_schedules_archetypes.xlsx') + self._archetypes = self._read_archetype_file() + self._schedules = self._read_schedules_file() + self._schedules = pd.ExcelFile(self._comnet_schedules_path) + + print(self._archetypes, self._schedules) + + def _read_schedules_file(self) -> Dict: + dictionary = {} + comnet_usages = UsageHelper().comnet_schedules_key_to_comnet_schedules + comnet_days = UsageHelper().comnet_days + for usage_name in comnet_usages: + _extracted_data = pd.read_excel(self._comnet_schedules_path, sheet_name=comnet_usages[usage_name], + skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA") + _schedules = {} + for row in range(0, 39, 3): + _schedule_values = {} + schedule_name = _extracted_data.loc[row:row, 'Description'].item() + for day in comnet_days: + start = row + end = row+1 + if day == cte.SATURDAY: + start = start+1 + end = end+1 + elif day == cte.SUNDAY or day == cte.HOLIDAY: + start = start + 2 + end = end + 2 + _schedule_values[day] = _extracted_data.iloc[start:end, 3:26].to_numpy().tolist()[0] + _schedules[schedule_name] = _schedule_values + dictionary[usage_name] = _schedules + print(dictionary) + return dictionary + + def _read_archetype_file(self) -> Dict: + """ + reads xlsx files containing usage information into a dictionary + :return : Dict + """ + number_usage_types = 33 + xl_file = pd.ExcelFile(self._comnet_archetypes_path) + file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", skiprows=[0, 1, 2], + nrows=number_usage_types, usecols="A:AB") + + lighting_data = {} + plug_loads_data = {} + occupancy_data = {} + ventilation_rate = {} + water_heating = {} + process_data = {} + schedules_key = {} + + for j in range(0, number_usage_types): + usage_parameters = file_data.iloc[j] + usage_type = usage_parameters[0] + lighting_data[usage_type] = usage_parameters[1:6].values.tolist() + plug_loads_data[usage_type] = usage_parameters[8:13].values.tolist() + occupancy_data[usage_type] = usage_parameters[17:20].values.tolist() + ventilation_rate[usage_type] = usage_parameters[20:21].values.tolist() + water_heating[usage_type] = usage_parameters[23:24].values.tolist() + process_data[usage_type] = usage_parameters[24:26].values.tolist() + schedules_key[usage_type] = usage_parameters[27:28].values.tolist() + + return {'lighting': lighting_data, + 'plug loads': plug_loads_data, + 'occupancy': occupancy_data, + 'ventilation rate': ventilation_rate, + 'water heating': water_heating, + 'process': process_data, + 'schedules_key': schedules_key} + + @staticmethod + def _calculate_reduced_values_from_extended_library(usage_zone, archetype): + number_of_days_per_type = {'WD': 251, 'Sat': 52, 'Sun': 62} + total = 0 + for schedule in archetype.thermal_control.hvac_availability_schedules: + if schedule.day_types[0] == cte.SATURDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sat'] + elif schedule.day_types[0] == cte.SUNDAY: + for value in schedule.values: + total += value * number_of_days_per_type['Sun'] + else: + for value in schedule.values: + total += value * number_of_days_per_type['WD'] + + usage_zone.hours_day = total / 365 + usage_zone.days_year = 365 + + + + + + + @staticmethod + def _parse_usage_type(comnet_usage, data, schedules_data): + _usage_zone = UsageZone() + + # lighting + + latent_fraction = ch().comnet_lighting_latent + convective_fraction = ch().comnet_lighting_convective + radiative_fraction = ch().comnet_lighting_radiant + density = data['lighting'][comnet_usage][4] + _lighting = Lighting(density, convective_fraction, radiative_fraction, latent_fraction, schedules) + # plug loads + _appliances = None + if data['plug loads'][comnet_usage][0] != 'n.a.': + _appliances = Appliances() + _appliances.latent_fraction = ch().comnet_plugs_latent + _appliances.convective_fraction = ch().comnet_plugs_convective + _appliances.radiative_fraction = ch().comnet_plugs_radiant + _appliances.density = data['plug loads'][comnet_usage][0] + + # occupancy + _occupancy = Occupancy() + value = data['occupancy'][comnet_usage][0] + if value != 0: + _occupancy.occupancy_density = value + else: + _occupancy.occupancy_density = 0 + _occupancy.sensible_convective_internal_gain = data['occupancy'][comnet_usage][1] \ + * ch().comnet_occupancy_sensible_convective + _occupancy.sensible_radiative_internal_gain = data['occupancy'][comnet_usage][1] \ + * ch().comnet_occupancy_sensible_radiant + _occupancy.latent_internal_gain = data['occupancy'][comnet_usage][2] + + _usage_zone.mechanical_air_change = data['ventilation rate'][comnet_usage][0] + + schedules_usage = UsageHelper.schedules_key(data['schedules_key'][comnet_usage][0]) + + _extracted_data = pd.read_excel(schedules_data, sheet_name=schedules_usage, + skiprows=[0, 1, 2, 3], nrows=39, usecols="A:AA") + schedules = [] + number_of_schedule_types = 13 + schedules_per_schedule_type = 3 + day_types = dict({'week_day': 0, 'saturday': 1, 'sunday': 2}) + for schedule_types in range(0, number_of_schedule_types): + name = '' + data_type = '' + for schedule_day in range(0, schedules_per_schedule_type): + _schedule = Schedule() + _schedule.time_step = cte.HOUR + _schedule.time_range = cte.DAY + row_cells = _extracted_data.iloc[schedules_per_schedule_type * schedule_types + schedule_day] + if schedule_day == day_types['week_day']: + name = row_cells[0] + data_type = row_cells[1] + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + elif schedule_day == day_types['saturday']: + _schedule.day_types = [cte.SATURDAY] + else: + _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: + values = [] + for cell in row_cells[schedules_per_schedule_type:].to_numpy(): + values.append((float(cell) - 32.) * 5 / 9) + _schedule.values = values + else: + _schedule.values = row_cells[schedules_per_schedule_type:].to_numpy() + schedules.append(_schedule) + + schedules_types = dict({'Occupancy': 0, 'Lights': 3, 'Receptacle': 6, 'Infiltration': 9, 'HVAC Avail': 12, + 'ClgSetPt': 15, 'HtgSetPt': 18}) + + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Occupancy'] + pointer]) + _occupancy.occupancy_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Lights'] + pointer]) + _lighting.schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['Receptacle'] + pointer]) + _appliances.schedules = _schedules + + _usage_zone.occupancy = _occupancy + _usage_zone.lighting = _lighting + _usage_zone.appliances = _appliances + + _control = ThermalControl() + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['HtgSetPt'] + pointer]) + _control.heating_set_point_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['ClgSetPt'] + pointer]) + _control.cooling_set_point_schedules = _schedules + _schedules = [] + for pointer in range(0, 3): + _schedules.append(schedules[schedules_types['HVAC Avail'] + pointer]) + _control.hvac_availability_schedules = _schedules + _usage_zone.thermal_control = _control + + return _usage_zone + + def _search_archetypes(self, libs_usage): + for item in self._data['lighting']: + comnet_usage = UsageHelper.comnet_from_libs_usage(libs_usage) + if comnet_usage == item: + usage_archetype = self._parse_usage_type(comnet_usage, self._data, self._xls) + return usage_archetype + return None + + def enrich_buildings(self): + """ + Returns the city with the usage parameters assigned to the buildings + :return: + """ + city = self._city + for building in city.buildings: + usage = GeometryHelper.libs_usage_from_libs_function(building.function) + try: + archetype_usage = self._search_archetypes(usage) + except KeyError: + sys.stderr.write(f'Building {building.name} has unknown archetype for building function:' + f' {building.function}, that assigns building usage as ' + f'{GeometryHelper.libs_usage_from_libs_function(building.function)}\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 = usage + self._assign_values_usage_zone(usage_zone, archetype_usage, volume_per_area) + usage_zone.percentage = 1 + self._calculate_reduced_values_from_extended_library(usage_zone, archetype_usage) + + internal_zone.usage_zones = [usage_zone] + + @staticmethod + def _assign_values_usage_zone(usage_zone, archetype, volume_per_area): + # Due to the fact that python is not a strong typed language, the wrong object type is assigned to + # usage_zone.occupancy when writing usage_zone.occupancy = archetype.occupancy. + # Same happens for lighting and appliances. Therefore, this walk around has been done. + usage_zone.mechanical_air_change = archetype.mechanical_air_change * cte.METERS_TO_FEET ** 2 \ + * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET ** 3 / volume_per_area + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density / cte.METERS_TO_FEET ** 2 + _occupancy.sensible_radiative_internal_gain = archetype.occupancy.sensible_radiative_internal_gain \ + * archetype.occupancy.occupancy_density / cte.METERS_TO_FEET ** 2 \ + * cte.BTU_H_TO_WATTS + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain \ + * archetype.occupancy.occupancy_density / cte.METERS_TO_FEET ** 2 \ + * cte.BTU_H_TO_WATTS + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain \ + * archetype.occupancy.occupancy_density / cte.METERS_TO_FEET ** 2 \ + * cte.BTU_H_TO_WATTS + _occupancy.occupancy_schedules = archetype.occupancy.occupancy_schedules + usage_zone.occupancy = _occupancy + _lighting = Lighting() + _lighting.density = archetype.lighting.density / cte.METERS_TO_FEET ** 2 + _lighting.convective_fraction = archetype.lighting.convective_fraction + _lighting.radiative_fraction = archetype.lighting.radiative_fraction + _lighting.latent_fraction = archetype.lighting.latent_fraction + _lighting.schedules = archetype.lighting.schedules + usage_zone.lighting = _lighting + _appliances = Appliances() + _appliances.density = archetype.appliances.density / cte.METERS_TO_FEET ** 2 + _appliances.convective_fraction = archetype.appliances.convective_fraction + _appliances.radiative_fraction = archetype.appliances.radiative_fraction + _appliances.latent_fraction = archetype.appliances.latent_fraction + _appliances.schedules = archetype.appliances.schedules + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules + _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules + _control.hvac_availability_schedules = archetype.thermal_control.hvac_availability_schedules + usage_zone.thermal_control = _control + + def names(self, category=None): pass diff --git a/catalog_factories/usage/usage_helper.py b/catalog_factories/usage/usage_helper.py new file mode 100644 index 00000000..6dfa624e --- /dev/null +++ b/catalog_factories/usage/usage_helper.py @@ -0,0 +1,126 @@ +""" +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 +from typing import Dict + + +class UsageHelper: + """ + Usage helper class + """ + _usage_to_hft = { + cte.RESIDENTIAL: 'residential', + cte.SINGLE_FAMILY_HOUSE: 'Single family house', + cte.MULTI_FAMILY_HOUSE: 'Multi-family house', + cte.EDUCATION: 'education', + cte.SCHOOL_WITHOUT_SHOWER: 'school without shower', + cte.SCHOOL_WITH_SHOWER: 'school with shower', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'retail shop / refrigerated food', + cte.HOTEL: 'hotel', + cte.HOTEL_MEDIUM_CLASS: 'hotel (Medium-class)', + cte.DORMITORY: 'dormitory', + cte.INDUSTRY: 'industry', + cte.RESTAURANT: 'restaurant', + cte.HEALTH_CARE: 'health care', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'Home for the aged or orphanage', + cte.OFFICE_AND_ADMINISTRATION: 'office and administration', + cte.EVENT_LOCATION: 'event location', + cte.HALL: 'hall', + cte.SPORTS_LOCATION: 'sport location', + cte.LABOR: 'Labor', + cte.GREEN_HOUSE: 'green house', + cte.NON_HEATED: 'non-heated'} + + @staticmethod + def hft_from_hub_usage(usage): + """ + Get HfT usage from the given internal usage key + :param usage: str + :return: str + """ + try: + return UsageHelper._usage_to_hft[usage] + except KeyError: + sys.stderr.write('Error: keyword not found to translate from hub_usage to hft usage.\n') + + _comnet_days = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY, cte.SATURDAY, cte.SUNDAY, cte.HOLIDAY] + + _usage_to_comnet = { + cte.RESIDENTIAL: 'BA Multifamily', + cte.SINGLE_FAMILY_HOUSE: 'BA Multifamily', + cte.MULTI_FAMILY_HOUSE: 'BA Multifamily', + cte.EDUCATION: 'BA School/University', + cte.SCHOOL_WITHOUT_SHOWER: 'BA School/University', + cte.SCHOOL_WITH_SHOWER: 'BA School/University', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'BA Retail', + cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD: 'BA Retail', + cte.HOTEL: 'BA Hotel', + cte.HOTEL_MEDIUM_CLASS: 'BA Hotel', + cte.DORMITORY: 'BA Dormitory', + cte.INDUSTRY: 'BA Manufacturing Facility', + cte.RESTAURANT: 'BA Dining: Family', + cte.HEALTH_CARE: 'BA Hospital', + cte.RETIREMENT_HOME_OR_ORPHANAGE: 'BA Multifamily', + cte.OFFICE_AND_ADMINISTRATION: 'BA Office', + cte.EVENT_LOCATION: 'BA Convention Center', + cte.HALL: 'BA Convention Center', + cte.SPORTS_LOCATION: 'BA Sports Arena', + cte.LABOR: 'BA Gymnasium', + cte.GREEN_HOUSE: cte.GREEN_HOUSE, + cte.NON_HEATED: cte.NON_HEATED + } + + _comnet_schedules_key_to_comnet_schedules = { + 'C-1 Assembly': 'C-1 Assembly', + 'C-2 Public': 'C-2 Health', + 'C-3 Hotel Motel': 'C-3 Hotel', + 'C-4 Manufacturing': 'C-4 Manufacturing', + 'C-5 Office': 'C-5 Office', + 'C-6 Parking Garage': 'C-6 Parking', + 'C-7 Restaurant': 'C-7 Restaurant', + 'C-8 Retail': 'C-8 Retail', + 'C-9 Schools': 'C-9 School', + 'C-10 Warehouse': 'C-10 Warehouse', + 'C-11 Laboratory': 'C-11 Lab', + 'C-12 Residential': 'C-12 Residential', + 'C-13 Data Center': 'C-13 Data', + 'C-14 Gymnasium': 'C-14 Gymnasium'} + + @property + def comnet_schedules_key_to_comnet_schedules(self) -> Dict: + return self._comnet_schedules_key_to_comnet_schedules + + @property + def comnet_days(self): + return self._comnet_days + + @staticmethod + def comnet_from_hub_usage(usage): + """ + Get Comnet usage from the given internal usage key + :param usage: str + :return: str + """ + try: + return UsageHelper._usage_to_comnet[usage] + except KeyError: + sys.stderr.write('Error: keyword not found to translate from hub_usage to comnet usage.\n') + + @staticmethod + def schedules_key(usage): + """ + Get Comnet schedules key from the list found in the Comnet usage file + :param usage: str + :return: str + """ + try: + return UsageHelper._comnet_schedules_key_to_comnet_schedules[usage] + except KeyError: + sys.stderr.write('Error: Comnet keyword not found. An update of the Comnet files might have been ' + 'done changing the keywords.\n') diff --git a/unittests/test_usage_catalog.py b/unittests/test_usage_catalog.py new file mode 100644 index 00000000..e886ecca --- /dev/null +++ b/unittests/test_usage_catalog.py @@ -0,0 +1,15 @@ +""" +TestUsageCatalog +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Concordia CERC group +Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +from unittest import TestCase +from catalog_factories.usage_catalog_factory import UsageCatalogFactory + + +class TestConstructionCatalog(TestCase): + def test_comnet_catalog(self): + catalog = UsageCatalogFactory('comnet').catalog + self.assertIsNotNone(catalog, 'catalog is none')