diff --git a/hub/city_model_structure/building.py b/hub/city_model_structure/building.py index 9b085a2a..ee5f86f3 100644 --- a/hub/city_model_structure/building.py +++ b/hub/city_model_structure/building.py @@ -114,7 +114,8 @@ class Building(CityObject): :return: [InternalZone] """ if self._internal_zones is None: - self._internal_zones = [InternalZone(self.surfaces, self.floor_area, self.volume)] + _number_of_storeys = self.eave_height * self.volume / self.floor_area + self._internal_zones = [InternalZone(self.surfaces, self.floor_area, self.volume, _number_of_storeys)] return self._internal_zones @property @@ -258,6 +259,12 @@ class Building(CityObject): Get building average storey height in meters :return: None or float """ + if len(self.internal_zones) > 1: + self._average_storey_height = 0 + for internal_zone in self.internal_zones: + self._average_storey_height += internal_zone.mean_height / len(self.internal_zones) + else: + self._average_storey_height = self.internal_zones[0].thermal_archetype.average_storey_height return self._average_storey_height @average_storey_height.setter @@ -393,6 +400,7 @@ class Building(CityObject): """ results = {} peak_lighting = 0 + peak = 0 for thermal_zone in self.thermal_zones_from_internal_zones: lighting = thermal_zone.lighting for schedule in lighting.schedules: @@ -411,6 +419,7 @@ class Building(CityObject): """ results = {} peak_appliances = 0 + peak = 0 for thermal_zone in self.thermal_zones_from_internal_zones: appliances = thermal_zone.appliances for schedule in appliances.schedules: diff --git a/hub/city_model_structure/building_demand/internal_zone.py b/hub/city_model_structure/building_demand/internal_zone.py index aa2cefa5..30c214ad 100644 --- a/hub/city_model_structure/building_demand/internal_zone.py +++ b/hub/city_model_structure/building_demand/internal_zone.py @@ -8,6 +8,7 @@ Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca import uuid from typing import Union, List from hub.city_model_structure.building_demand.usage import Usage +from hub.city_model_structure.building_demand.thermal_archetype import ThermalArchetype from hub.city_model_structure.building_demand.thermal_zone import ThermalZone from hub.city_model_structure.building_demand.thermal_boundary import ThermalBoundary from hub.city_model_structure.attributes.polyhedron import Polyhedron @@ -18,14 +19,16 @@ class InternalZone: """ InternalZone class """ - def __init__(self, surfaces, area, volume): + def __init__(self, surfaces, area, volume, number_of_storeys=None): self._surfaces = surfaces self._id = None self._geometry = None self._volume = volume self._area = area + self._number_of_storeys = number_of_storeys self._thermal_zones_from_internal_zones = None self._usages = None + self._thermal_archetype = None self._hvac_system = None @property @@ -75,10 +78,18 @@ class InternalZone: """ return self._area + @property + def mean_height(self): + """ + Get internal zone mean height in meters + :return: float + """ + return self.volume / self.area + @property def usages(self) -> [Usage]: """ - Get internal zone usage zones + Get usage archetypes :return: [Usage] """ return self._usages @@ -86,11 +97,27 @@ class InternalZone: @usages.setter def usages(self, value): """ - Set internal zone usage zones + Set usage archetypes :param value: [Usage] """ self._usages = value + @property + def thermal_archetype(self) -> ThermalArchetype: + """ + Get thermal archetype parameters + :return: ThermalArchetype + """ + return self._thermal_archetype + + @thermal_archetype.setter + def thermal_archetype(self, value): + """ + Set thermal archetype parameters + :param value: ThermalArchetype + """ + self._thermal_archetype = value + @property def hvac_system(self) -> Union[None, HvacSystem]: """ @@ -123,7 +150,9 @@ class InternalZone: windows_areas.append(hole.area) _thermal_boundary = ThermalBoundary(surface, surface.solid_polygon.area, windows_areas) _thermal_boundaries.append(_thermal_boundary) - _thermal_zone = ThermalZone(_thermal_boundaries, self, self.volume, self.area) + _thermal_zone = ThermalZone(_thermal_boundaries, self, self.volume, self.area, self._number_of_storeys) + for thermal_boundary in _thermal_zone.thermal_boundaries: + thermal_boundary.thermal_zones = [_thermal_zone] self._thermal_zones_from_internal_zones = [_thermal_zone] return self._thermal_zones_from_internal_zones diff --git a/hub/city_model_structure/building_demand/thermal_archetype.py b/hub/city_model_structure/building_demand/thermal_archetype.py index 47daf8a0..cd70912a 100644 --- a/hub/city_model_structure/building_demand/thermal_archetype.py +++ b/hub/city_model_structure/building_demand/thermal_archetype.py @@ -29,6 +29,14 @@ class ThermalArchetype: """ return self._constructions + @constructions.setter + def constructions(self, value): + """ + Set archetype constructions + :param value: [Construction] + """ + self._constructions = value + @property def average_storey_height(self): """ diff --git a/hub/city_model_structure/building_demand/thermal_boundary.py b/hub/city_model_structure/building_demand/thermal_boundary.py index d17a5b45..5858007c 100644 --- a/hub/city_model_structure/building_demand/thermal_boundary.py +++ b/hub/city_model_structure/building_demand/thermal_boundary.py @@ -7,7 +7,9 @@ Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concord """ import uuid +import math from typing import List, Union, TypeVar +import logging from hub.helpers.configuration_helper import ConfigurationHelper as ch import hub.helpers.constants as cte from hub.city_model_structure.building_demand.layer import Layer @@ -92,7 +94,7 @@ class ThermalBoundary: self._thickness = 0.0 if self.layers is not None: for layer in self.layers: - if not layer.material.no_mass: + if not layer.no_mass: self._thickness += layer.thickness return self._thickness @@ -148,24 +150,21 @@ class ThermalBoundary: else: _area = self.opaque_area * self.window_ratio / (1-self.window_ratio) _thermal_opening.area = _area + self._thermal_openings = [_thermal_opening] + for thermal_opening in self._thermal_openings: + thermal_opening.g_value = self._construction_archetype.window_g_value + thermal_opening.overall_u_value = self._construction_archetype.window_overall_u_value + thermal_opening.frame_ratio = self._construction_archetype.window_frame_ratio + thermal_opening.construction_name = self._construction_archetype.window_type return self._thermal_openings @property - def construction_name(self) -> Union[None, str]: - """ - Get construction name - :return: None or str - """ - return self._construction_name - - @construction_name.setter - def construction_name(self, value): - """ - Set construction name - :param value: str - """ - if value is not None: - self._construction_name = str(value) + def _construction_archetype(self): + construction_archetypes = self.thermal_zones[0].parent_internal_zone.thermal_archetype.constructions + for construction_archetype in construction_archetypes: + if str(self.type) == str(construction_archetype.type): + return construction_archetype + return None @property def layers(self) -> List[Layer]: @@ -173,16 +172,13 @@ class ThermalBoundary: Get thermal boundary layers :return: [Layers] """ + if self._construction_archetype is not None: + self._layers = self._construction_archetype.layers + else: + logging.error('Layers not defined\n') + raise ValueError('Layers not defined') return self._layers - @layers.setter - def layers(self, value): - """ - Set thermal boundary layers - :param value: [Layer] - """ - self._layers = value - @property def type(self): """ @@ -209,18 +205,23 @@ class ThermalBoundary: for window_area in self.windows_areas: total_window_area += window_area self._window_ratio = total_window_area / (self.opaque_area + total_window_area) + else: + if self.type in (cte.WALL, cte.ROOF): + if -math.sqrt(2) / 2 < math.sin(self.parent_surface.azimuth) < math.sqrt(2) / 2: + if 0 < math.cos(self.parent_surface.azimuth): + self._window_ratio = \ + float(self._construction_archetype.window_ratio['north']) / 100 + else: + self._window_ratio = \ + float(self._construction_archetype.window_ratio['south']) / 100 + elif math.sqrt(2) / 2 <= math.sin(self._parent_surface.azimuth): + self._window_ratio = \ + float(self._construction_archetype.window_ratio['east']) / 100 + else: + self._window_ratio = \ + float(self._construction_archetype.window_ratio['west']) / 100 return self._window_ratio - @window_ratio.setter - def window_ratio(self, value): - """ - Set thermal boundary window ratio - :param value: str - """ - if self._window_ratio_to_be_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]: """ @@ -245,10 +246,10 @@ class ThermalBoundary: r_value = 1.0/h_i + 1.0/h_e try: for layer in self.layers: - if layer.material.no_mass: - r_value += float(layer.material.thermal_resistance) + if layer.no_mass: + r_value += float(layer.thermal_resistance) else: - r_value += float(layer.thickness) / float(layer.material.conductivity) + r_value += float(layer.thickness) / float(layer.conductivity) self._u_value = 1.0/r_value except TypeError: raise TypeError('Constructions layers are not initialized') from TypeError diff --git a/hub/city_model_structure/building_demand/thermal_zone.py b/hub/city_model_structure/building_demand/thermal_zone.py index 42cbfa78..50179cf4 100644 --- a/hub/city_model_structure/building_demand/thermal_zone.py +++ b/hub/city_model_structure/building_demand/thermal_zone.py @@ -29,7 +29,12 @@ class ThermalZone: ThermalZone class """ - def __init__(self, thermal_boundaries, parent_internal_zone, volume, footprint_area, usage_name=None): + def __init__(self, thermal_boundaries, + parent_internal_zone, + volume, + footprint_area, + number_of_storeys, + usage_name=None): self._id = None self._parent_internal_zone = parent_internal_zone self._footprint_area = footprint_area @@ -43,6 +48,7 @@ class ThermalZone: self._ordinate_number = None self._view_factors_matrix = None self._total_floor_area = None + self._number_of_storeys = number_of_storeys self._usage_name = usage_name self._usage_from_parent = False if usage_name is None: @@ -58,6 +64,14 @@ class ThermalZone: self._domestic_hot_water = None self._usages = None + @property + def parent_internal_zone(self) -> InternalZone: + """ + Get the internal zone to which this thermal zone belongs + :return: InternalZone + """ + return self._parent_internal_zone + @property def usages(self): """ @@ -113,83 +127,45 @@ class ThermalZone: Get thermal zone additional thermal bridge u value per footprint area W/m2K :return: None or float """ + self._additional_thermal_bridge_u_value = self.parent_internal_zone.thermal_archetype.extra_loses_due_to_thermal_bridges return self._additional_thermal_bridge_u_value - @additional_thermal_bridge_u_value.setter - def additional_thermal_bridge_u_value(self, value): - """ - Set thermal zone additional thermal bridge u value per footprint area W/m2K - :param value: float - """ - if value is not None: - self._additional_thermal_bridge_u_value = float(value) - @property def effective_thermal_capacity(self) -> Union[None, float]: """ Get thermal zone effective thermal capacity in J/m3K :return: None or float """ + self._effective_thermal_capacity = self._parent_internal_zone.thermal_archetype.thermal_capacity return self._effective_thermal_capacity - @effective_thermal_capacity.setter - def effective_thermal_capacity(self, value): - """ - Set thermal zone effective thermal capacity in J/m3K - :param value: float - """ - if value is not None: - self._effective_thermal_capacity = float(value) - @property def indirectly_heated_area_ratio(self) -> Union[None, float]: """ Get thermal zone indirectly heated area ratio :return: None or float """ + self._indirectly_heated_area_ratio = self._parent_internal_zone.thermal_archetype.indirect_heated_ratio return self._indirectly_heated_area_ratio - @indirectly_heated_area_ratio.setter - def indirectly_heated_area_ratio(self, value): - """ - Set thermal zone indirectly heated area ratio - :param value: float - """ - if value is not None: - self._indirectly_heated_area_ratio = float(value) - @property def infiltration_rate_system_on(self): """ Get thermal zone infiltration rate system on in air changes per hour (ACH) :return: None or float """ + self._infiltration_rate_system_on = self._parent_internal_zone.thermal_archetype.infiltration_rate_for_ventilation_system_on return self._infiltration_rate_system_on - @infiltration_rate_system_on.setter - def infiltration_rate_system_on(self, value): - """ - Set thermal zone infiltration rate system on in air changes per hour (ACH) - :param value: float - """ - self._infiltration_rate_system_on = value - @property def infiltration_rate_system_off(self): """ Get thermal zone infiltration rate system off in air changes per hour (ACH) :return: None or float """ + self._infiltration_rate_system_off = self._parent_internal_zone.thermal_archetype.infiltration_rate_for_ventilation_system_off return self._infiltration_rate_system_off - @infiltration_rate_system_off.setter - def infiltration_rate_system_off(self, value): - """ - Set thermal zone infiltration rate system on in air changes per hour (ACH) - :param value: float - """ - self._infiltration_rate_system_off = value - @property def volume(self): """ @@ -684,12 +660,5 @@ class ThermalZone: Get the total floor area of this thermal zone :return: float """ + self._total_floor_area = self.footprint_area * self._number_of_storeys return self._total_floor_area - - @total_floor_area.setter - def total_floor_area(self, value): - """ - Set the total floor area of this thermal zone - :param value: float - """ - self._total_floor_area = value diff --git a/hub/helpers/thermal_zones_creation.py b/hub/helpers/thermal_zones_creation.py index 31398ae9..f116a272 100644 --- a/hub/helpers/thermal_zones_creation.py +++ b/hub/helpers/thermal_zones_creation.py @@ -62,11 +62,3 @@ class ThermalZonesCreation: thermal_zones = StoreysGeneration(building, building.internal_zones[0], divide_in_storeys=divide_in_storeys).thermal_zones building.internal_zones[0].thermal_zones_from_internal_zones = thermal_zones - - @staticmethod - def _search_construction_in_archetype(archetype, construction_type): - construction_archetypes = archetype.constructions - for construction_archetype in construction_archetypes: - if str(construction_type) == str(construction_archetype.type): - return construction_archetype - return None diff --git a/hub/imports/construction/eilat_physics_parameters.py b/hub/imports/construction/eilat_physics_parameters.py index 664ea121..d048dbd1 100644 --- a/hub/imports/construction/eilat_physics_parameters.py +++ b/hub/imports/construction/eilat_physics_parameters.py @@ -59,6 +59,7 @@ class EilatPhysicsParameters: @staticmethod def _assign_values(thermal_archetype, catalog_archetype): + thermal_archetype.average_storey_height = catalog_archetype.average_storey_height thermal_archetype.extra_loses_due_to_thermal_bridges = catalog_archetype.extra_loses_due_to_thermal_bridges thermal_archetype.indirect_heated_ratio = 0 thermal_archetype.infiltration_rate_for_ventilation_system_on = catalog_archetype.infiltration_rate_for_ventilation_system_on diff --git a/hub/imports/construction/helpers/construction_helper.py b/hub/imports/construction/helpers/construction_helper.py index 9ed66da2..5d697354 100644 --- a/hub/imports/construction/helpers/construction_helper.py +++ b/hub/imports/construction/helpers/construction_helper.py @@ -5,8 +5,6 @@ Copyright © 2022 Concordia CERC group Project Coder Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca """ -from hub.helpers import constants as cte - class ConstructionHelper: """ diff --git a/hub/imports/construction/nrcan_physics_parameters.py b/hub/imports/construction/nrcan_physics_parameters.py index 523243cc..b2c40fee 100644 --- a/hub/imports/construction/nrcan_physics_parameters.py +++ b/hub/imports/construction/nrcan_physics_parameters.py @@ -46,6 +46,8 @@ class NrcanPhysicsParameters: continue thermal_archetype = ThermalArchetype() self._assign_values(thermal_archetype, archetype) + for internal_zone in building.internal_zones: + internal_zone.thermal_archetype = thermal_archetype @staticmethod def _search_archetype(nrcan_catalog, function, year_of_construction, climate_zone): @@ -59,13 +61,20 @@ class NrcanPhysicsParameters: @staticmethod def _assign_values(thermal_archetype, catalog_archetype): + thermal_archetype.average_storey_height = catalog_archetype.average_storey_height thermal_archetype.extra_loses_due_to_thermal_bridges = catalog_archetype.extra_loses_due_to_thermal_bridges thermal_archetype.thermal_capacity = catalog_archetype.thermal_capacity thermal_archetype.indirect_heated_ratio = 0 thermal_archetype.infiltration_rate_for_ventilation_system_on = catalog_archetype.infiltration_rate_for_ventilation_system_on thermal_archetype.infiltration_rate_for_ventilation_system_off = catalog_archetype.infiltration_rate_for_ventilation_system_off + _constructions = [] for catalog_construction in catalog_archetype.constructions: construction = Construction() + construction.type = catalog_construction.type + if catalog_construction.window_ratio is not None: + for _orientation in catalog_construction.window_ratio: + if catalog_construction.window_ratio[_orientation] is None: + catalog_construction.window_ratio[_orientation] = 0 construction.window_ratio = catalog_construction.window_ratio _layers = [] for layer_archetype in catalog_construction.layers: @@ -91,3 +100,5 @@ class NrcanPhysicsParameters: construction.window_frame_ratio = window_archetype.frame_ratio construction.window_g_value = window_archetype.g_value construction.window_overall_u_value = window_archetype.overall_u_value + _constructions.append(construction) + thermal_archetype.constructions = _constructions diff --git a/hub/imports/construction/nrel_physics_parameters.py b/hub/imports/construction/nrel_physics_parameters.py index 3e1139fa..9a5503cf 100644 --- a/hub/imports/construction/nrel_physics_parameters.py +++ b/hub/imports/construction/nrel_physics_parameters.py @@ -61,6 +61,7 @@ class NrelPhysicsParameters: @staticmethod def _assign_values(thermal_archetype, catalog_archetype): + thermal_archetype.average_storey_height = catalog_archetype.average_storey_height thermal_archetype.extra_loses_due_to_thermal_bridges = catalog_archetype.extra_loses_due_to_thermal_bridges thermal_archetype.thermal_capacity = catalog_archetype.thermal_capacity thermal_archetype.indirect_heated_ratio = catalog_archetype.indirect_heated_ratio diff --git a/tests/test_construction_factory.py b/tests/test_construction_factory.py index d56441c2..02d6a0c2 100644 --- a/tests/test_construction_factory.py +++ b/tests/test_construction_factory.py @@ -133,12 +133,11 @@ class TestConstructionFactory(TestCase): 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_from_internal_zones, 'thermal_boundary delimits no thermal zone') + self.assertIsNotNone(thermal_boundary.thermal_zones, 'thermal_boundary delimits no thermal zone') self.assertIsNotNone(thermal_boundary.opaque_area, 'thermal_boundary area is none') self.assertIsNotNone(thermal_boundary.thickness, 'thermal_boundary thickness is none') self.assertIsNotNone(thermal_boundary.type, 'thermal_boundary type is 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')