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.

By 12 min read

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.

One rigid-motion field, two frame origins

Transform a body twist into space coordinates

T_sb maps body coordinates into the fixed space frame. The planar twist order is (ω, vₓ, vᵧ). Positive ω rotates counterclockwise about +z.

90
2.00
1.00
0.50
1.00
0.00
  • Amber solid: space v at the space origin
  • Blue dashed: rotate-only comparison at the space origin
  • Black solid: actual velocity at the body origin
At body origin (2.000, 1.000) meters, velocity is (0.000, 1.000) meters per second; the space linear twist coordinate is (0.500, 0.000)Fixed equal x and y scales in meters. A gray cross marks the space origin, and a gray ring marks the body origin with short body axes. Velocity arrows use one second as a display scale. Amber shows the motion field at the space origin; blue dashes show the rotate-only comparison there. Black shows the actual velocity at the body origin. Coincident arrows can overlap; circles mark exact zero arrows. The diagram shows one instant.-4-40044xy
Space coordinates are meters, with equal scales. The gray cross is the space origin; the ring is the body origin. Short gray body axes show x solid and y dashed. Each velocity arrow ends at its starting position plus 1 s times its velocity. This display scale does not predict the position one second later.
Body twist (ω, vₓ, vᵧ)
(0.500, 1.000, 0.000)
Space twist (ω, vₓ, vᵧ)
(0.500, 0.500, 0.000)
Rotated body v (m/s)
(0.000, 1.000)
Origin-shift term (m/s)
(0.500, -1.000)
Body-origin velocity (m/s)
(0.000, 1.000)
Rotate-only error (m/s)
1.118034
Recovered body twist
(0.500, 1.000, 0.000)
Linear round-trip error (m/s)
0.000000

Twist triples mix units: ω uses rad/s, and the two linear coordinates use m/s. The space v is the rigid-motion field evaluated at the space origin. It does not describe motion of the fixed space frame itself.

Planar Ad_T: body twist → space twist, in (ω, vₓ, vᵧ) order
Rowωvₓvᵧ
ω1.000.000.00
vₓ1.000.00-1.00
vᵧ-2.001.000.00

Space twist: (0.500, 0.500, 0.000). Body-origin velocity: (0.000, 1.000) m/s. Omitting the origin shift changes the space linear coordinates by 1.118034 m/s.

Changing the controls selects another instantaneous pose and body twist. The inverse adjoint recovers the input twist. Calculations retain more precision than the displayed values.

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