Chapter 4: Serial-Chain Kinematics Structure

Lesson 3: Open-Chain vs. Closed-Chain Modeling

This lesson develops a rigorous distinction between open-chain (serial) and closed-chain kinematic structures. We formalize configuration variables and loop-closure constraints, show how open chains naturally lead to unconstrained generalized coordinates, and introduce constraint equations for closed chains, using simple examples such as planar manipulators and four-bar linkages. We close with multi-language computational implementations and problem sets.

1. Conceptual Overview: What Is a Kinematic Chain?

In previous lessons, you saw how links and joints define a kinematic chain. Let \( \mathcal{L} = \{1,\dots,n\} \) denote the set of moving links and let \( \mathcal{J} = \{1,\dots,m\} \) denote the set of joints. Each joint \( j \in \mathcal{J} \) connects two links and introduces one or more generalized coordinates. Collect all generalized coordinates in the vector \( \mathbf{q} \in \mathbb{R}^n \).

A kinematic structure can be regarded as a graph whose vertices are links and whose edges are joints. Two fundamental cases are:

  • Open chain (serial chain): the underlying graph is a tree with one base link. There is a unique path between any two links, and there are no cycles.
  • Closed chain: the underlying graph contains one or more cycles (loops). Some links are connected in a way that returns to the starting link.

In both cases, we describe configurations by homogeneous transforms between link frames. Let \( \mathbf{T}_{i-1,i}(q_i) \in SE(3) \) be the homogeneous transform from frame \( i \) to frame \( i-1 \) associated with joint coordinate \( q_i \). The base link has frame \( \{0\} \).

flowchart LR
  B["Base link 0"] --> L1["Link 1 (open chain)"]
  L1 --> L2["Link 2"]
  L2 --> L3["Link 3"]
  L3 --> E1["End-effector (open)"]

  B2["Base link 0'"] --> C1["Link A (closed chain)"]
  C1 --> C2["Link B"]
  C2 --> C3["Link C"]
  C3 --> C4["Link D"]
  C4 --> B2
  C2 --> EE["End-effector inside loop"]
        

Intuitively, open chains are like a robot arm mounted to a base. Closed chains resemble mechanisms such as four-bar linkages or parallel robots, in which the end-effector is constrained by multiple legs forming loops.

2. Open-Chain Modeling as Serial Composition of Transforms

For an open chain, the structure is a tree rooted at the base. Because there are no loops, specifying all joint coordinates \( \mathbf{q} = [q_1,\dots,q_n]^T \) uniquely determines the pose of every link and the end-effector. There are no algebraic constraints between joint coordinates beyond simple bounds such as joint limits.

For a single serial chain from base frame \( \{0\} \) to end-effector frame \( \{n\} \), the forward kinematics map is a product of homogeneous transforms:

\[ \mathbf{T}_{0n}(\mathbf{q}) = \mathbf{T}_{0,1}(q_1)\, \mathbf{T}_{1,2}(q_2)\,\cdots\, \mathbf{T}_{n-1,n}(q_n) \in SE(3). \]

Using the notation from previous chapters, this defines a smooth mapping from joint space to configuration space:

\[ f:\mathbb{R}^n \rightarrow SE(3),\quad f(\mathbf{q}) = \mathbf{T}_{0n}(\mathbf{q}). \]

At this stage we do not yet compute derivatives of \( f \) (that will appear in the Jacobian and differential kinematics), but we already see that an open-chain manipulator has:

  • A configuration space that is locally \( n \)-dimensional (each joint variable is independent).
  • No algebraic constraints \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \) that must be enforced between joint coordinates.

In practice, open-chain models are convenient because many algorithms (forward kinematics, dynamics, control) can be written as recursive passes along the chain without having to solve constraint equations.

3. Closed-Chain Modeling and Loop-Closure Equations

In a closed chain, some links form one or more kinematic loops. Consider a loop that starts from a link with frame \( \{A\} \), traverses a sequence of joints and links, and returns to the same link/frame \(\{A\}\). Along that loop, the composition of relative transforms must be equal to a known constant transform.

For a simple loop with joints \( j_1,\dots,j_k \) and corresponding transforms \( \mathbf{T}_{j_\ell}(q_{j_\ell}) \), the loop-closure constraint can be written as

\[ \mathbf{T}_{loop}(\mathbf{q}) = \mathbf{T}_{j_1}(q_{j_1})\, \mathbf{T}_{j_2}(q_{j_2})\,\cdots\, \mathbf{T}_{j_k}(q_{j_k}) = \mathbf{T}_{loop}^{\ast}, \]

where \( \mathbf{T}_{loop}^{\ast} \in SE(3) \) is a fixed transform determined by the mechanism geometry (for example, the transform between two ground pivots).

To convert this matrix equation into scalar constraint equations, we use a minimal parameterization of \( SE(3) \). A common approach is to apply the matrix logarithm (introduced in the Lie groups chapter) and stack the rotational and translational components:

\[ \boldsymbol{\Phi}(\mathbf{q}) = \operatorname{Log}\!\left( \mathbf{T}_{loop}^{\ast\,\!-1}\, \mathbf{T}_{loop}(\mathbf{q}) \right) \in \mathbb{R}^6, \quad \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0}. \]

In a mechanism with multiple loops, we assemble all loop constraints into a single vector-valued function \( \boldsymbol{\Phi}:\mathbb{R}^n \rightarrow \mathbb{R}^c \), where \( c \) is the total number of independent scalar constraint equations needed to enforce closure of all loops. The feasible configuration manifold is then

\[ \mathcal{C} = \left\{ \mathbf{q} \in \mathbb{R}^n\;:\; \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \right\}. \]

For an open chain, \( c = 0 \) and \( \mathcal{C} \) is locally \( n \)-dimensional. For a closed chain, \( c > 0 \) and the dimension of \( \mathcal{C} \) is strictly smaller than \( n \).

4. Example — Planar Four-Bar Linkage as a Closed Chain

A planar four-bar linkage is one of the simplest closed-chain mechanisms and is fundamental in robot wrists and parallel manipulators. It consists of four rigid links connected by revolute joints in a loop.

flowchart TD
  A["Ground pivot A"] --- L1["Link 1 (length L1)"]
  L1 --- B["Joint B"]
  B --- L2["Link 2 (length L2)"]
  L2 --- C["Joint C"]
  C --- L3["Link 3 (length L3)"]
  L3 --- D["Joint D on ground link (length L0)"]
  D --- A
        

Place the ground pivot \( A \) at the origin of a planar frame and the ground pivot \( D \) on the x-axis at position \( \mathbf{p}_D = [L_0,\,0]^T \). Let links \( 1 \) and \( 3 \) have lengths \( L_1 \) and \( L_3 \), and let the joint angles at \( A \) and \( D \) be \( \theta_1 \) and \( \theta_3 \), measured from the positive x-axis.

The position of joint \( B \) is

\[ \mathbf{p}_B(\theta_1) = \begin{bmatrix} L_1 \cos\theta_1 \\ L_1 \sin\theta_1 \end{bmatrix}. \]

The position of joint \( C \), expressed from pivot \( D \), is

\[ \mathbf{p}_C(\theta_3) = \mathbf{p}_D + \begin{bmatrix} L_3 \cos\theta_3 \\ L_3 \sin\theta_3 \end{bmatrix} = \begin{bmatrix} L_0 + L_3 \cos\theta_3 \\ L_3 \sin\theta_3 \end{bmatrix}. \]

The coupler link between \( B \) and \( C \) has fixed length \( L_2 \). Loop closure requires that the distance between \( \mathbf{p}_B \) and \( \mathbf{p}_C \) equals \( L_2 \). This leads to the scalar constraint equation

\[ \Phi(\theta_1,\theta_3) = \left\| \mathbf{p}_B(\theta_1) - \mathbf{p}_C(\theta_3) \right\|^2 - L_2^2 = 0. \]

Expanding the norm yields

\[ \begin{aligned} \Phi(\theta_1,\theta_3) &= \left(L_1 \cos\theta_1 - L_0 - L_3 \cos\theta_3\right)^2 + \left(L_1 \sin\theta_1 - L_3 \sin\theta_3\right)^2 - L_2^2 = 0. \end{aligned} \]

Here \( (\theta_1,\theta_3) \) are not independent: given \( \theta_1 \), the loop-closure equation determines admissible values of \( \theta_3 \). If you treat both angles as coordinates, then the four-bar mechanism is described as a closed chain with:

  • \( n = 2 \) joint coordinates \( (\theta_1,\theta_3) \),
  • \( c = 1 \) scalar constraint equation \( \Phi(\theta_1,\theta_3)=0 \),
  • a configuration manifold locally of dimension \( n - c = 1 \).

In later lessons, this viewpoint will be unified with general DOF counting and parallel-robot modeling.

5. Constraint Jacobian and Independent Coordinates (Kinematic View)

Closed-chain modeling often starts by writing constraints in the form \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \). When \( \boldsymbol{\Phi} \) is differentiable, its Jacobian with respect to the joint coordinates is

\[ \mathbf{J}_\Phi(\mathbf{q}) = \frac{\partial \boldsymbol{\Phi}}{\partial \mathbf{q}}(\mathbf{q}) \in \mathbb{R}^{c \times n}, \]

where each row corresponds to gradients of a scalar constraint. If \( \mathbf{J}_\Phi(\mathbf{q}_0) \) has full row rank \( c \) at some configuration \( \mathbf{q}_0 \), then the implicit function theorem tells us that, locally around \( \mathbf{q}_0 \), we can solve the constraints to eliminate \( c \) of the coordinates, leaving \( n-c \) independent generalized coordinates.

Practically, we often choose a subset of joint coordinates \( \mathbf{q}_r \in \mathbb{R}^{n-c} \) as independent (or reduced coordinates), and express the remaining coordinates \( \mathbf{q}_d \in \mathbb{R}^c \) as functions \( \mathbf{q}_d = g(\mathbf{q}_r) \) obtained from solving the constraints. This gives an explicit parametrization

\[ \mathbf{q} = \begin{bmatrix} \mathbf{q}_r \\[4pt] \mathbf{q}_d(\mathbf{q}_r) \end{bmatrix},\quad \mathbf{q}_r \in \mathbb{R}^{n-c}, \]

and allows us to reuse open-chain-style kinematic formulas, but in terms of a reduced coordinate vector. This strategy is central to closed-chain kinematics and dynamics and will be extended later when we consider velocities and forces.

6. Computational Lab — Open vs Closed Chain in Python

We now implement simple planar models in Python using numpy. The open chain is a 2R planar manipulator. The closed chain is a four-bar linkage where we solve for the dependent angle using a scalar nonlinear equation.


import numpy as np

def rot2(theta):
    """2D rotation matrix."""
    c = np.cos(theta)
    s = np.sin(theta)
    return np.array([[c, -s],
                     [s,  c]])

def fk_planar_2R(q, L):
    """
    Open-chain forward kinematics for a planar 2R arm.
    q: array-like [theta1, theta2]
    L: array-like [L1, L2] link lengths
    Returns end-effector position in R^2.
    """
    theta1, theta2 = q
    L1, L2 = L

    p1 = rot2(theta1) @ np.array([L1, 0.0])
    p2 = p1 + rot2(theta1 + theta2) @ np.array([L2, 0.0])
    return p2

def fourbar_constraint(theta1, theta3, L0, L1, L2, L3):
    """
    Loop-closure constraint Phi(theta1, theta3) for the four-bar example.
    Returns scalar Phi; feasible configurations satisfy Phi = 0.
    """
    pB = np.array([L1 * np.cos(theta1),
                   L1 * np.sin(theta1)])
    pC = np.array([L0 + L3 * np.cos(theta3),
                   0.0 + L3 * np.sin(theta3)])
    Phi = np.dot(pB - pC, pB - pC) - L2**2
    return Phi

def solve_fourbar_theta3(theta1, L0, L1, L2, L3,
                         theta3_init=0.0,
                         tol=1e-10,
                         max_iter=50):
    """
    Solve Phi(theta1, theta3) = 0 for theta3 using Newton iteration.
    For robustness in practice, bracketed solvers or multiple initial
    guesses are recommended.
    """
    theta3 = float(theta3_init)
    for k in range(max_iter):
        Phi = fourbar_constraint(theta1, theta3, L0, L1, L2, L3)

        # Numerical derivative dPhi/dtheta3 via symmetric difference
        h = 1e-6
        Phi_p = fourbar_constraint(theta1, theta3 + h, L0, L1, L2, L3)
        Phi_m = fourbar_constraint(theta1, theta3 - h, L0, L1, L2, L3)
        dPhi = (Phi_p - Phi_m) / (2.0 * h)

        if abs(dPhi) < 1e-12:
            break  # avoid division by zero

        step = Phi / dPhi
        theta3 -= step

        if abs(step) < tol:
            break
    return theta3

if __name__ == "__main__":
    # Open chain: 2R arm
    L = [0.5, 0.4]
    q = [0.5, -0.3]
    p_ee = fk_planar_2R(q, L)
    print("Open-chain end-effector position:", p_ee)

    # Closed chain: four-bar linkage
    L0, L1, L2, L3 = 0.8, 0.5, 0.7, 0.4
    theta1 = 0.4
    theta3 = solve_fourbar_theta3(theta1, L0, L1, L2, L3, theta3_init=0.2)
    print("Closed-chain solution theta3:", theta3)
    print("Constraint residual:",
          fourbar_constraint(theta1, theta3, L0, L1, L2, L3))
      

Observe the structural difference: the open-chain model exports a function that maps directly from independent joint coordinates to end-effector position, whereas the closed-chain model includes an intermediate step of solving a constraint equation.

7. C++ Implementation Sketch

In C++, we can use simple structures for 2D vectors and write explicit functions for open- and closed-chain kinematics. Below we avoid external libraries for clarity.


#include <iostream>
#include <cmath>

struct Vec2 {
    double x;
    double y;
};

Vec2 rot2_times_vec(double theta, const Vec2 &v) {
    double c = std::cos(theta);
    double s = std::sin(theta);
    Vec2 out;
    out.x = c * v.x - s * v.y;
    out.y = s * v.x + c * v.y;
    return out;
}

Vec2 fk_planar_2R(double theta1, double theta2,
                  double L1, double L2) {
    Vec2 e1{L1, 0.0};
    Vec2 e2{L2, 0.0};

    Vec2 p1 = rot2_times_vec(theta1, e1);
    Vec2 p2 = p1;
    Vec2 e2_rot = rot2_times_vec(theta1 + theta2, e2);
    p2.x += e2_rot.x;
    p2.y += e2_rot.y;
    return p2;
}

double fourbar_constraint(double theta1, double theta3,
                          double L0, double L1, double L2, double L3) {
    Vec2 pB{L1 * std::cos(theta1),
            L1 * std::sin(theta1)};
    Vec2 pC{L0 + L3 * std::cos(theta3),
            L3 * std::sin(theta3)};
    double dx = pB.x - pC.x;
    double dy = pB.y - pC.y;
    return dx*dx + dy*dy - L2*L2;
}

double solve_fourbar_theta3(double theta1,
                            double L0, double L1, double L2, double L3,
                            double theta3_init = 0.0,
                            double tol = 1e-10,
                            int max_iter = 50) {
    double theta3 = theta3_init;
    for (int k = 0; k < max_iter; ++k) {
        double Phi = fourbar_constraint(theta1, theta3, L0, L1, L2, L3);
        double h = 1e-6;
        double Phi_p = fourbar_constraint(theta1, theta3 + h, L0, L1, L2, L3);
        double Phi_m = fourbar_constraint(theta1, theta3 - h, L0, L1, L2, L3);
        double dPhi = (Phi_p - Phi_m) / (2.0 * h);
        if (std::fabs(dPhi) < 1e-14) {
            break;
        }
        double step = Phi / dPhi;
        theta3 -= step;
        if (std::fabs(step) < tol) {
            break;
        }
    }
    return theta3;
}

int main() {
    // Open-chain example
    double theta1 = 0.5, theta2 = -0.3;
    double L1 = 0.5, L2 = 0.4;
    Vec2 p = fk_planar_2R(theta1, theta2, L1, L2);
    std::cout << "Open-chain end-effector: ("
              << p.x << ", " << p.y << ")\n";

    // Closed-chain four-bar example
    double L0 = 0.8, L3 = 0.4, Lmid = 0.7;
    double theta1_in = 0.4;
    double theta3 = solve_fourbar_theta3(theta1_in, L0, L1, Lmid, L3, 0.2);
    std::cout << "Closed-chain theta3: " << theta3 << "\n";
    std::cout << "Constraint residual: "
              << fourbar_constraint(theta1_in, theta3, L0, L1, Lmid, L3)
              << "\n";
    return 0;
}
      

Note again the extra layer of numerical solving for the closed chain, compared to straightforward evaluation for the open chain.

8. Java Implementation Sketch

In Java, we encapsulate the same computations in a class with static methods. For clarity we focus on computation, not on GUI or plotting.


public class OpenClosedKinematics {

    public static double[] rot2TimesVec(double theta, double[] v) {
        double c = Math.cos(theta);
        double s = Math.sin(theta);
        return new double[] {
            c * v[0] - s * v[1],
            s * v[0] + c * v[1]
        };
    }

    public static double[] fkPlanar2R(double theta1, double theta2,
                                      double L1, double L2) {
        double[] e1 = new double[] {L1, 0.0};
        double[] e2 = new double[] {L2, 0.0};

        double[] p1 = rot2TimesVec(theta1, e1);
        double[] e2rot = rot2TimesVec(theta1 + theta2, e2);

        double[] p2 = new double[2];
        p2[0] = p1[0] + e2rot[0];
        p2[1] = p1[1] + e2rot[1];
        return p2;
    }

    public static double fourbarConstraint(double theta1, double theta3,
                                           double L0, double L1,
                                           double L2, double L3) {
        double xB = L1 * Math.cos(theta1);
        double yB = L1 * Math.sin(theta1);
        double xC = L0 + L3 * Math.cos(theta3);
        double yC =        L3 * Math.sin(theta3);
        double dx = xB - xC;
        double dy = yB - yC;
        return dx*dx + dy*dy - L2*L2;
    }

    public static double solveFourbarTheta3(double theta1,
                                            double L0, double L1,
                                            double L2, double L3,
                                            double theta3Init,
                                            double tol, int maxIter) {
        double theta3 = theta3Init;
        for (int k = 0; k < maxIter; ++k) {
            double Phi = fourbarConstraint(theta1, theta3, L0, L1, L2, L3);
            double h = 1e-6;
            double PhiP = fourbarConstraint(theta1, theta3 + h, L0, L1, L2, L3);
            double PhiM = fourbarConstraint(theta1, theta3 - h, L0, L1, L2, L3);
            double dPhi = (PhiP - PhiM) / (2.0 * h);
            if (Math.abs(dPhi) < 1e-14) break;
            double step = Phi / dPhi;
            theta3 -= step;
            if (Math.abs(step) < tol) break;
        }
        return theta3;
    }

    public static void main(String[] args) {
        double theta1 = 0.5, theta2 = -0.3;
        double L1 = 0.5, L2 = 0.4;
        double[] p = fkPlanar2R(theta1, theta2, L1, L2);
        System.out.println("Open-chain end-effector: (" +
                           p[0] + ", " + p[1] + ")");

        double L0 = 0.8, L3 = 0.4, Lmid = 0.7;
        double theta1In = 0.4;
        double theta3 = solveFourbarTheta3(theta1In, L0, L1, Lmid, L3,
                                           0.2, 1e-10, 50);
        System.out.println("Closed-chain theta3: " + theta3);
        System.out.println("Constraint residual: " +
            fourbarConstraint(theta1In, theta3, L0, L1, Lmid, L3));
    }
}
      

9. MATLAB/Simulink Implementation

MATLAB is natural for matrix-based kinematics. Simulink can wrap these functions via a MATLAB Function block to build kinematic simulation models.


function demo_open_closed_kinematics()
    % Open-chain 2R forward kinematics
    L = [0.5; 0.4];
    q = [0.5; -0.3];
    p_ee = fk_planar_2R(q, L);
    fprintf('Open-chain end-effector: (%.4f, %.4f)\n', p_ee(1), p_ee(2));

    % Closed-chain four-bar: solve for dependent angle
    L0 = 0.8; L1 = 0.5; L2 = 0.7; L3 = 0.4;
    theta1 = 0.4;
    theta3_init = 0.2;
    theta3 = solve_fourbar_theta3(theta1, L0, L1, L2, L3, theta3_init);
    Phi_res = fourbar_constraint(theta1, theta3, L0, L1, L2, L3);
    fprintf('Closed-chain theta3: %.6f, residual: %.3e\n', theta3, Phi_res);
end

function p2 = fk_planar_2R(q, L)
    theta1 = q(1); theta2 = q(2);
    L1 = L(1); L2 = L(2);
    R1 = rot2(theta1);
    R12 = rot2(theta1 + theta2);
    p1 = R1 * [L1; 0];
    p2 = p1 + R12 * [L2; 0];
end

function R = rot2(theta)
    c = cos(theta); s = sin(theta);
    R = [c, -s; s, c];
end

function Phi = fourbar_constraint(theta1, theta3, L0, L1, L2, L3)
    pB = [L1 * cos(theta1); L1 * sin(theta1)];
    pC = [L0 + L3 * cos(theta3); L3 * sin(theta3)];
    d = pB - pC;
    Phi = d' * d - L2^2;
end

function theta3 = solve_fourbar_theta3(theta1, L0, L1, L2, L3, theta3_init)
    % Use fsolve (Optimization Toolbox) if available; otherwise, simple Newton.
    theta3 = theta3_init;
    for k = 1:50
        Phi = fourbar_constraint(theta1, theta3, L0, L1, L2, L3);
        h = 1e-6;
        Phi_p = fourbar_constraint(theta1, theta3 + h, L0, L1, L2, L3);
        Phi_m = fourbar_constraint(theta1, theta3 - h, L0, L1, L2, L3);
        dPhi = (Phi_p - Phi_m) / (2*h);
        if abs(dPhi) < 1e-14
            break;
        end
        step = Phi / dPhi;
        theta3 = theta3 - step;
        if abs(step) < 1e-10
            break;
        end
    end
end
      

Simulink note: to build a closed-chain block diagram, wrap fk_planar_2R or the four-bar solver in a MATLAB Function block, and feed joint angles from signal lines. The structural difference is that closed chains typically introduce algebraic loops in Simulink, reflecting the loop-closure constraints.

10. Wolfram Mathematica Implementation

Wolfram Mathematica supports symbolic manipulation of transforms and constraints. Below we define rotation matrices, open-chain kinematics, and a symbolic four-bar constraint, then solve for the dependent angle.


(* 2D rotation matrix *)
rot2[theta_] := { {Cos[theta], -Sin[theta]},
                 {Sin[theta],  Cos[theta]} };

(* Open-chain 2R kinematics *)
fkPlanar2R[{theta1_, theta2_}, {L1_, L2_}] :=
 Module[{p1, p2},
  p1 = rot2[theta1].{L1, 0};
  p2 = p1 + rot2[theta1 + theta2].{L2, 0};
  p2
 ]

(* Four-bar constraint symbolically *)
Clear[theta1, theta3, L0, L1, L2, L3];
pB = {L1*Cos[theta1], L1*Sin[theta1]};
pC = {L0 + L3*Cos[theta3], L3*Sin[theta3]};
Phi = (pB - pC).(pB - pC] - L2^2;

(* Solve for theta3 given geometric parameters and theta1 *)
solution =
  Solve[Phi == 0 /. {L0 -> 0.8, L1 -> 0.5, L2 -> 0.7, L3 -> 0.4,
                     theta1 -> 0.4}, theta3];

N[solution]
      

Symbolic tools can reveal multiple branches of the solution \( \theta_3 \), corresponding to different assembly modes of the mechanism (elbow-up vs. elbow-down configurations), which is a typical feature of closed chains.

11. Problems and Solutions

Problem 1 (Graph-based classification): Consider a mechanism whose links and joints are represented by a graph with 6 movable links plus a base link and 7 joints. The graph has no cycles. Classify the mechanism as open-chain or closed-chain and justify your answer in terms of constraints.

Solution: A graph with \( n_L = 7 \) links (including the base) and \( n_J = 7 \) joints but no cycles is a tree. There is a unique path connecting any pair of links, so there are no kinematic loops. Thus the mechanism is an open-chain (serial) structure. In terms of modeling, the joint coordinates can be taken as independent generalized coordinates with \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \) of dimension \( c = 0 \).

Problem 2 (Matrix loop-closure): A planar mechanism consists of a base frame \( \{0\} \), a revolute joint 1 with angle \( q_1 \), a prismatic joint 2 with displacement \( q_2 \), and a revolute joint 3 with angle \( q_3 \), returning to the base frame. The relative transforms are \( \mathbf{T}_{0,1}(q_1) \), \( \mathbf{T}_{1,2}(q_2) \), \( \mathbf{T}_{2,3}(q_3) \), and \( \mathbf{T}_{3,0}^{\ast} \) is a fixed transform from link 3 back to the base. Write the loop-closure equation in matrix form and express it as a constraint \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \).

Solution: Traversing the loop from the base:

\[ \mathbf{T}_{0,1}(q_1)\, \mathbf{T}_{1,2}(q_2)\, \mathbf{T}_{2,3}(q_3)\, \mathbf{T}_{3,0}^{\ast} = \mathbf{I}_{4}. \]

Rearranging,

\[ \mathbf{T}_{loop}(\mathbf{q}) = \mathbf{T}_{0,1}(q_1)\, \mathbf{T}_{1,2}(q_2)\, \mathbf{T}_{2,3}(q_3)\, \mathbf{T}_{3,0}^{\ast} = \mathbf{I}_4. \]

We can then define \( \boldsymbol{\Phi}(\mathbf{q}) = \operatorname{Log}(\mathbf{T}_{loop}(\mathbf{q})) \in \mathbb{R}^3 \) (planar case: one rotational and two translational components), and the constraint set is \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \).

Problem 3 (Four-bar scalar constraint): For the four-bar linkage in Section 4, take the constraint function \[ \Phi(\theta_1,\theta_3) = \left\| \mathbf{p}_B(\theta_1) - \mathbf{p}_C(\theta_3) \right\|^2 - L_2^2. \] Compute the partial derivatives \( \partial\Phi / \partial\theta_1 \) and \( \partial\Phi / \partial\theta_3 \), and interpret them as rows of the constraint Jacobian.

Solution: Recall \[ \mathbf{p}_B = \begin{bmatrix} L_1 \cos\theta_1 \\ L_1 \sin\theta_1 \end{bmatrix},\quad \mathbf{p}_C = \begin{bmatrix} L_0 + L_3 \cos\theta_3 \\ L_3 \sin\theta_3 \end{bmatrix}. \] Let \( \mathbf{d} = \mathbf{p}_B - \mathbf{p}_C \), so \( \Phi = \mathbf{d}^T \mathbf{d} - L_2^2 \). Then

\[ \frac{\partial\Phi}{\partial\theta_1} = 2\,\mathbf{d}^T \frac{\partial\mathbf{d}}{\partial\theta_1} = 2\,\mathbf{d}^T \begin{bmatrix} -L_1 \sin\theta_1 \\ L_1 \cos\theta_1 \end{bmatrix}, \]

\[ \frac{\partial\Phi}{\partial\theta_3} = 2\,\mathbf{d}^T \frac{\partial\mathbf{d}}{\partial\theta_3} = 2\,\mathbf{d}^T \begin{bmatrix} L_3 \sin\theta_3 \\ -L_3 \cos\theta_3 \end{bmatrix}. \]

In scalar form, the constraint Jacobian is \( \mathbf{J}_\Phi(\mathbf{q}) = \begin{bmatrix} \partial\Phi / \partial\theta_1 & \partial\Phi / \partial\theta_3 \end{bmatrix} \), which encodes the sensitivity of the loop-closure equation to changes in the joint angles.

Problem 4 (Dimension of configuration manifold): Let a mechanism have \( n \) joint coordinates \( \mathbf{q} \in \mathbb{R}^n \) and \( c \) independent differentiable constraint equations \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \) with full rank Jacobian \( \mathbf{J}_\Phi(\mathbf{q}_0) \) at some configuration \( \mathbf{q}_0 \). Show that, locally around \( \mathbf{q}_0 \), the configuration manifold has dimension \( n - c \).

Solution: By assumption, \( \mathbf{J}_\Phi(\mathbf{q}_0) \) has rank \( c \). The implicit function theorem applies: locally there exists a partition of coordinates into \( \mathbf{q} = (\mathbf{q}_r,\mathbf{q}_d) \) with \( \mathbf{q}_r \in \mathbb{R}^{n-c} \) and \( \mathbf{q}_d \in \mathbb{R}^c \), and a smooth function \( g \) such that \( \mathbf{q}_d = g(\mathbf{q}_r) \) parameterizes all solutions of \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \). Therefore, the solution set near \( \mathbf{q}_0 \) is diffeomorphic to an open set in \( \mathbb{R}^{n-c} \) and has local dimension \( n - c \). This formalizes the notion of independent generalized coordinates for a closed chain.

Problem 5 (Open vs closed chain in code): In the Python implementation, the open-chain function fk_planar_2R is called directly with joint angles. In contrast, the four-bar code uses solve_fourbar_theta3 before evaluating kinematics. Explain in modeling terms why the two-step process is necessary for the closed-chain mechanism.

Solution: For the open chain, the configuration map \( \mathbf{q} \mapsto \mathbf{T}_{0n}(\mathbf{q}) \) has no algebraic constraints: each joint angle is independent, so forward kinematics is a direct evaluation of a well-defined function. For the closed chain, the candidate joint variables \( (\theta_1,\theta_3) \) are not independent, because they must satisfy the loop-closure constraint \( \Phi(\theta_1,\theta_3) = 0 \). Therefore, given an input variable \( \theta_1 \), we must first compute a compatible dependent variable \( \theta_3 \) by solving the constraint equation. Only after enforcing this relation can we evaluate geometrically meaningful quantities such as the positions of coupler points. The solver thus enforces \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \) prior to using the kinematic model.

12. Summary

In this lesson, we distinguished open-chain (serial) and closed-chain kinematic structures at a modeling level. Open chains admit a direct mapping from independent joint coordinates to end-effector pose with no algebraic constraints. Closed chains require the introduction of loop-closure equations \( \boldsymbol{\Phi}(\mathbf{q}) = \mathbf{0} \), whose Jacobian \( \mathbf{J}_\Phi \) and local rank determine how many independent generalized coordinates can be chosen. Through the planar four-bar example, we saw how a single constraint couples joint angles, reducing the dimension of the configuration manifold. Multi-language implementations illustrated how closed chains introduce an extra layer of solving for dependent coordinates in numerical software. In the next lesson, these ideas will be connected to systematic DOF counting formulas and constraint-based modeling of more complex mechanisms.

13. References

  1. Denavit, J., & Hartenberg, R.S. (1955). A kinematic notation for lower-pair mechanisms based on matrices. Journal of Applied Mechanics, 22(2), 215–221.
  2. Uicker, J.J., Denavit, J., & Hartenberg, R.S. (1964). An approach to the kinematic analysis of spatial mechanisms. Journal of Applied Mechanics, 31(2), 309–314.
  3. Hunt, K.H. (1978). Kinematic geometry of mechanisms of parallel and serial type. Mechanism and Machine Theory, 13(5), 419–428.
  4. Waldron, K.J., & Hunt, K.H. (1985). Series-parallel dualities in actively coordinated mechanisms. The International Journal of Robotics Research, 4(2), 3–21.
  5. Angeles, J. (1988). The kinematics of parallel manipulators. IEEE Journal of Robotics and Automation, 4(4), 314–319.
  6. McCarthy, J.M. (1986). The kinematic synthesis of spatial four-bar linkages. Journal of Mechanisms, Transmissions, and Automation in Design, 108(4), 563–567.
  7. Tsai, L.W., & Morgan, A.P. (1985). Solving the kinematics of the most general six- and five-degree-of-freedom manipulators by continuation methods. Journal of Mechanisms, Transmissions, and Automation in Design, 107(2), 189–200.
  8. Park, F.C., & Kim, J.W. (1998). Manipulability of closed-chain mechanisms. Journal of Mechanical Design, 120(4), 542–548.