Trajectory (closed-form point-to-point generator)¶
rate_limiter bounds how fast a command can change and cbf_filter bounds where it can go,
but neither generates a reference to track in the first place -- quintic_trajectory fills
that gap with the simplest real, universal piece: a smooth, minimum-jerk-continuous path
between two points, for any number of joints at once.
import numpy as np
from dense_armor.utility.trajectory import quintic_trajectory
t, q, v, a = quintic_trajectory(q0=[0.0], qf=[10.0], T=2.0)
This returns a smooth position/velocity/acceleration profile that starts and ends at rest.
Pass v0/a0/vf/af to start or end already moving instead of at rest -- useful for
chaining segments so the robot doesn't stop at every intermediate point.
Works for any number of joints in one call -- pass q0/qf as arrays and every joint gets
its own independent polynomial over the same real time T.
trajectory ¶
utility/trajectory.py¶
Closed-form, no-training point-to-point trajectory generator --
rate_limiter.py bounds how fast a command can change and cbf_filter.py
bounds where it can go, but neither generates a REFERENCE to track in the
first place. This module fills that gap with the simplest real,
universal piece: a minimum-jerk-continuous quintic polynomial between two
points.
Scoped down deliberately from two real papers (both read in full before writing any code): Lozer, Scalera, Boscariol & Gasparetto, "Planning optimal minimum-jerk trajectories for redundant robots" (Robotics and Autonomous Systems, Elsevier) does multi-stage optimization with a full dynamic model on a real 7-DoF Franka Panda; Fried & Paternain, "A Bi-Level Optimization Approach to Joint Trajectory Optimization for Redundant Manipulators" (arXiv:2412.07859) does a convex inner/primal- dual outer optimization validated on a real UR10e. Both need a full kinematic/dynamic model (URDF parsing, Jacobians, torque limits) -- not implemented here, a real, separate, much larger undertaking.
THEORY: the classic quintic (5th-order) polynomial trajectory (see e.g. Craig, "Introduction to Robotics", or Piazzi & Visioli 2000) is the unique degree-5 polynomial per joint satisfying 6 real boundary conditions (position, velocity, acceleration at t=0 and t=T) -- solved here directly from the 6x6 linear system, not copied from a memorized formula, so a transcription error would fail the boundary-condition test rather than silently produce a wrong trajectory.
UNIVERSAL in the sense that matters for this stack: works for any number
of joints at once (any robot) since each joint's polynomial is
independent; needs no URDF, no kinematics, no dynamics, no robot
connection. Composes directly with rate_limiter/cbf_filter, which
already own rate-of-change and spatial safety -- this generator does not
need to worry about either.
VALIDATED, honestly, on TWO independent real physical domains (real LeRobot robot-arm joint data), promoted from Dense-Evolution-Discovery after both checked out (see docs/quintic_trajectory_planner.md there). A first validation attempt (real episode start/end frames as q0/qf) was thrown out as degenerate: a real pick-and-place task often returns close to its own starting configuration, giving several joints almost nothing to do and nothing meaningful to compare. Fixed by comparing each real joint's own min-to-max excursion within the episode instead: - SO-101 (single 6-DoF arm, real 30Hz): 6 real joint excursions checked. - ALOHA (bimanual, 14-DoF, real 50Hz -- a genuinely different real robot): 14 real joint excursions checked. Real result, all 20/20 real excursions: the quintic's peak velocity is always LOWER than the real recorded peak velocity for the same start, end, and real elapsed duration (ratio 0.05-0.62, mean 0.26). Expected, not a bug: the quintic is the smoothest possible path between two points, so it needs less peak speed than a real (teleoperated, not necessarily efficient) trajectory covering the same net real displacement in the same real time.
HONEST SCOPE: single-segment point-to-point only. Chaining several quintic segments across many waypoints is a real, natural next step, not implemented here.
quintic_trajectory ¶
Closed-form quintic point-to-point trajectory, any number of DOF.
Parameters¶
q0, qf : array-like, shape (n_dof,) Start and end position for each joint. T : float Total real trajectory duration. v0, a0, vf, af : array-like, shape (n_dof,), optional Start/end velocity and acceleration per joint (default 0 -- the standard "start and end at rest" case). n_samples : int Number of real time samples to return.
Returns¶
t : ndarray, shape (n_samples,) q, v, a : ndarray, shape (n_samples, n_dof) Position, velocity, acceleration at each sampled time.
Source code in dense_armor/utility/trajectory.py
See also: Rate limiter and CBF filter -- this module generates a reference to follow; those two keep whatever follows it safe in speed and space.
Details¶
Scoped down from two real papers proposing much larger URDF/dynamics-aware trajectory optimizers (Lozer, Scalera, Boscariol & Gasparetto, Robotics and Autonomous Systems; Fried & Paternain, arXiv:2412.07859) -- both read in full before writing this. Promoted from Dense-Evolution-Discovery after validation on two independent real physical domains (SO-101, ALOHA, 20 real joint excursions): the quintic's peak velocity is always lower than the real recorded peak velocity for the same start, end, and real elapsed duration -- expected, since it is the smoothest possible point-to-point path, not a bug. Single-segment point-to-point only; chaining several segments across many waypoints is a real next step, not implemented here.