diff --git a/city_model_structure/building_demand/internal_gains.py b/city_model_structure/building_demand/internal_gains.py index d2342a98..75e60cb7 100644 --- a/city_model_structure/building_demand/internal_gains.py +++ b/city_model_structure/building_demand/internal_gains.py @@ -13,11 +13,29 @@ class InternalGains: """ def __init__(self): + self._type = None self._average_internal_gain = None self._convective_fraction = None self._radiative_fraction = None self._latent_fraction = None + @property + def type(self) -> Union[None, str]: + """ + Get internal gains type + :return: None or string + """ + return self._type + + @type.setter + def type(self, value): + """ + Set internal gains type + :param value: string + """ + if value is not None: + self._type = str(value) + @property def average_internal_gain(self) -> Union[None, float]: """ @@ -30,7 +48,7 @@ class InternalGains: def average_internal_gain(self, value): """ Set internal gains average internal gain in W/m2 - :param value:float + :param value: float """ if value is not None: self._average_internal_gain = float(value) diff --git a/data/usage/comnet_archetypes.xlsx b/data/usage/comnet_archetypes.xlsx new file mode 100644 index 00000000..8e04fc17 Binary files /dev/null and b/data/usage/comnet_archetypes.xlsx differ diff --git a/helpers/constants.py b/helpers/constants.py index 8445fdfb..00416177 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -10,6 +10,7 @@ KELVIN = 273.15 # converters HOUR_TO_MINUTES = 60 METERS_TO_FEET = 3.28084 +BTU_H_TO_WATTS = 0.29307107 # time SECOND = 'second' @@ -40,7 +41,6 @@ TEMPERATURE = 'temperature' HUMIDITY = 'humidity' CONTROL_TYPE = 'control_type' -# todo: modify code to use global constants instead of hard-coded values # surface types WALL = 'Wall' GROUND_WALL = 'Ground wall' @@ -79,3 +79,11 @@ RETAIL = 'retail' HALL = 'hall' RESTAURANT = 'restaurant' EDUCATION = 'education' + +LIGHTING = 'Lights' +OCCUPANCY = 'Occupancy' +RECEPTACLE = 'Receptacle' +HVAC_AVAILABILITY = 'HVAC Avail' +INFILTRATION = 'Infiltration' +COOLING_SET_POINT = 'ClgSetPt' +HEATING_SET_POINT = 'HtgSetPt' diff --git a/helpers/yearly_from_daily_schedules.py b/helpers/yearly_from_daily_schedules.py new file mode 100644 index 00000000..c7c3bac5 --- /dev/null +++ b/helpers/yearly_from_daily_schedules.py @@ -0,0 +1,44 @@ +""" +Yearly from daily schedules module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +import calendar as cal +import helpers.constants as cte +from city_model_structure.attributes.schedule import Schedule + + +class YearlyFromDailySchedules: + """ + YearlyFromDailySchedules class + """ + def __init__(self, daily_schedules, year): + self._daily_schedules = daily_schedules + self._year = year + + @property + def yearly_schedule(self) -> Schedule: + """ + Creates a yearly schedule out of a set of daily schedules + :return: Schedule + """ + yearly_schedule = Schedule() + weekly_schedules = [0, 0, 0, 0, 0, 0, 0] + day_types = dict({cte.MONDAY: 0, cte.TUESDAY: 1, cte.WEDNESDAY: 2, cte.THURSDAY: 3, + cte.FRIDAY: 4, cte.SATURDAY: 5, cte.SUNDAY: 6}) + for daily_schedule in self._daily_schedules: + for day_type in daily_schedule.day_types: + weekly_schedules[day_types[day_type]] = daily_schedule.values + + values = [] + for month in range(1, 13): + for day in range(1, cal.monthlen(self._year, month)+1): + week_day = cal.weekday(self._year, month, day) + values.extend(weekly_schedules[week_day]) + yearly_schedule.type = self._daily_schedules[0].type + yearly_schedule.data_type = self._daily_schedules[0].data_type + yearly_schedule.time_range = cte.YEAR + yearly_schedule.time_step = cte.HOUR + yearly_schedule.values = values + + return yearly_schedule diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index f45e0f29..50b0b8ca 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -31,7 +31,6 @@ class UsPhysicsParameters(NrelPhysicsInterface): # it is assumed that all buildings have the same archetypes' keys for building in city.buildings: building_type = ConstructionHelper.nrel_from_function(building.function) - print(building_type) if building_type is None: return archetype = self._search_archetype(building_type, diff --git a/imports/usage/comnet_usage_parameters.py b/imports/usage/comnet_usage_parameters.py new file mode 100644 index 00000000..5d10312c --- /dev/null +++ b/imports/usage/comnet_usage_parameters.py @@ -0,0 +1,162 @@ +""" +ComnetUsageParameters model the usage properties +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +import sys +from typing import Dict +import pandas as pd + +import helpers.constants as cte +from helpers.configuration_helper import ConfigurationHelper as ch +from imports.geometry.helpers.geometry_helper import GeometryHelper +from imports.usage.helpers.usage_helper import UsageHelper +from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.internal_gains import InternalGains +from imports.usage.data_classes.hft_usage_zone_archetype import HftUsageZoneArchetype as huza +from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype as higa + + +class ComnetUsageParameters: + """ + ComnetUsageParameters class + """ + def __init__(self, city, base_path): + self._city = city + self._base_path = str(base_path / 'comnet_archetypes.xlsx') + self._usage_archetypes = [] + data = self._read_file() + for item in data['lighting']: + for usage in UsageHelper.usage_to_comnet: + comnet_usage = UsageHelper.usage_to_comnet[usage] + if comnet_usage == item: + usage_archetype = self._parse_zone_usage_type(comnet_usage, data) + self._usage_archetypes.append(usage_archetype) + + def _read_file(self) -> Dict: + """ + reads xlsx file containing usage information into a dictionary + :return : Dict + """ + number_usage_types = 33 + xl_file = pd.ExcelFile(self._base_path) + file_data = pd.read_excel(xl_file, sheet_name="Modeling Data", skiprows=[0, 1, 2], + nrows=number_usage_types, usecols="A:Z") + + lighting_data = {} + plug_loads_data = {} + occupancy_data = {} + ventilation_rate = {} + water_heating = {} + process_data = {} + + 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() + + return {'lighting': lighting_data, + 'plug loads': plug_loads_data, + 'occupancy': occupancy_data, + 'ventilation rate': ventilation_rate, + 'water heating': water_heating, + 'process': process_data} + + @staticmethod + def _parse_zone_usage_type(usage, data): + if data['occupancy'][usage][0] <= 0: + occupancy_density = 0 + else: + occupancy_density = 1 / data['occupancy'][usage][0] + mechanical_air_change = data['ventilation rate'][usage][0] + internal_gains = [] + # lighting + latent_fraction = ch().comnet_lighting_latent + convective_fraction = ch().comnet_lighting_convective + radiative_fraction = ch().comnet_lighting_radiant + average_internal_gain = data['lighting'][usage][4] + internal_gains.append(higa(internal_gains_type=cte.LIGHTING, average_internal_gain=average_internal_gain, + convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, + latent_fraction=latent_fraction)) + # occupancy + latent_fraction = data['occupancy'][usage][2] / (data['occupancy'][usage][1] + data['occupancy'][usage][2]) + sensible_fraction = float(1 - latent_fraction) + convective_fraction = sensible_fraction * ch().comnet_occupancy_sensible_convective + radiative_fraction = sensible_fraction * ch().comnet_occupancy_sensible_radiant + average_internal_gain = (data['occupancy'][usage][1] + data['occupancy'][usage][2]) \ + * occupancy_density * cte.BTU_H_TO_WATTS + internal_gains.append(higa(internal_gains_type=cte.OCCUPANCY, average_internal_gain=average_internal_gain, + convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, + latent_fraction=latent_fraction)) + # plug loads + if data['plug loads'][usage][0] != 'n.a.': + latent_fraction = ch().comnet_plugs_latent + convective_fraction = ch().comnet_plugs_convective + radiative_fraction = ch().comnet_plugs_radiant + average_internal_gain = data['plug loads'][usage][0] + internal_gains.append(higa(internal_gains_type=cte.RECEPTACLE, average_internal_gain=average_internal_gain, + convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, + latent_fraction=latent_fraction)) + + usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, + occupancy_density=occupancy_density, + mechanical_air_change=mechanical_air_change) + return usage_zone_archetype + + 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.usage_from_function(building.function) + height = building.average_storey_height + if height is None: + raise Exception('Average storey height not defined, ACH cannot be calculated') + if height <= 0: + raise Exception('Average storey height is zero, ACH cannot be calculated') + archetype = self._search_archetype(UsageHelper.comnet_from_usage(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'{GeometryHelper.usage_from_function(building.function)}\n') + continue + + # just one usage_zone + for thermal_zone in building.thermal_zones: + usage_zone = UsageZone() + usage_zone.usage = usage + self._assign_values(usage_zone, archetype, height) + usage_zone.volume = thermal_zone.volume + thermal_zone.usage_zones = [usage_zone] + + def _search_archetype(self, building_usage): + for building_archetype in self._usage_archetypes: + if building_archetype.usage == building_usage: + return building_archetype + return None + + @staticmethod + def _assign_values(usage_zone, archetype, height): + # Due to the fact that python is not a typed language, the wrong object type is assigned to + # usage_zone.internal_gains when writing usage_zone.internal_gains = archetype.internal_gains. + # Therefore, this walk around has been done. + internal_gains = [] + for archetype_internal_gain in archetype.internal_gains: + internal_gain = InternalGains() + internal_gain.type = archetype_internal_gain.type + internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain * cte.METERS_TO_FEET**2 + internal_gain.convective_fraction = archetype_internal_gain.convective_fraction + internal_gain.radiative_fraction = archetype_internal_gain.radiative_fraction + internal_gain.latent_fraction = archetype_internal_gain.latent_fraction + internal_gains.append(internal_gain) + usage_zone.internal_gains = internal_gains + usage_zone.occupancy_density = archetype.occupancy_density * cte.METERS_TO_FEET**2 + usage_zone.mechanical_air_change = archetype.mechanical_air_change * usage_zone.occupancy_density \ + * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET**3 / height diff --git a/imports/usage/data_classes/hft_internal_gains_archetype.py b/imports/usage/data_classes/hft_internal_gains_archetype.py index c9de13b0..cc21a77a 100644 --- a/imports/usage/data_classes/hft_internal_gains_archetype.py +++ b/imports/usage/data_classes/hft_internal_gains_archetype.py @@ -9,13 +9,30 @@ class HftInternalGainsArchetype: """ HftInternalGainsArchetype class """ - def __init__(self, average_internal_gain=None, convective_fraction=None, radiative_fraction=None, - latent_fraction=None): + def __init__(self, internal_gains_type=None, average_internal_gain=None, convective_fraction=None, \ + radiative_fraction=None, latent_fraction=None): + self._type = internal_gains_type self._average_internal_gain = average_internal_gain self._convective_fraction = convective_fraction self._radiative_fraction = radiative_fraction self._latent_fraction = latent_fraction + @property + def type(self): + """ + Get internal gains type + :return: string + """ + return self._type + + @type.setter + def type(self, value): + """ + Set internal gains type + :param value: string + """ + self._type = value + @property def average_internal_gain(self): """ diff --git a/imports/usage/helpers/usage_helper.py b/imports/usage/helpers/usage_helper.py index b46b08d3..310b4888 100644 --- a/imports/usage/helpers/usage_helper.py +++ b/imports/usage/helpers/usage_helper.py @@ -36,3 +36,29 @@ class UsageHelper: except KeyError: sys.stderr.write('Error: keyword not found. Returned default HfT usage "residential"\n') return UsageHelper.hft_default_value + + usage_to_comnet = { + cte.RESIDENTIAL: 'BA Multifamily', + cte.INDUSTRY: 'BA Manufacturing Facility', + cte.OFFICE_ADMINISTRATION: 'BA Office', + cte.HOTEL: 'BA Hotel', + cte.HEALTH_CARE: 'BA Hospital', + cte.RETAIL: 'BA Retail', + cte.HALL: 'BA Town Hall', + cte.RESTAURANT: 'BA Dining: Bar Lounge/Leisure', + cte.EDUCATION: 'BA School/University' + } + comnet_default_value = 'BA Multifamily' + + @staticmethod + def comnet_from_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. Returned default Comnet usage "BA Multifamily"\n') + return UsageHelper.comnet_default_value diff --git a/imports/usage/hft_usage_interface.py b/imports/usage/hft_usage_interface.py index b6223295..3da6457d 100644 --- a/imports/usage/hft_usage_interface.py +++ b/imports/usage/hft_usage_interface.py @@ -68,7 +68,8 @@ class HftUsageInterface: average_internal_gain = 0 radiative_fraction = 0 - internal_gains.append(higa(average_internal_gain, convective_fraction, radiative_fraction, latent_fraction)) + internal_gains.append(higa(average_internal_gain=average_internal_gain, convective_fraction=convective_fraction, + radiative_fraction=radiative_fraction, latent_fraction=latent_fraction)) usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint, heating_set_back=heating_setback, cooling_set_point=cooling_setpoint, occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year, @@ -130,7 +131,8 @@ class HftUsageInterface: average_internal_gain = usage_zone_variant['schedules']['internGains']['averageInternGainPerSqm'] if 'radiantFraction' in usage_zone_variant['schedules']['internGains']: radiative_fraction = usage_zone_variant['schedules']['internGains']['radiantFraction'] - internal_gains.append(higa(average_internal_gain, convective_fraction, radiative_fraction, latent_fraction)) + internal_gains.append(higa(average_internal_gain=average_internal_gain, convective_fraction=convective_fraction, + radiative_fraction=radiative_fraction, latent_fraction=latent_fraction)) usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, heating_set_point=heating_setpoint, heating_set_back=heating_setback, cooling_set_point=cooling_setpoint, occupancy_density=occupancy_density, hours_day=hours_day, days_year=days_year, diff --git a/imports/usage_factory.py b/imports/usage_factory.py index a886c197..0e2875de 100644 --- a/imports/usage_factory.py +++ b/imports/usage_factory.py @@ -8,6 +8,7 @@ Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from pathlib import Path from imports.usage.hft_usage_parameters import HftUsageParameters from imports.usage.ca_usage_parameters import CaUsageParameters +from imports.usage.comnet_usage_parameters import ComnetUsageParameters # todo: handle missing lambda and rise error. @@ -38,6 +39,12 @@ class UsageFactory: """ return CaUsageParameters(self._city, self._base_path).enrich_buildings() + def _comnet(self): + """ + Enrich the city with COMNET usage library + """ + return ComnetUsageParameters(self._city, self._base_path).enrich_buildings() + def enrich(self): """ Enrich the city given to the class using the usage factory given handler