Chapter 5: Forward Kinematics (FK)

Lesson 4: FK for Redundant Arms

This lesson studies forward kinematics for redundant serial manipulators, i.e., arms with more joints than task-space dimensions (e.g., 7-DOF arms operating in 3D). We formulate redundancy as a property of the mapping \( \mathbf{q} \mapsto T(\mathbf{q}) \in \mathrm{SE}(3) \), use dimension counting and Jacobian-based arguments (from multivariable calculus) to understand non-uniqueness of joint configurations, and show how standard FK (PoE/DH) extends trivially to any number of joints. We then implement redundant FK in Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica.

1. Conceptual Overview — What is a Redundant Arm in FK?

Consider a serial manipulator with \( n \) joints and joint vector \( \mathbf{q} \in \mathbb{R}^n \). Its forward kinematics maps joint space to a task space (typically end-effector pose):

\[ f : \mathbb{R}^n \rightarrow \mathcal{T}, \qquad \mathbf{q} \mapsto T(\mathbf{q}) \in \mathrm{SE}(3), \]

where \( \mathcal{T} \subset \mathrm{SE}(3) \) is the task space (full 6-DOF pose, or some subset such as position only).

Let \( m = \dim(\mathcal{T}_\text{task}) \) be the number of independent task coordinates (for full pose in 3D we have \( m = 6 \), for planar position-only tasks, typically \( m = 2 \)). We say the manipulator is kinematically redundant with respect to that task if

\[ n > m. \]

Intuitively, there are “extra” joints that do not need to change to accomplish a given end-effector task. From the FK point of view:

  • The FK map \( f \) is still well-defined and computed as usual via product of exponentials (PoE) or Denavit–Hartenberg (DH).
  • For many end-effector poses \( T^\star \), there is not a single joint vector \( \mathbf{q}^\star \) with \( f(\mathbf{q}^\star) = T^\star \), but rather a continuum of such joint configurations (self-motion).

FK for redundant arms therefore has two roles:

  1. Compute \( T(\mathbf{q}) \) for any given joint vector.
  2. Understand the geometric structure of the set of joint configurations that map to the same end-effector pose.
flowchart TD
  A["Start: serial arm with n joints"] --> B["Define task space T (pose, position, etc.)"]
  B --> C["Determine task dimension m"]
  C --> D["Compare degrees: n vs m"]
  D --> E["Case 1: n = m (nonredundant)"]
  D --> F["Case 2: n greater than m (redundant)"]
  E --> G["FK: q -> T(q) (typically isolated solutions)"]
  F --> H["FK: q -> T(q) with self-motion sets"]
  H --> I["Many q give same T(q) (extra DOF usable later in IK/control)"]
        

2. PoE FK for an \( n \)-DOF (Possibly Redundant) Serial Arm

From Lesson 1 in this chapter, the Product of Exponentials (PoE) formulation writes the pose of the end-effector as a product of exponentials of twists and joint coordinates. For an \( n \)-joint arm:

\[ T(\mathbf{q}) = e^{[\mathcal{S}_1]\theta_1} \, e^{[\mathcal{S}_2]\theta_2} \cdots e^{[\mathcal{S}_n]\theta_n} \, M, \qquad \mathbf{q} = \begin{bmatrix} \theta_1 & \dots & \theta_n \end{bmatrix}^\top, \]

where \( \mathcal{S}_i \in \mathbb{R}^6 \) is the joint screw axis expressed in the space frame, and \( M \in \mathrm{SE}(3) \) is the home configuration of the end-effector when all \( \theta_i = 0 \).

Each screw axis splits into angular and linear parts:

\[ \mathcal{S}_i = \begin{bmatrix} \boldsymbol{\omega}_i \\ \mathbf{v}_i \end{bmatrix}, \qquad [\mathcal{S}_i] = \begin{bmatrix} [\boldsymbol{\omega}_i]_\times & \mathbf{v}_i \\ \mathbf{0}^\top & 0 \end{bmatrix}, \]

and \( e^{[\mathcal{S}_i]\theta_i} \in \mathrm{SE}(3) \) is a rigid transform obtained via the matrix exponential.

Crucial point: nothing in this formula depends on whether the arm is redundant or not. Redundancy only affects the relationship between joint space and task space dimensions, not the FK computation itself. In software, once you implement PoE FK for arbitrary \( n \), the same code handles both nonredundant and redundant arms.

For compact notation, define the stacked screw-axis matrix:

\[ \mathbf{S} = \begin{bmatrix} \mathcal{S}_1 & \cdots & \mathcal{S}_n \end{bmatrix} \in \mathbb{R}^{6 \times n}, \]

and write the FK map as the smooth function

\[ f(\mathbf{q}) = \left(\prod_{i=1}^n e^{[\mathcal{S}_i]\theta_i}\right) M. \]

When \( n > 6 \) (e.g., 7-DOF arms), \( f \) maps a higher-dimensional space into the 6-dimensional manifold \( \mathrm{SE}(3) \), which is the origin of redundancy.

3. Self-Motion Manifolds and Dimension Counting

To understand redundancy more mathematically, we temporarily embed the task space into \( \mathbb{R}^m \). For example, by choosing minimal local coordinates for pose (3 for position, 3 for orientation), we may write \( \mathbf{x} = h(T(\mathbf{q})) \in \mathbb{R}^m \). The composite FK map becomes:

\[ \mathbf{x} = g(\mathbf{q}) \in \mathbb{R}^m, \qquad g = h \circ f. \]

From multivariable calculus (Chapter 1), the Jacobian of \( g \) at \( \mathbf{q}^\star \) is

\[ J_g(\mathbf{q}^\star) = \left[ \frac{\partial g_i}{\partial q_j}(\mathbf{q}^\star) \right] \in \mathbb{R}^{m \times n}. \]

Suppose \( \mathbf{x}^\star = g(\mathbf{q}^\star) \). The self-motion set of \( \mathbf{x}^\star \) is the level set

\[ \mathcal{M}_{\mathbf{x}^\star} = \left\{ \mathbf{q} \in \mathbb{R}^n \,\middle|\, g(\mathbf{q}) = \mathbf{x}^\star \right\}. \]

Under regularity assumptions (smooth \( g \) and full row rank at \( \mathbf{q}^\star \)), the implicit function theorem tells us that near \( \mathbf{q}^\star \) the level set is a smooth manifold of dimension

\[ \dim \mathcal{M}_{\mathbf{x}^\star} = n - \operatorname{rank} J_g(\mathbf{q}^\star). \]

In the typical full-rank redundant case \( \operatorname{rank} J_g(\mathbf{q}^\star) = m \) and \( n > m \), we get

\[ \dim \mathcal{M}_{\mathbf{x}^\star} = n - m > 0, \]

meaning that there is a continuum of joint configurations mapping to the same end-effector task value \( \mathbf{x}^\star \). For a 7-DOF arm controlling a 6-DOF pose we have

\[ n = 7, \quad m = 6 \quad \Rightarrow \quad \dim \mathcal{M}_{\mathbf{x}^\star} = 1, \]

so self-motion appears as a one-dimensional curve in joint space (e.g., “elbow swivel” motions that preserve the end-effector pose).

A deeper topological fact (the invariance-of-domain theorem) implies that there is no continuous one-to-one map from an open subset of \( \mathbb{R}^n \) into \( \mathbb{R}^m \) when \( n > m \). Thus a smooth FK map from joint space to task space cannot be globally injective in the redundant case; multiple joint configurations must share the same task value somewhere.

4. Example — Planar 3R Arm Redundant for Position-Only Tasks

As a simple analytic example, consider a 3R planar arm with link lengths \( \ell_1, \ell_2, \ell_3 \) and joint angles \( \mathbf{q} = \begin{bmatrix} q_1 & q_2 & q_3 \end{bmatrix}^\top \).

The end-effector pose in the plane has position \( (x, y) \) and orientation \( \phi \):

\[ \begin{aligned} x(\mathbf{q}) &= \ell_1 \cos q_1 + \ell_2 \cos(q_1 + q_2) + \ell_3 \cos(q_1 + q_2 + q_3), \\ y(\mathbf{q}) &= \ell_1 \sin q_1 + \ell_2 \sin(q_1 + q_2) + \ell_3 \sin(q_1 + q_2 + q_3), \\ \phi(\mathbf{q}) &= q_1 + q_2 + q_3. \end{aligned} \]

If the task is to control the full planar pose \( (x, y, \phi) \), then \( m = 3 = n \) and the arm is not redundant (generically there are isolated FK preimages for a given pose). But if the task is only to control the position \( (x, y) \) while allowing arbitrary orientation, then \( m = 2 < 3 = n \) and the arm is redundant for this task. Specifically, for a fixed desired position \( (x^\star, y^\star) \) inside the reachable workspace, the self-motion set is generically one-dimensional.

One can see this explicitly by treating \( q_3 \) as a free parameter (a “posture parameter”) and solving a 2R arm IK problem in the remaining angles \( q_1, q_2 \) for the equivalent effective two-link robot with a modified second-link length that depends on \( q_3 \). For each feasible choice of \( q_3 \) that keeps the target position in the reachable region, there exist zero, one, or two pairs \( (q_1, q_2) \) that achieve the same \( (x^\star, y^\star) \).

This illustrates a general pattern: redundancy with respect to a reduced task (here position-only) leads to families of joint configurations mapping to the same task, even when the underlying arm is not globally redundant for its full pose space.

5. Implementation Pattern for Redundant FK (Language-Independent)

From an implementation perspective, supporting redundant arms means:

  • writing FK routines that accept arbitrary \( n \),
  • storing screw axes or DH parameters as lists/arrays of length \( n \),
  • looping over joints in a consistent frame convention (space or body frame for PoE, or link frames for DH).

The high-level algorithm for PoE FK is:

  1. Precompute and store the screw axes \( \mathcal{S}_i \) and home transform \( M \).
  2. Given \( \mathbf{q} \), form the matrix exponentials \( e^{[\mathcal{S}_i]\theta_i} \).
  3. Multiply them in order to obtain \( T(\mathbf{q}) \).
flowchart TD
  A["Input: S (6xn), M, joint vector q"] --> B["Initialize T = M"]
  B --> C["For i from n down to 1 (space frame):"]
  C --> D["Compute exp_Si = exp( [Si] * qi )"]
  D --> E["Update T = exp_Si * T"]
  E --> F["Return T as end-effector pose"]
        

In practice, you may also want intermediate link frames (for visualization or collision checking) by returning the partial products; the core idea for a redundant arm remains identical.

6. Python Implementation (PoE + roboticstoolbox)

We first implement FK for an arbitrary \( n \)-DOF arm using pure NumPy and PoE, then illustrate how a robotics library such as roboticstoolbox-python can represent a 7-DOF redundant arm (e.g., KUKA-type anthropomorphic arm).


import numpy as np

def skew3(omega):
    """Return the 3x3 skew-symmetric matrix [omega]_x given omega in R^3."""
    wx, wy, wz = omega
    return np.array([[0.0, -wz,  wy],
                     [wz,  0.0, -wx],
                     [-wy, wx,  0.0]])

def twist_hat(S):
    """Lift a 6-vector twist S = [omega; v] to a 4x4 se(3) matrix [S]."""
    omega = S[0:3]
    v = S[3:6]
    se3 = np.zeros((4, 4))
    se3[0:3, 0:3] = skew3(omega)
    se3[0:3, 3] = v
    return se3

def matrix_exp6(se3mat, theta):
    """
    Compute exp(se3mat * theta) using the Rodrigues formula.
    se3mat is a 4x4 matrix with rotational part in the top-left block.
    """
    Omega = se3mat[0:3, 0:3]
    v = se3mat[0:3, 3]
    # Extract axis-angle parameters from Omega (assume ||omega|| = 1 or 0)
    omega_hat = Omega
    omega = np.array([Omega[2,1] - Omega[1,2],
                      Omega[0,2] - Omega[2,0],
                      Omega[1,0] - Omega[0,1]]) * 0.5
    omega_norm = np.linalg.norm(omega)

    R = np.eye(3)
    p = np.zeros(3)

    if omega_norm > 1e-9:
        # Normalize
        omega = omega / omega_norm
        omega_hat = skew3(omega)
        # Rodrigues for rotation
        R = (np.eye(3)
             + np.sin(theta) * omega_hat
             + (1.0 - np.cos(theta)) * (omega_hat @ omega_hat))
        # Compute the matrix V(theta) used in screw motion
        V = (np.eye(3) * theta
             + (1.0 - np.cos(theta)) * omega_hat
             + (theta - np.sin(theta)) * (omega_hat @ omega_hat))
        p = V @ v
    else:
        # Pure translation
        R = np.eye(3)
        p = v * theta

    T = np.eye(4)
    T[0:3, 0:3] = R
    T[0:3, 3] = p
    return T

def fk_poe(S_list, M, q):
    """
    Forward kinematics via PoE for arbitrary n-DOF arm.
    S_list: (6, n) array of screw axes (space frame).
    M: 4x4 home configuration.
    q: (n,) vector of joint coordinates.
    """
    n = S_list.shape[1]
    T = M.copy()
    # Multiply exponentials from joint n down to 1 (space frame)
    for i in reversed(range(n)):
        S = S_list[:, i]
        se3mat = twist_hat(S)
        T = matrix_exp6(se3mat, q[i]) @ T
    return T

# Example usage: 7-DOF "toy" redundant arm (just for illustration)
# Here we choose simple screw axes; in practice you would compute them from the geometry.
S_list = np.array([
    [0, 0, 1, 0, 0, 0],      # Joint 1: rotation about z-axis
    [0, 1, 0, -0.3, 0, 0],   # Joint 2: rotation about y-axis
    [0, 1, 0, -0.6, 0, 0],   # Joint 3
    [0, 1, 0, -0.9, 0, 0],   # Joint 4
    [1, 0, 0, 0, -0.9, 0],   # Joint 5
    [0, 1, 0, -1.2, 0, 0],   # Joint 6
    [1, 0, 0, 0, -1.2, 0]    # Joint 7
], dtype=float).T  # shape (6, 7)

M = np.eye(4)
M[0:3, 3] = np.array([1.2, 0.0, 0.2])  # tool tip offset in home pose

q_example = np.deg2rad(np.array([0, 20, -30, 40, -10, 15, 5]))
T_example = fk_poe(S_list, M, q_example)
print("End-effector pose T(q) for redundant 7-DOF arm:\n", T_example)
      

With roboticstoolbox-python, one usually constructs a SerialLink or DHRobot model (or a Robot built from URDF). FK then reduces to a single method call:


# Example with roboticstoolbox (Peter Corke)
import roboticstoolbox as rtb
import numpy as np

# Here we load an existing redundant arm model if available (e.g. KUKA LBR iiwa 7-DOF)
# For illustration, we assume such a model exists in the toolbox.
robot = rtb.models.DH.Puma560()  # nonredundant example
# In practice, use a 7-DOF model such as robot = rtb.models.DH.KUKAiiwa() if provided.

q = np.array([0, 0.3, -0.5, 0.4, -0.2, 0.1])
T = robot.fkine(q)  # returns SE3 object
print(T.A)  # 4x4 homogeneous transform
      

For a truly redundant arm, fkine behaves identically: given any \( n \)-dimensional joint vector, it returns a unique pose \( T(\mathbf{q}) \).

7. C++ Implementation (DH + Eigen / KDL Remarks)

In C++, Eigen is a convenient linear algebra library to implement FK. For more complete robotics support, the Orocos Kinematics and Dynamics Library (KDL) and ROS-based frameworks (e.g., MoveIt) provide DH or URDF-based kinematic chains. Below is a minimal DH-style FK that works for arbitrary \( n \).


#include <iostream>
#include <vector>
#include <Eigen/Dense>

struct DHParam {
    double a;
    double alpha;
    double d;
    double theta_offset;
    bool   revolute;
};

Eigen::Matrix4d dhTransform(double a, double alpha, double d, double theta) {
    double ca = std::cos(alpha);
    double sa = std::sin(alpha);
    double ct = std::cos(theta);
    double st = std::sin(theta);

    Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
    T(0,0) = ct;   T(0,1) = -st * ca;  T(0,2) =  st * sa;  T(0,3) = a * ct;
    T(1,0) = st;   T(1,1) =  ct * ca;  T(1,2) = -ct * sa;  T(1,3) = a * st;
    T(2,0) = 0.0;  T(2,1) =  sa;       T(2,2) =  ca;       T(2,3) = d;
    return T;
}

Eigen::Matrix4d fkDH(const std::vector<DHParam>& dh,
                     const std::vector<double>& q) {
    std::size_t n = dh.size();
    if (q.size() != n) {
        throw std::runtime_error("fkDH: size mismatch between DH table and q.");
    }
    Eigen::Matrix4d T = Eigen::Matrix4d::Identity();
    for (std::size_t i = 0; i < n; ++i) {
        double theta = dh[i].theta_offset;
        double d = dh[i].d;
        if (dh[i].revolute) {
            theta += q[i];
        } else {
            d += q[i];
        }
        Eigen::Matrix4d Ti = dhTransform(dh[i].a, dh[i].alpha, d, theta);
        T = T * Ti;
    }
    return T;
}

int main() {
    // Example: 7-DOF redundant arm approximated by 7 revolute joints
    std::vector<DHParam> dh(7);
    for (int i = 0; i < 7; ++i) {
        dh[i].a = 0.2;
        dh[i].alpha = 0.0;
        dh[i].d = 0.0;
        dh[i].theta_offset = 0.0;
        dh[i].revolute = true;
    }

    std::vector<double> q = {0.0, 0.3, -0.5, 0.4, -0.2, 0.1, 0.05};
    Eigen::Matrix4d T = fkDH(dh, q);
    std::cout << "End-effector transform:\n" << T << std::endl;

    return 0;
}
      

In KDL, a redundant arm is described as a chain with \( n \) segments and joints; FK is computed by a KDL::ChainFkSolverPos_recursive object, which again is agnostic to whether \( n \) is 6 or 7.

8. Java Implementation (DH-Based)

Java has fewer dedicated robotics libraries, but numerical linear algebra packages such as EJML or Apache Commons Math are commonly used. For clarity, we implement a simple FK routine using plain double arrays and DH parameters.


public class RedundantFK {

    public static double[][] matMul(double[][] A, double[][] B) {
        double[][] C = new double[4][4];
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                double sum = 0.0;
                for (int k = 0; k < 4; k++) {
                    sum += A[i][k] * B[k][j];
                }
                C[i][j] = sum;
            }
        }
        return C;
    }

    public static double[][] dhTransform(double a, double alpha,
                                         double d, double theta) {
        double ca = Math.cos(alpha);
        double sa = Math.sin(alpha);
        double ct = Math.cos(theta);
        double st = Math.sin(theta);

        double[][] T = new double[4][4];
        T[0][0] = ct;   T[0][1] = -st * ca;  T[0][2] =  st * sa;  T[0][3] = a * ct;
        T[1][0] = st;   T[1][1] =  ct * ca;  T[1][2] = -ct * sa;  T[1][3] = a * st;
        T[2][0] = 0.0;  T[2][1] =  sa;       T[2][2] =  ca;       T[2][3] = d;
        T[3][0] = 0.0;  T[3][1] = 0.0;       T[3][2] = 0.0;       T[3][3] = 1.0;
        return T;
    }

    public static double[][] fkDH(double[][] dh, double[] q) {
        int n = dh.length;
        if (q.length != n) {
            throw new IllegalArgumentException("fkDH: size mismatch.");
        }
        double[][] T = new double[4][4];
        for (int i = 0; i < 4; i++) {
            T[i][i] = 1.0;
        }
        for (int i = 0; i < n; i++) {
            double a = dh[i][0];
            double alpha = dh[i][1];
            double d = dh[i][2];
            double thetaOffset = dh[i][3];
            boolean revolute = (dh[i][4] > 0.5); // 1.0 for revolute, 0.0 for prismatic

            double theta = thetaOffset;
            double di = d;
            if (revolute) {
                theta += q[i];
            } else {
                di += q[i];
            }
            double[][] Ti = dhTransform(a, alpha, di, theta);
            T = matMul(T, Ti);
        }
        return T;
    }

    public static void main(String[] args) {
        // DH table for a simple 7-DOF redundant arm (toy values)
        double[][] dh = new double[7][5];
        for (int i = 0; i < 7; i++) {
            dh[i][0] = 0.2;   // a
            dh[i][1] = 0.0;   // alpha
            dh[i][2] = 0.0;   // d
            dh[i][3] = 0.0;   // theta offset
            dh[i][4] = 1.0;   // revolute flag
        }

        double[] q = new double[] {0.0, 0.3, -0.5, 0.4, -0.2, 0.1, 0.05};
        double[][] T = fkDH(dh, q);

        System.out.println("End-effector transform:");
        for (int i = 0; i < 4; i++) {
            for (int j = 0; j < 4; j++) {
                System.out.printf("%8.4f ", T[i][j]);
            }
            System.out.println();
        }
    }
}
      

Again, the only difference between redundant and nonredundant arms is the number of joints \( n \). The FK code does not change structurally.

9. MATLAB / Simulink Implementation

MATLAB provides two complementary approaches:

  • symbolic or numeric coding of PoE/DH, and
  • toolbox-level models using rigidBodyTree from the Robotics System Toolbox.

(a) PoE-style FK for arbitrary n-DOF:


function T = fk_poe_matlab(S_list, M, q)
% S_list: 6xn matrix of screw axes (space frame)
% M: 4x4 home configuration
% q: nx1 vector of joint variables
n = size(S_list, 2);
T = M;
for i = n:-1:1
    S = S_list(:, i);
    se3mat = twist_hat(S);
    T = expm(se3mat * q(i)) * T;
end
end

function se3mat = twist_hat(S)
omega = S(1:3);
v = S(4:6);
se3mat = zeros(4, 4);
se3mat(1:3, 1:3) = skew3(omega);
se3mat(1:3, 4) = v;
end

function Omega = skew3(omega)
wx = omega(1); wy = omega(2); wz = omega(3);
Omega = [ 0   -wz   wy;
          wz   0   -wx;
         -wy   wx   0 ];
end
      

(b) 7-DOF redundant arm using rigidBodyTree and Simulink:


robot = rigidBodyTree("DataFormat","row","MaxNumBodies",8);

% Define 7 revolute joints with simple kinematics (toy example)
prevBodyName = robot.BaseName;
for i = 1:7
    body = rigidBody("body" + string(i));
    joint = rigidBodyJoint("joint" + string(i), "revolute");
    % Set joint transform using DH-like parameters
    setFixedTransform(joint, trvec2tform([0.2, 0, 0]));
    joint.JointAxis = [0 0 1]; % all rotate about z-axis here
    body.Joint = joint;
    addBody(robot, body, prevBodyName);
    prevBodyName = body.Name;
end

% End-effector offset
tool = rigidBody("tool");
tform_tool = trvec2tform([0, 0, 0.1]);
setFixedTransform(tool.Joint, tform_tool);
addBody(robot, tool, prevBodyName);

% Example configuration
q = [0 0.1 -0.2 0.3 -0.1 0.2 0.05];
T = getTransform(robot, q, "tool", robot.BaseName)

% In Simulink, the 'Rigid Body Tree' block can import 'robot' and compute FK
      

In Simulink, a model can use the Rigid Body Tree block for real-time FK evaluation, providing joint angles as inputs and the end-effector pose as outputs.

10. Wolfram Mathematica Implementation

Mathematica is well-suited for symbolic FK derivations. Below is a DH-based approach for an arbitrary \( n \)-DOF arm, with an example 3R planar redundant arm for position-only tasks.


(* DH transform as a function *)
DHTransform[a_, alpha_, d_, theta_] := Module[
  {ca = Cos[alpha], sa = Sin[alpha], ct = Cos[theta], st = Sin[theta]},
  {
    {ct, -st ca, st sa, a ct},
    {st, ct ca, -ct sa, a st},
    {0,  sa,    ca,    d},
    {0,  0,     0,     1}
  }
];

(* General FK for an n-DOF manipulator *)
FKDH[dh_, q_List] := Module[
  {n = Length[dh], T},
  If[Length[q] != n, 
    Print["Error: size mismatch"]; Return[$Failed];
  ];
  T = IdentityMatrix[4];
  Do[
    Module[{a, alpha, d, thetaOffset, revolute, thetaJoint, dJoint},
      {a, alpha, d, thetaOffset, revolute} = dh[[i]];
      thetaJoint = thetaOffset;
      dJoint = d;
      If[revolute == 1,
        thetaJoint = thetaJoint + q[[i]],
        dJoint = dJoint + q[[i]]
      ];
      T = T . DHTransform[a, alpha, dJoint, thetaJoint];
    ],
    {i, 1, n}
  ];
  T
];

(* Example: 3R planar arm, redundant for position-only tasks *)
dhPlanar3R = {
  {l1, 0, 0, 0, 1},
  {l2, 0, 0, 0, 1},
  {l3, 0, 0, 0, 1}
};

Tq = FKDH[dhPlanar3R, {q1, q2, q3}] // Simplify;
pos = Tq[[{1, 2}, 4]] // Simplify
      

Symbolic expressions for pos match the analytic functions \( x(\mathbf{q}), y(\mathbf{q}) \) given earlier, and can be used for further analysis (e.g., self-motion parameterizations or simplification before numerical implementation).

11. Problems and Solutions

Problem 1 (Dimension of Self-Motion for a Redundant Arm): Let \( f : \mathbb{R}^n \rightarrow \mathbb{R}^m \) be the FK map of an \( n \)-DOF arm to an \( m \)-dimensional task space, with \( n > m \). Suppose \( \mathbf{x}^\star = f(\mathbf{q}^\star) \) and \( J_f(\mathbf{q}^\star) \) has full row rank \( m \). Show that the self-motion set of \( \mathbf{x}^\star \) is locally an \( (n - m) \)-dimensional manifold.

Solution:

Define the level set \( \mathcal{M}_{\mathbf{x}^\star} = \{\mathbf{q} \mid f(\mathbf{q}) = \mathbf{x}^\star\} \). The implicit function theorem states that if \( f \) is smooth and its Jacobian at \( \mathbf{q}^\star \) has rank \( m \), then near \( \mathbf{q}^\star \) the set \( \mathcal{M}_{\mathbf{x}^\star} \) is a smooth submanifold of \( \mathbb{R}^n \) with dimension \( n - m \). Intuitively, the linearization at \( \mathbf{q}^\star \) is the null space of the Jacobian:

\[ T_{\mathbf{q}^\star} \mathcal{M}_{\mathbf{x}^\star} = \ker J_f(\mathbf{q}^\star), \qquad \dim \ker J_f(\mathbf{q}^\star) = n - \operatorname{rank} J_f(\mathbf{q}^\star) = n - m. \]

Hence, near \( \mathbf{q}^\star \), the self-motion set looks like an \( (n - m) \)-dimensional surface embedded in joint space.

Problem 2 (Redundancy of a 7-DOF Arm): Consider a 7-DOF serial arm in 3D whose task is to control the full end-effector pose (position and orientation). Assume the FK map is regular (Jacobian has full rank 6) at some configuration. What is the dimension of the local self-motion manifold? What changes if the task is restricted to position-only?

Solution:

For full pose, \( m = 6 \) and \( n = 7 \), so by the previous result the local self-motion manifold has dimension \( 7 - 6 = 1 \). If we restrict to position-only, then \( m = 3 \), and assuming full row rank, the local self-motion manifold dimension becomes \( 7 - 3 = 4 \), corresponding to the large freedom in internal posture when only the end-effector position is constrained.

Problem 3 (Planar 3R Position-Redundant Arm): For the 3R planar arm with link lengths \( \ell_1, \ell_2, \ell_3 > 0 \) and joint angles \( q_1, q_2, q_3 \), write the FK for position as \( (x(\mathbf{q}), y(\mathbf{q})) \). Show that if \( \ell_3 = 0 \) (degenerate third link), the arm is no longer redundant for position-only tasks.

Solution:

The position FK was already given:

\[ \begin{aligned} x(\mathbf{q}) &= \ell_1 \cos q_1 + \ell_2 \cos(q_1 + q_2) + \ell_3 \cos(q_1 + q_2 + q_3), \\ y(\mathbf{q}) &= \ell_1 \sin q_1 + \ell_2 \sin(q_1 + q_2) + \ell_3 \sin(q_1 + q_2 + q_3). \end{aligned} \]

If \( \ell_3 = 0 \), the terms involving \( q_3 \) vanish and \( (x, y) \) depend only on \( q_1, q_2 \). The coordinate \( q_3 \) becomes a “dummy” angle that does not affect the end-effector position at all; the effective map to position is \( g : \mathbb{R}^2 \rightarrow \mathbb{R}^2 \) plus a passive variable. For nondegenerate link lengths, we instead have genuine dependence on all three angles, so the map \( g : \mathbb{R}^3 \rightarrow \mathbb{R}^2 \) is redundant with one-dimensional self-motion manifolds (when reachable).

Problem 4 (Noninjectivity of FK in Redundant Case): Let \( f : \mathbb{R}^n \rightarrow \mathbb{R}^m \) be continuous with \( n > m \). Argue (at a high level, without full proof) why \( f \) cannot be globally one-to-one, and interpret this statement in the context of redundant FK.

Solution:

The invariance-of-domain theorem from topology implies that if \( f \) is continuous and injective from an open subset of \( \mathbb{R}^n \) into \( \mathbb{R}^m \), then \( n \leq m \). Thus, when \( n > m \), no continuous map from the entire joint space into the task space can be globally injective. For FK, this means there must exist at least two distinct joint configurations \( \mathbf{q}^{(1)} \neq \mathbf{q}^{(2)} \) such that \( f(\mathbf{q}^{(1)}) = f(\mathbf{q}^{(2)}) \), i.e., global self-motion is unavoidable somewhere in the workspace of a redundant manipulator.

Problem 5 (Language-Independent FK Design): Suppose you are designing a multi-language FK library (Python, C++, Java, MATLAB, Mathematica) for redundant arms using PoE. List the minimal data structures that must be shared conceptually across languages, and explain why the FK core does not depend on whether the arm is redundant.

Solution:

Conceptually, each implementation must contain:

  • a representation of screw axes \( \mathcal{S}_i \) (as 6-vectors or equivalent twists),
  • a representation of the home configuration \( M \in \mathrm{SE}(3) \),
  • a routine to compute the matrix exponential \( e^{[\mathcal{S}_i]\theta_i} \),
  • a loop over joints multiplying exponentials and finally \( M \).

These structures do not conceptually change when passing from 6-DOF to 7-DOF (or higher) arms; only the length of the joint vector and the number of stored twists changes. Redundancy is a property of the mapping between the joint vector and the task-space dimension, not of the FK algorithm itself.

12. Summary

In this lesson we:

  • Defined kinematic redundancy as the case \( n > m \), where \( n \) is the number of joints and \( m \) the dimension of the task space.
  • Expressed the FK of an \( n \)-DOF arm via the PoE formulation \( T(\mathbf{q}) = \prod_i e^{[\mathcal{S}_i]\theta_i} M \), which is structurally identical for redundant and nonredundant arms.
  • Used Jacobian-based dimension counting to show that self-motion manifolds have dimension \( n - m \) under regularity assumptions.
  • Illustrated redundancy on a planar 3R arm for position-only tasks and discussed self-motion in 7-DOF 3D arms.
  • Implemented FK for redundant arms in Python (PoE and toolbox-based), C++ (Eigen and DH), Java (DH arrays), MATLAB/Simulink (PoE and rigidBodyTree), and Wolfram Mathematica (symbolic DH).

Subsequent chapters on inverse and differential kinematics will exploit redundancy for secondary objectives (e.g., obstacle avoidance, manipulability shaping) using the FK foundations developed here.

13. References

  1. Whitney, D.E. (1969). Resolved motion rate control of manipulators and human prostheses. IEEE Transactions on Man-Machine Systems, 10(2), 47–53.
  2. 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.
  3. 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.
  4. Nakamura, Y., & Hanafusa, H. (1987). Inverse kinematic solutions with singularity robustness for robot manipulator control. Journal of Dynamic Systems, Measurement, and Control, 108(3), 163–171.
  5. Nakamura, Y. (1991). Advanced Robotics: Redundancy and Optimization. Addison-Wesley.
  6. Chiaverini, S., Siciliano, B., & Slotine, J.-J. (1994). A survey of the inverse kinematics for redundant manipulators. Journal of Intelligent and Robotic Systems, 14(2), 123–144.
  7. Siciliano, B. (1990). Kinematic control of redundant robot manipulators: A tutorial. Journal of Intelligent and Robotic Systems, 3(3), 201–212.
  8. Yoshikawa, T. (1985). Manipulability of robotic mechanisms. International Journal of Robotics Research, 4(2), 3–9.
  9. Murray, R.M., Li, Z., & Sastry, S.S. (1994). A Mathematical Introduction to Robotic Manipulation. CRC Press.
  10. Park, J., & Chung, W.K. (2005). Geometric integration on Euclidean group with application to articulated multibody systems. IEEE Transactions on Robotics, 21(5), 850–863.