# created June 2015
# by TEASER4 Development Team
"""This module includes the Building class
"""
import inspect
import random
import re
import warnings
from teaser.logic.buildingobjects.calculation.aixlib import AixLib
from teaser.logic.buildingobjects.calculation.ibpsa import IBPSA
from teaser.logic.buildingobjects.buildingsystems.buildingahu import BuildingAHU
[docs]class Building(object):
"""Building Class
This class is used to manage information and parameter calculation for
Buildings. It is the base class for all archetype buildings and holds a
list containing all ThermalZones instances. In addition it can hold an
optional attribute for CentralAHU instance, that is e.g. needed for
AixLib Simulation models.
Parameters
----------
parent: Project()
The parent class of this object, the Project the Building belongs to.
Allows for better control of hierarchical structures. If not None it
adds this Building instance to Project.buildings.
(default: None)
name : str
Individual name (default: None)
year_of_construction : int
Year of first construction (default: None)
net_leased_area : float [m2]
Total net leased area of building. This is area is NOT the footprint
of a building (default: None)
with_ahu : Boolean
If set to True, an empty instance of BuildingAHU is instantiated and
assigned to attribute central_ahu. This instance holds information for
central Air Handling units. Default is False.
internal_gains_mode: int [1, 2, 3]
mode for the internal gains calculation done in AixLib:
1. Temperature and activity degree dependent heat flux calculation for persons. The
calculation is based on SIA 2024 (default)
2. Temperature and activity degree independent heat flux calculation for persons, the max.
heatflowrate is prescribed by the parameter
fixed_heat_flow_rate_persons.
3. Temperature and activity degree dependent calculation with
consideration of moisture and co2. The moisture calculation is
based on SIA 2024 (2015) and regards persons and non-persons, the co2 calculation is based on
Engineering ToolBox (2004) and regards only persons.
Attributes
----------
central_ahu : instance of BuildingAHU
Teaser Instance of BuildingAHU if a central AHU is embedded into the
building (currently mostly needed for AixLib simulation).
number_of_floors : int
number of floors above ground (default: None)
height_of_floors : float [m]
Average height of the floors (default: None)
internal_id : float
Random id for the distinction between different buildings.
year_of_retrofit : int
Year of last retrofit.
type_of_building : string
Type of a Building (e.g. Building (unspecified), Office etc.).
building_id : None
ID of building, can be set by the user to keep track of a building
even outside of TEASER, e.g. in a simulation or in post-processing.
This is not the same as internal_id, as internal_id is e.g. not
exported to Modelica models!
street_name : string
Name of the street the building is located at. (optional)
city : string
Name of the city the building is located at. (optional)
longitude : float [degree]
Longitude of building location.
latitude : float [degree]
Latitude of building location.
thermal_zones : list
List with instances of ThermalZone(), that are located in this building.
outer_area : dict [degree: m2]
Dictionary with orientation as key and sum of outer wall areas of
that direction as value.
window_area : dict [degree: m2]
Dictionary with orientation as key and sum of window areas of
that direction as value.
bldg_height : float [m]
Total building height.
volume : float [m3]
Total volume of all thermal zones.
sum_heat_load : float [W]
Total heating load of all thermal zones.
sum_cooling_load : float [W]
Total heating load of all thermal zones. (currently not supported)
number_of_elements_calc : int
Number of elements that are used for thermal zone calculation in this
building.
1. OneElement
2. TwoElement
3. ThreeElement
4. FourElement
merge_windows_calc : boolean
True for merging the windows into the outer wall's RC-combination,
False for separate resistance for window, default is False. (Only
supported for IBPSA)
used_library_calc : str
'AixLib' for https://github.com/RWTH-EBC/AixLib
'IBPSA' for https://github.com/ibpsa/modelica
library_attr : Annex() or AixLib() instance
Classes with specific functions and attributes for building models in
IBPSA and AixLib. Python classes can be found in calculation package.
"""
def __init__(
self,
parent=None,
name=None,
year_of_construction=None,
net_leased_area=None,
with_ahu=False,
internal_gains_mode=1,
):
"""Constructor of Building Class
"""
self.parent = parent
self.name = name
self.year_of_construction = year_of_construction
self.net_leased_area = net_leased_area
self._with_ahu = with_ahu
if with_ahu is True:
self.central_ahu = BuildingAHU(self)
else:
self._central_ahu = None
self.internal_gains_mode = internal_gains_mode
self.number_of_floors = None
self.height_of_floors = None
self.internal_id = random.random()
self._year_of_retrofit = None
self.type_of_building = type(self).__name__
self.building_id = None
self.street_name = ""
self.city = ""
self.longitude = 6.05
self.latitude = 50.79
self._thermal_zones = []
self._outer_area = {}
self._window_area = {}
self.bldg_height = None
self.volume = 0
self.sum_heat_load = 0
self.sum_cooling_load = 0
self._number_of_elements_calc = 2
self._merge_windows_calc = False
self._used_library_calc = "AixLib"
self.library_attr = None
[docs] def set_outer_wall_area(self, new_area, orientation):
"""Outer area wall setter
sets the outer wall area of all walls of one direction and weights
them according to zone size. This function covers OuterWalls,
Rooftops, GroundFloors.
Parameters
----------
new_area : float
new_area of all outer walls of one orientation
orientation : float
orientation of the obtained walls
"""
for zone in self.thermal_zones:
for wall in zone.outer_walls:
if wall.orientation == orientation:
wall.area = ((new_area / self.net_leased_area) * zone.area) / sum(
count.orientation == orientation for count in zone.outer_walls
)
for roof in zone.rooftops:
if roof.orientation == orientation:
roof.area = ((new_area / self.net_leased_area) * zone.area) / sum(
count.orientation == orientation for count in zone.rooftops
)
for ground in zone.ground_floors:
if ground.orientation == orientation:
ground.area = ((new_area / self.net_leased_area) * zone.area) / sum(
count.orientation == orientation for count in zone.ground_floors
)
for door in zone.doors:
if door.orientation == orientation:
door.area = ((new_area / self.net_leased_area) * zone.area) / sum(
count.orientation == orientation for count in zone.doors
)
[docs] def set_window_area(self, new_area, orientation):
"""Window area setter
sets the window area of all windows of one direction and weights
them according to zone size
Parameters
----------
new_area : float
new_area of all window of one orientation
orientation : float
orientation of the obtained windows
"""
for zone in self.thermal_zones:
for win in zone.windows:
if win.orientation == orientation:
win.area = ((new_area / self.net_leased_area) * zone.area) / sum(
count.orientation == orientation for count in zone.windows
)
[docs] def get_outer_wall_area(self, orientation):
"""Get aggregated wall area of one orientation
Returns the area of all outer walls of one direction. This function
covers OuterWalls, GroundFloors and Rooftops.
Parameters
----------
orientation : float
orientation of the obtained wall
Returns
-------
sum_area : float
area of all walls of one direction
"""
sum_area = 0.0
for zone_count in self.thermal_zones:
for wall_count in zone_count.outer_walls:
if (
wall_count.orientation == orientation
and wall_count.area is not None
):
sum_area += wall_count.area
for roof_count in zone_count.rooftops:
if (
roof_count.orientation == orientation
and roof_count.area is not None
):
sum_area += roof_count.area
for ground_count in zone_count.ground_floors:
if (
ground_count.orientation == orientation
and ground_count.area is not None
):
sum_area += ground_count.area
return sum_area
[docs] def get_window_area(self, orientation):
"""Get aggregated window area of one orientation
returns the area of all windows of one direction
Parameters
----------
orientation : float
orientation of the obtained windows
Returns
-------
sum_area : float
area of all windows of one direction
"""
sum_area = 0.0
for zone_count in self.thermal_zones:
for win_count in zone_count.windows:
if win_count.orientation == orientation and win_count.area is not None:
sum_area += win_count.area
return sum_area
[docs] def get_inner_wall_area(self):
"""Get aggregated inner wall area
Returns the area of all inner walls. This function covers InnerWalls,
Ceilings and Floors.
Returns
-------
sum_area : float
area of all inner walls
"""
sum_area = 0.0
for zone_count in self.thermal_zones:
for wall_count in zone_count.inner_walls:
sum_area += wall_count.area
for floor in zone_count.floors:
sum_area += floor.area
for ceiling in zone_count.ceilings:
sum_area += ceiling.area
return sum_area
[docs] def fill_outer_area_dict(self):
"""Fills the attribute outer_area
Fills the dictionary outer_area with the sum of outer wall area
corresponding to the orientations of the building. This function
covers OuterWalls, GroundFloors and Rooftops.
"""
self.outer_area = {}
for zone_count in self.thermal_zones:
for wall_count in zone_count.outer_walls:
self.outer_area[wall_count.orientation] = None
for roof in zone_count.rooftops:
self.outer_area[roof.orientation] = None
for ground in zone_count.ground_floors:
self.outer_area[ground.orientation] = None
for key in self.outer_area:
self.outer_area[key] = self.get_outer_wall_area(key)
[docs] def fill_window_area_dict(self):
"""Fills the attribute
Fills the dictionary window_area with the sum of window area
corresponding to the orientations of the building.
"""
self.window_area = {}
for zone_count in self.thermal_zones:
for win_count in zone_count.windows:
self.window_area[win_count.orientation] = None
for key in self.window_area:
self.window_area[key] = self.get_window_area(key)
[docs] def calc_building_parameter(
self, number_of_elements=2, merge_windows=False, used_library="AixLib"
):
"""calc all building parameters
This functions calculates the parameters of all zones in a building
sums norm heat load of all zones
sums volume of all zones
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
used_library : str
used library (AixLib and IBPSA are supported)
"""
self._number_of_elements_calc = number_of_elements
self._merge_windows_calc = merge_windows
self._used_library_calc = used_library
for zone in self.thermal_zones:
zone.calc_zone_parameters(
number_of_elements=number_of_elements,
merge_windows=merge_windows,
t_bt=5,
)
self.sum_heat_load += zone.model_attr.heat_load
if self.used_library_calc == self.library_attr.__class__.__name__:
if self.used_library_calc == "AixLib":
self.library_attr.calc_auxiliary_attr()
else:
pass
elif self.library_attr is None:
if self.used_library_calc == "AixLib":
self.library_attr = AixLib(parent=self)
self.library_attr.calc_auxiliary_attr()
elif self.used_library_calc == "IBPSA":
self.library_attr = IBPSA(parent=self)
else:
warnings.warn(
"You set conflicting options for the used library "
"in Building or Project class and "
"calculation function of building. Your library "
"attributes are set to default using the library "
"you indicated in the function call, which is: "
+ self.used_library_calc
)
if self.used_library_calc == "AixLib":
self.library_attr = AixLib(parent=self)
self.library_attr.calc_auxiliary_attr()
elif self.used_library_calc == "IBPSA":
self.library_attr = IBPSA(parent=self)
[docs] def retrofit_building(
self,
year_of_retrofit=None,
type_of_retrofit=None,
window_type=None,
material=None,
):
"""Retrofits all zones in the building
Function call for each zone.
After retrofit, all parameters are calculated directly.
Parameters
----------
year_of_retrofit : float
Year of last retrofit.
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
"""
# Set self.sum_heat_load to zero to prevent summing up of old and new
# design heat load calculation values (see #518)
self.sum_heat_load = 0
if year_of_retrofit is not None:
self.year_of_retrofit = year_of_retrofit
for zone in self.thermal_zones:
zone.retrofit_zone(type_of_retrofit, window_type, material)
self.calc_building_parameter(
number_of_elements=self.number_of_elements_calc,
merge_windows=self.merge_windows_calc,
used_library=self.used_library_calc,
)
[docs] def rotate_building(self, angle):
"""Rotates the building to a given angle
This function covers OuterWall, Rooftop (if not flat roof) and Windows.
Parameters
----------
angle: float
rotation of the building clockwise, between 0 and 360 degrees
"""
for zone_count in self.thermal_zones:
new_angle = None
for wall_count in zone_count.outer_walls:
new_angle = wall_count.orientation + angle
if new_angle > 360.0:
wall_count.orientation = new_angle - 360.0
else:
wall_count.orientation = new_angle
for roof_count in zone_count.rooftops:
if roof_count.orientation != -1:
new_angle = roof_count.orientation + angle
if new_angle > 360.0:
roof_count.orientation = new_angle - 360.0
else:
roof_count.orientation = new_angle
else:
pass
for win_count in zone_count.windows:
new_angle = win_count.orientation + angle
if new_angle > 360.0:
win_count.orientation = new_angle - 360.0
else:
win_count.orientation = new_angle
[docs] def add_zone(self, thermal_zone):
"""Adds a thermal zone to the corresponding list
This function adds a ThermalZone instance to the the thermal_zones list
Parameters
----------
thermal_zone : ThermalZone()
ThermalZone() instance of TEASER
"""
ass_error_1 = "Zone has to be an instance of ThermalZone()"
assert type(thermal_zone).__name__ == "ThermalZone", ass_error_1
self._thermal_zones.append(thermal_zone)
@property
def parent(self):
return self.__parent
@parent.setter
def parent(self, value):
if value is not None:
ass_error_1 = "Parent has to be an instance of Project()"
assert type(value).__name__ == "Project", ass_error_1
self.__parent = value
if inspect.isclass(Building):
if self in self.__parent.buildings:
pass
else:
self.__parent.buildings.append(self)
else:
self.__parent = None
@property
def name(self):
return self.__name
@name.setter
def name(self, value):
if isinstance(value, str):
regex = re.compile("[^a-zA-z0-9]")
self.__name = regex.sub("", value)
else:
try:
value = str(value)
regex = re.compile("[^a-zA-z0-9]")
self.__name = regex.sub("", value)
except ValueError:
print("Can't convert name to string")
if self.__name[0].isdigit():
self.__name = "B" + self.__name
@property
def year_of_construction(self):
return self.__year_of_construction
@year_of_construction.setter
def year_of_construction(self, value):
if isinstance(value, int) or value is None:
self.__year_of_construction = value
else:
try:
value = int(value)
self.__year_of_construction = value
except:
raise ValueError("Can't convert year of construction to int")
@property
def number_of_floors(self):
return self.__number_of_floors
@number_of_floors.setter
def number_of_floors(self, value):
if isinstance(value, int) or value is None:
self.__number_of_floors = value
else:
try:
value = int(value)
self.__number_of_floors = value
except:
raise ValueError("Can't convert number of floors to int")
@property
def height_of_floors(self):
return self.__height_of_floors
@height_of_floors.setter
def height_of_floors(self, value):
if isinstance(value, float) or value is None:
self.__height_of_floors = value
else:
try:
value = float(value)
self.__height_of_floors = value
except:
raise ValueError("Can't convert height of floors to float")
@property
def net_leased_area(self):
return self.__net_leased_area
@net_leased_area.setter
def net_leased_area(self, value):
if isinstance(value, float):
self.__net_leased_area = value
elif value is None:
self.__net_leased_area = value
else:
try:
value = float(value)
self.__net_leased_area = value
except:
raise ValueError("Can't convert net leased area to float")
@property
def thermal_zones(self):
return self._thermal_zones
@thermal_zones.setter
def thermal_zones(self, value):
if value is None:
self._thermal_zones = []
@property
def outer_area(self):
return self._outer_area
@outer_area.setter
def outer_area(self, value):
self._outer_area = value
@property
def window_area(self):
return self._window_area
@window_area.setter
def window_area(self, value):
self._window_area = value
@property
def year_of_retrofit(self):
return self._year_of_retrofit
@year_of_retrofit.setter
def year_of_retrofit(self, value):
if self.year_of_construction is not None:
self._year_of_retrofit = value
else:
raise ValueError("Specify year of construction first")
@property
def with_ahu(self):
return self._with_ahu
@with_ahu.setter
def with_ahu(self, value):
if value is True and self.central_ahu is None:
self.central_ahu = BuildingAHU(self)
self._with_ahu = True
elif value is False and self.central_ahu:
self.central_ahu = None
self._with_ahu = False
@property
def central_ahu(self):
return self._central_ahu
@central_ahu.setter
def central_ahu(self, value):
if value is None:
self._central_ahu = value
else:
ass_error_1 = "central AHU has to be an instance of BuildingAHU()"
assert type(value).__name__ == "BuildingAHU", ass_error_1
self._central_ahu = value
@property
def number_of_elements_calc(self):
return self._number_of_elements_calc
@number_of_elements_calc.setter
def number_of_elements_calc(self, value):
ass_error_1 = "calculation_method has to be 1,2,3 or 4"
assert value != [1, 2, 3, 4], ass_error_1
if self.parent is None and value is None:
self._number_of_elements_calc = 2
elif self.parent is not None and value is None:
self._number_of_elements_calc = self.parent.number_of_elements_calc
elif value is not None:
self._number_of_elements_calc = value
@property
def merge_windows_calc(self):
return self._merge_windows_calc
@merge_windows_calc.setter
def merge_windows_calc(self, value):
ass_error_1 = "merge windows needs to be True or False"
assert value != [True, False], ass_error_1
if self.parent is None and value is None:
self._merge_windows_calc = 2
elif self.parent is not None and value is None:
self._merge_windows_calc = self.parent.merge_windows_calc
elif value is not None:
self._merge_windows_calc = value
@property
def used_library_calc(self):
return self._used_library_calc
@used_library_calc.setter
def used_library_calc(self, value):
ass_error_1 = "used library needs to be AixLib or IBPSA"
assert value != ["AixLib", "IBPSA"], ass_error_1
if self.parent is None and value is None:
self._used_library_calc = "AixLib"
elif self.parent is not None and value is None:
self._used_library_calc = self.parent.used_library_calc
elif value is not None:
self._used_library_calc = value
if self.used_library_calc == "AixLib":
self.library_attr = AixLib(parent=self)
elif self.used_library_calc == "IBPSA":
self.library_attr = IBPSA(parent=self)