Kinodynamic Planning (FLASK)¶
MotionPlanner.plan_kinodynamic plans a time-parameterised,
dynamically feasible trajectory directly — no separate time
parameterization step. It implements FLASK (Duong et al., "Ultrafast
Sampling-based Kinodynamic Planning via Differential Flatness", T-RO
2026): RRT-Connect in the differentially flat output space, where every
tree edge is a closed-form cubic that is collision-checked with the same
SIMD VAMP kernels as the geometric planner.
from drone_planning.planning import create_planner
from drone_planning.types import KinodynamicConfig
planner = create_planner("x500_whole_body", pointcloud=cloud)
result = planner.plan_kinodynamic(start, goal, config=KinodynamicConfig(time_limit=1.0))
traj = result.trajectory # KinodynamicTrajectory
times, q, qd, qdd = traj.sample_uniform(dt=0.02)
# qd[:, :4] = (x_dot, y_dot, z_dot, yaw_rate): the PX4 offboard velocity setpoint
# qd[:, 4:] = gimbal pan / tilt rates
It works for both subgroups (x500_base, x500_whole_body).
How it works¶
-
Flat outputs
A quadrotor is differentially flat in its position and yaw. With roll and pitch held by the flight controller,
(x, y, z, yaw)are all commanded directly — no nonholonomic constraint — and the gimbal pan and tilt are flat outputs of their own. -
Closed-form edges
Nodes are
(position, velocity)states. An edge is the cubic Hermite between its endpoints — the minimum-effort (LQMT) motion — with the durationT*that minimisessum_i int y_i''^2 / a_i^2 dt + rho T(a quartic root). -
Limits, exact
Checked exactly on each cubic: joint ranges, per-joint velocity and acceleration ranges (asymmetric for the climb rate), and the norm of the horizontal velocity (the roots of
v · a, a cubic) and acceleration (affine in time, so largest at an end).T*is stretched until they hold. -
SIMD collision checks
Each edge is sampled at VAMP's resolution along its arc length and checked in interleaved
ConfigurationBlockbatches (FLASK Alg. 3), then the solution is simplified the way VAMP simplifies geometric paths: greedy shortcuts (Alg. 4) plus subdivide-and-cut-corners smoothing, keeping a change only if it lowers the cost.
The limits¶
They come from PX4's default parameters (see Robot Model)
and can be scaled with KinodynamicConfig.velocity_scale /
acceleration_scale:
| Flat output | Velocity | Acceleration |
|---|---|---|
horizontal (x, y), norm |
12 m/s (MPC_XY_VEL_MAX) |
5 m/s² (MPC_ACC_HOR_MAX) |
vertical z |
−1.5 … 3 m/s (MPC_Z_VEL_MAX_DN/UP) |
−3 … 4 m/s² (MPC_ACC_DOWN/UP_MAX) |
| yaw | 60 °/s (MPC_YAWRAUTO_MAX) |
20 °/s² (MPC_YAWRAUTO_ACC) |
| gimbal pan / tilt | 30 °/s (MNT_RATE_YAW/PITCH) |
97 / 300 rad/s² (gimbal model torque) |
The yaw is a bounded joint in [−π, π] like in the geometric planner.
Tracking the trajectory¶
The trajectory is C¹ in every flat output: positions and velocities are
continuous; accelerations jump at segment knots. Stream
traj.velocity(t) as the velocity / yaw-rate setpoint (world frame, z
up — convert to NED for PX4) and the gimbal rates, with position feedback
on traj.position(t).
Pass the current state to replan on the fly — the new trajectory starts with the given velocity:
q_now, qd_now = traj.position(t), traj.velocity(t)
result = planner.plan_kinodynamic(q_now, new_goal, start_velocity=qd_now)
Trajectories are collision-free with zero clearance, up to the sampling resolution like every VAMP edge: the spheres are the margin.
Configuration¶
KinodynamicConfig holds the planner parameters; the limits come from
drone_planning/x500.py.
| Field | Default | Meaning |
|---|---|---|
time_limit |
1.0 |
Planning budget (s) |
rho |
1.0 |
Time weight; rest-to-rest edges peak at sqrt(rho) × the acceleration limit |
velocity_scale / acceleration_scale |
1.0 |
Scale every limit |
max_extension_time |
3.0 |
Tree step: longer cubics are truncated (node stays on the cubic) |
rest_sample_probability |
0.3 |
Share of zero-velocity samples (straight-line edges) |
simplify_iterations / simplify_time_limit |
5 / 0.05 |
Rounds and time cap for the VAMP-style simplification |
seed |
0 |
RNG seed (0 = nondeterministic) |
Results on the demo room¶
examples/planning/kinodynamic.py plans the four legs of the demo room
(examples/planning/_shared.py: a wall with a 1.2 × 1.0 m window and a
low beam, 99,715 points) 50 times each, with FLASK and with RRT-Connect
(plan()). "Peak / limit" is the largest velocity or acceleration over
all trajectories as a fraction of its limit (horizontal pair in norm), on
a 1 ms resampling.
Measured at commit 2fefdc3 on 2026-10-19, one core of an AMD EPYC 7543;
raw output in
/nas/pollux/tianrun/drone-planning-flask/g1_kinodynamic_example_50.txt.
| Subgroup | Leg | FLASK success | FLASK median / p95 | Duration | Peak / limit | RRT-Connect success | RRT-Connect solve median / p95 | plan() median |
|---|---|---|---|---|---|---|---|---|
x500_base |
through | 50/50 | 6.7 / 19.8 ms | 9.4 s | 1.000 | 50/50 | 1.2 / 3.3 ms | 7.6 ms |
x500_base |
back | 50/50 | 5.3 / 14.2 ms | 9.8 s | 1.000 | 50/50 | 1.2 / 3.3 ms | 6.3 ms |
x500_base |
diagonal | 50/50 | 4.2 / 11.2 ms | 5.9 s | 1.000 | 50/50 | 1.2 / 3.3 ms | 4.1 ms |
x500_base |
look down | 50/50 | 5.3 / 10.3 ms | 9.1 s | 1.000 | 50/50 | 1.2 / 3.9 ms | 7.2 ms |
x500_whole_body |
through | 50/50 | 5.8 / 15.5 ms | 9.8 s | 1.000 | 50/50 | 1.4 / 3.4 ms | 8.1 ms |
x500_whole_body |
back | 50/50 | 6.1 / 15.0 ms | 9.4 s | 1.000 | 50/50 | 1.2 / 4.0 ms | 7.0 ms |
x500_whole_body |
diagonal | 50/50 | 3.7 / 13.2 ms | 5.8 s | 1.000 | 50/50 | 1.2 / 3.3 ms | 4.1 ms |
x500_whole_body |
look down | 50/50 | 8.6 / 22.1 ms | 9.2 s | 1.000 | 50/50 | 1.2 / 2.3 ms | 7.0 ms |
plan() includes OMPL's path simplification. TOPP-RA times the
RRT-Connect paths, under per-joint limits, in 6.0–9.0 s (median per leg).
Takeaways:
- FLASK returns a trajectory within every limit in 3.7–8.6 ms (median per leg); the geometric planner finds a path in 1.2–1.4 ms but needs simplification and time parameterization on top.
- Durations are set by the slow yaw (20 °/s²) and gimbal (30 °/s) limits more than by the 12 m/s speed: tree nodes sample random yaw and gimbal angles, and shortcuts that remove a detour are kept only when collision-free, so FLASK trajectories carry yaw excursions that a geometric path smoothed by OMPL loses.