# created June 2015
# by TEASER4 Development Team
"""This module includes the ThermalZone class
"""
from __future__ import division
import random
import re
import warnings
from teaser.logic.buildingobjects.calculation.one_element import OneElement
from teaser.logic.buildingobjects.calculation.two_element import TwoElement
from teaser.logic.buildingobjects.calculation.three_element import ThreeElement
from teaser.logic.buildingobjects.calculation.four_element import FourElement
[docs]
class ThermalZone(object):
"""Thermal zone class.
This class is used to manage information and parameter calculation for
thermal zones. Each thermal zone has one specific calculation method,
which is specific to the used model (model_attr). For new model
implementation this attribute can be assigned to new classes.
Parameters
----------
parent: Building()
The parent class of this object, the Building the zone belongs to.
Allows for better control of hierarchical structures. If not None it
adds this ThermalZone instance to Building.thermal_zones.
Default is None
Attributes
----------
internal_id : float
Random id for the distinction between different zones.
name : str
Individual name.
area : float [m2]
Thermal zone area.
volume : float [m3]
Thermal zone volume.
outer_walls : list
List of OuterWall instances.
doors : list
List of Door instances.
rooftops : list
List of Rooftop instances.
ground_floors : list
List of GroundFloor instances.
windows : list
List of Window instances.
inner_walls : list
List of InnerWall instances.
floors : list
List of Floor instances.
ceilings: list
List of Ceiling instances.
use_conditions : UseConditions
Instance of UseConditions with all relevant information for the usage
of the thermal zone
model_attr : Union[OneElement, TwoElement, ThreeElement, FourElement]
Instance of OneElement(), TwoElement(), ThreeElement() or
FourElement(), that holds all calculation functions and attributes
needed for the specific model.
t_inside : float [K]
Normative indoor temperature for static heat load calculation.
The input of t_inside is ALWAYS in Kelvin
t_outside : float [K]
Normative outdoor temperature for static heat load calculation.
The input of t_inside is ALWAYS in Kelvin
t_ground : float [K]
Temperature directly at the outer side of ground floors for static
heat load calculation.
The input of t_ground is ALWAYS in Kelvin
density_air : float [kg/m3]
average density of the air in the thermal zone
heat_capac_air : float [J/K]
average heat capacity of the air in the thermal zone
"""
def __init__(self, parent=None):
"""Constructor for ThermalZone
"""
self.parent = parent
self.internal_id = random.random()
self.name = None
self._area = None
self._volume = None
self._outer_walls = []
self._doors = []
self._rooftops = []
self._ground_floors = []
self._windows = []
self._inner_walls = []
self._floors = []
self._ceilings = []
self._use_conditions = None
self._t_inside = 293.15
self._t_outside = 261.15
self.density_air = 1.25
self.heat_capac_air = 1002
self.t_ground = 286.15
[docs]
def calc_zone_parameters(
self,
number_of_elements=2,
merge_windows=False,
t_bt=5):
"""RC-Calculation for the thermal zone
Based on the input parameters (used model) this function instantiates
the corresponding calculation Class (e.g. TwoElement) and calculates
the zone parameters. Currently the function is able to distinguishes
between the number of elements, we distinguish between:
- one element: all outer walls are aggregated into one element,
inner wall are neglected
- two elements: exterior and interior walls are aggregated
- three elements: like 2, but floor or roofs are aggregated
separately
- four elements: roofs and floors are aggregated separately
For all four options we can chose if the thermal conduction through
the window is considered in a separate resistance or not.
Parameters
----------
number_of_elements : int
defines the number of elements, that area aggregated, between 1
and 4, default is 2
merge_windows : bool
True for merging the windows into the outer walls, False for
separate resistance for window, default is False (Only
supported for IBPSA)
t_bt : float
Time constant according to VDI 6007 (default t_bt = 5)
"""
if number_of_elements == 1:
self.model_attr = OneElement(
thermal_zone=self,
merge_windows=merge_windows,
t_bt=t_bt)
self.model_attr.calc_attributes()
elif number_of_elements == 2:
self.model_attr = TwoElement(
thermal_zone=self,
merge_windows=merge_windows,
t_bt=t_bt)
self.model_attr.calc_attributes()
elif number_of_elements == 3:
self.model_attr = ThreeElement(
thermal_zone=self,
merge_windows=merge_windows,
t_bt=t_bt)
self.model_attr.calc_attributes()
elif number_of_elements == 4:
self.model_attr = FourElement(
thermal_zone=self,
merge_windows=merge_windows,
t_bt=t_bt)
self.model_attr.calc_attributes()
[docs]
def find_walls(self, orientation, tilt):
"""Returns all outer walls with given orientation and tilt
This function returns a list of all OuterWall elements with the
same orientation and tilt.
Parameters
----------
orientation : float [degree]
Azimuth of the desired walls.
tilt : float [degree]
Tilt against the horizontal of the desired walls.
Returns
-------
elements : list
List of OuterWalls instances with desired orientation and tilt.
"""
elements = []
for i in self.outer_walls:
if i.orientation == orientation and i.tilt == tilt:
elements.append(i)
else:
pass
return elements
[docs]
def find_doors(self, orientation, tilt):
"""Returns all outer walls with given orientation and tilt
This function returns a list of all Doors elements with the
same orientation and tilt.
Parameters
----------
orientation : float [degree]
Azimuth of the desired walls.
tilt : float [degree]
Tilt against the horizontal of the desired walls.
Returns
-------
elements : list
List of Doors instances with desired orientation and tilt.
"""
elements = []
for i in self.doors:
if i.orientation == orientation and i.tilt == tilt:
elements.append(i)
else:
pass
return elements
[docs]
def find_rts(self, orientation, tilt):
"""Returns all rooftops with given orientation and tilt
This function returns a list of all Rooftop elements with the
same orientation and tilt.
Parameters
----------
orientation : float [degree]
Azimuth of the desired rooftops.
tilt : float [degree]
Tilt against the horizontal of the desired rooftops.
Returns
-------
elements : list
List of Rooftop instances with desired orientation and tilt.
"""
elements = []
for i in self.rooftops:
if i.orientation == orientation and i.tilt == tilt:
elements.append(i)
else:
pass
return elements
[docs]
def find_gfs(self, orientation, tilt):
"""Returns all ground floors with given orientation and tilt
This function returns a list of all GroundFloor elements with the
same orientation and tilt.
Parameters
----------
orientation : float [degree]
Azimuth of the desired ground floors.
tilt : float [degree]
Tilt against the horizontal of the desired ground floors.
Returns
-------
elements : list
List of GroundFloor instances with desired orientation and tilt.
"""
elements = []
for i in self.ground_floors:
if i.orientation == orientation and i.tilt == tilt:
elements.append(i)
else:
pass
return elements
[docs]
def find_wins(self, orientation, tilt):
"""Returns all windows with given orientation and tilt
This function returns a list of all Window elements with the
same orientation and tilt.
Parameters
----------
orientation : float [degree]
Azimuth of the desired windows.
tilt : float [degree]
Tilt against the horizontal of the desired windows.
Returns
-------
elements : list
List of Window instances with desired orientation and tilt.
"""
elements = []
for i in self.windows:
if i.orientation == orientation and i.tilt == tilt:
elements.append(i)
else:
pass
return elements
[docs]
def set_inner_wall_area(self):
"""Sets the inner wall area according to zone area
Sets the inner wall area according to zone area size if type building
approach is used. This function covers Floors, Ceilings and InnerWalls.
"""
ass_error_1 = "You need to specify parent for thermal zone"
assert self.parent is not None, ass_error_1
for floor in self.floors:
floor.area = (
(self.parent.number_of_floors - 1) /
self.parent.number_of_floors) * self.area
for ceiling in self.ceilings:
ceiling.area = (
(self.parent.number_of_floors - 1) /
self.parent.number_of_floors) * self.area
for wall in self.inner_walls:
typical_area = self.use_conditions.typical_length * \
self.use_conditions.typical_width
avg_room_nr = self.area / typical_area
wall.area = (avg_room_nr * (self.use_conditions.typical_length *
self.parent.height_of_floors +
2 * self.use_conditions.typical_width *
self.parent.height_of_floors))
[docs]
def set_volume_zone(self):
"""Sets the zone volume according to area and height of floors
Sets the volume of a zone according area and height of floors
(building attribute).
"""
ass_error_1 = "you need to specify parent for thermal zone"
assert self.parent is not None, ass_error_1
self.volume = self.area * self.parent.height_of_floors
[docs]
def retrofit_zone(
self,
type_of_retrofit=None,
window_type=None,
material=None):
"""Retrofits all walls and windows in the zone.
Function call for all elements facing the ambient or ground.
Distinguishes if the parent building is a archetype of type 'iwu' or
'tabula_de'. If TABULA is used, it will use the pre-defined wall
constructions of TABULA.
This function covers OuterWall, Rooftop, GroundFloor and Window.
Parameters
----------
type_of_retrofit : str
The classification of retrofit, if the archetype building
approach of TABULA is used.
window_type : str
Default: EnEv 2014
material : str
Default: EPS035
"""
if type_of_retrofit is None:
type_of_retrofit = 'retrofit'
if type(self.parent).__name__ in [
"SingleFamilyHouse", "TerracedHouse", "MultiFamilyHouse",
"ApartmentBlock"]:
for wall_count in self.outer_walls \
+ self.rooftops + self.ground_floors + self.doors + \
self.windows:
if "adv_retrofit" in wall_count.construction_data:
warnings.warn(
"already highest available standard"
+ self.parent.name + wall_count.name)
elif "standard" in wall_count.construction_data:
wall_count.load_type_element(
year=self.parent.year_of_construction,
construction=wall_count.construction_data.replace(
"standard", type_of_retrofit))
else:
wall_count.load_type_element(
year=self.parent.year_of_construction,
construction=wall_count.construction_data.replace(
"retrofit", type_of_retrofit))
else:
for wall_count in self.outer_walls:
wall_count.retrofit_wall(
self.parent.year_of_retrofit,
material)
for roof_count in self.rooftops:
roof_count.retrofit_wall(
self.parent.year_of_retrofit,
material)
for ground_count in self.ground_floors:
ground_count.retrofit_wall(
self.parent.year_of_retrofit,
material)
for win_count in self.windows:
win_count.replace_window(
self.parent.year_of_retrofit,
window_type)
[docs]
def delete(self):
"""Deletes the actual thermal zone safely.
This deletes the current thermal Zone and also refreshes the
thermal_zones list in the parent Building.
"""
for index, tz in enumerate(self.parent.thermal_zones):
if tz.internal_id == self.internal_id:
self.parent.net_leased_area -= self.area
self.parent.thermal_zones.pop(index)
break
[docs]
def add_element(self, building_element):
"""Adds a building element to the corresponding list
This function adds a BuildingElement instance to the the list
depending on the type of the Building Element
Parameters
----------
building_element : BuildingElement()
inherited objects of BuildingElement() instance of TEASER
"""
ass_error_1 = ("building_element has to be an instance of OuterWall,"
" Rooftop, GroundFloor, Window, InnerWall, "
"Ceiling or Floor")
assert type(building_element).__name__ in (
"OuterWall", "Rooftop", "GroundFloor",
"InnerWall", "Ceiling", "Floor",
"Window"), ass_error_1
if type(building_element).__name__ == "OuterWall":
self._outer_walls.append(building_element)
elif type(building_element).__name__ == "GroundFloor":
self._ground_floors.append(building_element)
elif type(building_element).__name__ == "Rooftop":
self._rooftops.append(building_element)
elif type(building_element).__name__ == "InnerWall":
self._inner_walls.append(building_element)
elif type(building_element).__name__ == "Ceiling":
self._ceilings.append(building_element)
elif type(building_element).__name__ == "Floor":
self._floors.append(building_element)
elif type(building_element).__name__ == "Window":
self._windows.append(building_element)
@property
def parent(self):
return self.__parent
@parent.setter
def parent(self, value):
from teaser.logic.buildingobjects.building import Building
import inspect
if value is not None:
if inspect.isclass(Building):
self.__parent = value
self.__parent.thermal_zones.append(self)
@property
def name(self):
return self._name
@name.setter
def name(self, value):
regex = re.compile('[^a-zA-z0-9]')
if isinstance(value, str):
name = regex.sub('', value)
else:
try:
name = regex.sub('', str(value))
except ValueError:
print("Can't convert name to string")
# check if another zone with same name exists
tz_names = [tz._name for tz in self.parent.thermal_zones[:-1]]
if name in tz_names:
i = 1
while True:
name_add = f"{name}_{i}"
if name_add not in tz_names:
name = name_add
break
i += 1
self._name = name
@property
def outer_walls(self):
return self._outer_walls
@outer_walls.setter
def outer_walls(self, value):
if value is None:
self._outer_walls = []
@property
def doors(self):
return self._doors
@doors.setter
def doors(self, value):
if value is None:
self._doors = []
@property
def rooftops(self):
return self._rooftops
@rooftops.setter
def rooftops(self, value):
if value is None:
self._rooftops = []
@property
def ground_floors(self):
return self._ground_floors
@ground_floors.setter
def ground_floors(self, value):
if value is None:
self._ground_floors = []
@property
def ceilings(self):
return self._ceilings
@ceilings.setter
def ceilings(self, value):
if value is None:
self._ceilings = []
@property
def floors(self):
return self._floors
@floors.setter
def floors(self, value):
if value is None:
self._floors = []
@property
def inner_walls(self):
return self._inner_walls
@inner_walls.setter
def inner_walls(self, value):
if value is None:
self._inner_walls = []
@property
def windows(self):
return self._windows
@windows.setter
def windows(self, value):
if value is None:
self._windows = []
@property
def use_conditions(self):
return self._use_conditions
@use_conditions.setter
def use_conditions(self, value):
ass_error_1 = "Use condition has to be an instance of UseConditions()"
assert type(value).__name__ == "UseConditions", ass_error_1
if value is not None:
self._use_conditions = value
self.typical_length = value.typical_length
self.typical_width = value.typical_width
self._use_conditions = value
@property
def area(self):
return self._area
@area.setter
def area(self, value):
if isinstance(value, float):
pass
elif value is None:
pass
else:
try:
value = float(value)
except:
raise ValueError("Can't convert zone area to float")
if self.parent is not None:
if self._area is None:
if self.parent.net_leased_area is None:
self.parent.net_leased_area = 0.0
self._area = value
self.parent.net_leased_area += value
else:
self.parent.net_leased_area -= self._area
self.parent.net_leased_area += value
self._area = value
else:
self._area = value
@property
def volume(self):
return self._volume
@volume.setter
def volume(self, value):
if isinstance(value, float):
pass
elif value is None:
pass
else:
try:
value = float(value)
except ValueError:
raise ValueError("Can't convert zone volume to float")
if self.parent is not None:
if self._volume is None:
self._volume = value
self.parent.volume += value
else:
self.parent.volume -= self._volume
self.parent.volume += value
self._volume = value
else:
self._volume = value
@property
def t_inside(self):
return self._t_inside
@t_inside.setter
def t_inside(self, value):
if isinstance(value, float):
self._t_inside = value
elif value is None:
self._t_inside = value
else:
try:
value = float(value)
self._t_inside = value
except:
raise ValueError("Can't convert temperature to float")
@property
def t_outside(self):
return self._t_outside
@t_outside.setter
def t_outside(self, value):
if isinstance(value, float):
self._t_outside = value
elif value is None:
self._t_outside = value
else:
try:
value = float(value)
self._t_outside = value
except:
raise ValueError("Can't convert temperature to float")