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
« 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# ------------------------------------------------------------------------------
8from copy import copy
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
19def clamp(n, smallest, largest):
20 return max(smallest, min(n, largest))
23class Office(RCNetwork):
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
40 self.bricks = Clinker()
42 def _create_network(self):
43 nodes = []
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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.
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)
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)
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)
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)
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)
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)
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)
186 window_west = Window(wall_west, "++xy++z")
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)
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)
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)
209 pillar_west = Pillar(window_west.frame_top, "++xy++z")
211 window_middle = Window(pillar_west.outer, "++xy++z", glass_width=0.72)
212 pillar_middle = Pillar(window_middle.frame_top, "++xy++z")
214 window_east = Window(pillar_middle.outer, "++xy++z", glass_width=1.995)
215 pillar_east = Pillar(window_east.frame_top, "++xy++z")
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)
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)
234 nodes.append(self.node)
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] = []
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
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]
268 self.nodes = [*self.air_gaps, *[n.node for n in self.plates]]
269 nodes.extend(self.nodes)
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 """
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)
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")
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")
309 self.air_outer = copy(self.air_inner)
310 self.air_west.place_adjacent(self.air_outer, "--xy++z")
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")
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")
320 nodes.extend(
321 [self.air_below, self.air_west, self.air_inner, self.air_outer, self.air_top, self.air_east])
323 self.radiator = Radiator(
324 self.air_inner,
325 "x--y--z",
326 dimensions,
327 number_plates=2,
328 plate_thickness=0.016
329 )
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 )
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 )
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 )
355 # connect every node with resistors
356 resistors = connect_cells_with_resistors(nodes)
358 self.rc_objects.set_lists(capacitors=nodes, resistors=resistors)
361if __name__ == '__main__':
362 office = Office()
363 office.create_network()
365 office.solve_network((0, 8760 * 3600))
367 nodes = office.rc_objects.nodes
368 solid_nodes = [n for n in office.rc_objects.nodes if n.material.name.lower() != "air"]
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)
375 color_cells_by_attribute(solid_nodes, "material.name", opacity=0.4, cmap="managua25")
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