Potential Functions
Attractive and repulsive potentials and gradient descent; distance from a range scan and from a grid; brushfire and wave-front as breadth-first search; the local-minimum disease of additive potentials and the navigation functions that cure it; and lifting workspace forces to Reach's joint torques through the transposed Jacobian.
The potential function approach directs a robot as if it were a particle moving in a gradient vector field.
In this chapter
Chapter 6 planned on a graph someone had already built. This chapter asks what a robot can do with nothing but a goal and a sense of distance: treat the goal as a valley, the obstacles as hills, and roll downhill. The construction is beautifully local. The gradient of the potential at needs only the goal and the nearest obstacle, so Rusty can compute it from a range scan and Reach from a few points on its links — no configuration space is ever built.
The "aha" comes in two halves. The first is a failure: a sum of one valley and some hills can have a second valley that is not the goal, and gradient descent rolls into it and stops. The widget that opens the chapter shows a bead doing exactly that in the mouth of a horseshoe, and then — this is the part people get wrong — between two convex discs. The disease is in the sum, not in concavity. The second half is the cure, and it is not a tweak to the hills. A distance-to-goal field has exactly one minimum, and on a grid the breadth-first search the reader already owns computes it: wave-front is BFS rooted at the goal, brushfire is BFS rooted at the obstacles. Navigation functions show that the single minimum can even be had smoothly, on a restricted family of worlds, so the local-minimum problem is a fact about additive potentials, not about potentials.
Everything here is incomplete except the wave-front planner, which is resolution complete and exponential in dimension; the chapter says so each time. What survives is the field idea: a scalar function whose gradient the robot follows. Chapter 8 collides brushfire fronts on the generalized Voronoi diagram, Chapter 19's CHOMP descends a distance field, and Chapter 23's reactive layer is this chapter running at a hundred hertz.
The problem: roll downhill, and ask who built the hill
Put a bead at the left edge of a table and a goal at the right. Define a scalar field that is small at the goal and large near obstacles, and let the bead move with velocity . With a single disc between them, the bead swerves around the disc and arrives. Replace the disc by a horseshoe whose mouth faces the bead, and it rolls into the mouth and stops.
Nothing is broken. The badge under the canvas reports what the mathematics says: the gradient has vanished at a point that is not the goal, and both eigenvalues of the Hessian there are positive. The bead sits at the bottom of a genuine bowl. The goal's pull and the base's push cancel exactly on the axis of the horseshoe, and the two arms push back toward the axis whenever the bead drifts off it, so the point is stable in every direction. Choset's figure 4.10 draws this and his figure 4.11 makes the sharper point: switch the widget to two convex discs and the bead still stops, between them. Concavity has nothing to do with it.
Now switch on wave-front mode. The heightfield changes to the breadth-first cost-to-goal of Chapter 6, computed on a tenth-of-a-metre raster of the same obstacles, and the bead escapes along a grid-shortest path. That field has one minimum, provably, because it is distance to the goal. The rest of the chapter is the difference between those two heightfields.
Building intuition
Why a sum has extra valleys
The additive potential is : a bowl centred at the goal plus a spike on each obstacle. Far from any obstacle only the bowl matters and the bead heads for the goal. Near an obstacle the spike takes over and pushes the bead off. In between, the two gradients can cancel, and the only question is whether the cancellation point is stable. Between two obstacles symmetric about the line to the goal it is: the repulsive push is purely axial on the axis by symmetry, it grows without bound toward the gap while the attractive pull stays bounded, so somewhere on the axis the two cancel; and off the axis both spikes push back toward it. Derivation 3 writes this out. The Potential Well's slider changes where the cancellation happens but cannot make it go away; the and sliders in the disclosure move it along the axis. Only the obstacle layout can remove a minimum, and the planner does not control the layout.
Fronts on a grid
The grid cure is older than the word "potential". Label every obstacle pixel , its free neighbours , their unlabelled neighbours , and so on: a fire spreading outward from every obstacle at once. When it is done, each pixel's label is one more than its link length to the nearest obstacle. That is brushfire, Choset §4.3.2, and it is a multi-source breadth-first search.
Two things to watch. First, the shape of a front: under 4-point connectivity the fronts are diamonds, because the label counts Manhattan steps; under 8-point they are squares, because the label counts chessboard steps. Neither is a circle. Brushfire computes a grid metric, and the hover readout compares it with the exact Euclidean distance transform ported from the sister book: the two agree only along the axes. Second, the solid pixels where two fronts meet. On the micro-grid that is column 4, equidistant from both obstacles; each of its three pixels was reached by both fronts and keeps two back pointers. Chapter 8 will call that column the grid generalized Voronoi diagram. Seed the same search from the goal pixel instead, labelled , and you have the wave-front planner of §4.5: a pixel's label is its grid distance to the goal, and "step to any neighbour labelled one less" is a shortest grid path.
One minimum, smoothly
Grids are exponential in dimension. Is the single minimum available without one? Rimon and Koditschek's answer is yes, on a sphere world — a disc with disjoint disc obstacles — and on anything diffeomorphic to one.
The field is , where is distance to the goal and is a product that vanishes on every obstacle boundary. At the Sculptor finds three minima; at two; from exactly one. Slide and watch the spurious minima slide toward their obstacles and turn into saddles. This is the point the widget exists to make: a potential with no spurious minima exists, and it is smooth. The price is the family of worlds it works on and a sharpening exponent whose threshold the theorem only promises exists. Star mode shows the trick that widens the family: a map that squashes each star-shaped obstacle onto a disc, with inheriting the single minimum because critical points correspond one-to-one under a diffeomorphism.
A body is not a point
Reach's configuration space is a torus; there is no "goal point" in the workspace to put a bowl at. Choset's §4.7 writes the potential in the workspace, at a few control points on the arm, and lifts each workspace force to a joint torque with the transposed Jacobian of that point.
Two red control points on the end effector pull it to the goal pose; slate points on the links push away from the block, the post and the shelf. The bars under the canvases are the lifted torques , one row per control point, and their sum — the gradient the arm descends. Watch the default run twice. Without link 1 drives straight through the post between its two repulsive points, which are far enough from the post that their push is weak; with — a floating control point at whichever link point is nearest any obstacle — the same start reaches the goal. Repelling the vertices of a body does not protect its edges (Choset's figure 4.20); reduces the risk and does not eliminate it, and the widget's alternate mode lets you find starts where even that is not enough.
The mathematics
| Symbol | Meaning | Note |
|---|---|---|
| potential, its attractive and repulsive parts, and the gradient the robot reads as a velocity | book-wide | |
| attractive and repulsive gains | ||
| radius where the attractive potential switches from quadratic to conic | ||
| repulsive distance of influence — global, or per obstacle | ||
| distance to QO_i, to the nearest obstacle, and the closest point on QO_i, so ∇d_i = (q − c)/d_i | book-wide | |
| descent step size at iteration i; the gradient tolerance in ‖∇U‖ < ε | ||
| Hessian; nondegenerate critical point ⇔ nonsingular Hessian | ||
| obstacle functions (< 0 inside, 0 on ∂QO_i, > 0 outside); their product | ||
| goal term d^{2κ}; analytic switch x/(λ + x); sharpening x^{1/κ} | ||
| navigation function (Choset Def. 4.6.1), φ = ξ_κ ∘ σ_1 ∘ γ_κ/β | ||
| star-to-sphere diffeomorphism; translated scaling maps; their scale factors; analytic switches | ||
| control point j and its Jacobian, u = J_jᵀ f; the floating control point nearest any obstacle |
Potentials, critical points, and the Hessian
In the colours every figure on this page uses, the descent is
The orange bead is , the red goal sits at the bottom of the bowl, and every slate obstacle contributes one spike through its distance . Point at a term and the matching layer in the Potential Well comes forward.
DerivationThe blended attractive potential has a continuous, bounded gradient
Statement (Choset eqs. 4.2–4.3). With
is continuous and its magnitude never exceeds .
Step 1 — inside. , of magnitude , vanishing linearly at the goal (eq. 4.1).
Step 2 — outside. , of magnitude exactly everywhere.
Step 3 — the seam. At both expressions equal ; the constant makes the values agree too, so is .
Why blend at all. The pure conic potential has gradient of magnitude everywhere and no direction at the goal — descent chatters around it. The pure quadratic vanishes nicely at the goal but asks for a velocity proportional to distance, which a far start turns into an absurd one. The blend takes each where it is good. The chapter's check pushes two hundred random seams and finds a gradient jump of (the finite width of the test) and a bound excess of .
DerivationGradient of distance to a convex obstacle
Statement (Choset eq. 4.7). If is the unique closest point of a convex to , then , a unit vector pointing away from the obstacle.
Step 1 — uniqueness. is strictly convex in and is convex and closed, so the minimiser is unique.
Step 2 — the envelope theorem. ; at a unique minimiser the derivative of the minimum with respect to is the partial derivative of the objective with held fixed.
Step 3 — differentiate. .
Step 4 — where convexity enters. A nonconvex obstacle can have two closest points, and is then not differentiable: the gradient jumps as crosses the set where they tie. Choset's §4.1 warns that a repulsive term written in oscillates where the nearest obstacle changes; a nonconvex obstacle does the same thing to inside one term.
The two cures. Sum the repulsion per convex obstacle, , and decompose nonconvex obstacles into convex pieces. The Potential Well's horseshoe is three rectangles for exactly this reason; the disclosure toggle gives you the single nonconvex polygon instead, and the bead chatters on its axis — the badge says "stalled" with a gradient of order one, because flips sign every step. The check compares the analytic over discs, a polygon and a wall segment with central differences at 352 random points: worst relative error . Choset's §4.3.1 reading of as a local minimum of over the bearing is the sensor version of this derivation, and it is where the Apartment's thin walls expose a limit — see the algorithm section.
The local-minimum disease
DerivationAdditive potentials admit non-goal minima — two convex discs suffice
Statement (Choset §4.4, figures 4.10–4.11). For two disc obstacles placed symmetrically about the line from the start to the goal, there is a point on that line with and positive-definite Hessian.
Step 1 — symmetry. On the axis of symmetry the two repulsive gradients are mirror images, so their sum is axial. The attractive gradient is axial too. So is axial on the axis.
Step 2 — unbounded push, bounded pull. As the point on the axis moves toward the gap, each falls toward its minimum and grows without bound if the gap is narrower than ; the attractive pull is bounded by (Derivation 1). Far from the discs, beyond , the push is zero and the pull is toward the goal.
Step 3 — a zero. The axial component of is continuous, negative (goalward) far from the gap and positive (away) close to it; the intermediate value theorem gives with .
Step 4 — transversal stability. Off the axis, the nearer disc pushes harder than the farther one and both push toward the axis; so the transverse second derivative is positive. The axial second derivative is positive because the push grows as the gap is approached. The Hessian is positive definite: is a minimum.
The numbers. Discs of radius at , goal , , , . From the descent stops after 160 steps at with Hessian eigenvalues 14.15 and 16.16, both positive. The chapter's check pins all of it. The horseshoe case is the same argument with the base supplying the push; Barraquand and Latombe's RPP escapes such minima with random walks and tries again, which is the honest fix of 1991 and the ancestor of Part III's sampling. The method is not complete.
Brushfire and wave-front are breadth-first search
DerivationBrushfire computes grid distance; wave-front computes grid cost-to-goal; both are BFS
Statement (Choset §4.3.2, §4.5; Chapter 6 Derivation 1). Labelling obstacle pixels , their free neighbours , theirs , … assigns each free pixel its link length to the nearest obstacle — Manhattan distance under 4-point connectivity, chessboard under 8-point. Seeding the goal pixel with instead assigns link length to the goal, and "step to a neighbour labelled one less" is a shortest grid path.
Step 1 — it is a BFS. Brushfire is breadth-first search on Chapter 6's grid graph with every obstacle pixel in the initial queue at depth ; wave-front is the same search with the goal alone at depth . Labels are depth plus the seed label.
Step 2 — depth is link length. Chapter 6, Derivation 1: a node's depth when first dequeued is its shortest link length from the seed set. On a unit-cost 4-connected grid that is the Manhattan distance; on an 8-connected one, the chessboard distance.
Step 3 — descent reaches the goal. On a bounded grid every reachable free pixel receives a label. A non-goal pixel labelled was labelled from a neighbour labelled , so such a neighbour exists; stepping to it strictly decreases the label and the walk ends at the goal after exactly steps. The field has one minimum.
Two fronts meeting. A pixel reached by fronts from two different obstacles keeps two back
pointers (Choset §5.2.5); the set of such pixels is the grid GVD of Chapter 8, and the micro-grid's
column 4 is its smallest instance. On an even gap the collision lands on a pixel; on an odd gap it
lands on the edge between two pixels, and the port flags both so the ridge has no holes. Grid
versus Euclidean. On forty random grids totalling 7020 free cells the 4-point and 8-point labels
equal the exact Manhattan and chessboard distances cell for cell, the exact Euclidean distance
lies between them, 99.5% of cells are within one cell of it, and the worst gap is 2.789 cells —
along a diagonal, where Manhattan overshoots by . The exact Euclidean distance transform
is a different algorithm, ported from the sister book's
map chapter
as mapping/edt, and Exercise 5 asks you to run the two side by side. Dimension. Pixels become
voxels, 4-point becomes 6-point, 8-point becomes 26-point, and the cell count is exponential in
the dimension of — which is why Part III exists.
Navigation functions
DerivationThe sphere-world navigation function
Statement (Choset §4.6.1, eqs. 4.8–4.13; Rimon and Koditschek). On a sphere world, is a navigation function for sufficiently large , where , , and .
Step 1 — the raw ratio. is zero only at the goal and on every obstacle boundary, where some . It is positive in between.
Step 2 — large κ pushes the goal term ahead. Away from obstacles, dominates once is large, so the gradient of the ratio points goalward and no critical point survives there.
Step 3 — near an obstacle, only it matters. The ratio is then times a slowly varying factor, and a critical point can lie only on the radial line from the obstacle to the goal; on that line the ratio decreases from the boundary toward the goal, so the Hessian cannot be positive definite. Spurious minima become saddles (Choset cites [239] for the proof).
Step 4 — squash. is strictly increasing, maps onto , and so moves no critical point and changes no type; it bounds the function.
Step 5 — sharpen. is strictly increasing on and makes the critical points nondegenerate (Morse); composing gives eq. (4.13).
Counting instead of eyeballing. Because , and are strictly increasing, the critical points of off the goal are those of , whose gradient is of order one where 's is — the plateau problem Choset's last paragraph of §4.6.1 admits: is flat near the goal and far from it and steep in between, which makes gradient descent on it numerically miserable. The Sculptor's beads descend the normalized gradient for that reason. The port finds critical points by damped Newton on from a lattice of seeds and classifies them by the Hessian of . On the five-disc world the sweep reads:
| 2 | 3 | 4 | 6 | 7 | 8 | 10 | |
|---|---|---|---|---|---|---|---|
| minima | 3 | 2 | 2 | 2 | 1 | 1 | 1 |
| saddles | 7 | 6 | 6 | 6 | 5 | 5 | 5 |
Choset's own five-disc figure 4.15 has three minima at and one at ; our discs sit elsewhere and lose their last spurious minimum at . In every column : on a disc with five holes the Euler characteristic is , and Morse theory makes that an invariant of the count — each spurious minimum that disappears takes exactly one saddle with it. The chapter's check pins the whole table. Nothing in the theorem says which suffices; it is existential, and in practice one sweeps.
DerivationNavigation functions pull back under diffeomorphisms; the star-to-sphere map
Statement (Choset §4.6.2, eqs. 4.14–4.19). If is a navigation function on and is a diffeomorphism — smooth, bijective, smooth inverse — then is a navigation function on . For a star world the map
is such a diffeomorphism onto the model sphere world for suitable .
Step 1 — critical points correspond. , and is invertible, so iff . The Hessians are congruent at critical points, so nondegeneracy and type survive. Minima map to minima: one on , one on .
Step 2 — each maps its star boundary onto a circle. On , , so : the point at bearing from lands at distance from at the same bearing. A star boundary of radius is squashed radially by .
Step 3 — the switches switch. On , gives and every with contains the factor , so and . Hence exactly on . At the goal , every , and .
Step 4 — suitable λ. Smoothness and bijectivity for some beyond a threshold are quoted from Rimon and Koditschek. The port measures the local half: on a lattice of free points.
What the port had to learn. Three things about this construction are easy to get wrong, and the chapter's check caught all three. First, is astronomically larger than for any of order one — on our world at — so a pins every switch at one and turns a rounding error of in a boundary into garbage for the other switches; the boundary is now handled exactly. Second, the model disc must be inscribed in its star: if a disc poked outside its star, would point outward in the star's valleys while points inward, and went negative in the switch collar for every large we tried. With inscribed discs, at over 1450 lattice points. Third, "suitable" is not decoration: at the minimum determinant is and the map folds. The Sculptor's slider spans both regimes. Virtual work, the other half of this derivation's collapsible, follows in §4.7 below.
Lifting forces to configuration space
DerivationVirtual work: a workspace force is a configuration-space force through Jᵀ
Statement (Choset §4.7.1, eq. 4.26). Power is coordinate-independent: for every . With , for all , so . The lifted forces of several control points are summed in configuration space, never in the workspace.
Example 4.7.1. A vertex of a planar rigid body at sits at (eq. 4.20), with Jacobian
(eq. 4.21), so (eq. 4.23), and with the world
vector from the body origin to the vertex. The check verifies this identity at a hundred random
configurations to against Chapter 4's bodyPointJacobian.
Reach. A point at fraction along link is the tip of a shorter arm, so its Jacobian is
Chapter 4's jacobian with the columns for joints beyond zero and the last link shortened to
; at on the last link it is jacobian, and the check says so. Then
is the exact gradient of by
the chain rule — including the floating point, by the envelope theorem of Derivation 2 — and the
check compares it with central differences at 213 configurations: worst relative error
.
Why summing in the workspace is wrong. Two control points have two Jacobians; for any single . Control points approximate the body; they are the finite sample that stands in for an integral over its area, and figure 4.20's collision between them is the price. reduces the price and does not eliminate it.
The algorithm
Choset's Algorithm 4 is the whole method; brushfire and wave-front are Chapter 6's breadth-first search with a different seed set; navigation functions are a formula; and lifting is a sum.
- In
- a means to compute ∇U(q) at a point q; a step schedule α(i); a tolerance ε
- Out
- the iterates {q(0), q(1), …, q(i)}, and why the loop stopped
- ;
- while do
- end while
- report: at the goal; or a local minimum (print the Hessian's eigenvalue signs); or a step limit
Honesty. Line 4 of Algorithm 4 as printed reads , while the text says "take a small step in the direction opposite the gradient" and . It is a sign typo; the code uses the minus. The port adds a cap on the step length (a repulsive spike must not fling the bead across the table), a collision test (so an too large for the spike is reported, not ignored), and a stall detector for the chatter of Derivation 2's nonsmooth seam, where never falls below and the bead goes nowhere.
- In
- an occupancy grid (0 free, 1 obstacle); 4- or 8-point connectivity; for wave-front, the goal pixel
- Out
- a label per pixel (1 + grid distance to the nearest obstacle, or 2 + grid distance to the goal); back pointers; the pixels where two fronts met
- Brushfire: label every obstacle pixel ; enqueue every free pixel adjacent to an obstacle with label and the obstacle's component as its source
- Wave-front: enqueue the goal pixel with label
- while the queue is not empty: pop ; for each free neighbour of (Chapter 6's
GridGraph.neighbors): - if is unlabelled: label it , point its back pointer at , inherit 's source, enqueue it
- else if (brushfire) 's source differs from 's and : record a second back pointer — two fronts met
- Grid gradient at a pixel: a neighbour with the lowest label (ties to the first in scan order E, W, N, S, then diagonals)
- Wave-front descent from : step to a neighbour labelled one less until the label is
Reading distance off the grid: is the grid distance, and the gradient points from the back-pointer pixel to the robot's — four or eight possible directions, "just as the grid is an approximation of the workspace, so is the computed gradient an approximation of the actual distance gradient" (Choset). The micro-example's gradient at points to : both and carry label and east comes first.
- In
- a range scan ρ(q, θ_k) on n bearings (Chapter 3's rangeScan)
- Out
- one (d_i, ∇d_i) per visible obstacle: the local minima of ρ over θ, with ∇d_i = −(cos θ*, sin θ*)
- for each bearing with a finite reading: if and and at least one is strict, is a local minimum (a plateau counts once, at its first ray)
- emit and : the ray faces the closest point of its obstacle, so the gradient points back along it
What the Apartment taught the check. Each ray reads the exact distance along itself, so the smallest local minimum is never below . Broadside — the foot of the perpendicular on the nearest wall at least from both ends — the ray nearest the perpendicular hits within of it and overestimates by at most , which is for 360 rays. But a zero-thickness wall seen end-on subtends no angle, and a fan of 360 rays can miss its end entirely; the nearest visible thing is then farther away. In 80 seeded poses, 70 were broadside and met the bound (the Apartment's axis-aligned walls are hit exactly perpendicularly by the fan, so the overestimate there is ), and 10 were end-on with a worst overestimate of 8.3%. No sensor-only estimator can do better; Choset's remark that "an obstacle distance function may be incorrect if the obstacle is partially occluded by another" is the same limit from the other side.
- In
- a sphere world (centre, r₀, discs) or a star world; q_goal; κ; for stars, λ
- Out
- φ(q) and ∇φ(q) by the quotient rule; for stars, (φ ∘ h_λ)(q) and J_hᵀ ∇φ by the chain rule
- ; ;
- ;
- critical points: damped Newton on , , from a lattice of free seeds; polish, deduplicate, classify by the Hessian of ; the goal is the minimum it always is
- star world: with a sending to outright; and
- suitable λ: over a lattice of free points, at least a quarter-radius clear of obstacle , of — so every switch is outside that collar
The check verifies the quotient-rule gradient against central differences at 210 points (worst relative error ) and that on the free space.
- In
- Reach and its scene; control points (link, fraction t, role); the goal configuration; whether to add r_float
- Out
- u(q) = Σ_j J_j(q)ᵀ ∇U_j(r_j(q)), the configuration-space gradient; per-term forces and torques for drawing
- for each control point : its world position from Chapter 2's FK; with attractive toward or repulsive from every obstacle within
- the control-point Jacobian (Chapter 4's
jacobianfor a shortened arm); - if floating: the link point nearest any obstacle (one segment–scene distance per link); treat it as one more repulsive control point at its
- descend with Algorithm 4, in joint space
Implementation in Rust
The potential crate is introduced here; brushfire is the owned grid distance transform that
Chapters 8 and 10 import, and Potential is the interface Chapter 19's CHOMP reads a distance
field through. Configurations are SVector<f64, N> so one Attractive serves a point in the
plane and a control point on Reach.
use nalgebra::SVector;
/// A differentiable scalar field over an N-dimensional configuration space. The gradient is
/// read as a *velocity* for first-order robots (Choset Ch. 4's convention through §4.6) and
/// as a *force* once it is lifted through Jᵀ (§4.7).
pub trait Potential<const N: usize> {
fn value(&self, q: &SVector<f64, N>) -> f64;
fn gradient(&self, q: &SVector<f64, N>) -> SVector<f64, N>;
}
/// Choset eqs. (4.2)–(4.3): quadratic within `d_goal_star`, conic beyond. The two agree in value
/// and gradient at the seam (Derivation 1), so descent never sees it; `f64::INFINITY` gives the
/// pure quadratic of eq. (4.1).
pub struct Attractive<const N: usize> { pub goal: SVector<f64, N>, pub zeta: f64, pub d_goal_star: f64 }
impl<const N: usize> Potential<N> for Attractive<N> {
fn value(&self, q: &SVector<f64, N>) -> f64 {
let d = (q - self.goal).norm();
if d <= self.d_goal_star { 0.5 * self.zeta * d * d }
else { self.d_goal_star * self.zeta * d - 0.5 * self.zeta * self.d_goal_star * self.d_goal_star }
}
fn gradient(&self, q: &SVector<f64, N>) -> SVector<f64, N> {
let diff = q - self.goal;
let d = diff.norm();
if d <= self.d_goal_star { self.zeta * diff } // eq. (4.1): ζ(q − q_goal)
else { (self.d_goal_star * self.zeta / d) * diff } // eq. (4.3): magnitude ζ d*
}
}
/// One repulsive term per obstacle (Choset §4.1, after eq. 4.7), so the gradient does not jump
/// when the nearest obstacle changes. `dist` is the Chapter 4 query: d_i(q) and its closest point.
pub struct Repulsive<'w, const N: usize> { pub dist: &'w dyn cspace::DistanceQuery<N>, pub eta: f64, pub q_star: f64 }
impl<const N: usize> Potential<N> for Repulsive<'_, N> {
fn value(&self, q: &SVector<f64, N>) -> f64 {
self.dist.distances(q).iter().filter(|o| o.d <= self.q_star)
.map(|o| 0.5 * self.eta * (1.0 / o.d - 1.0 / self.q_star).powi(2)) // eq. (4.4)
.sum()
}
fn gradient(&self, q: &SVector<f64, N>) -> SVector<f64, N> {
let mut g = SVector::zeros();
for o in self.dist.distances(q).iter().filter(|o| o.d <= self.q_star) {
// eq. (4.5): η (1/Q* − 1/d) (1/d²) ∇d, with ∇d = (q − c)/d (Derivation 2).
g += self.eta * (1.0 / self.q_star - 1.0 / o.d) / (o.d * o.d) * o.grad;
}
g
}
}
/// U = Σ terms — the whole method and its local minima.
pub struct Additive<const N: usize>(pub Vec<Box<dyn Potential<N>>>);Gradient descent returns the iterates and the reason it stopped, with the Hessian's eigenvalues when it stopped at a critical point that is not the goal — the stuck badge of the Potential Well.
use nalgebra::{SMatrix, SVector};
pub enum DescentOutcome { Goal, LocalMinimum { hessian_eigs: Vec<f64> }, StepLimit, Collision, Stalled }
/// Choset Algorithm 4 with the sign the text intends: q(i+1) = q(i) − α(i) ∇U(q(i)).
/// `alpha` is a schedule ("often made on an ad hoc or empirical basis", Choset); `eps` the
/// forgiving termination ‖∇U‖ < ε; `max_step` caps a repulsive spike's fling.
pub fn gradient_descent<const N: usize>(
u: &dyn Potential<N>, q_start: SVector<f64, N>, alpha: impl Fn(usize) -> f64,
eps: f64, max_steps: usize, goal: Option<(SVector<f64, N>, f64)>, max_step: f64,
) -> (Vec<SVector<f64, N>>, DescentOutcome) {
let mut path = vec![q_start];
for i in 0..max_steps {
let q = *path.last().unwrap();
if let Some((g, tol)) = goal { if (q - g).norm() <= tol { return (path, DescentOutcome::Goal); } }
let grad = u.gradient(&q);
if grad.norm() < eps {
// Not at the goal, gradient gone: classify the critical point by its Hessian.
let h = hessian(u, &q, 1e-4);
let eigs = h.symmetric_eigenvalues().iter().copied().collect();
return (path, DescentOutcome::LocalMinimum { hessian_eigs: eigs });
}
let mut step = -alpha(i) * grad;
if step.norm() > max_step { step *= max_step / step.norm(); }
path.push(q + step);
}
(path, DescentOutcome::StepLimit)
}
/// ∇²U by central differences of the gradient, symmetrized — Choset reads the type of a
/// critical point off its signature, and Chapter 8 reads the Morse index off the same matrix.
pub fn hessian<const N: usize>(u: &dyn Potential<N>, q: &SVector<f64, N>, h: f64) -> SMatrix<f64, N, N> {
let mut m = SMatrix::<f64, N, N>::zeros();
for j in 0..N {
let (mut qp, mut qm) = (*q, *q);
qp[j] += h; qm[j] -= h;
m.set_column(j, &((u.gradient(&qp) - u.gradient(&qm)) / (2.0 * h)));
}
0.5 * (m + m.transpose())
}Brushfire is the owned artifact. It is Chapter 6's breadth-first search with every obstacle pixel as a source, and it keeps the second back pointer that Chapter 8 reads as the grid GVD.
use std::collections::VecDeque;
#[derive(Clone, Copy)] pub enum Conn { Four, Eight }
/// Choset's labels: obstacle 1, free pixels 2, 3, …; `back2` is set where a second front from a
/// *different* obstacle arrived — §5.2.5's "two different back pointers", the grid GVD.
pub struct DistGrid { pub label: Vec<u32>, pub back: Vec<Option<usize>>, pub back2: Vec<Option<usize>>, pub source: Vec<i32>, pub w: usize, pub h: usize }
pub fn brushfire(occ: &cspace::Raster, conn: Conn) -> DistGrid {
let (w, h) = (occ.width(), occ.height());
let comp = obstacle_components(occ); // a front's source is an obstacle, not a pixel
let mut g = DistGrid { label: vec![0; w * h], back: vec![None; w * h], back2: vec![None; w * h], source: vec![-1; w * h], w, h };
let mut queue = VecDeque::new();
for k in 0..w * h {
if occ.is_occupied(k) { g.label[k] = 1; continue; }
// Step 1 of the text: free pixels neighbouring an obstacle are labelled 2.
if let Some(o) = occ.lattice_neighbors(k, conn).find(|&n| occ.is_occupied(n)) {
g.label[k] = 2; g.back[k] = Some(o); g.source[k] = comp[o]; queue.push_back(k);
}
}
while let Some(c) = queue.pop_front() {
let (lc, sc) = (g.label[c], g.source[c]);
for x in occ.free_neighbors(c, conn) { // Star(c) on Chapter 6's grid graph
if g.label[x] == 0 {
g.label[x] = lc + 1; g.back[x] = Some(c); g.source[x] = sc; queue.push_back(x);
} else if g.source[x] != sc && g.source[x] >= 0 && g.label[x] >= lc {
// Two fronts meet. Even gap: x was labelled lc + 1 by the other front and now gets
// its second back pointer. Odd gap: both pixels carry lc and the collision falls on
// their shared edge — both are flagged so the ridge has no holes.
if g.label[x] == lc + 1 && g.back2[x].is_none() { g.back2[x] = Some(c); }
g.mark_ridge(x); if g.label[x] == lc { g.mark_ridge(c); }
}
}
}
g
}
impl DistGrid {
/// Pixels two fronts from different obstacles reached — Chapter 8's grid GVD.
pub fn ridge_cells(&self) -> Vec<usize> { (0..self.w * self.h).filter(|&k| self.is_ridge(k)).collect() }
/// "Point to a lowest neighbor" (Choset §4.3.2); ties to the first in scan order.
pub fn grid_gradient(&self, cell: usize, conn: Conn) -> Option<usize> { /* lowest-label neighbour */ todo_lowest(self, cell, conn) }
}
/// Wave-front (§4.5): the same propagation from the goal pixel alone, seeded with 2.
pub fn wavefront(occ: &cspace::Raster, goal: usize, conn: Conn) -> DistGrid { propagate(occ, &[(goal, 2, 0)], conn) }The navigation function is eq. (4.13) with its analytic gradient, and the lifted potential for
Reach is a sum of Jᵀ f terms.
/// Eq. (4.13) on a sphere world; `Potential<2>` via the quotient rule.
pub struct NavigationFunction<'w> { pub world: &'w SphereWorld, pub goal: Point2<f64>, pub kappa: u32 }
impl Potential<2> for NavigationFunction<'_> {
fn value(&self, q: &Vector2<f64>) -> f64 {
let a = (q - self.goal.coords).norm_squared(); // γ_1 = d²
let b = a.powi(self.kappa as i32) + self.world.beta(q); // d^{2κ} + β
if b <= 0.0 { 1.0 } else { a * b.powf(-1.0 / self.kappa as f64) }
}
fn gradient(&self, q: &Vector2<f64>) -> Vector2<f64> {
let k = self.kappa as f64;
let diff = q - self.goal.coords;
let a = diff.norm_squared();
let (beta, grad_beta) = self.world.beta_and_gradient(q);
let b = a.powf(k) + beta;
let bm = b.powf(-1.0 / k);
let da = 2.0 * diff;
let db = k * a.powf(k - 1.0) * da + grad_beta;
da * bm - (a / k) * (bm / b) * db // quotient rule
}
}
/// §4.7.2–4.7.3: control points on Reach, each with a workspace potential; `gradient` is
/// u(q) = Σ_j J_j(q)ᵀ ∇U_j(r_j(q)), summed in C-space (eq. 4.26) — never in the workspace.
pub struct LiftedPotential<'r> { pub arm: &'r robots::Reach, pub scene: &'r collide::Scene, pub points: Vec<ControlPoint>, pub goal: Tn<2>, pub floating: bool }
impl Potential<2> for LiftedPotential<'_> {
fn value(&self, q: &Vector2<f64>) -> f64 { self.terms(q).iter().map(|t| t.value).sum() }
fn gradient(&self, q: &Vector2<f64>) -> Vector2<f64> {
let mut u = Vector2::zeros();
for t in self.terms(q) {
let j = cspace::control_point_jacobian(self.arm, q, t.link, t.t); // Chapter 4's jacobian, shortened
u += j.transpose() * t.force; // u = Jᵀ f
}
u
}
}The worked example, and its printed output
fn main() {
// Part B: one evaluation and one step, as in the text.
let goal = Vector2::new(4.0, 3.0);
let att = Attractive { goal, zeta: 1.0, d_goal_star: 10.0 };
let disc = cspace::DiscObstacle { center: Point2::new(0.0, -2.0), radius: 1.0 };
let rep = Repulsive { dist: &disc, eta: 1.0, q_star: 2.0 };
let q = Vector2::zeros();
println!("U_att = {} grad = {:?}", att.value(&q), att.gradient(&q));
println!("U_rep = {} grad = {:?}", rep.value(&q), rep.gradient(&q));
let u = Additive(vec![Box::new(att), Box::new(rep)]);
println!("U = {} grad = {:?}", u.value(&q), u.gradient(&q));
println!("q1 = {:?}", q - 0.1 * u.gradient(&q));
// Part A: the 7 × 3 micro-grid.
let occ = cspace::Raster::from_ascii(&[".......", "#.....#", "......."]); // first row is the top
let d = brushfire(&occ, Conn::Four);
for row in d.rows_top_first() { println!("{}", row.iter().map(u32::to_string).collect::<Vec<_>>().join(" ")); }
println!("ridge: {}", d.ridge_cells().iter().map(|&k| d.col_row(k)).map(|(c, r)| format!("({c},{r})")).collect::<Vec<_>>().join(" "));
// The κ sweep on the five-disc world.
for kappa in [2, 3, 4, 6, 7, 8, 10] {
let c = critical_points(&FIVE_DISC_WORLD, FIVE_DISC_GOAL, kappa);
println!("kappa={kappa}: minima={} saddles={}", c.minima, c.saddles);
}
}U_att = 12.5 grad = [-4.0, -3.0]
U_rep = 0.125 grad = [0.0, -0.5]
U = 12.625 grad = [-4.0, -3.5]
q1 = [0.4, 0.35]
2 3 4 5 4 3 2
1 2 3 4 3 2 1
2 3 4 5 4 3 2
ridge: (4,1) (4,2) (4,3)
kappa=2: minima=3 saddles=7
kappa=3: minima=2 saddles=6
kappa=4: minima=2 saddles=6
kappa=6: minima=2 saddles=6
kappa=7: minima=1 saddles=5
kappa=8: minima=1 saddles=5
kappa=10: minima=1 saddles=5Read Part B against the text. with gradient . The disc's closest point is , so and ; with
and , and . The total is , , and one step with lands at — away from the obstacle and
toward the goal. descent_step_matches_text pins these to ; brushfire_7x3_labels
asserts all 21 labels and the ridge set; brushfire_equals_bfs_depth_plus_one checks on seeded
grids that brushfire equals search::bfs multi-source depth and that the wave-front
descent has the Dijkstra cost under unit weights; kappa_sweep pins exactly one minimum from
; reach_field lifts a unit force at Reach's tip and asserts . The TypeScript port in web/lib/potential/ runs the same thirteen checks, and every
widget on this page is that port.
Two of the port's checks failed on first writing, and both failures were mathematics, not typos. The sensor-based distance check assumed the ray fan overestimates by at most the ray quantisation, and the Apartment's free-standing wall stubs, seen end-on, broke it by 8%: a zero-thickness wall subtends no angle, and the bound only holds broadside. The star-world check assumed maps every star boundary onto its circle for , which is true in exact arithmetic and false by six units in floating point once a boundary rounds to , because is and the switches cannot see a of order one. Both are now stated as the theorems actually say them.
Putting it together: Rusty in a doorway, Reach at the bench
The integration lab runs Rusty in the Apartment under a sensor-based potential: the repulsive
term is fed by the local minima of a 72-ray scan of radius 3 m (Algorithm SENSOR-BASED DISTANCE),
the attractive term by the goal in the corridor, and the descent is Algorithm 4 at
. From the middle of room A Rusty heads for the door, and in the alcove beside the
jamb — where the goal pulls through the wall and the two wall faces push toward the centre of the
alcove — it stops, with the stuck badge reading a positive-definite Hessian. The same raster the
Chapter 6 planner used, run through wavefront from the goal cell at 4-point connectivity, gives
a field with one minimum, and the grid descent walks Rusty out of the alcove and through the
door — hugging the jamb at one cell's clearance, which is Choset's "dangerously close to
obstacles" and the reason Chapter 8's Voronoi roadmap exists. The Potential Well's wave-front
toggle is this lab on a table; the Apartment version is Exercise 4's.
Reach's half of the lab is the Arm in a Field widget run to the end: the same start descended twice, with the first collision logged. Without link 1 meets the post after a dozen steps between its two fixed repulsive points; with it the arm reaches the goal pose in about five hundred. Move the goal into the block's shadow and you will find starts where it stalls in a local minimum of the lifted potential instead — the additive disease, now on the torus — and starts where even fails, because a floating point repels the nearest link point and nothing else.
Three pointers close the chapter. Brushfire's double-back-pointer pixels are the grid generalized Voronoi diagram, and Chapter 8 proves the exact version is a roadmap, with the Morse vocabulary this chapter's "nondegenerate critical point" was borrowing from. Chapter 19's CHOMP descends a potential whose obstacle term is a distance field exactly like the brushfire one, over a whole trajectory at once, and inherits every local minimum on this page. And Part III's planners are the field's eventual answer to incompleteness: when the valley you roll into is not the goal's, sample.
Exercises
- Foundation exerciseDifficulty 2 of 3Convexity of d_i and the seams of U_rep
Prove that is a convex function of when is convex (Choset Ch. 5, Problem 19, brought forward). Conclude that with the per-obstacle sum , the only places can fail to be continuous are the spheres — and show that in fact it is continuous there too, so the seams that remain are those of Derivation 2's Step 4: where a nonconvex obstacle has two closest points.
- Foundation exerciseDifficulty 2 of 3Wave-front paths are L₁-shortest
Show that the path produced by wave-front descent under 4-point connectivity is shortest in the (Manhattan) metric among all grid paths from the start to the goal (Choset Ch. 4, Problem 2). Then give a example where the 8-point descent path — shortest in link length — is not Euclidean-shortest among 8-connected paths, and say by how much.
On an empty 3 × 3 grid from the bottom-left to the top-right cell under 8-point connectivity, the wave-front path has two links. What is the Euclidean length of a two-link path that goes diagonal, diagonal, divided by the length of one that goes east, east, then north, north? (Give the ratio as a decimal.)
- Conceptual exerciseDifficulty 1 of 3Predict the κ threshold, then move an obstaclePredict first
In the Navigation Function Sculptor, read the critical-point count at κ = 6: two minima, six saddles. Without moving the slider: what is the smallest κ at which the count will read one minimum?
- Conceptual exerciseDifficulty 2 of 3A world of only convex discs, and the price of the escape
In the Potential Well choose two convex discs, drag the goal and the discs until the bead gets stuck, and record the Hessian eigenvalues the badge prints. Then enable wave-front mode and estimate how close the escape path passes to the discs — Choset's "dangerously close to obstacles" in §4.5. Explain why the wave-front path must graze: among all grid paths of the same link length to the goal it has no reason to prefer clearance, and the one found first by "step to any neighbour labelled one less" is whichever the scan order produced.
- Practical exerciseDifficulty 2 of 3Brushfire as Dijkstra, within one cell of the EDT
Implement an 8-point
brushfirewith diagonal cost (Chapter 6's grid metric) as a Dijkstra rather than a BFS: labels become real-valued distances and the queue becomes a heap. Property-test it on 200 seeded grids against the exact Euclidean distance transform ported from the sister book (mapping/edt): assert that every cell's label is within one cell of the EDT, and record the worst gap. Then repeat with diagonal cost and report whether the worst gap improves — and why it cannot reach zero (the octile metric is not the Euclidean one). - Practical exerciseDifficulty 3 of 3Wave-front on the torus
Implement Choset Ch. 4 Problem 5: a wave-front planner for Reach on the raster of Chapter 4, with the goal seeded at the goal configuration and adjacency wrapping across the seam at in both axes. Animate the arm on the Workbench along the descent path, and assert in a test that the path's link length equals the Chapter 6 breadth-first distance on the same torus graph — then compare the number of cells the wave-front labels with the number A* expands for the same query, and say which planner you would run twice.
References
- 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 4 is this chapter's source: the additive potential and its honesty items, Algorithm 4, sensor-based and brushfire distance, the local-minimum figures 4.10–4.11, the wave-front planner, Definition 4.6.1 and the sphere- and star-world constructions, and the virtual-work lifting of §4.7 with Example 4.7.1 and figure 4.20.
- Khatib, O. (1986) Real-Time Obstacle Avoidance for Manipulators and Mobile Robots. International Journal of Robotics Research 5(1), 90–98.doi:10.1177/027836498600500106 (opens in a new tab)
The artificial potential field for manipulators: attractive goal, repulsive obstacles, and forces at points on the links lifted through the Jacobian — §4.7 in its original form, written for control rather than planning.
- Rimon, E. and Koditschek, D. E. (1992) Exact Robot Navigation Using Artificial Potential Functions. IEEE Transactions on Robotics and Automation 8(5), 501–518.doi:10.1109/70.163777 (opens in a new tab)
Navigation functions: Definition 4.6.1, the sphere-world formula (4.13) with its existence-in-κ theorem, and the star-to-sphere diffeomorphism h_λ with its switches — Derivations 5 and 6.
- Barraquand, J. and Latombe, J.-C. (1991) Robot Motion Planning: A Distributed Representation Approach. International Journal of Robotics Research 10(6), 628–649.doi:10.1177/027836499101000604 (opens in a new tab)
The Randomized Path Planner: gradient descent on a grid potential with random walks to escape local minima — Choset's reference [37], the honest 1991 fix and the ancestor of Part III's sampling.
- Latombe, J.-C. (1991) Robot Motion Planning. Kluwer Academic Publishers.link to Robot Motion Planning (opens in a new tab)
Chapter 7 is the classical textbook treatment of potential-field methods, including the wave-front (numerical navigation function) planner and the local-minimum analysis this chapter condenses.
- Ratliff, N., Zucker, M., Bagnell, J. A., and Srinivasa, S. (2009) CHOMP: Gradient Optimization Techniques for Efficient Motion Planning. IEEE International Conference on Robotics and Automation (ICRA), 489–494.doi:10.1109/ROBOT.2009.5152817 (opens in a new tab)
Forward pointer only: the obstacle term of CHOMP is a distance field like brushfire's, descended over a whole trajectory, and it inherits this chapter's local minima. Chapter 19 derives it.
- Thrun, S., Burgard, W., and Fox, D. (2005) Probabilistic Robotics. MIT Press.link to Probabilistic Robotics (opens in a new tab)
The sister volume's map representations chapter ports the exact Euclidean distance transform this chapter compares brushfire against; the two are different algorithms computing different metrics.
