Robot Motion
Chapter 17PART VDynamics, Trajectories, and ConstraintsDifficulty: IntermediateEstimated reading time: 55 min

Robot Dynamics

The Lagrangian route from masses and lever arms to M(q) q̈ + C(q, q̇) q̇ + g(q) = τ — Christoffel symbols as the geometry of unforced motion, Pfaffian constraints with Lagrange multipliers, rigid-body rotation and Euler's equation, and an integrator whose energy bookkeeping is a test.

A path is not a complete description of the motion of a robot system, however, as the timing of the motion is not specified.
Howie Choset, Kevin Lynch, Seth Hutchinson, George Kantor, Wolfram Burgard, Lydia Kavraki, and Sebastian ThrunPrinciples of Robot Motion (2005), Chapter 10

In this chapter

Parts I through IV planned paths: curves in Qfree\Qfree with no clock attached. A real motor does not execute a curve. It applies a torque, and the configuration that results depends on masses, lever arms, and how fast everything else is already moving. This chapter builds the model that turns geometry into motion for the rest of Part V.

The idea the chapter unpacks fits in one sentence. The entire dynamics of an arm is encoded in one configuration-dependent symmetric matrix M(q)M(q). Kinetic energy is 12q˙TM(q)q˙\tfrac12 \dot q\T M(q)\dot q; the Coriolis and centrifugal forces are nothing but derivatives of MM — the Christoffel symbols; gravity is the gradient of a scalar. The reader who watched Reach's Jacobian ellipse collapse in Chapter 4 now watches a second ellipse — the inertia ellipse — breathe as the elbow folds, and learns why "force equals mass times acceleration" is false, term by term, in joint coordinates.

Everything here is Choset's Chapter 10, with one addition the 2005 text did not need because it assumed a Mathematica pipeline and never integrated anything: the skew-symmetry of M˙−2C\dot M - 2C is turned into a test oracle, and the integrator that powers every widget on this page is required to conserve energy to a stated tolerance. If the code and the prose ever disagree, the energy bar settles it.

The problem: a path says nothing about time

Take a path for Reach — a smooth curve on the torus, the kind every planner in Part III hands back — and replay it. Then replay it twice as fast.

t=0
Figure The same planned path (purple) replayed on Reach at two speeds, with a torque gauge for each joint. At 1× both gauges stay inside their limits of 20 and 12 N·m. At 2× the accelerations quadruple, the velocity-product terms quadruple, and both motors peg red. The planner that produced this curve never asked what the motors can do.

Nothing about the curve changed. What changed is q˙\dot q and q¨\ddot q, and the torque a joint must supply depends on both — quadratically on q˙\dot q, which is why doubling the speed does not double the torque but quadruples the part of it that comes from motion. A path is a set of configurations; a trajectory is that set with a clock, and only the clocked version can be feasible or infeasible. Chapter 18 chooses the clock. This chapter builds the thing the clock is measured against: the map from (q,q˙,q¨)(q, \dot q, \ddot q) to the torques τ\tau that produce them.

Building intuition

The arm is a different machine in every configuration

Before any formalism, watch Reach fall. The Lagrangian Lab hangs the arm on a vertical Workbench, releases it from a configuration you choose, and keeps the books.

Three things are worth noticing, and each becomes a theorem in the next section.

Energy is conserved, exactly, by construction. The total E=K+VE = K + V sits on its dashed reference line while kinetic and potential energy trade places violently. There is no friction in the default model and no torque, so this is what the physics must do — and the integrator reproduces it to about one part in 10810^8 over ten seconds. That number is not an accident; it is a unit test, and the end of the chapter prints it.

Switch Coriolis off and the physics lies. The toggle hands the same integrator a model in which the velocity-product vector C(q,q˙)q˙C(q,\dot q)\dot q has been zeroed — "a small correction," the folklore says. Within two swings the energy bar leaves its line. The arm gains or loses energy from nowhere, by more than twenty percent of what it started with. Those terms are not a correction. They are the only thing that makes joint-space Newton's law consistent with workspace Newton's law.

Neither Mq¨M\ddot q nor Cq˙C\dot q is a force. Watch the torque gauge. Under zero applied torque the three stacks satisfy Mq¨+Cq˙+g=0M\ddot q + C\dot q + g = 0 at every instant, and as the arm swings through different configurations the share carried by Mq¨M\ddot q and by Cq˙C\dot q changes while their sum does not. Choset says this precisely: "neither M(q)q¨M(q)\ddot q nor C(q,q˙)q˙C(q,\dot q)\dot q individually should be thought of as a generalized force; only their sum is a force."

Inertia is a metric

The second widget throws the arm away and keeps the matrix.

The orange curve is the set of joint velocities that carry exactly one joule of kinetic energy, {q˙:q˙TM(q)q˙=1}\{\dot q : \dot q\T M(q)\dot q = 1\}. If inertia were a number the curve would be a circle. It is an ellipse, its semi-axes are 1/λi1/\sqrt{\lambda_i} for the eigenvalues of M(q)M(q), and as q2q_2 sweeps from 00 to π\pi the entry M11M_{11} reads 33, then 22, then 11: the stretched arm is three times harder to swing about the shoulder than the folded one. Move the shoulder instead and nothing happens — there is no q1q_1 anywhere in MM, and the text will ask you to say why.

Notation used in this chapter
SymbolMeaningNote
q∈Rn,  u∈Rnq \in \R^n,\; u \in \R^nGeneralized coordinates and generalized forces, paired so that uᵀq̇ is power. For Reach u = τ, the joint torques.Chs. 18–19 keep u for controls.
L(q,q˙)=K(q,q˙)−V(q)L(q, \dot q) = K(q, \dot q) - V(q)The Lagrangian: kinetic minus potential energy.
M(q),  C(q,q˙),  g(q),  b(q,q˙)M(q),\; C(q, \dot q),\; g(q),\; b(q, \dot q)Inertia matrix, a Coriolis matrix, gravity vector, dissipation — the standard form (10.7).Book-wide (TOC §2).
Γjki(q),  Γi(q),  q˙TΓ(q)q˙\Gamma^i_{jk}(q),\; \Gamma^i(q),\; \dot q\T \Gamma(q) \dot qChristoffel symbols of M; the i-th symmetric n×n slice; the velocity-product vector (10.10).
T(q)f=uT(q) f = uActuation matrix and raw actuator forces; T = Jᵀ for forces applied at a body point (10.12).
A(q)q˙=0,  λ∈RkA(q) \dot q = 0,\; \lambda \in \R^kk Pfaffian constraints (rows ω_j(q)) and their Lagrange multipliers.
P,  PuP,\; P_uProjections onto motions that satisfy the constraints / forces that do work (10.18–10.19).
rcm,  Izz,  I,  Isr_{cm},\; I_{zz},\; \mathcal{I},\; \mathcal{I}_sCenter of mass; planar inertia about z; body-frame and spatial inertia tensors.
ω,  ω^,  R∈SO(3)\omega,\; \hat\omega,\; R \in \SOthreeBody angular velocity, its skew matrix, the orientation.
E=K+V,  hE = K + V,\; hTotal mechanical energy; the integrator step.

The mathematics

Definitions

Generalized coordinates and forces. A configuration is q∈Rnq \in \R^n locally — joint angles for Reach, (x,y,θ)(x, y, \theta) for a planar body — and a generalized force uu is anything that pairs with q˙\dot q to give power: uTq˙u\T\dot q is the rate at which work is done on the system. Torques pair with joint rates; a force through the center of mass pairs with its velocity.

The Lagrangian is L(q,q˙)=K(q,q˙)−V(q)L(q, \dot q) = K(q, \dot q) - V(q) with kinetic energy quadratic in the velocities and potential energy a function of configuration alone. The Euler–Lagrange equations (Choset eq. 10.1)

ddt∂L∂q˙−∂L∂q=u\frac{d}{dt}\frac{\partial L}{\partial \dot q} - \frac{\partial L}{\partial q} = u

are the equations of motion. Their derivation from the principle of least action is in any mechanics text; this chapter takes them as the recipe.

The inertia matrix M(q)=∂2K/∂q˙ ∂q˙M(q) = \partial^2 K / \partial\dot q\,\partial\dot q, so that K=12q˙TM(q)q˙K = \tfrac12\dot q\T M(q)\dot q (eqs. 10.13–10.14). It is symmetric by construction and positive definite because kinetic energy is positive for every nonzero velocity.

Christoffel symbols. For each ii, the n×nn \times n matrix with entries

Γjki(q)=12(∂Mij∂qk+∂Mik∂qj−∂Mjk∂qi)\Gamma^i_{jk}(q) = \tfrac12\left(\frac{\partial M_{ij}}{\partial q_k} + \frac{\partial M_{ik}}{\partial q_j} - \frac{\partial M_{jk}}{\partial q_i}\right)

(eq. 10.9). Terms with j=kj = k are centrifugal; terms with j≠kj \ne k are Coriolis. There are n3n^3 of them, symmetric in the lower pair.

A Pfaffian constraint is A(q)q˙=0A(q)\dot q = 0 for a k×nk \times n matrix of full row rank. It is holonomic if each row is ∂c/∂q\partial c/\partial q for some c(q)=0c(q) = 0 — the constraint is really a constraint on configurations in disguise — and nonholonomic otherwise. Chapter 20 decides which; this chapter only imposes them.

The inertia tensor of a rigid body with density ρ\rho occupying VV, in a frame at its center of mass, is I=∫Vρ(r) (∥r∥21−rrT) dV\mathcal{I} = \int_V \rho(r)\,(\|r\|^2\mathbb{1} - r r\T)\,dV (eq. 10.42). Its eigenvectors are the principal axes; its eigenvalues the principal moments.

Euler–Lagrange gives the standard form

DerivationFrom the Lagrangian to M q̈ + C q̇ + g = u

Step 1 — the momentum. ∂L/∂q˙=M(q)q˙\partial L/\partial\dot q = M(q)\dot q, because KK is a quadratic form and VV has no q˙\dot q in it.

Step 2 — its time derivative. ddt(Mq˙)=Mq¨+M˙q˙\tfrac{d}{dt}(M\dot q) = M\ddot q + \dot M\dot q, and since MM depends on tt only through qq, M˙ij=∑k(∂Mij/∂qk) q˙k\dot M_{ij} = \sum_k (\partial M_{ij}/\partial q_k)\,\dot q_k.

Step 3 — the configuration derivative. ∂L/∂qi=12q˙T(∂M/∂qi)q˙−∂V/∂qi\partial L/\partial q_i = \tfrac12\dot q\T(\partial M/\partial q_i)\dot q - \partial V/\partial q_i.

Step 4 — subtract and symmetrize. The ii-th equation reads

∑jMijq¨j+∑j,k∂Mij∂qkq˙jq˙k−12∑j,k∂Mjk∂qiq˙jq˙k+∂V∂qi=ui.\sum_j M_{ij}\ddot q_j + \sum_{j,k}\frac{\partial M_{ij}}{\partial q_k}\dot q_j\dot q_k - \tfrac12\sum_{j,k}\frac{\partial M_{jk}}{\partial q_i}\dot q_j\dot q_k + \frac{\partial V}{\partial q_i} = u_i .

The middle sum is a quadratic form in q˙\dot q whose coefficient matrix ∂Mij/∂qk\partial M_{ij}/\partial q_k is not symmetric in (j,k)(j, k). A quadratic form only sees the symmetric part, so replace it by 12(∂Mij/∂qk+∂Mik/∂qj)\tfrac12(\partial M_{ij}/\partial q_k + \partial M_{ik}/\partial q_j). Together with the third sum that is exactly ∑jkΓjkiq˙jq˙k\sum_{jk}\Gamma^i_{jk}\dot q_j\dot q_k — the symmetrization is where the three-term Christoffel formula comes from.

Step 5 — read off the pieces. Define ci=∑jkΓjkiq˙jq˙kc_i = \sum_{jk}\Gamma^i_{jk}\dot q_j\dot q_k (eq. 10.8), gi=∂V/∂qig_i = \partial V/\partial q_i, and any matrix CC with Cq˙=cC\dot q = c; the choice Cij=∑kΓjkiq˙kC_{ij} = \sum_k\Gamma^i_{jk}\dot q_k is the one the rest of the chapter uses. ■\blacksquare

The RP arm as a check (Choset Examples 10.1.2, 10.2.1). With M=diag⁡(I1+I2+m1r12+m2q22,  m2)M = \operatorname{diag}(I_1 + I_2 + m_1 r_1^2 + m_2 q_2^2,\; m_2), the only nonzero symbols are Γ121=Γ211=m2q2\Gamma^1_{12} = \Gamma^1_{21} = m_2 q_2 and Γ112=−m2q2\Gamma^2_{11} = -m_2 q_2, and the velocity-product vector is (2m2q2q˙1q˙2,  −m2q2q˙12)(2 m_2 q_2\dot q_1\dot q_2,\; -m_2 q_2\dot q_1^2) — eqs. (10.5)–(10.6) exactly. The cast of this book is revolute, so the RP arm survives only here.

CC is not unique; Cq˙C\dot q is. Add to CC any matrix SS with Sq˙=0S\dot q = 0 — for n=2n = 2, S=s [q˙2,−q˙1]T[q˙2,−q˙1]S = s\,[\dot q_2, -\dot q_1]\T[\dot q_2, -\dot q_1] for any scalar ss — and the equations of motion are unchanged. What is changed is the property the next derivation proves, which is why the Christoffel choice is the one worth naming.

Ṁ − 2C is skew-symmetric, so energy is conserved exactly

DerivationWhy the Christoffel choice conserves energy

Step 1 — write both pieces with the same partials. From Step 2 above, M˙ij=∑k∂Mij∂qkq˙k\dot M_{ij} = \sum_k\frac{\partial M_{ij}}{\partial q_k}\dot q_k. From the definition, 2Cij=∑k(∂Mij∂qk+∂Mik∂qj−∂Mjk∂qi)q˙k2C_{ij} = \sum_k\left(\frac{\partial M_{ij}}{\partial q_k} + \frac{\partial M_{ik}}{\partial q_j} - \frac{\partial M_{jk}}{\partial q_i}\right)\dot q_k.

Step 2 — the first terms cancel. Subtracting leaves (M˙−2C)ij=∑k(∂Mjk∂qi−∂Mik∂qj)q˙k(\dot M - 2C)_{ij} = \sum_k\left(\frac{\partial M_{jk}}{\partial q_i} - \frac{\partial M_{ik}}{\partial q_j}\right)\dot q_k.

Step 3 — what remains is antisymmetric. Swap ii and jj and the bracket changes sign: (M˙−2C)ji=−(M˙−2C)ij(\dot M - 2C)_{ji} = -(\dot M - 2C)_{ij}. So q˙T(M˙−2C)q˙=0\dot q\T(\dot M - 2C)\dot q = 0 for every q˙\dot q, since a quadratic form of a skew matrix vanishes identically.

Step 4 — differentiate the energy. E=12q˙TMq˙+VE = \tfrac12\dot q\T M\dot q + V, so E˙=q˙TMq¨+12q˙TM˙q˙+q˙Tg\dot E = \dot q\T M\ddot q + \tfrac12\dot q\T\dot M\dot q + \dot q\T g. Substitute Mq¨=u−Cq˙−g−bM\ddot q = u - C\dot q - g - b:

E˙=q˙T(u−b)+12q˙T(M˙−2C)q˙=q˙T(u−b).■\dot E = \dot q\T(u - b) + \tfrac12\dot q\T(\dot M - 2C)\dot q = \dot q\T(u - b). \qquad\blacksquare

Why this is the test the dynamics crate runs. The derivation used only (i) the correct MM, (ii) the correct Γ\Gamma obtained from it, and (iii) g=∂V/∂qg = \partial V/\partial q. An error in any of the three breaks the identity, and an integrator run from rest with u=b=0u = b = 0 will show it as energy drift. That is one assertion covering the whole derivation. The library check dynamics: released from rest … E stays within 1e-6 relative under RK4 is exactly this.

Why dropping CC breaks it. With C≡0C \equiv 0 the Step 4 remainder is 12q˙TM˙q˙\tfrac12\dot q\T\dot M\dot q, which is not zero — it is the rate at which the configuration-dependence of MM pumps energy in or out. That is the Lagrangian Lab's lie, and the sign of the drift depends on whether M˙\dot M is positive or negative along the motion (Exercise 4).

The 2R arm, worked in full

This is Choset's Problem 10.2 — Reach in a vertical plane — and the model every later chapter in Part V runs.

DerivationThe Lagrange recipe applied to Reach

Step 1 — centers of mass by forward kinematics. Chapter 2's φ\varphi evaluated at rir_i instead of LiL_i: c1=r1(cos⁡q1,sin⁡q1)c_1 = r_1(\cos q_1, \sin q_1) and c2=(L1cos⁡q1+r2cos⁡(q1+q2),  L1sin⁡q1+r2sin⁡(q1+q2))c_2 = (L_1\cos q_1 + r_2\cos(q_1 + q_2),\; L_1\sin q_1 + r_2\sin(q_1 + q_2)).

Step 2 — center-of-mass Jacobians. Differentiating, Jc1=r1(−sin⁡q10cos⁡q10)J_{c_1} = r_1\begin{pmatrix}-\sin q_1 & 0\\ \cos q_1 & 0\end{pmatrix} and

Jc2=(−L1sin⁡q1−r2sin⁡(q1+q2)−r2sin⁡(q1+q2)L1cos⁡q1+r2cos⁡(q1+q2)r2cos⁡(q1+q2)).J_{c_2} = \begin{pmatrix} -L_1\sin q_1 - r_2\sin(q_1 + q_2) & -r_2\sin(q_1 + q_2) \\ L_1\cos q_1 + r_2\cos(q_1 + q_2) & r_2\cos(q_1 + q_2)\end{pmatrix}.

These are Chapter 4's Jacobian construction applied to interior points of the links.

Step 3 — kinetic energy. Each link contributes translation of its center of mass plus rotation about it: K=∑i12(mi∥Jciq˙∥2+Iiωi2)K = \sum_i \tfrac12\left(m_i\|J_{c_i}\dot q\|^2 + I_i\omega_i^2\right) with ω1=q˙1\omega_1 = \dot q_1 and ω2=q˙1+q˙2\omega_2 = \dot q_1 + \dot q_2, i.e. Jω1=(1,0)J_{\omega_1} = (1, 0), Jω2=(1,1)J_{\omega_2} = (1, 1). Hence

M=∑i(miJciTJci+IiJωiTJωi).M = \sum_i \left( m_i J_{c_i}\T J_{c_i} + I_i J_{\omega_i}\T J_{\omega_i}\right).

Multiplying out: ∥Jc2q˙∥2=(L12+r22+2L1r2cos⁡q2)q˙12+2(r22+L1r2cos⁡q2)q˙1q˙2+r22q˙22\|J_{c_2}\dot q\|^2 = (L_1^2 + r_2^2 + 2L_1 r_2\cos q_2)\dot q_1^2 + 2(r_2^2 + L_1 r_2\cos q_2)\dot q_1\dot q_2 + r_2^2\dot q_2^2, using cos⁡q1cos⁡(q1+q2)+sin⁡q1sin⁡(q1+q2)=cos⁡q2\cos q_1\cos(q_1{+}q_2) + \sin q_1\sin(q_1{+}q_2) = \cos q_2. Collect by q˙iq˙j\dot q_i\dot q_j and the three entries of MM in the statement appear.

Step 4 — differentiate for Γ\Gamma. Only q2q_2 appears in MM, and only through cos⁡q2\cos q_2: ∂M11/∂q2=−2h\partial M_{11}/\partial q_2 = -2h, ∂M12/∂q2=−h\partial M_{12}/\partial q_2 = -h, every other partial is zero. Feeding these into the three-term formula: Γ121=12(∂M11/∂q2)=−h\Gamma^1_{12} = \tfrac12(\partial M_{11}/\partial q_2) = -h; Γ221=12(2 ∂M12/∂q2−∂M22/∂q1)=−h\Gamma^1_{22} = \tfrac12(2\,\partial M_{12}/\partial q_2 - \partial M_{22}/\partial q_1) = -h; Γ112=12(2 ∂M21/∂q1−∂M11/∂q2)=h\Gamma^2_{11} = \tfrac12(2\,\partial M_{21}/\partial q_1 - \partial M_{11}/\partial q_2) = h; Γ122=12(∂M21/∂q2+∂M22/∂q1−∂M12/∂q2)=0\Gamma^2_{12} = \tfrac12(\partial M_{21}/\partial q_2 + \partial M_{22}/\partial q_1 - \partial M_{12}/\partial q_2) = 0. Contracting, c1=−2hq˙1q˙2−hq˙22c_1 = -2h\dot q_1\dot q_2 - h\dot q_2^2 and c2=hq˙12c_2 = h\dot q_1^2 (Problem 10.3).

Step 5 — gravity. V=m1agr1sin⁡q1+m2ag(L1sin⁡q1+r2sin⁡(q1+q2))V = m_1 a_g r_1\sin q_1 + m_2 a_g\left(L_1\sin q_1 + r_2\sin(q_1 + q_2)\right), and g=∂V/∂qg = \partial V/\partial q is the statement. ■\blacksquare

The same recipe for 3R. Nothing in Steps 1–5 used n=2n = 2 except the trigonometric collapse in Step 3. For Reach's three-link variant there are three center-of-mass Jacobians, nine entries of MM and 27 symbols — too many to differentiate by hand without error, so the library assembles MM from the Jacobians and takes the Christoffel symbols by central differences. The 2R case is the check: the finite-difference symbols agree with the closed form above to 10−710^{-7} at fifty random states.

The numbers

Fix the micro-example that every later chapter in Part V reuses: L1=L2=1L_1 = L_2 = 1 m, uniform rods so ri=Li/2r_i = L_i/2, m1=2m_1 = 2 kg, m2=1m_2 = 1 kg, hence I1=m1L12/12=1/6I_1 = m_1L_1^2/12 = 1/6 and I2=1/12I_2 = 1/12 kg·m², ag=9.81a_g = 9.81 m/s², at Choset's worked pose q=(π/4,π/2)q = (\pi/4, \pi/2) from Example 3.8.1 — the configuration the reader already knows from Chapter 2. Then cos⁡q2=0\cos q_2 = 0 and

M(q)=(21/31/31/3)exactly:M11=16+112+2⋅14+1⋅(1+14)=2,  M22=112+14=13,  M12=M22+0.M(q) = \begin{pmatrix} 2 & 1/3 \\ 1/3 & 1/3 \end{pmatrix} \quad\text{exactly:}\quad M_{11} = \tfrac16 + \tfrac1{12} + 2\cdot\tfrac14 + 1\cdot(1 + \tfrac14) = 2,\; M_{22} = \tfrac1{12} + \tfrac14 = \tfrac13,\; M_{12} = M_{22} + 0 .

det⁡M=5/9>0\det M = 5/9 > 0 with eigenvalues 2.06422.0642 and 0.26910.2691. Gravity: g1=(1+1)(9.81)cos⁡π4+0.5⋅9.81cos⁡3π4=1.5⋅9.81⋅0.70711=10.405g_1 = (1 + 1)(9.81)\cos\tfrac\pi4 + 0.5\cdot 9.81\cos\tfrac{3\pi}4 = 1.5\cdot 9.81\cdot 0.70711 = 10.405 N·m and g2=0.5⋅9.81⋅(−0.70711)=−3.468g_2 = 0.5\cdot 9.81\cdot(-0.70711) = -3.468 N·m. With q˙=(1,0)\dot q = (1, 0) rad/s the kinetic energy is K=12M11=1.000K = \tfrac12 M_{11} = 1.000 J, h=m2L1r2sin⁡q2=0.5h = m_2 L_1 r_2\sin q_2 = 0.5, and Cq˙=(0,  hq˙12)=(0,0.5)C\dot q = (0,\; h\dot q_1^2) = (0, 0.5) N·m: joint 2 must push outward at half a newton-meter just to keep the elbow angle fixed while the shoulder turns. Released from rest at this pose with τ=0\tau = 0, E=V=17.342E = V = 17.342 J and must stay there.

Sweep q₂ through 0, π/2, π at the same masses. What is M₁₁ at q₂ = 0 and at q₂ = π?

Constrained dynamics and the projection P

Rusty's axle, Hitch's wheels, a knife-edge on ice: each may move along its heading and spin but not slide sideways. That is a constraint on velocities, and it enters the equations of motion through a Lagrange multiplier.

DerivationEliminating the multipliers

Step 1 — differentiate the constraint. Aq˙=0A\dot q = 0 holds for all time, so ddt(Aq˙)=Aq¨+A˙q˙=0\tfrac{d}{dt}(A\dot q) = A\ddot q + \dot A\dot q = 0 (eq. 10.17).

Step 2 — solve the force balance for q¨\ddot q. From (10.16), q¨=M−1(u+ATλ−Cq˙−g)\ddot q = M^{-1}(u + A\T\lambda - C\dot q - g).

Step 3 — substitute into the differentiated constraint. A˙q˙+AM−1(u+ATλ−Cq˙−g)=0\dot A\dot q + AM^{-1}(u + A\T\lambda - C\dot q - g) = 0.

Step 4 — solve for λ\lambda. The k×kk \times k matrix AM−1ATAM^{-1}A\T is invertible because AA has full row rank and M≻0M \succ 0: λ=(AM−1AT)−1(−A˙q˙+AM−1(Cq˙+g−u))\lambda = (AM^{-1}A\T)^{-1}\left(-\dot A\dot q + AM^{-1}(C\dot q + g - u)\right).

Step 5 — substitute back and factor. Using −A˙q˙=Aq¨-\dot A\dot q = A\ddot q and collecting, (1−AT(AM−1AT)−1AM−1)(Mq¨+Cq˙+g−u)=0(\mathbb{1} - A\T(AM^{-1}A\T)^{-1}AM^{-1})(M\ddot q + C\dot q + g - u) = 0. Call the bracket PuP_u (eq. 10.18); then P=M−1PuMP = M^{-1}P_u M (eq. 10.19) and rearranging gives the statement (eq. 10.20). ■\blacksquare

Orthogonality with respect to MM. PP and 1−P\mathbb{1} - P split a velocity into a part that satisfies the constraints and a part in the constrained directions, and (Pq˙)TM(1−P)q˙=0(P\dot q)\T M(\mathbb{1} - P)\dot q = 0 for every q˙\dot q. The inner product is MM, not the identity: Choset is explicit that this is "the appropriate one when discussing dynamics," because MM carries the metric of coordinates that mix lengths and angles. Also P=PuTP = P_u\T, which the library checks numerically alongside the rank.

The knife-edge (Example 10.3.1). q=(q1,q2,q3)q = (q_1, q_2, q_3) is contact point and heading, M=diag⁡(m,m,I)M = \operatorname{diag}(m, m, I), A=[sin⁡q3,−cos⁡q3,0]A = [\sin q_3, -\cos q_3, 0], A˙=q˙3[cos⁡q3,sin⁡q3,0]\dot A = \dot q_3[\cos q_3, \sin q_3, 0]. Then P=(cos⁡2q3sin⁡q3cos⁡q30sin⁡q3cos⁡q3sin⁡2q30001)P = \begin{pmatrix}\cos^2 q_3 & \sin q_3\cos q_3 & 0\\ \sin q_3\cos q_3 & \sin^2 q_3 & 0 \\ 0 & 0 & 1\end{pmatrix}, rank 2, and the closed forms (10.25)–(10.28) follow — including the constraint force λ1=(u2−mq˙1q˙3)cos⁡q3−(u1+mq˙2q˙3)sin⁡q3\lambda_1 = (u_2 - m\dot q_1\dot q_3)\cos q_3 - (u_1 + m\dot q_2\dot q_3)\sin q_3. The library solves the (n+k)×(n+k)(n + k) \times (n + k) system directly and reproduces those four formulas to 10−910^{-9} at thirty random states. This is Rusty's no-side-slip constraint, and Chapter 20 will show it is nonholonomic.

Euler's equation in the body frame

For a spinning rigid body no choice of three angles gives a smooth global coordinate system — the same fact Chapter 5 made about SO(3)\SOthree — so Choset does not pick one. He takes R∈SO(3)R \in \SOthree as the orientation and the body-frame angular velocity ω\omega as the velocity, and derives the dynamics directly.

DerivationFrom the spatial frame to the body frame

Step 1 — kinetic energy in the spatial frame. A point at rsr_s moves at ωs×rs\omega_s \times r_s, so K=12∫Vρ ∥ωs×rs∥2 dV=12ωsTIsωsK = \tfrac12\int_V\rho\,\|\omega_s \times r_s\|^2\,dV = \tfrac12\omega_s\T\mathcal{I}_s\omega_s with Is=∫Vρ(∥rs∥21−rsrsT) dV\mathcal{I}_s = \int_V\rho(\|r_s\|^2\mathbb{1} - r_s r_s\T)\,dV (eqs. 10.34–10.36). Because it is written in a fixed frame, Is\mathcal{I}_s changes as the body turns.

Step 2 — angular momentum and its rate. P=IsωsP = \mathcal{I}_s\omega_s and τs=P˙\tau_s = \dot P. The density does not change, so I˙s=ωs×Is\dot{\mathcal{I}}_s = \omega_s \times \mathcal{I}_s comes only from the rotation, giving τs=ωs×Isωs+Isω˙s\tau_s = \omega_s \times \mathcal{I}_s\omega_s + \mathcal{I}_s\dot\omega_s — Euler's equation in the inertial frame (10.37), or τs=ω^sIsωs+Isω˙s\tau_s = \hat\omega_s\mathcal{I}_s\omega_s + \mathcal{I}_s\dot\omega_s in matrix form (10.38).

Step 3 — change frames. ωs=Rω\omega_s = R\omega, τs=Rτ\tau_s = R\tau, Is=RIRT\mathcal{I}_s = R\mathcal{I}R\T, ω^s=R˙RT\hat\omega_s = \dot R R\T, and R˙=Rω^\dot R = R\hat\omega (10.39, 10.41).

Step 4 — substitute. Rτ=R˙RTRIRTRω+RIRT(R˙ω+Rω˙)R\tau = \dot R R\T R\mathcal{I}R\T R\omega + R\mathcal{I}R\T(\dot R\omega + R\dot\omega).

Step 5 — kill one term. R˙ω=Rω^ω=R(ω×ω)=0\dot R\omega = R\hat\omega\omega = R(\omega \times \omega) = 0.

Step 6 — premultiply by RTR\T. τ=RTR˙ Iω+Iω˙=ω^Iω+Iω˙\tau = R\T\dot R\,\mathcal{I}\omega + \mathcal{I}\dot\omega = \hat\omega\mathcal{I}\omega + \mathcal{I}\dot\omega. ■\blacksquare

The principal-axis form (10.45). Align the body frame with the eigenvectors of I\mathcal{I} and τx=Ixxω˙x+(Izz−Iyy)ωyωz\tau_x = I_{xx}\dot\omega_x + (I_{zz} - I_{yy})\omega_y\omega_z, with the two cyclic permutations. Set τ=0\tau = 0: ω˙x=(Iyy−Izz)ωyωz/Ixx\dot\omega_x = (I_{yy} - I_{zz})\omega_y\omega_z/I_{xx} is zero only if two of the components vanish. Spin about the axis of intermediate inertia and a small perturbation in the other two components grows exponentially — the flip the next widget shows. The parallel-axis theorem (10.33 planar, 10.46 spatial) and the composite-body rule I=∑iRi(Ii+mi(∥ri∥21−ririT))RiT\mathcal{I} = \sum_i R_i(\mathcal{I}_i + m_i(\|r_i\|^2\mathbb{1} - r_i r_i\T))R_i\T are functions in the library, not sections here.

Angular momentum and kinetic energy are conserved — the readout holds both to five decimals over the whole run — while the angular velocity is not. That is not a numerical artifact; it is what the equation says. Choset: "ω̇ may not be zero even if τ is zero. Although the angular momentum and kinetic energy of a rotating body are constant when no external torques are applied, the angular velocity of the body may not be constant."

RK4 on the first-order state, and what its energy drift means

Write x=(q,q˙)x = (q, \dot q) and x˙=(q˙,  M−1(u−Cq˙−g−b))\dot x = (\dot q,\; M^{-1}(u - C\dot q - g - b)). Classical RK4 has local error O(h5)O(h^5) and global error O(h4)O(h^4), and the energy error inherits that order. Three honesty items the prose must keep:

  1. RK4 is not symplectic. Its energy error is bounded by c h4c\,h^4 over the horizons we use because the global error is, not because of any structural guarantee. Run it for an hour and it will drift.
  2. The bound is empirical. The test asserts ∣E(t)−E(0)∣/E(0)<10−6|E(t) - E(0)|/E(0) < 10^{-6} for h=1h = 1 ms over 10 s on the micro-example released from rest; the measured value is about 10−810^{-8}.
  3. Explicit Euler drifts linearly and is kept only as the bad example: on the same run it loses track of the energy by more than ten percent. Semi-implicit (symplectic) Euler keeps the error bounded, but at first order the bound is also around ten percent on this violently swinging arm — cheap, not accurate.

The algorithm

Choset numbers none of these; the names follow his text.

Algorithmlagrange_recipe(links, φ, a_g)CostO(n²) Jacobian products for M; O(n³) Christoffel symbols
In
link parameters (L_i, r_i, m_i, I_i), forward kinematics, gravitational acceleration
Out
M(q), Γ(q), g(q) as callables
  1. for each link ii: ci(q)←c_i(q) \leftarrow position of its center of mass by φ\varphi at rir_i; Jci(q)←∂ci/∂qJ_{c_i}(q) \leftarrow \partial c_i/\partial q; Jωi←(1,…,1,0,…,0)J_{\omega_i} \leftarrow (1, \dots, 1, 0, \dots, 0) with ii ones
  2. M(q)←∑i(miJciTJci+IiJωiTJωi)M(q) \leftarrow \sum_i \left(m_i J_{c_i}\T J_{c_i} + I_i J_{\omega_i}\T J_{\omega_i}\right)
  3. V(q)←∑imiag [ci(q)]yV(q) \leftarrow \sum_i m_i a_g\, [c_i(q)]_y;   g(q)←∑imiag [Jci(q)]y,⋅\;g(q) \leftarrow \sum_i m_i a_g\,[J_{c_i}(q)]_{y,\cdot}
  4. Γjki(q)←12(∂kMij+∂jMik−∂iMjk)\Gamma^i_{jk}(q) \leftarrow \tfrac12(\partial_k M_{ij} + \partial_j M_{ik} - \partial_i M_{jk}), by closed form or central differences
  5. return (M,Γ,g)(M, \Gamma, g)
Algorithmforward_dynamics(q, q̇, u)Costone n×n solve, O(n³)
In
state and generalized force
Out
q̈
  1. c←q˙TΓ(q)q˙c \leftarrow \dot q\T\Gamma(q)\dot q;   r←u−c−g(q)−b(q,q˙)\;r \leftarrow u - c - g(q) - b(q, \dot q)
  2. return M(q)−1rM(q)^{-1} r by Cholesky or pivoted elimination — Choset eq. (11.46)
Algorithmconstrained_forward_dynamics(q, q̇, u, A, Ȧ)Costone (n+k)×(n+k) KKT solve
In
state, force, constraint matrix and its rate
Out
(q̈, λ)
  1. assemble (M−ATA0)(q¨λ)=(u−Cq˙−g−b−A˙q˙)\begin{pmatrix} M & -A\T \\ A & 0\end{pmatrix}\begin{pmatrix}\ddot q\\ \lambda\end{pmatrix} = \begin{pmatrix} u - C\dot q - g - b \\ -\dot A\dot q\end{pmatrix} — eqs. (10.16)–(10.17)
  2. solve; return (q¨,λ)(\ddot q, \lambda). The constraint force on the system is ATλA\T\lambda.
Algorithmrk4_step(f, x, h)Cost4 dynamics evaluations
In
ẋ = f(x), state, step
Out
x(t + h)
  1. k1←f(x)k_1 \leftarrow f(x); k2←f(x+h2k1)k_2 \leftarrow f(x + \tfrac h2 k_1); k3←f(x+h2k2)k_3 \leftarrow f(x + \tfrac h2 k_2); k4←f(x+hk3)k_4 \leftarrow f(x + h k_3)
  2. return x+h6(k1+2k2+2k3+k4)x + \tfrac h6(k_1 + 2k_2 + 2k_3 + k_4)
Algorithmeuler_body_step(R, ω, τ, 𝓘, h)CostO(1)
In
orientation as a unit quaternion, body angular velocity, body torque, body inertia tensor, step
Out
(R, ω) after h
  1. state x←(R,ω)x \leftarrow (R, \omega) with R˙=12R⊗(0,ω)\dot R = \tfrac12 R \otimes (0, \omega) and ω˙=I−1(τ−ω×Iω)\dot\omega = \mathcal{I}^{-1}(\tau - \omega \times \mathcal{I}\omega) — eqs. (10.43)–(10.44)
  2. one rk4_step on xx; renormalize RR

Implementation in Rust

The dynamics crate is introduced here and imported by Chapters 18–21 and 23. Its surface is a trait with four physical callbacks and two derived operations.

crates/dynamics/src/lib.rs
use nalgebra::{SMatrix, SVector};

/// A fully actuated second-order mechanical system in generalized coordinates.
/// Everything downstream (time scaling, iLQR, kinodynamic RRT) talks to this trait.
pub trait Dynamics<const N: usize> {
    /// M(q): symmetric positive definite. We return the full matrix — the
    /// Cholesky factorization is cheap for N ≤ 3 and keeps the code readable.
    fn mass(&self, q: &SVector<f64, N>) -> SMatrix<f64, N, N>;

    /// The velocity-product vector C(q, q̇) q̇ = q̇ᵀ Γ(q) q̇ — returned as a vector,
    /// because the matrix C is not unique and only the product is a generalized
    /// force (Choset §10.2).
    fn velocity_product(&self, q: &SVector<f64, N>, qd: &SVector<f64, N>) -> SVector<f64, N>;

    /// g(q) = ∂V/∂q.
    fn gravity(&self, q: &SVector<f64, N>) -> SVector<f64, N>;

    /// V(q), so E = K + V can be tracked. K = ½ q̇ᵀ M q̇ is provided below.
    fn potential_energy(&self, q: &SVector<f64, N>) -> f64;

    /// Dissipation b(q, q̇); default: none.
    fn dissipation(&self, _q: &SVector<f64, N>, _qd: &SVector<f64, N>) -> SVector<f64, N> {
        SVector::zeros()
    }

    /// q̈ = M⁻¹(u − C q̇ − g − b). Default impl; override only for a faster recursive form.
    fn forward(&self, q: &SVector<f64, N>, qd: &SVector<f64, N>, u: &SVector<f64, N>)
        -> SVector<f64, N>
    {
        let rhs = u - self.velocity_product(q, qd) - self.gravity(q) - self.dissipation(q, qd);
        self.mass(q).cholesky().expect("M(q) must be positive definite").solve(&rhs)
    }

    /// u = M q̈ + C q̇ + g + b — the torque that produces a given acceleration.
    fn inverse(&self, q: &SVector<f64, N>, qd: &SVector<f64, N>, qdd: &SVector<f64, N>)
        -> SVector<f64, N>
    {
        self.mass(q) * qdd + self.velocity_product(q, qd) + self.gravity(q) + self.dissipation(q, qd)
    }

    fn kinetic_energy(&self, q: &SVector<f64, N>, qd: &SVector<f64, N>) -> f64 {
        0.5 * qd.dot(&(self.mass(q) * qd))
    }

    fn energy(&self, q: &SVector<f64, N>, qd: &SVector<f64, N>) -> f64 {
        self.kinetic_energy(q, qd) + self.potential_energy(q)
    }
}

The 2R model is Derivation 3 typed in. Every expression is a line of the derivation, which is the point: a reader who has done the algebra should recognize the file.

crates/dynamics/src/reach2r.rs
use nalgebra::{Matrix2, Vector2};
use crate::Dynamics;

/// Choset Problem 10.2: planar 2R arm in a vertical plane. Units: kg, m, s.
pub struct Reach2R {
    pub l1: f64, pub r1: f64, pub m1: f64, pub i1: f64,
    pub l2: f64, pub r2: f64, pub m2: f64, pub i2: f64,
    pub a_g: f64, // 0.0 lays the Workbench flat
}

impl Reach2R {
    /// Uniform rods: r_i = L_i/2, I_i = m_i L_i²/12.
    pub fn uniform(l1: f64, m1: f64, l2: f64, m2: f64, a_g: f64) -> Self {
        Self {
            l1, r1: l1 / 2.0, m1, i1: m1 * l1 * l1 / 12.0,
            l2, r2: l2 / 2.0, m2, i2: m2 * l2 * l2 / 12.0,
            a_g,
        }
    }

    /// h(q) = m₂ L₁ r₂ sin q₂ — every velocity-product term is built from it.
    fn h(&self, q: &Vector2<f64>) -> f64 {
        self.m2 * self.l1 * self.r2 * q[1].sin()
    }

    /// Closed form: the four nonzero symbols Γ¹₁₂ = Γ¹₂₁ = Γ¹₂₂ = −h, Γ²₁₁ = h.
    pub fn christoffel(&self, q: &Vector2<f64>) -> [Matrix2<f64>; 2] {
        let h = self.h(q);
        [Matrix2::new(0.0, -h, -h, -h), Matrix2::new(h, 0.0, 0.0, 0.0)]
    }
}

impl Dynamics<2> for Reach2R {
    fn mass(&self, q: &Vector2<f64>) -> Matrix2<f64> {
        let c2 = q[1].cos();
        let m22 = self.i2 + self.m2 * self.r2 * self.r2;
        let m12 = m22 + self.m2 * self.l1 * self.r2 * c2;
        let m11 = self.i1 + self.i2 + self.m1 * self.r1 * self.r1
            + self.m2 * (self.l1 * self.l1 + self.r2 * self.r2 + 2.0 * self.l1 * self.r2 * c2);
        Matrix2::new(m11, m12, m12, m22)
    }

    fn velocity_product(&self, q: &Vector2<f64>, qd: &Vector2<f64>) -> Vector2<f64> {
        let h = self.h(q);
        Vector2::new(-h * qd[1] * qd[1] - 2.0 * h * qd[0] * qd[1], h * qd[0] * qd[0])
    }

    fn gravity(&self, q: &Vector2<f64>) -> Vector2<f64> {
        let (c1, c12) = (q[0].cos(), (q[0] + q[1]).cos());
        Vector2::new(
            (self.m1 * self.r1 + self.m2 * self.l1) * self.a_g * c1 + self.m2 * self.r2 * self.a_g * c12,
            self.m2 * self.r2 * self.a_g * c12,
        )
    }

    fn potential_energy(&self, q: &Vector2<f64>) -> f64 {
        self.m1 * self.a_g * self.r1 * q[0].sin()
            + self.m2 * self.a_g * (self.l1 * q[0].sin() + self.r2 * (q[0] + q[1]).sin())
    }
}

For arms longer than two links the symbols are differenced rather than derived. The function is generic over anything that can produce M(q)M(q), and the 2R closed form is what it is tested against.

crates/dynamics/src/lagrange.rs
use nalgebra::{SMatrix, SVector};

/// Γ^i_jk = ½(∂M_ij/∂q_k + ∂M_ik/∂q_j − ∂M_jk/∂q_i) (Choset eq. 10.9), with the
/// partials by central differences. O(n³) symbols from 2n evaluations of M.
/// The 3R arm has no other path; the 2R arm's closed form checks this one.
pub fn christoffel<const N: usize>(
    mass: impl Fn(&SVector<f64, N>) -> SMatrix<f64, N, N>,
    q: &SVector<f64, N>,
    step: f64,
) -> [SMatrix<f64, N, N>; N] {
    let mut d_m = [SMatrix::<f64, N, N>::zeros(); N]; // d_m[k] = ∂M/∂q_k
    for k in 0..N {
        let (mut qp, mut qm) = (*q, *q);
        qp[k] += step;
        qm[k] -= step;
        d_m[k] = (mass(&qp) - mass(&qm)) / (2.0 * step);
    }
    let mut gamma = [SMatrix::<f64, N, N>::zeros(); N];
    for i in 0..N {
        for j in 0..N {
            for k in 0..N {
                gamma[i][(j, k)] = 0.5 * (d_m[k][(i, j)] + d_m[j][(i, k)] - d_m[i][(j, k)]);
            }
        }
    }
    gamma
}

/// max |(Ṉ − 2C) + (Ṉ − 2C)ᵀ| — zero when C is the Christoffel choice. A
/// diagnostic for a model, not a step of any algorithm.
pub fn skew_symmetry_defect<const N: usize>(
    mass: impl Fn(&SVector<f64, N>) -> SMatrix<f64, N, N> + Copy,
    q: &SVector<f64, N>,
    qd: &SVector<f64, N>,
) -> f64 {
    let gamma = christoffel(mass, q, 1e-6);
    let mut c = SMatrix::<f64, N, N>::zeros();
    let mut m_dot = SMatrix::<f64, N, N>::zeros();
    for i in 0..N {
        for j in 0..N {
            for k in 0..N {
                c[(i, j)] += gamma[i][(j, k)] * qd[k];
            }
        }
    }
    // Ṁ = Σ_k ∂M/∂q_k q̇_k, recomputed here so the two sides share no code path.
    for k in 0..N {
        let (mut qp, mut qm) = (*q, *q);
        qp[k] += 1e-6;
        qm[k] -= 1e-6;
        m_dot += (mass(&qp) - mass(&qm)) / 2e-6 * qd[k];
    }
    let s = m_dot - 2.0 * c;
    (s + s.transpose()).abs().max()
}

The integrator is thirty lines, and the test next to it is the chapter's contract.

crates/dynamics/src/integrate.rs
use nalgebra::SVector;
use crate::Dynamics;

/// One classical RK4 step on x = (q, q̇) with u held over the step.
pub fn rk4_step<const N: usize>(
    dyn_: &impl Dynamics<N>,
    q: &SVector<f64, N>, qd: &SVector<f64, N>, u: &SVector<f64, N>, h: f64,
) -> (SVector<f64, N>, SVector<f64, N>) {
    let f = |q: &SVector<f64, N>, qd: &SVector<f64, N>| (*qd, dyn_.forward(q, qd, u));
    let (k1q, k1v) = f(q, qd);
    let (k2q, k2v) = f(&(q + k1q * (h / 2.0)), &(qd + k1v * (h / 2.0)));
    let (k3q, k3v) = f(&(q + k2q * (h / 2.0)), &(qd + k2v * (h / 2.0)));
    let (k4q, k4v) = f(&(q + k3q * h), &(qd + k3v * h));
    (
        q + (k1q + k2q * 2.0 + k3q * 2.0 + k4q) * (h / 6.0),
        qd + (k1v + k2v * 2.0 + k3v * 2.0 + k4v) * (h / 6.0),
    )
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::reach2r::Reach2R;
    use nalgebra::Vector2;
    use std::f64::consts::PI;

    /// Zero torque, zero friction ⇒ E constant. The tolerance is empirical
    /// (RK4 is not symplectic), calibrated once and never loosened.
    #[test]
    fn energy_drift_rk4() {
        let arm = Reach2R::uniform(1.0, 2.0, 1.0, 1.0, 9.81);
        let (mut q, mut qd) = (Vector2::new(PI / 4.0, PI / 2.0), Vector2::zeros());
        let e0 = arm.energy(&q, &qd);
        let mut worst = 0.0_f64;
        for _ in 0..10_000 {
            (q, qd) = rk4_step(&arm, &q, &qd, &Vector2::zeros(), 1e-3);
            worst = worst.max(((arm.energy(&q, &qd) - e0) / e0).abs());
        }
        assert!(worst < 1e-6, "RK4 energy drift {worst:e}");
    }
}

The worked example, printed

cargo run --example two_r_numbers -p dynamics builds Reach2R::uniform(1.0, 2.0, 1.0, 1.0, 9.81), evaluates it at q=(π/4,π/2)q = (\pi/4, \pi/2) and prints

M   = [[2.0000, 0.3333], [0.3333, 0.3333]]      det 0.5556   eig 2.0642 0.2691
g   = [10.4051, -3.4684]
Cq̇  = [0.0000, 0.5000]        for q̇ = (1, 0)
K   = 1.0000 J    V = 17.3418 J    E = 18.3418 J
M11 sweep over q2 ∈ {0, π/2, π}:  3.0000  2.0000  1.0000
energy drift, τ = 0, h = 1 ms, 10 s:  rk4 9.4e-9   semi-implicit Euler 1.1e-1   explicit Euler 1.4e-1

#[test] fn reproduces_micro_example() asserts each line to 10−410^{-4}. Three more tests pin the chapter's claims: christoffel_closed_form_matches_finite_differences (2R closed form against central differences with step 10−610^{-6}, agreement to 10−710^{-7}), energy_drift_rk4 above (with the explicit Euler run asserted to be worse than 10−310^{-3}, so the comparison in the widget is honest), and rapier_agrees, which assembles Reach in rapier2d as two rigid bodies joined by revolute joints and checks that one engine step at Δt=10−4\Delta t = 10^{-4} agrees with forward to 10−310^{-3} relative, and 2 s trajectories to 10−210^{-2} — the engine's own integrator sets that tolerance, and the text says so.

The widgets on this page run the TypeScript port in lib/dynamics/, a line-for-line translation of the Rust above. Its self-checks reproduce every number in the printout: M = [[2, 1/3], [1/3, 1/3]], g = (10.4051, −3.4684), C q̇ = (0, 0.5), the 3→2→13 \to 2 \to 1 sweep, the eigenvalues, the energy drift, the knife-edge closed forms, and the brick that flips.

Putting it together

The integration lab drops the same Reach2R into Chapter 2's Workbench with gravity on and a PD controller holding a planned waypoint:

τ=g(q∗)+Kp(q∗−q)−Kd q˙.\tau = g(q^*) + K_p(q^* - q) - K_d\,\dot q .

Hold the micro-example pose. The gravity compensation term alone is g(q∗)=(10.405,−3.468)g(q^*) = (10.405, -3.468) N·m — more than half the shoulder motor's 20 N·m budget just to stand still. Raise KpK_p and the arm overshoots on release; the overshoot is governed by M(q)M(q), which is why a gain that is crisp with the arm folded (M11=1M_{11} = 1) rings with it stretched (M11=3M_{11} = 3). The torque trace this lab produces, peak and all, is the limit Chapter 18's time scaler inherits.

Here is where the chapter closes and the next one opens. Take any planned path q(s)q(s), substitute q˙=q′s˙\dot q = q'\dot s and q¨=q′′s˙2+q′s¨\ddot q = q''\dot s^2 + q'\ddot s into the standard form, and the nn equations collapse to

a(s) s¨+b(s) s˙2+c(s)=τ,a=Mq′,  b=Mq′′+q′TΓq′,  c=g.a(s)\,\ddot s + b(s)\,\dot s^2 + c(s) = \tau, \qquad a = M q',\; b = M q'' + q'\T\Gamma q',\; c = g .

A path plus these equations is a one-dimensional dynamics in the path parameter. The whole of Chapter 18 lives in the (s,s˙)(s, \dot s) plane that equation defines. Chapter 19 linearizes forward along a trajectory for iLQR. Chapter 20 reuses A(q)q˙=0A(q)\dot q = 0 and asks whether the knife-edge constraint can be integrated. All three import this crate and none of them re-derives a line of it.

Exercises

  1. Foundation exerciseDifficulty 2 of 3The three-term formula, and what breaks without it

    Starting from K=12q˙TM(q)q˙K = \tfrac12\dot q\T M(q)\dot q, carry out Step 4 of the first derivation and show that the symmetrization produces exactly Γjki=12(∂kMij+∂jMik−∂iMjk)\Gamma^i_{jk} = \tfrac12(\partial_k M_{ij} + \partial_j M_{ik} - \partial_i M_{jk}). Then take C′=C+SC' = C + S with Sq˙=0S\dot q = 0 — for n=2n = 2, S=s wwTS = s\,w w\T with w=(q˙2,−q˙1)w = (\dot q_2, -\dot q_1) — and show the equations of motion are unchanged while M˙−2C′\dot M - 2C' is skew only if s=0s = 0.

  2. Foundation exerciseDifficulty 1 of 3Choset Problem 10.1: a point mass in polar coordinates

    With q=(r,θ)q = (r, \theta) and K=12m(r˙2+r2θ˙2)K = \tfrac12 m(\dot r^2 + r^2\dot\theta^2), write M(q)M(q), compute the Christoffel symbols, and verify Γ221=−mr\Gamma^1_{22} = -mr and Γ122=Γ212=mr\Gamma^2_{12} = \Gamma^2_{21} = mr. Then show that the unforced motions u=0u = 0 are straight lines in the Cartesian plane — so the symbols are not describing a force but the bending of straight lines in these coordinates.

    For m = 2 kg at r = 1.5 m, what is Γ²₁₂?

    kg·m
  3. Conceptual exerciseDifficulty 2 of 3Predict the ellipse
    Predict first

    In the Inertia Ellipsoid the semi-axes at q₂ = π/2 are 1.928 and 0.696. At q₂ = π, where M₁₁ = 1 and M₁₂ = 1/3 − 1/2 = −1/6, what happens to the two semi-axes?

  4. Conceptual exerciseDifficulty 2 of 3Which way does the lie go?

    In the Lagrangian Lab switch Coriolis off and release from (π/4,π/2)(\pi/4, \pi/2). Does EE increase or decrease first, and why does the sign depend on the release configuration? Confirm with the torque-gauge stacks.

  5. Practical exerciseDifficulty 2 of 3Reach3R from three Jacobians

    Implement Reach3R by assembling M(q)=∑i(miJciTJci+IiJωiTJωi)M(q) = \sum_i (m_i J_{c_i}\T J_{c_i} + I_i J_{\omega_i}\T J_{\omega_i}) from three center-of-mass Jacobians, and gg from the yy-rows of the same Jacobians. Add a property test that M(q)M(q) is positive definite on 1000 seeded configurations (SmallRng), that skew_symmetry_defect is below 10−610^{-6} with finite-difference symbols, and that inverse(forward(q, q̇, τ)) returns τ\tau to 10−910^{-9}. The TypeScript PlanarArm.reach3R() does the same with links of 0.6, 0.5, 0.4 m and 1.2, 1.0, 0.8 kg; its check M(q) is positive definite at 200 seeded configurations of each arm is the one to match.

  6. Practical exerciseDifficulty 3 of 3Rusty's chassis as a knife-edge (stretch)

    Implement Rusty's chassis as a planar rigid body with the no-side-slip Pfaffian constraint of Example 10.3.1 using constrained_forward_dynamics. Verify λ\lambda against eq. (10.28) and show that the reduced two-equation form of Choset p. 361, q¨1cos⁡q3+q¨2sin⁡q3=(u1cos⁡q3+u2sin⁡q3)/m\ddot q_1\cos q_3 + \ddot q_2\sin q_3 = (u_1\cos q_3 + u_2\sin q_3)/m and q¨3=u3/I\ddot q_3 = u_3/I, emerges from Pq¨=PM−1uP\ddot q = PM^{-1}u. Then drive it: with u3≠0u_3 \ne 0 and u1=u2=0u_1 = u_2 = 0 the body turns in place, and the constraint force λ1=−mq˙1q˙3cos⁡q3−mq˙2q˙3sin⁡q3\lambda_1 = -m\dot q_1\dot q_3\cos q_3 - m\dot q_2\dot q_3\sin q_3 is the centripetal force the floor supplies. Chapter 20 will show this constraint cannot be integrated to a constraint on qq.

References

  1. Choset, H., Lynch, K. M., Hutchinson, S., Kantor, G., Burgard, W., Kavraki, L. E., and Thrun, S. (2005) Principles of Robot Motion: Theory, Algorithms, and Implementations. MIT Press.link to Principles of Robot Motion: Theory, Algorithms, and Implementations (opens in a new tab)

    Chapter 10 is the source of everything here: the Lagrangian recipe, the standard form and Christoffel symbols (§10.2), Pfaffian constraints and the projections P and P_u (§10.3), and Euler's equation in both frames (§10.4). Our 2R arm is Problems 10.2–10.3 worked in full.

  2. Murray, R. M., Li, Z., and Sastry, S. S. (1994) A Mathematical Introduction to Robotic Manipulation. CRC Press.link to A Mathematical Introduction to Robotic Manipulation (opens in a new tab)

    Chapter 4 derives the manipulator equations with the Christoffel symbols in the index convention used here, and proves the skew-symmetry of Ṁ − 2C that this chapter turns into a test oracle.

  3. Lynch, K. M. and Park, F. C. (2017) Modern Robotics: Mechanics, Planning, and Control. Cambridge University Press.link to Modern Robotics: Mechanics, Planning, and Control (opens in a new tab)

    Chapter 8 covers the same dynamics with the Newton–Euler recursion as the computational alternative to the Lagrangian route taken here; written by one of Choset's co-authors.

  4. Featherstone, R. (2008) Rigid Body Dynamics Algorithms. Springer.doi:10.1007/978-1-4899-7560-7 (opens in a new tab)

    The composite-rigid-body and articulated-body algorithms that build M(q) and solve forward dynamics in O(n) — what replaces our O(n³) Jacobian assembly once an arm has more than a handful of joints.

  5. Carpentier, J., Saurel, G., Buondonno, G., Mirabel, J., Lamiraux, F., Stasse, O., and Mansard, N. (2019) The Pinocchio C++ library: A fast and flexible implementation of rigid body dynamics algorithms and their analytical derivatives. IEEE/SICE International Symposium on System Integration (SII).doi:10.1109/SII.2019.8700380 (opens in a new tab)

    A production rigid-body library that assembles the same quantities this chapter hand-rolls, with analytic derivatives — the modern form of the 'build M from link Jacobians' pattern behind Reach3R.

  6. Hairer, E., Lubich, C., and Wanner, G. (2006) Geometric Numerical Integration: Structure-Preserving Algorithms for Ordinary Differential Equations. Springer Series in Computational Mathematics 31, 2nd edition.doi:10.1007/3-540-30666-8 (opens in a new tab)

    Why RK4 is not symplectic and what symplectic integrators preserve instead — the theory behind the honesty item that the energy-drift bound is empirical.