explainer
Kinematic redundancy: use the motion a task leaves free
Split a three-link arm’s joint rates into a primary solution and null-space motion. Check the exact projector, compare damping leakage, and measure why a finite step can move a tool with zero initial velocity.
What you will learn
- Define redundancy and nullity for a specified robot task.
- Split joint rates into a minimum-norm primary solution and a null component.
- Verify that an exact projector preserves the primary instantaneous velocity.
- Identify leakage from a damped secondary map and drift from a finite step.
Before you start
A robot can have several joint motions that produce the same tool velocity. That freedom lets a controller change the arm’s posture while maintaining its primary task. The useful question is which motions preserve that task, and for how long.
We will add a third revolute joint to a planar arm. You will calculate an exact null-space motion, see its guarantee fail under a damped replacement, and measure the error from holding its rates for a finite time.
Define what the task leaves free
Redundancy depends on the task. Three independent joints can be redundant for a two-coordinate position task, while a task that also specifies tool orientation can use all three freedoms.
At a fixed configuration, the Jacobian J maps n joint rates to the chosen task velocity. The rank-nullity relation counts the local freedom:
dim null(J) = n − rank(J)
For a regular two-dimensional position task with three joints, rank J = 2 and nullity = 1. One independent joint-rate direction produces zero tool-position velocity. Multiples of that direction form J’s null space.
Rank can fall at a singular configuration. Then nullity increases, even as the tool loses an available velocity direction. Modern Robotics distinguishes these rank and redundancy cases.
Add a third link to the arm
This lesson uses three links of 2 m, 1 m, and 1 m. The earlier two-link examples had only 2 m and 1 m links. The shoulder stays at the world origin, with x right and y up.
Let q = (q₁, q₂, q₃). The shoulder angle q₁ is absolute; q₂ and q₃ are relative to their preceding links. Positive angles turn counterclockwise, and calculations use radians.
At q = (0°, 90°, −90°), the first joint beyond the shoulder is at (2, 0) m. The next joint is at (2, 1) m, and the tool is at (3, 1) m. Each Jacobian column is the tool velocity from moving that joint at 1 rad/s while holding the others still:
J =
[ −1, −1, 0 ]
[ 3, 1, 1 ]
ṗ = Jq̇
The entries have units m/rad. For example, the third joint rotates the final 1 m link upward, giving the column (0, 1). The first joint rotates the whole tool displacement (3, 1), giving (−1, 3).
Split primary and null-space motion
Choose a requested tool velocity v. The pseudoinverse gives q̇_primary = J⁺v, the smallest Euclidean joint-rate vector among the least-squares solutions. If v lies in J’s column space, that solution produces v exactly.
An arbitrary additional joint-rate request z can contain motion that would alter the primary task. The exact null-space projector N selects its component in null(J):
N = I − J⁺J
q̇ = J⁺v + Nz
JN = 0, N² = N, Nᵀ = N
The identity JN = 0 explains the task guarantee. Adding Nz leaves Jq̇_primary unchanged. The other two identities say N is an orthogonal projector: applying it twice has the same effect as applying it once.
If the request is unattainable, the primary result is JJ⁺v. Adding exact null motion preserves that best fit and its residual. It cannot create a missing tool direction.
The primary and null components are orthogonal in joint-rate coordinates. Adding a nonzero null component therefore increases the total squared rate norm. This is how the minimum-norm solution fits within the full family of solutions. Modern Robotics explains the pseudoinverse’s minimum-norm property.
Calculate a complete rate solution
Use the bent configuration above and request v = (0.5, 0.25) m/s. The exact minimum-norm rates are:
q̇_primary = (1/6, −2/3, 5/12) rad/s
n = (−1, 1, 2)/√6
Check Jn = 0. Since the null space has dimension one, N = nnᵀ. Multiplying the outer product gives:
N = (1/6) ×
[ 1, −1, −2 ]
[ −1, 1, 2 ]
[ −2, 2, 4 ]
Now request an additional third-joint rate through z = (0, 0, 0.5) rad/s. Projection gives Nz = (−1/6, 1/6, 1/3) rad/s. The combined result is q̇ = (0, −0.5, 0.75) rad/s.
Multiply by J: the x velocity is 0.5 m/s, and the y velocity is 0.25 m/s. All three joint contributions changed as needed to preserve the task. The third joint’s final rate is 0.75 rad/s; z was an additional preference before projection, not a hard target for the final joint rate.
Change the secondary request
Keep the exact projector selected and change the secondary joint 3 request. The combined joint rates change while the primary tool velocity stays fixed. The secondary leakage readout, ‖Jq̇_secondary‖₂, stays zero up to floating-point error.
Try Null motion only to set the primary request to zero. Then vary the time step and compare the held-rate endpoint with the initial tool position. The instantaneous velocity test and the finite endpoint comparison answer different questions.
The experiment uses four fixed configurations so its exact-rank formulas remain explicit. At each pose, it minimizes tool-velocity error first, then joint-rate norm. The secondary map adds the selected preference afterward.
Count the null space at singular poses
With all three angles zero, the straight arm reaches (4, 0) m. Its position Jacobian has rows (0, 0, 0) and (4, 2, 1). Rank is 1, so nullity is 3 − 1 = 2.
The position task still has two requested coordinates, but horizontal velocity is unavailable at that instant. For v = (0.5, 0.25) m/s, the exact primary fit produces (0, 0.25) m/s and leaves residual (0.5, 0) m/s. Every exact null addition preserves that fit.
At q = (0°, 180°, 0°), the last two links fold back along the first. The tool reaches the shoulder, and the Jacobian has rows (0, 0, 0) and (0, −2, −1). It also has rank 1 and nullity 2.
These extra null directions come from rank loss. They do not restore the missing task direction. At a regular configuration, this three-joint arm has one redundant position-task direction.
Adding tool orientation as a third output changes the calculation. Its Jacobian gains the row (1, 1, 1). At the worked bent pose, this full pose map has rank 3, leaving no null direction that preserves all three outputs.
Check what damping changes
A damped inverse uses Jλ# = Jᵀ(JJᵀ + λ²I)⁻¹. Replacing J⁺ with that inverse inside I − J⁺J generally destroys the exact null-space property:
Nλ = I − Jλ#J
JNλ = λ²(JJᵀ + λ²I)⁻¹J
That last expression is generally nonzero. A component along an exact null direction still stays in the null space, but other components can leak into the task. Nλ also generally fails the projector identity Nλ² = Nλ.
The comparison keeps the primary solution exact and changes only the secondary map. At the default pose with λ = 0.5 m/rad, the produced velocity becomes approximately (0.553691, 0.280201) m/s. The secondary component adds about 0.061603 m/s of task-space leakage.
Damping the primary inverse would also alter the primary fit. Our comparison isolates the secondary-map effect. Moe and colleagues discuss null-space task priorities and cases where damped schemes lose strict priority.
Keep the instantaneous guarantee local
The exact identity uses J at the current configuration. Hold q̇ for h seconds and update the joints to q_next = q + hq̇. Forward kinematics at q_next generally differs from the linear prediction p + hJq̇.
For the worked combined rates and h = 0.2 s, the linear prediction is (3.1, 1.05) m. The actual forward-kinematics endpoint is approximately (3.098584, 1.044983) m. Their distance is about 0.005213 m.
In the worked bent pose, pure null motion also has finite drift when its initial rates remain fixed. With z = (0, 0, 0.5), zero primary request, and h = 0.2 s, the tool moves about 0.003333 m from its starting point. Reducing h to 0.1 s reduces that error to about 0.000833 m, close to the factor of four expected from a second-order local error.
An ideal continuous command q̇(t) in null(J(q(t))) can preserve position along its trajectory. A practical discrete controller must recompute the Jacobian and account for numerical and tracking errors. Differential inverse kinematics develops that repeated local solve.
Secondary requests can come from a posture cost, such as z = −k∇H(q). They can also come from a learned policy that proposes joint motion while a primary controller handles the tool task. Projection acts on that proposal using the current model and metric; a model error can change the physical result.
Equal Euclidean joint-rate weights define the projector used here. Different weights define a different minimum-norm problem. A projected preference also provides no hard guarantee about joint bounds or collision clearance; use joint limits in inverse kinematics to examine explicit constraints.
Verify the split in Python
This standard-library example uses exact fractions for the linear calculations. It then uses trigonometry to evaluate one finite joint step. The inverse formula applies to this full-row-rank worked matrix.
from fractions import Fraction as F
from math import cos, sin, pi, hypot
J = ((F(-1), F(-1), F(0)), (F(3), F(1), F(1)))
dot = lambda a, b: sum(x*y for x, y in zip(a, b))
a, b, c = dot(J[0], J[0]), dot(J[0], J[1]), dot(J[1], J[1])
det = a*c - b*b
plus = tuple(((c*J[0][i] - b*J[1][i])/det,
(a*J[1][i] - b*J[0][i])/det) for i in range(3))
N = tuple(tuple(F(i == k) - plus[i][0]*J[0][k]
- plus[i][1]*J[1][k] for k in range(3))
for i in range(3))
v, z = (F(1, 2), F(1, 4)), (F(0), F(0), F(1, 2))
primary = tuple(dot(row, v) for row in plus)
secondary = tuple(dot(row, z) for row in N)
combined = tuple(x+y for x, y in zip(primary, secondary))
produced = tuple(dot(row, combined) for row in J)
assert all(dot(row, secondary) == 0 for row in J)
assert produced == v
q, h = (0.0, pi/2, -pi/2), 0.2
next_q = tuple(x + h*float(rate) for x, rate in zip(q, combined))
heading = 0.0
x = y = 0.0
for length, angle in zip((2, 1, 1), next_q):
heading += angle
x += length*cos(heading)
y += length*sin(heading)
prediction = (3 + h*float(produced[0]), 1 + h*float(produced[1]))
fmt = lambda values: "(" + ", ".join(f"{float(x):.6f}" for x in values) + ")"
print("primary:", fmt(primary))
print("secondary:", fmt(secondary))
print("combined:", fmt(combined))
print("produced:", fmt(produced))
print("linear prediction:", fmt(prediction))
print("held-rate endpoint:", fmt((x, y)))
print(f"finite-step mismatch: {hypot(x-prediction[0], y-prediction[1]):.6f} m")
Expected output:
primary: (0.166667, -0.666667, 0.416667)
secondary: (-0.166667, 0.166667, 0.333333)
combined: (0.000000, -0.500000, 0.750000)
produced: (0.500000, 0.250000)
linear prediction: (3.100000, 1.050000)
held-rate endpoint: (3.098584, 1.044983)
finite-step mismatch: 0.005213 m
The fraction assertions verify exact task preservation in the linear map. The last line measures a separate effect: the nonlinear position map over a finite step.
Try two null-space calculations
Exercise 1. Keep the worked bent pose and v = (0.5, 0.25) m/s, but set z = (0, 0, −0.5) rad/s. Find the secondary and combined rates. Does the tool velocity change? Is the combined rate norm smaller than the primary norm?
Show solution 1
Linearity reverses the secondary component to (1/6, −1/6, −1/3) rad/s. Adding the primary rates gives (1/3, −5/6, 1/12) rad/s. Multiplying by J still produces (0.5, 0.25) m/s.
The primary and secondary vectors are orthogonal. Their squared norms add: 31/48 + 1/6 = 13/16. The combined norm is √13/4 ≈ 0.901388 rad/s, larger than the primary norm √93/12 ≈ 0.803638 rad/s.
Exercise 2. At the straight configuration, J has rows (0, 0, 0) and (4, 2, 1). Find two independent null vectors, then explain why a request (0.5, 0) m/s remains impossible even though nullity is two.
Show solution 2
The vectors (1, −2, 0) and (0, 1, −2) both satisfy 4q̇₁ + 2q̇₂ + q̇₃ = 0. They are independent, and they span the two-dimensional null space.
Every produced velocity has x component zero because the first row of J is zero. Adding any combination of the null vectors leaves the produced velocity unchanged. More ways to preserve a task cannot supply a direction that the task map has lost.
Sources and further reading
- Kevin Lynch and Frank Park, Modern Robotics, 5.3: Singularities. Task dimensions, redundant manipulators, and configuration-dependent rank.
- Kevin Lynch and Frank Park, Modern Robotics, 6.2: Numerical Inverse Kinematics, Part 1. Pseudoinverse solutions and their minimum-norm and least-squares interpretations.
- Signe Moe and colleagues, Set-Based Tasks within the Singularity-Robust Multiple Task-Priority Inverse Kinematics Framework (2016). Null-space task priorities, closed-loop corrections for drift, and explicit treatment of inequality tasks.