Coverage for test/unit/elements/graphs/test_hvacgraph.py: 100%
143 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 17:09 +0000
1import unittest
3import networkx as nx
5from bim2sim.elements import hvac_elements as hvac
6from bim2sim.elements.hvac_elements import HVACPort
7from bim2sim.elements.graphs import hvac_graph
8from test.unit.elements.helper import SetupHelperHVAC
11class GraphHelper(SetupHelperHVAC):
13 def get_system_elements(self):
14 """ Simple generator system made of boiler, pump, expansion tank,
15 distributor and pipes.
16 """
17 flags = {}
18 with self.flag_manager(flags):
19 # generator circuit
20 boiler = self.element_generator(hvac.Boiler, rated_power=200)
21 gen_vl_a = [
22 self.element_generator(hvac.Pipe, length=100, diameter=40)
23 for i in range(3)]
24 h_pump = self.element_generator(
25 hvac.Pump, rated_power=2.2, rated_height=12,
26 rated_volume_flow=8)
27 gen_vl_b = [
28 self.element_generator(
29 hvac.Pipe, flags=['strand1'], length=100, diameter=40)
30 for i in range(5)]
31 distributor = self.element_generator(
32 hvac.Distributor, flags=['distributor']) # , volume=80
33 gen_rl_a = [
34 self.element_generator(hvac.Pipe, length=100, diameter=40)
35 for i in range(4)]
36 fitting = self.element_generator(
37 hvac.PipeFitting, n_ports=3, diameter=40, length=60)
38 gen_rl_b = [
39 self.element_generator(hvac.Pipe, length=100, diameter=40)
40 for i in range(4)]
41 gen_rl_c = [
42 self.element_generator(
43 hvac.Pipe, flags=['strand2'], length=(1 + i) * 40,
44 diameter=15)
45 for i in range(3)
46 ]
47 tank = self.element_generator(hvac.Storage, n_ports=1)
49 # connect
50 gen_vl = [boiler, *gen_vl_a, h_pump, *gen_vl_b, distributor]
51 self.connect_strait(gen_vl)
53 self.connect_strait([distributor, *gen_rl_a, fitting])
54 self.connect_strait([fitting, *gen_rl_b, boiler])
55 self.connect_strait([*gen_rl_c, tank])
56 fitting.ports[2].connect(gen_rl_c[0].ports[0])
58 # full system
59 gen_circuit = [
60 boiler, *gen_vl_a, h_pump, *gen_vl_b, distributor,
61 *gen_rl_a, fitting, *gen_rl_b, *gen_rl_c, tank
62 ]
64 return gen_circuit, flags
67def generate_element_strait(number=5, prefix=""):
68 """ Generate 2 port elements and connect them in line."""
69 elements = []
71 # create elements
72 for i in range(number):
73 ele = hvac.HVACProduct()
74 ele.name = prefix + str(i)
75 ele.ports.append(hvac.HVACPort(ele))
76 ele.ports.append(hvac.HVACPort(ele))
77 ele.inner_connections.extend(ele.get_inner_connections())
78 elements.append(ele)
79 # connect
80 if i > 0:
81 elements[i - 1].ports[1].connect(elements[i].ports[0])
82 return elements
85def attach(element1, element2, use_existing=False):
86 """ Connect elements.
88 If use_existing is True free ports (if any) are used else new ports
89 are created.
90 """
91 if use_existing:
92 free_ports1 = [port for port in element1.ports if not port.connection]
93 free_ports2 = [port for port in element2.ports if not port.connection]
94 else:
95 free_ports1 = []
96 free_ports2 = []
98 if free_ports1:
99 port_e1 = free_ports1[0]
100 else:
101 port_e1 = HVACPort(element1)
102 element1.ports.append(port_e1)
104 if free_ports2:
105 port_e2 = free_ports2[0]
106 else:
107 port_e2 = HVACPort(element2)
108 element2.ports.append(port_e2)
110 element1.inner_connections = element1.get_inner_connections()
111 element2.inner_connections = element2.get_inner_connections()
113 port_e1.connect(port_e2)
116# @patch.object(element.BaseElement, '__repr__', lambda self: self.name)
117class TestHVACGraph(unittest.TestCase):
118 helper = None
120 @classmethod
121 def setUpClass(cls):
122 cls.helper = GraphHelper()
124 def tearDown(self) -> None:
125 self.helper.reset()
127 def test_create(self):
128 """ Test instantiating and basic behaviour."""
129 strait = generate_element_strait()
130 single_loop = generate_element_strait()
131 attach(single_loop[0], single_loop[-1])
133 graph_strait = hvac_graph.HvacGraph(strait)
134 self.assertSetEqual(set(strait), set(graph_strait.elements))
135 self.assertEqual(len(graph_strait.get_connections()), len(strait) - 1)
137 graph_loop = hvac_graph.HvacGraph(single_loop)
138 self.assertSetEqual(set(single_loop), set(graph_loop.elements))
139 self.assertEqual(len(graph_loop.get_connections()), len(single_loop))
141 def test_merge(self):
142 """ Test merging elements into simpler representation."""
143 strait = generate_element_strait(10, "strait")
144 to_replace = strait[2:-3]
145 replacement = generate_element_strait(1, "replacement")[0]
147 graph = hvac_graph.HvacGraph(strait)
148 mapping = {port: None for ele in to_replace for port in ele.ports}
149 mapping[to_replace[0].ports[0]] = replacement.ports[0]
150 mapping[to_replace[-1].ports[1]] = replacement.ports[1]
152 graph.merge(mapping, [(replacement.ports[0], replacement.ports[1])])
154 for ele in graph.elements:
155 self.assertNotIn(ele, to_replace)
157 path_port = nx.shortest_path(
158 graph, strait[0].ports[0], strait[-1].ports[1])
159 self.assertIn(replacement.ports[0], path_port)
160 self.assertIn(replacement.ports[-1], path_port)
162 path_element = nx.shortest_path(
163 graph.element_graph, strait[0], strait[-1])
164 self.assertIn(replacement, path_element)
166 def test_type_chain(self):
167 """ Test chain detection."""
168 elements, flags = self.helper.get_system_elements()
169 port_graph = hvac_graph.HvacGraph(elements)
170 ele_graph = port_graph.element_graph
172 wanted = [hvac.Pipe]
173 chains = hvac_graph.HvacGraph.get_type_chains(ele_graph, wanted)
174 self.assertEqual(5, len(chains), "Unexpected number of chains found!")
176 wanted = [hvac.Pipe, hvac.Pump]
177 chains2 = hvac_graph.HvacGraph.get_type_chains(ele_graph, wanted)
178 self.assertEqual(4, len(chains2), "Unexpected number of chains found!")
180 def test_cycles(self):
181 """ Test cycle detection."""
182 # generate single cycle
183 core_cycle = generate_element_strait(10, 'core')
184 attach(core_cycle[0], core_cycle[-1], True)
186 # test single cycle
187 graph1 = hvac_graph.HvacGraph(core_cycle)
188 cyc1 = graph1.get_cycles()
189 self.assertEqual(len(cyc1), 1)
190 self.assertSetEqual(
191 set(cyc1[0]),
192 {port for ele in core_cycle for port in ele.ports})
194 # generate second cycle and attach it with loose end
195 attached1 = generate_element_strait(prefix='attached')
196 attach(core_cycle[0], attached1[0])
197 attach(core_cycle[5], attached1[-2])
199 # test double cycle
200 graph2 = hvac_graph.HvacGraph(core_cycle + attached1)
201 # graph2.plot(ports=True)
202 # graph2.plot(ports=False)
203 cyc2 = graph2.get_cycles()
204 self.assertEqual(len(cyc2), 2)
205 cyc2_elements = {port.parent for port in cyc2[0] + cyc2[1]}
206 ref_elements = set(core_cycle + attached1[:-1])
207 self.assertSetEqual(cyc2_elements, ref_elements)
209 def test_nodes(self):
210 """ Element and port graph nodes."""
211 elements, flags = self.helper.get_system_elements()
212 all_elements = set(elements)
213 all_ports = {port for ele in elements for port in ele.ports}
215 port_graph = hvac_graph.HvacGraph(elements)
216 self.assertSetEqual(set(port_graph.nodes), all_ports)
217 self.assertSetEqual(set(port_graph.elements), all_elements)
219 ele_graph = port_graph.element_graph
220 self.assertSetEqual(set(ele_graph.nodes), all_elements)
222 def test_subgraph_from_elements(self):
223 """ Test creating a subgraph of the port_graph with given elements."""
224 elements, flags = self.helper.get_system_elements()
225 port_graph = hvac_graph.HvacGraph(elements)
227 # test subgraph creation
228 sub_graph = port_graph.subgraph_from_elements(flags['strand1'])
229 self.assertEqual(set(sub_graph.elements), set(flags['strand1']))
231 # test if AssertionError is raised if elements are not in port_graph
232 classes_to_remove = {hvac.Pipe}
233 new_port_graph = hvac_graph.HvacGraph.remove_classes_from(
234 port_graph, classes_to_remove)
235 with self.assertRaises(AssertionError):
236 new_port_graph.subgraph_from_elements(flags['strand1'])
238 def test_remove_classes(self):
239 """ Test remove elements of given classes from port graph and
240 element graph."""
241 elements, flags = self.helper.get_system_elements()
242 port_graph = hvac_graph.HvacGraph(elements)
243 ele_graph = port_graph.element_graph
245 classes_to_remove = {hvac.Boiler, hvac.Distributor}
246 new_port_graph = hvac_graph.HvacGraph.remove_classes_from(
247 port_graph, classes_to_remove)
248 new_ele_graph = hvac_graph.HvacGraph.remove_classes_from(
249 ele_graph, classes_to_remove
250 )
252 self.assertFalse(hvac.Boiler and hvac.Distributor
253 in {ele.__class__ for ele in new_port_graph.elements})
254 self.assertFalse(hvac.Boiler and hvac.Distributor
255 in {node.__class__ for node in new_ele_graph.nodes})