2020-10-28 13:42:58 -04:00
|
|
|
"""
|
|
|
|
ThermalBoundary module
|
|
|
|
SPDX - License - Identifier: LGPL - 3.0 - or -later
|
2022-04-08 09:35:33 -04:00
|
|
|
Copyright © 2022 Concordia CERC group
|
|
|
|
Project Coder Guille Gutierrez guillermo.gutierrezmorote@concordia.ca
|
|
|
|
Code contributors: Pilar Monsalvete Alvarez de Uribarri pilar.monsalvete@concordia.ca
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2022-05-16 09:35:19 -04:00
|
|
|
|
2021-08-31 12:17:42 -04:00
|
|
|
import uuid
|
2022-11-08 15:57:50 -05:00
|
|
|
from typing import List, Union, TypeVar
|
2022-03-08 19:19:52 -05:00
|
|
|
from helpers.configuration_helper import ConfigurationHelper as ch
|
2021-08-06 12:28:20 -04:00
|
|
|
from city_model_structure.building_demand.layer import Layer
|
|
|
|
from city_model_structure.building_demand.thermal_opening import ThermalOpening
|
2021-08-27 17:20:24 -04:00
|
|
|
from city_model_structure.building_demand.thermal_zone import ThermalZone
|
2022-11-08 15:57:50 -05:00
|
|
|
|
|
|
|
Surface = TypeVar('Surface')
|
2021-08-27 17:20:24 -04:00
|
|
|
|
2020-10-28 13:42:58 -04:00
|
|
|
|
|
|
|
class ThermalBoundary:
|
|
|
|
"""
|
|
|
|
ThermalBoundary class
|
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
def __init__(self, parent_surface, opaque_area, windows_areas):
|
|
|
|
self._parent_surface = parent_surface
|
|
|
|
self._opaque_area = opaque_area
|
|
|
|
self._windows_areas = windows_areas
|
2021-08-31 12:17:42 -04:00
|
|
|
self._id = None
|
2021-08-27 17:20:24 -04:00
|
|
|
self._thermal_zones = None
|
2021-08-26 09:36:10 -04:00
|
|
|
self._thermal_openings = None
|
2020-10-28 13:42:58 -04:00
|
|
|
self._layers = None
|
2022-11-09 14:22:26 -05:00
|
|
|
self._he = ch().convective_heat_transfer_coefficient_exterior
|
|
|
|
self._hi = ch().convective_heat_transfer_coefficient_interior
|
2020-10-28 13:42:58 -04:00
|
|
|
self._u_value = None
|
2020-11-05 11:11:43 -05:00
|
|
|
self._construction_name = None
|
2021-06-23 09:53:33 -04:00
|
|
|
self._thickness = None
|
2022-11-09 14:22:26 -05:00
|
|
|
self._internal_surface = None
|
2022-03-08 19:19:52 -05:00
|
|
|
self._window_ratio = None
|
2022-10-28 17:46:53 -04:00
|
|
|
self._window_ratio_is_calculated = False
|
2021-03-16 12:19:35 -04:00
|
|
|
|
2021-08-31 12:17:42 -04:00
|
|
|
@property
|
|
|
|
def id(self):
|
|
|
|
"""
|
2022-05-16 09:35:19 -04:00
|
|
|
Get thermal zone id, a universally unique identifier randomly generated
|
2021-08-31 12:17:42 -04:00
|
|
|
:return: str
|
|
|
|
"""
|
|
|
|
if self._id is None:
|
|
|
|
self._id = uuid.uuid4()
|
|
|
|
return self._id
|
|
|
|
|
2020-11-12 13:50:43 -05:00
|
|
|
@property
|
2022-03-08 19:19:52 -05:00
|
|
|
def parent_surface(self) -> Surface:
|
2020-11-12 13:50:43 -05:00
|
|
|
"""
|
|
|
|
Get the surface that belongs to the thermal boundary
|
|
|
|
:return: Surface
|
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
return self._parent_surface
|
2020-11-12 13:50:43 -05:00
|
|
|
|
2020-10-28 13:42:58 -04:00
|
|
|
@property
|
2021-08-27 12:51:30 -04:00
|
|
|
def thermal_zones(self) -> List[ThermalZone]:
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
|
|
|
Get the thermal zones delimited by the thermal boundary
|
|
|
|
:return: [ThermalZone]
|
|
|
|
"""
|
2021-08-27 12:51:30 -04:00
|
|
|
return self._thermal_zones
|
2020-10-28 13:42:58 -04:00
|
|
|
|
2021-08-27 17:20:24 -04:00
|
|
|
@thermal_zones.setter
|
|
|
|
def thermal_zones(self, value):
|
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
Get the thermal zones delimited by the thermal boundary
|
2021-08-27 17:20:24 -04:00
|
|
|
:param value: [ThermalZone]
|
|
|
|
"""
|
|
|
|
self._thermal_zones = value
|
|
|
|
|
2020-10-28 13:42:58 -04:00
|
|
|
@property
|
2022-03-08 19:19:52 -05:00
|
|
|
def opaque_area(self):
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
Get the thermal boundary area in square meters
|
2020-10-28 13:42:58 -04:00
|
|
|
:return: float
|
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
return float(self._opaque_area)
|
2020-10-28 13:42:58 -04:00
|
|
|
|
|
|
|
@property
|
2021-06-23 09:53:33 -04:00
|
|
|
def thickness(self):
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get the thermal boundary thickness in meters
|
2020-10-28 13:42:58 -04:00
|
|
|
:return: float
|
|
|
|
"""
|
2021-06-23 09:53:33 -04:00
|
|
|
if self._thickness is None:
|
|
|
|
self._thickness = 0.0
|
|
|
|
if self.layers is not None:
|
|
|
|
for layer in self.layers:
|
2022-03-08 19:19:52 -05:00
|
|
|
if not layer.material.no_mass:
|
|
|
|
self._thickness += layer.thickness
|
2021-06-23 09:53:33 -04:00
|
|
|
return self._thickness
|
2020-10-28 13:42:58 -04:00
|
|
|
|
|
|
|
@property
|
2022-03-08 19:19:52 -05:00
|
|
|
def thermal_openings(self) -> Union[None, List[ThermalOpening]]:
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
|
|
|
Get thermal boundary thermal openings
|
2022-05-09 22:42:51 -04:00
|
|
|
:return: None or [ThermalOpening]
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2021-08-26 09:36:10 -04:00
|
|
|
if self._thermal_openings is None:
|
2022-03-08 19:19:52 -05:00
|
|
|
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]
|
2021-08-26 09:36:10 -04:00
|
|
|
else:
|
2022-03-08 19:19:52 -05:00
|
|
|
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 = []
|
2020-10-28 13:42:58 -04:00
|
|
|
return self._thermal_openings
|
|
|
|
|
2020-11-05 11:11:43 -05:00
|
|
|
@property
|
2021-09-14 13:46:48 -04:00
|
|
|
def construction_name(self) -> Union[None, str]:
|
2020-11-05 11:11:43 -05:00
|
|
|
"""
|
|
|
|
Get construction name
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or str
|
2020-11-05 11:11:43 -05:00
|
|
|
"""
|
|
|
|
return self._construction_name
|
|
|
|
|
|
|
|
@construction_name.setter
|
|
|
|
def construction_name(self, value):
|
2021-06-09 14:23:45 -04:00
|
|
|
"""
|
|
|
|
Set construction name
|
|
|
|
:param value: str
|
|
|
|
"""
|
2021-09-14 13:46:48 -04:00
|
|
|
if value is not None:
|
|
|
|
self._construction_name = str(value)
|
2020-11-05 11:11:43 -05:00
|
|
|
|
2020-10-28 13:42:58 -04:00
|
|
|
@property
|
|
|
|
def layers(self) -> List[Layer]:
|
|
|
|
"""
|
|
|
|
Get thermal boundary layers
|
|
|
|
:return: [Layers]
|
|
|
|
"""
|
|
|
|
return self._layers
|
|
|
|
|
|
|
|
@layers.setter
|
|
|
|
def layers(self, value):
|
|
|
|
"""
|
|
|
|
Set thermal boundary layers
|
|
|
|
:param value: [Layer]
|
|
|
|
"""
|
|
|
|
self._layers = value
|
|
|
|
|
|
|
|
@property
|
|
|
|
def type(self):
|
|
|
|
"""
|
2021-08-30 14:39:24 -04:00
|
|
|
Get thermal boundary surface type
|
2020-10-28 13:42:58 -04:00
|
|
|
:return: str
|
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
return self.parent_surface.type
|
2020-10-28 13:42:58 -04:00
|
|
|
|
|
|
|
@property
|
2021-09-14 13:46:48 -04:00
|
|
|
def window_ratio(self) -> Union[None, float]:
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
|
|
|
Get thermal boundary window ratio
|
2022-03-08 19:19:52 -05:00
|
|
|
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
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2022-03-08 19:19:52 -05:00
|
|
|
if self.windows_areas is not None:
|
2022-10-28 17:46:53 -04:00
|
|
|
if not self._window_ratio_is_calculated:
|
2022-03-08 19:19:52 -05:00
|
|
|
_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)
|
2020-10-28 13:42:58 -04:00
|
|
|
return self._window_ratio
|
|
|
|
|
|
|
|
@window_ratio.setter
|
|
|
|
def window_ratio(self, value):
|
|
|
|
"""
|
|
|
|
Set thermal boundary window ratio
|
2022-03-08 19:19:52 -05:00
|
|
|
:param value: str
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2022-10-28 17:46:53 -04:00
|
|
|
if self._window_ratio_is_calculated:
|
2022-03-08 19:19:52 -05:00
|
|
|
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
|
2020-10-28 13:42:58 -04:00
|
|
|
|
|
|
|
@property
|
2021-09-14 13:46:48 -04:00
|
|
|
def u_value(self) -> Union[None, float]:
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
2021-01-08 16:08:29 -05:00
|
|
|
Get thermal boundary U-value in W/m2K
|
2020-10-28 13:42:58 -04:00
|
|
|
internal and external convective coefficient in W/m2K values, can be configured at configuration.ini
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2020-10-28 13:42:58 -04:00
|
|
|
"""
|
|
|
|
if self._u_value is None:
|
2021-03-02 18:57:09 -05:00
|
|
|
h_i = self.hi
|
|
|
|
h_e = self.he
|
2020-10-28 13:42:58 -04:00
|
|
|
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)
|
|
|
|
else:
|
|
|
|
r_value = r_value + float(layer.material.conductivity) / float(layer.thickness)
|
|
|
|
self._u_value = 1.0/r_value
|
|
|
|
except TypeError:
|
2021-08-26 09:19:38 -04:00
|
|
|
raise Exception('Constructions layers are not initialized') from TypeError
|
2020-10-28 13:42:58 -04:00
|
|
|
return self._u_value
|
|
|
|
|
2021-01-06 16:42:38 -05:00
|
|
|
@u_value.setter
|
|
|
|
def u_value(self, value):
|
2021-01-08 16:08:29 -05:00
|
|
|
"""
|
|
|
|
Set thermal boundary U-value in W/m2K
|
|
|
|
:param value: float
|
|
|
|
"""
|
2021-09-14 13:46:48 -04:00
|
|
|
if value is not None:
|
|
|
|
self._u_value = float(value)
|
2021-01-06 16:42:38 -05:00
|
|
|
|
2021-03-02 18:57:09 -05:00
|
|
|
@property
|
2021-09-14 13:46:48 -04:00
|
|
|
def hi(self) -> Union[None, float]:
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
|
|
|
Get internal convective heat transfer coefficient (W/m2K)
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
|
|
|
return self._hi
|
|
|
|
|
|
|
|
@hi.setter
|
|
|
|
def hi(self, value):
|
|
|
|
"""
|
|
|
|
Set internal convective heat transfer coefficient (W/m2K)
|
2021-09-13 15:14:54 -04:00
|
|
|
:param value: float
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
2021-09-14 13:46:48 -04:00
|
|
|
if value is not None:
|
2022-03-08 19:19:52 -05:00
|
|
|
self._hi = value
|
2021-03-02 18:57:09 -05:00
|
|
|
|
|
|
|
@property
|
2021-09-14 13:46:48 -04:00
|
|
|
def he(self) -> Union[None, float]:
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
|
|
|
Get external convective heat transfer coefficient (W/m2K)
|
2021-09-14 13:46:48 -04:00
|
|
|
:return: None or float
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
|
|
|
return self._he
|
|
|
|
|
|
|
|
@he.setter
|
|
|
|
def he(self, value):
|
|
|
|
"""
|
|
|
|
Set external convective heat transfer coefficient (W/m2K)
|
2021-08-30 14:39:24 -04:00
|
|
|
:param value: float
|
2021-03-02 18:57:09 -05:00
|
|
|
"""
|
2021-09-14 13:46:48 -04:00
|
|
|
if value is not None:
|
2022-03-08 19:19:52 -05:00
|
|
|
self._he = value
|
2021-06-23 09:53:33 -04:00
|
|
|
|
|
|
|
@property
|
2022-11-09 14:22:26 -05:00
|
|
|
def internal_surface(self) -> Surface:
|
2021-06-23 09:53:33 -04:00
|
|
|
"""
|
|
|
|
Get the internal surface of the thermal boundary
|
|
|
|
:return: Surface
|
|
|
|
"""
|
2022-11-09 14:22:26 -05:00
|
|
|
if self._internal_surface is None:
|
|
|
|
self._internal_surface = self.parent_surface.inverse
|
|
|
|
return self._internal_surface
|