
    9 j                         S r S/rSSKrSSKJrJr  SSKJrJr  SSK	r
S rSS jrS rS	 r " S
 S5      r " S S5      rg)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                    ^  U R                   n[        U5      S:  a  Sn[        U5      Se [	        U 5      n[        US5      m[        U4S jU 5       5      $ ! [         a     N8f = f)a6  
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   ,   >#    U  H	  oT:H  v   M     g 7fN ).0itemfirsts     w/root/GenerationalWealth/GenerationalWealth/venv/lib/python3.13/site-packages/networkx/algorithms/isomorphism/ismags.py	<genexpr> are_all_equal.<locals>.<genexpr>   s     2u}s   )shapelenNotImplementedErrorAttributeErroriternextall)iterabler   messageiteratorr   s       @r   are_all_equalr      sq     9 u:>OG%g.D8  H~H4 E2222  s   A 
A&%A&c                   ^^ / mU  HS  nT H8  n[        [        U5      5      nT" X55      (       d  M&  UR                  U5          M?     TR                  U15        MU     U(       ao  [	        U4S jT 5       5      (       d  [
        R                  " ST ST< 35      e[	        UU4S jT 5       5      (       d  [
        R                  " ST ST< 35      eT Vs/ s H  n[        U5      PM     sn$ s  snf )aD  
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      >#    U  H=  n[         R                  " US 5        H  u  p#T" X#5      =(       a    T" X25      v   M      M?     g7f)   N)	itertoolscombinations)r   partt1t2tests       r   r   !make_partition.<locals>.<genexpr>   sD      
!#00q9 L)T"\)9 *!s   AAz 
Invalid partition created with z=.
Some items in a part do not match. This leads to
partition=c              3      >#    U  HW  nT  HM  nX:w  d  M
  [         R                  " X5        H(  u  p4T" X45      (       + =(       a    T" XC5      (       + v   M*     MO     MY     g 7fr   )r!   product)r   p1p2r$   r%   	partitionr&   s        r   r   r'      sc      
x 2 $++B3	 R1T"\!11 4	 2 2s   A"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_partitionr5      s   @ ID$t*%FD!!	  dV$   
!
 
 

 ""3D6 :,  
  

 
 
 ""3D6 :,  
 #,,)$CI),,,s   C0c                 h    [        U 5       VVVs0 s H  u  pU  H  o3U_M     M     snnn$ s  snnnf )z
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_dictr:      s/     &/y%9K%9dd"HdD%9KKKs   -c                 2  ^^^ [        T5      [        U 5      :  a5  U R                  5        H!  u  p4UT;  d  M  STU'   U H	  nSTX54'   M     M#     U R                  5       (       dB  U R                  5        V^Vs0 s H#  u  mnTTT   [        UUU4S jU 5       5      4_M%     snn$ U R                  5        V^Vs0 s HF  u  mnTTT   [        UUU4S jU 5       5      [        UUU4S jU R                  T    5       5      4_MH     snn$ s  snnf s  snnf )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   <   >#    U  H  nTTU4   TU   4v   M     g 7fr   r   r   ve_colorsn_colorsus     r   r   'color_degree_by_node.<locals>.<genexpr>  s"     $QDqhq!tnhqk%BD   c              3   <   >#    U  H  nTTU4   TU   4v   M     g 7fr   r   r=   s     r   r   rB     s"     @4aXad^Xa[14rC   c              3   <   >#    U  H  nTUT4   TU   4v   M     g 7fr   r   r=   s     r   r   rB     s"     F:aXad^Xa[1:rC   )r   	adjacencyis_directedr   _pred)Gr@   r?   nnbrsr>   rA   s    ``   `r   color_degree_by_noderL      s     8}s1v{{}GA "A%)HQTN  % ==?? ;;=
(4 W$QD$QQRR(
 	
 {{} %GAt 	
QK@4@@F1771:FF
 	

 % 

s   :*D<ADc                   *    \ rS rSrSrS rS rS rSrg)
EdgeLookupi  a  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                     Xl         g r   	edge_dict)selfrQ   s     r   __init__EdgeLookup.__init__0  s    "    c                 h    XR                   ;   a  U R                   U   $ U R                   US S S2      $ )NrP   )rR   edges     r   __getitem__EdgeLookup.__getitem__3  s2    >>!>>$''~~d4R4j))rU   c                 6    U R                   R                  5       $ r   )rQ   r2   )rR   s    r   r2   EdgeLookup.items8  s    ~~##%%rU   rP   N)	__name__
__module____qualname____firstlineno____doc__rS   rY   r2   __static_attributes__r   rU   r   rN   rN     s    "#*
&rU   rN   c                       \ rS rSrSrSS jrS rSS jrS rSS jr	S	 r
SS
 jrSS jrSS jrSS jrS r\S 5       rSS jrSS jr\S 5       r\S 5       rS r\S 5       rS rSrg)r   i<  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                 j   UR                  5       UR                  5       :w  a  [        S5      eXl        X l        XPl        U R                  X0R                  R                  U R                  R                  5      nUu  U l        U l        U l	        [        U R                  5      U l        [        U R                  5      U l        U R                  X@R                  R                  5       U R                  R                  5       5      nUu  U l        U l        U l        U R                  R                  5       (       a5  [        U R                  5      U l        [        U R                  5      U l        g['        [        U R                  5      5      U l        ['        [        U R                  5      5      U l        g)an  
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)rG   
ValueErrorgraphsubgraph_symmetry_cachecreate_aligned_partitionsnodes_sgn_partition_gn_partitionN_node_colorsr:   _sgn_colors
_gn_colorsedges_sge_partition_ge_partitionN_edge_colors_sge_colors
_ge_colorsrN   )rR   rf   rg   
node_match
edge_matchcache
node_partsedge_partitionss           r   rS   ISMAGS.__init__  sV   . ("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RDOrU   c                   ^ ^^^ Tc  [        T5      /n[        T5      /nXES4$ [        T[        R                  R                  R
                  5      n[        T[        R                  R                  R
                  5      nU(       d  UU4S jnOU U4S jnU(       d  UU4S jn	OU U4S jn	[        TU5      n[        TU	5      n0 n
0 n[        U5      [        U5      p[        R                  " [        U5      [        U5      5       GH  u  p[        [        XN   5      5      n[        [        X_   5      5      nU(       d  TU   OT R                  US      US      nU(       d  TU   OT R                  US      US      nT" UU5      (       d  M  X;   a3  [        R                  " ST SU< S	U S
XN    SX_    SXZU       S35      eX;   a7  [        R                  " ST SU< SU< S	U SX_    SXN    SXKU       S35      eXU'   XU'   GM	     U
R!                  5        VVs/ s H  u  pXN   X_   4PM     nnn[        U5      nU(       a&  [#        U6  Vs/ s H  n[%        U5      PM     snu  nnO/ / nnUU:  aW  [        U5       Vs/ s H  nUU
;  d  M  UU   PM     nn[%        U5      U-   n[%        U5      [        5       /[        U5      -  -   nUU:  aW  [        U5       Vs/ s H  nUU;  d  M  UU   PM     nn[%        U5      U-   n[%        U5      [        5       /[        U5      -  -   nUUU4$ s  snnf s  snf s  snf s  snf )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                 "   > T" TU    TU   5      $ r   r   )thing1thing2	sg_thingsthing_matchers     r   sg_match2ISMAGS.create_aligned_partitions.<locals>.sg_match5  s    $Yv%6	&8IJJrU   c                 f   > Xsu  p#u  pET" TR                   U   U   TR                   U   U   5      $ r   )rg   r~   r   u1v1u2v2rR   r   s         r   r   r   :  s:    %+"(2$T]]2%6r%:DMM"<Mb<QRRrU   c                 "   > T" TU    TU   5      $ r   r   )r~   r   g_thingsr   s     r   g_match1ISMAGS.create_aligned_partitions.<locals>.g_match@  s    $Xf%5x7GHHrU   c                 f   > Xsu  p#u  pET" TR                   U   U   TR                   U   U   5      $ r   )rf   r   s         r   r   r   E  s8    %+"(2$TZZ^B%7B9KLLrU   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 )r1   
isinstancer/   classesreportviewsOutEdgeDataViewr5   r   r!   r)   ranger   r   rg   rf   r0   r2   ziplist)rR   r   r   r   sg_partitiong_partitionsg_multiedgeg_multiedger   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   ri    ISMAGS.create_aligned_partitions  sr     	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;?+,B)59S>4==Q;PQTUVQW;XD&1(2,tzz"Q%7HA7OCT3'' #**.}o >:,8? ;!U"4\5F4G H''2&7u&~67r	;  ?**<]O L:-8N/L? K T0A B**6*;)<E'"67r	;  "$# #"3 >8 AJ@Q
@QWS\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$   $L6L<

M	M'
M5	Mc              #   l  ^#    U R                   (       d  0 v   gU R                  (       d  g[        U R                  5      [        U R                   5      :  a  g[        U R                  5      U R                  :  a  g[        U R
                  5      U R                  :  a  gU(       aI  U R                  5       nUR                  5        VVVs/ s H  u  p4U  H  oSU:w  d  M
  X54PM     M     nnnnO/ nU R                  5       mU R                  5       nUR                  5        H"  u  pTU   R                  [        U	5      5        M$     [        TR                  5       5      (       aC  [        TU4S jS9n
[        R                   " TU
   6 4TU
'   U R#                  U
TU5       Sh  vN   gs  snnnf  N7f)ai  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                 .   > [        S TU     5       5      $ )Nc              3   8   #    U  H  n[        U5      v   M     g 7fr   r   r   r   s     r   r   =ISMAGS.find_isomorphisms.<locals>.<lambda>.<locals>.<genexpr>  s     8VAQ   minrJ   	cand_setss    r   <lambda>*ISMAGS.find_isomorphisms.<locals>.<lambda>  s    S8VST8V5VrU   key)rg   rf   r   rk   rm   rq   rs   analyze_subgraph_symmetryr2   _get_node_color_candidate_sets_get_color_degree_candidatesr-   	frozensetanyvaluesr   intersection
_map_nodes)rR   symmetrycosetsrJ   cscoconstraintslookahead_candidatessgnlookahead_cands	start_sgnr   s              @r   find_isomorphismsISMAGS.find_isomorphisms~  sl    $ }}H_s4==11$$%(:(::$$%(:(::335F06Wuq2TVw7A77KWK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%   C	F4F+!F+,B9F4%F2&F4c                   ^ [        U R                  U R                  U R                  5      n[        U R                  U R
                  U R                  5      nUR                  5        VVVVV^s0 s HX  u  ntpEUUR                  5        VVV^s1 s H/  u  ntnm[        U4S j[        U5       5       5      (       d  M-  UiM1     snnn_MZ     snnnnn$ s  snnnf s  snnnnnf )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   r   >#    U  H,  u  pUR                  5         H  u  p4UTU   U   :*  v   M     M.     g 7fr   )r2   )r   idxcountscolorsg_cntg_countss        r   r   6ISMAGS._get_color_degree_candidates.<locals>.<genexpr>  s=      '?)/ hsmE22)7 3'?s   47)
rL   rf   ro   ru   rg   rn   rt   r2   r   r7   )rR   g_degsg_degr   _needed_countsgnr   s          `r   r   #ISMAGS._get_color_degree_candidates  s     %TZZ$//R%dmmT5E5EtGWGWX -3LLN
 
 -;((a */++-*7&BX '0'?  *7  -;
 	

s   / C,C?C	CCc              #     #    U R                   (       d  0 v   gU R                  (       d  gU(       aI  U R                  5       nUR                  5        VVVs/ s H  u  p4U  H  oSU:w  d  M
  X54PM     M     nnnnO/ nU R	                  5       n[        UR                  5       5      (       aG  U R                  SU R                   n[        S U 5       5      1n	U R                  XvU	5       Sh  vN   ggs  snnnf  N7f)aU  
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   6   #    U  H  o  H  o"v   M     M     g 7fr   r   )r   prJ   s      r   r   1ISMAGS.largest_common_subgraph.<locals>.<genexpr>  s     %KAAaas   )rg   rf   r   r2   r   r   r   rk   rm   r   _largest_common_subgraph)
rR   r   r   rJ   r   cnr   candidate_setsrelevant_partsto_be_mappeds
             r   largest_common_subgraphISMAGS.largest_common_subgraph  s     $ }}H335F06Wuq2TVw7A77KWKK<<>~$$&''!001E43E3EFN%%K%KKLL44\    Xs%   AC<C3+C36A6C<,C:-C<c           
      Z   U R                   U R                  p!U R                  b  [        [	        U R
                  R                  5      [	        U R
                  R                  5      [	        [        [        [        5      5      [	        UR                  5       5      U R
                  R                  5       45      nX0R                  ;   a  U R                  U   $ U R                  U R
                  X5      nU R                  U R
                  XU5      nU R                  b  X@R                  W'   U$ )a  
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``.
)rk   rt   rh   hashtuplerg   rj   rp   mapnode_partitionr2   rG   _refine_node_partition _process_ordered_pair_partitions)rR   r,   edge_colorsr   r   s        r   r    ISMAGS.analyze_subgraph_symmetry  s     "&!4!4d6F6F;+$----.$----.#e^45+++-.MM--/C ***++C00//yV	66MM9
 +(.  %rU   c                     [        U R                  5      [        U R                  5      :H  =(       a    U R                  U5      $ )zl
Returns True if :attr:`graph` is isomorphic to :attr:`subgraph` and
False otherwise.

Returns
-------
bool
)r   rg   rf   subgraph_is_isomorphicrR   r   s     r   is_isomorphicISMAGS.is_isomorphic  s7     4==!S_4 
9T9T:
 	
rU   c                 <    [        U R                  US9S5      nUSL$ )zz
Returns True if a subgraph of :attr:`graph` is isomorphic to
:attr:`subgraph` and False otherwise.

Returns
-------
bool
r   N)r   subgraph_isomorphisms_iter)rR   r   isoms      r   r   ISMAGS.subgraph_is_isomorphic&  s)     D33X3FM4rU   c              #      #    [        U R                  5      [        U R                  5      :X  a  U R                  US9 Sh  vN   gg N7f)zq
Does the same as :meth:`find_isomorphisms` if :attr:`graph` and
:attr:`subgraph` have the same number of nodes.
r   N)r   rf   rg   r   r   s     r   isomorphisms_iterISMAGS.isomorphisms_iter5  s?     
 tzz?c$--00666III 1Is   ?A
AA
c                 $    U R                  U5      $ )z/Alternative name for :meth:`find_isomorphisms`.)r   r   s     r   r   !ISMAGS.subgraph_isomorphisms_iter=  s    %%h//rU   c                 
   [        [        5      nU R                  R                   HP  nU R                  U   nX0R
                  :  a  X     M'  X   R                  [        U R                  U   5      5        MR     [        U5      $ )a  
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   r1   rg   rj   rn   rm   r-   r   rl   dict)rR   r   r   	sgn_colors       r   r   %ISMAGS._get_node_color_candidate_setsA  sr     %S)==&&C((-I...##''	$2D2DY2O(PQ ' N##rU   c                   ^ U4S jn[        U5      n[        XU5      m[        U4S jU 5       5      (       d  U VVs/ s H@  n[        U4S jU 5       5      (       a  U/O[	        [        XdSS9[        S9  H  nUPM     MB     nnn[        U5      n[        XU5      m[        U4S jU 5       5      (       d  M  U$ s  snnf )Nc                    > TU    TU   :H  $ r   r   node1node2color_degrees     r   equal_color2ISMAGS._refine_node_partition.<locals>.equal_colorZ      &,u*===rU   c              3   N   >#    U  H  n[        U4S  jU 5       5      v   M     g7f)c              3   .   >#    U  H
  nTU   v   M     g 7fr   r   r   rJ   r	  s     r   r   :ISMAGS._refine_node_partition.<locals>.<genexpr>.<genexpr>_  s     #?QLOQ   Nr   r   r   r	  s     r   r   0ISMAGS._refine_node_partition.<locals>.<genexpr>_  s     SAm#?Q#???   "%c              3   .   >#    U  H
  nTU   v   M     g 7fr   r   r  s     r   r   r  e  s     $Cd\!_dr  Fr3   r   )r:   rL   r   r   sortedr5   r   )	clsrf   r,   r   r
  node_colorsr#   r   r	  s	           @r   r   ISMAGS._refine_node_partitionX  s    	> +95+ELSSSS &%D %$Cd$CCC Ft NTWXY  Y %   /y9K/KPL SSSS s   AB=c           	   #   8  ^!#    U R                   nUR                  nU R                  nUR                  nU R                  n	U R                  n
UR                  5       n[        U5      n[        U5       VVs0 s H  u  pX_M	     nnn0 n0 nUc  UR                  5       n[        R                  " X!   6 nU1X!'   X[        U5      4/nU(       Ga,  US   u  pnU GH  nUU;   a  M  UU;   a  UU   nUU	 UUU'   UUU'   [        U5      [        U5      :X  a  UR                  5       v   UU	 UU	 MV  UUR                  5       -
  nUR                  5       m!U(       d  Xa   nUR                  5       UU   R                  5       -
  nU HO  nUU;  a  UnO.XUU4      nU VVs1 s H  nUU;   d  M  U  H  oiM     M     nnnT!U   [        U5      1-  T!U'   MQ     GONXa   R                  5       nUR                  U   R                  5       nUR                  5       UU   R                  5       -
  UR                  U   R                  5       -
  nU H  nUU;  a3  UU;  a  UnOXUU4      nU Vs1 s H  nUUS   :X  d  M  US   iM     nnOUU;  a*  XUU4      nU Vs1 s H  nUUS   :X  d  M  US   iM     nnOVXUU4      nU Vs1 s H  nUUS   :X  d  M  US   iM     nnXUU4      nUU Vs1 s H  nUUS   :X  d  M  US   iM     sn-  nT!U   [        U5      1-  T!U'   M     U HP  nUU4U;   a  [        XU   S-   S 5      nOUU4U;   a  [        USUU    5      nOM;  T!U   [        U5      1-  T!U'   MR     [!        UU!4S jS9n[        R                  " T!U   6 n U (       d  GM  U 1T!U'   UR#                  UT![        U 5      45          O!   UR%                  5         UU;   a	  UUU   	 UU	 U(       a  GM+  ggs  snnf s  snnf s  snf s  snf s  snf s  snf 7f)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.
NrW   r	   r   c                 .   > [        S TU     5       5      $ )Nc              3   8   #    U  H  n[        U5      v   M     g 7fr   r   r   s     r   r   6ISMAGS._map_nodes.<locals>.<lambda>.<locals>.<genexpr>  s     2P<a3q66<r   r   r   s    r   r   #ISMAGS._map_nodes.<locals>.<lambda>  s    s2P9Q<2P/PrU   r   )rg   _adjrf   rr   rt   rG   r   r7   keysr   r   r   r   copyrH   r1   r   r.   pop)"rR   r   r   r   r   rg   subgraph_adjrf   	graph_adjself_ge_partitionself_sge_colorsrG   gn_ID_to_nodeidrJ   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   ISMAGS._map_nodesm  s     ==}}

JJ	 ..****,U,5e,<=,<52,<=',,.L #//1DE-.tN';<=16r.C#$ '>$S\F#F+!"%Bw<3|#44%**,,#B*W\\^; +//1	 #+0H"+.."2Yr]5G5G5I"IK +x/(3I&7T	8R&SG4;(RGqrQwPQ1PQGI(R +4D/Yy=Q<R*R	$ !,  ,0557H (s 3 8 8 :I!(9R=+=+=+??%++b/BVBVBXX   !,x/#94,7	*;DRUI<V*W;B,Q7abAaDjTQqT7	,Q	#94*;CQUI<V*W;B,Q7abAaDjTQqT7	,Q	 +<CQUI<V*W;B,Q7abAaDjTQqT7	,Q*;DRUI<V*W )G-RGqrQqTzdadG-R R	*3D/Yy=Q<R*R	$' !,* (DT{k1$'B6G!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 e) >t )S( -R -R -R-Rsu   A2P5O:DP
P 
)P 
8B0P(P9	PPP)	P2PP	PP,P
=	P
C/P8"Pc              #   Z  ^#    Uc   [        U R                  R                  5      1n[        [	        [        U5      / 5      5      nSnU[        U R                  5      ::  aP  [        U[        S9 H>  n[        UU4S jS9nU R                  UTX&S9n [	        U5      n	U	v   U Sh  vN   SnM@     U(       d  US:X  a  g[        5       n
U H/  nU H&  nU R                  XU5      nU
R                  U5        M(     M1     U R                  TX*S9 Sh  vN   g Np! [         a     M  f = f N7f)z9
Find all largest common subgraphs honoring constraints.
NFr   c                 .   > [        S TU     5       5      $ )Nc              3   8   #    U  H  n[        U5      v   M     g 7fr   r   r   s     r   r   DISMAGS._largest_common_subgraph.<locals>.<lambda>.<locals>.<genexpr>  s     7V1Ar   r   )rJ   
candidatess    r   r   1ISMAGS._largest_common_subgraph.<locals>.<lambda>  s    C7V
ST7V4VrU   )r   Tr	   )r   rg   rj   r   r   r   rf   r  r   r   StopIterationr1   _remove_noder-   r   )rR   rA  r   r   current_size	found_isorj   next_sgn	isomorphsr   left_to_be_mappedr   	new_nodess    `           r   r   ISMAGS._largest_common_subgraph  s@    
 %dmm&9&9:;L 4\ 2B78	3tzz?*  &9u*VW OOj+ , 	%	?D J((( $I! :& ) E!E !--c+F	!%%i0  " 00 1 
 	
 	
3 )	 % :	
sC   BD+D	D+%D&A*D+D)D+
D&"D+%D&&D+c                 Z     U H  u  p4X0:X  d  M  XA;   d  M  Un   O   [        X1-
  5      $ M+  )z
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   )r9   rj   r   lowhighs        r   rD  ISMAGS._remove_node@  s:     (	;4=D )
 !00 rU   c                    ^ [        [        5      mU  H   nT[        U5         R                  U5        M"     [        [        R
                  " U4S j[        T5       5       6 5      $ )am  
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   V   >#    U  H  n[         R                  " TU   5      v   M      g 7fr   )r!   permutations)r   lby_lens     r   r   5ISMAGS._get_permutations_by_length.<locals>.<genexpr>f  s#     L^)((33^s   &))r   r   r   r.   r!   r)   r  )r2   r   rT  s     @r   _get_permutations_by_length"ISMAGS._get_permutations_by_lengthP  sY      T"D3t9$$T*  LVF^L
 	
rU   c           
   #     ^#    U4S jnU R                  XU5      nU/nU(       Ga  UR                  5       n[        U5      n[        XU5      m[	        U4S jU 5       5      (       a  [        U5      [        U5      :X  a  X#4v   Mh  / /nU GH  n	[        U	5      S:X  d  [        U4S jU	 5       5      (       a  U H  n
U
R                  U	5        M     MI  [        XSS9n[        U5      nUS:X  d)  U[        U Vs1 s H  n[        U5      iM     sn5      :X  a(  U H   nUR                  [        U[
        S95        M"     M  U R                  U5      n/ nU H>  nU H5  nU VVs/ s H  nU  H  nUPM     M     nnnUR                  UU-   5        M7     M@     UnGM     UR                  US S S2   5        U(       a  GM  g g s  snf s  snnf 7f)	Nc                    > TU    TU   :H  $ r   r   r  s     r   r
  'ISMAGS._refine_opp.<locals>.equal_colork  r  rU   c              3   N   >#    U  H  n[        U4S  jU 5       5      v   M     g7f)c              3   .   >#    U  H
  nTU   v   M     g 7fr   r   r  s     r   r   /ISMAGS._refine_opp.<locals>.<genexpr>.<genexpr>u  s      <!Qa!r  Nr  r  s     r   r   %ISMAGS._refine_opp.<locals>.<genexpr>u  s     Mf= <! <<<fr  r	   c              3   .   >#    U  H
  nTU   v   M     g 7fr   r   )r   r9   r	  s     r   r   r^  ~  s     2WRV$<3ERVr  Fr  r   rW   )r   r$  r:   rL   r   r   r   r.   r5   extendr  rV  )r  rf   topbottomr   r
  possible_bottomsr  more_bottomsr#   
new_bottomrefined_partRr   n_prR  new_partitionsnew_partitiontupsflat_pr	  s                        @r   _refine_oppISMAGS._refine_oppj  s    	> (([A"8%))+F.v6K/KPLMfMMMs8s6{*+% 4Lt9>]2WRV2W%W%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&   C7G:GAG#G9AG Gc                 <   [        5       n[        X5       H  u  p4[        U5      S:  d  [        U5      S:  a  X4:X  a  M*  [        SU  SU 35      eX4:w  d  MB  UR	                  [        [        [        U5      5      [        [        U5      5      45      5        M     U$ )a  
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, )r1   r   r   
IndexErrorr-   r   r   r   )top_partitionbottom_partitionrR  ra  bots        r   _find_permutationsISMAGS._find_permutations  s     uM<HC3x!|s3x!|
 : $$1?"5E4FH 
   DcOT$s)_+M!NO = rU   c                   ^"^#^$ [        S U 5       5      (       a  0 $ Sn[        U5       VVs0 s H  u  pgXv_M	     nnnU Vs/ s H  ow1PM     n	n0 n
[        U5       VVs0 s H  u  pX_M	     snnm"T"R                  m$U"U$4S jn/ nU" XU5        U(       Ga  US   u  nnnnnU GH  nUU:w  a  X   UU   :X  a  M  U1nU1nU Vs/ s H  nUR                  5       PM     nnUU==   U-  ss'   UR	                  UU5        U Vs/ s H  nUR                  5       PM     nnUU==   U-  ss'   UR	                  UU5        U R                  UUUU5      n/ nU H  nU(       a  [        S US    5       5      (       a  SnM(  O[        S [        U6  5       5      (       ap  U R                  " U6 nU HY  u  nnUU   m#UU   n T#U :w  d  M  U	U    n!U	T#   R                  U!5        [        5       U	U '   UR                  U#4S	 jU! 5       5        M[     M  U" U/UQ76   M     UR                  US S S2   5          O.   UR                  5         Xz;  a  XU      R                  5       X'   U(       a  GM  U
$ s  snnf s  snf s  snnf s  snf s  snf )
Nc              3   >   #    U  H  n[        U5      S :*  v   M     g7fr	   Nr   r   ra  s     r   r   :ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>  s     6s3x1}   Tc                    > U	4S j[        U5       5       n[        U5      u  pEnX&   n[        [        UT
S95      nU R	                  XXVU/5        g )Nc              3   l   >#    U  H)  u  pU  H  n[        U5      S :  d  M  TU   X14v   M     M+     g7fry  r   )r   r   t_partr9   
node_to_IDs       r   r   ZISMAGS._process_ordered_pair_partitions.<locals>._load_next_queue_entry.<locals>.<genexpr>  s>      #;KC"Dv;? .D!4-" .#;s   44r   )r7   r   r   r  r.   )r/  rr  rs  unmapped_nodesr   r9   part_ib_part
node2_iterr  
sort_by_IDs            r   _load_next_queue_entryGISMAGS._process_ordered_pair_partitions.<locals>._load_next_queue_entry  sS    #,]#;N ".1OAV%-FfV<=JLL-4TUrU   rW   c              3   >   #    U  H  n[        U5      S :*  v   M     g7fry  r   rz  s     r   r   r{    s     ?s3x1}r|  r   Fc              3   X   #    U  H   u  p[        U5      S :*  =(       d    X:H  v   M"     g7fry  r   )r   tbs      r   r   r{    s$     IytqSVq[2AF2ys   (*c              3   *   >#    U  H  oT4v   M
     g 7fr   r   )r   rJ   orb1s     r   r   r{    s     /N:aD	:s   )r   r7   rY   r#  insertrn  r   ru  updater1   r`  r$  )%rR   rf   rr  rs  r   finding_identityorbit_ir9   orbit_idorbitsr   irJ   r  r/  topsbottomsr  r  r  new_top_partnew_bot_partra  new_toprt  new_botoppsnew_qopprR  n1n2orb2
orbit_set2r  r  r  s%                                     @@@r   r   'ISMAGS._process_ordered_pair_partitions  s    6666I  7@7GH7GmgDM7GH%*+UT&U+'0'78'7tqad'78
++
	V u5EF6;Bi3D'4#5=X^x%F !%v %w156#388:6</v|4189#388:9</v|4 ''wMC ( ?A???/4,$ @ IsCyIII (,'>'>'D&2FB#+B<D#+B<D#t|-3D\
 &t 3 3J ?/2ut (/N:/N N '3 !*57377  : U4R4[)e $h 		% $*4.#9#>#>#@FLy ez k I+ 9@ 7 :s   I.I4 I9I?J)rs   rm   ru   rr   ro   rl   rt   rq   rn   rk   rh   rf   rg   )NNNT)Fr   )r]   r^   r_   r`   ra   rS   ri   r   r   r   r   r   r   r   r   r   classmethodr   r   r   staticmethodrD  rV  rn  ru  r   rb   r   rU   r   r   r   <  s    iV5Sn]*~5n
,(T"H
 J0$.  (N%`A
F 1 1 
 
208d  4crU   r  )ra   __all__r!   collectionsr   r   	functoolsr   r   networkxr/   r   r5   r:   rL   rN   r   r   rU   r   <module>r     sO   xt *  , # 3<A-HL"$N& &<^ ^rU   