A qpOASES wrapper for Python that ships
prebuilt binaries. It exposes qpOASES' online active-set solver through a thin,
numpy-friendly, MATLAB-quadprog-like interface.
pip install marinholab-solvers-qpoasesThe C++ part (marinholab::solvers::qpoases, with qpOASES bundled) is
distributed as the libmarinholab-solver-qpoases .deb from the
SmartArmStack APT repository hosted at
smartarmstack.github.io. On Ubuntu (Noble/24.04), add it and install:
curl -s --compressed "https://smartarmstack.github.io/smart_arm_stack_ROS2/KEY.gpg" \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/smartarmstack_lgpl.gpg >/dev/null
sudo curl -s --compressed -o /etc/apt/sources.list.d/smartarmstack_lgpl.list \
"https://smartarmstack.github.io/smart_arm_stack_ROS2/smartarmstack_lgpl.list"
sudo apt update
sudo apt-get install libmarinholab-solver-qpoasesThis is the same repository the SmartArmStack ROS2 packages come from; see smartarmstack.github.io for the full list of available packages.
Given a symmetric matrix H, a vector f, and (optionally) inequality and
equality constraint matrices, the solver solves the quadratic program
min_x 0.5 * x' H x + f' x
s.t. A x <= b
Aeq x = beq
Once a problem has been solved once, subsequent solves on the same Solver
instance are warm-started by default (Configuration.use_hotstart = True),
which is the main performance benefit of qpOASES for repeated, related QPs.
import numpy as np
from marinholab.solvers import qpoases
solver = qpoases.Solver()
H = np.eye(2) # positive definite Hessian
f = np.array([-1.0, -1.0]) # linear term
A = np.array([[1.0, 0.0]]) # x[0] <= 0.2
b = np.array([0.2])
x = solver.solve_quadratic_program(H, f, A, b,
Aeq=np.zeros((1, 2)),
beq=np.zeros((1,)))
# x ≈ [0.2, 1.0]Any of the four constraint arguments (A, b, Aeq, beq) can be None,
meaning "no such constraint". The matrix and its right-hand side must be
omitted (or provided) together:
# Unconstrained
x = solver.solve_quadratic_program(H, f, None, None, None, None)
# Equality constraints only
x = solver.solve_quadratic_program(H, f, None, None, Aeq, beq)solver.get_active_set() reports, for each row of the combined constraint
matrix (rows of A followed by rows of Aeq), whether it is:
-1active at its lower bound,0inactive,+1active at its upper bound (equality constraints are always+1, since their bounds coincide).
solver.solve_quadratic_program(H, f, A, b, Aeq, beq)
solver.get_active_set() # e.g. [ 1.0, 0.0, 0.0]The same solver is available directly in C++ (the Python wrapper is a thin pybind11 layer over it). It is built with Eigen and qpOASES:
#include <marinholab/solvers/qpoases.h>
namespace qpoases = marinholab::solvers::qpoases;
qpoases::Configuration config;
config.set("terminationTolerance", 1.0e-9); // the rest keeps its defaults
qpoases::Solver solver(config);
Eigen::MatrixXd H = Eigen::MatrixXd::Identity(2, 2);
Eigen::VectorXd f(2);
f << -1.0, -1.0;
Eigen::MatrixXd A(1, 2);
A << 1.0, 0.0; // x[0] <= 0.2
Eigen::VectorXd b(1);
b << 0.2;
Eigen::MatrixXd Aeq = Eigen::MatrixXd::Zero(1, 2);
Eigen::VectorXd beq = Eigen::VectorXd::Zero(1);
Eigen::VectorXd x = solver.solve_quadratic_program(H, f, A, b, Aeq, beq);
// x ≈ [0.2, 1.0]
Eigen::VectorXd active_set = solver.get_active_set(); // e.g. [1, 0]The API mirrors the Python one: solve_quadratic_program(H, f, A, b, Aeq, beq)
solves the QP above, and get_active_set() reports the active constraints with
the same -1 / 0 / +1 convention. Configuration is keyed by option name (see
below); a runnable version lives in example/example.cpp.
To build and run it (the example is off by default so pip install . is
unaffected):
cmake -B build -GNinja -DCMAKE_BUILD_TYPE=Release -DBUILD_EXAMPLES=ON
cmake --build build
./build/example_qpoasesAll of qpOASES' Options fields are exposed, plus a few wrapper-specific
settings, by name. Values are typed: bool, int, float, or, for enum
options, the enum value name (e.g. "HST_SEMIDEF", "PL_NONE"), so the public
API (and the C++ header, where values are a std::variant<bool, long long, double, std::string>) stays free of qpOASES types. Unset options use qpOASES'
own double-precision defaults except printLevel ("PL_NONE").
config = qpoases.Configuration()
config.set("hessian_type", qpoases.HessianType.HST_SEMIDEF) # H is rank-deficient
config.set("terminationTolerance", 1.0e-9) # tighter convergence
solver = qpoases.Solver(config)set() accepts a bool, an int, a float, an enum member (used by its name), or
a string, converted to the option's kind: config.set("terminationTolerance", "1.0e-9") also works, and an int is accepted for a real option. The other
accessors are:
| Method | Returns |
|---|---|
config.get(key) |
the option's value as bool, int, float, or str (or its default) |
config.has(key) |
whether key has been explicitly set |
config.keys() |
sorted list of all settable option names |
config.defaults() |
mapping of option name -> default value |
config.reset(key) / config.reset_all() |
revert to the default(s) |
Setting an unknown key, or a value that does not convert to the option's kind
(e.g. 1.5 for an integer option or True for a real one), raises
ValueError.
The enum types are re-exported for convenience: qpoases.BooleanType,
qpoases.HessianType, qpoases.PrintLevel, and qpoases.SubjectToStatus.
| Option | Default | Type | Description |
|---|---|---|---|
maximum_working_set_recalculations |
150 |
int |
Max working-set recalculations during the initial homotopy (nWSR passed to init/hotstart). Increase if solves hit the maximum. |
use_hotstart |
true |
bool |
Warm-start subsequent solves with hotstart() instead of re-initialising with init(). |
hessian_type |
HST_POSDEF |
HessianType |
Definiteness assumed for H; given to the underlying SQProblem. |
| Value | Meaning |
|---|---|
HST_ZERO |
Hessian is the zero matrix (LP formulation) |
HST_IDENTITY |
Hessian is the identity matrix |
HST_POSDEF |
Hessian is (strictly) positive definite |
HST_POSDEF_NULLSPACE |
Positive definite on the null space of active bounds/constraints |
HST_SEMIDEF |
Positive semi-definite |
HST_INDEF |
Indefinite |
HST_UNKNOWN |
Unknown |
These map 1:1 onto qpOASES' Options fields. Defaults match qpOASES' own
defaults for a double-precision build (see Options::setToDefault()),
except printLevel, which defaults to the least verbose level (PL_NONE).
See the qpOASES manual
for a full description of each option.
Booleans (set as "true" / "false")
| Option | Default | Description |
|---|---|---|
enableRamping |
true |
Enables the ramping strategy. |
enableFarBounds |
true |
Enables the far bounds strategy. |
enableFlippingBounds |
true |
Allows flipping active bounds between lower and upper values. |
enableRegularisation |
false |
Regularises H when (semi-)definiteness is detected. |
enableFullLITests |
false |
Uses the condition-hardened linear-independence (LI) test. |
enableNZCTests |
true |
Enables the nonzero-curvature test. |
enableEqualities |
false |
Treats equality constraints as always active. |
enableInertiaCorrection |
true |
Repairs the working set when negative curvature is found during a hotstart. |
enableDropInfeasibles |
false |
Whether infeasible constraints may be dropped. |
Integers (int_t)
| Option | Default | Description |
|---|---|---|
enableDriftCorrection |
1 |
Frequency of drift corrections (0 = off). |
enableCholeskyRefactorisation |
0 |
Frequency of full Cholesky refactorisation of the projected Hessian (0 = rank updates only). |
numRegularisationSteps |
0 |
Max successive regularisation steps. |
numRefinementSteps |
1 |
Max iterative-refinement steps. |
dropBoundPriority |
1 |
Priority used when dropping bounds. |
dropEqConPriority |
1 |
Priority used when dropping equality constraints. |
dropIneqConPriority |
1 |
Priority used when dropping inequality constraints. |
Reals (real_t, double)
| Option | Default | Description |
|---|---|---|
terminationTolerance |
5.0e6 * EPS (~1.1e-9) |
Relative tolerance that stops the homotopy. Smaller = more accurate, more work. |
boundTolerance |
1.0e6 * EPS |
Bound tolerance; a constraint whose bounds differ by less is treated as an equality. |
boundRelaxation |
1.0e4 |
Offset for relaxing bounds at the start of the initial homotopy (also the initial far-bound value). |
epsNum |
-1.0e3 * EPS |
Numerator tolerance for the ratio test. |
epsDen |
1.0e3 * EPS |
Denominator tolerance for the ratio test. |
maxPrimalJump |
1.0e8 |
Max allowed primal jump in nonzero-curvature tests. |
maxDualJump |
1.0e8 |
Max allowed dual jump in LI tests. |
initialRamping |
0.5 |
Start value of the ramping strategy. |
finalRamping |
1.0 |
Final value of the ramping strategy. |
initialFarBounds |
1.0e6 |
Initial size of the far bounds. |
growFarBounds |
1.0e3 |
Growth factor applied to the far bounds. |
epsFlipping |
1.0e3 * EPS |
Tolerance of the squared Cholesky diagonal factor that triggers flipping a bound. |
epsRegularisation |
1.0e3 * EPS |
Scaling factor of the identity matrix used for Hessian regularisation. |
epsIterRef |
1.0e2 * EPS |
Early-termination tolerance for iterative refinement. |
epsLITests |
1.0e5 * EPS |
Tolerance for the linear-independence tests. |
epsNZCTests |
3.0e3 * EPS |
Tolerance for the nonzero-curvature tests. |
rcondSMin |
1.0e-14 |
Min reciprocal condition number of the Schur complement before a refactorisation is triggered. |
Status / print enums
| Option | Default | Type | Description |
|---|---|---|---|
printLevel |
PL_NONE |
PrintLevel |
Verbosity of qpOASES output (PL_NONE, PL_LOW, PL_MEDIUM, PL_HIGH, PL_TABULAR, PL_DEBUG_ITER). Defaults to the least verbose level (PL_NONE), intentionally differing from qpOASES' own default (PL_MEDIUM). |
initialStatusBounds |
ST_LOWER |
SubjectToStatus |
Status assumed for all bounds at the first iteration. |
PL_DEBUG_ITER, PL_TABULAR, PL_NONE, PL_LOW, PL_MEDIUM, PL_HIGH.
ST_LOWER, ST_INACTIVE, ST_UPPER, ST_INFEASIBLE_LOWER,
ST_INFEASIBLE_UPPER, ST_UNDEFINED.
example/example.cpp— a standalone C++ usage ofmarinholab::solvers::qpoases::Solver. Build it withcmake -B build -GNinja -DBUILD_EXAMPLES=ON && cmake --build build, then run./build/example_qpoases.marinholab/solvers/qpoases/example.py— positive-definite and semi-definite solves, theNone-constraint path, and the active set. Run it withqpoases_example(installed as a console script).marinholab/solvers/qpoases/example_kinematics.py— an optional example showing the solver used in a hierarchical (task-priority) controller for a kinematically redundant robot, built ondqrobotics. It requires the optional dependenciesdqroboticsanddqrobotics-pyplot(pip install --pre dqrobotics dqrobotics-pyplot).
The package builds a C++ extension (via CMake + pybind11) and vendors qpOASES and pybind11 as git submodules.
git clone --recurse-submodules <repo>
pip install .Prerequisites: a C++23 compiler, CMake, an Eigen3 installation, and Python.
On Ubuntu: sudo apt-get install cmake libeigen3-dev.
The package ships a type stub (marinholab/solvers/qpoases/_core.pyi) and a
py.typed marker, so downstream projects can be checked with
Pyright (or Pylance) without extra
configuration. Run the project's own check with:
pyrightThe qpOASES library is LGPLv2.1; the wrapper is under the terms of the
included LICENSE file.