Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tutorial on how to SE2 #366

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
205 changes: 205 additions & 0 deletions docs/src/examples/howtorigidtransforms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
# [How to do Rigid Transforms](@id how_to_do_rigid_transforms)

This tutorial follows the previous tutorial [How to work with Rotations](@ref how_to_work_with_rotations). The examples aim to demonstrate how familiar rigid body transformations in 2D can be achieved with the JuliaManifolds packages. The previous tutorial showed how to use the `SpecialOrthogonal(2)` manifold by itself. This tutorial shows how the `ProductManifold` of `Translation` with `Rotations` is combined into a predefined [`Manifolds.SpecialEuclidean`](@ref).

The rigid transforms examples discussed here are in the same spirit as packages such as [CoordinateTransformations.jl](https://github.com/JuliaGeometry/CoordinateTransformations.jl). Manifolds.jl instead builds coordinate transformations from the ground up using fundamental abstractions about generalized manifolds. The `SpecialEuclidean(2)` group manifold used in this tutorial is one specific manifold, which is the product of two underlying manifolds.

!!! note
While many of the operations listed below at first seem excessive for basic rigid transform operations, this is not the case. Once the data formats and representations are setup, the actual `compose` and `apply` operations are super fast and syntactically easy. Significant efforts have gone into defining a general framework for manifold operations that can go beyond operations such as Riemannian geometry.

## Rigid Transforms Setup

Let's load load some packages first
```julia
using Manifolds
using LinearAlgebra
using StaticArrays
```

Consider a combination of translation with a rotation of some avatar or axis-dyad on the xy-plane, where each location (i.e. the combo or position and rotation) which will be represented as some point `p` on a group `G` manifold `M`. This is indicated by:
```julia
# xy dimension in this case is 2
n = 2
# the group manifold also known as SE(2)
G = SpecialEuclidean(n)
M = base_manifold(G)
# can separately take the Translation and Rotations manifolds within M
M_T = M[1]
M_R = M[2]
```

Next, define the default basis to use later and also a starting reference rotation and translation
```julia
B = DefaultOrthogonalBasis()
# choose the identity reference translation and rotation
Tr0, R0 = SA[0.0; 0], SA[1.0 0; 0 1]
```

To structure the tutorial, let's "walk" the perimeter of a rectangle, passing through various points on the manifold. To do so, we need to understand how coordinates, vectors in the Lie algebra, and group elements are used.

## From Coordinates, SE(2)

The coordinates are easily readible / sharable numbers that we humans frequently stack together as a vector. In this case, the three degrees of freedom representing a transformation, namely `[Δx; Δy; Δθ]`. To reduce the notational load, this example will drop the `Δ` and simply state `[x;y;θ]` as the transform coordinates (i.e. a point on the `SpecialEuclidean(2)` manifold, but not yet in the group data representation format).

First, define the individual manifold tangent vectors (translation, rotation) of the identity element coordinates
```julia
t0, w0 = [0.0; 0], get_vector(M_R, R0, 0, B)
```

!!! note
The `hat` notation could also be used in this case:
```julia
_t0, _w0 = [0.0; 0], hat(M_R, R0, 0)
@assert isapprox(t0, _t0); @assert isapprox(w0, _w0)
```

Next, define delta elements that each represent a certain "rigid body transform" so that the rectangular path can be completed. Starting from the "origin" (i.e. reference point) above, we define relative segments between points using the coordinates and we will `compose` these segments together in an upcoming section below:
```julia
# walk forward on side x and turn left
t1a, w1a = [1.0; 0], get_vector(M_R, R0, 0 , B)
t1b, w1b = [1.0; 0], get_vector(M_R, R0, π/2, B)

# walk positive y and turn left again
t2, w2 = [1.0; 0], get_vector(M_R, R0, π/2, B)

# walk negative x and turn left
t3, w3 = [2.0; 0], get_vector(M_R, R0, π/2, B)

# walk negative y back onto origin and turn left
t4, w4 = [1.0; 0], get_vector(M_R, R0, π/2, B)
```

Notice that each of these coordinates are **not** defined against the origin, but are fully relative translations and rotations.

Before we can compose these separate translation and rotation pairs, we need to leverage some pairing data structure by which Manifolds.jl can identify a Product manifold operation is needed. There are currently two ways to do so. The `compose` hereafter can work with either the `ProductRepr` or `ProductArray` representations.

### ProductRepr for SpecialEuclidean(2)

Let's first define the identity group element on the manifold directly:
```julia
p0 = ProductRepr(Tr0, R0)
```

!!! note
Using the language or reference frames, this is the identity reference to which other points will be defined. For example, consider measuring various points relative to a common "reference frame", then `p0` might represent that reference point in-and-about the other points on the same `SpecialEuclidean(2)` manifold.

Above, we defined individual tranlate and rotate vectors such as `t2,w2`. The next step towards using `compose` for rigid body transformations is to properly pair (i.e. data structuring) the Lie algebra elements and then map to the equivalent Lie group element on the group manifold `G` or `M`.

The trivial case is a zero vector from the point `p0`
```julia
# make Lie Algebra element
x0 = ProductRepr(t0, w0)

p0_ = exp(G, p0, x0) # or, exp(M, p0, x0)
```

The exponential maps the Lie algebra to the associated Lie group, but in this trivial zero-vector case should be exactly the same point on the manifold
```julia
@assert isapprox(p0_.parts[1], p0.parts[1]); @assert isapprox(p0_.parts[2], p0.parts[2])
```

All the other relative vector segments are mapped onto the manifold, and carefully not relative to the point `p0` in each case -- we will soon show why
```julia
# calculate the exponential mapping from point p0
x1a = ProductRepr(t1a, w1a) # Lie algebra element
p1a = exp(G, p0, x1a) # Lie group element

x1b = ProductRepr(t1b, w1b)
p1b = exp(G, p0, x1b)

x2 = ProductRepr(t2, w2)
p2 = exp(G, p0, x2)

x3 = ProductRepr(t3, w3)
p3 = exp(G, p0, x3)

x4 = ProductRepr(t4, w4)
p4 = exp(G, p0, x4)
```

If you want to continue using `ProductRepr` then skip ahead to [Compose Rigid Transforms, 2D](@ref compose_rigid_transforms_2d).

### ProductArray for SpecialEuclidean(2)

As mentioned earlier, there are two data representation formats available for product manifolds. This paragraph will repeat the same steps as the `ProductRepr` paragraph above, but using the `ProductArray` representation instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've done some work on a new power manifold representation which should make ProductArray essentially obsolete: #367 . What do you think? That new representation should offer comparable performance and be easier to use, at the price of having to implement non-mutating variants of some operations on manifolds separately from the mutating ones.

Copy link
Contributor Author

@dehann dehann May 26, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can definitely change the text on this page to whatever is best for Manifolds.jl and to keep the math rigor / utility. I'm 100% good with that :-) Thanks for taking the time to help! I did find ProductRepr a little easier at first, since the shape definitions added another layer to learn on day one.

Perhaps it is worth saying what I struggled with in the beginning, which is why I drafted these pages as they are/were. I was initially very confused trying to pick apart the differences between:

  • Group vs. base_manifold( of the Group ), which do I use because sometimes I get an error, other times not...
  • ProductRepr vs ProductArray, are these the same, is one a coordinate, do they work the same on all Manifolds...
  • What is a ShapeSpecification, and should I worry about Static...
  • What's up with hat vs get_vector...
  • is vee (where textbooks say 'vectorize') the same as get_vector...
  • does get_coordinates take either group or vector (i.e. algebra) elements, and should I always write that myself...
  • are 'coordinates' the same thing as a 'vector' and what does that have to do with shape, and how much does Julia's own ::Array get used for dispatch...
  • if you call exp, does the code know whether you are giving it coordinates or a algebra element...
  • What does Manifolds.jl even define as 'coordinates', I guess it has something to do with the DefaultBasis, maybe...

So hopefully these two new tutorials will help newcomers breeze through these (perhaps silly first order) questions, and quickly get to the harder stuff like "how do we implement and PR a new dispatch for this or that enhancement".

The reason I listed both representations is simply to help others get through the learning curve faster, and help contribute the commits needed towards a consolidated solution. Until a clear design template and "Julian" philosophy about which representation is "better" emerges, I think it will be difficult to say option 'A' vs. option '1' is better. The wide open aperture about data types in the representations (coordinates, vectors, groups) makes this a little harder to pin down in the early days.

I don't yet know the implementation details well enough to see how / where the differences between ProductRepr and ProductArray manifest. I might be a bit more pragmatic in this sense and happy to work with what's available without obstructing progress. In utopia, yes only a single one-size-fits-all representation would be nice. I hope to help contribute some code as I learn more, but that is a longer term thing.

So my suggestion then is perhaps we should move the ProductArray paragraph of this tutorial right to the very end of the page, and simply point out "please see the end of this page for an earlier alternative ProductArray data representation". The ProductArray representation is likely already in use. My feeling is the community will ultimately adopt one approach over the other, and most importantly the rigor/utility should not be lost. Good design and clean-up will result in fast code, but it has to be human readable so that a sizable audience can share the burdens. I think the risk now is the community adopts the "easier" vs the "better" approach. It's probably too soon to try force a deprecation any which way.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to help!

You're welcome 🙂 . I'm happy that you find Manifolds.jl useful.

I did find ProductRepr a little easier at first, since the shape definitions added another layer to learn on day one.

I'm really not satisfied with the design ProductArray, and I'm almost sure that we can make ProductRepr cover all possible use cases for product manifolds, so I'd suggest just reporting problems with that approach and not using ProductArray at all.

  • Group vs. base_manifold( of the Group ), which do I use because sometimes I get an error, other times not...

Calling base_manifold should rarely be necessary. Feel free to report any cases where calling an operation on base_manifold works but directly on a group it does not.

  • ProductRepr vs ProductArray, are these the same, is one a coordinate, do they work the same on all Manifolds...

I think you should just stop using ProductArray. It was an idea I had when I doubted ProductRepr could work with broadcasting and power manifolds, but now ProductRepr does work there.

  • What is a ShapeSpecification, and should I worry about Static...

Same as above, you shouldn't use ShapeSpecification.

  • What's up with hat vs get_vector...
  • is vee (where textbooks say 'vectorize') the same as get_vector...

vee and hat were supposed to be aliases to certain variants of get_coordinates and get_vector to make people familiar with them more comfortable but it doesn't seem to work this way... Personally, I never use hat and vee.

  • does get_coordinates take either group or vector (i.e. algebra) elements, and should I always write that myself...

get_coordinates actually takes both a group element and an algebra element. For groups where tangent vectors are represented as tangents at the identity element of the group, it does not matter what point you give there. But note that get_coordinates originates in differential geometry, not Lie theory, and as such it needs to know which point the vector is tangent at.

  • are 'coordinates' the same thing as a 'vector' and what does that have to do with shape, and how much does Julia's own ::Array get used for dispatch...

I don't quite understand the first part of the question, could you rephrase it? I guess it might be confusing that get_coordinates returns a ::Vector (or, sometimes SVector), but the thing you get there is the vector of coordinates, while get_vector returns a tangent vector (or a vector from a fibre of some other vector bundle but maybe let's leave that for another time -- I only mention it to explain why it isn't called get_tangent_vector).

I think we almost never dispatch on Julia's Array, though in many cases we dispatch on AbstractArray.

  • if you call exp, does the code know whether you are giving it coordinates or a algebra element...

exp always expects an element of the tangent space (so an algebra element), it may even silently give a wrong result if given coordinates. We have, though, ValidationManifold for verifying that you give correct arguments to functions in your algorithms.

  • What does Manifolds.jl even define as 'coordinates', I guess it has something to do with the DefaultBasis, maybe...

This might be a decent explanation (by @kellertuer 🙂 ): https://www.youtube.com/watch?v=md-FnDGCh9M&t=1060s . 'Coordinates' in Manifolds.jl generally refer to coordinates of a tangent vector (or f.e. cotangent vector also) in a basis, like in basic linear algebra: https://www.math.tamu.edu/~yvorobet/MATH304-2011A/Lect2-07web.pdf . The catch here is, while you can do lots of linear algebra ignoring the (often stated without appropriate explanation) distinction between elements of vector spaces and their coordinates with respect to a basis, the distinction is absolutely necessary in differential geometry. In Manifolds.jl we usually store tangent vectors as numbers that do not correspond to coefficient in any basis. This probably still needs some better explanation in docs.

So hopefully these two new tutorials will help newcomers breeze through these (perhaps silly first order) questions,

I think these questions are quite good actually, and show the deficiencies in our documentation. I'm not sure, though, where to put some of these answers, for example these regarding coordinates.

The reason I listed both representations is simply to help others get through the learning curve faster, and help contribute the commits needed towards a consolidated solution. Until a clear design template and "Julian" philosophy about which representation is "better" emerges, I think it will be difficult to say option 'A' vs. option '1' is better. The wide open aperture about data types in the representations (coordinates, vectors, groups) makes this a little harder to pin down in the early days.

I don't yet know the implementation details well enough to see how / where the differences between ProductRepr and ProductArray manifest. I might be a bit more pragmatic in this sense and happy to work with what's available without obstructing progress. In utopia, yes only a single one-size-fits-all representation would be nice. I hope to help contribute some code as I learn more, but that is a longer term thing.

So my suggestion then is perhaps we should move the ProductArray paragraph of this tutorial right to the very end of the page, and simply point out "please see the end of this page for an earlier alternative ProductArray data representation". The ProductArray representation is likely already in use. My feeling is the community will ultimately adopt one approach over the other, and most importantly the rigor/utility should not be lost. Good design and clean-up will result in fast code, but it has to be human readable so that a sizable audience can share the burdens. I think the risk now is the community adopts the "easier" vs the "better" approach. It's probably too soon to try force a deprecation any which way.

I'm currently 99% sure ProductArray should be just removed, and maybe I should've pulled the plug earlier. One particular breakthrough in this area is quite recent but I still feel a little embarrassed that you had to go through ProductArray. ProductRepr is definitely easier, and with the recent changes I'm certain it can also always be as fast, if not faster, than ProductArray.

The one thing that remains to be done is changing "can be" to "is" in the last sentence, which requires subverting a little our approach to non-mutating functions -- specifically, we will need to explicitly write some non-mutating methods instead of relying on allocation and mutating variants. I'll write more about it separately, the key thing here is that you should report any performance issues that may be caused by Manifolds.jl -- I'm aware of a lot of places where performance could be improved but I have limited time so I can't fix every potential issue 🙂 .

Finally, thank you for your comments! Also, feel to ask them more often, after reading them it looks like you haven't been asking enough questions when working through Manifolds.jl 🙂 . In any case I and Ronny are available at Julia's slack and zulip if you want some real-time conversation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uh! Neat, that even my talk from last year was linked; now you now how I sound and how much German accent my English has ;)

I looked a bit at vee and hat (cf. https://en.wikipedia.org/wiki/3D_rotation_group#Lie_algebra) and it seems that there is one confusion with your question

  • is vee (where textbooks say 'vectorize') the same as get_vector...

First of all – as far as I found hat/vee are used only with rotations and not even with Lie groups in general.

If I understand the wikipedia article corretcly; the hat function takes a set of coordinates $\omega$ and returns an alement from the Lie algebra. So with the identification of every tangent space hat reconstructs a vector from given coefficients in a basis, so hat=get_vector (to be precise https://github.com/JuliaManifolds/ManifoldsBase.jl/blob/194e27e985292e5e1685cf10e7bf27d4af40b9f7/src/bases.jl#L788).
On the other hand, vee(cf also https://juliamanifolds.github.io/Manifolds.jl/stable/interface.html#ManifoldsBase.vee-Tuple{AbstractManifold,Any,Any}) maps a vector that is given in a certain basis (note the Einstein summation on the left) to the coefficients, so it is vee=get_coordinates for the special case of using the VeeOrthogonalBasis (this is also the basis for hats get_vector by the way) … see the one code line https://github.com/JuliaManifolds/ManifoldsBase.jl/blob/194e27e985292e5e1685cf10e7bf27d4af40b9f7/src/bases.jl#L910).
We should maybe be more precise in this description in vee/hat, sure. My main reason for not having done that yet is, that I never used them until now.

Similarly to

  • does get_coordinates take either group or vector (i.e. algebra) elements, and should I always write that myself...

it takes a tangent vector (Lie algebra element) and returns coefficients (coordinates wrt that basis).

Copy link
Contributor Author

@dehann dehann May 27, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling base_manifold should rarely be necessary. Feel free to report any cases where calling an operation on base_manifold works but directly on a group it does not.

Ah, that helps thanks!

vee and hat were supposed to be aliases to certain variants of get_coordinates and get_vector to make people familiar with them more comfortable but it doesn't seem to work this way... Personally, I never use hat and vee.

Thanks, kellertuer also mentioned that. Hopefully 355 and 366 help newcomers through that faster than I was able to.

get_coordinates actually takes both a group element and an algebra element. For groups where tangent vectors are represented as tangents at the identity element of the group, it does not matter what point you give there. But note that get_coordinates originates in differential geometry, not Lie theory, and as such it needs to know which point the vector is tangent at

Is this written down anywhere in the current docs, I think that should be linked from these tutorial pages -- this is a good piece of info, thanks!

I don't quite understand the first part of the question, could you rephrase it? I guess it might be confusing that get_coordinates returns a ::Vector (or, sometimes SVector), but the thing you get there is the vector of coordinates, while get_vector returns a tangent vector (or a vector from a fibre of some other vector bundle but maybe let's leave that for another time -- I only mention it to explain why it isn't called get_tangent_vector).

Oh all good, that bullet point list above is just a quick list of problems I was not able to easiliy resolve from the documentation when I started. Hence those bullet points are specific things I'm trying to answer with these two new tutorials 355 and 366.

We have, though, ValidationManifold for verifying that you give correct arguments to functions in your algorithms.

  • Ah, should add that in these two new pages somewhere too.

This might be a decent explanation (by kellertuer slightly_smiling_face ):

  • ha, should definitely link that in these docs too. I'm taking this as an opportunity to boil down and capture all the conversations we've had in the past months into the two new shorter tutorials.

I think these questions are quite good actually, and show the deficiencies in our documentation. I'm not sure, though, where to put some of these answers, for example these regarding coordinates.

Great, great, let me chew on where to fit more of these details into the tutorials. I think most of this latest volley of notes should either be linked out to elsewhere at the right place. It should also be okay to add a paragraph or two for additional details.

I think these two new "tutorials" (355 and 366) are all thats needed at this stage. They are specifically about the end-to-end usage of two well known manifolds for newcomers at varying levels of prior knowledge. So the documentation criteria for me on these two pages is to rapidly learn the conventions, with basic do's and don'ts. The additional step then is a reader will either want verification of details if they are an expert (i.e. links to particular functions) and which headings are actually listed here. Another reader might first want to figure out the relation between for e.g. exp and retraction (as we spoke through last week).

I'm currently 99% sure ProductArray should be just removed, and maybe I should've pulled the plug earlier. One particular breakthrough in this area is quite recent but I still feel a little embarrassed that you had to go through ProductArray. ProductRepr is definitely easier, and with the recent changes I'm certain it can also always be as fast, if not faster, than ProductArray.

oh all good, Manifolds.jl is awesome! You guys have been a great help! Just moving to ProductRepr sounds good to me, and then focus effort on building out the required dispatches. I don't know enough of the internals just yet, but I'm slowly getting there.

you should report any performance issues that may be caused by Manifolds.jl -- I'm aware of a lot of places where performance could be improved but I have limited time so I can't fix every potential issue slightly_smiling_face .

All good thanks, we are likely to stumble into a few and have many performance related things to resolve too. Will help however best I can as things come up, at least just document.

If I understand the wikipedia article corretcly; the hat function takes a set of coordinates $\omega$ and returns an alement from the Lie algebra. So with the identification of every tangent space hat reconstructs a vector from given coefficients in a basis, so hat=get_vector

That's how I understand it now too. But not at all how I understood things at first -- I was trudging through runtests.jl to try understand how to do the end-to-end usage. It's much clearer now :-) And the problem I think is really just that first usage example, rather than a listing of function features. It's fine if you know which functions you want, but the problem is which one of the 100's is it... etc. So hopefully 355 and 366 helps.


!!! note
`ProductArray` might seem a bit more laborious than `ProductRepr` at first, but the increased structure allows for faster implementation details within the Manifolds.jl ecosystem.

The underlying data is represented according to a shape specification
```julia
reshaper = Manifolds.StaticReshaper()
shape_G = Manifolds.ShapeSpecification(reshaper, M)
shape_se = Manifolds.ShapeSpecification(reshaper, M.manifolds...)
```

And the associated Lie group elements (still `SpecialEuclidean(2)`) are mapped from the vector elements above
```julia
p0 = Manifolds.prod_point(shape_se, (t0,exp(M_R, R0, w0))... )
p1a = Manifolds.prod_point(shape_se, (t1a,exp(M_R, R0, w1a))... )
p1b = Manifolds.prod_point(shape_se, (t1b,exp(M_R, R0, w1b))... )
p2 = Manifolds.prod_point(shape_se, (t2,exp(M_R, R0, w2))... )
p3 = Manifolds.prod_point(shape_se, (t3,exp(M_R, R0, w3))... )
p4 = Manifolds.prod_point(shape_se, (t4,exp(M_R, R0, w4))... )
```

Just as with the `ProductRepr` approach, each of the points, e.g. `p2`, represent a relative rigid transform -- note again that the exponential map was done relative to the choosen identity `R0`. The identity point for translations `M_T` are trivial, i.e. `[0;0]`, and therefore not elaborated as is done with the Rotations under `M_R`.

## [Compose Rigid Transforms, 2D](@id compose_rigid_transforms_2d)

In this section all the details from above are brought together via the action `compose`. Recall the objective for this tutorial is to walk around a rectangle using the `Manifolds.SpecialEuclidean(2)` mechanizations and abstractions. Note that either the `ProductRepr` or `ProductArray` representations will dispatch the same for the code below, but the internal data representation formats will differ.

The fine grain details in this section are perhaps not as straight forward as they first appear, specifically how relative transforms (which are points on the manifold `SpecialEuclidean(2)`) relate to the consecutive positions of the walked rectangle (which are also points on the manifold `SpecialEuclidean(2)`).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can achieve a similar result using translate or apply for GroupOperationAction. Especially the last one may be helpful in expressing qualitative difference between the elements of SE(2) corresponding to positions, and elements corresponding to transforms.


```julia
## Consecutively compose the "rigid transforms" (i.e. points) together around a rectangle

p_01a = compose(G, p0, p1a)
p_01b = compose(G, p_01a, p1b)
p_012 = compose(G, p_01b, p2)
p_0123 = compose(G, p_012, p3)
p_01234 = compose(G, p_0123, p4)
```

Staring from the choosen origin `p0` and looking in the direction along the choosen x-axis (i.e. `θ = 0`), walk forwards 1.0 units towards a new point `p_01a`. This relative transform from `p0` on the manifold to the new point `p_01a` on the manifold is captured in the group element `p1a` (also `SpecialEuclidean(2)`).

Next, walk from `p_01a` to `p_01b` according to the relative transformation captured by the group element `p1b` which is again forwards 1.0 units, but also followed by a positive rotation of `π/2` radians. Taking the default basis `B` as an xy-tangent plane, we understand a positive rotation according to the right-hand-rule as going from the local x-axis towards the y-axis.

!!! note
As a sneak peak, we expect this new point `p_01b` in human readible coordinates to be at `[2;0;π/2]` relative to the choosen reference point `p0`. We will show and confirm this computation below.

It is worth reiterating that the relative group actions `p1a` and `p1b` are Lie group elements that were constructed from local Lie algebra vectors using `p0` in each case. Here, during the `compose` operation those "relative transforms" are chained together (i.e. composed) into new (totally separate) points `p_01a, p_01b` on the `SpecialEuclidean(2)` manifold. Our cartoon interpretation of walking around a rectangle mentally easiest when operating relative to the starting point `p0`. The full use of Manifolds.jl goes well beyond this "rigid transform" construct.

To complete the walk-about, we compose `p2` onto the previous to get a new point `p_012` which is now the opposite corner of the rectangle. Two more consecutive "transforms" `p3, p4` complete the rectangle through points `p_0123` and `p_02134`, respectively, and each time turning `π/2` radians. We therefore expect point `p_01234` to exactly the same as the reference starting point `p0`.

## To Coordinates, SE(2)

Thus far, we converted user inputs from a convenient coordinate represenation into two computational representations (either `ProductRepr` or `ProductArray`). The next step is to be able to convert back from the manifold data representations back to coordinates. In this example, we first take the logmap of a group element in `SpecialEuclidean(2)` to get the associated Lie algebra element, and then extract the coordinates from that vector, and lets do so for the point `p_01` as elluded to above
```julia
x_01b_ = get_coordinates(G, p0, log(G, p0, p_01b), B)

# check the coordinates are as expected
@assert isapprox( x_01b_[1:2], [2;0], atol = 1e-10 )
@assert isapprox( abs(x_01b_[3]), π/2 , atol = 1e-10 )
```

Notice that both the `log` and `get_coordinates` operations are performed relative to our choosen reference point `p0`. These operations therefore are extracting the tangent space vector (and it's coordinates) relative to the origin. If we were to extract the vector coordinates of `p_01b` relative to `p_01a`, we would expect to get the same numerical coordinate values used to construction the relative transform `p1b`.
dehann marked this conversation as resolved.
Show resolved Hide resolved

Similarly, we check that the opposite corner of the walked rectangle is as we expect
```julia
x_012_ = get_coordinates(G, p0, log(G, p0, p_012), B)
@assert isapprox( x_012_[1:2], [2;1], atol = 1e-10)
@assert isapprox( abs(x_012_[3]), π, atol = 1e-10 )
```

And also the past point on the path `p_01234` lands back on the reference point `p0` as expected
```julia
x_01234_ = get_coordinates(G, p0, log(G, p0, p_01234), B)
@assert isapprox( x_01234_, [0;0;0], atol = 1e-10 )
```

This concludes the tutorial with emphasis on functions:
- `get_vector` / `hat`,
- `exp` vs. `log`,
- `get_coordinates` / `vee`, and
- `compose`.