explainer
Adjoint transformations: express a twist in another frame
Transform angular-first twists between body and space frames. Derive the origin-shift term, distinguish linear twist coordinates from point velocity, and check a planar example with an interactive adjoint matrix.
What you will learn
- Transform a twist using an explicit frame direction and angular-first coordinate order.
- Explain how the reference origin changes the linear twist coordinates.
- Recover a point velocity from a spatial twist and verify the body-space relation.
- Check inverse transformations, composition order, and the dual power pairing.
Before you start
A robot's body origin moves upward at 1 meter per second. Its spatial twist can still have linear coordinates (0.5, 0) meters per second. Those numbers describe the same motion at different reference origins.
The adjoint transformation converts a twist between frames. It accounts for the frame rotation and the origin shift. You will derive that shift, calculate a complete planar example, and recover the velocity of the body origin from the spatial twist.
State the frame direction and coordinate order
Let T_ab = (R, p) map coordinates in frame b into frame a. R rotates b-coordinate vectors into a coordinates. The vector p gives the origin of b in frame a, so a point obeys q_a = Rq_b + p. The coordinate frames lesson develops this convention.
Use right-handed orthonormal frames and angular-first twists:
V = (ω, v)
V = (ωₓ, ωᵧ, ω_z, vₓ, vᵧ, v_z)
Angular velocity uses radians per second; v uses meters per second. Both parts use the axes of the same reference frame. The planar experiment keeps only (ω_z, vₓ, vᵧ), with +z pointing out of the page.
Coordinate order matters. A source that places translation first has a different block layout for its adjoint matrix. State the order before copying a formula or passing a six-vector between software libraries.
Read a twist as a velocity field
A rigid motion defines an instantaneous velocity at every spatial location. In a chosen reference frame, the twist V = (ω, v) gives this field:
u(q) = ω × q + v
At q = 0, the field value is v. At another point, the rotational term changes the velocity. Modern Robotics introduces twists using this dependence on the reference origin.
For a material point of the rigid body, u(q) is its physical velocity. The formula also extends the rigid-motion field to locations outside the body's material. Evaluating it at a fixed world origin does not mean that the world frame moves.
A point attached to frame b keeps constant b-coordinates even when its physical velocity is nonzero. The field expresses that physical motion in the chosen axes; it is not the time derivative of coordinates measured in a moving frame.
For example, consider rotation about a stationary body origin p = (2, 1) m at ω_z = 0.5 rad/s. The space-frame linear coordinate is v_s = −ω_s × p = (0.5, −1) m/s. At the body origin, ω_s × p + v_s = 0, as required.
Derive the adjoint transformation
The same physical velocity vector changes axes by rotation. Substitute q_b = Rᵀ(q_a − p) into R(ω_b × q_b + v_b). Proper rotations preserve cross products, giving:
u_a(q_a) = (Rω_b) × (q_a − p) + Rv_b
ω_a = Rω_b
v_a = Rv_b + p × (Rω_b)
The last term moves the reference origin. Its sign follows from the cross product: −ω_a × p = p × ω_a.
Define p̂ by p̂x = p × x. The equations become the six-dimensional adjoint representation:
V_a = Ad_T_ab V_b
Ad_T =
[ R, 0 ]
[ p̂R, R ]
Each displayed block is 3 × 3. The rotation blocks are dimensionless; the lower-left block carries the length scale that converts angular velocity into a linear contribution.
Ethan Eade derives the same origin-shift term by matrix conjugation. His SE(3) coordinates put translation first, so his blocks appear in a different order. With the hat notation used here, the coordinate-independent relation is V̂_a = T_ab V̂_b T_ab⁻¹.
Connect body and space twists to a moving pose
Now let T = T_sb(t) describe a moving body frame b relative to a fixed space frame s. Its derivative Ṫ is a tangent to the pose trajectory. Multiplying by the inverse on different sides gives two Lie-algebra matrices:
V̂_b = T⁻¹Ṫ
V̂_s = ṪT⁻¹
V_s = Ad_T V_b
The last identity follows by substituting Ṫ = TV̂_b. Modern Robotics gives these body and spatial matrix representations.
Looking at the translation columns makes the distinction concrete:
v_b = Rᵀṗ
v_s = ṗ − ω_s × p
ṗ = Rv_b = v_s + ω_s × p
Thus Rv_b gives the body's origin velocity in space coordinates. The spatial linear coordinate v_s also includes the reference-origin adjustment. Re-expressing a twist does not compute the relative motion of two independently moving bodies; that would require their motion information too.
Calculate an origin shift in the plane
Take a body heading of 90°, origin p = (2, 1) m, and body twist V_b = (0.5, 1, 0). The first entry is rad/s; the last two are m/s. This 90° rotation maps (x, y) to (−y, x).
First rotate the body linear coordinates: Rv_b = (0, 1) m/s. Next calculate p × (0, 0, 0.5) = (0.5, −1, 0) m/s. Adding the planar parts gives:
V_s = (0.5, 0.5, 0)
ṗ = (0.5, 0) + (−0.5, 1) = (0, 1) m/s
The planar restriction of the six-dimensional adjoint uses the order (ω_z, vₓ, vᵧ):
Ad_T =
[ 1, 0, 0 ]
[ pᵧ, cos θ, −sin θ ]
[ −pₓ, sin θ, cos θ ]
Ad_T for this pose =
[ 1, 0, 0 ]
[ 1, 0, −1 ]
[ −2, 1, 0 ]
Multiplying that matrix by (0.5, 1, 0) reproduces (0.5, 0.5, 0). A rotate-only calculation would report v_s = (0, 1), an error of √1.25 ≈ 1.118034 m/s in the linear coordinates.
Compare the two origins and their velocities
The amber arrow shows v_s at the space origin. The black arrow shows the actual body-origin velocity at p. Blue dashes show the rotate-only result placed at the space origin for comparison.
Try Spin about body origin. The body origin stays still, while the spatial linear coordinate remains nonzero. Then try Coincident origins or Pure translation; either removes the origin-shift term in this planar model.
The diagram shows an instant of motion. Its arrows use one second as a display scale so their lengths fit the position axes. A finite step along an instantaneous velocity generally differs from the trajectory of a rotating body.
Reverse and compose adjoint transformations
To recover body coordinates, use the inverse pose with the reverse frame direction:
V_b = Ad_(T_ab⁻¹) V_a
Ad_(T_ab⁻¹) = (Ad_T_ab)⁻¹
Ad_(T_ab T_bc) = Ad_T_ab Ad_T_bc
These identities follow from conjugation: apply T_bc around the tangent matrix, then T_ab. The order matches homogeneous transformation composition.
For the worked example, subtract the shift from v_s to get (0, 1), then rotate by −90° to recover v_b = (1, 0). The angular coordinate remains 0.5 rad/s.
The adjoint is linear in the twist for a fixed pose. It is generally not an orthogonal matrix, and a twist's raw Euclidean coordinate norm need not stay constant across origins. A norm that mixes radians per second and meters per second also needs an explicit choice of scale.
Keep motion and force coordinates consistent
A robot controller may describe a desired motion in tool coordinates while its kinematics use space coordinates. The adjoint converts between those representations. In a product of exponentials, it also lets us express screw axes in the frame required by a motion composition.
A learned model can predict a body-frame velocity correction. Before comparing it with a spatial-twist target, transform the correction with the current pose. If the target is the velocity of a tracked point, evaluate ω × q + v at that point instead; a six-vector twist and a point velocity encode different quantities.
There is a useful dual relation for forces. Write a wrench in moment-first order F = (m, f). The scalar FᵀV = m · ω + f · v is mechanical power when the motion and load use the same frame and reference origin.
Power stays unchanged when we re-express both quantities. Substituting V_a = Ad_T_ab V_b gives F_b = (Ad_T_ab)ᵀF_a, or equivalently F_a = (Ad_T_ab)⁻ᵀF_b. The transpose and transform direction belong together. Modern Robotics derives the wrench transformation from this power pairing.
The wrenches lesson builds that load from a point force and a couple. Change the reference origin there and check how the moment changes while the power pairing stays consistent.
Reproduce the transformation in Python
This standard-library example builds the planar adjoint, transforms the twist, evaluates the body-origin velocity, and reverses the frame change. Formatting removes rounded negative zero only from printed text.
from math import cos, hypot, pi, sin
def adjoint(angle, p):
c, s = cos(angle), sin(angle)
return ((1, 0, 0), (p[1], c, -s), (-p[0], s, c))
def apply(matrix, vector):
return tuple(sum(a*b for a, b in zip(row, vector))
for row in matrix)
def velocity(twist, point):
omega, vx, vy = twist
x, y = point
return vx-omega*y, vy+omega*x
def rotate(angle, vector):
c, s = cos(angle), sin(angle)
x, y = vector
return c*x-s*y, s*x+c*y
def show(values):
parts = [f"{value:.6f}" for value in values]
return "(" + ", ".join("0.000000" if float(part) == 0
else part for part in parts) + ")"
angle, p = pi/2, (2, 1)
body = (0.5, 1, 0)
space = apply(adjoint(angle, p), body)
origin_velocity = velocity(space, p)
rotated = rotate(angle, body[1:])
inverse_p = tuple(-value for value in rotate(-angle, p))
recovered = apply(adjoint(-angle, inverse_p), space)
error = hypot(space[1]-rotated[0], space[2]-rotated[1])
assert all(abs(a-b) < 1e-12 for a, b in zip(body, recovered))
assert hypot(origin_velocity[0]-rotated[0],
origin_velocity[1]-rotated[1]) < 1e-12
print("Space twist:", show(space))
print("Body-origin velocity:", show(origin_velocity))
print("Rotate-only linear coordinates:", show(rotated))
print(f"Rotate-only error: {error:.6f}")
print("Recovered body twist:", show(recovered))
Expected output:
Space twist: (0.500000, 0.500000, 0.000000)
Body-origin velocity: (0.000000, 1.000000)
Rotate-only linear coordinates: (0.000000, 1.000000)
Rotate-only error: 1.118034
Recovered body twist: (0.500000, 1.000000, 0.000000)
The angle input to sin and cos uses radians. The first component of each twist uses rad/s; each two-component velocity uses m/s. Keep these units visible when moving the calculation into a sensor or control pipeline.
Try it yourself
Exercise 1. Frames b and s have aligned axes, and the body origin is p = (3, 0) m. The body twist is (2, 0, 0), with ω_z in rad/s. Find the spatial twist and the velocity at the body origin. Then find the velocity of a body point located one meter along body x from that origin.
Show solution: a spin with an offset origin
The origin shift is p × ω = (0, −6) m/s, so V_s = (2, 0, −6). At p = (3, 0), the velocity is (0, −6) + (0, 6) = (0, 0). The body origin stays still.
The second point has space coordinates q = (4, 0). Its velocity is (0, −6) + (0, 8) = (0, 2) m/s. The two points belong to the same rigid body, but their instantaneous linear velocities differ.
Exercise 2. Let R rotate 90° and p = (2, 1) m. A spatial twist is V_s = (1, 2, −2). Recover V_b. What error would remain in the recovered linear coordinates if you rotated v_s by −90° without accounting for the origin shift?
Show solution: undo the shift before rotating
The shift is p × ω_s = (1, −2) m/s. Subtract it from v_s = (2, −2) to obtain Rv_b = (1, 0). A −90° rotation gives v_b = (0, −1), so V_b = (1, 0, −1).
Rotating v_s alone would give (−2, −2). Its difference from the correct v_b is (−2, −1), with length √5 m/s. Applying the full adjoint to the recovered V_b returns the supplied V_s.
Sources and further study
- Lynch and Park, Modern Robotics: Twists, Part 1 explains twist coordinates, reference origins, and instantaneous rigid motion.
- Lynch and Park, Modern Robotics: Twists, Part 2 introduces the six-dimensional adjoint and the body and spatial tangent matrices.
- Ethan Eade: Lie Groups for 2D and 3D Transformations, Sections 3.3 and 5.3, derives the SE(3) and SE(2) adjoints using translation-first coordinates.
- Lynch and Park, Modern Robotics: Wrenches derives the dual transformation from the invariance of mechanical power.