Coverage for teaser/project.py: 89%
311 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-29 16:01 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2025-04-29 16:01 +0000
1"""This module includes the Project class, which is the API for TEASER."""
3import warnings
4import os
5import re
6from typing import Optional, Union, List, Dict
7import teaser.logic.utilities as utilities
8import teaser.data.utilities as datahandling
9import teaser.data.input.teaserjson_input as tjson_in
10import teaser.data.output.teaserjson_output as tjson_out
11import teaser.data.output.aixlib_output as aixlib_output
12import teaser.data.output.besmod_output as besmod_output
13import teaser.data.output.ibpsa_output as ibpsa_output
14from teaser.data.dataclass import DataClass
15from teaser.logic.archetypebuildings.tabula.de.singlefamilyhouse import SingleFamilyHouse
16from teaser.logic.simulation.modelicainfo import ModelicaInfo
17from teaser.logic.retrofit import generate_buildings_for_all_element_combinations
20class Project(object):
21 """Top class for TEASER projects it serves as an API
23 The Project class is the top class for all TEASER projects and serves as an
24 API for script based interface. It is highly recommended to always
25 instantiate the Project class before starting to work with TEASER. It
26 contains functions to generate archetype buildings, to export models and to
27 save information for later use.
29 Parameters
30 ----------
31 load_data : boolean
32 boolean if data bases for materials, type elements and use conditions
33 should be loaded. default = False but will be automatically loaded
34 once you add a archetype building. For building generation from
35 scratch, set to True
37 Attributes
38 ----------
40 name : str
41 Name of the Project (default is 'Project')
42 modelica_info : instance of ModelicaInfo
43 TEASER instance of ModelicaInfo to store Modelica related
44 information, like used compiler, start and stop time, etc.
45 buildings : list
46 List of all buildings in one project, instances of Building()
47 data : instance of DataClass
48 TEASER instance of DataClass containing JSON binding classes
49 weather_file_path : str
50 Absolute path to weather file used for Modelica simulation. Default
51 weather file can be found in inputdata/weatherdata.
52 t_soil_mode : int
53 1 : constant value (stored in each zone)
54 2 : sine model (mean value and amplitude stored in each zone)
55 3 : from file (stored for the project)
56 heat load calculation will consider the constant zone.t_ground
57 t_soil_file_path : str
58 Absolute path to soil temperature file used for Modelica simulation if
59 t_soil_mode 3 is chosen.
60 Sample t_soil file can be found in inputdata/weatherdata.
61 number_of_elements_calc : int
62 Defines the number of elements, that are aggregated (1, 2, 3 or 4),
63 default is 2
64 merge_windows_calc : bool
65 True for merging the windows into the outer walls, False for
66 separate resistance for window, default is False (only supported for
67 IBPSA)
68 used_library_calc : str
69 used library (AixLib and IBPSA are supported)
70 dir_reference_results : str
71 Path to reference results in BuildingsPy format. If not None, the results
72 will be copied into the model output directories so that the exported
73 models can be regression tested against these results with BuildingsPy.
74 method_interzonal_material_enrichment : str
75 Method used to choose the default type element for interzonal elements.
76 'heating_difference': (default) check the difference between
77 thermal_zone.use_conditions.with_heating.
78 If equal, default element is inner element. If
79 not, an outer envelope type element is loaded,
80 with the outer layer on the non-heated side.
81 'cooling_difference': If there is no difference between heating states,
82 check the difference between
83 thermal_zone.use_conditions.with_cooling.
84 If equal, default element is inner element. If
85 not, an outer envelope type element is loaded,
86 with the outer layer on the non-cooled side.
87 'setpoint_difference_x': For interzonal elements where
88 'heating_difference' and 'cooling_difference'
89 lead to treatment as inner element, the
90 setpoints are compared. The float number x is
91 read from the string and used as maximum
92 difference. If a zone can be clearly identified
93 as "more conditioned" (i.e., the heating
94 setpoint is clearly higher or, if heating
95 setpoints are approximately equal, the cooling
96 setpoint is clearly lower, an outer envelope
97 type element is loaded, with the outer layer on
98 the "less conditioned" side.
99 method_interzonal_export : str
100 Method used to choose the way to export interzonal elements. Valid
101 strings are the same as for method_interzonal_material_enrichment.
102 Inner elements will always be lumped to the InnerWall. For "outer"
103 elements, interzonal heat flow will be modelled in the FiveElement
104 export. For OneElement to FourElement export (not recommended, because
105 not yet validated or tested), they will be lumped to the OuterWall
106 element from the heated/cooled/more conditioned side and to the
107 InnerWall from the other side.
108 """
110 def __init__(self, load_data=False):
111 """Constructor of Project Class.
112 """
113 self._name = "Project"
114 self.modelica_info = ModelicaInfo()
116 self.weather_file_path = utilities.get_full_path(
117 os.path.join(
118 "data",
119 "input",
120 "inputdata",
121 "weatherdata",
122 "DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos",
123 )
124 )
126 self.t_soil_mode = 1
128 self.t_soil_file_path = utilities.get_full_path(
129 os.path.join(
130 "data",
131 "input",
132 "inputdata",
133 "weatherdata",
134 "t_soil_sample_constant_283_15.mos",
135 )
136 )
138 self.buildings = []
140 self.load_data = load_data
142 self._number_of_elements_calc = 2
143 self._merge_windows_calc = False
144 self._used_library_calc = "AixLib"
146 if load_data:
147 raise ValueError("This option was deprecated")
148 self.data = None
150 self.dir_reference_results = None
152 self.method_interzonal_material_enrichment = 'heating_difference'
153 self.method_interzonal_export = 'heating_difference'
155 @staticmethod
156 def instantiate_data_class():
157 """Initialization of DataClass
159 Returns
160 -------
162 DataClass : Instance of DataClass()
164 """
165 return DataClass(
166 construction_data=datahandling.ConstructionData.iwu_heavy)
168 def calc_all_buildings(self, raise_errors=False):
169 """Calculates values for all project buildings
171 You need to set the following parameters in the Project class.
173 number_of_elements_calc : int
174 defines the number of elements, that area aggregated, between 1
175 and 5, default is 2
176 For AixLib you should always use 2 elements!!!
178 merge_windows_calc : bool
179 True for merging the windows into the outer walls, False for
180 separate resistance for window, default is False
181 For AixLib vdi calculation is True, ebc calculation is False
183 used_library_calc : str
184 used library (AixLib and IBPSA are supported)
186 """
187 if raise_errors is True:
188 for bldg in reversed(self.buildings):
189 bldg.calc_building_parameter(
190 number_of_elements=self._number_of_elements_calc,
191 merge_windows=self._merge_windows_calc,
192 used_library=self._used_library_calc,
193 )
194 else:
195 for bldg in reversed(self.buildings):
196 try:
197 bldg.calc_building_parameter(
198 number_of_elements=self._number_of_elements_calc,
199 merge_windows=self._merge_windows_calc,
200 used_library=self._used_library_calc,
201 )
202 except (ZeroDivisionError, TypeError):
203 warnings.warn(
204 "Following building can't be calculated and is "
205 "removed from buildings list. Use raise_errors=True "
206 "to get python errors and stop TEASER from deleting "
207 "this building:" + bldg.name
208 )
209 self.buildings.remove(bldg)
211 def retrofit_all_buildings(
212 self,
213 year_of_retrofit=None,
214 type_of_retrofit=None,
215 window_type=None,
216 material=None,
217 ):
218 """Retrofits all buildings in the project.
220 Depending on the used Archetype approach this function will retrofit
221 the building. If you have archetypes of both typologies (tabula and
222 iwu/BMBVS) you need to pass all keywords (see also Parameters section).
224 If TABULA approach is used, it will replace the current construction
225 with the construction specified in 'type_of_retrofit',
226 where 'retrofit' and 'adv_retrofit' are allowed.
228 'iwu' or 'BMVBS' Buildings in the project are retrofitted in the
229 following manner:
231 - replace all windows of the building to retrofitted window according
232 to the year of retrofit.
233 - add an additional insulation layer to all outer walls
234 (including roof, and ground floor).
235 The thickness of the insulation layer is calculated
236 that the U-Value of the wall corresponds to the retrofit standard of
237 the year of retrofit.
239 The needed parameters for the Modelica Model are calculated
240 automatically, using the calculation_method specified in the
241 first scenario.
243 Note: To Calculate U-Value, the standard TEASER coefficients for outer
244 and inner heat transfer are used.
246 Parameters
247 ----------
248 year_of_retrofit : int
249 the year the buildings are retrofitted, only 'iwu'/'bmbvs'
250 archetype approach.
251 type_of_retrofit : str
252 The classification of retrofit, if the archetype building
253 approach of TABULA is used.
254 window_type : str
255 Default: EnEv 2014, only 'iwu'/'bmbvs' archetype approach.
256 material : str
257 Default: EPS035, only 'iwu'/'bmbvs' archetype approach.
259 """
260 ass_error_type = "only 'retrofit' and 'adv_retrofit' are valid"
261 assert type_of_retrofit in [None, "adv_retrofit", "retrofit"], \
262 ass_error_type
263 tabula_buildings = []
264 iwu_buildings = []
266 for bldg in self.buildings:
267 if isinstance(bldg, SingleFamilyHouse):
268 if type_of_retrofit is None:
269 raise ValueError(
270 "you need to set type_of_retrofit for "
271 "TABULA retrofit"
272 )
273 tabula_buildings.append(bldg)
274 else:
275 if year_of_retrofit is None:
276 raise ValueError(
277 "you need to set year_of_retrofit for retrofit")
278 iwu_buildings.append(bldg)
280 if self.data == DataClass(
281 construction_data=datahandling.ConstructionData.iwu_heavy):
282 for bld_iwu in iwu_buildings:
283 bld_iwu.retrofit_building(
284 year_of_retrofit=year_of_retrofit,
285 window_type=window_type,
286 material=material,
287 )
288 self.data = DataClass(
289 construction_data=datahandling.ConstructionData.
290 tabula_de_standard)
291 for bld_tabula in tabula_buildings:
292 bld_tabula.retrofit_building(type_of_retrofit=type_of_retrofit)
294 else:
295 for bld_tabula in tabula_buildings:
296 bld_tabula.retrofit_building(type_of_retrofit=type_of_retrofit)
297 self.data = DataClass(
298 construction_data=datahandling.ConstructionData.iwu_heavy)
299 for bld_iwu in iwu_buildings:
300 bld_iwu.retrofit_building(
301 year_of_retrofit=year_of_retrofit,
302 window_type=window_type,
303 material=material,
304 )
306 def add_non_residential(
307 self,
308 construction_data,
309 geometry_data,
310 name,
311 year_of_construction,
312 number_of_floors,
313 height_of_floors,
314 net_leased_area,
315 with_ahu=True,
316 internal_gains_mode=1,
317 inner_wall_approximation_approach='teaser_default',
318 office_layout=None,
319 window_layout=None,
320 ):
321 """Add a non-residential building to the TEASER project.
322 This function adds a non-residential archetype building to the TEASER
323 project. You need to specify the method of the archetype generation.
324 Currently TEASER supports only method according to Lichtmess and BMVBS
325 for non-residential buildings. Further the type of geometry_data needs to be
326 specified. Currently TEASER supports four different types of
327 non-residential buildings ('office', 'institute', 'institute4',
328 'institute8'). For more information on specific archetype buildings and
329 methods, please read the docs of archetype classes.
331 This function also calculates the parameters of the buildings directly
332 with the settings set in the project (e.g. used_library_calc or
333 number_of_elements_calc).
335 Parameters
336 ----------
337 construction_data : str
338 Used data for construction, for bmvbs non-residential buildings 'iwu_heavy' is supported
339 geometry_data : str
340 Main geometry_data of the obtained building, currently only 'bmvbs_office',
341 'bmvbs_institute', 'bmvbs_institute4', 'bmvbs_institute8' are supported
342 name : str
343 Individual name
344 year_of_construction : int
345 Year of first construction
346 height_of_floors : float [m]
347 Average height of the buildings' floors
348 number_of_floors : int
349 Number of building's floors above ground
350 net_leased_area : float [m2]
351 Total net leased area of building. This is area is NOT the
352 footprint
353 of a building
354 with_ahu : Boolean
355 If set to True, an empty instance of BuildingAHU is instantiated
356 and
357 assigned to attribute central_ahu. This instance holds information
358 for central Air Handling units. Default is False.
359 internal_gains_mode: int [1, 2, 3]
360 mode for the internal gains calculation done in AixLib:
362 1. Temperature and activity degree dependent heat flux calculation for persons. The
363 calculation is based on SIA 2024 (default)
365 2. Temperature and activity degree independent heat flux calculation for persons, the max.
366 heatflowrate is prescribed by the parameter
367 fixed_heat_flow_rate_persons.
369 3. Temperature and activity degree dependent calculation with
370 consideration of moisture and co2. The moisture calculation is
371 based on SIA 2024 (2015) and regards persons and non-persons, the co2 calculation is based on
372 Engineering ToolBox (2004) and regards only persons.
374 inner_wall_approximation_approach : str
375 'teaser_default' (default) sets length of inner walls = typical
376 length * height of floors + 2 * typical width * height of floors
377 'typical_minus_outer' sets length of inner walls = 2 * typical
378 length * height of floors + 2 * typical width * height of floors
379 - length of outer or interzonal walls
380 'typical_minus_outer_extended' like 'typical_minus_outer', but also
381 considers that
382 a) a non-complete "average room" reduces its circumference
383 proportional to the square root of the area
384 b) rooftops, windows and ground floors (= walls with border to
385 soil) may have a vertical share
387 office_layout : int
388 Structure of the floor plan of office buildings, default is 1,
389 which is representative for one elongated floor.
391 1. elongated 1 floor
393 2. elongated 2 floors
395 3. compact (e.g. for a square base building)
397 window_layout : int
398 Structure of the window facade type, default is 1, which is
399 representative for a punctuated facade.
401 1. punctuated facade (individual windows)
403 2. banner facade (continuous windows)
405 3. full glazing
407 Returns
408 -------
409 type_bldg : Instance of Office()
411 """
412 if isinstance(construction_data, str):
413 construction_data = datahandling.ConstructionData(
414 construction_data)
415 if isinstance(geometry_data, str):
416 geometry_data = datahandling.GeometryData(geometry_data)
418 ass_error_construction_data = (
419 "only 'iwu_heavy' is a valid construction_data for "
420 "'non-residential' archetype generation")
422 assert construction_data.value == "iwu_heavy", \
423 ass_error_construction_data
425 ass_error_geometry_data = (
426 "geometry_data does not match the construction_data")
428 assert geometry_data in datahandling.allowed_geometries.get(
429 construction_data, []), ass_error_geometry_data
431 self.data = DataClass(construction_data)
433 type_bldg = datahandling.geometries[geometry_data](
434 parent=self,
435 name=name,
436 year_of_construction=year_of_construction,
437 number_of_floors=number_of_floors,
438 height_of_floors=height_of_floors,
439 net_leased_area=net_leased_area,
440 with_ahu=with_ahu,
441 internal_gains_mode=internal_gains_mode,
442 inner_wall_approximation_approach=inner_wall_approximation_approach,
443 office_layout=office_layout,
444 window_layout=window_layout,
445 construction_data=construction_data,
446 )
448 type_bldg.generate_archetype()
449 type_bldg.calc_building_parameter(
450 number_of_elements=self._number_of_elements_calc,
451 merge_windows=self._merge_windows_calc,
452 used_library=self._used_library_calc,
453 )
454 return type_bldg
456 def add_residential(
457 self,
458 construction_data,
459 geometry_data,
460 name,
461 year_of_construction,
462 number_of_floors,
463 height_of_floors,
464 net_leased_area,
465 with_ahu=False,
466 internal_gains_mode=1,
467 inner_wall_approximation_approach='teaser_default',
468 residential_layout=None,
469 neighbour_buildings=None,
470 attic=None,
471 cellar=None,
472 dormer=None,
473 number_of_apartments=None,
474 ):
475 """Add a residential building to the TEASER project.
477 This function adds a residential archetype building to the TEASER
478 project. You need to specify the construction_data of the archetype generation.
479 Currently TEASER supports only construction_data according 'iwu', 'urbanrenet',
480 'tabula_de' and 'tabula_dk' for residential buildings. Further the
481 type of geometry_data needs to be specified. Currently TEASER supports one type
482 of
483 residential building for 'iwu' and eleven types for 'urbanrenet'. For
484 more information on specific archetype buildings and methods, please
485 read the docs of archetype classes.
486 This function also calculates the parameters of the buildings directly
487 with the settings set in the project (e.g. used_library_calc or
488 number_of_elements_calc).
490 Parameters
491 ----------
492 construction_data : str
493 Used construction_data, currently supported values: 'iwu_heavy', 'iwu_light',
494 'tabula_de_standard', 'tabula_de_retrofit', 'tabula_de_adv_retrofit',
495 'tabula_dk_standard', 'tabula_dk_retrofit', 'tabula_dk_adv_retrofit'
496 and the KfW Efficiency house standards 'kfw_40', 'kfw_55', 'kfw_70', 'kfw_85, kfw_100'
498 geometry_data : str
499 Main geometry_data of the obtained building, currently supported values:
500 'iwu_single_family_dwelling', 'urbanrenet_est1a', 'urbanrenet_est1b',
501 'urbanrenet_est2', 'urbanrenet_est3', 'urbanrenet_est4a', 'urbanrenet_est4b',
502 'urbanrenet_est5' 'urbanrenet_est6', 'urbanrenet_est7', 'urbanrenet_est8a',
503 'urbanrenet_est8b'
504 'tabula_de_single_family_house', 'tabula_de_terraced_house',
505 'tabula_de_multi_family_house', 'tabula_de_apartment_block',
506 'tabula_dk_single_family_house', 'tabula_dk_terraced_house',
507 'tabula_dk_multi_family_house', 'tabula_dk_apartment_block'
509 name : str
510 Individual name
511 year_of_construction : int
512 Year of first construction
513 height_of_floors : float [m]
514 Average height of the buildings' floors
515 number_of_floors : int
516 Number of building's floors above ground
517 net_leased_area : float [m2]
518 Total net leased area of building. This is area is NOT the
519 footprint
520 of a building
521 with_ahu : Boolean
522 If set to True, an empty instance of BuildingAHU is instantiated
523 and
524 assigned to attribute central_ahu. This instance holds information
525 for central Air Handling units. Default is False.
526 internal_gains_mode: int [1, 2, 3]
527 mode for the internal gains calculation done in AixLib:
528 1. Temperature and activity degree dependent heat flux calculation for persons. The
529 calculation is based on SIA 2024 (default)
530 2. Temperature and activity degree independent heat flux calculation for persons, the max.
531 heatflowrate is prescribed by the parameter
532 fixed_heat_flow_rate_persons.
533 3. Temperature and activity degree dependent calculation with
534 consideration of moisture and co2. The moisture calculation is
535 based on SIA 2024 (2015) and regards persons and non-persons, the co2 calculation is based on
536 Engineering ToolBox (2004) and regards only persons.
538 inner_wall_approximation_approach : str
539 'teaser_default' (default) sets length of inner walls = typical
540 length * height of floors + 2 * typical width * height of floors
541 'typical_minus_outer' sets length of inner walls = 2 * typical
542 length * height of floors + 2 * typical width * height of floors
543 - length of outer or interzonal walls
544 'typical_minus_outer_extended' like 'typical_minus_outer', but also
545 considers that
546 a) a non-complete "average room" reduces its circumference
547 proportional to the square root of the area
548 b) rooftops, windows and ground floors (= walls with border to
549 soil) may have a vertical share
551 residential_layout : int
552 Structure of floor plan (default = 0) CAUTION only used for iwu
554 0. compact
556 1. elongated/complex
558 neighbour_buildings : int
559 Number of neighbour buildings. CAUTION: this will not change
560 the orientation of the buildings wall, but just the overall
561 exterior wall and window area(!) (default = 0)
563 0. no neighbour
564 1. one neighbour
565 2. two neighbours
567 attic : int
568 Design of the attic. CAUTION: this will not change the orientation
569 or tilt of the roof instances, but just adapt the roof area(!) (
570 default = 0) CAUTION only used for iwu
572 0. flat roof
573 1. non heated attic
574 2. partly heated attic
575 3. heated attic
577 cellar : int
578 Design of the of cellar CAUTION: this will not change the
579 orientation, tilt of GroundFloor instances, nor the number or area
580 of ThermalZones, but will change GroundFloor area(!) (default = 0)
581 CAUTION only used for iwu
583 0. no cellar
584 1. non heated cellar
585 2. partly heated cellar
586 3. heated cellar
588 dormer : str
589 Is a dormer attached to the roof? CAUTION: this will not
590 change roof or window orientation or tilt, but just adapt the roof
591 area(!) (default = 0) CAUTION only used for iwu
593 0. no dormer
594 1. dormer
596 number_of_apartments : int
597 number of apartments inside Building (default = 1). CAUTION only
598 used for urbanrenet
600 Returns
601 -------
602 type_bldg : Instance of Archetype Building
604 """
605 if isinstance(construction_data, str):
606 construction_data = datahandling.ConstructionData(
607 construction_data)
608 if isinstance(geometry_data, str):
609 geometry_data = datahandling.GeometryData(geometry_data)
611 ass_error_apart = (
612 "The keyword number_of_apartments does not have any "
613 "effect on archetype generation for 'iwu' or"
614 "'tabula_de', see docs for more information"
615 )
617 if ((construction_data.is_iwu() or
618 construction_data.is_tabula_de() or
619 construction_data.is_tabula_dk() or
620 construction_data.is_kfw())
621 and number_of_apartments is not None):
622 warnings.warn(ass_error_apart)
624 self.data = DataClass(construction_data)
626 ass_error_geometry_data = (
627 "geometry_data does not match the construction_data")
629 assert geometry_data in datahandling.allowed_geometries.get(
630 construction_data, []), ass_error_geometry_data
632 common_arg = {
633 'name': name,
634 'year_of_construction': year_of_construction,
635 'number_of_floors': number_of_floors,
636 'height_of_floors': height_of_floors,
637 'net_leased_area': net_leased_area,
638 'with_ahu': with_ahu,
639 'internal_gains_mode': internal_gains_mode,
640 'construction_data': construction_data,
641 'inner_wall_approximation_approach': inner_wall_approximation_approach,
642 }
644 urbanrenet_arg = common_arg.copy()
645 urbanrenet_arg.update({
646 'neighbour_buildings': neighbour_buildings,
647 })
649 iwu_arg = common_arg.copy()
650 iwu_arg.update({
651 'residential_layout': residential_layout,
652 'neighbour_buildings': neighbour_buildings,
653 'attic': attic,
654 'cellar': cellar,
655 'dormer': dormer,
656 })
658 if geometry_data == datahandling.GeometryData.IwuSingleFamilyDwelling:
659 type_bldg = datahandling.geometries[geometry_data](self, **iwu_arg)
660 elif geometry_data == datahandling.GeometryData.UrbanrenetEst1a:
661 type_bldg = datahandling.geometries[geometry_data](
662 self, **urbanrenet_arg)
663 elif geometry_data.value in [datahandling.GeometryData.UrbanrenetEst1b,
664 datahandling.GeometryData.UrbanrenetEst2,
665 datahandling.GeometryData.UrbanrenetEst3,
666 datahandling.GeometryData.UrbanrenetEst4a,
667 datahandling.GeometryData.UrbanrenetEst4b,
668 datahandling.GeometryData.UrbanrenetEst5,
669 datahandling.GeometryData.UrbanrenetEst6,
670 datahandling.GeometryData.UrbanrenetEst7,
671 datahandling.GeometryData.UrbanrenetEst8a,
672 datahandling.GeometryData.UrbanrenetEst8b]:
673 urbanrenet_arg['number_of_apartments'] = number_of_apartments
674 type_bldg = datahandling.geometries[geometry_data](
675 self, **urbanrenet_arg)
676 else:
677 type_bldg = datahandling.geometries[geometry_data](
678 self, **common_arg)
679 type_bldg.generate_archetype()
680 if (not construction_data.is_tabula_de() and not
681 construction_data.is_tabula_dk()):
682 type_bldg.calc_building_parameter(
683 number_of_elements=self._number_of_elements_calc,
684 merge_windows=self._merge_windows_calc,
685 used_library=self._used_library_calc,
686 )
687 return type_bldg
689 def add_residential_retrofit_combinations(
690 self,
691 elements: list = None,
692 retrofit_choices: list = None,
693 **add_residential_kwargs: dict
694 ):
695 """
696 Generate residential buildings for all possible combinations of
697 retrofit statuses for specified building elements.
699 This function creates multiple variations of a residential building based
700 on different retrofit options for specified building elements.
701 It's designed to work with TABULA archetypes.
703 Parameters
704 ----------
705 add_residential_kwargs : dict
706 Keyword arguments for the function add_residential.
707 elements : list, optional
708 List of building elements to consider for retrofit.
709 Defaults to ['outer_walls', 'windows', 'rooftops', 'ground_floors'].
710 retrofit_choices : list, optional
711 List of retrofit options to consider.
712 Defaults to ['standard', 'retrofit', 'adv_retrofit'].
714 Returns
715 -------
716 list: A list of names of the generated buildings.
718 Raises
719 ------
720 ValueError: If unsupported elements or retrofit choices are provided, or if the
721 construction data is not from TABULA DE or DK.
723 Note
724 ----
725 This function only works with TABULA DE or DK construction data.
726 """
727 return generate_buildings_for_all_element_combinations(
728 project_add_building_function=self.add_residential,
729 add_building_function_kwargs=add_residential_kwargs,
730 elements=elements,
731 retrofit_choices=retrofit_choices,
732 )
734 def save_project(self, file_name=None, path=None):
735 """Saves the project to a JSON file
737 Calls the function save_teaser_json in data.output.teaserjson_output
739 Parameters
740 ----------
742 file_name : string
743 name of the new file
744 path : string
745 if the Files should not be stored in OutputData, an alternative
746 can be specified
747 """
748 if file_name is None:
749 name = self.name
750 else:
751 name = file_name
753 if path is None:
754 new_path = os.path.join(utilities.get_default_path(), name)
755 else:
756 new_path = os.path.join(path, name)
758 tjson_out.save_teaser_json(new_path, self)
760 def load_project(self, path):
761 """Load the project from a json file (new format).
763 Calls the function load_teaser_json.
765 Parameters
766 ----------
767 path : string
768 full path to a json file
770 """
772 tjson_in.load_teaser_json(path, self)
774 def _check_buildings(self, internal_id: Optional[float] = None):
775 """Utility method to verify that the project has buildings and,
776 if an internal_id is provided, that it exists.
777 """
778 if not self.buildings:
779 raise ValueError("The project includes no buildings to export.")
780 if internal_id is not None and not any(bldg.internal_id == internal_id for bldg in self.buildings):
781 raise ValueError(f"Building with internal_id {internal_id} not found in the project.")
783 def export_aixlib(
784 self,
785 building_model=None,
786 zone_model=None,
787 corG=None,
788 internal_id=None,
789 path=None,
790 custom_multizone_template_path=None,
791 use_postprocessing_calc=False,
792 report=False,
793 export_vars=None
794 ):
795 """Exports values to a record file for Modelica simulation
797 Exports one (if internal_id is not None) or all buildings for
798 AixLib.ThermalZones.ReducedOrder.Multizone.MultizoneEquipped models
799 using the ThermalZoneEquipped model with a correction of g-value (
800 double pane glazing) and supporting models, like tables and weather
801 model. In contrast to versions < 0.5 TEASER now does not
802 support any model options, as we observed no need, since single
803 ThermalZones are identically with IBPSA models. If you miss one of
804 the old options please contact us.
806 Parameters
807 ----------
809 internal_id : float
810 setter of a specific building which will be exported, if None then
811 all buildings will be exported
812 path : string
813 if the Files should not be stored in default output path of TEASER,
814 an alternative path can be specified as a full path
815 custom_multizone_template_path : string
816 if a custom template for writing the multizone model should be used,
817 specify its full path as string
818 report: boolean
819 if True a model report in form of a html and csv file will be
820 created for the exported project.
821 export_vars: dict[str:list]
822 dict where key is a name for this variable selection and value is a
823 list of variables to export, wildcards can be used, multiple
824 variable selections are possible. This works only for Dymola. See
825 (https://www.claytex.com/blog/selection-of-variables-to-be-saved-in-the-result-file/)
826 """
828 self._check_buildings(internal_id)
830 if building_model is not None or zone_model is not None or corG is not None:
831 warnings.warn(
832 "building_model, zone_model and corG are no longer "
833 "supported for AixLib export and have no effect. "
834 "The keywords will be deleted within the next "
835 "version, consider rewriting your code."
836 )
837 if export_vars:
838 export_vars = self.process_export_vars(export_vars)
840 if path is None:
841 path = os.path.join(utilities.get_default_path(), self.name)
842 else:
843 path = os.path.join(path, self.name)
845 utilities.create_path(path)
847 if internal_id is None:
848 aixlib_output.export_multizone(
849 buildings=self.buildings, prj=self, path=path,
850 custom_multizone_template_path=custom_multizone_template_path,
851 use_postprocessing_calc=use_postprocessing_calc,
852 export_vars=export_vars
853 )
854 else:
855 for bldg in self.buildings:
856 if bldg.internal_id == internal_id:
857 aixlib_output.export_multizone(
858 buildings=[bldg], prj=self, path=path,
859 custom_multizone_template_path
860 =custom_multizone_template_path,
861 use_postprocessing_calc=use_postprocessing_calc,
862 export_vars=export_vars
863 )
865 if report:
866 self._write_report(path)
867 return path
869 def export_besmod(
870 self,
871 internal_id: Optional[float] = None,
872 examples: Optional[List[str]] = None,
873 path: Optional[str] = None,
874 THydSup_nominal: Optional[Union[float, Dict[str, float]]] = None,
875 QBuiOld_flow_design: Optional[Dict[str, Dict[str, float]]] = None,
876 THydSupOld_design: Optional[Union[float, Dict[str, float]]] = None,
877 custom_examples: Optional[Dict[str, str]] = None,
878 custom_script: Optional[Dict[str, str]] = None,
879 report: bool = False
880 ) -> str:
881 """Exports buildings for BESMod simulation
883 Exports one (if internal_id is not None) or all buildings as
884 BESMod.Systems.Demand.Building.TEASERThermalZone models. Additionally,
885 BESMod.Examples can be specified and directly exported including the building.
887 Parameters
888 ----------
890 internal_id : Optional[float]
891 Specifies a specific building to export by its internal ID. If None, all buildings are exported.
892 examples : Optional[List[str]]
893 Names of BESMod examples to export alongside the building models.
894 Supported Examples: "TEASERHeatLoadCalculation", "HeatPumpMonoenergetic", and "GasBoilerBuildingOnly".
895 path : Optional[str]
896 Alternative output path for storing the exported files. If None, the default TEASER output path is used.
897 THydSup_nominal : Optional[Union[float, Dict[str, float]]]
898 Nominal supply temperature(s) for the hydraulic system. Required for
899 certain examples (e.g., HeatPumpMonoenergetic, GasBoilerBuildingOnly).
900 See docstring of teaser.data.output.besmod_output.convert_input() for further information.
901 QBuiOld_flow_design : Optional[Dict[str, Dict[str, float]]]
902 For partially retrofitted systems specify the old nominal heat flow
903 of all zones in the Buildings in a nested dictionary with
904 the building names and in a level below the zone names as keys.
905 By default, only the radiator transfer system is not retrofitted in BESMod.
906 THydSupOld_design : Optional[Union[float, Dict[str, float]]]
907 Design supply temperatures for old, non-retrofitted hydraulic systems.
908 custom_examples: Optional[Dict[str, str]]
909 Specify custom examples with a dictionary containing the example name as the key and
910 the path to the corresponding custom mako template as the value.
911 custom_script: Optional[Dict[str, str]]
912 Specify custom .mos scripts for the existing and custom examples with a dictionary
913 containing the example name as the key and the path to the corresponding custom mako template as the value.
914 report : bool
915 If True, generates a model report in HTML and CSV format for the exported project. Default is False.
917 Returns
918 -------
919 str
920 The path where the exported files are stored.
921 """
923 self._check_buildings(internal_id)
925 if path is None:
926 path = os.path.join(utilities.get_default_path(), self.name)
927 else:
928 path = os.path.join(path, self.name)
930 utilities.create_path(path)
932 if internal_id is None:
933 besmod_output.export_besmod(
934 buildings=self.buildings, prj=self, path=path, examples=examples, THydSup_nominal=THydSup_nominal,
935 QBuiOld_flow_design=QBuiOld_flow_design, THydSupOld_design=THydSupOld_design,
936 custom_examples=custom_examples, custom_script=custom_script
937 )
938 else:
939 for bldg in self.buildings:
940 if bldg.internal_id == internal_id:
941 besmod_output.export_besmod(
942 buildings=[bldg], prj=self, path=path, examples=examples, THydSup_nominal=THydSup_nominal,
943 QBuiOld_flow_design=QBuiOld_flow_design, THydSupOld_design=THydSupOld_design,
944 custom_examples=custom_examples, custom_script=custom_script
945 )
947 if report:
948 self._write_report(path)
949 return path
951 def _write_report(self, path):
952 try:
953 from teaser.data.output.reports import model_report
954 except ImportError:
955 raise ImportError(
956 "To create the model report, you have to install TEASER "
957 "using the option report: pip install teaser[report] or install "
958 "plotly manually."
959 )
960 report_path = os.path.join(path, "Resources", "ModelReport")
961 model_report.create_model_report(prj=self, path=report_path)
963 def export_ibpsa(self, library="AixLib", internal_id=None, path=None):
964 """Exports values to a record file for Modelica simulation
966 For Annex 60 Library
968 Parameters
969 ----------
971 library : str
972 Used library within the framework of IBPSA library. The
973 models are identical in each library, but IBPSA Modelica library is
974 just a core set of models and should not be used standalone.
975 Valid values are 'AixLib' (default), 'Buildings',
976 'BuildingSystems' and 'IDEAS'.
977 internal_id : float
978 setter of a specific building which will be exported, if None then
979 all buildings will be exported
980 path : string
981 if the Files should not be stored in default output path of TEASER,
982 an alternative path can be specified as a full path
983 """
985 ass_error_1 = (
986 "library for IBPSA export has to be 'AixLib', "
987 "'Buildings', 'BuildingSystems' or 'IDEAS'"
988 )
990 assert library in [
991 "AixLib",
992 "Buildings",
993 "BuildingSystems",
994 "IDEAS",
995 ], ass_error_1
997 self._check_buildings(internal_id)
999 if path is None:
1000 path = os.path.join(utilities.get_default_path(), self.name)
1001 else:
1002 path = os.path.join(path, self.name)
1004 utilities.create_path(path)
1006 if internal_id is None:
1007 ibpsa_output.export_ibpsa(
1008 buildings=self.buildings, prj=self, path=path, library=library
1009 )
1010 else:
1011 for bldg in self.buildings:
1012 if bldg.internal_id == internal_id:
1013 ibpsa_output.export_ibpsa(buildings=[bldg], prj=self, path=path)
1014 return path
1016 def set_default(self, load_data=None):
1017 """Sets all attributes to default
1019 Caution: this will delete all buildings.
1021 Parameters
1022 ----------
1023 load_data : boolean, None-type
1024 boolean if data bindings for type elements and use conditions
1025 should be loaded (default = False), in addition it could be a None-
1026 type to use the already used data bindings
1027 """
1029 self._name = "Project"
1030 self.modelica_info = ModelicaInfo()
1032 self.weather_file_path = utilities.get_full_path(
1033 os.path.join(
1034 "data",
1035 "input",
1036 "inputdata",
1037 "weatherdata",
1038 "DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos",
1039 )
1040 )
1042 self.t_soil_mode = 1
1043 self.t_soil_file_path = utilities.get_full_path(
1044 os.path.join(
1045 "data",
1046 "input",
1047 "inputdata",
1048 "weatherdata",
1049 "t_soil_sample_constant_283_15.mos",
1050 )
1051 )
1053 self.buildings = []
1055 if load_data is True:
1056 self.data = self.instantiate_data_class()
1057 elif not load_data:
1058 pass
1059 else:
1060 self.data = None
1062 self._number_of_elements_calc = 2
1063 self._merge_windows_calc = False
1064 self._used_library_calc = "AixLib"
1066 def set_location_parameters(self,
1067 t_outside=262.65,
1068 t_ground=286.15,
1069 weather_file_path=None,
1070 calc_all_buildings=True):
1071 """ Set location specific parameters
1073 Temperatures are used for static heat load calculation
1074 and as parameters in the exports.
1075 Default location is Mannheim.
1077 Parameters
1078 ----------
1079 t_outside: float [K]
1080 Normative outdoor temperature for static heat load calculation.
1081 The input of t_inside is ALWAYS in Kelvin
1082 t_ground: float [K]
1083 Temperature directly at the outer side of ground floors for static
1084 heat load calculation.
1085 The input of t_ground is ALWAYS in Kelvin
1086 weather_file_path : str
1087 Absolute path to weather file used for Modelica simulation. Default
1088 weather file can be find in inputdata/weatherdata.
1089 calc_all_buildings: boolean
1090 If True, calculates all buildings new. Default is True.
1091 Important for new calculation of static heat load.
1092 """
1093 self.weather_file_path = weather_file_path
1094 for bldg in self.buildings:
1095 for tz in bldg.thermal_zones:
1096 tz.t_outside = t_outside
1097 tz.t_ground = t_ground
1098 if calc_all_buildings:
1099 self.calc_all_buildings()
1101 @staticmethod
1102 def process_export_vars(export_vars):
1103 """Process export vars to fit __Dymola_selections syntax."""
1104 export_vars_str = ''
1105 for index, (var_sel_name, var_list) in enumerate(
1106 export_vars.items(), start=1):
1107 if not isinstance(var_list, list):
1108 raise TypeError(f"Item of key {var_sel_name} in dict 'export_vars' is not an instance of a list!")
1109 export_vars_str += 'MatchVariable(name="'
1110 processed_list = '|'.join(map(str, export_vars[var_sel_name]))
1111 export_vars_str += processed_list
1112 export_vars_str += '",newName="'
1113 export_vars_str += var_sel_name + '.%path%")'
1114 if not index == len(export_vars):
1115 export_vars_str += ','
1116 return export_vars_str
1118 @property
1119 def weather_file_path(self):
1120 return self._weather_file_path
1122 @weather_file_path.setter
1123 def weather_file_path(self, value):
1124 if value is None:
1125 self._weather_file_path = utilities.get_full_path(
1126 os.path.join(
1127 "data",
1128 "input",
1129 "inputdata",
1130 "weatherdata",
1131 "DEU_BW_Mannheim_107290_TRY2010_12_Jahr_BBSR.mos",
1132 )
1133 )
1134 else:
1135 self._weather_file_path = os.path.normpath(value)
1136 self.weather_file_name = os.path.split(self.weather_file_path)[1]
1138 @property
1139 def t_soil_file_path(self):
1140 return self._t_soil_file_path
1142 @t_soil_file_path.setter
1143 def t_soil_file_path(self, value):
1144 if value is None:
1145 self._t_soil_file_path = utilities.get_full_path(
1146 os.path.join(
1147 "data",
1148 "input",
1149 "inputdata",
1150 "weatherdata",
1151 "t_soil_sample_constant_283_15.mos",
1152 )
1153 )
1154 else:
1155 self._t_soil_file_path = os.path.normpath(value)
1156 self.t_soil_file_name = os.path.split(self.t_soil_file_path)[1]
1158 @property
1159 def number_of_elements_calc(self):
1160 return self._number_of_elements_calc
1162 @number_of_elements_calc.setter
1163 def number_of_elements_calc(self, value):
1165 ass_error_1 = "number_of_elements_calc has to be 1,2,3 or 4"
1167 assert value != [1, 2, 3, 4], ass_error_1
1169 self._number_of_elements_calc = value
1171 for bldg in self.buildings:
1172 bldg.number_of_elements_calc = value
1174 @property
1175 def merge_windows_calc(self):
1176 return self._merge_windows_calc
1178 @merge_windows_calc.setter
1179 def merge_windows_calc(self, value):
1181 ass_error_1 = "merge windows needs to be True or False"
1183 assert value != [True, False], ass_error_1
1185 self._merge_windows_calc = value
1187 for bldg in self.buildings:
1188 bldg.merge_windows_calc = value
1190 @property
1191 def used_library_calc(self):
1192 return self._used_library_calc
1194 @used_library_calc.setter
1195 def used_library_calc(self, value):
1197 ass_error_1 = "used library needs to be AixLib or IBPSA"
1199 assert value != ["AixLib", "IBPSA"], ass_error_1
1201 self._used_library_calc = value
1203 for bldg in self.buildings:
1204 bldg.used_library_calc = value
1206 @property
1207 def name(self):
1208 return self._name
1210 @name.setter
1211 def name(self, value):
1212 if isinstance(value, str):
1213 regex = re.compile("[^a-zA-z0-9]")
1214 self._name = regex.sub("", value)
1215 else:
1216 try:
1217 value = str(value)
1218 regex = re.compile("[^a-zA-z0-9]")
1219 self._name = regex.sub("", value)
1220 except ValueError:
1221 print("Can't convert name to string")
1223 if self._name[0].isdigit():
1224 self._name = "P" + self._name
1226 @property
1227 def method_interzonal_material_enrichment(self):
1228 return self._method_interzonal_material_enrichment
1230 @method_interzonal_material_enrichment.setter
1231 def method_interzonal_material_enrichment(self, value):
1232 assert type(value) is str
1233 assert (value in ('heating_difference', 'cooling_difference')
1234 or value.startswith('setpoint_difference_'))
1235 if value.startswith('setpoint_difference_'):
1236 try:
1237 setpoint_diff = float(value.lstrip('setpoint_difference_'))
1238 self._method_interzonal_material_enrichment = value
1239 except ValueError:
1240 print("setpoint for method_interzonal_material_enrichment "
1241 "could not be converted to float.")
1242 else:
1243 self._method_interzonal_material_enrichment = value
1245 @property
1246 def method_interzonal_export(self):
1247 return self._method_interzonal_export
1249 @method_interzonal_export.setter
1250 def method_interzonal_export(self, value):
1251 assert type(value) is str
1252 assert (value in ('heating_difference', 'cooling_difference')
1253 or value.startswith('setpoint_difference_'))
1254 if value.startswith('setpoint_difference_'):
1255 try:
1256 setpoint_diff = float(value.lstrip('setpoint_difference_'))
1257 self._method_interzonal_export = value
1258 except ValueError:
1259 print("setpoint for method_interzonal_export "
1260 "could not be converted to float.")
1261 else:
1262 self._method_interzonal_export = value