Chapter 6: Inverse Kinematics (IK)
Lesson 5: Redundancy Resolution in IK
This lesson develops a rigorous velocity-level framework for resolving redundancy in inverse kinematics when the robot has more joints than task-space dimensions. We derive minimum-norm and weighted pseudoinverse formulations, introduce null-space projectors for secondary objectives (such as joint-limit avoidance), and present a gradient-projection method. We then implement redundancy resolution in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.
1. Redundant Manipulators and Velocity-Level IK
Consider a manipulator with \( n \) joints and a task-space of dimension \( m \) (e.g., planar position \( m=2 \), spatial pose \( m=6 \)). The manipulator is kinematically redundant for the task if \( n > m \). In velocity-level form, the differential kinematics (introduced in the previous lesson on numerical IK) are
\[ \dot{\mathbf{x}} = \mathbf{J}(\mathbf{q}) \, \dot{\mathbf{q}}, \quad \mathbf{J}(\mathbf{q}) \in \mathbb{R}^{m \times n}, \quad \mathbf{q} \in \mathbb{R}^n, \; \mathbf{x} \in \mathbb{R}^m. \]
For a desired task-space velocity \( \dot{\mathbf{x}}_{\text{d}} \), the velocity IK problem is to find joint velocities \( \dot{\mathbf{q}} \) satisfying \( \mathbf{J}(\mathbf{q}) \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} \). When \( n > m \) and \( \mathbf{J}(\mathbf{q}) \) has full row rank \( m \), this equation is underdetermined and admits infinitely many solutions. Redundancy resolution chooses one solution (or family of solutions) according to an optimality or behavioral criterion, for example:
- Minimize joint-speed norm \( \|\dot{\mathbf{q}}\| \) (minimum-norm IK).
- Avoid joint limits and self-collisions.
- Shape the posture (e.g., keep elbow away from obstacles).
In this lesson we restrict attention to kinematic redundancy at the velocity level, ignoring dynamics and control; the resulting \( \dot{\mathbf{q}} \) can be integrated to generate joint trajectories.
2. Minimum-Norm Pseudoinverse Solution
The most classical redundancy resolution is the minimum Euclidean norm solution of the equality-constrained problem
\[ \min_{\dot{\mathbf{q}}} \frac{1}{2} \, \dot{\mathbf{q}}^{\top} \dot{\mathbf{q}} \quad \text{subject to} \quad \mathbf{J}(\mathbf{q}) \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}}. \]
Introduce a Lagrange multiplier \( \boldsymbol{\lambda} \in \mathbb{R}^m \) and define the Lagrangian
\[ \mathcal{L}(\dot{\mathbf{q}}, \boldsymbol{\lambda}) = \tfrac{1}{2} \dot{\mathbf{q}}^{\top}\dot{\mathbf{q}} + \boldsymbol{\lambda}^{\top} \left( \mathbf{J} \dot{\mathbf{q}} - \dot{\mathbf{x}}_{\text{d}} \right). \]
Stationarity with respect to \( \dot{\mathbf{q}} \) gives
\[ \frac{\partial \mathcal{L}}{\partial \dot{\mathbf{q}}} = \dot{\mathbf{q}} + \mathbf{J}^{\top} \boldsymbol{\lambda} = \mathbf{0} \quad \Rightarrow \quad \dot{\mathbf{q}} = - \mathbf{J}^{\top} \boldsymbol{\lambda}. \]
Enforcing the constraint \( \mathbf{J} \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} \) yields
\[ \mathbf{J} \dot{\mathbf{q}} = - \mathbf{J} \mathbf{J}^{\top} \boldsymbol{\lambda} = \dot{\mathbf{x}}_{\text{d}} \quad \Rightarrow \quad \boldsymbol{\lambda} = - \left( \mathbf{J} \mathbf{J}^{\top} \right)^{-1} \dot{\mathbf{x}}_{\text{d}}, \]
assuming full row rank so that \( \mathbf{J} \mathbf{J}^{\top} \) is invertible. Substituting back gives the minimum-norm joint velocity
\[ \dot{\mathbf{q}}_{\star} = \mathbf{J}^{\top} \left( \mathbf{J} \mathbf{J}^{\top} \right)^{-1} \dot{\mathbf{x}}_{\text{d}} \equiv \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}}, \]
where \( \mathbf{J}^{\#} \) denotes the Moore–Penrose pseudoinverse for the full-row-rank case. If the task is not exactly reachable, the same expression solves the least-squares problem \( \min_{\dot{\mathbf{q}}} \|\mathbf{J}\dot{\mathbf{q}} - \dot{\mathbf{x}}_{\text{d}}\|^2 \) with minimum joint-speed norm.
flowchart TD
XD["Desired xdot_d"] --> JMAT["Compute J(q)"]
JMAT --> PINV["Compute J_pinv = J' (J J')^-1"]
PINV --> QDOT0["Primary solution qdot_0 = J_pinv * xdot_d"]
QDOT0 --> INT["Integrate qdot_0 to update q"]
3. Null-Space of the Jacobian and General Solution
Redundancy manifests as a nontrivial null-space of the Jacobian:
\[ \mathcal{N}(\mathbf{J}) = \left\{ \mathbf{z} \in \mathbb{R}^n \,\middle|\, \mathbf{J} \mathbf{z} = \mathbf{0} \right\}. \]
Any velocity \( \mathbf{z} \in \mathcal{N}(\mathbf{J}) \) leaves the task velocity unchanged, because \( \mathbf{J}\mathbf{z} = \mathbf{0} \). The general solution of \( \mathbf{J} \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} \) can be written as
\[ \dot{\mathbf{q}} = \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} + \left( \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \right) \mathbf{z}, \quad \mathbf{z} \in \mathbb{R}^n, \]
where \( \mathbf{I}_n \) is the \( n \times n \) identity matrix. The matrix \( \mathbf{N}(\mathbf{q}) = \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \) is the null-space projector.
Property 1: null-space.
\[ \mathbf{J} \mathbf{N} = \mathbf{J} \left( \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \right) = \mathbf{J} - \mathbf{J}\mathbf{J}^{\#}\mathbf{J} = \mathbf{J} - \mathbf{J} = \mathbf{0}, \]
because \( \mathbf{J}\mathbf{J}^{\#} \) is the identity on the row space of \( \mathbf{J} \) (one of the Penrose conditions). Thus every vector of the form \( \mathbf{N}\mathbf{z} \) lies in the null-space.
Property 2: idempotence.
\[ \mathbf{N}^2 = \left( \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \right) \left( \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \right) = \mathbf{I}_n - 2\mathbf{J}^{\#}\mathbf{J} + \mathbf{J}^{\#}\mathbf{J}\mathbf{J}^{\#}\mathbf{J}. \]
Since \( \mathbf{J}^{\#}\mathbf{J}\mathbf{J}^{\#} = \mathbf{J}^{\#} \), we obtain
\[ \mathbf{N}^2 = \mathbf{I}_n - 2\mathbf{J}^{\#}\mathbf{J} + \mathbf{J}^{\#}\mathbf{J} = \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} = \mathbf{N}, \]
so \( \mathbf{N} \) is indeed a projector (idempotent). Consequently, adding any term \( \mathbf{N}\mathbf{z} \) to the minimum-norm solution preserves the task velocity while exploring posture changes.
4. Secondary Objectives and Gradient Projection
Redundancy allows us to optimize a secondary objective while still tracking the primary task. Let \( H(\mathbf{q}) \) be a smooth scalar objective (e.g., a cost measuring distance to joint limits). The gradient-projection method chooses a null-space velocity in the direction of the negative gradient:
\[ \dot{\mathbf{q}} = \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} - \alpha \, \mathbf{N}(\mathbf{q}) \, \nabla_{\mathbf{q}} H(\mathbf{q}), \quad \alpha > 0. \]
The time derivative of the objective along the motion is
\[ \frac{\mathrm{d}H}{\mathrm{d}t} = \nabla_{\mathbf{q}} H(\mathbf{q})^{\top} \dot{\mathbf{q}} = \nabla_{\mathbf{q}} H^{\top} \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} - \alpha \nabla_{\mathbf{q}} H^{\top} \mathbf{N} \nabla_{\mathbf{q}} H. \]
The second term is nonpositive, because \( \mathbf{N} \) is symmetric positive semidefinite on the relevant subspace: \( \nabla_{\mathbf{q}} H^{\top} \mathbf{N} \nabla_{\mathbf{q}} H \ge 0 \). Therefore, for sufficiently small \( \alpha \), the secondary objective decreases while the primary task is preserved.
A common choice for joint-limit avoidance is the quadratic cost
\[ H(\mathbf{q}) = \frac{1}{2} \sum_{i=1}^{n} w_i \left( \frac{q_i - q_{i,\mathrm{mid}}}{q_{i,\mathrm{max}} - q_{i,\mathrm{min}}} \right)^{2}, \]
where \( q_{i,\mathrm{min}} \) and \( q_{i,\mathrm{max}} \) are joint limits, \( q_{i,\mathrm{mid}} \) is their midpoint, and \( w_i > 0 \) are weights.
flowchart TD
START["Desired xdot_d and posture objective H(q)"] --> JSTEP["Compute J(q) and J_pinv"]
JSTEP --> PRIMARY["Primary qdot_0 = J_pinv * xdot_d"]
START --> GRAD["Compute grad H(q)"]
GRAD --> PROJ["Compute N(q) = I - J_pinv * J"]
PROJ --> NSVEL["Null-space term qdot_H = -alpha * N * gradH"]
PRIMARY --> SUM["Total qdot = qdot_0 + qdot_H"]
NSVEL --> SUM
5. Weighted Pseudoinverse and Joint Weighting
The Euclidean norm in joint space is often not the most meaningful metric: some joints may be closer to limits or more costly to move. Let \( \mathbf{W} \in \mathbb{R}^{n \times n} \) be a symmetric positive-definite weight matrix. Consider the optimization
\[ \min_{\dot{\mathbf{q}}} \frac{1}{2} \dot{\mathbf{q}}^{\top} \mathbf{W} \dot{\mathbf{q}} \quad \text{subject to} \quad \mathbf{J} \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}}. \]
The Lagrangian is
\[ \mathcal{L}(\dot{\mathbf{q}}, \boldsymbol{\lambda}) = \tfrac{1}{2} \dot{\mathbf{q}}^{\top} \mathbf{W} \dot{\mathbf{q}} + \boldsymbol{\lambda}^{\top} \left( \mathbf{J} \dot{\mathbf{q}} - \dot{\mathbf{x}}_{\text{d}} \right). \]
Stationarity with respect to \( \dot{\mathbf{q}} \) gives
\[ \frac{\partial \mathcal{L}}{\partial \dot{\mathbf{q}}} = \mathbf{W} \dot{\mathbf{q}} + \mathbf{J}^{\top} \boldsymbol{\lambda} = \mathbf{0} \quad \Rightarrow \quad \dot{\mathbf{q}} = - \mathbf{W}^{-1} \mathbf{J}^{\top} \boldsymbol{\lambda}. \]
Enforcing the constraint:
\[ \mathbf{J} \dot{\mathbf{q}} = - \mathbf{J} \mathbf{W}^{-1} \mathbf{J}^{\top} \boldsymbol{\lambda} = \dot{\mathbf{x}}_{\text{d}} \quad \Rightarrow \quad \boldsymbol{\lambda} = - \left( \mathbf{J} \mathbf{W}^{-1} \mathbf{J}^{\top} \right)^{-1} \dot{\mathbf{x}}_{\text{d}}. \]
Thus,
\[ \dot{\mathbf{q}}_{\star} = \mathbf{W}^{-1} \mathbf{J}^{\top} \left( \mathbf{J} \mathbf{W}^{-1} \mathbf{J}^{\top} \right)^{-1} \dot{\mathbf{x}}_{\text{d}} \equiv \mathbf{J}^{\#}_{\mathbf{W}} \dot{\mathbf{x}}_{\text{d}}, \]
where \( \mathbf{J}^{\#}_{\mathbf{W}} \) is the weighted pseudoinverse. Large weights in \( \mathbf{W} \) suppress motion of the corresponding joints. The null-space projector is now \( \mathbf{N}_{\mathbf{W}} = \mathbf{I}_n - \mathbf{J}^{\#}_{\mathbf{W}}\mathbf{J} \), and the gradient-projection method can be generalized by replacing \( \mathbf{J}^{\#}, \mathbf{N} \) with their weighted counterparts.
6. Brief Note on Hierarchical (Task-Priority) IK
When multiple tasks are present (e.g., end-effector tracking as task 1, posture shaping as task 2), one can treat them hierarchically. Let \( \mathbf{J}_1 \) and \( \mathbf{J}_2 \) be the Jacobians of tasks 1 and 2, respectively. A standard two-level construction is
\[ \dot{\mathbf{q}} = \mathbf{J}_1^{\#} \dot{\mathbf{x}}_1 + \mathbf{N}_1 \left( \mathbf{J}_2 \mathbf{N}_1 \right)^{\#} \left( \dot{\mathbf{x}}_2 - \mathbf{J}_2 \mathbf{J}_1^{\#} \dot{\mathbf{x}}_1 \right), \]
where \( \mathbf{N}_1 = \mathbf{I}_n - \mathbf{J}_1^{\#}\mathbf{J}_1 \) projects the secondary task into the null-space of the primary task. This guarantees that the primary task is not affected by the secondary one (at least locally and away from singularities). In this course we focus mainly on the single-task case with null-space optimization; more advanced hierarchical schemes can be studied after mastering Jacobian properties in the next chapter.
7. Python Implementation of Redundancy Resolution
We now implement a simple redundancy-resolution step for a planar 3-DOF
arm tracking a 2D end-effector velocity. We use
numpy for linear algebra and a damped pseudoinverse to
handle near-singular configurations.
import numpy as np
def planar3_fk(q, link_lengths):
"""
Simple planar 3R forward kinematics returning end-effector position.
q: array-like, shape (3,)
link_lengths: array-like, shape (3,)
"""
q1, q2, q3 = q
l1, l2, l3 = link_lengths
c1 = np.cos(q1); s1 = np.sin(q1)
c12 = np.cos(q1 + q2); s12 = np.sin(q1 + q2)
c123 = np.cos(q1 + q2 + q3); s123 = np.sin(q1 + q2 + q3)
x = l1 * c1 + l2 * c12 + l3 * c123
y = l1 * s1 + l2 * s12 + l3 * s123
return np.array([x, y])
def planar3_jacobian(q, link_lengths):
"""
Jacobian J(q) mapping joint velocities to end-effector linear velocity.
Shape: (2, 3).
"""
q1, q2, q3 = q
l1, l2, l3 = link_lengths
s1 = np.sin(q1); c1 = np.cos(q1)
s12 = np.sin(q1 + q2); c12 = np.cos(q1 + q2)
s123 = np.sin(q1 + q2 + q3); c123 = np.cos(q1 + q2 + q3)
# Partial derivatives of x, y with respect to q1, q2, q3
# x = l1 c1 + l2 c12 + l3 c123
# y = l1 s1 + l2 s12 + l3 s123
J = np.zeros((2, 3))
J[0, 0] = -l1 * s1 - l2 * s12 - l3 * s123
J[1, 0] = l1 * c1 + l2 * c12 + l3 * c123
J[0, 1] = -l2 * s12 - l3 * s123
J[1, 1] = l2 * c12 + l3 * c123
J[0, 2] = -l3 * s123
J[1, 2] = l3 * c123
return J
def damped_pseudoinverse(J, lam=1e-3):
"""
Damped pseudoinverse: J^T (J J^T + lam^2 I)^(-1).
Suitable for m <= n and full (or nearly full) row rank.
"""
m, n = J.shape
JJt = J @ J.T
return J.T @ np.linalg.inv(JJt + (lam ** 2) * np.eye(m))
def joint_limit_cost(q, q_min, q_max, w=None):
"""
Quadratic joint-limit cost H(q).
"""
q_mid = 0.5 * (q_min + q_max)
span = q_max - q_min
if w is None:
w = np.ones_like(q)
normed = (q - q_mid) / span
return 0.5 * np.sum(w * normed * normed)
def joint_limit_grad(q, q_min, q_max, w=None):
"""
Gradient of the joint-limit cost with respect to q.
"""
q_mid = 0.5 * (q_min + q_max)
span = q_max - q_min
if w is None:
w = np.ones_like(q)
normed = (q - q_mid) / span
return w * normed / (span)
def redundancy_resolution_step(q, xdot_des, link_lengths,
q_min, q_max,
alpha=0.1, lam=1e-3):
"""
One Euler integration step of redundancy-resolved IK.
q: current joint configuration, shape (3,)
xdot_des: desired end-effector velocity (2,)
"""
J = planar3_jacobian(q, link_lengths)
J_pinv = damped_pseudoinverse(J, lam=lam)
# Primary task
qdot_0 = J_pinv @ xdot_des
# Null-space projector
N = np.eye(J.shape[1]) - J_pinv @ J
# Secondary objective: joint-limit avoidance
gradH = joint_limit_grad(q, q_min, q_max)
qdot_H = -alpha * (N @ gradH)
qdot = qdot_0 + qdot_H
return qdot
# Example usage (single time step)
if __name__ == "__main__":
link_lengths = np.array([0.4, 0.3, 0.2])
q = np.array([0.0, 0.3, -0.2])
q_min = np.array([-np.pi, -np.pi, -np.pi])
q_max = np.array([ np.pi, np.pi, np.pi])
# Track a small velocity in x direction
xdot_des = np.array([0.05, 0.0])
qdot = redundancy_resolution_step(q, xdot_des, link_lengths,
q_min, q_max,
alpha=0.2, lam=1e-3)
q_next = q + 0.01 * qdot # simple Euler step
print("qdot:", qdot)
print("q_next:", q_next)
This code demonstrates how null-space motion (driven by the joint-limit cost) can coexist with the primary tracking of the end-effector velocity. For longer trajectories, the step would be executed in a loop with updated task velocities and joint states.
8. C++ Implementation with Eigen
In C++, the Eigen library is standard for dense linear
algebra. The snippet below illustrates a generic damped pseudoinverse
and a redundancy-resolution step using the same planar 3R example.
#include <Eigen/Dense>
#include <iostream>
using Eigen::MatrixXd;
using Eigen::VectorXd;
MatrixXd dampedPseudoinverse(const MatrixXd& J, double lambda)
{
const int m = J.rows();
MatrixXd JJt = J * J.transpose();
MatrixXd I = MatrixXd::Identity(m, m);
MatrixXd inv = (JJt + (lambda * lambda) * I).inverse();
return J.transpose() * inv; // J^T (J J^T + lambda^2 I)^(-1)
}
MatrixXd planar3Jacobian(const VectorXd& q,
const VectorXd& link_lengths)
{
double q1 = q(0), q2 = q(1), q3 = q(2);
double l1 = link_lengths(0), l2 = link_lengths(1), l3 = link_lengths(2);
double s1 = std::sin(q1), c1 = std::cos(q1);
double s12 = std::sin(q1 + q2), c12 = std::cos(q1 + q2);
double s123 = std::sin(q1 + q2 + q3), c123 = std::cos(q1 + q2 + q3);
MatrixXd J(2, 3);
J(0, 0) = -l1 * s1 - l2 * s12 - l3 * s123;
J(1, 0) = l1 * c1 + l2 * c12 + l3 * c123;
J(0, 1) = -l2 * s12 - l3 * s123;
J(1, 1) = l2 * c12 + l3 * c123;
J(0, 2) = -l3 * s123;
J(1, 2) = l3 * c123;
return J;
}
VectorXd jointLimitGradient(const VectorXd& q,
const VectorXd& q_min,
const VectorXd& q_max)
{
VectorXd q_mid = 0.5 * (q_min + q_max);
VectorXd span = q_max - q_min;
VectorXd normed = (q - q_mid).cwiseQuotient(span);
// Unit weights for simplicity
return normed.cwiseQuotient(span);
}
VectorXd redundancyResolutionStep(const VectorXd& q,
const VectorXd& xdot_des,
const VectorXd& link_lengths,
const VectorXd& q_min,
const VectorXd& q_max,
double alpha,
double lambda)
{
MatrixXd J = planar3Jacobian(q, link_lengths);
MatrixXd J_pinv = dampedPseudoinverse(J, lambda);
VectorXd qdot_0 = J_pinv * xdot_des;
MatrixXd I = MatrixXd::Identity(J.cols(), J.cols());
MatrixXd N = I - J_pinv * J;
VectorXd gradH = jointLimitGradient(q, q_min, q_max);
VectorXd qdot_H = -alpha * (N * gradH);
VectorXd qdot = qdot_0 + qdot_H;
return qdot;
}
int main()
{
VectorXd link_lengths(3);
link_lengths << 0.4, 0.3, 0.2;
VectorXd q(3);
q << 0.0, 0.3, -0.2;
VectorXd q_min(3), q_max(3);
q_min << -M_PI, -M_PI, -M_PI;
q_max << M_PI, M_PI, M_PI;
VectorXd xdot_des(2);
xdot_des << 0.05, 0.0;
double alpha = 0.2;
double lambda = 1e-3;
VectorXd qdot = redundancyResolutionStep(q, xdot_des,
link_lengths,
q_min, q_max,
alpha, lambda);
VectorXd q_next = q + 0.01 * qdot;
std::cout << "qdot = " << qdot.transpose() << std::endl;
std::cout << "q_next = " << q_next.transpose() << std::endl;
return 0;
}
The structure mirrors the Python implementation. In a full robotics library, such functions would be methods attached to a manipulator model class with precomputed link parameters.
9. Java Implementation (EJML)
In Java, the EJML library (Efficient Java Matrix Library) is widely used for robotics-related linear algebra. Below is a minimal example of a damped pseudoinverse and redundancy-resolution step.
import org.ejml.simple.SimpleMatrix;
public class RedundantIK {
public static SimpleMatrix dampedPseudoinverse(SimpleMatrix J, double lambda) {
int m = J.numRows();
SimpleMatrix JJt = J.mult(J.transpose());
SimpleMatrix I = SimpleMatrix.identity(m);
SimpleMatrix inv = JJt.plus(I.scale(lambda * lambda)).invert();
return J.transpose().mult(inv);
}
public static SimpleMatrix planar3Jacobian(double[] q, double[] L) {
double q1 = q[0], q2 = q[1], q3 = q[2];
double l1 = L[0], l2 = L[1], l3 = L[2];
double s1 = Math.sin(q1), c1 = Math.cos(q1);
double s12 = Math.sin(q1 + q2), c12 = Math.cos(q1 + q2);
double s123 = Math.sin(q1 + q2 + q3), c123 = Math.cos(q1 + q2 + q3);
SimpleMatrix J = new SimpleMatrix(2, 3);
J.set(0, 0, -l1 * s1 - l2 * s12 - l3 * s123);
J.set(1, 0, l1 * c1 + l2 * c12 + l3 * c123);
J.set(0, 1, -l2 * s12 - l3 * s123);
J.set(1, 1, l2 * c12 + l3 * c123);
J.set(0, 2, -l3 * s123);
J.set(1, 2, l3 * c123);
return J;
}
public static SimpleMatrix jointLimitGradient(double[] q,
double[] qMin,
double[] qMax) {
int n = q.length;
SimpleMatrix grad = new SimpleMatrix(n, 1);
for (int i = 0; i < n; ++i) {
double qMid = 0.5 * (qMin[i] + qMax[i]);
double span = qMax[i] - qMin[i];
double normed = (q[i] - qMid) / span;
grad.set(i, 0, normed / span);
}
return grad;
}
public static SimpleMatrix redundancyResolutionStep(double[] q,
double[] xdotDes,
double[] L,
double[] qMin,
double[] qMax,
double alpha,
double lambda) {
SimpleMatrix J = planar3Jacobian(q, L);
SimpleMatrix Jpinv = dampedPseudoinverse(J, lambda);
SimpleMatrix xdot = new SimpleMatrix(2, 1, true, xdotDes);
SimpleMatrix qdot0 = Jpinv.mult(xdot);
SimpleMatrix I = SimpleMatrix.identity(J.numCols());
SimpleMatrix N = I.minus(Jpinv.mult(J));
SimpleMatrix gradH = jointLimitGradient(q, qMin, qMax);
SimpleMatrix qdotH = N.mult(gradH).scale(-alpha);
return qdot0.plus(qdotH);
}
public static void main(String[] args) {
double[] L = {0.4, 0.3, 0.2};
double[] q = {0.0, 0.3, -0.2};
double[] qMin = {-Math.PI, -Math.PI, -Math.PI};
double[] qMax = { Math.PI, Math.PI, Math.PI};
double[] xdotDes = {0.05, 0.0};
SimpleMatrix qdot = redundancyResolutionStep(q, xdotDes,
L, qMin, qMax,
0.2, 1e-3);
System.out.println("qdot = ");
qdot.print();
}
}
The overall structure remains identical across languages: compute \( \mathbf{J} \), construct a (possibly damped) pseudoinverse, form the null-space projector, and add a gradient-projection term for secondary objectives.
10. MATLAB/Simulink and Wolfram Mathematica Implementations
10.1 MATLAB / Simulink
MATLAB provides convenient functions pinv and
null. A velocity-level redundancy resolution step can be
coded as:
function qdot = redundancy_step(q, xdot_des, L, q_min, q_max, alpha, lambda)
% q: 3x1, xdot_des: 2x1, L: 3x1 link lengths
% q_min, q_max: 3x1 joint limits
J = planar3_jacobian(q, L);
% Damped pseudoinverse: J' * inv(J*J' + lambda^2 I)
m = size(J, 1);
J_pinv = J' * ((J*J' + (lambda^2)*eye(m)) \ eye(m));
qdot0 = J_pinv * xdot_des;
N = eye(size(J, 2)) - J_pinv * J;
% Joint-limit gradient
q_mid = 0.5 * (q_min + q_max);
span = (q_max - q_min);
normed = (q - q_mid) ./ span;
gradH = normed ./ span;
qdotH = -alpha * (N * gradH);
qdot = qdot0 + qdotH;
end
function J = planar3_jacobian(q, L)
q1 = q(1); q2 = q(2); q3 = q(3);
l1 = L(1); l2 = L(2); l3 = L(3);
s1 = sin(q1); c1 = cos(q1);
s12 = sin(q1 + q2); c12 = cos(q1 + q2);
s123 = sin(q1 + q2 + q3); c123 = cos(q1 + q2 + q3);
J = zeros(2, 3);
J(1,1) = -l1*s1 - l2*s12 - l3*s123;
J(2,1) = l1*c1 + l2*c12 + l3*c123;
J(1,2) = -l2*s12 - l3*s123;
J(2,2) = l2*c12 + l3*c123;
J(1,3) = -l3*s123;
J(2,3) = l3*c123;
end
In Simulink, this function can be called from a MATLAB Function block within a loop that integrates \( \dot{\mathbf{q}} \).
10.2 Wolfram Mathematica
In Mathematica, redundancy resolution can be expressed compactly using
PseudoInverse and NullSpace:
planar3Jacobian[q_, L_] := Module[
{q1, q2, q3, l1, l2, l3, s1, c1, s12, c12, s123, c123},
{q1, q2, q3} = q;
{l1, l2, l3} = L;
s1 = Sin[q1]; c1 = Cos[q1];
s12 = Sin[q1 + q2]; c12 = Cos[q1 + q2];
s123 = Sin[q1 + q2 + q3]; c123 = Cos[q1 + q2 + q3];
{
{-l1 s1 - l2 s12 - l3 s123, -l2 s12 - l3 s123, -l3 s123},
{ l1 c1 + l2 c12 + l3 c123, l2 c12 + l3 c123, l3 c123}
}
];
jointLimitGrad[q_, qmin_, qmax_] := Module[
{qmid, span, normed},
qmid = 0.5 (qmin + qmax);
span = qmax - qmin;
normed = (q - qmid)/span;
normed/span
];
redundancyStep[q_, xdotDes_, L_, qmin_, qmax_, alpha_, lambda_] := Module[
{J, m, Jpinv, I, N, gradH, qdot0, qdotH},
J = planar3Jacobian[q, L];
m = Length[J];
Jpinv = Transpose[J].Inverse[J.Transpose[J] + (lambda^2) IdentityMatrix[m]];
qdot0 = Jpinv.xdotDes;
I = IdentityMatrix[Length[q]];
N = I - Jpinv.J;
gradH = jointLimitGrad[q, qmin, qmax];
qdotH = -alpha N.gradH;
qdot0 + qdotH
];
(* Example call *)
L = {0.4, 0.3, 0.2};
q = {0., 0.3, -0.2};
qmin = {-Pi, -Pi, -Pi};
qmax = { Pi, Pi, Pi};
xdotDes = {0.05, 0.0};
qdot = redundancyStep[q, xdotDes, L, qmin, qmax, 0.2, 10.^-3];
Symbolic capabilities in Mathematica also allow analytic manipulation of Jacobians and null-spaces for simple manipulators, which can be used to verify the algebra derived earlier in the lesson.
11. Problems and Solutions
Problem 1 (General solution with pseudoinverse): Assume \( \mathbf{J} \in \mathbb{R}^{m \times n} \) has full row rank \( m \le n \). Show that every solution of \( \mathbf{J}\dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} \) can be written as \( \dot{\mathbf{q}} = \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} + (\mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J})\mathbf{z} \) for some \( \mathbf{z} \in \mathbb{R}^n \).
Solution:
Let \( \dot{\mathbf{q}}_{\star} = \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} \) be the minimum-norm solution. For any solution \( \dot{\mathbf{q}} \) we have
\[ \mathbf{J} \dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} = \mathbf{J} \dot{\mathbf{q}}_{\star}. \]
Hence \( \mathbf{J}(\dot{\mathbf{q}} - \dot{\mathbf{q}}_{\star}) = \mathbf{0} \), so the difference lies in the null-space: \( \dot{\mathbf{q}} - \dot{\mathbf{q}}_{\star} \in \mathcal{N}(\mathbf{J}) \). Because the null-space projector satisfies \( \mathcal{N}(\mathbf{J}) = \{ (\mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J})\mathbf{z} \} \), there exists \( \mathbf{z} \) such that
\[ \dot{\mathbf{q}} - \dot{\mathbf{q}}_{\star} = (\mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J})\mathbf{z}, \]
and thus \( \dot{\mathbf{q}} = \mathbf{J}^{\#} \dot{\mathbf{x}}_{\text{d}} + (\mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J})\mathbf{z} \) as claimed.
Problem 2 (Explicit pseudoinverse for a 2×3 Jacobian): Consider \( \mathbf{J} = \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \end{bmatrix} \). Compute \( \mathbf{J}^{\#} \) using \( \mathbf{J}^{\#} = \mathbf{J}^{\top}(\mathbf{J}\mathbf{J}^{\top})^{-1} \).
Solution:
First compute \( \mathbf{J}\mathbf{J}^{\top} \):
\[ \mathbf{J}\mathbf{J}^{\top} = \begin{bmatrix} 1 & 0 & 1 \\ 0 & 1 & 1 \end{bmatrix} \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{bmatrix} = \begin{bmatrix} 2 & 1 \\ 1 & 2 \end{bmatrix}. \]
Its inverse is \( (\mathbf{J}\mathbf{J}^{\top})^{-1} = \frac{1}{3} \begin{bmatrix} 2 & -1 \\ -1 & 2 \end{bmatrix} \), so
\[ \mathbf{J}^{\#} = \mathbf{J}^{\top}(\mathbf{J}\mathbf{J}^{\top})^{-1} = \frac{1}{3} \begin{bmatrix} 1 & 0 \\ 0 & 1 \\ 1 & 1 \end{bmatrix} \begin{bmatrix} 2 & -1 \\ -1 & 2 \end{bmatrix} = \frac{1}{3} \begin{bmatrix} 2 & -1 \\ -1 & 2 \\ 1 & 1 \end{bmatrix}. \]
This can be verified to satisfy the Moore–Penrose pseudoinverse conditions (e.g., \( \mathbf{J}\mathbf{J}^{\#}\mathbf{J} = \mathbf{J} \)).
Problem 3 (Descent property of gradient projection): Let \( \dot{\mathbf{q}} = \mathbf{J}^{\#}\dot{\mathbf{x}}_{\text{d}} - \alpha \mathbf{N} \nabla_{\mathbf{q}}H \) with \( \alpha > 0 \). Show that the null-space component always decreases \( H \) to first order, i.e., the contribution from the second term to \( \mathrm{d}H/\mathrm{d}t \) is nonpositive.
Solution:
The contribution of the null-space term is
\[ \left. \frac{\mathrm{d}H}{\mathrm{d}t} \right|_{\text{null}} = - \alpha \nabla_{\mathbf{q}}H^{\top} \mathbf{N} \nabla_{\mathbf{q}}H. \]
Since \( \mathbf{N} \) is a symmetric projector (idempotent and positive semidefinite), the quadratic form \( \nabla_{\mathbf{q}}H^{\top} \mathbf{N} \nabla_{\mathbf{q}}H \ge 0 \). Therefore the product with \( -\alpha \) is \( \le 0 \) for \( \alpha > 0 \), and the null-space term cannot increase the objective to first order.
Problem 4 (Weighted pseudoinverse from constrained optimization): Starting from the optimization problem \( \min_{\dot{\mathbf{q}}} \tfrac{1}{2} \dot{\mathbf{q}}^{\top} \mathbf{W} \dot{\mathbf{q}} \) subject to \( \mathbf{J}\dot{\mathbf{q}} = \dot{\mathbf{x}}_{\text{d}} \) with \( \mathbf{W} \) symmetric positive definite, derive the weighted pseudoinverse \( \mathbf{J}^{\#}_{\mathbf{W}} = \mathbf{W}^{-1}\mathbf{J}^{\top} (\mathbf{J}\mathbf{W}^{-1}\mathbf{J}^{\top})^{-1} \).
Solution:
This is exactly the derivation in Section 5. Using the Lagrangian and stationarity, we obtain \( \dot{\mathbf{q}} = -\mathbf{W}^{-1}\mathbf{J}^{\top}\boldsymbol{\lambda} \). Enforcing the constraint determines \( \boldsymbol{\lambda} = -(\mathbf{J}\mathbf{W}^{-1}\mathbf{J}^{\top})^{-1} \dot{\mathbf{x}}_{\text{d}} \), which yields \( \dot{\mathbf{q}}_{\star} = \mathbf{W}^{-1}\mathbf{J}^{\top} (\mathbf{J}\mathbf{W}^{-1}\mathbf{J}^{\top})^{-1} \dot{\mathbf{x}}_{\text{d}} \).
Problem 5 (Dimension of the null-space): Suppose \( \mathbf{J}(\mathbf{q}) \in \mathbb{R}^{m \times n} \) with rank \( \operatorname{rank}(\mathbf{J}) = m \) and \( n > m \). Show that the null-space \( \mathcal{N}(\mathbf{J}) \) has dimension \( n - m \) and interpret this in terms of redundant degrees of freedom.
Solution:
By the rank–nullity theorem, \( \dim(\mathcal{N}(\mathbf{J})) = n - \operatorname{rank}(\mathbf{J}) \). For full row rank \( \operatorname{rank}(\mathbf{J}) = m \), this gives \( \dim(\mathcal{N}(\mathbf{J})) = n - m \). Geometrically, this means that at each configuration the joint velocity space decomposes into a direct sum of a task-relevant subspace of dimension \( m \) and a task-irrelevant null-space of dimension \( n - m \). The latter corresponds precisely to the redundant degrees of freedom that can be used for secondary objectives.
12. Summary
In this lesson we studied redundancy resolution for inverse kinematics. Starting from the velocity relation \( \dot{\mathbf{x}} = \mathbf{J}(\mathbf{q})\dot{\mathbf{q}} \), we derived the minimum-norm pseudoinverse solution and showed that the general solution includes a null-space component that does not affect the primary task. We introduced the null-space projector \( \mathbf{N} = \mathbf{I}_n - \mathbf{J}^{\#}\mathbf{J} \) and used it to project gradients of secondary objectives, yielding the classical gradient-projection method. Weighted pseudoinverses allow joint-dependent metrics, and the same algebra underlies hierarchical task-priority schemes.
Implementation examples in Python, C++, Java, MATLAB/Simulink, and Mathematica demonstrated how the theory translates to code. These redundancy-resolution techniques will interact closely with Jacobian properties and manipulability measures, which we investigate in detail in the next chapter on differential kinematics and singularities.
13. References
- Liegeois, A. (1977). Automatic supervisory control of the configuration and behavior of multibody mechanisms. IEEE Transactions on Systems, Man, and Cybernetics, 7(12), 868–871.
- Nakamura, Y. (1987). Advanced robotics: redundancy and optimization. Proceedings of the IEEE, 75(4), 503–515.
- Siciliano, B. (1990). Kinematic control of redundant robot manipulators: A tutorial. Journal of Intelligent and Robotic Systems, 3(3), 201–212.
- Klein, C. A., & Huang, C.-H. (1983). Review of pseudoinverse control for use with kinematically redundant manipulators. IEEE Transactions on Systems, Man, and Cybernetics, 13(3), 245–250.
- Maciejewski, A. A., & Klein, C. A. (1985). Obstacle avoidance for kinematically redundant manipulators in dynamically varying environments. International Journal of Robotics Research, 4(3), 109–117.
- Nakamura, Y., & Hanafusa, H. (1986). Inverse kinematic solutions with singularity robustness for robot manipulator control. Journal of Dynamic Systems, Measurement, and Control, 108(3), 163–171.
- Baillieul, J. (1985). Kinematic programming alternatives for redundant manipulators. Proceedings of the IEEE International Conference on Robotics and Automation, 722–728.
- Yoshikawa, T. (1985). Manipulability of robotic mechanisms. International Journal of Robotics Research, 4(2), 3–9.