Skip to main content

lieplusplus_py

Swarm Systems Lab Python lieplusplus library

⚠️ Warning: This project is a work in progress.

Installation

pip install lieplusplus_py

Python Lie++ API Documentation

SO(3) - 3D Rotations

import numpy as np
from lieplusplus import SO3

# --- Constructors ---
R = SO3()                               # Identity rotation
R = SO3([1, 0, 0, 0])                   # Quaternion [w, x, y, z]
R = SO3(np.eye(3))                      # Rotation matrix (3×3)
R = SO3([0.1, 0.2, 0.3])                # Rotation vector (exp map)
R = SO3([1, 0, 0], [0, 1, 0])           # Rotation sending u -> v
R = SO3.random()                        # Random rotation

# --- Group operations ---
R3 = R1 * R2         # Compose rotations
v_rot = R * v        # Rotate a 3-vector
M_rot = R * M        # Rotate a 3×3 matrix

# --- Lie-theoretic operations (static) ---
u  = SO3.log(R)          # R^3 log-coordinate
R2 = SO3.exp(u)          # Exponential map
U  = SO3.wedge(u)        # 3×3 skew-symmetric matrix
u2 = SO3.vee(U)          # Vector from skew matrix
u0 = SO3.tangent_zero()  # Zero element of so(3)

# --- Jacobians ---
J    = SO3.leftJacobian(u)
Jinv = SO3.invLeftJacobian(u)

# --- Accessors ---
R.asMatrix()      # 3×3 rotation matrix
R.Adjoint()       # 3×3 Adjoint matrix
R.inv()           # Inverse rotation
R.q()             # Quaternion [w, x, y, z]

SE(3) - Poses

import numpy as np
from lieplusplus import SE3, SO3

# --- Constructors ---
T = SE3()                                 # Identity pose
T = SE3(R, t)                             # SO(3) + translation vector
T = SE3(np.eye(4))                        # Full 4×4 pose matrix
T = SE3([0.1, 0.2, 0.3], [1, 2, 3])       # Rotation vector + translation
T = SE3.random()                          # Random pose

# --- Group operations ---
T3 = T1 * T2       # Compose poses
p_world = T * p    # Transform a 3D point

# --- Lie-theoretic operations (static) ---
u  = SE3.log(T)        # R^6 tangent vector
T2 = SE3.exp(u)
U  = SE3.wedge(u)
u2 = SE3.vee(U)
u0 = SE3.tangent_zero()

# --- Accessors ---
T.asMatrix()       # 4×4 transformation matrix
T.Adjoint()        # 6×6 Adjoint matrix
T.inv()            # Inverse pose
T.R()              # Rotation (SO3)
T.q()              # Quaternion
T.translation()    # 3D translation vector

SE_2(3) - Extended Poses

import numpy as np
from lieplusplus import SE3_2, SO3

# --- Constructors ---
X = SE3_2()                               # Identity extended pose
X = SE3_2(R, [v, p])                      # Rotation + [velocity, position]
X = SE3_2(R, [np.zeros(3), np.ones(3)])   # Example
X = SE3_2.random()                        # Random extended pose

# --- Group operations ---
X3 = X1 * X2

# --- Lie-theoretic operations (static) ---
u  = SE3_2.log(X)       # R^9 tangent vector
X2 = SE3_2.exp(u)
U  = SE3_2.wedge(u)
u2 = SE3_2.vee(U)
u0 = SE3_2.tangent_zero()

# --- Accessors ---
X.asMatrix()    # 5×5 extended matrix
X.Adjoint()     # 9×9 Adjoint matrix
X.inv()         # Inverse extended pose
X.R()           # Rotation (SO3)
X.v()           # Velocity vector
X.p()           # Position vector

Vectorized operators (batch)

The group classes above are single-element by design: the algebra of one robot rarely needs vectorization. Calling them from Python in a loop is dominated not by the arithmetic (tens of nanoseconds) but by the pybind boundary crossing (~1-1.5 us per call), which a loop over N entities pays N times.

lieplusplus._core.batch runs the same kernels, crossing once per array:

import numpy as np
from lieplusplus._core import batch as B

xi = np.random.normal(size=(1000, 3)) * 0.01
R = B.so3_exp(xi)                 # (1000, 3, 3)
R = B.so3_retract(R, xi)          # R * exp(xi), the integration primitive

Measured on SO(3) retraction: ~3 us/entity for a Python loop vs ~0.04 us/entity batched (~68x at N=1000), and faster than an equivalent vectorized NumPy implementation, since the C++ loop materializes no temporaries.

What the batch API gives you

B.so3_exp(v)                      # (3,)     -> (3, 3)      shape polymorphic:
B.so3_exp(v_stack)                # (N, 3)   -> (N, 3, 3)   one element or a stack

B.so3_retract(one_R, tangents)    # (3,3) x (N,3) -> (N,3,3)   leading axis broadcasts N vs 1
B.so3_retract(R, tangents, out=R) # writes in place, allocating nothing

B.so3_exp(fortran_or_float32_or_list)   # any layout/dtype converts automatically

Shape errors name the operator and the argument:

so3_exp: argument 'x' must have shape (3) or (N, 3), got (4, 5)
so3_retract: leading dimensions do not broadcast, got 4 and 3

Available operators

SO(3) shapes
so3_exp / so3_log (N,3) -> (N,3,3) / (N,3,3) -> (N,3)
so3_inv (N,3,3) -> (N,3,3)
so3_compose (N,3,3) x (N,3,3) -> (N,3,3)
so3_retract (N,3,3) x (N,3) -> (N,3,3)
so3_rotate (N,3,3) x (N,3) -> (N,3)
so3_right_jacobian / so3_inv_right_jacobian (N,3) -> (N,3,3)
SE(3) shapes
se3_exp / se3_log (N,6) -> (N,4,4) / (N,4,4) -> (N,6)
se3_inv (N,4,4) -> (N,4,4)
se3_compose (N,4,4) x (N,4,4) -> (N,4,4)
se3_retract (N,4,4) x (N,6) -> (N,4,4)
se3_transform (N,4,4) x (N,3) -> (N,3)

Vectorizing a new operator

The machinery lives in src/pybind_batch.hpp and is library-agnostic - it only assumes fixed-size Eigen types in and out, so it can be dropped into any pybind11 project. Adding a vectorized operator is one line; shape checking, broadcasting, out=, layout conversion and error messages come for free:

namespace pb = pybind_batch;

pb::def_unary<Vec3d, Matrix3d>(m, "so3_exp",
    [](const Vec3d& u) { return SO3d::exp(u).asMatrix(); }, "Exponential map.");

pb::def_binary<Matrix3d, Vec3d, Matrix3d>(m, "so3_retract",
    [](const Matrix3d& R, const Vec3d& u) { return (SO3d(R) * SO3d::exp(u)).asMatrix(); },
    "Right retraction R*exp(u).");

The element types determine the array shapes: Vec3d means (N, 3), Matrix4d means (N, 4, 4).

Note. batch is a separate namespace from the scalar API on purpose. SO3.exp already accepts (3,) and (3,1) column vectors, so merging the two would make (3,1) ambiguous with a batch of 3. Keeping them apart lets batch dispatch unambiguously on ndim.

License

MIT

Metadata

Release files for lieplusplus-py 0.6.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for lieplusplus-py 0.6.1
File Size Uploaded
lieplusplus_py-0.6.1.tar.gz 86.5 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for lieplusplus-py 0.6.1
File Interpreter ABI Platform
lieplusplus_py-0.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.14 CPython 3.14 Linux glibc 2.28+ x86-64, Linux glibc 2.27+ x86-64 Details
lieplusplus_py-0.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.13 CPython 3.13 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details
lieplusplus_py-0.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl CPython 3.12 CPython 3.12 Linux glibc 2.27+ x86-64, Linux glibc 2.28+ x86-64 Details

Total release size: 6.3 MB

Release files / lieplusplus_py-0.6.1.tar.gz

Download URL lieplusplus_py-0.6.1.tar.gz
Size 86.5 kB
Tags Source
SHA-256 checksum
How to use checksums
3c39d94e82be65132abd931abc0381aca40f9dc121712889ac2448224ee36dd0
BLAKE2b-256 checksum
How to use checksums
498640ca736c2c3267c789829cffac8af28948767d0c465727d45fa5a9acff7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release files / lieplusplus_py-0.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL lieplusplus_py-0.6.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 2.1 MB
Tags CPython 3.14 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
fcb2c2a75e6cb5436aad8dcc9291089b59130470564d52dc152151a8cc5e32e3
BLAKE2b-256 checksum
How to use checksums
537b534d69ce4960503e970f078b79acbed26186d295f72ccdb907d05d04b71d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release files / lieplusplus_py-0.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL lieplusplus_py-0.6.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 2.1 MB
Tags CPython 3.13 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
a7dfb90691150e687f0a1dd99c12d4a228b6d50a92b915827976497223273bde
BLAKE2b-256 checksum
How to use checksums
db01e4df77095ed3a49727f34c328a8e59e2fd0cb4b1ad78b79c4f00d1fadb2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release files / lieplusplus_py-0.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl

Download URL lieplusplus_py-0.6.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Size 2.1 MB
Tags CPython 3.12 Linux glibc 2.27+ x86-64 Linux glibc 2.28+ x86-64
SHA-256 checksum
How to use checksums
8b71231100b6eaa1c0f8a927e89ab46fbe4a87ffb96c0078d2c773dc52602edb
BLAKE2b-256 checksum
How to use checksums
6b7dca84c7449cc14e3d24ee3066c491f68d49fa4659998a078e5b11d4d2ffce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.14.6

Release history Release notifications | RSS feed

0.7.0

4 release files

This release

0.6.1 This release

4 release files

0.6.0

4 release files

0.5.1

4 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page