Skip to content

Commit e42f136

Browse files
Merge pull request #54 from simulate-digital-rail/compare-operation
Add Compare Operation
2 parents 48ffb4f + d8b3537 commit e42f136

File tree

12 files changed

+1584
-246
lines changed

12 files changed

+1584
-246
lines changed

poetry.lock

Lines changed: 771 additions & 76 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ packages = [{include = "yaramo"}]
1010
python = "^3.11"
1111
pyproj = "^3.6.1"
1212
simplejson = "^3.19.2"
13-
13+
networkx = {extras = ["default"], version = "^3.4.2"}
14+
haversine = "^2.9.0"
1415

1516
[tool.poetry.group.dev.dependencies]
1617
black = "^22.12.0"

test/compare_test.py

Lines changed: 350 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,350 @@
1+
import pytest
2+
3+
from yaramo.model import (
4+
DbrefGeoNode,
5+
Edge,
6+
Node,
7+
Route,
8+
Signal,
9+
SignalDirection,
10+
SignalFunction,
11+
SignalKind,
12+
Topology,
13+
Wgs84GeoNode,
14+
)
15+
from yaramo.operations import Compare, CompareMode, CompareResult
16+
17+
18+
def test_identical_topologies():
19+
topology = Topology()
20+
node_1 = Node(geo_node=DbrefGeoNode(0, 0))
21+
node_2 = Node(geo_node=DbrefGeoNode(0, 10))
22+
node_3 = Node(geo_node=DbrefGeoNode(10, 0))
23+
node_4 = Node(geo_node=DbrefGeoNode(20, 0))
24+
node_5 = Node(geo_node=DbrefGeoNode(30, 0))
25+
node_6 = Node(geo_node=DbrefGeoNode(30, 10))
26+
edge_1 = Edge(node_1, node_3)
27+
edge_2 = Edge(node_2, node_3)
28+
edge_3 = Edge(node_4, node_3)
29+
edge_4 = Edge(node_4, node_5)
30+
edge_5 = Edge(node_4, node_6)
31+
signal_1 = Signal(
32+
edge_3, 5, SignalDirection.IN, SignalFunction.Block_Signal, SignalKind.Hauptsignal
33+
)
34+
edge_3.signals.append(signal_1)
35+
topology.add_nodes([node_1, node_2, node_3, node_4, node_5, node_6])
36+
topology.add_edges([edge_1, edge_2, edge_3, edge_4, edge_5])
37+
topology.add_signals([signal_1])
38+
topology.update_edge_lengths()
39+
40+
compare_modes = [CompareMode.EXACT, CompareMode.ISOMORPHIC]
41+
42+
for compare_mode in compare_modes:
43+
result = Compare.compare(
44+
topology, topology, compare_mode, given_node_matching={node_1: node_1}
45+
)
46+
assert result.node_distance == 0.0
47+
assert result.edge_length_difference == 0.0
48+
assert result.signal_distance == 0.0
49+
50+
51+
def test_identical_topologies_but_ids():
52+
topology_a = Topology()
53+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
54+
node_a2 = Node(geo_node=DbrefGeoNode(0, 10))
55+
node_a3 = Node(geo_node=DbrefGeoNode(10, 0))
56+
node_a4 = Node(geo_node=DbrefGeoNode(20, 0))
57+
node_a5 = Node(geo_node=DbrefGeoNode(30, 0))
58+
node_a6 = Node(geo_node=DbrefGeoNode(30, 10))
59+
edge_a1 = Edge(node_a1, node_a3)
60+
edge_a2 = Edge(node_a2, node_a3)
61+
edge_a3 = Edge(node_a4, node_a3)
62+
edge_a4 = Edge(node_a4, node_a5)
63+
edge_a5 = Edge(node_a4, node_a6)
64+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4, node_a5, node_a6])
65+
topology_a.add_edges([edge_a1, edge_a2, edge_a3, edge_a4, edge_a5])
66+
topology_a.update_edge_lengths()
67+
68+
topology_b = Topology()
69+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0))
70+
node_b2 = Node(geo_node=DbrefGeoNode(0, 10))
71+
node_b3 = Node(geo_node=DbrefGeoNode(12, 0)) # x differs from Node A3
72+
node_b4 = Node(geo_node=DbrefGeoNode(20, 3)) # y differs from Node A4
73+
node_b5 = Node(geo_node=DbrefGeoNode(30, 0))
74+
node_b6 = Node(geo_node=DbrefGeoNode(30, 10))
75+
edge_b1 = Edge(node_b1, node_b3)
76+
edge_b2 = Edge(node_b2, node_b3)
77+
edge_b3 = Edge(node_b4, node_b3)
78+
edge_b4 = Edge(node_b4, node_b5)
79+
edge_b5 = Edge(node_b4, node_b6)
80+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4, node_b5, node_b6])
81+
topology_b.add_edges([edge_b1, edge_b2, edge_b3, edge_b4, edge_b5])
82+
topology_b.update_edge_lengths()
83+
84+
compare_modes = [CompareMode.ISOMORPHIC]
85+
86+
for compare_mode in compare_modes:
87+
result = Compare.compare(
88+
topology_a, topology_b, compare_mode, given_node_matching={node_a1: node_b1}
89+
)
90+
assert result.node_distance == 5.0
91+
assert node_a1 in result.node_matching.element_matching
92+
assert result.node_matching.element_matching[node_a1] == node_b1
93+
assert node_a3 in result.node_matching.element_matching
94+
assert result.node_matching.element_matching[node_a3] == node_b3
95+
assert edge_a5 in result.edge_matching.element_matching
96+
assert result.edge_matching.element_matching[edge_a5] == edge_b5
97+
98+
99+
def test_edge_diff_and_signal_distance():
100+
topology_a = Topology()
101+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
102+
node_a2 = Node(geo_node=DbrefGeoNode(0, 10))
103+
node_a3 = Node(geo_node=DbrefGeoNode(10, 0))
104+
node_a4 = Node(geo_node=DbrefGeoNode(20, 0))
105+
node_a5 = Node(geo_node=DbrefGeoNode(30, 0))
106+
node_a6 = Node(geo_node=DbrefGeoNode(30, 10))
107+
edge_a1 = Edge(node_a1, node_a3)
108+
edge_a2 = Edge(node_a2, node_a3)
109+
edge_a3 = Edge(node_a3, node_a4)
110+
edge_a4 = Edge(node_a4, node_a5)
111+
edge_a5 = Edge(node_a4, node_a6)
112+
signal_a1 = Signal(
113+
edge_a3,
114+
2,
115+
SignalDirection.IN,
116+
SignalFunction.Block_Signal,
117+
SignalKind.Hauptsignal,
118+
name="a1",
119+
)
120+
edge_a3.signals.append(signal_a1)
121+
signal_a2 = Signal(
122+
edge_a3,
123+
7,
124+
SignalDirection.IN,
125+
SignalFunction.Block_Signal,
126+
SignalKind.Hauptsignal,
127+
name="a2",
128+
)
129+
edge_a3.signals.append(signal_a2)
130+
signal_a3 = Signal(
131+
edge_a3,
132+
5,
133+
SignalDirection.GEGEN,
134+
SignalFunction.Block_Signal,
135+
SignalKind.Hauptsignal,
136+
name="a3",
137+
)
138+
edge_a3.signals.append(signal_a3)
139+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4, node_a5, node_a6])
140+
topology_a.add_edges([edge_a1, edge_a2, edge_a3, edge_a4, edge_a5])
141+
topology_a.add_signals([signal_a1, signal_a2, signal_a3])
142+
topology_a.update_edge_lengths()
143+
144+
topology_b = Topology()
145+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0))
146+
node_b2 = Node(geo_node=DbrefGeoNode(0, 10))
147+
node_b3 = Node(geo_node=DbrefGeoNode(10, 0))
148+
node_b4 = Node(geo_node=DbrefGeoNode(22, 0))
149+
node_b5 = Node(geo_node=DbrefGeoNode(32, 0))
150+
node_b6 = Node(geo_node=DbrefGeoNode(32, 10))
151+
edge_b1 = Edge(node_b1, node_b3)
152+
edge_b2 = Edge(node_b2, node_b3)
153+
edge_b3 = Edge(node_b4, node_b3) # Edge is reversed compared to edge_a3
154+
edge_b4 = Edge(node_b4, node_b5)
155+
edge_b5 = Edge(node_b4, node_b6)
156+
signal_b1 = Signal(
157+
edge_b3,
158+
8,
159+
SignalDirection.GEGEN,
160+
SignalFunction.Block_Signal,
161+
SignalKind.Hauptsignal,
162+
name="b1",
163+
)
164+
edge_b3.signals.append(signal_b1)
165+
signal_b2 = Signal(
166+
edge_b3,
167+
4,
168+
SignalDirection.GEGEN,
169+
SignalFunction.Block_Signal,
170+
SignalKind.Hauptsignal,
171+
name="b2",
172+
)
173+
edge_b3.signals.append(signal_b2)
174+
signal_b3 = Signal(
175+
edge_b3,
176+
6,
177+
SignalDirection.IN,
178+
SignalFunction.Block_Signal,
179+
SignalKind.Hauptsignal,
180+
name="b3",
181+
)
182+
edge_b3.signals.append(signal_b3)
183+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4, node_b5, node_b6])
184+
topology_b.add_edges([edge_b1, edge_b2, edge_b3, edge_b4, edge_b5])
185+
topology_b.add_signals([signal_b1, signal_b2, signal_b3])
186+
topology_b.update_edge_lengths()
187+
188+
compare_modes = [CompareMode.ISOMORPHIC]
189+
190+
for compare_mode in compare_modes:
191+
result = Compare.compare(
192+
topology_a, topology_b, compare_mode, given_node_matching={node_a1: node_b1}
193+
)
194+
assert result.node_distance == 6.0
195+
assert result.edge_length_difference == 2.0
196+
assert edge_a3 in result.edge_matching.element_matching
197+
assert edge_b3 == result.edge_matching.element_matching[edge_a3]
198+
assert result.signal_distance == 4.0
199+
assert signal_a1 in result.signal_matching.element_matching
200+
assert signal_b1 == result.signal_matching.element_matching[signal_a1]
201+
assert signal_a2 in result.signal_matching.element_matching
202+
assert signal_b2 == result.signal_matching.element_matching[signal_a2]
203+
assert signal_a3 in result.signal_matching.element_matching
204+
assert signal_b3 == result.signal_matching.element_matching[signal_a3]
205+
206+
207+
def test_exact_matching_with_overlapping_graph():
208+
topology_a = Topology()
209+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
210+
node_a2 = Node(geo_node=DbrefGeoNode(0, 10))
211+
node_a3 = Node(geo_node=DbrefGeoNode(10, 0))
212+
node_a4 = Node(geo_node=DbrefGeoNode(20, 0))
213+
node_a5 = Node(geo_node=DbrefGeoNode(30, 0))
214+
node_a6 = Node(geo_node=DbrefGeoNode(30, 10))
215+
edge_a1 = Edge(node_a1, node_a3)
216+
edge_a2 = Edge(node_a2, node_a3)
217+
edge_a3 = Edge(node_a3, node_a4)
218+
edge_a4 = Edge(node_a4, node_a5)
219+
edge_a5 = Edge(node_a4, node_a6)
220+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4, node_a5, node_a6])
221+
topology_a.add_edges([edge_a1, edge_a2, edge_a3, edge_a4, edge_a5])
222+
topology_a.update_edge_lengths()
223+
224+
topology_b = Topology()
225+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0), uuid=node_a1.uuid)
226+
node_b2 = Node(geo_node=DbrefGeoNode(0, 10), uuid=node_a2.uuid)
227+
node_b3 = Node(geo_node=DbrefGeoNode(12, 0), uuid=node_a3.uuid) # x differs to Node A3
228+
node_b4 = Node(geo_node=DbrefGeoNode(20, 1), uuid=node_a4.uuid) # y differs to Node A3
229+
node_b5 = Node(geo_node=DbrefGeoNode(-10, 0))
230+
node_b6 = Node(geo_node=DbrefGeoNode(-10, 10))
231+
edge_b1 = Edge(node_b1, node_b3, uuid=edge_a1.uuid)
232+
edge_b2 = Edge(node_b2, node_b3, uuid=edge_a2.uuid)
233+
edge_b3 = Edge(node_b3, node_b4, uuid=edge_a3.uuid)
234+
edge_b4 = Edge(node_b5, node_b1)
235+
edge_b5 = Edge(node_b6, node_b1)
236+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4, node_b5, node_b6])
237+
topology_b.add_edges([edge_b1, edge_b2, edge_b3, edge_b4, edge_b5])
238+
topology_b.update_edge_lengths()
239+
240+
result = Compare.compare(topology_a, topology_b, CompareMode.EXACT)
241+
242+
assert result.node_distance == 3.0
243+
assert node_a1 in result.node_matching.element_matching
244+
assert node_b1 == result.node_matching.element_matching[node_a1]
245+
assert node_a2 in result.node_matching.element_matching
246+
assert node_b2 == result.node_matching.element_matching[node_a2]
247+
assert node_a5 in result.node_matching.not_found_in_b
248+
assert node_a6 in result.node_matching.not_found_in_b
249+
assert node_b5 in result.node_matching.not_found_in_a
250+
assert node_b6 in result.node_matching.not_found_in_a
251+
252+
253+
def test_non_isomorphic_topologies_different_node_and_edge_count():
254+
topology_a = Topology()
255+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
256+
node_a2 = Node(geo_node=DbrefGeoNode(0, 10))
257+
node_a3 = Node(geo_node=DbrefGeoNode(10, 0))
258+
node_a4 = Node(geo_node=DbrefGeoNode(20, 0))
259+
node_a5 = Node(geo_node=DbrefGeoNode(30, 0))
260+
node_a6 = Node(geo_node=DbrefGeoNode(30, 10))
261+
edge_a1 = Edge(node_a1, node_a3)
262+
edge_a2 = Edge(node_a2, node_a3)
263+
edge_a3 = Edge(node_a4, node_a3)
264+
edge_a4 = Edge(node_a4, node_a5)
265+
edge_a5 = Edge(node_a4, node_a6)
266+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4, node_a5, node_a6])
267+
topology_a.add_edges([edge_a1, edge_a2, edge_a3, edge_a4, edge_a5])
268+
269+
topology_b = Topology()
270+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0))
271+
node_b2 = Node(geo_node=DbrefGeoNode(0, 10))
272+
node_b3 = Node(geo_node=DbrefGeoNode(10, 0))
273+
node_b4 = Node(geo_node=DbrefGeoNode(20, 0))
274+
edge_b1 = Edge(node_b1, node_b3)
275+
edge_b2 = Edge(node_b2, node_b3)
276+
edge_b3 = Edge(node_b4, node_b3)
277+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4])
278+
topology_b.add_edges([edge_b1, edge_b2, edge_b3])
279+
280+
with pytest.raises(ValueError):
281+
result = Compare.compare(
282+
topology_a, topology_b, CompareMode.ISOMORPHIC, given_node_matching={node_a1: node_b1}
283+
)
284+
285+
286+
def test_non_isomorphic_topologies_same_node_and_edge_count():
287+
topology_a = Topology()
288+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
289+
node_a2 = Node(geo_node=DbrefGeoNode(10, 0))
290+
node_a3 = Node(geo_node=DbrefGeoNode(20, 0))
291+
node_a4 = Node(geo_node=DbrefGeoNode(30, 0))
292+
node_a5 = Node(geo_node=DbrefGeoNode(40, 10))
293+
node_a6 = Node(geo_node=DbrefGeoNode(40, 0))
294+
edge_a1 = Edge(node_a1, node_a2)
295+
edge_a2 = Edge(node_a2, node_a3)
296+
edge_a2b = Edge(node_a2, node_a3)
297+
edge_a2b.intermediate_geo_nodes.extend([DbrefGeoNode(12, 5), DbrefGeoNode(18, 5)])
298+
edge_a3 = Edge(node_a3, node_a4)
299+
edge_a4 = Edge(node_a4, node_a5)
300+
edge_a5 = Edge(node_a4, node_a6)
301+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4, node_a5, node_a6])
302+
topology_a.add_edges([edge_a1, edge_a2, edge_a2b, edge_a3, edge_a4, edge_a5])
303+
304+
topology_b = Topology()
305+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0))
306+
node_b2 = Node(geo_node=DbrefGeoNode(10, 0))
307+
node_b3 = Node(geo_node=DbrefGeoNode(20, 0))
308+
node_b4 = Node(geo_node=DbrefGeoNode(30, 0))
309+
node_b5 = Node(geo_node=DbrefGeoNode(13, 5))
310+
node_b6 = Node(geo_node=DbrefGeoNode(23, 5))
311+
edge_b1 = Edge(node_b1, node_b2)
312+
edge_b2 = Edge(node_b2, node_b3)
313+
edge_b3 = Edge(node_b3, node_b4)
314+
edge_b4 = Edge(node_b2, node_b5)
315+
edge_b5 = Edge(node_b3, node_b5)
316+
edge_b6 = Edge(node_b5, node_b6)
317+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4, node_b5, node_b6])
318+
topology_b.add_edges([edge_b1, edge_b2, edge_b3, edge_b4, edge_b5, edge_b6])
319+
320+
with pytest.raises(ValueError):
321+
result = Compare.compare(
322+
topology_a, topology_b, CompareMode.ISOMORPHIC, given_node_matching={node_a1: node_b1}
323+
)
324+
325+
326+
def test_isomorphic_topologies_without_given_node_matching():
327+
topology_a = Topology()
328+
node_a1 = Node(geo_node=DbrefGeoNode(0, 0))
329+
node_a2 = Node(geo_node=DbrefGeoNode(0, 10))
330+
node_a3 = Node(geo_node=DbrefGeoNode(10, 0))
331+
node_a4 = Node(geo_node=DbrefGeoNode(20, 0))
332+
edge_a1 = Edge(node_a1, node_a3)
333+
edge_a2 = Edge(node_a2, node_a3)
334+
edge_a3 = Edge(node_a4, node_a3)
335+
topology_a.add_nodes([node_a1, node_a2, node_a3, node_a4])
336+
topology_a.add_edges([edge_a1, edge_a2, edge_a3])
337+
338+
topology_b = Topology()
339+
node_b1 = Node(geo_node=DbrefGeoNode(0, 0))
340+
node_b2 = Node(geo_node=DbrefGeoNode(0, 10))
341+
node_b3 = Node(geo_node=DbrefGeoNode(10, 0))
342+
node_b4 = Node(geo_node=DbrefGeoNode(20, 0))
343+
edge_b1 = Edge(node_b1, node_b3)
344+
edge_b2 = Edge(node_b2, node_b3)
345+
edge_b3 = Edge(node_b3, node_b4)
346+
topology_b.add_nodes([node_b1, node_b2, node_b3, node_b4])
347+
topology_b.add_edges([edge_b1, edge_b2, edge_b3])
348+
349+
with pytest.raises(ValueError):
350+
result = Compare.compare(topology_a, topology_b, CompareMode.ISOMORPHIC)

0 commit comments

Comments
 (0)