Skip to content

Commit

Permalink
gh-37724: Implement faithful representations of nilpotent Lie algebras
Browse files Browse the repository at this point in the history
    
<!-- ^ Please provide a concise and informative title. -->
<!-- ^ Don't put issue numbers in the title, do this in the PR
description below. -->
<!-- ^ For example, instead of "Fixes #12345" use "Introduce new method
to calculate 1 + 2". -->
<!-- v Describe your changes below in detail. -->
<!-- v Why is this change required? What problem does it solve? -->
<!-- v If this PR resolves an open issue, please link to it here. For
example, "Fixes #12345". -->

We implement two methods to construct faithful representations of
nilpotent Lie algebras. This is a first step towards implementing Ado's
theorem.

### 📝 Checklist

<!-- Put an `x` in all the boxes that apply. -->

- [x] The title is concise and informative.
- [x] The description explains in detail what this PR is about.
- [x] I have linked a relevant issue or discussion.
- [x] I have created tests covering the changes.
- [x] I have updated the documentation accordingly.

### ⌛ Dependencies

<!-- List all open PRs that this PR logically depends on. For example,
-->
<!-- - #12345: short description why this is a dependency -->
<!-- - #34567: ... -->
    
URL: #37724
Reported by: Travis Scrimshaw
Reviewer(s): Matthias Köppe, Travis Scrimshaw
  • Loading branch information
Release Manager committed Apr 10, 2024
2 parents e16ce82 + 643d320 commit c5e0a87
Show file tree
Hide file tree
Showing 4 changed files with 448 additions and 1 deletion.
5 changes: 5 additions & 0 deletions src/doc/en/reference/references/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,11 @@ REFERENCES:
*Finite hypergeometric functions*,
:arxiv:`1505.02900`
.. [BEdG2009] Dietrich Burde, Bettina Eick, and Willem de Graaf.
*Computing faithful representations for nilpotent Lie algebras*.
J. Algebra, **322** no. 3 (2009) pp. 602-612.
doi:`10.1016/j.jalgebra.2009.04.041`, arxiv:`0807.2345`.
.. [Bee] Robert A. Beezer, *A First Course in Linear Algebra*,
http://linear.ups.edu/. Accessed 15 July 2010.
Expand Down
358 changes: 358 additions & 0 deletions src/sage/algebras/lie_algebras/representation.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
# https://www.gnu.org/licenses/
# ****************************************************************************

from sage.misc.lazy_attribute import lazy_attribute
from sage.misc.cachefunc import cached_method
from sage.sets.family import Family, AbstractFamily
from sage.matrix.constructor import matrix
from sage.combinat.free_module import CombinatorialFreeModule
from sage.categories.modules import Modules
from copy import copy
Expand Down Expand Up @@ -94,6 +97,44 @@ def _test_representation(self, **options):
for v in S:
tester.assertEqual(x.bracket(y) * v, x * (y * v) - y * (x * v))

def representation_matrix(self, elt):
"""
Return the matrix for the action of ``elt`` on ``self``.
EXAMPLES::
sage: H1 = lie_algebras.Heisenberg(QQ, 1)
sage: F = H1.faithful_representation(algorithm="minimal")
sage: P1 = F.representation_matrix(H1.gen(0)); P1
[0 0 0]
[0 0 0]
[1 0 0]
sage: Q1 = F.representation_matrix(H1.gen(1)); Q1
[ 0 0 0]
[ 0 0 -1]
[ 0 0 0]
sage: Z = P1.commutator(Q1); Z
[0 0 0]
[1 0 0]
[0 0 0]
sage: P1.commutator(Z) == Q1.commutator(Z) == 0
True
sage: (H1.gen(0) * F.an_element()).to_vector()
(0, 0, 2)
sage: P1 * F.an_element().to_vector()
(0, 0, 2)
sage: (H1.gen(1) * F.an_element()).to_vector()
(0, -3, 0)
sage: Q1 * F.an_element().to_vector()
(0, -3, 0)
sage: (H1.basis()['z'] * F.an_element()).to_vector()
(0, 2, 0)
sage: Z * F.an_element().to_vector()
(0, 2, 0)
"""
B = self.basis()
return matrix([(elt * B[k]).to_vector() for k in self.get_order()]).transpose()


class RepresentationByMorphism(CombinatorialFreeModule, Representation_abstract):
r"""
Expand Down Expand Up @@ -433,3 +474,320 @@ def _acted_upon_(self, scalar, self_on_left=False):
return None
return P.zero()
return super()._acted_upon_(scalar, self_on_left)


class FaithfulRepresentationNilpotentPBW(CombinatorialFreeModule, Representation_abstract):
r"""
Return a faithful reprensetation of a nilpotent Lie algebra
constructed using the PBW basis.
Let `L` be a `k`-step nilpotent Lie algebra. Define a weight function
on elements in `L` by the lower central series of `L`. Then a faithful
representation of `L` is `U(L) / U(L)^{k+1}`, where `U(L)^{k+1}`
is the (twosided) ideal of `U(L)` generated by all monomials
of weight at least `k + 1`.
We can also expand the ideal keeping the property that `I \cap Z(L) = 0`.
The resulting quotient `U(L) / I` remains faithful and is a minimal
faithful representation of `L` in the sense that it has no faithful
submodules or quotients. (Note: this is not necessarily the smallest
dimensional faithful representation of `L`.)
We consider an example of the rank 2 Heisenberg Lie algebra,
but with a non-standard basis given by `a = p_1 + z`, `b = q_1`,
and `c = q_1 + z`::
sage: scoeffs = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}}
sage: L.<a,b,c> = LieAlgebra(QQ, scoeffs)
sage: TestSuite(L).run(elements=list(L.basis()))
sage: L.is_nilpotent()
True
sage: L.derived_series()
(Lie algebra on 3 generators (a, b, c) over Rational Field,
Ideal (b - c) of Lie algebra on 3 generators (a, b, c) over Rational Field,
Ideal () of Lie algebra on 3 generators (a, b, c) over Rational Field)
sage: F = L.faithful_representation()
sage: L.an_element() * F.an_element()
2*F[1, 0, 0] + 8*F[1, 1, 0] + 3*F[2, 0, 0] + 4*F[0, 1, 0]
+ 4*F[0, 2, 0] + 4*F[0, 0, 1]
sage: MF = L.faithful_representation(algorithm="minimal")
sage: MF.dimension()
3
sage: [MF.representation_matrix(be) for be in L.basis()]
[
[0 0 0] [ 0 0 0] [ 0 0 0]
[0 0 0] [ 0 0 -1] [ 1 0 -1]
[1 0 0], [ 0 0 0], [ 0 0 0]
]
An example with ``minimal=True`` for `H_2 \oplus A_1`, where `A_1` is
a `1`-dimensional Abelian Lie algebra::
sage: scoeffs = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}}
sage: L.<a,b,c,d> = LieAlgebra(QQ, scoeffs)
sage: F = L.faithful_representation(); F
Faithful 11 dimensional representation of Lie algebra on 4
generators (a, b, c, d) over Rational Field
sage: MF = L.faithful_representation(algorithm="minimal"); MF
Minimal faithful representation of Lie algebra on 4
generators (a, b, c, d) over Rational Field
sage: MF.dimension()
4
INPUT:
- ``minimal`` -- boolean (default: ``False``); whether to construct
the minimal basis or not
REFERENCES:
- [BEdG2009]_
"""
def __init__(self, L, minimal=False):
r"""
Initialize ``self``.
EXAMPLES::
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: F = H2.faithful_representation()
sage: TestSuite(F).run(elements=list(F.basis()))
sage: MF = H2.faithful_representation(algorithm="minimal")
sage: TestSuite(MF).run(elements=list(MF.basis()))
sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}}
sage: L.<a,b,c> = LieAlgebra(QQ, sc)
sage: F = L.faithful_representation()
sage: TestSuite(F).run(elements=list(F.basis()))
sage: MF = L.faithful_representation(algorithm="minimal")
sage: TestSuite(MF).run(elements=list(MF.basis()))
"""
LCS = L.lower_central_series()
if LCS[-1].dimension() != 0:
raise ValueError("the Lie algebra must be nilpotent")
# construct an appropriate basis of L
basis_by_deg = {}
self._step = len(LCS) - 1
self._minimal = minimal
if self._minimal:
Z = L.center()
ZB = [L(b) for b in Z.basis()]
prev = LCS[-1]
for D in reversed(LCS[:-1]):
cur = []
for ind in range(len(ZB) - 1, -1, -1):
z = ZB[ind]
if z in D:
ZB.pop(ind)
cur.append(z)
k = self._step - len(basis_by_deg)
basis_by_deg[k] = cur
temp = [bred for b in D.basis() if (bred := Z.reduce(prev.reduce(L(b))))]
basis_by_deg[k].extend(L.echelon_form(temp))
prev = D
else:
prev = LCS[-1]
for D in reversed(LCS[:-1]):
temp = [L(bred) for b in D.basis() if (bred := prev.reduce(L(b)))]
basis_by_deg[self._step-len(basis_by_deg)] = L.echelon_form(temp)
prev = D

L_basis = sum((basis_by_deg[deg] for deg in sorted(basis_by_deg)), [])

if all(len(b.support()) == 1 for b in L_basis):
self._Lp = L
else:
cob = matrix([b._vector_() for b in L_basis]).transpose()
self._invcob = cob.inverse()
scoeffs = {}
for i, b in enumerate(L_basis):
for j, bp in enumerate(L_basis[i+1:], start=i+1):
scoeffs[i, j] = (self._invcob * b.bracket(bp)._vector_()).dict()
index_set = tuple(range(L.dimension()))
from sage.algebras.lie_algebras.lie_algebra import LieAlgebra
self._Lp = LieAlgebra(L.base_ring(), scoeffs, index_set=index_set)

self._pbw = self._Lp.pbw_basis()
self._degrees = tuple(sum(([deg] * len(B) for deg, B in sorted(basis_by_deg.items())), []))

from sage.sets.disjoint_union_enumerated_sets import DisjointUnionEnumeratedSets
from sage.combinat.integer_vector_weighted import WeightedIntegerVectors
indices = DisjointUnionEnumeratedSets([WeightedIntegerVectors(n, self._degrees)
for n in range(self._step+1)])

if self._minimal:
X = set([tuple(index) for index in indices])
monoid = self._pbw._indices
I = monoid._indices
one = L.base_ring().one()
pbw_gens = self._pbw.algebra_generators()
ZB = frozenset([L(b) for b in Z.basis()])
Zind = [i for i, b in enumerate(L_basis) if b in ZB]
Ztup = set()
for i in Zind:
vec = [0] * L.dimension()
vec[i] = 1
Ztup.add(tuple(vec))

def as_exp(s):
sm = s._monomial
return tuple([sm[i] if i in sm else 0 for i in I])

def test_ideal(m, X):
elt = self._pbw.element_class(self._pbw, {monoid(list(zip(I, m))): one})
for g in pbw_gens:
gelt = g * elt
if any(as_exp(s) in X for s in gelt.support()):
return False
return True

to_remove = set([None])
while to_remove:
X -= to_remove
to_remove = set()
for m in X:
m = tuple(m)
if m in Ztup or not test_ideal(m, X):
continue
to_remove.add(m)
indices = sorted(X)

Representation_abstract.__init__(self, L)
CombinatorialFreeModule.__init__(self, L.base_ring(), indices, prefix='F', bracket=False)

def _repr_(self):
r"""
Return a string representation of ``self``.
EXAMPLES::
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: H2.faithful_representation()
Faithful 16 dimensional representation of Heisenberg algebra
of rank 2 over Rational Field
"""
if self._minimal:
return "Minimal faithful representation of {}".format(self._lie_algebra)
return "Faithful {} dimensional representation of {}".format(self.dimension(), self._lie_algebra)

def _latex_(self):
r"""
Return a string representation of ``self``.
EXAMPLES::
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: latex(H2.faithful_representation())
U(\text{\texttt{Heisenberg...}}) / U(\text{\texttt{Heisenberg...}})^{3}
"""
from sage.misc.latex import latex
g = latex(self._lie_algebra)
ret = "U({0}) / U({0})^{{{1}}}".format(g, self._step+1)
if self._minimal:
return "\\min " + ret
return ret

def _project(self, elt):
r"""
The projection to ``self`` from the PBW basis.
EXAMPLES::
sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}}
sage: L.<a,b,c> = LieAlgebra(QQ, sc)
sage: F = L.faithful_representation()
sage: elt = F._to_pbw(a + b + c)^2; elt
PBW[0]^2 + 4*PBW[0]*PBW[1] - 2*PBW[0]*PBW[2] + 4*PBW[1]^2
- 4*PBW[1]*PBW[2] + PBW[2]^2 + 2*PBW[2]
sage: F._project(elt)
2*F[0, 0, 1] + 4*F[0, 2, 0] + 4*F[1, 1, 0] + F[2, 0, 0]
sage: F._project(F._to_pbw(a + b + c)^3)
0
"""
ret = {}
I = self._pbw._indices._indices
if self._minimal:
for m, c in elt._monomial_coefficients.items():
mm = m._monomial
vec = tuple([mm[i] if i in mm else 0 for i in I])
if vec in self._indices:
ret[self._indices(vec)] = c
else:
for m, c in elt._monomial_coefficients.items():
mm = m._monomial
vec = [mm[i] if i in mm else 0 for i in I]
if sum(e * d for e, d in zip(vec, self._degrees)) <= self._step:
ret[self._indices(vec)] = c
return self.element_class(self, ret)

def _to_pbw(self, elt):
"""
Return the PBW element corresponding to ``elt``.
EXAMPLES::
sage: sc = {('a','b'): {'b':-1, 'c':1}, ('a','c'): {'b':-1, 'c':1}}
sage: L.<a,b,c> = LieAlgebra(QQ, sc)
sage: F = L.faithful_representation()
sage: F._to_pbw(a)
PBW[0]
sage: F._to_pbw(b)
PBW[1]
sage: F._to_pbw(c)
PBW[1] - PBW[2]
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: F = H2.faithful_representation()
sage: F._to_pbw(sum(H2.basis()))
PBW['p1'] + PBW['p2'] + PBW['q1'] + PBW['q2'] + PBW['z']
"""
if self._Lp is self._lie_algebra:
return self._pbw(elt)
return self._pbw(self._Lp.from_vector(self._invcob * elt._vector_()))

class Element(CombinatorialFreeModule.Element):
def _lift_pbw(self):
"""
Return ``self`` as an element of the PBW basis.
EXAMPLES::
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: F = H2.faithful_representation()
sage: F.an_element()._lift_pbw()
3*PBW['q1'] + 2*PBW['q2'] + 2
"""
P = self.parent()
monoid = P._pbw._indices
I = monoid._indices
return P._pbw.element_class(P._pbw, {monoid(list(zip(I, m))): coeff
for m, coeff in self._monomial_coefficients.items()})

def _acted_upon_(self, scalar, self_on_left=False):
r"""
Return the action of ``scalar`` on ``self``.
EXAMPLES::
sage: H2 = lie_algebras.Heisenberg(QQ, 2)
sage: F = H2.faithful_representation()
sage: H2.an_element()
p1
sage: F.an_element()
2*F[0, 0, 0, 0, 0] + 2*F[0, 0, 0, 1, 0] + 3*F[0, 0, 1, 0, 0]
sage: H2.an_element() * F.an_element()
2*F[1, 0, 0, 0, 0] + 2*F[1, 0, 0, 1, 0] + 3*F[1, 0, 1, 0, 0]
sage: 5 * F.an_element()
10*F[0, 0, 0, 0, 0] + 10*F[0, 0, 0, 1, 0] + 15*F[0, 0, 1, 0, 0]
"""
P = self.parent()
if scalar in P._lie_algebra:
if self_on_left:
return None
if not self: # we are (already) the zero vector
return self
scalar = P._lie_algebra(scalar)
return P._project(P._to_pbw(scalar) * self._lift_pbw())

return super()._acted_upon_(scalar, self_on_left)
2 changes: 1 addition & 1 deletion src/sage/algebras/lie_algebras/structure_coefficients.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ def __classcall_private__(cls, R, s_coeff, names=None, index_set=None, **kwds):
from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
return AbelianLieAlgebra(R, names, index_set, **kwds)

if (names is None and len(index_set) <= 1) or len(names) <= 1:
if (names is None and len(index_set) <= 1) or (names is not None and len(names) <= 1):
from sage.algebras.lie_algebras.abelian import AbelianLieAlgebra
return AbelianLieAlgebra(R, names, index_set, **kwds)

Expand Down
Loading

0 comments on commit c5e0a87

Please sign in to comment.