Time Parameterization¶
The missing step between a geometric path and a command stream that a motor controller can execute. The motion planner gives you where to go — a list of waypoints in joint space — but not when. Time parameterization adds the timing: it assigns a time stamp to every point on the path so that per-joint velocity and acceleration limits are respected, and the trajectory is as fast as physically possible.
-
Time-optimal
Finds the fastest feasible velocity profile along the path. Every joint is at its velocity or acceleration limit at every instant — there is no slack left to speed up.
-
Bounded velocity + acceleration
Per-joint velocity and acceleration limits are hard constraints. The output trajectory never exceeds them (up to the integrator's numerical tolerance).
-
C++ hot path
The TOTG core is Eigen-only C++17, exposed through a single nanobind call. Python overhead is one round-trip per path.
Algorithm¶
The implementation is TOTG (Time-Optimal Trajectory Generation) by Kunz and Stilman (2012), vendored from MoveIt 2 and stripped down to a standalone Eigen-only library. It is the default time parameterizer in MoveIt 2.
The algorithm works in two stages:
- Path smoothing — interior waypoints get a circular blend
(controlled by
max_deviation) so the path is C^1^ continuous. You do not need to pre-smooth the path. - Forward-backward integration — a sweep forward under maximum acceleration, then backward, finds the time-optimal velocity profile \(\dot{s}(s)\) along the blended path.
The result is a trajectory \(q(t)\) with continuous velocity and bounded acceleration that starts and ends at rest.
When to skip OMPL's interpolation step
If you plan to time-parameterize the output, pass
interpolate=False to plan() or PlannerConfig. OMPL's
interpolation adds collinear waypoints on existing edges — TOTG
would just blend and un-blend them, doing redundant work for no
benefit.
Minimal example¶
import numpy as np
from autolife_planning.planning import create_planner
from autolife_planning.trajectory import TimeOptimalParameterizer
from autolife_planning.types import PlannerConfig
# 1. Plan a collision-free path (skip interpolation).
planner = create_planner(
"autolife_left_arm",
config=PlannerConfig(simplify=True, interpolate=False),
)
start = planner.extract_config(home_joints)
goal = planner.sample_valid()
result = planner.plan(start, goal)
path = result.path # (N, 7)
# 2. Time-parameterize.
vel_limits = np.full(planner.num_dof, 1.0) # rad/s
acc_limits = np.full(planner.num_dof, 2.0) # rad/s^2
param = TimeOptimalParameterizer(vel_limits, acc_limits)
traj = param.parameterize(path)
print(f"Duration: {traj.duration:.3f} s")
# 3. Sample at controller rate.
times, positions, velocities, accelerations = traj.sample_uniform(dt=0.01)
Configuration knobs¶
| Parameter | Default | Description |
|---|---|---|
max_velocity |
(required) | (ndof,) per-joint velocity limit (rad/s or m/s) |
max_acceleration |
(required) | (ndof,) per-joint acceleration limit |
max_deviation |
0.1 |
Radial blend tolerance at corners. Larger = faster cornering, but the trajectory deviates more from the original waypoints. |
time_step |
1e-3 |
Forward-integration step along the path arc length. Smaller = more accurate, slower. |
velocity_scaling |
1.0 |
Scale factor in (0, 1] applied to max_velocity. Use to slow the trajectory without changing the stored limits. |
acceleration_scaling |
1.0 |
Scale factor in (0, 1] applied to max_acceleration. |
Querying the trajectory¶
The returned Trajectory object supports both point and batch queries:
# Point queries at arbitrary time t.
pos = traj.position(t) # (ndof,)
vel = traj.velocity(t) # (ndof,)
acc = traj.acceleration(t) # (ndof,)
# Batch: user-supplied time grid.
pos, vel, acc = traj.sample(times) # each (T, ndof)
# Batch: uniform grid at controller dt — always includes t=0 and t=duration.
times, pos, vel, acc = traj.sample_uniform(dt=0.01)
Scaling for slower motion¶
Pass velocity_scaling or acceleration_scaling to parameterize()
to slow the trajectory without reconstructing the parameterizer:
One-shot convenience¶
For scripts where you only parameterize a single path:
from autolife_planning.trajectory import parameterize_path
traj = parameterize_path(path, vel_limits, acc_limits)
Pipeline recipe¶
A typical end-to-end pipeline:
plan(start, goal, simplify=True, interpolate=False)
│
▼
(N, ndof) path geometric, no timing
│
▼
TimeOptimalParameterizer.parameterize(path)
│
▼
Trajectory q(t), q̇(t), q̈(t) with bounded vel/acc
│
▼
traj.sample_uniform(dt) dense rollout at controller rate
│
▼
stream to hardware (times, positions, velocities, accelerations)
API reference¶
See the full API docs at Trajectory API.