diff --git a/catalogs/greenery/ecore_greenery/greenerycatalog.ecore b/catalogs/greenery/ecore_greenery/greenerycatalog.ecore deleted file mode 100644 index 5c3bb85d..00000000 --- a/catalogs/greenery/ecore_greenery/greenerycatalog.ecore +++ /dev/null @@ -1,268 +0,0 @@ - - - - -
- - - - -
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
- - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - diff --git a/catalogs/greenery/ecore_greenery/greenerycatalog.py b/catalogs/greenery/ecore_greenery/greenerycatalog.py deleted file mode 100644 index 30ac116c..00000000 --- a/catalogs/greenery/ecore_greenery/greenerycatalog.py +++ /dev/null @@ -1,318 +0,0 @@ -"""Definition of meta model 'greenerycatalog'.""" -from functools import partial -import pyecore.ecore as Ecore -from pyecore.ecore import * - - -name = 'greenerycatalog' -nsURI = 'http://ca.concordia/greenerycatalog' -nsPrefix = 'greenery' - -eClass = EPackage(name=name, nsURI=nsURI, nsPrefix=nsPrefix) - -eClassifiers = {} -getEClassifier = partial(Ecore.getEClassifier, searchspace=eClassifiers) -Management = EEnum('Management', literals=['Intensive', 'Extensive', 'SemiIntensive', 'NA']) - -Roughness = EEnum('Roughness', literals=['VeryRough', 'Rough', - 'MediumRough', 'MediumSmooth', 'Smooth', 'VerySmooth']) - - -class Soil(EObject, metaclass=MetaEClass): - - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - roughness = EAttribute(eType=Roughness, unique=True, derived=False, - changeable=True, default_value=Roughness.MediumRough) - conductivityOfDrySoil = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='1.0 W/(m*K)') - densityOfDrySoil = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='1100 kg/m³') - specificHeatOfDrySoil = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='1200 J/(kg*K)') - thermalAbsorptance = EAttribute(eType=EString, unique=True, - derived=False, changeable=True, default_value='0.9') - solarAbsorptance = EAttribute(eType=EString, unique=True, - derived=False, changeable=True, default_value='0.7') - visibleAbsorptance = EAttribute(eType=EString, unique=True, - derived=False, changeable=True, default_value='0.75') - saturationVolumetricMoistureContent = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='0.0') - residualVolumetricMoistureContent = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='0.05') - initialVolumetricMoistureContent = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='0.1') - - def __init__(self, *, name=None, roughness=None, conductivityOfDrySoil=None, densityOfDrySoil=None, specificHeatOfDrySoil=None, thermalAbsorptance=None, solarAbsorptance=None, visibleAbsorptance=None, saturationVolumetricMoistureContent=None, residualVolumetricMoistureContent=None, initialVolumetricMoistureContent=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if roughness is not None: - self.roughness = roughness - - if conductivityOfDrySoil is not None: - self.conductivityOfDrySoil = conductivityOfDrySoil - - if densityOfDrySoil is not None: - self.densityOfDrySoil = densityOfDrySoil - - if specificHeatOfDrySoil is not None: - self.specificHeatOfDrySoil = specificHeatOfDrySoil - - if thermalAbsorptance is not None: - self.thermalAbsorptance = thermalAbsorptance - - if solarAbsorptance is not None: - self.solarAbsorptance = solarAbsorptance - - if visibleAbsorptance is not None: - self.visibleAbsorptance = visibleAbsorptance - - if saturationVolumetricMoistureContent is not None: - self.saturationVolumetricMoistureContent = saturationVolumetricMoistureContent - - if residualVolumetricMoistureContent is not None: - self.residualVolumetricMoistureContent = residualVolumetricMoistureContent - - if initialVolumetricMoistureContent is not None: - self.initialVolumetricMoistureContent = initialVolumetricMoistureContent - - -class Plant(EObject, metaclass=MetaEClass): - - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - height = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='0.1 m') - leafAreaIndex = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='2.5') - leafReflectivity = EAttribute(eType=EString, unique=True, - derived=False, changeable=True, default_value='0.1') - leafEmissivity = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='0.9') - minimalStomatalResistance = EAttribute( - eType=EString, unique=True, derived=False, changeable=True, default_value='100.0 s/m') - co2Sequestration = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='kgCO₂eq') - growsOn = EReference(ordered=True, unique=True, containment=False, derived=False, upper=-1) - - def __init__(self, *, name=None, height=None, leafAreaIndex=None, leafReflectivity=None, leafEmissivity=None, minimalStomatalResistance=None, growsOn=None, co2Sequestration=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if height is not None: - self.height = height - - if leafAreaIndex is not None: - self.leafAreaIndex = leafAreaIndex - - if leafReflectivity is not None: - self.leafReflectivity = leafReflectivity - - if leafEmissivity is not None: - self.leafEmissivity = leafEmissivity - - if minimalStomatalResistance is not None: - self.minimalStomatalResistance = minimalStomatalResistance - - if co2Sequestration is not None: - self.co2Sequestration = co2Sequestration - - if growsOn: - self.growsOn.extend(growsOn) - - -class SupportEnvelope(EObject, metaclass=MetaEClass): - - roughness = EAttribute(eType=Roughness, unique=True, derived=False, - changeable=True, default_value=Roughness.MediumRough) - solarAbsorptance = EAttribute(eType=EDouble, unique=True, - derived=False, changeable=True, default_value=0.0) - conductivity = EAttribute(eType=EDouble, unique=True, derived=False, - changeable=True, default_value=0.0) - visibleAbsorptance = EAttribute(eType=EDouble, unique=True, - derived=False, changeable=True, default_value=0.0) - specificHeat = EAttribute(eType=EDouble, unique=True, derived=False, - changeable=True, default_value=0.0) - density = EAttribute(eType=EDouble, unique=True, derived=False, - changeable=True, default_value=0.0) - thermalAbsorptance = EAttribute(eType=EDouble, unique=True, - derived=False, changeable=True, default_value=0.0) - - def __init__(self, *, roughness=None, solarAbsorptance=None, conductivity=None, visibleAbsorptance=None, specificHeat=None, density=None, thermalAbsorptance=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if roughness is not None: - self.roughness = roughness - - if solarAbsorptance is not None: - self.solarAbsorptance = solarAbsorptance - - if conductivity is not None: - self.conductivity = conductivity - - if visibleAbsorptance is not None: - self.visibleAbsorptance = visibleAbsorptance - - if specificHeat is not None: - self.specificHeat = specificHeat - - if density is not None: - self.density = density - - if thermalAbsorptance is not None: - self.thermalAbsorptance = thermalAbsorptance - - -class GreeneryCatalog(EObject, metaclass=MetaEClass): - - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - description = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - source = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - plantCategories = EReference(ordered=True, unique=True, - containment=True, derived=False, upper=-1) - vegetationCategories = EReference(ordered=True, unique=True, - containment=True, derived=False, upper=-1) - soils = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1) - - def __init__(self, *, name=None, description=None, source=None, plantCategories=None, vegetationCategories=None, soils=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if description is not None: - self.description = description - - if source is not None: - self.source = source - - if plantCategories: - self.plantCategories.extend(plantCategories) - - if vegetationCategories: - self.vegetationCategories.extend(vegetationCategories) - - if soils: - self.soils.extend(soils) - - -class PlantCategory(EObject, metaclass=MetaEClass): - """Excluding (that is non-overlapping) categories like Trees, Hedeges, Grasses that help users finding a specific biol. plant species.""" - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - plants = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1) - - def __init__(self, *, name=None, plants=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if plants: - self.plants.extend(plants) - - -class IrrigationSchedule(EObject, metaclass=MetaEClass): - - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - - def __init__(self, *, name=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - -class Vegetation(EObject, metaclass=MetaEClass): - """Plant life or total plant cover (as of an area)""" - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - thicknessOfSoil = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='20 cm') - management = EAttribute(eType=Management, unique=True, derived=False, - changeable=True, default_value=Management.NA) - airGap = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='0.0 cm') - soil = EReference(ordered=True, unique=True, containment=False, derived=False) - plants = EReference(ordered=True, unique=True, containment=True, derived=False, upper=-1) - - def __init__(self, *, name=None, thicknessOfSoil=None, soil=None, plants=None, management=None, airGap=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if thicknessOfSoil is not None: - self.thicknessOfSoil = thicknessOfSoil - - if management is not None: - self.management = management - - if airGap is not None: - self.airGap = airGap - - if soil is not None: - self.soil = soil - - if plants: - self.plants.extend(plants) - - -class VegetationCategory(EObject, metaclass=MetaEClass): - """Excluding (that is non-overlapping) categories to help users finding a specific vegetation template.""" - name = EAttribute(eType=EString, unique=True, derived=False, changeable=True) - vegetationTemplates = EReference(ordered=True, unique=True, - containment=True, derived=False, upper=-1) - - def __init__(self, *, vegetationTemplates=None, name=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if name is not None: - self.name = name - - if vegetationTemplates: - self.vegetationTemplates.extend(vegetationTemplates) - - -class PlantPercentage(EObject, metaclass=MetaEClass): - - percentage = EAttribute(eType=EString, unique=True, derived=False, - changeable=True, default_value='100') - plant = EReference(ordered=True, unique=True, containment=False, derived=False) - - def __init__(self, *, percentage=None, plant=None): - # if kwargs: - # raise AttributeError('unexpected arguments: {}'.format(kwargs)) - - super().__init__() - - if percentage is not None: - self.percentage = percentage - - if plant is not None: - self.plant = plant diff --git a/catalogs/greenery/ecore_greenery/greenerycatalog_no_quantities.ecore b/catalogs/greenery/ecore_greenery/greenerycatalog_no_quantities.ecore deleted file mode 100644 index db58a9c0..00000000 --- a/catalogs/greenery/ecore_greenery/greenerycatalog_no_quantities.ecore +++ /dev/null @@ -1,268 +0,0 @@ - - - - -
- - - - -
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - -
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - -
- - - - -
-
- - - - - - - - - -
- - - - - - - - - - - - - -
-
- - - - - - - - - - - - - diff --git a/city_model_structure/attributes/polygon.py b/city_model_structure/attributes/polygon.py index cdfd56da..126e9ae4 100644 --- a/city_model_structure/attributes/polygon.py +++ b/city_model_structure/attributes/polygon.py @@ -16,6 +16,7 @@ from city_model_structure.attributes.plane import Plane from city_model_structure.attributes.point import Point import helpers.constants as cte + class Polygon: """ Polygon class @@ -78,6 +79,48 @@ class Polygon: z = vector_0[2] * vector_1[2] return x+y+z + def contains_point(self, point): + """ + Determines if the given point is contained by the current polygon + :return: boolean + """ + # fixme: This method doesn't seems to work. + n = len(self.vertices) + angle_sum = 0 + for i in range(0, n): + vector_0 = self.vertices[i] + vector_1 = self.vertices[(i+1) % n] + # set to origin + vector_0[0] = vector_0[0] - point.coordinates[0] + vector_0[1] = vector_0[1] - point.coordinates[1] + vector_0[2] = vector_0[2] - point.coordinates[2] + vector_1[0] = vector_1[0] - point.coordinates[0] + vector_1[1] = vector_1[1] - point.coordinates[1] + vector_1[2] = vector_1[2] - point.coordinates[2] + module = Polygon._module(vector_0) * Polygon._module(vector_1) + + scalar_product = Polygon._scalar_product(vector_0, vector_1) + angle = np.pi/2 + if module != 0: + angle = abs(np.arcsin(scalar_product / module)) + angle_sum += angle + print(angle_sum) + return abs(angle_sum - math.pi*2) < cte.EPSILON + + def contains_polygon(self, polygon): + """ + Determines if the given polygon is contained by the current polygon + :return: boolean + """ + print('contains') + for point in polygon.points: + print(point.coordinates, self.contains_point(point)) + + if not self.contains_point(point): + return False + print('Belong!') + return True + @property def points_list(self) -> np.ndarray: """ @@ -249,7 +292,7 @@ class Polygon: points_list = self.points_list normal = self.normal if np.linalg.norm(normal) == 0: - sys.stderr.write(f'Not able to triangulate polygon [normal length is 0]') + sys.stderr.write('Not able to triangulate polygon\n') return [self] # are points concave or convex? total_points_list, concave_points, convex_points = self._starting_lists(points_list, normal) @@ -297,10 +340,10 @@ class Polygon: continue break if len(total_points_list) <= 3 and len(convex_points) > 0: - sys.stderr.write('Not able to triangulate polygon [convex surface]\n') + sys.stderr.write('Not able to triangulate polygon\n') return [self] if j >= 100: - sys.stderr.write('Not able to triangulate polygon [infinite loop]\n') + sys.stderr.write('Not able to triangulate polygon\n') return [self] last_ear = self._triangle(points_list, total_points_list, concave_points[1]) ears.append(last_ear) diff --git a/city_model_structure/attributes/polyhedron.py b/city_model_structure/attributes/polyhedron.py index 44a1f8bc..3f292f79 100644 --- a/city_model_structure/attributes/polyhedron.py +++ b/city_model_structure/attributes/polyhedron.py @@ -114,7 +114,7 @@ class Polyhedron: if self._trimesh is None: for face in self.faces: if len(face) != 3: - sys.stderr.write(f'Not able to generate trimesh the face has {len(face)} vertices\n') + sys.stderr.write('Not able to generate trimesh\n') return None self._trimesh = Trimesh(vertices=self.vertices, faces=self.faces) return self._trimesh diff --git a/city_model_structure/attributes/schedule.py b/city_model_structure/attributes/schedule.py index 1924dd7b..2d009f7f 100644 --- a/city_model_structure/attributes/schedule.py +++ b/city_model_structure/attributes/schedule.py @@ -9,131 +9,131 @@ from typing import Union, List class Schedule: - """ + """ Schedule class """ - def __init__(self): - self._id = None - self._type = None - self._values = None - self._data_type = None - self._time_step = None - self._time_range = None - self._day_types = None + def __init__(self): + self._id = None + self._type = None + self._values = None + self._data_type = None + self._time_step = None + self._time_range = None + self._day_types = None - @property - def id(self): - """ + @property + def id(self): + """ Get schedule id, an universally unique identifier randomly generated :return: str """ - if self._id is None: - self._id = uuid.uuid4() - return self._id + if self._id is None: + self._id = uuid.uuid4() + return self._id - @property - def type(self) -> Union[None, str]: - """ + @property + def type(self) -> Union[None, str]: + """ Get schedule type :return: None or str """ - return self._type + return self._type - @type.setter - def type(self, value): - """ + @type.setter + def type(self, value): + """ Set schedule type :param: str """ - if value is not None: - self._type = str(value) + if value is not None: + self._type = str(value) - @property - def values(self): - """ + @property + def values(self): + """ Get schedule values :return: [Any] """ - return self._values + return self._values - @values.setter - def values(self, value): - """ + @values.setter + def values(self, value): + """ Set schedule values :param: [Any] """ - self._values = value + self._values = value - @property - def data_type(self) -> Union[None, str]: - """ + @property + def data_type(self) -> Union[None, str]: + """ Get schedule data type from: ['any_number', 'fraction', 'on_off', 'temperature', 'humidity', 'control_type'] :return: None or str """ - return self._data_type + return self._data_type - @data_type.setter - def data_type(self, value): - """ + @data_type.setter + def data_type(self, value): + """ Set schedule data type :param: str """ - if value is not None: - self._data_type = str(value) + if value is not None: + self._data_type = str(value) - @property - def time_step(self) -> Union[None, str]: - """ + @property + def time_step(self) -> Union[None, str]: + """ Get schedule time step from: ['second', 'minute', 'hour', 'day', 'week', 'month'] :return: None or str """ - return self._time_step + return self._time_step - @time_step.setter - def time_step(self, value): - """ + @time_step.setter + def time_step(self, value): + """ Set schedule time step :param: str """ - if value is not None: - self._time_step = str(value) + if value is not None: + self._time_step = str(value) - @property - def time_range(self) -> Union[None, str]: - """ + @property + def time_range(self) -> Union[None, str]: + """ Get schedule time range from: ['minute', 'hour', 'day', 'week', 'month', 'year'] :return: None or str """ - return self._time_range + return self._time_range - @time_range.setter - def time_range(self, value): - """ + @time_range.setter + def time_range(self, value): + """ Set schedule time range :param: str """ - if value is not None: - self._time_range = str(value) + if value is not None: + self._time_range = str(value) - @property - def day_types(self) -> Union[None, List[str]]: - """ + @property + def day_types(self) -> Union[None, List[str]]: + """ Get schedule day types, as many as needed from: ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday', 'holiday', 'winter_design_day', 'summer_design_day'] :return: None or [str] """ - return self._day_types + return self._day_types - @day_types.setter - def day_types(self, value): - """ + @day_types.setter + def day_types(self, value): + """ Set schedule day types :param: [str] """ - if value is not None: - self._day_types = [str(i) for i in value] + if value is not None: + self._day_types = [str(i) for i in value] diff --git a/city_model_structure/building.py b/city_model_structure/building.py index 951eab05..bab214a1 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -8,12 +8,9 @@ contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca from typing import List, Union import numpy as np from city_model_structure.building_demand.surface import Surface -from city_model_structure.building_demand.thermal_zone import ThermalZone -from city_model_structure.building_demand.thermal_boundary import ThermalBoundary -from city_model_structure.building_demand.usage_zone import UsageZone -from city_model_structure.building_demand.storey import Storey from city_model_structure.city_object import CityObject from city_model_structure.building_demand.household import Household +from city_model_structure.building_demand.internal_zone import InternalZone from city_model_structure.attributes.polyhedron import Polyhedron @@ -34,10 +31,8 @@ class Building(CityObject): self._floor_area = None self._roof_type = None self._storeys = None - self._geometrical_zones = None - self._thermal_zones = [] - self._thermal_boundaries = None - self._usage_zones = [] + self._internal_zones = None + self._shell = None self._type = 'building' self._heating = dict() self._cooling = dict() @@ -62,13 +57,26 @@ class Building(CityObject): self._internal_walls.append(surface) @property - def geometrical_zones(self) -> List[Polyhedron]: - if self._geometrical_zones is None: - polygons = [] - for surface in self.surfaces: - polygons.append(surface.perimeter_polygon) - self._geometrical_zones = [Polyhedron(polygons)] - return self._geometrical_zones + def shell(self) -> Polyhedron: + """ + Get building shell + :return: [Polyhedron] + """ + if self._shell is None: + self._shell = Polyhedron(self.surfaces) + return self._shell + + @property + def internal_zones(self) -> List[InternalZone]: + """ + Get building internal zones + For Lod up to 3, there is only one internal zone which corresponds to the building shell. + In LoD 4 there can be more than one. In this case the definition of surfaces and floor area must be redefined. + :return: [InternalZone] + """ + if self._internal_zones is None: + self._internal_zones = [InternalZone(self.surfaces, self.floor_area)] + return self._internal_zones @property def grounds(self) -> List[Surface]: @@ -78,28 +86,6 @@ class Building(CityObject): """ return self._grounds - @property - def is_heated(self): - """ - Get building heated flag - :return: Boolean - """ - for usage_zone in self.usage_zones: - if usage_zone.is_heated: - return usage_zone.is_heated - return False - - @property - def is_cooled(self): - """ - Get building cooled flag - :return: Boolean - """ - for usage_zone in self.usage_zones: - if usage_zone.is_cooled: - return usage_zone.is_cooled - return False - @property def roofs(self) -> List[Surface]: """ @@ -117,18 +103,7 @@ class Building(CityObject): return self._walls @property - def usage_zones(self) -> List[UsageZone]: - """ - Get city object usage zones - :return: [UsageZone] - """ - if len(self._usage_zones) == 0: - for thermal_zone in self.thermal_zones: - self._usage_zones.extend(thermal_zone.usage_zones) - return self._usage_zones - - @property - def terrains(self) -> List[Surface]: + def terrains(self) -> Union[None, List[Surface]]: """ Get city object terrain surfaces :return: [Surface] @@ -169,27 +144,6 @@ class Building(CityObject): if value is not None: self._basement_heated = int(value) - @property - def name(self): - """ - Get building name - :return: str - """ - return self._name - - @property - def thermal_zones(self) -> List[ThermalZone]: - """ - Get building thermal zones - :return: [ThermalZone] - """ - if len(self._thermal_zones) == 0: - if self.storeys is None: - return [] - for storey in self.storeys: - self._thermal_zones.append(storey.thermal_zone) - return self._thermal_zones - @property def heated_volume(self): """ @@ -213,7 +167,7 @@ class Building(CityObject): :param value: int """ if value is not None: - self._year_of_construction = value + self._year_of_construction = int(value) @property def function(self) -> Union[None, str]: @@ -310,22 +264,6 @@ class Building(CityObject): self._eave_height = max(self._eave_height, wall.upper_corner[2]) return self._eave_height - @property - def storeys(self) -> List[Storey]: - """ - Get building storeys - :return: [Storey] - """ - return self._storeys - - @storeys.setter - def storeys(self, value): - """ - Set building storeys - :param value: [Storey] - """ - self._storeys = value - @property def roof_type(self): """ @@ -341,6 +279,14 @@ class Building(CityObject): break return self._roof_type + @roof_type.setter + def roof_type(self, value): + """ + Set roof type for the building flat or pitch + :return: str + """ + self._roof_type = value + @property def floor_area(self): """ @@ -354,24 +300,6 @@ class Building(CityObject): self._floor_area += surface.perimeter_polygon.area return self._floor_area - @property - def thermal_boundaries(self) -> List[ThermalBoundary]: - """ - Get all thermal boundaries associated to the building's thermal zones - :return: [ThermalBoundary] - """ - if self._thermal_boundaries is None: - self._thermal_boundaries = [] - for thermal_zone in self.thermal_zones: - _thermal_boundary_duplicated = False - for thermal_boundary in thermal_zone.thermal_boundaries: - if len(thermal_boundary.thermal_zones) > 1: - if thermal_zone != thermal_boundary.thermal_zones[1]: - self._thermal_boundaries.append(thermal_boundary) - else: - self._thermal_boundaries.append(thermal_boundary) - return self._thermal_boundaries - @property def households(self) -> List[Household]: """ @@ -379,3 +307,18 @@ class Building(CityObject): :return: List[Household] """ return self._households + + @property + def is_conditioned(self): + """ + Get building heated flag + :return: Boolean + """ + if self.internal_zones is None: + return False + for internal_zone in self.internal_zones: + if internal_zone.usage_zones is not None: + for usage_zone in internal_zone.usage_zones: + if usage_zone.thermal_control is not None: + return True + return False diff --git a/city_model_structure/building_demand/appliances.py b/city_model_structure/building_demand/appliances.py new file mode 100644 index 00000000..8fd4f0f6 --- /dev/null +++ b/city_model_structure/building_demand/appliances.py @@ -0,0 +1,103 @@ +""" +Appliances module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union, List +from city_model_structure.attributes.schedule import Schedule + + +class Appliances: + """ + Appliances class + """ + def __init__(self): + self._appliances_density = None + self._convective_fraction = None + self._radiative_fraction = None + self._latent_fraction = None + self._schedules = None + + @property + def appliances_density(self) -> Union[None, float]: + """ + Get appliances density in Watts per m2 + :return: None or float + """ + return self._appliances_density + + @appliances_density.setter + def appliances_density(self, value): + """ + Set appliances density in Watts per m2 + :param value: float + """ + if value is not None: + self._appliances_density = float(value) + + @property + def convective_fraction(self) -> Union[None, float]: + """ + Get convective fraction + :return: None or float + """ + return self._convective_fraction + + @convective_fraction.setter + def convective_fraction(self, value): + """ + Set convective fraction + :param value: float + """ + if value is not None: + self._convective_fraction = float(value) + + @property + def radiative_fraction(self) -> Union[None, float]: + """ + Get radiant fraction + :return: None or float + """ + return self._radiative_fraction + + @radiative_fraction.setter + def radiative_fraction(self, value): + """ + Set radiant fraction + :param value: float + """ + if value is not None: + self._radiative_fraction = float(value) + + @property + def latent_fraction(self) -> Union[None, float]: + """ + Get latent fraction + :return: None or float + """ + return self._latent_fraction + + @latent_fraction.setter + def latent_fraction(self, value): + """ + Set latent fraction + :param value: float + """ + if value is not None: + self._latent_fraction = float(value) + + @property + def schedules(self) -> Union[None, List[Schedule]]: + """ + Get schedules + :return: None or [Schedule] + """ + return self._schedules + + @schedules.setter + def schedules(self, value): + """ + Set schedules + :param value: [Schedule] + """ + self._schedules = value diff --git a/city_model_structure/building_demand/internal_gains.py b/city_model_structure/building_demand/internal_gains.py index 75e60cb7..9384cf21 100644 --- a/city_model_structure/building_demand/internal_gains.py +++ b/city_model_structure/building_demand/internal_gains.py @@ -4,7 +4,8 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ -from typing import Union +from typing import Union, List +from city_model_structure.attributes.schedule import Schedule class InternalGains: @@ -18,6 +19,7 @@ class InternalGains: self._convective_fraction = None self._radiative_fraction = None self._latent_fraction = None + self._schedules = None @property def type(self) -> Union[None, str]: @@ -103,3 +105,19 @@ class InternalGains: """ if value is not None: self._latent_fraction = float(value) + + @property + def schedules(self) -> Union[None, List[Schedule]]: + """ + Get internal gain schedule + :return: [Schedule] + """ + return self._schedules + + @schedules.setter + def schedules(self, value): + """ + Set internal gain schedule + :param value: [Schedule] + """ + self._schedules = value diff --git a/city_model_structure/building_demand/internal_zone.py b/city_model_structure/building_demand/internal_zone.py new file mode 100644 index 00000000..74678473 --- /dev/null +++ b/city_model_structure/building_demand/internal_zone.py @@ -0,0 +1,123 @@ +""" +InternalZone module. It saves the original geometrical information from interiors together with some attributes of those +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import uuid +from typing import Union, List +from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.thermal_zone import ThermalZone +from city_model_structure.attributes.polyhedron import Polyhedron +from city_model_structure.energy_systems.hvac_system import HvacSystem + + +class InternalZone: + """ + InternalZone class + """ + def __init__(self, surfaces, area): + self._surfaces = surfaces + self._id = None + self._geometry = None + self._volume = None + self._area = area + self._thermal_zones = None + self._usage_zones = None + self._hvac_system = None + + @property + def id(self): + """ + Get internal zone id, an universally unique identifier randomly generated + :return: str + """ + if self._id is None: + self._id = uuid.uuid4() + return self._id + + @property + def geometry(self) -> Polyhedron: + """ + Get internal zone geometry + :return: Polyhedron + """ + if self._geometry is None: + polygons = [] + for surface in self.surfaces: + polygons.append(surface.perimeter_polygon) + self._geometry = Polyhedron(polygons) + return self._geometry + + @property + def surfaces(self): + """ + Get internal zone surfaces + :return: [Surface] + """ + return self._surfaces + + @property + def volume(self): + """ + Get internal zone volume in cubic meters + :return: float + """ + return self.geometry.volume + + @property + def area(self): + """ + Get internal zone area in square meters + :return: float + """ + return self._area + + @property + def usage_zones(self) -> [UsageZone]: + """ + Get internal zone usage zones + :return: [UsageZone] + """ + return self._usage_zones + + @usage_zones.setter + def usage_zones(self, value): + """ + Set internal zone usage zones + :param value: [UsageZone] + """ + self._usage_zones = value + + @property + def hvac_system(self) -> Union[None, HvacSystem]: + """ + Get HVAC system installed for this thermal zone + :return: None or HvacSystem + """ + return self._hvac_system + + @hvac_system.setter + def hvac_system(self, value): + """ + Set HVAC system installed for this thermal zone + :param value: HvacSystem + """ + self._hvac_system = value + + @property + def thermal_zones(self) -> Union[None, List[ThermalZone]]: + """ + Get building thermal zones + :return: [ThermalZone] + """ + return self._thermal_zones + + @thermal_zones.setter + def thermal_zones(self, value): + """ + Set city object thermal zones + :param value: [ThermalZone] + """ + self._thermal_zones = value + diff --git a/city_model_structure/building_demand/lighting.py b/city_model_structure/building_demand/lighting.py new file mode 100644 index 00000000..0e72249f --- /dev/null +++ b/city_model_structure/building_demand/lighting.py @@ -0,0 +1,103 @@ +""" +Lighting module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union, List +from city_model_structure.attributes.schedule import Schedule + + +class Lighting: + """ + Lighting class + """ + def __init__(self): + self._lighting_density = None + self._convective_fraction = None + self._radiative_fraction = None + self._latent_fraction = None + self._schedules = None + + @property + def lighting_density(self) -> Union[None, float]: + """ + Get lighting density in Watts per m2 + :return: None or float + """ + return self._lighting_density + + @lighting_density.setter + def lighting_density(self, value): + """ + Set lighting density in Watts per m2 + :param value: float + """ + if value is not None: + self._lighting_density = float(value) + + @property + def convective_fraction(self) -> Union[None, float]: + """ + Get convective fraction + :return: None or float + """ + return self._convective_fraction + + @convective_fraction.setter + def convective_fraction(self, value): + """ + Set convective fraction + :param value: float + """ + if value is not None: + self._convective_fraction = float(value) + + @property + def radiative_fraction(self) -> Union[None, float]: + """ + Get radiant fraction + :return: None or float + """ + return self._radiative_fraction + + @radiative_fraction.setter + def radiative_fraction(self, value): + """ + Set radiant fraction + :param value: float + """ + if value is not None: + self._radiative_fraction = float(value) + + @property + def latent_fraction(self) -> Union[None, float]: + """ + Get latent fraction + :return: None or float + """ + return self._latent_fraction + + @latent_fraction.setter + def latent_fraction(self, value): + """ + Set latent fraction + :param value: float + """ + if value is not None: + self._latent_fraction = float(value) + + @property + def schedules(self) -> Union[None, List[Schedule]]: + """ + Get schedules + :return: None or [Schedule] + """ + return self._schedules + + @schedules.setter + def schedules(self, value): + """ + Set schedules + :param value: [Schedule] + """ + self._schedules = value diff --git a/city_model_structure/building_demand/material.py b/city_model_structure/building_demand/material.py index cb015fa7..5762550d 100644 --- a/city_model_structure/building_demand/material.py +++ b/city_model_structure/building_demand/material.py @@ -6,6 +6,7 @@ Contributor Atiya atiya.atiya@mail.concordia.ca Contributor Mohammad Reza mohammad.seyedabadi@mail.concordia.ca """ +import ast from typing import Union @@ -14,6 +15,7 @@ class Material: Material class """ def __init__(self): + self._type = type self._id = None self._name = None self._conductivity = None @@ -25,7 +27,30 @@ class Material: self._visible_absorptance = None self._no_mass = False self._thermal_resistance = None - self._lca_id = None + self._embodied_carbon = None + self._embodied_carbon_unit = None + self._recycling_ratio = None + self._onsite_recycling_ratio = None + self._company_recycling_ratio = None + self._landfilling_ratio = None + self._cost = None + self._cost_unit = None + + @property + def type(self): + """ + Get material type + :return: str + """ + return self._type + + @type.setter + def type(self, value): + """ + Set material type + :param value: string + """ + self._type = str(value) @property def id(self): @@ -213,10 +238,137 @@ class Material: self._thermal_resistance = float(value) @property - def lca_id(self): - return self._lca_id + def embodied_carbon(self) -> Union[None, float]: + """ + Get material embodied carbon + :return: None or float + """ + return self._embodied_carbon - @lca_id.setter - def lca_id(self, value): - self._lca_id = value + @embodied_carbon.setter + def embodied_carbon(self, value): + """ + Set material embodied carbon + :param value: float + """ + if value is not None: + self._embodied_carbon = float(value) + @property + def embodied_carbon_unit(self) -> Union[None, str]: + """ + Get material embodied carbon unit + :return: None or string + """ + return self._embodied_carbon + + @embodied_carbon_unit.setter + def embodied_carbon_unit(self, value): + """ + Set material embodied carbon unit + :param value: string + """ + if value is not None: + self._embodied_carbon_unit = str(value) + + @property + def recycling_ratio(self) -> Union[None, float]: + """ + Get material recycling ratio + :return: None or float + """ + return self._recycling_ratio + + @recycling_ratio.setter + def recycling_ratio(self, value): + """ + Set material recycling ratio + :param value: float + """ + if value is not None: + self._recycling_ratio = float(value) + + @property + def onsite_recycling_ratio(self) -> Union[None, float]: + """ + Get material onsite recycling ratio + :return: None or float + """ + return self._onsite_recycling_ratio + + @onsite_recycling_ratio.setter + def onsite_recycling_ratio(self, value): + """ + Set material onsite recycling ratio + :param value: float + """ + if value is not None: + self._onsite_recycling_ratio = float(value) + + @property + def company_recycling_ratio(self) -> Union[None, float]: + """ + Get material company recycling ratio + :return: None or float + """ + return self._company_recycling_ratio + + @company_recycling_ratio.setter + def company_recycling_ratio(self, value): + """ + Set material company recycling ratio + :param value: float + """ + if value is not None: + self._company_recycling_ratio = float(value) + + @property + def landfilling_ratio(self) -> Union[None, float]: + """ + Get material landfilling ratio + :return: None or float + """ + return self._landfilling_ratio + + @landfilling_ratio.setter + def landfilling_ratio(self, value): + """ + Set material landfilling ratio + :param value: float + """ + if value is not None: + self._landfilling_ratio = float(value) + + @property + def cost(self) -> Union[None, float]: + """ + Get material cost + :return: None or float + """ + return self._cost + + @cost.setter + def cost(self, value): + """ + Set material cost + :param value: float + """ + if value is not None: + self._cost = float(value) + + @property + def cost_unit(self) -> Union[None, str]: + """ + Get material cost unit + :return: None or string + """ + return self._cost_unit + + @cost_unit.setter + def cost_unit(self, value): + """ + Set material cost unit + :param value: string + """ + if value is not None: + self._cost_unit = float(value) diff --git a/city_model_structure/building_demand/occupancy.py b/city_model_structure/building_demand/occupancy.py new file mode 100644 index 00000000..ec9ebf1e --- /dev/null +++ b/city_model_structure/building_demand/occupancy.py @@ -0,0 +1,121 @@ +""" +Occupancy module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union, List +from city_model_structure.attributes.schedule import Schedule +from city_model_structure.building_demand.occupant import Occupant + + +class Occupancy: + """ + Occupancy class + """ + def __init__(self): + self._occupancy_density = None + self._sensible_convective_internal_gain = None + self._sensible_radiative_internal_gain = None + self._latent_internal_gain = None + self._occupancy_schedules = None + self._occupants = None + + @property + def occupancy_density(self) -> Union[None, float]: + """ + Get density in m2 per person + :return: None or float + """ + return self._occupancy_density + + @occupancy_density.setter + def occupancy_density(self, value): + """ + Set density in m2 per persons + :param value: float + """ + if value is not None: + self._occupancy_density = float(value) + + @property + def sensible_convective_internal_gain(self) -> Union[None, float]: + """ + Get sensible convective internal gain in Watts per m2 + :return: None or float + """ + return self._sensible_convective_internal_gain + + @sensible_convective_internal_gain.setter + def sensible_convective_internal_gain(self, value): + """ + Set sensible convective internal gain in Watts per m2 + :param value: float + """ + if value is not None: + self._sensible_convective_internal_gain = float(value) + + @property + def sensible_radiative_internal_gain(self) -> Union[None, float]: + """ + Get sensible radiant internal gain in Watts per m2 + :return: None or float + """ + return self._sensible_radiative_internal_gain + + @sensible_radiative_internal_gain.setter + def sensible_radiative_internal_gain(self, value): + """ + Set sensible radiant internal gain in Watts per m2 + :param value: float + """ + if value is not None: + self._sensible_radiative_internal_gain = float(value) + + @property + def latent_internal_gain(self) -> Union[None, float]: + """ + Get latent internal gain in Watts per m2 + :return: None or float + """ + return self._latent_internal_gain + + @latent_internal_gain.setter + def latent_internal_gain(self, value): + """ + Set latent internal gain in Watts per m2 + :param value: float + """ + if value is not None: + self._latent_internal_gain = float(value) + + @property + def occupancy_schedules(self) -> Union[None, List[Schedule]]: + """ + Get occupancy schedules + :return: None or [Schedule] + """ + return self._occupancy_schedules + + @occupancy_schedules.setter + def occupancy_schedules(self, value): + """ + Set occupancy schedules + :param value: [Schedule] + """ + self._occupancy_schedules = value + + @property + def occupants(self) -> Union[None, List[Occupant]]: + """ + Get list of occupants + :return: None or List of Occupant + """ + return self._occupants + + @occupants.setter + def occupants(self, value): + """ + Set list of occupants + :param value: [Occupant] + """ + self._occupants = value diff --git a/city_model_structure/building_demand/occupants.py b/city_model_structure/building_demand/occupant.py similarity index 61% rename from city_model_structure/building_demand/occupants.py rename to city_model_structure/building_demand/occupant.py index 3d7a39b6..878d278c 100644 --- a/city_model_structure/building_demand/occupants.py +++ b/city_model_structure/building_demand/occupant.py @@ -1,19 +1,16 @@ """ -Occupants module +Occupant module SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Sanam Dabirian sanam.dabirian@mail.concordia.ca Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import TypeVar import calendar as cal -UsageZone = TypeVar('UsageZone') - -class Occupants: +class Occupant: """ - Occupants class + Occupant class """ def __init__(self): @@ -24,15 +21,11 @@ class Occupants: self._heat_dissipation = None self._occupancy_rate = None self._occupant_type = None - self._usage_zone = None - self._occupant_schedule = None - self._number_of_occupants = None self._arrival_time = None self._departure_time = None self._break_time = None self._day_of_week = None self._pd_of_meetings_duration = None - self._complete_year_schedule = None @property def heat_dissipation(self): @@ -82,46 +75,6 @@ class Occupants: """ self._occupant_type = float(value) - @property - def usage_zone(self) -> UsageZone: - """ - Get the zone an occupant is in - :return: UsageZone - """ - return self._usage_zone - - @property - def occupant_schedule(self): - """ - Get the schedules when an occupant is in a zone (24 values, 1 per hour of the day) - :return: [float] - """ - return self._occupant_schedule - - @occupant_schedule.setter - def occupant_schedule(self, value): - """ - Set the schedules when an occupant is in a zone (24 values, 1 per hour of the day) - :param value: [float] - """ - self._occupant_schedule = [float(i) for i in value] - - @property - def number_of_occupants(self): - """ - Get the number of occupants - :return: int - """ - return self._number_of_occupants - - @number_of_occupants.setter - def number_of_occupants(self, value): - """ - Set the number of occupants - :param value: int - """ - self._number_of_occupants = int(value) - @property def arrival_time(self): """ @@ -191,27 +144,3 @@ class Occupants: """ # todo @Sanam: what format are you expecting here?? self._pd_of_meetings_duration = value - - def get_complete_year_schedule(self, schedules): - """ - Get the a non-leap year (8760 h), starting on Monday schedules out of archetypal days of week - :return: [float] - """ - if self._complete_year_schedule is None: - self._complete_year_schedule = [] - for i in range(1, 13): - month_range = cal.monthrange(2015, i)[1] - for day in range(1, month_range+1): - if cal.weekday(2015, i, day) < 5: - for j in range(0, 24): - week_schedule = schedules['WD'][j] - self._complete_year_schedule.append(week_schedule) - elif cal.weekday(2015, i, day) == 5: - for j in range(0, 24): - week_schedule = schedules['Sat'][j] - self._complete_year_schedule.append(week_schedule) - else: - for j in range(0, 24): - week_schedule = schedules['Sun'][j] - self._complete_year_schedule.append(week_schedule) - return self._complete_year_schedule diff --git a/city_model_structure/building_demand/storey.py b/city_model_structure/building_demand/storey.py index f7a211d8..143eec29 100644 --- a/city_model_structure/building_demand/storey.py +++ b/city_model_structure/building_demand/storey.py @@ -12,12 +12,10 @@ from city_model_structure.building_demand.thermal_zone import ThermalZone class Storey: - # todo: rethink this class for buildings with windows """ Storey class """ - def __init__(self, name, storey_surfaces, neighbours, volume): - # todo: the information of the parent surface is lost -> need to recover it + def __init__(self, name, storey_surfaces, neighbours, volume, floor_area): self._name = name self._storey_surfaces = storey_surfaces self._thermal_boundaries = None @@ -25,6 +23,7 @@ class Storey: self._thermal_zone = None self._neighbours = neighbours self._volume = volume + self._floor_area = floor_area @property def name(self): @@ -59,7 +58,13 @@ class Storey: if self._thermal_boundaries is None: self._thermal_boundaries = [] for surface in self.surfaces: - self._thermal_boundaries.append(ThermalBoundary(surface)) + if surface.holes_polygons is None: + windows_areas = None + else: + windows_areas = [] + for hole in surface.holes_polygons: + windows_areas.append(hole.area) + self._thermal_boundaries.append(ThermalBoundary(surface, surface.solid_polygon.area, windows_areas)) return self._thermal_boundaries @property @@ -81,7 +86,7 @@ class Storey: :return: ThermalZone """ if self._thermal_zone is None: - self._thermal_zone = ThermalZone(self.thermal_boundaries, self.volume) + self._thermal_zone = ThermalZone(self.thermal_boundaries, self.volume, self.floor_area) return self._thermal_zone @property @@ -91,3 +96,11 @@ class Storey: :return: float """ return self._volume + + @property + def floor_area(self): + """ + Get storey's floor area in square meters + :return: float + """ + return self._floor_area diff --git a/city_model_structure/building_demand/surface.py b/city_model_structure/building_demand/surface.py index 1e2178c8..6c73d268 100644 --- a/city_model_structure/building_demand/surface.py +++ b/city_model_structure/building_demand/surface.py @@ -71,6 +71,7 @@ class Surface: if value is not None: self._id = str(value) + # todo: implement share surfaces @property def share_surfaces(self): """ @@ -191,6 +192,8 @@ class Surface: Get surface type Ground, Wall or Roof :return: str """ + + # todo: there are more types: internal wall, internal floor... this method must be redefined if self._type is None: grad = np.rad2deg(self.inclination) if grad >= 170: diff --git a/city_model_structure/building_demand/thermal_boundary.py b/city_model_structure/building_demand/thermal_boundary.py index 7b444a14..946e7642 100644 --- a/city_model_structure/building_demand/thermal_boundary.py +++ b/city_model_structure/building_demand/thermal_boundary.py @@ -5,25 +5,24 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import uuid -from typing import List, TypeVar, Union +from typing import List, Union +from helpers.configuration_helper import ConfigurationHelper as ch from city_model_structure.building_demand.layer import Layer from city_model_structure.building_demand.thermal_opening import ThermalOpening from city_model_structure.building_demand.thermal_zone import ThermalZone from city_model_structure.building_demand.surface import Surface -Polygon = TypeVar('Polygon') - class ThermalBoundary: """ ThermalBoundary class """ - def __init__(self, surface): - self._surface = surface + def __init__(self, parent_surface, opaque_area, windows_areas): + self._parent_surface = parent_surface + self._opaque_area = opaque_area + self._windows_areas = windows_areas self._id = None self._thermal_zones = None - # ToDo: up to at least LOD2 will be just one thermal opening per Thermal boundary only if window_ratio > 0, - # review for LOD3 and LOD4 self._thermal_openings = None self._layers = None self._outside_solar_absorptance = None @@ -32,16 +31,16 @@ class ThermalBoundary: self._u_value = None self._shortwave_reflectance = None self._construction_name = None - self._hi = 3.5 - self._he = 20 - self._window_ratio = None + self._hi = ch().convective_heat_transfer_coefficient_interior + self._he = ch().convective_heat_transfer_coefficient_exterior self._refurbishment_measure = None - self._surface_geometry = None self._thickness = None self._virtual_internal_surface = None self._inside_emissivity = None self._alpha_coefficient = None self._radiative_coefficient = None + self._window_ratio = None + self._calculated = False @property def id(self): @@ -54,13 +53,12 @@ class ThermalBoundary: return self._id @property - def surface(self) -> Surface: + def parent_surface(self) -> Surface: """ Get the surface that belongs to the thermal boundary :return: Surface """ - # todo: in LoD4 this property will be a list of surfaces, not only one - return self._surface + return self._parent_surface @property def thermal_zones(self) -> List[ThermalZone]: @@ -73,47 +71,35 @@ class ThermalBoundary: @thermal_zones.setter def thermal_zones(self, value): """ - Set the thermal zones delimited by the thermal boundary + Get the thermal zones delimited by the thermal boundary :param value: [ThermalZone] """ self._thermal_zones = value + # todo: do I need these two?? @property def azimuth(self): """ Get the thermal boundary azimuth in radians :return: float """ - return self._surface.azimuth + return self.parent_surface.azimuth @property def inclination(self): """ - Set the thermal boundary inclination in radians + Get the thermal boundary inclination in radians :return: float """ - return self._surface.inclination + return self.parent_surface.inclination @property - def area(self): + def opaque_area(self): """ - Set the thermal boundary area in square meters + Get the thermal boundary area in square meters :return: float """ - # to check the lod without depending on that parameter - if float(self.surface.solid_polygon.area) - float(self.surface.perimeter_polygon.area) < 1e-3: - area = float(self.surface.perimeter_polygon.area) * (1 - float(self.window_ratio)) - else: - area = self.surface.solid_polygon.area - return area - - @property - def _total_area_including_windows(self): - """ - Get the thermal boundary plus windows area in square meters - :return: float - """ - return self.surface.perimeter_polygon.area + return float(self._opaque_area) @property def thickness(self): @@ -125,7 +111,8 @@ class ThermalBoundary: self._thickness = 0.0 if self.layers is not None: for layer in self.layers: - self._thickness += layer.thickness + if not layer.material.no_mass: + self._thickness += layer.thickness return self._thickness @property @@ -181,30 +168,34 @@ class ThermalBoundary: self._outside_visible_absorptance = float(value) @property - def thermal_openings(self) -> List[ThermalOpening]: + def thermal_openings(self) -> Union[None, List[ThermalOpening]]: """ Get thermal boundary thermal openings :return: [ThermalOpening] """ if self._thermal_openings is None: - if float(self.window_ratio) > 0: - thermal_opening = ThermalOpening() - thermal_opening.area = float(self._total_area_including_windows) * float(self.window_ratio) - thermal_opening.hi = self.hi - thermal_opening.he = self.he - self._thermal_openings = [thermal_opening] + if self.window_ratio is not None: + if self.window_ratio == 0: + self._thermal_openings = [] + else: + thermal_opening = ThermalOpening() + if self.window_ratio == 1: + _area = self.opaque_area + else: + _area = self.opaque_area * self.window_ratio / (1-self.window_ratio) + thermal_opening.area = _area + self._thermal_openings = [thermal_opening] else: - self._thermal_openings = [] + if len(self.windows_areas) > 0: + self._thermal_openings = [] + for window_area in self.windows_areas: + thermal_opening = ThermalOpening() + thermal_opening.area = window_area + self._thermal_openings.append(thermal_opening) + else: + self._thermal_openings = [] return self._thermal_openings - @thermal_openings.setter - def thermal_openings(self, value): - """ - Set thermal boundary thermal openings - :param value: [ThermalOpening] - """ - self._thermal_openings = value - @property def construction_name(self) -> Union[None, str]: """ @@ -244,24 +235,47 @@ class ThermalBoundary: Get thermal boundary surface type :return: str """ - return self._surface.type + return self.parent_surface.type @property def window_ratio(self) -> Union[None, float]: """ Get thermal boundary window ratio - :return: None or float + It returns the window ratio calculated as the total windows' areas in a wall divided by + the total (opaque + transparent) area of that wall if windows are defined in the geometry imported. + If not, it returns the window ratio imported from an external source (e.g. construction library, manually assigned). + If none of those sources are available, it returns None. + :return: float """ + if self.windows_areas is not None: + if not self._calculated: + _calculated = True + if len(self.windows_areas) == 0: + self._window_ratio = 0 + else: + total_window_area = 0 + for window_area in self.windows_areas: + total_window_area += window_area + self._window_ratio = total_window_area / (self.opaque_area + total_window_area) return self._window_ratio @window_ratio.setter def window_ratio(self, value): """ Set thermal boundary window ratio - :param value: float + :param value: str """ - if value is not None: - self._window_ratio = float(value) + if self._calculated: + raise ValueError('Window ratio cannot be assigned when the windows are defined in the geometry.') + self._window_ratio = float(value) + + @property + def windows_areas(self) -> [float]: + """ + Get windows areas + :return: [float] + """ + return self._windows_areas @property def u_value(self) -> Union[None, float]: @@ -327,7 +341,7 @@ class ThermalBoundary: :param value: float """ if value is not None: - self._hi = float(value) + self._hi = value @property def he(self) -> Union[None, float]: @@ -344,14 +358,7 @@ class ThermalBoundary: :param value: float """ if value is not None: - self._he = float(value) - - @property - def surface_geometry(self): - """ - Raises not implemented error - """ - raise NotImplementedError + self._he = value @property def virtual_internal_surface(self) -> Surface: @@ -360,10 +367,9 @@ class ThermalBoundary: :return: Surface """ if self._virtual_internal_surface is None: - self._virtual_internal_surface = self.surface.inverse + self._virtual_internal_surface = self.parent_surface.inverse return self._virtual_internal_surface - # todo: need extract information from construction library or assume them at the beginning of workflows @property def inside_emissivity(self) -> Union[None, float]: """ diff --git a/city_model_structure/building_demand/thermal_control.py b/city_model_structure/building_demand/thermal_control.py new file mode 100644 index 00000000..0c4c8a62 --- /dev/null +++ b/city_model_structure/building_demand/thermal_control.py @@ -0,0 +1,144 @@ +""" +ThermalControl module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union, List +from city_model_structure.attributes.schedule import Schedule + + +class ThermalControl: + """ + ThermalControl class + """ + def __init__(self): + self._mean_heating_set_point = None + self._heating_set_back = None + self._mean_cooling_set_point = None + self._hvac_availability_schedules = None + self._heating_set_point_schedules = None + self._cooling_set_point_schedules = None + + @staticmethod + def _maximum_value(schedules): + maximum = -1000 + for schedule in schedules: + for value in schedule.values: + if value > maximum: + maximum = value + return maximum + + @staticmethod + def _minimum_value(schedules): + minimum = 1000 + for schedule in schedules: + for value in schedule.values: + if value < minimum: + minimum = value + return minimum + + @property + def mean_heating_set_point(self) -> Union[None, float]: + """ + Get heating set point defined for a thermal zone in Celsius + :return: None or float + """ + if self._mean_heating_set_point is None: + if self.heating_set_point_schedules is not None: + self._mean_heating_set_point = self._maximum_value(self.heating_set_point_schedules) + return self._mean_heating_set_point + + @mean_heating_set_point.setter + def mean_heating_set_point(self, value): + """ + Set heating set point defined for a thermal zone in Celsius + :param value: float + """ + self._mean_heating_set_point = value + + @property + def heating_set_back(self) -> Union[None, float]: + """ + Get heating set back defined for a thermal zone in Celsius + :return: None or float + """ + if self._heating_set_back is None: + if self.heating_set_point_schedules is not None: + self._heating_set_back = self._minimum_value(self.heating_set_point_schedules) + return self._heating_set_back + + @heating_set_back.setter + def heating_set_back(self, value): + """ + Set heating set back defined for a thermal zone in Celsius + :param value: float + """ + if value is not None: + self._heating_set_back = float(value) + + @property + def mean_cooling_set_point(self) -> Union[None, float]: + """ + Get cooling set point defined for a thermal zone in Celsius + :return: None or float + """ + if self._mean_cooling_set_point is None: + if self.cooling_set_point_schedules is not None: + self._mean_cooling_set_point = self._minimum_value(self.cooling_set_point_schedules) + return self._mean_cooling_set_point + + @mean_cooling_set_point.setter + def mean_cooling_set_point(self, value): + """ + Set cooling set point defined for a thermal zone in Celsius + :param value: float + """ + self._mean_cooling_set_point = value + + @property + def hvac_availability_schedules(self) -> Union[None, List[Schedule]]: + """ + Get the availability of the conditioning system defined for a thermal zone + :return: None or [Schedule] + """ + return self._hvac_availability_schedules + + @hvac_availability_schedules.setter + def hvac_availability_schedules(self, value): + """ + Set the availability of the conditioning system defined for a thermal zone + :param value: [Schedule] + """ + self._hvac_availability_schedules = value + + @property + def heating_set_point_schedules(self) -> Union[None, List[Schedule]]: + """ + Get heating set point schedule defined for a thermal zone in Celsius + :return: None or [Schedule] + """ + return self._heating_set_point_schedules + + @heating_set_point_schedules.setter + def heating_set_point_schedules(self, value): + """ + Set heating set point schedule defined for a thermal zone in Celsius + :param value: [Schedule] + """ + self._heating_set_point_schedules = value + + @property + def cooling_set_point_schedules(self) -> Union[None, List[Schedule]]: + """ + Get cooling set point schedule defined for a thermal zone in Celsius + :return: None or [Schedule] + """ + return self._cooling_set_point_schedules + + @cooling_set_point_schedules.setter + def cooling_set_point_schedules(self, value): + """ + Set cooling set point schedule defined for a thermal zone in Celsius + :param value: [Schedule] + """ + self._cooling_set_point_schedules = value diff --git a/city_model_structure/building_demand/thermal_opening.py b/city_model_structure/building_demand/thermal_opening.py index 0683f095..101dcffc 100644 --- a/city_model_structure/building_demand/thermal_opening.py +++ b/city_model_structure/building_demand/thermal_opening.py @@ -6,6 +6,7 @@ Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import uuid from typing import TypeVar, Union +from helpers.configuration_helper import ConfigurationHelper as ch Polygon = TypeVar('Polygon') @@ -25,9 +26,8 @@ class ThermalOpening: self._front_side_solar_transmittance_at_normal_incidence = None self._back_side_solar_transmittance_at_normal_incidence = None self._overall_u_value = None - self._hi = None - self._he = None - self._surface_geometry = None + self._hi = ch().convective_heat_transfer_coefficient_interior + self._he = ch().convective_heat_transfer_coefficient_exterior self._inside_emissivity = None self._alpha_coefficient = None self._radiative_coefficient = None @@ -240,15 +240,6 @@ class ThermalOpening: if value is not None: self._he = float(value) - @property - def surface_geometry(self) -> Polygon: - """ - Get the polygon that defines the thermal opening - :return: Polygon - """ - return self._surface_geometry - - # todo: need extract information from construction library or assume them at the beginning of workflows @property def inside_emissivity(self) -> Union[None, float]: """ diff --git a/city_model_structure/building_demand/thermal_zone.py b/city_model_structure/building_demand/thermal_zone.py index a3ad26e1..10f8ac0b 100644 --- a/city_model_structure/building_demand/thermal_zone.py +++ b/city_model_structure/building_demand/thermal_zone.py @@ -5,32 +5,34 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc Contributors Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import uuid -from typing import List, TypeVar, Union +from typing import List, Union, TypeVar from city_model_structure.building_demand.usage_zone import UsageZone -import ast +from city_model_structure.attributes.schedule import Schedule +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.energy_systems.hvac_system import HvacSystem ThermalBoundary = TypeVar('ThermalBoundary') -Polyhedron = TypeVar('Polyhedron') class ThermalZone: """ ThermalZone class """ - def __init__(self, thermal_boundaries, volume): - self._floor_area = None + def __init__(self, thermal_boundaries, volume, floor_area): + self._id = None + self._floor_area = floor_area self._thermal_boundaries = thermal_boundaries - self._is_mechanically_ventilated = None self._additional_thermal_bridge_u_value = None self._effective_thermal_capacity = None self._indirectly_heated_area_ratio = None self._infiltration_rate_system_on = None self._infiltration_rate_system_off = None - self._usage_zones = [] self._volume = volume - self._volume_geometry = None - self._id = None self._ordinate_number = None + self._usage_zones = None + self._thermal_control = None + self._hvac_system = None + self._view_factors_matrix = None @property def id(self): @@ -43,34 +45,11 @@ class ThermalZone: return self._id @property - def is_mechanically_ventilated(self) -> Union[None, bool]: - """ - Get thermal zone mechanical ventilation flag - :return: None or Boolean - """ - return self._is_mechanically_ventilated - - @is_mechanically_ventilated.setter - def is_mechanically_ventilated(self, value): - """ - Set thermal zone mechanical ventilation flag - :param value: Boolean - """ - if value is not None: - self._is_mechanically_ventilated = ast.literal_eval(value) - - @property - def floor_area(self): + def floor_area(self) -> float: """ Get thermal zone floor area in m2 :return: float """ - if self._floor_area is None: - self._floor_area = 0 - for thermal_boundary in self.thermal_boundaries: - s = thermal_boundary.surface - if s.type == 'Ground': - self._floor_area += s.perimeter_polygon.area return self._floor_area @property @@ -133,10 +112,10 @@ class ThermalZone: self._indirectly_heated_area_ratio = float(value) @property - def infiltration_rate_system_on(self) -> Union[None, float]: + def infiltration_rate_system_on(self) -> Union[None, Schedule]: """ Get thermal zone infiltration rate system on in air changes per hour (ACH) - :return: None or float + :return: None or Schedule """ return self._infiltration_rate_system_on @@ -144,16 +123,15 @@ class ThermalZone: def infiltration_rate_system_on(self, value): """ Set thermal zone infiltration rate system on in air changes per hour (ACH) - :param value: float + :param value: Schedule """ - if value is not None: - self._infiltration_rate_system_on = float(value) + self._infiltration_rate_system_on = value @property - def infiltration_rate_system_off(self) -> Union[None, float]: + def infiltration_rate_system_off(self) -> Union[None, Schedule]: """ Get thermal zone infiltration rate system off in air changes per hour (ACH) - :return: None or float + :return: None or Schedule """ return self._infiltration_rate_system_off @@ -161,26 +139,9 @@ class ThermalZone: def infiltration_rate_system_off(self, value): """ Set thermal zone infiltration rate system on in air changes per hour (ACH) - :param value: float + :param value: Schedule """ - if value is not None: - self._infiltration_rate_system_off = float(value) - - @property - def usage_zones(self) -> List[UsageZone]: - """ - Get thermal zone usage zones - :return: [UsageZone] - """ - return self._usage_zones - - @usage_zones.setter - def usage_zones(self, values): - """ - Set thermal zone usage zones - :param values: [UsageZone] - """ - self._usage_zones = values + self._infiltration_rate_system_off = value @property def volume(self): @@ -190,14 +151,6 @@ class ThermalZone: """ return self._volume - @property - def volume_geometry(self) -> Polyhedron: - """ - Get the polyhedron defined by the thermal zone - :return: Polyhedron - """ - return self._volume_geometry - @property def ordinate_number(self) -> Union[None, int]: """ @@ -214,3 +167,73 @@ class ThermalZone: """ if value is not None: self._ordinate_number = int(value) + + @property + def usage_zones(self) -> [UsageZone]: + """ + Get list of usage zones and the percentage of thermal zone's volume affected by that usage + From internal_zone + :return: [UsageZone] + """ + return self._usage_zones + + @usage_zones.setter + def usage_zones(self, values): + """ + Set list of usage zones and the percentage of thermal zone's volume affected by that usage + From internal_zone + :param values: [UsageZone] + """ + self._usage_zones = values + + @property + def thermal_control(self) -> Union[None, ThermalControl]: + """ + Get thermal control of this thermal zone + From internal_zone + :return: None or ThermalControl + """ + return self._thermal_control + + @thermal_control.setter + def thermal_control(self, value): + """ + Set thermal control for this thermal zone + From internal_zone + :param value: ThermalControl + """ + self._thermal_control = value + + @property + def hvac_system(self) -> Union[None, HvacSystem]: + """ + Get HVAC system installed for this thermal zone + From internal_zone + :return: None or HvacSystem + """ + return self._hvac_system + + @hvac_system.setter + def hvac_system(self, value): + """ + Set HVAC system installed for this thermal zone + From internal_zone + :param value: HvacSystem + """ + self._hvac_system = value + + @property + def view_factors_matrix(self): + """ + Get thermal zone view factors matrix + :return: [[float]] + """ + return self._view_factors_matrix + + @view_factors_matrix.setter + def view_factors_matrix(self, value): + """ + Set thermal zone view factors matrix + :param value: [[float]] + """ + self._view_factors_matrix = value diff --git a/city_model_structure/building_demand/usage_zone.py b/city_model_structure/building_demand/usage_zone.py index a2c16435..f8d77890 100644 --- a/city_model_structure/building_demand/usage_zone.py +++ b/city_model_structure/building_demand/usage_zone.py @@ -5,13 +5,13 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons Contributors Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ import uuid -from typing import List, TypeVar, Union -import ast - -InternalGains = TypeVar('InternalGains') -Occupants = TypeVar('Occupants') -Polyhedron = TypeVar('Polyhedron') -Schedule = TypeVar('Schedule') +from typing import List, Union +import helpers.constants as cte +from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.lighting import Lighting +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl class UsageZone: @@ -21,23 +21,17 @@ class UsageZone: def __init__(self): self._id = None self._usage = None - self._internal_gains = None - self._heating_setpoint = None - self._heating_setback = None - self._cooling_setpoint = None - self._occupancy_density = None + self._percentage = None + self._not_detailed_source_mean_annual_internal_gains = None self._hours_day = None self._days_year = None - self._dhw_average_volume_pers_day = None - self._dhw_preparation_temperature = None - self._electrical_app_average_consumption_sqm_year = None +# self._electrical_app_average_consumption_sqm_year = None self._mechanical_air_change = None - self._occupants = None - self._schedules = None - self._volume = None - self._volume_geometry = None - self._is_heated = False - self._is_cooled = False + self._occupancy = None + self._lighting = None + self._appliances = None + self._internal_gains = None + self._thermal_control = None @property def id(self): @@ -50,71 +44,54 @@ class UsageZone: return self._id @property - def internal_gains(self) -> List[InternalGains]: + def usage(self) -> Union[None, str]: """ - Get usage zone internal gains + Get usage zone usage + :return: None or str + """ + return self._usage + + @usage.setter + def usage(self, value): + """ + Set usage zone usage + :param value: str + """ + if value is not None: + self._usage = str(value) + + @property + def percentage(self): + """ + Get usage zone percentage in range[0,1] + :return: float + """ + return self._percentage + + @percentage.setter + def percentage(self, value): + """ + Set usage zone percentage in range[0,1] + :param value: float + """ + if value is not None: + self._percentage = float(value) + + @property + def not_detailed_source_mean_annual_internal_gains(self) -> List[InternalGains]: + """ + Get usage zone internal gains with unknown energy source :return: [InternalGains] """ - return self._internal_gains + return self._not_detailed_source_mean_annual_internal_gains - @internal_gains.setter - def internal_gains(self, value): + @not_detailed_source_mean_annual_internal_gains.setter + def not_detailed_source_mean_annual_internal_gains(self, value): """ - Set usage zone internal gains + Set usage zone internal gains with unknown energy source :param value: [InternalGains] """ - self._internal_gains = value - - @property - def heating_setpoint(self) -> Union[None, float]: - """ - Get usage zone heating set point in Celsius - :return: None or float - """ - return self._heating_setpoint - - @heating_setpoint.setter - def heating_setpoint(self, value): - """ - Set usage zone heating set point in Celsius - :param value: float - """ - if value is not None: - self._heating_setpoint = float(value) - - @property - def heating_setback(self) -> Union[None, float]: - """ - Get usage zone heating setback in Celsius - :return: None or float - """ - return self._heating_setback - - @heating_setback.setter - def heating_setback(self, value): - """ - Set usage zone heating setback in Celsius - :param value: float - """ - if value is not None: - self._heating_setback = float(value) - - @property - def cooling_setpoint(self) -> Union[None, float]: - """ - Get usage zone cooling setpoint in Celsius - :return: None or float - """ - return self._cooling_setpoint - - @cooling_setpoint.setter - def cooling_setpoint(self, value): - """ - Set usage zone cooling setpoint in Celsius - :param value: float - """ - if value is not None: - self._cooling_setpoint = float(value) + self._not_detailed_source_mean_annual_internal_gains = value @property def hours_day(self) -> Union[None, float]: @@ -167,106 +144,6 @@ class UsageZone: if value is not None: self._mechanical_air_change = float(value) - @property - def usage(self) -> Union[None, str]: - """ - Get usage zone usage - :return: None or str - """ - return self._usage - - @usage.setter - def usage(self, value): - """ - Set usage zone usage - :param value: str - """ - if value is not None: - self._usage = str(value) - - @property - def occupants(self) -> List[Occupants]: - """ - Get occupants data - :return: [Occupants] - """ - return self._occupants - - @occupants.setter - def occupants(self, values): - """ - Set occupants data - :param values: [Occupants] - """ - self._occupants = values - - @property - def schedules(self) -> List[Schedule]: - """ - Get usage zone schedules - :return: List[Schedule] - """ - return self._schedules - - @schedules.setter - def schedules(self, values): - """ - Set usage zone schedules - :param values: List[Schedule] - """ - self._schedules = values - - @property - def occupancy_density(self) -> Union[None, float]: - """ - Get density in persons per m2 - :return: None or float - """ - return self._occupancy_density - - @occupancy_density.setter - def occupancy_density(self, value): - """ - Set density in persons per m2 - :param value: float - """ - if value is not None: - self._occupancy_density = float(value) - - @property - def dhw_average_volume_pers_day(self) -> Union[None, float]: - """ - Get average DHW consumption in cubic meters per person per day - :return: None or float - """ - return self._dhw_average_volume_pers_day - - @dhw_average_volume_pers_day.setter - def dhw_average_volume_pers_day(self, value): - """ - Set average DHW consumption in cubic meters per person per day - :param value: float - """ - if value is not None: - self._dhw_average_volume_pers_day = float(value) - - @property - def dhw_preparation_temperature(self) -> Union[None, float]: - """ - Get preparation temperature of the DHW in Celsius - :return: None or float - """ - return self._dhw_preparation_temperature - - @dhw_preparation_temperature.setter - def dhw_preparation_temperature(self, value): - """ - Set preparation temperature of the DHW in Celsius - :param value: float - """ - if value is not None: - self._dhw_preparation_temperature = float(value) - @property def electrical_app_average_consumption_sqm_year(self) -> Union[None, float]: """ @@ -285,60 +162,109 @@ class UsageZone: self._electrical_app_average_consumption_sqm_year = float(value) @property - def volume_geometry(self) -> Polyhedron: + def occupancy(self) -> Union[None, Occupancy]: """ - Get the polyhedron defined by the usage zone - :return: Polyhedron + Get occupancy in the usage zone + :return: None or Occupancy """ - return self._volume_geometry + return self._occupancy + + @occupancy.setter + def occupancy(self, value): + """ + Set occupancy in the usage zone + :param value: Occupancy + """ + self._occupancy = value @property - def volume(self) -> Union[None, float]: + def lighting(self) -> Union[None, Lighting]: """ - Get the volume in cubic meters - :return: None or float + Get lighting information + :return: None or Lighting """ - return self._volume + return self._lighting - @volume.setter - def volume(self, value): + @lighting.setter + def lighting(self, value): """ - Set volume in cubic meters - :param value: float + Set lighting information + :param value: Lighting """ - if value is not None: - self._volume = float(value) + self._lighting = value @property - def is_heated(self) -> Union[None, bool]: + def appliances(self) -> Union[None, Appliances]: """ - Get thermal zone heated flag - :return: None or Boolean + Get appliances information + :return: None or Appliances """ - return self._is_heated + return self._appliances - @is_heated.setter - def is_heated(self, value): + @appliances.setter + def appliances(self, value): """ - Set thermal zone heated flag - :param value: Boolean + Set appliances information + :param value: Appliances """ - if value is not None: - self._is_heated = ast.literal_eval(value) + self._appliances = value + + def get_internal_gains(self) -> [InternalGains]: + """ + Calculates and returns the list of all internal gains defined + :return: InternalGains + """ + if self.occupancy is not None: + if self.occupancy.latent_internal_gain is not None: + _internal_gain = InternalGains() + _internal_gain.type = cte.OCCUPANCY + _total_heat_gain = (self.occupancy.sensible_convective_internal_gain + + self.occupancy.sensible_radiative_internal_gain + + self.occupancy.latent_internal_gain) + _internal_gain.average_internal_gain = _total_heat_gain + _internal_gain.latent_fraction = self.occupancy.latent_internal_gain / _total_heat_gain + _internal_gain.radiative_fraction = self.occupancy.sensible_radiative_internal_gain / _total_heat_gain + _internal_gain.convective_fraction = self.occupancy.sensible_convective_internal_gain / _total_heat_gain + _internal_gain.schedules = self.occupancy.occupancy_schedules + self._internal_gains = [_internal_gain] + if self.lighting is not None: + _internal_gain = InternalGains() + _internal_gain.type = cte.LIGHTING + _internal_gain.average_internal_gain = self.lighting.lighting_density + _internal_gain.latent_fraction = self.lighting.latent_fraction + _internal_gain.radiative_fraction = self.lighting.radiative_fraction + _internal_gain.convective_fraction = self.lighting.convective_fraction + _internal_gain.schedules = self.lighting.schedules + if self._internal_gains is not None: + self._internal_gains.append(_internal_gain) + else: + self._internal_gains = [_internal_gain] + if self.appliances is not None: + _internal_gain = InternalGains() + _internal_gain.type = cte.APPLIANCES + _internal_gain.average_internal_gain = self.appliances.appliances_density + _internal_gain.latent_fraction = self.appliances.latent_fraction + _internal_gain.radiative_fraction = self.appliances.radiative_fraction + _internal_gain.convective_fraction = self.appliances.convective_fraction + _internal_gain.schedules = self.appliances.schedules + if self._internal_gains is not None: + self._internal_gains.append(_internal_gain) + else: + self._internal_gains = [_internal_gain] + return self._internal_gains @property - def is_cooled(self) -> Union[None, bool]: + def thermal_control(self) -> Union[None, ThermalControl]: """ - Get thermal zone cooled flag - :return: None or Boolean + Get thermal control of this thermal zone + :return: None or ThermalControl """ - return self._is_cooled + return self._thermal_control - @is_cooled.setter - def is_cooled(self, value): + @thermal_control.setter + def thermal_control(self, value): """ - Set thermal zone cooled flag - :param value: Boolean + Set thermal control for this thermal zone + :param value: ThermalControl """ - if value is not None: - self._is_cooled = ast.literal_eval(value) + self._thermal_control = value diff --git a/city_model_structure/city_object.py b/city_model_structure/city_object.py index 2147909b..d71e3cea 100644 --- a/city_model_structure/city_object.py +++ b/city_model_structure/city_object.py @@ -36,13 +36,21 @@ class CityObject: self._beam = dict() self._sensors = [] + @property + def name(self): + """ + Get building name + :return: str + """ + return self._name + @property def lod(self) -> int: """ Get city object level of detail 1, 2, 3 or 4 :return: int """ - lod = math.log(self._lod, 2) + 1 + lod = int(math.log(self._lod, 2) + 1) return lod @property diff --git a/city_model_structure/energy_systems/hvac_system.py b/city_model_structure/energy_systems/hvac_system.py new file mode 100644 index 00000000..4bc6edf3 --- /dev/null +++ b/city_model_structure/energy_systems/hvac_system.py @@ -0,0 +1,32 @@ +""" +HvacSystem module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import Union + + +class HvacSystem: + """ + HvacSystem class + """ + def __init__(self): + self._type = None + + @property + def type(self) -> Union[None, str]: + """ + Get hvac system type a thermal zone + :return: None or str + """ + return self._type + + @type.setter + def type(self, value): + """ + Set heating set point defined for a thermal zone + :param value: str + """ + if value is not None: + self._type = str(value) + diff --git a/city_model_structure/iot/concordia_energy_sensor.py b/city_model_structure/iot/concordia_energy_sensor.py new file mode 100644 index 00000000..afc7b930 --- /dev/null +++ b/city_model_structure/iot/concordia_energy_sensor.py @@ -0,0 +1,45 @@ +""" +Energy Sensor module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +""" + +import pandas as pd +from city_model_structure.iot.sensor import Sensor + + +class ConcordiaEnergySensor(Sensor): + """ + Concordia energy sensor. + """ + + def __init__(self, name): + super().__init__() + self._name = name + self._interval = 5 + self._interval_units = 'minutes' + self._type = 'ConcordiaEnergySensor' + self._units = 'kW' + self._measures = pd.DataFrame(columns=["Date time", "Energy consumption"]) + + @property + def measures(self) -> pd.DataFrame: + """ + Get sensor measures [yyyy-mm-dd, hh:mm:ss kW] + :return: DataFrame["Date time", "Energy consumption"] + """ + return self._measures + + @measures.deleter + def measures(self): + """ + Delete sensor measures + """ + self._measures.drop = None + + def add_period(self, measures): + """ + Add or update a period measures to the dataframe + """ + measures = self._measures.append(measures, ignore_index=True) + self._measures = measures.drop_duplicates('Date time', keep='last') diff --git a/city_model_structure/iot/concordia_gas_flow_sensor.py b/city_model_structure/iot/concordia_gas_flow_sensor.py new file mode 100644 index 00000000..86da31bd --- /dev/null +++ b/city_model_structure/iot/concordia_gas_flow_sensor.py @@ -0,0 +1,45 @@ +""" +Gas Flow Sensor module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import pandas as pd +from city_model_structure.iot.sensor import Sensor + + +class ConcordiaGasFlowSensor(Sensor): + """ + Concordia gas flow sensor. + """ + + def __init__(self, name): + super().__init__() + self._name = name + self._interval = 5 + self._interval_units = 'minutes' + self._type = 'ConcordiaGasFlowSensor' + self._units = 'm3' + self._measures = pd.DataFrame(columns=["Date time", "Gas Flow Cumulative Monthly"]) + + @property + def measures(self) -> pd.DataFrame: + """ + Get sensor measures [yyyy-mm-dd, hh:mm:ss m3] + :return: DataFrame["Date time", "Gas Flow Cumulative Monthly"] + """ + return self._measures + + @measures.deleter + def measures(self): + """ + Delete sensor measures + """ + self._measures.drop = None + + def add_period(self, measures): + """ + Add or update a period measures to the dataframe + """ + measures = self._measures.append(measures, ignore_index=True) + self._measures = measures.drop_duplicates('Date time', keep='last') diff --git a/city_model_structure/iot/concordia_temperature_sensor.py b/city_model_structure/iot/concordia_temperature_sensor.py new file mode 100644 index 00000000..9ae6079c --- /dev/null +++ b/city_model_structure/iot/concordia_temperature_sensor.py @@ -0,0 +1,45 @@ +""" +Temperature Sensor module +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +import pandas as pd +from city_model_structure.iot.sensor import Sensor + + +class ConcordiaTemperatureSensor(Sensor): + """ + Concordia temperature sensor. + """ + + def __init__(self, name): + super().__init__() + self._name = name + self._interval = 5 + self._interval_units = 'minutes' + self._type = 'ConcordiaTemperatureSensor' + self._units = 'Celsius' + self._measures = pd.DataFrame(columns=["Date time", "Temperature"]) + + @property + def measures(self) -> pd.DataFrame: + """ + Get sensor measures [yyyy-mm-dd, hh:mm:ss Celsius] + :return: DataFrame["Date time", "Temperature"] + """ + return self._measures + + @measures.deleter + def measures(self): + """ + Delete sensor measures + """ + self._measures.drop = None + + def add_period(self, measures): + """ + Add or update a period measures to the dataframe + """ + measures = self._measures.append(measures, ignore_index=True) + self._measures = measures.drop_duplicates('Date time', keep='last') diff --git a/city_model_structure/iot/sensor.py b/city_model_structure/iot/sensor.py index c3801749..4eceafd1 100644 --- a/city_model_structure/iot/sensor.py +++ b/city_model_structure/iot/sensor.py @@ -1,73 +1,76 @@ """ Sensor module SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca +Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ -import uuid -from city_model_structure.iot.sensor_measure import SensorMeasure -from city_model_structure.iot.sensor_type import SensorType +from helpers.location import Location class Sensor: """ Sensor abstract class """ - def __init__(self, sensor_id=None, model=None, sensor_type=None, indoor=False, board=None): - self._id = sensor_id - self._model = model - self._type = sensor_type - self._indoor = indoor - self._board = board - self._measures = [] + def __init__(self): + self._name = None + self._type = None + self._units = None + self._location = None @property - def id(self): + def name(self): """ - Get the sensor id a random uuid will be assigned if no ID was provided to the constructor - :return: Id + Get sensor name + :return: str """ - if self._id is None: - self._id = uuid.uuid4() - return self._id + if self._name is None: + raise ValueError('Undefined sensor name') + return self._name + + @name.setter + def name(self, value): + """ + Set sensor name + :param value: str + """ + if value is not None: + self._name = str(value) @property - def type(self) -> SensorType: + def type(self): """ Get sensor type - :return: SensorTypeEnum or Error + :return: str """ - if self._type is None: - raise ValueError('Unknown sensor type') return self._type @property - def model(self): + def units(self): """ - Get sensor model is any - :return: str or None + Get sensor units + :return: str """ - return self._model + return self._units @property - def board(self): + def location(self) -> Location: """ - Get sensor board if any - :return: str or None + Get sensor location + :return: Location """ - return self._model + return self._location + + @location.setter + def location(self, value): + """ + Set sensor location + :param value: Location + """ + self._location = value @property - def indoor(self): - """ - Get is the sensor it's located indoor or outdoor - :return: boolean - """ - return self._indoor - - @property - def measures(self) -> [SensorMeasure]: + def measures(self): """ Raises not implemented error """ - return self._measures + raise NotImplementedError diff --git a/city_model_structure/iot/sensor_measure.py b/city_model_structure/iot/sensor_measure.py deleted file mode 100644 index afdd40db..00000000 --- a/city_model_structure/iot/sensor_measure.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Sensor measure module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" - -class SensorMeasure: - def __init__(self, latitude, longitude, utc_timestamp, value): - self._latitude = latitude - self._longitude = longitude - self._utc_timestamp = utc_timestamp - self._value = value - - @property - def latitude(self): - """ - Get measure latitude - """ - return self._latitude - - @property - def longitude(self): - """ - Get measure longitude - """ - return self._longitude - - @property - def utc_timestamp(self): - """ - Get measure timestamp in utc - """ - return self._utc_timestamp - - @property - def value(self): - """ - Get sensor measure value - """ - return self._value \ No newline at end of file diff --git a/city_model_structure/iot/sensor_type.py b/city_model_structure/iot/sensor_type.py deleted file mode 100644 index 414b6f60..00000000 --- a/city_model_structure/iot/sensor_type.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Sensor type module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" - -from enum import Enum - -class SensorType(Enum): - HUMIDITY = 0 - TEMPERATURE = 1 - CO2 = 2 - NOISE = 3 - PRESSURE = 4 - DIRECT_RADIATION = 5 - DIFFUSE_RADIATION = 6 - GLOBAL_RADIATION = 7 - AIR_QUALITY = 8 - GAS_FLOW = 9 - ENERGY = 10 diff --git a/city_model_structure/iot/station.py b/city_model_structure/iot/station.py deleted file mode 100644 index 8a15e62d..00000000 --- a/city_model_structure/iot/station.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -Station -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2022 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca -""" -import uuid - -from city_model_structure.iot.sensor import Sensor - - -class Station: - def __init__(self, station_id=None, _mobile=False): - self._id = station_id - self._mobile = _mobile - self._sensors = [] - - @property - def id(self): - """ - Get the station id a random uuid will be assigned if no ID was provided to the constructor - :return: Id - """ - if self._id is None: - self._id = uuid.uuid4() - return self._id - - @property - def _mobile(self): - """ - Get if the station is mobile or not - :return: bool - """ - return self._mobile - - @property - def sensors(self) -> [Sensor]: - """ - Get the sensors belonging to the station - :return: [Sensor] - """ - return self._sensors diff --git a/city_model_structure/lca_material.py b/city_model_structure/lca_material.py deleted file mode 100644 index 6d73384b..00000000 --- a/city_model_structure/lca_material.py +++ /dev/null @@ -1,242 +0,0 @@ -""" -Material module -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Atiya atiya.atiya@mail.concordia.ca -Contributor Mohammad Reza mohammad.seyedabadi@mail.concordia.ca -""" - -from typing import Union - -class LcaMaterial: - def __init__(self): - self._id = None - self._type = None - self._name = None - self._density = None - self._density_unit = None - self._embodied_carbon = None - self._embodied_carbon_unit = None - self._recycling_ratio = None - self._company_recycling_ratio = None - self._onsite_recycling_ratio = None - self._landfilling_ratio = None - self._cost = None - self._cost_unit = None - - @property - def id(self): - """ - Get material id - :return: int - """ - return self._id - - @id.setter - def id(self, value): - """ - Set material id - :param value: int - """ - self._id = int(value) - - @property - def type(self): - """ - Get material type - :return: str - """ - return self._type - - @type.setter - def type(self, value): - """ - Set material type - :param value: string - """ - self._type = str(value) - - @property - def name(self): - """ - Get material name - :return: str - """ - return self._name - - @name.setter - def name(self, value): - """ - Set material name - :param value: string - """ - self._name = str(value) - - @property - def density(self) -> Union[None, float]: - """ - Get material density in kg/m3 - :return: None or float - """ - return self._density - - @density.setter - def density(self, value): - """ - Set material density - :param value: float - """ - if value is not None: - self._density = float(value) - - @property - def density_unit(self) -> Union[None, str]: - """ - Get material density unit - :return: None or string - """ - return self._density_unit - - @density_unit.setter - def density_unit(self, value): - """ - Set material density unit - :param value: string - """ - if value is not None: - self._density_unit = str(value) - - @property - def embodied_carbon(self) -> Union[None, float]: - """ - Get material embodied carbon - :return: None or float - """ - return self._embodied_carbon - - @embodied_carbon.setter - def embodied_carbon(self, value): - """ - Set material embodied carbon - :param value: float - """ - if value is not None: - self._embodied_carbon = float(value) - - @property - def embodied_carbon_unit(self) -> Union[None, str]: - """ - Get material embodied carbon unit - :return: None or string - """ - return self._embodied_carbon - - @embodied_carbon_unit.setter - def embodied_carbon_unit(self, value): - """ - Set material embodied carbon unit - :param value: string - """ - if value is not None: - self._embodied_carbon_unit = str(value) - - @property - def recycling_ratio(self) -> Union[None, float]: - """ - Get material recycling ratio - :return: None or float - """ - return self._recycling_ratio - - @recycling_ratio.setter - def recycling_ratio(self, value): - """ - Set material recycling ratio - :param value: float - """ - if value is not None: - self._recycling_ratio = float(value) - - @property - def onsite_recycling_ratio(self) -> Union[None, float]: - """ - Get material onsite recycling ratio - :return: None or float - """ - return self._onsite_recycling_ratio - - @onsite_recycling_ratio.setter - def onsite_recycling_ratio(self, value): - """ - Set material onsite recycling ratio - :param value: float - """ - if value is not None: - self._onsite_recycling_ratio = float(value) - - @property - def company_recycling_ratio(self) -> Union[None, float]: - """ - Get material company recycling ratio - :return: None or float - """ - return self._company_recycling_ratio - - @company_recycling_ratio.setter - def company_recycling_ratio(self, value): - """ - Set material company recycling ratio - :param value: float - """ - if value is not None: - self._company_recycling_ratio = float(value) - - @property - def landfilling_ratio(self) -> Union[None, float]: - """ - Get material landfilling ratio - :return: None or float - """ - return self._landfilling_ratio - - @landfilling_ratio.setter - def landfilling_ratio(self, value): - """ - Set material landfilling ratio - :param value: float - """ - if value is not None: - self._landfilling_ratio = float(value) - - @property - def cost(self) -> Union[None, float]: - """ - Get material cost - :return: None or float - """ - return self._cost - - @cost.setter - def cost(self, value): - """ - Set material cost - :param value: float - """ - if value is not None: - self._cost = float(value) - - @property - def cost_unit(self) -> Union[None, str]: - """ - Get material cost unit - :return: None or string - """ - return self._cost_unit - - @cost_unit.setter - def cost_unit(self, value): - """ - Set material cost unit - :param value: string - """ - if value is not None: - self._cost_unit = float(value) \ No newline at end of file diff --git a/config/configuration.ini b/config/configuration.ini index b2ddfb04..399649f1 100644 --- a/config/configuration.ini +++ b/config/configuration.ini @@ -11,3 +11,5 @@ comnet_occupancy_sensible_radiant = 0.1 comnet_plugs_latent = 0 comnet_plugs_convective = 0.75 comnet_plugs_radiant = 0.25 +convective_heat_transfer_coefficient_interior = 3.5 +convective_heat_transfer_coefficient_exterior = 20 diff --git a/data/construction/ca_constructions_reduced.xml b/data/construction/ca_constructions_reduced.xml index 38a217d9..42bf460e 100644 --- a/data/construction/ca_constructions_reduced.xml +++ b/data/construction/ca_constructions_reduced.xml @@ -105,84 +105,52 @@ #wall below grade 0.512 - 0 - 0 0.512 - 0 - 0 0.67 - 0 - 0 0.848 - 0 - 0 1.048 - 0 - 0 1.154 - 0 - 0 1.243 - 0 - 0 1.425 - 0 - 0 #slab on grade 0.512 - 0 - 0 0.67 - 0 - 0 0.67 - 0 - 0 0.848 - 0 - 0 0.848 - 0 - 0 1.05 - 0 - 0 1.154 - 0 - 0 1.154 - 0 - 0 diff --git a/data/construction/us_constructions.xml b/data/construction/us_constructions.xml index 2d4a27f8..b7a2ab51 100644 --- a/data/construction/us_constructions.xml +++ b/data/construction/us_constructions.xml @@ -39,7 +39,7 @@ - + 1.311 2240 836.8 @@ -47,14 +47,14 @@ 0.7 0.7 - + true 0.21648 0.9 0.7 0.8 - + 0.045 265 836.8 @@ -62,7 +62,7 @@ 0.7 0.7 - + 0.6918 1858 837 @@ -70,7 +70,7 @@ 0.92 0.92 - + 1.7296 2243 837 @@ -78,7 +78,7 @@ 0.65 0.65 - + 0.0432 91 837 @@ -86,7 +86,7 @@ 0.5 0.5 - + 0.16 784.9 830 @@ -94,7 +94,7 @@ 0.92 0.92 - + 0.11 544.62 1210 @@ -102,7 +102,7 @@ 0.78 0.78 - + 0.115 513 1255 @@ -110,7 +110,7 @@ 0.78 0.78 - + 0.1211 593 2510 @@ -118,7 +118,7 @@ 0.78 0.78 - + 0.049 265 836.8 @@ -126,14 +126,14 @@ 0.7 0.7 - + true 0.36256 0.9 0.7 0.7 - + 45.006 7680 418.4 @@ -141,7 +141,7 @@ 0.7 0.3 - + 0.16 1121.29 1460 @@ -149,28 +149,28 @@ 0.7 0.7 - + true 0.21648 0.9 0.7 0.8 - + true 0.21648 0.9 0.7 0.8 - + true 0.21648 0.9 0.7 0.8 - + 0.045 265 836.8 @@ -178,7 +178,7 @@ 0.7 0.7 - + 44.96 7688.86 410 diff --git a/data/greenery/ecore_greenery_catalog.xml b/data/greenery/ecore_greenery_catalog.xml deleted file mode 100644 index 41b7801c..00000000 --- a/data/greenery/ecore_greenery_catalog.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/data/life_cycle_assessment/lca_data.xml b/data/life_cycle_assessment/lca_data.xml index 6e231b60..29bb356d 100644 --- a/data/life_cycle_assessment/lca_data.xml +++ b/data/life_cycle_assessment/lca_data.xml @@ -1,394 +1,5 @@ - - - 1.8 - 560 - 0.8 - 0.3 - 0.7 - 0.2 - .... - - - 1.2 - 310 - 0.8 - 0.3 - 0.7 - 0.2 - .... - - - 2 - 3080 - 0.8 - 0.3 - 0.7 - 0.2 - .... - - - 1.4 - 300 - 0.8 - 0.3 - 0.7 - 0.2 - .... - - - 1.6 - 900 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.6 - 2340 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.6 - 1570 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.4 - 1840 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.3 - 410 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.3 - 160 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.3 - 170 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.3 - 230 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.4 - 240 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.4 - 280 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.3 - 170 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.2 - 440 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.58 - 2660 - 0.95 - 0 - 1 - 0.05 - .... - - - 2.58 - 5260 - 0.95 - 0 - 1 - 0.05 - .... - - - 0.06 - 1760 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.122 - 3080 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.028 - 3180 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.024 - 5140 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.1 - 6040 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.3 - 5380 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.032 - 2150 - 0.9 - 0 - 1 - 0.1 - .... - - - 0.9 - 3420 - 0.6 - 0 - 1 - 0.4 - .... - - - 0.7 - 1430 - 0.6 - 0 - 1 - 0.4 - .... - - - 0.65 - 2780 - 0.6 - 0 - 1 - 0.4 - .... - - - 0.72 - 2190 - 0.6 - 0 - 1 - 0.4 - .... - - - 1.43 - 1070 - 0 - 0 - 0 - 1 - .... - - - 1.43 - 240 - 0 - 0 - 0 - 1 - .... - - - 1.43 - 430 - 0 - 0 - 0 - 1 - .... - - - 1.43 - 340 - 0 - 0 - 0 - 1 - .... - - - 1.2 - 440 - 0.8 - 0 - 1 - 0.2 - .... - - - 2.1 - 1410 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.43 - 250 - 0 - 0 - 0 - 1 - .... - - - 1.44 - 1480 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.44 - 2220 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.27 - 3960 - 0.8 - 0 - 1 - 0.2 - .... - - - 1.15 - 760 - 0.8 - 0 - 1 - 0.2 - .... - - - 8 - 3160 - 0.98 - 0 - 1 - 0.02 - .... - - - 2.7 - 5370 - 0.98 - 0 - 1 - 0.02 - .... - - - 7.85 - 3910 - 0.98 - 0 - 1 - 0.02 - .... - - 0.32 @@ -556,4 +167,393 @@ 1.00000 + + + 1.8 + 560 + 0.8 + 0.3 + 0.7 + 0.2 + .... + + + 1.2 + 310 + 0.8 + 0.3 + 0.7 + 0.2 + .... + + + 2 + 3080 + 0.8 + 0.3 + 0.7 + 0.2 + .... + + + 1.4 + 300 + 0.8 + 0.3 + 0.7 + 0.2 + .... + + + 1.6 + 900 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.6 + 2340 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.6 + 1570 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.4 + 1840 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.3 + 410 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.3 + 160 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.3 + 170 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.3 + 230 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.4 + 240 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.4 + 280 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.3 + 170 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.2 + 440 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.58 + 2660 + 0.95 + 0 + 1 + 0.05 + .... + + + 2.58 + 5260 + 0.95 + 0 + 1 + 0.05 + .... + + + 0.06 + 1760 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.122 + 3080 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.028 + 3180 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.024 + 5140 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.1 + 6040 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.3 + 5380 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.032 + 2150 + 0.9 + 0 + 1 + 0.1 + .... + + + 0.9 + 3420 + 0.6 + 0 + 1 + 0.4 + .... + + + 0.7 + 1430 + 0.6 + 0 + 1 + 0.4 + .... + + + 0.65 + 2780 + 0.6 + 0 + 1 + 0.4 + .... + + + 0.72 + 2190 + 0.6 + 0 + 1 + 0.4 + .... + + + 1.43 + 1070 + 0 + 0 + 0 + 1 + .... + + + 1.43 + 240 + 0 + 0 + 0 + 1 + .... + + + 1.43 + 430 + 0 + 0 + 0 + 1 + .... + + + 1.43 + 340 + 0 + 0 + 0 + 1 + .... + + + 1.2 + 440 + 0.8 + 0 + 1 + 0.2 + .... + + + 2.1 + 1410 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.43 + 250 + 0 + 0 + 0 + 1 + .... + + + 1.44 + 1480 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.44 + 2220 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.27 + 3960 + 0.8 + 0 + 1 + 0.2 + .... + + + 1.15 + 760 + 0.8 + 0 + 1 + 0.2 + .... + + + 8 + 3160 + 0.98 + 0 + 1 + 0.02 + .... + + + 2.7 + 5370 + 0.98 + 0 + 1 + 0.02 + .... + + + 7.85 + 3910 + 0.98 + 0 + 1 + 0.02 + .... + + \ No newline at end of file diff --git a/data/usage/comnet_schedules_archetypes.xlsx b/data/usage/comnet_schedules_archetypes.xlsx new file mode 100644 index 00000000..8fce488c Binary files /dev/null and b/data/usage/comnet_schedules_archetypes.xlsx differ diff --git a/data/usage/de_library.xml b/data/usage/de_library.xml index 0580d70a..a13e323c 100644 --- a/data/usage/de_library.xml +++ b/data/usage/de_library.xml @@ -84,7 +84,7 @@ DIN 18599-10 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 0.0 + 20.0 20.0 20.0 20.0 20.0 20.0 20.0 @@ -117,7 +117,7 @@ 16.0 16.0 16.0 16.0 16.0 16.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 20.0 - 20.0 20.0 20.0 20.0 20.0 20.0 0.0 + 20.0 20.0 20.0 20.0 20.0 20.0 20.0 diff --git a/exports/formats/energy_ade.py b/exports/formats/energy_ade.py index 60c10af2..cf335397 100644 --- a/exports/formats/energy_ade.py +++ b/exports/formats/energy_ade.py @@ -329,7 +329,7 @@ class EnergyAde: }, 'energy:area': { '@uom': 'm2', - '#text': f'{thermal_boundary.area}' + '#text': f'{thermal_boundary.opaque_area}' }, 'energy:surfaceGeometry': { 'gml:MultiSurface': { diff --git a/helpers/configuration_helper.py b/helpers/configuration_helper.py index 85677ceb..51d9a3d8 100644 --- a/helpers/configuration_helper.py +++ b/helpers/configuration_helper.py @@ -105,3 +105,19 @@ class ConfigurationHelper: :return: 0.1 """ return self._config.getfloat('buildings', 'comnet_occupancy_sensible_radiant').real + + @property + def convective_heat_transfer_coefficient_interior(self) -> float: + """ + Get configured convective heat transfer coefficient for surfaces inside the building + :return: 3.5 + """ + return self._config.getfloat('buildings', 'convective_heat_transfer_coefficient_interior').real + + @property + def convective_heat_transfer_coefficient_exterior(self) -> float: + """ + Get configured convective heat transfer coefficient for surfaces outside the building + :return: 20 + """ + return self._config.getfloat('buildings', 'convective_heat_transfer_coefficient_exterior').real diff --git a/helpers/constants.py b/helpers/constants.py index 4ce228d4..b4291102 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -11,6 +11,7 @@ KELVIN = 273.15 HOUR_TO_MINUTES = 60 METERS_TO_FEET = 3.28084 BTU_H_TO_WATTS = 0.29307107 +KILO_WATTS_HOUR_TO_JULES = 3600000 # time SECOND = 'second' @@ -57,39 +58,66 @@ WINDOW = 'Window' DOOR = 'Door' SKYLIGHT = 'Skylight' -# todo: homogenize function and usage!! -# function -RESIDENTIAL = 'residential' -SFH = 'single family house' -MFH = 'multifamily house' -HOTEL = 'hotel' -HOSPITAL = 'hospital' -OUTPATIENT = 'outpatient' -COMMERCIAL = 'commercial' -STRIP_MALL = 'strip mall' -WAREHOUSE = 'warehouse' +# functions and usages +SINGLE_FAMILY_HOUSE = 'single family house' +MULTI_FAMILY_HOUSE = 'multifamily house' +ROW_HOSE = 'row house' +MID_RISE_APARTMENT = 'mid rise apartment' +HIGH_RISE_APARTMENT = 'high rise apartment' +SMALL_OFFICE = 'small office' +MEDIUM_OFFICE = 'medium office' +LARGE_OFFICE = 'large office' PRIMARY_SCHOOL = 'primary school' SECONDARY_SCHOOL = 'secondary school' -OFFICE = 'office' -LARGE_OFFICE = 'large office' -OFFICE_WORKSHOP = 'office/workshop' - -# usage -INDUSTRY = 'industry' -OFFICE_ADMINISTRATION = 'office and administration' -HEALTH_CARE = 'health care' -RETAIL = 'retail' -HALL = 'hall' -RESTAURANT = 'restaurant' +STAND_ALONE_RETAIL = 'stand alone retail' +HOSPITAL = 'hospital' +OUT_PATIENT_HEALTH_CARE = 'out-patient health care' +STRIP_MALL = 'strip mall' +SUPERMARKET = 'supermarket' +WAREHOUSE = 'warehouse' +QUICK_SERVICE_RESTAURANT = 'quick service restaurant' +FULL_SERVICE_RESTAURANT = 'full service restaurant' +SMALL_HOTEL = 'small hotel' +LARGE_HOTEL = 'large hotel' +RESIDENTIAL = 'residential' EDUCATION = 'education' +SCHOOL_WITHOUT_SHOWER = 'school without shower' +SCHOOL_WITH_SHOWER = 'school with shower' +RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD = 'retail shop without refrigerated food' +RETAIL_SHOP_WITH_REFRIGERATED_FOOD = 'retail shop with refrigerated food' +HOTEL = 'hotel' +HOTEL_MEDIUM_CLASS = 'hotel medium class' +DORMITORY = 'dormitory' +INDUSTRY = 'industry' +RESTAURANT = 'restaurant' +HEALTH_CARE = 'health care' +RETIREMENT_HOME_OR_ORPHANAGE = 'retirement home or orphanage' +OFFICE_AND_ADMINISTRATION = 'office and administration' +EVENT_LOCATION = 'event location' +HALL = 'hall' +SPORTS_LOCATION = 'sports location' +LABOR = 'labor' +GREEN_HOUSE = 'green house' +NON_HEATED = 'non-heated' LIGHTING = 'Lights' OCCUPANCY = 'Occupancy' -RECEPTACLE = 'Receptacle' +APPLIANCES = 'Appliances' HVAC_AVAILABILITY = 'HVAC Avail' INFILTRATION = 'Infiltration' COOLING_SET_POINT = 'ClgSetPt' HEATING_SET_POINT = 'HtgSetPt' -# todo: are any of these two the receptacle concept?? EQUIPMENT = 'Equipment' ACTIVITY = 'Activity' + +# Geometry +EPSILON = 0.0000001 + +# HVAC types +ONLY_HEATING = 'Heating' +ONLY_COOLING = 'Colling' +ONLY_VENTILATION = 'Ventilation' +HEATING_AND_VENTILATION = 'Heating and ventilation' +COOLING_AND_VENTILATION = 'Cooling and ventilation' +HEATING_AND_COLLING = 'Heating and cooling' +FULL_HVAC = 'Heating and cooling and ventilation' diff --git a/helpers/monthly_to_hourly_demand.py b/helpers/monthly_to_hourly_demand.py index 4251507f..029a42dd 100644 --- a/helpers/monthly_to_hourly_demand.py +++ b/helpers/monthly_to_hourly_demand.py @@ -5,7 +5,7 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import calendar as cal import pandas as pd -from city_model_structure.building_demand.occupants import Occupants +from city_model_structure.building_demand.occupant import Occupant import helpers.constants as cte @@ -34,7 +34,7 @@ class MonthlyToHourlyDemand: # todo: if these are data frames, then they should be called as (Occupancy should be in low case): # usage_zone.schedules.Occupancy # self._conditioning_seasons.heating - occupancy = Occupants().get_complete_year_schedule(usage_zone.schedules['Occupancy']) + occupancy = Occupant().get_complete_year_schedule(usage_zone.schedules['Occupancy']) heating_schedule = self._conditioning_seasons['heating'] hourly_heating = [] @@ -92,7 +92,7 @@ class MonthlyToHourlyDemand: for usage_zone in self._building.usage_zones: temp_set = float(usage_zone.cooling_setpoint) temp_back = 100 - occupancy = Occupants().get_complete_year_schedule(usage_zone.schedules['Occupancy']) + occupancy = Occupant().get_complete_year_schedule(usage_zone.schedules['Occupancy']) cooling_schedule = self._conditioning_seasons['cooling'] hourly_cooling = [] diff --git a/imports/construction/ca_physics_parameters.py b/imports/construction/ca_physics_parameters.py index 25f3e1de..6f4120fc 100644 --- a/imports/construction/ca_physics_parameters.py +++ b/imports/construction/ca_physics_parameters.py @@ -6,7 +6,6 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons import sys from imports.construction.helpers.construction_helper import ConstructionHelper from imports.construction.nrel_physics_interface import NrelPhysicsInterface -from imports.construction.helpers.storeys_generation import StoreysGeneration class CaPhysicsParameters(NrelPhysicsInterface): @@ -25,16 +24,24 @@ class CaPhysicsParameters(NrelPhysicsInterface): city = self._city # it is assumed that all buildings have the same archetypes' keys for building in city.buildings: - archetype = self._search_archetype(ConstructionHelper.nrcan_from_function(building.function), - building.year_of_construction) - if archetype is None: + try: + archetype = self._search_archetype(ConstructionHelper.nrcan_from_libs_function(building.function), + building.year_of_construction) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function: ' - f'{ConstructionHelper.nrcan_from_function(building.function)} ' + f'{ConstructionHelper.nrcan_from_libs_function(building.function)} ' f'and building year of construction: {building.year_of_construction}\n') - continue + return - self._create_storeys(building, archetype) - self._assign_values(building, archetype) + # 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._assign_values(building.internal_zones, archetype) + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._calculate_view_factors(thermal_zone) def _search_archetype(self, function, year_of_construction): for building_archetype in self._building_archetypes: @@ -47,33 +54,29 @@ class CaPhysicsParameters(NrelPhysicsInterface): return building_archetype return None - def _assign_values(self, building, archetype): - for thermal_zone in building.thermal_zones: - thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value - thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity - thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio - thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on - thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off - for thermal_boundary in thermal_zone.thermal_boundaries: - construction_type = ConstructionHelper.nrcan_construction_types[thermal_boundary.type] - thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) - thermal_boundary.u_value = thermal_boundary_archetype.overall_u_value - thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance - thermal_boundary.construction_name = thermal_boundary_archetype.construction_name - thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio - if thermal_boundary.thermal_openings is not None: - for thermal_opening in thermal_boundary.thermal_openings: - if thermal_boundary_archetype.thermal_opening is not None: - thermal_opening_archetype = thermal_boundary_archetype.thermal_opening - thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio - thermal_opening.g_value = thermal_opening_archetype.g_value - thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value - - @staticmethod - def _create_storeys(building, archetype): - building.average_storey_height = archetype.average_storey_height - building.storeys_above_ground = archetype.storeys_above_ground - storeys_generation = StoreysGeneration(building) - storeys = storeys_generation.storeys - building.storeys = storeys - storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries() + 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.additional_thermal_bridge_u_value + thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_type = ConstructionHelper.nrcan_construction_types[thermal_boundary.type] + thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) + thermal_boundary.u_value = thermal_boundary_archetype.overall_u_value + thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance + thermal_boundary.construction_name = thermal_boundary_archetype.construction_name + try: + thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio + except ValueError: + # This is the normal operation way when the windows are defined in the geometry + continue + if thermal_boundary.thermal_openings is not None: + for thermal_opening in thermal_boundary.thermal_openings: + if thermal_boundary_archetype.thermal_opening_archetype is not None: + thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype + thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio + thermal_opening.g_value = thermal_opening_archetype.g_value + thermal_opening.overall_u_value = thermal_opening_archetype.overall_u_value diff --git a/imports/construction/data_classes/nrel_building_achetype.py b/imports/construction/data_classes/building_achetype.py similarity index 89% rename from imports/construction/data_classes/nrel_building_achetype.py rename to imports/construction/data_classes/building_achetype.py index 3956fb98..fb35bad6 100644 --- a/imports/construction/data_classes/nrel_building_achetype.py +++ b/imports/construction/data_classes/building_achetype.py @@ -1,15 +1,15 @@ """ -NrelBuildingArchetype stores construction information by building archetypes +BuildingArchetype stores construction information by building archetypes SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ from typing import List -from imports.construction.data_classes.nrel_thermal_boundary_archetype import NrelThermalBoundaryArchetype +from imports.construction.data_classes.thermal_boundary_archetype import ThermalBoundaryArchetype -class NrelBuildingArchetype: +class BuildingArchetype: """ - NrelBuildingArchetype class + BuildingArchetype class """ def __init__(self, archetype_keys, average_storey_height, storeys_above_ground, effective_thermal_capacity, additional_thermal_bridge_u_value, indirectly_heated_area_ratio, infiltration_rate_system_off, @@ -89,7 +89,7 @@ class NrelBuildingArchetype: return self._infiltration_rate_system_on @property - def thermal_boundary_archetypes(self) -> List[NrelThermalBoundaryArchetype]: + def thermal_boundary_archetypes(self) -> List[ThermalBoundaryArchetype]: """ Get thermal boundary archetypes associated to the building archetype :return: list of boundary archetypes diff --git a/imports/construction/data_classes/nrel_layer_archetype.py b/imports/construction/data_classes/layer_archetype.py similarity index 67% rename from imports/construction/data_classes/nrel_layer_archetype.py rename to imports/construction/data_classes/layer_archetype.py index 46e0ea64..adad7439 100644 --- a/imports/construction/data_classes/nrel_layer_archetype.py +++ b/imports/construction/data_classes/layer_archetype.py @@ -1,17 +1,16 @@ """ -NrelLayerArchetype stores layer and materials information, complementing the NrelBuildingArchetype class +LayerArchetype stores layer and materials information, complementing the BuildingArchetype class SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -class NrelLayerArchetype: +class LayerArchetype: """ - NrelLayerArchetype class + LayerArchetype class """ def __init__(self, name, solar_absorptance, thermal_absorptance, visible_absorptance, thickness=None, - conductivity=None, specific_heat=None, density=None, no_mass=False, thermal_resistance=None, - lca_id=None): + conductivity=None, specific_heat=None, density=None, no_mass=False, thermal_resistance=None): self._thickness = thickness self._conductivity = conductivity self._specific_heat = specific_heat @@ -22,12 +21,11 @@ class NrelLayerArchetype: self._no_mass = no_mass self._name = name self._thermal_resistance = thermal_resistance - self._lca_id = lca_id @property def thickness(self): """ - Get nrel layer archetype thickness in meters + Get thickness in meters :return: float """ return self._thickness @@ -35,7 +33,7 @@ class NrelLayerArchetype: @property def conductivity(self): """ - Get nrel layer archetype conductivity in W/mK + Get conductivity in W/mK :return: float """ return self._conductivity @@ -43,7 +41,7 @@ class NrelLayerArchetype: @property def specific_heat(self): """ - Get nrel layer archetype conductivity in J/kgK + Get specific heat in J/kgK :return: float """ return self._specific_heat @@ -51,7 +49,7 @@ class NrelLayerArchetype: @property def density(self): """ - Get nrel layer archetype density in kg/m3 + Get density in kg/m3 :return: float """ return self._density @@ -59,7 +57,7 @@ class NrelLayerArchetype: @property def solar_absorptance(self): """ - Get nrel layer archetype solar absorptance + Get solar absorptance :return: float """ return self._solar_absorptance @@ -67,7 +65,7 @@ class NrelLayerArchetype: @property def thermal_absorptance(self): """ - Get nrel layer archetype thermal absorptance + Get thermal absorptance :return: float """ return self._thermal_absorptance @@ -75,7 +73,7 @@ class NrelLayerArchetype: @property def visible_absorptance(self): """ - Get nrel layer archetype visible absorptance + Get visible absorptance :return: float """ return self._visible_absorptance @@ -83,7 +81,7 @@ class NrelLayerArchetype: @property def no_mass(self) -> bool: """ - Get nrel layer archetype no mass flag + Get no mass flag :return: Boolean """ return self._no_mass @@ -91,7 +89,7 @@ class NrelLayerArchetype: @property def name(self): """ - Get nrel layer archetype name + Get name :return: str """ return self._name @@ -99,15 +97,7 @@ class NrelLayerArchetype: @property def thermal_resistance(self): """ - Get nrel layer archetype thermal resistance in m2K/W + Get thermal resistance in m2K/W :return: float """ return self._thermal_resistance - - @property - def lca_id(self): - """ - Get nrel lca_id equivalent for the material - :return: int - """ - return self._lca_id diff --git a/imports/construction/data_classes/nrel_thermal_boundary_archetype.py b/imports/construction/data_classes/nrel_thermal_boundary_archetype.py deleted file mode 100644 index 78f78cb6..00000000 --- a/imports/construction/data_classes/nrel_thermal_boundary_archetype.py +++ /dev/null @@ -1,99 +0,0 @@ -""" -NrelThermalBoundaryArchetype stores thermal boundaries information, complementing the NrelBuildingArchetype class -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from typing import List - -from imports.construction.data_classes.nrel_layer_archetype import NrelLayerArchetype -from imports.construction.data_classes.nrel_thermal_opening_archetype import NrelThermalOpeningArchetype - - -class NrelThermalBoundaryArchetype: - """ - NrelThermalBoundaryArchetype class - """ - def __init__(self, boundary_type, window_ratio, construction_name, layers, thermal_opening, - outside_solar_absorptance=None, outside_thermal_absorptance=None, outside_visible_absorptance=None, - overall_u_value=None): - self._boundary_type = boundary_type - self._outside_solar_absorptance = outside_solar_absorptance - self._outside_thermal_absorptance = outside_thermal_absorptance - self._outside_visible_absorptance = outside_visible_absorptance - self._window_ratio = window_ratio - self._construction_name = construction_name - self._overall_u_value = overall_u_value - self._layers = layers - self._thermal_opening = thermal_opening - - @property - def boundary_type(self): - """ - Get nrel thermal boundary archetype type - :return: str - """ - return self._boundary_type - - @property - def outside_solar_absorptance(self): - """ - Get nrel thermal boundary archetype outside solar absorptance - :return: float - """ - return self._outside_solar_absorptance - - @property - def outside_thermal_absorptance(self): - """ - Get nrel thermal boundary archetype outside thermal absorptance - :return: float - """ - return self._outside_thermal_absorptance - - @property - def outside_visible_absorptance(self): - """ - Get nrel thermal boundary archetype outside visible absorptance - :return: float - """ - return self._outside_visible_absorptance - - @property - def window_ratio(self): - """ - Get nrel thermal boundary archetype window ratio - :return: float - """ - return self._window_ratio - - @property - def construction_name(self): - """ - Get nrel thermal boundary archetype construction name - :return: str - """ - return self._construction_name - - @property - def layers(self) -> List[NrelLayerArchetype]: - """ - Get nrel thermal boundary archetype layers - :return: [NrelLayerArchetype] - """ - return self._layers - - @property - def thermal_opening(self) -> NrelThermalOpeningArchetype: - """ - Get nrel thermal boundary archetype - :return: NrelThermalOpeningArchetype - """ - return self._thermal_opening - - @property - def overall_u_value(self): - """ - Get nrel thermal boundary archetype overall U-value in W/m2K - :return: float - """ - return self._overall_u_value diff --git a/imports/construction/data_classes/thermal_boundary_archetype.py b/imports/construction/data_classes/thermal_boundary_archetype.py new file mode 100644 index 00000000..11aecf44 --- /dev/null +++ b/imports/construction/data_classes/thermal_boundary_archetype.py @@ -0,0 +1,136 @@ +""" +ThermalBoundaryArchetype stores thermal boundaries information, complementing the BuildingArchetype class +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from typing import List + +from imports.construction.data_classes.layer_archetype import LayerArchetype +from imports.construction.data_classes.thermal_opening_archetype import ThermalOpeningArchetype + + +class ThermalBoundaryArchetype: + """ + ThermalBoundaryArchetype class + """ + def __init__(self, boundary_type, window_ratio, construction_name, layers, thermal_opening, + outside_solar_absorptance=None, outside_thermal_absorptance=None, outside_visible_absorptance=None, + overall_u_value=None, shortwave_reflectance=None, inside_emissivity=None, alpha_coefficient=None, + radiative_coefficient=None): + self._boundary_type = boundary_type + self._outside_solar_absorptance = outside_solar_absorptance + self._outside_thermal_absorptance = outside_thermal_absorptance + self._outside_visible_absorptance = outside_visible_absorptance + self._window_ratio = window_ratio + self._construction_name = construction_name + self._overall_u_value = overall_u_value + self._layers = layers + self._thermal_opening_archetype = thermal_opening + self._shortwave_reflectance = shortwave_reflectance + self._inside_emissivity = inside_emissivity + self._alpha_coefficient = alpha_coefficient + self._radiative_coefficient = radiative_coefficient + + @property + def boundary_type(self): + """ + Get type + :return: str + """ + return self._boundary_type + + @property + def outside_solar_absorptance(self): + """ + Get outside solar absorptance + :return: float + """ + return self._outside_solar_absorptance + + @property + def outside_thermal_absorptance(self): + """ + Get outside thermal absorptance + :return: float + """ + return self._outside_thermal_absorptance + + @property + def outside_visible_absorptance(self): + """ + Get outside visible absorptance + :return: float + """ + return self._outside_visible_absorptance + + @property + def window_ratio(self): + """ + Get window ratio + :return: float + """ + return self._window_ratio + + @property + def construction_name(self): + """ + Get construction name + :return: str + """ + return self._construction_name + + @property + def layers(self) -> List[LayerArchetype]: + """ + Get layers + :return: [NrelLayerArchetype] + """ + return self._layers + + @property + def thermal_opening_archetype(self) -> ThermalOpeningArchetype: + """ + Get thermal opening archetype + :return: ThermalOpeningArchetype + """ + return self._thermal_opening_archetype + + @property + def overall_u_value(self): + """ + Get overall U-value in W/m2K + :return: float + """ + return self._overall_u_value + + @property + def shortwave_reflectance(self): + """ + Get shortwave reflectance + :return: float + """ + return self._shortwave_reflectance + + @property + def inside_emissivity(self): + """ + Get emissivity inside + :return: float + """ + return self._inside_emissivity + + @property + def alpha_coefficient(self): + """ + Get alpha coefficient + :return: float + """ + return self._alpha_coefficient + + @property + def radiative_coefficient(self): + """ + Get radiative coefficient + :return: float + """ + return self._radiative_coefficient diff --git a/imports/construction/data_classes/nrel_thermal_opening_archetype.py b/imports/construction/data_classes/thermal_opening_archetype.py similarity index 56% rename from imports/construction/data_classes/nrel_thermal_opening_archetype.py rename to imports/construction/data_classes/thermal_opening_archetype.py index 9f696116..e1966267 100644 --- a/imports/construction/data_classes/nrel_thermal_opening_archetype.py +++ b/imports/construction/data_classes/thermal_opening_archetype.py @@ -1,30 +1,34 @@ """ -NrelThermalOpeningArchetype stores thermal openings information, complementing the NrelBuildingArchetype class +ThermalOpeningArchetype stores thermal openings information, complementing the BuildingArchetype class SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -class NrelThermalOpeningArchetype: +class ThermalOpeningArchetype: """ - NrelThermalOpeningArchetype class + ThermalOpeningArchetype class """ def __init__(self, conductivity=None, frame_ratio=None, g_value=None, thickness=None, back_side_solar_transmittance_at_normal_incidence=None, - front_side_solar_transmittance_at_normal_incidence=None, shgc=None, overall_u_value=None): + front_side_solar_transmittance_at_normal_incidence=None, overall_u_value=None, + openable_ratio=None, inside_emissivity=None, alpha_coefficient=None, radiative_coefficient=None): self._conductivity = conductivity self._frame_ratio = frame_ratio self._g_value = g_value self._thickness = thickness self._back_side_solar_transmittance_at_normal_incidence = back_side_solar_transmittance_at_normal_incidence self._front_side_solar_transmittance_at_normal_incidence = front_side_solar_transmittance_at_normal_incidence - self._shgc = shgc self._overall_u_value = overall_u_value + self._openable_ratio = openable_ratio + self._inside_emissivity = inside_emissivity + self._alpha_coefficient = alpha_coefficient + self._radiative_coefficient = radiative_coefficient @property def conductivity(self): """ - Get nrel thermal opening archetype conductivity in W/mK + Get conductivity in W/mK :return: float """ return self._conductivity @@ -32,7 +36,7 @@ class NrelThermalOpeningArchetype: @property def frame_ratio(self): """ - Get nrel thermal opening archetype frame ratio + Get frame ratio :return: float """ return self._frame_ratio @@ -40,7 +44,7 @@ class NrelThermalOpeningArchetype: @property def g_value(self): """ - Get nrel thermal opening archetype g-value + Get g-value, also called shgc :return: float """ return self._g_value @@ -48,7 +52,7 @@ class NrelThermalOpeningArchetype: @property def thickness(self): """ - Get nrel thermal opening archetype thickness in meters + Get thickness in meters :return: float """ return self._thickness @@ -56,7 +60,7 @@ class NrelThermalOpeningArchetype: @property def back_side_solar_transmittance_at_normal_incidence(self): """ - Get nrel thermal opening archetype back side solar transmittance at normal incidence + Get back side solar transmittance at normal incidence :return: float """ return self._back_side_solar_transmittance_at_normal_incidence @@ -64,23 +68,47 @@ class NrelThermalOpeningArchetype: @property def front_side_solar_transmittance_at_normal_incidence(self): """ - Get nrel thermal opening archetype front side solar transmittance at normal incidence + Get front side solar transmittance at normal incidence :return: float """ return self._front_side_solar_transmittance_at_normal_incidence - @property - def shgc(self): - """ - Get nrel thermal opening archetype shcg - :return: float - """ - return self._shgc - @property def overall_u_value(self): """ - Get nrel thermal opening archetype overall U-value in W/m2K - :param value: float + Get overall U-value in W/m2K + :return: float """ return self._overall_u_value + + @property + def openable_ratio(self): + """ + Get openable ratio + :return: float + """ + return self._openable_ratio + + @property + def inside_emissivity(self): + """ + Get emissivity inside + :return: float + """ + return self._inside_emissivity + + @property + def alpha_coefficient(self): + """ + Get alpha coefficient + :return: float + """ + return self._alpha_coefficient + + @property + def radiative_coefficient(self): + """ + Get radiative coefficient + :return: float + """ + return self._radiative_coefficient diff --git a/imports/construction/helpers/construction_helper.py b/imports/construction/helpers/construction_helper.py index 0cf05326..ba93d614 100644 --- a/imports/construction/helpers/construction_helper.py +++ b/imports/construction/helpers/construction_helper.py @@ -12,27 +12,35 @@ class ConstructionHelper: Construction helper """ # NREL - function_to_nrel = { + _function_to_nrel = { cte.RESIDENTIAL: 'residential', - cte.SFH: 'single family house', - cte.MFH: 'multifamily house', - cte.HOTEL: 'hotel', - cte.HOSPITAL: 'hospital', - cte.OUTPATIENT: 'outpatient', - cte.COMMERCIAL: 'commercial', - cte.STRIP_MALL: 'strip mall', - cte.WAREHOUSE: 'warehouse', + cte.SINGLE_FAMILY_HOUSE: 'residential', + cte.MULTI_FAMILY_HOUSE: 'residential', + cte.ROW_HOSE: 'residential', + cte.MID_RISE_APARTMENT: 'midrise apartment', + cte.HIGH_RISE_APARTMENT: 'high-rise apartment', + cte.SMALL_OFFICE: 'small office', + cte.MEDIUM_OFFICE: 'medium office', + cte.LARGE_OFFICE: 'large office', cte.PRIMARY_SCHOOL: 'primary school', cte.SECONDARY_SCHOOL: 'secondary school', - cte.OFFICE: 'office', - cte.LARGE_OFFICE: 'large office' + cte.STAND_ALONE_RETAIL: 'stand-alone retail', + cte.HOSPITAL: 'hospital', + cte.OUT_PATIENT_HEALTH_CARE: 'outpatient healthcare', + cte.STRIP_MALL: 'strip mall', + cte.SUPERMARKET: 'supermarket', + cte.WAREHOUSE: 'warehouse', + cte.QUICK_SERVICE_RESTAURANT: 'quick service restaurant', + cte.FULL_SERVICE_RESTAURANT: 'full service restaurant', + cte.SMALL_HOTEL: 'small hotel', + cte.LARGE_HOTEL: 'large hotel' } - nrel_function_default_value = 'residential' - nrel_standards = { + + _nrel_standards = { 'ASHRAE Std189': 1, 'ASHRAE 90.1_2004': 2 } - reference_city_to_nrel_climate_zone = { + _reference_city_to_nrel_climate_zone = { 'Miami': 'ASHRAE_2004:1A', 'Houston': 'ASHRAE_2004:2A', 'Phoenix': 'ASHRAE_2004:2B', @@ -51,6 +59,7 @@ class ConstructionHelper: 'Fairbanks': 'ASHRAE_2004:8A' } nrel_window_types = [cte.WINDOW, cte.DOOR, cte.SKYLIGHT] + nrel_construction_types = { cte.WALL: 'exterior wall', cte.INTERIOR_WALL: 'interior wall', @@ -62,24 +71,32 @@ class ConstructionHelper: } # NRCAN - function_to_nrcan = { + _function_to_nrcan = { cte.RESIDENTIAL: 'residential', - cte.SFH: 'single family house', - cte.MFH: 'multifamily house', - cte.HOTEL: 'hotel', - cte.HOSPITAL: 'hospital', - cte.OUTPATIENT: 'outpatient', - cte.COMMERCIAL: 'commercial', - cte.STRIP_MALL: 'strip mall', - cte.WAREHOUSE: 'warehouse', - cte.PRIMARY_SCHOOL: 'primary school', - cte.SECONDARY_SCHOOL: 'secondary school', - cte.OFFICE: 'office', - cte.LARGE_OFFICE: 'large office', - cte.OFFICE_WORKSHOP: 'residential' + cte.SINGLE_FAMILY_HOUSE: 'residential', + cte.MULTI_FAMILY_HOUSE: 'residential', + cte.ROW_HOSE: 'residential', + cte.MID_RISE_APARTMENT: 'residential', + cte.HIGH_RISE_APARTMENT: 'residential', + cte.SMALL_OFFICE: cte.SMALL_OFFICE, + cte.MEDIUM_OFFICE: cte.MEDIUM_OFFICE, + cte.LARGE_OFFICE: cte.LARGE_OFFICE, + cte.PRIMARY_SCHOOL: cte.PRIMARY_SCHOOL, + cte.SECONDARY_SCHOOL: cte.SECONDARY_SCHOOL, + cte.STAND_ALONE_RETAIL: cte.STAND_ALONE_RETAIL, + cte.HOSPITAL: cte.HOSPITAL, + cte.OUT_PATIENT_HEALTH_CARE: cte.OUT_PATIENT_HEALTH_CARE, + cte.STRIP_MALL: cte.STRIP_MALL, + cte.SUPERMARKET: cte.SUPERMARKET, + cte.WAREHOUSE: cte.WAREHOUSE, + cte.QUICK_SERVICE_RESTAURANT: cte.QUICK_SERVICE_RESTAURANT, + cte.FULL_SERVICE_RESTAURANT: cte.FULL_SERVICE_RESTAURANT, + cte.SMALL_HOTEL: cte.SMALL_HOTEL, + cte.LARGE_HOTEL: cte.LARGE_HOTEL } - nrcan_function_default_value = 'residential' + nrcan_window_types = [cte.WINDOW] + nrcan_construction_types = { cte.WALL: 'wall', cte.GROUND_WALL: 'basement_wall', @@ -90,17 +107,16 @@ class ConstructionHelper: } @staticmethod - def nrel_from_function(function): + def nrel_from_libs_function(function): """ Get NREL function from the given internal function key :param function: str :return: str """ try: - return ConstructionHelper.function_to_nrel[function] + return ConstructionHelper._function_to_nrel[function] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default NREL function "residential"\n') - return ConstructionHelper.nrel_function_default_value + sys.stderr.write('Error: keyword not found.\n') @staticmethod def yoc_to_nrel_standard(year_of_construction): @@ -136,17 +152,16 @@ class ConstructionHelper: :return: str """ reference_city = ConstructionHelper.city_to_reference_city(city) - return ConstructionHelper.reference_city_to_nrel_climate_zone[reference_city] + return ConstructionHelper._reference_city_to_nrel_climate_zone[reference_city] @staticmethod - def nrcan_from_function(function): + def nrcan_from_libs_function(function): """ Get NREL function from the given internal function key :param function: str :return: str """ try: - return ConstructionHelper.function_to_nrcan[function] + return ConstructionHelper._function_to_nrcan[function] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default NRCAN function "residential"\n') - return ConstructionHelper.nrcan_function_default_value + sys.stderr.write('Error: keyword not found.\n') diff --git a/imports/construction/helpers/storeys_generation.py b/imports/construction/helpers/storeys_generation.py index ae89cebb..da0b4dcc 100644 --- a/imports/construction/helpers/storeys_generation.py +++ b/imports/construction/helpers/storeys_generation.py @@ -1,17 +1,19 @@ """ Storeys generation helper SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import sys import math import numpy as np +from typing import List from helpers import constants as cte from city_model_structure.attributes.polygon import Polygon from city_model_structure.attributes.point import Point from city_model_structure.building_demand.storey import Storey from city_model_structure.building_demand.surface import Surface +from city_model_structure.building_demand.thermal_zone import ThermalZone class StoreysGeneration: @@ -20,11 +22,14 @@ class StoreysGeneration: """ def __init__(self, building, divide_in_storeys=False): self._building = building + self._thermal_zones = [] self._divide_in_storeys = divide_in_storeys - self._storeys = None + self._floor_area = 0 + for ground in building.grounds: + self._floor_area += ground.perimeter_polygon.area @property - def storeys(self) -> [Storey]: + def thermal_zones(self) -> List[ThermalZone]: """ Get subsections of building trimesh by storey in case of no interiors defined :return: [Storey] @@ -34,7 +39,21 @@ class StoreysGeneration: self._building.storeys_above_ground) number_of_storeys = 1 if not self._divide_in_storeys or number_of_storeys == 1: - return [Storey('storey_0', self._building.surfaces, [None, None], self._building.volume)] + storey = Storey('storey_0', self._building.surfaces, [None, None], self._building.volume, self._floor_area) + for thermal_boundary in storey.thermal_boundaries: + if thermal_boundary.type != cte.INTERIOR_WALL or thermal_boundary.type != cte.INTERIOR_SLAB: + # external thermal boundary -> only one thermal zone + thermal_zones = [storey.thermal_zone] + else: + # internal thermal boundary -> two thermal zones + grad = np.rad2deg(thermal_boundary.inclination) + if grad >= 170: + thermal_zones = [storey.thermal_zone, storey.neighbours[0]] + else: + thermal_zones = [storey.neighbours[1], storey.thermal_zone] + thermal_boundary.thermal_zones = thermal_zones + + return [storey.thermal_zone] if number_of_storeys == 0: raise Exception('Number of storeys cannot be 0') @@ -79,14 +98,32 @@ class StoreysGeneration: surfaces_child.append(ceiling) volume = ceiling.area_above_ground * height total_volume += volume - storeys.append(Storey(name, surfaces_child, neighbours, volume)) + storeys.append(Storey(name, surfaces_child, neighbours, volume, self._floor_area)) name = 'storey_' + str(number_of_storeys - 1) neighbours = ['storey_' + str(number_of_storeys - 2), None] volume = self._building.volume - total_volume if volume < 0: raise Exception('Error in storeys creation, volume of last storey cannot be lower that 0') - storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume)) - return storeys + storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume, self._floor_area)) + + for storey in storeys: + for thermal_boundary in storey.thermal_boundaries: + if thermal_boundary.type != cte.INTERIOR_WALL or thermal_boundary.type != cte.INTERIOR_SLAB: + # external thermal boundary -> only one thermal zone + thermal_zones = [storey.thermal_zone] + else: + # internal thermal boundary -> two thermal zones + grad = np.rad2deg(thermal_boundary.inclination) + if grad >= 170: + thermal_zones = [storey.thermal_zone, storey.neighbours[0]] + else: + thermal_zones = [storey.neighbours[1], storey.thermal_zone] + thermal_boundary.thermal_zones = thermal_zones + + for storey in storeys: + self._thermal_zones.append(storey.thermal_zone) + + return self._thermal_zones @staticmethod def _calculate_number_storeys_and_height(average_storey_height, eave_height, storeys_above_ground): @@ -136,22 +173,3 @@ class StoreysGeneration: for point in points: array_points.append(point.coordinates) return np.array(array_points) - - def assign_thermal_zones_delimited_by_thermal_boundaries(self): - """ - During storeys creation, the thermal boundaries and zones are also created. - It is afterwards needed to define which zones are delimited by each thermal boundary - """ - for storey in self._building.storeys: - for thermal_boundary in storey.thermal_boundaries: - if thermal_boundary.surface.type != cte.INTERIOR_WALL or thermal_boundary.surface.type != cte.INTERIOR_SLAB: - # external thermal boundary -> only one thermal zone - thermal_zones = [storey.thermal_zone] - else: - # internal thermal boundary -> two thermal zones - grad = np.rad2deg(thermal_boundary.surface.inclination) - if grad >= 170: - thermal_zones = [storey.thermal_zone, storey.neighbours[0]] - else: - thermal_zones = [storey.neighbours[1], storey.thermal_zone] - thermal_boundary.thermal_zones = thermal_zones diff --git a/imports/construction/nrel_physics_interface.py b/imports/construction/nrel_physics_interface.py index f01297b8..6f08e536 100644 --- a/imports/construction/nrel_physics_interface.py +++ b/imports/construction/nrel_physics_interface.py @@ -6,10 +6,11 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import xmltodict -from imports.construction.data_classes.nrel_building_achetype import NrelBuildingArchetype as nba -from imports.construction.data_classes.nrel_thermal_boundary_archetype import NrelThermalBoundaryArchetype as ntba -from imports.construction.data_classes.nrel_thermal_opening_archetype import NrelThermalOpeningArchetype as ntoa -from imports.construction.data_classes.nrel_layer_archetype import NrelLayerArchetype as nla +from imports.construction.data_classes.building_achetype import BuildingArchetype as nba +from imports.construction.data_classes.thermal_boundary_archetype import ThermalBoundaryArchetype as ntba +from imports.construction.data_classes.thermal_opening_archetype import ThermalOpeningArchetype as ntoa +from imports.construction.data_classes.layer_archetype import LayerArchetype as nla +from imports.construction.helpers.storeys_generation import StoreysGeneration class NrelPhysicsInterface: @@ -71,7 +72,6 @@ class NrelPhysicsInterface: for current_layer in c_lib['layers']['layer']: material_lib = self._search_construction_type('material', current_layer['material']) name = material_lib['@name'] - lca_id = material_lib['@lca_id'] solar_absorptance = material_lib['solar_absorptance']['#text'] thermal_absorptance = material_lib['thermal_absorptance']['#text'] visible_absorptance = material_lib['visible_absorptance']['#text'] @@ -82,7 +82,7 @@ class NrelPhysicsInterface: if units != 'm2 K/W': raise Exception(f'thermal resistance units = {units}, expected m2 K/W') layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, no_mass=no_mass, - thermal_resistance=thermal_resistance, lca_id=lca_id) + thermal_resistance=thermal_resistance) else: thickness = current_layer['thickness']['#text'] units = current_layer['thickness']['@units'] @@ -101,7 +101,7 @@ class NrelPhysicsInterface: if units != 'kg/m3': raise Exception(f'density units = {units}, expected kg/m3') layer = nla(name, solar_absorptance, thermal_absorptance, visible_absorptance, thickness=thickness, - conductivity=conductivity, specific_heat=specific_heat, density=density, lca_id=lca_id) + conductivity=conductivity, specific_heat=specific_heat, density=density) layers.append(layer) thermal_opening = None @@ -150,10 +150,14 @@ class NrelPhysicsInterface: units = c_lib['overall_u_value']['@units'] if units != 'W/m2 K': raise Exception(f'overall U-value units = {units}, expected W/m2 K') - outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text'] - thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers, - thermal_opening, outside_solar_absorptance=outside_solar_absorptance, - overall_u_value=overall_u_value) + if 'outside_solar_absorptance' in c_lib: + outside_solar_absorptance = c_lib['outside_solar_absorptance']['#text'] + thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers, + thermal_opening, outside_solar_absorptance=outside_solar_absorptance, + overall_u_value=overall_u_value) + else: + thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers, + thermal_opening, overall_u_value=overall_u_value) else: thermal_boundary_archetype = ntba(construction_type, window_ratio, construction_name, layers, thermal_opening) @@ -178,8 +182,57 @@ class NrelPhysicsInterface: return thermal_boundary raise Exception('Construction type not found') + # todo: verify windows + @staticmethod + def _calculate_view_factors(thermal_zone): + """ + Get thermal zone view factors matrix + :return: [[float]] + """ + total_area = 0 + for thermal_boundary in thermal_zone.thermal_boundaries: + total_area += thermal_boundary.opaque_area + for thermal_opening in thermal_boundary.thermal_openings: + total_area += thermal_opening.area + + view_factors_matrix = [] + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = 0 + if thermal_boundary_1.id != thermal_boundary_2.id: + value = thermal_boundary_2.opaque_area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening in thermal_boundary.thermal_openings: + value = thermal_opening.area / (total_area - thermal_boundary_1.opaque_area) + values.append(value) + view_factors_matrix.append(values) + + for thermal_boundary_1 in thermal_zone.thermal_boundaries: + values = [] + for thermal_opening_1 in thermal_boundary_1.thermal_openings: + for thermal_boundary_2 in thermal_zone.thermal_boundaries: + value = thermal_boundary_2.opaque_area / (total_area - thermal_opening_1.area) + values.append(value) + for thermal_boundary in thermal_zone.thermal_boundaries: + for thermal_opening_2 in thermal_boundary.thermal_openings: + value = 0 + if thermal_opening_1.id != thermal_opening_2.id: + value = thermal_opening_2.area / (total_area - thermal_opening_1.area) + values.append(value) + view_factors_matrix.append(values) + thermal_zone.view_factors_matrix = view_factors_matrix + def enrich_buildings(self): """ Raise not implemented error """ raise NotImplementedError + + @staticmethod + def _create_storeys(building, archetype): + building.average_storey_height = archetype.average_storey_height + building.storeys_above_ground = archetype.storeys_above_ground + thermal_zones = StoreysGeneration(building).thermal_zones + building.internal_zones[0].thermal_zones = thermal_zones diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index 76d7b0f4..7a23b318 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -10,7 +10,6 @@ from imports.construction.nrel_physics_interface import NrelPhysicsInterface from imports.construction.helpers.construction_helper import ConstructionHelper from city_model_structure.building_demand.layer import Layer from city_model_structure.building_demand.material import Material -from imports.construction.helpers.storeys_generation import StoreysGeneration class UsPhysicsParameters(NrelPhysicsInterface): @@ -30,19 +29,27 @@ class UsPhysicsParameters(NrelPhysicsInterface): city = self._city # it is assumed that all buildings have the same archetypes' keys for building in city.buildings: - building_type = ConstructionHelper.nrel_from_function(building.function) + building_type = ConstructionHelper.nrel_from_libs_function(building.function) if building_type is None: return - archetype = self._search_archetype(building_type, - ConstructionHelper.yoc_to_nrel_standard(building.year_of_construction), - self._climate_zone) - if archetype is None: + try: + archetype = self._search_archetype(building_type, + ConstructionHelper.yoc_to_nrel_standard(building.year_of_construction), + self._climate_zone) + except KeyError: sys.stderr.write(f'Building {building.name} has unknown archetype for building function: {building.function} ' f'and building year of construction: {building.year_of_construction}\n') - continue + return - self._create_storeys(building, archetype) - self._assign_values(building, archetype) + # 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._assign_values(building.internal_zones, archetype) + for internal_zone in building.internal_zones: + for thermal_zone in internal_zone.thermal_zones: + self._calculate_view_factors(thermal_zone) def _search_archetype(self, building_type, standard, climate_zone): for building_archetype in self._building_archetypes: @@ -53,57 +60,50 @@ class UsPhysicsParameters(NrelPhysicsInterface): return building_archetype return None - def _assign_values(self, building, archetype): - for thermal_zone in building.thermal_zones: - thermal_zone.additional_thermal_bridge_u_value = archetype.additional_thermal_bridge_u_value - thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity - thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio - thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on - thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off - for thermal_boundary in thermal_zone.thermal_boundaries: - construction_type = ConstructionHelper.nrel_construction_types[thermal_boundary.type] - thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) - if thermal_boundary_archetype.outside_solar_absorptance is not 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.additional_thermal_bridge_u_value + thermal_zone.effective_thermal_capacity = archetype.effective_thermal_capacity + thermal_zone.indirectly_heated_area_ratio = archetype.indirectly_heated_area_ratio + thermal_zone.infiltration_rate_system_on = archetype.infiltration_rate_system_on + thermal_zone.infiltration_rate_system_off = archetype.infiltration_rate_system_off + for thermal_boundary in thermal_zone.thermal_boundaries: + construction_type = ConstructionHelper.nrel_construction_types[thermal_boundary.type] + thermal_boundary_archetype = self._search_construction_in_archetype(archetype, construction_type) thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance - thermal_boundary.outside_thermal_absorptance = thermal_boundary_archetype.outside_thermal_absorptance - thermal_boundary.outside_visible_absorptance = thermal_boundary_archetype.outside_visible_absorptance - thermal_boundary.construction_name = thermal_boundary_archetype.construction_name - thermal_boundary.window_ratio = thermal_boundary_archetype.window_ratio - thermal_boundary.layers = [] - for layer_archetype in thermal_boundary_archetype.layers: - layer = Layer() - layer.thickness = layer_archetype.thickness - material = Material() - material.name = layer_archetype.name - material.no_mass = layer_archetype.no_mass - material.density = layer_archetype.density - material.conductivity = layer_archetype.conductivity - material.specific_heat = layer_archetype.specific_heat - material.solar_absorptance = layer_archetype.solar_absorptance - material.thermal_absorptance = layer_archetype.thermal_absorptance - material.visible_absorptance = layer_archetype.visible_absorptance - material.thermal_resistance = layer_archetype.thermal_resistance - if layer_archetype.lca_id is not None: - material.lca_id = layer_archetype.lca_id - layer.material = material - thermal_boundary.layers.append(layer) - for thermal_opening in thermal_boundary.thermal_openings: - if thermal_boundary_archetype.thermal_opening is not None: - thermal_opening_archetype = thermal_boundary_archetype.thermal_opening - thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio - thermal_opening.g_value = thermal_opening_archetype.g_value - thermal_opening.conductivity = thermal_opening_archetype.conductivity - thermal_opening.thickness = thermal_opening_archetype.thickness - thermal_opening.back_side_solar_transmittance_at_normal_incidence = \ - thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence - thermal_opening.front_side_solar_transmittance_at_normal_incidence = \ - thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence - - @staticmethod - def _create_storeys(building, archetype): - building.average_storey_height = archetype.average_storey_height - building.storeys_above_ground = archetype.storeys_above_ground - storeys_generation = StoreysGeneration(building) - storeys = storeys_generation.storeys - building.storeys = storeys - storeys_generation.assign_thermal_zones_delimited_by_thermal_boundaries() + thermal_boundary.outside_thermal_absorptance = thermal_boundary_archetype.outside_thermal_absorptance + thermal_boundary.outside_visible_absorptance = thermal_boundary_archetype.outside_visible_absorptance + thermal_boundary.construction_name = thermal_boundary_archetype.construction_name + try: + thermal_boundary.window_ratio = thermal_boundary_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 thermal_boundary_archetype.layers: + layer = Layer() + layer.thickness = layer_archetype.thickness + material = Material() + material.name = layer_archetype.name + material.no_mass = layer_archetype.no_mass + material.density = layer_archetype.density + material.conductivity = layer_archetype.conductivity + material.specific_heat = layer_archetype.specific_heat + material.solar_absorptance = layer_archetype.solar_absorptance + material.thermal_absorptance = layer_archetype.thermal_absorptance + material.visible_absorptance = layer_archetype.visible_absorptance + material.thermal_resistance = layer_archetype.thermal_resistance + layer.material = material + thermal_boundary.layers.append(layer) + for thermal_opening in thermal_boundary.thermal_openings: + if thermal_boundary_archetype.thermal_opening_archetype is not None: + thermal_opening_archetype = thermal_boundary_archetype.thermal_opening_archetype + thermal_opening.frame_ratio = thermal_opening_archetype.frame_ratio + thermal_opening.g_value = thermal_opening_archetype.g_value + thermal_opening.conductivity = thermal_opening_archetype.conductivity + thermal_opening.thickness = thermal_opening_archetype.thickness + thermal_opening.back_side_solar_transmittance_at_normal_incidence = \ + thermal_opening_archetype.back_side_solar_transmittance_at_normal_incidence + thermal_opening.front_side_solar_transmittance_at_normal_incidence = \ + thermal_opening_archetype.front_side_solar_transmittance_at_normal_incidence diff --git a/imports/customized_imports/helpers/sanam_customized_usage_helper.py b/imports/customized_imports/helpers/sanam_customized_usage_helper.py index a0d981b8..7bf73f65 100644 --- a/imports/customized_imports/helpers/sanam_customized_usage_helper.py +++ b/imports/customized_imports/helpers/sanam_customized_usage_helper.py @@ -14,7 +14,7 @@ class SanamCustomizedUsageHelper: usage_to_customized = { cte.RESIDENTIAL: 'residential', cte.INDUSTRY: 'manufacturing', - cte.OFFICE_ADMINISTRATION: 'office', + cte.OFFICE_AND_ADMINISTRATION: 'office', cte.HOTEL: 'hotel', cte.HEALTH_CARE: 'health', cte.RETAIL: 'retail', diff --git a/imports/customized_imports/sanam_customized_usage_parameters.py b/imports/customized_imports/sanam_customized_usage_parameters.py index 6604a8e5..ea3cac62 100644 --- a/imports/customized_imports/sanam_customized_usage_parameters.py +++ b/imports/customized_imports/sanam_customized_usage_parameters.py @@ -6,9 +6,11 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons import sys import xmltodict -from imports.geometry.helpers.geometry_helper import GeometryHelper as gh -from imports.usage.data_classes.hft_usage_zone_archetype import HftUsageZoneArchetype as huza 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: @@ -18,15 +20,10 @@ class SanamCustomizedUsageParameters: 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')) - for zone_usage_type in self._archetypes['buildingUsageLibrary']['zoneUsageType']: - usage = zone_usage_type['id'] - usage_archetype = self._parse_zone_usage_type(usage, zone_usage_type) - self._usage_archetypes.append(usage_archetype) - - self._city = city def enrich_buildings(self): """ @@ -35,48 +32,59 @@ class SanamCustomizedUsageParameters: """ city = self._city for building in city.buildings: - archetype = self._search_archetype(building.function) # todo: building.function or other translation??????? - 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') + 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'{gh.usage_from_function(building.function)}\n') - continue - mix_usage = False - if not mix_usage: - # just one usage_zone - for usage_zone in building.usage_zones: - self._assign_values(usage_zone, archetype, height) + f'{libs_usage}\n') + return - def _search_archetype(self, building_usage): - for building_archetype in self._usage_archetypes: - if building_archetype.usage == building_usage: - return building_archetype + 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, height): + def _assign_values(usage_zone, archetype, volume_per_area): usage_zone.usage = archetype.usage - # 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. - if archetype.occupancy_density is not None: - usage_zone.occupancy_density = archetype.occupancy_density - archetype_mechanical_air_change = float(archetype.mechanical_air_change) * float(usage_zone.occupancy_density) \ - * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET**3 / height - usage_zone.mechanical_air_change = archetype_mechanical_air_change + 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(usage_zone.occupancy.occupancy_density) * cte.METERS_TO_FEET ** 2 \ + * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET ** 3 / volume_per_area + usage_zone.mechanical_air_change = archetype_mechanical_air_change @staticmethod - def _parse_zone_usage_type(usage, zone_usage_type): - mechanical_air_change = zone_usage_type['endUses']['ventilation']['minimumVentilationRate']['#text'] - if 'occupancy' in zone_usage_type: - occupancy_density = zone_usage_type['occupancy']['occupancyDensity']['#text'] - usage_zone_archetype = huza(usage=usage, occupancy_density=occupancy_density, - mechanical_air_change=mechanical_air_change) - else: - usage_zone_archetype = huza(usage=usage, mechanical_air_change=mechanical_air_change) + 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 index bca8a87c..0a072f89 100644 --- a/imports/customized_imports_factory.py +++ b/imports/customized_imports_factory.py @@ -17,10 +17,6 @@ class CustomizedImportsFactory: self._importer_class = importer_class self._city = city self._base_path = base_path - for building in city.buildings: - if len(building.thermal_zones) == 0: - raise Exception('It seems that the customized imports factory is being called before the construction factory. ' - 'Please ensure that the construction factory is called first.') def enrich(self): """ diff --git a/imports/geometry/citygml.py b/imports/geometry/citygml.py index 847c2bef..09803d55 100644 --- a/imports/geometry/citygml.py +++ b/imports/geometry/citygml.py @@ -83,7 +83,7 @@ class CityGml: surfaces = CityGmlLod2(city_object).surfaces else: raise NotImplementedError("Not supported level of detail") - return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, []) + return Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) def _create_parts_consisting_building(self, city_object): name = city_object['@id'] diff --git a/imports/geometry/helpers/geometry_helper.py b/imports/geometry/helpers/geometry_helper.py index 9c432e15..7307673a 100644 --- a/imports/geometry/helpers/geometry_helper.py +++ b/imports/geometry/helpers/geometry_helper.py @@ -12,292 +12,273 @@ class GeometryHelper: Geometry helper """ # function - pluto_to_function = { - 'A0': 'single family house', - 'A1': 'single family house', - 'A2': 'single family house', - 'A3': 'single family house', - 'A4': 'single family house', - 'A5': 'single family house', - 'A6': 'single family house', - 'A7': 'single family house', - 'A8': 'single family house', - 'A9': 'single family house', - 'B1': 'multifamily house', - 'B2': 'multifamily house', - 'B3': 'multifamily house', - 'B9': 'multifamily house', - 'C0': 'residential', - 'C1': 'residential', - 'C2': 'residential', - 'C3': 'residential', - 'C4': 'residential', - 'C5': 'residential', - 'C6': 'residential', - 'C7': 'residential', - 'C8': 'residential', - 'C9': 'residential', - 'D0': 'residential', - 'D1': 'residential', - 'D2': 'residential', - 'D3': 'residential', - 'D4': 'residential', - 'D5': 'residential', - 'D6': 'residential', - 'D7': 'residential', - 'D8': 'residential', - 'D9': 'residential', - 'E1': 'warehouse', - 'E3': 'warehouse', - 'E4': 'warehouse', - 'E5': 'warehouse', - 'E7': 'warehouse', - 'E9': 'warehouse', - 'F1': 'warehouse', - 'F2': 'warehouse', - 'F4': 'warehouse', - 'F5': 'warehouse', - 'F8': 'warehouse', - 'F9': 'warehouse', - 'G0': 'office', - 'G1': 'office', - 'G2': 'office', - 'G3': 'office', - 'G4': 'office', - 'G5': 'office', - 'G6': 'office', - 'G7': 'office', - 'G8': 'office', - 'G9': 'office', - 'H1': 'hotel', - 'H2': 'hotel', - 'H3': 'hotel', - 'H4': 'hotel', - 'H5': 'hotel', - 'H6': 'hotel', - 'H7': 'hotel', - 'H8': 'hotel', - 'H9': 'hotel', - 'HB': 'hotel', - 'HH': 'hotel', - 'HR': 'hotel', - 'HS': 'hotel', - 'I1': 'hospital', - 'I2': 'outpatient', - 'I3': 'outpatient', - 'I4': 'residential', - 'I5': 'outpatient', - 'I6': 'outpatient', - 'I7': 'outpatient', - 'I9': 'outpatient', - 'J1': 'large office', - 'J2': 'large office', - 'J3': 'large office', - 'J4': 'large office', - 'J5': 'large office', - 'J6': 'large office', - 'J7': 'large office', - 'J8': 'large office', - 'J9': 'large office', - 'K1': 'strip mall', - 'K2': 'strip mall', - 'K3': 'strip mall', - 'K4': 'residential', - 'K5': 'restaurant', - 'K6': 'commercial', - 'K7': 'commercial', - 'K8': 'commercial', - 'K9': 'commercial', - 'L1': 'residential', - 'L2': 'residential', - 'L3': 'residential', - 'L8': 'residential', - 'L9': 'residential', - 'M1': 'large office', - 'M2': 'large office', - 'M3': 'large office', - 'M4': 'large office', - 'M9': 'large office', - 'N1': 'residential', - 'N2': 'residential', - 'N3': 'residential', - 'N4': 'residential', - 'N9': 'residential', - 'O1': 'office', - 'O2': 'office', - 'O3': 'office', - 'O4': 'office', - 'O5': 'office', - 'O6': 'office', - 'O7': 'office', - 'O8': 'office', - 'O9': 'office', - 'P1': 'large office', - 'P2': 'hotel', - 'P3': 'office', - 'P4': 'office', - 'P5': 'office', - 'P6': 'office', - 'P7': 'large office', - 'P8': 'large office', - 'P9': 'office', - 'Q0': 'office', - 'Q1': 'office', - 'Q2': 'office', - 'Q3': 'office', - 'Q4': 'office', - 'Q5': 'office', - 'Q6': 'office', - 'Q7': 'office', - 'Q8': 'office', - 'Q9': 'office', - 'R0': 'residential', - 'R1': 'residential', - 'R2': 'residential', - 'R3': 'residential', - 'R4': 'residential', - 'R5': 'residential', - 'R6': 'residential', - 'R7': 'residential', - 'R8': 'residential', - 'R9': 'residential', - 'RA': 'residential', - 'RB': 'residential', - 'RC': 'residential', - 'RD': 'residential', - 'RG': 'residential', - 'RH': 'residential', - 'RI': 'residential', - 'RK': 'residential', - 'RM': 'residential', - 'RR': 'residential', - 'RS': 'residential', - 'RW': 'residential', - 'RX': 'residential', - 'RZ': 'residential', - 'S0': 'residential', - 'S1': 'residential', - 'S2': 'residential', - 'S3': 'residential', - 'S4': 'residential', - 'S5': 'residential', - 'S9': 'residential', - 'T1': 'na', - 'T2': 'na', - 'T9': 'na', - 'U0': 'warehouse', - 'U1': 'warehouse', - 'U2': 'warehouse', - 'U3': 'warehouse', - 'U4': 'warehouse', - 'U5': 'warehouse', - 'U6': 'warehouse', - 'U7': 'warehouse', - 'U8': 'warehouse', - 'U9': 'warehouse', - 'V0': 'na', - 'V1': 'na', - 'V2': 'na', - 'V3': 'na', - 'V4': 'na', - 'V5': 'na', - 'V6': 'na', - 'V7': 'na', - 'V8': 'na', - 'V9': 'na', - 'W1': 'primary school', - 'W2': 'primary school', - 'W3': 'secondary school', - 'W4': 'secondary school', - 'W5': 'secondary school', - 'W6': 'secondary school', - 'W7': 'secondary school', - 'W8': 'primary school', - 'W9': 'secondary school', - 'Y1': 'large office', - 'Y2': 'large office', - 'Y3': 'large office', - 'Y4': 'large office', - 'Y5': 'large office', - 'Y6': 'large office', - 'Y7': 'large office', - 'Y8': 'large office', - 'Y9': 'large office', - 'Z0': 'na', - 'Z1': 'large office', - 'Z2': 'na', - 'Z3': 'na', - 'Z4': 'na', - 'Z5': 'na', - 'Z6': 'na', - 'Z7': 'na', - 'Z8': 'na', - 'Z9': 'na' + _pluto_to_function = { + 'A0': cte.SINGLE_FAMILY_HOUSE, + 'A1': cte.SINGLE_FAMILY_HOUSE, + 'A2': cte.SINGLE_FAMILY_HOUSE, + 'A3': cte.SINGLE_FAMILY_HOUSE, + 'A4': cte.SINGLE_FAMILY_HOUSE, + 'A5': cte.SINGLE_FAMILY_HOUSE, + 'A6': cte.SINGLE_FAMILY_HOUSE, + 'A7': cte.SINGLE_FAMILY_HOUSE, + 'A8': cte.SINGLE_FAMILY_HOUSE, + 'A9': cte.SINGLE_FAMILY_HOUSE, + 'B1': cte.MULTI_FAMILY_HOUSE, + 'B2': cte.MULTI_FAMILY_HOUSE, + 'B3': cte.MULTI_FAMILY_HOUSE, + 'B9': cte.MULTI_FAMILY_HOUSE, + 'C0': cte.RESIDENTIAL, + 'C1': cte.RESIDENTIAL, + 'C2': cte.RESIDENTIAL, + 'C3': cte.RESIDENTIAL, + 'C4': cte.RESIDENTIAL, + 'C5': cte.RESIDENTIAL, + 'C6': cte.RESIDENTIAL, + 'C7': cte.RESIDENTIAL, + 'C8': cte.RESIDENTIAL, + 'C9': cte.RESIDENTIAL, + 'D0': cte.RESIDENTIAL, + 'D1': cte.RESIDENTIAL, + 'D2': cte.RESIDENTIAL, + 'D3': cte.RESIDENTIAL, + 'D4': cte.RESIDENTIAL, + 'D5': cte.RESIDENTIAL, + 'D6': cte.RESIDENTIAL, + 'D7': cte.RESIDENTIAL, + 'D8': cte.RESIDENTIAL, + 'D9': cte.RESIDENTIAL, + 'E1': cte.WAREHOUSE, + 'E3': cte.WAREHOUSE, + 'E4': cte.WAREHOUSE, + 'E5': cte.WAREHOUSE, + 'E7': cte.WAREHOUSE, + 'E9': cte.WAREHOUSE, + 'F1': cte.WAREHOUSE, + 'F2': cte.WAREHOUSE, + 'F4': cte.WAREHOUSE, + 'F5': cte.WAREHOUSE, + 'F8': cte.WAREHOUSE, + 'F9': cte.WAREHOUSE, + 'G0': cte.SMALL_OFFICE, + 'G1': cte.SMALL_OFFICE, + 'G2': cte.SMALL_OFFICE, + 'G3': cte.SMALL_OFFICE, + 'G4': cte.SMALL_OFFICE, + 'G5': cte.SMALL_OFFICE, + 'G6': cte.SMALL_OFFICE, + 'G7': cte.SMALL_OFFICE, + 'G8': cte.SMALL_OFFICE, + 'G9': cte.SMALL_OFFICE, + 'H1': cte.HOTEL, + 'H2': cte.HOTEL, + 'H3': cte.HOTEL, + 'H4': cte.HOTEL, + 'H5': cte.HOTEL, + 'H6': cte.HOTEL, + 'H7': cte.HOTEL, + 'H8': cte.HOTEL, + 'H9': cte.HOTEL, + 'HB': cte.HOTEL, + 'HH': cte.HOTEL, + 'HR': cte.HOTEL, + 'HS': cte.HOTEL, + 'I1': cte.HOSPITAL, + 'I2': cte.OUT_PATIENT_HEALTH_CARE, + 'I3': cte.OUT_PATIENT_HEALTH_CARE, + 'I4': cte.RESIDENTIAL, + 'I5': cte.OUT_PATIENT_HEALTH_CARE, + 'I6': cte.OUT_PATIENT_HEALTH_CARE, + 'I7': cte.OUT_PATIENT_HEALTH_CARE, + 'I9': cte.OUT_PATIENT_HEALTH_CARE, + 'J1': cte.LARGE_OFFICE, + 'J2': cte.LARGE_OFFICE, + 'J3': cte.LARGE_OFFICE, + 'J4': cte.LARGE_OFFICE, + 'J5': cte.LARGE_OFFICE, + 'J6': cte.LARGE_OFFICE, + 'J7': cte.LARGE_OFFICE, + 'J8': cte.LARGE_OFFICE, + 'J9': cte.LARGE_OFFICE, + 'K1': cte.STRIP_MALL, + 'K2': cte.STRIP_MALL, + 'K3': cte.STRIP_MALL, + 'K4': cte.RESIDENTIAL, + 'K5': cte.RESTAURANT, + 'K6': cte.SUPERMARKET, + 'K7': cte.SUPERMARKET, + 'K8': cte.SUPERMARKET, + 'K9': cte.SUPERMARKET, + 'L1': cte.RESIDENTIAL, + 'L2': cte.RESIDENTIAL, + 'L3': cte.RESIDENTIAL, + 'L8': cte.RESIDENTIAL, + 'L9': cte.RESIDENTIAL, + 'M1': cte.LARGE_OFFICE, + 'M2': cte.LARGE_OFFICE, + 'M3': cte.LARGE_OFFICE, + 'M4': cte.LARGE_OFFICE, + 'M9': cte.LARGE_OFFICE, + 'N1': cte.RESIDENTIAL, + 'N2': cte.RESIDENTIAL, + 'N3': cte.RESIDENTIAL, + 'N4': cte.RESIDENTIAL, + 'N9': cte.RESIDENTIAL, + 'O1': cte.SMALL_OFFICE, + 'O2': cte.SMALL_OFFICE, + 'O3': cte.SMALL_OFFICE, + 'O4': cte.SMALL_OFFICE, + 'O5': cte.SMALL_OFFICE, + 'O6': cte.SMALL_OFFICE, + 'O7': cte.SMALL_OFFICE, + 'O8': cte.SMALL_OFFICE, + 'O9': cte.SMALL_OFFICE, + 'P1': cte.LARGE_OFFICE, + 'P2': cte.HOTEL, + 'P3': cte.SMALL_OFFICE, + 'P4': cte.SMALL_OFFICE, + 'P5': cte.SMALL_OFFICE, + 'P6': cte.SMALL_OFFICE, + 'P7': cte.LARGE_OFFICE, + 'P8': cte.LARGE_OFFICE, + 'P9': cte.SMALL_OFFICE, + 'Q0': cte.SMALL_OFFICE, + 'Q1': cte.SMALL_OFFICE, + 'Q2': cte.SMALL_OFFICE, + 'Q3': cte.SMALL_OFFICE, + 'Q4': cte.SMALL_OFFICE, + 'Q5': cte.SMALL_OFFICE, + 'Q6': cte.SMALL_OFFICE, + 'Q7': cte.SMALL_OFFICE, + 'Q8': cte.SMALL_OFFICE, + 'Q9': cte.SMALL_OFFICE, + 'R0': cte.RESIDENTIAL, + 'R1': cte.RESIDENTIAL, + 'R2': cte.RESIDENTIAL, + 'R3': cte.RESIDENTIAL, + 'R4': cte.RESIDENTIAL, + 'R5': cte.RESIDENTIAL, + 'R6': cte.RESIDENTIAL, + 'R7': cte.RESIDENTIAL, + 'R8': cte.RESIDENTIAL, + 'R9': cte.RESIDENTIAL, + 'RA': cte.RESIDENTIAL, + 'RB': cte.RESIDENTIAL, + 'RC': cte.RESIDENTIAL, + 'RD': cte.RESIDENTIAL, + 'RG': cte.RESIDENTIAL, + 'RH': cte.RESIDENTIAL, + 'RI': cte.RESIDENTIAL, + 'RK': cte.RESIDENTIAL, + 'RM': cte.RESIDENTIAL, + 'RR': cte.RESIDENTIAL, + 'RS': cte.RESIDENTIAL, + 'RW': cte.RESIDENTIAL, + 'RX': cte.RESIDENTIAL, + 'RZ': cte.RESIDENTIAL, + 'S0': cte.RESIDENTIAL, + 'S1': cte.RESIDENTIAL, + 'S2': cte.RESIDENTIAL, + 'S3': cte.RESIDENTIAL, + 'S4': cte.RESIDENTIAL, + 'S5': cte.RESIDENTIAL, + 'S9': cte.RESIDENTIAL, + 'U0': cte.WAREHOUSE, + 'U1': cte.WAREHOUSE, + 'U2': cte.WAREHOUSE, + 'U3': cte.WAREHOUSE, + 'U4': cte.WAREHOUSE, + 'U5': cte.WAREHOUSE, + 'U6': cte.WAREHOUSE, + 'U7': cte.WAREHOUSE, + 'U8': cte.WAREHOUSE, + 'U9': cte.WAREHOUSE, + 'W1': cte.PRIMARY_SCHOOL, + 'W2': cte.PRIMARY_SCHOOL, + 'W3': cte.SECONDARY_SCHOOL, + 'W4': cte.SECONDARY_SCHOOL, + 'W5': cte.SECONDARY_SCHOOL, + 'W6': cte.SECONDARY_SCHOOL, + 'W7': cte.SECONDARY_SCHOOL, + 'W8': cte.PRIMARY_SCHOOL, + 'W9': cte.SECONDARY_SCHOOL, + 'Y1': cte.LARGE_OFFICE, + 'Y2': cte.LARGE_OFFICE, + 'Y3': cte.LARGE_OFFICE, + 'Y4': cte.LARGE_OFFICE, + 'Y5': cte.LARGE_OFFICE, + 'Y6': cte.LARGE_OFFICE, + 'Y7': cte.LARGE_OFFICE, + 'Y8': cte.LARGE_OFFICE, + 'Y9': cte.LARGE_OFFICE, + 'Z1': cte.LARGE_OFFICE } - hft_to_function = { + _hft_to_function = { 'residential': cte.RESIDENTIAL, - 'single family house': cte.SFH, - 'multifamily house': cte.MFH, + 'single family house': cte.SINGLE_FAMILY_HOUSE, + 'multifamily house': cte.MULTI_FAMILY_HOUSE, 'hotel': cte.HOTEL, 'hospital': cte.HOSPITAL, - 'outpatient': cte.OUTPATIENT, - 'commercial': cte.COMMERCIAL, + 'outpatient': cte.OUT_PATIENT_HEALTH_CARE, + 'commercial': cte.SUPERMARKET, 'strip mall': cte.STRIP_MALL, 'warehouse': cte.WAREHOUSE, 'primary school': cte.PRIMARY_SCHOOL, 'secondary school': cte.SECONDARY_SCHOOL, - 'office': cte.OFFICE, + 'office': cte.MEDIUM_OFFICE, 'large office': cte.LARGE_OFFICE } # usage - function_to_usage = { - 'full service restaurant': 'restaurant', - 'highrise apartment': cte.RESIDENTIAL, - 'hospital': 'health care', - 'large hotel': 'hotel', - 'large office': 'office and administration', - 'medium office': 'office and administration', - 'midrise apartment': cte.RESIDENTIAL, - 'outpatient healthcare': 'health care', - 'primary school': 'education', - 'quick service restaurant': 'restaurant', - 'secondary school': 'education', - 'small hotel': 'hotel', - 'small office': 'office and administration', - 'stand alone retail': 'retail', - 'strip mall': 'hall', - 'supermarket': 'retail', - 'warehouse': 'industry', - 'residential': cte.RESIDENTIAL + _function_to_usage = { + cte.RESIDENTIAL: cte.RESIDENTIAL, + cte.SINGLE_FAMILY_HOUSE: cte.SINGLE_FAMILY_HOUSE, + cte.MULTI_FAMILY_HOUSE: cte.MULTI_FAMILY_HOUSE, + cte.ROW_HOSE: cte.RESIDENTIAL, + cte.MID_RISE_APARTMENT: cte.RESIDENTIAL, + cte.HIGH_RISE_APARTMENT: cte.RESIDENTIAL, + cte.SMALL_OFFICE: cte.OFFICE_AND_ADMINISTRATION, + cte.MEDIUM_OFFICE: cte.OFFICE_AND_ADMINISTRATION, + cte.LARGE_OFFICE: cte.OFFICE_AND_ADMINISTRATION, + cte.PRIMARY_SCHOOL: cte.EDUCATION, + cte.SECONDARY_SCHOOL: cte.EDUCATION, + cte.STAND_ALONE_RETAIL: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + cte.HOSPITAL: cte.HEALTH_CARE, + cte.OUT_PATIENT_HEALTH_CARE: cte.HEALTH_CARE, + cte.STRIP_MALL: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + cte.SUPERMARKET: cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, + cte.WAREHOUSE: cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, + cte.QUICK_SERVICE_RESTAURANT: cte.RESTAURANT, + cte.FULL_SERVICE_RESTAURANT: cte.RESTAURANT, + cte.SMALL_HOTEL: cte.HOTEL, + cte.LARGE_HOTEL: cte.HOTEL } @staticmethod - def function_from_hft(building_hft_function): + def libs_function_from_hft(building_hft_function): """ Get internal function from the given HfT function :param building_hft_function: str :return: str """ - return GeometryHelper.hft_to_function[building_hft_function] + return GeometryHelper._hft_to_function[building_hft_function] @staticmethod - def function_from_pluto(building_pluto_function): + def libs_function_from_pluto(building_pluto_function): """ Get internal function from the given pluto function :param building_pluto_function: str :return: str """ - return GeometryHelper.pluto_to_function[building_pluto_function] + return GeometryHelper._pluto_to_function[building_pluto_function] @staticmethod - def usage_from_function(building_function): + def libs_usage_from_libs_function(building_function): """ Get the internal usage for the given internal building function :param building_function: str :return: str """ - return GeometryHelper.function_to_usage[building_function] + return GeometryHelper._function_to_usage[building_function] @staticmethod def to_points_matrix(points): diff --git a/imports/geometry/obj.py b/imports/geometry/obj.py index 6349921b..3ce1fae3 100644 --- a/imports/geometry/obj.py +++ b/imports/geometry/obj.py @@ -76,6 +76,6 @@ class Obj: perimeter_polygon = solid_polygon surface = Surface(solid_polygon, perimeter_polygon) surfaces.append(surface) - building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner) + building = Building(name, lod, surfaces, year_of_construction, function, self._lower_corner, terrains=None) self._city.add_city_object(building) return self._city diff --git a/imports/geometry/rhino.py b/imports/geometry/rhino.py index 38c350f0..b9007ecb 100644 --- a/imports/geometry/rhino.py +++ b/imports/geometry/rhino.py @@ -3,16 +3,18 @@ Rhino module parses rhino files and import the geometry into the city model stru SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@concordia.ca """ -import numpy as np from numpy import inf -from rhino3dm import * -from rhino3dm._rhino3dm import MeshType +#from rhino3dm import * +#from rhino3dm._rhino3dm import MeshType +from city_model_structure.attributes.point import Point +import numpy as np +from helpers.configuration_helper import ConfigurationHelper from city_model_structure.attributes.polygon import Polygon from city_model_structure.building import Building -from city_model_structure.building_demand.surface import Surface as LibsSurface from city_model_structure.city import City -from helpers.configuration_helper import ConfigurationHelper +from city_model_structure.building_demand.surface import Surface as LibsSurface +from helpers.constants import EPSILON from imports.geometry.helpers.geometry_helper import GeometryHelper @@ -25,7 +27,19 @@ class Rhino: self._max_x = self._max_y = self._max_z = min_float @staticmethod - def _solid_points(coordinates): + def _in_perimeter(wall, corner): + res = wall.contains_point(Point(corner)) + print(f'belong: {res} wall:({wall.coordinates}) corner: ({corner})') + return res + + @staticmethod + def _add_hole(solid_polygon, hole): + first = solid_polygon.points[0] + points = first + hole.points + solid_polygon.points + return Polygon(points) + + @staticmethod + def _solid_points(coordinates) -> np.ndarray: solid_points = np.fromstring(coordinates, dtype=float, sep=' ') solid_points = GeometryHelper.to_points_matrix(solid_points) return solid_points @@ -82,6 +96,7 @@ class Rhino: windows.append(Polygon(surface.perimeter_polygon.inverse)) else: buildings.append(rhino_object) + print(f'windows: {len(windows)}') # todo: this method will be pretty inefficient for hole in windows: corner = hole.coordinates[0] diff --git a/imports/life_cycle_assessment/lca_machine.py b/imports/life_cycle_assessment/lca_machine.py index dbfe1760..87c35c5c 100644 --- a/imports/life_cycle_assessment/lca_machine.py +++ b/imports/life_cycle_assessment/lca_machine.py @@ -8,7 +8,6 @@ import xmltodict from pathlib import Path from city_model_structure.machine import Machine - class LcaMachine: def __init__(self, city, base_path): self._city = city @@ -17,15 +16,12 @@ class LcaMachine: def enrich(self): self._city.machines = [] - # print(self._base_path) path = Path(self._base_path / 'lca_data.xml').resolve() with open(path) as xml: self._lca = xmltodict.parse(xml.read()) - for machine in self._lca["library"]["machines"]['machine']: - self._city.machines.append(Machine(machine['@id'], machine['@name'], machine['work_efficiency']['#text'], - machine['work_efficiency']['@unit'], - machine['energy_consumption_rate']['#text'], - machine['energy_consumption_rate']['@unit'], - machine['carbon_emission_factor']['#text'], - machine['carbon_emission_factor']['@unit'])) + for machine in self._lca["library"]["machines"]['machine']: + self._city.machines.append(Machine(machine['@id'], machine['@name'], machine['work_efficiency']['#text'], + machine['work_efficiency']['@unit'], machine['energy_consumption_rate']['#text'], + machine['energy_consumption_rate']['@unit'], machine['carbon_emission_factor']['#text'], + machine['carbon_emission_factor']['@unit'])) diff --git a/imports/life_cycle_assessment/lca_material.py b/imports/life_cycle_assessment/lca_material.py index e0081b7c..5044b315 100644 --- a/imports/life_cycle_assessment/lca_material.py +++ b/imports/life_cycle_assessment/lca_material.py @@ -7,8 +7,6 @@ Contributor Mohammad Reza mohammad.seyedabadi@mail.concordia.ca import xmltodict from pathlib import Path from city_model_structure.building_demand.material import Material -from city_model_structure.lca_material import LcaMaterial as LcaMat - class LcaMaterial: def __init__(self, city, base_path): @@ -17,25 +15,26 @@ class LcaMaterial: self._lca = None def enrich(self): - self._city.lca_materials = [] + self._city.materials = [] path = Path(self._base_path / 'lca_data.xml').resolve() with open(path) as xml: self._lca = xmltodict.parse(xml.read()) for material in self._lca["library"]["building_materials"]['material']: - _lca_material = LcaMat() - _lca_material.type = material['@type'] - _lca_material.id = material['@id'] - _lca_material.name = material['@name'] - _lca_material.density = material['density']['#text'] - _lca_material.density_unit = material['density']['@unit'] - _lca_material.embodied_carbon = material['embodied_carbon']['#text'] - _lca_material.embodied_carbon_unit = material['embodied_carbon']['@unit'] - _lca_material.recycling_ratio = material['recycling_ratio'] - _lca_material.onsite_recycling_ratio = material['onsite_recycling_ratio'] - _lca_material.company_recycling_ratio = material['company_recycling_ratio'] - _lca_material.landfilling_ratio = material['landfilling_ratio'] - _lca_material.cost = 10 # todo: change this into material['cost']['#text'] - _lca_material._cost_unit = material['cost']['@unit'] - self._city.lca_materials.append(_lca_material) + _material = Material() + _material.type = material['@type'] + _material.id = material['@id'] + _material.name = material['@name'] + _material.density=material['density']['#text'] + _material.density_unit=material['density']['@unit'] + _material.embodied_carbon=material['embodied_carbon']['#text'] + _material.embodied_carbon_unit=material['embodied_carbon']['@unit'] + _material.recycling_ratio=material['recycling_ratio'] + _material.onsite_recycling_ratio=material['onsite_recycling_ratio'] + _material.company_recycling_ratio=material['company_recycling_ratio'] + _material.landfilling_ratio=material['landfilling_ratio'] + _material.cost=material['cost']['#text'] + _material._cost_unit=material['cost']['@unit'] + + self._city.materials.append(_material) diff --git a/imports/schedules/doe_idf.py b/imports/schedules/doe_idf.py index 1c9627de..61390199 100644 --- a/imports/schedules/doe_idf.py +++ b/imports/schedules/doe_idf.py @@ -10,6 +10,8 @@ import parseidf import xmltodict from imports.schedules.helpers.schedules_helper import SchedulesHelper from city_model_structure.attributes.schedule import Schedule +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.lighting import Lighting import helpers.constants as cte @@ -51,15 +53,16 @@ class DoeIdf: self._schedule_library = xmltodict.parse(xml.read()) for building in self._city.buildings: - for usage_zone in building.usage_zones: - for schedule_archetype in self._schedule_library['archetypes']['archetypes']: - function = schedule_archetype['@building_type'] - if SchedulesHelper.usage_from_function(function) == usage_zone.usage: - self._idf_schedules_path = (base_path / schedule_archetype['idf']['path']).resolve() - with open(self._idf_schedules_path, 'r') as file: - idf = parseidf.parse(file.read()) - self._load_schedule(idf, usage_zone) - break + for internal_zone in building.internal_zones: + for usage_zone in internal_zone.usage_zones: + for schedule_archetype in self._schedule_library['archetypes']['archetypes']: + function = schedule_archetype['@building_type'] + if SchedulesHelper.usage_from_function(function) == usage_zone.usage: + self._idf_schedules_path = (base_path / schedule_archetype['idf']['path']).resolve() + with open(self._idf_schedules_path, 'r') as file: + idf = parseidf.parse(file.read()) + self._load_schedule(idf, usage_zone) + break def _load_schedule(self, idf, usage_zone): schedules_day = {} @@ -129,4 +132,12 @@ class DoeIdf: continue schedules.append(schedule) - usage_zone.schedules = schedules + for schedule in schedules: + if schedule.type == cte.OCCUPANCY: + if usage_zone.occupancy is None: + usage_zone.occupancy = Occupancy() + usage_zone.occupancy.occupancy_schedules = [schedule] + elif schedule.type == cte.LIGHTING: + if usage_zone.lighting is None: + usage_zone.lighting = Lighting() + usage_zone.lighting.schedules = [schedule] diff --git a/imports/schedules/helpers/schedules_helper.py b/imports/schedules/helpers/schedules_helper.py index fa47b343..4d4881a5 100644 --- a/imports/schedules/helpers/schedules_helper.py +++ b/imports/schedules/helpers/schedules_helper.py @@ -12,43 +12,42 @@ class SchedulesHelper: """ Schedules helper """ - usage_to_comnet = { + _usage_to_comnet = { cte.RESIDENTIAL: 'C-12 Residential', cte.INDUSTRY: 'C-10 Warehouse', - cte.OFFICE_ADMINISTRATION: 'C-5 Office', + cte.OFFICE_AND_ADMINISTRATION: 'C-5 Office', cte.HOTEL: 'C-3 Hotel', cte.HEALTH_CARE: 'C-2 Health', - cte.RETAIL: 'C-8 Retail', + cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD: 'C-8 Retail', cte.HALL: 'C-8 Retail', cte.RESTAURANT: 'C-7 Restaurant', cte.EDUCATION: 'C-9 School' } - comnet_default_value = 'C-12 Residential' - comnet_to_data_type = { + _comnet_to_data_type = { 'Fraction': cte.FRACTION, 'OnOff': cte.ON_OFF, 'Temperature': cte.TEMPERATURE } # usage - function_to_usage = { + _function_to_usage = { 'full service restaurant': cte.RESTAURANT, 'high-rise apartment': cte.RESIDENTIAL, 'hospital': cte.HEALTH_CARE, 'large hotel': cte.HOTEL, - 'large office': cte.OFFICE_ADMINISTRATION, - 'medium office': cte.OFFICE_ADMINISTRATION, + 'large office': cte.OFFICE_AND_ADMINISTRATION, + 'medium office': cte.OFFICE_AND_ADMINISTRATION, 'midrise apartment': cte.RESIDENTIAL, 'outpatient healthcare': cte.HEALTH_CARE, 'primary school': cte.EDUCATION, 'quick service restaurant': cte.RESTAURANT, 'secondary school': cte.EDUCATION, 'small hotel': cte.HOTEL, - 'small office': cte.OFFICE_ADMINISTRATION, - 'stand-alone-retail': cte.RETAIL, + 'small office': cte.OFFICE_AND_ADMINISTRATION, + 'stand-alone-retail': cte.RETAIL_SHOP_WITHOUT_REFRIGERATED_FOOD, 'strip mall': cte.HALL, - 'supermarket': cte.RETAIL, + 'supermarket': cte.RETAIL_SHOP_WITH_REFRIGERATED_FOOD, 'warehouse': cte.INDUSTRY, 'residential': cte.RESIDENTIAL } @@ -61,10 +60,9 @@ class SchedulesHelper: :return: str """ try: - return SchedulesHelper.usage_to_comnet[usage] + return SchedulesHelper._usage_to_comnet[usage] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default Comnet schedules "residential"\n') - return SchedulesHelper.comnet_default_value + sys.stderr.write('Error: keyword not found.\n') @staticmethod def data_type_from_comnet(comnet_data_type): @@ -74,7 +72,7 @@ class SchedulesHelper: :return: str """ try: - return SchedulesHelper.comnet_to_data_type[comnet_data_type] + return SchedulesHelper._comnet_to_data_type[comnet_data_type] except KeyError: raise ValueError(f"Error: comnet data type keyword not found.") @@ -85,4 +83,4 @@ class SchedulesHelper: :param building_function: str :return: str """ - return SchedulesHelper.function_to_usage[building_function] + return SchedulesHelper._function_to_usage[building_function] diff --git a/imports/schedules_factory.py b/imports/schedules_factory.py index 8437593f..3842889a 100644 --- a/imports/schedules_factory.py +++ b/imports/schedules_factory.py @@ -6,7 +6,6 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc """ from pathlib import Path -from imports.schedules.comnet_schedules_parameters import ComnetSchedules from imports.schedules.doe_idf import DoeIdf @@ -19,15 +18,11 @@ class SchedulesFactory: self._city = city self._base_path = base_path for building in city.buildings: - if len(building.usage_zones) == 0: - raise Exception('It seems that the schedule factory is being called before the usage factory. ' - 'Please ensure that the usage factory is called first.') - - def _comnet(self): - """ - Enrich the city by using COMNET schedules as data source - """ - ComnetSchedules(self._city, self._base_path) + for internal_zone in building.internal_zones: + if len(internal_zone.usage_zones) == 0: + raise Exception('It seems that the schedule factory is being called before the usage factory. ' + 'Please ensure that the usage factory is called first as the usage zones must be ' + 'firstly generated.') def _doe_idf(self): """ diff --git a/imports/sensors/concordia_energy_consumption.py b/imports/sensors/concordia_energy_consumption.py index 686a4dd3..4f3c7d63 100644 --- a/imports/sensors/concordia_energy_consumption.py +++ b/imports/sensors/concordia_energy_consumption.py @@ -5,6 +5,7 @@ Copyright © 2021 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc """ import pandas as pd from imports.sensors.concordia_file_report import ConcordiaFileReport +from city_model_structure.iot.concordia_energy_sensor import ConcordiaEnergySensor class ConcordiaEnergyConsumption(ConcordiaFileReport): diff --git a/imports/sensors/concordia_gas_flow.py b/imports/sensors/concordia_gas_flow.py index 11dec2e2..e9176fc7 100644 --- a/imports/sensors/concordia_gas_flow.py +++ b/imports/sensors/concordia_gas_flow.py @@ -5,7 +5,7 @@ Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import pandas as pd from imports.sensors.concordia_file_report import ConcordiaFileReport - +from city_model_structure.iot.concordia_gas_flow_sensor import ConcordiaGasFlowSensor class ConcordiaGasFlow(ConcordiaFileReport): @@ -24,7 +24,6 @@ class ConcordiaGasFlow(ConcordiaFileReport): building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]] building_headers = ["Date time", "Gas Flow Cumulative Monthly"] building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1) - """ sensor = ConcordiaGasFlowSensor(self._sensors[i]) sensor_exist = False for j in range(len(obj.sensors)): @@ -35,4 +34,3 @@ class ConcordiaGasFlow(ConcordiaFileReport): if not sensor_exist: sensor.add_period(building_energy_consumption) obj.sensors.append(sensor) - """ \ No newline at end of file diff --git a/imports/sensors/concordia_temperature.py b/imports/sensors/concordia_temperature.py index e1f1cb32..f4b937a8 100644 --- a/imports/sensors/concordia_temperature.py +++ b/imports/sensors/concordia_temperature.py @@ -5,6 +5,7 @@ Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import pandas as pd from imports.sensors.concordia_file_report import ConcordiaFileReport +from city_model_structure.iot.concordia_temperature_sensor import ConcordiaTemperatureSensor class ConcordiaTemperature(ConcordiaFileReport): @@ -22,7 +23,6 @@ class ConcordiaTemperature(ConcordiaFileReport): building_measures = [self._measures["Date time"], self._measures[self._sensor_point[self._sensors[i]]]] building_headers = ["Date time", "Temperature"] building_energy_consumption = pd.concat(building_measures, keys=building_headers, axis=1) - """ sensor = ConcordiaTemperatureSensor(self._sensors[i]) sensor_exist = False for j in range(len(obj.sensors)): @@ -33,4 +33,3 @@ class ConcordiaTemperature(ConcordiaFileReport): if not sensor_exist: sensor.add_period(building_energy_consumption) obj.sensors.append(sensor) - """ \ No newline at end of file diff --git a/imports/usage/ca_usage_parameters.py b/imports/usage/ca_usage_parameters.py index 01cc85a5..b2320fa6 100644 --- a/imports/usage/ca_usage_parameters.py +++ b/imports/usage/ca_usage_parameters.py @@ -5,10 +5,14 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons """ import sys -from imports.geometry.helpers.geometry_helper import GeometryHelper as gh +from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.usage.hft_usage_interface import HftUsageInterface +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 city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl class CaUsageParameters(HftUsageInterface): @@ -18,9 +22,6 @@ class CaUsageParameters(HftUsageInterface): def __init__(self, city, base_path): super().__init__(base_path, 'ca_archetypes_reduced.xml') self._city = city - # todo: this is a wrong location for self._min_air_change -> re-think where to place this info - # and where it comes from - self._min_air_change = 0 def enrich_buildings(self): """ @@ -29,50 +30,53 @@ class CaUsageParameters(HftUsageInterface): """ city = self._city for building in city.buildings: - archetype = self._search_archetype(building.function) - if archetype is None: + usage = GeometryHelper().libs_usage_from_libs_function(building.function) + try: + archetype = self._search_archetype(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'{gh.usage_from_function(building.function)}\n') - continue - # todo: what to do with mix-usage usage from gml? - mix_usage = False - if not mix_usage: - # just one usage_zone - for thermal_zone in building.thermal_zones: - usage_zone = UsageZone() - self._assign_values(usage_zone, archetype) - usage_zone.volume = thermal_zone.volume - thermal_zone.usage_zones = [usage_zone] + f' {building.function}\n') + return - def _search_archetype(self, building_usage): + for internal_zone in building.internal_zones: + usage_zone = UsageZone() + usage_zone.usage = building.function + usage_zone.percentage = 1 + self._assign_values_usage_zone(usage_zone, archetype) + internal_zone.usage_zones = [usage_zone] + + def _search_archetype(self, libs_usage): + building_usage = UsageHelper().hft_from_libs_usage(libs_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): - usage_zone.usage = archetype.usage + def _assign_values_usage_zone(usage_zone, archetype): # 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.average_internal_gain = archetype_internal_gain.average_internal_gain - 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.heating_setpoint = archetype.heating_setpoint - usage_zone.heating_setback = archetype.heating_setback - usage_zone.cooling_setpoint = archetype.cooling_setpoint - usage_zone.occupancy_density = archetype.occupancy_density + usage_zone.mechanical_air_change = archetype.mechanical_air_change + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + usage_zone.occupancy = _occupancy usage_zone.hours_day = archetype.hours_day usage_zone.days_year = archetype.days_year - usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day - usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature - usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year - usage_zone.mechanical_air_change = archetype.mechanical_air_change + _appliances = Appliances() + _appliances.appliances_density = archetype.appliances.appliances_density + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.mean_heating_set_point = archetype.thermal_control.mean_heating_set_point + _control.heating_set_back = archetype.thermal_control.heating_set_back + _control.mean_cooling_set_point = archetype.thermal_control.mean_cooling_set_point + usage_zone.thermal_control = _control + _internal_gains = [] + for archetype_internal_gain in archetype.not_detailed_source_mean_annual_internal_gains: + _internal_gain = InternalGains() + _internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain + _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.not_detailed_source_mean_annual_internal_gains = _internal_gains diff --git a/imports/usage/comnet_usage_parameters.py b/imports/usage/comnet_usage_parameters.py index 5d10312c..5d159af7 100644 --- a/imports/usage/comnet_usage_parameters.py +++ b/imports/usage/comnet_usage_parameters.py @@ -3,6 +3,7 @@ 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 @@ -11,10 +12,13 @@ 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 imports.schedules.helpers.schedules_helper import SchedulesHelper 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 +from city_model_structure.building_demand.lighting import Lighting +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.attributes.schedule import Schedule class ComnetUsageParameters: @@ -24,24 +28,19 @@ class ComnetUsageParameters: 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) + self._data = self._read_file() + self._comnet_schedules_path = str(base_path / 'comnet_schedules_archetypes.xlsx') + self._xls = pd.ExcelFile(self._comnet_schedules_path) def _read_file(self) -> Dict: """ - reads xlsx file containing usage information into a dictionary + reads xlsx files 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") + nrows=number_usage_types, usecols="A:AB") lighting_data = {} plug_loads_data = {} @@ -49,6 +48,7 @@ class ComnetUsageParameters: ventilation_rate = {} water_heating = {} process_data = {} + schedules_key = {} for j in range(0, number_usage_types): usage_parameters = file_data.iloc[j] @@ -59,54 +59,129 @@ class ComnetUsageParameters: 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} + 'process': process_data, + 'schedules_key': schedules_key} @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)) + def _parse_usage_type(comnet_usage, data, schedules_data): + _usage_zone = UsageZone() - usage_zone_archetype = huza(usage=usage, internal_gains=internal_gains, - occupancy_density=occupancy_density, - mechanical_air_change=mechanical_air_change) - return usage_zone_archetype + # lighting + _lighting = Lighting() + _lighting.latent_fraction = ch().comnet_lighting_latent + _lighting.convective_fraction = ch().comnet_lighting_convective + _lighting.radiative_fraction = ch().comnet_lighting_radiant + _lighting.lighting_density = data['lighting'][comnet_usage][4] + + # 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.appliances_density = data['plug loads'][comnet_usage][0] + + # occupancy + _occupancy = Occupancy() + _occupancy.occupancy_density = data['occupancy'][comnet_usage][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] + + if _occupancy.occupancy_density <= 0: + _usage_zone.mechanical_air_change = 0 + else: + _usage_zone.mechanical_air_change = data['ventilation rate'][comnet_usage][0] / _occupancy.occupancy_density + + 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] + _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): """ @@ -115,48 +190,81 @@ class ComnetUsageParameters: """ 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: + 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.usage_from_function(building.function)}\n') - continue + f'{GeometryHelper.libs_usage_from_libs_function(building.function)}\n') + return - # just one usage_zone - for thermal_zone in building.thermal_zones: + 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, archetype, height) - usage_zone.volume = thermal_zone.volume - thermal_zone.usage_zones = [usage_zone] + 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) - def _search_archetype(self, building_usage): - for building_archetype in self._usage_archetypes: - if building_archetype.usage == building_usage: - return building_archetype - return None + internal_zone.usage_zones = [usage_zone] @staticmethod - def _assign_values(usage_zone, archetype, height): + def _assign_values_usage_zone(usage_zone, archetype, volume_per_area): # 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 + # 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 + _occupancy.latent_internal_gain = archetype.occupancy.latent_internal_gain + _occupancy.sensible_convective_internal_gain = archetype.occupancy.sensible_convective_internal_gain + _occupancy.occupancy_schedules = archetype.occupancy.occupancy_schedules + usage_zone.occupancy = _occupancy + _lighting = Lighting() + _lighting.lighting_density = archetype.lighting.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.appliances_density = archetype.appliances.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 + + @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 diff --git a/imports/usage/data_classes/hft_usage_zone_archetype.py b/imports/usage/data_classes/hft_usage_zone_archetype.py deleted file mode 100644 index b945e9c9..00000000 --- a/imports/usage/data_classes/hft_usage_zone_archetype.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -HftUsageZoneArchetype stores usage information by building archetypes -SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca -""" -from typing import List -from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype - - -class HftUsageZoneArchetype: - """ - HftUsageZoneArchetype class - """ - def __init__(self, usage=None, internal_gains=None, heating_set_point=None, heating_set_back=None, - cooling_set_point=None, occupancy_density=None, hours_day=None, days_year=None, - dhw_average_volume_pers_day=None, dhw_preparation_temperature=None, - electrical_app_average_consumption_sqm_year=None, mechanical_air_change=None, - occupancy=None, schedules=None): - self._usage = usage - self._internal_gains = internal_gains - self._heating_setpoint = heating_set_point - self._heating_setback = heating_set_back - self._cooling_setpoint = cooling_set_point - self._occupancy_density = occupancy_density - self._hours_day = hours_day - self._days_year = days_year - self._dhw_average_volume_pers_day = dhw_average_volume_pers_day - self._dhw_preparation_temperature = dhw_preparation_temperature - self._electrical_app_average_consumption_sqm_year = electrical_app_average_consumption_sqm_year - self._mechanical_air_change = mechanical_air_change - self._occupancy = occupancy - self._schedules = schedules - - @property - def internal_gains(self) -> List[HftInternalGainsArchetype]: - """ - Get usage zone internal gains - :return: [InternalGains] - """ - return self._internal_gains - - @property - def heating_setpoint(self): - """ - Get usage zone heating set point in celsius grads - :return: float - """ - return self._heating_setpoint - - @property - def heating_setback(self): - """ - Get usage zone heating setback in celsius grads - :return: float - """ - return self._heating_setback - - @property - def cooling_setpoint(self): - """ - Get usage zone cooling setpoint in celsius grads - :return: float - """ - return self._cooling_setpoint - - @property - def hours_day(self): - """ - Get usage zone usage hours per day - :return: float - """ - return self._hours_day - - @property - def days_year(self): - """ - Get usage zone usage days per year - :return: float - """ - return self._days_year - - @property - def mechanical_air_change(self): - """ - Set usage zone mechanical air change in air change per hour (ACH) - :return: float - """ - return self._mechanical_air_change - - @property - def usage(self): - """ - Get usage zone usage - :return: str - """ - return self._usage - - @property - def occupancy(self): - """ - Get schedules data - :return: [Occupancy] - """ - return self._occupancy - - @property - def schedules(self): - """ - Get schedules - :return: [Schedule_Values] - """ - return self._schedules - - @property - def occupancy_density(self): - """ - Get schedules density in persons per m2 - :return: float - """ - return self._occupancy_density - - @property - def dhw_average_volume_pers_day(self): - """ - Get average DHW consumption in m3 per person per day - :return: float - """ - return self._dhw_average_volume_pers_day - - @property - def dhw_preparation_temperature(self): - """ - Get preparation temperature of the DHW in degree Celsius - :return: float - """ - return self._dhw_preparation_temperature - - @property - def electrical_app_average_consumption_sqm_year(self): - """ - Get average consumption of electrical appliances in Joules per m2 and year (J/m2yr) - :return: float - """ - return self._electrical_app_average_consumption_sqm_year diff --git a/imports/usage/data_classes/usage_zone_archetype.py b/imports/usage/data_classes/usage_zone_archetype.py new file mode 100644 index 00000000..67d1f964 --- /dev/null +++ b/imports/usage/data_classes/usage_zone_archetype.py @@ -0,0 +1,98 @@ +""" +UsageZoneArchetype stores usage information by usage type +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" + +from typing import List +from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype + + +class UsageZoneArchetype: + """ + UsageZoneArchetype class + """ + def __init__(self, usage=None, not_detailed_source_mean_annual_internal_gains=None, hours_day=None, days_year=None, + electrical_app_average_consumption_sqm_year=None, mechanical_air_change=None, occupancy=None, + lighting=None, appliances=None): + self._usage = usage + self._not_detailed_source_mean_annual_internal_gains = not_detailed_source_mean_annual_internal_gains + self._hours_day = hours_day + self._days_year = days_year + self._electrical_app_average_consumption_sqm_year = electrical_app_average_consumption_sqm_year + self._mechanical_air_change = mechanical_air_change + self._occupancy = occupancy + self._lighting = lighting + self._appliances = appliances + + @property + def not_detailed_source_mean_annual_internal_gains(self) -> List[HftInternalGainsArchetype]: + """ + Get usage zone internal gains from not detailed heating source in W/m2 + :return: [InternalGains] + """ + return self._not_detailed_source_mean_annual_internal_gains + + @property + def hours_day(self): + """ + Get usage zone usage hours per day + :return: float + """ + return self._hours_day + + @property + def days_year(self): + """ + Get usage zone usage days per year + :return: float + """ + return self._days_year + + @property + def mechanical_air_change(self): + """ + Set usage zone mechanical air change in air change per hour (ACH) + :return: float + """ + return self._mechanical_air_change + + @property + def usage(self): + """ + Get usage zone usage + :return: str + """ + return self._usage + + @property + def electrical_app_average_consumption_sqm_year(self): + """ + Get average consumption of electrical appliances in Joules per m2 and year (J/m2yr) + :return: float + """ + return self._electrical_app_average_consumption_sqm_year + + @property + def occupancy(self): + """ + Get occupancy data + :return: Occupancy + """ + return self._occupancy + + @property + def lighting(self): + """ + Get lighting data + :return: Lighting + """ + return self._lighting + + @property + def appliances(self): + """ + Get appliances data + :return: Appliances + """ + return self._appliances diff --git a/imports/usage/helpers/usage_helper.py b/imports/usage/helpers/usage_helper.py index 310b4888..98f873da 100644 --- a/imports/usage/helpers/usage_helper.py +++ b/imports/usage/helpers/usage_helper.py @@ -11,54 +11,104 @@ class UsageHelper: """ Usage helper class """ - usage_to_hft = { + _usage_to_hft = { cte.RESIDENTIAL: 'residential', - cte.INDUSTRY: 'industry', - cte.OFFICE_ADMINISTRATION: 'office and administration', + 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.HEALTH_CARE: 'health care', - cte.RETAIL: 'retail', - cte.HALL: 'hall', + cte.HOTEL_MEDIUM_CLASS: 'hotel (Medium-class)', + cte.DORMITORY: 'dormitory', + cte.INDUSTRY: 'industry', cte.RESTAURANT: 'restaurant', - cte.EDUCATION: 'education' - } - hft_default_value = 'residential' + 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_usage(usage): + def hft_from_libs_usage(usage): """ Get HfT usage from the given internal usage key :param usage: str :return: str """ try: - return UsageHelper.usage_to_hft[usage] + return UsageHelper._usage_to_hft[usage] except KeyError: - sys.stderr.write('Error: keyword not found. Returned default HfT usage "residential"\n') - return UsageHelper.hft_default_value + sys.stderr.write('Error: keyword not found to translate from libs_usage to hft usage.\n') - usage_to_comnet = { + _usage_to_comnet = { cte.RESIDENTIAL: 'BA Multifamily', - cte.INDUSTRY: 'BA Manufacturing Facility', - cte.OFFICE_ADMINISTRATION: 'BA Office', + 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.RETAIL: 'BA Retail', - cte.HALL: 'BA Town Hall', - cte.RESTAURANT: 'BA Dining: Bar Lounge/Leisure', - cte.EDUCATION: 'BA School/University' + 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_default_value = 'BA Multifamily' + + _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'} @staticmethod - def comnet_from_usage(usage): + def comnet_from_libs_usage(usage): """ Get Comnet usage from the given internal usage key :param usage: str :return: str """ try: - return UsageHelper.usage_to_comnet[usage] + 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 + sys.stderr.write('Error: keyword not found to translate from libs_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/imports/usage/hft_usage_interface.py b/imports/usage/hft_usage_interface.py index 3da6457d..959afbef 100644 --- a/imports/usage/hft_usage_interface.py +++ b/imports/usage/hft_usage_interface.py @@ -1,12 +1,18 @@ """ -Hft-based interface, it reads format defined within the CERC team based on that one used in SimStadt and developed by -the IAF team at hft-Stuttgart and enriches the city with usage parameters +Hft-based interface, it reads format defined within the CERC team (based on that one used in SimStadt and developed by +the IAF team at hft-Stuttgart) SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2022 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import xmltodict -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 +import copy +from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.internal_gains import InternalGains +from city_model_structure.building_demand.occupancy import Occupancy +from city_model_structure.building_demand.appliances import Appliances +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.attributes.schedule import Schedule +import helpers.constants as cte class HftUsageInterface: @@ -31,113 +37,191 @@ class HftUsageInterface: @staticmethod def _parse_zone_usage_type(usage, zone_usage_type): - occupancy_density = zone_usage_type['occupancy']['occupancyDensity'] - hours_day = zone_usage_type['occupancy']['usageHoursPerDay'] - days_year = zone_usage_type['occupancy']['usageDaysPerYear'] - cooling_setpoint = zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature'] - heating_setpoint = zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature'] - heating_setback = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature'] - mechanical_air_change = None - if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None: - mechanical_air_change = zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate'] - dhw_average_volume_pers_day = None - dhw_preparation_temperature = None - if 'domestic_hot_water' in zone_usage_type['endUses']: - # liters to cubic meters - dhw_average_volume_pers_day = float( - zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000 - dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature'] - electrical_app_average_consumption_sqm_year = None - if 'all_electrical_appliances' in zone_usage_type['endUses']: - if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']: - # kWh to J - electrical_app_average_consumption_sqm_year = \ - float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) / 3.6 + usage_zone_archetype = UsageZone() + usage_zone_archetype.usage = usage - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - internal_gains = [] - if 'internGains' in zone_usage_type['occupancy']: - latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction'] - convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction'] - average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm'] - radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction'] - else: - latent_fraction = 0 - convective_fraction = 0 - average_internal_gain = 0 - radiative_fraction = 0 + if 'occupancy' in zone_usage_type: + _occupancy = Occupancy() + _occupancy.occupancy_density = zone_usage_type['occupancy']['occupancyDensity'] #todo: check units + usage_zone_archetype.hours_day = zone_usage_type['occupancy']['usageHoursPerDay'] + usage_zone_archetype.days_year = zone_usage_type['occupancy']['usageDaysPerYear'] + usage_zone_archetype.occupancy = _occupancy + + if 'internGains' in zone_usage_type['occupancy']: + _internal_gain = InternalGains() + _internal_gain.latent_fraction = zone_usage_type['occupancy']['internGains']['latentFraction'] + _internal_gain.convective_fraction = zone_usage_type['occupancy']['internGains']['convectiveFraction'] + _internal_gain.average_internal_gain = zone_usage_type['occupancy']['internGains']['averageInternGainPerSqm'] + _internal_gain.radiative_fraction = zone_usage_type['occupancy']['internGains']['radiantFraction'] + if 'load' in zone_usage_type['occupancy']['internGains']: + _schedule = Schedule() + _schedule.type = 'internal gains load' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.ANY_NUMBER + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['occupancy']['internGains']['load']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _internal_gain.schedules = [_schedule] + + usage_zone_archetype.not_detailed_source_mean_annual_internal_gains = [_internal_gain] + + if 'endUses' in zone_usage_type: + _thermal_control = ThermalControl() + if 'space_heating' in zone_usage_type['endUses']: + _thermal_control.mean_heating_set_point = \ + zone_usage_type['endUses']['space_heating']['heatingSetPointTemperature'] + _thermal_control.heating_set_back = zone_usage_type['endUses']['space_heating']['heatingSetBackTemperature'] + if 'schedule' in zone_usage_type['endUses']['space_heating']: + _schedule = Schedule() + _schedule.type = 'heating temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['endUses']['space_heating']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.heating_set_point_schedules = [_schedule] + + if 'space_cooling' in zone_usage_type['endUses']: + _thermal_control.mean_cooling_set_point = \ + zone_usage_type['endUses']['space_cooling']['coolingSetPointTemperature'] + if 'schedule' in zone_usage_type['endUses']['space_cooling']: + _schedule = Schedule() + _schedule.type = 'cooling temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = zone_usage_type['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.cooling_set_point_schedules = [_schedule] + + usage_zone_archetype.thermal_control = _thermal_control + + if 'ventilation' in zone_usage_type['endUses'] and zone_usage_type['endUses']['ventilation'] is not None: + usage_zone_archetype.mechanical_air_change = \ + zone_usage_type['endUses']['ventilation']['mechanicalAirChangeRate'] + + # todo: not used or assigned anywhere + if 'domestic_hot_water' in zone_usage_type['endUses']: + # liters to cubic meters + dhw_average_volume_pers_day = float( + zone_usage_type['endUses']['domestic_hot_water']['averageVolumePerPersAndDay']) / 1000 + dhw_preparation_temperature = zone_usage_type['endUses']['domestic_hot_water']['preparationTemperature'] + + if 'all_electrical_appliances' in zone_usage_type['endUses']: + if 'averageConsumptionPerSqmAndYear' in zone_usage_type['endUses']['all_electrical_appliances']: + # kWh to J + usage_zone_archetype.electrical_app_average_consumption_sqm_year = \ + float(zone_usage_type['endUses']['all_electrical_appliances']['averageConsumptionPerSqmAndYear']) \ + * cte.KILO_WATTS_HOUR_TO_JULES + + if 'appliance' in zone_usage_type: + _appliances = Appliances() + _appliances.appliances_density = zone_usage_type['appliance']['#text'] #todo: check units + + usage_zone_archetype.appliances = _appliances - 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, - dhw_average_volume_pers_day=dhw_average_volume_pers_day, - dhw_preparation_temperature=dhw_preparation_temperature, - electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year, - mechanical_air_change=mechanical_air_change) return usage_zone_archetype @staticmethod def _parse_zone_usage_variant(usage, usage_zone, usage_zone_variant): - # for the variants all is optional because it mimics the inheritance concept from OOP - occupancy_density = usage_zone.occupancy_density - hours_day = usage_zone.hours_day - days_year = usage_zone.days_year - cooling_setpoint = usage_zone.cooling_setpoint - heating_setpoint = usage_zone.heating_setpoint - heating_setback = usage_zone.heating_setback - mechanical_air_change = usage_zone.mechanical_air_change - dhw_average_volume_pers_day = usage_zone.dhw_average_volume_pers_day - dhw_preparation_temperature = usage_zone.dhw_preparation_temperature - electrical_app_average_consumption_sqm_year = usage_zone.electrical_app_average_consumption_sqm_year + # the variants mimic the inheritance concept from OOP + usage_zone_archetype = copy.deepcopy(usage_zone) + usage_zone_archetype.usage = usage - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - # for internal_gain in usage_zone.internal_gains: - internal_gains = usage_zone.internal_gains[0] - latent_fraction = internal_gains.latent_fraction - convective_fraction = internal_gains.convective_fraction - average_internal_gain = internal_gains.average_internal_gain - radiative_fraction = internal_gains.radiative_fraction + if 'occupancy' in usage_zone_variant: + _occupancy = Occupancy() + if 'occupancyDensity' in usage_zone_variant['occupancy']: + _occupancy.occupancy_density = usage_zone_variant['occupancy']['occupancyDensity'] # todo: check units + if 'usageHoursPerDay' in usage_zone_variant['occupancy']: + usage_zone_archetype.hours_day = usage_zone_variant['occupancy']['usageHoursPerDay'] + if 'usageDaysPerYear' in usage_zone_variant['occupancy']: + usage_zone_archetype.days_year = usage_zone_variant['occupancy']['usageDaysPerYear'] + usage_zone_archetype.occupancy = _occupancy + + if 'internGains' in usage_zone_variant['occupancy']: + _internal_gain = InternalGains() + if 'latentFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.latent_fraction = usage_zone_variant['occupancy']['internGains']['latentFraction'] + if 'convectiveFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.convective_fraction = usage_zone_variant['occupancy']['internGains']['convectiveFraction'] + if 'averageInternGainPerSqm' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.average_internal_gain = \ + usage_zone_variant['occupancy']['internGains']['averageInternGainPerSqm'] + if 'radiantFraction' in usage_zone_variant['occupancy']['internGains']: + _internal_gain.radiative_fraction = usage_zone_variant['occupancy']['internGains']['radiantFraction'] + if 'load' in usage_zone_variant['occupancy']['internGains']: + _schedule = Schedule() + _schedule.type = 'internal gains load' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.ANY_NUMBER + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['occupancy']['internGains']['load']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _internal_gain.schedules = [_schedule] + + usage_zone_archetype.not_detailed_source_mean_annual_internal_gains = [_internal_gain] + + if 'endUses' in usage_zone_variant: + _thermal_control = ThermalControl() + if 'space_heating' in usage_zone_variant['endUses']: + if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']: + _thermal_control.mean_heating_set_point = \ + usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature'] + if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']: + _thermal_control.heating_set_back = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature'] + if 'schedule' in usage_zone_variant['endUses']['space_heating']: + _schedule = Schedule() + _schedule.type = 'heating temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['endUses']['space_heating']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.heating_set_point_schedules = [_schedule] + + if 'space_cooling' in usage_zone_variant['endUses'] and \ + usage_zone_variant['endUses']['space_cooling'] is not None: + if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']: + _thermal_control.mean_cooling_set_point = \ + usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature'] + if 'schedule' in usage_zone_variant['endUses']['space_cooling']: + _schedule = Schedule() + _schedule.type = 'cooling temperature' + _schedule.time_range = cte.DAY + _schedule.time_step = cte.HOUR + _schedule.data_type = cte.TEMPERATURE + _schedule.day_types = [cte.MONDAY, cte.TUESDAY, cte.WEDNESDAY, cte.THURSDAY, cte.FRIDAY] + _values = usage_zone_variant['endUses']['space_cooling']['schedule']['weekDayProfile']['values'] + while ' ' in _values: + _values = _values.replace(' ', ' ') + _schedule.values = _values.split() + _thermal_control.cooling_set_point_schedules = [_schedule] + + usage_zone_archetype.thermal_control = _thermal_control + + if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None: + usage_zone_archetype.mechanical_air_change = \ + usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate'] + + if 'appliance' in usage_zone_variant: + _appliances = Appliances() + _appliances.appliances_density = usage_zone_variant['appliance']['#text'] # todo: check units + + usage_zone_archetype.appliances = _appliances - if 'space_cooling' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_cooling'] is not None: - if 'coolingSetPointTemperature' in usage_zone_variant['endUses']['space_cooling']: - cooling_setpoint = usage_zone_variant['endUses']['space_cooling']['coolingSetPointTemperature'] - if 'space_heating' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['space_heating'] is not None: - if 'heatingSetPointTemperature' in usage_zone_variant['endUses']['space_heating']: - heating_setpoint = usage_zone_variant['endUses']['space_heating']['heatingSetPointTemperature'] - if 'heatingSetBackTemperature' in usage_zone_variant['endUses']['space_heating']: - heating_setback = usage_zone_variant['endUses']['space_heating']['heatingSetBackTemperature'] - if 'ventilation' in usage_zone_variant['endUses'] and usage_zone_variant['endUses']['ventilation'] is not None: - if 'mechanicalAirChangeRate' in usage_zone_variant['endUses']['ventilation']: - mechanical_air_change = usage_zone_variant['endUses']['ventilation']['mechanicalAirChangeRate'] - # todo: for internal_gain in usage_zone_variant['schedules']['internGains']:???????????????? - # There are no more internal gains? How is it saved when more than one??? - if 'schedules' in usage_zone_variant: - if 'usageHoursPerDay' in usage_zone_variant['schedules']: - hours_day = usage_zone_variant['schedules']['usageHoursPerDay'] - if 'usageDaysPerYear' in usage_zone_variant['schedules']: - days_year = usage_zone_variant['schedules']['usageDaysPerYear'] - if 'internalGains' in usage_zone_variant['schedules'] and usage_zone_variant['schedules'][ - 'internGains'] is not None: - internal_gains = [] - if 'latentFraction' in usage_zone_variant['schedules']['internGains']: - latent_fraction = usage_zone_variant['schedules']['internGains']['latentFraction'] - if 'convectiveFraction' in usage_zone_variant['schedules']['internGains']: - convective_fraction = usage_zone_variant['schedules']['internGains']['convectiveFraction'] - if 'averageInternGainPerSqm' in usage_zone_variant['schedules']['internGains']: - 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=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, - dhw_average_volume_pers_day=dhw_average_volume_pers_day, - dhw_preparation_temperature=dhw_preparation_temperature, - electrical_app_average_consumption_sqm_year=electrical_app_average_consumption_sqm_year, - mechanical_air_change=mechanical_air_change) return usage_zone_archetype diff --git a/imports/usage/hft_usage_parameters.py b/imports/usage/hft_usage_parameters.py index fc254aa2..48a7b267 100644 --- a/imports/usage/hft_usage_parameters.py +++ b/imports/usage/hft_usage_parameters.py @@ -4,11 +4,12 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ import sys +import copy -from imports.geometry.helpers.geometry_helper import GeometryHelper as gh +from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.usage.hft_usage_interface import HftUsageInterface +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 class HftUsageParameters(HftUsageInterface): @@ -18,9 +19,6 @@ class HftUsageParameters(HftUsageInterface): def __init__(self, city, base_path): super().__init__(base_path, 'de_library.xml') self._city = city - # todo: this is a wrong location for self._min_air_change -> re-think where to place this info - # and where it comes from - self._min_air_change = 0 def enrich_buildings(self): """ @@ -29,23 +27,25 @@ class HftUsageParameters(HftUsageInterface): """ city = self._city for building in city.buildings: - archetype = self._search_archetype(gh.usage_from_function(building.function)) - if archetype is None: + usage = GeometryHelper().libs_usage_from_libs_function(building.function) + try: + archetype = self._search_archetype(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'{gh.usage_from_function(building.function)}\n') - continue - # todo: what to do with mix-usage usage from gml? - mix_usage = False - if not mix_usage: - # just one usage_zone - for thermal_zone in building.thermal_zones: - usage_zone = UsageZone() - self._assign_values(usage_zone, archetype) - usage_zone.volume = thermal_zone.volume - thermal_zone.usage_zones = [usage_zone] + f'{GeometryHelper().libs_usage_from_libs_function(building.function)}\n') + return - def _search_archetype(self, building_usage): + for internal_zone in building.internal_zones: + usage_zone = UsageZone() + libs_usage = GeometryHelper().libs_usage_from_libs_function(building.function) + usage_zone.usage = UsageHelper().hft_from_libs_usage(libs_usage) + self._assign_values(usage_zone, archetype) + usage_zone.percentage = 1 + internal_zone.usage_zones = [usage_zone] + + def _search_archetype(self, libs_usage): + building_usage = UsageHelper().hft_from_libs_usage(libs_usage) for building_archetype in self._usage_archetypes: if building_archetype.usage == building_usage: return building_archetype @@ -53,26 +53,42 @@ class HftUsageParameters(HftUsageInterface): @staticmethod def _assign_values(usage_zone, archetype): - usage_zone.usage = archetype.usage - # Due to the fact that python is not a typed language, the wrong object type is assigned to + """ # 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.average_internal_gain = archetype_internal_gain.average_internal_gain - 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.heating_setpoint = archetype.heating_setpoint - usage_zone.heating_setback = archetype.heating_setback - usage_zone.cooling_setpoint = archetype.cooling_setpoint - usage_zone.occupancy_density = archetype.occupancy_density - usage_zone.hours_day = archetype.hours_day - usage_zone.days_year = archetype.days_year - usage_zone.dhw_average_volume_pers_day = archetype.dhw_average_volume_pers_day - usage_zone.dhw_preparation_temperature = archetype.dhw_preparation_temperature - usage_zone.electrical_app_average_consumption_sqm_year = archetype.electrical_app_average_consumption_sqm_year + # Due to the fact that python is not a 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 + _occupancy = Occupancy() + _occupancy.occupancy_density = archetype.occupancy.occupancy_density + usage_zone.occupancy = _occupancy + _appliances = Appliances() + _appliances.appliances_density = archetype.appliances.appliances_density + usage_zone.appliances = _appliances + _control = ThermalControl() + _control.mean_heating_set_point = archetype.thermal_control.mean_heating_set_point + _control.heating_set_back = archetype.thermal_control.heating_set_back + _control.mean_cooling_set_point = archetype.thermal_control.mean_cooling_set_point + _control.cooling_set_point_schedules = archetype.thermal_control.cooling_set_point_schedules + _control.heating_set_point_schedules = archetype.thermal_control.heating_set_point_schedules + usage_zone.thermal_control = _control + _internal_gains = [] + for archetype_internal_gain in archetype.not_detailed_source_mean_annual_internal_gains: + _internal_gain = InternalGains() + _internal_gain.average_internal_gain = archetype_internal_gain.average_internal_gain + _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_gain.schedules = archetype_internal_gain.schedules + _internal_gains.append(_internal_gain) + usage_zone.not_detailed_source_mean_annual_internal_gains = _internal_gains + """ + usage_zone.mechanical_air_change = archetype.mechanical_air_change + usage_zone.occupancy = copy.deepcopy(archetype.occupancy) + usage_zone.appliances = copy.deepcopy(archetype.appliances) + usage_zone.thermal_control = copy.deepcopy(archetype.thermal_control) + usage_zone.not_detailed_source_mean_annual_internal_gains = \ + copy.deepcopy(archetype.not_detailed_source_mean_annual_internal_gains) + usage_zone.days_year = archetype.days_year + usage_zone.hours_day = archetype.hours_day diff --git a/imports/usage_factory.py b/imports/usage_factory.py index 0e2875de..165e7f45 100644 --- a/imports/usage_factory.py +++ b/imports/usage_factory.py @@ -22,10 +22,6 @@ class UsageFactory: self._handler = '_' + handler.lower().replace(' ', '_') self._city = city self._base_path = base_path - for building in city.buildings: - if len(building.thermal_zones) == 0: - raise Exception('It seems that the usage factory is being called before the construction factory. ' - 'Please ensure that the construction factory is called first.') def _hft(self): """ diff --git a/recognized_functions_and_usages.md b/recognized_functions_and_usages.md new file mode 100644 index 00000000..fad74352 --- /dev/null +++ b/recognized_functions_and_usages.md @@ -0,0 +1,64 @@ +# Functions and usages internally recognized within the libs + +The libs uses a list of building functions a building usages that are the only ones recognized. All new categories should be added to the dicctionaries that translate from the input formats to the libs functions. From the libs functions to the libs usages and from the libs usages and libs functions to the output formats. + +Input formats accepted: +* Function: + * pluto + * hft + +Output formats accepted: +* Function: + * nrel + * nrcan +* Usage: + * ca + * hft + * comnet + +Libs_functions: +* single family house +* multi family house +* row hose +* mid rise apartment +* high rise apartment +* residential +* small office +* medium office +* large office +* primary school +* secondary school +* stand alone retail +* hospital +* out-patient health care +* strip mall +* supermarket +* ware house +* quick service restaurant +* full service restaurant +* small hotel +* large hotel + +Libs_usage: +* residential +* single family house +* multi family house +* education +* school without shower +* school with shower +* retail shop without refrigerated food +* retail shop with refrigerated food +* hotel +* hotel medium class +* dormitory +* industry +* restaurant +* health care +* retirement home or orphanage +* office and administration +* event location +* hall +* sports location +* labor +* green-house +* non-heated diff --git a/requirements.txt b/requirements.txt index 5329b459..cce571d9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -13,7 +13,7 @@ openpyxl~=3.0.7 networkx~=2.5.1 parseidf~=1.0.0 ply~=3.11 -rhino3dm~=7.11.1 +rhino3dm~=7.7.0 scipy==1.7.1 PyYAML==6.0 yaml~=0.2.5 diff --git a/unittests/test_construction_factory.py b/unittests/test_construction_factory.py index 03266f95..9348e8d3 100644 --- a/unittests/test_construction_factory.py +++ b/unittests/test_construction_factory.py @@ -1,15 +1,16 @@ """ TestConstructionFactory test and validate the city model structure construction parameters SPDX - License - Identifier: LGPL - 3.0 - or -later -Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +Copyright © 2022 Project Author 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.construction_factory import ConstructionFactory from imports.geometry.helpers.geometry_helper import GeometryHelper -from imports.life_cycle_assessment_factory import LifeCycleAssessment + class TestConstructionFactory(TestCase): """ @@ -23,46 +24,139 @@ class TestConstructionFactory(TestCase): self._city = None self._example_path = (Path(__file__).parent / 'tests_data').resolve() - def _get_citygml(self, file=None): - if file is None: - file = 'pluto_building.gml' + def _get_citygml(self, file): file_path = (self._example_path / file).resolve() self._city = GeometryFactory('citygml', file_path).city - LifeCycleAssessment('material', self._city).enrich() self.assertIsNotNone(self._city, 'city is none') return self._city - def test_city_with_construction_extended_library(self): - """ - Enrich the city with the physic information and verify it - :return: None - """ - - city = self._get_citygml() + def _check_buildings(self, city): for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] - self.assertIsNotNone(building.surfaces, 'Building has no surfaces') + self.assertIsNotNone(building.name, 'building name is none') + self.assertIsNotNone(building.lod, 'building lod is none') + self.assertIsNotNone(building.type, 'building type is none') + self.assertIsNotNone(building.volume, 'building volume is none') + self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') + self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none') + self.assertIsNotNone(building.surfaces, 'building surfaces is none') + self.assertIsNotNone(building.centroid, 'building centroid is none') + self.assertIsNotNone(building.max_height, 'building max_height is none') + self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') + self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') + self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') + self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertIsNotNone(building.lower_corner, 'building lower corner is none') + self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') + self.assertIsNotNone(building.grounds, 'building grounds is none') + self.assertIsNotNone(building.walls, 'building walls is none') + self.assertIsNotNone(building.roofs, 'building roofs is none') + for internal_zone in building.internal_zones: + self.assertIsNone(internal_zone.usage_zones, 'usage zones are defined') + self.assertTrue(len(internal_zone.thermal_zones) > 0, 'thermal zones are not defined') + self.assertIsNone(building.basement_heated, 'building basement_heated is not none') + self.assertIsNone(building.attic_heated, 'building attic_heated is not none') + self.assertIsNone(building.terrains, 'building terrains is not none') + self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') + self.assertIsNotNone(building.function, 'building function is none') + self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none') + self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none') + self.assertEqual(len(building.heating), 0, 'building heating is not none') + self.assertEqual(len(building.cooling), 0, 'building cooling is not none') + self.assertIsNotNone(building.eave_height, 'building eave height is none') + self.assertIsNotNone(building.roof_type, 'building roof type is none') + self.assertIsNotNone(building.floor_area, 'building floor_area is none') + self.assertIsNone(building.households, 'building households is not none') + self.assertFalse(building.is_conditioned, 'building is conditioned') - # case 1: NREL - ConstructionFactory('nrel', city).enrich() + 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.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') + self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio, + 'thermal_zone indirectly_heated_area_ratio is none') + self.assertIsNotNone(thermal_zone.infiltration_rate_system_off, + 'thermal_zone infiltration_rate_system_off is none') + self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'thermal_zone infiltration_rate_system_on is none') + self.assertIsNotNone(thermal_zone.volume, 'thermal_zone volume is none') + self.assertIsNone(thermal_zone.ordinate_number, 'thermal_zone ordinate number is not none') + self.assertIsNotNone(thermal_zone.view_factors_matrix, 'thermal_zone view factors matrix is none') + self.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not none') + self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal_control is not none') + self.assertIsNone(thermal_zone.hvac_system, 'thermal_zone hvac_system is not none') + + def _check_thermal_boundaries(self, thermal_zone): + for thermal_boundary in thermal_zone.thermal_boundaries: + self.assertIsNotNone(thermal_boundary.id, 'thermal_boundary id is none') + self.assertIsNotNone(thermal_boundary.parent_surface, 'thermal_boundary surface is none') + self.assertIsNotNone(thermal_boundary.thermal_zones, 'thermal_boundary delimits no thermal zone') + self.assertIsNotNone(thermal_boundary.opaque_area, 'thermal_boundary area is none') + self.assertIsNotNone(thermal_boundary.azimuth, 'thermal_boundary azimuth is none') + self.assertIsNotNone(thermal_boundary.inclination, 'thermal_boundary inclination is none') + self.assertIsNotNone(thermal_boundary.thickness, 'thermal_boundary thickness is none') + self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is none') + if thermal_boundary.type is not cte.GROUND: + self.assertIsNotNone(thermal_boundary.outside_solar_absorptance, 'outside_solar_absorptance is none') + self.assertIsNotNone(thermal_boundary.shortwave_reflectance, 'shortwave_reflectance is none') + else: + self.assertIsNone(thermal_boundary.outside_solar_absorptance, 'outside_solar_absorptance is not none') + self.assertIsNone(thermal_boundary.shortwave_reflectance, 'shortwave_reflectance is not none') + self.assertIsNotNone(thermal_boundary.thermal_openings, 'thermal_openings is none') + self.assertIsNotNone(thermal_boundary.construction_name, 'construction_name is none') + self.assertIsNotNone(thermal_boundary.window_ratio, 'window_ratio is none') + self.assertIsNone(thermal_boundary.windows_areas, 'windows_areas is not none') + self.assertIsNotNone(thermal_boundary.u_value, 'u_value is none') + self.assertIsNotNone(thermal_boundary.hi, 'hi is none') + self.assertIsNotNone(thermal_boundary.he, 'he is none') + self.assertIsNotNone(thermal_boundary.virtual_internal_surface, 'virtual_internal_surface is none') + self.assertIsNone(thermal_boundary.inside_emissivity, 'inside_emissivity is not none') + self.assertIsNone(thermal_boundary.alpha_coefficient, 'alpha_coefficient is not none') + self.assertIsNone(thermal_boundary.radiative_coefficient, 'radiative_coefficient is not none') + + def _check_thermal_openings(self, thermal_boundary): + for thermal_opening in thermal_boundary.thermal_openings: + self.assertIsNotNone(thermal_opening.id, 'thermal opening id is not none') + self.assertIsNotNone(thermal_opening.area, 'thermal opening area is not none') + self.assertRaises(Exception, lambda: thermal_opening.openable_ratio, + 'thermal_opening openable_ratio is not raising an exception') + self.assertIsNotNone(thermal_opening.frame_ratio, 'thermal opening frame_ratio is not none') + self.assertIsNotNone(thermal_opening.g_value, 'thermal opening g_value is not none') + self.assertIsNotNone(thermal_opening.overall_u_value, 'thermal opening overall_u_value is not none') + self.assertIsNotNone(thermal_opening.hi, 'thermal opening hi is not none') + self.assertIsNotNone(thermal_opening.he, 'thermal opening he is not none') + self.assertIsNone(thermal_opening.inside_emissivity, 'thermal opening inside_emissivity is not none') + self.assertIsNone(thermal_opening.alpha_coefficient, 'thermal opening alpha_coefficient is not none') + self.assertIsNone(thermal_opening.radiative_coefficient, 'thermal opening radiative_coefficient is not none') + + def test_citygml_function(self): + """ + Test city objects' functions in the city + """ + # case 1: hft + file = 'one_building_in_kelowna.gml' + function_format = 'hft' + city = self._get_citygml(file) for building in city.buildings: - self.assertIsNotNone(building.average_storey_height, 'average_storey_height is none') - self.assertIsNotNone(building.storeys_above_ground, 'storeys_above_ground is none') - self.assertIsNot(len(building.thermal_zones), 0, 'no building thermal_zones defined') - for thermal_zone in building.thermal_zones: - self.assertIsNotNone(thermal_zone.effective_thermal_capacity, 'effective_thermal_capacity is none') - self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, - 'additional_thermal_bridge_u_value is none') - self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio, 'indirectly_heated_area_ratio is none') - self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'infiltration_rate_system_on is none') - self.assertIsNotNone(thermal_zone.infiltration_rate_system_off, 'infiltration_rate_system_off is none') - self.assertIsNotNone(thermal_zone.thermal_boundaries, 'thermal_boundaries is none') - for thermal_boundary in thermal_zone.thermal_boundaries: - if thermal_boundary.surface.type != 'Ground': - self.assertIsNotNone(thermal_boundary.outside_solar_absorptance, 'outside_solar_absorptance is none') - self.assertIsNotNone(thermal_boundary.window_ratio, 'window_ratio is none') - for layer in thermal_boundary.layers: - self.assertTrue(city.lca_material(layer.material.lca_id) is not None) + building.function = self._internal_function(function_format, building.function) + self.assertEqual(building.function, 'residential', 'format hft') + + # case 2: Pluto + file = 'pluto_building.gml' + function_format = 'pluto' + city = self._get_citygml(file) + for building in city.buildings: + building.function = self._internal_function(function_format, building.function) + self.assertEqual(building.function, 'secondary school', 'format pluto') + + # case 3: Alkis + file = 'one_building_in_kelowna_alkis.gml' + function_format = 'alkis' + city = self._get_citygml(file) + for building in city.buildings: + self.assertRaises(Exception, lambda: self._internal_function(function_format, building.function)) def test_city_with_construction_reduced_library(self): """ @@ -71,22 +165,69 @@ class TestConstructionFactory(TestCase): file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.hft_to_function[building.function] - # case 2: NRCAN + building.function = GeometryHelper.libs_function_from_hft(building.function) ConstructionFactory('nrcan', city).enrich() + + self._check_buildings(city) for building in city.buildings: - self.assertIsNotNone(building.average_storey_height, 'average_storey_height is none') - self.assertIsNotNone(building.storeys_above_ground, 'storeys_above_ground is none') - self.assertIsNot(len(building.thermal_zones), 0, 'no building thermal_zones defined') - for thermal_zone in building.thermal_zones: - self.assertIsNotNone(thermal_zone.effective_thermal_capacity, 'effective_thermal_capacity is none') - self.assertIsNotNone(thermal_zone.additional_thermal_bridge_u_value, - 'additional_thermal_bridge_u_value is none') - self.assertIsNotNone(thermal_zone.indirectly_heated_area_ratio, 'indirectly_heated_area_ratio is none') - self.assertIsNotNone(thermal_zone.infiltration_rate_system_on, 'infiltration_rate_system_on is none') - self.assertIsNotNone(thermal_zone.infiltration_rate_system_off, 'infiltration_rate_system_off is none') - self.assertIsNotNone(thermal_zone.thermal_boundaries, 'thermal_boundaries is none') - self.assertIsNot(len(thermal_zone.thermal_boundaries), 0, 'no boundaries of thermal_zone defined') - for thermal_boundary in thermal_zone.thermal_boundaries: - self.assertIsNotNone(thermal_boundary.outside_solar_absorptance, 'outside_solar_absorptance is none') - self.assertIsNotNone(thermal_boundary.window_ratio, 'window_ratio is none') + for internal_zone in building.internal_zones: + self._check_thermal_zones(internal_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_boundaries(thermal_zone) + for thermal_boundary in thermal_zone.thermal_boundaries: + self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') + self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') + self.assertIsNone(thermal_boundary.layers, 'layers is not none') + + self._check_thermal_openings(thermal_boundary) + for thermal_opening in thermal_boundary.thermal_openings: + self.assertIsNone(thermal_opening.conductivity, 'thermal_opening conductivity is not none') + self.assertIsNone(thermal_opening.thickness, 'thermal opening thickness is not none') + self.assertIsNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, + 'thermal opening front_side_solar_transmittance_at_normal_incidence is not none') + self.assertIsNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, + 'thermal opening back_side_solar_transmittance_at_normal_incidence is not none') + + def test_city_with_construction_extended_library(self): + """ + Enrich the city with the construction information and verify it + """ + file = 'pluto_building.gml' + city = self._get_citygml(file) + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_pluto(building.function) + ConstructionFactory('nrel', city).enrich() + + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self._check_thermal_zones(internal_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_boundaries(thermal_zone) + for thermal_boundary in thermal_zone.thermal_boundaries: + if thermal_boundary.type is not cte.GROUND: + self.assertIsNotNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is none') + self.assertIsNotNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is none') + else: + self.assertIsNone(thermal_boundary.outside_thermal_absorptance, 'outside_thermal_absorptance is not none') + self.assertIsNone(thermal_boundary.outside_visible_absorptance, 'outside_visible_absorptance is not none') + self.assertIsNotNone(thermal_boundary.layers, 'layers is none') + + self._check_thermal_openings(thermal_boundary) + for thermal_opening in thermal_boundary.thermal_openings: + self.assertIsNotNone(thermal_opening.conductivity, 'thermal_opening conductivity is none') + self.assertIsNotNone(thermal_opening.thickness, 'thermal opening thickness is none') + self.assertIsNotNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, + 'thermal opening front_side_solar_transmittance_at_normal_incidence is none') + self.assertIsNotNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, + 'thermal opening back_side_solar_transmittance_at_normal_incidence is none') + + @staticmethod + def _internal_function(function_format, original_function): + if function_format == 'hft': + new_function = GeometryHelper.libs_function_from_hft(original_function) + elif function_format == 'pluto': + new_function = GeometryHelper.libs_function_from_pluto(original_function) + else: + raise Exception('Function key not recognized. Implemented only "hft" and "pluto"') + return new_function diff --git a/unittests/test_customized_imports_factory.py b/unittests/test_customized_imports_factory.py index 80373480..245a8821 100644 --- a/unittests/test_customized_imports_factory.py +++ b/unittests/test_customized_imports_factory.py @@ -6,8 +6,8 @@ Copyright © 2021 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons from pathlib import Path from unittest import TestCase +import helpers.constants as cte from imports.geometry_factory import GeometryFactory -from imports.construction_factory import ConstructionFactory 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 @@ -28,7 +28,6 @@ class TestCustomizedImportsFactory(TestCase): file_path = (self._example_path / file).resolve() _city = GeometryFactory('citygml', file_path).city self.assertIsNotNone(_city, 'city is none') - ConstructionFactory('nrel', _city).enrich() UsageFactory('hft', _city).enrich() return _city @@ -44,6 +43,9 @@ class TestCustomizedImportsFactory(TestCase): CustomizedImportsFactory(scp, city).enrich() for building in city.buildings: - self.assertIsNot(len(building.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in building.usage_zones: - self.assertIsNotNone(usage_zone.mechanical_air_change, 'usage is none') + 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') diff --git a/unittests/test_doe_idf.py b/unittests/test_doe_idf.py index 76ce8440..c75e6372 100644 --- a/unittests/test_doe_idf.py +++ b/unittests/test_doe_idf.py @@ -34,15 +34,22 @@ class TestBuildings(TestCase): ExportsFactory('idf', city, output_path).export() self.assertEqual(10, len(city.buildings)) for building in city.buildings: - self.assertTrue(len(building.usage_zones) > 0) - for usage_zone in building.usage_zones: - self.assertIsNot(len(usage_zone.schedules), 0, 'no usage_zones schedules defined') - for schedule in usage_zone.schedules: - self.assertIsNotNone(schedule.type) - self.assertIsNotNone(schedule.values) - self.assertIsNotNone(schedule.data_type) - self.assertIsNotNone(schedule.time_step) - self.assertIsNotNone(schedule.time_range) - self.assertIsNotNone(schedule.day_types) - - + for internal_zone in building.internal_zones: + self.assertTrue(len(internal_zone.usage_zones) > 0) + for usage_zone in internal_zone.usage_zones: + self.assertIsNot(len(usage_zone.occupancy.occupancy_schedules), 0, 'no occupancy schedules defined') + for schedule in usage_zone.occupancy.occupancy_schedules: + self.assertIsNotNone(schedule.type) + self.assertIsNotNone(schedule.values) + self.assertIsNotNone(schedule.data_type) + self.assertIsNotNone(schedule.time_step) + self.assertIsNotNone(schedule.time_range) + self.assertIsNotNone(schedule.day_types) + self.assertIsNot(len(usage_zone.lighting.schedules), 0, 'no lighting schedules defined') + for schedule in usage_zone.lighting.schedules: + self.assertIsNotNone(schedule.type) + self.assertIsNotNone(schedule.values) + self.assertIsNotNone(schedule.data_type) + self.assertIsNotNone(schedule.time_step) + self.assertIsNotNone(schedule.time_range) + self.assertIsNotNone(schedule.day_types) diff --git a/unittests/test_enrichement.py b/unittests/test_enrichement.py new file mode 100644 index 00000000..95861429 --- /dev/null +++ b/unittests/test_enrichement.py @@ -0,0 +1,163 @@ +""" +TestGeometryFactory test and validate the city model structure geometric parameters +SPDX - License - Identifier: LGPL - 3.0 - or -later +Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca +""" +from pathlib import Path +from unittest import TestCase +from imports.geometry_factory import GeometryFactory +from imports.geometry.helpers.geometry_helper import GeometryHelper +from imports.usage_factory import UsageFactory +from imports.construction_factory import ConstructionFactory + + +class TestGeometryFactory(TestCase): + """ + Non-functional TestGeometryFactory + Load testing + """ + def setUp(self) -> None: + """ + Test setup + :return: None + """ + self._city = None + self._example_path = (Path(__file__).parent / 'tests_data').resolve() + + def _get_citygml(self, file): + file_path = (self._example_path / file).resolve() + self._city = GeometryFactory('citygml', file_path).city + self.assertIsNotNone(self._city, 'city is none') + return self._city + + def _check_buildings(self, city): + for building in city.buildings: + self.assertIsNotNone(building.internal_zones, 'no internal zones created') + for internal_zone in building.internal_zones: + self.assertIsNotNone(internal_zone.usage_zones, 'usage zones are not defined') + self.assertIsNotNone(internal_zone.thermal_zones, 'thermal zones are not defined') + #self.assertIsNotNone(building.basement_heated, 'building basement_heated is none') + #self.assertIsNotNone(building.attic_heated, 'building attic_heated is none') + self.assertIsNotNone(building.average_storey_height, 'building average_storey_height is none') + self.assertIsNotNone(building.storeys_above_ground, 'building storeys_above_ground is none') + self.assertTrue(building.is_conditioned, 'building is_conditioned is not conditioned') + + def _check_usage_zone(self, usage_zone): + self.assertIsNotNone(usage_zone.id, 'usage id is none') + + def _check_thermal_zones(self, thermal_zone): + self.assertIsNotNone(thermal_zone.id, 'thermal_zone id is none') + self.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not none') + self.assertIsNone(thermal_zone.thermal_control, 'thermal_zone thermal_control is not none') + + @staticmethod + def _prepare_case_usage_first(city, input_key, construction_key, usage_key): + if input_key == 'pluto': + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_pluto(building.function) + elif input_key == 'hft': + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_hft(building.function) + UsageFactory(usage_key, city).enrich() + ConstructionFactory(construction_key, city).enrich() + + @staticmethod + def _prepare_case_construction_first(city, input_key, construction_key, usage_key): + if input_key == 'pluto': + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_pluto(building.function) + elif input_key == 'hft': + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_hft(building.function) + ConstructionFactory(construction_key, city).enrich() + UsageFactory(usage_key, city).enrich() + + def test_enrichment(self): + """ + Test enrichment of the city with different order and all possible combinations + :return: None + """ + file_1 = 'one_building_in_kelowna.gml' + file_2 = 'pluto_building.gml' + file_3 = 'C40_Final.gml' + _construction_keys = ['nrel', 'nrcan'] + _usage_keys = ['ca', 'comnet'] # todo: add 'hft' + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + city = self._get_citygml(file_1) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_construction_first(city, 'hft', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + for usage_key in _usage_keys: + city = self._get_citygml(file_1) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_usage_first(city, 'hft', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + if construction_key != 'nrcan': + for usage_key in _usage_keys: + if usage_key != 'ca': + city = self._get_citygml(file_2) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_construction_first(city, 'pluto', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + for construction_key in _construction_keys: + if construction_key != 'nrcan': + for usage_key in _usage_keys: + if usage_key != 'ca': + city = self._get_citygml(file_2) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_usage_first(city, 'pluto', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) + + city = self._get_citygml(file_3) + self.assertTrue(len(city.buildings) == 10) + + for construction_key in _construction_keys: + if construction_key != 'nrcan': + for usage_key in _usage_keys: + if usage_key != 'ca': + city = self._get_citygml(file_2) + self.assertTrue(len(city.buildings) == 1) + self._prepare_case_construction_first(city, 'pluto', construction_key, usage_key) + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + for thermal_zone in internal_zone.thermal_zones: + self._check_thermal_zones(thermal_zone) diff --git a/unittests/test_exports.py b/unittests/test_exports.py index b4c38be1..20900b6f 100644 --- a/unittests/test_exports.py +++ b/unittests/test_exports.py @@ -9,8 +9,8 @@ from pathlib import Path from unittest import TestCase import pandas as pd from imports.geometry_factory import GeometryFactory +from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.construction_factory import ConstructionFactory -from imports.schedules_factory import SchedulesFactory from imports.usage_factory import UsageFactory from exports.exports_factory import ExportsFactory import helpers.constants as cte @@ -45,9 +45,10 @@ class TestExports(TestCase): else: file_path = (self._example_path / 'one_building_in_kelowna.gml').resolve() self._complete_city = self._get_citygml(file_path) + for building in self._complete_city.buildings: + building.function = GeometryHelper().libs_function_from_hft(building.function) ConstructionFactory('nrel', self._complete_city).enrich() UsageFactory('ca', self._complete_city).enrich() - SchedulesFactory('comnet', self._complete_city).enrich() cli = 'C:\\Users\\Pilar\\PycharmProjects\\monthlyenergybalance\\tests_data\\weather\\inseldb_Summerland.cli' self._complete_city.climate_file = Path(cli) self._complete_city.climate_reference_city = 'Summerland' diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index 77d17256..d3fc1473 100644 --- a/unittests/test_geometry_factory.py +++ b/unittests/test_geometry_factory.py @@ -6,8 +6,6 @@ Copyright © 2020 Project Author Pilar Monsalvete Alvarez de Uribarri pilar.mons from pathlib import Path from unittest import TestCase from imports.geometry_factory import GeometryFactory -from imports.geometry.helpers.geometry_helper import GeometryHelper -from city_model_structure.building_demand.thermal_opening import ThermalOpening class TestGeometryFactory(TestCase): @@ -36,20 +34,66 @@ class TestGeometryFactory(TestCase): self.assertIsNotNone(self._city, 'city is none') return self._city - @staticmethod - def _internal_function(function_format, original_function): - new_function = original_function - if function_format == 'hft': - new_function = GeometryHelper.hft_to_function[original_function] - elif function_format == 'pluto': - new_function = GeometryHelper.pluto_to_function[original_function] - elif function_format == 'alkis': - # todo: not implemented yet!! - raise NotImplementedError - return new_function + def _check_buildings(self, city): + for building in city.buildings: + self.assertIsNotNone(building.name, 'building name is none') + self.assertIsNotNone(building.lod, 'building lod is none') + self.assertIsNotNone(building.type, 'building type is none') + self.assertIsNotNone(building.volume, 'building volume is none') + self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') + self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none') + self.assertIsNotNone(building.surfaces, 'building surfaces is none') + self.assertIsNotNone(building.centroid, 'building centroid is none') + self.assertIsNotNone(building.max_height, 'building max_height is none') + self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') + self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') + self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') + self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertIsNotNone(building.lower_corner, 'building lower corner is none') + self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') + self.assertIsNotNone(building.grounds, 'building grounds is none') + self.assertIsNotNone(building.walls, 'building walls is none') + self.assertIsNotNone(building.roofs, 'building roofs is none') + self.assertIsNotNone(building.internal_zones, 'building internal zones is none') + for internal_zone in building.internal_zones: + self.assertIsNone(internal_zone.usage_zones, 'usage zones are defined') + self.assertIsNone(internal_zone.thermal_zones, 'thermal zones are defined') + self.assertIsNone(building.basement_heated, 'building basement_heated is not none') + self.assertIsNone(building.attic_heated, 'building attic_heated is not none') + self.assertIsNone(building.terrains, 'building terrains is not none') + self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') + self.assertIsNotNone(building.function, 'building function is none') + self.assertIsNone(building.average_storey_height, 'building average_storey_height is not none') + self.assertIsNone(building.storeys_above_ground, 'building storeys_above_ground is not none') + self.assertEqual(len(building.heating), 0, 'building heating is not none') + self.assertEqual(len(building.cooling), 0, 'building cooling is not none') + self.assertIsNotNone(building.eave_height, 'building eave height is none') + self.assertIsNotNone(building.roof_type, 'building roof type is none') + self.assertIsNotNone(building.floor_area, 'building floor_area is none') + self.assertIsNone(building.households, 'building households is not none') + self.assertFalse(building.is_conditioned, 'building is_conditioned is conditioned') + + def _check_surfaces(self, building): + for surface in building.surfaces: + self.assertIsNotNone(surface.name, 'surface name is none') + self.assertIsNotNone(surface.id, 'surface id is none') + self.assertIsNone(surface.swr, 'surface swr is not none') + self.assertIsNotNone(surface.lower_corner, 'surface envelope_lower_corner is none') + self.assertIsNotNone(surface.upper_corner, 'surface envelope_upper_corner is none') + self.assertIsNotNone(surface.area_below_ground, 'surface area_below_ground is none') + self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none') + self.assertIsNotNone(surface.azimuth, 'surface azimuth is none') + self.assertIsNotNone(surface.type, 'surface type is none') + self.assertIsNotNone(surface.inclination, 'surface inclination is none') + self.assertEqual(len(surface.global_irradiance), 0, 'global irradiance is calculated') + self.assertIsNotNone(surface.perimeter_polygon, 'surface perimeter_polygon is none') + self.assertIsNone(surface.holes_polygons, 'surface hole_polygons is not none') + self.assertIsNotNone(surface.solid_polygon, 'surface solid_polygon is none') + self.assertIsNone(surface.pv_system_installed, 'surface PV system installed is not none') # citygml_classes - def test_citygml_buildings(self): + def test_import_citygml(self): """ Test city objects in the city :return: None @@ -57,175 +101,9 @@ class TestGeometryFactory(TestCase): file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) self.assertTrue(len(city.buildings) == 1) + self._check_buildings(city) for building in city.buildings: - self.assertIsNotNone(building.name, 'building name is none') - self.assertIsNotNone(building.lod, 'building lod is none') - self.assertIsNotNone(building.centroid, 'building centroid is none') - self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') - self.assertIsNotNone(building.function, 'building function is none') - self.assertIsNotNone(building.volume, 'building volume is none') - self.assertIsNotNone(building.surfaces, 'building surfaces is none') - self.assertIsNotNone(building.surfaces[0].name, 'surface not found') - self.assertIsNone(building.basement_heated, 'building basement_heated is not none') - self.assertIsNone(building.attic_heated, 'building attic_heated is not none') - self.assertIsNotNone(building.terrains, 'building terrains is none') - self.assertIsNone(building.average_storey_height, 'building average_storey_height is not none') - self.assertIsNotNone(building.type, 'building type is none') - self.assertIsNotNone(building.max_height, 'building max_height is none') - self.assertIsNotNone(building.floor_area, 'building floor_area is none') - - def test_citygml_surfaces(self): - """ - Test surfaces in city objects - :return: None - """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - for surface in building.surfaces: - self.assertIsNotNone(surface.name, 'surface name is none') - self.assertIsNotNone(surface.type, 'surface type is none') - self.assertIsNotNone(surface.azimuth, 'surface azimuth is none') - self.assertIsNotNone(surface.inclination, 'surface inclination is none') - self.assertIsNotNone(surface.area_below_ground, 'surface area_below_ground is none') - self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none') - self.assertIsNotNone(surface.global_irradiance, 'monthly irradiance is none') - self.assertIsNone(surface.swr, 'surface swr is not none') - self.assertIsNotNone(surface.lower_corner, 'surface envelope_lower_corner is none') - self.assertIsNotNone(surface.upper_corner, 'surface envelope_upper_corner is none') - self.assertIsNotNone(surface.area_above_ground, 'surface area_above_ground is none') - self.assertIsNotNone(surface.perimeter_polygon, 'surface perimeter_polygon is none') - self.assertIsNone(surface.holes_polygons, 'surface hole_polygons is not none') - self.assertIsNotNone(surface.solid_polygon, 'surface solid_polygon is none') - - def test_citygml_thermal_zone(self): - """ - Test thermal zones in city objects - :return: None - """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - self.assertIsNot(len(building.thermal_zones), 0, 'no building thermal_zones defined') - for thermal_zone in building.thermal_zones: - self.assertIsNotNone(thermal_zone.thermal_boundaries, 'thermal_zone thermal_boundaries is none') - self.assertIsNotNone(thermal_zone.floor_area, 'thermal_zone floor_area is none') - self.assertIsNone(thermal_zone.additional_thermal_bridge_u_value, - 'thermal_zone additional_thermal_bridge_u_value is not none') - self.assertIsNone(thermal_zone.effective_thermal_capacity, - 'thermal_zone effective_thermal_capacity is not none') - self.assertIsNone(thermal_zone.indirectly_heated_area_ratio, - 'thermal_zone indirectly_heated_area_ratio is not none') - self.assertIsNone(thermal_zone.infiltration_rate_system_off, - 'thermal_zone infiltration_rate_system_off is not none') - self.assertIsNone(thermal_zone.infiltration_rate_system_on, - 'thermal_zone infiltration_rate_system_on is not none') - self.assertIsNone(thermal_zone.usage_zones, - 'thermal_zone usage_zones are not none') - - def test_citygml_thermal_boundary(self): - """ - Test thermal boundaries in thermal zones - :return: None - """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - self.assertIsNot(len(building.thermal_zones), 0, 'no building thermal_zones defined') - for thermal_zone in building.thermal_zones: - self.assertIsNot(len(thermal_zone.thermal_boundaries), 0, 'no building thermal_boundaries defined') - for thermal_boundary in thermal_zone.thermal_boundaries: - print(thermal_boundary.surface.type) - print(thermal_boundary.surface.area_above_ground) - self.assertIsNotNone(thermal_boundary.surface, 'thermal_boundary surface is none') - self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is none') - self.assertIsNotNone(thermal_boundary.area, 'thermal_boundary area is none') - self.assertIsNotNone(thermal_boundary.azimuth, 'thermal_boundary azimuth is none') - self.assertIsNotNone(thermal_boundary.thermal_zones, 'thermal_boundary delimits is none') - self.assertIsNotNone(thermal_boundary.inclination, 'thermal_boundary inclination is none') - - def test_citygml_thermal_opening(self): - """ - Test thermal openings in thermal zones - :return: None - """ - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - self.assertIsNot(len(building.thermal_zones), 0, 'no building thermal_zones defined') - for thermal_zone in building.thermal_zones: - self.assertIsNot(len(thermal_zone.thermal_boundaries), 0, 'no building thermal_boundaries defined') - for thermal_boundary in thermal_zone.thermal_boundaries: - thermal_opening: ThermalOpening - for thermal_opening in thermal_boundary.thermal_openings: - self.assertIsNone(thermal_opening.frame_ratio, 'thermal_opening frame_ratio was initialized') - self.assertIsNone(thermal_opening.g_value, 'thermal_opening g_value was initialized') - self.assertIsNone(thermal_opening.conductivity, 'thermal_opening conductivity_w_mk was initialized') - self.assertIsNone(thermal_opening.back_side_solar_transmittance_at_normal_incidence, - 'thermal_opening back_side_solar_transmittance_at_normal_incidence was initialized') - self.assertRaises(Exception, lambda: thermal_opening.openable_ratio, - 'thermal_opening openable_ratio is not raising an exception') - self.assertIsNone(thermal_opening.front_side_solar_transmittance_at_normal_incidence, - 'thermal_opening front_side_solar_transmittance_at_normal_incidence was initialized') - self.assertIsNone(thermal_opening.thickness, 'thermal_opening thickness_m was initialized') - self.assertRaises(Exception, lambda: thermal_opening.overall_u_value, - 'thermal_opening u_value was initialized') - self.assertIsNone(thermal_opening.overall_u_value, 'thermal_opening overall_u_value was initialized') - self.assertIsNone(thermal_opening.hi, 'thermal_opening hi was initialized') - self.assertIsNone(thermal_opening.he, 'thermal_opening he was initialized') - - def test_citygml_function(self): - """ - Test city objects' functions in the city - :return: None - """ - # case 1: hft - file = 'one_building_in_kelowna.gml' - function_format = 'hft' - city = self._get_citygml(file) - for building in city.buildings: - building.function = self._internal_function(function_format, building.function) - self.assertEqual(building.function, 'residential', 'format hft') - - # case 2: Pluto - file = 'pluto_building.gml' - function_format = 'pluto' - city = self._get_citygml(file) - for building in city.buildings: - building.function = self._internal_function(function_format, building.function) - self.assertEqual(building.function, 'secondary school', 'format pluto') - - # case 3: Alkis - file = 'one_building_in_kelowna_alkis.gml' - function_format = 'alkis' - city = self._get_citygml(file) - for building in city.buildings: - self.assertRaises(Exception, lambda: self._internal_function(function_format, building.function)) - - def test_citygml_storeys(self): - """ - Test division by storeys of buildings - :return: None - """ - - file = 'one_building_in_kelowna.gml' - city = self._get_citygml(file) - for building in city.buildings: - print('building') - for surface in building.surfaces: - print(surface.name) - print(surface.type) - print(surface.perimeter_polygon.area) - building.average_storey_height = 1.5 - building.storeys_above_ground = 2 - storeys = building.storeys - for storey in storeys: - print(storey.name) - print(storey.neighbours) - for surface in storey.surfaces: - print(surface.name) - print(surface.type) - print(surface.perimeter_polygon.area) + self._check_surfaces(building) # obj def test_import_obj(self): @@ -235,8 +113,10 @@ class TestGeometryFactory(TestCase): file = 'kelowna.obj' city = self._get_obj(file) self.assertIsNotNone(city, 'city is none') + self.assertTrue(len(city.buildings) == 1) + self._check_buildings(city) for building in city.buildings: - self.assertIsNotNone(building, 'building is none') + self._check_surfaces(building) # osm def test_subway(self): diff --git a/unittests/test_life_cycle_assessment_factory.py b/unittests/test_life_cycle_assessment_factory.py index 9e2e3e62..f7134f20 100644 --- a/unittests/test_life_cycle_assessment_factory.py +++ b/unittests/test_life_cycle_assessment_factory.py @@ -47,10 +47,8 @@ class TestLifeCycleAssessment(TestCase): city_file = "../unittests/tests_data/C40_Final.gml" city = GeometryFactory('citygml', city_file).city LifeCycleAssessment('material', city).enrich() - self.assertTrue(len(city.lca_materials) > 0) - for lca_material in city.lca_materials: - lca_mat = city.lca_material(lca_material.id) - self.assertTrue(lca_mat is not None) + for material in city.materials: + self.assertTrue(len(city.materials) > 0) diff --git a/unittests/test_schedules_factory.py b/unittests/test_schedules_factory.py index 349c5c08..f59f3cf6 100644 --- a/unittests/test_schedules_factory.py +++ b/unittests/test_schedules_factory.py @@ -31,30 +31,10 @@ class TestSchedulesFactory(TestCase): ConstructionFactory('nrel', _city).enrich() self.assertIsNotNone(_city, 'city is none') for building in _city.buildings: - building.function = GeometryHelper.hft_to_function[building.function] + building.function = GeometryHelper.libs_function_from_hft(building.function) UsageFactory('hft', _city).enrich() return _city - def test_comnet_archetypes(self): - """ - Enrich the city with commet schedule archetypes and verify it - """ - file = (self._example_path / 'one_building_in_kelowna.gml').resolve() - city = self._get_citygml(file) - occupancy_handler = 'comnet' - SchedulesFactory(occupancy_handler, city).enrich() - for building in city.buildings: - self.assertIsNot(len(building.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in building.usage_zones: - self.assertIsNot(len(usage_zone.schedules), 0, 'no usage_zones schedules defined') - for schedule in usage_zone.schedules: - self.assertIsNotNone(schedule.type) - self.assertIsNotNone(schedule.values) - self.assertIsNotNone(schedule.data_type) - self.assertIsNotNone(schedule.time_step) - self.assertIsNotNone(schedule.time_range) - self.assertIsNotNone(schedule.day_types) - def test_doe_idf_archetypes(self): """ Enrich the city with doe_idf schedule archetypes and verify it @@ -64,6 +44,22 @@ class TestSchedulesFactory(TestCase): occupancy_handler = 'doe_idf' SchedulesFactory(occupancy_handler, city).enrich() for building in city.buildings: - for usage_zone in building.usage_zones: - for schedule in usage_zone.schedules: - print(schedule) + for internal_zone in building.internal_zones: + self.assertTrue(len(internal_zone.usage_zones) > 0) + for usage_zone in internal_zone.usage_zones: + self.assertIsNot(len(usage_zone.occupancy.occupancy_schedules), 0, 'no occupancy schedules defined') + for schedule in usage_zone.occupancy.occupancy_schedules: + self.assertIsNotNone(schedule.type) + self.assertIsNotNone(schedule.values) + self.assertIsNotNone(schedule.data_type) + self.assertIsNotNone(schedule.time_step) + self.assertIsNotNone(schedule.time_range) + self.assertIsNotNone(schedule.day_types) + self.assertIsNot(len(usage_zone.lighting.schedules), 0, 'no lighting schedules defined') + for schedule in usage_zone.lighting.schedules: + self.assertIsNotNone(schedule.type) + self.assertIsNotNone(schedule.values) + self.assertIsNotNone(schedule.data_type) + self.assertIsNotNone(schedule.time_step) + self.assertIsNotNone(schedule.time_range) + self.assertIsNotNone(schedule.day_types) diff --git a/unittests/test_sensors_factory.py b/unittests/test_sensors_factory.py index f0de55c6..55b13e01 100644 --- a/unittests/test_sensors_factory.py +++ b/unittests/test_sensors_factory.py @@ -50,8 +50,8 @@ class TestSensorsFactory(TestCase): Load concordia sensors and verify it """ SensorsFactory('cec', self._city, self._end_point).enrich() - # SensorsFactory('cgf', self._city, self._end_point).enrich() - # SensorsFactory('ct', self._city, self._end_point).enrich() + SensorsFactory('cgf', self._city, self._end_point).enrich() + SensorsFactory('ct', self._city, self._end_point).enrich() for city_object in self._city.city_objects: print(city_object.name, len(city_object.sensors)) for sensor in city_object.sensors: diff --git a/unittests/test_usage_factory.py b/unittests/test_usage_factory.py index abb434f9..269457be 100644 --- a/unittests/test_usage_factory.py +++ b/unittests/test_usage_factory.py @@ -7,9 +7,7 @@ from pathlib import Path from unittest import TestCase from imports.geometry_factory import GeometryFactory -from imports.construction_factory import ConstructionFactory from imports.usage_factory import UsageFactory -from imports.construction_factory import ConstructionFactory from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.construction_factory import ConstructionFactory @@ -23,66 +21,161 @@ class TestUsageFactory(TestCase): Configure test environment :return: """ + self._city = None 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') - return _city + self._city = GeometryFactory('citygml', file_path).city + self.assertIsNotNone(self._city, 'city is none') + return self._city - def test_comnet(self): + def _check_buildings(self, city): + for building in city.buildings: + self.assertIsNotNone(building.name, 'building name is none') + self.assertIsNotNone(building.lod, 'building lod is none') + self.assertIsNotNone(building.type, 'building type is none') + self.assertIsNotNone(building.volume, 'building volume is none') + self.assertIsNotNone(building.detailed_polyhedron, 'building detailed polyhedron is none') + self.assertIsNotNone(building.simplified_polyhedron, 'building simplified polyhedron is none') + self.assertIsNotNone(building.surfaces, 'building surfaces is none') + self.assertIsNotNone(building.centroid, 'building centroid is none') + self.assertIsNotNone(building.max_height, 'building max_height is none') + self.assertEqual(len(building.external_temperature), 0, 'building external temperature is calculated') + self.assertEqual(len(building.global_horizontal), 0, 'building global horizontal is calculated') + self.assertEqual(len(building.diffuse), 0, 'building diffuse is calculated') + self.assertEqual(len(building.beam), 0, 'building beam is calculated') + self.assertIsNotNone(building.lower_corner, 'building lower corner is none') + self.assertEqual(len(building.sensors), 0, 'building sensors are assigned') + self.assertIsNotNone(building.internal_zones, 'no internal zones created') + self.assertIsNotNone(building.grounds, 'building grounds is none') + self.assertIsNotNone(building.walls, 'building walls is none') + self.assertIsNotNone(building.roofs, 'building roofs is none') + for internal_zone in building.internal_zones: + self.assertTrue(len(internal_zone.usage_zones) > 0, 'usage zones are not defined') + self.assertIsNone(internal_zone.thermal_zones, 'thermal zones are defined') + self.assertIsNone(building.basement_heated, 'building basement_heated is not none') + self.assertIsNone(building.attic_heated, 'building attic_heated is not none') + self.assertIsNone(building.terrains, 'building terrains is not none') + self.assertIsNotNone(building.year_of_construction, 'building year_of_construction is none') + self.assertIsNotNone(building.function, 'building function is none') + self.assertIsNone(building.average_storey_height, 'building average_storey_height is not none') + self.assertIsNone(building.storeys_above_ground, 'building storeys_above_ground is not none') + self.assertEqual(len(building.heating), 0, 'building heating is not none') + self.assertEqual(len(building.cooling), 0, 'building cooling is not none') + self.assertIsNotNone(building.eave_height, 'building eave height is none') + self.assertIsNotNone(building.roof_type, 'building roof type is none') + self.assertIsNotNone(building.floor_area, 'building floor_area is none') + self.assertIsNone(building.households, 'building households is not none') + self.assertTrue(building.is_conditioned, 'building is not conditioned') + + def _check_usage_zone(self, usage_zone): + self.assertIsNotNone(usage_zone.usage, 'usage is none') + self.assertIsNotNone(usage_zone.percentage, 'usage percentage is none') + self.assertIsNotNone(usage_zone.get_internal_gains, 'internal gains is none') + self.assertIsNotNone(usage_zone.hours_day, 'hours per day is none') + self.assertIsNotNone(usage_zone.days_year, 'days per year is none') + self.assertIsNotNone(usage_zone.thermal_control, 'thermal control is none') + self.assertIsNotNone(usage_zone.thermal_control.mean_heating_set_point, 'control heating set point is none') + self.assertIsNotNone(usage_zone.thermal_control.heating_set_back, 'control heating set back is none') + self.assertIsNotNone(usage_zone.thermal_control.mean_cooling_set_point, 'control cooling set point is none') + + def test_import_comnet(self): + """ + Enrich the city with the usage information from comnet and verify it + """ file = 'pluto_building.gml' city = self._get_citygml(file) for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] + building.function = GeometryHelper.libs_function_from_pluto(building.function) - ConstructionFactory('nrel', city).enrich() UsageFactory('comnet', city).enrich() - - def test_city_with_usage(self): - """ - Enrich the city with the usage information and verify it - :return: None - """ - # case 1: HFT - file = 'pluto_building.gml' - city = self._get_citygml(file) + self._check_buildings(city) for building in city.buildings: - building.function = GeometryHelper.pluto_to_function[building.function] - ConstructionFactory('nrel', city).enrich() - UsageFactory('hft', city).enrich() - for building in city.buildings: - self.assertIsNot(len(building.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in building.usage_zones: - self.assertIsNotNone(usage_zone.usage, 'usage is none') - self.assertIsNotNone(usage_zone.internal_gains, 'usage is none') - self.assertIsNotNone(usage_zone.cooling_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setback, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.occupancy_density, 'usage is none') - self.assertIsNotNone(usage_zone.hours_day, 'usage is none') - self.assertIsNotNone(usage_zone.days_year, 'usage is none') - self.assertIsNotNone(usage_zone.dhw_average_volume_pers_day, 'usage is none') - self.assertIsNotNone(usage_zone.dhw_preparation_temperature, 'usage is none') - self.assertIsNotNone(usage_zone.electrical_app_average_consumption_sqm_year, 'usage is none') - self.assertIsNotNone(usage_zone.is_heated, 'thermal_zone heated is none') - self.assertIsNotNone(usage_zone.is_cooled, 'thermal_zone cooled is none') + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + self.assertIsNotNone(usage_zone.mechanical_air_change, 'mechanical air change is none') + self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, + 'control heating set point schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, + 'control cooling set point schedule is none') + self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') + occupancy = usage_zone.occupancy + self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') + self.assertIsNotNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') + self.assertIsNotNone(occupancy.sensible_convective_internal_gain, + 'occupancy sensible convective internal gain is none') + self.assertIsNotNone(occupancy.sensible_radiative_internal_gain, + 'occupancy sensible radiant internal gain is none') + self.assertIsNotNone(occupancy.occupancy_schedules, 'occupancy schedule is none') + self.assertIsNone(occupancy.occupants, 'occupancy density is not none') + self.assertIsNotNone(usage_zone.lighting, 'lighting is none') + lighting = usage_zone.lighting + self.assertIsNotNone(lighting.lighting_density, 'lighting density is none') + self.assertIsNotNone(lighting.latent_fraction, 'lighting latent fraction is none') + self.assertIsNotNone(lighting.convective_fraction, 'lighting convective fraction is none') + self.assertIsNotNone(lighting.radiative_fraction, 'lighting radiant fraction is none') + self.assertIsNotNone(lighting.schedules, 'lighting schedule is none') + self.assertIsNotNone(usage_zone.appliances, 'appliances is none') + appliances = usage_zone.appliances + self.assertIsNotNone(appliances.appliances_density, 'appliances density is none') + self.assertIsNotNone(appliances.latent_fraction, 'appliances latent fraction is none') + self.assertIsNotNone(appliances.convective_fraction, 'appliances convective fraction is none') + self.assertIsNotNone(appliances.radiative_fraction, 'appliances radiant fraction is none') + self.assertIsNotNone(appliances.schedules, 'appliances schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.hvac_availability_schedules, + 'control hvac availability is none') - - # case 2: CA + def test_import_ca(self): + """ + Enrich the city with the usage information from canada and verify it + """ file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) - ConstructionFactory('nrel', city).enrich() UsageFactory('ca', city).enrich() + self._check_buildings(city) for building in city.buildings: - self.assertIsNot(len(building.usage_zones), 0, 'no building usage_zones defined') - for usage_zone in building.usage_zones: - self.assertIsNotNone(usage_zone.usage, 'usage is none') - self.assertIsNotNone(usage_zone.internal_gains, 'usage is none') - self.assertIsNotNone(usage_zone.cooling_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setback, 'usage is none') - self.assertIsNotNone(usage_zone.heating_setpoint, 'usage is none') - self.assertIsNotNone(usage_zone.occupancy_density, 'usage is none') - self.assertIsNotNone(usage_zone.hours_day, 'usage is none') - self.assertIsNotNone(usage_zone.days_year, 'usage is none') + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + self.assertIsNotNone(usage_zone.mechanical_air_change, 'mechanical air change is none') + self.assertIsNotNone(usage_zone.not_detailed_source_mean_annual_internal_gains, + 'not detailed internal gains is none') + + def test_import_hft(self): + """ + Enrich the city with the usage information from hft and verify it + """ + file = 'pluto_building.gml' + city = self._get_citygml(file) + for building in city.buildings: + building.function = GeometryHelper.libs_function_from_pluto(building.function) + + UsageFactory('hft', city).enrich() + self._check_buildings(city) + for building in city.buildings: + for internal_zone in building.internal_zones: + self.assertIsNot(len(internal_zone.usage_zones), 0, 'no building usage_zones defined') + for usage_zone in internal_zone.usage_zones: + self._check_usage_zone(usage_zone) + self.assertIsNone(usage_zone.mechanical_air_change, 'mechanical air change is not none') + self.assertIsNotNone(usage_zone.thermal_control.heating_set_point_schedules, + 'control heating set point schedule is none') + self.assertIsNotNone(usage_zone.thermal_control.cooling_set_point_schedules, + 'control cooling set point schedule is none') + self.assertIsNotNone(usage_zone.occupancy, 'occupancy is none') + occupancy = usage_zone.occupancy + self.assertIsNotNone(occupancy.occupancy_density, 'occupancy density is none') + self.assertIsNone(occupancy.latent_internal_gain, 'occupancy latent internal gain is none') + self.assertIsNone(occupancy.sensible_convective_internal_gain, + 'occupancy sensible convective internal gain is not none') + self.assertIsNone(occupancy.sensible_radiative_internal_gain, + 'occupancy sensible radiant internal gain is not none') + self.assertIsNone(occupancy.occupancy_schedules, 'occupancy schedule is not none') + self.assertIsNone(occupancy.occupants, 'occupancy density is not none') + self.assertIsNone(usage_zone.lighting, 'lighting is not none') + self.assertIsNone(usage_zone.appliances, 'appliances is not none') +