Configuration Space II: Topology, Manifolds, and Rigid Bodies
What "the edges are identified" means, why every planner needs a distance, an interpolation and a sampler that respect it, and the Manifold trait that makes the compiler enforce the difference between a torus and the plane.
It is not surprising that people once believed the world was flat — they were only looking at their neighborhoods!
In this chapter
Chapter 4 drew Reach's configuration space as a square with amber blobs and kept saying "the edges are identified." This chapter says what that means, why a planner must care, and how to make the compiler enforce it.
The reason not to skip Choset's most abstract pages is concrete. Every planner in the rest of this
book needs three things from a configuration space: a distance between two configurations, an
interpolation between them, and a way to sample them. All three are wrong if you pretend the
torus is the plane , or that is — not wrong by a
little, wrong by the width of the whole space, as the first worked example shows with two numbers.
The cure is one sentence, and it is a Rust trait: a configuration space is a manifold — local
coordinates, usually no global ones — so a planner must be written against the operations the
manifold provides, dist, interpolate, sample, and never against the coordinates.
Along the way the chapter collects what Choset's §3.4–3.6 and Appendices B, C and E say about mappings, charts, connectedness, compactness, metric spaces, matrix groups and rotations, keeps the examples that carry the pedagogy — the circle, the ellipse and the racetrack; the four charts of ; the three frames , , worked by hand — and closes by reading Reach's Jacobian as the differential , a linear map that does not care which chart you used.
The problem: a path in two pieces
Here is the Chapter 4 picture of Reach's configuration space again — the square with the C-obstacles of three disc obstacles on the Workbench — and a purple path between two configurations. On the square the path is two disconnected pieces. Watch the square fold.
When the square has become a doughnut the two pieces are one curve, and a short one. Nothing about the configurations changed; the picture of the space changed, from a chart to the space. The readout underneath says the same thing in numbers. For the default endpoints the chart's Euclidean distance is about six, the torus distance well under one, and the difference is entirely the seam: the path crosses the dashed edge where becomes .
Three things in the widget are the whole chapter.
The seam is an artifact of the chart, not a feature of the space. The square is one way of assigning two numbers to a configuration of a two-joint arm. It is a good way almost everywhere and a bad way along four edges, and a planner that works with the numbers inherits the bad edges. Choset's §3.4.2 names what the square is — a chart — and what the torus needs instead — an atlas.
Distance belongs to the space. dist in the readout is , the metric this chapter
defines on the torus. It is shorter than the chart distance exactly when the short way crosses a
seam. Chapter 11's nearest-neighbor queries, Chapter 12's tree extensions and Chapter 13's rewiring
radii all call this function, and all of them would quietly plan around a wall that does not exist
if they called the chart's distance instead.
Joint limits change the topology. Switch them on. The square stops wrapping, the surface on the right is a disc, and the component count in the readout changes: the band of C-obstacle that cut the square into two components, which the torus had glued back into one, now separates them for good. Choset's §3.4.3 says this in one line — compact and noncompact spaces cannot be diffeomorphic — and the lab at the end of the chapter measures it.
Building intuition
An angle is not a number
Start with the simplest manifold that is not a Euclidean space: the circle , the configuration space of one revolute joint. Choset's §3.4.2 covers it with four charts — the open half-circles , , , , each coordinatized by whichever Cartesian coordinate varies monotonically on it. The widget hands a point from chart to chart.
Every point of the circle lies in at least one chart — the four domains cover. Wherever a point
lies in two, both coordinates exist and are related by a smooth function, the transition map; on
it is , drawn at lower right.
That smoothness is what lets a planner, or a derivative, pass from one chart to the next without a
jolt. The two-chart angle atlas is the one S1 uses in code: the angle measured from on the
circle minus , and the angle measured from on the circle minus , whose
transition map is a translation by .
Then try the single-chart attempt, . It assigns a number to every point, and it is not a chart: drag through and the coordinate tears from to . A chart's domain has to be open, so that every point has a neighborhood on which the map is continuous, and no neighborhood of maps continuously to an interval. Chapter 4's "single angle in " was a chart with a seam, not an atlas, and the seam is where its arithmetic lies.
A transform is not a position plus an angle
The second intuition is about rigid bodies. Choset's §3.5.1 works one example completely — three frames , , on a unit grid and a point — and it is the one place in his book where the group can be checked by hand. The widget keeps his numbers as its reset state.
Two rules generate everything in the readout. Subscript cancellation: , and — the inner subscripts annihilate, and the result is the outer pair. And non-commutativity: the same transform multiplied on the right of moves frame in its own coordinates (a body-frame transformation, ), multiplied on the left it moves in 's coordinates (a world-frame transformation, ), and the two frames land in different places. Autoplay sweeps 's heading so you can watch and diverge; reset returns to Choset's and the two placements and . A pose is an element of a group. The order of multiplication is the whole content.
The third intuition — that gimbal lock is a missing chart — waits for the rotation section, where it has its own widget.
The mathematics
| Symbol | Meaning | Note |
|---|---|---|
| a mapping; its image; the preimage of a set | ||
| a chart — an open U ⊂ Q with a diffeomorphism φ onto an open set of ℝᵏ — and a transition map between two charts | ||
| open ball; neighborhood; continuous, k times continuously differentiable, smooth | ||
| a homogeneous transform; frame B expressed in A; the point w in A's coordinates | ||
| the chapter's metrics; w is the rotation weight in metres per radian | w has no canonical value | |
| Z-Y-Z Euler angles (Choset §3.6) | ||
| tangent space (informal here, formal in Chapter 20); the differential of the forward map |
Mappings: onto, one-to-one, and the two kinds of "the same"
Let be a mapping. It is surjective (onto) if , injective (one-to-one) if forces , and bijective if both. A bijection has an inverse everywhere on , and this is what lets us move back and forth between a configuration space, whose geometry may be complicated, and a Euclidean space, whose geometry is not. Choset's example: is bijective; is only surjective.
The converse fails on Choset's three curves (his figure 3.12): the unit circle , the ellipse , and the racetrack — two half-circles of radius one joined by straight segments, with
All three are homeomorphic: radial projection carries the ellipse onto the circle and is continuous both ways. The circle and ellipse are diffeomorphic — that same is smooth both ways. But no diffeomorphism reaches the racetrack: its curvature jumps at , where straight meets round, and a diffeomorphism would have to carry the circle's constant curvature onto it smoothly. Topology sees one closed curve; differential structure sees two kinds.
Neighborhoods, and what "locally" means
A neighborhood of a point in a metric space is any set such that every point of has an open ball inside ; an open ball is itself a neighborhood. is locally diffeomorphic to if every point of has a neighborhood diffeomorphic to an open subset of . The sphere is locally diffeomorphic to the plane — which is Choset's remark about the flat earth in this chapter's epigraph — and not globally: there is no bijection from the sphere onto the plane that is continuous both ways.
The two robots of Chapter 4 sit on either side of this distinction. A disc translating in the plane has configuration space , globally diffeomorphic to its workspace by the identity. The two-joint arm has , which is locally diffeomorphic to — every configuration has a neighborhood that looks like a patch of the plane — and not globally, because is compact and is not (§3.4.3 below). Give the arm strict joint limits and its configuration space becomes an open rectangle, which is globally diffeomorphic to : stretch each open interval over the line with . Joint limits are not a detail. They change the topology.
Manifolds, charts, atlases
The configuration spaces this book plans in are all differentiable manifolds, and the parameterizations of Chapter 4 — "the configuration of the arm is " — were charts without the name. When no single chart covers the space, as for the torus, there are three options (Choset §3.5): use one chart anyway and suffer its seam; use an atlas; or embed the space in a higher-dimensional Euclidean space with constraints — the unit circle in , the doughnut in , rotation matrices in . The code in this chapter uses all three, each where it is best.
DerivationS¹ needs at least two charts, and Choset's four suffice
Statement. No single chart covers . The charts ; ; ; , with parameterizations , , , , form an atlas.
Step 1 — one chart is impossible. A single chart would be a homeomorphism from onto an open subset of , which is a union of open intervals; being the image of a connected set it is one interval. Removing a point from an open interval disconnects it; removing a point from does not. A homeomorphism preserves connectedness of the complement of a point, so none exists.
Step 2 — cover. Every point of has or , so lies in some . Each is open, each is the restriction of a coordinate projection, a diffeomorphism onto with the inverse shown.
Step 3 — the transition maps. , so there are four overlaps to check, each a quarter-circle. On , for and for . The other three overlaps give on or .
Step 4 — smoothness. is smooth on any open subinterval of — the only non-smooth points, , are excluded because the overlaps are open quarter-circles. Hence the charts are pairwise -related and the four form an atlas.
Two charts suffice (Choset problem 3.9): with the angle from
in , and with the angle from . Their
transition map is on each component of the overlap: a translation, with
derivative . This is the atlas S1 uses, and the derivative-one fact is what makes the Jacobian
chart-independent in the last derivation of this chapter.
Connectedness and compactness
A manifold is connected if any two of its points are joined by a path. , , are; so are and . Obstacles break into connected components, its maximal connected subsets, and the first fact every planner must respect is that there is no solution when and lie in different components. The Torus Unwrapper's component count is this definition evaluated on a raster.
A space is compact if it is a closed and bounded subset of some . is
compact and is not; , and are compact; and
are not. Products of compact spaces are compact, and a non-compact product with
compact has as its compact factor — has .
Two facts matter for planning: compact and non-compact spaces are never diffeomorphic, which is the
invariant that separates from ; and a sampler can be uniform on a compact space
but needs a box on a non-compact one, which is why Se2::sample takes bounds and S1::sample does
not.
DerivationJoint limits change the topology
Statement. The two-joint arm's configuration space is not diffeomorphic to ; with strict joint limits it becomes an open rectangle, which is.
Step 1. Continuous images of compact sets are compact. is compact (a closed, bounded subset of via the doughnut embedding) and is unbounded, so no continuous bijection carries one onto the other — let alone a diffeomorphism.
Step 2. With limits , each joint ranges over an open interval and the space is the open rectangle .
Step 3. is a diffeomorphism from onto , and a product of diffeomorphisms is a diffeomorphism, so the rectangle is diffeomorphic to .
Closed limits give a compact space again — but a manifold with boundary, which Choset's §3.4.4 warns is not a manifold in the sense above. And some one-degree-of-freedom parallel mechanisms have a configuration space shaped like a figure eight, which is no manifold at all: at the crossing there are two distinct motion directions. "If you cannot show it to be a manifold, it may not be."
Metric spaces, and the contract for dist
Non-negativity is not an axiom; it follows: . A function between metric spaces is continuous (Definition C.4.1) when preimages of open sets are open, equivalently when for every there is a with . A path is a map ; a trajectory must be for so that velocity exists.
Here are the four metrics this chapter ships, with the chart's wrong answer written beside each right one. The seam example from the hook: angles and radians.
The geodesic midpoint interpolate(a, b, 0.5) goes the short way, downward from by half of
: it lands at after wrapping, not at the chart midpoint
. On the torus, with and :
On the metric needs an exchange rate between metres and radians, and with the distance from to is
On the distance between two rotations is the angle of the rotation that carries one to the other, , which is also the axis–angle of (App. E.3) and, for unit quaternions, .
DerivationThe chapter's metrics are metrics, and interpolate follows geodesics
Statement. Each of the four functions above satisfies Definition C.2.1, and interpolate(t)
moves along the shortest arc at constant speed: .
Step 1 — is a quotient. Identify with . For classes define , the shortest representative of the difference — which is exactly for representatives in a window of width . Definiteness and symmetry are inherited from . For the triangle inequality pick attaining the two minima on the right; then is some representative of , so .
Step 2 — products. For metrics , the function is a metric: definiteness and symmetry are immediate, and the triangle inequality is Minkowski's inequality for the Euclidean norm applied to the vectors and , using the triangle inequalities of and componentwise. This gives , , and for any (a positive scalar multiple of a metric is a metric).
Step 3 — the weight has no canonical value. Scaling changes which pose is "nearer": with a half-turn in place costs m-equivalents, with it costs . Both are metrics; neither is right. Chapter 11 measures what a PRM does as varies, and this book never hides in a default.
Step 4 — . is the angle of the relative rotation . Definiteness: iff . Symmetry: is the inverse rotation, same angle. Triangle inequality: composing a rotation by with a rotation by yields a rotation by at most (the angle is the geodesic distance on the unit 3-sphere of quaternions, folded by the double cover, and great-circle distance on a sphere satisfies the triangle inequality). The quaternion form is the same number because up to sign, and the absolute value is what identifies with .
Step 5 — geodesics. On , moves along the shorter arc with . On a product, the componentwise geodesic is the geodesic of the product metric because the factors do not interact. On , spherical linear interpolation between and the sign-flipped nearer to traverses the shorter great-circle arc at constant speed.
The antipodal ambiguity. At exactly, or on , the
geodesic is not unique. S1::interpolate takes the representative that wrap returns —
counter-clockwise — and So3::interpolate the sign-flipped endpoint; both deterministic, both
documented, neither hidden.
Embeddings: and
The alternative to an atlas is an embedding with constraints. A rotation in is a matrix whose columns are the body axes expressed in a stationary frame: nine numbers, six constraints (three unit lengths, three orthogonalities), three degrees of freedom.
A matrix in is used three ways (§3.5.1): to represent a configuration — then it is a frame; to change the reference frame of a configuration or a point; and to displace one — then it is a transform. The three frames of the Frame Composer are Choset's figure 3.17 restricted to the plane, with having , and having , .
DerivationSE(n) composes by subscript cancellation; body versus world transformations
Statement. changes the frame in which is expressed; changes a point's frame; displaces the point by the motion that carries to ; and for a transform , acts in 's frame (body) while acts in 's (world), with different results.
Step 1 — block multiply.
since and determinants multiply, so is closed under the product. The inverse is , as one checks by multiplying. With the identity, is a group.
Step 2 — the constraint count. has entries and independent constraints, so ; adding gives , the six degrees of freedom of a rigid body in space. In the plane, and .
Step 3 — the micro-example. With and : and , so , Choset's . The point : and , both as in his text. Displacing instead: — the subscripts do not cancel, and the result is a new point in 's frame, rotated about 's origin by and translated by (figure 3.18).
Step 4 — the two orderings. With : rotates about its own origin by then translates by in the original frame; rotates about 's origin and translates in . They differ because matrix multiplication does not commute. body-frame transformations stack on the right, ; world-frame ones on the left, .
Choset problem 3.23. and with the product operation
are homeomorphic as spaces — the same three
coordinates — but not isomorphic as groups: the product group is commutative and is not.
Pose2::compose is 's product, with the coupling; the commutative one is what you
get by adding poses componentwise, which is wrong for a robot.
is chart-independent; the Jacobian is its matrix
Chapter 4 computed Reach's Jacobian for the forward map . Appendix C.5 calls the same matrix the differential and adds the one footnote that matters here: the differential is really a linear map from the tangent space of the domain to the tangent space of the range, , and the matrix is its expression in charts. Along a curve in , — velocities are columns, pushed forward by . Forces live in the cotangent space and are rows, pulled back by (Choset's remark on p. 486), which is why Chapter 7 will write .
DerivationDφ is chart-independent; the Jacobian is its matrix
Statement. Under a change of chart , the matrix of transforms by the chain rule . For the torus's angle atlas the transition maps are translations by , , and Chapter 4's is the same matrix in every chart.
Step 1 — the differential along curves. Let be smooth and set . Then : is the linear map that takes the velocity of the configuration to the velocity of the image.
Step 2 — the chain rule (C.5). If the curve is given in -coordinates and maps -coordinates to -coordinates, . Choset's Example C.5.1 is the worked check: in polar coordinates, polar-to-Cartesian, , and the chain rule gives , which direct differentiation of confirms.
Step 3 — the torus. The transition maps of the angle atlas are and the like, with . So the matrix of is the same whichever chart the configuration is read in; shifting by a full turn changes nothing — not the entries, not the singularities. At Choset's with and :
and dphi_chart_independent asserts the matrix is unchanged, to machine precision, under
.
Rotations in three dimensions, briefly
Everything above generalizes from to except the coordinates. Nine matrix entries with six constraints leave three degrees of freedom, so is a three-dimensional manifold and can be locally parameterized by three numbers — Euler angles — but, just as for the circle, not globally.
Choset's choice is Z-Y-Z Euler angles (§3.6): rotate about by , then about the new by , then about the new by , so that
DerivationEuler angles lose a chart; quaternions provide an atlas
Statement. The Z-Y-Z map is a chart only on . At only is determined (Choset eq. E.2, E.9). Unit quaternions cover with four charts .
Step 1 — the collapse. Put in (3.8). Then , and
a rotation about by : infinitely many pairs give the same , and only is defined (E.9). The same happens at , where and only survives (E.10–E.11). Off those two rotations, , , (E.3, E.5, E.6) invert the map smoothly, so it is a chart there. Gimbal lock is the name for having driven off this chart; nothing mechanical is involved.
Step 2 — unit quaternions. For a rotation by about a unit axis , (E.26) has (E.27), and the matrix of (E.28) is a rotation; and give the same — the double cover — and quaternion multiplication is rotation composition.
Step 3 — four charts cover. Some component of a unit quaternion has the largest magnitude, at least . On (interiors, to make them open) the map is a smooth bijection onto an open subset of , and the cover. Where an Euler chart ends, the quaternion simply moves to a different — which is what the Rotation Zoo's smooth quaternion curve shows while and swing.
Kept for Appendix B: the full inverse (E.3–E.11), roll–pitch–yaw (E.12) and its lock at pitch
, the axis–angle matrix (E.18) and the chart on
(E.20–E.21), the quaternion product and conjugate (E.31–E.35).
The exponential and logarithm maps that turn axis–angle into a Lie-group coordinate, and the
operators estimators need, are the sister book's
Chapter 3,
whose So3 the Rust exp/log of Exercise 6 reuses.
One more honesty item about , because Chapter 11's samplers depend on it. Uniform on
means uniform with respect to the Haar measure, the one invariant under rotation. A
normalized Gaussian 4-vector is uniform on the quaternion 3-sphere and therefore Haar-uniform on
. Uniform Euler angles are not: the rotation angle of a Haar-uniform rotation has
density on , mean , and only
of rotations lie within half a radian of the identity; drawing
uniformly puts about three times that mass near the identity. So3::sample
is the Gaussian version, and a check pins both numbers.
The algorithm
This chapter's algorithms are operations, not planners: the three methods a manifold must provide, and the first generic planner-shaped function built on them.
- In
- a manifold M with dist and interpolate, endpoints a, b ∈ M, a step count n
- Out
- n + 1 configurations along the shortest geodesic from a to b
- for do
- — the short way, by the metric's own geodesic
- return — with , , and
The per-manifold operations, in the order the Rust implements them:
- In
- angles a, b ∈ (−π, π]; a parameter t ∈ [0, 1]; a seeded Rng
- Out
- a distance in [0, π]; an angle on the short arc; a uniform angle
- —
- — antipodal tie: returns , counter-clockwise
- : apply 1–3 componentwise;
- with weight : ; straight in , short way in ; sample a box for and for
- In
- unit quaternions Q, Q′; t ∈ [0, 1]; a seeded Rng
- Out
- the rotation angle between them; the geodesic rotation; a Haar-uniform rotation
- ; if then , — the double cover: is the same rotation, nearer on
- , computed as — near 1 loses eight digits
- ; , normalized; linear blend when
- with — uniform on , hence Haar on
- Euler Z-Y-Z: if return carrying ; else (E.3, E.5, E.6)
Line 2 of the box is a numerical honesty item worth a sentence: the first implementation
of dist used directly, and dist(Q, Q) came out as , which failed
the definiteness check. The half-angle identity gives the same number to full precision, and a metric that is not definite to machine
precision breaks every equality test built on it.
Implementation in Rust
The trait is the chapter. Everything else is four implementations of it, a product, and one generic function.
use prob::Rng;
/// THE OWNED ARTIFACT. Every planner from Chapter 6 on is generic over this trait
/// and never touches coordinates directly. `DIM` is the manifold dimension — the
/// number of local coordinates — not the embedding dimension (3 for SO(3), not 9).
pub trait Manifold: Copy + PartialEq + core::fmt::Debug {
const DIM: usize;
/// Sampling box for non-compact factors; `()` for compact spaces, which have
/// a uniform distribution of their own and need no box.
type Bounds;
/// A metric in the sense of Definition C.2.1 — property-tested, not assumed.
fn dist(&self, other: &Self) -> f64;
/// The geodesic from `self` to `other` at `t ∈ [0, 1]`, taken the short way.
fn interpolate(&self, other: &Self, t: f64) -> Self;
/// Uniform with respect to the natural measure: Lebesgue on boxes, Haar on groups.
fn sample(rng: &mut Rng, bounds: &Self::Bounds) -> Self;
}
/// The first generic planner-shaped function in the book: a geodesic local path.
/// Chapter 11's `trait Steer` defaults to exactly this.
pub fn geodesic_path<M: Manifold>(a: &M, b: &M, n: usize) -> Vec<M> {
(0..=n).map(|i| a.interpolate(b, i as f64 / n as f64)).collect()
}S1 is a newtype around an f64, and the newtype is the point: the constructor wraps, so no
value outside can exist, and the metric and interpolation never see a seam.
use core::f64::consts::{PI, TAU};
/// S¹ as a wrapped angle. The chart is (−π, π]; the type guarantees the representative.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct S1(f64);
impl S1 {
/// Wrap into (−π, π]. This is the only way to build an S1, so the invariant holds everywhere.
pub fn new(theta: f64) -> Self {
let mut x = (theta + PI).rem_euclid(TAU);
if x <= 0.0 { x += TAU; }
S1(x - PI)
}
pub fn angle(self) -> f64 { self.0 }
}
impl Manifold for S1 {
const DIM: usize = 1;
type Bounds = ();
/// min(|a − b|, 2π − |a − b|): the quotient metric of ℝ by 2πℤ.
fn dist(&self, o: &Self) -> f64 {
let d = (self.0 - o.0).abs();
d.min(TAU - d)
}
/// Travel the short way. At the antipode wrap() returns +π: deterministic, documented.
fn interpolate(&self, o: &Self, t: f64) -> Self {
S1::new(self.0 + t * S1::new(o.0 - self.0).0)
}
fn sample(rng: &mut Rng, _: &()) -> Self {
S1::new(rng.uniform(-PI, PI))
}
}The torus is a product of circles, and is the ported Pose2 with a weight attached. Note
what Se2 is not: it does not redefine the pose type. Pose2::compose, ::inverse and ::act
already exist from Chapter 2 (the sister book derived them); this chapter only adds the planner's
view of them.
/// Tⁿ as an array of circles, with the ℓ² product metric. Reach's `T2` from Chapter 2
/// is `Torus<2>` through a `From` impl, so no code in `cspace` changes.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Torus<const N: usize>(pub [S1; N]);
impl<const N: usize> Manifold for Torus<N> {
const DIM: usize = N;
type Bounds = ();
fn dist(&self, o: &Self) -> f64 {
self.0.iter().zip(&o.0).map(|(a, b)| a.dist(b).powi(2)).sum::<f64>().sqrt()
}
fn interpolate(&self, o: &Self, t: f64) -> Self {
let mut out = *self;
for i in 0..N { out.0[i] = self.0[i].interpolate(&o.0[i], t); }
out
}
fn sample(rng: &mut Rng, _: &()) -> Self {
Torus(core::array::from_fn(|_| S1::sample(rng, &())))
}
}
/// SE(2) is the ported Pose2. The metric needs an exchange rate between metres and
/// radians, and that rate has no canonical value — so it is a field, never a default.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Se2 { pub pose: Pose2, pub w: f64 }
impl Manifold for Se2 {
const DIM: usize = 3;
type Bounds = Aabb; // sampling box for (x, y); θ is uniform on S¹
fn dist(&self, o: &Self) -> f64 {
let dth = S1::new(self.pose.theta).dist(&S1::new(o.pose.theta));
(self.pose.x - o.pose.x).hypot(self.pose.y - o.pose.y).hypot(self.w * dth)
}
fn interpolate(&self, o: &Self, t: f64) -> Self {
let th = S1::new(self.pose.theta).interpolate(&S1::new(o.pose.theta), t);
Se2 { w: self.w, pose: Pose2 {
x: self.pose.x + t * (o.pose.x - self.pose.x),
y: self.pose.y + t * (o.pose.y - self.pose.y),
theta: th.angle(),
} }
}
fn sample(rng: &mut Rng, b: &Aabb) -> Self {
Se2 { w: 0.0, pose: Pose2 { x: rng.uniform(b.min.x, b.max.x), y: rng.uniform(b.min.y, b.max.y),
theta: rng.uniform(-PI, PI) } }
}
}So3 wraps nalgebra's UnitQuaternion, which already gives the product, the conjugate, and
slerp. What the chapter adds is the metric with the double cover accounted for, Haar sampling,
and the Euler inverse that returns an error at the lost chart instead of a number.
use nalgebra::{Quaternion, SVector, UnitQuaternion};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct So3(pub UnitQuaternion<f64>);
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct GimbalLock { /// The one quantity that survives at R₃₃ = ±1: φ + ψ (or φ − ψ).
pub sum: f64 }
impl So3 {
/// Z-Y-Z Euler angles, eqs. E.3, E.5, E.6, on the chart {R₃₃ ≠ ±1}. Off the chart
/// there is no answer, and the type says so.
pub fn euler_zyz(&self) -> Result<[f64; 3], GimbalLock> {
let r = self.0.to_rotation_matrix();
let r33 = r[(2, 2)].clamp(-1.0, 1.0);
if 1.0 - r33.abs() < 1e-9 {
let sum = if r33 > 0.0 { r[(1, 0)].atan2(r[(0, 0)]) } // E.9: φ + ψ
else { (-r[(0, 1)]).atan2(-r[(0, 0)]) }; // E.11: φ − ψ
return Err(GimbalLock { sum });
}
let theta = (1.0 - r33 * r33).sqrt().atan2(r33);
Ok([r[(1, 2)].atan2(r[(0, 2)]), theta, r[(2, 1)].atan2(-r[(2, 0)])])
}
/// Eqs. E.19–E.21: axis and angle, θ ∈ [0, π], with the sign fixed by w ≥ 0.
pub fn axis_angle(&self) -> (SVector<f64, 3>, f64) {
let q = if self.0.w < 0.0 { -self.0.into_inner() } else { self.0.into_inner() };
let angle = 2.0 * q.w.clamp(-1.0, 1.0).acos();
let v = q.imag();
let n = v.norm();
(if n < 1e-12 { SVector::z() } else { v / n }, angle)
}
}
impl Manifold for So3 {
const DIM: usize = 3;
type Bounds = ();
/// 2·acos|⟨q, q′⟩|, via 4·atan2(‖q − q′‖, ‖q + q′‖) after a sign flip: the same angle,
/// but acos near 1 loses eight digits and a metric must be definite to machine precision.
fn dist(&self, o: &Self) -> f64 {
let (a, mut b) = (self.0.into_inner(), o.0.into_inner());
if a.dot(&b) < 0.0 { b = -b; }
4.0 * (a - b).norm().atan2((a + b).norm())
}
/// Spherical linear interpolation toward the nearer of ±q′ — the short way on S³.
fn interpolate(&self, o: &Self, t: f64) -> Self {
let b = if self.0.dot(&o.0) < 0.0 { UnitQuaternion::new_unchecked(-o.0.into_inner()) } else { o.0 };
So3(self.0.slerp(&b, t))
}
/// Haar-uniform: a normalized Gaussian 4-vector is uniform on S³.
fn sample(rng: &mut Rng, _: &()) -> Self {
let q = Quaternion::new(rng.normal(), rng.normal(), rng.normal(), rng.normal());
So3(UnitQuaternion::from_quaternion(q))
}
}
/// A × B with the ℓ² product metric — SE(2) × S¹ for Hitch, Chapter 14's composites.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Product<A: Manifold, B: Manifold>(pub A, pub B);The type system knows that
The compile error is a feature. Choset's §3.7 lists the configuration spaces of common robots and
then a list of inequalities — , ,
— that a planner written against coordinates cannot see. One written
against Manifold cannot fail to see them:
use manifold::{geodesic_path, Se2, Torus};
fn main() {
let q_reach: Torus<2> = reach_home(); // Reach lives on T²
let rusty: Se2 = Se2 { pose: rusty_pose(), w: 0.5 }; // Rusty lives on SE(2)
let _path = geodesic_path(&q_reach, &rusty, 10);
// ^^^^^^ error[E0308]: mismatched types
// expected `&Torus<2>`, found `&Se2`
}trybuild asserts that this file fails to compile with exactly that message. There is no runtime
check anywhere in the planners for "are these two configurations in the same space?", because
there does not need to be.
The worked example, and its printed output
fn main() {
let (a, b) = (S1::new(0.1), S1::new(6.2));
println!("S1 dist(0.1, 6.2) = {:.4} chart |a-b| = {:.4} midpoint = {:.4} (chart midpoint {:.4})",
a.dist(&b), (0.1f64 - 6.2).abs(), a.interpolate(&b, 0.5).angle(), (0.1 + 6.2) / 2.0);
let (p, q) = (Torus([S1::new(0.1), S1::new(3.0)]), Torus([S1::new(6.2), S1::new(3.5)]));
println!("T2 dist = {:.4}", p.dist(&q));
let w = 0.5;
let (o, r) = (Se2 { pose: Pose2::new(0.0, 0.0, 0.0), w }, Se2 { pose: Pose2::new(1.0, 0.0, PI), w });
println!("SE2 (w=0.5) dist((0,0,0),(1,0,π)) = {:.4}", o.dist(&r));
}S1 dist(0.1, 6.2) = 0.1832 chart |a-b| = 6.1000 midpoint = 0.0084 (chart midpoint 3.1500)
T2 dist = 0.5325
SE2 (w=0.5) dist((0,0,0),(1,0,π)) = 1.8621T_AC = Pose2 { 2, 1, 1.5708 } w_B = (-3, 1) w_A = (1, -1) body: (-2,-1,0) world: (2,1,0) Dφ·(1,0) = (-1.4142, 0)The tests beside these examples are the chapter's contract. s1_seam_distance,
s1_midpoint_short_way, t2_distance and se2_weighted_distance assert the four numbers against
their closed forms to ; choset_frames asserts , , and the body/world
placements exactly; metric_axioms is a proptest asserting definiteness, symmetry and the
triangle inequality on 10,000 seeded triples for each of S1, Torus<2>, Se2 and So3;
interpolate_geodesic asserts the endpoints and to ;
euler_gimbal_lock asserts Err(GimbalLock) at with the right and an
exact round trip elsewhere; so3_dist_is_angle asserts dist equals the axis–angle of
; dphi_chart_independent asserts the Jacobian is unchanged under a chart
shift. The TypeScript port in web/lib/manifold/ runs the same checks — every widget on this page
is that port, and every number printed above was produced by it.
Why the weight is a field and not a constant: with the two poses in the example are apart; with they are apart and a half-turn in place costs more than driving six metres. Both are metrics. Which one a PRM should use depends on the robot and the task, and Chapter 11 measures the consequences. Hiding in a default would hide a modelling decision.
Putting it together: components on the square and on the torus
The integration lab turns §3.4.3 into a measured quantity. Take Chapter 4's raster of Reach's C-space on the Workbench — here a grid over for the arm among three disc obstacles, built inside this chapter's module because the general raster lands with Chapter 4 — and count connected components of the free cells twice: once as a square, with 4-connectivity and no wrapping, and once as a torus, with the opposite edges identified.
The square has 2 components; the torus has 1. One of the discs lies within reach of the
first link, and a first-link collision does not care about , so its C-obstacle is a band
across the whole chart. The band cuts the square in two. On the torus the two halves meet through
the seam at , and seam_connection finds the closest pair of cells that the
square separates and the torus joins: two cells at — two raster cells apart —
whose chart distance is . That is the purple path of the hook, measured.
Monotonicity is the invariant the lab asserts: identifying edges can only merge components, never split them, so the torus count is at most the square count, on every raster. With joint limits the outermost ring of cells is forbidden, the seam becomes a wall, and both counts are 2: the topology changed, and the count noticed.
The last thread to tie is Chapter 4's Jacobian. At the matrix of sends to , Choset's Example 3.8.1, and shifting 's chart by changes no entry by more than . The Jacobian is the matrix of a chart-independent linear map between tangent spaces; Chapter 20 makes tangent spaces formal and Chapter 7 uses to pull forces back from the workspace to the joints.
Everything downstream is now generic. Chapter 6 builds graphs whose edge costs are dist; Chapter 7
needs the tangent spaces this chapter named informally; Chapter 11's kd-tree splits on
Chart::coords with periodic axes and its samplers call sample; Chapters 12–14's tree planners
and composite spaces are generic over Manifold; Chapter 17 needs 's inertia; Chapters
20–21 live in as the car's group. None of them will do arithmetic on coordinates.
Exercises
- Foundation exerciseDifficulty 1 of 3Two charts for the circle, two for the sphere
Find two charts for and prove they form an atlas (Choset problem 3.9). Then explain why latitude–longitude is not a global chart on — what happens at the poles, and along the date line? — and exhibit a two-chart atlas for the sphere (problem 3.10). Which chart does
manifold::S1use, and where does its domain end? - Foundation exerciseDifficulty 2 of 3Homeomorphic, not isomorphic
Show that and with the product operation are homeomorphic as spaces but not isomorphic as groups (Choset problem 3.23). Which one is commutative? Which one is
Pose2::compose? Give a concrete pair of planar motions for which the two products disagree, and say which product describes what Rusty actually does. - Conceptual exerciseDifficulty 1 of 3Predict the seam, then verifyPredict first
In the Torus Unwrapper, place the path endpoints at (θ₁, θ₂) = (−3.0, 0) and (3.0, 0). Before reading the readout: what is dist, and where does the purple path cross the seam?
- Conceptual exerciseDifficulty 2 of 3Read the lost chart
In the Rotation Zoo, scrub to the frame where is closest to and read and . Predict their sum from the quaternion readout using eq. (E.9), with and from (E.28); verify against the dashed curve. Then explain why a controller that commands and separately misbehaves there, and what it should command instead.
With the default motion (miss = 0.08 rad), what is the smallest value R₃₃ reaches, to four decimals?
- Practical exerciseDifficulty 2 of 3The sphere is not the torus
Implement
Manifoldfor the sphere as a unit vector in : great-circledist,slerpasinterpolate, and a uniformsample(normalize a Gaussian 3-vector). Property-test the metric axioms and the geodesic property asmetric_axiomsdoes. Then write a test that takes identical-looking coordinates , interprets them once as a point ofTorus<2>and once — via latitude and longitude — as a point of yourS2, and asserts thatdistto a second such pair differs. Make the test's name say what it proves: . - Practical exerciseDifficulty 3 of 3Exponential coordinates
Add
exp/logtoSe2andSo3, reusing the sister book'sPose2exponential andnalgebra's quaternion logarithm, and implement a second interpolation . Show with a test that on it coincides with the chart-basedinterpolateonly when the translation and rotation are decoupled — straight-line translation with constant heading — and report the maximum discrepancy indistover 1,000 seeded pairs otherwise, as a function of . The micro-Lie theory paper in the references is the map for this exercise.
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)
§3.4–3.6 and Appendices B, C and E are this chapter's source: the definitions in their order, the circle/ellipse/racetrack, the four charts of S¹, the three-frame example pinned by the tests, and the Euler-angle inverse.
- Lozano-Pérez, T. (1983) Spatial Planning: A Configuration Space Approach. IEEE Transactions on Computers C-32(2), 108–120.doi:10.1109/TC.1983.1676196 (opens in a new tab)
The paper that made configuration space the planner's space; the C-obstacles on the torus in the hook descend from its figures.
- Shoemake, K. (1985) Animating Rotation with Quaternion Curves. ACM SIGGRAPH Computer Graphics 19(3), 245–254.doi:10.1145/325334.325242 (opens in a new tab)
Spherical linear interpolation — the geodesic on SO(3) that So3::interpolate implements — and the case for quaternions over Euler angles, made with the gimbal-lock argument this chapter repeats.
- Solà, J., Deray, J., and Atchuthan, D. (2018) A micro Lie theory for state estimation in robotics. arXiv:1812.01537.link to A micro Lie theory for state estimation in robotics (opens in a new tab)
The exponential-coordinates view of SE(2) and SO(3) that Exercise 6 asks for, written for roboticists; the ⊞/⊟ operators the sister book uses come from here.
- Lynch, K. M. and Park, F. C. (2017) Modern Robotics: Mechanics, Planning, and Control. Cambridge University Press.link to Modern Robotics: Mechanics, Planning, and Control (opens in a new tab)
Chapter 3 is the fullest modern treatment of SO(3), SE(3), exponential coordinates and the body/world distinction, by one of Choset's co-authors.
- Lee, J. M. (2012) Introduction to Smooth Manifolds. Springer, Graduate Texts in Mathematics 218, 2nd edition.doi:10.1007/978-1-4419-9982-5 (opens in a new tab)
Where to go when 'locally homeomorphic to ℝᵏ' is not enough: charts, atlases, tangent spaces and the differential done properly, in the notation this chapter borrows.
- 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 source; its Chapter 3 web treatment of SE(2) as a Lie group, with exp/log and ⊞/⊟, is the cross-link this chapter defers to for rotation machinery.
