Phase 1 & 2 - Development Summary

Phase 1: Core Rust Library Design (oxmpl)

The project began with the goal of creating a modern, safe, and extensible motion planning library in Rust. The architecture was built around a set of generic traits to ensure flexibility and composability.

Key Architectural Components

  1. State Trait: A marker trait representing a single point or configuration in a state space (e.g., a robot's joint angles).
  2. StateSpace Trait: The core trait defining the "universe" for states. It's responsible for the geometry and topology of the space.
    • Methods: distance(), interpolate(), enforce_bounds(), satisfies_bounds(), and sample_uniform().
    • Properties: It stores fundamental properties of the space, such as its dimension and bounds.
    • Error Handling: The sample_uniform method was designed to return a Result to gracefully handle sampling from unbounded spaces.
  3. StateValidityChecker Trait: A simple trait with an is_valid(&self, state: &S) -> bool method. This allows users to provide problem-specific collision checking logic.
  4. Goal Trait Hierarchy: A tiered system of traits was designed to give planners different levels of information about the goal.
    • Goal: The base trait, answers "is the goal satisfied?" with is_satisfied().
    • GoalRegion: A subtrait of Goal, answers "how far is a state from the goal?" with distance_goal(). This provides a crucial heuristic for guided planners.
    • GoalSampleableRegion: A subtrait of GoalRegion, answers "give me a random state in the goal" with sample_goal(). This is essential for goal-biasing and bidirectional planners.
  5. Planner Trait: A generic trait that defines the contract for all planning algorithms.
    • setup(&mut self, problem_def, validity_checker): Configures the planner for a specific problem.
    • solve(&self, timeout): Runs the planning algorithm.

Concrete Implementations

Project Organization and Testing

Phase 2: Python Bindings (oxmpl-py)

The goal was to provide a high-performance Rust backend with a flexible, Pythonic user interface.

Core Technology Choices

Binding Architecture and Key Patterns

  1. Namespacing: To mirror OMPL's clean API, the bindings were organized into Python submodules (oxmpl_py.base, oxmpl_py.geometric.planners). This was achieved by creating corresponding Rust modules and using m.add_submodule() in the main #[pymodule] function.
  2. The Callback Bridge (Key Feature): To allow users to define problem logic in Python, a robust callback bridge was created:
    • Rust Wrapper Structs: Created PyStateValidityChecker and PyGoal structs in Rust.
    • Holding Python Objects: These structs hold a PyObject, which is a safe, reference-counted handle to a user's Python function or class instance.
    • Implementing Rust Traits: The core Rust traits (StateValidityChecker, Goal, GoalRegion, etc.) were implemented for these wrapper structs.
    • Calling Python from Rust: Inside the trait methods, the implementation acquires Python's Global Interpreter Lock (GIL), calls the method on the stored Python object (e.g., self.instance.call_method1(...)), and handles the conversion of arguments, return values, and exceptions. This makes the planner completely unaware that it's executing Python code.
  3. Code Generation with Macros: To solve the problem of repetitive boilerplate code when wrapping multiple planners for multiple state spaces, a define_planner_wrapper! macro was created.
    • This macro_rules! macro acts as a template that generates the full #[pyclass] and #[pymethods] boilerplate for any given planner.
    • This makes the binding code highly scalable and maintainable. Adding a new planner binding becomes a single macro invocation.

Final Result

The final product is a two-crate workspace:

  1. oxmpl: A pure Rust library with a generic, powerful, and type-safe API for motion planning.
  2. oxmpl-py: A lightweight bindings crate that exposes oxmpl's functionality to Python. It provides a clean, namespaced, and Pythonic API where users can define their own problem logic (like obstacle checkers and goal definitions) in pure Python functions and classes, which are then seamlessly called by the high-performance Rust planner backend.
    This architecture successfully combines the performance and safety of Rust with the flexibility and ease of use of Python.