Coverage for pyrc \ model \ office.py: 8%

155 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-13 16:59 +0200

1# ------------------------------------------------------------------------------- 

2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan 

3# ------------------------------------------------------------------------------ 

4# License 

5# This file is part of PyRC, distributed under GPL-3.0-or-later. 

6# ------------------------------------------------------------------------------ 

7 

8from copy import copy 

9 

10from pyrc.core.connecting import connect_cells_with_resistors 

11from pyrc.core.materials import * 

12from pyrc.core.network import RCNetwork 

13from pyrc.core.nodes import Node 

14from pyrc.core.visualization.viewer import Viewer 

15from pyrc.core.visualization.vpython import color_cells_by_attribute 

16from pyrc.tools.science import celsius_to_kelvin 

17 

18 

19def clamp(n, smallest, largest): 

20 return max(smallest, min(n, largest)) 

21 

22 

23class Office(RCNetwork): 

24 

25 def __init__(self, 

26 *args, 

27 **kwargs): 

28 """ 

29 Parameters 

30 ---------- 

31 args : list 

32 The arguments for `RCNetwork` . 

33 kwargs : dict 

34 Keyword arguments for `RCNetwork` . 

35 """ 

36 super().__init__(*args, **kwargs) 

37 self.outer_alpha = 25 # ISO 10292 chapter 8 

38 self.inner_alpha = 7.3 # ISO 10292 chapter 8 

39 

40 self.bricks = Clinker() 

41 

42 def _create_network(self): 

43 nodes = [] 

44 

45 def get_node(**kwargs): 

46 my_kwargs = { 

47 "material": Air(), 

48 "temperature": celsius_to_kelvin(20), 

49 "position": (0, 0, 0), 

50 "delta": (1, 1, 1), 

51 "rc_objects": self.rc_objects, 

52 "rc_solution": self.rc_solution, 

53 } 

54 my_kwargs.update(kwargs) 

55 return Node(**my_kwargs) 

56 

57 wall_west = Node( 

58 material=WoodWall(), 

59 temperature=celsius_to_kelvin(20), 

60 position=(5.520 / 2, 0.115 / 2, 2.750 / 2), 

61 delta=(5.520, 0.115, 2.750), 

62 rc_objects=self.rc_objects, 

63 rc_solution=self.rc_solution, 

64 ) 

65 nodes.append(wall_west) 

66 

67 wall_north = Node( 

68 material=self.bricks, 

69 temperature=celsius_to_kelvin(20), 

70 position=(0, 0, 0), 

71 delta=(0.115, 2.620, 2.060), 

72 rc_objects=self.rc_objects, 

73 rc_solution=self.rc_solution, 

74 ) 

75 wall_west.place_adjacent(wall_north, "--xy--z") 

76 nodes.append(wall_north) 

77 

78 door = Node( 

79 material=Wood(), 

80 temperature=celsius_to_kelvin(20), 

81 position=(0, 0, 0), 

82 delta=(0.040, 0.910, 2.035), 

83 rc_objects=self.rc_objects, 

84 rc_solution=self.rc_solution, 

85 ) 

86 wall_north.place_adjacent(door, "++xy--z") 

87 nodes.append(door) 

88 

89 over_door = Node( 

90 material=self.bricks, 

91 temperature=celsius_to_kelvin(20), 

92 position=(0, 0, 0), 

93 delta=(0.115, 0.910, 0.025), 

94 rc_objects=self.rc_objects, 

95 rc_solution=self.rc_solution, 

96 ) 

97 door.place_adjacent(over_door, "++x--yz") 

98 nodes.append(over_door) 

99 

100 wall_north_top = Node( 

101 material=self.bricks, 

102 temperature=celsius_to_kelvin(20), 

103 position=(0, 0, 0), 

104 delta=(0.115, 6.265, 0.190), 

105 rc_objects=self.rc_objects, 

106 rc_solution=self.rc_solution, 

107 ) 

108 wall_west.place_adjacent(wall_north_top, "--xy++z") 

109 nodes.append(wall_north_top) 

110 

111 air_door = get_node(delta=(0.075, 0.91, 2.035)) 

112 door.place_adjacent(air_door, "-x--y--z") 

113 nodes.append(air_door) 

114 

115 wall_north_east = get_node(delta=(0.115, 2.735, 2.06), material=WoodWall()) 

116 air_door.place_adjacent(wall_north_east, "--xy--z") 

117 nodes.append(wall_north_east) 

118 

119 window_hallway = get_node(delta=(0.115, 6.265, 0.5), material=WindowHallway()) 

120 wall_north.place_adjacent(window_hallway, "--x--yz") 

121 nodes.append(window_hallway) 

122 

123 wall_east = get_node(material=self.bricks, delta=(5.520, 0.325, 2.750)) 

124 wall_north_east.place_adjacent(wall_east, "--xy--z") 

125 nodes.append(wall_east) 

126 

127 air_node = get_node(delta=(4.88, 6.265, 2.75)) 

128 wall_north.place_adjacent(air_node, "x--y--z") 

129 nodes.append(air_node) 

130 

131 class Window: 

132 def __init__(self, 

133 place_node, 

134 alignment_string, 

135 glass_width=1.99, 

136 glass_height=1.58, 

137 frame_width=0.16, 

138 air_thickness: float | int = 0.44, 

139 ): 

140 """ 

141 Adds a Windows to the Office including the air in front of it. 

142 

143 Parameters 

144 ---------- 

145 place_node : Node 

146 The Node on which the top of the frame is aligned to. 

147 alignment_string : str 

148 The alignment string how the top of the window frame is aligned to the place_node. 

149 glass_width : float | int, optional 

150 The glass width (without the frame!) 

151 glass_height : float | int, optional 

152 The glass height (without the frame!) 

153 frame_width : float | int, optional 

154 The frame width. 

155 air_thickness : float | int, optional 

156 Thickness of the air that should be added in front of the Window (inside the room). 

157 """ 

158 nonlocal nodes 

159 self.window_height = (glass_height + 2 * frame_width) 

160 self.window_width = (glass_width + 2 * frame_width) 

161 

162 self.frame_top = get_node(delta=(0.085, self.window_width, frame_width), material=WindowFrame()) 

163 place_node.place_adjacent(self.frame_top, alignment_string) 

164 nodes.append(self.frame_top) 

165 

166 self.frame_west = get_node(delta=(0.085, frame_width, glass_height), material=WindowFrame()) 

167 self.frame_top.place_adjacent(self.frame_west, "++x--y-z") 

168 nodes.append(self.frame_west) 

169 

170 self.frame_bottom = copy(self.frame_top) 

171 self.frame_west.place_adjacent(self.frame_bottom, "++x--y-z") 

172 nodes.append(self.frame_bottom) 

173 

174 self.frame_east = copy(self.frame_west) 

175 self.frame_top.place_adjacent(self.frame_east, "++x++y-z") 

176 nodes.append(self.frame_east) 

177 

178 self.glass = get_node(delta=(0.085, glass_width, glass_height), material=WindowGlass()) 

179 self.frame_west.place_adjacent(self.glass, "++xy++z") 

180 nodes.append(self.glass) 

181 

182 self.air = get_node(delta=(air_thickness, self.window_width, self.window_height)) 

183 self.frame_bottom.place_adjacent(self.air, "-x--y--z") 

184 nodes.append(self.air) 

185 

186 window_west = Window(wall_west, "++xy++z") 

187 

188 class Pillar: 

189 def __init__(self, 

190 place_node, 

191 alignment_string, 

192 office_height=2.75, 

193 wall_below_height=0.85, 

194 ): 

195 nonlocal nodes 

196 window_height = office_height - wall_below_height 

197 self.outer = get_node(delta=(0.085, 0.2, window_height), material=WindowInBetween()) 

198 place_node.place_adjacent(self.outer, alignment_string) 

199 nodes.append(self.outer) 

200 

201 self.middle = get_node(delta=(0.24, 0.2, window_height), material=Cladding()) 

202 self.outer.place_adjacent(self.middle, "-x--y++z") 

203 nodes.append(self.middle) 

204 

205 self.pillar = get_node(delta=(0.2, 0.2, office_height), material=Concrete()) 

206 self.middle.place_adjacent(self.pillar, "-x--y++z") 

207 nodes.append(self.pillar) 

208 

209 pillar_west = Pillar(window_west.frame_top, "++xy++z") 

210 

211 window_middle = Window(pillar_west.outer, "++xy++z", glass_width=0.72) 

212 pillar_middle = Pillar(window_middle.frame_top, "++xy++z") 

213 

214 window_east = Window(pillar_middle.outer, "++xy++z", glass_width=1.995) 

215 pillar_east = Pillar(window_east.frame_top, "++xy++z") 

216 

217 # wall below windows 

218 wall_below_windows = get_node( 

219 delta=(0.325, 6.265, 0.85), material=self.bricks 

220 ) 

221 wall_west.place_adjacent(wall_below_windows, "++xy--z") 

222 nodes.append(wall_below_windows) 

223 

224 class RadiatorPlate: 

225 def __init__(self, 

226 place_node: Node, 

227 alignment_string, 

228 dimensions, 

229 ): 

230 nonlocal nodes 

231 self.node = get_node(delta=dimensions, material=MetalWater(metal_ratio=1.8 / 16)) 

232 place_node.place_adjacent(self.node, alignment_string) 

233 

234 nodes.append(self.node) 

235 

236 class Radiator: 

237 def __init__(self, 

238 place_node, 

239 alignment_string, 

240 dimensions, 

241 number_plates=2, 

242 plate_thickness=0.016): 

243 nonlocal nodes 

244 self.plates: list[RadiatorPlate] = [] 

245 self.air_gaps: list[Node] = [] 

246 

247 if number_plates > 1: 

248 self.air_gap_thickness = (dimensions[0] - plate_thickness * number_plates) / (number_plates - 1) 

249 assert self.air_gap_thickness > 0.0 

250 else: 

251 self.air_gap_thickness = None 

252 

253 for i in range(number_plates): 

254 self.plates.append( 

255 RadiatorPlate( 

256 place_node=place_node, 

257 alignment_string=alignment_string, 

258 dimensions=(plate_thickness, *dimensions[1:]), 

259 ) 

260 ) 

261 if i != number_plates - 1: 

262 place_node: Node = self.plates[-1].node 

263 alignment_string = "x--y--z" 

264 self.air_gaps.append(get_node(delta=(self.air_gap_thickness, *dimensions[1:]))) 

265 place_node.place_adjacent(self.air_gaps[-1], alignment_string) 

266 place_node = self.air_gaps[-1] 

267 

268 self.nodes = [*self.air_gaps, *[n.node for n in self.plates]] 

269 nodes.extend(self.nodes) 

270 

271 class RadiatorBundle: 

272 def __init__(self, 

273 place_node, 

274 alignment_string, 

275 dimensions: tuple = None, 

276 vertex_offset: tuple = None, 

277 air_box: tuple = None, 

278 ): 

279 """ 

280 

281 Parameters 

282 ---------- 

283 place_node 

284 alignment_string 

285 dimensions : tuple, optional 

286 The dimensions of the radiator. 

287 vertex_offset : tuple, optional 

288 The offset of the vertex that is in the following coordinates: (-x, -y, -z). 

289 This is the lower, inner, west vertex. 

290 air_box : tuple, optional 

291 The dimensions of the whole air volume containing the radiator. 

292 """ 

293 nonlocal nodes 

294 if dimensions is None: 

295 dimensions = (0.081, 1.83, 0.42) 

296 if air_box is None: 

297 air_box = (0.2, 2.31, 0.85) 

298 if vertex_offset is None: 

299 vertex_offset = (0.65, 0.23, 0.21) 

300 self.air_below = get_node(delta=(air_box[0], air_box[1], vertex_offset[2])) 

301 place_node.place_adjacent(self.air_below, alignment_string) 

302 

303 self.air_west = get_node(delta=(air_box[0], vertex_offset[1], dimensions[2])) 

304 self.air_below.place_adjacent(self.air_west, "--x--yz") 

305 

306 self.air_inner = get_node(delta=(vertex_offset[0], vertex_offset[1], dimensions[2])) 

307 self.air_west.place_adjacent(self.air_inner, "--xy--z") 

308 

309 self.air_outer = copy(self.air_inner) 

310 self.air_west.place_adjacent(self.air_outer, "--xy++z") 

311 

312 self.air_top = get_node(delta=(air_box[0], air_box[1], air_box[2] - dimensions[2] - vertex_offset[2])) 

313 self.air_west.place_adjacent(self.air_top, "--yz") 

314 self.air_inner.place_adjacent(self.air_top, "--x") 

315 

316 self.air_east = get_node( 

317 delta=(air_box[0], air_box[1] - dimensions[1] - vertex_offset[1], dimensions[2])) 

318 self.air_below.place_adjacent(self.air_east, "--x++yz") 

319 

320 nodes.extend( 

321 [self.air_below, self.air_west, self.air_inner, self.air_outer, self.air_top, self.air_east]) 

322 

323 self.radiator = Radiator( 

324 self.air_inner, 

325 "x--y--z", 

326 dimensions, 

327 number_plates=2, 

328 plate_thickness=0.016 

329 ) 

330 

331 radiator_bundle_west = RadiatorBundle( 

332 air_node, 

333 "x--y--z", 

334 dimensions=(0.081, 1.83, 0.42), 

335 vertex_offset=(0.065, 0.23, 0.21), 

336 air_box=(0.2, 2.31, 0.85), 

337 ) 

338 

339 radiator_bundle_middle = RadiatorBundle( 

340 pillar_west.pillar, 

341 "--xy--z", 

342 dimensions=(0.081, 0.705, 0.42), 

343 vertex_offset=(0.065, 0.23, 0.21), 

344 air_box=(0.2, 1.04, 0.85), 

345 ) 

346 

347 radiator_bundle_east = RadiatorBundle( 

348 pillar_middle.pillar, 

349 "--xy--z", 

350 dimensions=(0.081, 1.83, 0.42), 

351 vertex_offset=(0.065, 0.23, 0.21), 

352 air_box=(0.2, 2.315, 0.85), 

353 ) 

354 

355 # connect every node with resistors 

356 resistors = connect_cells_with_resistors(nodes) 

357 

358 self.rc_objects.set_lists(capacitors=nodes, resistors=resistors) 

359 

360 

361if __name__ == '__main__': 

362 office = Office() 

363 office.create_network() 

364 

365 office.solve_network((0, 8760 * 3600)) 

366 

367 nodes = office.rc_objects.nodes 

368 solid_nodes = [n for n in office.rc_objects.nodes if n.material.name.lower() != "air"] 

369 

370 viewer = Viewer( 

371 wireframe=((5.520 / 2, 6.705 / 2, 2.750 / 2), (5.520, 6.705, 2.750)) 

372 ) 

373 viewer.add_new_from_list(solid_nodes) 

374 

375 color_cells_by_attribute(solid_nodes, "material.name", opacity=0.4, cmap="managua25") 

376 

377 print("DONE") 

378 # from vpython import color 

379 # air_node: Node 

380 # for node in nodes: 

381 # if node.material.name == "air": 

382 # node.vbox.opacity = 0.0 

383 # node.vbox.color = color.blue 

384 # else: 

385 # node.vbox.opacity = 0.4 

386 # 

387 # for node in nodes: 

388 # node.vbox.opacity = 0.5