Development Guide

Workflow

  1. Branch: feat/…, fix/…, chore/… — include the issue number (e.g. feat/42-new-planner).
  2. Implement: follow conventions below.
  3. Test: run all applicable test suites (see checklist).
  4. PR: fill out the PR template and paste test output.

Every task should have a GitHub issue. Reference it in commits (fixes #42) and in the PR body (Closes #42).

Environment

The recommended environment uses Pixi. All dependencies (CUDA headers, Eigen, nanobind, pytest) are pinned in pixi.toml.

pixi run install   # build & install Python bindings
pixi run test      # pytest tests/ -v
pixi run tracking  # quadrotor lemniscate tracking with plot

For a standalone C++ build:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

Test checklist

Before closing a GitHub issue, run all applicable suites:

Trajectory tracking controllers

pixi run tracking
  • Automated assert: RMSE < 2.0 m (regression guard).
  • Paper baseline: ~0.69 m — report the actual value in the closing comment.

Core kernel / config

./build/tests/mppi_gtest      # must report [  PASSED  ]
./build/tests/i_mppi_gtest    # must report [  PASSED  ]
./build/tests/pendulum_test   # prints PASSED
./build/tests/bspline_test    # prints PASSED
./build/tests/fsmi_unit_test  # prints PASSED

FSMI / occupancy grid

./build/tests/fsmi_unit_test

Python bindings

pixi run install
pixi run test

Adding new dynamics and cost

Every controller is templated on a (Dynamics, Cost) pair — you need both.

1. Dynamics

Create include/mppi/instantiations/my_system.cuh:

struct MySystem {
    static constexpr int STATE_DIM   =;
    static constexpr int CONTROL_DIM =;

    /* Called once per GPU thread, inside the rollout loop. */
    __device__ void step(
        const float* x, const float* u, float* x_next, float dt
    ) const {}
};

2. Cost

In the same header (or a separate one):

struct MyCost {
    /* Running cost at each timestep. */
    __device__ float compute(const float* x, const float* u, int t) const {}

    /* Terminal cost at the end of the horizon. */
    __device__ float terminal(const float* x) const {}
};

3. Test

Add tests/my_system_gtest.cu and register it in tests/CMakeLists.txt:

add_executable(my_system_gtest my_system_gtest.cu)
target_link_libraries(my_system_gtest cuda_mppi_lib GTest::gtest_main)

4. Python binding (optional)

In bindings/bindings.cu, instantiate the controller and expose it:

using MyCtrl = MPPIController<MySystem, MyCost>;
nb::class_<MyCtrl>(m, "MySystemMPPI")
    .def(nb::init<MPPIConfig, MySystem, MyCost>())
    .def("compute",     &MyCtrl::compute)
    .def("get_action",  &MyCtrl::get_action)
    .def("shift",       &MyCtrl::shift);

Coding conventions

  • CUDA: __device__ for rollout-path code. No malloc/new inside kernels.
  • Error handling: wrap all cudaMalloc, cudaMemcpy, curandGenerate* calls with HANDLE_ERROR(...).
  • Sync: call cudaDeviceSynchronize() after every kernel launch whose result is read on the host.
  • Comments: use codedoc-style block comments (/* … */) for prose; // for short inline notes.
  • Commits: Conventional Commitsfeat, fix, perf, refactor, test, docs, chore.