Note

Click here to download the full example code

# LieTensor Tutorial¶

Uncomment this if you’re using google colab to run this script

```
# !pip install pypose
```

`LieTensor`

is the cornerstone of PyPose project. `LieTensor`

is derived from
`torch.tensor`

. it represents Lie Algebra or Lie Group. It support all
the `torch.tensor`

features and also specific features for Lie Theory.

We will see eventually in this tutorial that, with `LieTensor`

,
one could easily implement operations often used in robotics applications.

In PyPose, we would want to utilize the powerful network training API the comes with PyTorch.
So, we will go a step further to see how we can use `LieTensor`

in training a simple network.

```
import torch
import pypose as pp
```

## 1. Intialization¶

The first thing we need to know is how to initialize a LieTensor.
Use `pypose.LieTensor`

or alias like `pypose.so3`

, specify the `data`

and `ltpye`

.
See list of `ltype`

here.

Note that the last dimension
of `data`

has to align with the `LieTensor.ltype.dimension`

because LieTensor has different length with respect to different `ltype`

.
Here we have a `(2,3)`

shaped tensor, because `so3_type`

requires a dimension of 3 for each element.

It is recommanded to use alias to initialize LieTensor.

```
data = torch.randn(2, 3, requires_grad=True, device='cuda:0')
a = pp.LieTensor(data, ltype=pp.so3_type)
print('a:', a)
b = pp.so3(data)
print('b:', b)
```

```
a: so3Type LieTensor:
LieTensor([[ 0.8837, -0.0328, 0.8041],
[ 1.3325, 1.2731, -0.2630]], device='cuda:0',
grad_fn=<AliasBackward0>)
b: so3Type LieTensor:
LieTensor([[ 0.8837, -0.0328, 0.8041],
[ 1.3325, 1.2731, -0.2630]], device='cuda:0',
grad_fn=<AliasBackward0>)
```

Like `PyTorch`

, you can initialize an identity `LieTensor`

or a random `LieTensor`

.
Use the function related to each `ltype`

. For example, here we used `pypose.identity_SE3`

and `pypose.randn_se3`

. The usage is similar with `torch.randn`

, except the shape we input
is `lshape`

.
The only difference between `LieTensor.lshape`

and `tensor.shape`

is the last dimension is hidden, since
`lshape`

takes the last dimension as a single `ltype`

item.

You might notice the case difference here. In PyPose, uppercase refers to Lie Group, and lowercase refers to Lie Algebra. It is recommanded to use Lie Group, unless Lie Algebra is absolutely necessary.

`LieTensor.lview`

here is used to change the shape of a `LieTensor`

,
similar to `torch.view`

.
The difference is that `LieTensor.lview`

does not modify the last dimension.
It is intuitive since we need each element in `x`

stays a `SE3`

ltype.

```
x = pp.identity_SE3(2,1)
y = pp.randn_se3(2,2)
print('x.shape:', x.shape, '\nx.gshape:', x.lshape)
print(x.lview(2))
print(y)
```

```
x.shape: torch.Size([2, 1, 7])
x.gshape: torch.Size([2, 1])
SE3Type LieTensor:
LieTensor([[0., 0., 0., 0., 0., 0., 1.],
[0., 0., 0., 0., 0., 0., 1.]])
se3Type LieTensor:
LieTensor([[[-0.2927, 0.9180, 0.7799, -0.1386, -0.0364, -0.0509],
[ 0.2092, 0.3220, -2.3106, 0.3449, 0.2309, -0.1783]],
[[-0.5513, 0.1097, 0.0892, -0.3271, 0.2301, 0.3649],
[-2.2766, -1.1630, 0.6606, 0.1979, -0.3437, -0.4147]]])
```

## 2. All arguments in PyTorch are supported¶

`LieTensor`

is derived from `torch.tensor`

, so it inherit all the
attributes of a `tensor`

.
You could specify `device`

, `dtype`

, and `requires_grad`

during the initialization,
just like PyTorch.

```
a = pp.randn_SO3(3, device="cuda:0", dtype=torch.double, requires_grad=True)
b = pp.identity_like(a, device="cpu")
a, b
```

```
(SO3Type LieTensor:
LieTensor([[ 0.4589, 0.1967, -0.4157, 0.7602],
[ 0.3526, -0.0463, 0.4711, 0.8072],
[-0.7619, -0.2284, 0.1162, 0.5949]], device='cuda:0',
dtype=torch.float64, requires_grad=True), SO3Type LieTensor:
LieTensor([[0., 0., 0., 1.],
[0., 0., 0., 1.],
[0., 0., 0., 1.]]))
```

And also, easy data type transform.

```
t = a.float()
a, t
```

```
(SO3Type LieTensor:
LieTensor([[ 0.4589, 0.1967, -0.4157, 0.7602],
[ 0.3526, -0.0463, 0.4711, 0.8072],
[-0.7619, -0.2284, 0.1162, 0.5949]], device='cuda:0',
dtype=torch.float64, requires_grad=True), SO3Type LieTensor:
LieTensor([[ 0.4589, 0.1967, -0.4157, 0.7602],
[ 0.3526, -0.0463, 0.4711, 0.8072],
[-0.7619, -0.2284, 0.1162, 0.5949]], device='cuda:0',
grad_fn=<AliasBackward0>))
```

Slicing and Shaping
`LieTensor`

concatination is also the same as `Pytorch`

.

```
A = pp.randn_SO3(2,2)
B = pp.randn_SO3(2,1)
C = torch.cat([A,B], dim=1) # Tensor cat
C[0,1] = pp.randn_SO3(1) # Slicing set
D = C[1,:].Log() # Slicing get
E, F = torch.split(C, [1,2], dim=1) # Tensor split
print('A:', A.lshape)
print('B:', B.lshape)
print('C:', C.lshape)
print('D:', D.lshape)
print('E:', E.lshape)
print('F:', F.lshape)
```

```
A: torch.Size([2, 2])
B: torch.Size([2, 1])
C: torch.Size([2, 3])
D: torch.Size([3])
E: torch.Size([2, 1])
F: torch.Size([2, 2])
```

## 3. Exponential, Logarithm and Inversion Function¶

`LieTensor.Exp`

is the Exponential function defined in Lie Theory,
which transform a input Lie Algebra to Lie Group.
`LieTensor.Log`

is the Logarithm function, whcih transform Lie Group back to Lie Algebra.
See the doc of
LieTensor.Exp and
LieTensor.Log
for the math.

`LieTensor.Inv`

gives us the inversion of a `LieTensor`

.
Assume you have a `LieTensor`

of `pypose.so3_type`

representing a rotation \({\rm R}\), the Inv will give you \({\rm R^{-1}}\).
See LieTensor.Inv.

```
(x * y.Exp()).Inv().Log()
```

```
se3Type LieTensor:
LieTensor([[[ 0.2927, -0.9180, -0.7799, 0.1386, 0.0364, 0.0509],
[-0.2092, -0.3220, 2.3106, -0.3449, -0.2309, 0.1783]],
[[ 0.5513, -0.1097, -0.0892, 0.3271, -0.2301, -0.3649],
[ 2.2766, 1.1630, -0.6606, -0.1979, 0.3437, 0.4147]]])
```

## 4. Adjoint Transforms¶

We also have adjoint operations. Assume `X`

is a Lie Group,
and `a`

is a small left increment in Lie Algebra.
Adjoint operation will input `a`

and output a right increment `b`

that gives ther same transformation.
See pypose.Adj for more details.

```
X = pp.randn_Sim3(6, dtype=torch.double)
a = pp.randn_sim3(6, dtype=torch.double)
b = X.AdjT(a)
print((X * b.Exp() - a.Exp() * X).abs().mean() < 1e-7)
X = pp.randn_SE3(8)
a = pp.randn_se3(8)
b = X.Adj(a)
print((b.Exp() * X - X * a.Exp()).abs().mean() < 1e-7)
```

```
tensor(True)
tensor(True)
```

## 5. Grdients¶

As mentioned at the beginning, we would want to utilize the powerful
network training API the comes with PyTorch.
We might want to start by calculating gradients,
which is a core step of any network training.
First, we need to initialize the `LieTensor`

of which we want to get gradients.
Remember to set `requires_grad=True`

.

```
x = pp.randn_so3(3, sigma=0.1, requires_grad=True, device="cuda")
assert x.is_leaf
```

And, just like in PyTorch, we will define a `loss`

, and call `loss.backward`

.
That’s it. Exactly the same with PyTorch.

```
loss = (x.Exp().Log()**2).sin().sum() # Just test, No physical meaning
loss.backward()
y = x.detach()
loss, x.grad, x, y
```

```
(tensor(0.0499, device='cuda:0', grad_fn=<SumBackward0>), tensor([[ 0.2864, -0.3138, 0.0028],
[ 0.0393, -0.0210, -0.0566],
[-0.0808, 0.0492, -0.0706]], device='cuda:0'), so3Type LieTensor:
LieTensor([[ 0.1432, -0.1569, 0.0014],
[ 0.0197, -0.0105, -0.0283],
[-0.0404, 0.0246, -0.0353]], device='cuda:0', requires_grad=True), so3Type LieTensor:
LieTensor([[ 0.1432, -0.1569, 0.0014],
[ 0.0197, -0.0105, -0.0283],
[-0.0404, 0.0246, -0.0353]], device='cuda:0'))
```

## 6. Test a Module¶

Now that we know all the basic operations, we might start ahead to build our first network.
First of all, we define our `TestNet`

as follows. Still, it doesn’t have any physical meaning.

```
from torch import nn
def count_parameters(model):
return sum(p.numel() for p in model.parameters() if p.requires_grad)
class TestNet(nn.Module):
def __init__(self, n):
super().__init__()
self.weight = pp.Parameter(pp.randn_so3(n))
def forward(self, x):
return self.weight.Exp() * x
```

Like PyTorch, we instantiate our network, optimizer, and scheduler. Scheduler here is to control the learning rate, see lr_scheduler.MultiStepLR for more detail.

Then, inside the loop, we run our training. If you are not familiar with the training process, we would recommand you reading one of the PyTorch tutorial, like this.

```
n,epoch = 4, 5
net = TestNet(n).cuda()
optimizer = torch.optim.SGD(net.parameters(), lr = 0.2, momentum=0.9)
scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[2,4], gamma=0.5)
print("Before Optimization:\n", net.weight)
for i in range(epoch):
optimizer.zero_grad()
inputs = pp.randn_SO3(n).cuda()
outputs = net(inputs)
loss = outputs.abs().sum()
loss.backward()
optimizer.step()
scheduler.step()
print(loss)
print("Parameter:", count_parameters(net))
print("After Optimization:\n", net.weight)
```

```
Before Optimization:
so3Type Parameter:
Parameter containing:
Parameter(Parameter([[ 0.7052, 0.2846, 0.1662],
[ 0.6597, 0.1625, -1.1947],
[-0.6071, 0.4234, -0.1463],
[ 0.6531, 0.0366, -0.3926]], device='cuda:0', requires_grad=True))
tensor(6.7154, device='cuda:0', grad_fn=<SumBackward0>)
tensor(6.2863, device='cuda:0', grad_fn=<SumBackward0>)
tensor(6.4192, device='cuda:0', grad_fn=<SumBackward0>)
tensor(6.3144, device='cuda:0', grad_fn=<SumBackward0>)
tensor(5.8411, device='cuda:0', grad_fn=<SumBackward0>)
Parameter: 12
After Optimization:
so3Type Parameter:
Parameter containing:
Parameter(Parameter([[-0.6589, 0.1844, 0.0655],
[-0.5309, 0.1576, 0.0722],
[-0.0647, -0.2265, 0.0657],
[ 0.2051, 0.1157, 0.3390]], device='cuda:0', requires_grad=True))
```

And then we are finished with our `LieTensor`

tutorial.
Hopefully you are more familiar with it by now.

Now you may be free to explore other tutorials. See How PyPose can be utilized in real robotics applications.

**Total running time of the script:** ( 0 minutes 0.075 seconds)