Skip to content

Repository files navigation

solver-qpoases

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.

Installation

Python (PyPI)

pip install marinholab-solvers-qpoases

C++ (Ubuntu .deb from the SmartArmStack PPA)

The 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-qpoases

This is the same repository the SmartArmStack ROS2 packages come from; see smartarmstack.github.io for the full list of available packages.

Overview

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.

Quickstart

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]

Omitting constraints

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)

Active set

solver.get_active_set() reports, for each row of the combined constraint matrix (rows of A followed by rows of Aeq), whether it is:

  • -1 active at its lower bound,
  • 0 inactive,
  • +1 active 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]

C++ API

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_qpoases

Configuration

All 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.

Wrapper-specific options

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.

Hessian definiteness (HessianType)

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

qpOASES options

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.

Print levels (PrintLevel)

PL_DEBUG_ITER, PL_TABULAR, PL_NONE, PL_LOW, PL_MEDIUM, PL_HIGH.

Bound/constraint statuses (SubjectToStatus)

ST_LOWER, ST_INACTIVE, ST_UPPER, ST_INFEASIBLE_LOWER, ST_INFEASIBLE_UPPER, ST_UNDEFINED.

Examples

  • example/example.cpp — a standalone C++ usage of marinholab::solvers::qpoases::Solver. Build it with cmake -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, the None-constraint path, and the active set. Run it with qpoases_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 on dqrobotics. It requires the optional dependencies dqrobotics and dqrobotics-pyplot (pip install --pre dqrobotics dqrobotics-pyplot).

Building from source

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.

Type checking

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:

pyright

License

The qpOASES library is LGPLv2.1; the wrapper is under the terms of the included LICENSE file.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Used by

Contributors

Languages