### diff env/lib/python3.9/site-packages/networkx/algorithms/isomorphism/matchhelpers.py @ 0:4f3585e2f14bdraftdefaulttip

author shellac Mon, 22 Mar 2021 18:12:50 +0000
line wrap: on
line diff
```--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/env/lib/python3.9/site-packages/networkx/algorithms/isomorphism/matchhelpers.py	Mon Mar 22 18:12:50 2021 +0000
@@ -0,0 +1,387 @@
+"""Functions which help end users define customize node_match and
+edge_match functions to use during isomorphism checks.
+"""
+from itertools import permutations
+import types
+
+__all__ = [
+    "categorical_node_match",
+    "categorical_edge_match",
+    "categorical_multiedge_match",
+    "numerical_node_match",
+    "numerical_edge_match",
+    "numerical_multiedge_match",
+    "generic_node_match",
+    "generic_edge_match",
+    "generic_multiedge_match",
+]
+
+
+def copyfunc(f, name=None):
+    """Returns a deepcopy of a function."""
+    return types.FunctionType(
+        f.__code__, f.__globals__, name or f.__name__, f.__defaults__, f.__closure__
+    )
+
+
+def allclose(x, y, rtol=1.0000000000000001e-05, atol=1e-08):
+    """Returns True if x and y are sufficiently close, elementwise.
+
+    Parameters
+    ----------
+    rtol : float
+        The relative error tolerance.
+    atol : float
+        The absolute error tolerance.
+
+    """
+    # assume finite weights, see numpy.allclose() for reference
+    for xi, yi in zip(x, y):
+        if not (abs(xi - yi) <= atol + rtol * abs(yi)):
+            return False
+    return True
+
+
+def close(x, y, rtol=1.0000000000000001e-05, atol=1e-08):
+    """Returns True if x and y are sufficiently close.
+
+    Parameters
+    ----------
+    rtol : float
+        The relative error tolerance.
+    atol : float
+        The absolute error tolerance.
+
+    """
+    # assume finite weights, see numpy.allclose() for reference
+    return abs(x - y) <= atol + rtol * abs(y)
+
+
+categorical_doc = """
+Returns a comparison function for a categorical node attribute.
+
+The value(s) of the attr(s) must be hashable and comparable via the ==
+operator since they are placed into a set([]) object.  If the sets from
+G1 and G2 are the same, then the constructed function returns True.
+
+Parameters
+----------
+attr : string | list
+    The categorical node attribute to compare, or a list of categorical
+    node attributes to compare.
+default : value | list
+    The default value for the categorical node attribute, or a list of
+    default values for the categorical node attributes.
+
+Returns
+-------
+match : function
+    The customized, categorical `node_match` function.
+
+Examples
+--------
+>>> import networkx.algorithms.isomorphism as iso
+>>> nm = iso.categorical_node_match("size", 1)
+>>> nm = iso.categorical_node_match(["color", "size"], ["red", 2])
+
+"""
+
+
+def categorical_node_match(attr, default):
+    if isinstance(attr, str):
+
+        def match(data1, data2):
+            return data1.get(attr, default) == data2.get(attr, default)
+
+    else:
+        attrs = list(zip(attr, default))  # Python 3
+
+        def match(data1, data2):
+            return all(data1.get(attr, d) == data2.get(attr, d) for attr, d in attrs)
+
+    return match
+
+
+try:
+    categorical_edge_match = copyfunc(categorical_node_match, "categorical_edge_match")
+except NotImplementedError:
+    # IronPython lacks support for types.FunctionType.
+    # https://github.com/networkx/networkx/issues/949
+    # https://github.com/networkx/networkx/issues/1127
+    def categorical_edge_match(*args, **kwargs):
+        return categorical_node_match(*args, **kwargs)
+
+
+def categorical_multiedge_match(attr, default):
+    if isinstance(attr, str):
+
+        def match(datasets1, datasets2):
+            values1 = {data.get(attr, default) for data in datasets1.values()}
+            values2 = {data.get(attr, default) for data in datasets2.values()}
+            return values1 == values2
+
+    else:
+        attrs = list(zip(attr, default))  # Python 3
+
+        def match(datasets1, datasets2):
+            values1 = set()
+            for data1 in datasets1.values():
+                x = tuple(data1.get(attr, d) for attr, d in attrs)
+            values2 = set()
+            for data2 in datasets2.values():
+                x = tuple(data2.get(attr, d) for attr, d in attrs)
+            return values1 == values2
+
+    return match
+
+
+# Docstrings for categorical functions.
+categorical_node_match.__doc__ = categorical_doc
+categorical_edge_match.__doc__ = categorical_doc.replace("node", "edge")
+tmpdoc = categorical_doc.replace("node", "edge")
+tmpdoc = tmpdoc.replace("categorical_edge_match", "categorical_multiedge_match")
+categorical_multiedge_match.__doc__ = tmpdoc
+
+
+numerical_doc = """
+Returns a comparison function for a numerical node attribute.
+
+The value(s) of the attr(s) must be numerical and sortable.  If the
+sorted list of values from G1 and G2 are the same within some
+tolerance, then the constructed function returns True.
+
+Parameters
+----------
+attr : string | list
+    The numerical node attribute to compare, or a list of numerical
+    node attributes to compare.
+default : value | list
+    The default value for the numerical node attribute, or a list of
+    default values for the numerical node attributes.
+rtol : float
+    The relative error tolerance.
+atol : float
+    The absolute error tolerance.
+
+Returns
+-------
+match : function
+    The customized, numerical `node_match` function.
+
+Examples
+--------
+>>> import networkx.algorithms.isomorphism as iso
+>>> nm = iso.numerical_node_match("weight", 1.0)
+>>> nm = iso.numerical_node_match(["weight", "linewidth"], [0.25, 0.5])
+
+"""
+
+
+def numerical_node_match(attr, default, rtol=1.0000000000000001e-05, atol=1e-08):
+    if isinstance(attr, str):
+
+        def match(data1, data2):
+            return close(
+                data1.get(attr, default), data2.get(attr, default), rtol=rtol, atol=atol
+            )
+
+    else:
+        attrs = list(zip(attr, default))  # Python 3
+
+        def match(data1, data2):
+            values1 = [data1.get(attr, d) for attr, d in attrs]
+            values2 = [data2.get(attr, d) for attr, d in attrs]
+            return allclose(values1, values2, rtol=rtol, atol=atol)
+
+    return match
+
+
+try:
+    numerical_edge_match = copyfunc(numerical_node_match, "numerical_edge_match")
+except NotImplementedError:
+    # IronPython lacks support for types.FunctionType.
+    # https://github.com/networkx/networkx/issues/949
+    # https://github.com/networkx/networkx/issues/1127
+    def numerical_edge_match(*args, **kwargs):
+        return numerical_node_match(*args, **kwargs)
+
+
+def numerical_multiedge_match(attr, default, rtol=1.0000000000000001e-05, atol=1e-08):
+    if isinstance(attr, str):
+
+        def match(datasets1, datasets2):
+            values1 = sorted([data.get(attr, default) for data in datasets1.values()])
+            values2 = sorted([data.get(attr, default) for data in datasets2.values()])
+            return allclose(values1, values2, rtol=rtol, atol=atol)
+
+    else:
+        attrs = list(zip(attr, default))  # Python 3
+
+        def match(datasets1, datasets2):
+            values1 = []
+            for data1 in datasets1.values():
+                x = tuple(data1.get(attr, d) for attr, d in attrs)
+                values1.append(x)
+            values2 = []
+            for data2 in datasets2.values():
+                x = tuple(data2.get(attr, d) for attr, d in attrs)
+                values2.append(x)
+            values1.sort()
+            values2.sort()
+            for xi, yi in zip(values1, values2):
+                if not allclose(xi, yi, rtol=rtol, atol=atol):
+                    return False
+            else:
+                return True
+
+    return match
+
+
+# Docstrings for numerical functions.
+numerical_node_match.__doc__ = numerical_doc
+numerical_edge_match.__doc__ = numerical_doc.replace("node", "edge")
+tmpdoc = numerical_doc.replace("node", "edge")
+tmpdoc = tmpdoc.replace("numerical_edge_match", "numerical_multiedge_match")
+numerical_multiedge_match.__doc__ = tmpdoc
+
+
+generic_doc = """
+Returns a comparison function for a generic attribute.
+
+The value(s) of the attr(s) are compared using the specified
+operators. If all the attributes are equal, then the constructed
+function returns True.
+
+Parameters
+----------
+attr : string | list
+    The node attribute to compare, or a list of node attributes
+    to compare.
+default : value | list
+    The default value for the node attribute, or a list of
+    default values for the node attributes.
+op : callable | list
+    The operator to use when comparing attribute values, or a list
+    of operators to use when comparing values for each attribute.
+
+Returns
+-------
+match : function
+    The customized, generic `node_match` function.
+
+Examples
+--------
+>>> from operator import eq
+>>> from networkx.algorithms.isomorphism.matchhelpers import close
+>>> from networkx.algorithms.isomorphism import generic_node_match
+>>> nm = generic_node_match("weight", 1.0, close)
+>>> nm = generic_node_match("color", "red", eq)
+>>> nm = generic_node_match(["weight", "color"], [1.0, "red"], [close, eq])
+
+"""
+
+
+def generic_node_match(attr, default, op):
+    if isinstance(attr, str):
+
+        def match(data1, data2):
+            return op(data1.get(attr, default), data2.get(attr, default))
+
+    else:
+        attrs = list(zip(attr, default, op))  # Python 3
+
+        def match(data1, data2):
+            for attr, d, operator in attrs:
+                if not operator(data1.get(attr, d), data2.get(attr, d)):
+                    return False
+            else:
+                return True
+
+    return match
+
+
+try:
+    generic_edge_match = copyfunc(generic_node_match, "generic_edge_match")
+except NotImplementedError:
+    # IronPython lacks support for types.FunctionType.
+    # https://github.com/networkx/networkx/issues/949
+    # https://github.com/networkx/networkx/issues/1127
+    def generic_edge_match(*args, **kwargs):
+        return generic_node_match(*args, **kwargs)
+
+
+def generic_multiedge_match(attr, default, op):
+    """Returns a comparison function for a generic attribute.
+
+    The value(s) of the attr(s) are compared using the specified
+    operators. If all the attributes are equal, then the constructed
+    function returns True. Potentially, the constructed edge_match
+    function can be slow since it must verify that no isomorphism
+    exists between the multiedges before it returns False.
+
+    Parameters
+    ----------
+    attr : string | list
+        The edge attribute to compare, or a list of node attributes
+        to compare.
+    default : value | list
+        The default value for the edge attribute, or a list of
+        default values for the dgeattributes.
+    op : callable | list
+        The operator to use when comparing attribute values, or a list
+        of operators to use when comparing values for each attribute.
+
+    Returns
+    -------
+    match : function
+        The customized, generic `edge_match` function.
+
+    Examples
+    --------
+    >>> from operator import eq
+    >>> from networkx.algorithms.isomorphism.matchhelpers import close
+    >>> from networkx.algorithms.isomorphism import generic_node_match
+    >>> nm = generic_node_match("weight", 1.0, close)
+    >>> nm = generic_node_match("color", "red", eq)
+    >>> nm = generic_node_match(["weight", "color"], [1.0, "red"], [close, eq])
+    ...
+
+    """
+
+    # This is slow, but generic.
+    # We must test every possible isomorphism between the edges.
+    if isinstance(attr, str):
+        attr = [attr]
+        default = [default]
+        op = [op]
+    attrs = list(zip(attr, default))  # Python 3
+
+    def match(datasets1, datasets2):
+        values1 = []
+        for data1 in datasets1.values():
+            x = tuple(data1.get(attr, d) for attr, d in attrs)
+            values1.append(x)
+        values2 = []
+        for data2 in datasets2.values():
+            x = tuple(data2.get(attr, d) for attr, d in attrs)
+            values2.append(x)
+        for vals2 in permutations(values2):
+            for xi, yi in zip(values1, vals2):
+                if not all(map(lambda x, y, z: z(x, y), xi, yi, op)):
+                    # This is not an isomorphism, go to next permutation.
+                    break
+            else:
+                # Then we found an isomorphism.
+                return True
+        else:
+            # Then there are no isomorphisms between the multiedges.
+            return False
+
+    return match
+
+
+# Docstrings for numerical functions.
+generic_node_match.__doc__ = generic_doc
+generic_edge_match.__doc__ = generic_doc.replace("node", "edge")```