diff --git a/src/doc/en/reference/algebras/lie_algebras.rst b/src/doc/en/reference/algebras/lie_algebras.rst index 03f66f4da88..4fefbe909f5 100644 --- a/src/doc/en/reference/algebras/lie_algebras.rst +++ b/src/doc/en/reference/algebras/lie_algebras.rst @@ -7,6 +7,8 @@ Lie Algebras sage/algebras/lie_algebras/abelian sage/algebras/lie_algebras/affine_lie_algebra sage/algebras/lie_algebras/bch + sage/algebras/lie_algebras/bgg_resolution + sage/algebras/lie_algebras/bgg_dual_module sage/algebras/lie_algebras/center_uea sage/algebras/lie_algebras/classical_lie_algebra sage/algebras/lie_algebras/examples diff --git a/src/doc/en/reference/references/index.rst b/src/doc/en/reference/references/index.rst index b7725c7848b..87ce659b04f 100644 --- a/src/doc/en/reference/references/index.rst +++ b/src/doc/en/reference/references/index.rst @@ -3486,6 +3486,10 @@ REFERENCES: Trans. Am. Math. Soc. 375, No. 6, 4411-4427 (2022). :arxiv:`1906.09633`, :doi:`10.1090/tran/8606`. +.. [Humphreys08] James E. Humphreys. *Representations of Semisimple Lie + Algebras in the BGG Category* `\mathcal{O}`. + Graduate Studies in Mathematics. Amer. Math. Soc., 2008. + .. [Hutz2007] \B. Hutz. Arithmetic Dynamics on Varieties of dimension greater than one. PhD Thesis, Brown University 2007 diff --git a/src/sage/algebras/lie_algebras/bgg_dual_module.py b/src/sage/algebras/lie_algebras/bgg_dual_module.py new file mode 100644 index 00000000000..01517c4e51f --- /dev/null +++ b/src/sage/algebras/lie_algebras/bgg_dual_module.py @@ -0,0 +1,1174 @@ +r""" +BGG Category O Dual Modules + +AUTHORS: + +- Travis Scrimshaw (2024-01-07): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2024 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.cachefunc import cached_method +from sage.categories.enumerated_sets import EnumeratedSets +from sage.categories.monoids import Monoids +from sage.structure.parent import Parent +from sage.structure.indexed_generators import IndexedGenerators +from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid, IndexedMonoid +from sage.combinat.free_module import CombinatorialFreeModule +from sage.sets.family import Family +from sage.sets.finite_enumerated_set import FiniteEnumeratedSet +from sage.matrix.constructor import matrix +from sage.rings.integer_ring import ZZ +from sage.data_structures.blas_dict import iaxpy +from sage.algebras.lie_algebras.verma_module import ModulePrinting + + +class BGGDualModule(CombinatorialFreeModule): + r""" + The dual module `M^{\vee}` in the BGG Category `\mathcal{O}`. + + Let `\tau` be the transpose map of a semisimple (finite dimensional) + Lie algebra `\mathfrak{g}` over a field `R`. Let `M \in \mathcal{O}`. + The *BGG dual module* is the `R`-module `M^{\vee} := + \bigoplus_{\lambda} M_{\lambda}^*` which has a `U(\mathfrak{g})`-module + structure given by + + .. MATH:: + + x \cdot \phi(v) := \phi(\tau(x) \cdot v), + + which is also a weight module with the same grading as `M`. + + The basis we chose to work with here is the natural dual basis to the + distinguished basis `B` of `M`. That is, we define the dual function + to `b` as `\phi_b(c) = \delta_{bc}`. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1]) + sage: Mc = M.dual() + sage: B = Mc.basis() + sage: it = iter(B) + sage: elts = [next(it) for _ in range(7)]; elts + [v[2*Lambda[1]]^*, + f[-alpha[1]]*v[2*Lambda[1]]^*, + f[-alpha[1]]^2*v[2*Lambda[1]]^*, + f[-alpha[1]]^3*v[2*Lambda[1]]^*, + f[-alpha[1]]^4*v[2*Lambda[1]]^*, + f[-alpha[1]]^5*v[2*Lambda[1]]^*, + f[-alpha[1]]^6*v[2*Lambda[1]]^*] + sage: e, h, f = g.pbw_basis().algebra_generators() + sage: [f * vec for vec in elts] + [2*f[-alpha[1]]*v[2*Lambda[1]]^*, + 2*f[-alpha[1]]^2*v[2*Lambda[1]]^*, + 0, + -4*f[-alpha[1]]^4*v[2*Lambda[1]]^*, + -10*f[-alpha[1]]^5*v[2*Lambda[1]]^*, + -18*f[-alpha[1]]^6*v[2*Lambda[1]]^*, + -28*f[-alpha[1]]^7*v[2*Lambda[1]]^*] + sage: [e * vec for vec in elts] + [0, + v[2*Lambda[1]]^*, + f[-alpha[1]]*v[2*Lambda[1]]^*, + f[-alpha[1]]^2*v[2*Lambda[1]]^*, + f[-alpha[1]]^3*v[2*Lambda[1]]^*, + f[-alpha[1]]^4*v[2*Lambda[1]]^*, + f[-alpha[1]]^5*v[2*Lambda[1]]^*] + sage: [h * vec for vec in elts] + [2*v[2*Lambda[1]]^*, + 0, + -2*f[-alpha[1]]^2*v[2*Lambda[1]]^*, + -4*f[-alpha[1]]^3*v[2*Lambda[1]]^*, + -6*f[-alpha[1]]^4*v[2*Lambda[1]]^*, + -8*f[-alpha[1]]^5*v[2*Lambda[1]]^*, + -10*f[-alpha[1]]^6*v[2*Lambda[1]]^*] + + REFERENCES: + + - [Humphreys08]_ + """ + def __init__(self, module): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = g.verma_module(2*La[1] + La[2]) + sage: Mc = M.dual() + sage: TestSuite(Mc).run() + + sage: M = g.verma_module(2/3*La[1] - 3/5*La[2]) + sage: Mc = M.dual() + sage: TestSuite(Mc).run() + """ + self._module = module + self._g = module.lie_algebra() + self._pbw = self._g.pbw_basis() + base_ring = module.base_ring() + indices = module.indices() + category = module.category() + CombinatorialFreeModule.__init__(self, base_ring, indices, category=category, + **module.print_options()) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1]) + sage: M.dual() + BGG Dual of Verma module with highest weight 2*Lambda[1] of + Lie algebra of ['A', 1] in the Chevalley basis + """ + return "BGG Dual of " + repr(self._module) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1]) + sage: Mc = M.dual() + sage: latex(Mc) + { M_{2 \Lambda_{1}} }^{\vee} + """ + from sage.misc.latex import latex + return "{" + latex(self._module) + "}^{\\vee}" + + def _repr_generator(self, m): + r""" + Return a string representation of the generator indexed by ``m``. + + EXAMPLES:: + + sage: g = lie_algebras.sp(QQ, 4) + sage: La = g.cartan_type().root_system().ambient_space().fundamental_weights() + sage: Mc = g.verma_module(La[1] + 3/7*La[2]).dual() + sage: f1, f2 = g.f() + sage: x = g.pbw_basis()(g([f1, [f1, f2]])) + sage: v = x * Mc.highest_weight_vector() + sage: Mc._repr_generator(v.leading_support()) + 'f[-alpha[1]]*f[-alpha[1] - alpha[2]]*v[(10/7, 3/7)]^*' + """ + return self._module._repr_generator(m) + "^*" + + def _latex_generator(self, m): + """ + Return a latex representation of the generator indexed by ``m``. + + EXAMPLES:: + + sage: g = lie_algebras.sp(QQ, 4) + sage: La = g.cartan_type().root_system().ambient_space().fundamental_weights() + sage: Mc = g.verma_module(La[1] + 3/7*La[2]).dual() + sage: f1, f2 = g.f() + sage: x = g.pbw_basis()(g([f1, [f1, f2]])) + sage: v = x * Mc.highest_weight_vector() + sage: Mc._latex_generator(v.leading_support()) + { f_{-\alpha_{1}} f_{-\alpha_{1} - \alpha_{2}} v_{\frac{10}{7} e_{0} + \frac{3}{7} e_{1}} }^{\vee} + """ + return "{" + self._module._latex_generator(m) + "}^{\\vee}" + + _repr_term = _repr_generator + _latex_term = _latex_generator + + def degree_on_basis(self, m): + r""" + Return the degree of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['D', 5]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = g.verma_module(La[1] + La[4] - 1/3*La[5]) + sage: Mc = M.dual() + sage: elt = Mc.an_element(); elt + f[-alpha[5]]^2*f[-alpha[4]]^2*f[-alpha[3]]^3*v[Lambda[1] + Lambda[4] - 1/3*Lambda[5]]^* + + 2*f[-alpha[5]]*v[Lambda[1] + Lambda[4] - 1/3*Lambda[5]]^* + + 3*f[-alpha[4]]*v[Lambda[1] + Lambda[4] - 1/3*Lambda[5]]^* + + v[Lambda[1] + Lambda[4] - 1/3*Lambda[5]]^* + sage: [M.degree_on_basis(m) for m in elt.support()] + [Lambda[1] + 3*Lambda[2] - 2*Lambda[3] - 4/3*Lambda[5], + Lambda[1] + Lambda[4] - 1/3*Lambda[5], + Lambda[1] + Lambda[3] + Lambda[4] - 7/3*Lambda[5], + Lambda[1] + Lambda[3] - Lambda[4] - 1/3*Lambda[5]] + """ + return self._module.degree_on_basis(m) + + def highest_weight(self): + r""" + Return the highest weight of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 7]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = g.verma_module(2*La[1] + 5/3*La[4] - 3*La[6]) + sage: Mc = M.dual() + sage: Mc.highest_weight() + 2*Lambda[1] + 5/3*Lambda[4] - 3*Lambda[6] + """ + return self._module.highest_weight() + + def highest_weight_vector(self): + r""" + Return the highest weight vector of ``self`` (assuming the + defining module defines such a vector). + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1]) + sage: Mc = M.dual() + sage: Mc.highest_weight_vector() + v[2*Lambda[1]]^* + """ + hwv = self._module.highest_weight_vector() + return self.element_class(self, hwv.monomial_coefficients(copy=False)) + + def lie_algebra(self): + r""" + Return the underlying Lie algebra of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 3]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1] + La[3]) + sage: Mc = M.dual() + sage: Mc.lie_algebra() is g + True + """ + return self._g + + def dual(self): + r""" + Return the dual module of ``self``. + + In Category `\mathcal{O}`, we have `(M^{\vee})^{\vee} \cong M`, so + we return the defining module `M` of `M^{\vee}`. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['F', 4]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = g.verma_module(La[1] - 5/3*La[2] + 3*La[4]) + sage: Mc = M.dual() + sage: Mc.dual() is M + True + """ + return self._module + + @cached_method + def _lie_algebra_on_basis(self, b, m): + r""" + Return the action of the Lie algebra basis element indexed by ``b`` + on the basis element of ``self`` indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = g.verma_module(La[1]) + sage: Mc = M.dual() + sage: it = iter(Mc.basis()) + sage: list(g.basis()) + [E[alpha[2]], E[alpha[1]], E[alpha[1] + alpha[2]], E[alpha[1] + 2*alpha[2]], + h1, h2, + E[-alpha[2]], E[-alpha[1]], E[-alpha[1] - alpha[2]], E[-alpha[1] - 2*alpha[2]]] + sage: for _ in range(3): + ....: m = next(it).leading_support() + ....: print(m, [Mc._lie_algebra_on_basis(k, m) for k in g.basis().keys()]) + 1 [0, 0, 0, 0, v[Lambda[1]]^*, 0, 0, f[-alpha[1]]*v[Lambda[1]]^*, + -2*f[-alpha[2]]*f[-alpha[1]]*v[Lambda[1]]^* + 2*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^*, + -2*f[-alpha[2]]^2*f[-alpha[1]]*v[Lambda[1]]^* + + 2*f[-alpha[2]]*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^* + + f[-alpha[1] - 2*alpha[2]]*v[Lambda[1]]^*] + f[-alpha[2]] [v[Lambda[1]]^*, 0, 0, 0, 2*f[-alpha[2]]*v[Lambda[1]]^*, + -2*f[-alpha[2]]*v[Lambda[1]]^*, -2*f[-alpha[2]]^2*v[Lambda[1]]^*, + f[-alpha[2]]*f[-alpha[1]]*v[Lambda[1]]^* + f[-alpha[1] - alpha[2]]*v[Lambda[1]]^*, + -4*f[-alpha[2]]^2*f[-alpha[1]]*v[Lambda[1]]^* - f[-alpha[1] - 2*alpha[2]]*v[Lambda[1]]^*, + -6*f[-alpha[2]]^3*f[-alpha[1]]*v[Lambda[1]]^* + 2*f[-alpha[2]]^2*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^*] + f[-alpha[1]] [0, v[Lambda[1]]^*, 0, 0, -f[-alpha[1]]*v[Lambda[1]]^*, + 2*f[-alpha[1]]*v[Lambda[1]]^*, + 2*f[-alpha[2]]*f[-alpha[1]]*v[Lambda[1]]^* - 2*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^*, + 0, 0, f[-alpha[1]]*f[-alpha[1] - 2*alpha[2]]*v[Lambda[1]]^* + 2*f[-alpha[1] - alpha[2]]^2*v[Lambda[1]]^*] + """ + al = self._g.degree_on_basis(b) + wt = self.degree_on_basis(m) + if al == 0: # b is indexing part of the Cartan subalgebra + # We are assuming b is part of the coroot lattice. + # FIXME: Add something at the category level to return this. + ac = b + return self.term(m, wt.scalar(ac)) + + # TODO: Avoid calling homogeneous_component_basis() as the result is not cached + gens = self._module.homogeneous_component_basis(wt + al) + elt = self._g.basis()[b] + # TODO: Determine if we can meaningfully store these results. + # Computing gens is ~1/3 of the computation and vecs is ~2/3. + vecs = {g.leading_support(): elt.transpose() * g for g in gens} + return self.element_class(self, {k: c for k, v in vecs.items() if (c := v[m])}) + + def _pbw_monomial_on_basis(self, p, m): + r""" + Return the action of the PBW monomial indexed by ``p`` on the basis + element of ``self`` indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: PBW = g.pbw_basis() + sage: e, h, f = PBW.algebra_generators() + sage: M = g.verma_module(2*La[1]) + sage: Mc = M.dual() + sage: v = Mc.highest_weight_vector() + sage: Mc._pbw_monomial_on_basis((e*f^2).leading_support(), v.leading_support()) + 4*f[-alpha[1]]*v[2*Lambda[1]]^* + sage: B = Mc.basis() + sage: it = iter(B) + sage: elts = [next(it) for _ in range(7)]; elts + [v[2*Lambda[1]]^*, + f[-alpha[1]]*v[2*Lambda[1]]^*, + f[-alpha[1]]^2*v[2*Lambda[1]]^*, + f[-alpha[1]]^3*v[2*Lambda[1]]^*, + f[-alpha[1]]^4*v[2*Lambda[1]]^*, + f[-alpha[1]]^5*v[2*Lambda[1]]^*, + f[-alpha[1]]^6*v[2*Lambda[1]]^*] + """ + ret = self.monomial(m) + for b, exp in reversed(p._sorted_items()): + for _ in range(exp): + ret = self.linear_combination((self._lie_algebra_on_basis(b, m), mc) + for m, mc in ret._monomial_coefficients.items()) + return ret + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=False): + r""" + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: PBW = g.pbw_basis() + sage: e, h, f = PBW.algebra_generators() + sage: M = g.verma_module(2*La[1]) + sage: Mc = M.dual() + sage: v = Mc.highest_weight_vector() + sage: (h*e^2*f^2) * v + 8*v[2*Lambda[1]]^* + sage: g.casimir_element(UEA=PBW) * v + v[2*Lambda[1]]^* + sage: 5 * v + 5*v[2*Lambda[1]]^* + """ + P = self.parent() + # Check for scalars first + if scalar in P.base_ring(): + # Don't have this be a super call + return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + + # Check for Lie algebra elements + try: + scalar = P._g(scalar) + except (ValueError, TypeError): + pass + if scalar.parent() is P._g: + if self_on_left: # only implemented as a left module + return None + mc = scalar.monomial_coefficients(copy=False) + return P.linear_combination((P._lie_algebra_on_basis(b, m), bc * mc) + for b, bc in mc.items() + for m, mc in self._monomial_coefficients.items()) + + # Check for PBW elements + try: + scalar = P._pbw(scalar) + except (ValueError, TypeError): + # Cannot be made into a PBW element, so propagate it up + return CombinatorialFreeModule.Element._acted_upon_(self, + scalar, self_on_left) + + # We only implement x * self, i.e., as a left module + if self_on_left: + return None + + mc = scalar.monomial_coefficients(copy=False) + return P.linear_combination((P._pbw_monomial_on_basis(p, m), pc * mc) + for p, pc in mc.items() + for m, mc in self._monomial_coefficients.items()) + + +##################################################################### +## Simple modules + + +# This is an abuse as the monoid is not free. +# TODO: Rewrite this (or the indexed monoid class) to use explicit vectors +# since we only want to consider ordered elements. +# Note, such a rewrite would force the Lie algebra to be finite dimensional. +class SimpleModuleIndices(IndexedFreeAbelianMonoid): + r""" + The indices of the basis for a simple `U(\mathfrak{g})`-module. + + .. NOTE:: + + The current implementation assumes the Lie algebra `\mathfrak{g}` + is finite dimensional. + """ + # This is only necessary because of the IndexedMonoid.__classcall__. + @staticmethod + def __classcall__(cls, simple, prefix='f', **kwds): + r""" + Normalize input to ensure a unique representation. + + TESTS:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[3]) + sage: from sage.algebras.lie_algebras.bgg_dual_module import SimpleModuleIndices + sage: SimpleModuleIndices(L) is L._indices + True + """ + return super(IndexedMonoid, cls).__classcall__(cls, simple, prefix=prefix, **kwds) + + def __init__(self, simple, prefix, category=None, **kwds): + r""" + Initialize ``self``. + + TESTS:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: I = g.simple_module(2*La[1] + La[2]).indices() + sage: TestSuite(I).run() + + sage: I = g.simple_module(2*La[1] - 1/3*La[2]).indices() + sage: TestSuite(I).run(max_runs=150) # long time + """ + self._simple = simple + self._g = simple.lie_algebra() + self._reached_max_depth = False + # Below was mostly copied from IndexedMonoid.__init__() + self._indices = FiniteEnumeratedSet(self._g._negative_half_index_set()) + category = Monoids().or_subcategory(category) + category = category & EnumeratedSets() + category = category.FinitelyGeneratedAsMagma() + if self._simple._dom_int: + category = category.Finite() + else: + category = category.Infinite() + Parent.__init__(self, category=category) + + # ignore the optional 'key' since it only affects CachedRepresentation + kwds.pop('key', None) + sorting_key = kwds.pop('sorting_key', self._simple._pbw._monoid_key) + IndexedGenerators.__init__(self, self._indices, prefix, sorting_key=sorting_key, **kwds) + + self._sorted_supp = sorted(self._g._negative_half_index_set(), key=self._simple._pbw._basis_key, + reverse=self.print_options()['sorting_reverse']) + self._basis = {self.one(): self._simple._ambient.highest_weight_vector()} + self._lead_supp_to_index = {self._simple._ambient.highest_weight_vector().leading_support(): self.one()} + # This is used for iteration and keeps track of the current depth + self._basis_by_depth = [dict(self._basis)] + # The basis is given as a list of indices corresponding to basis vectors in self._basis + self._weight_space_bases = {self._simple.highest_weight(): [self.one()]} + + def _an_element_(self): + r""" + Return an element of ``self``. + + The only element we can quickly guarantee is in ``self`` is 1, + so we return this. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: I = g.simple_module(2*La[1] + La[2]).indices() + sage: I._an_element_() + 1 + """ + return self.one() + + def _weight_max_depth(self, mu): + r""" + Return the maximum depth of the weight ``mu``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: P = g.cartan_type().root_system().weight_lattice() + sage: La = P.fundamental_weights() + sage: al = P.simple_roots() + sage: wt = 2*La[1] + La[2] + sage: I = g.simple_module(wt).indices() + sage: I._weight_max_depth(wt) + 0 + sage: I._weight_max_depth(wt + al[2]) is None + True + sage: I._weight_max_depth(wt - 2*al[2] - 5*al[4] - 3*al[6]) + 10 + + sage: g = LieAlgebra(QQ, cartan_type=['F', 4]) + sage: P = g.cartan_type().root_system().weight_space() + sage: La = P.fundamental_weights() + sage: al = P.simple_roots() + sage: wt = 2*La[1] - 3/2*La[2] + sage: I = g.simple_module(wt).indices() + sage: I._weight_max_depth(wt) + 0 + sage: I._weight_max_depth(wt + al[2]) is None + True + sage: I._weight_max_depth(wt - 2*al[2] - 3*al[4]) + 5 + sage: I._weight_max_depth(wt - 2/3*al[1]) is None + True + """ + al = (self._simple.highest_weight() - mu)._to_root_vector() + if any(c not in ZZ or c < 0 for c in al): + return None + return sum(al) + + def weight_space_basis(self, mu): + r""" + Return the indices of the ``mu`` weight space basis elements. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: P = g.cartan_type().root_system().weight_lattice() + sage: La = P.fundamental_weights() + sage: al = P.simple_roots() + sage: wt = -3*La[1] + 3*La[2] + sage: I = g.simple_module(wt).indices() + sage: I.weight_space_basis(wt) + [1] + sage: I.weight_space_basis(wt - al[1]) + [f[-alpha[1]]] + sage: I.weight_space_basis(wt - al[2]) + [f[-alpha[2]]] + sage: I.weight_space_basis(wt - al[1] - al[2]) + [f[-alpha[1] - alpha[2]], f[-alpha[2]]*f[-alpha[1]]] + sage: I.weight_space_basis(wt - 4*al[1]) + [f[-alpha[1]]^4] + sage: I.weight_space_basis(wt - 4*al[2]) + [] + """ + if self._reached_max_depth: + return self._weight_space_bases.get(mu, []) + + max_depth = self._weight_max_depth(mu) + while max_depth >= len(self._basis_by_depth): + if self._reached_max_depth: # we've already reached everything + break + self._construct_next_level() + return self._weight_space_bases.get(mu, []) + + def __contains__(self, m): + r""" + Check if ``m`` is contained in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]) + sage: I = L.indices() + sage: I.one() in I + True + sage: it = iter(I) + sage: for _ in range(3): + ....: elt = next(it) + ....: print(elt, elt in I) + 1 True + f[-alpha[1]] True + f[-alpha[1] - alpha[2]] True + sage: gens = list(I.gens()); gens + [f[-alpha[2]], + f[-alpha[1]], + f[-alpha[1] - alpha[2]], + f[-2*alpha[1] - alpha[2]], + f[-3*alpha[1] - alpha[2]], + f[-3*alpha[1] - 2*alpha[2]]] + sage: gens[1] in I + True + sage: gens[0] * gens[1] in I + False + sage: gens[2] in I + True + sage: gens[0]^10 in I + False + sage: gens[5]^6 * gens[2]^10 in I + False + """ + if not isinstance(m, self.Element) or m.parent() is not self: + return False + depth = m.length() + while depth >= len(self._basis_by_depth): + if self._reached_max_depth: # we've already reached everything + break + self._construct_next_level() + return m in self._basis + + def __iter__(self): + r""" + Iterate over ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[2]) + sage: I = L.indices() + sage: list(I) + [1, f[-alpha[2]], f[-alpha[1] - alpha[2]], f[-alpha[1] - 2*alpha[2]]] + + sage: L = g.simple_module(La[1]-La[2]) + sage: I = L.indices() + sage: it = iter(I) + sage: [next(it) for _ in range(6)] + [1, f[-alpha[2]], f[-alpha[1]], f[-alpha[1] - alpha[2]], + f[-alpha[1] - 2*alpha[2]], f[-alpha[2]]^2] + """ + depth = 0 + while True: + while depth >= len(self._basis_by_depth): + if self._reached_max_depth: # we've already reached everything + return + self._construct_next_level() + yield from self._basis_by_depth[depth] + depth += 1 + + def _construct_next_level(self): + r""" + Construct the image for the next level of ``self``. + + ALGORITHM: + + For each image vector of `f_{\beta_1}^{b_1} \cdots f_{\beta_k}^{b_k} + v_{\lambda}` at the current depth `b_1 + \cdots b_k`, consider the + image under multiplication by every generator `f_{\alpha}` for + all `\alpha \leq \beta_1` in the fixed PBW ordering of the (dual) + Verma module. + + .. TODO:: + + Avoid unnecessary computations by using the corresponding + (combinatorial) crystal. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['C', 3]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]) + sage: I = L.indices() + sage: len(I._basis) + 1 + sage: I._construct_next_level() + sage: len(I._basis) + 6 + sage: I._reached_max_depth + False + sage: I._construct_next_level() # long time + sage: len(I._basis) # long time + 6 + sage: I._reached_max_depth # long time + True + sage: I._construct_next_level() # long time + """ + if self._reached_max_depth: # we've already reached everything + return # so nothing more to do + + gens = self._g.basis() + next_level = {} + R = self._g.base_ring() + ambient = self._simple._ambient + pbw = ambient._pbw + for m, vec in self._basis_by_depth[-1].items(): + # find the first support index + ind = len(self._sorted_supp) + for i, ls in enumerate(self._sorted_supp): + if ls in m._monomial: + ind = i + 1 + break + + for ls in self._sorted_supp[:ind]: + mp = dict(m._monomial) # make a (shallow) copy + mp[ls] = mp.get(ls, 0) + 1 + key = self.element_class(self, mp) + new_vec = gens[ls] * vec + if not new_vec: + continue + # Echelonize the corresponding weight space + mu = ambient.degree_on_basis(key) + if mu not in self._weight_space_bases: + # the only vector in the weight space + self._weight_space_bases[mu] = [key] + next_level[key] = new_vec + self._basis[key] = next_level[key] + lead_supp = next_level[key].trailing_support(key=pbw._monomial_key) + self._lead_supp_to_index[lead_supp] = key + continue + + supp = set() + wt_basis = [self._basis[k] for k in self._weight_space_bases[mu]] + wt_basis.append(new_vec) + for b in wt_basis: + supp.update(b.support()) + supp = sorted(supp, key=pbw._monomial_key) + mat = matrix(R, [[b[s] for s in supp] for b in wt_basis]) + mat.echelonize() + for i, k in enumerate(self._weight_space_bases[mu]): + data = {supp[ind]: R(c) for ind, c in mat[i].iteritems() if c} + self._basis[k] = ambient.element_class(ambient, data) + i = mat.nrows() - 1 + data = {supp[ind]: R(c) for ind, c in mat[i].iteritems() if c} + if data: + next_level[key] = ambient.element_class(ambient, data) + self._basis[key] = next_level[key] + lead_supp = next_level[key].trailing_support(key=pbw._monomial_key) + self._lead_supp_to_index[lead_supp] = key + self._weight_space_bases[mu].append(key) + + if not next_level: + self._reached_max_depth = True + return + self._basis_by_depth.append(next_level) + + @cached_method + def cardinality(self): + r""" + Return the cardinality of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]+La[4]) + sage: L._indices.cardinality() + 51975 + """ + if self._simple._dom_int: + weight = self._simple.highest_weight() + Phi = self._g.cartan_type().root_system() + P = Phi.weight_lattice() + coroots = Phi.root_lattice().simple_coroots() + la = P._from_dict({i: weight.scalar(ac) for i, ac in coroots.items()}) + from sage.combinat.crystals.monomial_crystals import CrystalOfNakajimaMonomials + return CrystalOfNakajimaMonomials(la).cardinality() + from sage.rings.infinity import infinity + return infinity + + +class SimpleModule(ModulePrinting, CombinatorialFreeModule): + r""" + Return the simple module `L_{\lambda}` as the image of the natural + morphism `\phi: M_{\lambda} \to M_{\lambda}^{\vee}`. + """ + @staticmethod + def __classcall_private__(cls, g, weight, *args, **kwds): + r""" + Normalize input to ensure a unique representation and return + the correct type. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: type(g.simple_module(La[1] + La[2])) + + sage: type(g.simple_module(La[1] - La[2])) + + sage: type(g.simple_module(La[1] + 3/2*La[2])) + + """ + if weight.is_dominant_weight(): + return FiniteDimensionalSimpleModule(g, weight, *args, **kwds) + return super().__classcall__(cls, g, weight, *args, **kwds) + + def __init__(self, g, weight, prefix='f', basis_key=None, **kwds): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: TestSuite(L).run() + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] - La[2]) + sage: TestSuite(L).run() + """ + self._g = g + self._weight = weight + self._dom_int = weight.is_dominant_weight() + self._verma = g.verma_module(weight, basis_key=basis_key) + self._ambient = self._verma.dual() + self._pbw = self._verma.pbw_basis() + base_ring = self._g.base_ring() + indices = SimpleModuleIndices(self, prefix=prefix, **kwds) + category = self._ambient.category().Subobjects() + if self._dom_int: + category = category.FiniteDimensional() + ModulePrinting.__init__(self, 'u') + CombinatorialFreeModule.__init__(self, base_ring, indices, category=category, + **self._ambient.print_options()) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: g.simple_module(2*La[1]) + Simple module with highest weight 2*Lambda[1] of + Lie algebra of ['A', 1] in the Chevalley basis + """ + return "Simple module with highest weight {} of {}".format(self._weight, self._g) + + def _latex_(self): + r""" + Return a latex representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(2*La[1]) + sage: latex(L) + L_{2 \Lambda_{1}} + """ + from sage.misc.latex import latex + return "L_{{{}}}".format(latex(self._weight)) + + def ambient(self): + r""" + Return the ambient module of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(2*La[1]) + sage: L.ambient() + BGG Dual of Verma module with highest weight 2*Lambda[1] of + Lie algebra of ['G', 2] in the Chevalley basis + """ + return self._ambient + + @lazy_attribute + def lift(self): + r""" + Return the lift map of ``self`` to the ambient dual Verma module. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]) + sage: [L.lift(b) for b in L.basis()] # long time + [v[Lambda[1]]^*, + f[-alpha[1]]*v[Lambda[1]]^*, + f[-alpha[2]]*f[-alpha[1]]*v[Lambda[1]]^* - f[-alpha[1] - alpha[2]]*v[Lambda[1]]^*, + f[-alpha[1]]*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^* + + f[-2*alpha[1] - alpha[2]]*v[Lambda[1]]^*, + f[-alpha[1]]^2*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^* + + f[-alpha[1]]*f[-2*alpha[1] - alpha[2]]*v[Lambda[1]]^* + + 1/2*f[-3*alpha[1] - alpha[2]]*v[Lambda[1]]^*, + f[-alpha[2]]*f[-alpha[1]]^2*f[-alpha[1] - alpha[2]]*v[Lambda[1]]^* + + f[-alpha[2]]*f[-alpha[1]]*f[-2*alpha[1] - alpha[2]]*v[Lambda[1]]^* + + 1/2*f[-alpha[2]]*f[-3*alpha[1] - alpha[2]]*v[Lambda[1]]^* + - f[-alpha[1] - alpha[2]]*f[-2*alpha[1] - alpha[2]]*v[Lambda[1]]^* + + 1/2*f[-3*alpha[1] - 2*alpha[2]]*v[Lambda[1]]^*, + f[-alpha[1]]*f[-alpha[1] - alpha[2]]*f[-2*alpha[1] - alpha[2]]*v[Lambda[1]]^* + - 1/2*f[-alpha[1]]*f[-3*alpha[1] - 2*alpha[2]]*v[Lambda[1]]^* + - 1/2*f[-alpha[1] - alpha[2]]*f[-3*alpha[1] - alpha[2]]*v[Lambda[1]]^* + + f[-2*alpha[1] - alpha[2]]^2*v[Lambda[1]]^*] + """ + return self.module_morphism(self._lift_on_basis, codomain=self._ambient, unitriangular="upper") + + def retract(self, x): + r""" + Return the retraction of ``x`` in ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(2*La[1]) + sage: L.retract(L.lift(sum(L.basis()))) + f[-alpha[1]]^2*u[2*Lambda[1]] + f[-alpha[1]]*f[-alpha[1] - alpha[2]]*u[2*Lambda[1]] + + f[-alpha[1] - alpha[2]]^2*u[2*Lambda[1]] + f[-alpha[1]]*u[2*Lambda[1]] + + f[-alpha[1] - alpha[2]]*u[2*Lambda[1]] + u[2*Lambda[1]] + sage: B = list(L.basis()) + sage: L.retract(3/2*L.lift(B[0]) - L.lift(B[2]) - 10/3*L.lift(B[3])) + -10/3*f[-alpha[1]]^2*u[2*Lambda[1]] + - f[-alpha[1] - alpha[2]]*u[2*Lambda[1]] + + 3/2*u[2*Lambda[1]] + """ + supp = sorted(x.support(), key=self._pbw._monomial_key) + data = x.monomial_coefficients(copy=True) # this is destructive to data + R = self.base_ring() + ret = {} + for ls in supp: + if ls not in data: + continue + if ls not in self._indices._lead_supp_to_index: + mu = self._ambient.degree_on_basis(ls) + # this will guarantee the computation is correct + self._indices.weight_space_basis(mu) + if ls not in self._indices._lead_supp_to_index: + raise ValueError(f"not an element of the simple module of weight {self._weight}") + key = self._indices._lead_supp_to_index[ls] + vec = self._indices._basis[key] + coeff = R(data[ls] / vec[ls]) + iaxpy(-coeff, vec._monomial_coefficients, data) + ret[key] = coeff + return self.element_class(self, ret) + + def _lift_on_basis(self, m): + r""" + Return the lift of the basis element indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(2*La[1]) + sage: I = L.indices() + sage: gen = list(I.gens())[0] + sage: L._lift_on_basis(gen^2) + 4*f[-alpha[1]]^2*v[2*Lambda[1]]^* + sage: L._lift_on_basis(gen^3) + Traceback (most recent call last): + ... + ValueError: f[-alpha[1]]^3 does not index a basis element + """ + # This builds the result up to the necessary depth + if m not in self._indices: + raise ValueError(f"{m} does not index a basis element") + return self._indices._basis[m] + + def dual(self): + r""" + Return the dual module of ``self``, which is ``self`` since simple + modules are self-dual. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 4]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(2*La[1] + 3*La[4]) + sage: L.dual() is L + True + """ + return self + + def highest_weight(self): + r""" + Return the highest weight of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.so(QQ, 7) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: L.highest_weight() + Lambda[1] + Lambda[2] + """ + return self._weight + + @cached_method + def highest_weight_vector(self): + r""" + Return the highest weight vector of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.sp(QQ, 6) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: L.highest_weight_vector() + u[Lambda[1] + Lambda[2]] + """ + one = self.base_ring().one() + return self._from_dict({self._indices.one(): one}, + remove_zeros=False, coerce=False) + + def lie_algebra(self): + r""" + Return the underlying Lie algebra of ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.so(QQ, 9) + sage: La = g.cartan_type().root_system().weight_space().fundamental_weights() + sage: L = g.simple_module(La[3] - 1/2*La[1]) + sage: L.lie_algebra() + Lie algebra of ['B', 4] in the Chevalley basis + """ + return self._g + + def pbw_basis(self): + r""" + Return the PBW basis of the underlying Lie algebra + used to define ``self``. + + EXAMPLES:: + + sage: g = lie_algebras.so(QQ, 8) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[2] - 2*La[3]) + sage: L.pbw_basis() + Universal enveloping algebra of Lie algebra of ['D', 4] in the Chevalley basis + in the Poincare-Birkhoff-Witt basis + """ + return self._pbw + + def homogeneous_component_basis(self, mu): + r""" + Return a basis for the ``mu`` weight space of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: P = g.cartan_type().root_system().weight_lattice() + sage: La = P.fundamental_weights() + sage: la = La[1] + La[2] + sage: L = g.simple_module(la) + sage: from itertools import product + sage: al = P.simple_roots() + sage: for wts in product(range(4), repeat=2): + ....: mu = la - wts[0] * al[1] - wts[1] * al[2] + ....: print(mu) + ....: print(L.homogeneous_component_basis(mu)) + Lambda[1] + Lambda[2] + Family (u[Lambda[1] + Lambda[2]],) + 2*Lambda[1] - Lambda[2] + Family (f[-alpha[2]]*u[Lambda[1] + Lambda[2]],) + 3*Lambda[1] - 3*Lambda[2] + Family () + 4*Lambda[1] - 5*Lambda[2] + Family () + -Lambda[1] + 2*Lambda[2] + Family (f[-alpha[1]]*u[Lambda[1] + Lambda[2]],) + 0 + Family (f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]], f[-alpha[2]]*f[-alpha[1]]*u[Lambda[1] + Lambda[2]]) + Lambda[1] - 2*Lambda[2] + Family (f[-alpha[2]]*f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]],) + 2*Lambda[1] - 4*Lambda[2] + Family () + -3*Lambda[1] + 3*Lambda[2] + Family () + -2*Lambda[1] + Lambda[2] + Family (f[-alpha[1]]*f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]],) + -Lambda[1] - Lambda[2] + Family (f[-alpha[1] - alpha[2]]^2*u[Lambda[1] + Lambda[2]],) + -3*Lambda[2] + Family () + -5*Lambda[1] + 4*Lambda[2] + Family () + -4*Lambda[1] + 2*Lambda[2] + Family () + -3*Lambda[1] + Family () + -2*Lambda[1] - 2*Lambda[2] + Family () + """ + return Family([self.monomial(b) for b in self._indices.weight_space_basis(mu)]) + + weight_space_basis = homogeneous_component_basis + + class Element(CombinatorialFreeModule.Element): + def _acted_upon_(self, scalar, self_on_left=True): + r""" + Return the action of ``scalar`` on ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: v = L.highest_weight_vector(); v + u[Lambda[1] + Lambda[2]] + sage: f1, f2 = g.pbw_basis().f() + sage: 5 * v + 5*u[Lambda[1] + Lambda[2]] + sage: f1 * f2 * v + f[-alpha[2]]*f[-alpha[1]]*u[Lambda[1] + Lambda[2]] + + f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]] + sage: f2 * f1 * v + -f[-alpha[2]]*f[-alpha[1]]*u[Lambda[1] + Lambda[2]] + + 2*f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]] + sage: f2 * f2 * f1 * v + -2*f[-alpha[2]]*f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]] + sage: f1 * f2 * f1 * v + f[-alpha[1]]*f[-alpha[1] - alpha[2]]*u[Lambda[1] + Lambda[2]] + sage: f2 * f1 * f2 * f1 * v + f[-alpha[1] - alpha[2]]^2*u[Lambda[1] + Lambda[2]] + sage: f1 * f2 * f2 * f1 * v + 2*f[-alpha[1] - alpha[2]]^2*u[Lambda[1] + Lambda[2]] + """ + # check for scalars first + ret = CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) + if ret is not None: + return ret + + if self_on_left: # this is a left module action + return None + + P = self.parent() + return P.retract(scalar * P.lift(self)) + + _lmul_ = _acted_upon_ + _rmul_ = _acted_upon_ + +class FiniteDimensionalSimpleModule(SimpleModule): + """ + A finite dimensional simple module. + """ + def bgg_resolution(self): + """ + Return the BGG resolution of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: L.bgg_resolution() + BGG resolution of Simple module with highest weight Lambda[1] + Lambda[2] + of Lie algebra of ['A', 2] in the Chevalley basis + """ + from sage.algebras.lie_algebras.bgg_resolution import BGGResolution + return BGGResolution(self) diff --git a/src/sage/algebras/lie_algebras/bgg_resolution.py b/src/sage/algebras/lie_algebras/bgg_resolution.py new file mode 100644 index 00000000000..89ccb3d2079 --- /dev/null +++ b/src/sage/algebras/lie_algebras/bgg_resolution.py @@ -0,0 +1,229 @@ +r""" +BGG Resolutions + +AUTHORS: + +- Travis Scrimshaw (2024-01-07): Initial version +""" + +#***************************************************************************** +# Copyright (C) 2024 Travis Scrimshaw +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from sage.misc.cachefunc import cached_function +from sage.structure.unique_representation import UniqueRepresentation +from sage.matrix.constructor import matrix +from sage.rings.integer_ring import ZZ +from sage.homology.chain_complex import ChainComplex_class + +class BGGResolution(UniqueRepresentation, ChainComplex_class): + r""" + The BGG resolution of a simple module. + + We realize the BGG resolution as a chain complex, where the `(-1)`-th + factor corresponds to the finite dimensional simple module `L_{\lambda}` + and the `i`-th factor (`i \geq 0`) corresponds to + + .. MATH:: + + M_i := \bigoplus_{\substack{w \in W \\ \ell(w) = i}} M_{w\lambda}. + + Since the morphisms can be defined in terms of the image of the + highest weight vectors, we only encode this information as a + (finite) chain complex. We do not include the final natural projection + map `p: M_{\lambda} \to L_{\lambda}` since the highest weight vector of + weight `\lambda` only occurs in `M_{\lambda}` and `L_{\lambda}`. + + INPUT: + + - ``L`` -- a simple module + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + 4*La[2]) + sage: res = L.bgg_resolution() + sage: ascii_art(res) + [ 1 -1] [1] + [1 1] [-1 1] [1] + 0 <-- C_0 <------ C_1 <-------- C_2 <---- C_3 <-- 0 + + sage: g = LieAlgebra(QQ, cartan_type=['D', 4]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2] + 3*La[3]) + sage: res = L.bgg_resolution() + sage: w0 = WeylGroup(g.cartan_type(), prefix='s').long_element() + sage: all(res.differential(i) * res.differential(i+1) == 0 + ....: for i in range(w0.length())) + True + """ + def __init__(self, L): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: res = L.bgg_resolution() + sage: TestSuite(res).run() + """ + from sage.combinat.root_system.weyl_group import WeylGroup + ct = L.lie_algebra().cartan_type() + self._cartan_type = ct + self._simple = L + self._W = WeylGroup(ct, prefix='s') + # finish intialization + R = self._simple.base_ring() + differentials, mod_order = build_differentials(self._W) + differentials = {deg: mat.change_ring(R) for deg, mat in differentials.items()} + for deg in differentials: + differentials[deg].set_immutable() + self._module_order = mod_order + super().__init__(ZZ, -ZZ.one(), R, differentials) + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]) + sage: L.bgg_resolution() + BGG resolution of Simple module with highest weight Lambda[1] + of Lie algebra of ['B', 2] in the Chevalley basis + """ + return "BGG resolution of " + repr(self._simple) + + def simple_module(self): + r""" + Return the simple module `L_{\lambda}` defining ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['C', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: res = L.bgg_resolution() + sage: res.simple_module() is L + True + """ + return self._simple + + def module_order(self, i): + r""" + Return a tuple of Weyl group elements of length ``i`` + determining the ordering of the direct sum defining the + differential matrix. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: L = g.simple_module(La[1]) + sage: res = L.bgg_resolution() + sage: [res.module_order(i) for i in range(7)] + [[1], + [s2, s1], + [s2*s1, s1*s2], + [s1*s2*s1, s2*s1*s2], + [s1*s2*s1*s2, s2*s1*s2*s1], + [s1*s2*s1*s2*s1, s2*s1*s2*s1*s2], + [s2*s1*s2*s1*s2*s1]] + """ + if i not in self._module_order: + return [] + return self._module_order[i] + + +@cached_function +def build_differentials(W): + r""" + Construct the differentials for the BGG resolution corresponding + to the Weyl group `W`. + + ALGORITHM: + + We use the fact that (parabolic) Bruhat order is built locally + from squares, all values defining the differential are `+1` or `-1`, + and the product over the two different paths must sum to `0`. + This is outlined in Ch. 6 of [Humphreys08]_. + + This only depends on the Coxeter group `W`. There is no stabilizer + for any dominant integral weight `\lambda` undert the dot action + (i.e., the stabilizer of `\lambda + \rho` is empty). + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.bgg_resolution import build_differentials + sage: W = WeylGroup(['B', 2], prefix='s') + sage: D, O = build_differentials(W) + sage: D + {0: [], + 1: [-1 1], + 2: [1 1] + [1 1], + 3: [ 1 -1] + [-1 1], + 4: [1] + [1], + 5: []} + sage: O + {0: [1], + 1: [s2, s1], + 2: [s2*s1, s1*s2], + 3: [s2*s1*s2, s1*s2*s1], + 4: [s2*s1*s2*s1]} + """ + from itertools import combinations + w0 = W.long_element() + maxlen = w0.length() + module_order = {i: [] for i in range(maxlen+1)} + for w in W: + module_order[w.length()].append(w) + + one = ZZ.one() + # Set the initial step + prev = {w: (j, frozenset([0])) for j, w in enumerate(module_order[maxlen-1])} + prev_mat = matrix(ZZ, [[one]]*len(module_order[maxlen-1]), immutable=True) + differentials = {maxlen: prev_mat} + for i in range(maxlen-2, -1, -1): + mat = matrix.zero(ZZ, len(module_order[i]), len(prev)) + cur = {} + for j, w in enumerate(module_order[i]): + # build the data to find the squares + covers = frozenset([prev[v][0] for v in w.bruhat_upper_covers()]) + cur[w] = (j, covers) + # set the indices in each square + for v, vp in combinations(w.bruhat_upper_covers(), 2): + # get the (unique) value at the top of the square + dv = prev[v] + dvp = prev[vp] + vind = dv[0] + vpind = dvp[0] + for uind in dv[1] & dvp[1]: + # set the entires corresponding to the square + if not mat[j, vind]: + if not mat[j, vpind]: + mat[j, vpind] = one + mat[j, vind] = -mat[j, vpind] * prev_mat[vpind, uind] * prev_mat[vind, uind] + elif not mat[j, vpind]: + mat[j, vpind] = -mat[j, vind] * prev_mat[vpind, uind] * prev_mat[vind, uind] + else: + assert mat[j, vpind] * prev_mat[vpind, uind] + mat[j, vind] * prev_mat[vind, uind] == 0 + differentials[i+1] = mat + prev = cur + prev_mat = mat + differentials[0] = matrix.zero(ZZ, 0, 1) + differentials[maxlen+1] = matrix.zero(ZZ, 1, 0) + return differentials, module_order diff --git a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py index 3b59303d0dc..813a5a45293 100644 --- a/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py +++ b/src/sage/algebras/lie_algebras/poincare_birkhoff_witt.py @@ -18,9 +18,11 @@ #***************************************************************************** from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.structure.element import get_coercion_model from operator import mul from sage.categories.algebras import Algebras +from sage.categories.triangular_kac_moody_algebras import TriangularKacMoodyAlgebras from sage.monoids.indexed_free_monoid import IndexedFreeAbelianMonoid from sage.combinat.free_module import CombinatorialFreeModule from sage.sets.family import Family @@ -61,12 +63,12 @@ class PoincareBirkhoffWittBasis(CombinatorialFreeModule): We then do some computations; in particular, we check that `[E, F] = H`:: - sage: E,F,H = PBW.algebra_generators() - sage: E*F + sage: E, F, H = PBW.algebra_generators() + sage: E * F PBW['E']*PBW['F'] - sage: F*E + sage: F * E PBW['E']*PBW['F'] - PBW['H'] - sage: E*F - F*E + sage: E * F - F * E PBW['H'] Next we construct another instance of the PBW basis, but sorted in the @@ -108,7 +110,7 @@ class PoincareBirkhoffWittBasis(CombinatorialFreeModule): """ @staticmethod def __classcall_private__(cls, g, basis_key=None, prefix='PBW', **kwds): - """ + r""" Normalize input to ensure a unique representation. TESTS:: @@ -120,25 +122,30 @@ def __classcall_private__(cls, g, basis_key=None, prefix='PBW', **kwds): sage: P1 is P2 True """ - return super().__classcall__(cls, - g, basis_key, prefix, **kwds) + if g in TriangularKacMoodyAlgebras.FiniteDimensional: + return PoincareBirkhoffWittBasisSemisimpleLieAlgebra(g, basis_key, prefix, **kwds) + return super().__classcall__(cls, g, basis_key, prefix, **kwds) def __init__(self, g, basis_key, prefix, **kwds): - """ + r""" Initialize ``self``. TESTS:: - sage: L = lie_algebras.sl(QQ, 2) - sage: PBW = L.pbw_basis() - sage: E,F,H = PBW.algebra_generators() - sage: TestSuite(PBW).run(elements=[E, F, H]) - sage: TestSuite(PBW).run(elements=[E, F, H, E*F + H]) # long time + sage: L = lie_algebras.VirasoroAlgebra(QQ) + sage: U = L.pbw_basis() + sage: d = U.algebra_generators() + sage: TestSuite(U).run() + sage: elts = [d[1], d[-1], d[2], d[-2]*d[1], d[-1]*d[1], d[3]^3*d[5], d['c']] + sage: TestSuite(U).run(elements=elts) # long time """ if basis_key is not None: self._basis_key = basis_key else: - self._basis_key_inverse = None + try: + self._basis_key = g._basis_key + except AttributeError: + self._basis_key_inverse = None R = g.base_ring() self._g = g @@ -630,3 +637,178 @@ def _act_on_(self, x, self_on_left): ret += term return ret return None + + +class PoincareBirkhoffWittBasisSemisimpleLieAlgebra(PoincareBirkhoffWittBasis): + r""" + The Poincare-Birkhoff-Witt basis of a finite dimensional triangular + Kac-Moody Lie algebra (i.e., a semisimple Lie algebra). + """ + def __init__(self, g, basis_key=None, *args, **kwds): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: U = lie_algebras.so(QQ, 5).pbw_basis() + sage: TestSuite(U).run() + + sage: L = lie_algebras.sl(QQ, 2) + sage: PBW = L.pbw_basis() + sage: E, F, H = PBW.algebra_generators() + sage: TestSuite(PBW).run(elements=[E, F, H]) + sage: TestSuite(PBW).run(elements=[E, F, H, E*F + H]) # long time + """ + super().__init__(g, basis_key, *args, **kwds) + if self._basis_key == self._g._triangular_key: + self._triangular_pbw = self + else: + self._triangular_pbw = self._g.pbw_basis(basis_key=self._g._triangular_key) + + def e(self, i=None): + r""" + Return the generators `e` of ``self``. + + INPUT: + + - ``i`` -- (optional) if specified, return just the + generator `e_i` + + EXAMPLES:: + + sage: U = lie_algebras.so(QQ, 5).pbw_basis() + sage: U.e() + Finite family {1: PBW[alpha[1]], 2: PBW[alpha[2]]} + sage: U.e(1) + PBW[alpha[1]] + """ + if i is None: + return Family({i: self.e(i) for i in self._g.cartan_type().index_set()}) + return self(self._g.e(i)) + + def f(self, i=None): + r""" + Return the generators `f` of ``self``. + + INPUT: + + - ``i`` -- (optional) if specified, return just the + generator `f_i` + + EXAMPLES:: + + sage: U = lie_algebras.so(QQ, 5).pbw_basis() + sage: U.f() + Finite family {1: PBW[-alpha[1]], 2: PBW[-alpha[2]]} + sage: U.f(1) + PBW[-alpha[1]] + """ + if i is None: + return Family({i: self.f(i) for i in self._g.cartan_type().index_set()}) + return self(self._g.f(i)) + + def contravariant_form(self, x, y): + r""" + Return the (universal) contravariant form of ``x`` and ``y``. + + Let `\varphi \colon U(\mathfrak{g}) \to U(\mathfrak{h})` denote + the projection onto the Cartan subalgebra and `\tau` be the transpose + map. The *(universal) contravariant form* is defined as + + .. MATH:: + + (x, y) = \varphi(\tau(x) y). + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: U = g.pbw_basis() + sage: f1, f2 = U.f() + sage: e1, e2 = U.e() + sage: U.contravariant_form(U.one(), U.one()) + 1 + sage: U.contravariant_form(f1, f1) + PBW[alphacheck[1]] + sage: U.contravariant_form(f2, f2) + PBW[alphacheck[2]] + sage: U.contravariant_form(f1*f2, f1*f2) + PBW[alphacheck[1]]*PBW[alphacheck[2]] + 3*PBW[alphacheck[2]] + sage: U.contravariant_form(e1*e1*e2, e2*e1*e2) + 0 + sage: cas = U.casimir_element() + sage: ccc = U.contravariant_form(cas, cas); ccc + 1/144*PBW[alphacheck[1]]^4 + 1/24*PBW[alphacheck[1]]^3*PBW[alphacheck[2]] + + 5/48*PBW[alphacheck[1]]^2*PBW[alphacheck[2]]^2 + + 1/8*PBW[alphacheck[1]]*PBW[alphacheck[2]]^3 + 1/16*PBW[alphacheck[2]]^4 + + 5/72*PBW[alphacheck[1]]^3 + 1/3*PBW[alphacheck[1]]^2*PBW[alphacheck[2]] + + 7/12*PBW[alphacheck[1]]*PBW[alphacheck[2]]^2 + 3/8*PBW[alphacheck[2]]^3 + + 25/144*PBW[alphacheck[1]]^2 + 5/8*PBW[alphacheck[1]]*PBW[alphacheck[2]] + + 9/16*PBW[alphacheck[2]]^2 + sage: ccc.parent() is U + True + """ + x = self._triangular_pbw(x) + y = self._triangular_pbw(y) + temp = (x.transpose() * y)._monomial_coefficients + part = self._g._part_on_basis + ret = {mon: temp[mon] for mon in temp if all(part(b) == 0 for b in mon.support())} + # TODO: Construct this direct in ``self`` + return self(self._triangular_pbw.element_class(self._triangular_pbw, ret)) + + @cached_method + def _transpose_on_basis(self, m): + """ + Return the transpose map applied to the basis element indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['E', 6]) + sage: U = g.pbw_basis() + sage: f1, f2, f3, f4, f5, f6 = U.f() + sage: e1, e2, e3, e4, e5, e6 = U.e() + sage: elt = e1 * e4^2 * f1 * f2^3 + sage: U._transpose_on_basis(elt.support()[0]) + PBW[alpha[2]]^3*PBW[alpha[1]]*PBW[-alpha[4]]^2*PBW[-alpha[1]] + """ + I = self._indices + basis_mapping = self._g._transpose_basis_mapping + return self.prod(self.monomial(I({basis_mapping[k]: e})) + for k, e in reversed(m._sorted_items())) + + @lazy_attribute + def transpose(self): + r""" + The transpose map. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['F', 4]) + sage: U = g.pbw_basis() + sage: U.transpose + Generic endomorphism of Universal enveloping algebra of Lie algebra + of ['F', 4] in the Chevalley basis in the Poincare-Birkhoff-Witt basis + """ + return self.module_morphism(self._transpose_on_basis, codomain=self) + + class Element(PoincareBirkhoffWittBasis.Element): + def transpose(self): + r""" + Return the transpose map of ``self``. + + This is the tranpose map on the Lie algebra extended + as an anti-involution of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['D', 4]) + sage: U = g.pbw_basis() + sage: e = U.e() + sage: f = U.f() + sage: elts = [e[1], e[1]*e[2], e[3]+e[4], e[1]*e[3]*e[4] + e[2], + ....: f[1], f[1]*f[2], f[3]+f[4], e[1]*e[3]*e[4] + e[2], + ....: e[1]*f[1], f[1]*e[1], (e[2]*f[2] - f[2]*e[2])^2] + sage: all((b*bp).transpose() == bp.transpose() * b.transpose() + ....: for b in elts for bp in elts) + True + """ + return self.parent().transpose(self) diff --git a/src/sage/algebras/lie_algebras/verma_module.py b/src/sage/algebras/lie_algebras/verma_module.py index 7970e30ef8d..71f2ff58a85 100644 --- a/src/sage/algebras/lie_algebras/verma_module.py +++ b/src/sage/algebras/lie_algebras/verma_module.py @@ -35,7 +35,80 @@ from sage.rings.rational_field import QQ -class VermaModule(CombinatorialFreeModule): +class ModulePrinting: + """ + Helper mixin class for printing the module vectors. + """ + def __init__(self, vector_name='v'): + r""" + Initialize ``self``. + + EXAMPLES:: + + sage: from sage.algebras.lie_algebras.verma_module import ModulePrinting + sage: MP = ModulePrinting() + sage: TestSuite(MP).run(skip="_test_pickling") + """ + self.__vector_name = vector_name + + def _repr_generator(self, m): + r""" + Return a string representation of the generator indexed by ``m``. + + EXAMPLES:: + + sage: L = lie_algebras.sp(QQ, 4) + sage: La = L.cartan_type().root_system().ambient_space().fundamental_weights() + sage: M = L.verma_module(-1/2*La[1] + 3/7*La[2]) + sage: f1, f2 = L.f() + sage: x = M.pbw_basis()(L([f1, [f1, f2]])) + sage: v = x * M.highest_weight_vector() + sage: M._repr_generator(v.leading_support()) + 'f[-2*alpha[1] - alpha[2]]*v[(-1/14, 3/7)]' + + sage: M.highest_weight_vector() + v[(-1/14, 3/7)] + sage: 2 * M.highest_weight_vector() + 2*v[(-1/14, 3/7)] + """ + ret = super()._repr_generator(m) + if ret == '1': + ret = '' + else: + ret += '*' + return ret + self.__vector_name + "[{}]".format(self._weight) + + def _latex_generator(self, m): + r""" + Return a latex representation of the generator indexed by ``m``. + + EXAMPLES:: + + sage: L = lie_algebras.sp(QQ, 4) + sage: La = L.cartan_type().root_system().ambient_space().fundamental_weights() + sage: M = L.verma_module(-1/2*La[1] + 3/7*La[2]) + sage: f1, f2 = L.f() + sage: x = M.pbw_basis()(L([f1, [f1, f2]])) + sage: v = x * M.highest_weight_vector() + sage: M._latex_generator(v.leading_support()) + f_{-2 \alpha_{1} - \alpha_{2}} v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} + + sage: latex(2 * M.highest_weight_vector()) + 2 v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} + sage: latex(M.highest_weight_vector()) + v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} + """ + ret = super()._latex_generator(m) + if ret == '1': + ret = '' + from sage.misc.latex import latex + return ret + " {}_{{{}}}".format(self.__vector_name, latex(self._weight)) + + _repr_term = _repr_generator + _latex_term = _latex_generator + + +class VermaModule(ModulePrinting, CombinatorialFreeModule): r""" A Verma module. @@ -50,7 +123,7 @@ class VermaModule(CombinatorialFreeModule): where `F_{\lambda}` is the `U(\mathfrak{b})` module such that `h \in U(\mathfrak{h})` acts as multiplication by - `\langle \lambda, h \rangle` and `U\mathfrak{g}^+) F_{\lambda} = 0`. + `\langle \lambda, h \rangle` and `U(\mathfrak{g}^+) F_{\lambda} = 0`. INPUT: @@ -118,9 +191,10 @@ def __init__(self, g, weight, basis_key=None, prefix='f', **kwds): prefix='', bracket=False, latex_bracket=False, sorting_key=self._monomial_key, category=Modules(R).WithBasis().Graded()) + ModulePrinting.__init__(self) def _triangular_key(self, x): - """ + r""" Return a key for sorting for the index ``x`` that respects the triangular decomposition by `U^-, U^0, U^+`. @@ -233,64 +307,8 @@ def _latex_(self): from sage.misc.latex import latex return "M_{{{}}}".format(latex(self._weight)) - def _repr_generator(self, m): - r""" - Return a string representation of the generator indexed by ``m``. - - EXAMPLES:: - - sage: L = lie_algebras.sp(QQ, 4) - sage: La = L.cartan_type().root_system().ambient_space().fundamental_weights() - sage: M = L.verma_module(-1/2*La[1] + 3/7*La[2]) - sage: f1, f2 = L.f(1), L.f(2) - sage: x = M.pbw_basis()(L([f1, [f1, f2]])) - sage: v = x * M.highest_weight_vector() - sage: M._repr_generator(v.leading_support()) - 'f[-2*alpha[1] - alpha[2]]*v[(-1/14, 3/7)]' - - sage: M.highest_weight_vector() - v[(-1/14, 3/7)] - sage: 2 * M.highest_weight_vector() - 2*v[(-1/14, 3/7)] - """ - ret = super()._repr_generator(m) - if ret == '1': - ret = '' - else: - ret += '*' - return ret + "v[{}]".format(self._weight) - - def _latex_generator(self, m): - r""" - Return a latex representation of the generator indexed by ``m``. - - EXAMPLES:: - - sage: L = lie_algebras.sp(QQ, 4) - sage: La = L.cartan_type().root_system().ambient_space().fundamental_weights() - sage: M = L.verma_module(-1/2*La[1] + 3/7*La[2]) - sage: f1, f2 = L.f(1), L.f(2) - sage: x = M.pbw_basis()(L([f1, [f1, f2]])) - sage: v = x * M.highest_weight_vector() - sage: M._latex_generator(v.leading_support()) - f_{-2 \alpha_{1} - \alpha_{2}} v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} - - sage: latex(2 * M.highest_weight_vector()) - 2 v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} - sage: latex(M.highest_weight_vector()) - v_{-\frac{1}{14} e_{0} + \frac{3}{7} e_{1}} - """ - ret = super()._latex_generator(m) - if ret == '1': - ret = '' - from sage.misc.latex import latex - return ret + " v_{{{}}}".format(latex(self._weight)) - - _repr_term = _repr_generator - _latex_term = _latex_generator - def lie_algebra(self): - """ + r""" Return the underlying Lie algebra of ``self``. EXAMPLES:: @@ -304,7 +322,7 @@ def lie_algebra(self): return self._g def pbw_basis(self): - """ + r""" Return the PBW basis of the underlying Lie algebra used to define ``self``. @@ -366,6 +384,26 @@ def highest_weight(self): """ return self._weight + def dual(self): + r""" + Return the dual module `M(\lambda)^{\vee}` in Category `\mathcal{O}`. + + EXAMPLES:: + + sage: L = lie_algebras.sl(QQ, 2) + sage: La = L.cartan_type().root_system().weight_space().fundamental_weights() + sage: M = L.verma_module(2*La[1]) + sage: Mc = M.dual() + + sage: Mp = L.verma_module(-2*La[1]) + sage: Mp.dual() is Mp + True + """ + if self.is_simple(): + return self + from sage.algebras.lie_algebras.bgg_dual_module import BGGDualModule + return BGGDualModule(self) + def degree_on_basis(self, m): r""" Return the degree (or weight) of the basis element indexed by ``m``. @@ -451,6 +489,47 @@ def _element_constructor_(self, x): return self.highest_weight_vector()._acted_upon_(x, False) return super()._element_constructor_(self, x) + def contravariant_form(self, x, y): + r""" + Return the contravariant form of ``x`` and ``y``. + + Let `C(x, y)` denote the (universal) contravariant form on + `U(\mathfrak{g})`. Then the contravariant form on `M(\lambda)` is + given by evaluating `C(x, y) \in U(\mathfrak{h})` at `\lambda`. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 1]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(2*La[1]) + sage: U = M.pbw_basis() + sage: v = M.highest_weight_vector() + sage: e, h, f = U.algebra_generators() + sage: elts = [f^k * v for k in range(8)]; elts + [v[2*Lambda[1]], f[-alpha[1]]*v[2*Lambda[1]], + f[-alpha[1]]^2*v[2*Lambda[1]], f[-alpha[1]]^3*v[2*Lambda[1]], + f[-alpha[1]]^4*v[2*Lambda[1]], f[-alpha[1]]^5*v[2*Lambda[1]], + f[-alpha[1]]^6*v[2*Lambda[1]], f[-alpha[1]]^7*v[2*Lambda[1]]] + sage: matrix([[M.contravariant_form(x, y) for x in elts] for y in elts]) + [1 0 0 0 0 0 0 0] + [0 2 0 0 0 0 0 0] + [0 0 4 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + [0 0 0 0 0 0 0 0] + """ + pbw = self._pbw + I = pbw._indices + xlift = pbw.element_class(pbw, {I(m._monomial): c for m, c in x._monomial_coefficients.items()}) + ylift = pbw.element_class(pbw, {I(m._monomial): c for m, c in y._monomial_coefficients.items()}) + univ = pbw.contravariant_form(xlift, ylift) + la = self._weight + R = self.base_ring() + return R.sum(c * R.prod(la.scalar(k) ** e for k, e in m._monomial.items()) + for m, c in univ._monomial_coefficients.items()) + @lazy_attribute def _dominant_data(self): r""" @@ -505,6 +584,66 @@ def is_singular(self): """ return not self._dominant_data[0].is_dominant() + def is_simple(self): + r""" + Return if ``self`` is a simple module. + + A Verma module `M_{\lambda}` is simple if and only if `\lambda` + is *Verma antidominant* in the sense + + .. MATH:: + + \langle \lambda + \rho, \alpha^{\vee} \rangle \notin \ZZ_{>0} + + for all positive roots `\alpha`. + + EXAMPLES:: + + sage: L = lie_algebras.sl(QQ, 3) + sage: La = L.cartan_type().root_system().weight_space().fundamental_weights() + sage: L.verma_module(La[1] + La[2]).is_simple() + False + sage: L.verma_module(-La[1] - La[2]).is_simple() + True + sage: L.verma_module(3/2*La[1] + 1/2*La[2]).is_simple() + False + sage: L.verma_module(3/2*La[1] + 1/3*La[2]).is_simple() + True + sage: L.verma_module(-3*La[1] + 2/3*La[2]).is_simple() + True + """ + return self._weight.is_verma_dominant(positive=False) + + def is_projective(self): + r""" + Return if ``self`` is a projective module in Category `\mathcal{O}`. + + A Verma module `M_{\lambda}` is projective (in Category `\mathcal{O}` + if and only if `\lambda` is *Verma dominant* in the sense + + .. MATH:: + + \langle \lambda + \rho, \alpha^{\vee} \rangle \notin \ZZ_{<0} + + for all positive roots `\alpha`. + + EXAMPLES:: + + sage: L = lie_algebras.sl(QQ, 3) + sage: La = L.cartan_type().root_system().weight_space().fundamental_weights() + sage: L.verma_module(La[1] + La[2]).is_projective() + True + sage: L.verma_module(-La[1] - La[2]).is_projective() + True + sage: L.verma_module(3/2*La[1] + 1/2*La[2]).is_projective() + True + sage: L.verma_module(3/2*La[1] + 1/3*La[2]).is_projective() + True + sage: L.verma_module(-3*La[1] + 2/3*La[2]).is_projective() + False + """ + return self._weight.is_verma_dominant() + def homogeneous_component_basis(self, d): r""" Return a basis for the ``d``-th homogeneous component of ``self``. @@ -528,11 +667,13 @@ def homogeneous_component_basis(self, d): sage: M.homogeneous_component_basis(mu - La[1]) Family () """ - diff = _convert_wt_to_root(d - self._weight) + diff = (d - self._weight)._to_root_vector() if diff is None or not all(coeff <= 0 and coeff in ZZ for coeff in diff): return Family([]) return sorted(self._homogeneous_component_f(diff)) + weight_space_basis = homogeneous_component_basis + @cached_method def _homogeneous_component_f(self, d): r""" @@ -605,17 +746,22 @@ def _Hom_(self, Y, category=None, **options): sage: type(H) <...VermaModuleHomset_with_category_with_equality_by_id'> """ - if not (isinstance(Y, VermaModule) and self._g is Y._g): - raise TypeError("{} must be a Verma module of {}".format(Y, self._g)) + from sage.algebras.lie_algebras.bgg_dual_module import BGGDualModule, SimpleModule + if not ((isinstance(Y, (VermaModule, SimpleModule)) + or (isinstance(Y, BGGDualModule) and Y._module is self)) + and self._g is Y.lie_algebra()): + raise TypeError("{} must be an object in Category O of {}".format(Y, self._g)) if category is not None and not category.is_subcategory(self.category()): raise TypeError("{} is not a subcategory of {}".format(category, self.category())) return VermaModuleHomset(self, Y) class Element(CombinatorialFreeModule.Element): def _acted_upon_(self, scalar, self_on_left=False): - """ + r""" Return the action of ``scalar`` on ``self``. + EXAMPLES: + Check that other PBW algebras have an action:: sage: L = lie_algebras.sp(QQ, 6) @@ -639,6 +785,7 @@ def _acted_upon_(self, scalar, self_on_left=False): """ P = self.parent() # Check for scalars first + # TODO: Pass by these checks if a PBW basis element of the Lie algebra if scalar in P.base_ring(): # Don't have this be a super call return CombinatorialFreeModule.Element._acted_upon_(self, scalar, self_on_left) @@ -694,13 +841,14 @@ def _acted_upon_(self, scalar, self_on_left=False): _lmul_ = _acted_upon_ _rmul_ = _acted_upon_ + ##################################################################### ## Morphisms and Homset class VermaModuleMorphism(Morphism): - """ - A morphism of Verma modules. + r""" + A morphism of a Verma module to another module in Category `\mathcal{O}`. """ def __init__(self, parent, scalar): """ @@ -765,7 +913,7 @@ def _repr_defn(self): v = self.domain().highest_weight_vector() if not self._scalar: return "{} |--> {}".format(v, self.codomain().zero()) - return "{} |--> {}".format(v, self._scalar * self.parent().singular_vector()) + return "{} |--> {}".format(v, self._scalar * self.parent().highest_weight_image()) def _richcmp_(self, other, op): r""" @@ -800,7 +948,7 @@ def _call_(self, x): sage: M = L.verma_module(La[1] + La[2]) sage: Mp = L.verma_module(M.highest_weight().dot_action([1,2])) sage: pbw = M.pbw_basis() - sage: f1, f2 = pbw(L.f(1)), pbw(L.f(2)) + sage: f1, f2 = pbw.f() sage: v = Mp.highest_weight_vector() sage: phi = Hom(Mp, M).natural_map() sage: phi(f1 * v) == f1 * phi(v) @@ -816,14 +964,14 @@ def _call_(self, x): sage: psi(v) 0 """ - if not self._scalar or self.parent().singular_vector() is None: + if not self._scalar or not self.parent().highest_weight_image(): return self.codomain().zero() mc = x.monomial_coefficients(copy=False) return self.codomain().linear_combination((self._on_basis(m), self._scalar * c) for m,c in mc.items()) def _on_basis(self, m): - """ + r""" Return the image of the basis element indexed by ``m``. EXAMPLES:: @@ -833,14 +981,17 @@ def _on_basis(self, m): sage: M = L.verma_module(La[1] + La[2]) sage: Mp = L.verma_module(M.highest_weight().dot_action([1,2])) sage: pbw = M.pbw_basis() - sage: f1, f2 = pbw(L.f(1)), pbw(L.f(2)) + sage: f1, f2 = pbw.f() sage: v = Mp.highest_weight_vector() sage: phi = Hom(Mp, M).natural_map() sage: phi._on_basis((f1 * v).leading_support()) == f1 * phi(v) True """ + vec = self.parent().highest_weight_image() + if not vec: + return vec pbw = self.codomain()._pbw - return pbw.monomial(pbw._indices(m.dict())) * self.parent().singular_vector() + return pbw.monomial(pbw._indices(m.dict())) * vec def _add_(self, other): """ @@ -936,8 +1087,10 @@ def is_injective(self): r""" Return if ``self`` is injective or not. - A Verma module morphism `\phi : M \to M'` is injective if - and only if `\dim \hom(M, M') = 1` and `\phi \neq 0`. + A morphism `\phi : M \to M'` from a Verma module `M` to another + Verma module `M'` is injective if and only if `\dim \hom(M, M') = 1` + and `\phi \neq 0`. If `M'` is a dual Verma or simple module, then + the result is not injective. EXAMPLES:: @@ -955,15 +1108,20 @@ def is_injective(self): sage: psi.is_injective() False """ - return self.parent().singular_vector() is not None and bool(self._scalar) + if not isinstance(self.codomain(), VermaModule): + return False + return bool(self._scalar) def is_surjective(self): - """ + r""" Return if ``self`` is surjective or not. - A Verma module morphism is surjective if and only if the - domain is equal to the codomain and it is not the zero - morphism. + A morphism `\phi : M \to M'` from a Verma module `M` to another + Verma module `M'` is surjective if and only if the domain is + equal to the codomain and it is not the zero morphism. + + If `M'` is a simple module, then this surjective if and only if + `\dim \hom(M, M') = 1` and `\phi \neq 0`. EXAMPLES:: @@ -980,13 +1138,66 @@ def is_surjective(self): sage: psi.is_surjective() False """ - return self.domain() == self.codomain() and bool(self._scalar) + if not bool(self._scalar): + return False + + if isinstance(self.codomain(), VermaModule): + return self.domain() == self.codomain() + + from sage.algebras.lie_algebras.bgg_dual_module import SimpleModule + if isinstance(self.codomain(), SimpleModule): + return self.domain().highest_weight() == self.codomain().highest_weight() + + return False + + def image(self): + r""" + Return the image of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(La[1] + 2*La[2]) + sage: Mp = g.verma_module(La[1] + 3*La[2]) + sage: phi = Hom(M, Mp).natural_map() + sage: phi.image() + Free module generated by {} over Rational Field + sage: Mc = M.dual() + sage: phi = Hom(M, Mc).natural_map() + sage: L = phi.image(); L + Simple module with highest weight Lambda[1] + 2*Lambda[2] of + Lie algebra of ['B', 2] in the Chevalley basis + sage: psi = Hom(M, L).natural_map() + sage: psi.image() + Simple module with highest weight Lambda[1] + 2*Lambda[2] of + Lie algebra of ['B', 2] in the Chevalley basis + """ + C = self.codomain() + if not bool(self._scalar): + return C.submodule([]) + + if isinstance(C, VermaModule): + if self.domain() == C: + return C + raise NotImplementedError("submodules of Verma modules not yet implemented") + + from sage.algebras.lie_algebras.bgg_dual_module import BGGDualModule, SimpleModule + if isinstance(C, BGGDualModule) and isinstance(C._module, VermaModule): + return SimpleModule(C.lie_algebra(), C.highest_weight(), prefix=C._indices.prefix(), + basis_key=C._module._basis_key) + + if isinstance(self.codomain(), SimpleModule): + return self.codomain() class VermaModuleHomset(Homset): r""" - The set of morphisms from one Verma module to another - considered as `U(\mathfrak{g})`-representations. + The set of morphisms from a Verma module to another module in + Category `\mathcal{O}` considered as `U(\mathfrak{g})`-representations. + + This currently assumes the codomain is a Verma module, its dual, + or a simple module. Let `M_{w \cdot \lambda}` and `M_{w' \cdot \lambda'}` be Verma modules, `\cdot` is the dot action, and `\lambda + \rho`, @@ -998,9 +1209,13 @@ class VermaModuleHomset(Homset): if and only if `\lambda = \lambda'` and `w' \leq w` in Bruhat order. Otherwise the homset is 0 dimensional. + + If the codomain is a dual Verma module `M_{\mu}^{\vee}`, then the + homset is `\delta_{\lambda\mu}` dimensional. When `\mu = \lambda`, + the image is the simple module `L_{\lambda}`. """ def __call__(self, x, **options): - """ + r""" Construct a morphism in this homset from ``x`` if possible. EXAMPLES:: @@ -1056,7 +1271,7 @@ def __call__(self, x, **options): return super().__call__(x, **options) def _an_element_(self): - """ + r""" Return an element of ``self``. EXAMPLES:: @@ -1077,6 +1292,37 @@ def _an_element_(self): """ return self.natural_map() + def highest_weight_image(self): + r""" + Return the image of the highest weight vector of the domain + in the codomain. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['C', 3]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module(La[1] + 2*La[3]) + sage: Mc = M.dual() + sage: H = Hom(M, Mc) + sage: H.highest_weight_image() + v[Lambda[1] + 2*Lambda[3]]^* + sage: L = H.natural_map().image() + sage: Hp = Hom(M, L) + sage: Hp.highest_weight_image() + u[Lambda[1] + 2*Lambda[3]] + """ + C = self.codomain() + if isinstance(C, VermaModule): + # singular_vector() is cached, so we can safely call it twice + if self.singular_vector() is None: + return C.zero() + return self.singular_vector() + # Otherwise, it is a dual Verma or a simple, so the image + # must be the highest weight vector. + if self.domain().highest_weight() == C.highest_weight(): + return C.highest_weight_vector() + return C.zero() + @cached_method def singular_vector(self): r""" @@ -1087,12 +1333,16 @@ def singular_vector(self): ALGORITHM: We essentially follow the algorithm laid out in [deG2005]_. - We use the `\mathfrak{sl}_2` relation on - `M_{s_i \cdot \lambda} \to M_{\lambda}`, where - `\langle \lambda + \delta, \alpha_i^{\vee} \rangle = m > 0`, - i.e., the weight `\lambda` is `i`-dominant with respect to - the dot action. From here, we construct the singular vector - `f_i^m v_{\lambda}`. We iterate this until we reach `\mu`. + We split the main computation into two cases. If there exists + an `i` such that `\langle \lambda + \rho, \alpha_i^{\vee} + \rangle = m > 0` (i.e., the weight `\lambda` is `i`-dominant + with respect to the dot action), then we use the `\mathfrak{sl}_2` + relation on `M_{s_i \cdot \lambda} \to M_{\lambda}` to + construct the singular vector `f_i^m v_{\lambda}`. Otherwise + we find the shortest root `\alpha` such that `\langle \lambda + + \rho, \alpha^{\vee} \rangle > 0` and explicitly compute the + kernel with respect to the weight basis elements. We iterate + this until we reach `\mu`. EXAMPLES:: @@ -1103,13 +1353,15 @@ def singular_vector(self): sage: M = L.verma_module(la) sage: Mp = L.verma_module(mu) sage: H = Hom(Mp, M) - sage: H.singular_vector() + sage: v = H.singular_vector(); v f[-alpha[2]]*f[-alpha[1]]^3*v[Lambda[1] - Lambda[3]] + 3*f[-alpha[1]]^2*f[-alpha[1] - alpha[2]]*v[Lambda[1] - Lambda[3]] + sage: v.degree() == Mp.highest_weight() + True :: - sage: L = LieAlgebra(QQ, cartan_type=['F',4]) + sage: L = LieAlgebra(QQ, cartan_type=['F', 4]) sage: La = L.cartan_type().root_system().weight_space().fundamental_weights() sage: la = La[1] + La[2] - La[3] sage: mu = la.dot_action([1,2,3,2]) @@ -1119,7 +1371,9 @@ def singular_vector(self): sage: v = H.singular_vector() sage: pbw = M.pbw_basis() sage: E = [pbw(e) for e in L.e()] - sage: all(e * v == M.zero() for e in E) + sage: all(e * v == M.zero() for e in E) # long time + True + sage: v.degree() == Mp.highest_weight() True When `w \cdot \lambda \notin \lambda + Q^-`, there does not @@ -1135,6 +1389,24 @@ def singular_vector(self): sage: H.singular_vector() is None True + When we need to apply a non-simple reflection, we can compute + the singular vector (see :issue:`36793`):: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: La = g.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: M = g.verma_module((0*La[1]).dot_action([1])) + sage: Mp = g.verma_module((0*La[1]).dot_action([1,2])) + sage: H = Hom(Mp, M) + sage: v = H.singular_vector(); v + 1/2*f[-alpha[2]]*f[-alpha[1]]*v[-2*Lambda[1] + Lambda[2]] + + f[-alpha[1] - alpha[2]]*v[-2*Lambda[1] + Lambda[2]] + sage: pbw = M.pbw_basis() + sage: E = [pbw(e) for e in g.e()] + sage: all(e * v == M.zero() for e in E) + True + sage: v.degree() == Mp.highest_weight() + True + TESTS:: sage: L = lie_algebras.sl(QQ, 3) @@ -1154,29 +1426,100 @@ def singular_vector(self): return None from sage.combinat.root_system.coxeter_group import CoxeterGroup + from sage.matrix.constructor import matrix W = CoxeterGroup(self.domain()._g._cartan_type) - wp = W.from_reduced_word(self.domain()._dominant_data[1]) - w = W.from_reduced_word(self.codomain()._dominant_data[1]) + # We take the inverse to account for the left versus right action + wp = W.from_reduced_word(reversed(self.domain()._dominant_data[1])) + w = W.from_reduced_word(reversed(self.codomain()._dominant_data[1])) if not w.bruhat_le(wp): return None C = self.codomain() pbw = C._pbw - f = C._g.f() - F = {i: pbw(f[i]) for i in f.keys()} - red_word = (wp * ~w).reduced_word() + F = pbw.f() + E = pbw.e() + index_set = F.keys() + cur_w = w rho = C._weight.parent().rho() ac = C._weight.parent().simple_coroots() elt = pbw.one() wt = C._weight - # Construct the singular vector by iterated embeddings of Verma - # modules (without constructing the modules themselves) - for i in reversed(red_word): - exp = (wt + rho).scalar(ac[i]) - if exp not in ZZ or exp < 0: - return None - elt = F[i]**ZZ(exp) * elt - wt = wt.dot_action([i]) - return C.highest_weight_vector()._acted_upon_(elt, False) + pos_roots_by_ht = C._g._cartan_type.root_system().root_lattice().positive_roots_by_height() + assert all(sum(rt.coefficients()) == 1 for rt in pos_roots_by_ht[:len(index_set)]) + # for this, we don't need to check the simple roots + pos_roots_by_ht = pos_roots_by_ht[len(index_set):] + + while cur_w != wp: + ind = None + for i in cur_w.descents(side='right', positive=True): + exp = (wt + rho).scalar(ac[i]) + if exp not in ZZ or exp <= 0: + continue + # We need to check that the result is still smaller in Bruhat order + next_w = cur_w.apply_simple_reflection_right(i) + if not next_w.bruhat_le(wp): + continue + ind = i + # favor a path in weak order so we only do sl_2 relations + if not next_w.weak_le(wp, side="right"): + continue + break + if ind is None: # no simple root; need a more general approach + # We search for the shortest root that can be applied to minimize + # the size of the basis needed to compute the kernel. + for rt in pos_roots_by_ht: + exp = (wt + rho).scalar(rt.associated_coroot()) + # We need to check that the result is still smaller in Bruhat order + i, wd = rt.to_simple_root(reduced_word=True) + refl = wd + (i,) + tuple(reversed(wd)) + next_w = cur_w.apply_reflections(refl, side='right', word_type="simple") + if exp not in ZZ or exp <= 0: + continue + if not next_w.bruhat_le(wp): + continue + # We construct the Verma module of the appropriate weight in + # order to reduce the dimension and number of multiplications. + Mp = C._g.verma_module(wt) + basis = sorted(Mp._homogeneous_component_f(-rt.to_vector()), key=str) + for i in index_set: + image = [E[i] * b for b in basis] + supp = set() + for vec in image: + supp.update(vec._monomial_coefficients) + supp = sorted(supp, key=pbw._monomial_key) + if not supp: # everything is in the kernel + continue + M = matrix(pbw.base_ring(), [[v[s] for v in image] for s in supp]) + ker = M.right_kernel_matrix() + basis = [C.linear_combination((basis[j], c) for j, c in kv.iteritems()) + for kv in ker.rows()] + + assert len(basis) == 1 + if Mp is C: # We've constructed the element in the codomain + assert next_w == wp + assert basis[0].degree() == self.domain().highest_weight() + return basis[0] + pbw_elt = pbw.element_class(pbw, {pbw._indices(m._monomial): c + for m, c in basis[0]._monomial_coefficients.items()}) + elt = pbw_elt * elt + wt = wt.dot_action(refl) + cur_w = next_w + break + else: + #assert False, "unable to find root" + # Have a more explicit check at the beginning using the integral + # orbit action for the correct version of dominance; see, e.g., + # Humphreys "Representations of Semisimple Lie Algebras in the BGG Category O". + return None + else: + # Construct the singular vector by iterated embeddings of Verma + # modules from the sl_2 relations (without constructing + # the modules themselves) + elt = F[ind]**ZZ(exp) * elt + wt = wt.dot_action([ind]) + cur_w = cur_w.apply_simple_reflection_right(ind) + ret = C.highest_weight_vector()._acted_upon_(elt, False) + assert ret.degree() == self.domain().highest_weight() + return ret @cached_method def natural_map(self): @@ -1209,7 +1552,7 @@ def natural_map(self): of Lie algebra of ['A', 2] in the Chevalley basis Defn: v[Lambda[1] + 2*Lambda[2]] |--> 0 """ - if self.singular_vector() is None: + if not self.highest_weight_image(): return self.zero() return self.element_class(self, self.base_ring().one()) @@ -1236,7 +1579,7 @@ def zero(self): return self.element_class(self, self.base_ring().zero()) def dimension(self): - """ + r""" Return the dimension of ``self`` (as a vector space over the base ring). @@ -1255,12 +1598,12 @@ def dimension(self): sage: H.dimension() 0 """ - if self.singular_vector() is None: + if not self.highest_weight_image(): return ZZ.zero() return ZZ.one() def basis(self): - """ + r""" Return a basis of ``self``. EXAMPLES:: @@ -1278,50 +1621,8 @@ def basis(self): sage: H.basis() Family () """ - if self.singular_vector() is None: + if not self.highest_weight_image(): return Family([]) return Family([self.natural_map()]) Element = VermaModuleMorphism - - -def _convert_wt_to_root(wt): - r""" - Helper function to express ``wt`` as a linear combination - of simple roots. - - INPUT: - - - ``wt`` -- an element of a weight lattice realization - - OUTPUT: - - A vector over `\QQ` representing ``wt`` as a linear combination - of simple roots. - - EXAMPLES:: - - sage: from sage.algebras.lie_algebras.verma_module import _convert_wt_to_root - sage: P = RootSystem(['A',3]).weight_lattice() - sage: La = P.fundamental_weights() - sage: [_convert_wt_to_root(al) for al in P.simple_roots()] - [(1, 0, 0), (0, 1, 0), (0, 0, 1)] - sage: _convert_wt_to_root(La[1] + La[2]) - (5/4, 3/2, 3/4) - - sage: L = RootSystem(['A',3]).ambient_space() - sage: e = L.basis() - sage: _convert_wt_to_root(e[0] + 3*e[3]) - sage: _convert_wt_to_root(e[0] - e[1]) - (1, 0, 0) - sage: _convert_wt_to_root(e[0] + 2*e[1] - 3*e[2]) - (1, 3, 0) - """ - v = wt.to_vector().change_ring(QQ) - al = [a.to_vector() for a in wt.parent().simple_roots()] - b = v.parent().linear_dependence([v] + al) - if len(b) != 1 or b[0] == 0: - return None - b = b[0] # Get the actual vector that gives the linear dependency - # Get v as a linear combination of the simple roots - return vector(QQ, [-x / b[0] for x in b[1:]]) diff --git a/src/sage/categories/coxeter_groups.py b/src/sage/categories/coxeter_groups.py index 25261db6645..e430e3034a1 100644 --- a/src/sage/categories/coxeter_groups.py +++ b/src/sage/categories/coxeter_groups.py @@ -2720,21 +2720,23 @@ def bruhat_le(self, other): return self.apply_simple_projection(desc, length_increasing=False).bruhat_le(other.apply_simple_reflection(desc)) return self == other + @cached_in_parent_method def weak_le(self, other, side='right'): - """ - Comparison in weak order. + r""" + Perform the comparison between ``self`` and ``other`` in + weak (Bruhat) order. INPUT: - - other -- an element of the same Coxeter group - - side -- 'left' or 'right' (default: 'right') + - ``other`` -- an element of the same Coxeter group + - ``side`` -- string (default: ``'right'``); ``'left'`` or ``'right'`` OUTPUT: a boolean - This returns whether ``self`` <= ``other`` in left - (resp. right) weak order, that is if 'v' can be obtained - from 'v' by length increasing multiplication by simple - reflections on the left (resp. right). + This returns whether `u \leq v`, where `u` is ``self`` and `v` + is ``other``, in left (resp. right) weak order, that is if `v` + can be obtained from `u` by length increasing multiplication by + simple reflections on the left (resp. right). EXAMPLES:: diff --git a/src/sage/categories/triangular_kac_moody_algebras.py b/src/sage/categories/triangular_kac_moody_algebras.py index 0a7988282d3..677b20e06f4 100644 --- a/src/sage/categories/triangular_kac_moody_algebras.py +++ b/src/sage/categories/triangular_kac_moody_algebras.py @@ -19,12 +19,14 @@ from sage.misc.abstract_method import abstract_method from sage.misc.cachefunc import cached_method +from sage.misc.lazy_attribute import lazy_attribute from sage.categories.category_types import Category_over_base_ring +from sage.categories.category_with_axiom import CategoryWithAxiom_over_base_ring from sage.categories.kac_moody_algebras import KacMoodyAlgebras class TriangularKacMoodyAlgebras(Category_over_base_ring): - """ + r""" Category of Kac-Moody algebras with a distinguished basis that respects the triangular decomposition. @@ -61,11 +63,18 @@ def _part_on_basis(self, m): EXAMPLES:: - sage: L = lie_algebras.so(QQ, 5) # needs sage.combinat sage.modules - sage: L.f() # needs sage.combinat sage.modules + sage: # needs sage.combinat sage.modules + sage: L = lie_algebras.so(QQ, 5) + sage: L.f() # indirect doctest Finite family {1: E[-alpha[1]], 2: E[-alpha[2]]} - sage: L.f(1) # needs sage.combinat sage.modules + sage: f1 = L.f(1); f1 # indirect doctest E[-alpha[1]] + sage: L._part_on_basis(f1.leading_support()) + -1 + sage: L._part_on_basis(L.e(1).leading_support()) + 1 + sage: all(L._part_on_basis(b) == 0 for b in L.basis() if b.degree() == 0) + True """ deg = self.degree_on_basis(m) if not deg: @@ -200,6 +209,29 @@ def _weight_action(self, m, wt): 0 """ + def _triangular_key(self, x): + r""" + Return a key for sorting for the index ``x`` that respects + the triangular decomposition by `U^-, U^0, U^+`. + + EXAMPLES:: + + sage: L = lie_algebras.sl(QQ, 3) + sage: La = L.cartan_type().root_system().weight_lattice().fundamental_weights() + sage: sorted(L.basis().keys(), key=L._basis_key) + [alpha[2], alpha[1], alpha[1] + alpha[2], + alphacheck[1], alphacheck[2], + -alpha[2], -alpha[1], -alpha[1] - alpha[2]] + sage: sorted(L.basis().keys(), key=L._triangular_key) + [-alpha[2], -alpha[1], -alpha[1] - alpha[2], + alphacheck[1], alphacheck[2], + alpha[2], alpha[1], alpha[1] + alpha[2]] + """ + try: + return (self._part_on_basis(x), self._basis_key(x)) + except AttributeError: + return (self._part_on_basis(x), x) + def verma_module(self, la, basis_key=None, **kwds): """ Return the Verma module with highest weight ``la`` @@ -224,6 +256,49 @@ def verma_module(self, la, basis_key=None, **kwds): from sage.algebras.lie_algebras.verma_module import VermaModule return VermaModule(self, la, basis_key=basis_key, **kwds) + def simple_module(self, la, basis_key=None, **kwds): + r""" + Return the simple module with highest weight ``la`` + over ``self``. + + INPUT: + + - ``la`` -- the weight + - ``basis_key`` -- (optional) a key function for the indexing + set of the basis elements of ``self`` + + OUTPUT: + + If ``la`` is :meth:`Verma antidominant + `, + then this returns the + :class:`~sage.algebras.lie_algebras.verma_module.VermaModule` + of highest weight ``la`` (which is simple and more efficient). + Otherwise this returns a + :class:`~sage.algebras.lie_algebras.bgg_dual_module.SimpleModule`. + + EXAMPLES:: + + sage: # needs sage.combinat sage.modules + sage: g = lie_algebras.sl(QQ, 3) + sage: P = g.cartan_type().root_system().weight_lattice() + sage: La = P.fundamental_weights() + sage: L = g.simple_module(La[1] + La[2]) + sage: L + Simple module with highest weight Lambda[1] + Lambda[2] + of Lie algebra of ['A', 2] in the Chevalley basis + sage: L = g.simple_module(-La[1] - La[2]) + sage: L + Verma module with highest weight -Lambda[1] - Lambda[2] + of Lie algebra of ['A', 2] in the Chevalley basis + """ + if la.is_verma_dominant(positive=False): + from sage.algebras.lie_algebras.verma_module import VermaModule + return VermaModule(self, la, basis_key=basis_key, **kwds) + + from sage.algebras.lie_algebras.bgg_dual_module import SimpleModule + return SimpleModule(self, la, basis_key=basis_key, **kwds) + class ElementMethods: def part(self): """ @@ -263,3 +338,99 @@ def part(self): if all(k == 0 for k in S): return 0 raise ValueError("element is not in one part") + + class FiniteDimensional(CategoryWithAxiom_over_base_ring): + r""" + Category of finite dimensional Kac-Moody algebras (which correspond + to semisimple Lie algebras) with a distinguished basis that + respects the triangular decomposition. + """ + class ParentMethods: + @lazy_attribute + def _transpose_basis_mapping(self): + """ + The mapping on basis elements for the transpose map. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['A', 2]) + sage: g._transpose_basis_mapping + {-alpha[1]: alpha[1], + -alpha[1] - alpha[2]: alpha[1] + alpha[2], + -alpha[2]: alpha[2], + alpha[1]: -alpha[1], + alpha[1] + alpha[2]: -alpha[1] - alpha[2], + alpha[2]: -alpha[2], + alphacheck[1]: alphacheck[1], + alphacheck[2]: alphacheck[2]} + """ + Q = self.cartan_type().root_system().root_lattice() + K = self.basis().keys() + deg_map = {} + ret = {} + for k in K: + deg = self.degree_on_basis(k) + if deg: + deg_map[deg] = k + else: + ret[k] = k + for al, k in deg_map.items(): + ret[k] = deg_map[-al] + return ret + + @cached_method + def _transpose_on_basis(self, m): + r""" + Return the transpose map on the basis element indexed by ``m``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: B = g.basis() + sage: [(B[k], g._transpose_on_basis(k)) for k in B.keys()] + [(E[alpha[2]], E[-alpha[2]]), + (E[alpha[1]], E[-alpha[1]]), + (E[alpha[1] + alpha[2]], E[-alpha[1] - alpha[2]]), + (E[alpha[1] + 2*alpha[2]], E[-alpha[1] - 2*alpha[2]]), + (h1, h1), + (h2, h2), + (E[-alpha[2]], E[alpha[2]]), + (E[-alpha[1]], E[alpha[1]]), + (E[-alpha[1] - alpha[2]], E[alpha[1] + alpha[2]]), + (E[-alpha[1] - 2*alpha[2]], E[alpha[1] + 2*alpha[2]])] + """ + return self.monomial(self._transpose_basis_mapping[m]) + + @lazy_attribute + def transpose(self): + r""" + The transpose map of ``self``. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['B', 2]) + sage: g.transpose + Generic endomorphism of Lie algebra of ['B', 2] in the Chevalley basis + """ + return self.module_morphism(self._transpose_on_basis, codomain=self) + + class ElementMethods: + def transpose(self): + r""" + Return the transpose of ``self``. + + The transpose `\tau` is the map that sends the root basis + elements `e_{\alpha} \leftrightarrow e_{-\alpha}` and fixes + the Cartan subalgebra `h_{\alpha}`. It is an anti-involution + in the sense `[\tau(a), \tau(b)] = \tau([b, a])`. + + EXAMPLES:: + + sage: g = LieAlgebra(QQ, cartan_type=['G', 2]) + sage: for b in g.basis(): + ....: for bp in g.basis(): + ....: a = g[b.transpose(), bp.transpose()] + ....: ap = g[bp, b].transpose() + ....: assert a == ap + """ + return self.parent().transpose(self) diff --git a/src/sage/combinat/root_system/root_lattice_realizations.py b/src/sage/combinat/root_system/root_lattice_realizations.py index 7539a5a85bb..435c2fcfc65 100644 --- a/src/sage/combinat/root_system/root_lattice_realizations.py +++ b/src/sage/combinat/root_system/root_lattice_realizations.py @@ -3912,6 +3912,57 @@ def is_dominant_weight(self): # Or is_dominant_integral_weight? return all(self.inner_product(alphacheck[i]) in NN for i in self.parent().index_set()) + def is_verma_dominant(self, positive=True): + r""" + Return if ``self`` is Verma dominant. + + A weight `\lambda` is *Verma dominant* if + + .. MATH:: + + \langle \lambda + \rho, \alpha^{\vee} \rangle \notin \ZZ_{<0} + + for all positive roots `\alpha`. Note that begin Verma dominant does + *not* imply that `\langle \lambda+\rho, \alpha^{\vee} \rangle \geq 0` + for any positive root `\alpha`. This is used to determine if + a Verma module is simple or projective. + + INPUT: + + - ``positive`` -- boolean (default: ``True``); if ``False``, then + this checks if the weight is Verma anti-dominant, where + `\ZZ_{<0}` is replaced with `\ZZ_{>0}` in the definition. + + EXAMPLES:: + + sage: P = RootSystem(['A', 3]).weight_space() + sage: La = P.fundamental_weights() + sage: alphacheck = P.coroot_lattice().positive_roots() + sage: rho = P.rho() + sage: (La[1] + 2*La[2]).is_verma_dominant() + True + sage: la = La[1] - 3/2*La[3] - rho + sage: la.is_verma_dominant() + True + sage: la.is_verma_dominant(positive=False) + False + sage: [(la+rho).scalar(coroot) for coroot in alphacheck] + [1, 0, -3/2, 1, -3/2, -1/2] + sage: mu = 1/2*La[1] - 3/2*La[3] - rho + sage: mu.is_verma_dominant() + False + sage: mu.is_verma_dominant(positive=False) + True + sage: [(mu+rho).scalar(coroot) for coroot in alphacheck] + [1/2, 0, -3/2, 1/2, -3/2, -1] + """ + P = self.parent() + alphacheck = P.coroot_lattice().positive_roots() + wt = self + P.rho() + if positive: + return not any((c := wt.scalar(ac)) in ZZ and c < 0 for ac in alphacheck) + return not any((c := wt.scalar(ac)) in ZZ and c > 0 for ac in alphacheck) + ########################################################################## # weak order ########################################################################## diff --git a/src/sage/combinat/root_system/weight_lattice_realizations.py b/src/sage/combinat/root_system/weight_lattice_realizations.py index 8859b2135b5..801b4da79b7 100644 --- a/src/sage/combinat/root_system/weight_lattice_realizations.py +++ b/src/sage/combinat/root_system/weight_lattice_realizations.py @@ -25,6 +25,7 @@ from sage.categories.category_types import Category_over_base_ring from sage.sets.family import Family from .root_lattice_realizations import RootLatticeRealizations +from sage.rings.rational_field import QQ class WeightLatticeRealizations(Category_over_base_ring): @@ -908,6 +909,25 @@ def weyl_dimension(self, highest_weight): d = prod((rho.scalar(x) for x in pr), Integer(1)) return Integer(n/d) + @lazy_attribute + def _inverse_cartan_matrix(self): + r""" + Return the inverse Cartan matrix defining ``self``. + + EXAMPLES:: + + sage: RootSystem(['A', 3]).ambient_lattice()._inverse_cartan_matrix + [3/4 1/2 1/4] + [1/2 1 1/2] + [1/4 1/2 3/4] + sage: RootSystem(['G', 2]).weight_lattice()._inverse_cartan_matrix + [2 3] + [1 2] + """ + ret = self.cartan_type().cartan_matrix().inverse() + ret.set_immutable() + return ret + @lazy_attribute def _symmetric_form_matrix(self): r""" @@ -960,7 +980,7 @@ def _symmetric_form_matrix(self): cm = ct.cartan_matrix() if cm.det() != 0: diag = matrix.diagonal(cm.symmetrizer()) - return cm.inverse().transpose() * diag + return self._inverse_cartan_matrix.transpose() * diag if not ct.is_affine(): raise ValueError("only implemented for affine types when the" @@ -1131,4 +1151,37 @@ def to_weight_space(self, base_ring=None): if base_ring is None: base_ring = L.base_ring() - return L.root_system.weight_space(base_ring).sum_of_terms([i, base_ring(self.scalar(L.simple_coroot(i)))] for i in L.cartan_type().index_set()) + wt_space = L.root_system.weight_space(base_ring) + simple_coroots = L.simple_coroots() + return wt_space.sum_of_terms(((i, base_ring(self.scalar(ac))) + for i, ac in simple_coroots.items()), + distinct=True) + + @cached_method + def _to_root_vector(self): + r""" + Helper method to express ``self`` as a linear combination + of simple roots. + + OUTPUT: + + A list with entries in `\QQ` representing ``self`` as a linear + combination of simple roots. + + EXAMPLES:: + + sage: L = RootSystem(['A', 3]).ambient_space() + sage: e = L.basis() + sage: (e[0] + 3*e[3])._to_root_vector() # not in the root space + sage: (e[0] - e[1])._to_root_vector() + (1, 0, 0) + sage: (e[0] + 2*e[1] - 3*e[2])._to_root_vector() + (1, 3, 0) + """ + v = self.to_vector().change_ring(QQ) + al = [a.to_vector() for a in self.parent().simple_roots()] + b = v.parent().linear_dependence([v] + al) + if len(b) != 1 or b[0] == 0: + return None + b = b[0] # Get the actual vector that gives the linear dependency + return b[1:].change_ring(QQ) / -b[0] diff --git a/src/sage/combinat/root_system/weight_space.py b/src/sage/combinat/root_system/weight_space.py index 3df5449c7bd..fce5f57f94d 100644 --- a/src/sage/combinat/root_system/weight_space.py +++ b/src/sage/combinat/root_system/weight_space.py @@ -515,9 +515,9 @@ def scalar(self, lambdacheck): return sum( (self[i]*c for (i,c) in lambdacheck), zero) def is_dominant(self): - """ - Checks whether an element in the weight space lies in the positive cone spanned - by the basis elements (fundamental weights). + r""" + Checks whether an element in the weight space lies in the positive + cone spanned by the basis elements (fundamental weights). EXAMPLES:: @@ -530,9 +530,9 @@ def is_dominant(self): sage: w.is_dominant() False - In the extended affine weight lattice, 'delta' is orthogonal to + In the extended affine weight lattice, ``'delta'`` is orthogonal to the positive coroots, so adding or subtracting it should not - affect dominance :: + affect dominance:: sage: P = RootSystem(['A',2,1]).weight_lattice(extended=true) sage: Lambda = P.fundamental_weights() @@ -540,9 +540,43 @@ def is_dominant(self): sage: w = Lambda[1] - delta # needs sage.graphs sage: w.is_dominant() # needs sage.graphs True + """ + index_set = set(self.parent().index_set()) + return all(c >= 0 for i, c in self._monomial_coefficients.items() if i in index_set) + + def is_dominant_weight(self): + r""" + Checks whether an element in the weight space lies in the positive + `\ZZ`-lattice cone spanned by the basis elements (fundamental weights). + + EXAMPLES:: + + sage: W = RootSystem(['A',3]).weight_space() + sage: Lambda = W.basis() + sage: w = Lambda[1] + Lambda[3] + sage: w.is_dominant_weight() + True + sage: w = Lambda[1] + 2/3*Lambda[3] + sage: w.is_dominant_weight() + False + sage: w = Lambda[1] - Lambda[2] + sage: w.is_dominant_weight() + False + In the extended affine weight lattice, ``'delta'`` is orthogonal to + the positive coroots, so adding or subtracting it should not + affect dominance:: + + sage: P = RootSystem(['A',2,1]).weight_lattice(extended=true) + sage: Lambda = P.fundamental_weights() + sage: delta = P.null_root() # needs sage.graphs + sage: w = Lambda[1] - delta # needs sage.graphs + sage: w.is_dominant_weight() # needs sage.graphs + True """ - return all(self.coefficient(i) >= 0 for i in self.parent().index_set()) + index_set = set(self.parent().index_set()) + from sage.rings.integer_ring import ZZ + return all(c in ZZ and c >= 0 for i, c in self._monomial_coefficients.items() if i in index_set) def to_ambient(self): r""" @@ -580,5 +614,27 @@ def to_weight_space(self): """ return self + @cached_method + def _to_root_vector(self): + r""" + Helper method to express ``self`` as a linear combination + of simple roots. + + OUTPUT: + + A vector with entries in `\QQ` representing ``self`` as a linear + combination of simple roots. + + EXAMPLES:: + + sage: P = RootSystem(['A',3]).weight_lattice() + sage: La = P.fundamental_weights() + sage: [al._to_root_vector() for al in P.simple_roots()] + [(1, 0, 0), (0, 1, 0), (0, 0, 1)] + sage: (La[1] + La[2])._to_root_vector() + (5/4, 3/2, 3/4) + """ + return self.parent()._inverse_cartan_matrix * self.to_vector() + WeightSpace.Element = WeightSpaceElement diff --git a/src/sage/groups/indexed_free_group.py b/src/sage/groups/indexed_free_group.py index 02a4838d77c..ad484532f13 100644 --- a/src/sage/groups/indexed_free_group.py +++ b/src/sage/groups/indexed_free_group.py @@ -347,9 +347,7 @@ def _element_constructor_(self, x=None): sage: G({1: 3, -2: 12}) F[-2]^12*F[1]^3 sage: G(-5) - Traceback (most recent call last): - ... - TypeError: unable to convert -5, use gen() instead + F[-5] TESTS:: diff --git a/src/sage/monoids/indexed_free_monoid.py b/src/sage/monoids/indexed_free_monoid.py index 66af0a2fb5c..a50aa7cdf12 100644 --- a/src/sage/monoids/indexed_free_monoid.py +++ b/src/sage/monoids/indexed_free_monoid.py @@ -288,7 +288,11 @@ def support(self): [0, 2] """ supp = {key for key, exp in self._sorted_items() if exp != 0} - return sorted(supp) + try: + return sorted(supp, key=print_options['sorting_key'], + reverse=print_options['sorting_reverse']) + except Exception: # Sorting the output is a plus, but if we can't, no big deal + return list(supp) def leading_support(self): """ @@ -523,7 +527,7 @@ def _sorted_items(self): try: v.sort(key=print_options['sorting_key'], reverse=print_options['sorting_reverse']) - except Exception: # Sorting the output is a plus, but if we can't, no big deal + except Exception: # Sorting the output is a plus, but if we can't, no big deal pass return v @@ -773,14 +777,12 @@ def _element_constructor_(self, x=None): sage: F([[1, 3], [-2, 12]]) F[-2]^12*F[1]^3 sage: F(-5) - Traceback (most recent call last): - ... - TypeError: unable to convert -5, use gen() instead + F[-5] """ if x is None: return self.one() if x in self._indices: - raise TypeError(f"unable to convert {x!r}, use gen() instead") + return self.gen(x) return self.element_class(self, x) def _an_element_(self): @@ -933,9 +935,9 @@ def gen(self, x): if x not in self._indices: raise IndexError(f"{x} is not in the index set") try: - return self.element_class(self, ((self._indices(x),1),)) - except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) - return self.element_class(self, ((x,1),)) + return self.element_class(self, ((self._indices(x), ZZ.one()),)) + except (ValueError, TypeError, NotImplementedError): # Backup (e.g., if it is a string) + return self.element_class(self, ((x, ZZ.one()),)) class IndexedFreeAbelianMonoid(IndexedMonoid): """ @@ -961,6 +963,11 @@ class IndexedFreeAbelianMonoid(IndexedMonoid): sage: F = FreeAbelianMonoid(index_set=Partitions(), prefix='A', bracket=False, scalar_mult='%') sage: F.gen([3,1,1]) * F.gen([2,2]) A[2, 2]%A[3, 1, 1] + + .. TODO:: + + Implement a subclass when the index sets is finite that utilizes + vectors or the polydict monomials with the index order fixed. """ def _repr_(self): """ @@ -1045,10 +1052,15 @@ def gen(self, x): Traceback (most recent call last): ... IndexError: 0 is not in the index set + + sage: F = lie_algebras.VirasoroAlgebra(QQ).pbw_basis().indices(); F + Free abelian monoid indexed by Disjoint union of Family ({'c'}, Integer Ring) + sage: F.gen('c') + PBW['c'] """ if x not in self._indices: raise IndexError(f"{x} is not in the index set") try: - return self.element_class(self, {self._indices(x): 1}) - except (TypeError, NotImplementedError): # Backup (e.g., if it is a string) - return self.element_class(self, {x: 1}) + return self.element_class(self, {self._indices(x): ZZ.one()}) + except (ValueError, TypeError, NotImplementedError): # Backup (e.g., if it is a string) + return self.element_class(self, {x: ZZ.one()})