def _construct_UEA(self):
            """
            Construct the universal enveloping algebra of ``self``.

            EXAMPLES::

                sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example()
                sage: UEA = L._construct_UEA(); UEA
                Noncommutative Multivariate Polynomial Ring in b0, b1, b2
                 over Rational Field, nc-relations: {}
                sage: UEA.relations(add_commutative=True)
                {b1*b0: b0*b1, b2*b0: b0*b2, b2*b1: b1*b2}

            ::

                sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}, ('y','z'):{'x':1}, ('z','x'):{'y':1}})
                sage: UEA = L._construct_UEA(); UEA
                Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field,
                 nc-relations: {...}
                sage: sorted(UEA.relations().items(), key=str)
                [(y*x, x*y - z), (z*x, x*z + y), (z*y, y*z - x)]
            """
            # Create the UEA relations
            # We need to get names for the basis elements, not just the generators
            I = self._basis_ordering
            try:
                names = [str(x) for x in I]

                def names_map(x):
                    return x

                F = FreeAlgebra(self.base_ring(), names)
            except ValueError:
                names = ['b{}'.format(i) for i in range(self.dimension())]
                self._UEA_names_map = {g: names[i] for i, g in enumerate(I)}
                names_map = self._UEA_names_map.__getitem__
                F = FreeAlgebra(self.base_ring(), names)
            # ``F`` is the free algebra over the basis of ``self``. The
            # universal enveloping algebra of ``self`` will be constructed
            # as a quotient of ``F``.
            d = F.gens_dict()
            rels = {}
            S = self.structure_coefficients(True)

            # Construct the map from indices to names of the UEA
            def get_var(g):
                return d[names_map(g)]

            # The function ``get_var`` sends an element of the basis of
            # ``self`` to the corresponding element of ``F``.
            for k in S.keys():
                g0 = get_var(k[0])
                g1 = get_var(k[1])
                if g0 < g1:
                    rels[g1 * g0] = g0 * g1 - F.sum(val * get_var(g)
                                                    for g, val in S[k])
                else:
                    rels[g0 * g1] = g1 * g0 + F.sum(val * get_var(g)
                                                    for g, val in S[k])
            return F.g_algebra(rels)
    def __init__(self, field, relations, names):
        """
        The finitely presented algebra equivalent to the free algebra over `field` generated by
        `names` modulo the ideal generated by `relations`.
        """
        if field not in Fields: raise TypeError('Base ring must be a field.')

        if type(relations) == str: relations = tuple(relations.split(','))
        elif type(relations) == list: relations = tuple(relations)
        elif not isinstance(relations, tuple):
            raise TypeError(
                'Relations must be given as a list, tuple, or string.')

        if type(names) == str: names = tuple(names.split(','))
        elif type(names) == list: names = tuple(names)
        elif not isinstance(names, tuple):
            raise TypeError(
                'Generators must be given as a list, tuple, or string.')

        self._ngens = len(names)
        self._nrels = len(relations)
        self._free_alg = FreeAlgebra(field, self._ngens, names)
        self._ideal = self._free_alg.ideal(relations)

        self._reduce = []
        for f in self._ideal.gens():
            mons = f.monomials()
            if len(mons) == 1:
                self._reduce.append([_to_word(f), Word(''), field.zero()])
            if len(mons) == 2:
                coeffs = f.coefficients()
                if mons[0] < mons[1]:
                    self._reduce.append([
                        _to_word(mons[1]),
                        _to_word(mons[0]), -coeffs[0] * coeffs[1]**(-1)
                    ])
                elif mons[0] > mons[1]:
                    self._reduce.append([
                        _to_word(mons[0]),
                        _to_word(mons[1]), -coeffs[1] * coeffs[0]**(-1)
                    ])

        QuotientRing_nc.__init__(self,
                                 self._free_alg,
                                 self._ideal,
                                 names,
                                 category=AlgebrasWithBasis(field))
Example #3
0
def hamilton_quatalg(R):
    """
    Hamilton quaternion algebra over the commutative ring R,
    constructed as a free algebra quotient.

    INPUT:
        - R -- a commutative ring

    OUTPUT:
        - Q -- quaternion algebra
        - gens -- generators for Q

    EXAMPLES::

        sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(ZZ)
        sage: H
        Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Integer Ring
        sage: i^2
        -1
        sage: i in H
        True

    Note that there is another vastly more efficient models for
    quaternion algebras in Sage; the one here is mainly for testing
    purposes::

        sage: R.<i,j,k> = QuaternionAlgebra(QQ,-1,-1)  # much fast than the above 
    """
    n = 3
    from sage.algebras.free_algebra import FreeAlgebra
    from sage.matrix.all import MatrixSpace
    A = FreeAlgebra(R, n, 'i')
    F = A.monoid()
    i, j, k = F.gens()
    mons = [F(1), i, j, k]
    M = MatrixSpace(R, 4)
    mats = [
        M([0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0]),
        M([0, 0, 1, 0, 0, 0, 0, 1, -1, 0, 0, 0, 0, -1, 0, 0]),
        M([0, 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, -1, 0, 0, 0])
    ]
    H3 = FreeAlgebraQuotient(A, mons, mats, names=('i', 'j', 'k'))
    return H3, H3.gens()
    def _construct_UEA(self):
        """
        Construct the universal enveloping algebra of ``self``.

        EXAMPLES::

            sage: L.<x, y> = LieAlgebra(QQ)
            sage: L._construct_UEA()
            Free Algebra on 2 generators (x, y) over Rational Field
        """
        # TODO: Pass the index set along once FreeAlgebra accepts it
        return FreeAlgebra(self.base_ring(), len(self._names), self._names)
def hamilton_quatalg(R):
    """
    Hamilton quaternion algebra over the commutative ring R,
    constructed as a free algebra quotient.

    INPUT:
        - R -- a commutative ring

    OUTPUT:
        - Q -- quaternion algebra
        - gens -- generators for Q

    EXAMPLES::

        sage: H, (i,j,k) = sage.algebras.free_algebra_quotient.hamilton_quatalg(ZZ)
        sage: H
        Free algebra quotient on 3 generators ('i', 'j', 'k') and dimension 4 over Integer Ring
        sage: i^2
        -1
        sage: i in H
        True

    Note that there is another vastly more efficient models for
    quaternion algebras in Sage; the one here is mainly for testing
    purposes::

        sage: R.<i,j,k> = QuaternionAlgebra(QQ,-1,-1)  # much fast than the above
    """
    n = 3
    from sage.algebras.free_algebra import FreeAlgebra
    from sage.matrix.all import MatrixSpace
    A = FreeAlgebra(R, n, 'i')
    F = A.monoid()
    i, j, k = F.gens()
    mons = [ F(1), i, j, k ]
    M = MatrixSpace(R,4)
    mats = [M([0,1,0,0, -1,0,0,0, 0,0,0,-1, 0,0,1,0]),  M([0,0,1,0, 0,0,0,1, -1,0,0,0, 0,-1,0,0]),  M([0,0,0,1, 0,0,-1,0, 0,1,0,0, -1,0,0,0]) ]
    H3 = FreeAlgebraQuotient(A,mons,mats, names=('i','j','k'))
    return H3, H3.gens()
    def _construct_UEA(self):
        """
        Construct the universal enveloping algebra of ``self``.

        EXAMPLES::

            sage: L.<x, y> = LieAlgebra(QQ)
            sage: L._construct_UEA()
            Free Algebra on 2 generators (x, y) over Rational Field
            sage: L.<x> = LieAlgebra(QQ)
            sage: L._construct_UEA()
            Free Algebra on 1 generators (x,) over Rational Field
        """
        return FreeAlgebra(self.base_ring(), len(self._names), self._names)
Example #7
0
    def diff_alg(self):
        r"""
        Return the algebra of differential operators
        (over QQ) which is used on rational functions
        representing elements of ``self``.

        EXAMPLES::

            sage: from graded_ring import ModularFormsRing
            sage: ModularFormsRing().diff_alg()
            Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1}

            sage: from space import CuspForms
            sage: CuspForms(k=12, base_ring=AA).diff_alg()
            Noncommutative Multivariate Polynomial Ring in X, Y, Z, dX, dY, dZ over Rational Field, nc-relations: {dY*Y: Y*dY + 1, dZ*Z: Z*dZ + 1, dX*X: X*dX + 1}
        """

        # We only use two operators for now which do not involve 'd', so for performance
        # reason we choose FractionField(base_ring) instead of self.coeff_ring().
        free_alg         = FreeAlgebra(FractionField(ZZ),6,'X,Y,Z,dX,dY,dZ')
        (X,Y,Z,dX,dY,dZ) = free_alg.gens()
        diff_alg         = free_alg.g_algebra({dX*X:1+X*dX,dY*Y:1+Y*dY,dZ*Z:1+Z*dZ})
        
        return diff_alg
        def _construct_UEA(self):
            """
            Construct the universal enveloping algebra of ``self``.

            EXAMPLES::

                sage: L = LieAlgebras(QQ).FiniteDimensional().WithBasis().example()
                sage: UEA = L._construct_UEA(); UEA
                Noncommutative Multivariate Polynomial Ring in b0, b1, b2
                 over Rational Field, nc-relations: {}
                sage: UEA.relations(add_commutative=True)
                {b1*b0: b0*b1, b2*b0: b0*b2, b2*b1: b1*b2}

            ::

                sage: L.<x,y,z> = LieAlgebra(QQ, {('x','y'):{'z':1}, ('y','z'):{'x':1}, ('z','x'):{'y':1}})
                sage: UEA = L._construct_UEA(); UEA
                Noncommutative Multivariate Polynomial Ring in x, y, z over Rational Field,
                 nc-relations: {...}
                sage: sorted(UEA.relations().items(), key=str)
                [(y*x, x*y - z), (z*x, x*z + y), (z*y, y*z - x)]
            """
            # Create the UEA relations
            # We need to get names for the basis elements, not just the generators
            I = self._basis_ordering
            try:
                names = [str(x) for x in I]
                def names_map(x): return x
                F = FreeAlgebra(self.base_ring(), names)
            except ValueError:
                names = ['b{}'.format(i) for i in range(self.dimension())]
                self._UEA_names_map = {g: names[i] for i,g in enumerate(I)}
                names_map = self._UEA_names_map.__getitem__
                F = FreeAlgebra(self.base_ring(), names)
            # ``F`` is the free algebra over the basis of ``self``. The
            # universal enveloping algebra of ``self`` will be constructed
            # as a quotient of ``F``.
            d = F.gens_dict()
            rels = {}
            S = self.structure_coefficients(True)
            # Construct the map from indices to names of the UEA
            def get_var(g): return d[names_map(g)]
            # The function ``get_var`` sends an element of the basis of
            # ``self`` to the corresponding element of ``F``.
            for k in S.keys():
                g0 = get_var(k[0])
                g1 = get_var(k[1])
                if g0 < g1:
                    rels[g1*g0] = g0*g1 - F.sum(val*get_var(g) for g, val in S[k])
                else:
                    rels[g0*g1] = g1*g0 + F.sum(val*get_var(g) for g, val in S[k])
            return F.g_algebra(rels)
Example #9
0
    def __classcall_private__(cls, R=None, arg0=None, arg1=None, names=None,
                              index_set=None, abelian=False, **kwds):
        """
        Select the correct parent based upon input.

        TESTS::

            sage: LieAlgebra(QQ, abelian=True, names='x,y,z')
            Abelian Lie algebra on 3 generators (x, y, z) over Rational Field
            sage: LieAlgebra(QQ, {('e','h'): {'e':-2}, ('f','h'): {'f':2},
            ....:                 ('e','f'): {'h':1}}, names='e,f,h')
            Lie algebra on 3 generators (e, f, h) over Rational Field
        """
        # Parse associative algebra input
        # -----

        assoc = kwds.get("associative", None)
        if assoc is not None:
            return LieAlgebraFromAssociative(assoc, names=names, index_set=index_set)

        # Parse input as a Cartan type
        # -----

        ct = kwds.get("cartan_type", None)
        if ct is not None:
            from sage.combinat.root_system.cartan_type import CartanType
            ct = CartanType(ct)
            if ct.is_affine():
                from sage.algebras.lie_algebras.affine_lie_algebra import AffineLieAlgebra
                return AffineLieAlgebra(R, cartan_type=ct,
                                        kac_moody=kwds.get("kac_moody", True))
            if not ct.is_finite():
                raise NotImplementedError("non-finite types are not implemented yet, see trac #14901 for details")
            rep = kwds.get("representation", "bracket")
            if rep == 'bracket':
                from sage.algebras.lie_algebras.classical_lie_algebra import LieAlgebraChevalleyBasis
                return LieAlgebraChevalleyBasis(R, ct)
            if rep == 'matrix':
                from sage.algebras.lie_algebras.classical_lie_algebra import ClassicalMatrixLieAlgebra
                return ClassicalMatrixLieAlgebra(R, ct)
            raise ValueError("invalid representation")

        # Parse the remaining arguments
        # -----

        if R is None:
            raise ValueError("invalid arguments")

        check_assoc = lambda A: (isinstance(A, (Ring, MatrixSpace))
                                 or A in Rings()
                                 or A in Algebras(R).Associative())
        if arg0 in ZZ or check_assoc(arg1):
            # Check if we need to swap the arguments
            arg0, arg1 = arg1, arg0

        # Parse the first argument
        # -----

        if isinstance(arg0, dict):
            if not arg0:
                from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
                return AbelianLieAlgebra(R, names, index_set)
            elif isinstance(next(iter(arg0.keys())), (list, tuple)):
                # We assume it is some structure coefficients
                arg1, arg0 = arg0, arg1

        if isinstance(arg0, (list, tuple)):
            if all(isinstance(x, str) for x in arg0):
                # If they are all strings, then it is a list of variables
                names = tuple(arg0)

        if isinstance(arg0, str):
            names = tuple(arg0.split(','))
        elif isinstance(names, str):
            names = tuple(names.split(','))

        # Parse the second argument

        if isinstance(arg1, dict):
            # Assume it is some structure coefficients
            from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients
            return LieAlgebraWithStructureCoefficients(R, arg1, names, index_set, **kwds)

        # Otherwise it must be either a free or abelian Lie algebra

        if arg1 in ZZ:
            if isinstance(arg0, str):
                names = arg0
            if names is None:
                index_set = list(range(arg1))
            else:
                if isinstance(names, str):
                    names = tuple(names.split(','))
                    if arg1 != 1 and len(names) == 1:
                        names = tuple('{}{}'.format(names[0], i)
                                      for i in range(arg1))
                if arg1 != len(names):
                    raise ValueError("the number of names must equal the"
                                     " number of generators")

        if abelian:
            from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
            return AbelianLieAlgebra(R, names, index_set)

        # Otherwise it is the free Lie algebra
        rep = kwds.get("representation", "bracket")
        if rep == "polynomial":
            # Construct the free Lie algebra from polynomials in the
            #   free (associative unital) algebra
            # TODO: Change this to accept an index set once FreeAlgebra accepts one
            from sage.algebras.free_algebra import FreeAlgebra
            F = FreeAlgebra(R, names)
            if index_set is None:
                index_set = F.variable_names()
            # TODO: As part of #16823, this should instead construct a
            #   subclass with specialized methods for the free Lie algebra
            return LieAlgebraFromAssociative(F, F.gens(), names=names, index_set=index_set)

        raise NotImplementedError("the free Lie algebra has only been"
                                  " implemented using polynomials in the"
                                  " free algebra, see trac ticket #16823")
Example #10
0
    def __classcall_private__(cls,
                              R=None,
                              arg0=None,
                              arg1=None,
                              names=None,
                              index_set=None,
                              abelian=False,
                              **kwds):
        """
        Select the correct parent based upon input.

        TESTS::

            sage: LieAlgebra(QQ, abelian=True, names='x,y,z')
            Abelian Lie algebra on 3 generators (x, y, z) over Rational Field
            sage: LieAlgebra(QQ, {('e','h'): {'e':-2}, ('f','h'): {'f':2},
            ....:                 ('e','f'): {'h':1}}, names='e,f,h')
            Lie algebra on 3 generators (e, f, h) over Rational Field
        """
        # Parse associative algebra input
        # -----

        assoc = kwds.get("associative", None)
        if assoc is not None:
            return LieAlgebraFromAssociative(assoc,
                                             names=names,
                                             index_set=index_set)

        # Parse input as a Cartan type
        # -----

        ct = kwds.get("cartan_type", None)
        if ct is not None:
            from sage.combinat.root_system.cartan_type import CartanType
            ct = CartanType(ct)
            if ct.is_affine():
                from sage.algebras.lie_algebras.affine_lie_algebra import AffineLieAlgebra
                return AffineLieAlgebra(R,
                                        cartan_type=ct,
                                        kac_moody=kwds.get("kac_moody", True))
            if not ct.is_finite():
                raise NotImplementedError(
                    "non-finite types are not implemented yet, see trac #14901 for details"
                )
            rep = kwds.get("representation", "bracket")
            if rep == 'bracket':
                from sage.algebras.lie_algebras.classical_lie_algebra import LieAlgebraChevalleyBasis
                return LieAlgebraChevalleyBasis(R, ct)
            if rep == 'matrix':
                from sage.algebras.lie_algebras.classical_lie_algebra import ClassicalMatrixLieAlgebra
                return ClassicalMatrixLieAlgebra(R, ct)
            raise ValueError("invalid representation")

        # Parse the remaining arguments
        # -----

        if R is None:
            raise ValueError("invalid arguments")

        check_assoc = lambda A: (isinstance(A, (Ring, MatrixSpace)) or A in
                                 Rings() or A in Algebras(R).Associative())
        if arg0 in ZZ or check_assoc(arg1):
            # Check if we need to swap the arguments
            arg0, arg1 = arg1, arg0

        # Parse the first argument
        # -----

        if isinstance(arg0, dict):
            if not arg0:
                from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
                return AbelianLieAlgebra(R, names, index_set)
            elif isinstance(next(iter(arg0.keys())), (list, tuple)):
                # We assume it is some structure coefficients
                arg1, arg0 = arg0, arg1

        if isinstance(arg0, (list, tuple)):
            if all(isinstance(x, str) for x in arg0):
                # If they are all strings, then it is a list of variables
                names = tuple(arg0)

        if isinstance(arg0, str):
            names = tuple(arg0.split(','))
        elif isinstance(names, str):
            names = tuple(names.split(','))

        # Parse the second argument

        if isinstance(arg1, dict):
            # Assume it is some structure coefficients
            from sage.algebras.lie_algebras.structure_coefficients import LieAlgebraWithStructureCoefficients
            return LieAlgebraWithStructureCoefficients(R, arg1, names,
                                                       index_set, **kwds)

        # Otherwise it must be either a free or abelian Lie algebra

        if arg1 in ZZ:
            if isinstance(arg0, str):
                names = arg0
            if names is None:
                index_set = list(range(arg1))
            else:
                if isinstance(names, str):
                    names = tuple(names.split(','))
                    if arg1 != 1 and len(names) == 1:
                        names = tuple('{}{}'.format(names[0], i)
                                      for i in range(arg1))
                if arg1 != len(names):
                    raise ValueError("the number of names must equal the"
                                     " number of generators")

        if abelian:
            from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
            return AbelianLieAlgebra(R, names, index_set)

        # Otherwise it is the free Lie algebra
        rep = kwds.get("representation", "bracket")
        if rep == "polynomial":
            # Construct the free Lie algebra from polynomials in the
            #   free (associative unital) algebra
            # TODO: Change this to accept an index set once FreeAlgebra accepts one
            from sage.algebras.free_algebra import FreeAlgebra
            F = FreeAlgebra(R, names)
            if index_set is None:
                index_set = F.variable_names()
            # TODO: As part of #16823, this should instead construct a
            #   subclass with specialized methods for the free Lie algebra
            return LieAlgebraFromAssociative(F,
                                             F.gens(),
                                             names=names,
                                             index_set=index_set)

        raise NotImplementedError("the free Lie algebra has only been"
                                  " implemented using polynomials in the"
                                  " free algebra, see trac ticket #16823")
    def has_irred_rep(self, n, gen_set=None, restrict=None, force=False):
        """
        Returns `True` if there exists an `n`-dimensional irreducible representation of `self`,
        and `False` otherwise. Of course, this function runs `has_rep(n, restrict)` to verify
        there is a representation in the first place, and returns `False` if not.

        The argument `restrict` may be used equivalenty to its use in `has_rep()`.

        The argument `gen_set` may be set to `'PBW'` or `'pbw'`, if `self` has an algebra basis
        similar to that of a Poincaré-Birkhoff-Witt basis.

        Alternatively, an explicit generating set for the algorithm implemented by this function
        can be given, as a tuple or array of `FreeAlgebraElements`. This is only useful if the
        package cannot reduce the elements of `self`, but they can be reduced in theory.

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError(
                'Base field must be computable. If %s is computable' %
                self.base_field() + ' then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        if not self.has_rep(n, restrict): return False
        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        from sage.groups.all import SymmetricGroup
        import math

        B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1),
                           'x',
                           order='deglex')
        M = MatrixSpace(B, n, sparse=True)

        gen_matrix = list()
        if not isinstance(restrict, (tuple, list)):
            restrict = [None for i in range(self.ngens())]
        if len(restrict) != self.ngens():
            raise ValueError(
                'Length of restrict does not match number of generators.')
        for i in range(self.ngens()):
            ith_gen_matrix = []
            for j in range(n):
                for k in range(n):
                    if restrict[i] == 'upper' and j > k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'lower' and j < k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'diagonal' and j != k:
                        ith_gen_matrix.append(B.zero())
                    else:
                        ith_gen_matrix.append(B.gen(j + (j + 1) * k +
                                                    i * n**2))
            gen_matrix.append(M(ith_gen_matrix))

        relB = list()
        for i in range(self.nrels()):
            relB += self._to_matrix(self.rel(i), M, gen_matrix).list()

        Z = FreeAlgebra(ZZ, 2 * n - 2, 'Y')
        standard_poly = Z(0)
        for s in SymmetricGroup(2 * n - 2).list():
            standard_poly += s.sign() * reduce(
                lambda x, y: x * y, [Z('Y%s' % (i - 1)) for i in s.tuple()])

        if n <= 6 and is_NumberField(self.base_field()): p = 2 * n
        else:
            p = int(
                math.floor(n *
                           math.sqrt(2 * n**2 / float(n - 1) + 1 / float(4)) +
                           n / float(2) - 3))

        if isinstance(gen_set, (tuple, list)):
            try:
                gen_set = [
                    self._to_matrix(elt, M, gen_matrix) for elt in gen_set
                ]
            except (NameError, TypeError) as error:
                print(error)

        if gen_set == None:
            word_gen_set = list(self._create_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        elif gen_set == 'pbw' or gen_set == 'PBW':
            word_gen_set = list(self._create_pbw_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        else:
            raise TypeError('Invalid generating set.')

        ordering = [i for i in range(2 * n - 2)]
        max_ordering = [
            len(gen_set) - (2 * n - 2) + i for i in range(2 * n - 2)
        ]
        ordering.insert(0, 0)
        max_ordering.insert(0, len(gen_set))
        rep_exists = False
        z = B.gen(B.ngens() - 1)

        while ordering[0] != max_ordering[0]:
            y = gen_set[ordering[0]].trace_of_product(
                standard_poly.subs({
                    Z('Y%s' % (j - 1)): gen_set[ordering[j]]
                    for j in range(1, 2 * n - 1)
                }))
            radB_test = relB + [B(1) - z * y]
            if B.one() not in B.ideal(radB_test):
                rep_exists = True
                break
            for i in range(2 * n - 2, -1, -1):
                if i != 0 and ordering[i] != max_ordering[i]:
                    ordering[i] += 1
                    break
                elif i == 0:
                    ordering[i] += 1
                    if ordering[i] != max_ordering[i]:
                        for j in range(1, 2 * n - 1):
                            ordering[j] = j - 1

        return rep_exists
class FinitelyPresentedAlgebra(QuotientRing_nc, Algebra):
    """
    A class for finitely presented algebras.
    """

    Element = FinitelyPresentedAlgebraElement

    def __init__(self, field, relations, names):
        """
        The finitely presented algebra equivalent to the free algebra over `field` generated by
        `names` modulo the ideal generated by `relations`.
        """
        if field not in Fields: raise TypeError('Base ring must be a field.')

        if type(relations) == str: relations = tuple(relations.split(','))
        elif type(relations) == list: relations = tuple(relations)
        elif not isinstance(relations, tuple):
            raise TypeError(
                'Relations must be given as a list, tuple, or string.')

        if type(names) == str: names = tuple(names.split(','))
        elif type(names) == list: names = tuple(names)
        elif not isinstance(names, tuple):
            raise TypeError(
                'Generators must be given as a list, tuple, or string.')

        self._ngens = len(names)
        self._nrels = len(relations)
        self._free_alg = FreeAlgebra(field, self._ngens, names)
        self._ideal = self._free_alg.ideal(relations)

        self._reduce = []
        for f in self._ideal.gens():
            mons = f.monomials()
            if len(mons) == 1:
                self._reduce.append([_to_word(f), Word(''), field.zero()])
            if len(mons) == 2:
                coeffs = f.coefficients()
                if mons[0] < mons[1]:
                    self._reduce.append([
                        _to_word(mons[1]),
                        _to_word(mons[0]), -coeffs[0] * coeffs[1]**(-1)
                    ])
                elif mons[0] > mons[1]:
                    self._reduce.append([
                        _to_word(mons[0]),
                        _to_word(mons[1]), -coeffs[1] * coeffs[0]**(-1)
                    ])

        QuotientRing_nc.__init__(self,
                                 self._free_alg,
                                 self._ideal,
                                 names,
                                 category=AlgebrasWithBasis(field))

    def _repr_(self):
        """
        Returns a text representation of `self`.
        """
        return 'Finitely presented algebra over {} with presentation <{} | {}>'.format(
            self.base_field(),
            ', '.join([str(self.gen(i)) for i in range(self.ngens())]),
            ', '.join([str(self.rel(i)) for i in range(self.nrels())]))

    def _latex_(self):
        """
        Returns a LaTeX representation of `self`.
        """
        return '{} \\langle {} \\mid {} \\rangle'.format(
            self.base_field()._latex_(),
            ', '.join(self.free_algebra().latex_variable_names()),
            ', '.join([self.rel(i)._latex_() for i in range(self.nrels())]))

    def _element_constructor_(self, f, coerce=True):
        """
        Converts `f` into an element of `self`.
        """
        if isinstance(f, FinitelyPresentedAlgebraElement):
            if f.parent() is self:
                return f
            f = f.lift()
        if coerce:
            A = self.free_algebra()
            f = A(f)
        return self.element_class(self, f)

    def ngens(self):
        """
        Returns the number of generations of `self`.
        """
        return self._ngens

    def nrels(self):
        """
        Returns the number of relations of `self`.
        """
        return self._nrels

    def base_ring(self):
        """
        Returns the number of relations of `self`.
        """
        return self.free_algebra().base_ring()

    def base_field(self):
        """
        Same functionality as `base_ring()`.
        """
        return self.free_algebra().base_ring()

    def free_algebra(self):
        """
        Returns the base free algebra of `self`, which is a `FreeAlgebra` object over its base
        field with generators matching the generators of `self`.
        """
        return self._free_alg

    def gen(self, i):
        """
        Returns the i-th generator of `self`, as a `FreeAlgebraElement`.
        """
        return self.free_algebra().gen(i)

    def gens(self):
        """
        Returns the generators of `self`, as a tuple.
        """
        return self.free_algebra().gens()

    def rel(self, i):
        """
        Returns the i-th relation of `self`, as a `FreeAlgebraElement`.
        """
        return self.defining_ideal().gen(i)

    def rels(self):
        """
        Returns the relations of `self`, as a tuple.
        """
        return self.defining_ideal().gens()

    def one(self):
        """
        Returns the multiplicative identity of `self`, which is equal to the multiplicative
        identity of its base field.
        """
        return self.element_class(self, self.free_algebra().one())

    def zero(self):
        """
        Returns the additive identity of `self`, which is equal to the additive identity of its
        base field.
        """
        return self.element_class(self, self.free_algebra().zero())

    def monoid(self):
        """
        Returns the free monoid on the generators of `self`.
        """
        return self.free_algebra().monoid()

    def has_rep(self, n, restrict=None, force=False):
        """
        Returns `True` if there exists an `n`-dimensional representation of `self`, and `False`
        otherwise.

        The optional argument `restrict` may be used to restrict the possible images of the
        generators. To do so, `restrict` must be a tuple with entries of `None`, `'diagonal'`,
        `'lower'`, or `'upper'`. Its length must match the number of generators of `self`.

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError(
                'Base field must be computable. If %s is computable' %
                self.base_field() + ' then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        import math

        B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1),
                           'x',
                           order='deglex')
        M = MatrixSpace(B, n, sparse=True)

        gen_matrix = list()
        if not isinstance(restrict, (tuple, list)):
            restrict = [None for i in range(self.ngens())]
        if len(restrict) != self.ngens():
            raise ValueError(
                'Length of restrict does not match number of generators.')
        for i in range(self.ngens()):
            ith_gen_matrix = []
            for j in range(n):
                for k in range(n):
                    if restrict[i] == 'upper' and j > k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'lower' and j < k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'diagonal' and j != k:
                        ith_gen_matrix.append(B.zero())
                    else:
                        ith_gen_matrix.append(B.gen(j + (j + 1) * k +
                                                    i * n**2))
            gen_matrix.append(M(ith_gen_matrix))

        relB_list = list()
        for i in range(self.nrels()):
            relB_list += self._to_matrix(self.rel(i), M, gen_matrix).list()
        relB = B.ideal(relB_list)

        if relB.dimension() == -1: return False
        else: return True

    def has_irred_rep(self, n, gen_set=None, restrict=None, force=False):
        """
        Returns `True` if there exists an `n`-dimensional irreducible representation of `self`,
        and `False` otherwise. Of course, this function runs `has_rep(n, restrict)` to verify
        there is a representation in the first place, and returns `False` if not.

        The argument `restrict` may be used equivalenty to its use in `has_rep()`.

        The argument `gen_set` may be set to `'PBW'` or `'pbw'`, if `self` has an algebra basis
        similar to that of a Poincaré-Birkhoff-Witt basis.

        Alternatively, an explicit generating set for the algorithm implemented by this function
        can be given, as a tuple or array of `FreeAlgebraElements`. This is only useful if the
        package cannot reduce the elements of `self`, but they can be reduced in theory.

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError(
                'Base field must be computable. If %s is computable' %
                self.base_field() + ' then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        if not self.has_rep(n, restrict): return False
        from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
        from sage.groups.all import SymmetricGroup
        import math

        B = PolynomialRing(self.base_field(), (self.ngens() * n**2 + 1),
                           'x',
                           order='deglex')
        M = MatrixSpace(B, n, sparse=True)

        gen_matrix = list()
        if not isinstance(restrict, (tuple, list)):
            restrict = [None for i in range(self.ngens())]
        if len(restrict) != self.ngens():
            raise ValueError(
                'Length of restrict does not match number of generators.')
        for i in range(self.ngens()):
            ith_gen_matrix = []
            for j in range(n):
                for k in range(n):
                    if restrict[i] == 'upper' and j > k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'lower' and j < k:
                        ith_gen_matrix.append(B.zero())
                    elif restrict[i] == 'diagonal' and j != k:
                        ith_gen_matrix.append(B.zero())
                    else:
                        ith_gen_matrix.append(B.gen(j + (j + 1) * k +
                                                    i * n**2))
            gen_matrix.append(M(ith_gen_matrix))

        relB = list()
        for i in range(self.nrels()):
            relB += self._to_matrix(self.rel(i), M, gen_matrix).list()

        Z = FreeAlgebra(ZZ, 2 * n - 2, 'Y')
        standard_poly = Z(0)
        for s in SymmetricGroup(2 * n - 2).list():
            standard_poly += s.sign() * reduce(
                lambda x, y: x * y, [Z('Y%s' % (i - 1)) for i in s.tuple()])

        if n <= 6 and is_NumberField(self.base_field()): p = 2 * n
        else:
            p = int(
                math.floor(n *
                           math.sqrt(2 * n**2 / float(n - 1) + 1 / float(4)) +
                           n / float(2) - 3))

        if isinstance(gen_set, (tuple, list)):
            try:
                gen_set = [
                    self._to_matrix(elt, M, gen_matrix) for elt in gen_set
                ]
            except (NameError, TypeError) as error:
                print(error)

        if gen_set == None:
            word_gen_set = list(self._create_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        elif gen_set == 'pbw' or gen_set == 'PBW':
            word_gen_set = list(self._create_pbw_rep_gen_set(n, p))
            gen_set = [
                self._to_matrix(_to_element(self, [[word, self.one()]]), M,
                                gen_matrix) for word in word_gen_set
            ]

        else:
            raise TypeError('Invalid generating set.')

        ordering = [i for i in range(2 * n - 2)]
        max_ordering = [
            len(gen_set) - (2 * n - 2) + i for i in range(2 * n - 2)
        ]
        ordering.insert(0, 0)
        max_ordering.insert(0, len(gen_set))
        rep_exists = False
        z = B.gen(B.ngens() - 1)

        while ordering[0] != max_ordering[0]:
            y = gen_set[ordering[0]].trace_of_product(
                standard_poly.subs({
                    Z('Y%s' % (j - 1)): gen_set[ordering[j]]
                    for j in range(1, 2 * n - 1)
                }))
            radB_test = relB + [B(1) - z * y]
            if B.one() not in B.ideal(radB_test):
                rep_exists = True
                break
            for i in range(2 * n - 2, -1, -1):
                if i != 0 and ordering[i] != max_ordering[i]:
                    ordering[i] += 1
                    break
                elif i == 0:
                    ordering[i] += 1
                    if ordering[i] != max_ordering[i]:
                        for j in range(1, 2 * n - 1):
                            ordering[j] = j - 1

        return rep_exists

    def is_rep(self, image, n, force=False):
        """
        Returns `True` if the map generated by mapping the generators to the matrices defined in
        `image` is an `n`-dimensional representation of `self`, and `False` otherwise. The entries
        of `image` must be `n`-by-`n` matrices with entries in the algebraic closure of the base
        field of `self`. Its length must match the number of generators of `self.`

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError('Base field must be computable. If %s is' %
                            self.base_field() +
                            '  computable then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        M = MatrixSpace(self.base_field().algebraic_closure(), n, sparse=True)
        if len(image) != self.ngens():
            raise ValueError(
                'Length of image does not match number of generators.')
        if False in {mat in M for mat in image}:
            raise TypeError('Improper image, must contain elements of %s.' %
                            M._repr_())
        image = [M(image[i]) for i in range(len(image))]

        for rel in self.rels():
            if self._to_matrix(rel, M, image) != M.zero(): return False
        return True

    def is_irred_rep(self, image, n, force=False):
        """
        Returns `True` if the map generated by mapping the generators to the matrices defined in
        `image` is an `n`-dimensional irreducible representation of `self`, and `False` otherwise.
        Like above, the entries of `image` must be `n`-by-`n` matrices with entries in the
        algebraic closure of the base field of `self`. Its length must match the number of
        generators of `self.`

        Use `force=True` if the function does not recognize the base field as computable, but the
        field is computable.
        """
        if (not force and self.base_field() not in NumberFields
                and self.base_field() not in FiniteFields):
            raise TypeError(
                'Base field must be computable. If %s is computable' %
                self.base_field() + ' then use force=True to bypass this.')

        if n not in ZZ or n < 1:
            raise ValueError('Dimension must be a positive integer.')

        if not self.is_rep(image, n): return False

        from sage.matrix.all import Matrix
        from sage.rings.number_field.number_field import is_NumberField
        import math

        M = MatrixSpace(self.base_field().algebraic_closure(), n, sparse=True)
        image = [M(image[i]).list() for i in range(len(image))]

        if n <= 6 and is_NumberField(self.base_field()): p = 2 * n
        else:
            p = int(
                math.floor(n *
                           math.sqrt(2 * n**2 / float(n - 1) + 1 / float(4)) +
                           n / float(2) - 3))

        prod_set = list(self._create_prod_set(image, n, p))
        prod_set.append(M.one())
        vector = [mat.list() for mat in prod_set]
        return 0 not in Matrix(vector).echelon_form().diagonal()

    def _create_rep_gen_set(self, n, p):
        """
        A helper function. Creates the gen_set for has_irred_rep().
        """
        import itertools
        alphabet = [_to_word(self.gen(i)) for i in range(self.ngens())]
        power_word = [alphabet[i]**n for i in range(self.ngens())]
        for i in range(1, p + 1):
            for words in itertools.product(alphabet, repeat=i):
                yield_word = True
                word = Word('')
                for w in words:
                    word = word * w
                rewrite = []
                empty_word = Word('')
                for rel in self._reduce:
                    if rel[0].is_factor(word):
                        yield_word = False
                        break
                if yield_word:
                    for pw in power_word:
                        if pw.is_factor(word):
                            yield_word = False
                            break
                if yield_word: yield word

    def _create_pbw_rep_gen_set(self, n, p):
        """
        A helper function. Creates the `gen_set` for `has_irred_rep()` when `gen_set='pbw'`.
        """
        import itertools
        alphabet = [_to_word(self.gen(i)) for i in range(self.ngens())]
        power_word = [alphabet[i]**n for i in range(self.ngens())]
        commuter = []
        for w in alphabet:
            for v in alphabet:
                if w > v: commuter.append(w * v)
        for i in range(1, p + 1):
            for words in itertools.product(alphabet, repeat=i):
                yield_word = True
                word = Word('')
                for w in words:
                    word = word * w
                for c in commuter:
                    if c.is_factor(word): yield_word = False
                for pw in power_word:
                    if pw.is_factor(word): yield_word = False
                if yield_word: yield word

    def _create_prod_set(self, image, n, p):
        """
        A helper function. Creates all products of matrices up to and including length `p`.
        """
        import itertools
        M = MatrixSpace(self.base_field().algebraic_closure(), n, sparse=True)
        for i in range(1, p + 1):
            for matrices in itertools.product(image, repeat=i):
                result = M.one()
                for m in matrices:
                    result = result * M(m)
                yield result

    def _to_matrix(self, f, M, image):
        """
        A helper function. Converts a `FinitelyPresentedAlgebraElement` into a matrix.
        """
        f_string = str(f).replace('^', '**')
        for i in range(self.ngens()):
            f_string = f_string.replace(str(self.gen(i)),
                                        '$' + str(self.gen(i)))
        for j in range(self.ngens()):
            f_string = f_string.replace('$' + str(self.gen(j)),
                                        'image[%s]' % j)
        return M(eval(f_string))