Repository: networkx/networkx

Issue:
ISMAGS trips AssertionError for specific symmetry patterns
There is an error in the ISMAGS implementation. For some specific cases it trips an AssertionError.
This happens for graphs of this shape, where all nodes and edges are equivalent.
```
5 - 4 \     / 12 - 13
       0 - 3
9 - 8 /     \ 16 - 17
```

During symmetry detection/analysis, nodes 0 and 3 are coupled and no longer equivalent. At that point {4, 8} and {12, 16} are no longer equivalent, and neither are {5, 9} and {13, 17}. Coupling 4 and refinement results in 5 and 9 getting their own partitions, *but not 13 and 17*. This results in tripping the `assert` on line https://github.com/networkx/networkx/blob/main/networkx/algorithms/isomorphism/ismags.py#L1080. 
The good news is that this case cannot result in new symmetries, so it can be safely changed to an `if: return [], cosets`.

This fix is in https://github.com/marrink-lab/vermouth-martinize/commit/d5be20bb17992eaef3b34484d2b3dd7097e1c51c (with testcase), but I don't have the time to turn that into a PR at the moment...



Discussion:
@pckroon Sorry for reviving such an old thread... Any updates on this issue? I can (with your permission) port the fix you linked to if you don't have the time to work on this!
No worries.
No updates from my side, and feel free to try to get my fix into nx. I really don't have the time



Patch:
diff --git a/networkx/algorithms/isomorphism/ismags.py b/networkx/algorithms/isomorphism/ismags.py
index 24819faf95c..0387dbee44f 100644
--- a/networkx/algorithms/isomorphism/ismags.py
+++ b/networkx/algorithms/isomorphism/ismags.py
@@ -1087,9 +1087,20 @@ def _process_ordered_pair_partitions(
         else:
             cosets = cosets.copy()
 
-        assert all(
+        if not all(
             len(t_p) == len(b_p) for t_p, b_p in zip(top_partitions, bottom_partitions)
-        )
+        ):
+            # This used to be an assertion, but it gets tripped in rare cases:
+            # 5 - 4 \     / 12 - 13
+            #        0 - 3
+            # 9 - 8 /     \ 16 - 17
+            # Assume 0 and 3 are coupled and no longer equivalent. At that point
+            # {4, 8} and {12, 16} are no longer equivalent, and neither are
+            # {5, 9} and {13, 17}. Coupling 4 and refinement results in 5 and 9
+            # getting their own partitions, *but not 13 and 17*. Further
+            # iterations will attempt to couple 5 to {13, 17}, which cannot
+            # result in more symmetries?
+            return [], cosets
 
         # BASECASE
         if all(len(top) == 1 for top in top_partitions):


Test patch:
diff --git a/networkx/algorithms/isomorphism/tests/test_ismags.py b/networkx/algorithms/isomorphism/tests/test_ismags.py
index bc4070ac5bd..46e5cea586e 100644
--- a/networkx/algorithms/isomorphism/tests/test_ismags.py
+++ b/networkx/algorithms/isomorphism/tests/test_ismags.py
@@ -48,6 +48,30 @@ class TestSelfIsomorphism:
             ],
         ),
         ([], [(0, 1), (1, 2), (1, 4), (2, 3), (3, 5), (3, 6)]),
+        (
+            # 5 - 4 \     / 12 - 13
+            #        0 - 3
+            # 9 - 8 /     \ 16 - 17
+            # Assume 0 and 3 are coupled and no longer equivalent.
+            # Coupling node 4 to 8 means that 5 and 9
+            # are no longer equivalent, pushing them in their own partitions.
+            # However, {5, 9} was considered equivalent to {13, 17}, which is *not*
+            # taken into account in the second refinement, tripping a (former)
+            # assertion failure. Note that this is actually the minimal failing
+            # example.
+            [],
+            [
+                (0, 3),
+                (0, 4),
+                (4, 5),
+                (0, 8),
+                (8, 9),
+                (3, 12),
+                (12, 13),
+                (3, 16),
+                (16, 17),
+            ],
+        ),
     ]
 
     def test_self_isomorphism(self):