Skip to content

Commit b97629c

Browse files
Merge branch 'operations' into compare-operation
# Conflicts: # yaramo/operations/__init__.py
2 parents a325919 + dc0849e commit b97629c

File tree

4 files changed

+154
-16
lines changed

4 files changed

+154
-16
lines changed

test/split_test.py

Lines changed: 82 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
Topology,
1212
Wgs84GeoNode,
1313
)
14-
from yaramo.operations import Split
14+
from yaramo.operations import Label, Split
1515

1616

1717
def test_simple_split():
@@ -179,7 +179,7 @@ def test_invalid_split_not_enough_edges():
179179
topology_a, topology_b = Split.split(topology, split_edges={edge_4: 5.0})
180180

181181

182-
def test_invalid_split_more_than_two_partitions():
182+
def test_split_in_three_partitions():
183183
topology = Topology()
184184
node_1 = Node(geo_node=Wgs84GeoNode(0, 10))
185185
node_2 = Node(geo_node=Wgs84GeoNode(0, 0))
@@ -193,14 +193,91 @@ def test_invalid_split_more_than_two_partitions():
193193
edge_2 = Edge(node_2, node_3)
194194
edge_3 = Edge(node_3, node_4)
195195
edge_4 = Edge(node_4, node_5)
196-
edge_5 = Edge(node_4, node_6)
196+
edge_5 = Edge(node_6, node_4)
197197
edge_6 = Edge(node_6, node_7)
198198
edge_7 = Edge(node_6, node_8)
199199
topology.add_nodes([node_1, node_2, node_3, node_4, node_5, node_6, node_7, node_8])
200200
topology.add_edges([edge_1, edge_2, edge_3, edge_4, edge_5, edge_6, edge_7])
201201

202-
with pytest.raises(ValueError):
203-
topology_a, topology_b = Split.split(topology, split_edges={edge_3: 5.0, edge_5: 5.0})
202+
topology_a, topology_b = Split.split(topology, split_edges={edge_3: 5.0, edge_5: 5.0})
203+
assert set([node_1, node_2, node_3, node_6, node_7, node_8]).issubset(topology_a.nodes.values())
204+
assert set([node_4, node_5]).issubset(topology_b.nodes.values())
205+
assert set([edge_1, edge_2, edge_6, edge_7]).issubset(topology_a.edges.values())
206+
assert set([edge_4]).issubset(topology_b.edges.values())
207+
208+
209+
def test_split_in_more_than_three_partitions():
210+
topology = Topology()
211+
node_1 = Node(geo_node=Wgs84GeoNode(0, 10))
212+
node_2 = Node(geo_node=Wgs84GeoNode(0, 0))
213+
node_3 = Node(geo_node=Wgs84GeoNode(10, 0)) # Point
214+
node_4 = Node(geo_node=Wgs84GeoNode(20, 0)) # Point
215+
node_5 = Node(geo_node=Wgs84GeoNode(30, 0)) # Point
216+
node_6 = Node(geo_node=Wgs84GeoNode(40, 10)) # Point
217+
node_7 = Node(geo_node=Wgs84GeoNode(50, 10))
218+
node_8 = Node(geo_node=Wgs84GeoNode(50, 20))
219+
node_9 = Node(geo_node=Wgs84GeoNode(50, 5))
220+
node_A = Node(geo_node=Wgs84GeoNode(50, 0))
221+
edge_1 = Edge(node_1, node_3)
222+
edge_2 = Edge(node_2, node_3)
223+
edge_3 = Edge(node_3, node_4)
224+
edge_4 = Edge(node_4, node_5)
225+
edge_5 = Edge(node_6, node_4)
226+
edge_6 = Edge(node_6, node_7)
227+
edge_7 = Edge(node_6, node_8)
228+
edge_8 = Edge(node_5, node_9)
229+
edge_9 = Edge(node_5, node_A)
230+
topology.add_nodes(
231+
[node_1, node_2, node_3, node_4, node_5, node_6, node_7, node_8, node_9, node_A]
232+
)
233+
topology.add_edges([edge_1, edge_2, edge_3, edge_4, edge_5, edge_6, edge_7, edge_8, edge_9])
234+
235+
topology_a, topology_b = Split.split(
236+
topology, split_edges={edge_3: 5.0, edge_5: 5.0, edge_4: 5.0}
237+
)
238+
assert set([node_1, node_2, node_3, node_5, node_6, node_7, node_8, node_9, node_A]).issubset(
239+
topology_a.nodes.values()
240+
)
241+
assert set([node_4]).issubset(topology_b.nodes.values())
242+
assert set([edge_1, edge_2, edge_6, edge_7, edge_8, edge_9]).issubset(topology_a.edges.values())
243+
# No predefined edges are left in topology B due to split edges
244+
245+
246+
def test_add_non_connected_elements_to_a_partition():
247+
topology = Topology()
248+
node_1 = Node(geo_node=Wgs84GeoNode(0, 0))
249+
node_2 = Node(geo_node=Wgs84GeoNode(0, 10))
250+
node_3 = Node(geo_node=Wgs84GeoNode(10, 0)) # Point
251+
node_4 = Node(geo_node=Wgs84GeoNode(20, 0)) # Point
252+
node_5 = Node(geo_node=Wgs84GeoNode(30, 0))
253+
node_6 = Node(geo_node=Wgs84GeoNode(30, 10))
254+
node_b1 = Node(geo_node=Wgs84GeoNode(100, 0))
255+
node_b2 = Node(geo_node=Wgs84GeoNode(110, 0)) # Point
256+
node_b3 = Node(geo_node=Wgs84GeoNode(120, 0))
257+
node_b4 = Node(geo_node=Wgs84GeoNode(120, 10))
258+
edge_1 = Edge(node_1, node_3)
259+
edge_2 = Edge(node_2, node_3)
260+
edge_3 = Edge(node_3, node_4)
261+
edge_4 = Edge(node_4, node_5)
262+
edge_5 = Edge(node_4, node_6)
263+
edge_b1 = Edge(node_b1, node_b2)
264+
edge_b2 = Edge(node_b2, node_b3)
265+
edge_b3 = Edge(node_b2, node_b4)
266+
topology.add_nodes(
267+
[node_1, node_2, node_3, node_4, node_5, node_6, node_b1, node_b2, node_b3, node_b4]
268+
)
269+
topology.add_edges([edge_1, edge_2, edge_3, edge_4, edge_5, edge_b1, edge_b2, edge_b3])
270+
271+
topology_a, topology_b = Split.split(
272+
topology, split_edges={edge_3: 5.0}, add_missing_elements_to_topology=Label.A_Topology
273+
)
274+
275+
assert set([node_1, node_2, node_3, node_b1, node_b2, node_b3, node_b4]).issubset(
276+
topology_a.nodes.values()
277+
)
278+
assert set([node_4, node_5, node_6]).issubset(topology_b.nodes.values())
279+
assert set([edge_1, edge_2, edge_b1, edge_b2, edge_b3]).issubset(topology_a.edges.values())
280+
assert set([edge_4, edge_5]).issubset(topology_b.edges.values())
204281

205282

206283
def test_elements_on_split_edges():

yaramo/operations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
from .compare import Compare, CompareMode, CompareResult
2-
from .split import Split
2+
from .split import Label, Split
33
from .union import Union

yaramo/operations/compare.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,15 @@ def compare(
6161
raise ValueError(
6262
"For isomorphic topologies, at least one mathing node needs to be given."
6363
)
64-
Compare._calc_isomorphic_matching(result, topology_a, topology_b, given_node_matching, skip_signals)
64+
Compare._calc_isomorphic_matching(
65+
result, topology_a, topology_b, given_node_matching, skip_signals
66+
)
6567

6668
result.node_distance = Compare._calc_distance_for_matching(
67-
result.node_matching, exclude_ends_in_calculation, start_node_a=list(given_node_matching.keys())[0], start_node_b=list(given_node_matching.values())[0]
69+
result.node_matching,
70+
exclude_ends_in_calculation,
71+
start_node_a=list(given_node_matching.keys())[0],
72+
start_node_b=list(given_node_matching.values())[0],
6873
)
6974
result.edge_length_difference = Compare._calc_distance_for_matching(
7075
result.edge_matching, exclude_ends_in_calculation, element_type="edge"
@@ -159,6 +164,7 @@ def __add_edges_to_matching(__edge_a: Edge, __edge_b: Edge):
159164
)
160165

161166
if not skip_signals:
167+
162168
def __add_signal_lists_to_matching(
163169
signal_list_a: List[Signal], signal_list_b: List[Signal]
164170
):
@@ -173,7 +179,9 @@ def __add_signal_lists_to_matching(
173179
if not edge_a.signals and not edge_b.signals:
174180
continue
175181
if not edge_a.signals or not edge_b.signals:
176-
raise ValueError(f"Signals on edges {edge_a.uuid} and {edge_b.uuid} does not match")
182+
raise ValueError(
183+
f"Signals on edges {edge_a.uuid} and {edge_b.uuid} does not match"
184+
)
177185
in_direction_a = [
178186
signal for signal in edge_a.signals if signal.direction == SignalDirection.IN
179187
]
@@ -219,7 +227,11 @@ def _are_topologies_isomorphic(topology_a: Topology, topology_b: Topology):
219227

220228
@staticmethod
221229
def _calc_distance_for_matching(
222-
matching: CompareMatching, exclude_ends_in_calculation, element_type: str = "node", start_node_a: Node = None, start_node_b: Node = None,
230+
matching: CompareMatching,
231+
exclude_ends_in_calculation,
232+
element_type: str = "node",
233+
start_node_a: Node = None,
234+
start_node_b: Node = None,
223235
):
224236
if not matching.element_matching:
225237
return -1.0
@@ -235,15 +247,20 @@ def _calc_distance_for_matching(
235247
start_geo_node_b = start_node_b.geo_node
236248
geo_node_a: GeoNode = element_a.geo_node
237249
geo_node_b: GeoNode = element_b.geo_node
238-
distance = abs(start_geo_node_a.get_distance_to_other_geo_node(geo_node_a) - start_geo_node_b.get_distance_to_other_geo_node(geo_node_b))
250+
distance = abs(
251+
start_geo_node_a.get_distance_to_other_geo_node(geo_node_a)
252+
- start_geo_node_b.get_distance_to_other_geo_node(geo_node_b)
253+
)
239254
print(f"From {element_a.uuid} to {element_b.uuid}: {distance}")
240255
distance_sum += distance
241256
elif element_type == "edge":
242257
if exclude_ends_in_calculation and (
243258
not element_a.node_a.is_point() or not element_a.node_b.is_point()
244259
):
245260
continue
246-
print(f"Edge {element_a.uuid} compared to {element_b.uuid}: {abs(element_a.length - element_b.length)}")
261+
print(
262+
f"Edge {element_a.uuid} compared to {element_b.uuid}: {abs(element_a.length - element_b.length)}"
263+
)
247264
distance_sum += abs(element_a.length - element_b.length)
248265
elif element_type == "signal":
249266
x_a, y_a = element_a.get_calculated_coordinates()

yaramo/operations/split.py

Lines changed: 48 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,20 @@ class Label(Enum):
88
A_Topology = auto()
99
B_Topology = auto()
1010

11+
@staticmethod
12+
def get_opposite_label(label):
13+
if label == Label.A_Topology:
14+
return Label.B_Topology
15+
return Label.A_Topology
16+
1117

1218
class Split:
1319
@staticmethod
14-
def split(topology: Topology, split_edges: Dict[Edge, float]) -> Tuple[Topology, Topology]:
20+
def split(
21+
topology: Topology,
22+
split_edges: Dict[Edge, float],
23+
add_missing_elements_to_topology: None | Label = None,
24+
) -> Tuple[Topology, Topology]:
1525
Split._validate_split_edges(split_edges)
1626

1727
topology_a = Topology()
@@ -20,6 +30,11 @@ def split(topology: Topology, split_edges: Dict[Edge, float]) -> Tuple[Topology,
2030
new_end_nodes = Split._split_edges(topology, split_edges)
2131
node_labels, edge_labels, signal_labels = Split._label_elements(topology, new_end_nodes)
2232

33+
if add_missing_elements_to_topology is not None:
34+
Split._assign_missing_elements_to_label(
35+
topology, node_labels, edge_labels, signal_labels, add_missing_elements_to_topology
36+
)
37+
2338
# Add nodes, edges and signals to destination topologies.
2439
def _add_elements_to_topology(
2540
_element_label_matching, _topology_a_method, _topology_b_method
@@ -165,12 +180,41 @@ def _dfs(_start_node: Node, _label):
165180

166181
if not new_end_nodes:
167182
raise ValueError("No new end nodes found. Split not possible")
168-
any_pair = list(new_end_nodes.values())[0]
169-
_dfs(any_pair[0], Label.A_Topology)
170-
_dfs(any_pair[1], Label.B_Topology)
183+
184+
for end_node_pair in new_end_nodes.values():
185+
node_a = end_node_pair[0]
186+
node_b = end_node_pair[1]
187+
if node_a not in node_labels and node_b not in node_labels:
188+
_dfs(node_a, Label.A_Topology)
189+
_dfs(node_b, Label.B_Topology)
190+
elif node_a in node_labels and node_b in node_labels:
191+
if node_labels[node_a] == node_labels[node_b]:
192+
raise ValueError("Split edges do not fully split. Split not possible.")
193+
elif node_a in node_labels:
194+
_dfs(node_b, Label.get_opposite_label(node_labels[node_a]))
195+
elif node_b in node_labels:
196+
_dfs(node_a, Label.get_opposite_label(node_labels[node_b]))
171197

172198
return node_labels, edge_labels, signal_labels
173199

200+
@staticmethod
201+
def _assign_missing_elements_to_label(
202+
topology: Topology,
203+
node_labels: Dict[Node, Label],
204+
edge_labels: Dict[Edge, Label],
205+
signal_labels: Dict[Signal, Label],
206+
label: Label,
207+
):
208+
for node in topology.nodes.values():
209+
if node not in node_labels:
210+
node_labels[node] = label
211+
for edge in topology.edges.values():
212+
if edge not in edge_labels:
213+
edge_labels[edge] = label
214+
for signal in topology.signals.values():
215+
if signal not in signal_labels:
216+
signal_labels[signal] = label
217+
174218
@staticmethod
175219
def _validate_for_data_loss(
176220
topology: Topology,

0 commit comments

Comments
 (0)