Motion Planning¶
Motion Planner¶
MotionPlanner(robot_name: str, config: PlannerConfig | None = None, pointcloud: np.ndarray | None = None, base_config: np.ndarray | None = None, constraints: list | None = None, costs: list | None = None)
¶
Motion planner using OMPL + VAMP C++ backend.
All internals are private. The public API accepts and returns only numpy arrays.
For subgroup planners the helper methods :meth:extract_config,
:meth:embed_config, and :meth:embed_path convert between the
reduced DOF space used by the planner and the full 24-DOF body
configuration.
Source code in autolife_planning/planning/motion_planner.py
bounds: tuple[np.ndarray, np.ndarray]
property
¶
(lower, upper) joint limits of the active DOF.
joint_names: list[str]
property
¶
Joint names controlled by this planner, in DOF order.
is_subgroup: bool
property
¶
True if this planner controls a subset of the full body.
subgroup_indices: np.ndarray | None
property
¶
Indices of this planner's joints in the full 24-DOF config.
base_config: np.ndarray
property
¶
The 24-DOF stance frozen for joints outside this subgroup.
has_pointcloud: bool
property
¶
True if a pointcloud is currently registered.
clear_constraints() -> None
¶
set_constraints(constraints: list) -> None
¶
Replace all constraints: clear existing, then push new ones.
clear_costs() -> None
¶
set_costs(costs: list) -> None
¶
add_pointcloud(pointcloud: np.ndarray) -> None
¶
Set the scene pointcloud, replacing any previously-registered cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
Source code in autolife_planning/planning/motion_planner.py
remove_pointcloud() -> bool
¶
Drop the currently-registered pointcloud.
Returns False if there was no cloud to remove.
filter_pointcloud(pointcloud: np.ndarray, min_dist: float, max_range: float, origin: np.ndarray | list[float], workspace_min: np.ndarray | list[float], workspace_max: np.ndarray | list[float], cull: bool = True) -> np.ndarray
¶
Spatially downsample a point cloud via Morton-curve sorting.
Keeps one representative point per min_dist neighbourhood and
discards points farther than max_range from origin or
outside the [workspace_min, workspace_max] bounding box.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
min_dist
|
float
|
Minimum distance between two retained points. |
required |
max_range
|
float
|
Maximum distance from |
required |
origin
|
ndarray | list[float]
|
|
required |
workspace_min
|
ndarray | list[float]
|
|
required |
workspace_max
|
ndarray | list[float]
|
|
required |
cull
|
bool
|
If |
True
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in autolife_planning/planning/motion_planner.py
filter_self_from_pointcloud(pointcloud: np.ndarray, point_radius: float, config: np.ndarray) -> np.ndarray
¶
Remove points that collide with the robot body or environment.
Computes forward kinematics at config, then drops every
point whose inflated sphere (radius point_radius) overlaps
any robot collision sphere or any registered obstacle.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
pointcloud
|
ndarray
|
|
required |
point_radius
|
float
|
Inflation radius for each point. |
required |
config
|
ndarray
|
Active-DOF configuration (same space as |
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in autolife_planning/planning/motion_planner.py
set_subgroup(robot_name: str, base_config: np.ndarray | None = None) -> None
¶
Switch active joints without rebuilding the collision environment.
Clears all constraints. The pointcloud is preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_name
|
str
|
Subgroup name from |
required |
base_config
|
ndarray | None
|
24-DOF frozen config for inactive joints. Defaults to the previously stored base config. |
None
|
Source code in autolife_planning/planning/motion_planner.py
extract_config(full_config: np.ndarray) -> np.ndarray
¶
Extract this planner's joints from a full 24-DOF configuration.
Source code in autolife_planning/planning/motion_planner.py
embed_config(config: np.ndarray, base_config: np.ndarray | None = None) -> np.ndarray
¶
Embed a subgroup config into a full 24-DOF configuration.
base_config defaults to the planner's stored base — the same
24-DOF values the C++ collision checker injects for inactive
joints — so the embedded config matches what was validated.
Source code in autolife_planning/planning/motion_planner.py
embed_path(path: np.ndarray, base_config: np.ndarray | None = None) -> np.ndarray
¶
Convert a subgroup path (N, sub_dof) to (N, 24).
base_config defaults to the planner's stored base — the same
24-DOF values the C++ collision checker injects for inactive
joints — so the embedded path matches what was validated.
Source code in autolife_planning/planning/motion_planner.py
plan(start: np.ndarray, goal: np.ndarray, time_limit: float | None = None) -> PlanningResult
¶
Plan a collision-free path from start to goal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
ndarray
|
Start configuration (active DOF). |
required |
goal
|
ndarray
|
Goal configuration (active DOF). |
required |
time_limit
|
float | None
|
Optional per-call override for the solver time
limit. Defaults to |
None
|
Source code in autolife_planning/planning/motion_planner.py
simplify_path(path: np.ndarray, time_limit: float = 1.0) -> np.ndarray
¶
Run OMPL's shortcut-based path simplifier on path.
Same pipeline plan(..., simplify=True) uses internally
(reduceVertices + collapseCloseVertices + shortcutPath
+ B-spline smoothing), but detached so you can apply it to any
path you already have — e.g. replay an old plan with a
different collision environment.
Shortcuts only consult the motion validator. Custom soft
costs (:class:Cost) are ignored; for cost-driven plans, run
:meth:plan with simplify=False and leave the path
untouched unless you've explicitly decided shortcut shaping
is acceptable.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
ndarray
|
|
required |
time_limit
|
float
|
Wall-clock budget for the simplifier, seconds. |
1.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in autolife_planning/planning/motion_planner.py
interpolate_path(path: np.ndarray, count: int = 0, resolution: float = 64.0) -> np.ndarray
¶
Densify path with uniform waypoints along every edge.
Three modes (pick one; the other must be zero):
* ``count > 0`` — exactly that many total waypoints
distributed proportionally to edge length.
* ``resolution > 0.0`` — ``ceil(edge_length * resolution)``
waypoints per edge (uniform density in state-space
distance — the default).
* both ``0`` — OMPL's default longest-valid-segment
fraction.
Uses StateSpace::interpolate internally, so the inserted
states stay on the constraint manifold for projected state
spaces as well as flat ones. No collision check is performed
— the densification only lifts points along the existing
piecewise-linear edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
ndarray
|
|
required |
count
|
int
|
Exact total waypoint count if |
0
|
resolution
|
float
|
Waypoints per unit state-space distance if
|
64.0
|
Returns:
| Type | Description |
|---|---|
ndarray
|
|
Source code in autolife_planning/planning/motion_planner.py
validate(configuration: np.ndarray) -> bool
¶
validate_batch(configurations: np.ndarray) -> np.ndarray
¶
Batched collision check — one SIMD block per rake configs.
Packs rake distinct configurations into a single VAMP
ConfigurationBlock<rake> and runs one fkcc<rake> call
per block, so N queries cost ceil(N / rake) SIMD
sweeps in the common case. When a packed block fails we fall
back to per-lane single-state checks for that block only, so
the returned array is always exactly one bool per input.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configurations
|
ndarray
|
|
required |
Returns:
| Type | Description |
|---|---|
ndarray
|
|
ndarray
|
|
Source code in autolife_planning/planning/motion_planner.py
sample_valid(max_samples: int = 100000) -> np.ndarray
¶
Sample a random collision-free configuration.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If none of |
Source code in autolife_planning/planning/motion_planner.py
Factory Function¶
create_planner(robot_name: str = 'autolife', config: PlannerConfig | None = None, pointcloud: np.ndarray | None = None, base_config: np.ndarray | None = None, constraints: list | None = None, costs: list | None = None) -> MotionPlanner
¶
Create a motion planner for any robot or subgroup.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
robot_name
|
str
|
Robot or subgroup name. Use :func: |
'autolife'
|
config
|
PlannerConfig | None
|
Planner configuration (uses defaults if None). |
None
|
pointcloud
|
ndarray | None
|
|
None
|
base_config
|
ndarray | None
|
24-DOF values to inject for joints not controlled
by this planner (i.e. the frozen joints of a subgroup).
Defaults to |
None
|
constraints
|
list | None
|
Optional list of
:class: |
None
|
costs
|
list | None
|
Optional list of
:class: |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
MotionPlanner
|
class: |
Source code in autolife_planning/planning/motion_planner.py
Available Robots¶
available_robots() -> list[str]
¶
Return all available robot names for planning.
Constraint¶
Constraint(residual: ca.SX, q_sym: ca.SX, name: str = 'constraint')
dataclass
¶
A user-defined holonomic constraint, JIT-compiled via CasADi.
Constructing this class takes the symbolic Jacobian via
ca.jacobian(residual, q_sym), generates C and compiles it to a
cached shared library (see :func:compile_function).
SymbolicContext(subgroup: str, base_config: np.ndarray | None = None)
¶
CasADi-friendly view of the planner's active subgroup.
Holds the CasADi symbolic joint vector q matching the subgroup's
active dimension. Joints outside the subgroup are frozen at
base_config, the same 24-DOF body vector
([x, y, theta, j0..j20]) the planner uses.
Source code in autolife_planning/planning/symbolic.py
link_pose(link_name: str, q_active: ca.SX | None = None) -> ca.SX
¶
Symbolic 4x4 world pose of a URDF link.
Uses self.q unless another active-dimension symbol is given.
Source code in autolife_planning/planning/symbolic.py
link_translation(link_name: str, q_active: ca.SX | None = None) -> ca.SX
¶
Symbolic 3-vector: link position in world frame.
link_rotation(link_name: str, q_active: ca.SX | None = None) -> ca.SX
¶
evaluate_link_pose(link_name: str, q_active_numeric: np.ndarray) -> np.ndarray
¶
Compute a NUMERIC 4x4 link pose (handy for building targets).
Source code in autolife_planning/planning/symbolic.py
project(q_init: np.ndarray, residual: ca.SX, tol: float = 1e-08, max_iters: int = 100) -> np.ndarray
¶
Project a joint configuration onto the manifold residual(q) = 0.
Runs damped Gauss-Newton on the CasADi Jacobian — the same
iteration OMPL's ProjectedStateSpace runs internally — so
the returned config will pass the planner's tolerance check
and can be used directly as a start or goal state.
Source code in autolife_planning/planning/symbolic.py
Cost¶
Cost(expression: ca.SX, q_sym: ca.SX, name: str = 'cost', weight: float = 1.0)
dataclass
¶
A user-defined soft path cost, JIT-compiled via CasADi.
Pass a scalar CasADi expression in the planner's active joint vector. Construction triggers (on cold cache):
1. symbolic gradient via ``ca.gradient(expression, q_sym)``
2. C code generation and compilation to a cached shared library
(see :func:`~autolife_planning.planning.symbolic.compile_function`)
The C++ planner wraps the loaded function in an OMPL
StateCostIntegralObjective — so per-state values are
trapezoidally integrated along every motion, which is what
RRT*-family optimal planners expect.
The expression must be non-negative (OMPL objectives accumulate
with operator+ and the optimal-planner tooling assumes the
zero cost is the minimum). We don't enforce this at runtime
because that would require evaluating the symbolic expression,
but violating it produces nonsensical RRT* solutions.
Low-level C++ binding¶
OmplVampPlanner
¶
OMPL planner with VAMP SIMD-accelerated collision checking.
Two construction modes:
OmplVampPlanner()— full body, 24 DOF (3 base + 21 joints).OmplVampPlanner(active_indices, frozen_config)— subgroup planner over the joints listed inactive_indices; the C++ collision checker injectsfrozen_configfor every other slot in the 24-DOF body on every state and motion validity query.
add_pointcloud(points: NDArray[np.float32], point_radius: float) -> None
¶
Set the scene pointcloud.
The planner holds at most one cloud; calling this replaces any previously-registered cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
points
|
NDArray[float32]
|
|
required |
point_radius
|
float
|
Inflation radius applied to every cloud point. |
required |
Source code in autolife_planning/_ompl_vamp.pyi
remove_pointcloud() -> bool
¶
has_pointcloud() -> bool
¶
add_sphere(center: Sequence[float], radius: float) -> None
¶
clear_environment() -> None
¶
add_compiled_constraint(so_path: str, symbol_name: str, ambient_dim: int, co_dim: int) -> None
¶
Load a CasADi-generated shared library as an OMPL constraint.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
so_path
|
str
|
Path to the compiled |
required |
symbol_name
|
str
|
CasADi function symbol name inside the library. |
required |
ambient_dim
|
int
|
Dimension of the joint space (must match
:meth: |
required |
co_dim
|
int
|
Number of constraint equations (rows of the residual). |
required |
Source code in autolife_planning/_ompl_vamp.pyi
clear_constraints() -> None
¶
num_constraints() -> int
¶
add_compiled_cost(so_path: str, symbol_name: str, ambient_dim: int, weight: float = 1.0) -> None
¶
Load a CasADi-generated shared library as a soft path cost.
The cost is wrapped as an ompl::StateCostIntegralObjective
— trapezoidally integrated along every motion — and drives
the search of asymptotically-optimal planners (RRT, BIT,
AIT*, …). Multiple costs are summed with their weight.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
so_path
|
str
|
Path to the compiled |
required |
symbol_name
|
str
|
CasADi function symbol name inside the library. |
required |
ambient_dim
|
int
|
Dimension of the joint space (must match
:meth: |
required |
weight
|
float
|
Positive scalar multiplier applied to the cost. |
1.0
|
Source code in autolife_planning/_ompl_vamp.pyi
clear_costs() -> None
¶
num_costs() -> int
¶
plan(start: NDArray[np.float64], goal: NDArray[np.float64], planner_name: str = 'rrtc', time_limit: float = 10.0, simplify: bool = True, interpolate: bool = True, resolution: float = 64.0) -> PlanResult
¶
Plan a collision-free path from start to goal.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
start
|
NDArray[float64]
|
Active-DOF start configuration (length :meth: |
required |
goal
|
NDArray[float64]
|
Active-DOF goal configuration (length :meth: |
required |
planner_name
|
str
|
OMPL planner identifier (e.g. |
'rrtc'
|
time_limit
|
float
|
Solver time limit in seconds. |
10.0
|
simplify
|
bool
|
If true, run |
True
|
interpolate
|
bool
|
If true, densify the simplified path. |
True
|
resolution
|
float
|
Waypoints per unit of state-space distance. Each
edge of length |
64.0
|
Source code in autolife_planning/_ompl_vamp.pyi
simplify_path(path: NDArray[np.float64], time_limit: float = 1.0) -> NDArray[np.float64]
¶
Run OMPL's shortcut simplifier on a waypoint list.
Reuses the current collision environment and constraints. Shortcuts only consult the motion validator, so custom soft costs are ignored. Returns the simplified path as waypoints.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
NDArray[float64]
|
|
required |
time_limit
|
float
|
Simplifier wall-clock budget, seconds. |
1.0
|
Source code in autolife_planning/_ompl_vamp.pyi
interpolate_path(path: NDArray[np.float64], count: int = 0, resolution: float = 64.0) -> NDArray[np.float64]
¶
Densify a waypoint list along its existing edges.
Pass at most one of count (exact total waypoints,
distributed by edge length) or resolution (waypoints per
unit of state-space distance). Both zero falls back to OMPL's
default longest-valid-segment fraction. No collision check is
performed — densification only calls StateSpace::interpolate
on the existing edges.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
NDArray[float64]
|
|
required |
count
|
int
|
Total waypoint count if > 0. |
0
|
resolution
|
float
|
Waypoints per unit distance if > 0.0. |
64.0
|
Source code in autolife_planning/_ompl_vamp.pyi
validate(config: NDArray[np.float64]) -> bool
¶
Return True if config is collision-free.
config must have length :meth:dimension. Subgroup
planners expand it to a full 24-DOF state with the stored
frozen_config before checking.
Source code in autolife_planning/_ompl_vamp.pyi
validate_batch(configs: NDArray[np.float64]) -> NDArray[np.bool_]
¶
Batched collision check — deep SIMD, per-config result.
Packs up to rake distinct configurations into a single
VAMP ConfigurationBlock<rake> so one Robot::fkcc<rake>
call sphere-FKs and collision-checks all of them
simultaneously — the same SIMD primitive the motion-edge
validator uses for interpolated samples, fed independent
configs per lane instead.
Returns one bool per input config in input order. In the
common case (most configs valid) costs ceil(N / rake)
SIMD calls; when a packed block fails, only that block falls
back to per-lane single-state checks.
configs has shape (N, dimension()).
Subgroup planners expand each to a full 24-DOF state with
the stored frozen_config before packing.
Source code in autolife_planning/_ompl_vamp.pyi
dimension() -> int
¶
lower_bounds() -> NDArray[np.float64]
¶
upper_bounds() -> NDArray[np.float64]
¶
filter_pointcloud(points: NDArray[np.float32], min_dist: float, max_range: float, origin: Sequence[float], workspace_min: Sequence[float], workspace_max: Sequence[float], cull: bool = True) -> NDArray[np.float32]
¶
Morton-curve downsampling of an (N, 3) cloud, optionally
culled to max_range from origin and to the workspace box.
Source code in autolife_planning/_ompl_vamp.pyi
filter_self_from_pointcloud(points: NDArray[np.float32], point_radius: float, config: NDArray[np.float64]) -> NDArray[np.float32]
¶
Drop the points of an (N, 3) cloud that overlap the robot's
spheres at config (active DOF) or the current environment.
Source code in autolife_planning/_ompl_vamp.pyi
set_subgroup(active_indices: Sequence[int], frozen_config: Sequence[float]) -> None
¶
Switch to a different subgroup without rebuilding the environment.
Clears all constraints. The pointcloud and collision geometry are preserved.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
active_indices
|
Sequence[int]
|
Positions in the full 24-DOF body that this planner will plan over, in DOF order. |
required |
frozen_config
|
Sequence[float]
|
24-DOF stance to inject for every joint not
in |
required |
Source code in autolife_planning/_ompl_vamp.pyi
set_full_body() -> None
¶
Switch back to full-body planning (24 DOF).
Clears all constraints. The pointcloud and collision geometry are preserved.