Pure pursuit

Chase a point a fixed distance ahead on the path, and choose the one number that decides everything.

U10.4Following a trajectoryUniversity35 min

Do this lesson in the simulator

Cross-track control works on a straight leg. On a curve it has a problem: correcting towards the nearest point on the path aims the robot at the path, not along it, so it arrives at the curve pointing across it and has to correct again. The result is a robot that is always slightly late to every bend.

Pure pursuit fixes it with one idea: do not aim at the nearest point, aim at a point a fixed distance further along.

1. project the robot onto the path      -> arc length s
2. take the point at arc length s + L   -> the look-ahead point
3. drive towards that point

L is the look-ahead distance, and it is the only number in the algorithm with any real choice in it. Everything good and everything bad about pure pursuit is a consequence of that one value.

Why it works

Aiming at a point ahead builds the curvature of the path into the demand automatically. On a bend, the look-ahead point is already round the corner, so the robot starts turning before it reaches the corner, which is what a human driver does and what a nearest-point controller refuses to do.

A robot beside the lesson's path, with its nearest point on the path and its look-ahead point 18 cm further onthe pathrobotnearest point, s = 72look-ahead point, s + L = 90L = 18 along the pathvelocity
The robot is 8 cm off the tape just before the bend. Aiming at the nearest point (red) would only pull it sideways onto the straight. The point 18 cm further along (green) is already on the bend, so the velocity aimed at it starts turning before the robot gets there.

For a car, the classic result is that chasing a point at distance L with lateral offset y in the body frame gives an arc of curvature

kappa = 2 * y / (L * L)

which is why the algorithm is popular: it turns a geometry problem straight into a steering command with no controller tuning at all. Note the L * L in the denominator. The effective cross-track gain is 2 / L^2, so doubling the look-ahead makes the controller four times gentler.

On a holonomic robot the geometry is simpler still. There is no steering to solve for: point the velocity vector at the look-ahead point and let the inverse kinematics from U2 work out the commands. The heading is then free, and can be used for something else entirely, such as keeping a camera pointed at a landmark.

The trade-off

This is the one real trade-off in the module and it is worth stating carefully.

look-ahead what you get what it costs
small (3 to 6 cm) tight tracking on straights, corners taken almost exactly on a robot that steers like a car, a weave: the correction is aggressive and the drive's lag turns aggression into overshoot. Pointing the velocity, as here, does not weave
medium (15 to 25 cm) smooth, stable, corners cut a little the compromise everybody ships
large (50 cm) very smooth, very stable, ignores noise corners cut badly, and on a tight bend the robot can cut across the inside and leave the path entirely

Two specific failure modes are worth recognising on sight.

Weaving with small L, on a robot that steers. A car, or a robot driven like one, can only move sideways by turning, so it corrects an error in two steps: turn towards the path, then let the turn carry it across. With a small L the turn it asks for is large, and the drive's lag (0.25 s here) keeps the heading swinging after the robot is back on the line, so it crosses the path and the next correction is the other way. It weaves down a straight with a period of about 2 s. You can see it in the simulator: a follower that turns to face the look-ahead point and drives forwards only weaves up to 4.1 cm either side of this tape at L = 3 and 14 cm/s, and stays within 0.4 cm at L = 5. The weave stops once L is about 0.3 s of travel. Raising L fixes it. Raising the gain does not.

The task's follower does not weave, and it is worth knowing why. Pointing the velocity at the look-ahead point moves the robot sideways directly, with no heading in between to swing past the line, so the lag only slows the correction down. At L = 6 it never leaves the tape by more than 0.9 cm, and even at L = 2 it stays within 0.5 cm at 11, 14 and 20 cm/s. For this robot a short look-ahead costs very little on the mat, which is one of the quiet advantages of being able to move in any direction.

Corner cutting with large L. On a corner, the look-ahead point is across the bend rather than round it, and the robot takes the chord. The error is roughly the sagitta of the chord, which for a 90 degree corner is a good fraction of L. If the path was planned to keep clear of an obstacle, that cut is exactly where the obstacle is, which is how a perfectly good plan produces a collision.

There is also a steady-state offset on a constant curvature path. Pointing the velocity straight at the look-ahead point means moving along a chord, so the robot settles inside the circle, at the radius where that chord is tangent to its own path: an offset of R * (1 - cos(L / R)), about L^2 / (2 * R). On the 40 cm bend here that is 4.0 cm at L = 18, and a follower like the task's came 4.5 cm off at worst. Worth knowing before you spend an afternoon tuning it out.

On a steady bend, the robot settles inside the path where its chord to the look-ahead point is tangent to its own circlecentreoffset 4.0 cmrobotlook-ahead pointthe tape, radius 40dashed: wherethe robot settlesL = 18
Pointing the velocity at the look-ahead point means moving along a chord (green arrow). The robot settles on the dashed circle, where that chord just touches its own path: R times (1 - cos(L / R)) = 4.0 cm inside the 40 cm bend at L = 18.

Speed-dependent look-ahead

The standard practice is L = L0 + k * v. The argument is time: at speed v, a look-ahead of L is L / v seconds of preview, and what the controller needs is a roughly constant preview time, not a constant distance. Fast means look further. For a robot that steers it also falls out of the stability analysis, because the lag costs a fixed number of seconds, not a fixed number of centimetres.

Finding the look-ahead point

Textbooks describe it as the intersection of a circle of radius L centred on the robot with the path. That is correct and it is fragile: when the robot is more than L from the path there is no intersection at all, and you need a special case for exactly the situation where the controller matters most.

Projecting first is better. Find the nearest point on the path, take its arc length, add L, and read off that point. It is defined everywhere, it is monotone (the robot cannot be sent backwards by noise), and it is a handful of lines for a polyline.

Two ways to find the look-ahead point for a robot further than L from the pathcircle meets pathradius L = 187 cm short: no point at allrobotproject, then add Lnearest+ L = 18look-ahead pointrobot
The robot is 25 cm off the path with L = 18. A circle of radius L never reaches the path, so the textbook method has no answer. Projecting first always gives one: the nearest point, then L further along, and the robot heads back on a slant that already points down the path.
from bugbot import *
import math
connect()

arc = [(80 + 40 * math.cos(math.radians(180 - k * 11.25)),
        120 + 40 * math.sin(math.radians(180 - k * 11.25))) for k in range(9)]
PATH = [(40.0, 40.0)] + arc + [(150.0, 160.0)]
cum = [0.0]
for (ax, ay), (bx, by) in zip(PATH, PATH[1:]):
    cum.append(cum[-1] + math.hypot(bx - ax, by - ay))
print("the path is", round(cum[-1], 1), "cm long, in", len(PATH) - 1, "segments")

Run this in the simulator

Task: chase the look-ahead point

The tape runs straight from (40, 40) to (40, 120), round a quarter circle of radius 40 centred on (80, 120), then straight to (150, 160). Follow it with pure pursuit, plot off path and speed, print path: (the length of the path) and drove: (how far the robot actually travelled), and stop at the far end.

from bugbot import *
import math
connect()

DT = 0.1
START = (40.0, 40.0)
CRUISE, LOOK = 11.0, 18.0

Challenges

  1. Run it at LOOK = 6 and at LOOK = 45. Which one cuts the corner, and by how much? Does the short one weave at all?
  2. Steer it like a car instead: drive forwards only, and turn at v * 2 * sin(alpha) / L radians a second, where alpha is the angle of the look-ahead point from the way the robot faces. Run L = 3 at 14 cm/s and watch the first straight, then try L = 5.
  3. Make the look-ahead grow with speed and see whether you can keep both the straights and the corner.
  4. Measure the largest off path value in the corner and compare it with L * L / (2 * 40).