
    ܛ7i                         d Z dgZddlZddlmZmZ ddlmZmZ ddl	Z
d ZddZd Zd	 Z G d
 d      Z G d d      Zy)a)  
ISMAGS Algorithm
================

Provides a Python implementation of the ISMAGS algorithm. [1]_

ISMAGS does a symmetry analysis to find the constraints on isomorphisms if
we preclude yielding isomorphisms that differ by a symmetry of the subgraph.
For example, if the subgraph contains a 4-cycle, every isomorphism would have a
symmetric version with the nodes rotated relative to the original isomorphism.
By encoding these symmetries as constraints we reduce the search space for
isomorphisms and we also simplify processing the resulting isomorphisms.

ISMAGS finds (subgraph) isomorphisms between two graphs, taking the
symmetry of the subgraph into account. In most cases the VF2 algorithm is
faster (at least on small graphs) than this implementation, but in some cases
there are an exponential number of isomorphisms that are symmetrically
equivalent. In that case, the ISMAGS algorithm will provide only one isomorphism
per symmetry group, speeding up finding isomorphisms and avoiding the task of
post-processing many effectively identical isomorphisms.

>>> petersen = nx.petersen_graph()
>>> ismags = nx.isomorphism.ISMAGS(petersen, petersen)
>>> isomorphisms = list(ismags.isomorphisms_iter(symmetry=False))
>>> len(isomorphisms)
120
>>> isomorphisms = list(ismags.isomorphisms_iter(symmetry=True))
>>> answer = [{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9}]
>>> answer == isomorphisms
True

In addition, this implementation also provides an interface to find the
largest common induced subgraph [2]_ between any two graphs, again taking
symmetry into account. Given ``graph`` and ``subgraph`` the algorithm will remove
nodes from the ``subgraph`` until ``subgraph`` is isomorphic to a subgraph of
``graph``. Since only the symmetry of ``subgraph`` is taken into account it is
worth thinking about how you provide your graphs:

>>> graph1 = nx.path_graph(4)
>>> graph2 = nx.star_graph(3)
>>> ismags = nx.isomorphism.ISMAGS(graph1, graph2)
>>> ismags.is_isomorphic()
False
>>> largest_common_subgraph = list(ismags.largest_common_subgraph())
>>> answer = [{1: 0, 0: 1, 2: 2}, {2: 0, 1: 1, 3: 2}]
>>> answer == largest_common_subgraph
True
>>> ismags2 = nx.isomorphism.ISMAGS(graph2, graph1)
>>> largest_common_subgraph = list(ismags2.largest_common_subgraph())
>>> answer = [
...     {1: 0, 0: 1, 2: 2},
...     {1: 0, 0: 1, 3: 2},
...     {2: 0, 0: 1, 1: 2},
...     {2: 0, 0: 1, 3: 2},
...     {3: 0, 0: 1, 1: 2},
...     {3: 0, 0: 1, 2: 2},
... ]
>>> answer == largest_common_subgraph
True

However, when not taking symmetry into account, it doesn't matter:

>>> largest_common_subgraph = list(ismags.largest_common_subgraph(symmetry=False))
>>> answer = [
...     {1: 0, 0: 1, 2: 2},
...     {1: 0, 2: 1, 0: 2},
...     {2: 0, 1: 1, 3: 2},
...     {2: 0, 3: 1, 1: 2},
...     {1: 0, 0: 1, 2: 3},
...     {1: 0, 2: 1, 0: 3},
...     {2: 0, 1: 1, 3: 3},
...     {2: 0, 3: 1, 1: 3},
...     {1: 0, 0: 2, 2: 3},
...     {1: 0, 2: 2, 0: 3},
...     {2: 0, 1: 2, 3: 3},
...     {2: 0, 3: 2, 1: 3},
... ]
>>> answer == largest_common_subgraph
True
>>> largest_common_subgraph = list(ismags2.largest_common_subgraph(symmetry=False))
>>> answer = [
...     {1: 0, 0: 1, 2: 2},
...     {1: 0, 0: 1, 3: 2},
...     {2: 0, 0: 1, 1: 2},
...     {2: 0, 0: 1, 3: 2},
...     {3: 0, 0: 1, 1: 2},
...     {3: 0, 0: 1, 2: 2},
...     {1: 1, 0: 2, 2: 3},
...     {1: 1, 0: 2, 3: 3},
...     {2: 1, 0: 2, 1: 3},
...     {2: 1, 0: 2, 3: 3},
...     {3: 1, 0: 2, 1: 3},
...     {3: 1, 0: 2, 2: 3},
... ]
>>> answer == largest_common_subgraph
True

Notes
-----
- Node and edge equality is assumed to be transitive: if A is equal to B, and
  B is equal to C, then A is equal to C.

- With a method that yields subgraph isomorphisms, we can construct functions like
  ``is_subgraph_isomorphic`` by checking for any yielded mapping. And functions like
  ``is_isomorphic`` by checking whether the subgraph has the same number of nodes as
  the graph and is also subgraph isomorphic. This subpackage also allows a
  ``symmetry`` bool keyword argument so you can find isomorphisms with or
  without the symmetry constraints.

- For more information, see [2]_ and the documentation for :class:`ISMAGS`
  which includes a description of the algorithm.

References
----------
.. [1] M. Houbraken, S. Demeyer, T. Michoel, P. Audenaert, D. Colle,
   M. Pickavet, "The Index-Based Subgraph Matching Algorithm with General
   Symmetries (ISMAGS): Exploiting Symmetry for Faster Subgraph
   Enumeration", PLoS One 9(5): e97896, 2014.
   https://doi.org/10.1371/journal.pone.0097896
.. [2] https://en.wikipedia.org/wiki/Maximum_common_induced_subgraph
ISMAGS    N)Counterdefaultdict)reducewrapsc                     	 | j                   }t        |      dkD  rd}t        |      dt	        |       }t        |d      t        fd|D              S # t        $ r Y 6w xY w)af  
    Returns ``True`` if and only if all elements in `iterable` are equal; and
    ``False`` otherwise.

    Parameters
    ----------
    iterable: collections.abc.Iterable
        The container whose elements will be checked.

    Returns
    -------
    bool
        ``True`` iff all elements in `iterable` compare equal, ``False``
        otherwise.
       z7The function does not works on multidimensional arrays.Nc              3   (   K   | ]	  }|k(    y wN ).0itemfirsts     c/home/rose/Desktop/poly/venv/lib/python3.12/site-packages/networkx/algorithms/isomorphism/ismags.py	<genexpr>z are_all_equal.<locals>.<genexpr>   s     2tu}   )shapelenNotImplementedErrorAttributeErroriternextall)iterabler   messageiteratorr   s       @r   are_all_equalr      sj     9 u:>OG%g.D8H~H4 E2222  s   A 	A"!A"c                    g | D ]K  }D ]2  }t        t        |            } ||      s!|j                  |        9 j                  |h       M |r_t	        fdD              st        j                  d d      t	        fdD              st        j                  d d      D cg c]  }t        |       c}S c c}w )a  
    Partitions items into sets based on the outcome of ``test(item1, item2)``.
    Pairs of items for which `test` returns `True` end up in the same set.

    Parameters
    ----------
    items : collections.abc.Iterable[collections.abc.Hashable]
        Items to partition
    test : collections.abc.Callable[collections.abc.Hashable, collections.abc.Hashable]
        A function that will be called with 2 arguments, taken from items.
        Should return `True` if those 2 items match/tests so need to end up in the same
        part of the partition, and `False` otherwise.
    check : bool optional (default: True)
        If ``True``, check that the resulting partition satisfies the match criteria.
        Every item should match every item in its part and none outside the part.

    Returns
    -------
    list[set]
        A partition as a list of sets (the parts). Each set contains some of
        the items in `items`, such that all items are in exactly one part and every
        pair of items in each part matches. The following will be true:
        ``all(thing_matcher(*pair) for pair in itertools.combinations(items, 2))``

    Notes
    -----
    The function `test` is assumed to be transitive: if ``test(a, b)`` and
    ``test(b, c)`` return ``True``, then ``test(a, c)`` must also be ``True``.
    The function `test` is assumed to be commutative: if ``test(a, b)``
    returns ``True`` then ``test(b, a)`` returns ``True``.
    c              3      K   | ]6  }t        j                  |d       D ]  \  }} ||      xr	  ||        8 yw)   N)	itertoolscombinations)r   partt1t2tests       r   r   z!make_partition.<locals>.<genexpr>   sK      
!#00q9B RL)T"b\)9 *!s   <?z 
Invalid partition created with z=.
Some items in a part do not match. This leads to
partition=c              3      K   | ]D  }D ]=  }||k7  r6t        j                  ||      D ]  \  }} ||       xr
  ||         ? F y wr   )r!   product)r   p1p2r$   r%   	partitionr&   s        r   r   z!make_partition.<locals>.<genexpr>   sg      
Rx#++B3B	 R1T"b\!11 4	 2 2s   A
Az;.
Some items match multiple parts. This leads to
partition=)r   r   addappendr   nxNetworkXErrorset)itemsr&   checkr   r#   p_itemr+   s    `    @r   make_partitionr4      s    @ ID$t*%FD&!	  dV$   
!
 

 ""3D6 :,  
  

 
 ""3D6 :,  
 #,,)$CI),,,s   :Cc                 `    t        |       D ci c]  \  }}|D ]  }||  c}}}S c c}}}w )a#  
    Creates a dictionary that maps each item in each part to the index of
    the part to which it belongs.

    Parameters
    ----------
    partition: collections.abc.Sequence[collections.abc.Iterable]
        As returned by :func:`make_partition`.

    Returns
    -------
    dict
    )	enumerate)r+   IDr#   nodes       r   node_to_part_ID_dictr9      s3     &/y%9K%9TddD"HdD%9KKKs   )c                 
   t              t        |       k  r0| j                         D ]  \  }}|vsd|<   |D ]	  }d||f<     | j                         s>| j                         D ci c]!  \  }   t        fd|D              f# c}}S | j                         D ci c]C  \  }   t        fd|D              t        fd| j                     D              fE c}}S c c}}w c c}}w )a  Returns a dict by node to counts of edge and node color for that node.

    This returns a dict by node to a 2-tuple of node color and degree by
    (edge color and nbr color). E.g. ``{0: (1, {(0, 2): 5})}`` means that
    node ``0`` has node type 1 and has 5 edges of type 0 that go to nodes of type 2.
    Thus, this is a measure of degree (edge count) by color of edge and color
    of the node on the other side of that edge.

    For directed graphs the degree counts is a 2-tuple of (in, out) degree counts.

    Ideally, if edge_match is None, this could get simplified to just the node
    color on the other end of the edge. Similarly if node_match is None then only
    edge color is tracked. And if both are None, we simply count the number of edges.
    Nc              3   6   K   | ]  }|f   |   f  y wr   r   r   ve_colorsn_colorsus     r   r   z'color_degree_by_node.<locals>.<genexpr>  s#     $QDqhq!tnhqk%BD   c              3   6   K   | ]  }|f   |   f  y wr   r   r<   s     r   r   z'color_degree_by_node.<locals>.<genexpr>  s#     @4aXad^Xa[14rA   c              3   6   K   | ]  }|f   |   f  y wr   r   r<   s     r   r   z'color_degree_by_node.<locals>.<genexpr>  s#     F:aXad^Xa[1:rA   )r   	adjacencyis_directedr   _pred)Gr?   r>   nnbrsr=   r@   s    ``   `r   color_degree_by_noderJ      s     8}s1v{{}GAt "A%)HQTN  % ==? ;;=
(4 W$QD$QQRR(
 	
 {{} %GAt 	
QK@4@@F1771:FF
 	

 % 

s   /&C9-AC?c                   "    e Zd ZdZd Zd Zd Zy)
EdgeLookupa  Class to handle getitem for undirected edges.

    Note that ``items()`` iterates over one of the two representations of the edge
    (u, v) and (v, u). So this technically doesn't violate the Mapping
    invariant that (k,v) pairs reported by ``items()`` satisfy ``.__getitem__(k) == v``.
    But we are violating the spirit of the protocol by having keys available
    for lookup by ``__getitem__`` that are not reported by ``items()``.

    Note that if we used frozensets for undirected edges we would have the same
    behavior we see here. You could ``__getitem__`` either ``{u, v}`` or ``{v, u}``
    and get the same value -- yet ``items()`` would only report one of the two.
    So from that perspective we *are* following the Mapping protocol. Our keys
    are undirected edges. We are using 2-tuples as an imperfect representation
    of these edges. We are not using 2-tuples as keys. Only as imperfect edges
    and we use the edges as keys.
    c                     || _         y r   	edge_dict)selfrO   s     r   __init__zEdgeLookup.__init__0  s	    "    c                 f    || j                   v r| j                   |   S | j                   |d d d      S )NrN   )rP   edges     r   __getitem__zEdgeLookup.__getitem__3  s4    4>>!>>$''~~d4R4j))rR   c                 6    | j                   j                         S r   )rO   r1   )rP   s    r   r1   zEdgeLookup.items8  s    ~~##%%rR   N)__name__
__module____qualname____doc__rQ   rV   r1   r   rR   r   rL   rL     s    "#*
&rR   rL   c                       e Zd ZdZddZd ZddZd ZddZd Z	dd	Z
dd
ZddZddZd Zed        ZddZddZed        Zed        Zd Zed        Zd Zy)r   a?,  
    Implements the ISMAGS subgraph matching algorithm. [1]_ ISMAGS stands for
    "Index-based Subgraph Matching Algorithm with General Symmetries". As the
    name implies, it is symmetry aware and will only generate non-symmetric
    isomorphisms.

    Attributes
    ----------
    graph: networkx.Graph
    subgraph: networkx.Graph

    Notes
    -----
    ISMAGS does a symmetry analysis to find the constraints on isomorphisms if
    we preclude yielding isomorphisms that differ by a symmetry of the subgraph.
    For example, if the subgraph is a 4-cycle, every isomorphism would have a
    symmetric version with the nodes rotated relative to the original isomorphism.
    By encoding these symmetries as constraints we reduce the search space for
    isomorphisms and we also simplify processing the resulting isomorphisms.

    **Symmetry Analysis**

    The constraints in ISMAGS are based off the handling in ``nauty`` and its many
    variants, in particular ``saucy``, as discussed in the ISMAGS paper [1]_.
    That paper cites [3]_ for details on symmetry handling. Figure 2 of [3]_
    describes the DFS approach to symmetries used here and relying on a data structure
    called an Ordered Pair Partitions(OPP). This consists of a pair of partitions
    where each part represents nodes with the same degree-by-color over all colors.
    We refine these partitions simultaneously in a way to result in permutations
    of the nodes that preserve the graph structure. We thus find automorphisms
    for the subgraph of interest. From those we identify pairs of nodes which
    are structurally equivalent. We then constrain our problem by requiring the
    first of the pair to always be assigned first in the isomorphism -- thus
    constraining the isomorphisms reported to only one example from the set of all
    symmetrically equivalent isomorphisms. These constraints are computed once
    based on the subgraph symmetries and then used throughout the DFS search for
    isomorphisms.

    Finding the symmetries involves a DFS of the OPP wherein we "couple" a node
    to a node in its degree-by-color part of the partition. This "coupling" is done
    via assigning a new color in the top partition to the node being coupled,
    and the same new color in the bottom partition to the node being coupled to.
    This new color has only one node in each partition. The new color also requires
    that we "refine" both top and bottom partitions by splitting parts until each
    part represents a common degree-by-color value. Those refinements introduce
    new colors as the parts are split during refinement. Parts do not get combined
    during refinement. So the coupling/refining process always results in at least
    one new part with only one node in both the top and bottom partition. In practice
    we usually refine into many new one-node parts in both partitions.
    We continue in this way until each node has its own part/color in the top partition
    -- and the node in the bottom partition with that color is the symmetric node.
    That is, an OPP represents an automorphism, and thus a symmetry
    of the subgraph when each color has a single node in the top partition and a single
    node in the bottom partition. From those automorphisms we build up a set of nodes
    that can be obtained from each other by symmetry (they are mutually symmetric).
    That set of nodes is called an "orbit" of the subgraph under symmetry.

    After finding the orbits for one symmetry, we backtrack in the DFS by removing the
    latest coupling and replacing it with a coupling from the same top node to a new
    bottom node in its degree-by-color grouping. When all possible couplings for that
    node are considered we backtrack to the previously coupled node and recouple in
    a DFS manner.

    We can prune the DFS search tree in helpful ways. The paper [2]_ demonstrates 6
    situations of interest in the DFS where pruning is possible:

    - An **Automorphism OPP** is an OPP where every part in both partitions
      contains a single node. The mapping/automorphism is found by mapping
      each top node to the bottom node in the same color part. For example,
      ``[({1}, {2}, {3}); ({2}, {3}, {1})]`` represents the mapping of each
      node to the next in a triangle. It rotates the nodes around the triangle.
    - The **Identity OPP** is the first automorphism found during the DFS. It
      appears on the left side of the DFS tree and is first due to our ordering of
      coupling nodes to be in an arbitrary but fixed ordering of the nodes. This
      automorphism does not show any symmetries, but it ensures the orbit for each
      node includes itself and it sets us up for handling the symmetries. Note that
      a subgraph with no symmetries will only have the identity automorphism.
    - A **Non-isomorphic OPP** occurs when refinement creates a different number of
      parts in the top partition than in the bottom partition. This means no symmetries
      will be found during further processing of that subtree of the DFS. We prune
      the subtree and continue.
    - A **Matching OPP** is such that each top part that has more than one node is
      in fact equal to the bottom part with the same color. The many-node-parts match
      exactly. The single-node parts then represent symmetries that do not permute
      any matching nodes. Matching OPPs arise while finding the Identity Mapping. But
      the single-node parts are identical in the two partitions, so no useful symmetries
      are found. But after the Identity Mapping is found, every Matching OPP encountered
      will have different nodes in at least two single-node parts of the same color.
      So these positions in the DFS provide us with symmetries without any
      need to find the whole automorphism. We can prune the subtree, update the orbits
      and backtrack. Any larger symmetries that combine these symmetries with symmetries
      of the many-node-parts do not need to be explored because the symmetry "generators"
      found in this way provide a basis for all symmetries. We will find the symmetry
      generators of the many-node-parts at another subtree of the DFS.
    - An **Orbit Pruning OPP** is an OPP where the node coupling to be considered next
      has both nodes already known to be in the same orbit. We have already identified
      those permutations when we discovered the orbit. So we can prune the resulting
      subtree. This is the primary pruning discussed in [1]_.
    - A **Coset Point** in the DFS is a point of the tree when a node is first
      back-tracked. That is, its couplings have all been analyzed once and we backtrack
      to its parent. So, said another way, when a node is backtracked to and is about to
      be mapped to a different node for the first time, its child in the DFS has been
      completely analysed. Thus the orbit for that child at this point in the DFS is
      the full orbit for symmetries involving only that child or larger nodes in the
      node order. All smaller nodes are mapped to themselves.
      This orbit is due to symmetries not involving smaller nodes. Such an orbit is
      called the "coset" of that node. The Coset Point does not lead to pruning or to
      more symmetries. It is the point in the process where we store the **coset** of
      the node being backtracked. We use the cosets to construct the symmetry
      constraints.

    Once the pruned DFS tree has been traversed, we have collected the cosets of some
    special nodes. Often most nodes are not coupled during the progression down the left
    side of the DFS. They are separated from other nodes during the partition refinement
    process after coupling. So they never get coupled directly. Thus the number of cosets
    we find is typically many fewer than the number of nodes.

    We turn those cosets into constraints on the nodes when building non-symmetric
    isomorphisms. The node whose coset is used is paired with each other node in the
    coset. These node-pairs form the constraints. During isomorphism construction we
    always select the first of the constraint before the other. This removes subtrees
    from the DFS traversal space used to build isomorphisms.

    The constraints we obtain via symmetry analysis of the subgraph are used for
    finding non-symmetric isomorphisms. We prune the isomorphism tree based on
    the constraints we obtain from the symmetry analysis.

    **Isomorphism Construction**

    Once we have symmetry constraints on the isomorphisms, ISMAGS constructs the allowed
    isomorphisms by mapping each node of the subgraph to all possible nodes (with the
    same degree-by-color) from the graph. We partition all nodes into degree-by-color
    parts and order the subgraph nodes we consider using smallest part size first.
    The idea is to try to map the most difficult subgraph nodes first (most difficult
    here means least number of graph candidates).

    By considering each potential subgraph node to graph candidate mapping image in turn,
    we perform a DFS traversal of partial mappings. If the mapping is rejected due to
    the graph neighbors not matching the degree-by-color of the subgraph neighbors, or
    rejected due to the constraints imposed from symmetry, we prune that subtree and
    consider a new graph candidate node for that subgraph node. When no more graph
    candidates remain we backtrack to the previous node in the mapping and consider a
    new graph candidate for that node. If we ever get to a depth where all subgraph nodes
    are mapped and no structural requirements or symmetry constraints are violated,
    we have found an isomorphism. We yield that mapping and backtrack to find other
    isomorphisms.

    As we visit more neighbors, the graph candidate nodes have to satisfy more structural
    restrictions. As described in the ISMAGS paper, [1]_, we store each set of structural
    restrictions separately as a set of possible candidate nodes rather than computing
    the intersection of that set with the known graph candidates for the subgraph node.
    We delay taking the intersection until that node is selected to be in the mapping.
    While choosing the node with fewest candidates, we avoid computing the intersection
    by using the size of the minimal set to be intersected rather than the size of the
    intersection. This may make the node ordering slightly worse via a savings of
    many intersections most of which are not ever needed.

    References
    ----------
    .. [1] M. Houbraken, S. Demeyer, T. Michoel, P. Audenaert, D. Colle,
       M. Pickavet, "The Index-Based Subgraph Matching Algorithm with General
       Symmetries (ISMAGS): Exploiting Symmetry for Faster Subgraph
       Enumeration", PLoS One 9(5): e97896, 2014.
       https://doi.org/10.1371/journal.pone.0097896
    .. [2] https://en.wikipedia.org/wiki/Maximum_common_induced_subgraph
    .. [3] Hadi Katebi, Karem A. Sakallah and Igor L. Markov
       "Graph Symmetry Detection and Canonical Labeling: Differences and Synergies"
       in "Turing-100. The Alan Turing Centenary" Ed: A. Voronkov p. 181 -- 195, (2012).
       https://doi.org/10.29007/gzc1 https://arxiv.org/abs/1208.6271
    Nc                 h   |j                         |j                         k7  rt        d      || _        || _        || _        | j                  || j                  j                  | j                  j                        }|\  | _        | _        | _	        t        | j                        | _        t        | j                        | _        | j                  || j                  j                         | j                  j                               }|\  | _        | _        | _        | j                  j                         r5t        | j                        | _        t        | j                        | _        yt'        t        | j                              | _        t'        t        | j                              | _        y)a  
        Parameters
        ----------
        graph: networkx.Graph
        subgraph: networkx.Graph
        node_match: collections.abc.Callable or None
            Function used to determine whether two nodes are equivalent. Its
            signature should look like ``f(n1: dict, n2: dict) -> bool``, with
            `n1` and `n2` node property dicts. See also
            :func:`~networkx.algorithms.isomorphism.categorical_node_match` and
            friends.
            If `None`, all nodes are considered equal.
        edge_match: collections.abc.Callable or None
            Function used to determine whether two edges are equivalent. Its
            signature should look like ``f(e1: dict, e2: dict) -> bool``, with
            `e1` and `e2` edge property dicts. See also
            :func:`~networkx.algorithms.isomorphism.categorical_edge_match` and
            friends.
            If `None`, all edges are considered equal.
        cache: collections.abc.Mapping
            A cache used for caching graph symmetries.
        z2Directed and undirected graphs cannot be compared.N)rE   
ValueErrorgraphsubgraph_symmetry_cachecreate_aligned_partitionsnodes_sgn_partition_gn_partitionN_node_colorsr9   _sgn_colors
_gn_colorsedges_sge_partition_ge_partitionN_edge_colors_sge_colors
_ge_colorsrL   )rP   r_   r`   
node_match
edge_matchcache
node_partsedge_partitionss           r   rQ   zISMAGS.__init__  s]   . ("6"6"88QRR 
 $ 33++TZZ-=-=

 GQCT/1C/0C0CD.t/A/AB88++-tzz/?/?/A
 GVCT/1C::!!#3D4G4GHD243E3EFDO)*>t?R?R*STD()=d>P>P)QRDOrR   c                 .    t              g}t              g}||dfS t        t        j                  j                  j
                        }t        t        j                  j                  j
                        }|sfd}n fd}|sfd}	n fd}	t        |      }t        |	      }i }
i }t        |      t        |      }}t        j                  t        |      t        |            D ]  \  }}t        t        ||               }t        t        ||               }|s|   n j                  |d      |d      }|s|   n j                  |d      |d      } ||      s|||
v r4t        j                  d d|d	| d
||    d||    d||
|       d      ||v r7t        j                  d d|d|d	| d||    d||    d|||       d      ||
|<   |||<    |
j!                         D cg c]  \  }}||   ||   f }}}t        |      }|r#t#        | D cg c]  }t%        |       c}\  }}ng g }}||k  rQt        |      D cg c]  }||
vs||    }}t%        |      |z   }t%        |      t               gt        |      z  z   }||k  rQt        |      D cg c]  }||vs||    }}t%        |      |z   }t%        |      t               gt        |      z  z   }|||fS c c}}w c c}w c c}w c c}w )a  Partitions of "things" (nodes or edges) from subgraph and graph
        based on function `thing_matcher`.

        Returns: sg_partition, g_partition, number_of_matched_parts

        The first `number_of_matched_parts` parts in each partition
        match in order, e.g. 2nd part matches other's 2nd part.
        Warning: nodes in parts after that have no matching nodes in the other graph.
        For morphisms those nodes can't appear in the mapping.
        r	   c                 "     |    |         S r   r   )thing1thing2	sg_thingsthing_matchers     r   sg_matchz2ISMAGS.create_aligned_partitions.<locals>.sg_match5  s    $Yv%6	&8IJJrR   c                 l    | |c\  }}\  }} j                   |   |   j                   |   |         S r   )r`   rv   rw   u1v1u2v2rP   ry   s         r   rz   z2ISMAGS.create_aligned_partitions.<locals>.sg_match:  s@    %+V"R(2r$T]]2%6r%:DMM"<Mb<QRRrR   c                 "     |    |         S r   r   )rv   rw   g_thingsry   s     r   g_matchz1ISMAGS.create_aligned_partitions.<locals>.g_match@  s    $Xf%5x7GHHrR   c                 l    | |c\  }}\  }} j                   |   |   j                   |   |         S r   )r_   r|   s         r   r   z1ISMAGS.create_aligned_partitions.<locals>.g_matchE  s>    %+V"R(2r$TZZ^B%7B9KLLrR   r   z
Matching function z- seems faulty.
Partition found: sg_partition=z
So z in subgraph part z matches two graph parts z and 
z!
Matching function seems broken: z
Partitions found: g_partition=z sg_partition=z in graph part z matches two subgraph parts )r0   
isinstancer.   classesreportviewsOutEdgeDataViewr4   r   r!   r(   ranger   r   r`   r_   r/   r1   ziplist)rP   ry   rx   r   sg_partitiong_partitionsg_multiedgeg_multiedgerz   r   	sgc_to_gc	gc_to_sgcsNNsgcgcsgtgtsgt_gt_	new_orderNcolorsxnew_sg_pnew_g_pcextras   ````                       r   rb   z ISMAGS.create_aligned_partitions  s     	N+Lx=/Ka// ")RZZ-C-C-S-ST 2::+A+A+Q+QRK
S I
M &i:$Xw7 		L!3{#3A ((rE!H=GCtL-./Cd;r?+,B)59S>4==Q;PQTUVQW;XD&1(2,tzz"Q%7HA7OCT3' )#**.}o >:,8? ;!U"4\#5F4G H''22&7u&y~67r	;  ?**<]O L:-8N/L? K TR0A B**6s*;)<E'	"67r	;  "$	# #	"3 >8 AJ@Q
@QWS"\#B0@Q 	 
 i.25y/ B/Qa/ BHg "BgHR<.3BiNi1I;M\!_iENH~-H7msugE
&::GQ;-21XLX)9K[^XEL7me+GH~#e*(<<H'))#

 !C O Ms$   L4L#	L-L9	LLc              #   4  K   | j                   si  y| j                  syt        | j                        t        | j                         k  ryt        | j                        | j                  kD  ryt        | j
                        | j                  kD  ry|rD| j                         }|j                         D cg c]  \  }}|D ]  }||k7  s	||f  }}}}ng }| j                         | j                         }|j                         D ]"  \  }}	|   j                  t        |	             $ t        j                               rDt        fd      }
t        j                   |
    f|
<   | j#                  |
|      E d{    yc c}}}w 7 w)a  Find all subgraph isomorphisms between subgraph and graph

        Finds isomorphisms where :attr:`subgraph` <= :attr:`graph`.

        Parameters
        ----------
        symmetry: bool
            Whether symmetry should be taken into account. If False, found
            isomorphisms may be symmetrically equivalent.

        Yields
        ------
        dict
            The found isomorphism mappings of {graph_node: subgraph_node}.
        Nc                 .    t        d |    D              S )Nc              3   2   K   | ]  }t        |        y wr   r   r   r   s     r   r   z=ISMAGS.find_isomorphisms.<locals>.<lambda>.<locals>.<genexpr>  s     8VAQ   minrH   	cand_setss    r   <lambda>z*ISMAGS.find_isomorphisms.<locals>.<lambda>  s    S8VST8V5VrR   key)r`   r_   r   rd   rf   rj   rl   analyze_subgraph_symmetryr1   _get_node_color_candidate_sets_get_color_degree_candidatesr,   	frozensetanyvaluesr   intersection
_map_nodes)rP   symmetrycosetsrH   cscoconstraintslookahead_candidatessgnlookahead_cands	start_sgnr   s              @r   find_isomorphismszISMAGS.find_isomorphisms~  sn    $ }}H_s4==11$$%(:(::$$%(:(::335F06Wuq"2qTVwAr77KWK779	#@@B$8$>$>$@ CcNy9: %A y!"
 I+VWI$-$:$:Ii<P$Q#SIi y)[III% X" Js%   B7F:FFB4F	F
Fc                    t        | j                  | j                  | j                        }t        | j                  | j
                  | j                        }|j                         D ci c]M  \  }^}}||j                         D ch c]&  \  }^}t        fdt        |      D              r|( c}}}O c}}}}}S c c}}}w c c}}}}}w )z
        Returns a mapping of {subgraph node: set of graph nodes} for
        which the graph nodes are feasible mapping candidate_sets for the
        subgraph node, as determined by looking ahead one edge.
        c              3   j   K   | ]*  \  }}|j                         D ]  \  }}||   |   k    , y wr   )r1   )r   idxcountscolorsg_cntg_countss        r   r   z6ISMAGS._get_color_degree_candidates.<locals>.<genexpr>  sB      '?V)/v hsmE22)7 3'?s   03)
rJ   r_   rh   rn   r`   rg   rm   r1   r   r6   )rP   g_degsg_degr   _needed_countsgnr   s          `r   r   z#ISMAGS._get_color_degree_candidates  s     %TZZ$//R%dmmT5E5EtGWGWX -3LLN
 
 -;((a- */++-*7&BX '0'?  *7  -;
 	

s   / C+C:CCc              #     K   | j                   si  y| j                  sy|rD| j                         }|j                         D cg c]  \  }}|D ]  }||k7  s	||f  }}}}ng }| j	                         }t        |j                               rH| j                  d| j                   }t        d |D              h}	| j                  |||	      E d{    yyc c}}}w 7 w)a  
        Find the largest common induced subgraphs between :attr:`subgraph` and
        :attr:`graph`.

        Parameters
        ----------
        symmetry: bool
            Whether symmetry should be taken into account. If False, found
            largest common subgraphs may be symmetrically equivalent.

        Yields
        ------
        dict
            The found isomorphism mappings of {graph_node: subgraph_node}.
        Nc              3   .   K   | ]  }|D ]  }|   y wr   r   )r   prH   s      r   r   z1ISMAGS.largest_common_subgraph.<locals>.<genexpr>  s     %KAAaas   )r`   r_   r   r1   r   r   r   rd   rf   r   _largest_common_subgraph)
rP   r   r   rH   r   cnr   candidate_setsrelevant_partsto_be_mappeds
             r   largest_common_subgraphzISMAGS.largest_common_subgraph  s     $ }}H335F06Wuq"2qTVwAr77KWK<<>~$$&'!001E43E3EFN%%K%KKLL44\    Xs%   AC$CC"A2C$C"C$c           
      \   | j                   | j                  }}| j                  t        t	        | j
                  j                        t	        | j
                  j                        t	        t        t        t                    t	        |j                               | j
                  j                         f      }|| j                  v r| j                  |   S | j                  | j
                  ||      }| j                  | j
                  |||      }| j                  || j                  <   |S )aF  
        Find a minimal set of permutations and corresponding co-sets that
        describe the symmetry of ``self.subgraph``, given the node and edge
        equalities given by `node_partition` and `edge_colors`, respectively.

        Returns
        -------
        dict[collections.abc.Hashable, set[collections.abc.Hashable]]
            The found co-sets. The co-sets is a dictionary of
            ``{node key: set of node keys}``.
            Every key-value pair describes which ``values`` can be interchanged
            without changing nodes less than ``key``.
        )rd   rm   ra   hashtupler`   rc   ri   mapnode_partitionr1   rE   _refine_node_partition _process_ordered_pair_partitions)rP   r+   edge_colorsr   r   s        r   r   z ISMAGS.analyze_subgraph_symmetry  s    "&!4!4d6F6F;	+$----.$----.#e^45+++-.MM--/C d***++C00//y+V	66MM9i
 +(.D  %rR   c                 ~    t        | j                        t        | j                        k(  xr | j                  |      S )z
        Returns True if :attr:`graph` is isomorphic to :attr:`subgraph` and
        False otherwise.

        Returns
        -------
        bool
        )r   r`   r_   subgraph_is_isomorphicrP   r   s     r   is_isomorphiczISMAGS.is_isomorphic  s7     4==!S_4 
9T9T:
 	
rR   c                 B    t        | j                  |      d      }|duS )z
        Returns True if a subgraph of :attr:`graph` is isomorphic to
        :attr:`subgraph` and False otherwise.

        Returns
        -------
        bool
        r   N)r   subgraph_isomorphisms_iter)rP   r   isoms      r   r   zISMAGS.subgraph_is_isomorphic&  s)     D33X3FM4rR   c              #      K   t        | j                        t        | j                        k(  r| j                  |      E d{    yy7 w)z
        Does the same as :meth:`find_isomorphisms` if :attr:`graph` and
        :attr:`subgraph` have the same number of nodes.
        r   N)r   r_   r`   r   r   s     r   isomorphisms_iterzISMAGS.isomorphisms_iter5  s@     
 tzz?c$--00666III 1Is   AAA
Ac                 $    | j                  |      S )z/Alternative name for :meth:`find_isomorphisms`.)r   r   s     r   r   z!ISMAGS.subgraph_isomorphisms_iter=  s    %%h//rR   c                    t        t              }| j                  j                  D ]P  }| j                  |   }|| j
                  k\  r||    '||   j                  t        | j                  |                R t        |      S )ab  
        Per node in subgraph find all nodes in graph that have the same color.
        Stored as a dict-of-set-of-frozenset. The dict is keyed by node to a
        collection of frozensets of graph nodes. Each of these frozensets are
        a restriction. The node can be mapped only to nodes in the frozenset.
        Thus it must be mapped to nodes in the intersection of all these sets.
        We store the sets to delay taking the intersection of them. This helps
        for two reasons: Firstly any duplicate restriction sets can be ignored;
        Secondly, some nodes will not need the intersection to be constructed.
        Note: a dict-of-list-of-set would store duplicate sets in the list and
        we want to avoid that. But I wonder if checking hash/equality when `add`ing
        removes the benefit of avoiding computing intersections.
        )
r   r0   r`   rc   rg   rf   r,   r   re   dict)rP   r   r   	sgn_colors       r   r   z%ISMAGS._get_node_color_candidate_setsA  sx     %S)==&&C((-ID...s#s#''	$2D2DY2O(PQ ' N##rR   c                 d   fd}t        |      }t        |||      t        fd|D              sw|D cg c]=  }t        fd|D              r|gnt	        t        ||d      t              D ]  }| ? }}}t        |      }t        |||      t        fd|D              sw|S c c}}w )Nc                     |    |   k(  S r   r   node1node2color_degrees     r   equal_colorz2ISMAGS._refine_node_partition.<locals>.equal_colorZ      &,u*===rR   c              3   F   K   | ]  }t        fd |D                yw)c              3   (   K   | ]	  }|     y wr   r   r   rH   r   s     r   r   z:ISMAGS._refine_node_partition.<locals>.<genexpr>.<genexpr>_  s     #?QLOQr   Nr   r   r   r   s     r   r   z0ISMAGS._refine_node_partition.<locals>.<genexpr>_  s     SAm#?Q#??   !c              3   (   K   | ]	  }|     y wr   r   r   s     r   r   z0ISMAGS._refine_node_partition.<locals>.<genexpr>e  s     $Cd\!_dr   Fr2   r   )r9   rJ   r   r   sortedr4   r   )	clsr_   r+   r   r   node_colorsr#   r   r   s	           @r   r   zISMAGS._refine_node_partitionX  s    	> +95+E;LSSS &%D %$Cd$CC Ft[ NTWXY  Y %   /y9K/{KPL SSS s   AB,c           	   #     !K   | j                   }|j                  }| j                  }|j                  }| j                  }	| j                  }
|j                         }t        |      }t        |      D ci c]  \  }}||
 }}}i }i }||j                         }t        j                  ||    }|h||<   ||t        |      fg}|r|d   \  }}}|D ]  }||v r	||v r||   }||= |||<   |||<   t        |      t        |      k(  r|j                          ||= ||= O||j                         z
  }|j                         !|su||   }|j                         ||   j                         z
  }|D ]E  }||vr|}n'|	|
||f      }|D ch c]  }||v s|D ]  }|  }}}!|   t        |      hz  !|<   G n7||   j                         }|j                  |   j                         }|j                         ||   j                         z
  |j                  |   j                         z
  }|D ]  }||vr-||vr|}n|	|
||f      }|D ch c]  }||d   k(  s|d    }}nx||vr&|	|
||f      }|D ch c]  }||d   k(  s|d    }}nN|	|
||f      }|D ch c]  }||d   k(  s|d    }}|	|
||f      }||D ch c]  }||d   k(  s|d    c}z  }!|   t        |      hz  !|<    |D ]K  }||f|v rt        |||   dz   d       }n||f|v rt        |d||          }n7!|   t        |      hz  !|<   M t!        |!fd      }t        j                  !|    } | s| h!|<   |j#                  |!t        |       f        n |j%                          ||v r	|||   = ||= |ryyc c}}w c c}}w c c}w c c}w c c}w c c}w w)a  
        Find all subgraph isomorphisms honoring constraints.
        The collection `candidate_sets` is stored as a dict-of-set-of-frozenset.
        The dict is keyed by node to a collection of candidate frozensets. Any
        viable candidate must belong to all the frozensets in the collection.
        So each frozenset added to the collection is a restriction on the candidates.

        According to the paper, we store the collection of sets rather than their
        intersection to delay computing many intersections with the hope of avoiding
        them completely. Having the middle collection be a set also means that
        duplicate restrictions on candidates are ignored, avoiding another intersection.
        NrT   r	   r   c                 .    t        d |    D              S )Nc              3   2   K   | ]  }t        |        y wr   r   r   s     r   r   z6ISMAGS._map_nodes.<locals>.<lambda>.<locals>.<genexpr>  s     2P<a3q6<r   r   r   s    r   r   z#ISMAGS._map_nodes.<locals>.<lambda>  s    s2P9Q<2P/PrR   r   )r`   _adjr_   rk   rm   rE   r   r6   keysr   r   r   r   copyrF   r0   r   r-   pop)"rP   r   r   r   r   r`   subgraph_adjr_   	graph_adjself_ge_partitionself_sge_colorsrE   gn_ID_to_nodeidrH   gn_node_to_IDmappingrev_mappingsgn_candidatesqueuesgn_cand_iterr   old_gnleft_to_mapsgn_nbrsnot_gn_nbrssgn2	gn2_candsg_edgese	sgn_predsnew_sgnnew_sgn_candidatesr   s"                                    @r   r   zISMAGS._map_nodesm  s     ==}}

JJ	 ..****,U,5e,<=,<52qB,<=',,.L #//1DE-.s~tN';<=16r.C#$ '>$S\F#F+!"%Bw<3|#44%**,,#B*W\\^; +//1	 #+C0H"+.."2Yr]5G5G5I"IK +x/(3I&7T	8R&SG4;(RGqrQwPQ1PQGI(R +4D/Yy=Q<R*R	$ !,  ,C0557H (s 3 8 8 :I!(9R=+=+=+??%++b/BVBVBXX   !,x/#94,7	*;ODRUI<V*W;B,Q7abAaDjQqT7	,Q#94*;OCQUI<V*W;B,Q7abAaDjQqT7	,Q +<OCQUI<V*W;B,Q7abAaDjQqT7	,Q*;ODRUI<V*W )G-RGqrQqTzadG-R R	*3D/Yy=Q<R*R	$' !,* (DT{k1$'mB6G!6K6M(N$O	3$'6Ib8I(J$K	 &/o99M8N&NIdO ( %P &/%;%;Yw=O%P")&8%9	'"gy$7I2JKLw $z 		'>#GCL1G ) >t )S( -R -R -R-Rsu   A2O5N5D	O	N;
N;
 B,OOO!O6OOOO*O1OO
O
CO3"Oc              #   V  K   | t        | j                  j                        h}t        t	        t        |      g             }d}|t        | j                        k  rWt        |t              D ]C  }t        |fd      }| j                  |||      }	 t	        |      }	|	 |E d{    d}E |s|dk(  ryt               }
|D ]-  }|D ]&  }| j                  |||      }|
j                  |       ( / | j                  ||
      E d{    y7 i# t        $ r Y w xY w7 w)zI
        Find all largest common subgraphs honoring constraints.
        NFr   c                 .    t        d |    D              S )Nc              3   2   K   | ]  }t        |        y wr   r   r   s     r   r   zDISMAGS._largest_common_subgraph.<locals>.<lambda>.<locals>.<genexpr>  s     7V1Ar   r   )rH   
candidatess    r   r   z1ISMAGS._largest_common_subgraph.<locals>.<lambda>  s    C7V
ST7V4VrR   )r   Tr	   )r   r`   rc   r   r   r   r_   r   r   r   StopIterationr0   _remove_noder,   r   )rP   r   r   r   current_size	found_isorc   next_sgn	isomorphsr   left_to_be_mappedr   	new_nodess    `           r   r   zISMAGS._largest_common_subgraph  sH    
 %dmm&9&9:;L 4\ 2B78	3tzz?*  &9u*VW OOj+E , 	%	?D J((( $I! :& ) E!E !--c5+F	!%%i0  " 002C 1 
 	
 	
3 )	 % :	
sC   BD)D#	D),D-A#D)D'D)	D$!D)#D$$D)c                 T    	 |D ]  \  }}|| k(  s||v s|}  n t        || hz
        S ()a&  
        Returns a new set where node has been removed from nodes, subject to
        symmetry constraints. We know, that for every constraint we have
        those subgraph nodes are equal. So whenever we would remove the
        lower part of a constraint, remove the higher instead.
        )r   )r8   rc   r   lowhighs        r   r"  zISMAGS._remove_node@  sB     (	T$;45=D )
 !$00 rR   c                     t        t              | D ]  }t        |         j                  |       ! t        t	        j
                  fdt              D               S )a  
        Get all permutations of items, but only permute items with the same
        length.

        >>> found = list(ISMAGS._get_permutations_by_length([{1}, {2}, {3, 4}, {4, 5}]))
        >>> answer = [
        ...     (({1}, {2}), ({3, 4}, {4, 5})),
        ...     (({1}, {2}), ({4, 5}, {3, 4})),
        ...     (({2}, {1}), ({3, 4}, {4, 5})),
        ...     (({2}, {1}), ({4, 5}, {3, 4})),
        ... ]
        >>> found == answer
        True
        c              3   N   K   | ]  }t        j                  |           y wr   )r!   permutations)r   lby_lens     r   r   z5ISMAGS._get_permutations_by_length.<locals>.<genexpr>f  s!     L^)((3^s   "%)r   r   r   r-   r!   r(   r   )r1   r   r0  s     @r   _get_permutations_by_lengthz"ISMAGS._get_permutations_by_lengthP  sY      T"D3t9$$T*  LVF^L
 	
rR   c           
   #   J  K   fd}| j                  |||      }|g}|ru|j                         }t        |      }t        |||      t	        fd|D              rt        |      t        |      k(  r||f ]g g}|D ]  }	t        |	      dk(  st        fd|	D              r|D ]  }
|
j                  |	        >t        |	|d      }t        |      }|dk(  s%|t        |D ch c]  }t        |       c}      k(  r(|D ]"  }|j                  t        |t
                     $ | j                  |      }g }|D ]5  }|D ].  }|D cg c]  }|D ]  }|  }}}|j                  ||z          0 7 |} |j                  |d d d          |rty y c c}w c c}}w w)	Nc                     |    |   k(  S r   r   r   s     r   r   z'ISMAGS._refine_opp.<locals>.equal_colork  r   rR   c              3   F   K   | ]  }t        fd |D                yw)c              3   (   K   | ]	  }|     y wr   r   r   s     r   r   z/ISMAGS._refine_opp.<locals>.<genexpr>.<genexpr>u  s      <!Qa!r   Nr   r   s     r   r   z%ISMAGS._refine_opp.<locals>.<genexpr>u  s     Mf= <! <<fr   r	   c              3   (   K   | ]	  }|     y wr   r   )r   r8   r   s     r   r   z%ISMAGS._refine_opp.<locals>.<genexpr>~  s     2WRV$<3ERVr   Fr   r   rT   )r   r  r9   rJ   r   r   r   r-   r4   extendr   r1  )r   r_   topbottomr   r   possible_bottomsr   more_bottomsr#   
new_bottomrefined_partRr   n_pr.  new_partitionsnew_partitiontupsflat_pr   s                        @r   _refine_oppzISMAGS._refine_oppj  s    	> (([A"8%))+F.v6K/{KPLMfMMs8s6{*v+% 4Lt9>]2WRV2W%W&2
"))$/ '3 $2$5#QLL)AAvc<*H<a3q6<*H&I!I#/CJJvl'DE $0 (+'F'F|'T *,-9M%178)Fq#Q!#!q)F . 5 5mf6L M &2 .:
 (65 : ##L2$67S ( +I  *Gs%   C$F#'F9AF#F8F#F#c                 4   t               }t        | |      D ]~  \  }}t        |      dkD  st        |      dkD  r||k(  r(t        d|  d|       ||k7  s?|j	                  t        t        t        |            t        t        |            f              |S )a6  
        Return a set of 2-tuples of nodes. These nodes are not equal
        but are mapped to each other in the symmetry represented by this OPP.
        Swapping all the 2-tuples of nodes in this set permutes the nodes
        but retains the graph structure. Thus it is a symmetry of the subgraph.
        r	   z/Not all nodes are matched. This is impossible: z, )r0   r   r   
IndexErrorr,   r   r   r   )top_partitionbottom_partitionr.  r8  bots        r   _find_permutationszISMAGS._find_permutations  s     uM+;<HC3x!|s3x!|
 #: $$1?"5E4FH 
   DcOT$s)_+M!NO = rR   c                   "#$ t        d |D              ri S d}t        |      D ci c]  \  }}||
 }}}|D cg c]  }|h }	}i }
t        |      D ci c]  \  }}||
 c}}""j                  $"$fd}g } ||||       |r|d   \  }}}}}|D ]k  }||k7  r||   ||   k(  r|h}|h}|D cg c]  }|j                          }}||xx   |z  cc<   |j	                  ||       |D cg c]  }|j                          }}||xx   |z  cc<   |j	                  ||       | j                  ||||      }g }|D ]  }|rt        d |d   D              rd}t        d t        | D              rj | j                  | }|D ]U  \  }}||   #||   } #| k7  s|	|    }!|	#   j                  |!       t               |	| <   |j                  #fd	|!D               W  ||g|   |j                  |d d d           n. |j                          ||
vr|	||      j                         |
|<   |r|
S c c}}w c c}w c c}}w c c}w c c}w )
Nc              3   8   K   | ]  }t        |      d k    ywr	   Nr   r   r8  s     r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>  s     6s3x1}   Tc                     	fdt        |      D        }t        |      \  }}}||   }t        t        |
            }| j	                  |||||g       y )Nc              3   ^   K   | ]$  \  }}|D ]  }t        |      d kD  r
|   ||f  & ywrN  r   )r   r   t_partr8   
node_to_IDs       r   r   zZISMAGS._process_ordered_pair_partitions.<locals>._load_next_queue_entry.<locals>.<genexpr>  sA      #;KC"Dv;? D!4-" .#;s   *-r   )r6   r   r   r   r-   )r  rH  rI  unmapped_nodesr   r8   part_ib_part
node2_iterrT  
sort_by_IDs            r   _load_next_queue_entryzGISMAGS._process_ordered_pair_partitions.<locals>._load_next_queue_entry  s\    #,]#;N ".1OAtV%f-FfV<=JLL-)94TUrR   rT   c              3   8   K   | ]  }t        |      d k    ywrN  r   rO  s     r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>  s     ?s3x1}rP  r   Fc              3   L   K   | ]  \  }}t        |      d k  xs ||k(    ywrN  r   )r   tbs      r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>  s)     Iytq!SVq[2AF2ys   "$c              3   &   K   | ]  }|f 
 y wr   r   )r   rH   orb1s     r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>  s     /N:aD	:s   )r   r6   rV   r  insertrE  r   rK  updater0   r7  r  )%rP   r_   rH  rI  r   finding_identityorbit_ir8   orbit_idorbitsr   irH   rZ  r  topsbottomsrV  rX  r   new_top_partnew_bot_partr8  new_toprJ  new_botoppsnew_qoppr.  n1n2orb2
orbit_set2rT  r`  rY  s%                                     @@@r   r   z'ISMAGS._process_ordered_pair_partitions  s    666I  7@7GH7GmgtD'M7GH%*+UT4&U+'0'78'7tq!ad'78
++
	V um5EF6;Bi3D'4#5=Xd^x%F !%v %w156#388:6</v|4189#388:9</v|4 ''wMC ( ?A??/4,$IsCyII (?t'>'>'D&2FB#+B<D#+B<D#t|-3D\
 &t 3 3J ?/2ut (/N:/N N '3 !*57377  : U4R4[)e $h 		v% $*(4.#9#>#>#@F4Ly z k I+ 9@ 7 :s   H9
H?I6I
2I)NNNT)Fr   )rX   rY   rZ   r[   rQ   rb   r   r   r   r   r   r   r   r   r   classmethodr   r   r   staticmethodr"  r1  rE  rK  r   r   rR   r   r   r   <  s    iV5Sn]*~5n
,(T"H
 J0$.  (N%`A
F 1 1 
 
208d  4crR   ru  )r[   __all__r!   collectionsr   r   	functoolsr   r   networkxr.   r   r4   r9   rJ   rL   r   r   rR   r   <module>r|     sO   xt *  , # 3<A-HL"$N& &<^ ^rR   