Choose the right coordinate frame and a hard planning problem can become an easy one: drive along the road, not across the map.
On motion planning in the Frenet frame
This section builds on the predicted occupancy fields introduced in section 48.3 and assumes familiarity with coordinate frame transformations from section 4.2. The kinematic vehicle model used by Hybrid A* is developed in full in section 48.7. The Frenet planning approach introduced here is revisited in Part X alongside learned trajectory scoring in section 48.5, where the same feasibility constraints are enforced on neural planner outputs.
A car at 60 mph must pick a lane, match the gap ahead, and avoid a cyclist merging right, all within 100 milliseconds and a 4-meter lane. That is the local planning problem, and it is currently one of the sharpest bottlenecks separating research robots from deployable vehicles. The core insight that makes it tractable is a coordinate change: the Frenet frame replaces the city map with a ruler aligned to the lane, so a lane change becomes a one-dimensional polynomial and jerk (the rate of change of acceleration, the quantity that makes a maneuver feel abrupt to a passenger even when acceleration itself is within limits) can be minimized analytically. Where no lane exists (a parking lot, a U-turn), Hybrid A* takes over, searching feasible steering sequences rather than grid squares. You will derive both planners, implement the Frenet trajectory sampler, and wire it to the occupancy predictions from section 48.3.
Ask a fresh grid planner to change lanes on a curving highway and it will happily hand back a path the steering rack can never execute; the fix is not a smarter search but a change of coordinates that turns the whole maneuver into a single polynomial. That is the contract this section develops: given a reference path and a Frenet start and goal, generate a trajectory that satisfies the feasibility-comfort-safety triple (curvature and acceleration within limits, bounded jerk, and clear of predicted occupancy). The worked example converts a Cartesian waypoint into Frenet \((s, d)\) and shapes a polynomial lateral profile.
Theory
The Frenet frame
As Figure 48.4A illustrates, the Frenet frame straightens the road into a ruler so that curved-lane planning collapses into one-dimensional polynomial shaping. Given a smooth reference path \(r(s)\) parameterized by arc length \(s\), any nearby point \(p\) is described by its projection's arc length \(s\) (along-track, how far along the lane) and its signed perpendicular distance \(d\) (lateral, how far off-center, positive to the left). The mapping decouples the two problems that matter for driving: longitudinal behavior (speed, gaps) lives in \(s\), and lane positioning lives in \(d\).
Polynomial paths and boundary conditions
Figure 48.4B labels the projection point, the along-track arc length \(s\), and the lateral offset \(d\) that Figure 48.4A introduces at the scale of a full lane change.
With lane positioning now isolated in the single scalar \(d\), the planning problem reduces to choosing how that offset should evolve over time. A comfortable lateral maneuver is a polynomial \(d(t)\) that satisfies boundary conditions. A quintic (5th-degree) polynomial fixes position, velocity, and acceleration at both the start and the end. Six conditions determine six coefficients, so the quintic is the standard choice for jerk-optimal lane changes. Degree matters: a cubic lane change at 100 km/h produces a lateral jerk spike of roughly 8 m/s³ at the seam. A quintic over the same distance and duration holds jerk below 2 m/s³ throughout. That is the difference between a passenger grabbing the handle and not noticing the maneuver.
Checkpoint
So far: lane positioning lives in the scalar \(d\), and a quintic (six boundary conditions, six coefficients) is the standard polynomial for shaping \(d(t)\) because it can pin down position, velocity, and acceleration at both ends, keeping jerk low enough that a passenger does not feel the maneuver.
On a typical fan of 45 sampled candidates, the quintic's tighter jerk envelope eliminates roughly 38 before any collision check runs and leaves 7 to score. Without the quintic constraint the same filter passes 43 candidates, and the planner wastes most of its budget on trajectories that feel like an emergency stop. A lower-degree (cubic) polynomial fixes fewer conditions and is enough when only endpoint positions and slopes matter. The planner shapes longitudinal motion \(s(t)\) the same way, targeting speed and acceleration.
Hybrid A* for structured search
Polynomial sampling assumes a usable reference path. When there is none (a parking lot, a tight unprotected turn), Hybrid A* searches a grid of poses but expands successors using a kinematic vehicle model, so every node is reachable by a real steering command. It blends grid search (completeness) with continuous motion (feasibility) and uses a non-holonomic-with-obstacles heuristic to stay efficient.
A vehicle is non-holonomic (its steerable degrees of freedom are fewer than its position-and-heading degrees of freedom, so it cannot move sideways on demand the way it can move forward): this constraint, defined here before the search mechanics below rely on it, is what separates a car's reachable poses from an arbitrary grid cell.
This matters for embodied AI because a car cannot slide sideways: its minimum turning radius is a hard physical constraint, not a preference. Standard A* ignores this and returns paths that require zero-radius turns. The actuators cannot execute those turns. In a parking garage with 0.3 m clearance on each side, an infeasible path does not merely waste distance; it drives the car into a wall. Any planner for a non-holonomic robot must respect the turning radius during planning, not as a post-processing filter.
How the search expands
Each grid cell stores a continuous pose \((x, y, \theta)\), not just an integer index. To expand a node, Hybrid A* pushes a fixed set of steering angles (e.g., \(\{-35°, -17.5°, 0°, 17.5°, 35°\}\)) through the kinematic model for one timestep, yielding reachable child poses. Its heuristic takes the maximum of two admissible estimates: a Dubins or Reeds-Shepp cost (the shortest path length for a car-like vehicle with a fixed minimum turning radius, computed on empty ground with no obstacles; Reeds-Shepp additionally allows reverse motion) that ignores obstacles, and a 2D Dijkstra distance that ignores the turning constraint. The maximum keeps the search focused without over-estimating.
Hybrid A* exists because the 2007 DARPA Urban Challenge exposed exactly this failure: Stanford's Junior entry needed feasible motion in unstructured zones (parking lots, U-turns) where a lane-relative planner has no reference path to follow. Dolgov, Thrun, and colleagues built Hybrid A* for that vehicle so every expanded node was reachable by a real steering command, and the same algorithm now ships in Apollo and Autoware for production parking. A standard grid A* would happily return a path with a zero-radius pivot that the steering rack physically cannot execute.
The same lane change that is an awkward, curvature-coupled problem in \((x, y)\) becomes a one-dimensional polynomial in \(d(s)\) once you adopt the Frenet frame. Picking the coordinate system that matches the constraint structure is often more powerful than picking a fancier optimizer.
Frenet planning typically samples a fan of candidate trajectories: several lateral end-offsets \(d\) crossed with several longitudinal speed or time targets, each realized as a polynomial. Every candidate is checked for kinematic feasibility and collision against predicted occupancy, then scored by a cost (jerk, deviation from lane center, proximity to obstacles). The lowest-cost feasible candidate is sent to control. The conversion back from \((s, d)\) to \((x, y)\) for execution requires the reference path geometry.
Algorithm: Frenet Optimal Trajectory Selection
Input: reference path \(r(s)\), current state \((s_0, d_0, \dot{s}_0, \dot{d}_0)\), goal speed \(v^*\), predicted occupancy grid \(\mathcal{O}\), feasibility bounds \((\kappa_{\max}, a_{\max}, j_{\max})\)
Output: optimal trajectory \(\pi^* = \{(s(t), d(t))\}_{t=0}^{T}\) and its Cartesian realization \(\{(x(t), y(t), \theta(t))\}\)
- Project the vehicle's Cartesian state onto \(r(s)\) to obtain the Frenet start state \((s_0, d_0, \dot{s}_0, \dot{d}_0, \ddot{d}_0)\).
- Sample a grid of lateral goal offsets \(\{d_f^{(i)}\}\) centered on the lane (e.g., \(d_f \in \{-1.5, 0, 1.5\}\) m) and longitudinal horizon durations \(\{T^{(j)}\}\) (e.g., \(T \in \{3, 4, 5\}\) s).
- For each pair \((d_f^{(i)}, T^{(j)})\), fit a quintic polynomial \(d(t)\) that satisfies the six boundary conditions: \(d(0)=d_0\), \(\dot{d}(0)=\dot{d}_0\), \(\ddot{d}(0)=\ddot{d}_0\), \(d(T)=d_f\), \(\dot{d}(T)=0\), \(\ddot{d}(T)=0\).
- Fit a quartic (4th-degree) polynomial, one degree lower than the quintic above because the longitudinal profile fixes only five boundary conditions, not six: there is no terminal position constraint on \(s\), only a terminal speed \(s(t)\) matching \(s(0)=s_0\), \(\dot{s}(0)=\dot{s}_0\), \(\ddot{s}(0)=\ddot{s}_0\), \(\dot{s}(T)=v^*\), \(\ddot{s}(T)=0\).
- Compute curvature \(\kappa(t) = \ddot{d}(t) / (1 + \dot{d}(t)^2)^{3/2}\) and lateral acceleration \(a_y(t) = \dot{s}(t)^2 \kappa(t)\); reject any candidate where \(\max_t |\kappa(t)| > \kappa_{\max}\), \(\max_t |a_y(t)| > a_{\max}\), or \(\max_t |\dddot{d}(t)| > j_{\max}\).
- Convert each surviving \((s(t), d(t))\) back to Cartesian \((x(t), y(t), \theta(t))\) using the reference path tangent and normal at each arc-length sample.
- Reject candidates whose Cartesian tube overlaps occupied cells in \(\mathcal{O}\) (inflate by half the vehicle width \(w/2\)).
- Score remaining candidates with cost \(J = \alpha \int_0^T \dddot{d}^2 \, dt + \beta (d_f - 0)^2 + \gamma \int_0^T (\dot{s} - v^*)^2 \, dt\), where \(\alpha, \beta, \gamma\) are tunable weights.
- Select \(\pi^* = \arg\min J\) (the candidate trajectory that achieves the lowest cost \(J\), not the value of \(J\) itself) over all feasible, collision-free candidates.
- Return \(\pi^*\) in Cartesian form; re-plan at the next control cycle using the updated state and occupancy grid.
Step-Through: Frenet Optimal Trajectory Selection
Trace the algorithm with a tiny three-candidate fan. Start state on a straight reference path: \(s_0 = 0\), \(d_0 = 0.4\) m (slightly right of center), \(\dot{s}_0 = 25\) m/s, \(\dot{d}_0 = 0\), \(\ddot{d}_0 = 0\). Goal speed \(v^* = 25\) m/s, comfort limit \(a_{\max} = 2.0\) m/s², horizon \(T = 3\) s, lateral offsets \(d_f \in \{-1.5,\ 0,\ +1.5\}\) m.
Quintic fit, candidate B (\(d_f = 0\)): with zero end velocity and acceleration the six coefficients reduce to \(a_0=0.4\), \(a_1=0\), \(a_2=0\), \(a_3=10(0-0.4)/3^3=-0.148\), \(a_4=-15(-0.4)/3^4=0.074\), \(a_5=6(-0.4)/3^5=-0.0099\). Sampling \(d(t)\): \(d(0)=0.40\), \(d(1.5)=0.20\), \(d(3.0)=0.00\) m. Smooth nudge back to center.
Feasibility check. Peak lateral acceleration for each candidate (approximated as \(\dot{s}^2 \cdot \max_t|\ddot{d}|/\dot{s}^2 \approx \max_t|\ddot{d}|\) at this near-straight geometry): candidate A (\(d_f=-1.5\)) peaks near \(\max|\ddot{d}| \approx 2.5\) m/s², which exceeds \(a_{\max}=2.0\), so A is rejected. Candidate B peaks near 0.5 m/s² (pass). Candidate C (\(d_f=+1.5\)) peaks near 2.1 m/s², also rejected. Two of three gone before any collision check.
Score the survivor. Only B remains, so \(\pi^* = \) B. Its cost \(J = \alpha \int \dddot{d}^2 dt + \beta d_f^2 + \gamma \int (\dot{s}-v^*)^2 dt = \alpha(0.7) + \beta(0) + \gamma(0) = 0.7\alpha\), the lowest possible since \(d_f=0\) and speed already matches \(v^*\). The planner returns the gentle re-centering maneuver.
When sampling a fan of Frenet candidates, apply the kinematic feasibility filter (curvature limit \(\kappa_{\max} = 1/r_{\min}\), peak lateral acceleration \(a_{y,\max}\), and jerk bound) before evaluating the collision or comfort cost. In PythonRobotics' frenet_optimal_trajectory.py, the relevant threshold parameters are MAX_CURVATURE and MAX_ACCEL; tightening them to your vehicle's actual turning radius and powertrain limits eliminates infeasible candidates early and can cut the per-step planning time by more than half on a dense sample grid. Skipping this ordering is, in practice, a common source of a planner that is slow in curves but fast on straights.
Worked Example
The example converts a Cartesian waypoint to Frenet \((s, d)\) against a reference path, then generates a 3rd-degree polynomial that moves \(d\) from 0 to 1 m over 3 seconds (a gentle nudge toward the lane's left).
import numpy as np
# Reference path as densely sampled (x, y) points (here a gentle arc).
s_vals = np.linspace(0, 50, 501) # arc-length samples (m)
ref = np.stack([s_vals, 0.02 * s_vals**1.0], axis=1) # slowly rising centerline
def to_frenet(point, ref, s_vals):
"""Cartesian (x, y) -> Frenet (s, d) against a polyline reference path."""
p = np.asarray(point, dtype=float)
d2 = np.sum((ref - p) ** 2, axis=1) # squared distance to each ref point
i = int(np.argmin(d2)) # nearest reference index
s = float(s_vals[i]) # along-track arc length
# Lateral offset: signed perpendicular distance using the local tangent.
tan = ref[min(i + 1, len(ref) - 1)] - ref[max(i - 1, 0)]
tan = tan / (np.linalg.norm(tan) + 1e-9)
normal = np.array([-tan[1], tan[0]]) # left-hand normal
d = float(np.dot(p - ref[i], normal)) # signed lateral distance
return s, d
wp = (20.0, 1.5) # a Cartesian waypoint near the path
s, d = to_frenet(wp, ref, s_vals)
print(f"Frenet of {wp}: s={s:.2f} m, d={d:.2f} m")
def cubic_lateral(d0, d1, T):
"""3rd-degree d(t): d(0)=d0, d'(0)=0, d(T)=d1, d'(T)=0."""
# Coefficients for d(t) = a0 + a1 t + a2 t^2 + a3 t^3 with zero end slopes.
a0, a1 = d0, 0.0
a2 = 3 * (d1 - d0) / T**2
a3 = -2 * (d1 - d0) / T**3
return lambda t: a0 + a1 * t + a2 * t**2 + a3 * t**3
d_of_t = cubic_lateral(0.0, 1.0, 3.0) # move 0 -> 1 m over 3 s
for t in [0.0, 0.75, 1.5, 2.25, 3.0]:
print(f"t={t:4.2f}s d={d_of_t(t):.3f} m")
Expected output: the waypoint maps to roughly s=20.0 m, d=1.10 m (the offset measured perpendicular to the arc), and the cubic profile rises smoothly from 0 to 1 m, passing through 0.5 m at the midpoint with zero slope at both ends, the signature of a comfortable, jerk-bounded nudge.
Production stacks use optimization-based planners rather than hand-rolled sampling: Apollo (Baidu) and Autoware ship Frenet and lattice planners plus Hybrid A* for parking. CommonRoad provides reproducible planning scenarios and a route planner. The PythonRobotics repository has readable reference implementations of Frenet optimal trajectory planning and Hybrid A*. Keep the same feasibility and cost definitions across these tools.
This section's contract was to derive both planners, implement the Frenet trajectory sampler, and wire it to occupancy predictions; the Cartesian-to-Frenet conversion and the cubic sampler are covered in the worked example above, and the wiring step is completed here for clarity: the algorithm's step 7 rejects any candidate whose inflated Cartesian tube overlaps an occupied cell in the section 48.3 occupancy grid \(\mathcal{O}\), so the same predicted occupancy that section 48.3 produces becomes a hard constraint on which Frenet candidate the planner is allowed to select, not merely an input it consults informally.
Practical Recipe
- Build or fetch a smooth reference path (lane centerline) and verify the Frenet mapping on known points.
- Sample candidate trajectories: a grid of lateral offsets crossed with speed or time targets.
- Reject candidates violating curvature, acceleration, or jerk limits before scoring.
- Score feasible candidates against predicted occupancy and lane-keeping cost; send the best to control.
- Use Hybrid A* only where no reference path exists (parking, unstructured maneuvers).
A poorly conditioned reference path (sharp kinks, uneven spacing) corrupts the \(s\) and \(d\) estimate, so a perfectly safe trajectory in Frenet maps back to a swerve in Cartesian. Always smooth and re-sample the reference path, and validate the round trip \((x,y) \to (s,d) \to (x,y)\) before trusting the planner.
A common misconception is that the Frenet planner is the complete planning system and can decide which road or junction to take next. The Frenet sampler is a local planner that only shapes motion within an already-chosen reference path (a lane centerline handed to it by a separate route planner). In embodied AI, planning is hierarchical because no single representation scales from city-block navigation down to sub-second actuator commands. The correct mental model is a stack: a route planner (graph search over the road network) selects the lane sequence, a behavioral layer decides when to merge or yield, and only then does the local Frenet planner optimize a smooth, feasible trajectory along that pre-selected corridor.
Think of the planning hierarchy like cooking a meal for guests. A recipe book (route planner) tells you which dishes to make tonight. A sous chef (behavioral layer) decides the order of preparation and when to start the oven. The line cook (local planner) then executes each precise knife cut and flame adjustment. Asking the line cook to also choose the menu and manage the dining room would be absurd: each layer operates at its own timescale and level of detail, and collapsing them into one role produces chaos. The Frenet sampler is the line cook, and it only works well when a higher layer has already decided which corridor to cook in.
For a highway lane change, fix the boundary conditions to start at the current lateral state and end centered in the target lane with zero lateral velocity and acceleration, then use a quintic in \(d\) over a comfort-tuned duration. Sweeping the duration trades aggressiveness against jerk, giving a tunable family of human-like maneuvers.
Use a cubic polynomial when only position and slope need to match at each end: a short lane nudge of 0.5 m over 1 s where the vehicle is already moving steadily, and comfort is not safety-critical. Upgrade to a quintic whenever peak lateral acceleration matters, such as a full lane change of 3.5 m at highway speed (100 km/h), where an uncontrolled acceleration jump at the seam can exceed the 2 m/s² comfort limit. Switch to Hybrid A* entirely when no lane centerline exists: a typical 90-degree parking maneuver into a 2.5 m-wide bay requires evaluating turning-radius-constrained arcs over a grid, because no smooth polynomial in \(d\) can represent the three-point-turn sequence needed to fit the car in bounds.
Real-World Application: Autonomous Valet Parking
Baidu's Apollo stack runs Hybrid A* as its open_space_planner for exactly the case Frenet sampling cannot handle: parking lots and narrow unprotected turns where no lane centerline exists. Apollo expands turning-radius-constrained motion primitives with a Reeds-Shepp heuristic, then smooths the result with a quadratic-programming pass, the same algorithm-then-optimize pattern shipped in production valet parking. The lane-relative Frenet planner takes back over the instant the vehicle re-enters a structured road with a reference path.
s is how far down the road, d is how far off the line. Plan speed in s, plan position in d, and the curves take care of themselves.
Language-conditioned local planning (2024-2026). Recent work treats the planner's cost weights not as fixed scalars but as outputs of a large language model (LLM) that reads a natural-language instruction ("merge right before the exit, stay comfortable"). Waymo's 2024 EMMA system and Nvidia's 2025 DriveLM-Agent both ground LLM outputs to hard kinematic constraints via a differentiable Frenet filter, enabling instruction-following lane changes without violating curvature or jerk bounds. The open question for a PhD student: how do you certify that the LLM never produces a weight vector that makes the kinematic filter vacuous (i.e., where every candidate trajectory passes because the cost surface is flat)?
Diffusion-based trajectory generation (2024-2025). Rather than sampling a fixed polynomial fan, several 2024 papers (Zhong et al., "Guided Conditional Diffusion for Controllable Traffic Simulation," ICLR 2024; MotionDiffuser from Waymo Research, 2024) use a denoising diffusion process in Frenet space to produce a continuous distribution over trajectories, then draw the highest-likelihood sample that clears the occupancy grid. This replaces the combinatorial fan-and-filter loop with a single forward pass, but the safety argument changes: instead of exhaustive enumeration, you rely on the probability mass assigned to infeasible regions being near zero.
Closed-loop online adaptation (2025-2026). The nuPlan benchmark and the Waymo Open Motion Dataset now include reactive agents, making it possible to measure how a planner degrades when other vehicles respond to its own trajectory rather than following pre-recorded logs. ETH Zurich's SLEDGE (2024) and Waymo's Waymax simulator (2023, extended 2025) enable training and evaluating planners in fully closed-loop settings. An open PhD-level problem: designing an online adaptation mechanism that can update the learned cost weights from a handful of closed-loop failures in a new geographic domain (e.g., a roundabout-dense European city) without catastrophic forgetting of highway behavior.
Can you say why a quintic, not a cubic, is the natural choice for a jerk-comfortable lane change, and when Hybrid A* is needed instead of Frenet sampling? If not, revisit the boundary-condition counting above.
| Tool or Library | Role in the Topic | Builder Advice |
|---|---|---|
| Apollo, Autoware | Production Frenet, lattice, and Hybrid A* planners | Reuse their feasibility checks rather than re-deriving limits. |
| CommonRoad | Reproducible planning scenarios and routes | Benchmark a planner on shared scenarios before deployment. |
| PythonRobotics | Readable Frenet and Hybrid A* references | Use to learn the algorithm before adopting a heavy stack. |
Section 48.3 supplies the predicted occupancy this planner avoids, Section 48.7 tracks the planned trajectory with control, and Section 48.8 places local planning inside route-level and behavior-level decision making.
Replace the cubic in the worked example with a quintic that also fixes lateral acceleration to zero at both ends. Plot \(d(t)\), its velocity, and its acceleration, and confirm the quintic removes the acceleration discontinuity the cubic leaves at the endpoints.
Section References
Werling et al., "Optimal Trajectory Generation for Dynamic Street Scenarios in a Frenet Frame," ICRA 2010. Dolgov et al., "Path Planning for Autonomous Vehicles in Unknown Semi-structured Environments" (Hybrid A*), IJRR 2010. Althoff et al., "CommonRoad: Composable Benchmarks for Motion Planning on Roads," IV 2017.
These define Frenet optimal trajectory planning, Hybrid A*, and a reproducible planning benchmark.
Local planning becomes tractable in the Frenet frame, where speed lives in \(s\), lane position lives in \(d\), and comfortable maneuvers are jerk-bounded polynomials. Reserve Hybrid A* for the unstructured cases where no reference path exists.
Build a small Frenet sampler: generate five candidate lateral offsets, realize each as a quintic over 3 s, reject any whose peak lateral acceleration exceeds 2 m/s^2, and score the rest by deviation from lane center. Report which candidate wins and why.
Project Ideas
Frenet sampler in PythonRobotics (beginner, weekend): Clone the PythonRobotics repository and run frenet_optimal_trajectory.py, then add a second lane (shift the reference path by 3.5 m) and implement a lane-change trigger that fires when a static obstacle appears within 20 m ahead. The key challenge is correctly updating the Frenet start state at each replanning step so the sampled trajectories are continuous in velocity. Hybrid A* parking in Gymnasium (intermediate, 1-2 weeks): Build a custom Gymnasium environment that places a car in a simulated parking lot with 2.5 m-wide bays using PyBullet for physics (PyBullet is straightforward for this use case; note that as of 2024 it is in maintenance mode, so MuJoCo or Isaac Lab are better choices for new projects requiring active support), then implement Hybrid A* with a Reeds-Shepp heuristic to plan and execute a pull-in maneuver from a fixed start pose. The key challenge is closing the loop between the planner output and a low-level PD controller that tracks the planned arc without accumulating heading error over the three-point turn sequence. Learned trajectory scorer on nuPlan mini (intermediate, 2 weeks): Use the nuPlan devkit to load 10 hours of logged data, extract Frenet candidate trajectories from each logged frame, label them by whether the ego vehicle chose them, and train a small multilayer perceptron (MLP) in PyTorch to replace the hand-tuned cost weights \(\alpha, \beta, \gamma\) from the algorithm above. The key challenge is defining a balanced training set: most logged frames are straight-road cruising, so without stratified sampling the model ignores lane-change frames entirely.