
    ިsg                     b    d Z dgZddlZddlmZmZ ddlmZmZ d Z	d Z
d Zd	 Z G d
 d      Zy)a  
ISMAGS Algorithm
================

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

It is capable of finding (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 is an exponential number of isomorphisms that are symmetrically
equivalent. In that case, the ISMAGS algorithm will provide only one solution
per symmetry group.

>>> 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
-----
- The current implementation works for undirected graphs only. The algorithm
  in general should work for directed graphs as well though.
- Node keys for both provided graphs need to be fully orderable as well as
  hashable.
- 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.

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     Y/var/www/html/venv/lib/python3.12/site-packages/networkx/algorithms/isomorphism/ismags.py	<genexpr>z are_all_equal.<locals>.<genexpr>   s     2tu}2   )shapelenNotImplementedErrorAttributeErroriternextall)iterabler   messageiteratorr   s       @r   are_all_equalr   t   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 |S )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 need to end up in the same
        partition, and `False` otherwise.

    Returns
    -------
    list[set]
        A list of sets, with each set containing part of the items in `items`,
        such that ``all(test(*pair) for pair in  itertools.combinations(set, 2))
        == True``

    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``.
    )r   r   addappend)itemstest
partitionsr   	partitionp_items         r   make_partitionsr&      sh    4 J &# 	&I$y/*FD&!d#		& tf%&     c                 H    i }t        |       D ]  \  }}|D ]  }|||<   	  |S )a/  
    Creates a dictionary that maps each item in each partition to the index of
    the partition to which it belongs.

    Parameters
    ----------
    partitions: collections.abc.Sequence[collections.abc.Iterable]
        As returned by :func:`make_partitions`.

    Returns
    -------
    dict
    )	enumerate)r#   colorscolorkeyskeys        r   partition_to_colorr.      s@     F ,  t 	 CF3K	   Mr'   c                     t        |       } | j                         }t        t        j                  | t        |            } t        |      |      S )ax  
    Given an collection of sets, returns the intersection of those sets.

    Parameters
    ----------
    collection_of_sets: collections.abc.Collection[set]
        A collection of sets.

    Returns
    -------
    set
        An intersection of all sets in `collection_of_sets`. Will have the same
        type as the item initially taken from `collection_of_sets`.
    )listpopr   setintersectiontype)collection_of_setsr   outs      r   	intersectr7      sH     01""$E
!!#5s5z
BC4;sr'   c                      e Zd ZdZd&dZed        Zed        Zed        Zed        Z	ed        Z
ed	        Zed
        Zed        Zed        Zed        Zed        Zed        Zd'dZe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ed        Zed        Ze d(d       Z!d Z"d)dZ#d*d Z$ed!        Z%ed"        Z&ed#        Z'd$ Z(	 	 d)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.

    Notes
    -----
    The implementation imposes additional conditions compared to the VF2
    algorithm on the graphs provided and the comparison functions
    (:attr:`node_equality` and :attr:`edge_equality`):

     - Node keys in both graphs must be orderable as well as hashable.
     - Equality must be transitive: if A is equal to B, and B is equal to C,
       then A must be equal to C.

    Attributes
    ----------
    graph: networkx.Graph
    subgraph: networkx.Graph
    node_equality: collections.abc.Callable
        The function called to see if two nodes should be considered equal.
        It's signature looks like this:
        ``f(graph1: networkx.Graph, node1, graph2: networkx.Graph, node2) -> bool``.
        `node1` is a node in `graph1`, and `node2` a node in `graph2`.
        Constructed from the argument `node_match`.
    edge_equality: collections.abc.Callable
        The function called to see if two edges should be considered equal.
        It's signature looks like this:
        ``f(graph1: networkx.Graph, edge1, graph2: networkx.Graph, edge2) -> bool``.
        `edge1` is an edge in `graph1`, and `edge2` an edge in `graph2`.
        Constructed from the argument `edge_match`.

    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
    Nc                    || _         || _        || _        d| _        d| _        d| _        d| _        d| _        d| _        d| _	        d| _
        d| _        d| _        |k| j                  d       | _        t        | j                  j                         g| _        t        | j                   j                         g| _        ddi| _        n| j                  |      | _        |k| j#                  d       | _        t        | j                  j&                        g| _        t        | j                   j&                        g| _        ddi| _        y| 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.
        Nc                      yNTr   )n1n2s     r   <lambda>z!ISMAGS.__init__.<locals>.<lambda>@      r'   r   c                      yr;   r   )e1e2s     r   r>   z!ISMAGS.__init__.<locals>.<lambda>G  r?   r'   )graphsubgraph_symmetry_cache_sgn_partitions__sge_partitions__sgn_colors__sge_colors__gn_partitions__ge_partitions__gn_colors__ge_colors__node_compat__edge_compat__node_match_makernode_equalityr2   nodes_edge_match_makeredge_equalityedges)selfrC   rD   
node_match
edge_matchcaches         r   __init__zISMAGS.__init__  sF   2 
 $ !% $  ##!!!%!7!78K!LD%()<)<%=$>D!$'

(8(8$9#:D "#QD!%!7!7
!CD!%!7!78K!LD%()<)<%=$>D!$'

(8(8$9#:D "#QD!%!7!7
!CDr'   c                       j                   * fd}t         j                  j                  |       _          j                   S )Nc                 T    j                  j                  | j                  |      S r   )rQ   rD   node1node2rV   s     r   	nodematchz)ISMAGS._sgn_partitions.<locals>.nodematchR  "    ))$--uUUr'   )rF   r&   rD   rR   rV   r`   s   ` r   _sgn_partitionszISMAGS._sgn_partitionsN  <      (V %4DMM4G4G$SD!$$$r'   c                       j                   * fd}t         j                  j                  |       _          j                   S )Nc                 T    j                  j                  | j                  |      S r   )rT   rD   edge1edge2rV   s     r   	edgematchz)ISMAGS._sge_partitions.<locals>.edgematch\  ra   r'   )rG   r&   rD   rU   rV   rj   s   ` r   _sge_partitionszISMAGS._sge_partitionsX  rd   r'   c                       j                   * fd}t         j                  j                  |       _          j                   S )Nc                 T    j                  j                  | j                  |      S r   )rQ   rC   r]   s     r   r`   z(ISMAGS._gn_partitions.<locals>.nodematchf  "    ))$**eTZZOOr'   )rJ   r&   rC   rR   rb   s   ` r   _gn_partitionszISMAGS._gn_partitionsb  <    'P $34::3C3CY#OD ###r'   c                       j                   * fd}t         j                  j                  |       _          j                   S )Nc                 T    j                  j                  | j                  |      S r   )rT   rC   rg   s     r   rj   z(ISMAGS._ge_partitions.<locals>.edgematchp  ro   r'   )rK   r&   rC   rU   rk   s   ` r   _ge_partitionszISMAGS._ge_partitionsl  rq   r'   c                 f    | j                   t        | j                        | _         | j                   S r   )rH   r.   rc   rV   s    r   _sgn_colorszISMAGS._sgn_colorsv  -    $ 243G3G HD   r'   c                 f    | j                   t        | j                        | _         | j                   S r   )rI   r.   rl   rv   s    r   _sge_colorszISMAGS._sge_colors|  rx   r'   c                 f    | j                   t        | j                        | _         | j                   S r   )rL   r.   rp   rv   s    r   
_gn_colorszISMAGS._gn_colors  -    #1$2E2EFDr'   c                 f    | j                   t        | j                        | _         | j                   S r   )rM   r.   rt   rv   s    r   
_ge_colorszISMAGS._ge_colors  r}   r'   c                    | j                   | j                   S i | _         t        j                  t        t	        | j
                              t        t	        | j                                    D ]  \  }}t        t        | j
                  |               }t        t        | j                  |               }| j                  | j                  || j                  |      sq|| j                   |<    | j                   S r   )rN   	itertoolsproductranger   rc   rp   r   r   rQ   rD   rC   )rV   sgn_part_colorgn_part_colorsgngns        r   _node_compatibilityzISMAGS._node_compatibility      )%%%-6->->#d**+,eC8K8K4L.M.
 	C)NM tD00@ABCd4..}=>?B!!$--djj"E5B"">2	C !!!r'   c                    | j                   | j                   S i | _         t        j                  t        t	        | j
                              t        t	        | j                                    D ]  \  }}t        t        | j
                  |               }t        t        | j                  |               }| j                  | j                  || j                  |      sq|| j                   |<    | j                   S r   )rO   r   r   r   r   rl   rt   r   r   rT   rD   rC   )rV   sge_part_colorge_part_colorsgeges        r   _edge_compatibilityzISMAGS._edge_compatibility  r   r'   c                 .     t                fd       }|S )Nc                 J     | j                   |   |j                   |         S r   )rR   )graph1r^   graph2r_   cmps       r   comparerz*ISMAGS._node_match_maker.<locals>.comparer  "    v||E*FLL,?@@r'   r   r   r   s   ` r   rP   zISMAGS._node_match_maker  "    	s	A 
	A r'   c                 .     t                fd       }|S )Nc                 J     | j                   |   |j                   |         S r   )rU   )r   rh   r   ri   r   s       r   r   z*ISMAGS._edge_match_maker.<locals>.comparer  r   r'   r   r   s   ` r   rS   zISMAGS._edge_match_maker  r   r'   c              #     	K   | j                   si  y| j                  syt        | j                        t        | j                         k  ry|rF| j                  | j                   | j                  | j
                        \  }}| j                  |      }ng }| j                         	| j                         }| j                   D ]  }||   }|s	|   t        |      hz  	|<   ! t        	j                               r>t        		fd      }t        	|         f	|<   | j                  |	|      E d{    yy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        |    t              S Nr-   minr   n
candidatess    r   r>   z*ISMAGS.find_isomorphisms.<locals>.<lambda>  s    c*Q-S6Q r'   r   )rD   rC   r   analyze_symmetryrc   rz   _make_constraints_find_nodecolor_candidates_get_lookahead_candidates	frozensetanyvaluesr   r7   
_map_nodes)
rV   symmetry_cosetsconstraintsla_candidatesr   extra_candidates	start_sgnr   s
            @r   find_isomorphismszISMAGS.find_isomorphisms  s3    $ }}H_s4==11--t33T5E5EIAv 008KK446
668== 	RC,S1",S/Y?O5P4Q"Q
3	R
 z  "#J,QRI%.z)/D%E$GJy!y*kJJJ Ks   CE A(E 7D>8E c                     t               }| |   }|D ]+  }||   }||f|v r|||f   }n|||f   }|||fxx   dz  cc<   - |S )z
        For `node` in `graph`, count the number of edges of a specific color
        it has to nodes of a specific color.
        r	   )r   )	rC   node
node_color
edge_colorcounts	neighborsneighborn_colore_colors	            r   _find_neighbor_color_countz!ISMAGS._find_neighbor_color_count  sr     $K	! 	*H *Gh:-$T8^4$Xt^47G#$)$	* r'   c                 v   i }| j                   D ]7  }| j                  | j                   || j                  | j                        ||<   9 t	        t
              }| j                  D ]  }| j                  | j                  || j                  | j                        }t               |j                         D ].  \  \  }}}	 | j                  |   }	| j                  |   }
||	|
f<   0 |j                         D ]/  \  }t        fdD              s||   j                  |       1  |S # t        $ r Y w xY w)z
        Returns a mapping of {subgraph node: collection of graph nodes} for
        which the graph nodes are feasible candidates for the subgraph node, as
        determined by looking ahead one edge.
        c              3   4   K   | ]  }|   |   k    y wr   r   )r   xg_countnew_sg_counts     r   r   z3ISMAGS._get_lookahead_candidates.<locals>.<genexpr>  s     K|A'!*4Ks   )rC   r   r|   r   r   r2   rD   rw   rz   r   r!   r   r   KeyErrorr   r   )rV   g_countsr   r   r   sg_count	sge_color	sgn_colorcountge_colorgn_colorr   r   s              @@r   r   z ISMAGS._get_lookahead_candidates  sE    ** 	B::

BHRL	 !%
== 	,C66sD$4$4d6F6FH #9L191A =-&I=#77	BH#77	BH 8=L8!34=  (~~/ ,GKlKKsO''+,	,$    s   ?D,,	D87D8c              #   h  K   | j                   si  y| j                  sy|rF| j                  | j                   | j                  | j                        \  }}| j                  |      }ng }| j                         }t        |j                               r| j                  ||      E d{    yy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}.
        N)
rD   rC   r   rc   rz   r   r   r   r   _largest_common_subgraph)rV   r   r   r   r   r   s         r   largest_common_subgraphzISMAGS.largest_common_subgraph  s     $ }}H--t33T5E5EIAv 008KK446
z  "#44ZMMM Ns   B'B2)B0*B2c           
         | j                   t        t        |j                        t        |j                        t        t        t        |            t        |j                               f      }|| j                   v r| j                   |   S t        | j                  |||            }t        |      dk(  sJ |d   }| j                  ||||      \  }}| j                   ||f| j                   <   ||fS )a  
        Find a minimal set of permutations and corresponding co-sets that
        describe the symmetry of `graph`, given the node and edge equalities
        given by `node_partitions` and `edge_colors`, respectively.

        Parameters
        ----------
        graph : networkx.Graph
            The graph whose symmetry should be analyzed.
        node_partitions : list of sets
            A list of sets containing node keys. Node keys in the same set
            are considered equivalent. Every node key in `graph` should be in
            exactly one of the sets. If all nodes are equivalent, this should
            be ``[set(graph.nodes)]``.
        edge_colors : dict mapping edges to their colors
            A dict mapping every edge in `graph` to its corresponding color.
            Edges with the same color are considered equivalent. If all edges
            are equivalent, this should be ``{e: 0 for e in graph.edges}``.


        Returns
        -------
        set[frozenset]
            The found permutations. This is a set of frozensets of pairs of node
            keys which can be exchanged without changing :attr:`subgraph`.
        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``.
        r	   r   )rE   hashtuplerR   rU   mapr!   r0   _refine_node_partitionsr    _process_ordered_pair_partitions)rV   rC   node_partitionsedge_colorsr-   permutationsr   s          r   r   zISMAGS.analyze_symmetryB  s    @ +%++&%++&#e_56+++-.	C d***++C00((M
 ?#q((()!,#DD?O[ 
f +(4f(<D  %V##r'   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   rD   rC   subgraph_is_isomorphicrV   r   s     r   is_isomorphiczISMAGS.is_isomorphicy  s7     4==!S_4 
9T9T:
 	
r'   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)rV   r   isoms      r   r   zISMAGS.subgraph_is_isomorphic  s)     D33X3FM4r'   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   rC   rD   r   r   s     r   isomorphisms_iterzISMAGS.isomorphisms_iter  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//r'   c                    t        t              }| j                  j                  D ]u  }| j                  |   }|| j
                  v r:| j
                  |   }||   j                  t        | j                  |                Z||   j                  t                      w t        |      }|j                         D ]  \  }}t        |      ||<    |S )zX
        Per node in subgraph find all nodes in graph that have the same color.
        )r   r2   rD   rR   rw   r   r   r   rp   dictr!   )rV   r   r   r   r   optionss         r   r   z!ISMAGS._find_nodecolor_candidates  s     !%
==&& 	1C((-ID44433I>3##Id.A.A(.K$LM3##IK0	1 *%
&,,. 	1LC'0JsO	1r'   c                 z    g }| j                         D ]%  \  }}|D ]  }||k7  s	|j                  ||f        ' |S )z/
        Turn cosets into constraints.
        )r!   r    )r   r   node_inode_tsnode_ts        r   r   zISMAGS._make_constraints  sT    
 %||~ 	9OFG! 9V#&&'789	9
 r'   c                 2   t        d       }| j                  D ]D  \  }}||f|v r|||f   }n|||f   }||   |||   fxx   dz  cc<   ||   |||   fxx   dz  cc<   F i }| j                  D ]&  }||   t        ||   j	                               f||<   ( |S )z
        For every node in graph, come up with a color that combines 1) the
        color of the node, and 2) the number of edges of a color to each type
        of node.
        c                       t        t              S r   )r   intr   r'   r   r>   z.ISMAGS._find_node_edge_color.<locals>.<lambda>  s    [%5 r'   r	   )r   rU   rR   r2   r!   )	rC   node_colorsr   r   r^   r_   ecolornode_edge_colorsr   s	            r   _find_node_edge_colorzISMAGS._find_node_edge_color  s     56!KK 		;LE5u~,$UE\2$UE\2 5M&+e"445:55M&+e"445:5		; KK 	RD%0%6F4L<N<N<P8Q%QT"	R  r'   c              #      K   t        t              | D ]  }t        |         j                  |       ! t	        j
                  fdt              D         E d{    y7 w)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   r   )r   lby_lens     r   r   z5ISMAGS._get_permutations_by_length.<locals>.<genexpr>  s      HAi$$VAY/Hs   "%N)r   r0   r   r    r   r   sorted)r!   r   r   s     @r   _get_permutations_by_lengthz"ISMAGS._get_permutations_by_length  s_       T" 	+D3t9$$T*	+ $$HH
 	
 	
s   AA&A$A&c           
   #     K   fd}t        |      }t        |      }| j                  |||      t        fd|D              r| yg }|g}|D ]  }	t	        fd|	D              st        |	|      }
|rt        |
      dk7  rt        |
D ch c]  }t        |       c}      t        |
D cg c]  }t        |       c}      k7  rD| j                  |
      }g }|D ])  }|D ]"  }|j                  |t        |d         z          $ + |}|D ]"  }|j                  t        |
t                     $ |D ]  }|j                  |	         |D ]  }| j                  ||||      E d{      yc c}w c c}w 7 w)z
        Given a partition of nodes in graph, make the partitions smaller such
        that all nodes in a partition have 1) the same color, and 2) the same
        number of edges to specific other partitions.
        c                     |    |   k(  S r   r   )r^   r_   r   s     r   equal_colorz3ISMAGS._refine_node_partitions.<locals>.equal_color  s    #E*.>u.EEEr'   c              3   F   K   | ]  }t        fd |D                yw)c              3   (   K   | ]	  }|     y wr   r   r   r   r   s     r   r   z;ISMAGS._refine_node_partitions.<locals>.<genexpr>.<genexpr>  s     GT*40Gr   N)r   )r   r$   r   s     r   r   z1ISMAGS._refine_node_partitions.<locals>.<genexpr>  s$      
 GYGG
s   !Nc              3   (   K   | ]	  }|     y wr   r   r   s     r   r   z1ISMAGS._refine_node_partitions.<locals>.<genexpr>  s      ND!1$!7 Nr   r	   r   r   )r0   r.   r   r   r   r&   r   r   r    extendr   r   )clsrC   r   r   branchr   r   new_partitionsoutputr$   refinedrr   
new_outputn_ppermutationr   s                   @r   r   zISMAGS._refine_node_partitions  s    	F /(944UKU 
,
 
 "! !( 	*I  NI NN))[AG)W5SV56#w>W!s1v>W:XX $'#B#B7#KL!#J% J+7 JK&--cDQ4H.HIJJ (F% =

6's#;<= " *CJJy)*/	*2  	TC225#{FSSS	T' 6>W( Ts+   BF E4
 F /E9B+F ,E>-F c                     ||f| j                   v r| j                   ||f   }n| j                   ||f   }|| j                  v r | j                  |   }| j                  |   }|S g }|S )z
        Returns all edges in :attr:`graph` that have the same colour as the
        edge between sgn1 and sgn2 in :attr:`subgraph`.
        )rz   r   rt   )rV   sgn1sgn2r   r   g_edgess         r   _edges_of_same_colorzISMAGS._edges_of_same_color#  s    
 $<4+++((t4I((t4I000//	:H))(3G  Gr'   c           	   #     K   |i }n|j                         }|t        | j                  j                        }t	        ||         }t        |g      ||<   |D ]  }||j                         v s||vr|||<   |t        |j                               k(  r'|j                         D 	ci c]  \  }}	|	|
 c}	} c|t        |j                               z
  }
|j                         t        | j                  |         }t        | j                  j                        t        | j                  |         z
  }|
D ]  }||vr|}n/| j                  ||      }|D ch c]  }|D ]	  }||v s|  }}}|   j                  t        |      g      |<   ||f|v r | j                  D ch c]
  }||kD  s	| }}n'||f|v r | j                  D ch c]
  }||k  s	| }}n|   j                  t        |      g      |<    t        |
fd      }| j                  ||||      E d{     yc c}	}w c c}}w c c}w c c}w 7 w)zF
        Find all subgraph isomorphisms honoring constraints.
        Nc                 ,    t        |    t              S r   r   )r   new_candidatess    r   r>   z#ISMAGS._map_nodes.<locals>.<lambda>u  s    c.:KQT6U r'   r   )mappingto_be_mapped)copyr2   rD   rR   r7   r   r   r,   r!   rC   r  unionr   r   )rV   r   r   r   r  r  sgn_candidatesr   kvleft_to_mapsgn_nbrsnot_gn_nbrsr
  gn2_optionsr  er   gn2next_sgnr  s                       @r   r   zISMAGS._map_nodes4  si     ?GllnGt}}223L
 #:c?3#^$45
3  8	BW^^%%L)@ GCLs7<<>22(/81q!t88&W\\^)<<K'__.N4==-.Hdjj../#djjn2EEK# x'"-K #77TBG /6"Lq"L!B!G1"L1"LK"L (6d';'A'A{+,(t$ ;+-26**"I3b3"IK"IC[K/26**"I3b3"IK"I'5d';'A'A{+,(t$5@ ;,UVH) '   e8	 9 #M #J"IsV   B%I(H65B&IH<
*H<
08I(
I3I7I
IIAI-I.I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        |    t              S r   r   r   s    r   r>   z1ISMAGS._largest_common_subgraph.<locals>.<lambda>  s    C
134O r'   )r  Tr	   )r   rD   rR   r   r   r   rC   r   r   r   StopIterationr2   _remove_noder   r   )rV   r   r   r  current_size	found_isorR   r  	isomorphsr   left_to_be_mappedr   	new_nodess    `           r   r   zISMAGS._largest_common_subgraph  sW     %dmm&9&9:;L 4\ 2B78	3tzz?*  &9 %u*OP OOj+E , 	%	?D J((( $I!%& ) E! 	1E 1 !--c5+F	!%%i01	1 002C 1 
 	
 	
3 )	 % :	
sC   BD)D#	D),D-A#D)D'D)	D$!D)#D$$D)c                 V    	 |D ]  \  }}|| k(  s||v s|}  n 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   )r   rR   r   lowhighs        r   r!  zISMAGS._remove_node  sL     ( 	T$;45=D
   $((r'   c                 (   t               }t        | |      D ]x  \  }}t        |      dk7  st        |      dk7  rt        d|  d|       ||k7  s9|j	                  t        t        t        |            t        t        |            f             z |S )z
        Return the pairs of top/bottom partitions where the partitions are
        different. Ensures that all partitions in both top and bottom
        partitions have size 1.
        r	   z/Not all nodes are coupled. This is impossible: z, )r2   zipr   
IndexErrorr   r   r   r   )top_partitionsbottom_partitionsr   topbots        r   _find_permutationszISMAGS._find_permutations  s     uN,=> 	PHC3x1}CA $$2#326G5HJ  cz  DcOT$s)_+M!NO	P r'   c                     |D ]Q  }|\  }}dx}}t        |       D ]  \  }}|| n||v r|}||v s|} ||k7  s8| |   j                  | |          | |= S y)z
        Update orbits based on permutations. Orbits is modified in place.
        For every pair of items in permutations their respective orbits are
        merged.
        N)r)   update)	orbitsr   r  r   r_   r   secondidxorbits	            r   _update_orbitszISMAGS._update_orbits  s     ( 	#K%KD% "!EF'/ !
U$);5=EE> F! u$$VF^46N	#r'   c              #     K   ||   }||   }	||v r||	v sJ |D 
cg c]  }
|
j                          }}
|D cg c]  }|j                          }}|h||hz
  f}|h|	|hz
  f}||= ||= |||| |||| | j                  |||      }| j                  |||d      }t        |      }t        |      dk(  sJ |d   }|D ]  }t        |      |f  yc c}
w c c}w w)z
        Generate new partitions from top and bottom_partitions where t_node is
        coupled to b_node. pair_idx is the index of the partitions where t_ and
        b_node can be found.
        T)r   r	   r   N)r  r   r0   r   )rV   r-  r.  pair_idxt_nodeb_noderC   r   t_partitionb_partitionr/  new_top_partitionsr0  new_bottom_partitionsnew_t_groupsnew_b_groupss                   r   _couple_nodeszISMAGS._couple_nodes   sF     %X.'1$;)>>>4BCSchhjCC7H I I Ixx!77xx!77x(!(+0<8H-3?hx0!99%{
 !% < <(+d != !
 ""45%&!+++/2( 	0C)*C//	0' D Is   CCCCBCc           
         ||j                   D cg c]  }|h }}n|}|i }n|j                         }t        d t        ||      D              sJ t        d |D              r/| j	                  ||      }| j                  ||       |r|g|fS g |fS g }t        |      D 	
ch c]  \  }	}
|
D ]  }t        |
      dkD  r||	f   }}
}	}t        |      \  }||   }t        |      D ]~  t        |      dk(  rk7  rt        fd|D              r-| j                  |||||      }|D ]6  }|\  }}| j                  ||||||      \  }}||z  }|j                  |       8  t        ||      D ch c]!  \  }}|D ]  }t        |      dk(  r||k(  r| # }}}}|j                   D ch c]
  }|k  s	| }}||k  xr |v}|r|D ]  }|v s|j                         |<    ||fS c c}w c c}}
}	w c c}}}w c c}w )z
        Processes ordered pair partitions as per the reference paper. Finds and
        returns all permutations and cosets that leave the graph unchanged.
        c              3   P   K   | ]  \  }}t        |      t        |      k(     y wr   r   )r   t_pb_ps      r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>B  s&      
%-S#CHC 
s   $&c              3   8   K   | ]  }t        |      d k(    yw)r	   NrF  )r   r/  s     r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>G  s     7s3x1}7s   r	   c              3   2   K   | ]  }|v xr |v   y wr   r   )r   r7  r   r_   s     r   r   z:ISMAGS._process_ordered_pair_partitions.<locals>.<genexpr>^  s&      %5:0%5.0%s   )rR   r  r   r+  r1  r8  r)   r   r   r   r   rC  r   r3  )rV   rC   r-  r.  r   r4  r   r   r   r6  r=  unmapped_nodesr:  r>  r#   oppr?  r@  	new_perms
new_cosetsr/  bottomr  mappedks
find_cosetr7  r_   s          `                   @r   r   z'ISMAGS._process_ordered_pair_partitions)  s    >).5tf5F5
 F>F[[]F 
14^EV1W
 
 	
 

 77722>CTUL5$~v--6z! %.n$=
 
 [#
 ;!# 3K

 
 ^,h'1K( !	*E;1$u} %>D% "  ++!J " *<?9"$9(,(M(M&))%	: 	)j)*)!	*J  #>3DE
 
V
 3x1} 

 
 1ADa116\8d&&8
 05=#(::<F4L0 V##i 64
X
 2s   
G6'#G;=&H5
H	 H	)NNN)T)F)NNr   )*__name__
__module____qualname____doc__rZ   propertyrc   rl   rp   rt   rw   rz   r|   r   r   r   staticmethodrP   rS   r   r   r   r   r   r   r   r   r   r   r   r   r   classmethodr   r  r   r   r!  r1  r8  rC  r   r   r'   r   r   r      s   (T?DB % % % % $ $ $ $ ! !
 ! !
    
    
 " " " "    .`   @%N5$n
 J0" 
 
    0 
 
. 0T 0Td"HZA
F ) )   & # #,'0^ b$r'   )rV  __all__r   collectionsr   r   	functoolsr   r   r   r&   r.   r7   r   r   r'   r   <module>r]     s?   jX *  , #3<#L**i$ i$r'   