Chapter 5: Forward Kinematics (FK)
Lesson 5: Numerical FK and Implementation Lab (Python toolbox)
This lesson focuses on numerical realization of forward kinematics (FK) maps for serial manipulators. We connect the PoE and DH formulations from previous lessons to concrete algorithms and software patterns, and implement a small multi-language FK toolbox (Python, C++, Java, MATLAB/Simulink, and Wolfram Mathematica). Emphasis is on correctness, numerical robustness, and reusable software structure.
1. Numerical FK as a Map \( \mathbb{R}^n \rightarrow SE(3) \)
For an \(n\)-DOF serial manipulator with generalized coordinates \( \mathbf{q} = [q_1,\dots,q_n]^\top \in \mathbb{R}^n \), forward kinematics is the map
\[ \mathcal{F} : \mathbb{R}^n \rightarrow SE(3), \qquad \mathbf{T}(\mathbf{q}) = \mathcal{F}(\mathbf{q}), \]
where \( \mathbf{T}(\mathbf{q}) \in SE(3) \) encodes the end-effector pose as a homogeneous transform
\[ \mathbf{T}(\mathbf{q}) = \begin{bmatrix} \mathbf{R}(\mathbf{q}) & \mathbf{p}(\mathbf{q}) \\ \mathbf{0}^\top & 1 \end{bmatrix}, \]
with rotation \( \mathbf{R}(\mathbf{q}) \in SO(3) \) and position \( \mathbf{p}(\mathbf{q}) \in \mathbb{R}^3 \). In previous lessons we expressed \( \mathcal{F} \) symbolically using the Product of Exponentials (PoE) and Denavit–Hartenberg (DH) conventions. In this lesson we focus on numerical evaluation of \( \mathbf{T}(\mathbf{q}) \) for many joint vectors.
In the PoE formulation, assuming screw axes \( \widehat{\boldsymbol{\xi}}_i \in \mathfrak{se}(3) \) expressed in the space frame and a home configuration \( \mathbf{M} \in SE(3) \),
\[ \mathbf{T}(\mathbf{q}) = \exp(\widehat{\boldsymbol{\xi}}_1 q_1)\, \exp(\widehat{\boldsymbol{\xi}}_2 q_2)\,\cdots\, \exp(\widehat{\boldsymbol{\xi}}_n q_n)\,\mathbf{M}. \]
In the (standard) DH formulation with link transforms \( \mathbf{A}_i(q_i) \in SE(3) \):
\[ \mathbf{T}(\mathbf{q}) = \mathbf{A}_1(q_1)\,\mathbf{A}_2(q_2)\,\cdots\,\mathbf{A}_n(q_n). \]
A numerical FK toolbox thus needs:
- A data structure for storing screw axes or DH parameters.
- Reliable routines for \( \exp(\widehat{\boldsymbol{\xi}}\, q) \) or \( \mathbf{A}_i(q_i) \).
- Efficient and stable matrix multiplication in \(SE(3)\).
- Unit tests to ensure \( \mathbf{T}(\mathbf{q}) \in SE(3) \) up to numerical tolerance.
2. Matrix Exponential for Twists
Let a twist in 6D coordinates be \( \boldsymbol{\xi} = (\boldsymbol{\omega}, \mathbf{v}) \in \mathbb{R}^6 \), with angular part \( \boldsymbol{\omega} \in \mathbb{R}^3 \) and linear part \( \mathbf{v} \in \mathbb{R}^3 \). Its hat representation in \( \mathfrak{se}(3) \) is
\[ \widehat{\boldsymbol{\xi}} = \begin{bmatrix} \widehat{\boldsymbol{\omega}} & \mathbf{v} \\ \mathbf{0}^\top & 0 \end{bmatrix}, \quad \widehat{\boldsymbol{\omega}} = \begin{bmatrix} 0 & -\omega_3 & \omega_2 \\ \omega_3 & 0 & -\omega_1 \\ -\omega_2 & \omega_1 & 0 \end{bmatrix}. \]
For a revolute joint with joint variable \( q \) and \( \|\boldsymbol{\omega}\| = 1 \), the matrix exponential \( \exp(\widehat{\boldsymbol{\xi}} q) \) admits a closed form:
\[ \exp(\widehat{\boldsymbol{\xi}} q) = \begin{bmatrix} \mathbf{R}(q) & \mathbf{p}(q) \\ \mathbf{0}^\top & 1 \end{bmatrix}, \]
\[ \mathbf{R}(q) = \mathbf{I}_3 + \sin q\,\widehat{\boldsymbol{\omega}} + (1-\cos q)\,\widehat{\boldsymbol{\omega}}^2, \]
\[ \mathbf{p}(q) = \left( \mathbf{I}_3\,q + (1-\cos q)\,\widehat{\boldsymbol{\omega}} + (q-\sin q)\,\widehat{\boldsymbol{\omega}}^2 \right)\mathbf{v}. \]
For a prismatic joint with \( \boldsymbol{\omega} = \mathbf{0} \),
\[ \exp(\widehat{\boldsymbol{\xi}} q) = \begin{bmatrix} \mathbf{I}_3 & \mathbf{v}\,q \\ \mathbf{0}^\top & 1 \end{bmatrix}. \]
In practice, we implement these formulas directly instead of calling a general matrix exponential routine, achieving both efficiency and better numerical structure preservation (orthogonality of \( \mathbf{R}(q) \)).
3. Algorithmic Structure of Numerical FK
A generic numerical FK routine for PoE-based models can be written as:
- Read joint vector \( \mathbf{q} \).
- For each joint \( i \), compute \( \mathbf{T}_i(q_i) = \exp(\widehat{\boldsymbol{\xi}}_i q_i) \).
- Multiply transforms in the correct order.
- Optionally multiply by the home configuration \( \mathbf{M} \).
- Return \( \mathbf{T}(\mathbf{q}) \) and, if required, intermediate frames.
flowchart TD
Q["Joint vector q"] --> MODEL["Robot model (screws or DH params)"]
MODEL --> LOOP["For i = 1..n"]
LOOP --> Ti["Compute transform T_i(q_i)"]
Ti --> PROD["Accumulate product T = T * T_i"]
PROD --> EE["End-effector pose T(q) in SE(3)"]
EE --> CHECK["Optional checks: det(R) approx 1, R^T R approx I"]
For DH, the internal loop replaces the twist exponential with \( \mathbf{A}_i(q_i) \) computed from \( (a_i, \alpha_i, d_i, \theta_i) \). For example, the standard DH transform is
\[ \mathbf{A}_i(q_i) = \begin{bmatrix} \cos\theta_i & -\sin\theta_i \cos\alpha_i & \sin\theta_i \sin\alpha_i & a_i \cos\theta_i \\ \sin\theta_i & \cos\theta_i \cos\alpha_i & -\cos\theta_i \sin\alpha_i & a_i \sin\theta_i \\ 0 & \sin\alpha_i & \cos\alpha_i & d_i \\ 0 & 0 & 0 & 1 \end{bmatrix}, \]
where typically \( \theta_i = q_i + \theta_i^{\text{offset}} \) or \( d_i = q_i + d_i^{\text{offset}} \) depending on joint type.
4. Python Toolbox – Core Design and Implementation
Python is a natural choice for a prototype FK toolbox thanks to
numpy for linear algebra and the availability of robotics
libraries like Peter Corke's roboticstoolbox-python. Here
we implement a minimal PoE-based FK core without depending on
external robotics packages, to make the underlying mathematics explicit.
4.1 Core Lie Algebra Utilities
import numpy as np
def skew3(omega):
"""Return 3x3 skew-symmetric matrix hat(omega)."""
wx, wy, wz = omega
return np.array([
[0.0, -wz, wy],
[wz, 0.0, -wx],
[-wy, wx, 0.0]
])
def exp_twist(xi, q, eps=1e-9):
"""
Exponential map for a twist xi = (omega, v) in R^6.
Returns a 4x4 SE(3) matrix exp(hat(xi) q).
"""
omega = np.asarray(xi[:3]).reshape(3,)
v = np.asarray(xi[3:]).reshape(3,)
theta = q
if np.linalg.norm(omega) < eps: # prismatic
R = np.eye(3)
p = v * theta
else:
omega = omega / np.linalg.norm(omega)
w_hat = skew3(omega)
R = (
np.eye(3)
+ np.sin(theta) * w_hat
+ (1.0 - np.cos(theta)) * (w_hat @ w_hat)
)
V = (
np.eye(3) * theta
+ (1.0 - np.cos(theta)) * w_hat
+ (theta - np.sin(theta)) * (w_hat @ w_hat)
)
p = V @ v
T = np.eye(4)
T[:3, :3] = R
T[:3, 3] = p
return T
4.2 FK for a Serial Chain (PoE)
class RobotPoe:
"""
Simple serial manipulator model using the PoE formulation.
Attributes
----------
S_list : (6, n) array
Columns are screw axes in the space frame.
M : (4, 4) array
Home configuration of the end-effector.
"""
def __init__(self, S_list, M):
S_list = np.asarray(S_list, dtype=float)
assert S_list.shape[0] == 6
self.S_list = S_list
self.n = S_list.shape[1]
self.M = np.asarray(M, dtype=float)
def fk(self, q):
"""
Compute T(q) = exp(S1 q1) ... exp(Sn qn) M.
Parameters
----------
q : array_like, shape (n,)
Joint angles (revolute) or displacements (prismatic).
Returns
-------
T : (4, 4) ndarray
Homogeneous transform in SE(3).
"""
q = np.asarray(q, dtype=float).reshape(self.n,)
T = np.eye(4)
for i in range(self.n):
xi = self.S_list[:, i]
T = T @ exp_twist(xi, q[i])
return T @ self.M
def check_se3(self, T, tol=1e-6):
"""
Check whether T is numerically in SE(3).
Returns (ok, eps_R, eps_det).
"""
R = T[:3, :3]
RtR = R.T @ R
eps_R = np.linalg.norm(RtR - np.eye(3), ord="fro")
eps_det = abs(np.linalg.det(R) - 1.0)
ok = (eps_R < tol) and (eps_det < tol)
return ok, eps_R, eps_det
This design separates geometric information (\( \mathbf{S}_i \), \( \mathbf{M} \)) from joint variables. It is straightforward to wrap this in higher-level tooling (e.g., loading parameters from a YAML file, adding visualization, etc.).
5. Toolbox Architecture (Language-Agnostic)
To support multiple languages, we often separate the model description (e.g., PoE or DH parameters) from language-specific bindings. A typical layout:
flowchart TD
P["Parameter file (JSON/YAML)"] --> LOAD["Model loader"]
LOAD --> ROBOT["RobotModel (language-independent concept)"]
ROBOT --> FKFN["fk(q) core routine"]
FKFN --> PY["Python wrapper"]
FKFN --> CPP["C++ wrapper"]
FKFN --> JAVA["Java wrapper"]
PY --> APPS["Simulation / plotting"]
CPP --> APPS
JAVA --> APPS
Mathematically, all these bindings implement the same map \( \mathcal{F} \); differences are in memory layout, numeric precision, and performance. The rest of the lesson illustrates one implementation in each language.
6. C++ Implementation with Eigen/KDL-style Design
In C++, the Eigen library is the de facto standard for
matrix computations, while robot libraries such as
KDL (Kinematics and Dynamics Library) provide higher-level
kinematic primitives. Below is a compact PoE-based FK implementation
using Eigen, following the same mathematics as in the Python toolbox.
#include <Eigen/Dense>
#include <vector>
using SE3 = Eigen::Matrix4d;
using Vec3 = Eigen::Vector3d;
using Twist = Eigen::Matrix<double, 6, 1>;
Eigen::Matrix3d skew3(const Vec3& w) {
Eigen::Matrix3d W;
W << 0.0, -w.z(), w.y(),
w.z(), 0.0, -w.x(),
-w.y(), w.x(), 0.0;
return W;
}
SE3 expTwist(const Twist& xi, double q, double eps = 1e-9) {
Vec3 w = xi.head<3>();
Vec3 v = xi.tail<3>();
SE3 T = SE3::Identity();
double theta = q;
if (w.norm() < eps) {
// prismatic
T.block<3,3>(0,0) = Eigen::Matrix3d::Identity();
T.block<3,1>(0,3) = v * theta;
} else {
w.normalize();
Eigen::Matrix3d W = skew3(w);
Eigen::Matrix3d W2 = W * W;
Eigen::Matrix3d R =
Eigen::Matrix3d::Identity()
+ std::sin(theta) * W
+ (1.0 - std::cos(theta)) * W2;
Eigen::Matrix3d V =
Eigen::Matrix3d::Identity() * theta
+ (1.0 - std::cos(theta)) * W
+ (theta - std::sin(theta)) * W2;
T.block<3,3>(0,0) = R;
T.block<3,1>(0,3) = V * v;
}
return T;
}
struct RobotPoeCpp {
std::vector<Twist> S_list;
SE3 M;
SE3 fk(const std::vector<double>& q) const {
SE3 T = SE3::Identity();
for (std::size_t i = 0; i < S_list.size(); ++i) {
T = T * expTwist(S_list[i], q[i]);
}
return T * M;
}
};
This skeleton mirrors the Python design. In a full robotics library, one
would further integrate with KDL segment-based structures
and add unit tests for \( \det(\mathbf{R}) \) and
orthogonality.
7. Java Implementation with EJML
For Java, a widely used numerical library is
EJML (Efficient Java Matrix Library). We outline a minimal
PoE-based FK class. The mathematical operations are exactly those used
in Python and C++.
import org.ejml.simple.SimpleMatrix;
public class RobotPoeJava {
// S_list: 6 x n matrix (each column is a twist)
private final SimpleMatrix S_list;
private final SimpleMatrix M;
private final int n;
public RobotPoeJava(SimpleMatrix S_list, SimpleMatrix M) {
if (S_list.numRows() != 6) {
throw new IllegalArgumentException("S_list must be 6 x n");
}
this.S_list = S_list;
this.M = M;
this.n = S_list.numCols();
}
private SimpleMatrix skew3(SimpleMatrix w) {
double wx = w.get(0);
double wy = w.get(1);
double wz = w.get(2);
double[][] data = {
{0.0, -wz, wy},
{wz, 0.0, -wx},
{-wy, wx, 0.0}
};
return new SimpleMatrix(data);
}
private SimpleMatrix expTwist(SimpleMatrix xi, double q, double eps) {
SimpleMatrix w = xi.extractMatrix(0, 3, 0, 1);
SimpleMatrix v = xi.extractMatrix(3, 6, 0, 1);
double theta = q;
double normW = Math.sqrt(
w.get(0) * w.get(0) +
w.get(1) * w.get(1) +
w.get(2) * w.get(2)
);
SimpleMatrix T = SimpleMatrix.identity(4);
if (normW < eps) {
// prismatic
T.insertIntoThis(0, 0, SimpleMatrix.identity(3));
T.insertIntoThis(0, 3, v.scale(theta));
} else {
SimpleMatrix w_unit = w.divide(normW);
SimpleMatrix W = skew3(w_unit);
SimpleMatrix W2 = W.mult(W);
SimpleMatrix I3 = SimpleMatrix.identity(3);
SimpleMatrix R = I3
.plus(W.scale(Math.sin(theta)))
.plus(W2.scale(1.0 - Math.cos(theta)));
SimpleMatrix V = I3.scale(theta)
.plus(W.scale(1.0 - Math.cos(theta)))
.plus(W2.scale(theta - Math.sin(theta)));
T.insertIntoThis(0, 0, R);
T.insertIntoThis(0, 3, V.mult(v));
}
return T;
}
public SimpleMatrix fk(double[] q) {
if (q.length != n) {
throw new IllegalArgumentException("q must have length n");
}
SimpleMatrix T = SimpleMatrix.identity(4);
double eps = 1e-9;
for (int i = 0; i < n; ++i) {
SimpleMatrix xi = S_list.extractMatrix(0, 6, i, i + 1);
T = T.mult(expTwist(xi, q[i], eps));
}
return T.mult(M);
}
}
This provides a reusable FK core in Java. The same pattern can be wrapped inside Android or desktop applications for visualization and simulation.
8. MATLAB and Simulink Integration
MATLAB has a rich ecosystem for robotics, notably
Robotics System Toolbox with rigidBodyTree
objects. We again implement a minimal PoE-based FK function from scratch
and mention Simulink integration.
8.1 MATLAB Function for PoE FK
function T = fk_poe(S_list, M, q)
%FK_POE Forward kinematics using the product of exponentials.
% S_list: 6 x n matrix of screw axes (space frame)
% M : 4 x 4 home configuration
% q : n x 1 vector of joint variables
n = size(S_list, 2);
T = eye(4);
for i = 1:n
xi = S_list(:, i);
T = T * exp_twist(xi, q(i));
end
T = T * M;
end
function T = exp_twist(xi, q)
omega = xi(1:3);
v = xi(4:6);
theta = q;
if norm(omega) < 1e-9
R = eye(3);
p = v * theta;
else
omega = omega / norm(omega);
wx = omega(1); wy = omega(2); wz = omega(3);
W = [ 0, -wz, wy;
wz, 0, -wx;
-wy, wx, 0 ];
W2 = W * W;
R = eye(3) + sin(theta) * W + (1 - cos(theta)) * W2;
V = eye(3) * theta + (1 - cos(theta)) * W + (theta - sin(theta)) * W2;
p = V * v;
end
T = eye(4);
T(1:3,1:3) = R;
T(1:3,4) = p;
end
8.2 Simulink Use
To use this in Simulink, one typically wraps fk_poe in a
MATLAB Function block:
-
Create a MATLAB Function block and paste the core of
fk_poe. - Expose \( \mathbf{q} \) as input and components of \( \mathbf{T} \) as outputs.
-
Set
S_listandMas block parameters or constants. - Connect to downstream blocks (visualization, logging, or further kinematic processing).
This produces a real-time-capable numeric FK block suitable for simulation of joint trajectories (without yet involving dynamics or control).
9. Wolfram Mathematica Workflow
Wolfram Mathematica supports both symbolic and numeric matrix exponentials, making it convenient for verifying FK formulas. We implement the PoE map symbolically and then evaluate numerically.
(* Hat operator for so(3) *)
Skew3[{wx_, wy_, wz_}] := {
{0, -wz, wy},
{wz, 0, -wx},
{-wy, wx, 0}
};
(* Hat operator for se(3) given a 6-vector twist *)
TwistHat[{wx_, wy_, wz_, vx_, vy_, vz_}] := Module[
{W, v},
W = Skew3[{wx, wy, wz}];
v = { {vx}, {vy}, {vz} };
ArrayFlatten[{
{W, v},
{{0, 0, 0, 0}}
}]
];
(* Exponential of a twist *)
ExpTwist[xi_List, q_] := MatrixExp[TwistHat[xi] q];
(* Forward kinematics for a list of twists SList and home configuration M *)
FKPoE[SList_List, M_, q_List] := Module[
{n = Length[SList], T},
T = IdentityMatrix[4];
Do[
T = T . ExpTwist[SList[[i]], q[[i]]],
{i, 1, n}
];
T . M
];
(* Example: numeric evaluation for some q *)
SList = {
{0, 0, 1, 0, 0, 0},
{0, 0, 1, 0, -0.5, 0},
{0, 0, 1, 0, -1.0, 0}
};
M = { {1, 0, 0, 1.5},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1} };
qvec = {Pi/6, -Pi/4, Pi/3};
Tq = FKPoE[SList, M, qvec] // N
Symbolic expressions for \( \mathbf{T}(\mathbf{q}) \) can be simplified and exported to other languages (e.g., as C or Java code) while retaining the exact analytic structure derived earlier in the course.
10. Numerical Accuracy, Stability, and Testing
Because FK is a composition of many nonlinear operations, floating-point errors can accumulate. We discuss some simple diagnostics.
10.1 SE(3) Consistency Tests
Given a numerically computed transform \( \mathbf{T}(\mathbf{q}) \), define
\[ \epsilon_R(\mathbf{q}) = \left\|\mathbf{R}(\mathbf{q})^\top\mathbf{R}(\mathbf{q}) - \mathbf{I}_3\right\|_F, \quad \epsilon_{\det}(\mathbf{q}) = \bigl|\det(\mathbf{R}(\mathbf{q})) - 1\bigr|. \]
In exact arithmetic, both would be zero; in finite precision they should remain small (e.g., \( \epsilon_R \approx 10^{-12} \) for double precision in moderate chain lengths). Large deviations indicate bugs or extreme ill-conditioning.
10.2 Pose Error Metrics
Suppose we have an analytic reference transform \( \mathbf{T}_{\text{ref}}(\mathbf{q}) \) and a numerical implementation \( \widetilde{\mathbf{T}}(\mathbf{q}) \). A common translational error metric is
\[ e_{\text{pos}}(\mathbf{q}) = \left\|\mathbf{p}_{\text{ref}}(\mathbf{q}) - \widetilde{\mathbf{p}}(\mathbf{q})\right\|_2. \]
For rotation, define the relative rotation \( \mathbf{R}_{\text{rel}}(\mathbf{q}) = \mathbf{R}_{\text{ref}}(\mathbf{q})\,\widetilde{\mathbf{R}}(\mathbf{q})^\top \) and the geodesic angle
\[ e_{\text{rot}}(\mathbf{q}) = \arccos\left( \frac{\operatorname{trace}(\mathbf{R}_{\text{rel}}(\mathbf{q})) - 1}{2} \right). \]
Empirically sampling many joint vectors \( \mathbf{q} \) and measuring \( e_{\text{pos}} \) and \( e_{\text{rot}} \) is a robust way of validating the toolbox implementation against a known-good formulation (e.g., symbolic Mathematica expressions).
11. Problems and Solutions
Problem 1 (2R Planar Arm – Analytic vs PoE Numerical FK): Consider a planar 2R arm with link lengths \( L_1, L_2 \) and joint angles \( q_1, q_2 \). Using standard planar geometry, the end-effector position is
\[ x = L_1 \cos q_1 + L_2 \cos(q_1 + q_2), \qquad y = L_1 \sin q_1 + L_2 \sin(q_1 + q_2). \]
(a) Derive a PoE representation (screw axes and \( \mathbf{M} \)). (b) Show that the numeric PoE FK yields the same \( (x, y) \).
Solution:
(a) Model the base frame at the first joint with the \(z\)-axis coming out of the plane. The first joint rotates about \( \boldsymbol{\omega}_1 = [0, 0, 1]^\top \) passing through the origin, so for a revolute joint in the space frame:
\[ \boldsymbol{\xi}_1 = \begin{bmatrix} \boldsymbol{\omega}_1 \\ \mathbf{v}_1 \end{bmatrix}, \quad \mathbf{v}_1 = -\boldsymbol{\omega}_1 \times \mathbf{q}_1^0 = \mathbf{0}, \]
because the joint axis passes through the origin. The second joint rotates about a parallel axis at position \( [L_1, 0, 0]^\top \), so
\[ \boldsymbol{\xi}_2 = \begin{bmatrix} \boldsymbol{\omega}_1 \\ \mathbf{v}_2 \end{bmatrix}, \quad \mathbf{v}_2 = -\boldsymbol{\omega}_1 \times \begin{bmatrix} L_1 \\ 0 \\ 0 \end{bmatrix} = \begin{bmatrix} 0 \\ -L_1 \\ 0 \end{bmatrix}. \]
Take the home configuration \( \mathbf{M} \) as the end-effector pose when \( q_1 = q_2 = 0 \), that is the point \( (L_1 + L_2, 0, 0) \) with identity rotation:
\[ \mathbf{M} = \begin{bmatrix} 1 & 0 & 0 & L_1 + L_2 \\ 0 & 1 & 0 & 0 \\ 0 & 0 & 1 & 0 \\ 0 & 0 & 0 & 1 \end{bmatrix}. \]
(b) Using the general PoE expression \( \mathbf{T}(\mathbf{q}) = \exp(\widehat{\boldsymbol{\xi}}_1 q_1)\exp(\widehat{\boldsymbol{\xi}}_2 q_2)\mathbf{M} \), substituting the planar twist exponentials reduces exactly to the planar rotation matrices with angles \( q_1 \) and \( q_1+q_2 \). Extracting translation from the final transform yields the same \( x, y \) as the analytic formulas, confirming the correctness of the numeric implementation.
Problem 2 (DH-based FK Implementation): For a 3-DOF manipulator with standard DH parameters \( (a_i, \alpha_i, d_i, \theta_i) \), write the expression for \( \mathbf{T}(\mathbf{q}) \) and propose a numerical algorithm that uses these parameters as input.
Solution:
For each joint \( i = 1,2,3 \), the standard DH transform is \( \mathbf{A}_i(q_i) \) as given in Section 3, with either \( \theta_i = q_i + \theta_i^{\text{offset}} \) (revolute) or \( d_i = q_i + d_i^{\text{offset}} \) (prismatic). Then
\[ \mathbf{T}(\mathbf{q}) = \mathbf{A}_1(q_1)\,\mathbf{A}_2(q_2)\,\mathbf{A}_3(q_3). \]
The numerical algorithm:
- Pre-store all constant DH parameters and joint types.
- Given \( \mathbf{q} \), compute \( \theta_i \) or \( d_i \) for each joint.
- Compute each \( \mathbf{A}_i(q_i) \) with the closed-form trigonometric expressions.
- Multiply the \( 4\times 4 \) matrices in order to obtain \( \mathbf{T}(\mathbf{q}) \).
This is straightforward to implement in all discussed languages using the same pattern of a loop over joints.
Problem 3 (Rotational Orthogonality Check): Let \( \widetilde{\mathbf{R}} \) be a numerically computed rotation matrix. Show that if \( \epsilon_R = \|\widetilde{\mathbf{R}}^\top \widetilde{\mathbf{R}} - \mathbf{I}_3\|_F \) and \( \epsilon_{\det} = |\det(\widetilde{\mathbf{R}}) - 1| \) are both small, then \( \widetilde{\mathbf{R}} \) is close to a true rotation in \( SO(3) \). Why does this justify accepting the result in numeric FK?
Solution:
The set \( SO(3) \) is defined by the constraints \( \mathbf{R}^\top \mathbf{R} = \mathbf{I}_3 \) and \( \det(\mathbf{R}) = 1 \). If \( \epsilon_R \) and \( \epsilon_{\det} \) are small, then \( \widetilde{\mathbf{R}} \) satisfies these constraints approximately. Because \( SO(3) \) is a compact manifold and the constraints are smooth, there exists a nearby exact rotation \( \mathbf{R}^\star \in SO(3) \) with \( \|\mathbf{R}^\star - \widetilde{\mathbf{R}}\| \) bounded by a function of \( \epsilon_R \) and \( \epsilon_{\det} \). Thus \( \widetilde{\mathbf{R}} \) can be treated as a numerically valid rotation. In practice, if \( \epsilon_R \) and \( \epsilon_{\det} \) are below thresholds consistent with double-precision arithmetic, the FK result is considered trustworthy.
Problem 4 (Complexity of FK): Consider an \( n \)-DOF serial chain. Assume that computing each \( \mathbf{T}_i(q_i) \) costs a constant number of floating-point operations, and that \( 4\times 4 \) matrix multiplication costs a fixed number \( C_{\text{mult}} \) of operations. What is the computational complexity of a naive FK implementation? How does this compare with precomputing constant transforms (e.g., for offsets)?
Solution:
Computing each \( \mathbf{T}_i(q_i) \) involves a constant number of trigonometric and algebraic operations, so the cost is \( O(n) \) over all joints. Each matrix multiplication \( \mathbf{T} \leftarrow \mathbf{T}\mathbf{T}_i \) costs \( C_{\text{mult}} \) floating-point operations, again \( O(1) \) per joint. Therefore, the total complexity is
\[ O(n) \quad \text{for computing all } \mathbf{T}_i \quad \text{plus} \quad O(n) \quad \text{for the products}, \]
which is overall linear \( O(n) \). Precomputing constant parts of the transforms (e.g., fixed rotations due to offsets) reduces the constant factor but not the asymptotic complexity; we still must perform a constant amount of work per joint.
Problem 5 (Numeric vs Symbolic FK Cross-Check): Suppose Mathematica produces an exact analytic expression \( \mathbf{T}_{\text{sym}}(\mathbf{q}) \) and your Python toolbox computes \( \mathbf{T}_{\text{num}}(\mathbf{q}) \). Describe a systematic procedure for verifying the Python implementation using random tests and the error metrics introduced in this lesson.
Solution:
- Generate a large set of random joint vectors \( \mathbf{q}^{(k)} \) inside the joint limits.
- For each sample, compute \( \mathbf{T}_{\text{sym}}(\mathbf{q}^{(k)}) \) in Mathematica (exported or re-implemented in high precision) and \( \mathbf{T}_{\text{num}}(\mathbf{q}^{(k)}) \) in Python.
- Compute \( e_{\text{pos}}(\mathbf{q}^{(k)}) \) and \( e_{\text{rot}}(\mathbf{q}^{(k)}) \) as defined in Section 10 for every sample.
- Report summary statistics (max, mean) of these errors. If they are on the order of machine precision (e.g., \( 10^{-12} \)–\( 10^{-10} \) for double precision), the implementations agree.
- Also check \( \epsilon_R \) and \( \epsilon_{\det} \) for \( \mathbf{T}_{\text{num}}(\mathbf{q}^{(k)}) \) to ensure membership in \( SE(3) \) up to tolerance.
This cross-check exploits both symbolic accuracy and numeric sampling to provide high confidence in the software implementation.
12. Summary
In this lab-oriented lesson we translated the abstract PoE and DH formulations of forward kinematics into concrete, numerical algorithms and reusable software components. We implemented the twist exponential and FK map in Python, C++, Java, MATLAB, Simulink, and Wolfram Mathematica, and discussed diagnostics for checking that numeric transforms remain in \( SE(3) \). These FK toolboxes form a foundation for subsequent topics such as inverse kinematics, differential kinematics, and eventually dynamics and control, where accurate evaluation of \( \mathbf{T}(\mathbf{q}) \) is essential.
13. References
- Brockett, R.W. (1984). Robotic manipulators and the product of exponentials formula. Mathematical Theory of Networks and Systems, 120–129.
- Murray, R.M., Li, Z., & Sastry, S.S. (1994). A Mathematical Introduction to Robotic Manipulation. CRC Press. (Chapters on Lie groups and PoE formulation.)
- Park, F.C., & Brockett, R.W. (1994). Kinematic dexterity of robotic mechanisms. International Journal of Robotics Research, 13(1), 1–15.
- Angeles, J. (1997). On the numerical kinematic analysis of robotic manipulators. Mechanism and Machine Theory, 32(8), 939–951.
- Featherstone, R. (2005). A beginner's guide to 6-D vectors (part 1: kinematics). IEEE Robotics & Automation Magazine, 12(3), 83–92.
- Spong, M.W., Hutchinson, S., & Vidyasagar, M. (2005). Robot Modeling and Control. Wiley. (Chapters on kinematics.)
- Lynch, K.M., & Park, F.C. (2017). Modern Robotics: Mechanics, Planning, and Control. Cambridge University Press. (Chapters on PoE and numerical kinematics.)
- Siciliano, B., Sciavicco, L., Villani, L., & Oriolo, G. (2009). Robotics: Modelling, Planning and Control. Springer.
- Yoshikawa, T. (1985). Manipulability of robotic mechanisms. International Journal of Robotics Research, 4(2), 3–9.
- Angeles, J. (2013). Fundamentals of Robotic Mechanical Systems: Theory, Methods, and Algorithms. Springer.