A ROS 2 Nonlinear Model Predictive Controller (NMPC) for quadrotors using the Acados solver. Formulates the tracking problem with an error-based cost in Euler angle representation and uses atan2-based yaw wrapping for correct angular error computation.
This package implements a well-established NMPC technique and was built during my PhD at Georgia Tech's FACTSLab to serve as the baseline against which my novel control strategy — Newton-Raphson Flow — is benchmarked. See the newton_raphson_px4 controller for that comparison.
📖 Table of Contents
nmpc_acados_px4 is not standalone — it is a ROS 2 package that imports from its sibling packages (quad_platforms, quad_trajectories, ROS2Logger) and the PX4 message definitions (px4_msgs). For these dependencies to resolve, all of them must live side-by-side under the src/ directory of a single ROS 2 workspace so that colcon build discovers and builds them together:
ros2px4_ws/ # your ROS 2 workspace (any name)
└── src/
├── nmpc_acados_px4/ # this package
├── quad_platforms/ # platform abstraction (mass, geometry, limits)
├── quad_trajectories/ # trajectory definitions
├── ROS2Logger/ # CSV experiment logging (imported as `ros2_logger`)
└── px4_msgs/ # PX4 ROS 2 message definitions
Then build once from the workspace root so every package is sourced into the same overlay:
cd ros2px4_ws
colcon build --symlink-install
source install/setup.bashIf you clone nmpc_acados_px4 on its own and try to build it, the import of quad_platforms / ros2_logger and the px4_msgs dependency will fail — the other packages simply aren't on the ROS 2 package path. Cloning them as siblings under the same src/ is what makes the dependencies "work out."
Note:
ROS2Loggeris the GitHub repository name; the package it installs (and the name youimport) isros2_logger. Acados and SciPy are installed separately (see Acados Setup below), not as workspace packages.
This controller solves a finite-horizon optimal control problem at every timestep:
- Model — 9D state
[x, y, z, vx, vy, vz, roll, pitch, yaw]with 4D input[thrust, p, q, r] - Error-based cost — the stage cost penalizes
[position_err, velocity_err, euler_err, input_err](13D); the terminal cost drops the input term (9D) - Wrapped yaw error — uses
atan2(sin(yaw - yaw_ref), cos(yaw - yaw_ref))to avoid discontinuities at +/-pi - Acados solver — generates and compiles C code for the QP sub-problems, enabling real-time MPC
- Platform-aware Acados cache — solver artifacts are reused when the platform/mass and NMPC formulation stamp still match, and regenerated automatically when they do not
- Error-state cost formulation — references are passed as stage-wise parameters, not embedded in the cost
- Nonlinear least squares — cost type is
NONLINEAR_LSwith configurable weight matrices - Input constraints — hard bounds on thrust
[0, 27] Nand body rates[-0.8, 0.8] rad/s - PX4 integration — publishes attitude setpoints and offboard commands via
px4_msgs - Structured logging — optional CSV logging via ros2_logger
Stage cost (13D):
| Component | Weight | Dimension |
|---|---|---|
| Position error | 2e3 |
3 |
| Velocity error | 2e1 |
3 |
| Roll/pitch error | 2e1 |
2 |
| Yaw error | 2e2 |
1 |
| Thrust | 1e1 |
1 |
| Body rates (p, q) | 1e3 |
2 |
| Yaw rate (r) | 1e2 |
1 |
Terminal cost (9D): same as stage cost without input terms (position 1e3, velocity 1e1, roll/pitch 1e1, yaw 1e2).
source install/setup.bash
# Fly a figure-8 in simulation
ros2 run nmpc_acados_px4 run_node --platform sim --trajectory fig8_horz
# Hardware flight with logging
ros2 run nmpc_acados_px4 run_node --platform hw --trajectory helix --log
# fig8_contraction with feedforward, logged with _ff marker in filename
ros2 run nmpc_acados_px4 run_node --platform sim --trajectory fig8_contraction --ff --log
# -> logs to: sim_nmpc_acados_px4_fig8_contraction_ff_1x.csv| Flag | Description |
|---|---|
--platform {sim,hw} |
Target platform (required) |
--trajectory {hover,yaw_only,circle_horz,...} |
Trajectory type (required) |
--hover-mode {1..8} |
Hover sub-mode (1-4 for hardware) |
--log |
Enable CSV data logging |
--log-file NAME |
Custom log filename |
--double-speed |
2x trajectory speed |
--short |
Short variant (fig8_vert) |
--spin |
Enable yaw rotation |
--flight-period SEC |
Custom flight duration |
--ff |
Mark log filename with _ff (only valid with fig8_contraction) |
- quad_trajectories — trajectory definitions
- quad_platforms — platform abstraction
- ROS2Logger — experiment logging
- px4_msgs — PX4 ROS 2 message definitions (use the
v1.16_minimal_msgsbranch for PX4 v1.16) - Acados and
acados_template - SciPy
nmpc_acados_px4/
├── nmpc_acados_px4/
│ ├── run_node.py # CLI entry point and argument parsing
│ └── ros2px4_node.py # ROS 2 node (subscriptions, publishers, control loop)
└── nmpc_acados_px4_utils/
├── controller/
│ └── nmpc/
│ ├── generate_nmpc.py # NMPC problem formulation and C-code generation
│ └── acados_model.py # Quadrotor Euler dynamics model for Acados
├── px4_utils/ # PX4 interface and flight phase management
├── transformations/ # Yaw adjustment utilities
├── main_utils.py # Helper functions
└── jax_utils.py # JAX configuration
There are four ways to set this up, from easiest to most manual. If you just want it running, use option 1 (Docker + script). All of them produce the same workspace layout.
| # | Path | Effort | When to use |
|---|---|---|---|
| 1 | Docker + script | easiest | You want the fastest, most reproducible setup. Script clones everything and links it to the container. |
| 2 | Docker (manual) | easy | Same container, but you prefer running the vcs import / make steps yourself. |
| 3 | Script (native) | medium | You build directly on the host (no Docker) and want the workspace laid out for you. |
| 4 | Manual (native) | most manual | You want full control of every clone and build step on the host. |
The Docker image already ships ROS 2, a prebuilt
px4_msgsoverlay, and the Python deps (acados interface, SciPy/JAX). Native builds (options 3–4) require you to install ROS 2, acados, andpx4_msgsyourself.
The bundled script clones PX4-ROS2-Docker, lays out src/ with every sibling package, and symlinks the workspace to the container's mount point so make run works with no arguments:
./scripts/setup_ws_docker.sh # full controller stack
./scripts/setup_ws_docker.sh --minimal # only what NMPC needs--help lists options (--ws, --docker, --https). Here px4_msgs is intentionally not cloned into src/ — the image ships a prebuilt copy at /opt/ws_px4_msgs. Then, from the Docker repo: make build → make run → make build_ros.
Follow the PX4-ROS2-Docker README: clone it, vcs import the controller packages into your workspace src/, then make run / make build_ros. Identical result to option 1, done by hand.
To build on the host, this script lays out the workspace including px4_msgs and can optionally build it:
./scripts/setup_ws_native.sh # lay out ~/ros2px4_ws/src
./scripts/setup_ws_native.sh --minimal --build # only NMPC's deps, then colcon buildRun it from this checkout and it symlinks the current repo into the workspace instead of re-cloning. You still need ROS 2, acados, and SciPy installed (see Acados Setup).
Clone this package and its sibling dependencies into one workspace src/, then build from the workspace root:
mkdir -p ros2px4_ws/src && cd ros2px4_ws/src
git clone git@github.com:evannsmc/nmpc_acados_px4.git
git clone git@github.com:evannsmc/quad_platforms.git
git clone git@github.com:evannsmc/quad_trajectories.git
git clone git@github.com:evannsmc/ROS2Logger.git
git clone -b v1.16_minimal_msgs git@github.com:evannsmc/px4_msgs.git # PX4 v1.16 minimal messages
cd .. && colcon build --symlink-install
source install/setup.bashAcados and SciPy still need to be installed separately — see Acados Setup below.
For hardware flights that rely on an external motion-capture system for state estimation (instead of onboard GPS/VIO), clone the bridge for your mocap system plus its supporting packages into your workspace src/, alongside this controller:
cd <your_ws>/src
# Pick the bridge for your mocap system:
git clone git@github.com:evannsmc/vicon4px4.git # Vicon
git clone git@github.com:evannsmc/optitrack4px4.git # OptiTrack
# Supporting packages for the mocap → PX4 pipeline (skip px4_msgs if already cloned):
git clone -b v1.16_minimal_msgs git@github.com:evannsmc/px4_msgs.git
git clone git@github.com:evannsmc/mocap_msgs.git
git clone git@github.com:evannsmc/mocap_px4_relays.git
cd .. && colcon build --symlink-installSee the vicon4px4 and optitrack4px4 READMEs for system-specific configuration (rigid-body/frame names, host/IP, and ROS_DOMAIN_ID).
Acados must be installed separately before building this package. Follow the steps below or the official instructions.
Clone and build from source:
git clone https://github.com/acados/acados.git
cd acados
git submodule update --recursive --initmkdir -p build
cd build
cmake -DACADOS_WITH_QPOASES=ON ..
# add more optional arguments e.g. -DACADOS_WITH_DAQP=ON
make install -j4Install the template as per the Python interface instructions:
pip install -e <acados_root>/interfaces/acados_templateAdd these environment variables to your shell init (e.g., ~/.bashrc), then source ~/.bashrc:
acados_root="your_acados_root" # e.g. "/home/user/acados"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"$acados_root/lib"
export ACADOS_SOURCE_DIR="$acados_root"Required for rendering C code templates:
- Download the correct binaries from the t_renderer releases
- Place the binary in
<acados_root>/bin - Rename it to
t_renderer(e.g.t_renderer-v0.2.0-linux-arm64->t_renderer) - Make it executable:
chmod +x <acados_root>/bin/t_rendererpython3 <acados_root>/examples/acados_python/getting_started/minimal_example_ocp.pyIf it runs and plots with no errors, you're done!
American Control Conference 2024 — paper | Personal repo | FACTSLab repo
Transactions on Control Systems Technology 2025 — paper | Personal repo | FACTSLab repo
Transactions on Robotics 2025 | Personal repo | FACTSLab repo
This project is part of the evannsmc open-source portfolio.
MIT