Coverage for teaser/project.py: 89%

311 statements  

« 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.""" 

2 

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 

18 

19 

20class Project(object): 

21 """Top class for TEASER projects it serves as an API 

22 

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. 

28 

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 

36 

37 Attributes 

38 ---------- 

39 

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 """ 

109 

110 def __init__(self, load_data=False): 

111 """Constructor of Project Class. 

112 """ 

113 self._name = "Project" 

114 self.modelica_info = ModelicaInfo() 

115 

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 ) 

125 

126 self.t_soil_mode = 1 

127 

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 ) 

137 

138 self.buildings = [] 

139 

140 self.load_data = load_data 

141 

142 self._number_of_elements_calc = 2 

143 self._merge_windows_calc = False 

144 self._used_library_calc = "AixLib" 

145 

146 if load_data: 

147 raise ValueError("This option was deprecated") 

148 self.data = None 

149 

150 self.dir_reference_results = None 

151 

152 self.method_interzonal_material_enrichment = 'heating_difference' 

153 self.method_interzonal_export = 'heating_difference' 

154 

155 @staticmethod 

156 def instantiate_data_class(): 

157 """Initialization of DataClass 

158 

159 Returns 

160 ------- 

161 

162 DataClass : Instance of DataClass() 

163 

164 """ 

165 return DataClass( 

166 construction_data=datahandling.ConstructionData.iwu_heavy) 

167 

168 def calc_all_buildings(self, raise_errors=False): 

169 """Calculates values for all project buildings 

170 

171 You need to set the following parameters in the Project class. 

172 

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!!! 

177 

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 

182 

183 used_library_calc : str 

184 used library (AixLib and IBPSA are supported) 

185 

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) 

210 

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. 

219 

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). 

223 

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. 

227 

228 'iwu' or 'BMVBS' Buildings in the project are retrofitted in the 

229 following manner: 

230 

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. 

238 

239 The needed parameters for the Modelica Model are calculated 

240 automatically, using the calculation_method specified in the 

241 first scenario. 

242 

243 Note: To Calculate U-Value, the standard TEASER coefficients for outer 

244 and inner heat transfer are used. 

245 

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. 

258 

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 = [] 

265 

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) 

279 

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) 

293 

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 ) 

305 

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. 

330 

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). 

334 

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: 

361 

362 1. Temperature and activity degree dependent heat flux calculation for persons. The 

363 calculation is based on SIA 2024 (default) 

364 

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. 

368 

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. 

373 

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 

386 

387 office_layout : int 

388 Structure of the floor plan of office buildings, default is 1, 

389 which is representative for one elongated floor. 

390 

391 1. elongated 1 floor 

392 

393 2. elongated 2 floors 

394 

395 3. compact (e.g. for a square base building) 

396 

397 window_layout : int 

398 Structure of the window facade type, default is 1, which is 

399 representative for a punctuated facade. 

400 

401 1. punctuated facade (individual windows) 

402 

403 2. banner facade (continuous windows) 

404 

405 3. full glazing 

406 

407 Returns 

408 ------- 

409 type_bldg : Instance of Office() 

410 

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) 

417 

418 ass_error_construction_data = ( 

419 "only 'iwu_heavy' is a valid construction_data for " 

420 "'non-residential' archetype generation") 

421 

422 assert construction_data.value == "iwu_heavy", \ 

423 ass_error_construction_data 

424 

425 ass_error_geometry_data = ( 

426 "geometry_data does not match the construction_data") 

427 

428 assert geometry_data in datahandling.allowed_geometries.get( 

429 construction_data, []), ass_error_geometry_data 

430 

431 self.data = DataClass(construction_data) 

432 

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 ) 

447 

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 

455 

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. 

476 

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). 

489 

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' 

497 

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' 

508 

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. 

537 

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 

550 

551 residential_layout : int 

552 Structure of floor plan (default = 0) CAUTION only used for iwu 

553 

554 0. compact 

555 

556 1. elongated/complex 

557 

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) 

562 

563 0. no neighbour 

564 1. one neighbour 

565 2. two neighbours 

566 

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 

571 

572 0. flat roof 

573 1. non heated attic 

574 2. partly heated attic 

575 3. heated attic 

576 

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 

582 

583 0. no cellar 

584 1. non heated cellar 

585 2. partly heated cellar 

586 3. heated cellar 

587 

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 

592 

593 0. no dormer 

594 1. dormer 

595 

596 number_of_apartments : int 

597 number of apartments inside Building (default = 1). CAUTION only 

598 used for urbanrenet 

599 

600 Returns 

601 ------- 

602 type_bldg : Instance of Archetype Building 

603 

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) 

610 

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 ) 

616 

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) 

623 

624 self.data = DataClass(construction_data) 

625 

626 ass_error_geometry_data = ( 

627 "geometry_data does not match the construction_data") 

628 

629 assert geometry_data in datahandling.allowed_geometries.get( 

630 construction_data, []), ass_error_geometry_data 

631 

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 } 

643 

644 urbanrenet_arg = common_arg.copy() 

645 urbanrenet_arg.update({ 

646 'neighbour_buildings': neighbour_buildings, 

647 }) 

648 

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 }) 

657 

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 

688 

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. 

698 

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. 

702 

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']. 

713 

714 Returns 

715 ------- 

716 list: A list of names of the generated buildings. 

717 

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. 

722 

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 ) 

733 

734 def save_project(self, file_name=None, path=None): 

735 """Saves the project to a JSON file 

736 

737 Calls the function save_teaser_json in data.output.teaserjson_output 

738 

739 Parameters 

740 ---------- 

741 

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 

752 

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) 

757 

758 tjson_out.save_teaser_json(new_path, self) 

759 

760 def load_project(self, path): 

761 """Load the project from a json file (new format). 

762 

763 Calls the function load_teaser_json. 

764 

765 Parameters 

766 ---------- 

767 path : string 

768 full path to a json file 

769 

770 """ 

771 

772 tjson_in.load_teaser_json(path, self) 

773 

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.") 

782 

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 

796 

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. 

805 

806 Parameters 

807 ---------- 

808 

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 """ 

827 

828 self._check_buildings(internal_id) 

829 

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) 

839 

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) 

844 

845 utilities.create_path(path) 

846 

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 ) 

864 

865 if report: 

866 self._write_report(path) 

867 return path 

868 

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 

882 

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. 

886 

887 Parameters 

888 ---------- 

889 

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. 

916 

917 Returns 

918 ------- 

919 str 

920 The path where the exported files are stored. 

921 """ 

922 

923 self._check_buildings(internal_id) 

924 

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) 

929 

930 utilities.create_path(path) 

931 

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 ) 

946 

947 if report: 

948 self._write_report(path) 

949 return path 

950 

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) 

962 

963 def export_ibpsa(self, library="AixLib", internal_id=None, path=None): 

964 """Exports values to a record file for Modelica simulation 

965 

966 For Annex 60 Library 

967 

968 Parameters 

969 ---------- 

970 

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 """ 

984 

985 ass_error_1 = ( 

986 "library for IBPSA export has to be 'AixLib', " 

987 "'Buildings', 'BuildingSystems' or 'IDEAS'" 

988 ) 

989 

990 assert library in [ 

991 "AixLib", 

992 "Buildings", 

993 "BuildingSystems", 

994 "IDEAS", 

995 ], ass_error_1 

996 

997 self._check_buildings(internal_id) 

998 

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) 

1003 

1004 utilities.create_path(path) 

1005 

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 

1015 

1016 def set_default(self, load_data=None): 

1017 """Sets all attributes to default 

1018 

1019 Caution: this will delete all buildings. 

1020 

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 """ 

1028 

1029 self._name = "Project" 

1030 self.modelica_info = ModelicaInfo() 

1031 

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 ) 

1041 

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 ) 

1052 

1053 self.buildings = [] 

1054 

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 

1061 

1062 self._number_of_elements_calc = 2 

1063 self._merge_windows_calc = False 

1064 self._used_library_calc = "AixLib" 

1065 

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 

1072 

1073 Temperatures are used for static heat load calculation 

1074 and as parameters in the exports. 

1075 Default location is Mannheim. 

1076 

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() 

1100 

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 

1117 

1118 @property 

1119 def weather_file_path(self): 

1120 return self._weather_file_path 

1121 

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] 

1137 

1138 @property 

1139 def t_soil_file_path(self): 

1140 return self._t_soil_file_path 

1141 

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] 

1157 

1158 @property 

1159 def number_of_elements_calc(self): 

1160 return self._number_of_elements_calc 

1161 

1162 @number_of_elements_calc.setter 

1163 def number_of_elements_calc(self, value): 

1164 

1165 ass_error_1 = "number_of_elements_calc has to be 1,2,3 or 4" 

1166 

1167 assert value != [1, 2, 3, 4], ass_error_1 

1168 

1169 self._number_of_elements_calc = value 

1170 

1171 for bldg in self.buildings: 

1172 bldg.number_of_elements_calc = value 

1173 

1174 @property 

1175 def merge_windows_calc(self): 

1176 return self._merge_windows_calc 

1177 

1178 @merge_windows_calc.setter 

1179 def merge_windows_calc(self, value): 

1180 

1181 ass_error_1 = "merge windows needs to be True or False" 

1182 

1183 assert value != [True, False], ass_error_1 

1184 

1185 self._merge_windows_calc = value 

1186 

1187 for bldg in self.buildings: 

1188 bldg.merge_windows_calc = value 

1189 

1190 @property 

1191 def used_library_calc(self): 

1192 return self._used_library_calc 

1193 

1194 @used_library_calc.setter 

1195 def used_library_calc(self, value): 

1196 

1197 ass_error_1 = "used library needs to be AixLib or IBPSA" 

1198 

1199 assert value != ["AixLib", "IBPSA"], ass_error_1 

1200 

1201 self._used_library_calc = value 

1202 

1203 for bldg in self.buildings: 

1204 bldg.used_library_calc = value 

1205 

1206 @property 

1207 def name(self): 

1208 return self._name 

1209 

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") 

1222 

1223 if self._name[0].isdigit(): 

1224 self._name = "P" + self._name 

1225 

1226 @property 

1227 def method_interzonal_material_enrichment(self): 

1228 return self._method_interzonal_material_enrichment 

1229 

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 

1244 

1245 @property 

1246 def method_interzonal_export(self): 

1247 return self._method_interzonal_export 

1248 

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