Coverage for teaser/logic/buildingobjects/buildingphysics/buildingelement.py: 83%

317 statements  

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

1"""This module contains the Base class for all building elements.""" 

2 

3from __future__ import division 

4import warnings 

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

6import teaser.data.input.buildingelement_input_json as buildingelement_input 

7import numpy as np 

8import random 

9import re 

10 

11 

12class BuildingElement(object): 

13 """Building element class. 

14 

15 This is the base class for all building elements. Building elements are 

16 all physical elements that may serve as boundaries for a thermal zone or 

17 building. 

18 

19 Parameters 

20 ---------- 

21 

22 parent : ThermalZone() 

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

24 Allows for better control of hierarchical structures. 

25 Default is None. 

26 

27 Attributes 

28 ---------- 

29 

30 internal_id : float 

31 Random id for the distinction between different elements. 

32 name : str 

33 Individual name 

34 construction_data : str 

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

36 distinction between different constructions types in the same 

37 building age period. 

38 year_of_retrofit : int 

39 Year of last retrofit 

40 year_of_construction : int 

41 Year of first construction 

42 building_age_group : list 

43 Determines the building age period that this building 

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

45 area : float [m2] 

46 Area of building element 

47 tilt : float [degree] 

48 Tilt against horizontal 

49 orientation : float [degree] 

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

51 270: west) 

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

53 Constant heat transfer coefficient of convection inner side (facing 

54 the zone) 

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

56 Constant heat transfer coefficient of radiation inner side (facing 

57 the zone) 

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

59 Constant heat transfer coefficient of convection outer side (facing 

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

61 GroundFloors this value is set to 0.0 

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

63 Constant heat transfer coefficient of radiation outer side (facing 

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

65 GroundFloors this value is set to 0.0 

66 layer : list 

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

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

69 element 

70 

71 Calculated Attributes 

72 

73 r1 : float [K/W] 

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

75 r2 : float [K/W] 

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

77 r3 : float [K/W] 

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

79 c1 : float [J/K] 

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

81 c2 : float [J/K] 

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

83 c1_korr : float [J/K] 

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

85 asymmetrical thermal load given in VDI 6007 

86 u_value : float [W/m2K) 

87 U-Value of building element 

88 ua_value : float [W/K] 

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

90 r_inner_conv : float [K/W] 

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

92 zone) 

93 r_inner_rad : float [K/W] 

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

95 zone) 

96 r_inner_comb : float [K/W] 

97 Combined convective and radiative resistance of building element on 

98 inner side (facing the zone) 

99 r_outer_conv : float [K/W] 

100 Convective resistance of building element on outer side (facing 

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

102 GroundFloors this value is set to 0.0 

103 r_outer_rad : float [K/W] 

104 Radiative resistance of building element on outer side (facing 

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

106 GroundFloors this value is set to 0.0 

107 r_outer_comb : float [K/W] 

108 Combined convective and radiative resistance of building element on 

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

110 InnerWalls and GroundFloors this value is set to 0.0 

111 wf_out : float 

112 Weightfactor of building element ua_value/ua_value_zone 

113 """ 

114 

115 def __init__(self, parent=None): 

116 """Constructor for BuildingElement 

117 """ 

118 

119 self.parent = parent 

120 

121 self.internal_id = random.random() 

122 

123 self.name = None 

124 self._construction_data = None 

125 self._year_of_retrofit = None 

126 self._year_of_construction = None 

127 self.building_age_group = [None, None] 

128 

129 self._area = None 

130 self._tilt = None 

131 self._orientation = None 

132 self._inner_convection = None 

133 self._inner_radiation = None 

134 self._outer_convection = None 

135 self._outer_radiation = None 

136 

137 self._layer = [] 

138 

139 self.r1 = 0.0 

140 self.r2 = 0.0 

141 self.r3 = 0.0 

142 self.c1 = 0.0 

143 self.c2 = 0.0 

144 self.c1_korr = 0.0 

145 self.ua_value = 0.0 

146 self.r_conduc = 0.0 

147 self.r_inner_conv = 0.0 

148 self.r_inner_rad = 0.0 

149 self.r_inner_comb = 0.0 

150 self.r_outer_conv = 0.0 

151 self.r_outer_rad = 0.0 

152 self.r_outer_comb = 0.0 

153 self.wf_out = 0.0 

154 

155 def calc_ua_value(self): 

156 """U*A value for building element. 

157 

158 Calculates the U*A value and resistances for radiative and 

159 convective heat transfer of a building element. 

160 """ 

161 

162 self.ua_value = 0.0 

163 self.r_conduc = 0.0 

164 self.r_inner_conv = 0.0 

165 self.r_inner_rad = 0.0 

166 self.r_inner_comb = 0.0 

167 self.r_outer_conv = 0.0 

168 self.r_outer_rad = 0.0 

169 self.r_outer_comb = 0.0 

170 r_conduc = 0.0 

171 for count_layer in self.layer: 

172 r_conduc += ( 

173 count_layer.thickness / count_layer.material.thermal_conduc 

174 ) 

175 

176 try: 

177 self.r_conduc = r_conduc * (1 / self.area) 

178 self.r_inner_conv = (1 / self.inner_convection) * (1 / self.area) 

179 self.r_inner_rad = (1 / self.inner_radiation) * (1 / self.area) 

180 self.r_inner_comb = 1 / (1 / self.r_inner_conv + 1 / self.r_inner_rad) 

181 

182 if self.outer_convection is not None \ 

183 and self.outer_radiation is not None: 

184 

185 self.r_outer_conv = (1 / self.outer_convection) * (1 / self.area) 

186 self.r_outer_rad = (1 / self.outer_radiation) * (1 / self.area) 

187 self.r_outer_comb = 1 / \ 

188 (1 / self.r_outer_conv + 1 / self.r_outer_rad) 

189 

190 self.ua_value = (1 / ( 

191 self.r_inner_comb + self.r_conduc + self.r_outer_comb)) 

192 self.u_value = self.ua_value / self.area 

193 except ZeroDivisionError: 

194 pass 

195 

196 

197 def gather_element_properties(self): 

198 """Helper function for matrix calculation. 

199 

200 Gathers all material properties of the building element and returns 

201 them as a np.array. Needed for the calculation of the matrix in 

202 equivalent_res(t_bt) especially for walls. 

203 

204 Returns 

205 ------- 

206 number_of_layer : int 

207 number of layer (length of layer list) 

208 density : np.array 

209 Numpy array with length of number of layer, filled with density 

210 of each layer 

211 thermal_conduc : np.array 

212 Numpy array with length of number of layer, filled with 

213 thermal_conduc of each layer 

214 heat_capac : np.array 

215 Numpy array with length of number of layer, filled with heat_capac 

216 of each layer 

217 thickness : np.array 

218 Numpy array with length of number of layer, filled with thickness 

219 of each layer 

220 """ 

221 

222 number_of_layer = len(self.layer) 

223 density = np.zeros(number_of_layer) 

224 thermal_conduc = np.zeros(number_of_layer) 

225 heat_capac = np.zeros(number_of_layer) 

226 thickness = np.zeros(number_of_layer) 

227 

228 for i in range(number_of_layer): 

229 

230 density[i] = self.layer[i].material.density 

231 thermal_conduc[i] = self.layer[i].material.thermal_conduc 

232 heat_capac[i] = self.layer[i].material.heat_capac 

233 thickness[i] = self.layer[i].thickness 

234 

235 return number_of_layer, density, thermal_conduc, heat_capac, thickness 

236 

237 def add_layer(self, layer, position=None): 

238 """Adds a layer at a certain position 

239 

240 This function adds a Layer instance to the layer list at a given 

241 position 

242 

243 Parameters 

244 ---------- 

245 layer : instance of Layer 

246 Layer instance of TEASER 

247 position : int 

248 position in the wall starting from 0 (inner side) 

249 

250 """ 

251 ass_error_1 = "Layer has to be an instance of Layer()" 

252 

253 assert isinstance(layer, Layer), ass_error_1 

254 

255 if position is None: 

256 self._layer.append(layer) 

257 else: 

258 self._layer.insert(position, layer) 

259 

260 def add_layer_list(self, layer_list): 

261 """Appends a layer set to the layer list 

262 

263 The layer list has to be in correct order 

264 

265 Parameters 

266 ---------- 

267 layer_list : list 

268 list of sorted TEASER Layer instances 

269 """ 

270 ass_error_1 = "Layer has to be an instance of Layer()" 

271 for lay_count in layer_list: 

272 

273 assert isinstance(lay_count, Layer), ass_error_1 

274 

275 self._layer.append(lay_count) 

276 

277 def load_type_element( 

278 self, 

279 year=None, 

280 construction=None, 

281 data_class=None, 

282 element_type=None, 

283 reverse_layers=False, 

284 type_element_key=None 

285 ): 

286 """Typical element loader. 

287 

288 Loads typical building elements according to their construction 

289 year and their construction type from a json. 

290 

291 This function will only work if the parents to Building are set. 

292 

293 Parameters 

294 ---------- 

295 year : int 

296 Year of construction 

297 

298 construction : str 

299 Construction type, code list ('heavy', 'light') 

300 

301 data_class : DataClass() 

302 DataClass containing the bindings for TypeBuildingElement and 

303 Material (typically this is the data class stored in prj.data, 

304 but the user can individually change that. Default is 

305 self.parent.parent.parent.data (which is data_class in current 

306 project) 

307 

308 element_type : str 

309 Element type to load - only to specify if the json entry for a 

310 different type than type(element) is to be loaded, e.g. InnerWall 

311 instead of OuterWall 

312 

313 reverse_layers : bool 

314 defines if layer list should be reversed 

315 

316 type_element_key : str 

317 Element to load - specify the full json entry 

318 

319 """ 

320 

321 if type_element_key is None and (year is None or construction is None): 

322 raise ValueError("specify either year and construction " 

323 "or type_element_key") 

324 

325 if data_class is None: 

326 data_class = self.parent.parent.parent.data 

327 else: 

328 data_class = data_class 

329 

330 self.layer = None 

331 self._inner_convection = None 

332 self._inner_radiation = None 

333 self._outer_convection = None 

334 self._outer_radiation = None 

335 

336 if type_element_key: 

337 try: 

338 buildingelement_input.load_type_element_by_key( 

339 element=self, type_element_key=type_element_key, 

340 data_class=data_class, reverse_layers=reverse_layers 

341 ) 

342 except KeyError: 

343 warnings.warn( 

344 ('Type element ' + type_element_key + ' was not found. ' 

345 + 'Going back to default element for year and construction' 

346 + '...') 

347 ) 

348 type_element_key = None 

349 

350 if not type_element_key: 

351 buildingelement_input.load_type_element( 

352 element=self, year=year, construction=construction, 

353 data_class=data_class, element_type=element_type, 

354 reverse_layers=reverse_layers 

355 ) 

356 

357 def save_type_element(self, data_class=None): 

358 """Typical element saver. 

359 

360 Saves typical building elements according to their construction 

361 year and their construction type in the the json file for type building 

362 elements. If the Project parent is set, it automatically saves it to 

363 the file given in Project.data. Alternatively you can specify a path to 

364 a file of TypeBuildingElements. If this file does not exist, 

365 a new file is created. 

366 

367 Parameters 

368 ---------- 

369 

370 data_class : DataClass() 

371 DataClass containing the bindings for TypeBuildingElement and 

372 Material (typically this is the data class stored in prj.data, 

373 but the user can individually change that. Default is 

374 self.parent.parent.parent.data (which is data_class in current 

375 project) 

376 

377 """ 

378 

379 if data_class is None: 

380 data_class = self.parent.parent.parent.data 

381 else: 

382 data_class = data_class 

383 

384 import teaser.data.output.buildingelement_output as \ 

385 buildingelement_output 

386 

387 buildingelement_output.save_type_element(element=self, 

388 data_class=data_class) 

389 

390 def delete_type_element(self, data_class=None): 

391 """Deletes typical element. 

392 

393 Deletes typical building elements according to their construction 

394 year and their construction type in the the json file for type building 

395 elements. If the Project parent is set, it automatically saves it to 

396 the file given in Project.data. Alternatively you can specify a path to 

397 a file of TypeBuildingElements. If this file does not exist, 

398 a new file is created. 

399 

400 Parameters 

401 ---------- 

402 

403 data_class : DataClass() 

404 DataClass containing the bindings for TypeBuildingElement and 

405 Material (typically this is the data class stored in prj.data, 

406 but the user can individually change that. Default is 

407 self.parent.parent.parent.data (which is data_class in current 

408 project) 

409 

410 """ 

411 

412 if data_class is None: 

413 data_class = self.parent.parent.parent.data 

414 else: 

415 data_class = data_class 

416 

417 import teaser.data.output.buildingelement_output as \ 

418 buildingelement_output 

419 

420 buildingelement_output.delete_type_element(element=self, 

421 data_class=data_class) 

422 

423 def set_calc_default(self): 

424 """Sets all calculated values of the Building Element to zero 

425 """ 

426 self.r1 = 0.0 

427 self.r2 = 0.0 

428 self.r3 = 0.0 

429 self.c1 = 0.0 

430 self.c2 = 0.0 

431 self.c1_korr = 0.0 

432 self.ua_value = 0.0 

433 self.r_conduc = 0.0 

434 self.r_inner_conv = 0.0 

435 self.r_inner_rad = 0.0 

436 self.r_inner_comb = 0.0 

437 self.r_outer_conv = 0.0 

438 self.r_outer_rad = 0.0 

439 self.r_outer_comb = 0.0 

440 

441 @property 

442 def name(self): 

443 return self._name 

444 

445 @name.setter 

446 def name(self, value): 

447 if isinstance(value, str): 

448 

449 if value: 

450 regex = re.compile('[^a-zA-z0-9]') 

451 self._name = regex.sub('', value) 

452 if self._name == "None": 

453 self._name = "BuildinElement" + str( 

454 random.randint(1, 500000)) 

455 elif value is None: 

456 self._value = "BuildinElement" + str(random.randint(1, 500000)) 

457 else: 

458 try: 

459 value = str(value) 

460 regex = re.compile('[^a-zA-z0-9]') 

461 self._name = regex.sub('', value) 

462 except ValueError: 

463 print("Can't convert name to string") 

464 

465 @property 

466 def year_of_retrofit(self): 

467 return self._year_of_retrofit 

468 

469 @year_of_retrofit.setter 

470 def year_of_retrofit(self, value): 

471 

472 if isinstance(value, int): 

473 pass 

474 elif value is None: 

475 pass 

476 else: 

477 try: 

478 value = int(value) 

479 except: 

480 raise ValueError("Can't convert year of retrofit to float") 

481 

482 if value is not None: 

483 if self.year_of_construction is not None: 

484 self._year_of_retrofit = value 

485 else: 

486 raise ValueError("Specify year of construction first") 

487 

488 @property 

489 def orientation(self): 

490 return self._orientation 

491 

492 @orientation.setter 

493 def orientation(self, value): 

494 

495 self._orientation = value 

496 if type(self).__name__ == "OuterWall": 

497 if (self.parent is not None and self.parent.parent is not None and 

498 self.area is not None): 

499 self.parent.parent.fill_outer_area_dict() 

500 elif type(self).__name__ == "Window": 

501 if (self.parent is not None and self.parent.parent is not None and 

502 self.area is not None): 

503 self.parent.parent.fill_window_area_dict() 

504 

505 @property 

506 def layer(self): 

507 return self._layer 

508 

509 @layer.setter 

510 def layer(self, value): 

511 

512 if value is None: 

513 self._layer = [] 

514 

515 if self.inner_convection is not None and\ 

516 self.inner_radiation is not None and\ 

517 self.area is not None: 

518 self.calc_ua_value() 

519 

520 @property 

521 def inner_convection(self): 

522 return self._inner_convection 

523 

524 @inner_convection.setter 

525 def inner_convection(self, value): 

526 

527 if isinstance(value, float): 

528 pass 

529 elif value is None: 

530 pass 

531 else: 

532 try: 

533 value = float(value) 

534 except: 

535 raise ValueError("Can't convert inner convection to float") 

536 

537 if value is not None: 

538 self._inner_convection = value 

539 if self.inner_convection is not None and\ 

540 self.inner_radiation is not None and\ 

541 self.area is not None: 

542 self.calc_ua_value() 

543 

544 @property 

545 def inner_radiation(self): 

546 return self._inner_radiation 

547 

548 @inner_radiation.setter 

549 def inner_radiation(self, value): 

550 

551 if isinstance(value, float): 

552 pass 

553 elif value is None: 

554 pass 

555 else: 

556 try: 

557 value = float(value) 

558 except: 

559 raise ValueError("Can't convert inner radiation to float") 

560 

561 if value is not None: 

562 self._inner_radiation = value 

563 if self.inner_convection is not None and\ 

564 self.inner_radiation is not None and\ 

565 self.area is not None: 

566 self.calc_ua_value() 

567 

568 @property 

569 def outer_convection(self): 

570 return self._outer_convection 

571 

572 @outer_convection.setter 

573 def outer_convection(self, value): 

574 

575 if isinstance(value, float): 

576 pass 

577 elif value is None: 

578 pass 

579 else: 

580 try: 

581 value = float(value) 

582 except: 

583 raise ValueError("Can't convert outer convection to float") 

584 

585 if value is not None: 

586 self._outer_convection = value 

587 if self.inner_convection is not None and\ 

588 self.inner_radiation is not None and\ 

589 self.area is not None: 

590 self.calc_ua_value() 

591 

592 @property 

593 def outer_radiation(self): 

594 return self._outer_radiation 

595 

596 @outer_radiation.setter 

597 def outer_radiation(self, value): 

598 

599 if isinstance(value, float): 

600 pass 

601 elif value is None: 

602 pass 

603 else: 

604 try: 

605 value = float(value) 

606 except: 

607 raise ValueError("Can't convert outer radiation to float") 

608 

609 if value is not None: 

610 self._outer_radiation = value 

611 if self.inner_convection is not None and\ 

612 self.inner_radiation is not None and\ 

613 self.area is not None: 

614 self.calc_ua_value() 

615 

616 @property 

617 def area(self): 

618 return self._area 

619 

620 @area.setter 

621 def area(self, value): 

622 

623 if isinstance(value, float): 

624 pass 

625 elif value is None: 

626 pass 

627 else: 

628 try: 

629 value = float(value) 

630 except: 

631 raise ValueError("Can't convert element area to float") 

632 

633 if value is not None: 

634 self._area = value 

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

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

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

638 if (self.parent is not None and self.parent.parent is not None and 

639 self.orientation is not None): 

640 self.parent.parent.fill_outer_area_dict() 

641 elif type(self).__name__ == "Window": 

642 if self.parent is not None and self.orientation is not None: 

643 self.parent.parent.fill_window_area_dict() 

644 if self.inner_convection is not None and\ 

645 self.inner_radiation is not None and\ 

646 self.area is not None: 

647 self.calc_ua_value() 

648 

649 @property 

650 def tilt(self): 

651 return self._tilt 

652 

653 @tilt.setter 

654 def tilt(self, value): 

655 

656 if isinstance(value, float): 

657 self._tilt = value 

658 elif value is None: 

659 self._tilt = value 

660 else: 

661 try: 

662 value = float(value) 

663 self._tilt = value 

664 except: 

665 raise ValueError("Can't convert tilt to float") 

666 

667 @property 

668 def year_of_construction(self): 

669 if self._year_of_construction is None: 

670 return self.parent.parent.year_of_construction 

671 return self._year_of_construction 

672 

673 @year_of_construction.setter 

674 def year_of_construction(self, value): 

675 

676 if isinstance(value, float): 

677 self._year_of_construction = value 

678 elif value is None: 

679 self._year_of_construction = value 

680 else: 

681 try: 

682 value = int(value) 

683 self._year_of_construction = value 

684 except: 

685 raise ValueError("Can't convert year to int") 

686 

687 @property 

688 def construction_data(self): 

689 return self._construction_data 

690 

691 @construction_data.setter 

692 def construction_data(self, value): 

693 

694 self._construction_data = value