Reference/API

Motion Planning

robokit.helpers.motion_plan.MotionPlanner(
config: MotionPlannerConfig,
robot: Robot,
target_links: Union[str, int, Sequence[Union[str, int]]],
scene: Optional[WarpScene] = None,
device: Optional[wp_device_type] = None
)

Batched motion planning with goal IK, trajectory optimization stages, and retry.

Lifecycle

  1. __init__ copies configuration and constructs goal IK, the optimizer stages, and their retiming.
  2. warmup(batch_size) fixes the batch size and allocates all batch-shaped state.
  3. solve_offline(...), which solve_offline_numpy and solve_offline_torch wrap, places the waypoints, builds candidates, solves and retries trajectories, then retimes them with the last stage's retime and computes motion_time.
  4. solve_online(...) runs a fresh gradient solve with executed-history smoothing, or updates the retained MPPI action distribution.
Parameters
configMotionPlannerConfigrequired

Planner configuration.

robotRobotrequired

Robot model.

target_linksUnion[str, int, Sequence[Union[str, int]]]required

One target link or a sequence, using names or indices.

sceneOptional[WarpScene]= None

Optional collision scene.

deviceOptional[wp_device_type]= None

Warp device.

Members
method[source]
MotionPlanner.warmup(
batch_size: int
)

Fix the planner batch size and allocate its batch-shaped buffers.

Parameters
batch_sizeintrequired

Number of queries solved together.

method[source]
MotionPlanner.set_active_joint_mask(
joint_mask: Sequence[float]
)

Set which actuated joints IK and trajectory optimization may change; before the first solve it also limits the trajectory collision Jacobians to the active joints, which later masks must stay within.

Parameters
joint_maskSequence[float]required

One value per actuated joint; zero pins the joint.

method[source]
MotionPlanner.solve_offline(
waypoints: Sequence[Union[wp.array, Dict[str, wp.array]]],
start_T_world_base: Optional[wp.array] = None,
seed_q: Optional[wp.array] = None,
scene_indices: Optional[wp.array] = None,
segment_check_collision: Optional[Sequence[bool]] = None,
segment_fixed_duration: Optional[Sequence[Optional[float]]] = None,
segment_locked: Optional[Sequence[bool]] = None,
attached_objects: Optional[Sequence[AttachedObject]] = None,
fixed_relative_poses: Optional[Sequence[FixedRelativePose]] = None
) -> MotionPlanResult[wp.array]

Plan batched Warp trajectories through waypoints, start first and goal last; the NumPy and Torch APIs wrap it.

Lifecycle

  1. Parse segments: each segment's world collision flag, fixed duration and lock.
  2. Solve waypoint IK: each pose but a final goal by goal IK (scene collision, no self collision), resting at its neighbors' blend.
  3. Create split: each segment's frames by its duration, else evenly, then the locked frames and collision.
  4. Solve: _solve_offline_attempt with retries, one CUDA graph under cuda_graph="solve" but for durations; it builds each guess's goal by goal IK, its later poses' answers nearest its neighbors' blend, and straight lines through its waypoints, on frames resplit by the time each line needs; after the first stage, several segments resplit their frames again.
  5. Retime: every plan under the last stage's retime, its fixed segments held still, and its motion time.
Parameters
waypointsSequence[Union[wp.array, Dict[str, wp.array]]]required

Ordered list of at least two: joint arrays (batch, dofs) in robot.spec.actuated_joint_names order, wp_vec7 arrays (batch,) or (batch, target links) posing the target links, or dicts of target link names to wp_vec7 (batch,) posing some of them (other keys are ignored). A link a dict leaves out is free at its frame, so each link reaches its own waypoints in turn: the first guess keeps it at its pose of the waypoint before (blended toward any later joint waypoint; a start pose's at seed_q or the next joint waypoint, one needed), and the stages move it from there only as their costs pull. Every link passes each waypoint at the same instant, and the goal names all. An inner waypoint holds the links it names at its joints or IK answer, contact included.

start_T_world_baseOptional[wp.array]= None

A floating base's start poses, wp_vec7 (batch,); such plans take a start and a goal.

seed_qOptional[wp.array]= None

Optional IK seed: the goal IK's initialization, or the rest of a pose waypoint before any joint one.

scene_indicesOptional[wp.array]= None

Optional scene index per batch row; defaults to zero.

segment_check_collisionOptional[Sequence[bool]]= None

One flag per segment (waypoint i to i + 1); False drops world collision there (self collision stays), and from the checks of anchors only such segments touch, e.g. a contact. Defaults to all True. Every segment_* and waypoint_* list applies to all batch rows alike.

segment_fixed_durationOptional[Sequence[Optional[float]]]= None

Seconds per segment, or None to let the planner time it. A duration gives the segment round(duration / dt) frames (at least two) in the solve, keeps them through the resplit and is its time in interpolate; None segments share the rest of num_timesteps, which the durations must fit.

segment_lockedOptional[Sequence[bool]]= None

One flag per segment; True keeps its frames on the straight joint-space line between its waypoints (the tool's path between them is not straight), whatever it passes through, and holds the links both its waypoints name.

attached_objectsOptional[Sequence[AttachedObject]]= None

Objects the robot carries, on the spheres max_attached_spheres reserves on the target link each one's link is rigid with: checked by world and self collision over their segments (an inner waypoint: those both its segments carry), in the stages, their scores and waypoint and goal IK.

fixed_relative_posesOptional[Sequence[FixedRelativePose]]= None

Link pairs whose relative pose the gradient stages hold over their segments, at most max_fixed_relative_poses.

Returns
MotionPlanResult[wp.array]

last and only their rows return; more raise ValueError("Batch size mismatch").

method[source]
MotionPlanner.solve_offline_numpy(
waypoints: Sequence[Union[np.ndarray, Dict[str, np.ndarray]]],
start_T_world_base: Optional[np.ndarray] = None,
seed_q: Optional[np.ndarray] = None,
scene_indices: Optional[np.ndarray] = None,
segment_check_collision: Optional[Sequence[bool]] = None,
segment_fixed_duration: Optional[Sequence[Optional[float]]] = None,
segment_locked: Optional[Sequence[bool]] = None,
attached_objects: Optional[Sequence[AttachedObject]] = None,
fixed_relative_poses: Optional[Sequence[FixedRelativePose]] = None
) -> MotionPlanResult[np.ndarray]

NumPy wrapper for solve_offline: a pose waypoint is a dict of target links to 4x4 or [x, y, z, qw, qx, qy, qz] poses, unbatched values broadcast across queries, and the result holds owned copies.

Parameters
waypointsSequence[Union[np.ndarray, Dict[str, np.ndarray]]]required
start_T_world_baseOptional[np.ndarray]= None
seed_qOptional[np.ndarray]= None
scene_indicesOptional[np.ndarray]= None
segment_check_collisionOptional[Sequence[bool]]= None
segment_fixed_durationOptional[Sequence[Optional[float]]]= None
segment_lockedOptional[Sequence[bool]]= None
attached_objectsOptional[Sequence[AttachedObject]]= None
fixed_relative_posesOptional[Sequence[FixedRelativePose]]= None
Returns
MotionPlanResult[np.ndarray]
method[source]
MotionPlanner.solve_offline_torch(
waypoints: Sequence[Union[torch.Tensor, Dict[str, torch.Tensor]]],
start_T_world_base: Optional[torch.Tensor] = None,
seed_q: Optional[torch.Tensor] = None,
scene_indices: Optional[torch.Tensor] = None,
segment_check_collision: Optional[Sequence[bool]] = None,
segment_fixed_duration: Optional[Sequence[Optional[float]]] = None,
segment_locked: Optional[Sequence[bool]] = None,
attached_objects: Optional[Sequence[AttachedObject]] = None,
fixed_relative_poses: Optional[Sequence[FixedRelativePose]] = None
) -> MotionPlanResult[torch.Tensor]

Torch wrapper for solve_offline, as solve_offline_numpy with 4x4 pose matrices; plans without waypoint IK stay on device.

Parameters
waypointsSequence[Union[torch.Tensor, Dict[str, torch.Tensor]]]required
start_T_world_baseOptional[torch.Tensor]= None
seed_qOptional[torch.Tensor]= None
scene_indicesOptional[torch.Tensor]= None
segment_check_collisionOptional[Sequence[bool]]= None
segment_fixed_durationOptional[Sequence[Optional[float]]]= None
segment_lockedOptional[Sequence[bool]]= None
attached_objectsOptional[Sequence[AttachedObject]]= None
fixed_relative_posesOptional[Sequence[FixedRelativePose]]= None
Returns
MotionPlanResult[torch.Tensor]
method[source]
MotionPlanner.solve_online(
start_state: RobotState,
T_world_target: Optional[wp.array] = None,
target_q: Optional[wp.array] = None,
online_state: Optional[OnlineState] = None,
scene_indices: Optional[wp.array] = None
) -> Tuple[RobotState, OnlineState]

Solve one online planning update.

Parameters
start_stateRobotStaterequired

Current configurations with q shaped (batch, dofs), and base poses for a floating base.

T_world_targetOptional[wp.array]= None

Optional wp_vec7 pose goals, (batch,) or (batch, target links).

target_qOptional[wp.array]= None

Optional solved IK goals or joint-space goals with shape (batch, dofs).

online_stateOptional[OnlineState]= None

Optional state returned by the previous update.

scene_indicesOptional[wp.array]= None

Optional scene indices with shape (batch,).

Returns
Tuple[RobotState, OnlineState]

The optimized trajectories, a borrowed buffer, and the caller-owned online state.

method[source]
MotionPlanner.solve_online_numpy(
start_q: np.ndarray,
start_T_world_base: Optional[np.ndarray] = None,
T_world_target: Optional[np.ndarray] = None,
target_q: Optional[np.ndarray] = None,
online_state: Optional[OnlineState] = None,
scene_indices: Optional[np.ndarray] = None
) -> MotionPlanResult[np.ndarray]

NumPy wrapper for solve_online.

Parameters
start_qnp.ndarrayrequired
start_T_world_baseOptional[np.ndarray]= None
T_world_targetOptional[np.ndarray]= None
target_qOptional[np.ndarray]= None
online_stateOptional[OnlineState]= None
scene_indicesOptional[np.ndarray]= None
Returns
MotionPlanResult[np.ndarray]
method[source]
MotionPlanner.compute_metrics(
state: RobotState,
T_world_target: Optional[wp.array] = None,
target_q: Optional[wp.array] = None,
scene_indices: Optional[wp.array] = None,
position_threshold_m: float = 0.005,
orientation_threshold_rad: float = 0.05,
q_threshold: float = 0.05,
penetration_threshold_mm: float = 5.0,
out: Optional[MotionPlanMetrics[wp.array]] = None
) -> MotionPlanMetrics[wp.array]

Endpoint accuracy of state against exactly one pose or joint goal, independent of term weights.

Parameters
stateRobotStaterequired

Trajectories with shape (batch, frames, dofs); any planner's output works.

T_world_targetOptional[wp.array]= None

Optional batched pose goals.

target_qOptional[wp.array]= None

Optional batched joint-space goals.

scene_indicesOptional[wp.array]= None
position_threshold_mfloat= 0.005

Success threshold on translation error.

orientation_threshold_radfloat= 0.05

Success threshold on rotation error.

q_thresholdfloat= 0.05

Success threshold on joint error, for a joint goal.

penetration_threshold_mmfloat= 5.0

Success threshold on scene penetration.

outOptional[MotionPlanMetrics[wp.array]]= None

Buffers to write in place, from an earlier call; allocated when omitted.

Returns
MotionPlanMetrics[wp.array]

Endpoint errors and success per batch element.

method[source]
MotionPlanner.compute_metrics_numpy(
q_traj: np.ndarray,
T_world_base_traj: Optional[np.ndarray] = None,
T_world_target: Optional[np.ndarray] = None,
target_q: Optional[np.ndarray] = None,
scene_indices: Optional[np.ndarray] = None,
position_threshold_m: float = 0.005,
orientation_threshold_rad: float = 0.05,
q_threshold: float = 0.05,
penetration_threshold_mm: float = 5.0
) -> MotionPlanMetrics[np.ndarray]

compute_metrics for numpy inputs, returning numpy arrays.

Parameters
q_trajnp.ndarrayrequired
T_world_base_trajOptional[np.ndarray]= None
T_world_targetOptional[np.ndarray]= None
target_qOptional[np.ndarray]= None
scene_indicesOptional[np.ndarray]= None
position_threshold_mfloat= 0.005
orientation_threshold_radfloat= 0.05
q_thresholdfloat= 0.05
penetration_threshold_mmfloat= 5.0
Returns
MotionPlanMetrics[np.ndarray]
robokit.helpers.motion_plan.MotionPlannerConfig(
num_timesteps: int = 32,
dt: float = 0.25,
max_attached_spheres: int = 0,
max_fixed_relative_poses: int = 0,
fixed_relative_pose_weight: Tuple[float, float] = (3000.0, 1000.0),
acceleration_limit: float = 15.0,
jerk_limit: float = 500.0,
duration_weight: float = 0.0,
goal_ik: IKSolverConfig = build_default_motion_plan_ik_config(),
init_sample_range: float = 0.2,
init_detour_samples: Tuple[int, int] = (16, 64),
stages: Tuple[Union[GradientTrajectoryOptimizerConfig, MppiTrajectoryOptimizerConfig], ...] = (lambda: (build_default_gradient_trajectory_optimizer_config(),))(),
stage_dt_scale: float = 0.0,
stage_retime: Optional[RetimeConfig] = None,
stage_retries: int = 0,
stage_retry_iters: int = 0,
stage_retry_dt_growth: float = 1.025,
max_retries: int = 1,
retime: Optional[RetimeConfig] = None,
resplit_waypoints: bool = True,
cuda_graph: Literal['none', 'stages', 'solve'] = 'stages'
)

Configuration for offline or online motion planning.

Parameters
num_timestepsint= 32
dtfloat= 0.25
max_attached_spheresint= 0
max_fixed_relative_posesint= 0
fixed_relative_pose_weightTuple[float, float]= (3000.0, 1000.0)
acceleration_limitfloat= 15.0
jerk_limitfloat= 500.0
duration_weightfloat= 0.0
goal_ikIKSolverConfig= build_default_motion_plan_ik_config()
init_sample_rangefloat= 0.2
init_detour_samplesTuple[int, int]= (16, 64)
stagesTuple[Union[GradientTrajectoryOptimizerConfig, MppiTrajectoryOptimizerConfig], ...]= (lambda: (build_default_gradient_trajectory_optimizer_config(),))()
stage_dt_scalefloat= 0.0
stage_retimeOptional[RetimeConfig]= None
stage_retriesint= 0
stage_retry_itersint= 0
stage_retry_dt_growthfloat= 1.025
max_retriesint= 1
retimeOptional[RetimeConfig]= None
resplit_waypointsbool= True
cuda_graphLiteral['none', 'stages', 'solve']= 'stages'
Members
attribute[source]
MotionPlannerConfig.num_timesteps: int = 32

Steps per plan; a plan has one more frame.

attribute[source]
MotionPlannerConfig.dt: float = 0.25

Planning step (s).

attribute[source]
MotionPlannerConfig.max_attached_spheres: int = 0

Collision spheres reserved on each target link for attached_objects, which every solve places there.

attribute[source]
MotionPlannerConfig.max_fixed_relative_poses: int = 0

Link pairs reserved for fixed_relative_poses, which every solve sets.

attribute[source]
MotionPlannerConfig.fixed_relative_pose_weight: Tuple[float, float] = (3000.0, 1000.0)

Position and rotation weights of fixed_relative_poses, per m and per rad of drift.

attribute[source]
MotionPlannerConfig.acceleration_limit: float = 15.0

Joint acceleration limit (rad/s²): the stages' bounds and ranking, MPPI rollouts, retimes and resplits.

attribute[source]
MotionPlannerConfig.jerk_limit: float = 500.0

Joint jerk limit (rad/s³): the stages' bounds and ranking, retimes and resplits.

attribute[source]
MotionPlannerConfig.duration_weight: float = 0.0

0: the last stage also optimizes each free segment's duration, from the query's step, at this cost per second

of plan time; its derivative bounds use the optimized durations, which the returned retime only lengthens.

attribute[source]
MotionPlannerConfig.goal_ik: IKSolverConfig = field(default_factory=build_default_motion_plan_ik_config)

Goal IK, which turns pose goals into joint goals; fewer goals than trajectory candidates repeat across them.

attribute[source]
MotionPlannerConfig.init_sample_range: float = 0.2

Spread of the sampled seed candidates, as a fraction of each joint range; MPPI stages sample their own.

attribute[source]
MotionPlannerConfig.init_detour_samples: Tuple[int, int] = (16, 64)

Random-midpoint detours per detour candidate (first round, retries); the shortest collision-free one seeds it.

attribute[source]
MotionPlannerConfig.stages: Tuple[Union[GradientTrajectoryOptimizerConfig, MppiTrajectoryOptimizerConfig], ...] = field(default_factory=lambda: (build_default_gradient_trajectory_optimizer_config(),))

Optimizer stages, run in order on the candidate population every planning round.

attribute[source]
MotionPlannerConfig.stage_dt_scale: float = 0.0

0: each later stage optimizes at this multiple of the previous stage's fastest feasible retimed step.

attribute[source]
MotionPlannerConfig.stage_retime: Optional[RetimeConfig] = None

Retime between stages (stage_dt_scale); its minimum_dt floors the later stages' steps.

attribute[source]
MotionPlannerConfig.stage_retries: int = 0

Reruns of a failed last stage whose predecessor passed, at a growing step; each query keeps its better plan.

attribute[source]
MotionPlannerConfig.stage_retry_iters: int = 0

Iterations of each stage retry.

attribute[source]
MotionPlannerConfig.stage_retry_dt_growth: float = 1.025

Factor each stage retry grows the step by.

attribute[source]
MotionPlannerConfig.max_retries: int = 1

Rounds retrying failed plans with new goals and seeds; needs a gradient first stage with several candidates.

attribute[source]
MotionPlannerConfig.retime: Optional[RetimeConfig] = None

Retime of the returned plan; None keeps the planning step.

attribute[source]
MotionPlannerConfig.resplit_waypoints: bool = True

Resplit a waypoint plan's frames by the time each part needs, so a short segment takes fewer: its straight lines' before the solve, and its paths' after the first stage, which the later gradient stages smooth; False keeps the even split.

attribute[source]
MotionPlannerConfig.cuda_graph: Literal['none', 'stages', 'solve'] = 'stages'

CUDA graphs: none, each stage's and goal IK's solve, or the whole NumPy solve with its device-side retries; a capture makes a planner's first solve take seconds longer.

robokit.helpers.motion_plan.MotionPlanResult(
q_traj: ArrayT,
T_world_base_traj: Optional[ArrayT] = None,
motion_time: Optional[ArrayT] = None,
online_state: Optional[OnlineState] = None,
waypoint_indices: Optional[Tuple[int, ...]] = None,
segment_fixed_duration: Optional[Tuple[Optional[float], ...]] = None,
segment_duration: Optional[ArrayT] = None
)
Bases: Generic[ArrayT]

Planner output with offline timing or online state.

Offline: q_traj (batch, frames, dofs) with frames = num_timesteps + 1, motion_time (batch,) (s) and waypoint_indices each waypoint's frame in q_traj (one per waypoint, 0 first, the same for every row).

Parameters
q_trajArrayTrequired
T_world_base_trajOptional[ArrayT]= None
motion_timeOptional[ArrayT]= None
online_stateOptional[OnlineState]= None
waypoint_indicesOptional[Tuple[int, ...]]= None
segment_fixed_durationOptional[Tuple[Optional[float], ...]]= None
segment_durationOptional[ArrayT]= None
Members
attribute[source]
MotionPlanResult.q_traj: ArrayT
attribute[source]
MotionPlanResult.T_world_base_traj: Optional[ArrayT] = None
attribute[source]
MotionPlanResult.motion_time: Optional[ArrayT] = None
attribute[source]
MotionPlanResult.online_state: Optional[OnlineState] = None
attribute[source]
MotionPlanResult.waypoint_indices: Optional[Tuple[int, ...]] = None
attribute[source]
MotionPlanResult.segment_fixed_duration: Optional[Tuple[Optional[float], ...]] = None
attribute[source]
MotionPlanResult.segment_duration: Optional[ArrayT] = None
method[source]
MotionPlanResult.interpolate(
dt: float,
speed_scale: float = 1.0
) -> ArrayT

Joint trajectories sampled every dt (s) or slightly less across motion_time.

Each segment between waypoints spreads ceil(its time / dt) steps evenly over its frames, so every waypoint is a sample; a plan then holds its last frame, so a batch shares one length. A segment with a fixed segment_fixed_duration takes that time; the others run at the plan's retimed step or segment_duration.

Example

python
>>> import numpy as np
>>> q_traj = np.array([[[0.0], [1.0], [2.0]]], np.float32)
>>> MotionPlanResult(q_traj, motion_time=np.array([1.0], np.float32)).interpolate(0.25)[0, :, 0]
array([0. , 0.5, 1. , 1.5, 2. ], dtype=float32)
Parameters
dtfloatrequired

Control step (s).

speed_scalefloat= 1.0

Fraction of the plan's speed; 0.5 plays its retimed segments over twice their time.

Returns
ArrayT

Joint positions with shape (batch, samples, dofs), NumPy or Torch as q_traj.

Objective configuration

GradientTrajectoryOptimizerConfig and MppiTrajectoryOptimizerConfig accept terms, a list of ResidualTask templates. Objective weights and cost options are task constructor parameters. Use config.task(name) to retrieve a named task; for TrajectoryTask, configure its dense_task. deepcopy(config) creates an independent preset whose task attributes can be assigned directly. Config-level cost-weight arguments have been removed. IK objectives are supplied through MotionPlannerConfig.goal_ik.