Opened 9 months ago

Closed 9 months ago

# Nilpotent Lie groups

Reported by: Owned by: gh-ehaka major sage-8.5 group theory Lie groups, Lie algebras, manifolds, nilpotent tscrim, egourgoulhon Eero Hakavuori Eric Gourgoulhon, Travis Scrimshaw N/A b2ef2d9 (Commits) #26080

### Description

Implementation of nilpotent Lie groups as manifolds with a distinguished global coordinate system (exponential coordinates).

Planned features:

• representation of elements in exponential coordinates of the first or second kind
• multiplication of symbolic points (using the manifold point framework)
• the frames of left-invariant and right-invariant vector fields
• the exponential map exp:\mathfrak{g}\to G and the logarithm
• the adjoint map Ad:G\times \mathfrak{g}\to\mathfrak{g}

### comment:1 Changed 9 months ago by gh-ehaka

• Branch set to u/gh-ehaka/nilpotent_lie_groups-26344
• Commit set to 63006ea2885a50569e2b95b24b83db8a7998dbc8

A partial first version is now ready, containing the exponential map, exponential coordinates of the first and second kind and group multiplication. Vector fields and the adjoint map are still completely missing, but I thought it would be good to get a working copy up in case there are already some glaring flaws.

In particular, one thing that did not feel quite right was the current method of computing the symbolic group law. The only way I could think of was to extract the structural coefficients of the Lie algebra and to create a new Lie algebra over SR using the same structural coefficients. Two immediate issues of the current approach are

1. the overhead of creating a new Lie algebra
2. the original base ring of the Lie algebra needs to admit a coercion into SR

Another thing is how to handle the symbolic group law. Currently it is done by creating two dummy-variables, computing the group law as a vector expression in exponential coordinates (through the BCH formula) and then evaluating the group law by substitution of the dummy-variables.

This is still work-in-progress, but I would be happy to hear comments or suggestions.

New commits:

 ​1276e0e trac #26080: initial implementation of the BCH formula ​5972b6b trac #26080: converted bch iterator to a generator function and added interface for non-nilpotent Lie algebras ​e12ea13 trac #26080: efficiency improvements ​369f7a6 trac #26080: replaced old stopiteration ​63006ea trac #26344: minimal working example of nilpotent Lie groups as manifolds

### comment:2 Changed 9 months ago by gh-ehaka

• Dependencies set to #26080

### comment:3 Changed 9 months ago by tscrim

Eric, I am cc-ing you in case you have any comments.

The symbolic ring is usually a bit of a slow beast. it definitely feels wrong to have to have a second copy of the Lie algebra over SR. You probably could just use the structure coeffs info.

### comment:4 Changed 9 months ago by egourgoulhon

Good to see that Lie groups are arriving in Sage! I'll have a look tomorrow, focusing on the SR issue.

### comment:5 Changed 9 months ago by git

• Commit changed from 63006ea2885a50569e2b95b24b83db8a7998dbc8 to 8e4dfb1820eb636dd5c5b84b3de90bab50445a12

Branch pushed to git repo; I updated commit sha1. New commits:

 ​8e4dfb1 trac #26344: invariant vector fields

### comment:6 follow-up: ↓ 7 Changed 9 months ago by tscrim

I am not sure I agree with caching the *_invariant_frame. I think this is already done by the atlas for the manifold and there is probably not much benefit to having this cached at the top level as not a lot of computation is done in the methods themselves.

Another comment is that structure_coefficients should exist anytime the Lie algebra has a basis because of the category. I believe the variable_names() and indices() should not fail either. So I don't see a meaningful exception being thrown.

Now that I have enough time, I see why you are using the symbolic ring to create the group law. You are considering things as generic parameters and seeing what the result is. In principle, creating the LieAlgebraWithStructureCoefficients should be relatively quick. It might be better to implement a change_ring() method for the class instead of the helper function (which could have more general use). Unfortunately I don't see a way out of creating that (at least, one that is efficient), but it should not take a lot (well, a disproportionate amount) of memory.

### comment:7 in reply to: ↑ 6 Changed 9 months ago by gh-ehaka

I am not sure I agree with caching the *_invariant_frame. I think this is already done by the atlas for the manifold and there is probably not much benefit to having this cached at the top level as not a lot of computation is done in the methods themselves.

Right, I did not realize there is manifold level caching. I will remove it from here.

Another comment is that structure_coefficients should exist anytime the Lie algebra has a basis because of the category.

This is a good point, and I should in fact change indices() to basis().keys() as in the category code of structure_coefficients, so it will work regardless within the category.

It might be better to implement a change_ring() method for the class instead of the helper function (which could have more general use).

Ideally construction of a Lie group would not rely on the Lie algebra having a change_ring() method, but instead only rely on things required/defined in the finite dimensional (nilpotent) Lie algebras with basis category.

However it would make sense to by default use change_ring if it exists, and to implement this at least for LieAlgebraWithStructureCoefficients. Then only in the case when change_ring is not defined, the code could fall back to creation of a new Lie algebra using structure_coefficients().

### comment:8 Changed 9 months ago by git

• Commit changed from 8e4dfb1820eb636dd5c5b84b3de90bab50445a12 to 61963e06463f59ef392fec1d5e488870788d25c6

Branch pushed to git repo; I updated commit sha1. New commits:

 ​61963e0 trac #26344: added change_ring to LieAlgebraWithStructureCoefficients and modified symbolic Lie algebra creation

### comment:9 follow-up: ↓ 10 Changed 9 months ago by egourgoulhon

I gave a first glance: this looks very nice!

A very minor remark: in the example shown in line 151 of lie_group.py (as well as in other similar examples), a shortcut for

sage: list(X[0].components(exp1_frame))


is

sage: X[0][exp1_frame,:]


Maybe you chose the longer version as being more explicit? Also you can display the components of the vector field X[0] by

sage: X[0].display(exp1_frame)
X_0 = d/dx_0 - 1/2*x_1 d/dx_2


Since exp1_frame is the default vector frame on G, this can be shorten to simply X[0].display().

### comment:10 in reply to: ↑ 9 ; follow-up: ↓ 11 Changed 9 months ago by gh-ehaka

I gave a first glance: this looks very nice!

A very minor remark: in the example shown in line 151 of lie_group.py (as well as in other similar examples), a shortcut for

sage: list(X[0].components(exp1_frame))


is

sage: X[0][exp1_frame,:]


Maybe you chose the longer version as being more explicit? Also you can display the components of the vector field X[0] by

sage: X[0].display(exp1_frame)
X_0 = d/dx_0 - 1/2*x_1 d/dx_2


This choice was just a consequence of lack of knowledge on my part. Thanks for the advice, this will clean up the doc quite a bit!

Since exp1_frame is the default vector frame on G, this can be shorten to simply X[0].display().

At this point I don't know what should be the default frame. The best sounding options are either the coordinate frame of the default system of coordinates, or the left-invariant frame.

### comment:11 in reply to: ↑ 10 Changed 9 months ago by egourgoulhon

Since exp1_frame is the default vector frame on G, this can be shorten to simply X[0].display().

At this point I don't know what should be the default frame. The best sounding options are either the coordinate frame of the default system of coordinates, or the left-invariant frame.

In this case, it is probably better to keep the explicit form, i.e. X[0].display(exp1_frame).

### comment:12 follow-up: ↓ 13 Changed 9 months ago by egourgoulhon

In the documentation, maybe you could use the method at() of vector fields to illustrate that the members of G.left_invariant_frame() do obey the definition of a left invariant vector field, for instance:

sage: L = lie_algebras.Heisenberg(QQ, 1)
sage: G = NilpotentLieGroup(L, 'G'); G
Lie group of Heisenberg algebra of rank 1 over Rational Field
sage: p,q,z = L.basis()


Let us first introduce a specific element k and generic element g in G:

sage: k = G.exp(p)*G.exp(q); k
exp(p1 + q1 + 1/2*z)
sage: g = G(G.chart_exp1()[:]); g  # a generic element of G
exp(x_0*p1 + x_1*q1 + x_2*z)


We introduce next the left translation by k:

sage: L_k = G.diff_map(G, coord_functions=(k*g).coordinates()); L_k
Differentiable map from the Lie group of Heisenberg algebra of rank 1 over Rational Field to itself
sage: L_k.display()
G --> G
(x_0, x_1, x_2) |--> (x_0 + 1, x_1 + 1, -1/2*x_0 + 1/2*x_1 + x_2 + 1/2)


Then we check that X[0] obeys the definition of a left invariant vector field as follows:

sage: X = G.left_invariant_frame(); X
Vector frame (G, (X_0,X_1,X_2))
sage: L_k.differential(g)(X[0].at(g)) == X[0].at(L_k(g))
True


Of course, we have as well

sage: L_k.differential(g)(X[1].at(g)) == X[1].at(L_k(g))
True
sage: L_k.differential(g)(X[2].at(g)) == X[2].at(L_k(g))
True


Of course, a full check would require that k is a generic element of G; this is possible, by defining k from symbolic coordinates.

### comment:13 in reply to: ↑ 12 Changed 9 months ago by gh-ehaka

• Branch changed from u/gh-ehaka/nilpotent_lie_groups-26344 to public/groups/nilpotent_lie_groups-26344
• Commit changed from 61963e06463f59ef392fec1d5e488870788d25c6 to 8a022e85abb13436b8dfc18528f45621309aa4f3

I moved the code over to a public branch so you can feel free to add improvements in if you so wish. I'm also still happy to hear comments and corrections and do the implementation myself as well.

In the documentation, maybe you could use the method at() of vector fields to illustrate that the members of G.left_invariant_frame() do obey the definition of a left invariant vector field

Thanks for the suggestion, this is indeed a very good demonstration of basic Lie group functionality. I also ended up adding the creation of left/right translations as a prebuilt method as these maps should see heavy use in practice.

I have still to add in the methods for the logarithm and adjoint maps, after that I would for the moment be satisfied with the basic Lie group functionality. Or at least I can't think of what it is I am missing.

New commits:

 ​8a022e8 trac #26344: added methods to create left and right translations and improved docs

### comment:14 Changed 9 months ago by tscrim

There is also no real penalty for having more tickets (at least, I will continue to be reviewing tickets for at least the next year :)). So it doesn't hurt to do things in smaller bites too.

### comment:15 Changed 9 months ago by git

• Commit changed from 8a022e85abb13436b8dfc18528f45621309aa4f3 to c16d1f1bac71cbf7f6c21f24cd6f9284a43cebe2

Branch pushed to git repo; I updated commit sha1. New commits:

 ​c16d1f1 trac #26344: conjugation, adjoint and logarithm

### comment:16 Changed 9 months ago by gh-ehaka

• Status changed from new to needs_review

The missing parts of the initial set of features I had in mind are now in. It didn't seem right to add the adjoint without adding the conjugation map as well so that is now there as well.

At this point I'll move the ticket over to the bug-hunting/improvement phase.

### comment:17 Changed 9 months ago by egourgoulhon

I took the liberty of adding a new section "Lie groups" at the manifold metaticket #18528 and put your ticket there. I hope you don't mind.

### comment:18 Changed 9 months ago by egourgoulhon

A question came to my mind: at the moment

sage: L = G.lie_algebra(); L
Heisenberg algebra of rank 1 over Rational Field
sage: T1G = G.tangent_space(G.one()); T1G
Tangent space at exp(0)


are two different beasts:

sage: L.category()
Category of finite dimensional nilpotent lie algebras with basis over Rational Field
sage: T1G.category()
Category of finite dimensional vector spaces over Symbolic Ring
sage: L.basis()
Finite family {'p1': p1, 'q1': q1, 'z': z}
sage: T1G.bases()
[Basis (d/dx_0,d/dx_1,d/dx_2) on the Tangent space at exp(0)]


also as Python objects: L inherits from sage.algebras.lie_algebras.lie_algebra.LieAlgebraWithGenerators, while T1G inherits from sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.

Would it be worth to implement the canonical vector space isomorphism between the two? Would this be useful? A natural way to do it would be via Sage coercions, i.e. implement at least a coercion from L to T1G (the reverse may not work for vectors with symbolic components). If you think this is worth, this could anyway be postponed to another ticket...

Last edited 9 months ago by egourgoulhon (previous) (diff)

### comment:19 Changed 9 months ago by gh-ehaka

I took the liberty of adding a new section "Lie groups" at the manifold metaticket #18528 and put your ticket there. I hope you don't mind.

This is perfectly fine by me, organization is good.

Would it be worth to implement the canonical vector space isomorphism between the two? Would this be useful? A natural way to do it would be via Sage coercions, i.e. implement at least a coercion from L to T1G (the reverse may not work for vectors with symbolic components). If you think this is worth, this could anyway be postponed to another ticket...

Yes this makes sense, and at least the simple coercion should be quite immediate to define, since the basis of the Lie algebra and the basis coming from the exponential coordinate frame are already in direct correspondence as lists.

It might actually also be nice to give T1G the full Lie algebra structure and upgrade the coercion to one of Lie algebras. Since vector fields already have a Lie bracket operation defined, this should be doable through computation with the left-invariant frame. It would also allow computing symbolic Lie brackets.

All of this combined would add up to quite a bit though. I think leaving it to a new ticket would be a good idea.

### comment:20 Changed 9 months ago by tscrim

While having a coercion would be good, as Eric points out, the mismatch of base rings would force the direction of the coercion L -> T1G. However, because coercions have to preserve structure, T1G would have to carry a Lie algebra structure.

Now, this does not have to be a coercion. You could implement the conversions (and have a helper method, such as to_construction_Lie_algebra or some better name) between the two objects. You probably could do this for the tangent space at a generic element since that just corresponds to conjugating by the element.

Also as previously stated, that would be good for a followup ticket.

For this ticket, the only other things I might want to see are a lie_group method on the Lie algebra (a generic one would raise a NotImplementedError or be an @abstract_method(optional=True)) and an exp method on the Lie algebra element that does self.parent().lie_group().exp(self).

### comment:21 Changed 9 months ago by git

• Commit changed from c16d1f1bac71cbf7f6c21f24cd6f9284a43cebe2 to 7607b8c2a0358502c3796410640db3763ee82967

Branch pushed to git repo; I updated commit sha1. New commits:

 ​72700a2 trac #26344: added interface to Lie groups from Lie algebras ​7607b8c trac #26344: added Lie group _name to _repr_

### comment:22 Changed 9 months ago by git

• Commit changed from 7607b8c2a0358502c3796410640db3763ee82967 to d3c77ac7b0569691b39e0c4e4f0b3ec4aefbaad4

Branch pushed to git repo; I updated commit sha1. New commits:

 ​d3c77ac trac #26344: added name parameter to Lie algebra element method exp

### comment:23 follow-up: ↓ 25 Changed 9 months ago by gh-ehaka

I currently set the Lie algebra -> Lie group interface to default to naming the group 'G'. This allows both L.lie_group() and X.exp(), since requiring the name parameter for exp did not seem convenient for use.

Should different(ly named) Lie groups of the same Lie algebra have a predefined canonical coercion between each other? Or should these be considered separate objects? I'm not sure about the intended use of the _name parameter when the underlying manifold is the same. In particular, it is clear that

sage: NilpotentLieGroup(L, 'G') is NilpotentLieGroup(L, 'H')
False


is desired behavior. Would

sage: NilpotentLieGroup(L, 'G') == NilpotentLieGroup(L, 'H')
False


also be desired behavior?

Last edited 9 months ago by gh-ehaka (previous) (diff)

### comment:24 follow-up: ↓ 26 Changed 9 months ago by gh-ehaka

Another question I have is where should the Lie group functionality go in the source and reference? Currently the source is under sage.groups.lie_group.py and the doc is not linked anywhere since I didn't know where to put it.

### comment:25 in reply to: ↑ 23 Changed 9 months ago by egourgoulhon

I currently set the Lie algebra -> Lie group interface to default to naming the group 'G'. This allows both L.lie_group() and X.exp(), since requiring the name parameter for exp did not seem convenient for use.

I would favor passing the Lie group itself and not the string representing its name as the argument of the exp defined in the element methods of LieAlgebras, i.e. something like

def exp(self, lie_group=None):
if not lie_group:
return self.parent().lie_group().exp(self)
return lie_group.exp(self)


This seems more robust to me, especially at this level (element method of all Lie algebras).

Should different(ly named) Lie groups of the same Lie algebra have a predefined canonical coercion between each other? Or should these be considered separate objects? I'm not sure about the intended use of the _name parameter when the underlying manifold is the same. In particular, it is clear that

sage: NilpotentLieGroup(L, 'G') is NilpotentLieGroup(L, 'H')
False


is desired behavior.

Yes.

Would

sage: NilpotentLieGroup(L, 'G') == NilpotentLieGroup(L, 'H')
False


also be desired behavior?

I would say yes as well (this would be in the same vein as what we have for manifolds), but I am not 100% sure this is the best option.

### comment:26 in reply to: ↑ 24 Changed 9 months ago by egourgoulhon

Another question I have is where should the Lie group functionality go in the source and reference? Currently the source is under sage.groups.lie_group.py and the doc is not linked anywhere since I didn't know where to put it.

For the reference manual, there are naturally two options:

since Lie groups lie at the intersection of both topics. Depending on which one you choose, I would rename the source file lie_group.py to nilpotent_lie_group.py and place it in the new subdirectory src/sage/groups/lie_groups or src/sage/manifolds/differentiable/lie_groups.

### comment:27 Changed 9 months ago by tscrim

I would probably put it in with the groups as I think that is there more prominent feature. Moreover, our canonical Lie groups are usually thought of as (matrix) groups, GL, O, U, etc.

### comment:28 Changed 9 months ago by git

• Commit changed from d3c77ac7b0569691b39e0c4e4f0b3ec4aefbaad4 to 383a19aea9a8f5d3541557a0f5b698e9338d3300

Branch pushed to git repo; I updated commit sha1. New commits:

 ​383a19a trac #26344: lie_gps subfolder and doc fixes

### comment:29 Changed 9 months ago by gh-ehaka

I moved the file to sage/groups/lie_gps/nilpotent_lie_group.py. All the other subfolders in groups were *_gps so I copied that folder convention.

I added the subsection of Lie groups to the group reference, but it is slightly disturbing to have a separate "Lie groups" subsection when there is the matrix groups heading containing GL and others. On the other hand, the abstract-manifold Lie group doesn't fit under the "Matrix and affine groups" heading, and extending it to something like "Matrix, affine, and Lie groups" seemed even worse. Hopefully this a problem that will solve itself once more Lie group functionality eventually gets added in so that the "Lie groups" heading feels more fleshed out.

### comment:30 Changed 9 months ago by tscrim

I would argue that this is okay since the other matrix groups do not know they are Lie groups when working over topological fields and also work for a larger class of base fields (e.g., finite fields).

### comment:31 Changed 9 months ago by git

• Commit changed from 383a19aea9a8f5d3541557a0f5b698e9338d3300 to 790338ea26862e2800068d3a3d7fe35db5e9cfbc

Branch pushed to git repo; I updated commit sha1. New commits:

 ​790338e trac #26344: doc tweaks

### comment:32 Changed 9 months ago by gh-ehaka

I changed the docs of NilpotentLieGroup to suggest using the L.lie_group('G') syntax over NilpotentLieGroup(L, 'G'). Having now played around with these for a bit, the former seemed to be the more convenient entry point.

Although now the NilpotentLieGroup entry point is not really even relevant as it does not provide any extra functionality. Possibly it could/should be removed from the global namespace? The only benefit I see to keeping it at this moment is being able to use NilpotentLieGroup? to see some usage examples.

### comment:33 Changed 9 months ago by git

• Commit changed from 790338ea26862e2800068d3a3d7fe35db5e9cfbc to e0d909f408fa28a35a351144fff2fee44dd2aa91

Branch pushed to git repo; I updated commit sha1. New commits:

 ​e0d909f New (sub)catalog of groups.lie, some small code improvements and doc tweaks.

### comment:34 follow-up: ↓ 37 Changed 9 months ago by tscrim

• Reviewers set to Eric Gourgoulhon and Travis Scrimshaw

It probably should be removed, but it should be a new subcatalog groups.lie. So I did this.

I removed the Group.__init__ as that was not doing anything useful.

I cached chart_exp2 and just called that instead of _Exp2.

Other misc doc tweaks.

If my changes are good, then I think we can set a positive review unless Eric has some more comments.

### comment:35 Changed 9 months ago by git

• Commit changed from e0d909f408fa28a35a351144fff2fee44dd2aa91 to b2ef2d9896ca92c139c12781b7d92d2b56397bec

Branch pushed to git repo; I updated commit sha1. New commits:

 ​b2ef2d9 trac #26344: avoid creation of exp2 chart in _repr_ + typo fix

### comment:36 Changed 9 months ago by gh-ehaka

Ah, I didn't know about the groups catalog, this is a very good solution, thanks! I fixed a typo in the catalog and removed the now empty file lie_gps/all.py

I modified the _repr_ method of Lie group elements to avoid unnecessarily calling chart_exp2 in case the default chart is _Exp1, since the computation is quite expensive. Although the current form is not ideal either, since now if the default coordinate chart is something other than chart_exp2 or _Exp1, then it will still unnecessarily compute the transitions to and from exp-2 coordinates. Not sure how to handle this without the _Exp2=None default.

Other than that, the changes look good to me.

Last edited 9 months ago by gh-ehaka (previous) (diff)

### comment:37 in reply to: ↑ 34 Changed 9 months ago by egourgoulhon

If my changes are good, then I think we can set a positive review unless Eric has some more comments.

Everything looks good to me, so I agree with the positive review. Thanks for the nice work!

### comment:38 Changed 9 months ago by egourgoulhon

• Status changed from needs_review to positive_review

### comment:39 Changed 9 months ago by vbraun

• Branch changed from public/groups/nilpotent_lie_groups-26344 to b2ef2d9896ca92c139c12781b7d92d2b56397bec
• Resolution set to fixed
• Status changed from positive_review to closed

### comment:40 Changed 8 months ago by jdemeyer

• Commit b2ef2d9896ca92c139c12781b7d92d2b56397bec deleted
• Reviewers changed from Eric Gourgoulhon and Travis Scrimshaw to Eric Gourgoulhon, Travis Scrimshaw

### comment:41 Changed 8 months ago by embray

• Milestone changed from sage-8.4 to sage-8.5

This should be re-targeted for 8.5.

Note: See TracTickets for help on using tickets.