|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
|
prox_mpc repositoryprox_mpc_benchmark prox_mpc_controller prox_mpc_core prox_mpc_demo prox_mpc_msgs prox_mpc_obstacle_tracker prox_mpc_test_models |
ROS Distro
|
Repository Summary
| Checkout URI | https://github.com/simone-contorno/prox_mpc.git |
| VCS Type | git |
| VCS Version | main |
| Last Updated | 2026-07-28 |
| Dev Status | DEVELOPED |
| Released | RELEASED |
| Contributing |
Help Wanted (-)
Good First Issues (-) Pull Requests to Review (-) |
Packages
| Name | Version |
|---|---|
| prox_mpc_benchmark | 1.0.0 |
| prox_mpc_controller | 1.0.0 |
| prox_mpc_core | 1.0.0 |
| prox_mpc_demo | 1.0.0 |
| prox_mpc_msgs | 1.0.0 |
| prox_mpc_obstacle_tracker | 1.0.0 |
| prox_mpc_test_models | 1.0.0 |
README
ProxMPC
Nonlinear Model Predictive Control for ROS 2, packaged as a reusable core and a Nav2 controller plugin.
The controller solves the nonlinear optimal-control problem with a Sequential Quadratic Programming (SQP) scheme that repeatedly builds and solves a Quadratic Program with the ProxQP solver, using Eigen for linear algebra. The same engine handles linear models for free: with linear dynamics the SQP converges in a single QP solve.
Table of Contents
- Demonstration
- Where it stands
- Known limits and future work
- Packages
- Architecture and docs
- Requirements
- Build
- Test and lint
- Provenance
- License
Demonstration
The predictive ProxMPC controller reaching the goal in the four benchmark scenarios (no obstacle, static box, dynamic line, dynamic circle) on the kinematic plant, shown in RViz. Each obstacle is drawn as a ground-truth body (the orange cylinder) next to its costmap footprint. The GIF loops inline and links to the full-resolution mp4.
Regenerate it - the per-scenario clips land in prox_mpc_benchmark/results/
(gitignored), and the combiner writes the committed grid mp4 + inline GIF to
doc/media/ (see prox_mpc_benchmark/doc/videos.md
for the Xvfb/display note on Wayland and every parameter):
ros2 run prox_mpc_benchmark record_scenarios.py
ros2 run prox_mpc_benchmark combine_grid.sh --output doc/media/prox_mpc_demo_grid.mp4
Where it stands
ProxMPC is benchmarked head-to-head against the four stock Nav2 Jazzy local controllers - DWB, MPPI, Regulated Pure Pursuit, and Graceful - plus Vector Pursuit, the one external community controller included as a fair peer (Apache-2.0). Every controller drives the same plant from the same start to the same goal, at a matched 0.5 m/s speed cap and a shared 2.0 s prediction horizon, and perceives obstacles through the same costmaps. The full method and every number are in doc/controller-comparison-results.md; the summary is below.
These are simulation results on a kinematic plant, measured on an x86-64 dev host (Intel Core i7-10750H, 6 cores / 12 threads, 31 GiB RAM, Ubuntu 24.04.4) - not on physical robot hardware and not contact-dynamics. A collision is a would-be overlap of the robot and obstacle discs, scored identically for every controller. Gazebo validation is a single open-cell run; full Gazebo and hardware validation remain open.
| Controller | Tracking RMS (open) | Compute p50 / p95 (open) | Static clearance | Multi-obstacle margin |
|---|---|---|---|---|
| ProxMPC | 0.0004 m | 0.75 / 1.15 ms | +0.352 m | -0.118 m, +0.190 m predictive |
| DWB | 0.0001 m | 2.46 / 2.70 ms | +0.093 m | +0.080 m |
| MPPI | 0.0029 m | 2.61 / 2.91 ms | +0.207 m | +0.048 m |
| Regulated Pure Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.213 m | +0.125 m |
| Vector Pursuit | 0.0000 m | 0.21 / 0.25 ms | +0.175 m (stops short) | +0.024 m |
| Graceful | 0.0000 m | 0.15 / 0.20 ms | +0.207 m | -0.013 m |
Multi-obstacle margin is the median closest approach over six two-mover cells (30 runs per controller, 60 for MPPI’s 10 repeats); positive clears the obstacle. The margin is reported instead of a collision count on purpose. Those cells are deliberately marginal, so 40-80 % of runs finish within 0.15 m of the threshold and the collision count is dominated by scheduling jitter: the same cell, with the same binary, returned 1/5, 5/5, and 2/5 collisions on three separate runs. The median margin is stable across the same runs and is the honest discriminator. Counts are still reported per cell in doc/controller-comparison-results.md, which is the source of truth.
Strengths
- Tracking on par with the best. Sub-millimetre cross-track on an empty straight traverse (0.0004 m RMS, 5/5 success).
- Lightest of the optimising controllers. ~0.75 ms median per cycle on the open cell, ~3.3x lighter than DWB and ~3.5x than MPPI at equal tracking accuracy, and 1.1-2.7x lighter across the obstacle cells (the margin narrows as the obstacle field tightens and the QP gets harder), at 5.0-9.1 %
File truncated at 100 lines see the full file
CONTRIBUTING
Contributing to ProxMPC
Thanks for your interest in contributing to ProxMPC. This is a ROS 2 Jazzy package set, and contributions are expected to match the conventions already established in this codebase rather than introduce new ones. When in doubt, grep for how an existing package already solved the same problem and follow that pattern.
Table of Contents
Code style
-
Language defaults. C++17 is the primary language across every package
(
prox_mpc_core,prox_mpc_controller,prox_mpc_obstacle_tracker,prox_mpc_msgs,prox_mpc_test_models,prox_mpc_demo,prox_mpc_benchmark). Python is used only where the repo already uses it: the orchestration/analysis scripts underprox_mpc_benchmark/scripts/, targeting Python 3.12. Those scripts live in anament_cmakepackage (prox_mpc_benchmark/package.xmldeclares<build_type>ament_cmake</build_type>) and are installed, not built as anament_pythonpackage - follow that pattern rather than converting a package toament_python. -
CMake.
cmake_minimum_required(VERSION 3.28)is the floor in every package’sCMakeLists.txt; do not lower it. -
Formatting is
ament_uncrustify-only. As stated in the package READMEs (e.g.prox_mpc_core/README.md,prox_mpc_controller/README.md,prox_mpc_obstacle_tracker/README.md):cpplintandament_copyrightare disabled - uncrustify is the single enforced C++ formatter, and files carry a short SPDX header with the full text in LICENSE, for example:
// Copyright 2026 Simone Contorno
// SPDX-License-Identifier: Apache-2.0
Match this two-line header (adapted for # comments in Python) at the top
of every new source file; do not add a full license block per file.
-
Lint runs through
colcon test, not standalone. Every package’sCMakeLists.txtcallsfind_package(ament_lint_auto REQUIRED)andament_lint_auto_find_test_dependencies()underBUILD_TESTING, and.github/workflows/ci.yamlinvokescolcon test --return-code-on-test-failureafter the build. That is the whole lint path in this repo - there is no separateament_uncrustify --reformator standalone lint invocation in CI, so verify locally the same way: build, thencolcon testin your overlay. -
RAII and ownership. Use
std::unique_ptrby default for exclusive ownership (e.g.prox_mpc_obstacle_tracker’sstd::unique_ptr<Tracker> tracker_); reservestd::shared_ptrfor genuine shared ownership, such as the tracker node’s shared ROS infrastructure objects (std::shared_ptr<tf2_ros::Buffer>, theLifecyclePublisher). Never store astd::shared_ptrby reference or use one solely to extend an object’s lifetime; passconst std::shared_ptr&when only observing it. - Named constants over magic numbers, and explicit narrowing conversions - narrow to a lower-precision type only at a tightly scoped boundary, with a comment explaining why (see the existing exceptions called out in ROS parameter and Eigen/solver code for precedent).
Commit conventions
-
Branching follows GitHub Flow:
mainis the stable branch,devis the integration branch, and topic work happens onfeat/*,fix/*,chore/*, ortest/*branches merged in via pull request. -
Commit messages follow Conventional Commits, scoped to the package or
area they touch, matching real history in this repo, for example:
feat(controller): predictive dynamic-obstacle avoidance and model speed-cap forwardingtest(core): cover model input velocity-bound (v_min/v_max) overridedocs(benchmark): matched-cap results table and fair-comparison prosechore(release): bump packages to 1.0.0
- One logical change per commit. Keep unrelated refactors, formatting-only changes, and behavior changes in separate commits so the history stays reviewable and bisectable.
-
No DCO / sign-off is currently required. This repository has no
Signed-off-bytrailer convention in its commit history and no existingCONTRIBUTING-adjacent policy or.github/template requiring one - do not add a sign-off trailer unless a maintainer asks for it in review.
Testing standards
-
Frameworks. Every test in this repo is a GoogleTest (with GMock
available) suite registered via
ament_add_gtest- there is noament_add_pytest_test, nolaunch_testing, and no.pytest file anywhere in the tree. If you add Python-facing behavior that needs its own test (as opposed to being exercised through a C++ node under test), discuss the framework choice in the PR first rather than assuming pytest is already wired up. -
Where tests live. Tests live in
<package>/test/, one.cppfile per suite, registered in that package’sCMakeLists.txtunderif(BUILD_TESTING). Current suites, for reference:-
prox_mpc_core/test/:test_model_interface,test_mpc_regression,test_custom_model,test_obstacle_k,test_utils. -
prox_mpc_controller/test/test_prox_mpc_controller.cpp. -
prox_mpc_obstacle_tracker/test/:test_clustering,test_imm_filter,test_tracker,test_obstacle_tracker_node. -
prox_mpc_demo/test/test_simulation_node.cpp. -
prox_mpc_benchmark/test/:test_metrics,test_obstacle_field.
-
File truncated at 100 lines see the full file
