# sage_setup: distribution = sagemath-combinat
# sage.doctest: needs sage.combinat sage.modules sage.groups
r"""
Representations of the symmetric group

.. TODO::

    - construct the product of two irreducible representations.

    - implement Induction/Restriction of representations.

.. WARNING::

    This code uses a different convention than in Sagan's book "The Symmetric
    Group"
"""
# ****************************************************************************
#       Copyright (C) 2009 Franco Saliola <saliola@gmail.com>
#                     2024 Travis Scrimshaw <tcscrims at gmail.com>
#
#  Distributed under the terms of the GNU General Public License (GPL)
#
#    This code is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    General Public License for more details.
#
#  The full text of the GPL is available at:
#
#                  https://www.gnu.org/licenses/
# ****************************************************************************

from sage.misc.lazy_import import lazy_import
from sage.misc.functional import sqrt
from sage.combinat.partition import Partition, Partitions
from sage.combinat.permutation import Permutation, Permutations, from_cycles
from sage.combinat.tableau import StandardTableaux, Tableau
from sage.matrix.constructor import matrix
from sage.misc.cachefunc import cached_method
from sage.misc.lazy_attribute import lazy_attribute
from sage.rings.integer_ring import ZZ
from sage.rings.rational_field import QQ
from sage.structure.parent import Parent
from sage.structure.element import Element
from sage.structure.unique_representation import UniqueRepresentation
from sage.sets.finite_enumerated_set import FiniteEnumeratedSets

lazy_import("sage.combinat.yang_baxter_graph", "YangBaxterGraph_partition")
lazy_import("sage.groups.perm_gps.constructor", "PermutationGroupElement", as_='PermutationConstructor')
lazy_import("sage.symbolic.ring", "SR")


# #### Constructor function ################################################


def SymmetricGroupRepresentation(partition, implementation='specht',
        ring=None, cache_matrices=True):
    r"""
    The irreducible representation of the symmetric group corresponding to
    ``partition``.

    INPUT:

    - ``partition`` -- a partition of a positive integer

    - ``implementation`` -- string (default: ``'specht'``); one of:

      * ``'seminormal'`` -- for Young's seminormal representation
      * ``'orthogonal'`` -- for Young's orthogonal representation
      * ``'specht'`` -- for Specht's representation
      * ``'unitary'`` -- for the unitary representation

    - ``ring`` -- the ring over which the representation is defined

    - ``cache_matrices`` -- boolean (default: ``True``); if ``True``, then any
      representation matrices that are computed are cached

    EXAMPLES:

    Young's orthogonal representation: the matrices are orthogonal.

    ::

        sage: orth = SymmetricGroupRepresentation([2,1], "orthogonal"); orth            # needs sage.symbolic
        Orthogonal representation of the symmetric group corresponding to [2, 1]
        sage: all(a*a.transpose() == a.parent().identity_matrix() for a in orth)        # needs sage.symbolic
        True

    ::

        sage: # needs sage.symbolic
        sage: orth = SymmetricGroupRepresentation([3,2], "orthogonal"); orth
        Orthogonal representation of the symmetric group corresponding to [3, 2]
        sage: orth([2,1,3,4,5])
        [ 1  0  0  0  0]
        [ 0  1  0  0  0]
        [ 0  0 -1  0  0]
        [ 0  0  0  1  0]
        [ 0  0  0  0 -1]
        sage: orth([1,3,2,4,5])
        [          1           0           0           0           0]
        [          0        -1/2 1/2*sqrt(3)           0           0]
        [          0 1/2*sqrt(3)         1/2           0           0]
        [          0           0           0        -1/2 1/2*sqrt(3)]
        [          0           0           0 1/2*sqrt(3)         1/2]
        sage: orth([1,2,4,3,5])
        [       -1/3 2/3*sqrt(2)           0           0           0]
        [2/3*sqrt(2)         1/3           0           0           0]
        [          0           0           1           0           0]
        [          0           0           0           1           0]
        [          0           0           0           0          -1]

    The Specht representation::

        sage: spc = SymmetricGroupRepresentation([3,2], "specht")
        sage: spc.scalar_product_matrix(Permutation([1,2,3,4,5]))
        [ 1  0  0  0  0]
        [ 0 -1  0  0  0]
        [ 0  0  1  0  0]
        [ 0  0  0  1  0]
        [-1  0  0  0 -1]
        sage: spc.scalar_product_matrix(Permutation([5,4,3,2,1]))
        [ 1 -1  0  1  0]
        [ 0  0  1  0 -1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [-1  0  0  0 -1]
        sage: spc([5,4,3,2,1])
        [ 1 -1  0  1  0]
        [ 0  0 -1  0  1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [ 0  1  0 -1  1]
        sage: spc.verify_representation()
        True

    The unitary representation::

        sage: unitary = SymmetricGroupRepresentation([3,1], "unitary"); unitary         # needs sage.symbolic
        Unitary representation of the symmetric group corresponding to [3, 1]

        sage: # needs sage.rings.finite_rings
        sage: unitary_GF49 = SymmetricGroupRepresentation([3,1], "unitary", ring=GF(7**2))
        sage: unitary_GF49
        Unitary representation of the symmetric group corresponding to [3, 1]
        sage: unitary_GF49([2,1,3,4])
        [6 0 0]
        [0 1 0]
        [0 0 1]
        sage: unitary_GF49([3,2,1,4])
        [       4 2*z2 + 3        0]
        [5*z2 + 5        3        0]
        [       0        0        1]
        sage: unitary_GF49.verify_representation()
        True

    By default, any representation matrices that are computed are cached::

        sage: spc = SymmetricGroupRepresentation([3,2], "specht")
        sage: spc([5,4,3,2,1])
        [ 1 -1  0  1  0]
        [ 0  0 -1  0  1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [ 0  1  0 -1  1]
        sage: spc._cache__representation_matrix
        {(([5, 4, 3, 2, 1],), ()): [ 1 -1  0  1  0]
        [ 0  0 -1  0  1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [ 0  1  0 -1  1]}

    This can be turned off with the keyword ``cache_matrices``::

        sage: spc = SymmetricGroupRepresentation([3,2], "specht", cache_matrices=False)
        sage: spc([5,4,3,2,1])
        [ 1 -1  0  1  0]
        [ 0  0 -1  0  1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [ 0  1  0 -1  1]
        sage: hasattr(spc, '_cache__representation_matrix')
        False

    .. NOTE::

        The implementation is based on the paper [Las]_.

    REFERENCES:

    .. [Las] Alain Lascoux, 'Young representations of the symmetric group.'
       http://phalanstere.univ-mlv.fr/~al/ARTICLES/ProcCrac.ps.gz

    AUTHORS:

    - Franco Saliola (2009-04-23)
    """
    partition = Partition(partition)
    Rep = SymmetricGroupRepresentations(sum(partition), implementation=implementation,
                                        ring=ring, cache_matrices=cache_matrices)
    return Rep(partition)


def SymmetricGroupRepresentations(n, implementation='specht', ring=None,
        cache_matrices=True):
    r"""
    Irreducible representations of the symmetric group.

    INPUT:

    - ``n`` -- positive integer

    - ``implementation`` -- string (default: ``'specht'``); one of:

      * ``'seminormal'`` -- for Young's seminormal representation
      * ``'orthogonal'`` -- for Young's orthogonal representation
      * ``'specht'`` -- for Specht's representation
      * ``'unitary'`` -- for the unitary representation

    - ``ring`` -- the ring over which the representation is defined

    - ``cache_matrices`` -- boolean (default: ``True``); if ``True``, then any
      representation matrices that are computed are cached

    EXAMPLES:

    Young's orthogonal representation: the matrices are orthogonal.

    ::

        sage: orth = SymmetricGroupRepresentations(3, "orthogonal"); orth               # needs sage.symbolic
        Orthogonal representations of the symmetric group of order 3! over Symbolic Ring
        sage: orth.list()                                                               # needs sage.symbolic
        [Orthogonal representation of the symmetric group corresponding to [3],
         Orthogonal representation of the symmetric group corresponding to [2, 1],
         Orthogonal representation of the symmetric group corresponding to [1, 1, 1]]
        sage: orth([2,1])([1,2,3])                                                      # needs sage.symbolic
        [1 0]
        [0 1]

    Young's seminormal representation.

    ::

        sage: snorm = SymmetricGroupRepresentations(3, "seminormal"); snorm
        Seminormal representations of the symmetric group of order 3! over Rational Field
        sage: sgn = snorm([1,1,1]); sgn
        Seminormal representation of the symmetric group corresponding to [1, 1, 1]
        sage: list(map(sgn, Permutations(3)))
        [[1], [-1], [-1], [1], [1], [-1]]

    The Specht Representation.

    ::

        sage: spc = SymmetricGroupRepresentations(5, "specht"); spc
        Specht representations of the symmetric group of order 5! over Integer Ring
        sage: spc([3,2])([5,4,3,2,1])
        [ 1 -1  0  1  0]
        [ 0  0 -1  0  1]
        [ 0  0  0 -1  1]
        [ 0  1 -1 -1  1]
        [ 0  1  0 -1  1]

    The unitary representation.

    ::

        sage: unitary = SymmetricGroupRepresentations(3, "unitary"); unitary            # needs sage.symbolic
        Unitary representations of the symmetric group of order 3! over Symbolic Ring

        sage: # needs sage.rings.finite_rings
        sage: unitary_GF49 = SymmetricGroupRepresentations(4, "unitary", ring=GF(7**2)); unitary_GF49
        Unitary representations of the symmetric group of order 4! over Finite Field in z2 of size 7^2
        sage: unitary_GF49([3,1])([2,1,3,4])
        [6 0 0]
        [0 1 0]
        [0 0 1]
        sage: unitary_GF49([3,1])([3,2,1,4])
        [       4 2*z2 + 3        0]
        [5*z2 + 5        3        0]
        [       0        0        1]

    .. NOTE::

        The implementation is based on the paper [Las]_.

    AUTHORS:

    - Franco Saliola (2009-04-23)
    """
    if implementation == "seminormal":
        return YoungRepresentations_Seminormal(n, ring=ring, cache_matrices=cache_matrices)
    elif implementation == "orthogonal":
        return YoungRepresentations_Orthogonal(n, ring=ring, cache_matrices=cache_matrices)
    elif implementation == "specht":
        return SpechtRepresentations(n, ring=ring, cache_matrices=cache_matrices)
    elif implementation == "unitary":
        return UnitaryRepresentations(n, ring=ring, cache_matrices=cache_matrices)
    else:
        raise NotImplementedError("only seminormal, orthogonal and specht are implemented")

# #### Generic classes for symmetric group representations #################


class SymmetricGroupRepresentation_generic_class(Element):
    r"""
    Generic methods for a representation of the symmetric group.
    """
    _default_ring = None

    def __init__(self, parent, partition):
        r"""
        An irreducible representation of the symmetric group corresponding
        to ``partition``.

        For more information, see the documentation for
        :func:`SymmetricGroupRepresentation`.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3])
            sage: spc([3,2,1])
            [1]
            sage: spc == loads(dumps(spc))
            True

            sage: spc = SymmetricGroupRepresentation([3], cache_matrices=False)
            sage: spc([3,2,1])
            [1]
            sage: spc == loads(dumps(spc))
            True
        """
        self._partition = Partition(partition)
        self._n = parent._n
        self._ring = parent._ring
        if not parent._cache_matrices:
            self.representation_matrix = self._representation_matrix_uncached
        Element.__init__(self, parent)

    def __hash__(self):
        r"""
        TESTS::

            sage: spc1 = SymmetricGroupRepresentation([3], cache_matrices=True)
            sage: hash(spc1) ^^ hash((3,)) == hash(ZZ)
            True
        """
        return hash(self._ring) ^ hash(self._partition)

    def __eq__(self, other):
        r"""
        Test for equality.

        EXAMPLES::

            sage: spc1 = SymmetricGroupRepresentation([3], cache_matrices=True)
            sage: spc1([3,1,2])
            [1]
            sage: spc2 = loads(dumps(spc1))
            sage: spc1 == spc2
            True

        ::

            sage: spc3 = SymmetricGroupRepresentation([3], cache_matrices=False)
            sage: spc3([3,1,2])
            [1]
            sage: spc4 = loads(dumps(spc3))
            sage: spc3 == spc4
            True
            sage: spc1 == spc3
            True

        TESTS:

        The following tests against some bug that was fixed in :issue:`8611`::

            sage: spc = SymmetricGroupRepresentation([3])
            sage: spc.important_info = 'Sage rules'
            sage: spc == SymmetricGroupRepresentation([3])
            True
        """
        if not isinstance(other, type(self)):
            return False
        return (self._ring, self._partition) == (other._ring, other._partition)

    def __ne__(self, other):
        """
        Test for inequality.

        EXAMPLES::

            sage: spc1 = SymmetricGroupRepresentation([3], cache_matrices=True)
            sage: loads(dumps(spc1)) != spc1
            False
            sage: spc2 = SymmetricGroupRepresentation([2,1])
            sage: spc1 != spc2
            True
            sage: spc3 = SymmetricGroupRepresentation([3], cache_matrices=False)
            sage: spc1 != spc3
            False
        """
        return not (self == other)

    def __call__(self, permutation):
        r"""
        Return the image of ``permutation`` in the representation.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([2,1])
            sage: spc([1,3,2])
            [ 1  0]
            [ 1 -1]
        """
        return self.representation_matrix(Permutation(permutation))

    def __iter__(self):
        r"""
        Iterate over the matrices representing the elements of the
        symmetric group.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([1,1,1])
            sage: list(spc)
            [[1], [-1], [-1], [1], [1], [-1]]
        """
        for permutation in Permutations(self._n):
            yield self.representation_matrix(permutation)

    def verify_representation(self):
        r"""
        Verify the representation.

        This tests that the images of the simple transpositions are
        involutions and tests that the braid relations hold.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([1,1,1])
            sage: spc.verify_representation()
            True
            sage: spc = SymmetricGroupRepresentation([4,2,1])
            sage: spc.verify_representation()
            True
        """
        n = self._n
        transpositions = [from_cycles(n, ((i, i + 1),)) for i in range(1, n)]
        repn_matrices = [self.representation_matrix(t) for t in transpositions]
        for i, si in enumerate(repn_matrices):
            for j, sj in enumerate(repn_matrices):
                if i == j:
                    if si * sj != si.parent().identity_matrix():
                        return False, "si si != 1 for i = %s" % (i,)
                elif abs(i - j) > 1:
                    if si * sj != sj * si:
                        return False, "si sj != sj si for (i,j) =(%s,%s)" % (i, j)
                else:
                    if si * sj * si != sj * si * sj:
                        return False, "si sj si != sj si sj for (i,j) = (%s,%s)" % (i, j)
        return True

    def to_character(self):
        r"""
        Return the character of the representation.

        EXAMPLES:

        The trivial character::

            sage: rho = SymmetricGroupRepresentation([3])
            sage: chi = rho.to_character(); chi
            Character of Symmetric group of order 3! as a permutation group
            sage: chi.values()
            [1, 1, 1]
            sage: all(chi(g) == 1 for g in SymmetricGroup(3))
            True

        The sign character::

            sage: rho = SymmetricGroupRepresentation([1,1,1])
            sage: chi = rho.to_character(); chi
            Character of Symmetric group of order 3! as a permutation group
            sage: chi.values()
            [1, -1, 1]
            sage: all(chi(g) == g.sign() for g in SymmetricGroup(3))
            True

        The defining representation::

            sage: triv = SymmetricGroupRepresentation([4])
            sage: hook = SymmetricGroupRepresentation([3,1])
            sage: def_rep = lambda p : triv(p).block_sum(hook(p)).trace()
            sage: list(map(def_rep, Permutations(4)))
            [4, 2, 2, 1, 1, 2, 2, 0, 1, 0, 0, 1, 1, 0, 2, 1, 0, 0, 0, 1, 1, 2, 0, 0]
            sage: [p.to_matrix().trace() for p in Permutations(4)]
            [4, 2, 2, 1, 1, 2, 2, 0, 1, 0, 0, 1, 1, 0, 2, 1, 0, 0, 0, 1, 1, 2, 0, 0]
        """
        from sage.groups.perm_gps.permgroup_named import SymmetricGroup
        Sym = SymmetricGroup(sum(self._partition))
        values = [self(g).trace() for g in Sym.conjugacy_classes_representatives()]
        return Sym.character(values)


class SymmetricGroupRepresentations_class(UniqueRepresentation,Parent):
    r"""
    Generic methods for the CombinatorialClass of irreducible
    representations of the symmetric group.
    """

    def __init__(self, n, ring=None, cache_matrices=True):
        r"""
        Irreducible representations of the symmetric group.

        See the documentation for :func:`SymmetricGroupRepresentations`
        for more information.

        EXAMPLES::

            sage: snorm = SymmetricGroupRepresentations(3, "seminormal")
            sage: snorm == loads(dumps(snorm))
            True
        """
        self._n = n
        self._ring = ring if ring is not None else self._default_ring
        self._cache_matrices = cache_matrices
        Parent.__init__(self, category=FiniteEnumeratedSets())

    def _element_constructor_(self, partition):
        r"""
        Return the irreducible representation corresponding to ``partition``.

        EXAMPLES::

            sage: sp = SymmetricGroupRepresentations(3, "specht")
            sage: sp([1,1,1])
            Specht representation of the symmetric group corresponding to [1, 1, 1]

            sage: snorm = SymmetricGroupRepresentations(3, "seminormal")
            sage: snorm([2,1])
            Seminormal representation of the symmetric group corresponding to [2, 1]
        """
        if Partition(partition).size() != self._n:
            raise TypeError("not a partition of %s" % self._n)
        return self.element_class(self, partition)

    def cardinality(self):
        """
        Return the cardinality of ``self``.

        EXAMPLES::

            sage: sp = SymmetricGroupRepresentations(4, "specht")
            sage: sp.cardinality()
            5
        """
        return Partitions(self._n).cardinality()

    def __iter__(self):
        r"""
        Iterate through all the irreducible representations of the
        symmetric group.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentations(3, "orthogonal")                 # needs sage.symbolic
            sage: for x in orth: print(x)                                               # needs sage.symbolic
            Orthogonal representation of the symmetric group corresponding to [3]
            Orthogonal representation of the symmetric group corresponding to [2, 1]
            Orthogonal representation of the symmetric group corresponding to [1, 1, 1]
        """
        for partition in Partitions(self._n):
            yield self.element_class(self, partition)

# #### Young's Seminormal Representation ###################################


class YoungRepresentation_generic(SymmetricGroupRepresentation_generic_class):
    r"""
    Generic methods for Young's representations of the symmetric group.
    """
    @lazy_attribute
    def _yang_baxter_graph(self):
        r"""
        Return the Yang-Baxter graph associated with the representation,
        with vertices labelled by the vector of contents of the partition.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([3,2], "orthogonal")              # needs sage.symbolic
            sage: orth._yang_baxter_graph                                               # needs sage.symbolic
            Yang-Baxter graph of [3, 2], with top vertex (0, -1, 2, 1, 0)
        """
        Y = YangBaxterGraph_partition(self._partition)
        n = self._n
        # relabel vertices with "vector of contents"
        Y.relabel_vertices(partition_to_vector_of_contents(self._partition,
                                                           reverse=True))
        # relabel edges with "differences"
        edge_relabel_dict = {}
        for u, v, op in Y.edges():
            i = op.position() + 1
            edge_relabel_dict[u, v] = (n - i, QQ((1, u[i] - u[i - 1])))
        Y.relabel_edges(edge_relabel_dict)
        return Y

    @lazy_attribute
    def _tableau_dict(self):
        r"""
        A dictionary pairing the vertices of the underlying Yang-Baxter
        graph with standard tableau.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([3,2], "orthogonal")              # needs sage.symbolic
            sage: orth._tableau_dict                                                    # needs sage.symbolic
            {(0, -1, 2, 1, 0): [[1, 2, 3], [4, 5]],
             (0, 2, -1, 1, 0): [[1, 2, 4], [3, 5]],
             (0, 2, 1, -1, 0): [[1, 3, 4], [2, 5]],
             (2, 0, -1, 1, 0): [[1, 2, 5], [3, 4]],
             (2, 0, 1, -1, 0): [[1, 3, 5], [2, 4]]}
        """
        # construct a dictionary pairing vertices with tableau
        t = StandardTableaux(self._partition).last()
        tableau_dict = {self._yang_baxter_graph.root(): t}
        for u, w, (i, _) in self._yang_baxter_graph._edges_in_bfs():
            # TODO: improve the following
            si = PermutationConstructor((i, i + 1))
            tableau_dict[w] = Tableau([[si(b) for b in row]
                                       for row in tableau_dict[u]])
        return tableau_dict

    @lazy_attribute
    def _word_dict(self):
        r"""
        A dictionary pairing the vertices of the underlying Yang-Baxter
        graph with words readings of standard tableau.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([3,2], "orthogonal")              # needs sage.symbolic
            sage: orth._word_dict                                                       # needs sage.symbolic
            {(0, -1, 2, 1, 0): (4, 5, 1, 2, 3),
             (0, 2, -1, 1, 0): (3, 5, 1, 2, 4),
             (0, 2, 1, -1, 0): (2, 5, 1, 3, 4),
             (2, 0, -1, 1, 0): (3, 4, 1, 2, 5),
             (2, 0, 1, -1, 0): (2, 4, 1, 3, 5)}
        """
        return {v: sum(reversed(t), ())
                for v, t in self._tableau_dict.items()}

    @cached_method
    def representation_matrix_for_simple_transposition(self, i):
        r"""
        Return the matrix representing the transposition that swaps ``i`` and
        ``i+1``.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([2,1], "orthogonal")              # needs sage.symbolic
            sage: orth.representation_matrix_for_simple_transposition(1)                # needs sage.symbolic
            [ 1  0]
            [ 0 -1]
            sage: orth.representation_matrix_for_simple_transposition(2)                # needs sage.symbolic
            [       -1/2 1/2*sqrt(3)]
            [1/2*sqrt(3)         1/2]

            sage: norm = SymmetricGroupRepresentation([2,1], "seminormal")
            sage: norm.representation_matrix_for_simple_transposition(1)
            [ 1  0]
            [ 0 -1]
            sage: norm.representation_matrix_for_simple_transposition(2)
            [-1/2  3/2]
            [ 1/2  1/2]
        """
        from copy import copy
        if not (1 <= i < sum(self._partition)):
            raise TypeError
        Y = self._yang_baxter_graph
        index_lookup = {b: a for a, b in enumerate(list(Y))}
        digraph = copy(Y._digraph)
        digraph.delete_edges((u, v) for (u, v, (j, beta)) in digraph.edges(sort=True)
                             if j != i)
        M = matrix(self._ring, digraph.num_verts())
        for g in digraph.connected_components_subgraphs():
            if g.num_verts() == 1:
                [v] = g.vertices(sort=True)
                w = self._word_dict[v]
                trivial = None
                for j, a in enumerate(w):
                    if a == i and w[j + 1] == i + 1:
                        trivial = True
                        break
                    elif a == i + 1:
                        trivial = False
                        break
                j = index_lookup[v]
                M[j, j] = 1 if trivial is True else -1
            else:
                [(u, v, (j, beta))] = g.edges(sort=True)
                iu = index_lookup[u]
                iv = index_lookup[v]
                M[iu, iu], M[iu, iv], M[iv, iu], M[iv, iv] = \
                    self._2x2_matrix_entries(self._ring(beta))
        return M

    def _representation_matrix_uncached(self, permutation):
        r"""
        Return the matrix representing ``permutation``.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([2,1], "orthogonal")              # needs sage.symbolic
            sage: orth._representation_matrix_uncached(Permutation([2,1,3]))            # needs sage.symbolic
            [ 1  0]
            [ 0 -1]
            sage: orth._representation_matrix_uncached(Permutation([1,3,2]))            # needs sage.symbolic
            [       -1/2 1/2*sqrt(3)]
            [1/2*sqrt(3)         1/2]

        ::

            sage: norm = SymmetricGroupRepresentation([2,1], "seminormal")
            sage: p = PermutationGroupElement([2,1,3])
            sage: norm._representation_matrix_uncached(p)
            [ 1  0]
            [ 0 -1]
            sage: p = PermutationGroupElement([1,3,2])
            sage: norm._representation_matrix_uncached(p)
            [-1/2  3/2]
            [ 1/2  1/2]
        """
        m = self._yang_baxter_graph._digraph.num_verts()
        M = matrix(self._ring, m, m, 1)
        for i in Permutation(permutation).reduced_word():
            M *= self.representation_matrix_for_simple_transposition(i)
        return M

    @cached_method
    def representation_matrix(self, permutation):
        r"""
        Return the matrix representing ``permutation``.

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([2,1], "orthogonal")              # needs sage.symbolic
            sage: orth.representation_matrix(Permutation([2,1,3]))                      # needs sage.symbolic
            [ 1  0]
            [ 0 -1]
            sage: orth.representation_matrix(Permutation([1,3,2]))                      # needs sage.symbolic
            [       -1/2 1/2*sqrt(3)]
            [1/2*sqrt(3)         1/2]

        ::

            sage: norm = SymmetricGroupRepresentation([2,1], "seminormal")
            sage: p = PermutationGroupElement([2,1,3])
            sage: norm.representation_matrix(p)
            [ 1  0]
            [ 0 -1]
            sage: p = PermutationGroupElement([1,3,2])
            sage: norm.representation_matrix(p)
            [-1/2  3/2]
            [ 1/2  1/2]
        """
        ret = self._representation_matrix_uncached(permutation)
        ret.set_immutable()
        return ret


class YoungRepresentation_Seminormal(YoungRepresentation_generic):
    def _repr_(self):
        r"""
        String representation of ``self``.

        EXAMPLES::

            sage: SymmetricGroupRepresentation([2,1], "seminormal")
            Seminormal representation of the symmetric group corresponding to [2, 1]
        """
        return "Seminormal representation of the symmetric group corresponding to {}".format(self._partition)

    def _2x2_matrix_entries(self, beta):
        r"""
        Young's representations are constructed by combining
        `2 \times 2`-matrices that depend on ``beta``.

        For the seminormal representation, this is the following matrix::

            [  -beta     1+beta ]
            [ 1-beta      beta  ]

        EXAMPLES::

            sage: snorm = SymmetricGroupRepresentation([2,1], "seminormal")
            sage: snorm._2x2_matrix_entries(1/2)
            (-1/2, 3/2, 1/2, 1/2)
        """
        return (-beta, 1 + beta, 1 - beta, beta)


class YoungRepresentations_Seminormal(SymmetricGroupRepresentations_class):
    _default_ring = QQ

    Element = YoungRepresentation_Seminormal

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

        EXAMPLES::

            sage: from sage.combinat.symmetric_group_representations import YoungRepresentations_Seminormal
            sage: YoungRepresentations_Seminormal(3)
            Seminormal representations of the symmetric group of order 3! over Rational Field
        """
        return "Seminormal representations of the symmetric group of order %s! over %s" % (self._n, self._ring)

# #### Young's Orthogonal Representation ###################################


class YoungRepresentation_Orthogonal(YoungRepresentation_generic):
    def _repr_(self):
        r"""
        String representation of ``self``.

        EXAMPLES::

            sage: SymmetricGroupRepresentation([2,1], "orthogonal")                     # needs sage.symbolic
            Orthogonal representation of the symmetric group corresponding to [2, 1]
        """
        return "Orthogonal representation of the symmetric group corresponding to {}".format(self._partition)

    def _2x2_matrix_entries(self, beta):
        r"""
        Young's representations are constructed by combining
        `2 \times 2`-matrices that depend on ``beta``.

        For the orthogonal representation, this is the following matrix::

            [     -beta       sqrt(1-beta^2) ]
            [ sqrt(1-beta^2)       beta      ]

        EXAMPLES::

            sage: orth = SymmetricGroupRepresentation([2,1], "orthogonal")              # needs sage.symbolic
            sage: orth._2x2_matrix_entries(1/2)                                         # needs sage.symbolic
            (-1/2, 1/2*sqrt(3), 1/2*sqrt(3), 1/2)
        """
        return (-beta, sqrt(1 - beta**2), sqrt(1 - beta**2), beta)


class YoungRepresentations_Orthogonal(SymmetricGroupRepresentations_class):
    _default_ring = SR

    Element = YoungRepresentation_Orthogonal

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

        EXAMPLES::

            sage: from sage.combinat.symmetric_group_representations import YoungRepresentations_Orthogonal
            sage: YoungRepresentations_Orthogonal(3)                                    # needs sage.symbolic
            Orthogonal representations of the symmetric group of order 3! over Symbolic Ring
        """
        return "Orthogonal representations of the symmetric group of order %s! over %s" % (self._n, self._ring)

# #### Specht Representation ###############################################


class SpechtRepresentation(SymmetricGroupRepresentation_generic_class):
    def _repr_(self):
        r"""
        String representation of ``self``.

        EXAMPLES::

            sage: SymmetricGroupRepresentation([2,1], "specht")
            Specht representation of the symmetric group corresponding to [2, 1]
        """
        return "Specht representation of the symmetric group corresponding to {}".format(self._partition)

    _default_ring = ZZ

    @lazy_attribute
    def _yang_baxter_graph(self):
        r"""
        Construct and cache the underlying Yang-Baxter graph.

        EXAMPLES::

            sage: rho = SymmetricGroupRepresentation([3,2], 'specht')
            sage: rho._yang_baxter_graph
            Yang-Baxter graph of [3, 2], with top vertex (1, 0, 2, 1, 0)
        """
        return YangBaxterGraph_partition(self._partition)

    @lazy_attribute
    def _dual_vertices(self):
        r"""
        Return a list of the dual vertices of the vertices of the underlying
        Yang-Baxter graph.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,2], 'specht')
            sage: spc._dual_vertices
            [(3, 3, 0, 0, 0), (3, 0, 3, 0, 0), (3, 0, 0, 3, 0), (0, 3, 3, 0, 0), (0, 3, 0, 3, 0)]
        """
        top = self._yang_baxter_graph.root()
        exponents = tuple(i - x for i, x in enumerate(reversed(top)))[::-1]
        relabelling = self._yang_baxter_graph.vertex_relabelling_dict(exponents)
        return [relabelling[u] for u in self._yang_baxter_graph]

    @cached_method
    def scalar_product(self, u, v):
        r"""
        Return ``0`` if ``u+v`` is not a permutation, and the signature of the
        permutation otherwise.

        This is the scalar product of a vertex ``u`` of the underlying
        Yang-Baxter graph with the vertex ``v`` in the 'dual' Yang-Baxter
        graph.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,2], 'specht')
            sage: spc.scalar_product((1,0,2,1,0),(0,3,0,3,0))
            -1
            sage: spc.scalar_product((1,0,2,1,0),(3,0,0,3,0))
            0
        """
        uv = [a + v[i] + 1 for i, a in enumerate(u)]
        if uv not in Permutations():
            return 0
        else:
            return Permutation(uv).signature()

    def scalar_product_matrix(self, permutation=None):
        r"""
        Return the scalar product matrix corresponding to ``permutation``.

        The entries are given by the scalar products of ``u`` and
        ``permutation.action(v)``, where ``u`` is a vertex in the underlying
        Yang-Baxter graph and ``v`` is a vertex in the dual graph.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,1], 'specht')
            sage: spc.scalar_product_matrix()
            [ 1  0  0]
            [ 0 -1  0]
            [ 0  0  1]
        """
        if permutation is None:
            permutation = Permutation(range(1, 1 + self._n))
        Q = matrix(self._ring, len(self._yang_baxter_graph))
        for i, v in enumerate(self._dual_vertices):
            for j, u in enumerate(self._yang_baxter_graph):
                Q[i, j] = self.scalar_product(tuple(permutation.action(v)), u)
        return Q

    @lazy_attribute
    def _scalar_product_matrix_inverse(self):
        r"""
        Compute and store the inverse of the scalar product matrix.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,1], 'specht')
            sage: spc._scalar_product_matrix_inverse
            [ 1  0  0]
            [ 0 -1  0]
            [ 0  0  1]
        """
        return self.scalar_product_matrix().inverse()

    @cached_method
    def representation_matrix(self, permutation):
        r"""
        Return the matrix representing the ``permutation`` in this
        irreducible representation.

        .. NOTE::

            This method caches the results.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,1], 'specht')
            sage: spc.representation_matrix(Permutation([2,1,3,4]))
            [ 0 -1  0]
            [-1  0  0]
            [ 0  0  1]
            sage: spc.representation_matrix(Permutation([3,2,1,4]))
            [0 0 1]
            [0 1 0]
            [1 0 0]
        """
        ret = self._representation_matrix_uncached(permutation)
        ret.set_immutable()
        return ret

    def _representation_matrix_uncached(self, permutation):
        r"""
        Return the matrix representing the ``permutation`` in this
        irreducible representation.

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentation([3,1], 'specht')
            sage: spc._representation_matrix_uncached(Permutation([2,1,3,4]))
            [ 0 -1  0]
            [-1  0  0]
            [ 0  0  1]
            sage: spc._representation_matrix_uncached(Permutation([3,2,1,4]))
            [0 0 1]
            [0 1 0]
            [1 0 0]
        """
        R = self.scalar_product_matrix(permutation)
        return self._scalar_product_matrix_inverse * R


class SpechtRepresentations(SymmetricGroupRepresentations_class):
    _default_ring = ZZ

    Element = SpechtRepresentation

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

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentations(4)
            sage: spc
            Specht representations of the symmetric group of order 4! over Integer Ring
        """
        return "Specht representations of the symmetric group of order %s! over %s" % (self._n, self._ring)

# #### Unitary Representation ###############################################


class UnitaryRepresentation(SymmetricGroupRepresentation_generic_class):
    r"""
    A unitary representation of the symmetric group.

    In characteristic zero, this is the same as the orthogonal representation since
    they are defined over the reals. In positive characteristic, we are able to construct
    representations when the field is finite, has square order, and the characteristic does not divide
    the order of the group. In this case, we construct the representation by first
    constructing the orthogonal representation `\rho` and then taking the extended
    Cholesky decomposition of the unique solution `U` to the equation
    `\rho(g)^T U \rho(g) = U` for all `g` in `G`.
    """
    def __init__(self, parent, partition):
        r"""
        Initialize ``self``.

        EXAMPLES::

            sage: # needs sage.symbolic
            sage: U = SymmetricGroupRepresentation([2,1], "unitary")
            sage: TestSuite(U).run()

            sage: U = SymmetricGroupRepresentation([2,1], "unitary", GF(7))
            Traceback (most recent call last):
            ...
            ValueError: the base ring must be a finite field of square order

            sage: # needs sage.rings.finite_rings
            sage: U = SymmetricGroupRepresentation([2,1], "unitary", GF(7**2))
            sage: TestSuite(U).run()
        """
        if parent._ring.characteristic() == 0:
            orth = SymmetricGroupRepresentation(partition, 'orthogonal')
            self.representation_matrix = orth.representation_matrix
            self._representation_matrix_uncached = orth._representation_matrix_uncached
        else:
            if not (parent._ring.is_field()
                    and parent._ring.is_finite()
                    and parent._ring.order().is_square()):
                raise ValueError("the base ring must be a finite field of square order")
            from sage.arith.misc import factorial
            if parent._ring.characteristic().divides(factorial(parent._n)):
                raise NotImplementedError("not implemented when p|n!; dimension of invariant forms may be greater than one")
            self._q = parent._ring.order().sqrt()
            self._specht = Permutations(sum(partition)).algebra(parent._ring).specht_module(partition)
        super().__init__(parent, partition)

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

        EXAMPLES::

            sage: SymmetricGroupRepresentation([2,1], "unitary")                        # needs sage.symbolic
            Unitary representation of the symmetric group corresponding to [2, 1]
        """
        return f"Unitary representation of the symmetric group corresponding to {self._partition}"

    @cached_method
    def representation_matrix(self, permutation):
        r"""
        Return the matrix representing the ``permutation`` in this
        irreducible representation.

        .. NOTE::

            This method caches the results.

        EXAMPLES::

            sage: # needs sage.rings.finite_rings
            sage: unitary_specht = SymmetricGroupRepresentation([3,1], 'unitary', ring=GF(7**2))
            sage: unitary_specht.representation_matrix(Permutation([2,1,3,4]))
            [6 0 0]
            [0 1 0]
            [0 0 1]
            sage: unitary_specht.representation_matrix(Permutation([3,2,1,4]))
            [       4 2*z2 + 3        0]
            [5*z2 + 5        3        0]
            [       0        0        1]
            sage: all(A * A.H == 1 for A in [unitary_specht.representation_matrix(g)
            ....:                            for g in Permutations(4)])
            True
        """
        ret = self._representation_matrix_uncached(permutation)
        ret.set_immutable()
        return ret

    @lazy_attribute
    def _unitary_change_basis_matrix(self):
        """
        Compute the change of basis matrix.

        We first compute a `G`-invariant symmetric bilinear form. This yields a solution `U`
        to the equation `\rho(g)^T U \rho(g) = U` for all `g` in `G`. We can
        solve this equation by computing the null space of the matrix of coefficients of the linear
        equations. Generically, this null space will be one dimensional (it may have higher dimension
        in the modular case when the decomposition factors have multiplicity). We then take the
        extended Cholesky decomposition of the unique solution to the equation.

        EXAMPLES::

            sage: # needs sage.rings.finite_rings
            sage: unitary_specht = SymmetricGroupRepresentation([3,1], 'unitary', ring=GF(7**2))
            sage: unitary_specht._unitary_change_basis_matrix
            [       1        4        4]
            [       0 2*z2 + 2 3*z2 + 3]
            [       0        0 6*z2 + 1]
            sage: unitary_specht = SymmetricGroupRepresentation([2,2], 'unitary', ring=GF(7**2))
            sage: unitary_specht._unitary_change_basis_matrix
            [       1        4]
            [       0 2*z2 + 2]
        """
        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        G = Permutations(self._n)
        F = self._ring
        rho = self._specht.representation_matrix

        # compute the invariant symmetric bilinear matrix
        d_rho = self._specht.dimension()
        R = PolynomialRing(F, 'u', d_rho**2)
        U_vars = R.gens()
        Utemp = matrix(R, d_rho, d_rho, U_vars)

        def augmented_matrix(g):
            rho_g = rho(g)
            equation_matrix = rho_g.transpose() * Utemp * rho_g.conjugate() - Utemp
            augmented_system = []
            for i in range(d_rho):
                for j in range(d_rho):
                    linear_expression = equation_matrix[i, j]
                    row = [linear_expression.coefficient(u) for u in U_vars]
                    augmented_system.append(row)
            return augmented_system

        total_system = sum((augmented_matrix(g) for g in G), [])
        null_space = matrix(F, total_system).right_kernel()
        U = matrix(F, d_rho, d_rho, null_space.basis()[0])
        return U.cholesky(extended=True).H

    def _representation_matrix_uncached(self, permutation):
        r"""
        Return the matrix representing the ``permutation`` in this
        irreducible representation.

        We use the unitary change of basis matrix to conjugate
        the representation matrix. These are unitary since

        .. MATH::

            (A rho(g) A^{-1})^* \cdot (A rho(g) A^{-1})
              = (A^{-1})^* rho(g)^* A^* A rho(g) A^{-1}
              = (A^{-1})^*H rho(g)^T U rho(g) A^{-1}
              = (A^{-1})^* U A^{-1} = 1.

        .. NOTE::

            This method caches the results.

        EXAMPLES::

            sage: # needs sage.rings.finite_rings
            sage: unitary_specht = SymmetricGroupRepresentation([2,2], 'unitary', ring=GF(7**2))
            sage: unitary_specht._representation_matrix_uncached(Permutation([3,1,4,2]))
            [       4 5*z2 + 4]
            [2*z2 + 2        3]
            sage: unitary_specht._representation_matrix_uncached(Permutation([1,2,4,3]))
            [6 0]
            [0 1]
            sage: all(A * A.H == 1 for A in [unitary_specht._representation_matrix_uncached(g)
            ....:                            for g in Permutations(4)])
            True
        """
        assert self._ring.characteristic() > 0
        rho = self._specht.representation_matrix
        A = self._unitary_change_basis_matrix
        return A * rho(permutation) * A.inverse()


class UnitaryRepresentations(SymmetricGroupRepresentations_class):
    _default_ring = SR

    Element = UnitaryRepresentation

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

        EXAMPLES::

            sage: spc = SymmetricGroupRepresentations(4)
            sage: spc
            Specht representations of the symmetric group of order 4! over Integer Ring
        """
        return "Unitary representations of the symmetric group of order %s! over %s" % (self._n, self._ring)

# ##### Miscellaneous functions ############################################


def partition_to_vector_of_contents(partition, reverse=False):
    r"""
    Return the "vector of contents" associated to ``partition``.

    EXAMPLES::

        sage: from sage.combinat.symmetric_group_representations import partition_to_vector_of_contents
        sage: partition_to_vector_of_contents([3,2])
        (0, 1, 2, -1, 0)
    """
    v = []
    for i, p in enumerate(partition):
        v.extend(range(-i, -i + p))
    if reverse:
        return tuple(v)[::-1]
    return tuple(v)


# #### Garsia-Procesi modules ################################################

from sage.rings.quotient_ring import QuotientRing_generic
from sage.combinat.specht_module import SymmetricGroupRepresentation as SymmetricGroupRepresentation_mixin


class GarsiaProcesiModule(UniqueRepresentation, QuotientRing_generic, SymmetricGroupRepresentation_mixin):
    r"""
    A Garsia-Procesi module.

    Let `\lambda` be a partition of `n` and `R` be a commutative
    ring. The *Garsia-Procesi module* is defined by `R_{\lambda}
    := R[x_1, \ldots, x_n] / I_{\lambda}`, where

    .. MATH::

        I_{\lambda} := \langle e_r(x_{i_1}, \ldots, x_{i_k}) \mid
        \{i_1, \ldots, i_k\} \subseteq [n] \text{ and }
        k \geq r > k - d_k(\lambda) \rangle,

    with `e_r` being the `r`-the elementary symmetric function and
    `d_k(\lambda) = \lambda'_n + \cdots + \lambda'_{n+1-k}`, is the
    *Tanisaki ideal*.

    If we consider `R = \QQ`, then the Garsia-Procesi module has the
    following interpretation. Let `\mathcal{F}_n = GL_n / B` denote
    the (complex type A) flag variety. Consider the Springer fiber
    `F_{\lambda} \subseteq \mathcal{F}_n` associated to a nilpotent
    matrix with Jordan blocks sizes `\lambda`. Springer showed that
    the cohomology ring `H^*(F_{\lambda})` admits a graded `S_n`-action
    that agrees with the induced representation of the sign representation
    of the Young subgroup `S_{\lambda}`. From work of De Concini
    and Procesi, this `S_n`-representation is isomorphic to `R_{\lambda}`.
    Moreover, the graded Frobenius image is known to be a modified
    Hall-Littlewood polynomial.

    EXAMPLES::

        sage: SGA = SymmetricGroupAlgebra(QQ, 7)
        sage: GP421 = SGA.garsia_procesi_module([4, 2, 1])
        sage: GP421.dimension()
        105
        sage: v = GP421.an_element(); v
        -gp1 - gp2 - gp3 - gp4 - gp5 - gp6
        sage: SGA.an_element() * v
        -6*gp1 - 6*gp2 - 6*gp3 - 6*gp4 - 6*gp5 - 5*gp6

    We verify the result is a modified Hall-Littlewood polynomial by using
    the `Q'` Hall-Littlewood polynomials, replacing `q \mapsto q^{-1}` and
    multiplying by the smallest power of `q` so the coefficients are again
    polynomials::

        sage: GP421.graded_frobenius_image()
        q^4*s[4, 2, 1] + q^3*s[4, 3] + q^3*s[5, 1, 1] + (q^3+q^2)*s[5, 2]
         + (q^2+q)*s[6, 1] + s[7]
        sage: R.<q> = QQ[]
        sage: Sym = SymmetricFunctions(R)
        sage: s = Sym.s()
        sage: Qp = Sym.hall_littlewood(q).Qp()
        sage: mHL = s(Qp[4,2,1]); mHL
        s[4, 2, 1] + q*s[4, 3] + q*s[5, 1, 1] + (q^2+q)*s[5, 2]
         + (q^3+q^2)*s[6, 1] + q^4*s[7]
        sage: mHL.map_coefficients(lambda c: R(q^4*c(q^-1)))
        q^4*s[4, 2, 1] + q^3*s[4, 3] + q^3*s[5, 1, 1] + (q^3+q^2)*s[5, 2]
         + (q^2+q)*s[6, 1] + s[7]

    We show that the maximal degree component corresponds to the Yamanouchi
    words of content `\lambda`::

        sage: B = GP421.graded_decomposition(4).basis()
        sage: top_deg = [Word([i+1 for i in b.lift().lift().exponents()[0]]) for b in B]
        sage: yamanouchi = [P.to_packed_word() for P in OrderedSetPartitions(range(7), [4, 2, 1])
        ....:               if P.to_packed_word().reversal().is_yamanouchi()]
        sage: set(top_deg) == set(yamanouchi)
        True
    """
    @staticmethod
    def __classcall_private__(cls, SGA, shape):
        """
        Normalize input to ensure a unique representation.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 5)
            sage: from sage.combinat.symmetric_group_representations import GarsiaProcesiModule
            sage: GP1 = GarsiaProcesiModule(SGA, [2, 2, 1])
            sage: GP2 = GarsiaProcesiModule(SGA, Partitions(5)([2, 2, 1]))
            sage: GP1 is GP2
            True
            sage: GarsiaProcesiModule(SGA, [3])
            Traceback (most recent call last):
            ...
            ValueError: [3] is not a partition of 5
        """
        shape = Partition(shape)
        if sum(shape) != SGA.n:
            raise ValueError(f"{shape} is not a partition of {SGA.n}")
        return super().__classcall__(cls, SGA, shape)

    def __init__(self, SGA, shape):
        r"""
        Initialize ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: TestSuite(GP).run()

            sage: SGA = SymmetricGroupAlgebra(GF(2), 5)
            sage: GP = SGA.garsia_procesi_module([3, 1, 1])
            sage: TestSuite(GP).run()
        """
        self._shape = shape
        SymmetricGroupRepresentation_mixin.__init__(self, SGA)

        # Construct the Tanisaki ideal
        from sage.combinat.sf.sf import SymmetricFunctions
        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        from itertools import combinations
        n = SGA.n

        conj = list(shape.conjugate())
        conj += [0]*(n - len(conj))

        def p(k):
            return sum(conj[i] for i in range(n-k, n))

        BR = SGA.base_ring()
        R = PolynomialRing(BR, 'x', n)
        gens = R.gens()
        e = SymmetricFunctions(BR).e()
        I = R.ideal([e[d].expand(k)(*S)
                     for k in range(n+1) for d in range(k-p(k)+1, k+1)
                     for S in combinations(gens, k)])

        # Finalize the initialization
        names = tuple([f"gp{i}" for i in range(n)])
        from sage.categories.commutative_rings import CommutativeRings
        from sage.categories.algebras import Algebras
        cat = CommutativeRings().Quotients() & Algebras(SGA.base_ring()).Graded().WithBasis().FiniteDimensional()
        QuotientRing_generic.__init__(self, R, I, names=names, category=cat)

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

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: SGA.garsia_procesi_module([2, 2])
            Garsia-Procesi module of shape [2, 2] over Rational Field
        """
        return "Garsia-Procesi module of shape {} over {}".format(self._shape, self.base_ring())

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

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: latex(GP)
            R_{{\def\lr#1{\multicolumn{1}{|@{\hspace{.6ex}}c@{\hspace{.6ex}}|}{\raisebox{-.3ex}{$#1$}}}
            \raisebox{-.6ex}{$\begin{array}[b]{*{2}c}\cline{1-2}
            \lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{1-2}
            \lr{\phantom{x}}&\lr{\phantom{x}}\\\cline{1-2}
            \end{array}$}
            }}^{\Bold{Q}}
        """
        from sage.misc.latex import latex
        return "R_{{{}}}^{{{}}}".format(latex(self._shape), latex(self.base_ring()))

    def _coerce_map_from_base_ring(self):
        r"""
        Disable the coercion from the base ring from the category.

        TESTS::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: GP._coerce_map_from_base_ring() is None
            True
        """
        return None  # don't need anything special

    @cached_method
    def get_order(self):
        """
        Return the order of the elements in the basis.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: GP.get_order()
            (0, 1, 2, 3, 4, 5)
        """
        return tuple(self.basis().keys())

    @cached_method
    def basis(self):
        r"""
        Return a basis of ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: GP.basis()
            Family (gp2*gp3, gp1*gp3, gp3, gp2, gp1, 1)
        """
        from sage.sets.family import Family
        B = self.defining_ideal().normal_basis()
        return Family([self.retract(b) for b in B])

    @cached_method
    def one_basis(self):
        r"""
        Return the index of the basis element `1`.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 4)
            sage: GP = SGA.garsia_procesi_module([2, 2])
            sage: GP.one_basis()
            5
        """
        B = self.defining_ideal().normal_basis()
        for i, b in enumerate(B):
            if b.is_one():
                return ZZ(i)

    @cached_method
    def dimension(self):
        r"""
        Return the dimension of ``self``.

        The graded Frobenius character of the Garsia-Procesi module
        `R_{\lambda}` is given by the modified Hall-Littlewood polynomial
        `\widetilde{H}_{\lambda'}(x; q)`.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 5)
            sage: Sym = SymmetricFunctions(QQ)
            sage: s = Sym.s()
            sage: Qp = Sym.hall_littlewood(1).Qp()
            sage: for la in Partitions(5):
            ....:     print(SGA.garsia_procesi_module(la).dimension(),
            ....:           sum(c * StandardTableaux(la).cardinality()
            ....:               for la, c in s(Qp[la])))
            1 1
            5 5
            10 10
            20 20
            30 30
            60 60
            120 120
        """
        return self.defining_ideal().vector_space_dimension()

    @cached_method
    def graded_frobenius_image(self):
        r"""
        Return the graded Frobenius image of ``self``.

        The graded Frobenius image is the sum of the :meth:`frobenius_image`
        of each graded component, which is known to result in the modified
        Hall-Littlewood polynomial `\widetilde{H}_{\lambda}(x; q)`.

        EXAMPLES:

        We verify that the result is the modified Hall-Littlewood polynomial
        for `n = 5`::

            sage: R.<q> = QQ[]
            sage: Sym = SymmetricFunctions(R)
            sage: s = Sym.s()
            sage: Qp = Sym.hall_littlewood(q).Qp()
            sage: SGA = SymmetricGroupAlgebra(QQ, 5)
            sage: for la in Partitions(5):
            ....:     f = SGA.garsia_procesi_module(la).graded_frobenius_image()
            ....:     d = f[la].degree()
            ....:     assert f.map_coefficients(lambda c: R(c(~q)*q^d)) == s(Qp[la])
        """
        from sage.combinat.sf.sf import SymmetricFunctions
        R = QQ['q']
        q = R.gen()
        Sym = SymmetricFunctions(R)
        p = Sym.p()
        s = Sym.s()
        G = self._semigroup
        CCR = [(elt, elt.cycle_type()) for elt in G.conjugacy_classes_representatives()]
        B = self.basis()
        return s(p._from_dict({la: coeff / la.centralizer_size() for elt, la in CCR
                               if (coeff := sum(q**b.degree() * (elt * b).lift().monomial_coefficient(b.lift())
                                                for b in B))},
                              remove_zeros=False))

    @cached_method
    def graded_character(self):
        r"""
        Return the graded character of ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(QQ, 5)
            sage: GP = SGA.garsia_procesi_module([2, 2, 1])
            sage: gchi = GP.graded_character(); gchi
            (5*q^4 + 11*q^3 + 9*q^2 + 4*q + 1, -q^4 + q^3 + 3*q^2 + 2*q + 1,
             q^4 - q^3 + q^2 + 1, -q^4 - q^3 + q + 1, -q^4 + q^3 - q + 1,
             q^4 - q^3 - q^2 + 1, q^3 - q^2 - q + 1)
            sage: R.<q> = QQ[]
            sage: gchi == sum(q^d * D.character()
            ....:             for d, D in GP.graded_decomposition().items())
            True
        """
        q = QQ['q'].gen()
        G = self._semigroup
        B = self.basis()
        from sage.modules.free_module_element import vector
        return vector([sum(q**b.degree() * (g * b).lift().monomial_coefficient(b.lift()) for b in B)
                       for g in G.conjugacy_classes_representatives()],
                      immutable=True)

    @lazy_attribute
    def _graded_decomposition(self):
        """
        Construct the (internal) dictionary that encodes the graded
        decomposition of ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(GF(2), 5)
            sage: GP32 = SGA.garsia_procesi_module([3, 2])
            sage: GP32._graded_decomposition
            {0: Subrepresentation with basis {0} of Garsia-Procesi ...,
             1: Subrepresentation with basis {0, 1, 2, 3} of Garsia-Procesi ...,
             2: Subrepresentation with basis {0, 1, 2, 3, 4} of Garsia-Procesi ...}
        """
        d = {}
        for b in self.basis():
            deg = b.degree()
            if deg not in d:
                d[deg] = [b]
            else:
                d[deg].append(b)
        return {deg: self.subrepresentation(gens, is_closed=True)
                for deg, gens in sorted(d.items())}

    def graded_decomposition(self, k=None):
        r"""
        Return the decomposition of ``self`` as a direct sum of
        representations given by a fixed grading.

        INPUT:

        - ``k`` -- (optional) integer; if given, return the `k`-th graded part

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(GF(2), 5)
            sage: GP32 = SGA.garsia_procesi_module([3, 2])
            sage: decomp = GP32.graded_decomposition(); decomp
            {0: Subrepresentation with basis {0} of Garsia-Procesi ...,
             1: Subrepresentation with basis {0, 1, 2, 3} of Garsia-Procesi ...,
             2: Subrepresentation with basis {0, 1, 2, 3, 4} of Garsia-Procesi ...}
            sage: decomp[2] is GP32.graded_decomposition(2)
            True
            sage: GP32.graded_decomposition(10)
            Subrepresentation with basis {} of Garsia-Procesi module
             of shape [3, 2] over Finite Field of size 2
        """
        if k is None:
            # make a copy since mutable
            return dict(self._graded_decomposition)
        if k < 0 or k not in self._graded_decomposition:
            return self.subrepresentation([], is_closed=True)
        return self._graded_decomposition[k]

    def graded_representation_matrix(self, elt, q=None):
        r"""
        Return the matrix corresponding to the left action of the symmetric
        group (algebra) element ``elt`` on ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(GF(3), 3)
            sage: GP = SGA.garsia_procesi_module([1, 1, 1])
            sage: elt = SGA.an_element(); elt
            [1, 2, 3] + 2*[1, 3, 2] + [3, 1, 2]
            sage: X = GP.graded_representation_matrix(elt); X
            [  0   0   0   0   0   0]
            [  0 q^2   0   0   0   0]
            [  0 q^2   0   0   0   0]
            [  0   0   0   q   0   0]
            [  0   0   0   q   0   0]
            [  0   0   0   0   0   1]
            sage: X.parent()
            Full MatrixSpace of 6 by 6 dense matrices over
             Univariate Polynomial Ring in q over Finite Field of size 3
            sage: R.<q> = GF(3)[]
            sage: t = R.quotient([q^2+2*q+1]).gen()
            sage: GP.graded_representation_matrix(elt, t)
            [       0        0        0        0        0        0]
            [       0 qbar + 2        0        0        0        0]
            [       0 qbar + 2        0        0        0        0]
            [       0        0        0     qbar        0        0]
            [       0        0        0     qbar        0        0]
            [       0        0        0        0        0        1]
        """
        if q is None:
            q = self.base_ring()['q'].gen()
        R = q.parent()
        return matrix(R, [q**b.degree() * (elt * b).to_vector().change_ring(R)
                          for b in self.basis()])

    def graded_brauer_character(self):
        r"""
        Return the graded Brauer character of ``self``.

        EXAMPLES::

            sage: SGA = SymmetricGroupAlgebra(GF(2), 5)
            sage: GP311 = SGA.garsia_procesi_module([3, 1, 1])
            sage: GP311.graded_brauer_character()
            (6*q^3 + 9*q^2 + 4*q + 1, q + 1, q^3 - q^2 - q + 1)
        """
        q = QQ['q'].gen()
        return sum(q**d * SM.brauer_character() for d, SM in self._graded_decomposition.items())

    class Element(QuotientRing_generic.Element):
        def _acted_upon_(self, scalar, self_on_left=True):
            r"""
            Return the action of ``scalar`` on ``self``.

            EXAMPLES::

                sage: SGA = SymmetricGroupAlgebra(GF(3), 4)
                sage: GP22 = SGA.garsia_procesi_module([2, 2])
                sage: x = SGA.an_element(); x
                [1, 2, 3, 4] + 2*[1, 2, 4, 3] + [4, 1, 2, 3]
                sage: v = GP22.an_element(); v
                -gp1 - gp2 - gp3
                sage: g = SGA.group().an_element(); g
                [4, 1, 2, 3]
                sage: g * v  # indirect doctest
                gp3
                sage: x * v  # indirect doctest
                gp3
                sage: 2 * v  # indirect doctest
                gp1 + gp2 + gp3
            """
            P = self.parent()
            if scalar in P.base_ring():
                return super()._acted_upon_(scalar, self_on_left)
            if scalar in P._semigroup:
                gens = P.ambient().gens()
                return P.retract(self.lift().subs({g: gens[scalar(i+1)-1] for i, g in enumerate(gens)}))
            if not self_on_left and scalar in P._semigroup_algebra:
                scalar = P._semigroup_algebra(scalar)
                gens = P.ambient().gens()
                return P.sum(c * P.retract(self.lift().subs({g: gens[sigma(i+1)-1] for i, g in enumerate(gens)}))
                             for sigma, c in scalar.monomial_coefficients(copy=False).items())
            return super()._acted_upon_(scalar, self_on_left)

        def to_vector(self, order=None):
            r"""
            Return ``self`` as a (dense) free module vector.

            EXAMPLES::

                sage: SGA = SymmetricGroupAlgebra(GF(3), 4)
                sage: GP22 = SGA.garsia_procesi_module([2, 2])
                sage: v = GP22.an_element(); v
                -gp1 - gp2 - gp3
                sage: v.to_vector()
                (0, 0, 2, 2, 2, 0)
            """
            P = self.parent()
            B = P.basis()
            FM = P._dense_free_module()
            f = self.lift()
            return FM([f.monomial_coefficient(b.lift()) for b in B])

        _vector_ = to_vector

        def monomial_coefficients(self, copy=None):
            r"""
            Return the monomial coefficients of ``self``.

            EXAMPLES::

                sage: SGA = SymmetricGroupAlgebra(GF(3), 4)
                sage: GP31 = SGA.garsia_procesi_module([3, 1])
                sage: v = GP31.an_element(); v
                -gp1 - gp2 - gp3
                sage: v.monomial_coefficients()
                {0: 2, 1: 2, 2: 2, 3: 0}
            """
            B = self.parent().basis()
            f = self.lift()
            return {i: f.monomial_coefficient(b.lift()) for i, b in enumerate(B)}

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

            EXAMPLES::

                sage: SGA = SymmetricGroupAlgebra(GF(3), 4)
                sage: GP22 = SGA.garsia_procesi_module([2, 2])
                sage: for b in GP22.basis():
                ....:     print(b, b.degree())
                gp2*gp3 2
                gp1*gp3 2
                gp3 1
                gp2 1
                gp1 1
                1 0
                sage: v = sum(GP22.basis())
                sage: v.degree()
                2
            """
            return self.lift().degree()

        def homogeneous_degree(self):
            r"""
            Return the (homogeneous) degree of ``self`` if homogeneous
            otherwise raise an error.

            EXAMPLES::

                sage: SGA = SymmetricGroupAlgebra(GF(2), 4)
                sage: GP31 = SGA.garsia_procesi_module([3, 1])
                sage: for b in GP31.basis():
                ....:     print(b, b.homogeneous_degree())
                gp3 1
                gp2 1
                gp1 1
                1 0
                sage: v = sum(GP31.basis()); v
                gp1 + gp2 + gp3 + 1
                sage: v.homogeneous_degree()
                Traceback (most recent call last):
                ...
                ValueError: element is not homogeneous

            TESTS::

                sage: SGA = SymmetricGroupAlgebra(GF(3), 4)
                sage: GP4 = SGA.garsia_procesi_module([4])
                sage: GP4.zero().homogeneous_degree()
                Traceback (most recent call last):
                ...
                ValueError: the zero element does not have a well-defined degree
            """
            if not self:
                raise ValueError("the zero element does not have a well-defined degree")
            f = self.lift()
            if not f.is_homogeneous():
                raise ValueError("element is not homogeneous")
            return f.degree()
