From b97bc143254dccfd076a6e83840aae14930da892 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 25 Aug 2021 18:08:00 -0400 Subject: [PATCH] Workaround is_isormorphic returning false for 2 empty graphs (#422) * Workaround is_isormorphic returning false for 2 empty graphs This commit works around the bug in the vf2 implementation for empty graphs where no mapping is found for is_isomorphic. It's just a workaround for the issue reported in #421 and I'm sure there is a better real fix, but in the interest of having a solution proposed in the short term this was fast. Fixes #421 * Bump versions and prepare for release * Update src/isomorphism/vf2.rs Co-authored-by: Ivan Carvalho * Add subgraph isomorphic tests too --- Cargo.lock | 4 ++- Cargo.toml | 2 +- ...ix-empty-isomorphism-ccea235b81119b20.yaml | 5 +++ setup.py | 2 +- src/isomorphism/vf2.rs | 6 ++++ tests/digraph/test_isomorphic.py | 32 +++++++++++++++++++ tests/digraph/test_subgraph_isomorphic.py | 28 ++++++++++++++++ tests/graph/test_isomorphic.py | 29 +++++++++++++++++ tests/graph/test_subgraph_isomorphic.py | 28 ++++++++++++++++ 9 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/0.10.0/fix-empty-isomorphism-ccea235b81119b20.yaml diff --git a/Cargo.lock b/Cargo.lock index a83f15f50..bcff748e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ahash" version = "0.7.4" @@ -511,7 +513,7 @@ dependencies = [ [[package]] name = "retworkx" -version = "0.10.0" +version = "0.10.1" dependencies = [ "ahash", "fixedbitset", diff --git a/Cargo.toml b/Cargo.toml index e4ca22864..6e13d0fbc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "retworkx" description = "A python graph library implemented in Rust" -version = "0.10.0" +version = "0.10.1" authors = ["Matthew Treinish "] license = "Apache-2.0" readme = "README.md" diff --git a/releasenotes/notes/0.10.0/fix-empty-isomorphism-ccea235b81119b20.yaml b/releasenotes/notes/0.10.0/fix-empty-isomorphism-ccea235b81119b20.yaml new file mode 100644 index 000000000..eefed6220 --- /dev/null +++ b/releasenotes/notes/0.10.0/fix-empty-isomorphism-ccea235b81119b20.yaml @@ -0,0 +1,5 @@ +--- +prelude: > + This is a bugfix release that fixes a regression introduced in the previous + 0.10.0 release. In 0.10.0 the :func:`~retworkx.is_isomorphic` function when + comparing 2 empty graph objects would incorrectly return ``False``. diff --git a/setup.py b/setup.py index b26ecc000..86f7613cb 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ def readme(): setup( name="retworkx", - version="0.10.0", + version="0.10.1", description="A python graph library implemented in Rust", long_description=readme(), long_description_content_type='text/markdown', diff --git a/src/isomorphism/vf2.rs b/src/isomorphism/vf2.rs index 9e8bfab1e..6f836c523 100644 --- a/src/isomorphism/vf2.rs +++ b/src/isomorphism/vf2.rs @@ -375,6 +375,12 @@ pub fn is_isomorphic( { return Ok(false); } + // TODO: Remove this. This is just a hacky workaround to fix #421 fast, + // we should fix VF2Algorithm.next() to return an empty hashmap for + // 2 empty graphs + if g1.node_count() == 0 && g1.edge_count() == 0 { + return Ok(true); + } let mut vf2 = Vf2Algorithm::new( py, g0, g1, node_match, edge_match, id_order, ordering, induced, diff --git a/tests/digraph/test_isomorphic.py b/tests/digraph/test_isomorphic.py index 171f2c9b3..49ed31161 100644 --- a/tests/digraph/test_isomorphic.py +++ b/tests/digraph/test_isomorphic.py @@ -17,6 +17,38 @@ class TestIsomorphic(unittest.TestCase): + def test_empty_isomorphic_identical(self): + dag_a = retworkx.PyDAG() + dag_b = retworkx.PyDAG() + + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic(dag_a, dag_b, id_order=id_order) + ) + + def test_empty_isomorphic_mismatch_node_data(self): + dag_a = retworkx.PyDAG() + dag_b = retworkx.PyDAG() + + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic(dag_a, dag_b, id_order=id_order) + ) + + def test_empty_isomorphic_compare_nodes_mismatch_node_data(self): + dag_a = retworkx.PyDAG() + dag_b = retworkx.PyDAG() + + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic( + dag_a, dag_b, lambda x, y: x == y, id_order=id_order + ) + ) + def test_isomorphic_identical(self): dag_a = retworkx.PyDAG() dag_b = retworkx.PyDAG() diff --git a/tests/digraph/test_subgraph_isomorphic.py b/tests/digraph/test_subgraph_isomorphic.py index c6df3c88f..31494bbeb 100644 --- a/tests/digraph/test_subgraph_isomorphic.py +++ b/tests/digraph/test_subgraph_isomorphic.py @@ -16,6 +16,34 @@ class TestSubgraphIsomorphic(unittest.TestCase): + def test_empty_subgraph_isomorphic_identical(self): + g_a = retworkx.PyDiGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic(g_a, g_a, id_order=id_order) + ) + + def test_empty_subgraph_isomorphic_mismatch_node_data(self): + g_a = retworkx.PyDiGraph() + g_b = retworkx.PyDiGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic(g_a, g_b, id_order=id_order) + ) + + def test_empty_subgraph_isomorphic_compare_nodes_mismatch_node_data(self): + g_a = retworkx.PyDiGraph() + g_b = retworkx.PyDiGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic( + g_a, g_b, lambda x, y: x == y, id_order=id_order + ) + ) + def test_subgraph_isomorphic_identical(self): g_a = retworkx.PyDiGraph() nodes = g_a.add_nodes_from(["a_1", "a_2", "a_3"]) diff --git a/tests/graph/test_isomorphic.py b/tests/graph/test_isomorphic.py index 425bdac00..5deb72ee5 100644 --- a/tests/graph/test_isomorphic.py +++ b/tests/graph/test_isomorphic.py @@ -16,6 +16,35 @@ class TestIsomorphic(unittest.TestCase): + def test_empty_isomorphic_identical(self): + g_a = retworkx.PyGraph() + g_b = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic(g_a, g_b, id_order=id_order) + ) + + def test_empty_isomorphic_mismatch_node_data(self): + g_a = retworkx.PyGraph() + g_b = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic(g_a, g_b, id_order=id_order) + ) + + def test_empty_isomorphic_compare_nodes_mismatch_node_data(self): + g_a = retworkx.PyGraph() + g_b = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_isomorphic( + g_a, g_b, lambda x, y: x == y, id_order=id_order + ) + ) + def test_isomorphic_identical(self): g_a = retworkx.PyGraph() g_b = retworkx.PyGraph() diff --git a/tests/graph/test_subgraph_isomorphic.py b/tests/graph/test_subgraph_isomorphic.py index 7309082ea..65c3a0bb9 100644 --- a/tests/graph/test_subgraph_isomorphic.py +++ b/tests/graph/test_subgraph_isomorphic.py @@ -16,6 +16,34 @@ class TestSubgraphIsomorphic(unittest.TestCase): + def test_empty_subgraph_isomorphic_identical(self): + g_a = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic(g_a, g_a, id_order=id_order) + ) + + def test_empty_subgraph_isomorphic_mismatch_node_data(self): + g_a = retworkx.PyGraph() + g_b = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic(g_a, g_b, id_order=id_order) + ) + + def test_empty_subgraph_isomorphic_compare_nodes_mismatch_node_data(self): + g_a = retworkx.PyGraph() + g_b = retworkx.PyGraph() + for id_order in [False, True]: + with self.subTest(id_order=id_order): + self.assertTrue( + retworkx.is_subgraph_isomorphic( + g_a, g_b, lambda x, y: x == y, id_order=id_order + ) + ) + def test_subgraph_isomorphic_identical(self): g_a = retworkx.PyGraph() nodes = g_a.add_nodes_from(["a_1", "a_2", "a_3"])