r"""
Right-Angled Artin Groups

A *right-angled Artin group* (often abbrivated as RAAG) is a group which
has a presentation whose only relations are commutators between generators.
These are also known as graph groups, since they are (uniquely) encoded by
(simple) graphs, or partially commmutative groups.

AUTHORS:

- Travis Scrimshaw (2013-09-01): Initial version
"""

##############################################################################
#       Copyright (C) 2013 Travis Scrimshaw <tscrim at ucdavis.edu>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#  The full text of the GPL is available at:
#
#                  http://www.gnu.org/licenses/
##############################################################################

from sage.misc.cachefunc import cached_method
from sage.structure.list_clone import ClonableArray
from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement
from sage.groups.free_group import FreeGroup
from sage.rings.integer import Integer
from sage.rings.integer_ring import IntegerRing
from sage.graphs.graph import Graph

class RightAngledArtinGroup(FinitelyPresentedGroup):
    r"""
    The right-angled Artin group defined by a graph `G`.

    Let `\Gamma = \{V(\Gamma), E(\Gamma)\}` be a simple graph.
    A *right-angled Artin group* (commonly abbriated as RAAG) is the group

    .. MATH::

        A_{\Gamma} = \langle g_v : v \in V(\Gamma)
        \mid [g_u, g_v] \text{ if } \{u, v\} \notin E(\Gamma) \rangle.

    These are sometimes known as graph groups or partitally commutative groups.
    This RAAG's contains both free groups, given by the complete graphs,
    and free abelian groups, given by disjoint vertices.

    .. WARNING::

        This is the opposite convention of some papers.

    Right-angled Artin groups contain many remarkable properties and have a
    very rich structure despite their simple presentation. Here are some
    known facts:

    - The word problem is solvable.
    - They are known to be rigid; that is for any finite simple graphs
      `\Delta` and `\Gamma`, we have `A_{\Delta} \cong A_{\Gamma}` if and
      only if `\Delta \cong \Gamma` [Droms1987]_.
    - They embed as a finite index subgroup of a right-angled Coxeter group
      (which is the same definition as above except with the additional
      relations `g_v^2 = 1` for all `v \in V(\Gamma)`).
    - In [BB1997]_, it was shown they contain subgroups that statisfy the
      property `FP_2` but are not finitely presented by considering the
      kernal of `\phi : A_{\Gamma} \to \ZZ` by `g_v \mapsto 1` (i.e. words of
      exponent sum 0).
    - `A_{\Gamma}` has a finite `K(\pi, 1)` space.
    - `A_{\Gamma}` acts freely and cocompactly on a finite dimensional
      `CAT(0)` space, and so it is biautomatic.
    - Given an Artin group `B` with generators `s_i`, then any subgroup
      generated by a collection of `v_i = s_i^{k_i}` where `k_i \geq 2` is a
      RAAG where `[v_i, v_j] = 1` if and only if `[s_i, s_j] = 1` [CP2001]_.

    The normal forms for RAAG's in Sage are those described in [VW1994]_ and
    gathers commuting groups together.

    EXAMPLES::

        sage: Gamma = Graph(4)
        sage: G = RightAngledArtinGroup(Gamma)
        sage: a,b,c,d = G.gens()
        sage: a*c*d^4*a^-3*b
        v0^-2*v1*v2*v3^4

        sage: Gamma = graphs.CompleteGraph(4)
        sage: G = RightAngledArtinGroup(Gamma)
        sage: a,b,c,d = G.gens()
        sage: a*c*d^4*a^-3*b
        v0*v2*v3^4*v0^-3*v1

        sage: Gamma = graphs.CycleGraph(5)
        sage: G = RightAngledArtinGroup(Gamma)
        sage: G
        Right-angled Artin group of Cycle graph
        sage: a,b,c,d,e = G.gens()
        sage: e^-1*c*b*e*b^-1*c^-4
        v2^-3

    REFERENCES:

    .. [Charney2006] Ruth Charney.
       *An introduction to right-angled Artin groups*.
       http://people.brandeis.edu/~charney/papers/RAAGfinal.pdf,
       :arxiv:`math/0610668`.

    .. [BB1997] Mladen Bestvina and Noel Brady.
       *Morse theory and finiteness properties of groups*. Invent. Math.
       **129** (1997). No. 3, 445-470. www.math.ou.edu/~nbrady/papers/morse.ps.

    .. [Droms1987] Carl Droms. *Isomorphisms of graph groups*. Proc. of
       the Amer. Math. Soc. **100** (1987). No 3.
       http://educ.jmu.edu/~dromscg/vita/preprints/Isomorphisms.pdf

    .. [CP2001] John Crisp and Luis Paris. *The solution to a conjecture of
       Tits on the subgroup generated by the squares of the generators of an
       Artin group*. Invent. Math. **145** (2001). No 1, 19-36.
       :arxiv:`math/0003133`.

    .. [VW1994] Leonard Van Wyk. *Graph groups are biautomatic*. J. Pure Appl.
       Alg. **94** (1994). no. 3, 341-352.

    - :wikipedia:`Artin_group#Right-angled_Artin_groups`
    """
    @staticmethod
    def __classcall_private__(cls, G):
        """
        Normalize input to ensure a unique representation.

        TESTS::

            sage: G1 = RightAngledArtinGroup(graphs.CycleGraph(5))
            sage: Gamma = Graph([(0,1),(1,2),(2,3),(3,4),(4,0)])
            sage: G2 = RightAngledArtinGroup(Gamma)
            sage: G3 = RightAngledArtinGroup([(0,1),(1,2),(2,3),(3,4),(4,0)])
            sage: G1 is G2 and G2 is G3
            True

        Handle the empty graph::

            sage: RightAngledArtinGroup(Graph())
            Traceback (most recent call last):
            ...
            ValueError: the graph must not be empty
        """
        if not isinstance(G, Graph):
            G = Graph(G, immutable=True)
        else:
            G = G.copy(immutable=True)
        if G.num_verts() == 0:
            raise ValueError("the graph must not be empty")
        return super(RightAngledArtinGroup, cls).__classcall__(cls, G)

    def __init__(self, G):
        """
        Initialize ``self``.

        INPUT:

        - ``G`` -- a graph

        TESTS::

            sage: G = RightAngledArtinGroup(graphs.CycleGraph(5))
            sage: TestSuite(G).run()
        """
        self._graph = G
        F = FreeGroup(names=['v{}'.format(v) for v in self._graph.vertices()])
        CG = Graph(G).complement() # Make sure it's mutable
        CG.relabel() # Standardize the labels
        rels = tuple(F([i+1, j+1, -i-1, -j-1]) for i,j in CG.edges(False)) #+/- 1 for indexing
        FinitelyPresentedGroup.__init__(self, F, rels)

    def _repr_(self):
        """
        Return a string representation of ``self``.

        TESTS::

            sage: RightAngledArtinGroup(graphs.CycleGraph(5))
            Right-angled Artin group of Cycle graph
        """
        return "Right-angled Artin group of {}".format(self._graph)

    def gen(self, i):
        """
        Return the ``i``-th generator of ``self``.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.gen(2)
            v2
        """
        return self.element_class( self, ((i, 1),) )

    def gens(self):
        """
        Return the generators of ``self``.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.gens()
            (v0, v1, v2, v3, v4)
            sage: Gamma = Graph([('x', 'y'), ('y', 'zeta')])
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.gens()
            (vx, vy, vzeta)
        """
        return tuple(self.gen(i) for i in range(self._graph.num_verts()))

    def ngens(self):
        """
        Return the number of generators of ``self``.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.ngens()
            5
        """
        return self._graph.num_verts()

    def cardinality(self):
        r"""
        Return the number of group elements.

        OUTPUT:

        Infinity.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.cardinality()
            +Infinity
        """
        from sage.rings.infinity import Infinity
        return Infinity

    order = cardinality

    def as_permutation_group(self):
        r"""
        Raise a ``ValueError`` error since right-angled Artin groups
        are infinite, so they have no isomorphic permutation group.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.as_permutation_group()
            Traceback (most recent call last):
            ...
            ValueError: the group is infinite
        """
        raise ValueError("the group is infinite")

    def graph(self):
        """
        Return the defining graph of ``self``.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.graph()
            Cycle graph: Graph on 5 vertices
        """
        return self._graph

    @cached_method
    def one(self):
        """
        Return the identity element `1`.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G.one()
            1
        """
        return self.element_class(self, ())

    one_element = one

    def _element_constructor_(self, x):
        """
        Construct an element of ``self`` from ``x``.

        TESTS::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: elt = G([[0,3], [3,1], [2,1], [1,1], [3,1]]); elt
            v0^3*v3*v2*v1*v3
            sage: G(elt)
            v0^3*v3*v2*v1*v3
            sage: G(1)
            1
        """
        if isinstance(x, RightAngledArtinGroup.Element):
            if x.parent() is self:
                return x
            raise ValueError("there is no coercion from {} into {}".format(x.parent(), self))
        if x == 1:
            return self.one()
        verts = self._graph.vertices()
        x = [ [verts.index(s[0]), s[1]] for s in x]
        return self.element_class(self, self._normal_form(x))

    def _normal_form(self, word):
        """
        Return the normal form of the word ``word``. Helper function for
        creating elements.

        EXAMPLES::

            sage: Gamma = graphs.CycleGraph(5)
            sage: G = RightAngledArtinGroup(Gamma)
            sage: G._normal_form([[0,2], [3,1], [2,1], [0,1], [1,1], [3,1]])
            ([0, 3], [3, 1], [2, 1], [1, 1], [3, 1])
            sage: a,b,c,d,e = G.gens()
            sage: a^2 * d * c * a * b * d
            v0^3*v3*v2*v1*v3
            sage: a*b*d == d*a*b and a*b*d == a*d*b
            True
            sage: a*c*a^-1*c^-1
            1
            sage: (a*b*c*d*e)^2 * (a*b*c*d*e)^-2
            1
        """
        pos = 0
        G = self._graph
        v = G.vertices()
        w = [list(_) for _ in word] # Make a (2 level) deep copy
        while pos < len(w):
            comm_set = [w[pos][0]] # The current set of totally commuting elements
            i = pos + 1

            while i < len(w):
                letter = w[i][0] # The current letter
                # Check if this could fit in the commuting set
                if letter in comm_set:
                    # Try to move it in
                    if any(G.has_edge(v[w[j][0]], v[letter]) for j in range(pos + len(comm_set), i)):
                        # We can't, so go onto the next letter
                        i += 1
                        continue
                    j = comm_set.index(letter)
                    w[pos+j][1] += w[i][1]
                    w.pop(i)
                    i -= 1 # Since we removed a syllable
                    # Check cancellations
                    if w[pos+j][1] == 0:
                        w.pop(pos+j)
                        comm_set.pop(j)
                        i -= 1
                        if not comm_set:
                            pos = 0 # Start again since cancellation can be pronounced effects
                            break
                elif all(not G.has_edge(v[w[j][0]], v[letter]) for j in range(pos, i)):
                    j = 0
                    for x in comm_set:
                        if x > letter:
                            break
                        j += 1
                    w.insert(pos+j, w.pop(i))
                    comm_set.insert(j, letter)

                i += 1
            pos += len(comm_set)
        return tuple(w)

    class Element(FinitelyPresentedGroupElement):
        """
        An element of a right-angled Artin group (RAAG).

        Elements of RAAGs are modeled as lists of pairs ``[i, p]`` where
        ``i`` is the index of a vertex in the defining graph (with some
        fixed order of the vertices) and ``p`` is the power.
        """
        def __init__(self, parent, lst):
            """
            Initialize ``self``.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: elt = G.prod(G.gens())
                sage: TestSuite(elt).run()
            """
            self._data = lst
            elt = []
            for i,p in lst:
                if p > 0:
                    elt.extend([i+1]*p)
                elif p < 0:
                    elt.extend([-i-1]*-p)
            FinitelyPresentedGroupElement.__init__(self, parent, elt)

        def __reduce__(self):
            """
            Used in pickling.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: elt = G.prod(G.gens())
                sage: loads(dumps(elt)) == elt
                True
            """
            P = self.parent()
            V = P._graph.vertices()
            return (P, ([[V[i], p] for i,p in self._data],))

        def _repr_(self):
            """
            Return a string representation of ``self``.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: a,b,c,d,e = G.gens()
                sage: a * b^2 * e^-3
                v0*v1^2*v4^-3
                sage: Gamma = Graph([('x', 'y'), ('y', 'zeta')])
                sage: G = RightAngledArtinGroup(Gamma)
                sage: x,y,z = G.gens()
                sage: z * y^-2 * x^3
                vzeta*vy^-2*vx^3
            """
            if not self._data:
                return '1'
            v = self.parent()._graph.vertices()
            to_str = lambda name,p: "v{}".format(name) if p == 1 else "v{}^{}".format(name, p)
            return '*'.join(to_str(v[i], p) for i,p in self._data)

        def _latex_(self):
            r"""
            Return a LaTeX representation of ``self``.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: a,b,c,d,e = G.gens()
                sage: latex(a*b*e^-4*d^3)
                \sigma_{0}\sigma_{1}\sigma_{4}^{-4}\sigma_{3}^{3}
                sage: latex(G.one())
                1
                sage: Gamma = Graph([('x', 'y'), ('y', 'zeta')])
                sage: G = RightAngledArtinGroup(Gamma)
                sage: x,y,z = G.gens()
                sage: latex(x^-5*y*z^3)
                \sigma_{\text{\texttt{x}}}^{-5}\sigma_{\text{\texttt{y}}}\sigma_{\text{\texttt{zeta}}}^{3}
            """
            if not self._data:
                return '1'

            from sage.misc.latex import latex
            latexrepr = ''
            v = self.parent()._graph.vertices()
            for i,p in self._data:
                latexrepr += "\\sigma_{{{}}}".format(latex(v[i]))
                if p != 1:
                    latexrepr += "^{{{}}}".format(p)
            return latexrepr

        def _mul_(self, y):
            """
            Return ``self`` multiplied by ``y``.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: a,b,c,d,e = G.gens()
                sage: a * b
                v0*v1
                sage: b * a
                v1*v0
                sage: a*b*c*d*e
                v0*v1*v2*v3*v4
                sage: a^2*d*c*a*b*d
                v0^3*v3*v2*v1*v3
                sage: e^-1*a*b*d*c*a^-2*e*d*b^2*e*b^-3
                v4^-1*v0*v3*v1*v0^-2*v2*v1^-1*v4*v3*v4
            """
            P = self.parent()
            lst = self._data + y._data
            return self.__class__(P, P._normal_form(lst))

        def __pow__(self, n):
            """
            Implement exponentiation.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: elt = G.prod(G.gens())
                sage: elt**3
                v0*v1*v2*v3*v4*v0*v1*v2*v3*v4*v0*v1*v2*v3*v4
                sage: elt^-2
                v4^-1*v3^-1*v2^-1*v1^-1*v0^-1*v4^-1*v3^-1*v2^-1*v1^-1*v0^-1
                sage: elt^0
                1
            """
            P = self.parent()
            if not n:
                return P.one()

            if n < 0:
                lst = sum([self._data for i in range(-n)], ()) # Positive product
                lst = [ [x[0], -x[1]] for x in reversed(lst)] # Now invert
                return self.__class__(P, P._normal_form(lst))

            lst = sum([self._data for i in range(n)], ())
            return self.__class__(self.parent(), P._normal_form(lst))

        def __invert__(self):
            """
            Return the inverse of ``self``.

            TESTS::

                sage: Gamma = graphs.CycleGraph(5)
                sage: G = RightAngledArtinGroup(Gamma)
                sage: a,b,c,d,e = G.gens()
                sage: (a * b)^-2
                v1^-1*v0^-1*v1^-1*v0^-1
            """
            P = self.parent()
            lst = [ [x[0], -x[1]] for x in reversed(self._data)]
            return self.__class__(P, P._normal_form(lst))

