Coverage for teaser/logic/buildingobjects/buildingphysics/wall.py: 78%

201 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2025-04-29 16:01 +0000

1"""asd""" 

2from teaser.logic.buildingobjects.buildingphysics.buildingelement \ 

3 import BuildingElement 

4from teaser.logic.buildingobjects.buildingphysics.layer import Layer 

5from teaser.logic.buildingobjects.buildingphysics.material import Material 

6import numpy as np 

7import warnings 

8 

9 

10class Wall(BuildingElement): 

11 """Wall class 

12 

13 This class holds functions and information for walls. It inherits for 

14 BuildingElement() and is a base class for all inner and outer walls. 

15 

16 Parameters 

17 ---------- 

18 

19 parent : ThermalZone() 

20 The parent class of this object, the ThermalZone the BE belongs to. 

21 Allows for better control of hierarchical structures. 

22 Default is None. 

23 

24 

25 Attributes 

26 ---------- 

27 

28 internal_id : float 

29 Random id for the distinction between different elements. 

30 name : str 

31 Individual name 

32 construction_data : str 

33 Type of construction (e.g. "heavy" or "light"). Needed for 

34 distinction between different constructions types in the same 

35 building age period. 

36 year_of_retrofit : int 

37 Year of last retrofit 

38 year_of_construction : int 

39 Year of first construction 

40 building_age_group : list 

41 Determines the building age period that this building 

42 element belongs to [begin, end], e.g. [1984, 1994] 

43 area : float [m2] 

44 Area of building element 

45 tilt : float [degree] 

46 Tilt against horizontal 

47 orientation : float [degree] 

48 Azimuth direction of building element (0 : north, 90: east, 180: south, 

49 270: west) 

50 inner_convection : float [W/(m2*K)] 

51 Constant heat transfer coefficient of convection inner side (facing 

52 the zone) 

53 inner_radiation : float [W/(m2*K)] 

54 Constant heat transfer coefficient of radiation inner side (facing 

55 the zone) 

56 outer_convection : float [W/(m2*K)] 

57 Constant heat transfer coefficient of convection outer side (facing 

58 the ambient or adjacent zone). Currently for all InnerWalls and 

59 GroundFloors this value is set to 0.0 

60 outer_radiation : float [W/(m2*K)] 

61 Constant heat transfer coefficient of radiation outer side (facing 

62 the ambient or adjacent zone). Currently for all InnerWalls and 

63 GroundFloors this value is set to 0.0 

64 layer : list 

65 List of all layers of a building element (to be filled with Layer 

66 objects). Use element.layer = None to delete all layers of the building 

67 element 

68 other_side : ThermalZone() 

69 the thermal zone on the other side of the building element (only for 

70 interzonal elements) 

71 interzonal_type_material : str 

72 one of (None (default), 'inner', 'outer_ordered', 'outer_reversed') 

73 describes as which kind of element the element is treated when loading 

74 type elements. Caution: Make sure that the complimentary element of 

75 the other zone is also changed accordingly if this is adapted manually 

76 None: treatment based on project.method_interzonal_export_enrichment 

77 'inner': InterzonalWall treated as InnerWall, 

78 InterzonalFloor treated as Floor, 

79 InterzonalCeiling treated as Ceiling 

80 'outer_ordered': InterzonalWall treated as Wall, 

81 InterzonalFloor treated as GroundFloor, 

82 InterzonalCeiling treated as Rooftop 

83 'outer_reversed': InterzonalWall treated as Wall, 

84 InterzonalFloor treated as Rooftop, 

85 InterzonalCeiling treated as GroundFloor, but with 

86 reversed layers, resulting in the reversed 

87 sequence of layers as for the complimentary 

88 element declared as 'outer_ordered' 

89 interzonal_type_export : str 

90 one of (None (default), 'inner', 'outer_ordered', 'outer_reversed') 

91 describes as which kind of element the element is treated when exporting 

92 to Modelica. Caution: Make sure that the complimentary element of 

93 the other zone is also changed accordingly if this is adapted manually 

94 'inner': element will be lumped with InnerWall. No heat flow to the 

95 zone on the other side will be modelled. 

96 'outer_ordered': element will be lumped with OuterWall (OneElement 

97 to FourElement export) or treated as border to an 

98 adjacent zone (FiveElement export). Borders to the 

99 same adjacent zone will be lumped. 

100 'outer_reversed': like 'outer_ordered', but the lumping follows 

101 VDI 6007-1 in reversed order, resulting in the 

102 reversed order of resistances and capacitors as 

103 for the complimentary element declared as 

104 'outer_ordered' 

105 

106 Calculated Attributes 

107 

108 r1 : float [K/W] 

109 equivalent resistance R1 of the analogous model given in VDI 6007 

110 r2 : float [K/W] 

111 equivalent resistance R2 of the analogous model given in VDI 6007 

112 r3 : float [K/W] 

113 equivalent resistance R3 of the analogous model given in VDI 6007 

114 c1 : float [J/K] 

115 equivalent capacity C1 of the analogous model given in VDI 6007 

116 c2 : float [J/K] 

117 equivalent capacity C2 of the analogous model given in VDI 6007 

118 c1_korr : float [J/K] 

119 corrected capacity C1,korr for building elements in the case of 

120 asymmetrical thermal load given in VDI 6007 

121 calc_u: Required area-specific U-value in retrofit cases [W/K] 

122 ua_value : float [W/K] 

123 UA-Value of building element (Area times U-Value) 

124 r_inner_conv : float [K/W] 

125 Convective resistance of building element on inner side (facing the 

126 zone) 

127 r_inner_rad : float [K/W] 

128 Radiative resistance of building element on inner side (facing the 

129 zone) 

130 r_inner_conv : float [K/W] 

131 Combined convective and radiative resistance of building element on 

132 inner side (facing the zone) 

133 r_outer_conv : float [K/W] 

134 Convective resistance of building element on outer side (facing 

135 the ambient or adjacent zone). Currently for all InnerWalls and 

136 GroundFloors this value is set to 0.0 

137 r_outer_rad : float [K/W] 

138 Radiative resistance of building element on outer side (facing 

139 the ambient or adjacent zone). Currently for all InnerWalls and 

140 GroundFloors this value is set to 0.0 

141 r_outer_comb : float [K/W] 

142 Combined convective and radiative resistance of building element on 

143 outer side (facing the ambient or adjacent zone). Currently for all 

144 InnerWalls and GroundFloors this value is set to 0.0 

145 wf_out : float 

146 Weightfactor of building element ua_value/ua_value_zone 

147 """ 

148 

149 def __init__(self, parent=None, other_side=None): 

150 """Constructor of Wall 

151 """ 

152 self.other_side = other_side 

153 self.interzonal_type_material = None 

154 self.interzonal_type_export = None 

155 super(Wall, self).__init__(parent) 

156 

157 def calc_equivalent_res(self, t_bt=7): 

158 """Equivalent resistance according to VDI 6007. 

159 

160 Calculates the equivalent resistance and capacity of a wall according 

161 to VDI 6007 guideline. (Analogous model). 

162 

163 Parameters 

164 ---------- 

165 t_bt : int 

166 Time constant according to VDI 6007 (default t_bt = 7) 

167 """ 

168 

169 nr_of_layer, density, thermal_conduc, heat_capac, thickness = \ 

170 self.gather_element_properties() 

171 

172 reverse_layers = self.interzonal_type_export == 'outer_reversed' 

173 

174 if reverse_layers: 

175 density = density[-1::-1] 

176 thermal_conduc = thermal_conduc[-1::-1] 

177 heat_capac = heat_capac[-1::-1] 

178 thickness = thickness[-1::-1] 

179 

180 omega = 2 * np.pi / (86400 * t_bt) 

181 

182 r_layer = thickness / thermal_conduc 

183 c_layer = heat_capac * density * thickness * 1000 

184 

185 re11 = np.cosh(np.sqrt(0.5 * omega * r_layer * c_layer)) * \ 

186 np.cos(np.sqrt(0.5 * omega * r_layer * c_layer)) 

187 im11 = np.sinh(np.sqrt(0.5 * omega * r_layer * c_layer)) * \ 

188 np.sin(np.sqrt(0.5 * omega * r_layer * c_layer)) 

189 re12 = r_layer * np.sqrt(1 / (2 * omega * r_layer * c_layer)) * \ 

190 (np.cosh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

191 np.sin(np.sqrt(0.5 * omega * r_layer * c_layer)) + 

192 np.sinh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

193 np.cos(np.sqrt(0.5 * omega * r_layer * c_layer))) 

194 im12 = r_layer * np.sqrt(1 / (2 * omega * r_layer * c_layer)) * \ 

195 (np.cosh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

196 np.sin(np.sqrt(0.5 * omega * r_layer * c_layer)) - 

197 np.sinh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

198 np.cos(np.sqrt(0.5 * omega * r_layer * c_layer))) 

199 re21 = (-1 / r_layer) * (np.sqrt(0.5 * omega * r_layer * c_layer)) * \ 

200 (np.cosh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

201 np.sin(np.sqrt(0.5 * omega * r_layer * c_layer)) - 

202 np.sinh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

203 np.cos(np.sqrt(0.5 * omega * r_layer * c_layer))) 

204 im21 = (1 / r_layer) * (np.sqrt(0.5 * omega * r_layer * c_layer)) * \ 

205 (np.cosh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

206 np.sin(np.sqrt(0.5 * omega * r_layer * c_layer)) + 

207 np.sinh(np.sqrt(0.5 * omega * r_layer * c_layer)) * 

208 np.cos(np.sqrt(0.5 * omega * r_layer * c_layer))) 

209 re22 = re11 

210 im22 = im11 

211 

212 # -----setting up the matrix for each layer 

213 a_layer = np.zeros((nr_of_layer, 4, 4)) 

214 

215 for i, r in enumerate(re11): 

216 a_layer[i][0][0] = r 

217 a_layer[i][0][1] = im11[i] 

218 a_layer[i][0][2] = re12[i] 

219 a_layer[i][0][3] = im12[i] 

220 a_layer[i][1][0] = -im11[i] 

221 a_layer[i][1][1] = re11[i] 

222 a_layer[i][1][2] = -im12[i] 

223 a_layer[i][1][3] = re12[i] 

224 a_layer[i][2][0] = re21[i] 

225 a_layer[i][2][1] = im21[i] 

226 a_layer[i][2][2] = re22[i] 

227 a_layer[i][2][3] = im22[i] 

228 a_layer[i][3][0] = -im21[i] 

229 a_layer[i][3][1] = re21[i] 

230 a_layer[i][3][2] = -im22[i] 

231 a_layer[i][3][3] = re22[i] 

232 

233 # -----multiplication of the matrix 

234 new_mat = np.diag(np.ones(4)) 

235 

236 for count_layer in a_layer: 

237 new_mat = np.dot(new_mat, count_layer) 

238 

239 # calculation of equivalent Resistance and capacities of each element 

240 self.r1 = (1 / self.area) * ((new_mat[3][3] - 1) * 

241 new_mat[0][2] + new_mat[2][3] * 

242 new_mat[0][3]) / \ 

243 ((new_mat[3][3] - 1) ** 2 + new_mat[2][3] ** 2) 

244 self.r2 = (1 / self.area) * ((new_mat[0][0] - 1) * 

245 new_mat[0][2] + new_mat[0][1] * 

246 new_mat[0][3]) / \ 

247 ((new_mat[0][0] - 1) ** 2 + new_mat[0][1] ** 2) 

248 self.c1 = self.area * ((new_mat[3][3] - 1) ** 2 + 

249 (new_mat[2][3]) ** 2) / \ 

250 (omega * (new_mat[0][2] * new_mat[2][3] - 

251 (new_mat[3][3] - 1) * new_mat[0][3])) 

252 self.c2 = self.area * ((new_mat[0][0] - 1) ** 2 + 

253 (new_mat[0][1]) ** 2) / ( 

254 omega * (new_mat[0][2] * 

255 new_mat[0][1] - 

256 (new_mat[0][0] - 1) * 

257 new_mat[0][3])) 

258 self.r3 = (1 / self.area) * (np.sum(r_layer)) - self.r1 - self.r2 

259 

260 r_wall = self.r1 + self.r2 + self.r3 

261 

262 self.c1_korr = (1 / (omega * self.r1)) \ 

263 * ((r_wall * self.area 

264 - new_mat[0][2] * new_mat[3][3] 

265 - new_mat[0][3] * new_mat[2][3]) 

266 / (new_mat[3][3] * new_mat[0][3] 

267 - new_mat[0][2] * new_mat[2][3])) 

268 

269 if reverse_layers: 

270 former_r2 = self.r2 

271 self.r2 = self.r1 

272 self.r1 = former_r2 

273 former_c2 = self.c2 

274 self.c2 = self.c1 

275 self.c1 = former_c2 

276 

277 if type(self).__name__ == "OuterWall" \ 

278 or type(self).__name__ == "Rooftop" \ 

279 or type(self).__name__ == "GroundFloor": 

280 self.c1 = self.c1_korr 

281 

282 def insulate_wall( 

283 self, 

284 material=None, 

285 thickness=None, 

286 add_at_position=None, 

287 add_plaster_material=None, 

288 add_plaster_thickness=None): 

289 """Retrofit the walls with an additional insulation layer 

290 

291 Adds an additional layer on the wall 

292 

293 Parameters 

294 ---------- 

295 material : string 

296 Type of material, that is used for insulation, default = EPS035 

297 thickness : float 

298 thickness of the insulation layer, default = None 

299 add_at_position : int 

300 position at which to insert the insulation layer. 

301 0 inside, None (default) or -1 outside/other side 

302 add_plaster_material : int 

303 material of plaster to add, default = None. Is only applied if 

304 add_plaster_thickness is not None 

305 can only be applied if add_at_position is 0 or None 

306 add_plaster_thickness : float 

307 thickness of the plaster layer, default = None 

308 

309 Returns 

310 ------- 

311 insulation_index : int 

312 index of the insulation layer in the layer list 

313 

314 """ 

315 if material is None: 

316 material = "EPS035" 

317 else: 

318 pass 

319 

320 if add_at_position == -1: 

321 add_at_position = None 

322 

323 ext_layer = Layer(self, parent_position=add_at_position) 

324 new_material = Material(ext_layer) 

325 new_material.load_material_template( 

326 material, 

327 data_class=self.parent.parent.parent.data) 

328 

329 if thickness is None: 

330 pass 

331 else: 

332 ext_layer.thickness = thickness 

333 

334 ext_layer.material = new_material 

335 

336 insulation_index = len(self.layer) - 1 if add_at_position is None \ 

337 else add_at_position 

338 

339 if add_plaster_thickness is not None: 

340 ass_error_1 = "If plaster is added, insulation must be applied at" \ 

341 " inside or outside" 

342 

343 assert add_at_position is None or add_at_position == 0, ass_error_1 

344 if add_plaster_material is None: 

345 add_plaster_material = 'insulating_plaster' 

346 plaster_layer = Layer(self, parent_position=add_at_position) 

347 plaster_material = Material(plaster_layer) 

348 plaster_material.load_material_template( 

349 add_plaster_material, 

350 data_class=self.parent.parent.parent.data) 

351 plaster_layer.thickness = add_plaster_thickness 

352 if add_at_position == 0: 

353 insulation_index = 1 

354 

355 return insulation_index 

356 

357 def retrofit_wall(self, 

358 year_of_retrofit, 

359 material=None, 

360 add_at_position=None): 

361 """Retrofits wall to German refurbishment standards. 

362 

363 This function adds an additional layer of insulation and sets the 

364 thickness of the layer according to the retrofit standard in the 

365 year of refurbishment. Refurbishment year must be newer then 1977. 

366 Refurbishment layers are added on the unheated/outside of outer walls, 

367 rooftops, ground floors and interzonal elements between heated and 

368 unheated zones if not otherwise specified. 

369 

370 Note: To Calculate thickness and U-Value, the standard TEASER 

371 coefficients for outer and inner heat transfer are used. 

372 

373 The used Standards are namely the Waermeschutzverordnung (WSVO) and 

374 Energieeinsparverordnung (EnEv) 

375 

376 Parameters 

377 ---------- 

378 material : string 

379 Type of material, that is used for insulation 

380 year_of_retrofit : int 

381 Year of the retrofit of the wall/building 

382 add_at_position : int 

383 position at which to insert the insulation layer. 

384 0 inside, None (default) or -1 outside/other side 

385 

386 

387 """ 

388 raise NotImplementedError("Please call this method only against " 

389 "OuterWall, Rooftop, GroundFloor, and " 

390 "interzonal elements") 

391 

392 def initialize_retrofit(self, 

393 material, 

394 year_of_retrofit, 

395 add_at_position=None): 

396 """Checks the retrofit inputs and sets material and year of retrofit 

397 if needed.""" 

398 self.set_calc_default() 

399 self.calc_ua_value() 

400 

401 if material is None: 

402 material = "EPS_perimeter_insulation_top_layer" 

403 else: 

404 pass 

405 

406 if year_of_retrofit < 1977: 

407 year_of_retrofit = 1977 

408 warnings.warn("You are using a year of retrofit not supported\ 

409 by teaser. We will change your year of retrofit to 1977\ 

410 for the calculation. Be careful!") 

411 

412 if add_at_position is None: 

413 ins_layer_index = -1 # default: outside 

414 else: 

415 ins_layer_index = add_at_position 

416 

417 return material, year_of_retrofit, ins_layer_index 

418 

419 def set_insulation(self, 

420 material, 

421 calc_u, 

422 year_of_retrofit, 

423 ins_layer_index=-1): 

424 """Sets the correct insulation thickness based on the given u-value""" 

425 if calc_u: 

426 if self.u_value <= calc_u: 

427 warnings.warn( 

428 f'No retrofit needed for {self.name} as u value ' 

429 f'is already lower than needed.') 

430 else: 

431 ins_layer_index = self.insulate_wall( 

432 material, 

433 add_at_position=ins_layer_index 

434 ) 

435 d_ins = self.calc_ins_layer_thickness( 

436 calc_u, 

437 ins_layer_index 

438 ) 

439 self.layer[ins_layer_index].thickness = d_ins 

440 self.layer[ins_layer_index].id = len(self.layer) 

441 else: 

442 warnings.warn( 

443 f'No fitting retrofit type found for {year_of_retrofit}') 

444 

445 def calc_ins_layer_thickness(self, calc_u, ins_layer_index): 

446 """Calculates the thickness of the fresh insulated layer from 

447 retrofit""" 

448 r_conduc_rem = 0 

449 

450 for layer_index, count_layer in enumerate(self.layer): 

451 if layer_index == ins_layer_index: 

452 pass 

453 else: 

454 r_conduc_rem += (count_layer.thickness / 

455 count_layer.material.thermal_conduc) 

456 

457 lambda_ins = self.layer[ins_layer_index].material.thermal_conduc 

458 

459 d_ins = lambda_ins * (1 / calc_u - self.r_outer_comb * self.area - 

460 self.r_inner_comb * self.area - r_conduc_rem) 

461 return d_ins 

462 

463 

464 def _interzonal_type_standard_value(self, method): 

465 """return the standard value for the treatment of interzonal elements 

466 

467 Refer to the documentation of project for details 

468 

469 Parameters 

470 ---------- 

471 method : str 

472 a valid value of project.method_interzonal_material_enrichment or 

473 project.method_interzonal_export 

474 

475 Returns 

476 ------- 

477 value : str 

478 'inner', 'outer_ordered', or 'outer_reversed' 

479 

480 """ 

481 this_use = self.parent.use_conditions 

482 try: 

483 other_use = self.other_side.use_conditions 

484 except AttributeError: 

485 other_use = None 

486 if other_use is None: 

487 if not this_use.with_heating and not this_use.with_cooling: 

488 value = 'inner' 

489 else: 

490 value = 'outer_ordered' 

491 elif method == 'heating_difference': 

492 if ((other_use.with_heating and this_use.with_heating) 

493 or (not other_use.with_heating 

494 and not this_use.with_heating)): 

495 value = 'inner' 

496 elif this_use.with_heating is True: 

497 value = 'outer_ordered' 

498 else: # this_use.with_heating is False: 

499 value = 'outer_reversed' 

500 elif (method == 'heating_cooling_difference' 

501 or method.startswith('setpoint_difference_')): 

502 # first decision: different heating conditions? 

503 if this_use.with_heating and not other_use.with_heating: 

504 value = 'outer_ordered' 

505 elif not this_use.with_heating and other_use.with_heating: 

506 value = 'outer_reversed' 

507 # second decision: different cooling conditions? 

508 elif this_use.with_cooling and not other_use.with_cooling: 

509 value = 'outer_ordered' 

510 elif not this_use.with_cooling and other_use.with_cooling: 

511 value = 'outer_reversed' 

512 elif not method.startswith('setpoint_difference_'): 

513 value = 'inner' 

514 else: 

515 # third decision: compare setpoints 

516 max_setpoint_diff = float( 

517 method.lstrip('setpoint_difference_') 

518 ) 

519 

520 setpoint_diff_heating = np.subtract( 

521 this_use.schedules["heating_profile"], 

522 other_use.schedules["heating_profile"] 

523 ) 

524 this_warmer = any(setpoint_diff_heating > max_setpoint_diff) 

525 other_warmer = any(setpoint_diff_heating < -max_setpoint_diff) 

526 

527 setpoint_diff_cooling = np.subtract( 

528 this_use.schedules["cooling_profile"], 

529 other_use.schedules["cooling_profile"] 

530 ) 

531 this_colder = any(setpoint_diff_cooling < -max_setpoint_diff) 

532 other_colder = any(setpoint_diff_cooling > max_setpoint_diff) 

533 

534 if this_warmer and not other_warmer: 

535 value = 'outer_ordered' 

536 elif other_warmer and not this_warmer: 

537 value = 'outer_reversed' 

538 elif this_colder and not other_colder: 

539 value = 'outer_ordered' 

540 elif other_colder and not this_colder: 

541 value = 'outer_reversed' 

542 else: 

543 value = 'inner' 

544 

545 return value 

546 

547 @property 

548 def other_side(self): 

549 return self._other_side 

550 

551 @other_side.setter 

552 def other_side(self, value): 

553 if value is not None: 

554 ass_error_1 = "Other side has to be an instance of ThermalZone()" 

555 assert type(value).__name__ == "ThermalZone", ass_error_1 

556 ass_error_2 = "Other side can only be set for interzonal elements" 

557 assert type(self).__name__ in ("InterzonalWall", "InterzonalFloor", 

558 "InterzonalCeiling"), ass_error_2 

559 self._other_side = value 

560 else: 

561 self._other_side = None 

562 

563 @property 

564 def interzonal_type_material(self): 

565 if self._interzonal_type_material is not None: 

566 return self._interzonal_type_material 

567 else: 

568 return self._interzonal_type_standard_value( 

569 method=self.parent.parent.parent.method_interzonal_material_enrichment 

570 ) 

571 

572 @interzonal_type_material.setter 

573 def interzonal_type_material(self, value): 

574 allowed_values = (None, 'inner', 'outer_ordered', 'outer_reversed') 

575 assert value in allowed_values 

576 self._interzonal_type_material = value 

577 

578 @property 

579 def interzonal_type_export(self): 

580 if self._interzonal_type_export is not None: 

581 return self._interzonal_type_export 

582 else: 

583 return self._interzonal_type_standard_value( 

584 method=self.parent.parent.parent.method_interzonal_export 

585 ) 

586 return value 

587 

588 @interzonal_type_export.setter 

589 def interzonal_type_export(self, value): 

590 allowed_values = (None, 'inner', 'outer_ordered', 'outer_reversed') 

591 assert value in allowed_values 

592 self._interzonal_type_export = value