Principles of Robot Autonomy I · Lesson 5 of 15 · Stanford AA274A

From a Polyline to a Trajectory the Robot Can Actually Drive

The planner hands you waypoints. The motors need a smooth, timed, dynamically-feasible path — a position and a velocity at every instant. This lesson builds that bridge with polynomials and the elegant trick of differential flatness.

Prerequisites: solving a small linear system + basic derivatives + the unicycle model (Lesson 2). That's it.
11
Chapters
6
Simulations
0
Assumed Knowledge

Chapter 0: A Polyline Is Not a Plan

In Lesson 3 you watched A* and RRT search a map and hand back a route. But look closely at what they return: a list of waypoints — corner, corner, corner — connected by straight line segments. A jagged polyline. Geometrically it threads between the obstacles. But try to drive it.

At the first corner the robot would have to stop dead, spin to face the next segment, and accelerate again — an instantaneous change of direction. Real wheels can't do that. The path has infinite curvature at every corner: to turn a sharp angle at any nonzero speed you'd need infinite sideways acceleration, which means infinite force, which the motors do not have.

And there's a second, quieter problem. The polyline tells the robot where to go but not when, or how fast. It is pure geometry — a shape on the floor with no clock attached. A robot doesn't consume geometry. Its controller (Lesson 6) needs to know, at every tick: "right now you should be at this point, moving at this velocity, turning at this rate." That is a trajectory — a timed, smooth function of the clock — not a polyline.

The gap this lesson closes. A planner gives you a path (a shape). A controller needs a trajectory (a smooth, timed motion with a velocity at every instant) that the robot's own dynamics can actually produce. Turning the first into the second is trajectory generation, and it's the last planning step before the wheels turn.

Watch the difference. Below, the same waypoints are followed two ways. The jagged path forces hard stops at every corner (the speed dial slams to zero); the smooth trajectory glides through, keeping speed up and turning gently. Press Drive and compare.

Jagged Polyline vs. Smooth Trajectory

Same four waypoints. Toggle the mode and press Drive. Watch the speed dial: the polyline must brake to zero at each corner (infinite curvature); the smooth trajectory keeps speed and turns gradually.

ready

The jagged path is kinematically infeasible at speed: no real robot survives a corner with zero turning radius. The smooth one is what we'll learn to build — and the surprising news is that for wheeled robots it's not hard. The trick is to pick the right variables to be smooth, and let everything else follow.

Planner
map + goal → waypoints (a jagged geometric path)
→ this lesson →
Trajectory generator
waypoints → smooth, timed ξ(t) with v(t), ω(t)
Controller (Lesson 6)
ξ(t) → track it despite disturbances

Notice where we are on the autonomy stack. Trajectory generation lives inside the Motion Planning module — it is the bridge between the discrete planner above it and the continuous controller below. It produces the reference the controller will later chase.

Why can't a robot simply drive the raw waypoint polyline returned by A*?

Chapter 1: Path vs. Trajectory — the Clock Is the Difference

The two words sound interchangeable in everyday speech, but in robotics they are precisely different, and the difference is the whole point of this lesson. Let's pin both down.

A path is a geometric object: a curve in space, a set of positions with no notion of time. "Drive along this arc, then this line." It answers where, never when. Formally it's a function of a path parameter — often arc length s — written ξ(s). Slide s from 0 to its end and you sweep out the shape, but nothing tells you how fast.

A trajectory is a path with a clock bolted on: a function of time that says where the robot is at every instant. We write it

ξ : ℝ → ℝn,    t ↦ ξ(t) = [x(t), y(t), θ(t)]

Read this as: "ξ is a function that takes a time t (a real number) and returns the robot's full configuration at that time — here a point in 3." Here n is the dimension of the configuration (3 for our wheeled robot: x, y, heading θ). Because ξ(t) is a function of time, we can differentiate it — and that derivative is the velocity, exactly what the motors need.

Path : trajectory :: photo : movie. A path is a still photo of the route — the shape, frozen. A trajectory is the movie — the same shape, but now playing at a definite speed, so at second 3.2 you know precisely where the robot is and how fast it's moving. The controller reads the movie frame by frame.

What boundary conditions does a trajectory need?

To specify a trajectory we don't list every point — that's infinitely many numbers. Instead we pin down its boundary conditions: the values it must take at the start and end. For a wheeled robot a complete boundary spec is:

That's it. A handful of numbers at the two ends. The job of trajectory generation is to interpolate a smooth curve through them — smooth enough that the robot's wheels can produce it. Notice the velocity boundary conditions: a path never mentions velocity, but a trajectory must, because a controller hand-off ("start tracking at speed 0.5 m/s, heading south") only makes sense if the trajectory was built to honor it.

Play with the boundary conditions below. Each handle sets a start/goal position; the heading arrows set θ at the ends. The curve redraws to honor all of them — that's the interpolation problem we'll solve by hand in the next chapter.

Boundary Conditions → a Curve

Drag the orange start and green goal handles. Drag the small dots on each to set the start/goal heading. The smooth curve honoring all four conditions redraws live. (This is exactly the cubic we derive next chapter.)

drag a handle
Why we work with the endpoints, not the middle. Specifying the whole curve point-by-point is an infinite-dimensional problem. Specifying a few boundary conditions and letting a smooth function fill the gap is finite — just a handful of equations to solve. That reduction is what makes trajectory generation tractable, and it's the engine of the entire chapter to come.
What does a trajectory carry that a path does not?

Chapter 2: Polynomial Trajectories — a Cubic by Hand

We need a smooth function of time that hits our boundary conditions. The simplest, most useful choice is a polynomial in t. Each coordinate of the robot gets its own polynomial; we'll do one coordinate now and the rest are identical.

A polynomial is smooth (infinitely differentiable), easy to differentiate to get velocity, and — crucially — linear in its coefficients, which turns "find the curve" into "solve a linear system." We parameterize a coordinate x(t) as a weighted sum of basis functions ψi(t):

x(t) = a0ψ0(t) + a1ψ1(t) + a2ψ2(t) + a3ψ3(t)

The textbook's choice of basis is the monomials: ψ0=1, ψ1=t, ψ2=t², ψ3=t³. So

x(t) = a0 + a1t + a2t² + a3

The unknowns are the four coefficients a0, a1, a2, a3. Why exactly four? Because we have four boundary conditions to honor on this one coordinate: position and velocity at both ends. Four conditions, four unknowns — a square system. The velocity is the time derivative, which for a polynomial is trivial:

&xdot;(t) = a1 + 2a2t + 3a3

Worked example with real numbers

Let's design one coordinate concretely. Suppose we want, on a horizon T = 2 seconds:

x(0) = 0,   &xdot;(0) = 1,   x(2) = 4,   &xdot;(2) = 0

"Start at 0 moving at speed 1, end at 4 having coasted to a stop." Plug each condition into the polynomial. This is the entire method — watch every step.

Condition 1 — x(0)=0. Set t=0: every term with a t vanishes, leaving

x(0) = a0 = 0  ⇒   a0 = 0

Condition 2 — &xdot;(0)=1. Set t=0 in the velocity: again everything with a t dies, leaving

&xdot;(0) = a1 = 1  ⇒   a1 = 1

So the first two coefficients fall out for free — the t=0 conditions always read off a0 and a1 directly. The remaining two come from the t=T end.

Condition 3 — x(2)=4. Plug t=2 (and the known a0=0, a1=1):

0 + (1)(2) + a2(2)² + a3(2)³ = 4  ⇒   2 + 4a2 + 8a3 = 4  ⇒   4a2 + 8a3 = 2

Condition 4 — &xdot;(2)=0. Plug t=2 into the velocity (a1=1):

1 + 2a2(2) + 3a3(2)² = 0  ⇒   1 + 4a2 + 12a3 = 0  ⇒   4a2 + 12a3 = −1

Solve the 2×2 that remains. Subtract the first from the second to cancel a2:

(4a2 + 12a3) − (4a2 + 8a3) = −1 − 2  ⇒   4a3 = −3  ⇒   a3 = −0.75

Back-substitute into 4a2 + 8a3 = 2:

4a2 + 8(−0.75) = 2  ⇒   4a2 − 6 = 2  ⇒   a2 = 2

The trajectory. Assembling all four coefficients:

x(t) = t + 2t² − 0.75 t³

Sanity check the goal: x(2) = 2 + 8 − 6 = 4 ✓, and &xdot;(2) = 1 + 8 − 9 = 0 ✓. It starts at 0 with speed 1, ends at 4 at rest. We just designed a motion with arithmetic.

The same thing as a 4×4 linear system

Doing it by elimination is fine for two unknowns, but the general recipe writes all four conditions as one matrix equation M·a = b. Each row evaluates a basis (or its derivative) at an endpoint. With basis [1, t, t², t³] and its derivative [0, 1, 2t, 3t²] at t=0 and t=T:

┌ 1  0  0   0 ┐┌a0┐  ┌ x(0) ┐
│ 0  1  0   0 ││a1│ = │ &xdot;(0) │
│ 1  T  T²  T³││a2│  │ x(T) │
└ 0  1  2T 3T²┘└a3┘  └ &xdot;(T) ┘

With T=2 and the right-hand side b = [0, 1, 4, 0], solving this 4×4 gives back exactly a = [0, 1, 2, −0.75] — the same coefficients we found by hand. The top-left 2×2 being the identity is why a0 and a1 read off directly.

Why the matrix is always solvable (when it is). The matrix M depends only on the basis and the endpoint times — not on the data b. For monomials with distinct endpoints it is full rank (invertible), so there's exactly one cubic per boundary spec. Change the goal? Same M, new b, one solve. This is the workhorse of trajectory generation.

From scratch, in Python

The hand derivation maps line-for-line onto code. Build M and b, then solve.

python
import numpy as np

def cubic_coeffs(x0, xdot0, xf, xdotf, T):
    # Solve for [a0,a1,a2,a3] so that x(t)=a0+a1 t+a2 t^2+a3 t^3
    # matches position+velocity at t=0 and t=T.
    M = np.array([
        [1, 0, 0,        0       ],   # x(0)   = a0
        [0, 1, 0,        0       ],   # xdot(0)= a1
        [1, T, T**2,    T**3    ],   # x(T)
        [0, 1, 2*T,     3*T**2  ],   # xdot(T)
    ], dtype=float)
    b = np.array([x0, xdot0, xf, xdotf], dtype=float)
    return np.linalg.solve(M, b)

a = cubic_coeffs(0, 1, 4, 0, T=2)
print(a)            # [ 0.    1.    2.   -0.75]  -- matches the hand solve

def eval_poly(a, t):
    return a[0] + a[1]*t + a[2]*t**2 + a[3]*t**3
print(eval_poly(a, 2))  # 4.0

And the idiomatic one-liner once you trust the pattern — NumPy can evaluate the polynomial and its derivative for you:

python
# a is in increasing-power order [a0,a1,a2,a3]; np.polyval wants decreasing power
p   = np.polynomial.Polynomial(a)       # increasing-power, clean
pos = p(2.0)                            # 4.0
vel = p.deriv()(2.0)                     # 0.0   (xdot at T)

What about cubic vs. quintic?

A cubic has four coefficients, so it pins down four conditions: position and velocity at each end. If you also care about acceleration at the ends (smooth start/stop with no jerk in the force), you need two more conditions, hence two more coefficients — a degree-5 polynomial, the quintic:

x(t) = a0 + a1t + a2t² + a3t³ + a4t⁴ + a5t⁵

Same recipe, bigger matrix: six conditions (position, velocity, acceleration at both ends) → a 6×6 solve. The rule is mechanical: each derivative you constrain at each endpoint costs one more coefficient. 2 ends × (pos, vel) = 4 → cubic. 2 ends × (pos, vel, accel) = 6 → quintic.

Solve the Cubic Live

Set the four boundary conditions and the horizon T. The widget builds M and b, solves for the coefficients, and plots x(t) (orange) with its velocity &xdot;(t) (teal). Watch the coefficient readout update — it's the matrix solve, live.

x(0)0 &xdot;(0)1 x(T)4 &xdot;(T)0 T2
a = ...
You want to constrain position, velocity, AND acceleration at both the start and end of a coordinate. What polynomial degree do you need?

Chapter 3: Why a Smooth Curve Still Isn't Enough — Nonholonomy

We can now fit a beautiful smooth cubic through any boundary conditions. So we're done — just fit a curve and drive it? No. There's a subtler trap, and it's the deepest idea in wheeled-robot motion. The curve might be smooth and still be physically impossible for the robot to follow.

Recall the unicycle model from Lesson 2 — the simplest wheeled robot. Its motion obeys

&xdot; = V cosθ,    &ydot; = V sinθ,    &thetadot; = ω

where V is the forward speed and ω the turning rate — the two things you actually command. Stare at the first two equations. They say the velocity vector (&xdot;, &ydot;) always points along the heading θ. The robot can only ever move in the direction it faces. It cannot slide sideways. Ever.

This is the nonholonomic constraint — "rolling without side-slip." A wheel can roll forward or pivot, but it cannot translate along its own axle. Written as an equation, the sideways velocity is forced to zero:

&xdot; sinθ − &ydot; cosθ = 0
The misconception that bites everyone. "If the curve is smooth, the robot can drive it." False. A smooth curve can still demand sideways motion — for instance a perfect circle traced at constant heading, or any curve where the velocity vector doesn't line up with the way the robot is pointing. Smoothness is necessary but not sufficient. The curve must also be consistent with the robot's model: at every instant the velocity must point along the heading, and the curvature must stay within what ω can deliver.

So an arbitrary smooth (x(t), y(t)) is not automatically drivable. We need the heading θ(t) and the turn rate ω(t) it implies to be ones the robot can actually produce, in step with where it's pointing. The naive approach — fit x and y independently and hope — can produce a curve no wheeled robot on Earth can track.

Try it below: drag the curve into a shape and watch the required heading arrows. When the curve doubles back or makes a hairpin, the heading has to swing violently — demanding a turn rate ω the robot can't hit. The red flag means "infeasible: this needs a turn faster than ωmax."

Is This Curve Drivable?

Drag the control points to reshape the curve. The arrows show the heading the unicycle would need at each point. Segments needing a turn rate above ωmax flash red — infeasible. Gentle curves stay green.

ωmax1.6 drag a point

So the question becomes: how do we generate a smooth curve that is guaranteed to be consistent with the robot's dynamics? If we could pick a few variables to be smooth and have the heading, speed, and turn rate fall out automatically — always feasible by construction — we'd be done. That is exactly what differential flatness gives us. It's the hero of this lesson, and it's next.

Why is a smooth curve (x(t), y(t)) not automatically drivable by a wheeled robot?

Chapter 4: Differential Flatness — the Idea That Makes It Easy

Here is the beautiful idea, due to Fliess and made practical for robots by Van Nieuwstadt and Murray. For a special class of systems, there exists a magic choice of outputs — the flat outputs — such that every state and every input of the system can be written as an algebraic function of those outputs and a finite number of their time derivatives. No integration. No solving differential equations. Just plug in derivatives.

Definition (differential flatness). A system &xdot; = a(x, u) is differentially flat if there is a set of outputs z (equal in number to the inputs) such that all states x and inputs u can be recovered as functions of z and finitely many of its derivatives: x = β(z, ż, …), u = γ(z, ż, …). In words: pick a curve for z and the entire feasible trajectory comes along for free.

Why does this matter so much? Because if the position (x, y) is a valid flat output, then any smooth curve we draw for (x(t), y(t)) — like the cubics from Chapter 2 — automatically defines a heading, speed, and turn rate that satisfy the dynamics exactly. The infeasibility worry from Chapter 3 evaporates: feasibility is guaranteed by construction (as long as we avoid one singularity, Chapter 6). We get to design in the easy space (just draw x and y) and the hard variables follow.

The unicycle is flat — let's prove it

Claim: the unicycle's flat output is its position z = (x, y). We'll recover θ, V, and ω from x, y and their derivatives, using nothing but algebra. Start from the model:

&xdot; = V cosθ  (i),    &ydot; = V sinθ  (ii),    &thetadot; = ω  (iii)

Step 1 — recover the speed V. Square (i) and (ii) and add them. Using cos²θ + sin²θ = 1:

&xdot;² + &ydot;² = V²cos²θ + V²sin²θ = V²
⇒   V = √(&xdot;² + &ydot;²)

The speed is just the magnitude of the velocity vector — obvious in hindsight, but now it's derived purely from the flat output's first derivatives.

Step 2 — recover the heading θ. Divide (ii) by (i): the V's cancel, leaving the slope of the velocity vector.

&ydot; / &xdot; = (V sinθ)/(V cosθ) = tanθ  ⇒   θ = atan2(&ydot;, &xdot;)

(We use atan2, the two-argument arctangent, instead of plain arctan(&ydot;/&xdot;) so the heading lands in the correct quadrant for all directions, not just the right half-plane.) The robot's heading is simply the direction its position is currently moving — which makes total sense for a robot that can only go where it points.

Step 3 — recover the turn rate ω. This is the only one needing a second derivative. We want ω = &thetadot;. Since θ = atan2(&ydot;, &xdot;), differentiate. The derivative of atan2(g, f) with respect to time is (f·ġ − g·&fdot;)/(f² + g²). Here f = &xdot; so &fdot; = &xddot;, and g = &ydot; so ġ = &yddot;:

ω = &thetadot; = (&xdot;&yddot; − &ydot;&xddot;) / (&xdot;² + &ydot;²)

Some books write the numerator as &xddot;&ydot; − &yddot;&xdot; with the opposite sign convention — it's the same magnitude, just a sign choice for turn direction. The denominator is again. So the turn rate is the cross product of velocity and acceleration, normalized by speed squared — precisely the signed curvature times speed, which is what a turn rate is.

The whole result on one line. Pick any smooth (x(t), y(t)). Then, for free: V = √(&xdot;²+&ydot;²),   θ = atan2(&ydot;, &xdot;),   ω = (&xdot;&yddot; − &ydot;&xddot;)/(&xdot;²+&ydot;²). All three drop out of x, y and their first two derivatives. No integration anywhere. That is the payoff of flatness.

Recover the inputs in code

python
import numpy as np

def flat_to_state_input(xd, yd, xdd, ydd):
    # xd,yd = first derivs of flat output; xdd,ydd = second derivs.
    # Returns (theta, V, omega) for the unicycle, all algebraic.
    V2    = xd*xd + yd*yd
    V     = np.sqrt(V2)
    theta = np.arctan2(yd, xd)                 # quadrant-correct heading
    omega = (xd*ydd - yd*xdd) / V2             # cross(vel, acc) / V^2
    return theta, V, omega

Three lines turn a position curve into a fully feasible robot trajectory. This little function is, quite literally, the heart of the entire homework problem for this lecture — and of countless real trajectory generators.

Below, draw a position curve and watch θ, V, and ω pop out at any point you hover.

Flat Output → (θ, V, ω)

A figure-flowing position curve is drawn. Move the slider to pick a time along it; the readout shows the recovered heading θ, speed V, and turn rate ω computed from the derivatives at that point — the heading arrow and a curvature circle update live.

time t0.35
θ, V, ω = ...
For the unicycle with flat output (x, y), how is the turn rate ω obtained?

Chapter 5: Designing a Flat Trajectory — End to End with Numbers

Now we assemble the whole pipeline. The recipe (straight from the lecture notes) is three steps:

1. Boundary conditions
translate (x, V, θ) at the ends into conditions on the flat output (x, y) and its derivatives
2. Fit smooth z(t)
solve the polynomial linear system for x(t) and y(t)
3. Recover x, u
apply the flatness formulas to get θ(t), V(t), ω(t)

Step 1 — translate pose+velocity into flat-output conditions

The boundary spec is given in robot terms: start/end position, heading, and speed. We must convert these into conditions on x, y and their derivatives, because that's what the polynomial fit consumes. The bridge is the flatness map run backwards: from &xdot; = V cosθ and &ydot; = V sinθ,

&xdot;(0) = V0cosθ0,    &ydot;(0) = V0sinθ0

So a start heading and speed become a start velocity vector for the flat output. Let's use this homework-style spec:

x(0)=0, y(0)=0, V0=0.5, θ0=−π/2;   x(T)=5, y(T)=5, Vf=0.5, θf=−π/2;   T=15

Convert the ends. At θ0 = −π/2 (pointing straight down, in −y): cos(−π/2)=0, sin(−π/2)=−1. So

&xdot;(0) = 0.5·0 = 0,    &ydot;(0) = 0.5·(−1) = −0.5

And identically at the end (same heading and speed): &xdot;(T)=0, &ydot;(T)=−0.5. Now we have four conditions per coordinate — exactly a cubic each.

Step 2 — fit the two cubics

x-coordinate. Conditions: x(0)=0, &xdot;(0)=0, x(15)=5, &xdot;(15)=0. From t=0: a0=0, a1=0. From t=15:

225a2 + 3375a3 = 5  (pos),    30a2 + 675a3 = 0  (vel)

From the velocity equation a2 = −22.5 a3. Substitute: 225(−22.5 a3) + 3375 a3 = 5−5062.5 a3 + 3375 a3 = 5−1687.5 a3 = 5a3 ≈ −0.002963, and a2 ≈ 0.06667. So

x(t) ≈ 0.06667 t² − 0.002963 t³

y-coordinate. Conditions: y(0)=0, &ydot;(0)=−0.5, y(15)=5, &ydot;(15)=−0.5. From t=0: b0=0, b1=−0.5. From t=15:

−7.5 + 225b2 + 3375b3 = 5 ⇒ 225b2+3375b3=12.5;   −0.5 + 30b2 + 675b3 = −0.5 ⇒ 30b2+675b3=0

Velocity gives b2 = −22.5 b3; substituting as before: −1687.5 b3 = 12.5b3 ≈ −0.007407, b2 ≈ 0.16667. So

y(t) ≈ −0.5 t + 0.16667 t² − 0.007407 t³

Step 3 — recover the inputs at a sample time

Let's evaluate everything at t = 7.5 (the midpoint). First the derivatives. For x: &xdot; = 0.13333 t − 0.008889 t², &xddot; = 0.13333 − 0.017778 t.

&xdot;(7.5) = 1.0 − 0.5 = 0.5,    &xddot;(7.5) = 0.13333 − 0.13333 = 0

For y: &ydot; = −0.5 + 0.33333 t − 0.022222 t², &yddot; = 0.33333 − 0.044444 t.

&ydot;(7.5) = −0.5 + 2.5 − 1.25 = 0.75,    &yddot;(7.5) = 0.33333 − 0.33333 = 0

Now the flatness formulas at t=7.5:

V = √(0.5² + 0.75²) = √(0.25 + 0.5625) = √0.8125 ≈ 0.901 m/s
θ = atan2(0.75, 0.5) ≈ 0.983 rad ≈ 56.3°
ω = (&xdot;&yddot; − &ydot;&xddot;)/V² = (0.5·0 − 0.75·0)/0.8125 = 0 rad/s

So at the midpoint the robot is moving at about 0.90 m/s, heading up-and-right at 56°, and (because both accelerations happen to vanish there) momentarily not turning. Every number came from evaluating polynomials and plugging into three formulas — no differential equation was ever solved. That is differential flatness doing its job.

Watch the speed: it's not constant. A common error is to assume the polynomial trajectory moves at a steady speed. It does not — here V starts at 0.5, rises to ~0.90 at the middle, and returns to 0.5. The polynomial controls position; speed is whatever the derivative happens to be. If you have a speed limit, you must check it (and possibly time-scale — Chapter 6), not assume it.

The whole pipeline, end to end, in code:

python
import numpy as np

def cubic_coeffs(p0, v0, pf, vf, T):
    M = np.array([[1,0,0,0],[0,1,0,0],
                  [1,T,T**2,T**3],[0,1,2*T,3*T**2]], float)
    return np.linalg.solve(M, np.array([p0,v0,pf,vf], float))

T = 15.0; V0 = Vf = 0.5; th0 = thf = -np.pi/2
# Step 1: pose+speed -> flat-output velocity boundary conditions
ax = cubic_coeffs(0, V0*np.cos(th0), 5, Vf*np.cos(thf), T)   # x(t)
ay = cubic_coeffs(0, V0*np.sin(th0), 5, Vf*np.sin(thf), T)   # y(t)

def deriv(a, t, k):                 # k-th derivative of the cubic at t
    c = np.polynomial.Polynomial(a)
    for _ in range(k): c = c.deriv()
    return c(t)

t = 7.5
xd, yd   = deriv(ax,t,1), deriv(ay,t,1)
xdd, ydd = deriv(ax,t,2), deriv(ay,t,2)
V     = np.hypot(xd, yd)
theta = np.arctan2(yd, xd)
omega = (xd*ydd - yd*xdd) / (xd*xd + yd*yd)
print(round(V,3), round(theta,3), round(omega,3))  # 0.901 0.983 0.0
In the flat-trajectory design recipe, what is the FIRST step?

Chapter 6: Singularities & Feasibility — When the Magic Breaks

Differential flatness looked too good. There's a catch, and it lives right in the denominators. Look again at the recovery formulas:

θ = atan2(&ydot;, &xdot;),    ω = (&xdot;&yddot; − &ydot;&xddot;)/(&xdot;²+&ydot;²) = (…)/V²

What happens when the robot stops — when V = √(&xdot;²+&ydot;²) = 0? The denominator of ω becomes zero. ω is undefined. And θ = atan2(0, 0) is also undefined — if you're not moving, your direction of motion is meaningless. This is the flatness singularity at V = 0.

The deep reason behind the homework's "why can't V(tf)=0?" If you demand the robot end at rest (Vf=0), then &xdot;(T)=&ydot;(T)=0, so V(T)=0 and the heading and turn rate at the final instant become undefined — you literally cannot read off θ(T) or ω(T) from the flat output. The recovery map needs V≠0 (recall the Jacobian J in the extended model has det(J)=V, so it's only invertible for V>0). That's why these designs keep a small nonzero boundary speed.

The fix in practice: keep V > 0 throughout the trajectory — never plan a stop in the middle of a flat segment, and give the endpoints a small nonzero speed. If you genuinely must stop, you stitch trajectories: stop is its own segment handled by a different controller, then a fresh flat segment launches from a small speed.

Two flavors of feasibility limit

Even away from the singularity, the recovered inputs must respect the robot's physical limits. Two show up constantly:

The trouble is that polynomial coefficients don't obviously control these — you fit for position, and speed/turn-rate are whatever derivatives fall out. So you generate the trajectory, then check. If a limit is violated, the cleanest cure is the elegant idea of time scaling.

Time scaling: same path, slower clock

Here is the key separation. A trajectory bundles two things: a geometric path (the shape on the floor) and a timing law (how fast you move along it). Time scaling says: keep the shape, change only the clock. If you traverse the identical curve but take longer, every velocity shrinks — without changing where the robot goes.

The lecture's clean demonstration: take a 1D path x(s) = x0 + s(xf − x0) for s ∈ [0,1], and drive the parameter s with a smooth cubic timing law that starts and ends at rest:

s(t) = (3/T²) t² − (2/T³) t³

This satisfies s(0)=0, s(T)=1, ⋅(0)=⋅(T)=0. The maximum speed along the path comes out to

&xdot;max = (3 / 2T)(xf − x0)

Read that: doubling T halves the peak speed. The geometry is untouched — same start, same end, same shape — but the whole motion is gentler. So if your candidate trajectory's peak speed is, say, 0.9 m/s and your limit is 0.5, you scale time by the ratio (here ~1.8× longer) and the speed drops under the cap while the path stays exactly the same.

Why this works so neatly for wheeled robots. For the unicycle, picking arc length as the path parameter makes the geometric controls clean: the geometric shape is governed by the steering, and the speed can be chosen independently. Slowing down doesn't change the shape, so a speed-limited robot can drive the very same route — just more slowly. Time scaling converts a hard input-bound constraint into a simple "stretch the clock" operation.

Below, watch a candidate trajectory's speed profile against the limit, and stretch T with the slider. When the peak speed dips under Vmax, the trajectory turns feasible — same path, slower clock.

Time Scaling: Stretch the Clock

Top: the fixed geometric path (unchanged by T). Bottom: the speed profile V(t) vs. the dashed limit Vmax. Drag T larger — the speed curve flattens under the limit and the readout flips to feasible. The path never moves.

T (s)4 Vmax0.7
Your flat trajectory's peak speed exceeds the robot's limit. What does time scaling do to fix it?

Chapter 7: The Optimization View — the Best Trajectory, Not Just a Good One

Flatness gives us a feasible trajectory quickly. But often we want the best one: smoothest, least energy, fastest. That's the realm of trajectory optimization (also called optimal control). Instead of just interpolating boundary conditions, we minimize a cost.

The general optimal control problem, straight from the lecture, looks like this. We choose the state trajectory x(t) and inputs u(t) to minimize a cost while obeying the dynamics and limits:

minimize   h(x(tf)) + ∫t0tf g(x(t), u(t)) dt
subject to   &xdot;(t) = a(x(t), u(t))  (dynamics)
             x ∈ X, u ∈ U  (state & input limits)
             x(t0) = x0  (start condition)

Decoding the symbols: g is the stage cost (the price you pay at each instant — e.g. control effort), h is the terminal cost (a price on where you end up), and the integral sums the stage cost over the whole motion. The constraint &xdot; = a(x,u) forces the trajectory to obey physics; the set memberships enforce limits.

What cost do we pick?

The cost is the engineering decision — it encodes what "good" means:

Smoothness = minimizing a derivative. This is the unifying insight. "Smooth" isn't vague — it's quantitative. Minimize squared acceleration and you get minimum-jerk-ish motion. Minimize squared snap (4th derivative) and you get the silkiest path. The order of the derivative you penalize sets how many degrees of smoothness you buy. That's why minimum-snap quadrotor trajectories look so graceful.

How is it solved? Discretize, then optimize.

The problem above is infinite-dimensional — the unknown is a whole continuous function. Computers need finitely many numbers. The dominant approach is the direct method: chop time into N points, treat the values xi, ui at those points as the unknowns, and approximate the dynamics with a discrete update (e.g. forward Euler xi+1 = xi + Δt·a(xi, ui)). The cost integral becomes a sum. This turns the calculus problem into a nonlinear program (NLP) — minimize-a-function-over-vectors — that solvers like IPOPT, SNOPT, or CasADi crunch.

The special case that's just a QP

Here's where flatness and optimization shake hands. If you parameterize the flat output as a polynomial (as in Chapter 2), the trajectory is linear in the coefficients. A smoothness cost like "integral of squared acceleration" is then a quadratic function of those coefficients, and the boundary conditions are linear equality constraints. Minimizing a quadratic subject to linear constraints is a quadratic program (QP) — convex, fast, with a unique global minimum:

minimizea   a Q a  (squared-derivative smoothness)
subject to   M a = b  (boundary & waypoint conditions)

where a stacks all polynomial coefficients, Q is built from integrals of products of basis derivatives, and M a = b is exactly the boundary-condition system from Chapter 2 (plus rows for any interior waypoints). When you have more coefficients than constraints, the QP picks the smoothest feasible trajectory among the infinitely many that fit. This is the backbone of minimum-snap planning.

Cost Trades: Effort vs. Speed

A 1D point goes 0→1 over horizon T. Slide the effort weight: high weight buys a gentle, low-acceleration glide (small peak force); low weight lets it rush. Watch the position, velocity, and the cost readout trade off — the optimizer's dial, in your hand.

effort weight0.5
Why does parameterizing a flat output as a polynomial often turn trajectory optimization into a QP (quadratic program)?

Chapter 8: The Trajectory Designer — Build, Drive, Read the Inputs

Time for the payoff. Everything we've built — cubic fitting, flatness recovery, time scaling — lives in one interactive tool. Drag the start and goal poses; the lesson fits a flat cubic trajectory for x(t) and y(t), then drives a robot along it while plotting the recovered speed V(t) and turn rate ω(t) live. A slider for the total time T shows time scaling in action: slower T lowers the required V and ω.

Three things to look for as you play:

Interactive Flat-Trajectory Designer

Drag the orange start and green goal handles (and the small heading dots) to set the boundary poses. Press Drive to animate the robot along the fitted flat trajectory. The right panels plot V(t) and ω(t) with their limits. Stretch T to time-scale; toggle the waypoint to bend the path through a middle point.

T (s)10
drag a handle, then Drive

This is the whole lesson in one widget: a planner-style endpoint spec goes in; a smooth, timed, dynamically-feasible trajectory comes out, complete with the exact velocity and turn-rate commands the controller in Lesson 6 will track. You designed it by dragging two dots.

What just happened, in one breath. You specified boundary poses (the what). The tool fit polynomials for the flat output x, y (the smooth curve), differentiated them, and applied V=√(&xdot;²+&ydot;²), θ=atan2(&ydot;,&xdot;), ω=(&xdot;&yddot;−&ydot;&xddot;)/V² to get the feasible inputs (the how) — with T as the time-scaling dial to respect limits. No differential equation solved, ever.

Chapter 9: In the Real World — Receding Horizon & the Hand-off

Our trajectory is computed open-loop: a function of time and the initial condition only, with no feedback. The lecture is blunt about the consequence: "open-loop control laws suffer from robustness issues since they do not make corrections based on real-time observations." If a gust of wind, a wheel slip, or a modeling error nudges the robot off the plan, an open-loop trajectory has no idea and no recourse. It walks to the chair with its eyes closed.

Two practical patterns fix this.

Pattern 1 — the two-step design (reference + tracker)

This is the dominant architecture, and it's exactly the bridge to Lesson 6. Split control into two layers:

Reference (open-loop)
this lesson: flat trajectory ξ(t), V(t), ω(t) — the plan
Tracker (closed-loop)
Lesson 6: PID/LQR drives the tracking error to zero in real time

The open-loop trajectory provides the nominal motion — what should happen in a perfect world. The closed-loop tracker measures the actual pose, compares it to the reference, and applies corrections to kill the tracking error. The reference says "be here, going this fast"; the tracker makes sure you actually are. Our whole lesson built the reference half. Lesson 6 builds the tracker.

Pattern 2 — receding horizon (MPC)

Model Predictive Control (MPC) takes the idea further: don't compute one long trajectory once. Instead, re-plan continuously. At each instant, solve a short trajectory-optimization problem over a finite look-ahead window (the horizon), execute only the first slice of the resulting plan, then — one tick later, with fresh sensor data — re-solve from the new actual state. The horizon slides forward with time, hence "receding."

Why MPC is the best of both worlds. Each solve is an open-loop trajectory optimization (everything from Chapter 7). But because you re-solve every tick from the measured state, the overall loop is closed — disturbances get corrected at the next re-plan. MPC also handles constraints (speed/turn limits, obstacles) naturally, since each solve is a constrained optimization. The cost is compute: you're solving an optimization problem in real time, dozens of times a second.

So the trajectory you generate in this lesson is never the end of the story — it's the reference that a closed-loop layer (a tracker, or a re-planning MPC loop) consumes to stay robust. Open-loop generation and closed-loop tracking are partners, not rivals.

Open-Loop Drift vs. Receding-Horizon Re-plan

A disturbance pushes the robot off plan each step. Open-loop commits to one trajectory and drifts away; receding-horizon re-fits a fresh flat trajectory from the current measured pose each tick and stays locked on the goal. Toggle and press Run.

disturb0.018 ready
An open-loop trajectory is computed once. How does Model Predictive Control (MPC) make the overall behavior robust to disturbances?

Chapter 10: Connections — the Cheat-Sheet & What's Next

You now own one of the most useful tricks in robotics: turning a planner's waypoints into a smooth, timed, dynamically-feasible trajectory, almost for free, via differential flatness. Let's consolidate.

The one-page cheat-sheet

ConceptWhat it isThe key formula / fact
Pathgeometry only — a shape, no clockξ(s), function of path parameter s
Trajectorypath + timing — position & velocity at every instantξ(t), function of time t
Cubic fitsmooth coord from 4 boundary conditionsx(t)=a₀+a₁t+a₂t²+a₃t³, solve M·a=b
Flat outputvariables that algebraically yield all states/inputsunicycle: z = (x, y)
Recover speedfrom flat-output derivativesV = √(&xdot;² + &ydot;²)
Recover headingdirection of motionθ = atan2(&ydot;, &xdot;)
Recover turn ratecross(vel,acc)/V²ω = (&xdot;&yddot; − &ydot;&xddot;)/(&xdot;²+&ydot;²)
Singularityrecovery breaks when stoppedV = 0 ⇒ θ, ω undefined — keep V > 0
Time scalingrespect speed limits without changing the pathlarger T ⇒ lower peak speed; &xdot;max ∝ 1/T
Optimizationbest trajectory, not just feasiblemin cost s.t. dynamics+limits; polynomial ⇒ QP

Where this sits on the autonomy stack

Trajectory generation is the lower half of the Motion Planning & Control module. It consumes the discrete plan from the search algorithms above it and produces the continuous reference the controller below it tracks:

"What I cannot create, I do not understand." — Richard Feynman.
You can now create a dynamically-feasible robot trajectory from scratch — from boundary poses to the exact V(t) and ω(t) commands. Next, Lesson 6: how a controller actually tracks it when the world pushes back.
On the autonomy stack, what does trajectory generation hand off to the controller (Lesson 6)?