Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Return an iterator over all valid Vf2 mappings #376

Merged
merged 35 commits into from
Aug 16, 2021
Merged
Show file tree
Hide file tree
Changes from 26 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
abf1dba
Add vf2 mapping functions
mtreinish Jun 17, 2021
4301835
Fix reverse mapping construction with vf2pp mapping
mtreinish Jun 21, 2021
f1960d7
Add vf2pp remapping failure test case
mtreinish Jun 21, 2021
815c925
Don't remap indices if no match found
mtreinish Jun 23, 2021
2f6ccf6
Merge remote-tracking branch 'origin/main' into vf2-mapping
mtreinish Jun 24, 2021
1962edf
Fix clippy failures
mtreinish Jun 24, 2021
f2ade99
Merge remote-tracking branch 'origin/main' into vf2-mapping
mtreinish Jun 24, 2021
43e8aa0
Use NodeMap for return type
mtreinish Jun 24, 2021
90aa850
Run cargo fmt
mtreinish Jun 24, 2021
cbc7d07
Merge remote-tracking branch 'origin/main' into vf2-mapping
mtreinish Jun 25, 2021
80f70f5
return an iterator over all valid vf2 mappings
georgios-ts Jul 2, 2021
73a749f
update release note
georgios-ts Jul 2, 2021
f937852
lint
georgios-ts Jul 2, 2021
a3cfc4f
merge upstream/main
georgios-ts Jul 7, 2021
8510bd4
fix Vf2ppSorter to use node id instead of edge insertion order as a t…
georgios-ts Jul 15, 2021
c085e43
fix clippy warning
georgios-ts Jul 15, 2021
78c21e6
merge 'upstream/main'
georgios-ts Jul 15, 2021
8f3c701
update text signature
georgios-ts Jul 15, 2021
e39dcbe
update tests since now we can deterministically predict the output ma…
georgios-ts Jul 15, 2021
9b9bf91
add call_limit kwarg in Vf2 algorithm
georgios-ts Jul 23, 2021
17ae6f1
Merge 'upstream/main' into vf2-mapping
georgios-ts Jul 23, 2021
42317bc
run cargo fmt
georgios-ts Jul 23, 2021
d62af07
lint
georgios-ts Jul 23, 2021
4f759a1
Merge 'upstream/main' into vf2-mapping
georgios-ts Aug 12, 2021
41e7c5d
resolve all conflicts
georgios-ts Aug 12, 2021
3ce7499
update release note and add more tests
georgios-ts Aug 12, 2021
83a3d01
call_limit does not need to be explicitly set to None
georgios-ts Aug 16, 2021
4c5a9f1
simplify iteration in gc protocol
georgios-ts Aug 16, 2021
539f491
update vf2 doc
georgios-ts Aug 16, 2021
edc7288
Merge branch 'main' into vf2-mapping
mtreinish Aug 16, 2021
ecce089
cargo fmt + remove unnecessary mut ref in semantic matcher
georgios-ts Aug 16, 2021
847a192
more tests
georgios-ts Aug 16, 2021
32de477
fix clippy error
georgios-ts Aug 16, 2021
085ac8e
fix failing test case
georgios-ts Aug 16, 2021
1573e7b
Fix docstring typo
mtreinish Aug 16, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Isomorphism
retworkx.is_isomorphic
retworkx.is_subgraph_isomorphic
retworkx.is_isomorphic_node_match
retworkx.vf2_mapping

.. _matching:

Expand Down Expand Up @@ -208,6 +209,7 @@ the functions from the explicitly typed based on the data type.

retworkx.digraph_is_isomorphic
retworkx.digraph_is_subgraph_isomorphic
retworkx.digraph_vf2_mapping
retworkx.digraph_distance_matrix
retworkx.digraph_floyd_warshall
retworkx.digraph_floyd_warshall_numpy
Expand Down Expand Up @@ -248,6 +250,7 @@ typed API based on the data type.

retworkx.graph_is_isomorphic
retworkx.graph_is_subgraph_isomorphic
retworkx.graph_vf2_mapping
retworkx.graph_distance_matrix
retworkx.graph_floyd_warshall
retworkx.graph_floyd_warshall_numpy
Expand Down
26 changes: 26 additions & 0 deletions releasenotes/notes/vf2-mapping-6fd49ab8b1b552c2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
features:
- |
Added a new function, :func:`retworkx.vf2_mapping`, which will use the
vf2 isomorphism algorithm (which is also used for
:func:`retworkx.is_isomorphic` and :func:`retworkx.is_subgraph_isomorphic`)
to return an iterator over all valid isomorphic mappings between two graphs.
For example:

.. jupyter-execute::

import retworkx

graph = retworkx.generators.directed_grid_graph(10, 10)
other_graph = retworkx.generators.directed_grid_graph(4, 4)
vf2 = retworkx.vf2_mapping(graph, other_graph, subgraph=True)
try:
mapping = next(vf2)
print(mapping)
except StopIteration:
pass
- |
Added a new kwarg, ``call_limit`` to :func:`retworkx.is_isomorphic` and
:func:`retworkx.is_subgraph_isomorphic` which is used to set an upper
bound on the number of states that VF2 algorithm visits while searching for a
solution. If it exceeds this limit, the algorithm will stop and return false.
142 changes: 135 additions & 7 deletions retworkx/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,7 +715,12 @@ def _graph_dfs_edges(graph, source):

@functools.singledispatch
def is_isomorphic(
first, second, node_matcher=None, edge_matcher=None, id_order=True
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
call_limit=None,
):
"""Determine if 2 graphs are isomorphic

Expand Down Expand Up @@ -750,6 +755,9 @@ def is_isomorphic(
:param bool id_order: If set to ``False`` this function will use a
heuristic matching order based on [VF2]_ paper. Otherwise it will
default to matching the nodes in order specified by their ids.
:param int call_limit: An optional bound on the number of states that VF2
algorithm visits while searching for a solution. If it exceeds this limit,
the algorithm will stop and return ``False``. Default: ``None``.

:returns: ``True`` if the 2 graphs are isomorphic, ``False`` if they are
not.
Expand All @@ -763,19 +771,29 @@ def is_isomorphic(

@is_isomorphic.register(PyDiGraph)
def _digraph_is_isomorphic(
first, second, node_matcher=None, edge_matcher=None, id_order=True
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
call_limit=None,
):
return digraph_is_isomorphic(
first, second, node_matcher, edge_matcher, id_order
first, second, node_matcher, edge_matcher, id_order, call_limit
)


@is_isomorphic.register(PyGraph)
def _graph_is_isomorphic(
first, second, node_matcher=None, edge_matcher=None, id_order=True
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
call_limit=None,
):
return graph_is_isomorphic(
first, second, node_matcher, edge_matcher, id_order
first, second, node_matcher, edge_matcher, id_order, call_limit
)


Expand Down Expand Up @@ -836,6 +854,7 @@ def is_subgraph_isomorphic(
edge_matcher=None,
id_order=False,
induced=True,
call_limit=None,
):
"""Determine if 2 graphs are subgraph isomorphic

Expand Down Expand Up @@ -873,6 +892,9 @@ def is_subgraph_isomorphic(
:param bool induced: If set to ``True`` this function will check the existence
of a node-induced subgraph of first isomorphic to second graph.
Default: ``True``.
:param int call_limit: An optional bound on the number of states that VF2
algorithm visits while searching for a solution. If it exceeds this limit,
the algorithm will stop and return ``False``. Default: ``None``.

:returns: ``True`` if there is a subgraph of `first` isomorphic to `second`
, ``False`` if there is not.
Expand All @@ -889,9 +911,10 @@ def _digraph_is_subgraph_isomorphic(
edge_matcher=None,
id_order=False,
induced=True,
call_limit=None,
):
return digraph_is_subgraph_isomorphic(
first, second, node_matcher, edge_matcher, id_order, induced
first, second, node_matcher, edge_matcher, id_order, induced, call_limit
)


Expand All @@ -903,9 +926,10 @@ def _graph_is_subgraph_isomorphic(
edge_matcher=None,
id_order=False,
induced=True,
call_limit=None,
):
return graph_is_subgraph_isomorphic(
first, second, node_matcher, edge_matcher, id_order, induced
first, second, node_matcher, edge_matcher, id_order, induced, call_limit
)


Expand Down Expand Up @@ -1392,3 +1416,107 @@ def _digraph_num_shortest_paths_unweighted(graph, source):
@num_shortest_paths_unweighted.register(PyGraph)
def _graph_num_shortest_paths_unweighted(graph, source):
return graph_num_shortest_paths_unweighted(graph, source)


@functools.singledispatch
def vf2_mapping(
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
subgraph=False,
induced=True,
call_limit=None,
):
"""
Return an iterator over all vf2 mappings between two graphs.

This funcion will run the vf2 algorithm used from
:func:`~retworkx.is_isomorphic` and :func:`~retworkx.is_subgraph_isomorphic`
but instead of returning a boolean it will return an iterator over all possible
mapping of node ids found from ``first`` to ``second``. If the graphs are not
isomorphic then the iterator will be empty. A simple example that retrieves
one mapping would be::

graph_a = retworkx.generators.path_graph(3)
graph_b = retworkx.generators.path_graph(2)
vf2 = retworkx.vf2_mapping(graph_a, graph_b, subgraph=True)
try:
mapping = next(vf2)
except StopIteration:
pass

:param first: The first graph to find the mapping for
:param second: The second graph to find the mapping for
:param node_matcher: An optional python callable object that takes 2
positional arguments, one for each node data object in either graph.
If the return of this function evaluates to True then the nodes
passed to it are vieded as matching.
:param edge_matcher: A python callable object that takes 2 positional
one for each edge data object. If the return of this
function evaluates to True then the edges passed to it are vieded
as matching.
:param bool id_order: If set to ``False`` this function will use a
heuristic matching order based on [VF2]_ paper. Otherwise it will
default to matching the nodes in order specified by their ids.
:param bool subgraph: If set to ``True`` the function will return the
subgraph isomorphic found between the graphs.
:param bool induced: If set to ``True`` this function will check the existence
of a node-induced subgraph of first isomorphic to second graph.
Default: ``True``.
:param int call_limit: An optional bound on the number of states that VF2
algorithm visits while searching for a solution. If it exceeds this limit,
the algorithm will stop. Default: ``None``.

:returns: An iterator over dicitonaries of node indices from ``first`` to node
indices in ``second`` representing the mapping found.
:rtype: Iterable[NodeMap]
"""
raise TypeError("Invalid Input Type %s for graph" % type(first))


@vf2_mapping.register(PyDiGraph)
def _digraph_vf2_mapping(
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
subgraph=False,
induced=True,
call_limit=None,
):
return digraph_vf2_mapping(
first,
second,
node_matcher=node_matcher,
edge_matcher=edge_matcher,
id_order=id_order,
subgraph=subgraph,
induced=induced,
call_limit=call_limit,
)


@vf2_mapping.register(PyGraph)
def _graph_vf2_mapping(
first,
second,
node_matcher=None,
edge_matcher=None,
id_order=True,
subgraph=False,
induced=True,
call_limit=None,
):
return graph_vf2_mapping(
first,
second,
node_matcher=node_matcher,
edge_matcher=edge_matcher,
id_order=id_order,
subgraph=subgraph,
induced=induced,
call_limit=call_limit,
)
Loading