# -*- coding: utf-8 -*-
r"""
Fock Space

AUTHORS:

- Travis Scrimshaw (2013-05-03): Initial version
"""

# ****************************************************************************
#       Copyright (C) 2013-2017 Travis Scrimshaw <tcscrims at gmail.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#                  https://www.gnu.org/licenses/
# ****************************************************************************

from sage.misc.cachefunc import cached_method
from sage.misc.bindable_class import BindableClass
from sage.structure.parent import Parent
from sage.structure.unique_representation import UniqueRepresentation
from sage.structure.global_options import GlobalOptions
from sage.categories.modules_with_basis import ModulesWithBasis
from sage.categories.realizations import Realizations, Category_realization_of_parent

from sage.rings.all import ZZ
from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
from sage.rings.fraction_field import FractionField
from sage.rings.finite_rings.integer_mod_ring import IntegerModRing
from sage.combinat.free_module import CombinatorialFreeModule
from sage.combinat.partition import (_Partitions, Partitions,
                                     RegularPartitions_truncated)
from sage.combinat.partition_tuple import PartitionTuples
from sage.algebras.quantum_groups.q_numbers import q_factorial


#############################
## Fock space options

class FockSpaceOptions(GlobalOptions):
    r"""
    Sets and displays the global options for elements of the Fock
    space classes.  If no parameters are set, then the function
    returns a copy of the options dictionary.

    The ``options`` to Fock space can be accessed as the method
    :obj:`FockSpaceOptions` of :class:`FockSpace` and
    related parent classes.

    @OPTIONS@

    EXAMPLES::

        sage: FS = FockSpace(4)
        sage: F = FS.natural()
        sage: x = F.an_element()
        sage: y = x.f(3,2,2,0,1)
        sage: y
        ((3*q^2+3)/q)*|3, 3, 1> + (3*q^2+3)*|3, 2, 1, 1>
        sage: Partitions.options.display = 'diagram'
        sage: y
        ((3*q^2+3)/q)*|3, 3, 1> + (3*q^2+3)*|3, 2, 1, 1>
        sage: ascii_art(y)
        ((3*q^2+3)/q)*|***\  + (3*q^2+3)*|***\
                      |*** >             |**  \
                      |*  /              |*   /
                                         |*  /
        sage: FockSpace.options.display = 'list'
        sage: ascii_art(y)
        ((3*q^2+3)/q)*F    + (3*q^2+3)*F
                       ***              ***
                       ***              **
                       *                *
                                        *
        sage: Partitions.options.display = 'compact_high'
        sage: y
        ((3*q^2+3)/q)*F3^2,1 + (3*q^2+3)*F3,2,1^2

        sage: Partitions.options._reset()
        sage: FockSpace.options._reset()
    """
    NAME = 'FockSpace'
    module = 'sage.algebras.quantum_groups.fock_space'

    display = dict(default="ket",
                   description='Specifies how terms of the natural basis of Fock space should be printed',
                   values=dict(ket='displayed as a ket in bra-ket notation',
                               list='displayed as a list'),
                   case_sensitive=False)


###############################################################################
## Fock space

class FockSpace(Parent, UniqueRepresentation):
    r"""
    The (fermionic) Fock space of `U_q(\widehat{\mathfrak{sl}}_n)` with
    multicharge `(\gamma_1, \ldots, \gamma_m)`.

    Fix a positive integer `n > 1` and fix a sequence
    `\gamma = (\gamma_1, \ldots, \gamma_m)`, where `\gamma_i \in \ZZ / n \ZZ`.
    *(fermionic) Fock space* `\mathcal{F}` with multicharge `\gamma` is a
    `U_q(\widehat{\mathfrak{gl}}_n)`-representation with a basis
    `\{ |\lambda \rangle \}`, where `\lambda` is a partition tuple of
    level `m`. By considering `\mathcal{F}` as a
    `U_q(\widehat{\mathfrak{sl}}_n)`-representation,
    it is not irreducible, but the submodule generated by
    `| \emptyset^m \rangle` is isomorphic to the highest weight module
    `V(\mu)`, where the highest weight `\mu = \sum_i \Lambda_{\gamma_i}`.

    Let `R_i(\lambda)` and `A_i(\lambda)` be the set of removable and
    addable, respectively, `i`-cells of `\lambda`, where an `i`-cell is
    a cell of residue `i` (i.e., content modulo n).
    The action of `U_q(\widehat{\mathfrak{sl}}_n)` is given as follows:

    .. MATH::

        \begin{aligned}
        e_i | \lambda \rangle & = \sum_{c \in R_i(\lambda)}
        q^{M_i(\lambda, c)} | \lambda + c \rangle, \\
        f_i | \lambda \rangle & = \sum_{c \in A_i(\lambda)}
        q^{N_i(\lambda, c)} | \lambda - c \rangle, \\
        q^{h_i} | \lambda \rangle & = q^{N_i(\lambda)} | \lambda \rangle, \\
        q^d | \lambda \rangle & = q^{-N^{(0)}(\lambda)} | \lambda \rangle,
        \end{aligned}

    where

    - `M_i(\lambda, c)` (resp. `N_i(\lambda, c)`) is the number of removable
      (resp. addable) `i`-cells of `\lambda` below (resp. above) `c` minus
      the number of addable (resp. removable) `i`-cells of `\lambda` below
      (resp. above) `c`,
    - `N_i(\lambda)` is the number of addable `i`-cells minus the number of
      removable `i`-cells, and
    - `N^{(0)}(\lambda)` is the total number of `0`-cells of `\lambda`.

    Another interpretation of Fock space is as a semi-infinite wedge
    product (which each factor we can think of as fermions). This allows
    a description of the `U_q(\widehat{\mathfrak{gl}}_n)` action, as well
    as an explicit description of the bar involution. In particular, the
    bar involution is the unique semi-linear map satisfying

    - `q \mapsto q^{-1}`,
    - `\overline{| \emptyset \rangle} = | \emptyset \rangle`, and
    - `\overline{f_i | \lambda \rangle} = f_i \overline{| \lambda \rangle}`.

    We then define the *canonical basis* or *(lower) global crystal basis*
    as the unique basis of `\mathcal{F}` such that

    - `\overline{G(\lambda)} = G(\lambda)`,
    - `G(\lambda) \equiv | \lambda \rangle \mod q \ZZ[q]`.

    It is also known that this basis is upper unitriangular with respect to
    dominance order and that both the natural basis and the canonical basis
    of `\mathcal{F}` are `\ZZ`-graded by `|\lambda|`. Additionally, the
    transition matrices `(d_{\lambda, \nu})_{\lambda,\nu \vdash n}` given by

    .. MATH::

        G(\nu) = \sum_{\lambda \vdash |\nu|} d_{\lambda,\nu} |\lambda \rangle

    described the decomposition matrices of the Hecke algebras when
    restricting to `V(\mu)` [Ariki1996]_.

    To go between the canonical basis and the natural basis, for level 1
    Fock space, we follow the LLT algorithm [LLT1996]_. Indeed, we first
    construct an basis `\{ A(\nu) \}` that is an approximation to the
    lower global crystal basis, in the sense that it is bar-invariant,
    and then use Gaussian elimination to construct the lower global
    crystal basis. For higher level Fock space, we follow [Fayers2010]_,
    where the higher level is considered as a tensor product space
    of the corresponding level 1 Fock spaces.

    There are three bases currently implemented:

    - The natural basis:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpace.F`.
    - The approximation basis that comes from LLT(-type) algorithms:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpace.A`.
    - The lower global crystal basis:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpace.G`.

    .. TODO::

        - Implement the approximation and lower global crystal bases on
          all partition tuples.
        - Implement the bar involution.
        - Implement the full `U_q(\widehat{\mathfrak{gl}})`-action.

    INPUT:

    - ``n`` -- the value `n`
    - ``multicharge`` -- (default: ``[0]``) the multicharge
    - ``q`` -- (optional) the parameter `q`
    - ``base_ring`` -- (optional) the base ring containing ``q``

    EXAMPLES:

    We start by constructing the natural basis and doing
    some computations::

        sage: Fock = FockSpace(3)
        sage: F = Fock.natural()
        sage: u = F.highest_weight_vector()
        sage: u.f(0,2,(1,2),0)
        |2, 2, 1> + q*|2, 1, 1, 1>
        sage: u.f(0,2,(1,2),0,2)
        |3, 2, 1> + q*|3, 1, 1, 1> + q*|2, 2, 2> + q^2*|2, 1, 1, 1, 1>
        sage: x = u.f(0,2,(1,2),0,2)
        sage: [x.h(i) for i in range(3)]
        [q*|3, 2, 1> + q^2*|3, 1, 1, 1> + q^2*|2, 2, 2> + q^3*|2, 1, 1, 1, 1>,
         |3, 2, 1> + q*|3, 1, 1, 1> + q*|2, 2, 2> + q^2*|2, 1, 1, 1, 1>,
         |3, 2, 1> + q*|3, 1, 1, 1> + q*|2, 2, 2> + q^2*|2, 1, 1, 1, 1>]
        sage: [x.h_inverse(i) for i in range(3)]
        [1/q*|3, 2, 1> + |3, 1, 1, 1> + |2, 2, 2> + q*|2, 1, 1, 1, 1>,
         |3, 2, 1> + q*|3, 1, 1, 1> + q*|2, 2, 2> + q^2*|2, 1, 1, 1, 1>,
         |3, 2, 1> + q*|3, 1, 1, 1> + q*|2, 2, 2> + q^2*|2, 1, 1, 1, 1>]
        sage: x.d()
        1/q^2*|3, 2, 1> + 1/q*|3, 1, 1, 1> + 1/q*|2, 2, 2> + |2, 1, 1, 1, 1>

    Next, we construct the approximation and lower global crystal bases
    and convert to the natural basis::

        sage: A = Fock.A()
        sage: G = Fock.G()
        sage: F(A[4,2,2,1])
        |4, 2, 2, 1> + q*|4, 2, 1, 1, 1>
        sage: F(G[4,2,2,1])
        |4, 2, 2, 1> + q*|4, 2, 1, 1, 1>
        sage: F(A[7,3,2,1,1])
        |7, 3, 2, 1, 1> + q*|7, 2, 2, 2, 1> + q^2*|7, 2, 2, 1, 1, 1>
         + q*|6, 3, 3, 1, 1> + q^2*|6, 2, 2, 2, 2> + q^3*|6, 2, 2, 1, 1, 1, 1>
         + q*|5, 5, 2, 1, 1> + q^2*|5, 4, 3, 1, 1> + (q^2+1)*|4, 4, 3, 2, 1>
         + (q^3+q)*|4, 4, 3, 1, 1, 1> + (q^3+q)*|4, 4, 2, 2, 2>
         + (q^4+q^2)*|4, 4, 2, 1, 1, 1, 1> + q*|4, 3, 3, 3, 1>
         + q^2*|4, 3, 2, 1, 1, 1, 1, 1> + q^2*|4, 2, 2, 2, 2, 2>
         + q^3*|4, 2, 2, 2, 1, 1, 1, 1> + q^2*|3, 3, 3, 3, 2>
         + q^3*|3, 3, 3, 1, 1, 1, 1, 1> + q^3*|3, 2, 2, 2, 2, 2, 1>
         + q^4*|3, 2, 2, 2, 2, 1, 1, 1>
        sage: F(G[7,3,2,1,1])
        |7, 3, 2, 1, 1> + q*|7, 2, 2, 2, 1> + q^2*|7, 2, 2, 1, 1, 1>
         + q*|6, 3, 3, 1, 1> + q^2*|6, 2, 2, 2, 2>
         + q^3*|6, 2, 2, 1, 1, 1, 1> + q*|5, 5, 2, 1, 1>
         + q^2*|5, 4, 3, 1, 1> + q^2*|4, 4, 3, 2, 1>
         + q^3*|4, 4, 3, 1, 1, 1> + q^3*|4, 4, 2, 2, 2>
         + q^4*|4, 4, 2, 1, 1, 1, 1>
        sage: A(F(G[7,3,2,1,1]))
        A[7, 3, 2, 1, 1] - A[4, 4, 3, 2, 1]
        sage: G(F(A[7,3,2,1,1]))
        G[7, 3, 2, 1, 1] + G[4, 4, 3, 2, 1]
        sage: A(F(G[8,4,3,2,2,1]))
        A[8, 4, 3, 2, 2, 1] - A[6, 4, 4, 2, 2, 1, 1] - A[5, 5, 4, 3, 2, 1]
         + ((-q^2-1)/q)*A[5, 4, 4, 3, 2, 1, 1]
        sage: G(F(A[8,4,3,2,2,1]))
        G[8, 4, 3, 2, 2, 1] + G[6, 4, 4, 2, 2, 1, 1] + G[5, 5, 4, 3, 2, 1]
         + ((q^2+1)/q)*G[5, 4, 4, 3, 2, 1, 1]

    We can also construct higher level Fock spaces and perform
    similar computations::

        sage: Fock = FockSpace(3, [1,0])
        sage: F = Fock.natural()
        sage: A = Fock.A()
        sage: G = Fock.G()
        sage: F(G[[2,1],[4,1,1]])
        |[2, 1], [4, 1, 1]> + q*|[2, 1], [3, 2, 1]>
         + q^2*|[2, 1], [3, 1, 1, 1]> + q^2*|[2], [4, 2, 1]>
         + q^3*|[2], [4, 1, 1, 1]> + q^4*|[2], [3, 2, 1, 1]>
         + q*|[1, 1, 1], [4, 1, 1]> + q^2*|[1, 1, 1], [3, 2, 1]>
         + q^3*|[1, 1, 1], [3, 1, 1, 1]> + q^2*|[1, 1], [3, 2, 2]>
         + q^3*|[1, 1], [3, 1, 1, 1, 1]> + q^3*|[1], [4, 2, 2]>
         + q^4*|[1], [4, 1, 1, 1, 1]> + q^4*|[1], [3, 2, 2, 1]>
         + q^5*|[1], [3, 2, 1, 1, 1]>
        sage: A(F(G[[2,1],[4,1,1]]))
        A([2, 1], [4, 1, 1]) - A([2], [4, 2, 1])
        sage: G(F(A[[2,1],[4,1,1]]))
        G([2, 1], [4, 1, 1]) + G([2], [4, 2, 1])

    For level `0`, the truncated Fock space of [GW1999]_
    is implemented. This can be used to improve the speed
    of the computation of the lower global crystal basis,
    provided the truncation is not too small::

        sage: FS = FockSpace(2)
        sage: F = FS.natural()
        sage: G = FS.G()
        sage: FS3 = FockSpace(2, truncated=3)
        sage: F3 = FS3.natural()
        sage: G3 = FS3.G()
        sage: F(G[6,2,1])
        |6, 2, 1> + q*|5, 3, 1> + q^2*|5, 2, 2> + q^3*|5, 2, 1, 1>
         + q*|4, 2, 1, 1, 1> + q^2*|3, 3, 1, 1, 1> + q^3*|3, 2, 2, 1, 1>
         + q^4*|3, 2, 1, 1, 1, 1>
        sage: F3(G3[6,2,1])
        |6, 2, 1> + q*|5, 3, 1> + q^2*|5, 2, 2>
        sage: FS5 = FockSpace(2, truncated=5)
        sage: F5 = FS5.natural()
        sage: G5 = FS5.G()
        sage: F5(G5[6,2,1])
        |6, 2, 1> + q*|5, 3, 1> + q^2*|5, 2, 2> + q^3*|5, 2, 1, 1>
         + q*|4, 2, 1, 1, 1> + q^2*|3, 3, 1, 1, 1> + q^3*|3, 2, 2, 1, 1>

    REFERENCES:

    - [Ariki1996]_
    - [LLT1996]_
    - [Fayers2010]_
    - [GW1999]_
    """
    @staticmethod
    def __classcall_private__(cls, n, multicharge=[0], q=None, base_ring=None, truncated=None):
        r"""
        Standardize input to ensure a unique representation.

        EXAMPLES::

            sage: R.<q> = ZZ[]
            sage: F1 = FockSpace(3, [0])
            sage: F2 = FockSpace(3, 0, q)
            sage: F3 = FockSpace(3, (0,), q, R)
            sage: F1 is F2 and F2 is F3
            True
        """
        if q is None:
            base_ring = PolynomialRing(ZZ, 'q')
            q = base_ring.gen(0)
        if base_ring is None:
            base_ring = q.parent()
        base_ring = FractionField(base_ring)
        q = base_ring(q)
        M = IntegerModRing(n)
        if multicharge in ZZ:
            multicharge = (multicharge,)
        multicharge = tuple(M(e) for e in multicharge)
        if truncated is not None:
            return FockSpaceTruncated(n, truncated, q, base_ring)
        return super(FockSpace, cls).__classcall__(cls, n, multicharge, q, base_ring)

    def __init__(self, n, multicharge, q, base_ring):
        r"""
        Initialize ``self``.

        EXAMPLES::

            sage: F = FockSpace(3, [0])
            sage: TestSuite(F).run()
            sage: F = FockSpace(3, [1, 2])
            sage: TestSuite(F).run()
        """
        self._n = n
        self._q = q
        self._multicharge = multicharge
        self._index_set = set(range(n))
        cat = ModulesWithBasis(base_ring).WithRealizations()
        Parent.__init__(self, base=base_ring, category=cat)
        self._realizations = [self.natural(), self.A(), self.G()]

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

        EXAMPLES::

            sage: FockSpace(2)
            Fock space of rank 2 of multicharge (0,) over Fraction Field
             of Univariate Polynomial Ring in q over Integer Ring
            sage: FockSpace(4, [2, 0, 1])
            Fock space of rank 4 of multicharge (2, 0, 1) over Fraction Field
             of Univariate Polynomial Ring in q over Integer Ring
        """
        return "Fock space of rank {} of multicharge {} over {}".format(
                        self._n, self._multicharge, self.base_ring())

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

        EXAMPLES::

            sage: F = FockSpace(2)
            sage: latex(F)
            \mathcal{F}_{q}^{2}\left(0\right)
            sage: F = FockSpace(4, [2, 0, 1])
            sage: latex(F)
            \mathcal{F}_{q}^{4}\left(2, 0, 1\right)
        """
        from sage.misc.latex import latex
        return "\\mathcal{{F}}_{{{q}}}^{{{n}}}{mc}".format(q=latex(self._q), n=self._n,
                                                           mc=latex(self._multicharge))

    options = FockSpaceOptions

    def q(self):
        r"""
        Return the parameter `q` of ``self``.

        EXAMPLES::

            sage: F = FockSpace(2)
            sage: F.q()
            q

            sage: F = FockSpace(2, q=-1)
            sage: F.q()
            -1
        """
        return self._q

    def multicharge(self):
        r"""
        Return the multicharge of ``self``.

        EXAMPLES::

            sage: F = FockSpace(2)
            sage: F.multicharge()
            (0,)

            sage: F = FockSpace(4, [2, 0, 1])
            sage: F.multicharge()
            (2, 0, 1)
        """
        return self._multicharge

    def a_realization(self):
        r"""
        Return a realization of ``self``.

        EXAMPLES::

            sage: FS = FockSpace(2)
            sage: FS.a_realization()
            Fock space of rank 2 of multicharge (0,) over
             Fraction Field of Univariate Polynomial Ring in q over Integer Ring
             in the natural basis
        """
        return self.natural()

    def inject_shorthands(self, verbose=True):
        r"""
        Import standard shorthands into the global namespace.

        INPUT:

        - ``verbose`` -- boolean (default ``True``) if ``True``, prints
          the defined shorthands

        EXAMPLES::

            sage: FS = FockSpace(4)
            sage: FS.inject_shorthands()
            Injecting A as shorthand for Fock space of rank 4
             of multicharge (0,) over Fraction Field
             of Univariate Polynomial Ring in q over Integer Ring
             in the approximation basis
            Injecting F as shorthand for Fock space of rank 4
             of multicharge (0,) over Fraction Field
             of Univariate Polynomial Ring in q over Integer Ring
             in the natural basis
            Injecting G as shorthand for Fock space of rank 4
             of multicharge (0,) over Fraction Field
             of Univariate Polynomial Ring in q over Integer Ring
             in the lower global crystal basis
        """
        from sage.misc.misc import inject_variable
        for shorthand in ['A', 'F', 'G']:
            realization = getattr(self, shorthand)()
            if verbose:
                print('Injecting {} as shorthand for {}'.format(shorthand, realization))
            inject_variable(shorthand, realization)

    def highest_weight_vector(self):
        r"""
        Return the module generator of ``self`` in the natural basis.

        EXAMPLES::

            sage: FS = FockSpace(2)
            sage: FS.highest_weight_vector()
            |>
            sage: FS = FockSpace(4, [2, 0, 1])
            sage: FS.highest_weight_vector()
            |[], [], []>
        """
        return self.natural().highest_weight_vector()

    def __getitem__(self, i):
        r"""
        Return the basis element indexed by ``i``.

        INPUT:

        - ``i`` -- a partition

        EXAMPLES::

            sage: FS = FockSpace(2)
            sage: FS[[]]
            |>
            sage: FS[1]
            |1>
            sage: FS[2,2,1]
            |2, 2, 1>

            sage: FS = FockSpace(3, [1, 2])
            sage: FS[[], []]
            |[], []>
            sage: FS[[2,1], [3,1,1]]
            |[2, 1], [3, 1, 1]>
        """
        return self.natural()[i]

    class F(CombinatorialFreeModule, BindableClass):
        r"""
        The natural basis of the Fock space.

        This is the basis indexed by partitions. This has an action
        of the quantum group `U_q(\widehat{\mathfrak{sl}}_n)`
        described in
        :class:`~sage.algebras.quantum_groups.fock_space.FockSpace`.

        EXAMPLES:

        We construct the natural basis and perform some computations::

            sage: F = FockSpace(4).natural()
            sage: q = F.q()
            sage: u = F.highest_weight_vector()
            sage: u
            |>
            sage: u.f(0,1,2)
            |3>
            sage: u.f(0,1,3)
            |2, 1>
            sage: u.f(0,1,2,0)
            0
            sage: u.f(0,1,3,2)
            |3, 1> + q*|2, 1, 1>
            sage: u.f(0,1,2,3)
            |4> + q*|3, 1>
            sage: u.f(0,1,3,2,2,0)
            ((q^2+1)/q)*|3, 2, 1>
            sage: x = (q^4 * u + u.f(0,1,3,(2,2)))
            sage: x
            |3, 1, 1> + q^4*|>
            sage: x.f(0,1,3)
            |4, 3, 1> + q*|4, 2, 1, 1> + q*|3, 3, 2>
             + q^2*|3, 2, 2, 1> + q^4*|2, 1>
            sage: x.h_inverse(2)
            q^2*|3, 1, 1> + q^4*|>
            sage: x.h_inverse(0)
            1/q*|3, 1, 1> + q^3*|>
            sage: x.d()
            1/q*|3, 1, 1> + q^4*|>
            sage: x.e(2)
            |3, 1> + q*|2, 1, 1>
        """
        def __init__(self, F):
            """
            Initialize ``self``.

            EXAMPLES::

                sage: F = FockSpace(2).natural()
                sage: TestSuite(F).run()  # long time
            """
            self._basis_name = "natural"
            # If the cell x is above the cell y
            if len(F._multicharge) == 1: # For partitions
                self._above = lambda x,y: x[0] < y[0]
            else: # For partition tuples
                self._above = lambda x,y: x[0] < y[0] or (x[0] == y[0] and x[1] < y[1])
            self._addable = lambda la,i: [x for x in la.outside_corners()
                                          if la.content(*x, multicharge=F._multicharge) == i]
            self._removable = lambda la,i: [x for x in la.corners()
                                            if la.content(*x, multicharge=F._multicharge) == i]

            indices = PartitionTuples(level=len(F._multicharge))
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='F',
                                             latex_prefix='',
                                             bracket=False,
                                             latex_bracket=['\\left\\lvert', '\\right\\rangle'],
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))

        options = FockSpaceOptions

        def _repr_term(self, m):
            r"""
            Return a representation of the monomial indexed by ``m``.

            EXAMPLES::

                sage: F = FockSpace(2).natural()
                sage: F._repr_term(Partition([2,1,1]))
                '|2, 1, 1>'
                sage: F.highest_weight_vector()
                |>
                sage: F = FockSpace(2, [2, 1, 1]).natural()
                sage: mg = F.highest_weight_vector(); mg
                |[], [], []>
                sage: q = F.q()
                sage: mg.f(1).f(1).f(0) / (q^-1 + q)
                |[1], [1], [1]> + q*|[], [2], [1]> + q^2*|[], [1, 1], [1]>
                 + q^3*|[], [1], [2]> + q^4*|[], [1], [1, 1]>
            """
            if self.options.display != 'ket':
                return CombinatorialFreeModule._repr_term(self, m)
            return '|' + m._repr_list()[1:-1] + ">" # Strip the outer brackets of m

        def _ascii_art_term(self, m):
            r"""
            Return a representation of the monomial indexed by ``m``.

            EXAMPLES::

                sage: FS = FockSpace(4)
                sage: F = FS.natural()
                sage: x = F.an_element()
                sage: ascii_art(x)
                3*|**> + 2*|*> + 2*|->
                sage: ascii_art(x.f(3,2,2,0,1))
                ((3*q^2+3)/q)*|***\  + (3*q^2+3)*|***\
                              |*** >             |**  \
                              |*  /              |*   /
                                                 |*  /
            """
            if self.options.display != 'ket':
                return CombinatorialFreeModule._ascii_art_term(self, m)
            from sage.typeset.ascii_art import AsciiArt, ascii_art
            a = ascii_art(m)
            h = a.height()
            l = AsciiArt(['|']*h)
            r = AsciiArt([' '*i + '\\' for i in range(h//2)], baseline=0)
            if h % 2:
                r *= AsciiArt([' '*(h//2) + '>'], baseline=0)
            r *= AsciiArt([' '*i + '/' for i in reversed(range(h//2))], baseline=0)
            ret = l + a + r
            ret._baseline = h - 1
            return ret

        def _unicode_art_term(self, m):
            r"""
            Return an unicode art representing the generator indexed by ``m``.

            TESTS::

                sage: FS = FockSpace(4)
                sage: F = FS.natural()
                sage: x = F.an_element()
                sage: unicode_art(x)
                3*│┌┬┐╲ + 2*│┌┐╲ + 2*│∅〉
                  │└┴┘╱     │└┘╱
                sage: unicode_art(x.f(3,2,2,0,1))
                ((3*q^2+3)/q)*│┌┬┬┐╲  + (3*q^2+3)*│┌┬┬┐╲
                              │├┼┼┤ ╲             │├┼┼┘ ╲
                              │├┼┴┘ ╱             │├┼┘   〉
                              │└┘  ╱              │├┤   ╱
                                                  │└┘  ╱
            """
            if self.options.display != 'ket':
                return CombinatorialFreeModule._ascii_art_term(self, m)
            from sage.typeset.unicode_art import UnicodeArt, unicode_art
            a = unicode_art(m)
            h = a.height()
            l = UnicodeArt([u'│']*h, baseline=0)
            r = UnicodeArt([u" "*i + u'╲' for i in range(h//2)], baseline=0)
            if h % 2:
                r *= UnicodeArt([u" "*(h//2) + u'〉'], baseline=0)
            r *= UnicodeArt([u" "*i + u'╱' for i in reversed(range(h//2))], baseline=0)
            ret = l + a + r
            ret._baseline = h - 1
            return ret

        def _test_representation(self, **options):
            r"""
            Test that ``self`` is a
            `U_q(\widehat{\mathfrak{sl}}_n)`-representation.

            EXAMPLES::

                sage: F = FockSpace(3, [0,1]).natural()
                sage: F._test_representation()  # long time
            """
            from sage.combinat.root_system.cartan_matrix import CartanMatrix
            from sage.combinat.root_system.root_system import RootSystem
            from sage.algebras.quantum_groups.q_numbers import q_binomial

            tester = self._tester(**options)
            F = self.realization_of()
            q = F.q()
            n = F._n
            I = F._index_set
            A = CartanMatrix(['A',n-1,1])
            P = RootSystem(['A',n-1,1]).weight_lattice()
            al = P.simple_roots()
            ac = P.simple_coroots()
            zero = self.zero()
            for x in self.some_elements():
                for i in I:
                    for j in I:
                        tester.assertEqual(x.h_inverse(j).f(i).h(j), q**-al[i].scalar(ac[j]) * x.f(i))
                        tester.assertEqual(x.h_inverse(j).e(i).h(j), q**al[i].scalar(ac[j]) * x.e(i))
                        if i == j:
                            tester.assertEqual(x.f(i).e(i) - x.e(i).f(i),
                                               (x.h(i) - x.h_inverse(i)) / (q - q**-1))
                            continue
                        tester.assertEqual(x.f(j).e(i) - x.e(i).f(j), zero)
                        aij = A[i,j]
                        tester.assertEqual(zero,
                                           sum((-1)**k
                                           * q_binomial(1-aij, k, q)
                                           * x.e(*([i]*(1-aij-k) + [j] + [i]*k))
                                           for k in range(1-aij+1)))
                        tester.assertEqual(zero,
                                           sum((-1)**k
                                           * q_binomial(1-aij, k, q)
                                           * x.f(*([i]*(1-aij-k) + [j] + [i]*k))
                                           for k in range(1-aij+1)))

        class Element(CombinatorialFreeModule.Element):
            """
            An element in the Fock space.
            """
            def _e(self, i):
                r"""
                Apply `e_i` to ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F[2,1,1]._e(1)
                    1/q*|1, 1, 1>
                    sage: F[2,1,1]._e(0)
                    |2, 1>
                    sage: F[3,2,1]._e(1)
                    0

                    sage: F = FockSpace(4, [2, 0, 1])
                    sage: F[[2,1],[1],[2]]._e(2)
                    |[2, 1], [1], [1]>
                """
                P = self.parent()
                def N_left(la, x, i):
                    return (sum(1 for y in P._addable(la, i) if P._above(x, y))
                            - sum(1 for y in P._removable(la, i) if P._above(x, y)))
                q = P.realization_of()._q
                return P.sum_of_terms(( la.remove_cell(*x), c * q**(-N_left(la, x, i)) )
                                      for la,c in self for x in P._removable(la, i))

            def e(self, *data):
                r"""
                Apply the action of the divided difference operator
                `e_i^{(p)}` on ``self``.

                INPUT:

                - ``*data`` -- a list of indices or pairs `(i, p)`

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F[2,1,1].e(1)
                    1/q*|1, 1, 1>
                    sage: F[2,1,1].e(0)
                    |2, 1>
                    sage: F[2,1,1].e(0).e(1)
                    |2> + q*|1, 1>
                    sage: F[2,1,1].e(0).e(1).e(1)
                    ((q^2+1)/q)*|1>
                    sage: F[2,1,1].e(0).e((1, 2))
                    |1>
                    sage: F[2,1,1].e(0, 1, 1, 1)
                    0
                    sage: F[2,1,1].e(0, (1, 3))
                    0
                    sage: F[2,1,1].e(0, (1,2), 0)
                    |>
                    sage: F[2,1,1].e(1, 0, 1, 0)
                    1/q*|>

                    sage: F = FockSpace(4, [2, 0, 1])
                    sage: F[[2,1],[1],[2]]
                    |[2, 1], [1], [2]>
                    sage: F[[2,1],[1],[2]].e(2)
                    |[2, 1], [1], [1]>
                    sage: F[[2,1],[1],[2]].e(1)
                    1/q*|[2], [1], [2]>
                    sage: F[[2,1],[1],[2]].e(0)
                    1/q*|[2, 1], [], [2]>
                    sage: F[[2,1],[1],[2]].e(3)
                    1/q^2*|[1, 1], [1], [2]>
                    sage: F[[2,1],[1],[2]].e(3, 2, 1)
                    1/q^2*|[1, 1], [1], []> + 1/q^2*|[1], [1], [1]>
                    sage: F[[2,1],[1],[2]].e(3, 2, 1, 0, 1, 2)
                    2/q^3*|[], [], []>
                """
                ret = self
                q = self.parent().realization_of()._q
                I = self.parent().realization_of()._index_set
                for i in data:
                    if isinstance(i, tuple):
                        i, p = i
                    else:
                        p = 1
                    if i not in I:
                        raise ValueError("{} not in the index set".format(i))

                    for _ in range(p):
                        ret = ret._e(i)
                    if p > 1:
                        ret = ret / q_factorial(p, q)
                return ret

            def _f(self, i):
                r"""
                Apply `f_i` to ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F.highest_weight_vector()._f(0)
                    |1>
                    sage: F[5,2,2,1]._f(0)
                    1/q*|5, 2, 2, 2> + |5, 2, 2, 1, 1>
                    sage: F[5,2,2,1]._f(1)
                    |6, 2, 2, 1> + q*|5, 3, 2, 1>

                    sage: F = FockSpace(4, [2, 0, 1])
                    sage: F[[3,1], [1,1,1], [4,2,2]]._f(0)
                    1/q*|[3, 1, 1], [1, 1, 1], [4, 2, 2]>
                    sage: F[[3,1], [1,1,1], [4,2,2]]._f(1)
                    |[4, 1], [1, 1, 1], [4, 2, 2]>
                     + |[3, 1], [2, 1, 1], [4, 2, 2]>
                     + q*|[3, 1], [1, 1, 1, 1], [4, 2, 2]>
                     + q^2*|[3, 1], [1, 1, 1], [5, 2, 2]>
                """
                P = self.parent()
                def N_right(la, x, i):
                    return (sum(1 for y in P._addable(la, i) if P._above(y, x))
                            - sum(1 for y in P._removable(la, i) if P._above(y, x)))
                q = P.realization_of()._q
                return P.sum_of_terms( (la.add_cell(*x), c * q**N_right(la, x, i))
                                       for la,c in self for x in P._addable(la, i) )

            def f(self, *data):
                r"""
                Apply the action of the divided difference operator
                `f_i^{(p)}` on ``self``.

                INPUT:

                - ``*data`` -- a list of indices or pairs `(i, p)`

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: mg = F.highest_weight_vector()
                    sage: mg.f(0)
                    |1>
                    sage: mg.f(0).f(1)
                    |2> + q*|1, 1>
                    sage: mg.f(0).f(0)
                    0
                    sage: mg.f((0, 2))
                    0
                    sage: mg.f(0, 1, 1)
                    ((q^2+1)/q)*|2, 1>
                    sage: mg.f(0, (1, 2))
                    |2, 1>
                    sage: mg.f(0, 1, 0)
                    |3> + q*|1, 1, 1>

                    sage: F = FockSpace(4, [2, 0, 1])
                    sage: mg = F.highest_weight_vector()
                    sage: mg.f(0)
                    |[], [1], []>
                    sage: mg.f(2)
                    |[1], [], []>
                    sage: mg.f(1)
                    |[], [], [1]>
                    sage: mg.f(1, 0)
                    |[], [1], [1]> + q*|[], [], [1, 1]>
                    sage: mg.f(0, 1)
                    |[], [2], []> + q*|[], [1], [1]>
                    sage: mg.f(0, 1, 3)
                    |[], [2, 1], []> + q*|[], [1, 1], [1]>
                    sage: mg.f(3)
                    0
                """
                ret = self
                q = self.parent().realization_of()._q
                I = self.parent().realization_of()._index_set
                for i in data:
                    if isinstance(i, tuple):
                        i, p = i
                    else:
                        p = 1
                    if i not in I:
                        raise ValueError("{} not in the index set".format(i))

                    for _ in range(p):
                        ret = ret._f(i)
                    if p > 1:
                        ret = ret / q_factorial(p, q)
                return ret

            def h(self, *data):
                r"""
                Apply the action of `h_i` on ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F[2,1,1].h(0)
                    q*|2, 1, 1>
                    sage: F[2,1,1].h(1)
                    |2, 1, 1>
                    sage: F[2,1,1].h(0, 0)
                    q^2*|2, 1, 1>

                    sage: F = FockSpace(4, [2,0,1])
                    sage: elt = F[[2,1],[1],[2]]
                    sage: elt.h(0)
                    q^2*|[2, 1], [1], [2]>
                    sage: elt.h(1)
                    |[2, 1], [1], [2]>
                    sage: elt.h(2)
                    |[2, 1], [1], [2]>
                    sage: elt.h(3)
                    q*|[2, 1], [1], [2]>
                """
                P = self.parent()
                q = P.realization_of()._q
                I = self.parent().realization_of()._index_set
                d = self.monomial_coefficients(copy=True)
                for i in data:
                    if i not in I:
                        raise ValueError("{} not in the index set".format(i))
                    for la in d:
                        d[la] *= q**(len(P._addable(la, i)) - len(P._removable(la, i)))
                return P._from_dict(d, coerce=False)

            def h_inverse(self, *data):
                r"""
                Apply the action of `h_i^{-1}` on ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F[2,1,1].h_inverse(0)
                    1/q*|2, 1, 1>
                    sage: F[2,1,1].h_inverse(1)
                    |2, 1, 1>
                    sage: F[2,1,1].h_inverse(0, 0)
                    1/q^2*|2, 1, 1>

                    sage: F = FockSpace(4, [2,0,1])
                    sage: elt = F[[2,1],[1],[2]]
                    sage: elt.h_inverse(0)
                    1/q^2*|[2, 1], [1], [2]>
                    sage: elt.h_inverse(1)
                    |[2, 1], [1], [2]>
                    sage: elt.h_inverse(2)
                    |[2, 1], [1], [2]>
                    sage: elt.h_inverse(3)
                    1/q*|[2, 1], [1], [2]>
                """
                P = self.parent()
                q = P.realization_of()._q
                I = self.parent().realization_of()._index_set
                d = self.monomial_coefficients(copy=True)
                for i in data:
                    if i not in I:
                        raise ValueError("{} not in the index set".format(i))
                    for la in d:
                        d[la] *= q**-(len(P._addable(la, i)) - len(P._removable(la, i)))
                return P._from_dict(d, coerce=False)

            def d(self):
                r"""
                Apply the action of `d` on ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2)
                    sage: F.highest_weight_vector().d()
                    |>
                    sage: F[2,1,1].d()
                    1/q^2*|2, 1, 1>
                    sage: F[5,3,3,1,1,1].d()
                    1/q^7*|5, 3, 3, 1, 1, 1>

                    sage: F = FockSpace(4, [2,0,1])
                    sage: F.highest_weight_vector().d()
                    |[], [], []>
                    sage: F[[2,1],[1],[2]].d()
                    1/q*|[2, 1], [1], [2]>
                    sage: F[[4,2,2,1],[1],[5,2]].d()
                    1/q^5*|[4, 2, 2, 1], [1], [5, 2]>
                """
                P = self.parent()
                R = P.realization_of()
                q = R._q
                d = self.monomial_coefficients(copy=True)
                for la in d:
                    d[la] *= q**-sum(1 for x in la.cells()
                                     if la.content(*x, multicharge=R._multicharge) == 0)
                return P._from_dict(d, coerce=False)

    natural = F

    class A(CombinatorialFreeModule, BindableClass):
        r"""
        The `A` basis of the Fock space which is the approximation
        of the lower global crystal basis.

        The approximation basis `A` is a basis that is constructed
        from the highest weight element by applying divided
        difference operators using the ladder construction of
        [LLT1996]_ and [GW1999]_. Thus, this basis is bar invariant
        and upper unitriangular (using dominance order on partitions)
        when expressed in the natural basis. This basis is then
        converted to the lower global crystal basis by using
        Gaussian elimination.

        EXAMPLES:

        We construct Example 6.5 and 6.7 in [LLT1996]_::

            sage: FS = FockSpace(2)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: A = FS.A()
            sage: F(A[5])
            |5> + |3, 2> + 2*q*|3, 1, 1> + q^2*|2, 2, 1> + q^2*|1, 1, 1, 1, 1>
            sage: F(A[4,1])
            |4, 1> + q*|2, 1, 1, 1>
            sage: F(A[3,2])
            |3, 2> + q*|3, 1, 1> + q^2*|2, 2, 1>
            sage: F(G[5])
            |5> + q*|3, 1, 1> + q^2*|1, 1, 1, 1, 1>

        We construct the examples in Section 5.1 of [Fayers2010]_::

            sage: FS = FockSpace(2, [0, 0])
            sage: F = FS.natural()
            sage: A = FS.A()
            sage: F(A[[2,1],[1]])
            |[2, 1], [1]> + q*|[2], [2]> + q^2*|[2], [1, 1]> + q^2*|[1, 1], [2]>
             + q^3*|[1, 1], [1, 1]> + q^4*|[1], [2, 1]>
            sage: F(A[[4],[]])
            |[4], []> + q*|[3, 1], []> + q*|[2, 1, 1], []>
             + (q^2+1)*|[2, 1], [1]> + 2*q*|[2], [2]> + 2*q^2*|[2], [1, 1]>
             + q^2*|[1, 1, 1, 1], []> + 2*q^2*|[1, 1], [2]>
             + 2*q^3*|[1, 1], [1, 1]> + (q^4+q^2)*|[1], [2, 1]>
             + q^2*|[], [4]> + q^3*|[], [3, 1]> + q^3*|[], [2, 1, 1]>
             + q^4*|[], [1, 1, 1, 1]>
        """
        def __init__(self, F):
            r"""
            Initialize ``self``.

            EXAMPLES::

                sage: A = FockSpace(2).A()
                sage: TestSuite(A).run()
            """
            self._basis_name = "approximation"
            indices = PartitionTuples(level=len(F._multicharge),
                                      regular=F._n)
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='A', bracket=False,
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))
            self.module_morphism(self._A_to_fock_basis,
                                 triangular='upper', unitriangular=True,
                                 codomain=F.natural()).register_as_coercion()

        options = FockSpaceOptions

        @cached_method
        def _A_to_fock_basis(self, la):
            r"""
            Return the `A` basis indexed by ``la`` in the natural basis.

            EXAMPLES::

                sage: A = FockSpace(3).A()
                sage: A._A_to_fock_basis(Partition([3]))
                |3> + q*|2, 1>
                sage: A._A_to_fock_basis(Partition([2,1]))
                |2, 1> + q*|1, 1, 1>

                sage: FS = FockSpace(2, [0,1])
                sage: F = FS.natural()
                sage: A = FS.A()
                sage: F(A[[],[1]])
                |[], [1]>
            """
            R = self.realization_of()
            fock = R.natural()

            if la.size() == 0:
                return fock.highest_weight_vector()

            if len(R._multicharge) > 1:
                # Find one more than the first non-empty partition
                k = 1
                for p in la:
                    if p.size() != 0:
                        break
                    k += 1

                # Reduce down to the lower level Fock space and do the computation
                #   and then lift back up to us by prepending empty partitions
                if k == len(R._multicharge): # This means we get the empty partition
                    cur = fock.highest_weight_vector()
                else:
                    F = FockSpace(R._n, R._multicharge[k:], R._q, R.base_ring())
                    Gp = F.G()
                    if k + 1 == len(R._multicharge):
                        cur = Gp._G_to_fock_basis(Gp._indices(la[k]))
                        cur = fock.sum_of_terms((fock._indices([[]]*k + [p]), c)
                                                for p,c in cur)
                    else:
                        cur = Gp._G_to_fock_basis(Gp._indices(la[k:]))
                        cur = fock.sum_of_terms((fock._indices([[]]*k + list(pt)), c)
                                                for pt,c in cur)
                la = la[k-1]
                r = R._multicharge[k-1]
            else:
                cur = fock.highest_weight_vector()
                r = R._multicharge[0]

            # Get the ladders and apply it to the current element
            corners = la.corners()
            cells = set(la.cells())
            q = R._q
            k = R._n - 1 # This is sl_{k+1}
            b = ZZ.zero()
            # While there is some cell left to count
            while any(c[1]*k + c[0] >= b for c in corners):
                power = 0
                i = -b + r # This will be converted to a mod n number
                for x in range(0, b // k + 1):
                    if (b-x*k, x) in cells:
                        power += 1
                        cur = cur.f(i)
                cur /= q_factorial(power, q)
                b += 1
            return cur

    approximation = A

    class G(CombinatorialFreeModule, BindableClass):
        r"""
        The lower global crystal basis living inside of Fock space.

        EXAMPLES:

        We construct some of the tables/entries given in Section 10
        of [LLT1996]_. For `\widehat{\mathfrak{sl}}_2`::

            sage: FS = FockSpace(2)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[2])
            |2> + q*|1, 1>
            sage: F(G[3])
            |3> + q*|1, 1, 1>
            sage: F(G[2,1])
            |2, 1>
            sage: F(G[4])
            |4> + q*|3, 1> + q*|2, 1, 1> + q^2*|1, 1, 1, 1>
            sage: F(G[3,1])
            |3, 1> + q*|2, 2> + q^2*|2, 1, 1>
            sage: F(G[5])
            |5> + q*|3, 1, 1> + q^2*|1, 1, 1, 1, 1>
            sage: F(G[4,2])
            |4, 2> + q*|4, 1, 1> + q*|3, 3> + q^2*|3, 1, 1, 1>
             + q^2*|2, 2, 2> + q^3*|2, 2, 1, 1>
            sage: F(G[4,2,1])
            |4, 2, 1> + q*|3, 3, 1> + q^2*|3, 2, 2> + q^3*|3, 2, 1, 1>
            sage: F(G[6,2])
            |6, 2> + q*|6, 1, 1> + q*|5, 3> + q^2*|5, 1, 1, 1> + q*|4, 3, 1>
             + q^2*|4, 2, 2> + (q^3+q)*|4, 2, 1, 1> + q^2*|4, 1, 1, 1, 1>
             + q^2*|3, 3, 1, 1> + q^3*|3, 2, 2, 1> + q^3*|3, 1, 1, 1, 1, 1>
             + q^3*|2, 2, 2, 1, 1> + q^4*|2, 2, 1, 1, 1, 1>
            sage: F(G[5,3,1])
            |5, 3, 1> + q*|5, 2, 2> + q^2*|5, 2, 1, 1> + q*|4, 4, 1>
             + q^2*|4, 2, 1, 1, 1> + q^2*|3, 3, 3> + q^3*|3, 3, 1, 1, 1>
             + q^3*|3, 2, 2, 2> + q^4*|3, 2, 2, 1, 1>
            sage: F(G[4,3,2,1])
            |4, 3, 2, 1>
            sage: F(G[7,2,1])
            |7, 2, 1> + q*|5, 2, 1, 1, 1> + q^2*|3, 2, 1, 1, 1, 1, 1>
            sage: F(G[10,1])
            |10, 1> + q*|8, 1, 1, 1> + q^2*|6, 1, 1, 1, 1, 1>
             + q^3*|4, 1, 1, 1, 1, 1, 1, 1>
             + q^4*|2, 1, 1, 1, 1, 1, 1, 1, 1, 1>
            sage: F(G[6,3,2])
            |6, 3, 2> + q*|6, 3, 1, 1> + q^2*|6, 2, 2, 1> + q^3*|5, 3, 2, 1>
             + q*|4, 3, 2, 1, 1> + q^2*|4, 3, 1, 1, 1, 1>
             + q^3*|4, 2, 2, 1, 1, 1> + q^4*|3, 3, 2, 1, 1, 1>
            sage: F(G[5,3,2,1])
            |5, 3, 2, 1> + q*|4, 4, 2, 1> + q^2*|4, 3, 3, 1>
             + q^3*|4, 3, 2, 2> + q^4*|4, 3, 2, 1, 1>

        For `\widehat{\mathfrak{sl}}_3`::

            sage: FS = FockSpace(3)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[2])
            |2>
            sage: F(G[1,1])
            |1, 1>
            sage: F(G[3])
            |3> + q*|2, 1>
            sage: F(G[2,1])
            |2, 1> + q*|1, 1, 1>
            sage: F(G[4])
            |4> + q*|2, 2>
            sage: F(G[3,1])
            |3, 1>
            sage: F(G[2,2])
            |2, 2> + q*|1, 1, 1, 1>
            sage: F(G[2,1,1])
            |2, 1, 1>
            sage: F(G[5])
            |5> + q*|2, 2, 1>
            sage: F(G[2,2,1])
            |2, 2, 1> + q*|2, 1, 1, 1>
            sage: F(G[4,1,1])
            |4, 1, 1> + q*|3, 2, 1> + q^2*|3, 1, 1, 1>
            sage: F(G[5,2])
            |5, 2> + q*|4, 3> + q^2*|4, 2, 1>
            sage: F(G[8])
            |8> + q*|5, 2, 1> + q*|3, 3, 1, 1> + q^2*|2, 2, 2, 2>
            sage: F(G[7,2])
            |7, 2> + q*|4, 2, 2, 1>
            sage: F(G[6,2,2])
            |6, 2, 2> + q*|6, 1, 1, 1, 1> + q*|4, 4, 2> + q^2*|3, 3, 2, 1, 1>

        For `\widehat{\mathfrak{sl}}_4`::

            sage: FS = FockSpace(4)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[4])
            |4> + q*|3, 1>
            sage: F(G[3,1])
            |3, 1> + q*|2, 1, 1>
            sage: F(G[2,2])
            |2, 2>
            sage: F(G[2,1,1])
            |2, 1, 1> + q*|1, 1, 1, 1>
            sage: F(G[3,2])
            |3, 2> + q*|2, 2, 1>
            sage: F(G[2,2,2])
            |2, 2, 2> + q*|1, 1, 1, 1, 1, 1>
            sage: F(G[6,1])
            |6, 1> + q*|4, 3>
            sage: F(G[3,2,2,1])
            |3, 2, 2, 1> + q*|3, 1, 1, 1, 1, 1> + q*|2, 2, 2, 2>
             + q^2*|2, 1, 1, 1, 1, 1, 1>
            sage: F(G[7,2])
            |7, 2> + q*|6, 2, 1> + q*|5, 4> + q^2*|5, 3, 1>
            sage: F(G[5,2,2,1])
            |5, 2, 2, 1> + q*|5, 1, 1, 1, 1, 1> + q*|4, 2, 2, 1, 1>
             + q^2*|4, 2, 1, 1, 1, 1>

        We construct the examples in Section 5.1 of [Fayers2010]_::

            sage: FS = FockSpace(2, [0, 0])
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[[2,1],[1]])
            |[2, 1], [1]> + q*|[2], [2]> + q^2*|[2], [1, 1]>
             + q^2*|[1, 1], [2]> + q^3*|[1, 1], [1, 1]> + q^4*|[1], [2, 1]>
            sage: F(G[[4],[]])
            |[4], []> + q*|[3, 1], []> + q*|[2, 1, 1], []> + q^2*|[2, 1], [1]>
             + q*|[2], [2]> + q^2*|[2], [1, 1]> + q^2*|[1, 1, 1, 1], []>
             + q^2*|[1, 1], [2]> + q^3*|[1, 1], [1, 1]> + q^2*|[1], [2, 1]>
             + q^2*|[], [4]> + q^3*|[], [3, 1]> + q^3*|[], [2, 1, 1]>
             + q^4*|[], [1, 1, 1, 1]>
        """
        def __init__(self, F):
            r"""
            Initialize ``self``.

            EXAMPLES::

                sage: G = FockSpace(2).G()
                sage: TestSuite(G).run()
            """
            self._basis_name = "lower global crystal"
            indices = PartitionTuples(level=len(F._multicharge),
                                      regular=F._n)
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='G', bracket=False,
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))
            self.module_morphism(self._G_to_fock_basis,
                                 triangular='upper', unitriangular=True,
                                 codomain=F.natural()).register_as_coercion()

        options = FockSpaceOptions

        @cached_method
        def _G_to_fock_basis(self, la):
            r"""
            Return the `G` basis indexed by ``la`` in the natural basis.

            EXAMPLES::

                sage: G = FockSpace(3).G()
                sage: G._G_to_fock_basis(Partition([3]))
                |3> + q*|2, 1>
                sage: G._G_to_fock_basis(Partition([2,1]))
                |2, 1> + q*|1, 1, 1>
            """
            # Special case for the empty partition
            if la.size() == 0:
                return self.realization_of().natural().highest_weight_vector()

            # Special case for empty leading partitions
            R = self.realization_of()
            if len(R._multicharge) > 1 and la[0].size() == 0:
                fock = R.natural()
                # Find the first non-empty partition
                k = 0
                for p in la:
                    if p.size() != 0:
                        break
                    k += 1

                # Reduce down to the lower level Fock space and do the computation
                #   and then lift back up by prepending empty partitions
                # Note that this will never be for the empty partition, which
                #   is already taken care of
                F = FockSpace(R._n, R._multicharge[k:], R._q, R.base_ring())
                Gp = F.G()
                if k + 1 == len(R._multicharge):
                    cur = Gp._G_to_fock_basis(Gp._indices(la[k]))
                    return fock.sum_of_terms((fock._indices([[]]*k + [p]), c) for p,c in cur)
                cur = Gp._G_to_fock_basis(Gp._indices(la[k:]))
                return fock.sum_of_terms((fock._indices([[]]*k + list(pt)), c) for pt,c in cur)

            cur = R.A()._A_to_fock_basis(la)
            s = cur.support()
            s.sort() # Sort lex, which respects dominance order
            s.pop() # Remove the largest

            q = R._q
            while s:
                mu = s.pop()
                d = cur[mu].denominator()
                k = d.degree()
                n = cur[mu].numerator()
                if k != 0 or n.constant_coefficient() != 0:
                    gamma = sum(n[i] * (q**(i-k) + q**(k-i))
                                for i in range(min(n.degree(), k)))
                    gamma += n[k]
                    cur -= gamma * self._G_to_fock_basis(mu)

                    # Add any new support elements
                    for x in cur.support():
                        if x == mu or not mu.dominates(x): # Add only things (strictly) dominated by mu
                            continue
                        for i in reversed(range(len(s))):
                            if not s[i].dominates(x):
                                s.insert(i+1, x)
                                break
            return cur

    lower_global_crystal = G
    canonical = G


###############################################################################
## Bases Category

class FockSpaceBases(Category_realization_of_parent):
    r"""
    The category of bases of a (truncated) Fock space.
    """
    def __init__(self, base):
        r"""
        Initialize the bases of a Fock space.

        INPUT:

        - ``base`` -- a Fock space

        TESTS::

            sage: from sage.algebras.quantum_groups.fock_space import FockSpaceBases
            sage: F = FockSpace(2)
            sage: bases = FockSpaceBases(F)
            sage: TestSuite(bases).run()
        """
        Category_realization_of_parent.__init__(self, base)

    def _repr_(self):
        r"""
        Return the representation of ``self``.

        EXAMPLES::

            sage: from sage.algebras.quantum_groups.fock_space import FockSpaceBases
            sage: F = FockSpace(2)
            sage: FockSpaceBases(F)
            Category of bases of Fock space of rank 2 of multicharge (0,) over
             Fraction Field of Univariate Polynomial Ring in q over Integer Ring
        """
        return "Category of bases of {}".format(self.base())

    def super_categories(self):
        r"""
        The super categories of ``self``.

        EXAMPLES::

            sage: from sage.algebras.quantum_groups.fock_space import FockSpaceBases
            sage: F = FockSpace(2)
            sage: bases = FockSpaceBases(F)
            sage: bases.super_categories()
            [Category of vector spaces with basis over Fraction Field
              of Univariate Polynomial Ring in q over Integer Ring,
             Category of realizations of Fock space of rank 2 of multicharge (0,)
              over Fraction Field of Univariate Polynomial Ring in q over Integer Ring]
        """
        return [ModulesWithBasis(self.base().base_ring()), Realizations(self.base())]

    class ParentMethods:
        def _repr_(self):
            r"""
            Text representation of this basis of Fock space.

            EXAMPLES::

                sage: FS = FockSpace(2)
                sage: FS.A()
                Fock space of rank 2 of multicharge (0,) over Fraction Field of
                 Univariate Polynomial Ring in q over Integer Ring
                 in the approximation basis
                sage: FS.G()
                Fock space of rank 2 of multicharge (0,) over Fraction Field of
                 Univariate Polynomial Ring in q over Integer Ring
                 in the lower global crystal basis
            """
            return "{} in the {} basis".format(self.realization_of(), self._basis_name)

        def some_elements(self):
            r"""
            Return some elements of ``self``.

            EXAMPLES::

                sage: F = FockSpace(3).natural()
                sage: F.some_elements()[::13]
                [3*|2> + 2*|1> + 2*|>,
                 |5>,
                 |3, 1, 1, 1>,
                 |3, 2, 2>,
                 |5, 1, 1, 1>,
                 |2, 2, 1, 1, 1, 1>,
                 |5, 2, 1, 1>,
                 |3, 2, 1, 1, 1, 1>]

                sage: F = FockSpace(3, [0,1]).natural()
                sage: F.some_elements()[::13]
                [2*|[1], []> + 4*|[], [1]> + |[], []>,
                 |[1, 1], [1]>,
                 |[1, 1, 1], [1]>,
                 |[5], []>,
                 |[3], [1, 1]>,
                 |[1], [2, 2]>,
                 |[4, 1, 1], []>,
                 |[2, 1, 1, 1], [1]>]
            """
            others = [self.monomial(la) for la in self.basis().keys().some_elements()]
            return [self.an_element()] + others

        def q(self):
            r"""
            Return the parameter `q` of ``self``.

            EXAMPLES::

                sage: FS = FockSpace(2)
                sage: A = FS.A()
                sage: A.q()
                q

                sage: FS = FockSpace(2, q=-1)
                sage: G = FS.G()
                sage: G.q()
                -1
            """
            return self.realization_of()._q

        def multicharge(self):
            r"""
            Return the multicharge of ``self``.

            EXAMPLES::

                sage: FS = FockSpace(4)
                sage: A = FS.A()
                sage: A.multicharge()
                (0,)

                sage: FS = FockSpace(4, [1,0,2])
                sage: G = FS.G()
                sage: G.multicharge()
                (1, 0, 2)
            """
            return self.realization_of()._multicharge

        @cached_method
        def highest_weight_vector(self):
            r"""
            Return the highest weight vector of ``self``.

            EXAMPLES::

                sage: FS = FockSpace(2)
                sage: F = FS.natural()
                sage: F.highest_weight_vector()
                |>
                sage: A = FS.A()
                sage: A.highest_weight_vector()
                A[]
                sage: G = FS.G()
                sage: G.highest_weight_vector()
                G[]
            """
            level = len(self.realization_of()._multicharge)
            if level == 1:
                return self.monomial(self._indices([]))
            return self.monomial(self._indices([[]]*level))

        def __getitem__(self, i):
            r"""
            Return the basis element indexed by ``i``.

            INPUT:

            - ``i`` -- a partition

            EXAMPLES::

                sage: F = FockSpace(3)
                sage: A = F.A()
                sage: A[[]]
                A[]
                sage: A[4]
                A[4]
                sage: A[2,2,1]
                A[2, 2, 1]
                sage: G = F.G()
                sage: G[[]]
                G[]
                sage: G[4]
                G[4]
                sage: G[2,2,1]
                G[2, 2, 1]

            For higher levels::

                sage: F = FockSpace(2, [0, 0])
                sage: G = F.G()
                sage: G[[2,1],[1]]
                G([2, 1], [1])

            TESTS::

                sage: F = FockSpace(3)
                sage: A = F.A()
                sage: A[2,2,2,1]
                Traceback (most recent call last):
                ...
                ValueError: [2, 2, 2, 1] is not an element of 3-Regular Partitions

                sage: F = FockSpace(3, [0, 0])
                sage: A = F.A()
                sage: A[[], [2,2,2,1]]
                Traceback (most recent call last):
                ...
                ValueError: [[], [2, 2, 2, 1]] is not a 3-Regular partition tuples of level 2
            """
            if i in ZZ:
                i = [i]

            i = self._indices(i)
            if i.size() == 0:
                return self.highest_weight_vector()
            return self.monomial(i)

###############################################################################
## Truncated Fock space

class FockSpaceTruncated(FockSpace):
    r"""
    This is the Fock space given by partitions of length no more than `k`.

    This can be formed as the quotient `\mathcal{F} / \mathcal{F}_k`,
    where `\mathcal{F}_k` is the submodule spanned by all diagrams
    of length (strictly) more than `k`.

    We have three bases:

    - The natural basis indexed by truncated `n`-regular partitions:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpaceTruncated.F`.
    - The approximation basis that comes from LLT(-type) algorithms:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpaceTruncated.A`.
    - The lower global crystal basis:
      :class:`~sage.algebras.quantum_groups.fock_space.FockSpaceTruncated.G`.

    .. SEEALSO::

        :class:`FockSpace`

    EXAMPLES::

        sage: F = FockSpace(2, truncated=2)
        sage: mg = F.highest_weight_vector()
        sage: mg.f(0)
        |1>
        sage: mg.f(0).f(1)
        |2> + q*|1, 1>
        sage: mg.f(0).f(1).f(0)
        |3>

    Compare this to the full Fock space::

        sage: F = FockSpace(2)
        sage: mg = F.highest_weight_vector()
        sage: mg.f(0).f(1).f(0)
        |3> + q*|1, 1, 1>

    REFERENCES:

    - [GW1999]_
    """
    @staticmethod
    def __classcall_private__(cls, n, k, q=None, base_ring=None):
        r"""
        Standardize input to ensure a unique representation.

        EXAMPLES::

            sage: R.<q> = ZZ[]
            sage: F1 = FockSpace(3, truncated=2)
            sage: F2 = FockSpace(3, q=q, truncated=2)
            sage: F3 = FockSpace(3, q=q, base_ring=R, truncated=2)
            sage: F1 is F2 and F2 is F3
            True
            sage: from sage.algebras.quantum_groups.fock_space import FockSpaceTruncated
            sage: F4 = FockSpaceTruncated(3, 2, q, R)
            sage: F1 is F4
            True
        """
        if q is None:
            base_ring = PolynomialRing(ZZ, 'q')
            q = base_ring.gen(0)
        if base_ring is None:
            base_ring = q.parent()
        base_ring = FractionField(base_ring)
        q = base_ring(q)
        return super(FockSpace, cls).__classcall__(cls, n, k, q, base_ring)

    def __init__(self, n, k, q, base_ring):
        r"""
        Initialize ``self``.

        EXAMPLES::

            sage: F = FockSpace(2, truncated=3)
            sage: TestSuite(F).run()
        """
        M = IntegerModRing(n)
        self._k = k
        FockSpace.__init__(self, n, (M(0),), q, base_ring)

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

        EXAMPLES::

            sage: FockSpace(2, truncated=3)
            Fock space of rank 2 truncated at 3 over Fraction Field of
             Univariate Polynomial Ring in q over Integer Ring
        """
        return "Fock space of rank {} truncated at {} over {}".format(self._n, self._k, self.base_ring())

    class F(CombinatorialFreeModule, BindableClass):
        r"""
        The natural basis of the truncated Fock space.

        This is the natural basis of the full Fock space projected
        onto the truncated Fock space. It inherits the
        `U_q(\widehat{\widetilde{sl}}_n)`-action from the action
        on the full Fock space.

        EXAMPLES::

            sage: FS = FockSpace(4)
            sage: F = FS.natural()
            sage: FS3 = FockSpace(4, truncated=3)
            sage: F3 = FS3.natural()
            sage: u = F.highest_weight_vector()
            sage: u3 = F3.highest_weight_vector()

            sage: u3.f(0,3,2,1)
            |2, 1, 1>
            sage: u.f(0,3,2,1)
            |2, 1, 1> + q*|1, 1, 1, 1>

            sage: u.f(0,3,2,1,1)
            ((q^2+1)/q)*|2, 1, 1, 1>
            sage: u3.f(0,3,2,1,1)
            0
        """
        def __init__(self, F):
            r"""
            Initialize ``self``.

            EXAMPLES::

                sage: F = FockSpace(2, truncated=3).natural()
                sage: TestSuite(F).run()  # long time
            """
            self._basis_name = "natural"
            # If the cell x is above the cell y
            if len(F._multicharge) == 1: # For partitions
                self._above = lambda x,y: x[0] < y[0]
            else: # For partition tuples
                self._above = lambda x,y: x[0] < y[0] or (x[0] == y[0] and x[1] < y[1])
            self._addable = lambda la,i: [x for x in la.outside_corners()
                                          if la.content(*x, multicharge=F._multicharge) == i]
            self._removable = lambda la,i: [x for x in la.corners()
                                            if la.content(*x, multicharge=F._multicharge) == i]

            indices = Partitions(F._n, max_length=F._k)
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='', bracket=['|', '>'],
                                             latex_bracket=['\\lvert', '\\rangle'],
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))

        options = FockSpaceOptions

        def _repr_term(self, m):
            r"""
            Return a representation of the monomial indexed by ``m``.

            EXAMPLES::

                sage: F = FockSpace(2, truncated=3).natural()
                sage: F._repr_term(Partition([2,1,1]))
                '|2, 1, 1>'
                sage: F.highest_weight_vector()
                |>
            """
            return '|' + repr(m)[1:-1] + ">" # Strip the outer brackets of m

        class Element(FockSpace.natural.Element):
            r"""
            An element in the truncated Fock space.
            """
            def _f(self, i):
                r"""
                Apply the action of `f_i` on ``self``.

                EXAMPLES::

                    sage: F = FockSpace(2, truncated=3).natural()
                    sage: mg = F.highest_weight_vector()
                    sage: mg.f(0)
                    |1>
                    sage: mg.f(0,1)
                    |2> + q*|1, 1>
                    sage: mg.f(0,1,0)
                    |3> + q*|1, 1, 1>
                    sage: mg.f(0,1,0,0)
                    0
                    sage: mg.f(0,1,0,1)
                    |4> + q*|3, 1> + q*|2, 1, 1>
                    sage: mg.f(0,1,0,1,0)
                    |5> + |3, 2> + 2*q*|3, 1, 1> + q^2*|2, 2, 1>
                """
                P = self.parent()
                def N_right(la, x, i):
                    return (sum(1 for y in P._addable(la, i) if P._above(y, x))
                            - sum(1 for y in P._removable(la, i) if P._above(y, x)))
                q = P.realization_of()._q
                k = P.realization_of()._k
                return P.sum_of_terms([(la.add_cell(*x), c * q**N_right(la, x, i))
                                       for la,c in self for x in P._addable(la, i)
                                       if x[0] < k])

    natural = F

    class A(CombinatorialFreeModule, BindableClass):
        r"""
        The `A` basis of the Fock space, which is the approximation
        basis of the lower global crystal basis.

        INPUT:

        - ``algorithm`` -- (default ``'GW'``) the algorithm to use when
          computing this basis in the Fock space; the possible values are:

          * ``'GW'`` -- use the algorithm given by Goodman and Wenzl
            in [GW1999]_
          * ``'LLT'`` -- use the LLT algorithm given in [LLT1996]_

        .. NOTE::

            The bases produced by the two algorithms are not the same
            in general.

        EXAMPLES::

            sage: FS = FockSpace(5, truncated=4)
            sage: F = FS.natural()
            sage: A = FS.A()

        We demonstrate that they are different bases, but both algorithms
        still compute the basis `G`::

            sage: A2 = FS.A('LLT')
            sage: G = FS.G()
            sage: F(A[12,9])
            |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + (q^2+1)*|8, 8, 4, 1>
            sage: F(A2[12,9])
            |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + (q^2+2)*|8, 8, 4, 1>
            sage: G._G_to_fock_basis(Partition([12,9]), 'GW')
            |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + q^2*|8, 8, 4, 1>
            sage: G._G_to_fock_basis(Partition([12,9]), 'LLT')
            |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + q^2*|8, 8, 4, 1>
        """
        def __init__(self, F, algorithm='GW'):
            r"""
            Initialize ``self``.

            EXAMPLES::

                sage: FS = FockSpace(2, truncated=3)
                sage: A = FS.A()
                sage: TestSuite(A).run()
                sage: A2 = FS.A('LLT')
                sage: TestSuite(A2).run()
            """
            self._basis_name = "approximation"
            if algorithm not in ['GW', 'LLT']:
                raise ValueError("invalid algorithm")
            self._alg = algorithm
            indices = RegularPartitions_truncated(F._n, F._k)
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='A', bracket=False,
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))
            self.module_morphism(self._A_to_fock_basis,
                                 triangular='upper', unitriangular=True,
                                 codomain=F.natural()).register_as_coercion()

        options = FockSpaceOptions

        @cached_method
        def _LLT(self, la):
            r"""
            Return the result from the regular LLT algorithm on the partition
            ``la`` to compute the `A`-basis element in the corresponding
            Fock space.

            EXAMPLES::

                sage: FS = FockSpace(5, truncated=4)
                sage: F = FS.natural()
                sage: A = FS.A()
                sage: F(A[12,9])
                |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + (q^2+1)*|8, 8, 4, 1>
                sage: A._LLT(Partition([12,9]))
                |12, 9> + q*|12, 4, 4, 1> + q*|8, 8, 5> + (q^2+2)*|8, 8, 4, 1>
            """
            R = self.realization_of()
            fock = R.natural()
            k = R._k

            cur = fock.highest_weight_vector()
            # Get the ladders and apply it to the current element
            corners = la.corners()
            cells = set(la.cells())
            q = R._q
            k = R._n - 1 # This is sl_{k+1}
            r = R._multicharge[0]
            b = ZZ.zero()
            while any(c[1]*k + c[0] >= b for c in corners): # While there is some cell left to count
                power = 0
                i = -b + r # This will be converted to a mod n number
                for x in range(0, b // k + 1):
                    if (b-x*k, x) in cells:
                        power += 1
                        cur = cur.f(i)
                cur /= q_factorial(power, q)
                b += 1
            return cur

        def _skew_tableau(self, cur, nu, d):
            r"""
            Return the action of the skew tableaux formed from ``nu`` by
            applying the ``d`` fundamental weights.

            EXAMPLES::

                sage: FS = FockSpace(3, truncated=3)
                sage: F = FS.natural()
                sage: A = FS.A()
                sage: G = FS.G()
                sage: sk = A._skew_tableau(F(G[6,3]), Partition([6,3]), [1,1]); sk
                |8, 4> + q*|8, 2, 2> + q*|7, 4, 1> + q^2*|7, 3, 2> + q*|6, 6>
                 + q^2*|6, 5, 1> + q^2*|5, 4, 3> + q^3*|4, 4, 4>
                sage: sk == F(A[8,4])
                True
            """
            R = self.realization_of()
            last = None
            power = 0
            q = R._q
            for i in reversed(range(len(d))):
                for dummy in range(d[i]):
                    for j in range(i+1):
                        col = nu[j] if j < len(nu) else 0
                        res = nu.content(j, col, multicharge=R._multicharge)
                        if res != last:
                            cur /= q_factorial(power, q)
                            power = 1
                            last = res
                        else:
                            power += 1
                        cur = cur.f(res)
                        nu = nu.add_cell(j, col)
            return cur / q_factorial(power, q)

        @cached_method
        def _A_to_fock_basis(self, la):
            r"""
            Return the `A` basis indexed by ``la`` in the natural basis.

            EXAMPLES::

                sage: FS = FockSpace(2, truncated=3)
                sage: F = FS.natural()
                sage: A = FS.A()
                sage: A._A_to_fock_basis(Partition([2,1]))
                |2, 1>
                sage: F(A[3,1])
                |3, 1> + q*|2, 2> + q^2*|2, 1, 1>
                sage: A._A_to_fock_basis(Partition([3]))
                |3> + q*|1, 1, 1>

                sage: FS = FockSpace(2, truncated=5)
                sage: F = FS.natural()
                sage: A = FS.A()
                sage: F(A[7,4,3])
                |7, 4, 3> + q*|7, 4, 1, 1, 1> + q^2*|7, 2, 2, 2, 1> + |5, 4, 3, 2>
                 + 2*q*|5, 4, 3, 1, 1> + 2*q^2*|5, 4, 2, 2, 1>
                 + 2*q^3*|5, 3, 3, 2, 1> + q^4*|4, 4, 3, 2, 1>
                sage: F(A[7,4,3,2])
                |7, 4, 3, 2> + q*|7, 4, 3, 1, 1> + q^2*|7, 4, 2, 2, 1>
                 + q^3*|7, 3, 3, 2, 1> + q^4*|6, 4, 3, 2, 1>
            """
            if self._alg == 'LLT':
                return self._LLT(la)

            # Otherwise we use the GW algorithm
            fock = self.realization_of().natural()
            k = self.realization_of()._k

            # We do some special cases first
            # For the empty partition
            if la.size() == 0:
                return fock.highest_weight_vector()

            # For length k partitions
            if len(la) == k:
                G = self.realization_of().G()
                return G._G_to_fock_basis(la)

            # For critical partitions
            n = self.realization_of()._n
            if len(la) == k-1 and all((la[i] - la[i+1] + 1) % n == 0 for i in range(k-2)) \
                    and (la[-1] + 1) % n == 0:
                return fock.monomial(la)

            # For interior partitions
            shifted = [la[i] - (n - 1)*(k - 1 - i) for i in range(len(la))]
            if len(la) == k - 1 and shifted in _Partitions:
                # Construct the d's and the critical partition
                d = [(la[i] - la[i+1] + 1) % n for i in range(len(la)-1)]
                d.append((la[-1] + 1) % n)
                crit = list(la)
                for i,d_i in enumerate(d):
                    for j in range(i+1):
                        crit[j] -= d_i
                nu = fock._indices(crit)
                return self._skew_tableau(fock.monomial(nu), nu, d)

            # For non-interior partitions
            # Construct the d's and the partition ``a``
            a = list(la) + [0]*(k - 1 - len(la)) # Add 0's to get the correct length
            a = [a[i] + (k - 1 - i) for i in range(k-1)] # Shift the diagram
            #shifted = list(a) # Make a copy of the shifted partition in case we need it later
            d = [(a[i] - a[i+1]) % n for i in range(k-2)]
            d.append(a[-1] % n)
            for i,d_i in enumerate(d):
                for j in range(i+1):
                    a[j] -= d_i
            if sum(a) == 0: # a is contained in the fundamental box
                return self._LLT(la)

            p = list(a) # Make a copy that we can change
            for i in range(k-2):
                if a[i] - a[i+1] == 0:
                    d[i] -= 1
                    for j in range(i+1):
                        p[j] += 1
            if a[-1] == 0:
                d[-1] -= 1
                for j in range(k-1):
                    p[j] += 1
            p = [p[i] - (k - 1 - i) for i in range(k-1)]
            I = self._indices
            nu = I(p)

            while not max(nu.to_exp()) < n:
                # It is not regular, so we need to find the first regular
                #   partition after adding columns
                while d[-1] == 0:
                    d.pop()
                for j in range(d[-1]):
                    p[j] += 1
                nu = I(p)

            if la == nu:
                j = -1
                for i in range(k-2):
                    if p[i] - p[i+1] == 0:
                        j = -2
                        break
                    if p[i] > n and p[i] - p[i+1] > n:
                        j = i
                if j != -2 and p[-1] > n:
                    j = k - 1
                if j < 0:
                    return self._LLT(la)

                G = self.realization_of().G()
                nu = I([p[i] - n if i <= j else p[i] for i in range(k-1)])
                d = [0]*j + [n]
                return self._skew_tableau(G._G_to_fock_basis(nu), nu, d)

            G = self.realization_of().G()
            return self._skew_tableau(G._G_to_fock_basis(nu), nu, d)

    approximation = A

    class G(CombinatorialFreeModule, BindableClass):
        r"""
        The lower global crystal basis living inside of a
        truncated Fock space.

        EXAMPLES::

            sage: FS = FockSpace(4, truncated=2)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[3,1])
            |3, 1>
            sage: F(G[6,2])
            |6, 2> + q*|5, 3>
            sage: F(G[14])
            |14> + q*|11, 3>

            sage: FS = FockSpace(3, truncated=4)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[4,1])
            |4, 1> + q*|3, 2>
            sage: F(G[4,2,2])
            |4, 2, 2> + q*|3, 2, 2, 1>

        We check against the tables in [LLT1996]_ (after truncating)::

            sage: FS = FockSpace(3, truncated=3)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[10])
            |10> + q*|8, 2> + q*|7, 2, 1>
            sage: F(G[6,4])
            |6, 4> + q*|6, 2, 2> + q^2*|4, 4, 2>
            sage: F(G[5,5])
            |5, 5> + q*|4, 3, 3>

            sage: FS = FockSpace(4, truncated=3)
            sage: F = FS.natural()
            sage: G = FS.G()
            sage: F(G[3,3,1])
            |3, 3, 1>
            sage: F(G[3,2,2])
            |3, 2, 2>
            sage: F(G[7])
            |7> + q*|3, 3, 1>
        """
        def __init__(self, F):
            r"""
            Initialize ``self``.

            EXAMPLES::

                sage: G = FockSpace(2, truncated=3).G()
                sage: TestSuite(G).run()
                sage: G = FockSpace(4, truncated=3).G()
                sage: TestSuite(G).run()
            """
            self._basis_name = "lower global crystal"
            indices = RegularPartitions_truncated(F._n, F._k)
            CombinatorialFreeModule.__init__(self, F.base_ring(), indices,
                                             prefix='G', bracket=False,
                                             sorting_reverse=True,
                                             category=FockSpaceBases(F))
            self.module_morphism(self._G_to_fock_basis,
                                 triangular='upper', unitriangular=True,
                                 codomain=F.natural()).register_as_coercion()

        options = FockSpaceOptions

        @cached_method
        def _G_to_fock_basis(self, la, algorithm='GW'):
            r"""
            Return the `G` basis indexed by ``la`` in the natural basis.

            EXAMPLES::

                sage: G = FockSpace(3, truncated=3).G()
                sage: G._G_to_fock_basis(Partition([3]))
                |3> + q*|2, 1>
                sage: G._G_to_fock_basis(Partition([2,1]))
                |2, 1> + q*|1, 1, 1>
                sage: G._G_to_fock_basis(Partition([2,1]), 'LLT')
                |2, 1> + q*|1, 1, 1>
            """
            fock = self.realization_of().natural()

            # Special cases:
            # For the empty partition
            if la.size() == 0:
                return fock.highest_weight_vector()

            # For length k partitions
            if algorithm == 'GW':
                n = self.realization_of()._n
                k = self.realization_of()._k
                if len(la) == k:
                    x = la[-1]
                    mu = _Partitions([p - x for p in la])
                    def add_cols(nu):
                        return _Partitions([ v + x for v in list(nu) + [0]*(k - len(nu)) ])
                    return fock.sum_of_terms((add_cols(nu), c) for nu,c in self._G_to_fock_basis(mu))

                # For critical partitions
                n = self.realization_of()._n
                if len(la) == k-1 and all((la[i] - la[i+1] + 1) % n == 0 for i in range(k-2)) \
                        and (la[-1] + 1) % n == 0:
                    return fock.monomial(la)

            # Perform the triangular reduction
            cur = self.realization_of().A(algorithm)._A_to_fock_basis(la)
            s = cur.support()
            s.sort() # Sort lex, which respects dominance order
            s.pop() # Remove the largest

            q = self.realization_of()._q
            while s:
                mu = s.pop()
                d = cur[mu].denominator()
                k = d.degree()
                n = cur[mu].numerator()
                if k != 0 or n.constant_coefficient() != 0:
                    gamma = sum(n[i] * (q**(i-k) + q**(k-i))
                                for i in range(min(n.degree(), k)))
                    gamma += n[k]
                    cur -= gamma * self._G_to_fock_basis(mu, algorithm)

                    # Add any new support elements
                    for x in cur.support():
                        if x == mu or not mu.dominates(x): # Add only things (strictly) dominated by mu
                            continue
                        for i in reversed(range(len(s))):
                            if not s[i].dominates(x):
                                s.insert(i+1, x)
                                break
            return cur

    lower_global_crystal = G
    canonical = G
