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/building.py b/city_model_structure/building.py index 951eab05..f7d2dc16 100644 --- a/city_model_structure/building.py +++ b/city_model_structure/building.py @@ -5,17 +5,17 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from typing import List, Union +from typing import List, Union, TypeVar 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.attributes.polyhedron import Polyhedron +ThermalZone = TypeVar('ThermalZone') + class Building(CityObject): """ @@ -35,9 +35,8 @@ class Building(CityObject): self._roof_type = None self._storeys = None self._geometrical_zones = None - self._thermal_zones = [] - self._thermal_boundaries = None - self._usage_zones = [] + self._thermal_zones = None + self._usage_zones = None self._type = 'building' self._heating = dict() self._cooling = dict() @@ -78,28 +77,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 +94,23 @@ class Building(CityObject): return self._walls @property - def usage_zones(self) -> List[UsageZone]: + def usage_zones(self) -> Union[None, 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 + @usage_zones.setter + def usage_zones(self, value): + """ + Set city object usage zones + :param value: [UsageZone] + """ + self._usage_zones = value + @property - def terrains(self) -> List[Surface]: + def terrains(self) -> Union[None, List[Surface]]: """ Get city object terrain surfaces :return: [Surface] @@ -170,26 +152,21 @@ class Building(CityObject): self._basement_heated = int(value) @property - def name(self): - """ - Get building name - :return: str - """ - return self._name - - @property - def thermal_zones(self) -> List[ThermalZone]: + def thermal_zones(self) -> Union[None, 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 + @thermal_zones.setter + def thermal_zones(self, value): + """ + Set city object thermal zones + :param value: [ThermalZone] + """ + self._thermal_zones = value + @property def heated_volume(self): """ @@ -213,7 +190,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]: @@ -341,6 +318,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 +339,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 +346,16 @@ class Building(CityObject): :return: List[Household] """ return self._households + + @property + def is_conditioned(self): + """ + Get building heated flag + :return: Boolean + """ + if self.thermal_zones is None: + return False + for thermal_zone in self.thermal_zones: + if thermal_zone.hvac_system 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..ed09f795 --- /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 +from city_model_structure.attributes.schedule import Schedule + + +class Appliances: + """ + Appliances class + """ + def __init__(self): + self._lighting_density = None + self._convective_fraction = None + self._radiant_fraction = None + self._latent_fraction = None + self._schedule = 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 radiant_fraction(self) -> Union[None, float]: + """ + Get radiant fraction + :return: None or float + """ + return self._radiant_fraction + + @radiant_fraction.setter + def radiant_fraction(self, value): + """ + Set radiant fraction + :param value: float + """ + if value is not None: + self._radiant_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 schedule(self) -> Union[None, Schedule]: + """ + Get schedule + :return: None or Schedule + """ + return self._schedule + + @schedule.setter + def schedule(self, value): + """ + Set schedule + :param value: Schedule + """ + self._schedule = value diff --git a/city_model_structure/building_demand/internal_gains.py b/city_model_structure/building_demand/internal_gains.py index 75e60cb7..4cfeabb9 100644 --- a/city_model_structure/building_demand/internal_gains.py +++ b/city_model_structure/building_demand/internal_gains.py @@ -5,6 +5,7 @@ Copyright © 2020 Project Author Guille Gutierrez guillermo.gutierrezmorote@conc """ from typing import Union +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._schedule = 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 schedule(self) -> Union[None, Schedule]: + """ + Get internal gain schedule + :return: Schedule + """ + return self._schedule + + @schedule.setter + def schedule(self, value): + """ + Set internal gain schedule + :param value: Schedule + """ + self._schedule = 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..0407a432 --- /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 +from city_model_structure.attributes.schedule import Schedule + + +class Lighting: + """ + Lighting class + """ + def __init__(self): + self._lighting_density = None + self._convective_fraction = None + self._radiant_fraction = None + self._latent_fraction = None + self._schedule = 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 radiant_fraction(self) -> Union[None, float]: + """ + Get radiant fraction + :return: None or float + """ + return self._radiant_fraction + + @radiant_fraction.setter + def radiant_fraction(self, value): + """ + Set radiant fraction + :param value: float + """ + if value is not None: + self._radiant_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 schedule(self) -> Union[None, Schedule]: + """ + Get schedule + :return: None or Schedule + """ + return self._schedule + + @schedule.setter + def schedule(self, value): + """ + Set schedule + :param value: Schedule + """ + self._schedule = 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..443030ca --- /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_radiant_internal_gain = None + self._latent_internal_gain = None + self._occupancy_schedule = 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_radiant_internal_gain(self) -> Union[None, float]: + """ + Get sensible radiant internal gain in Watts per m2 + :return: None or float + """ + return self._sensible_radiant_internal_gain + + @sensible_radiant_internal_gain.setter + def sensible_radiant_internal_gain(self, value): + """ + Set sensible radiant internal gain in Watts per m2 + :param value: float + """ + if value is not None: + self._sensible_radiant_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_schedule(self) -> Union[None, Schedule]: + """ + Get occupancy schedule + :return: None or Schedule + """ + return self._occupancy_schedule + + @occupancy_schedule.setter + def occupancy_schedule(self, value): + """ + Set occupancy schedule + :param value: Schedule + """ + self._occupancy_schedule = 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 79% rename from city_model_structure/building_demand/occupants.py rename to city_model_structure/building_demand/occupant.py index 3d7a39b6..8a7dd533 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): """ 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..20888023 --- /dev/null +++ b/city_model_structure/building_demand/thermal_control.py @@ -0,0 +1,86 @@ +""" +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 +from city_model_structure.attributes.schedule import Schedule + + +class ThermalControl: + """ + ThermalControl class + """ + def __init__(self): + self._heating_set_point = None + self._cooling_set_point = None + self._hvac_availability = None + self._heating_set_back = None + + @property + def heating_set_point(self) -> Union[None, Schedule]: + """ + Get heating set point defined for a thermal zone in Celsius + :return: None or Schedule + """ + return self._heating_set_point + + @heating_set_point.setter + def heating_set_point(self, value): + """ + Set heating set point defined for a thermal zone in Celsius + :param value: Schedule + """ + self._heating_set_point = value + + @property + def heating_set_back(self) -> Union[None, float]: + """ + Get heating set back defined for a thermal zone in Celsius + Heating set back is the only parameter which is not a schedule as it is either one value or it is implicit in the + set point schedule + :return: None or float + """ + 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 cooling_set_point(self) -> Union[None, Schedule]: + """ + Get cooling set point defined for a thermal zone in Celsius + :return: None or Schedule + """ + return self._cooling_set_point + + @cooling_set_point.setter + def cooling_set_point(self, value): + """ + Set cooling set point defined for a thermal zone in Celsius + :param value: Schedule + """ + self._cooling_set_point = value + + @property + def hvac_availability(self) -> Union[None, Schedule]: + """ + Get the availability of the conditioning system defined for a thermal zone + :return: None or Schedule + """ + return self._hvac_availability + + @hvac_availability.setter + def hvac_availability(self, value): + """ + Set the availability of the conditioning system defined for a thermal zone + :param value: Schedule + """ + self._hvac_availability = 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..dd20bc28 100644 --- a/city_model_structure/building_demand/thermal_zone.py +++ b/city_model_structure/building_demand/thermal_zone.py @@ -5,32 +5,33 @@ 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, Tuple, TypeVar from city_model_structure.building_demand.usage_zone import UsageZone -import ast +from city_model_structure.building_demand.thermal_control import ThermalControl +from city_model_structure.energy_systems.hvac_system import HvacSystem +from city_model_structure.attributes.schedule import Schedule 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._usage_zones = None self._volume = volume - self._volume_geometry = None - self._id = None self._ordinate_number = None + self._thermal_control = None + self._hvac_system = None @property def id(self): @@ -43,34 +44,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 +111,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 +122,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,15 +138,14 @@ 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) + self._infiltration_rate_system_off = value @property - def usage_zones(self) -> List[UsageZone]: + def usage_zones(self) -> Tuple[float, UsageZone]: """ - Get thermal zone usage zones + Get list of usage zones and the percentage of thermal zone's volume affected by that usage :return: [UsageZone] """ return self._usage_zones @@ -177,8 +153,8 @@ class ThermalZone: @usage_zones.setter def usage_zones(self, values): """ - Set thermal zone usage zones - :param values: [UsageZone] + Set list of usage zones and the percentage of thermal zone's volume affected by that usage + :param values: Tuple[float, UsageZone] """ self._usage_zones = values @@ -190,14 +166,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 +182,35 @@ class ThermalZone: """ if value is not None: self._ordinate_number = int(value) + + @property + def thermal_control(self) -> Union[None, ThermalControl]: + """ + Get thermal control of this thermal zone + :return: None or ThermalControl + """ + return self._thermal_control + + @thermal_control.setter + def thermal_control(self, value): + """ + Set thermal control for this thermal zone + :param value: ThermalControl + """ + self._thermal_control = 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 diff --git a/city_model_structure/building_demand/usage_zone.py b/city_model_structure/building_demand/usage_zone.py index a2c16435..590ce6d5 100644 --- a/city_model_structure/building_demand/usage_zone.py +++ b/city_model_structure/building_demand/usage_zone.py @@ -5,13 +5,12 @@ 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 class UsageZone: @@ -21,23 +20,15 @@ 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._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 @property def id(self): @@ -50,71 +41,20 @@ class UsageZone: return self._id @property - def internal_gains(self) -> List[InternalGains]: + def not_detailed_source_mean_annual_internal_gains(self) -> List[InternalGains]: """ - Get usage zone internal gains + 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]: @@ -184,89 +124,6 @@ class UsageZone: 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 +142,93 @@ 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 - @property - def is_cooled(self) -> Union[None, bool]: + def get_internal_gains(self) -> [InternalGains]: """ - Get thermal zone cooled flag - :return: None or Boolean + Calculates and returns the list of all internal gains defined + :return: InternalGains """ - return self._is_cooled - - @is_cooled.setter - def is_cooled(self, value): - """ - Set thermal zone cooled flag - :param value: Boolean - """ - if value is not None: - self._is_cooled = ast.literal_eval(value) + 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_radiant_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_radiant_internal_gain / _total_heat_gain + _internal_gain.convective_fraction = self.occupancy.sensible_convective_internal_gain / _total_heat_gain + _internal_gain.schedule = self.occupancy.occupancy_schedule + 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.radiant_fraction + _internal_gain.convective_fraction = self.lighting.convective_fraction + _internal_gain.schedule = self.lighting.schedule + 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.lighting_density + _internal_gain.latent_fraction = self.appliances.latent_fraction + _internal_gain.radiative_fraction = self.appliances.radiant_fraction + _internal_gain.convective_fraction = self.appliances.convective_fraction + _internal_gain.schedule = self.appliances.schedule + if self._internal_gains is not None: + self._internal_gains.append(_internal_gain) + else: + self._internal_gains = [_internal_gain] + return self._internal_gains 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/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..b55cf955 100644 --- a/helpers/constants.py +++ b/helpers/constants.py @@ -85,11 +85,22 @@ EDUCATION = 'education' 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..d04c81ea 100644 --- a/imports/construction/ca_physics_parameters.py +++ b/imports/construction/ca_physics_parameters.py @@ -33,7 +33,14 @@ class CaPhysicsParameters(NrelPhysicsInterface): f'and building year of construction: {building.year_of_construction}\n') continue - self._create_storeys(building, archetype) + # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned + if building.thermal_zones is None: + self._create_storeys(building, archetype) + thermal_zones = [] + for storey in building.storeys: + thermal_zones.append(storey.thermal_zone) + building.thermal_zones = thermal_zones + self._assign_values(building, archetype) def _search_archetype(self, function, year_of_construction): @@ -60,11 +67,15 @@ class CaPhysicsParameters(NrelPhysicsInterface): 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 + 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 is not None: - thermal_opening_archetype = thermal_boundary_archetype.thermal_opening + 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/storeys_generation.py b/imports/construction/helpers/storeys_generation.py index ae89cebb..b67a820d 100644 --- a/imports/construction/helpers/storeys_generation.py +++ b/imports/construction/helpers/storeys_generation.py @@ -1,7 +1,7 @@ """ 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 @@ -22,6 +22,9 @@ class StoreysGeneration: self._building = building 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]: @@ -34,7 +37,7 @@ 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)] + return [Storey('storey_0', self._building.surfaces, [None, None], self._building.volume, self._floor_area)] if number_of_storeys == 0: raise Exception('Number of storeys cannot be 0') @@ -79,13 +82,13 @@ 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)) + storeys.append(Storey(name, surfaces_child_last_storey, neighbours, volume, self._floor_area)) return storeys @staticmethod @@ -144,12 +147,12 @@ class StoreysGeneration: """ 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: + 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.surface.inclination) + grad = np.rad2deg(thermal_boundary.inclination) if grad >= 170: thermal_zones = [storey.thermal_zone, storey.neighbours[0]] else: diff --git a/imports/construction/nrel_physics_interface.py b/imports/construction/nrel_physics_interface.py index f01297b8..9065ca9e 100644 --- a/imports/construction/nrel_physics_interface.py +++ b/imports/construction/nrel_physics_interface.py @@ -6,10 +6,10 @@ 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 class NrelPhysicsInterface: @@ -71,7 +71,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 +81,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 +100,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 +149,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) diff --git a/imports/construction/us_physics_parameters.py b/imports/construction/us_physics_parameters.py index 76d7b0f4..6e19e96d 100644 --- a/imports/construction/us_physics_parameters.py +++ b/imports/construction/us_physics_parameters.py @@ -41,7 +41,14 @@ class UsPhysicsParameters(NrelPhysicsInterface): f'and building year of construction: {building.year_of_construction}\n') continue - self._create_storeys(building, archetype) + # if building has no thermal zones defined from geometry, one thermal zone per storey is assigned + if building.thermal_zones is None: + self._create_storeys(building, archetype) + thermal_zones = [] + for storey in building.storeys: + thermal_zones.append(storey.thermal_zone) + building.thermal_zones = thermal_zones + self._assign_values(building, archetype) def _search_archetype(self, building_type, standard, climate_zone): @@ -63,12 +70,15 @@ class UsPhysicsParameters(NrelPhysicsInterface): 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: - thermal_boundary.outside_solar_absorptance = thermal_boundary_archetype.outside_solar_absorptance + 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 + 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() @@ -83,13 +93,11 @@ class UsPhysicsParameters(NrelPhysicsInterface): 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 + 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 diff --git a/imports/customized_imports/sanam_customized_usage_parameters.py b/imports/customized_imports/sanam_customized_usage_parameters.py index 6604a8e5..442ec9d3 100644 --- a/imports/customized_imports/sanam_customized_usage_parameters.py +++ b/imports/customized_imports/sanam_customized_usage_parameters.py @@ -7,7 +7,7 @@ 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 +from imports.usage.data_classes.usage_zone_archetype import UsageZoneArchetype as huza import helpers.constants as cte 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/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/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..cd921332 100644 --- a/imports/usage/ca_usage_parameters.py +++ b/imports/usage/ca_usage_parameters.py @@ -58,14 +58,14 @@ class CaUsageParameters(HftUsageInterface): # 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: + 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.internal_gains = internal_gains + usage_zone.not_detailed_source_mean_annual_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 diff --git a/imports/usage/comnet_usage_parameters.py b/imports/usage/comnet_usage_parameters.py index 5d10312c..0d0a835d 100644 --- a/imports/usage/comnet_usage_parameters.py +++ b/imports/usage/comnet_usage_parameters.py @@ -12,9 +12,10 @@ from helpers.configuration_helper import ConfigurationHelper as ch from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.usage.helpers.usage_helper import UsageHelper from city_model_structure.building_demand.usage_zone import UsageZone +from city_model_structure.building_demand.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.internal_gains import InternalGains -from imports.usage.data_classes.hft_usage_zone_archetype import HftUsageZoneArchetype as huza -from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype as higa class ComnetUsageParameters: @@ -68,45 +69,44 @@ class ComnetUsageParameters: 'process': process_data} @staticmethod - def _parse_zone_usage_type(usage, data): - if data['occupancy'][usage][0] <= 0: - occupancy_density = 0 - else: - occupancy_density = 1 / data['occupancy'][usage][0] - mechanical_air_change = data['ventilation rate'][usage][0] - internal_gains = [] - # lighting - latent_fraction = ch().comnet_lighting_latent - convective_fraction = ch().comnet_lighting_convective - radiative_fraction = ch().comnet_lighting_radiant - average_internal_gain = data['lighting'][usage][4] - internal_gains.append(higa(internal_gains_type=cte.LIGHTING, average_internal_gain=average_internal_gain, - convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, - latent_fraction=latent_fraction)) - # occupancy - latent_fraction = data['occupancy'][usage][2] / (data['occupancy'][usage][1] + data['occupancy'][usage][2]) - sensible_fraction = float(1 - latent_fraction) - convective_fraction = sensible_fraction * ch().comnet_occupancy_sensible_convective - radiative_fraction = sensible_fraction * ch().comnet_occupancy_sensible_radiant - average_internal_gain = (data['occupancy'][usage][1] + data['occupancy'][usage][2]) \ - * occupancy_density * cte.BTU_H_TO_WATTS - internal_gains.append(higa(internal_gains_type=cte.OCCUPANCY, average_internal_gain=average_internal_gain, - convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, - latent_fraction=latent_fraction)) - # plug loads - if data['plug loads'][usage][0] != 'n.a.': - latent_fraction = ch().comnet_plugs_latent - convective_fraction = ch().comnet_plugs_convective - radiative_fraction = ch().comnet_plugs_radiant - average_internal_gain = data['plug loads'][usage][0] - internal_gains.append(higa(internal_gains_type=cte.RECEPTACLE, average_internal_gain=average_internal_gain, - convective_fraction=convective_fraction, radiative_fraction=radiative_fraction, - latent_fraction=latent_fraction)) + def _parse_zone_usage_type(usage, height, data): + _usage_zone = UsageZone() + _usage_zone.usage = usage - 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.average_internal_gain = data['lighting'][usage][4] + + # plug loads + _appliances = None + if data['plug loads'][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.average_internal_gain = data['plug loads'][usage][0] + + # occupancy + _occupancy = Occupancy() + _occupancy.occupancy_density = data['occupancy'][usage][0] * cte.METERS_TO_FEET**2 + _occupancy.sensible_convective_internal_gain = data['occupancy'][usage][1] \ + * ch().comnet_occupancy_sensible_convective + _occupancy.sensible_radiant_internal_gain = data['occupancy'][usage][1] * ch().comnet_occupancy_sensible_radiant + _occupancy.latent_internal_gain = data['occupancy'][usage][2] + + if _occupancy.occupancy_density <= 0: + _usage_zone.mechanical_air_change = 0 + else: + _usage_zone.mechanical_air_change = data['ventilation rate'][usage][0] / _occupancy.occupancy_density \ + * cte.HOUR_TO_MINUTES / cte.METERS_TO_FEET**3 / height + + _usage_zone.occupancy = _occupancy + _usage_zone.lighting = _lighting + _usage_zone.appliances = _appliances + return _usage_zone def enrich_buildings(self): """ @@ -151,12 +151,11 @@ class ComnetUsageParameters: 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.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.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_density = archetype.occupancy_density + usage_zone.mechanical_air_change = archetype.mechanical_air_change \ No newline at end of file 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/hft_usage_interface.py b/imports/usage/hft_usage_interface.py index 3da6457d..ed6dfefc 100644 --- a/imports/usage/hft_usage_interface.py +++ b/imports/usage/hft_usage_interface.py @@ -5,7 +5,7 @@ SPDX - License - Identifier: LGPL - 3.0 - or -later Copyright © 2020 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.usage_zone_archetype import UsageZoneArchetype as huza from imports.usage.data_classes.hft_internal_gains_archetype import HftInternalGainsArchetype as higa @@ -96,7 +96,7 @@ class HftUsageInterface: # 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] + internal_gains = usage_zone.not_detailed_source_mean_annual_internal_gains[0] latent_fraction = internal_gains.latent_fraction convective_fraction = internal_gains.convective_fraction average_internal_gain = internal_gains.average_internal_gain diff --git a/imports/usage/hft_usage_parameters.py b/imports/usage/hft_usage_parameters.py index fc254aa2..d2deeeae 100644 --- a/imports/usage/hft_usage_parameters.py +++ b/imports/usage/hft_usage_parameters.py @@ -58,14 +58,14 @@ class HftUsageParameters(HftUsageInterface): # 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: + 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.internal_gains = internal_gains + usage_zone.not_detailed_source_mean_annual_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 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..e0ece40a 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,138 @@ 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.geometrical_zones, 'no geometrical 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.assertIsNone(building.usage_zones, 'usage zones are defined') + self.assertTrue(len(building.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.storeys, 'building storeys are not defined') + 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, building): + for thermal_zone in building.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.assertIsNone(thermal_zone.usage_zones, 'thermal_zone usage_zones is not 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.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 is none') + 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): """ @@ -72,21 +165,69 @@ class TestConstructionFactory(TestCase): city = self._get_citygml(file) for building in city.buildings: building.function = GeometryHelper.hft_to_function[building.function] - # case 2: NRCAN 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') + self._check_thermal_zones(building) 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') + self._check_thermal_boundaries(thermal_zone) 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') + 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.pluto_to_function[building.function] + ConstructionFactory('nrel', city).enrich() + + self._check_buildings(city) + for building in city.buildings: + self._check_thermal_zones(building) + for thermal_zone in building.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.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 + else: + raise Exception('Function key not recognized. Implemented only "hft" and "pluto"') + return new_function diff --git a/unittests/test_geometry_factory.py b/unittests/test_geometry_factory.py index 77d17256..b9142591 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,65 @@ 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.geometrical_zones, 'no geometrical 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.assertIsNone(building.usage_zones, 'usage zones are defined') + self.assertIsNone(building.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.assertIsNone(building.storeys, 'building storeys are defined') + 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 +100,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 +112,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..0506abae 100644 --- a/unittests/test_schedules_factory.py +++ b/unittests/test_schedules_factory.py @@ -67,3 +67,4 @@ class TestSchedulesFactory(TestCase): for usage_zone in building.usage_zones: for schedule in usage_zone.schedules: print(schedule) + print(usage_zone.schedules[schedule]) 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..3f8f38ea 100644 --- a/unittests/test_usage_factory.py +++ b/unittests/test_usage_factory.py @@ -7,8 +7,8 @@ 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.schedules_factory import SchedulesFactory from imports.construction_factory import ConstructionFactory from imports.geometry.helpers.geometry_helper import GeometryHelper from imports.construction_factory import ConstructionFactory @@ -23,29 +23,119 @@ 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.geometrical_zones, 'no geometrical 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.assertTrue(len(building.usage_zones) > 0, 'usage zones are not defined') + self.assertTrue(len(building.thermal_zones) > 0, '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.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.storeys, 'building storeys are not defined') + 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_thermal_zones(self, building): + for thermal_zone in building.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.usage_zones, 'thermal_zone usage_zones 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.thermal_control, 'thermal_zone thermal_control is not none') + self.assertIsNotNone(thermal_zone.hvac_system, 'thermal_zone hvac_system is not none') + + def _check_usage_zones(self, building): + for usage_zone in building.usage_zones: + self.assertIsNotNone(usage_zone.usage, 'usage is none') + self.assertIsNotNone(usage_zone.not_detailed_source_mean_annual_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') + + def _check_hvac(self, thermal_zone): + self.assertIsNotNone(None, 'hvac') + + def _check_control(self, thermal_zone): + self.assertIsNotNone(None, 'control') + + 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] - ConstructionFactory('nrel', city).enrich() - UsageFactory('comnet', city).enrich() - def test_city_with_usage(self): + UsageFactory('comnet', city).enrich() + SchedulesFactory('comnet', city).enrich() + self._check_buildings(city) + for building in city.buildings: + self._check_thermal_zones(building) + for thermal_zone in building.thermal_zones: + self._check_hvac(thermal_zone) + self._check_control(thermal_zone) + self.assertIsNotNone(building.usage_zones, 'building usage zones is none') + self._check_usage_zones(building) + + def test_import_hft(self): """ - Enrich the city with the usage information and verify it - :return: None + Enrich the city with the usage information from hft and verify it """ - # case 1: HFT file = 'pluto_building.gml' city = self._get_citygml(file) for building in city.buildings: @@ -56,7 +146,7 @@ class TestUsageFactory(TestCase): 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.not_detailed_source_mean_annual_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') @@ -69,8 +159,10 @@ class TestUsageFactory(TestCase): self.assertIsNotNone(usage_zone.is_heated, 'thermal_zone heated is none') self.assertIsNotNone(usage_zone.is_cooled, 'thermal_zone cooled is none') - - # case 2: CA + def test_import_ca(self): + """ + Enrich the city with the usage information from hft and verify it + """ file = 'one_building_in_kelowna.gml' city = self._get_citygml(file) ConstructionFactory('nrel', city).enrich() @@ -79,7 +171,7 @@ class TestUsageFactory(TestCase): 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.not_detailed_source_mean_annual_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')