Showing preview only (552K chars total). Download the full file or copy to clipboard to get everything.
Repository: stephane-caron/qpsolvers
Branch: main
Commit: a9b509eff8cd
Files: 111
Total size: 520.4 KB
Directory structure:
gitextract_sm8s1023/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── changelog.yml
│ ├── ci.yml
│ ├── docs.yml
│ └── pypi.yml
├── .gitignore
├── CHANGELOG.md
├── CITATION.cff
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── doc/
│ ├── conf.py
│ ├── developer-notes.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── least-squares.rst
│ ├── quadratic-programming.rst
│ ├── references.rst
│ ├── supported-solvers.rst
│ └── unsupported-solvers.rst
├── examples/
│ ├── README.md
│ ├── box_inequalities.py
│ ├── constrained_linear_regression.py
│ ├── dual_multipliers.py
│ ├── lasso_regularization.py
│ ├── least_squares.py
│ ├── model_predictive_control.py
│ ├── quadratic_program.py
│ ├── sparse_least_squares.py
│ ├── test_dense_problem.py
│ ├── test_model_predictive_control.py
│ ├── test_random_problems.py
│ └── test_sparse_problem.py
├── pyproject.toml
├── qpsolvers/
│ ├── __init__.py
│ ├── active_set.py
│ ├── conversions/
│ │ ├── __init__.py
│ │ ├── combine_linear_box_inequalities.py
│ │ ├── ensure_sparse_matrices.py
│ │ ├── linear_from_box_inequalities.py
│ │ ├── socp_from_qp.py
│ │ └── split_dual_linear_box.py
│ ├── exceptions.py
│ ├── problem.py
│ ├── problems.py
│ ├── py.typed
│ ├── solution.py
│ ├── solve_ls.py
│ ├── solve_problem.py
│ ├── solve_qp.py
│ ├── solve_unconstrained.py
│ ├── solvers/
│ │ ├── __init__.py
│ │ ├── clarabel_.py
│ │ ├── copt_.py
│ │ ├── cvxopt_.py
│ │ ├── daqp_.py
│ │ ├── ecos_.py
│ │ ├── gurobi_.py
│ │ ├── highs_.py
│ │ ├── hpipm_.py
│ │ ├── jaxopt_osqp_.py
│ │ ├── kvxopt_.py
│ │ ├── mosek_.py
│ │ ├── nppro_.py
│ │ ├── osqp_.py
│ │ ├── pdhcg_.py
│ │ ├── piqp_.py
│ │ ├── proxqp_.py
│ │ ├── pyqpmad_.py
│ │ ├── qpalm_.py
│ │ ├── qpax_.py
│ │ ├── qpoases_.py
│ │ ├── qpswift_.py
│ │ ├── qtqp_.py
│ │ ├── quadprog_.py
│ │ ├── scs_.py
│ │ └── sip_.py
│ ├── utils.py
│ └── warnings.py
└── tests/
├── __init__.py
├── problems.py
├── test_clarabel.py
├── test_combine_linear_box_inequalities.py
├── test_conversions.py
├── test_copt.py
├── test_cvxopt.py
├── test_ecos.py
├── test_gurobi.py
├── test_highs.py
├── test_jaxopt_osqp.py
├── test_kvxopt.py
├── test_mosek.py
├── test_nppro.py
├── test_osqp.py
├── test_piqp.py
├── test_problem.py
├── test_proxqp.py
├── test_pyqpmad.py
├── test_qpax.py
├── test_qpoases.py
├── test_qpswift.py
├── test_quadprog.py
├── test_scs.py
├── test_sip.py
├── test_solution.py
├── test_solve_ls.py
├── test_solve_problem.py
├── test_solve_qp.py
├── test_timings.py
├── test_unfeasible_problem.py
└── test_utils.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitattributes
================================================
pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff
================================================
FILE: .github/workflows/changelog.yml
================================================
name: Changelog
on:
pull_request:
branches: [ main ]
jobs:
changelog:
name: "Check changelog update"
runs-on: ubuntu-latest
steps:
- uses: tarides/changelog-check-action@v2
with:
changelog: CHANGELOG.md
changelog_success:
name: "Changelog success"
runs-on: ubuntu-latest
needs: [changelog]
steps:
- run: echo "Changelog workflow completed successfully"
================================================
FILE: .github/workflows/ci.yml
================================================
name: CI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
jobs:
check-secrets:
name: "Check availability of GitHub secrets"
runs-on: ubuntu-latest
outputs:
has-secrets: ${{ steps.secret-check.outputs.available }}
steps:
- name: Check whether GitHub secrets are available
id: secret-check
shell: bash
run: |
if [ '${{ secrets.MSK_LICENSE }}' != '' ]; then
echo "available=true" >> ${GITHUB_OUTPUT};
else
echo "available=false" >> ${GITHUB_OUTPUT};
fi
coverage:
name: "Coverage"
runs-on: ubuntu-latest
needs: [check-secrets]
if: needs.check-secrets.outputs.has-secrets == 'true'
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Setup Pixi"
uses: prefix-dev/setup-pixi@v0.8.8
with:
pixi-version: v0.59.0
cache: true
- name: "Prepare license files"
env:
MSK_LICENSE: ${{ secrets.MSK_LICENSE }}
run: |
echo "${MSK_LICENSE}" > ${{ github.workspace }}/mosek.lic
- name: "Check code coverage"
env:
MOSEKLM_LICENSE_FILE: ${{ github.workspace }}/mosek.lic
run: |
pixi run coverage
- name: "Upload coverage results"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
pixi run coveralls
licensed:
name: "Test licensed solvers on ${{ matrix.os }}"
runs-on: ${{ matrix.os }}
needs: [check-secrets]
if: needs.check-secrets.outputs.has-secrets == 'true'
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Setup Pixi"
uses: prefix-dev/setup-pixi@v0.8.8
with:
pixi-version: v0.59.0
environments: licensed
cache: true
- name: "Prepare license files"
env:
MSK_LICENSE: ${{ secrets.MSK_LICENSE }}
run: |
echo "${MSK_LICENSE}" > ${{ github.workspace }}/mosek.lic
- name: "Test licensed solvers"
env:
MOSEKLM_LICENSE_FILE: ${{ github.workspace }}/mosek.lic
run: |
pixi run -e licensed licensed
lint:
name: "Code style"
runs-on: ubuntu-latest
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Setup Pixi"
uses: prefix-dev/setup-pixi@v0.8.8
with:
pixi-version: v0.59.0
environments: lint
cache: true
- name: "Lint qpsolvers"
run: |
pixi run lint
test:
name: "Test ${{ matrix.os }} with ${{ matrix.pyenv }}"
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
pyenv: [py310, py311, py312, py313]
env:
PIXI_ENV: test-${{ matrix.pyenv }}
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Setup Pixi"
uses: prefix-dev/setup-pixi@v0.8.8
with:
pixi-version: v0.59.0
environments: ${{ env.PIXI_ENV }}
cache: true
- name: "Run unit tests"
run: |
pixi run --environment ${{ env.PIXI_ENV }} test
ci_success:
name: "CI success"
runs-on: ubuntu-latest
needs: [coverage, licensed, lint, test]
steps:
- run: echo "CI workflow completed successfully"
================================================
FILE: .github/workflows/docs.yml
================================================
name: Documentation
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
docs:
name: "GitHub Pages"
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: "Checkout Git repository"
uses: actions/checkout@v4
- name: "Setup Pixi"
uses: prefix-dev/setup-pixi@v0.8.8
with:
pixi-version: v0.59.0
environments: docs
cache: true
- name: "Checkout qpSWIFT"
uses: actions/checkout@v4
with:
repository: qpSWIFT/qpSWIFT
path: qpSWIFT
- name: "Install qpSWIFT"
run: |
cd qpSWIFT/python
pixi run -e docs python setup.py install
- name: "Build documentation"
run: |
pixi run -e docs docs-build
- name: "Deploy to GitHub Pages"
uses: peaceiris/actions-gh-pages@v3
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}
with:
publish_branch: gh-pages
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: _build/
force_orphan: true
================================================
FILE: .github/workflows/pypi.yml
================================================
name: PyPI
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
pypi:
name: "Install from PyPI"
runs-on: ubuntu-latest
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Install dependencies"
run: |
python -m pip install --upgrade pip
- name: "Install package"
run: python -m pip install qpsolvers
- name: "Install at least one solver"
run: python -m pip install quadprog
- name: "Test module import"
run: python -c "import qpsolvers"
testpypi:
name: "Install from TestPyPI"
runs-on: ubuntu-latest
steps:
- name: "Checkout sources"
uses: actions/checkout@v4
- name: "Install dependencies"
run: |
python -m pip install --upgrade pip
- name: "Install package"
run: python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ qpsolvers
- name: "Install at least one solver"
run: python -m pip install quadprog
- name: "Test module import"
run: python -c "import qpsolvers"
pypi_success:
name: "PyPI success"
runs-on: ubuntu-latest
needs: [pypi, testpypi]
steps:
- run: echo "PyPI workflow completed successfully"
================================================
FILE: .gitignore
================================================
*.pyc
*.pyo
.coverage
.ropeproject
.tox
MANIFEST
_build/
build/
dist/
examples/qpsolvers
gurobi.log
htmlcov/
qpsolvers.egg-info
================================================
FILE: CHANGELOG.md
================================================
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Add build and solve timing info (thanks to @ahoarau)
- DAQP: Warm starts and time limit are now enabled in DAQP (thanks to @darnstrom)
- New solver: qpmad (thanks to @ahoarau)
- Support Python 3.13
### Fixed
- HiGHS: update interface following code changes in HiGHS v1.14.0
## [4.11.0] - 2026-03-16
### Added
- New solver: PDHCG (thanks to @Lhongpei)
### Changed
- Change params in copt test to avoid unexpected timeout fail (thanks to @Salancelot)
- Merge unsupported solvers submodule within the main solvers submodule
- Remove unintended copied copyright information in copt_.py (thanks to @Salancelot)
### Fixed
- CICD: Clean up OS-specific features and reduce environments (thanks to @ahoarau)
## [4.10.0] - 2026-03-10
### Added
- New solver: [COPT](https://guide.coap.online/copt/en-doc/index.html) (thanks to @Salancelot)
### Changed
- Gurobi: update to newer Gurobi API
### Fixed
- CICD: Add Gurobi to pixi PyPI dependencies
## [4.9.0] - 2026-03-04
### Added
- Add `unpack_as_dense` function to problems
- Add type annotations to internal `__solve_sparse_ls` function
- New solver: [QTQP](https://github.com/google-deepmind/qtqp)
### Changed
- Bump minimum Python version to 3.10
- CICD: Configure Git attributes for the pixi.lock file
- CICD: Switch from tox to pixi
- KVXOPT: Update type annotations
- SIP: Ensure returned primal and dual vectors are NumPy arrays
- SIP: Remove unused CVXOPT-related code from solver interface
### Fixed
- Correct type annotation of SOCP conversion function
- Correct type annotation of solver function prototypes
- ECOS: Rename internal intermediate variables to improve type checking
- ECOS: Revise type annotations in solver interface
- Fix sparse type annotations in `concatenate_bound`
- Fix type annotation corner case in `linear_from_box_inequalities`
- Fix type annotations in internal `__solve_dense_ls` function
- Fix variable name re-use errors raised by newer versions of mypy
- SIP: Forward allow-non-PSD argument in `solve_qp` variant of the interface
- Unpack problems as NumPy arrays for dense solver APIs
### Removed
- Remove support for Python 3.9
- CICD: Remove Python 3.9
## [4.8.2] - 2025-11-25
### Added
- docs: Document warm-starting for all solver interfaces
### Changed
- Prevent deprecation warnings from OSQP related to solver status (thanks to @jkeust)
- Prevent warning message from OSQP about conversion to a CSC matrix (thanks to @jkeust)
- Slightly more explicit error message when a solver is not found
## [4.8.1] - 2025-08-07
### Changed
- Clarabel: Warn when problem is unconstrained and solved by `lsqr` (thanks to @proyan)
## [4.8.0] - 2025-07-02
### Added
- PIQP: Add support for new API in v0.6.0 (thanks to @RSchwan)
- PIQP: Add new solver options to the documentation (thanks to @RSchwan)
### Fixed
- CICD: Enable macOS tests for KVXOPT (thanks to @sanurielf)
## [4.7.1] - 2025-06-03
### Added
- `py.typed` file to indicate tools like `mypy` to use type annotations (thanks to @ValerianRey)
## [4.7.0] - 2025-05-13
### Added
- New solver: [SIP](https://github.com/joaospinto/sip_python) (thanks to @joaospinto)
- warnings: Add `SparseConversionWarning` to filter the corresponding warning
- warnings: Base class `QPWarning` for all qpsolvers-related warnings
- warnings: Recall solver name when issuing conversion warnings
### Changed
- Add solver name argument to internal `ensure_sparse_matrices` function
- CICD: Update Python version to 3.10 in coverage job
### Fixed
- docs: Add jaxopt.OSQP to the list of supported solvers
### Removed
- OSQP: Remove pre-1.0 version pin
- OSQP: Update interface after relase of v1.0.4
- Warning that was issued every time an unsupported solver is available
## [4.6.0] - 2025-04-17
### Added
- New solver: [KVXOPT](https://github.com/sanurielf/kvxopt/) (thanks to @agroudiev)
- jaxopt.OSQP: Support JAX array inputs when jaxopt.OSQP is the selected solver
## [4.5.1] - 2025-04-10
### Changed
- CICD: Update checkout action to v4
### Fixed
- OSQP: Temporary fix in returning primal-dual infeasibility certificates
## [4.5.0] - 2025-03-04
### Added
- HPIPM: Document new `tol_dual_gap` parameter
- New solver: [jaxopt.OSQP](https://jaxopt.github.io/stable/_autosummary/jaxopt.OSQP.html)
- Support Python 3.12
### Changed
- Bump minimum Python version to 3.8
- CICD: Remove Python 3.8 from continuous integration
- Fix output datatypes when splitting linear-box dual multipliers
- OSQP: version-pin to < 1.0.0 pending an interface update
- Warn when solving unconstrained problem by SciPy's LSQR rather than QP solver
### Fixed
- Fix mypy error in `Solution.primal_residual`
## [4.4.0] - 2024-09-24
### Added
- HPIPM: Link to reference paper for details on solver modes
- New solver: [qpax](https://github.com/kevin-tracy/qpax) (thanks to @lvjonok)
## [4.3.3] - 2024-08-06
### Changed
- CICD: Remove Gurobi from macOS continuous integration
- CICD: Remove Python 3.7 from continuous integration
- CICD: Update ruff to 0.4.3
### Fixed
- CICD: Fix coverage and licensed-solver workflows
- CICD: Install missing dependency in licensed solver test environment
- Clarabel: Catch pyO3 panics that can happen when building a problem
- Default arguments to active set dataclass to `None` rather than empty list
- PIQP: Warning message about CSC matrix conversions (thanks to @itsahmedkhalil)
- Update all instances of `np.infty` to `np.inf`
## [4.3.2] - 2024-03-25
### Added
- Optional dependency: `wheels_only` for solvers with pre-compiled binaries
### Changed
- Update developer notes in the documentation
- Update some solver tolerances in unit tests
- Warn rather than raise when there is no solver detected
### Fixed
- CICD: Update micromamba setup action
## [4.3.1] - 2024-02-06
### Fixed
- Gurobi: sign of inequality multipliers (thanks to @563925743)
## [4.3.0] - 2024-01-23
### Added
- Extend continuous integration to Python 3.11
- Function to get the CUTE classification string of the problem
- Optional dependencies for all solvers in the list available on PyPI
### Changed
- **Breaking:** no default QP solver installed along with the library
- NPPro: update exit flag value to match new solver API (thanks to @ottapav)
### Fixed
- Documentation: Add Clarabel to the list of supported solvers (thanks to @ogencoglu)
- Documentation: Correct note in `solve_ls` documentation (thanks to @ogencoglu)
- Documentation: Correct output of LS example (thanks to @ogencoglu)
## [4.2.0] - 2023-12-21
### Added
- Example: [lasso regularization](https://scaron.info/blog/lasso-regularization-in-quadratic-programming.html)
- `Problem.load` function
- `Problem.save` function
## [4.1.1] - 2023-12-05
### Changed
- Mark QPALM as a sparse solver only
## [4.1.0] - 2023-12-04
### Added
- New solver: [QPALM](https://kul-optec.github.io/QPALM/Doxygen/)
- Unit test for internal linear-box inequality combination
### Changed
- Internal: refactor linear-box inequality combination function
- Renamed main branch of the repository from `master` to `main`
### Fixed
- Fix combination of box inequalities with empty linear inequalities
- Gurobi: Account for a slight regression in QPSUT01 performance
## [4.0.1] - 2023-11-01
### Added
- Allow installation of a subset of QP solvers from PyPI
## [4.0.0] - 2023-08-30
### Added
- New solver: [PIQP](https://github.com/PREDICT-EPFL/piqp) (thanks to @shaoanlu)
- Type for active set of equality and inequality constraints
### Changed
- **Breaking:** condition number requires an active set (thanks to @aescande)
## [3.5.0] - 2023-08-16
### Added
- New solver: [HPIPM](https://github.com/giaf/hpipm) (thanks to @adamheins)
### Changed
- MOSEK: Disable CI test on QPSUT03 due to regression with 10.1.8
- MOSEK: Relax test tolerances as latest version is less accurate with defaults
## [3.4.0] - 2023-04-28
### Changed
- Converted THANKS file to [CFF](https://citation-file-format.github.io/)
- ECOS: raise a ProblemError if inequality vectors contain infinite values
- ECOS: raise a ProblemError if the cost matrix is not positive definite
- MOSEK is now a supported solver (thanks to @uricohen and @aszekMosek)
### Fixed
- Residual and duality gap computations when solution is not found
- Update OSQP version to 0.6.2.post9 for testing
## [3.3.1] - 2023-04-12
### Fixed
- DAQP: Update to 0.5.1 to fix installation of arm64 wheels
## [3.3.0] - 2023-04-11
### Added
- New sample problems in `qpsolvers.problems`
- New solver: [DAQP](https://darnstrom.github.io/daqp/) (thanks to @darnstrom)
### Changed
- Dual multipliers are empty arrays rather than None when no constraint
- Store solver results even when solution is not found
- Switch to `Solution.found` as solver success status (thanks to @rxian)
### Fixed
- Unit test on actual solution to QPSUT03 problem
## [3.2.0] - 2023-03-29
### Added
- Sparse strategy to convert LS problems to QP (thanks to @bodono)
- Start `problems` submodule to collect sample test problems
### Fixed
- Clarabel: upstream handling of infinite values in inequalities
- CVXOPT: option passing
## [3.1.0] - 2023-03-07
### Added
- New solver: NPPro
### Changed
- Documentation: separate support and unsupported solver lists
- Exclude unsupported solvers from code coverage report
- Move unsupported solvers to a separate submodule
- Remove CVXOPT from dependencies as it doesn't have arm64 wheels
- Remove quadprog from dependencies as it doesn't have arm64 wheels
## [3.0.0] - 2023-02-28
### Added
- Exception `ParamError` for incorrect solver parameters
- Exception `SolverError` for solver failures
### Changed
- All functions throw only qpsolvers-owned exceptions
- CVXOPT: rethrow `ValueError` as either `ProblemError` or `SolverError`
- Checking `Solution.is_empty` becomes `not Solution.found`
- Install open source solvers with wheels by default
- Remove `solve_safer_qp`
- Remove `sym_proj` parameter
## [2.8.1] - 2023-02-28
### Changed
- Expose `solve_unconstrained` function from main module
### Fixed
- Clarabel: handle unconstrained problems
- README: correct and improve FAQ on non-convex problems (thanks to @nrontsis)
## [2.8.0] - 2023-02-27
### Added
- New solver: [Clarabel](https://github.com/oxfordcontrol/Clarabel.rs)
### Changed
- Move documentation to [GitHub Pages](https://qpsolvers.github.io/qpsolvers/)
- Remove Python 2 installation instructions
## [2.7.4] - 2023-01-31
### Fixed
- Check vector shapes in problem constructor
## [2.7.3] - 2023-01-16
### Added
- qpOASES: return number of WSR in solution extra info
### Fixed
- CVXOPT: fix domain errors when some bounds are infinite
- qpOASES: fix missing lower bound when there is no equality constraint
- qpOASES: handle infinite bounds
- qpOASES: segmentation fault with conda feedstock
## [2.7.2] - 2023-01-02
### Added
- ECOS: handle two more exit flags
- Exception `ProblemError` for problem formulation errors
- Exception `QPError` as a base class for exceptions
- Property to check if a Problem has sparse matrices
- qpOASES: raise a ProblemError when matrices are not dense
- qpSWIFT: raise a ProblemError when matrices are not dense
- quadprog: raise a ProblemError when matrices are not dense
### Changed
- Add `use_sparse` argument to internal linear-from-box conversion
- Restrict condition number calculation to dense problems for now
## [2.7.1] - 2022-12-23
### Added
- Document problem conversion functions in developer notes
- ECOS: handle more exit flags
### Changed
- quadprog: use internal `split_dual_linear_box` conversion function
### Fixed
- SCS: require at least version 3.2
- Solution: duality gap computation under infinite box bounds
## [2.7.0] - 2022-12-15
### Added
- Continuous integration for macOS
- CVXOPT: return dual multipliers
- ECOS: return dual multipliers
- Example: dual multipliers
- Gurobi: return dual multipliers
- HiGHS: return dual multipliers
- MOSEK: return dual multipliers
- OSQP: return dual multipliers
- Problem class with utility metrics on quadratic programs
- Problem: condition number
- ProxQP: return dual multipliers
- qpOASES: return dual multipliers
- qpOASES: return objective value
- qpSWIFT: return dual multipliers
- qpSWIFT: return objective value
- quadprog: return dual multipliers
- SCS: return dual multipliers
### Changed
- Code: move `solve_safer_qp` to a separate source file
- Code: refactor location of internal conversions submodule
- ProxQP: bump minimum supported version to 0.2.9
### Fixed
- qpOASES: eliminate redundant equality constraints
## [2.6.0] - 2022-11-14
### Added
- Example: constrained linear regression
- Example: sparse linear least squares
- Gurobi: forward keyword arguments as solver parameters
- Handle diagonal matrices when combining linear and box inequalities
- qpOASES: pre-defined options parameter
- qpOASES: time limit parameter
### Changed
- CVXOPT: forward all keyword arguments as solver options
- Deprecate `solve_safer_qp` and warn about future removal
- Example: disable verbose output in least squares example
- HiGHS: forward all keyword arguments as solver options
- OSQP: drop support for versions <= 0.5.0
- OSQP: streamline stacking of box inequalities
- ProxQP: also consider constraint matrices to select backend
- qpOASES: forward all keyword arguments as solver options
- qpOASES: forward box inequalities directly
- Remove CVXPY which is not a solver
- SCS: `SOLVED_INACCURATE` is now considered a failure
### Fixed
- Dot product bug in `solve_ls` with sparse matrices
- MOSEK: restore CVXOPT options after calling MOSEK
- ProxQP: fix box inequality shapes when combining bounds
- qpOASES: non-persistent solver options between calls
- qpOASES: return failure on `RET_INIT_FAILED*` return codes
## [2.5.0] - 2022-11-04
### Added
- CVXOPT: absolute tolerance parameter
- CVXOPT: feasibility tolerance parameter
- CVXOPT: limit maximum number of iterations
- CVXOPT: refinement parameter
- CVXOPT: relative tolerance parameter
- Documentation: reference solver papers
- ECOS: document additional parameters
- Gurobi: time limit parameter
- HiGHS: dual feasibility tolerance parameter
- HiGHS: primal feasibility tolerance parameter
- HiGHS: time limit parameter
### Changed
- CVXOPT matrices are not valid types for qpsolvers any more
- CVXOPT: improve documentation
- CVXOPT: solver is now listed as sparse as well
- ECOS: type annotations allow sparse input matrices
- OSQP: don't override default solver tolerances
- Remove internal CVXOPT-specific type annotation
- Restrict matrix types to NumPy arrays and SciPy CSC matrices
- SCS: don't override default solver tolerances
- Simplify intermediate internal type annotations
### Fixed
- CVXOPT: pass warm-start primal properly
- ECOS: forward keyword arguments
- OSQP: dense arrays for vectors in type annotations
- SCS: fix handling of problems with only box inequalities
## [2.4.1] - 2022-10-21
### Changed
- Update ProxQP to version 0.2.2
## [2.4.0] - 2022-09-29
### Added
- New solver: [HiGHS](https://github.com/ERGO-Code/HiGHS)
- Raise error when there is no available solver
### Changed
- Make sure plot is shown in MPC example
- Print expected solutions in QP, LS and box-inequality examples
- Renamed starter solvers optional deps to `open_source_solvers`
### Fixed
- Correct documentation of `R` argument to `solve_ls`
## [2.3.0] - 2022-09-06
### Added
- New solver: [ProxQP](https://github.com/Simple-Robotics/proxsuite)
### Changed
- Clean up unused dependencies in GitHub workflow
- Non-default solver parameters in unit tests to test their precision
### Fixed
- Configuration of `tox-gh-actions` for Python 3.7
- Enforce `USING_COVERAGE` in GitHub workflow configuration
- Remove redundant solver loop from `test_all_shapes`
## [2.2.0] - 2022-08-15
### Added
- Add `lb` and `ub` arguments to all `<solver>_solve_qp` functions
- Internal `conversions` submodule
### Changed
- Moved `concatenate_bounds` to internal `conversions` submodule
- Moved `convert_to_socp` to internal `conversions` submodule
- Renamed `concatenate_bounds` to `linear_from_box_inequalities`
- Renamed internal `convert_to_socp` function to `socp_from_qp`
## [2.1.0] - 2022-07-25
### Added
- Document how to add a new QP solver to the library
- Example with (box) lower and upper bounds
- Test case where `lb` XOR `ub` is set
### Changed
- SCS: use the box cone API when lower/upper bounds are set
## [2.0.0] - 2022-07-05
### Added
- Exception `NoSolverSelected` raised when the solver kwarg is missing
- Starter set of QP solvers as optional dependencies
- Test exceptions raised by `solve_ls` and `solve_qp`
### Changed
- **Breaking:** `solver` keyword argument is now mandatory for `solve_ls`
- **Breaking:** `solver` keyword argument is now mandatory for `solve_qp`
- Quadratic programming example now randomly selects an available solver
## [1.10.0] - 2022-06-25
### Changed
- qpSWIFT: forward solver options as keywords arguments as with other solvers
## [1.9.1] - 2022-05-02
### Fixed
- OSQP: pass extra keyword arguments properly (thanks to @urob)
## [1.9.0] - 2022-04-03
### Added
- Benchmark on model predictive control problem
- Model predictive control example
- qpSWIFT 0.0.2 solver interface
### Changed
- Compute colors automatically in benchmark example
### Fixed
- Bounds concatenation for CVXOPT sparse matrices
## [1.8.1] - 2022-03-05
### Added
- Setup instructions for Microsoft Visual Studio
- Unit tests where the problem is unbounded below
### Changed
- Minimum supported Python version is now 3.7
### Fixed
- Clear all Pylint warnings
- Disable Pylint false positives that are covered by mypy
- ECOS: raise a ValueError when the cost matrix is not positive definite
## [1.8.0] - 2022-01-13
### Added
- Build and test for Python 3.10 in GitHub Actions
### Changed
- Moved SCS to sparse solvers
- Re-run solver benchmark reported to the README
- Removed `requirements2.txt` and update Python 2 installation instructions
- Updated SCS to new 3.0 version
### Fixed
- Handle sparse matrices in `print_matrix_vector`
- Match `__all__` in model and top-level `__init__.py`
- Run unit tests in GitHub Actions
- Typing error in bound concatenation
## [1.7.2] - 2021-11-24
### Added
- Convenience function to prettyprint a matrix and vector side by side
### Changed
- Move old tests from the examples folder to the unit test suite
- Removed deprecated `requirements.txt` installation file
- Renamed `solvers` optional dependencies to `all_pypi_solvers`
## [1.7.1] - 2021-10-02
### Fixed
- Make CVXOPT optional again (thanks to @adamoppenheimer)
## [1.7.0] - 2021-09-19
### Added
- Example script corresponding exactly to the README
- Handle lower and upper bounds with sparse matrices (thanks to @MeindertHH)
- SCS 2.0 solver interface
- Type annotations to all solve functions
- Unit tests: package coverage is now 94%
### Changed
- ECOS: simplify sparse matrix conversions
- Ignore warnings when running unit tests
- Inequality tolerance is now 1e-10 when validating solvers on README example
- Refactor QP to SOCP conversion to use more than one SOCP solver
- Rename "example problem" for testing to "README problem" (less ambiguous)
- Rename `sw` parameter of `solve_safer_qp` to `sr` for "slack repulsion"
- Reorganize code with a qpsolvers/solvers submodule
- quadprog: warning when `initvals is not None` is now verbose
### Fixed
- OSQP: forward keyword arguments to solver properly
- quadprog: forward keyword arguments to solver properly
## [1.6.1] - 2021-04-09
### Fixed
- Add quadprog dependency properly in `pyproject.toml`
## [1.6.0] - 2021-04-09
### Added
- Add `__version__` to main module
- First unit tests to check all solvers over a pre-defined set of problems
- GitHub Actions now make sure the project is built and tested upon updates
- Type hints now decorate all function definitions
### Changed
- Code formatting now applies [Black](https://github.com/psf/black)
- ECOS: refactor SOCP conversion to improve function readability
- Gurobi: performance significantly improved by new matrix API (thanks to @DKenefake)
### Fixed
- CVXPY: properly return `None` on unfeasible problems
- Consistently warn when `initvals` is passed but ignored by solver interface
- ECOS: properly return `None` on unfeasible problems
- Fix `None` case in `solve_safer_qp` (found by static type checking)
- Fix warnings in repository-level `__init__.py`
- OSQP: properly return `None` on unfeasible problems
- Pass Flake8 validation for overall code style
- Reduce complexity of entry `solve_qp` via a module-level solve-function index
- Remove Python 2 compatibility line from examples
- quadprog: properly return `None` on unfeasible problems (thanks to @DKenefake)
## [1.5.0] - 2020-12-05
### Added
- Upgrade to Python 3 and deprecate Python 2
- Saved Python 2 package versions to `requirements2.txt`
### Fixed
- Deprecation warning in CVXPY
## [1.4.1] - 2020-11-29
### Added
- New `solve_ls` function to solve linear Least Squares problems
### Fixed
- Call to `print` in PyPI description
- Handling of quadprog ValueError exceptions
## [1.4.0] - 2020-07-04
### Added
- Solver settings can now by passed to `solve_qp` as keyword arguments
- Started an [API documentation](https://scaron.info/doc/qpsolvers/)
### Changed
- Made `verbose` an explicit keyword argument of all internal functions
- OSQP settings now match precision of other solvers (thanks to @Neotriple)
## [1.3.1] - 2020-06-13
### Fixed
- Equation of quadratic program on [PyPI page](https://pypi.org/project/qpsolvers/)
## [1.3.0] - 2020-05-16
### Added
- Lower and upper bound keyword arguments `lb` and `ub`
### Fixed
- Check that equality/inequality matrices/vectors are provided consistently
- Relaxed offset check in [test\_solvers.py](examples/test_solvers.py)
## [1.2.1] - 2020-05-16
### Added
- CVXPY: verbose keyword argument
- ECOS: verbose keyword argument
- Gurobi: verbose keyword argument
- OSQP: verbose keyword argument
### Fixed
- Ignore verbosity argument when solver is not available
## [1.2.0] - 2020-05-16
### Added
- cvxopt: verbose keyword argument
- mosek: verbose keyword argument
- qpoases: verbose keyword argument
## [1.1.2] - 2020-05-15
### Fixed
- osqp: handle both old and more recent versions
## [1.1.1] - 2020-05-15
### Fixed
- Avoid variable name clash in OSQP
- Handle quadprog exception to avoid confusion on cost matrix notation
## [1.1.0] - 2020-03-07
### Added
- ECOS solver interface (no need to go through CVXPY any more)
- Update ECOS performance in benchmark (much better than before!)
### Fixed
- Fix link to ECOS in setup.py
- Remove ned for IPython in solver test
- Update notes on P matrix
## [1.0.7] - 2019-10-26
### Changed
- Always reshape A or G vectors into one-line matrices
### Fixed
- cvxopt: handle case where G and h are None but not A and b
- osqp: handle case where G and h are None
- osqp: handle case where both G and A are one-line matrices
- qpoases: handle case where G and h are None but not A and b
## [1.0.6] - 2019-10-26
Thanks to Brian Delhaisse and Soeren Wolfers who contributed fixes to this
release!
### Fixed
- quadprog: handle case where G and h are None
- quadprog: handle cas where A.ndim == 1
- Make examples compatible with both Python 2 and Python 3
## [1.0.5] - 2019-04-10
### Added
- Equality constraint shown in the README example
- Installation file `requirements.txt`
- Installation instructions for qpOASES
- OSQP: automatic CSC matrix conversions (with performance warnings)
- This change log
### Fixed
- CVXOPT: case where A is one-dimensional
- qpOASES: case where both G and A are not None
- quadprog: wrapper for one-dimensional A matrix (thanks to @nvitucci)
### Changed
- CVXOPT version is now 1.1.8 due to [this issue](https://github.com/urinieto/msaf-gpl/issues/2)
- Examples now in a [separate folder](examples)
## [1.0.4] - 2018-07-05
### Added
- A changelog :)
[unreleased]: https://github.com/qpsolvers/qpsolvers/compare/v4.11.0...HEAD
[4.11.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.11.0
[4.10.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.10.0
[4.9.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.9.0
[4.8.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.2
[4.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.1
[4.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.0
[4.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.7.1
[4.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.7.0
[4.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.6.0
[4.5.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.5.1
[4.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.5.0
[4.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.4.0
[4.3.3]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.3
[4.3.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.2
[4.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.1
[4.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.0
[4.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.2.0
[4.1.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.1.1
[4.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.1.0
[4.0.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.0.1
[4.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.0.0
[3.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.5.0
[3.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.4.0
[3.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.3.1
[3.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.3.0
[3.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.2.0
[3.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.1.0
[3.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.0.0
[2.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.8.1
[2.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.8.0
[2.7.3]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.3
[2.7.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.2
[2.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.1
[2.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.0
[2.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.6.0
[2.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.5.0
[2.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.4.0
[2.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.3.0
[2.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.2.0
[2.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.1.0
[2.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.0.0
[1.10.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.10.0
[1.9.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.9.1
[1.9.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.9.0
[1.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.8.1
[1.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.8.0
[1.7.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.2
[1.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.1
[1.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.0
[1.6.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.6.1
[1.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.6.0
[1.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.5.0
[1.4.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.4.1
[1.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.4.0
[1.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.3.1
[1.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.3.0
[1.2.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.2.1
[1.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.2.0
[1.1.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.2
[1.1.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.1
[1.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.0
[1.0.7]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.7
[1.0.6]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.6
[1.0.5]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.5
[1.0.4]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.4
================================================
FILE: CITATION.cff
================================================
cff-version: 1.2.0
message: "If you find this code helpful, please cite it as below."
title: "qpsolvers: Quadratic Programming Solvers in Python"
version: 4.11.0
date-released: 2026-03-10
url: "https://github.com/qpsolvers/qpsolvers"
license: "LGPL-3.0"
authors:
- family-names: "Caron"
given-names: "Stéphane"
orcid: "https://orcid.org/0000-0003-2906-692X"
- family-names: "Arnström"
given-names: "Daniel"
- family-names: "Bonagiri"
given-names: "Suraj"
- family-names: "Dechaume"
given-names: "Antoine"
- family-names: "Flowers"
given-names: "Nikolai"
- family-names: "Heins"
given-names: "Adam"
- family-names: "Ishikawa"
given-names: "Takuma"
- family-names: "Kenefake"
given-names: "Dustin"
- family-names: "Mazzamuto"
given-names: "Giacomo"
- family-names: "Meoli"
given-names: "Donato"
- family-names: "O'Donoghue"
given-names: "Brendan"
- family-names: "Oppenheimer"
given-names: "Adam A."
- family-names: "Otta"
given-names: "Pavel"
- family-names: "Pandala"
given-names: "Abhishek"
- family-names: "Quiroz Omaña"
given-names: "Juan José"
- family-names: "Rontsis"
given-names: "Nikitas"
- family-names: "Shah"
given-names: "Paarth"
- family-names: "St-Jean"
given-names: "Samuel"
- family-names: "Vitucci"
given-names: "Nicola"
- family-names: "Wolfers"
given-names: "Soeren"
- family-names: "Yang"
given-names: "Fengyu"
- family-names: "Delhaisse"
given-names: "Brian"
- family-names: "MeindertHH"
- family-names: "rimaddo"
- family-names: "urob"
- family-names: "shaoanlu"
- family-names: "Sandoval"
given-names: "Uriel"
- family-names: "Khalil"
given-names: "Ahmed"
- family-names: "Kozlov"
given-names: "Lev"
- family-names: "Groudiev"
given-names: "Antoine"
- family-names: "Sousa Pinto"
given-names: "João"
orcid: "https://orcid.org/0000-0003-2469-2809"
- family-names: "Rey"
given-names: "Valérian"
- family-names: "Schwan"
given-names: "Roland"
- family-names: "Budhiraja"
given-names: "Rohan"
- family-names: "Keustermans"
given-names: "Johannes"
- family-names: "Wu"
given-names: "Yubin"
- family-names: "Li"
given-names: "Hongpei"
orcid: "https://orcid.org/0009-0000-3001-8883"
================================================
FILE: CONTRIBUTING.md
================================================
# 👷 Contributing
There are many ways you can contribute to qpsolvers. Here are some ideas:
- Add a [new solver](https://scaron.info/doc/qpsolvers/developer-notes.html#adding-a-new-solver) to the library, for instance one from the [wish list](https://github.com/qpsolvers/qpsolvers/issues?q=is%3Aissue+is%3Aopen+label%3A%22new+solver%22)
- Describe your use case in [Show and tell](https://github.com/qpsolvers/qpsolvers/discussions/categories/show-and-tell)
- Suggest improvements to the library, see for instance [these contributions](https://github.com/qpsolvers/qpsolvers/pulls?q=is%3Apr+-author%3Astephane-caron+is%3Amerged)
- Find code that is [not covered](https://coveralls.io/github/qpsolvers/qpsolvers?branch=main) by unit tests, and add a test for it
- Address one of the [open issues](https://github.com/qpsolvers/qpsolvers/issues?q=is%3Aissue+is%3Aopen)
When you contribute a PR to the library, make sure to add yourself to `CITATION.cff` and to the BibTeX citation in the readme.
================================================
FILE: LICENSE
================================================
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
================================================
FILE: README.md
================================================
# Quadratic Programming Solvers in Python
[](https://github.com/qpsolvers/qpsolvers/actions)
[](https://qpsolvers.github.io/qpsolvers/)
[](https://coveralls.io/github/qpsolvers/qpsolvers?branch=main)
[](https://anaconda.org/conda-forge/qpsolvers)
[](https://pypi.org/project/qpsolvers/)
[](https://pypistats.org/packages/qpsolvers)
This library provides a [`solve_qp`](https://qpsolvers.github.io/qpsolvers/quadratic-programming.html#qpsolvers.solve_qp) function to solve convex quadratic programs:
$$
\begin{split}
\begin{array}{ll}
\underset{x}{\mbox{minimize}}
& \frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}
\end{split}
$$
Vector inequalities apply coordinate by coordinate. The function returns the primal solution $x^\*$ found by the backend QP solver, or ``None`` in case of failure/unfeasible problem. All solvers require the problem to be convex, meaning the matrix $P$ should be [positive semi-definite](https://en.wikipedia.org/wiki/Definite_symmetric_matrix). Some solvers further require the problem to be strictly convex, meaning $P$ should be positive definite.
**Dual multipliers:** there is also a [`solve_problem`](https://qpsolvers.github.io/qpsolvers/quadratic-programming.html#qpsolvers.solve_problem) function that returns not only the primal solution, but also its dual multipliers and all other relevant quantities computed by the backend solver.
## Example
To solve a quadratic program, build the matrices that define it and call ``solve_qp``, selecting the backend QP solver via the ``solver`` keyword argument:
```python
import numpy as np
from qpsolvers import solve_qp
M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = M.T @ M # this is a positive definite matrix
q = np.array([3.0, 2.0, 3.0]) @ M
G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])
h = np.array([3.0, 2.0, -2.0])
A = np.array([1.0, 1.0, 1.0])
b = np.array([1.0])
x = solve_qp(P, q, G, h, A, b, solver="proxqp")
print(f"QP solution: {x = }")
```
This example outputs the solution ``[0.30769231, -0.69230769, 1.38461538]``. It is also possible to get dual multipliers at the solution, as shown in [this example](https://qpsolvers.github.io/qpsolvers/quadratic-programming.html#dual-multipliers).
## Installation
### From conda-forge
```console
conda install -c conda-forge qpsolvers
```
### From PyPI
To install the library with open source QP solvers:
```console
pip install qpsolvers[open_source_solvers]
```
This one-size-fits-all installation may not work immediately on all systems (for instance if [a solver tries to compile from source](https://github.com/quadprog/quadprog/issues/42)). If you run into any issue, check out the following variants:
- ``pip install qpsolvers[wheels_only]`` will only install solvers with pre-compiled binaries,
- ``pip install qpsolvers[clarabel,daqp,proxqp,scs]`` (for instance) will install the listed set of QP solvers,
- ``pip install qpsolvers`` will only install the library itself.
When imported, qpsolvers loads all the solvers it can find and lists them in ``qpsolvers.available_solvers``.
## Solvers
| Solver | Keyword | Algorithm | API | License |
|------------------------------------------------------------------------------|-----------------| --------- | --- | ------- |
| [Clarabel](https://github.com/oxfordcontrol/Clarabel.rs) | ``clarabel`` | Interior point | Sparse | Apache-2.0 |
| [COPT](https://www.shanshu.ai/copt) | ``copt`` | Interior point | Sparse | Commercial |
| [CVXOPT](http://cvxopt.org/) | ``cvxopt`` | Interior point | Dense | GPL-3.0 |
| [DAQP](https://github.com/darnstrom/daqp) | ``daqp`` | Active set | Dense | MIT |
| [ECOS](https://web.stanford.edu/~boyd/papers/ecos.html) | ``ecos`` | Interior point | Sparse | GPL-3.0 |
| [Gurobi](https://www.gurobi.com/) | ``gurobi`` | Interior point | Sparse | Commercial |
| [HiGHS](https://highs.dev/) | ``highs`` | Active set | Sparse | MIT |
| [HPIPM](https://github.com/giaf/hpipm) | ``hpipm`` | Interior point | Dense | BSD-2-Clause |
| [jaxopt.OSQP](https://jaxopt.github.io/stable/_autosummary/jaxopt.OSQP.html) | ``jaxopt_osqp`` | Augmented Lagrangian | Dense | Apache-2.0 |
| [KVXOPT](https://github.com/sanurielf/kvxopt) | ``kvxopt`` | Interior point | Dense & Sparse | GPL-3.0 |
| [MOSEK](https://mosek.com/) | ``mosek`` | Interior point | Sparse | Commercial |
| NPPro | ``nppro`` | Active set | Dense | Commercial |
| [OSQP](https://osqp.org/) | ``osqp`` | Augmented Lagrangian | Sparse | Apache-2.0 |
| [PDHCG](https://github.com/Lhongpei/PDHCG-II) | ``pdhcg`` | Primal-dual hybrid gradient | Dense & Sparse | Apache-2.0 |
| [PIQP](https://github.com/PREDICT-EPFL/piqp) | ``piqp`` | Proximal interior point | Dense & Sparse | BSD-2-Clause |
| [ProxQP](https://github.com/Simple-Robotics/proxsuite) | ``proxqp`` | Augmented Lagrangian | Dense & Sparse | BSD-2-Clause |
| [QPALM](https://github.com/kul-optec/QPALM) | ``qpalm`` | Augmented Lagrangian | Sparse | LGPL-3.0 |
| [qpmad](https://github.com/asherikov/qpmad) | ``qpmad`` | Active set | Dense | Apache-2.0 |
| [QTQP](https://github.com/google-deepmind/qtqp) | ``qtqp`` | Interior point | Sparse | Apache-2.0 |
| [qpax](https://github.com/kevin-tracy/qpax/) | ``qpax`` | Interior point | Dense | MIT |
| [qpOASES](https://github.com/coin-or/qpOASES) | ``qpoases`` | Active set | Dense | LGPL-2.1 |
| [qpSWIFT](https://github.com/qpSWIFT/qpSWIFT) | ``qpswift`` | Interior point | Sparse | GPL-3.0 |
| [quadprog](https://github.com/quadprog/quadprog) | ``quadprog`` | Active set | Dense | GPL-2.0 |
| [SCS](https://www.cvxgrp.org/scs/) | ``scs`` | Augmented Lagrangian | Sparse | MIT |
| [SIP](https://github.com/joaospinto/sip_python) | ``sip`` | Barrier Augmented Lagrangian | Sparse | MIT |
Matrix arguments are NumPy arrays for dense solvers and SciPy Compressed Sparse Column (CSC) matrices for sparse ones.
## Frequently Asked Questions
- [Can I print the list of solvers available on my machine?](https://github.com/qpsolvers/qpsolvers/discussions/37)
- [Is it possible to solve a least squares rather than a quadratic program?](https://github.com/qpsolvers/qpsolvers/discussions/223)
- [I have a squared norm in my cost function, how can I apply a QP solver to my problem?](https://github.com/qpsolvers/qpsolvers/discussions/224)
- [I have a non-convex quadratic program, is there a solver I can use?](https://github.com/qpsolvers/qpsolvers/discussions/240)
- [I have quadratic equality constraints, is there a solver I can use?](https://github.com/qpsolvers/qpsolvers/discussions/241)
- [Error: Mircrosoft Visual C++ 14.0 or greater is required on Windows](https://github.com/qpsolvers/qpsolvers/discussions/257)
- [Can I add penalty terms as in ridge regression or LASSO?](https://github.com/qpsolvers/qpsolvers/discussions/272)
## Benchmark
QP solvers come with their strengths and weaknesses depending on the algorithmic choices they make. To help you find the ones most suited to your problems, you can check out the results from [`qpbenchmark`](https://github.com/qpsolvers/qpbenchmark), a benchmark for QP solvers in Python. The benchmark is divided into test sets, each test set representing a different distribution of quadratic programs with specific dimensions and structure (large sparse problems, optimal control problems, ...):
- 📈 [Free-for-all test set](https://github.com/qpsolvers/free_for_all_qpbenchmark): open to all problems submitted by the community.
- 📈 [Maros-Meszaros test set](https://github.com/qpsolvers/maros_meszaros_qpbenchmark): hard problems curated by the numerical optimization community.
- 📈 [MPC test set](https://github.com/qpsolvers/mpc_qpbenchmark): convex model predictive control problems arising in robotics.
## Citing qpsolvers
If you find this project useful, please consider giving it a :star: or citing it if your work is scientific:
```bibtex
@software{qpsolvers,
title = {{qpsolvers: Quadratic Programming Solvers in Python}},
author = {Caron, Stéphane and Arnström, Daniel and Bonagiri, Suraj and Dechaume, Antoine and Flowers, Nikolai and Heins, Adam and Ishikawa, Takuma and Kenefake, Dustin and Mazzamuto, Giacomo and Meoli, Donato and O'Donoghue, Brendan and Oppenheimer, Adam A. and Otta, Pavel and Pandala, Abhishek and Quiroz Omaña, Juan José and Rontsis, Nikitas and Shah, Paarth and St-Jean, Samuel and Vitucci, Nicola and Wolfers, Soeren and Yang, Fengyu and Delhaisse, Brian and MeindertHH and rimaddo and urob and shaoanlu and Sandoval, Uriel and Khalil, Ahmed and Kozlov, Lev and Groudiev, Antoine and Sousa Pinto, João and Schwan, Roland and Budhiraja, Rohan and Keustermans, Johannes and Wu, Yubin and Li, Hongpei},
license = {LGPL-3.0},
url = {https://github.com/qpsolvers/qpsolvers},
version = {4.11.0},
year = {2026}
}
```
Don't forget to add yourself to the BibTeX above and to `CITATION.cff` if you contribute to this repository.
## Contributing
We welcome contributions! The first step is to install the library and use it. Report any bug in the [issue tracker](https://github.com/qpsolvers/qpsolvers/issues). If you're a developer looking to hack on open source, check out the [contribution guidelines](https://github.com/qpsolvers/qpsolvers/blob/main/CONTRIBUTING.md) for suggestions.
## See also
- [qpbenchmark](https://github.com/qpsolvers/qpbenchmark/): Benchmark for quadratic programming solvers available in Python.
- [qpsolvers-eigen](https://github.com/ami-iit/qpsolvers-eigen): C++ abstraction layer for quadratic programming solvers using Eigen.
================================================
FILE: doc/conf.py
================================================
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2024 Stéphane Caron and the qpsolvers contributors
import re
import sys
from os.path import abspath, dirname, join
sys.path.insert(0, abspath(".."))
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.coverage",
"sphinx-mathjax-offline",
"sphinx.ext.napoleon", # before sphinx_autodoc_typehints
"sphinx_autodoc_typehints",
]
# List of modules to be mocked up
autodoc_mock_imports = [
"coptpy",
"ecos",
"gurobipy",
"hpipm_python",
"mosek",
"nppro",
"osqp",
"pdhcg",
"qpoases",
"qpSWIFT",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = []
# The suffix(es) of source filenames.
source_suffix = {".rst": "restructuredtext"}
# The master toctree document.
master_doc = "index"
# General information about the project.
project = "qpsolvers"
copyright = "2016-2024 Stéphane Caron and the qpsolvers contributors"
author = "Stéphane Caron"
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
# The short X.Y version.
version = None
# The full version, including alpha/beta/rc tags.
release = None
# Read version info directly from the module's __init__.py
init_path = join(dirname(dirname(str(abspath(__file__)))), "qpsolvers")
with open(f"{init_path}/__init__.py", "r") as fh:
for line in fh:
match = re.match(
r'__version__ = "((\d+)\.(\d+)\.\d+)[a-z0-9\-]*".*',
line,
)
if match is not None:
release = f"{match.group(2)}.{match.group(3)}"
version = match.group(1)
assert len(release.split(".")) == 2, f"{release=}"
assert len(version.split(".")) == 3, f"{version=}"
break
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = "en"
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ["build", "Thumbs.db", ".DS_Store"]
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "furo"
# Override Pygments style.
pygments_style = "sphinx"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {}
# Output file base name for HTML help builder.
htmlhelp_basename = "qpsolversdoc"
================================================
FILE: doc/developer-notes.rst
================================================
***************
Developer notes
***************
Adding a new solver
===================
Let's imagine we want to add a new solver called *AwesomeQP*. The solver keyword, the string passed via the ``solver`` keyword argument, is the lowercase version of the vernacular name of a QP solver. For our imaginary solver, the keyword is therefore ``"awesomeqp"``.
The process to add AwesomeQP to *qpsolvers* goes as follows:
1. Create a new file ``qpsolvers/solvers/awesomeqp_.py`` (named after the solver keyword, with a trailing underscore)
2. Implement in this file a function ``awesomeqp_solve_problem`` that returns a :class:`.Solution`
3. Implement in the same file a function ``awesomeqp_solve_qp`` to connect it to the historical API, typically as follows:
.. code:: python
def awesomeqp_solve_qp(P, q, G, h, A, b, lb, ub, initvals=None, verbose=False, **kwargs):
) -> Optional[np.ndarray]:
r"""Solve a quadratic program using AwesomeQP.
[document parameters and return values here]
"""
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = awesomeqp_solve_problem(
problem, initvals, verbose, backend, **kwargs
)
return solution.x if solution.found else None
4. Define the two function prototypes for ``awesomeqp_solve_problem`` and ``awesomeqp_solve_qp`` in ``qpsolvers/solvers/__init__.py``:
.. code:: python
# AwesomeQP
# ========
awesome_solve_qp: Optional[
Callable[
[
ndarray,
ndarray,
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[str],
bool,
],
Optional[ndarray],
]
] = None
.. note::
The prototype needs to match the actual function. You can check its correctness by running ``tox -e py`` in the repository.
5. Below the prototype, import the function into the ``solve_function`` dictionary:
.. code:: python
try:
from .awesomeqp_ import awesomeqp_solve_qp
solve_function["awesomeqp"] = awesomeqp_solve_qp
available_solvers.append("awesomeqp")
# dense_solvers.append("awesomeqp") if applicable
# sparse_solvers.append("awesomeqp") if applicable
except ImportError:
pass
6. Append the solver identifier to ``dense_solvers`` or ``sparse_solvers``, if applicable
7. Import ``awesomeqp_solve_qp`` from ``qpsolvers/__init__.py`` and add it to ``__all__``
8. Add the solver to ``doc/src/supported-solvers.rst``
9. Add the solver to the *Solvers* section of the README
10. Assuming AwesomeQP is distributed on `PyPI <https://pypi.org/>`__, add it to the ``[testenv]`` and ``[testenv:coverage]`` environments of ``tox.ini`` for unit testing
11. Assuming AwesomeQP is distributed on Conda or PyPI, add it to the list of dependencies in ``doc/environment.yml``
12. Log the new solver as an addition in the changelog
13. If you are a new contributor, feel free to add your name to ``CITATION.cff``.
Problem conversions
===================
.. automodule:: qpsolvers.conversions
:members:
Testing locally
===============
To run all CI checks locally, go to the repository folder and run
.. code:: bash
tox -e py
This will run linters and unit tests.
================================================
FILE: doc/index.rst
================================================
.. title:: Table of Contents
#########
qpsolvers
#########
Unified interface to Quadratic Programming (QP) solvers available in Python.
The library provides a one-stop shop :func:`.solve_qp` function with a ``solver`` keyword argument to select the backend solver. It solves :ref:`convex quadratic programs <Quadratic programming>` in standard form:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}\end{split}
A similar function is provided for :ref:`least squares <Least squares>`.
.. toctree::
:maxdepth: 1
installation.rst
quadratic-programming.rst
least-squares.rst
supported-solvers.rst
unsupported-solvers.rst
developer-notes.rst
references.rst
================================================
FILE: doc/installation.rst
================================================
************
Installation
************
Linux
=====
Conda
-----
To install the library from `conda-forge <https://conda-forge.org/>`__, simply run:
.. code:: bash
conda install -c conda-forge qpsolvers
PyPI
----
First, install the pip package manager, for example on a recent Debian-based distribution with Python 3:
.. code:: bash
sudo apt install python3-dev
You can then install the library by:
.. code:: bash
pip install qpsolvers
Add the ``--user`` parameter for a user-only installation.
Windows
=======
Anaconda
--------
- First, install the `Visual C++ Build Tools <https://visualstudio.microsoft.com/visual-cpp-build-tools/>`_
- Install your Python environment, for instance `Anaconda <https://docs.anaconda.com/anaconda/install/windows/>`_
- Install the library from conda-forge, for instance in a terminal opened from the Anaconda Navigator:
.. code:: bash
conda install -c conda-forge qpsolvers
Microsoft Visual Studio
-----------------------
- Open Microsoft Visual Studio
- Create a new project:
- Select a new "Python Application" project template
- Click "Next"
- Give a name to your project
- Click "Create"
- Go to Tools → Python → Python Environments:
- To the left of the "Python Environments" tab that opens, select a Python version >= 3.8
- Click on "Packages (PyPI)"
- In the search box, type "qpsolvers"
- Below the search box, click on "Run command: pip install qpsolvers"
- A window pops up asking for administrator privileges: grant them
- Check the text messages in the "Output" pane at the bottom of the window
- Go to the main code tab (it should be your project name followed by the ".py" extension)
- Copy the `example code <https://github.com/qpsolvers/qpsolvers#example>`_ from the README and paste it there
- Click on the "Run" icon in the toolbar to execute this program
At this point a ``python.exe`` window should open with the following output:
.. code:: bash
QP solution: x = [0.30769231, -0.69230769, 1.38461538]
Press any key to continue . . .
Solvers
=======
Open source solvers
-------------------
To install at once all open source QP solvers available from the `Python
Package Index <https://pypi.org/>`_, run the ``pip`` command as follows:
.. code:: bash
pip install "qpsolvers[open_source_solvers]"
You can also install a subset of QP solvers of your liking, for instance:
.. code:: bash
pip install qpsolvers[clarabel,daqp,proxqp,scs]
.. _gurobi-install:
Gurobi
------
Gurobi comes with a `one-line pip installation
<https://www.gurobi.com/documentation/9.1/quickstart_linux/cs_using_pip_to_install_gr.html>`_
where you can fetch the solver directly from the company servers:
.. code:: bash
python -m pip install -i https://pypi.gurobi.com gurobipy
This version comes with limitations. For instance, trying to solve a problem
with 200 optimization variables fails with the following warning:
.. code:: python
Warning: Model too large for size-limited license; visit https://www.gurobi.com/free-trial for a full license
.. _copt-install:
COPT
------
COPT comes with an installation doc at `COPT installation
<https://guide.coap.online/copt/en-doc/install.html>`_
where you can install by pip:
.. code:: bash
python -m pip install coptpy
This version comes with limitations. For instance, trying to solve a problem
with 200 optimization variables fails with the following warning:
.. code:: python
No license found. Starting COPT with size limitations for non-commercial use
Please apply for a license from www.shanshu.ai/copt
.. _qpoases-install:
HiGHS
-----
The simplest way to install HiGHS is:
.. code:: bash
pip install highspy
If this solution doesn't work for you, follow the `Python installation
instructions <https://github.com/ERGO-Code/HiGHS#python>`__ from the README.
PDHCG
-----
You can install the GPU-accelerated PDHCG solver directly from PyPI:
.. code:: bash
pip install pdhcg
Note that PDHCG requires an NVIDIA GPU and CUDA 12.0+. If your system has multiple CUDA versions or the installation fails to find the compiler, you must explicitly point to your modern CUDA compiler using environment variables before installing:
.. code:: bash
export CUDACXX=/your/path/to/nvcc
export SKBUILD_CMAKE_ARGS="-DCMAKE_CUDA_COMPILER=/your/path/to/nvcc"
pip install pdhcg
quadprog
--------
You can install the quadprog solver from PyPI:
.. code:: bash
pip install quadprog
This package comes with wheels to avoid recompiling the solver from source.
qpOASES
-------
The simplest way to install qpOASES is via conda-forge:
.. code:: bash
conda install -c conda-forge qpoases
You can also check out the `official qpOASES installation page
<https://projects.coin-or.org/qpOASES/wiki/QpoasesInstallation>`_ for the
latest release.
================================================
FILE: doc/least-squares.rst
================================================
.. _Least squares:
*************
Least squares
*************
To solve a linear least-squares problem, simply build the matrices that define
it and call the :func:`.solve_ls` function:
.. code:: python
from numpy import array, dot
from qpsolvers import solve_ls
R = array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])
s = array([3., 2., 3.])
G = array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])
h = array([3., 2., -2.]).reshape((3,))
x_sol = solve_ls(R, s, G, h, solver="osqp")
print(f"LS solution: {x_sol = }")
The backend QP solver is selected among :ref:`supported solvers <Supported
solvers>` via the ``solver`` keyword argument. This example outputs the
solution ``[0.12997217, -0.06498019, 1.74004125]``.
.. autofunction:: qpsolvers.solve_ls
See the ``examples/`` folder in the repository for more advanced use cases. For
a more general introduction you can also check out this post on `least squares
in Python <https://scaron.info/robotics/least-squares.html>`_.
================================================
FILE: doc/quadratic-programming.rst
================================================
.. _Quadratic programming:
*********************
Quadratic programming
*********************
Primal problem
==============
A quadratic program is defined in standard form as:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}\end{split}
The vectors :math:`lb` and :math:`ub` can contain :math:`\pm \infty` values to
disable bounds on some coordinates. To solve such a problem, build the matrices
that define it and call the :func:`.solve_qp` function:
.. code:: python
from numpy import array, dot
from qpsolvers import solve_qp
M = array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])
P = dot(M.T, M) # quick way to build a symmetric matrix
q = dot(array([3., 2., 3.]), M).reshape((3,))
G = array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])
h = array([3., 2., -2.]).reshape((3,))
A = array([1., 1., 1.])
b = array([1.])
x = solve_qp(P, q, G, h, A, b, solver="osqp")
print(f"QP solution: x = {x}")
The backend QP solver is selected among :ref:`supported solvers <Supported
solvers>` via the ``solver`` keyword argument. This example outputs the
solution ``[0.30769231, -0.69230769, 1.38461538]``.
.. autofunction:: qpsolvers.solve_qp
See the ``examples/`` folder in the repository for more use cases. For a more
general introduction you can also check out this post on `quadratic programming
in Python <https://scaron.info/blog/quadratic-programming-in-python.html>`_.
Problem class
=============
Alternatively, we can define the matrices and vectors using the :class:`.Problem` class:
.. autoclass:: qpsolvers.problem.Problem
:members:
The solve function corresponding to :class:`.Problem` is :func:`.solve_problem`
rather than :func:`.solve_qp`.
Dual multipliers
================
The dual of the quadratic program defined above can be written as:
.. math::
\begin{split}\begin{array}{ll}
\underset{x, z, y, z_{\mathit{box}}}{\mbox{maximize}} &
-\frac{1}{2} x^T P x - h^T z - b^T y
- lb^T z_{\mathit{box}}^- - ub^T z_{\mathit{box}}^+ \\
\mbox{subject to}
& P x + G^T z + A^T y + z_{\mathit{box}} + q = 0 \\
& z \geq 0
\end{array}\end{split}
were :math:`v^- = \min(v, 0)` and :math:`v^+ = \max(v, 0)`. To solve both a
problem and its dual, getting a full primal-dual solution :math:`(x^*, z^*,
y^*, z_\mathit{box}^*)`, build a :class:`.Problem` and call the
:func:`.solve_problem` function:
.. code:: python
import numpy as np
from qpsolvers import Problem, solve_problem
M = np.array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])
P = M.T.dot(M) # quick way to build a symmetric matrix
q = np.array([3., 2., 3.]).dot(M).reshape((3,))
G = np.array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])
h = np.array([3., 2., -2.]).reshape((3,))
A = np.array([1., 1., 1.])
b = np.array([1.])
lb = -0.6 * np.ones(3)
ub = +0.7 * np.ones(3)
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = solve_problem(problem, solver="proxqp")
print(f"Primal: x = {solution.x}")
print(f"Dual (Gx <= h): z = {solution.z}")
print(f"Dual (Ax == b): y = {solution.y}")
print(f"Dual (lb <= x <= ub): z_box = {solution.z_box}")
The function returns a :class:`.Solution` with both primal and dual vectors. This example outputs the following solution:
.. code::
Primal: x = [ 0.63333169 -0.33333307 0.70000137]
Dual (Gx <= h): z = [0. 0. 7.66660538]
Dual (Ax == b): y = [-16.63326017]
Dual (lb <= x <= ub): z_box = [ 0. 0. 26.26649724]
.. autofunction:: qpsolvers.solve_problem
See the ``examples/`` folder in the repository for more use cases. For an
introduction to dual multipliers you can also check out this post on
`optimality conditions and numerical tolerances in QP solvers
<https://scaron.info/blog/optimality-conditions-and-numerical-tolerances-in-qp-solvers.html>`_.
Optimality of a solution
========================
The :class:`.Solution` class describes the solution found by a solver to a
given problem. It is linked to the corresponding :class:`.Problem`, which it
can use for instance to check residuals. We can for instance check the
optimality of the solution returned by a solver with:
.. code:: python
import numpy as np
from qpsolvers import Problem, solve_problem
M = np.array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])
P = M.T.dot(M) # quick way to build a symmetric matrix
q = np.array([3., 2., 3.]).dot(M).reshape((3,))
G = np.array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])
h = np.array([3., 2., -2.]).reshape((3,))
A = np.array([1., 1., 1.])
b = np.array([1.])
lb = -0.6 * np.ones(3)
ub = +0.7 * np.ones(3)
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = solve_problem(problem, solver="qpalm")
print(f"- Solution is{'' if solution.is_optimal(1e-8) else ' NOT'} optimal")
print(f"- Primal residual: {solution.primal_residual():.1e}")
print(f"- Dual residual: {solution.dual_residual():.1e}")
print(f"- Duality gap: {solution.duality_gap():.1e}")
This example prints:
.. code::
- Solution is optimal
- Primal residual: 1.1e-16
- Dual residual: 1.4e-14
- Duality gap: 0.0e+00
You can check out [Caron2022]_ for an overview of optimality conditions and why
a solution is optimal if and only if these three residuals are zero.
.. autoclass:: qpsolvers.solution.Solution
:members:
================================================
FILE: doc/references.rst
================================================
**********
References
**********
.. [Tracy2024] `On the Differentiability of the Primal-Dual Interior-Point Method <https://arxiv.org/abs/2406.11749>`_, K. Tracy and Z. Manchester. ArXiv, 2024.
.. [Schwan2023] `PIQP: A Proximal Interior-Point Quadratic Programming Solver <https://arxiv.org/abs/2304.00290>`_, R. Schwan, Y. Jiang, D. Kuhn, C.N. Jones. ArXiv, 2023.
.. [Arnstrom2022] `A dual active-set solver for embedded quadratic programming using recursive LDL updates <https://doi.org/10.1109/TAC.2022.3176430>`_, D. Arnström, A. Bemporad and D. Axehill. IEEE Transactions on Automatic Control, 2022, 67, no. 8 p. 4362-4369.
.. [Bambade2022] `PROX-QP: Yet another Quadratic Programming Solver for Robotics and beyond <https://hal.inria.fr/hal-03683733/file/Yet_another_QP_solver_for_robotics_and_beyond.pdf/>`__, A. Bambade, S. El-Kazdadi, A. Taylor and J. Carpentier. Robotics: Science and Systems. 2022.
.. [Caron2022] `Optimality conditions and numerical tolerances in QP solvers <https://scaron.info/blog/optimality-conditions-and-numerical-tolerances-in-qp-solvers.html>`_, S. Caron, 2022.
.. [Hermans2022] `QPALM: A Newton-type Proximal Augmented Lagrangian Method for Quadratic Programs <https://arxiv.org/pdf/2010.02653.pdf>`_, B. Hermans, A. Themelis and P. Patrinos. Mathematical Programming Computation, 2022, vol. 14, no 3, p. 497-541.
.. [ODonoghue2021] `Operator splitting for a homogeneous embedding of the linear complementarity problem <https://arxiv.org/abs/2004.02177>`_, B. O'Donoghue. SIAM Journal on Optimization, 2021, vol. 31, no 3, p. 1999-2023.
.. [Frison2020] `HPIPM: a high-performance quadratic programming framework for model predictive control <https://arxiv.org/abs/2003.02547>`__, G. Frison and M. Diehl. IFAC-PapersOnline, 2020, vol. 53, no 2, p. 6563-6569.
.. [Stellato2020] `OSQP: An Operator Splitting Solver for Quadratic Programs <https://arxiv.org/abs/1711.08013>`__, B. Stellato, G. Banjac, P. Goulart, A. Bemporad, and S. Boyd. Mathematical Programming Computation, 2020, vol. 12, no 4, p. 637-672.
.. [Pandala2019] `qpSWIFT: A real-time sparse quadratic program solver for robotic applications <https://doi.org/10.1109/LRA.2019.2926664>`_, A. G. Pandala, Y. Ding and H. W. Park. IEEE Robotics and Automation Letters, 2019, vol. 4, no 4, p. 3355-3362.
.. [Huangfu2018] *Parallelizing the dual revised simplex method*. Q. Huangfu and J. Hall. Mathematical Programming Computation, 2018, vol. 10, no 1, p. 119-142.
.. [Ferreau2014] `qpOASES: A parametric active-set algorithm for quadratic programming <http://mpc.zib.de/archive/2014/4/Ferreau2014_Article_QpOASESAParametricActive-setAl.pdf>`_, H. J. Ferreau, C. Kirches, A. Potschka, H. G. Bock and M. Diehl. Mathematical Programming Computation, 2014, vol. 6, no 4, p. 327-363.
.. [Domahidi2013] `ECOS: An SOCP solver for embedded systems <https://web.stanford.edu/~boyd/papers/ecos.html>`_, A. Domahidi, E. Chu and S. Boyd. European Control Conference. IEEE, 2013. p. 3071-3076.
.. [Vandenberghe2010] `The CVXOPT linear and quadratic cone program solvers <https://www.seas.ucla.edu/~vandenbe/publications/coneprog.pdf>`_, L. Vandenberghe. 2010.
.. [Goldfarb1983] *A numerically stable dual method for solving strictly convex quadratic programs*. D. Goldfarb and A. Idnani. Mathematical Programming, vol. 27, p. 1-33.
================================================
FILE: doc/supported-solvers.rst
================================================
.. _Supported solvers:
*****************
Supported solvers
*****************
Solvers that are detected as installed on your machine are listed in:
.. autodata:: qpsolvers.available_solvers
Clarabel
========
.. automodule:: qpsolvers.solvers.clarabel_
:members:
COPT
========
.. automodule:: qpsolvers.solvers.copt_
:members:
CVXOPT
======
.. automodule:: qpsolvers.solvers.cvxopt_
:members:
DAQP
======
.. automodule:: qpsolvers.solvers.daqp_
:members:
ECOS
====
.. automodule:: qpsolvers.solvers.ecos_
:members:
Gurobi
======
.. automodule:: qpsolvers.solvers.gurobi_
:members:
HiGHS
=====
.. automodule:: qpsolvers.solvers.highs_
:members:
HPIPM
=====
.. automodule:: qpsolvers.solvers.hpipm_
:members:
jaxopt.OSQP
===========
.. automodule:: qpsolvers.solvers.jaxopt_osqp_
:members:
KVXOPT
======
.. automodule:: qpsolvers.solvers.kvxopt_
:members:
MOSEK
=====
.. automodule:: qpsolvers.solvers.mosek_
:members:
OSQP
====
.. automodule:: qpsolvers.solvers.osqp_
:members:
PIQP
======
.. automodule:: qpsolvers.solvers.piqp_
:members:
ProxQP
======
.. automodule:: qpsolvers.solvers.proxqp_
:members:
pyqpmad
=======
.. automodule:: qpsolvers.solvers.pyqpmad_
:members:
QPALM
=====
.. automodule:: qpsolvers.solvers.qpalm_
:members:
qpOASES
=======
.. automodule:: qpsolvers.solvers.qpoases_
:members:
qpSWIFT
=======
.. automodule:: qpsolvers.solvers.qpswift_
:members:
qpax
===
.. automodule:: qpsolvers.solvers.qpax_
:members:
QTQP
====
.. automodule:: qpsolvers.solvers.qtqp_
:members:
quadprog
========
.. automodule:: qpsolvers.solvers.quadprog_
:members:
SCS
===
.. automodule:: qpsolvers.solvers.scs_
:members:
SIP
===
.. automodule:: qpsolvers.solvers.sip_
:members:
================================================
FILE: doc/unsupported-solvers.rst
================================================
*******************
Unsupported solvers
*******************
Unsupported solvers will be made available if they are detected on your system, but their performance is not guaranteed as they are not part of `continuous integration <https://github.com/qpsolvers/qpsolvers/actions>`__ (typically because they are not open source).
PDHCG
=====
.. automodule:: qpsolvers.solvers.pdhcg_
:members:
NPPro
=====
.. automodule:: qpsolvers.solvers.nppro_
:members:
================================================
FILE: examples/README.md
================================================
# Quadratic programming examples
Examples are roughly sorted from simple to complex. The basic ones are:
- [Quadratic program](quadratic_program.py)
- [Linear least squares](least_squares.py)
- [Box inequalities](box_inequalities.py)
- [Sparse linear least squares](sparse_least_squares.py)
For more advance use cases, check out:
- [Dual multipliers](dual_multipliers.py)
## Applications
Feel free to share yours in [Show and tell](https://github.com/qpsolvers/qpsolvers/discussions/categories/show-and-tell)!
- [Constrained linear regression](constrained_linear_regression.py)
- [Model predictive control for humanoid locomotion](model_predictive_control.py)
================================================
FILE: examples/box_inequalities.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test an available QP solvers on a small problem with box inequalities."""
import random
from time import perf_counter
import numpy as np
from qpsolvers import available_solvers, print_matrix_vector, solve_qp
M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([3.0, 2.0, 3.0]), M)
A = np.array([1.0, 1.0, 1.0])
b = np.array([1.0])
lb = -0.5 * np.ones(3)
ub = 1.0 * np.ones(3)
x_sol = np.array([0.41463414566726164, -0.41463414566726164, 1.0])
if __name__ == "__main__":
start_time = perf_counter()
solver = random.choice(available_solvers)
x = solve_qp(P, q, A=A, b=b, lb=lb, ub=ub, solver=solver)
end_time = perf_counter()
print("")
print(" min. 1/2 x^T P x + q^T x")
print(" s.t. A * x == b")
print(" lb <= x <= ub")
print("")
print_matrix_vector(P, "P", q, "q")
print("")
print_matrix_vector(A, "A", b, "b")
print("")
print_matrix_vector(lb.reshape((3, 1)), "lb", ub, "ub")
print("")
print(f"Solution: x = {x}")
print(f"It should be close to x* = {x_sol}")
print(f"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}")
================================================
FILE: examples/constrained_linear_regression.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test a random QP solver on a constrained linear regression problem.
This example originates from:
https://stackoverflow.com/a/74422084
See also:
https://scaron.info/blog/simple-linear-regression-with-online-updates.html
"""
import random
import numpy as np
import qpsolvers
from qpsolvers import solve_ls
a = np.array([1.2, 2.3, 4.2])
b = np.array([1.0, 5.0, 6.0])
c = np.array([5.4, 6.2, 1.9])
m = np.vstack([a, b, c])
y = np.array([5.3, 0.9, 5.6])
# Objective: || [a b c] x - y ||^2
R = m.T
s = y
# Constraint: sum(x) = 1
A = np.ones((1, 3))
b = np.array([1.0])
# Constraint: x >= 0
lb = np.zeros(3)
if __name__ == "__main__":
solver = random.choice(qpsolvers.available_solvers)
x = solve_ls(R, s, A=A, b=b, lb=lb, solver=solver)
print(f"Found solution {x=}")
================================================
FILE: examples/dual_multipliers.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Get both primal and dual solutions to a quadratic program."""
import random
import numpy as np
from qpsolvers import (
Problem,
available_solvers,
print_matrix_vector,
solve_problem,
)
M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([3.0, 2.0, 3.0]), M)
G = np.array([[4.0, 2.0, 0.0], [-1.0, 2.0, -1.0]])
h = np.array([1.0, -2.0])
A = np.array([1.0, 1.0, 1.0]).reshape((1, 3))
b = np.array([1.0])
lb = np.array([-0.5, -0.4, -0.5])
ub = np.array([1.0, 1.0, 1.0])
if __name__ == "__main__":
solver = random.choice(available_solvers)
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = solve_problem(problem, solver)
print("========================= PRIMAL PROBLEM =========================")
print("")
print(" min. 1/2 x^T P x + q^T x")
print(" s.t. G x <= h")
print(" A x == b")
print(" lb <= x <= ub")
print("")
print_matrix_vector(P, "P", q, "q")
print("")
print_matrix_vector(G, "G", h, "h")
print("")
print_matrix_vector(A, "A", b, "b")
print("")
print_matrix_vector(lb.reshape((3, 1)), "lb", ub, "ub")
print("")
print("============================ SOLUTION ============================")
print("")
print(f'Found with solver="{solver}"')
print("")
print_matrix_vector(
solution.x.reshape((3, 1)),
"Primal x*",
solution.z,
"Dual (Gx <= h) z*",
)
print("")
print_matrix_vector(
solution.y.reshape((1, 1)),
"Dual (Ax == b) y*",
solution.z_box.reshape((3, 1)),
"Dual (lb <= x <= ub) z_box*",
)
print("")
print("=== Optimality checks ===")
print(f"- Primal residual: {solution.primal_residual():.1e}")
print(f"- Dual residual: {solution.dual_residual():.1e}")
print(f"- Duality gap: {solution.duality_gap():.1e}")
print("")
================================================
FILE: examples/lasso_regularization.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Apply lasso regularization to a quadratic program.
Details:
https://scaron.info/blog/lasso-regularization-in-quadratic-programming.html
"""
import numpy as np
from qpsolvers import solve_qp
# Objective: || R x - s ||^2
n = 6
R = np.diag(range(1, n + 1))
s = np.ones(n)
# Convert our least-squares objective to quadratic programming
P = np.dot(R.transpose(), R)
q = -np.dot(s.transpose(), R)
# Linear inequality constraints: G x <= h
G = np.array(
[
[1.0, 0.0] * (n // 2),
[0.0, 1.0] * (n // 2),
]
)
h = np.array([10.0, -10.0])
# Lasso parameter
t: float = 10.0
# Lasso: inequality constraints
G_lasso = np.vstack(
[
np.hstack([G, np.zeros((G.shape[0], n))]),
np.hstack([+np.eye(n), -np.eye(n)]),
np.hstack([-np.eye(n), -np.eye(n)]),
np.hstack([np.zeros((1, n)), np.ones((1, n))]),
]
)
h_lasso = np.hstack([h, np.zeros(n), np.zeros(n), t])
# Lasso: objective
P_lasso = np.vstack(
[
np.hstack([P, np.zeros((n, n))]),
np.zeros((n, 2 * n)),
]
)
q_lasso = np.hstack([q, np.ones(n)])
if __name__ == "__main__":
x_unreg = solve_qp(P, q, G, h, solver="proxqp")
print(f"Solution without lasso: {x_unreg = }")
lasso_res = solve_qp(P_lasso, q_lasso, G_lasso, h_lasso, solver="proxqp")
x_lasso = lasso_res[:n]
z_lasso = lasso_res[n:]
print(f"Solution with lasso ({t=}): {x_lasso = }")
print(f"We can check that abs(x_lasso) = {z_lasso = }")
================================================
FILE: examples/least_squares.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test a random available QP solver on a small least-squares problem."""
import random
from time import perf_counter
import numpy as np
import qpsolvers
from qpsolvers import print_matrix_vector, solve_ls
R = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
s = np.array([3.0, 2.0, 3.0])
G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])
h = np.array([3.0, 2.0, -2.0])
x_sol = np.array([0.1299734765610818, -0.0649867382805409, 1.7400530468778364])
if __name__ == "__main__":
start_time = perf_counter()
solver = random.choice(qpsolvers.available_solvers)
x = solve_ls(R, s, G, h, solver=solver, verbose=False)
end_time = perf_counter()
print("")
print(" min. || R * x - s ||^2")
print(" s.t. G * x <= h")
print("")
print_matrix_vector(R, "R", s, "s")
print("")
print_matrix_vector(G, "G", h, "h")
print("")
print(f"Solution: x = {x}")
print(f"It should be close to x* = {x_sol}")
print(f"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}")
================================================
FILE: examples/model_predictive_control.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test the "quadprog" QP solver on a model predictive control problem.
The problem is to balance a humanoid robot walking on a flat horizontal floor.
See the following post for context:
https://scaron.info/robot-locomotion/prototyping-a-walking-pattern-generator.html
"""
import random
from dataclasses import dataclass
from typing import Optional
import numpy as np
import matplotlib.pyplot as plt
from scipy.sparse import csc_matrix
import qpsolvers
from qpsolvers import solve_qp
gravity = 9.81 # [m] / [s]^2
@dataclass
class HumanoidSteppingProblem:
com_height: float = 0.8
dsp_duration: float = 0.1
end_pos: float = 0.3
foot_length: float = 0.1
horizon_duration: float = 2.5
nb_timesteps: int = 16
ssp_duration: float = 0.7
start_pos: float = 0.0
class LinearModelPredictiveControl:
"""
Linear model predictive control for a system with linear dynamics and
linear constraints. This class is fully documented at:
https://scaron.info/doc/pymanoid/walking-pattern-generation.html#pymanoid.mpc.LinearPredictiveControl
"""
def __init__(
self,
A,
B,
C,
D,
e,
x_init,
x_goal,
nb_timesteps: int,
wxt: Optional[float],
wxc: Optional[float],
wu: float,
):
assert C is not None or D is not None, "use LQR for unconstrained case"
assert (
wu > 0.0
), "non-negative control weight needed for regularization"
assert wxt is not None or wxc is not None, "set either wxt or wxc"
u_dim = B.shape[1]
x_dim = A.shape[1]
self.A = A
self.B = B
self.C = C
self.D = D
self.G = None
self.P = None
self.U = None
self.U_dim = u_dim * nb_timesteps
self.e = e
self.h = None
self.nb_timesteps = nb_timesteps
self.q = None
self.u_dim = u_dim
self.wu = wu
self.wxc = wxc
self.wxt = wxt
self.x_dim = x_dim
self.x_goal = x_goal
self.x_init = x_init
#
self.build()
def build(self):
phi = np.eye(self.x_dim)
psi = np.zeros((self.x_dim, self.U_dim))
G_list, h_list = [], []
phi_list, psi_list = [], []
for k in range(self.nb_timesteps):
# Loop invariant: x == psi * U + phi * x_init
if self.wxc is not None:
phi_list.append(phi)
psi_list.append(psi)
C = self.C[k] if type(self.C) is list else self.C
D = self.D[k] if type(self.D) is list else self.D
e = self.e[k] if type(self.e) is list else self.e
G = np.zeros((e.shape[0], self.U_dim))
h = e if C is None else e - np.dot(C.dot(phi), self.x_init)
if D is not None:
# we rely on G == 0 to avoid a slower +=
G[:, k * self.u_dim : (k + 1) * self.u_dim] = D
if C is not None:
G += C.dot(psi)
if k == 0 and D is None: # corner case, input has no effect
assert np.all(h >= 0.0)
else: # regular case
G_list.append(G)
h_list.append(h)
phi = self.A.dot(phi)
psi = self.A.dot(psi)
psi[:, self.u_dim * k : self.u_dim * (k + 1)] = self.B
P = self.wu * np.eye(self.U_dim)
q = np.zeros(self.U_dim)
if self.wxt is not None and self.wxt > 1e-10:
c = np.dot(phi, self.x_init) - self.x_goal
P += self.wxt * np.dot(psi.T, psi)
q += self.wxt * np.dot(c.T, psi)
if self.wxc is not None and self.wxc > 1e-10:
Phi = np.vstack(phi_list)
Psi = np.vstack(psi_list)
X_goal = np.hstack([self.x_goal] * self.nb_timesteps)
c = np.dot(Phi, self.x_init) - X_goal
P += self.wxc * np.dot(Psi.T, Psi)
q += self.wxc * np.dot(c.T, Psi)
self.P = P
self.q = q
self.G = np.vstack(G_list)
self.h = np.hstack(h_list)
self.P_csc = csc_matrix(self.P)
self.G_csc = csc_matrix(self.G)
def solve(self, solver: str, sparse: bool = False, **kwargs):
P = self.P_csc if sparse else self.P
G = self.G_csc if sparse else self.G
U = solve_qp(P, self.q, G, self.h, solver=solver, **kwargs)
self.U = U.reshape((self.nb_timesteps, self.u_dim))
@property
def states(self):
assert self.U is not None, "you need to solve() the MPC problem first"
X = np.zeros((self.nb_timesteps + 1, self.x_dim))
X[0] = self.x_init
for k in range(self.nb_timesteps):
X[k + 1] = self.A.dot(X[k]) + self.B.dot(self.U[k])
return X
class HumanoidModelPredictiveControl(LinearModelPredictiveControl):
def __init__(self, problem: HumanoidSteppingProblem):
T = problem.horizon_duration / problem.nb_timesteps
nb_init_dsp_steps = int(round(problem.dsp_duration / T))
nb_init_ssp_steps = int(round(problem.ssp_duration / T))
nb_dsp_steps = int(round(problem.dsp_duration / T))
state_matrix = np.array(
[[1.0, T, T ** 2 / 2.0], [0.0, 1.0, T], [0.0, 0.0, 1.0]]
)
control_matrix = np.array([T ** 3 / 6.0, T ** 2 / 2.0, T])
control_matrix = control_matrix.reshape((3, 1))
zmp_from_state = np.array([1.0, 0.0, -problem.com_height / gravity])
ineq_matrix = np.array([+zmp_from_state, -zmp_from_state])
cur_max = problem.start_pos + 0.5 * problem.foot_length
cur_min = problem.start_pos - 0.5 * problem.foot_length
next_max = problem.end_pos + 0.5 * problem.foot_length
next_min = problem.end_pos - 0.5 * problem.foot_length
ineq_vector = [
np.array([+1000.0, +1000.0])
if i < nb_init_dsp_steps
else np.array([+cur_max, -cur_min])
if i - nb_init_dsp_steps <= nb_init_ssp_steps
else np.array([+1000.0, +1000.0])
if i - nb_init_dsp_steps - nb_init_ssp_steps < nb_dsp_steps
else np.array([+next_max, -next_min])
for i in range(problem.nb_timesteps)
]
super().__init__(
state_matrix,
control_matrix,
ineq_matrix,
None,
ineq_vector,
x_init=np.array([problem.start_pos, 0.0, 0.0]),
x_goal=np.array([problem.end_pos, 0.0, 0.0]),
nb_timesteps=problem.nb_timesteps,
wxt=1.0,
wxc=None,
wu=1e-3,
)
def plot_mpc_solution(problem, mpc):
t = np.linspace(0.0, problem.horizon_duration, problem.nb_timesteps + 1)
X = mpc.states
zmp_from_state = np.array([1.0, 0.0, -problem.com_height / gravity])
zmp = X.dot(zmp_from_state)
pos = X[:, 0]
zmp_min = [x[0] if abs(x[0]) < 10 else None for x in mpc.e]
zmp_max = [-x[1] if abs(x[1]) < 10 else None for x in mpc.e]
zmp_min.append(zmp_min[-1])
zmp_max.append(zmp_max[-1])
plt.ion()
plt.clf()
plt.plot(t, pos)
plt.plot(t, zmp, "r-")
plt.plot(t, zmp_min, "g:")
plt.plot(t, zmp_max, "b:")
plt.grid(True)
plt.show(block=True)
if __name__ == "__main__":
problem = HumanoidSteppingProblem()
mpc = HumanoidModelPredictiveControl(problem)
mpc.solve(solver=random.choice(qpsolvers.available_solvers))
plot_mpc_solution(problem, mpc)
================================================
FILE: examples/quadratic_program.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test the "quadprog" QP solver on a small dense problem."""
import random
from time import perf_counter
import numpy as np
from qpsolvers import available_solvers, print_matrix_vector, solve_qp
M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([3.0, 2.0, 3.0]), M)
G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])
h = np.array([3.0, 2.0, -2.0])
A = np.array([1.0, 1.0, 1.0])
b = np.array([1.0])
x_sol = np.array([0.3076923111580727, -0.6923076888419274, 1.3846153776838548])
if __name__ == "__main__":
start_time = perf_counter()
solver = random.choice(available_solvers)
x = solve_qp(P, q, G, h, A, b, solver=solver)
end_time = perf_counter()
print("")
print(" min. 1/2 x^T P x + q^T x")
print(" s.t. G * x <= h")
print(" A * x == b")
print("")
print_matrix_vector(P, "P", q, "q")
print("")
print_matrix_vector(G, "G", h, "h")
print("")
print_matrix_vector(A, "A", b, "b")
print("")
print(f"Solution: x = {x}")
print(f"It should be close to x* = {x_sol}")
print(f"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}")
================================================
FILE: examples/sparse_least_squares.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test a random sparse QP solver on a sparse least-squares problem.
See also: https://stackoverflow.com/a/74415546/3721564
"""
import random
from time import perf_counter
import qpsolvers
from qpsolvers import solve_ls
from qpsolvers.problems import get_sparse_least_squares
if __name__ == "__main__":
solver = random.choice(qpsolvers.sparse_solvers)
R, s, G, h, A, b, lb, ub = get_sparse_least_squares(n=150_000)
start_time = perf_counter()
x = solve_ls(
R,
s,
G,
h,
A,
b,
lb,
ub,
solver=solver,
verbose=False,
sparse_conversion=True,
)
end_time = perf_counter()
duration_ms = 1e3 * (end_time - start_time)
tol = 1e-6 # tolerance for checks
print("")
print(" min. || x - s ||^2")
print(" s.t. G * x <= h")
print(" sum(x) = 42")
print(" 0 <= x")
print("")
print(f"Found solution in {duration_ms:.0f} milliseconds with {solver}")
print("")
print(f"- Objective: {0.5 * (x - s).dot(x - s):.1f}")
print(f"- G * x <= h: {(G.dot(x) <= h + tol).all()}")
print(f"- x >= 0: {(x + tol >= 0.0).all()}")
print(f"- sum(x) = {x.sum():.1f}")
print("")
================================================
FILE: examples/test_dense_problem.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test all available QP solvers on a dense quadratic program."""
from os.path import basename
from IPython import get_ipython
from numpy import array, dot
from qpsolvers import dense_solvers, solve_qp, sparse_solvers
from scipy.sparse import csc_matrix
M = array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = dot(M.T, M)
q = dot(array([3.0, 2.0, 3.0]), M)
G = array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])
h = array([3.0, 2.0, -2.0])
P_csc = csc_matrix(P)
G_csc = csc_matrix(G)
if __name__ == "__main__":
if get_ipython() is None:
print(
"This example should be run with IPython:\n\n"
f"\tipython -i {basename(__file__)}\n"
)
exit()
dense_instr = {
solver: f"u = solve_qp(P, q, G, h, solver='{solver}')"
for solver in dense_solvers
}
sparse_instr = {
solver: f"u = solve_qp(P_csc, q, G_csc, h, solver='{solver}')"
for solver in sparse_solvers
}
benchmark = "https://github.com/qpsolvers/qpbenchmark"
print("\nTesting all QP solvers on one given dense quadratic program")
print(f"For a proper benchmark, check out {benchmark}")
sol0 = solve_qp(P, q, G, h, solver=dense_solvers[0])
abstol = 2e-4 # tolerance on absolute solution error
for solver in dense_solvers:
sol = solve_qp(P, q, G, h, solver=solver)
for solver in sparse_solvers:
sol = solve_qp(P_csc, q, G_csc, h, solver=solver)
print("\nDense solvers\n-------------")
for solver, instr in dense_instr.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
print("\nSparse solvers\n--------------")
for solver, instr in sparse_instr.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
================================================
FILE: examples/test_model_predictive_control.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test all available QP solvers on a model predictive control problem."""
from os.path import basename
from IPython import get_ipython
from qpsolvers import dense_solvers, sparse_solvers
from model_predictive_control import (
HumanoidModelPredictiveControl,
HumanoidSteppingProblem,
)
problem = HumanoidSteppingProblem()
mpc = HumanoidModelPredictiveControl(problem)
if __name__ == "__main__":
if get_ipython() is None:
print(
"This example should be run with IPython:\n\n"
f"\tipython -i {basename(__file__)}\n"
)
exit()
dense_instr = {
solver: f"u = mpc.solve(solver='{solver}', sparse=False)"
for solver in dense_solvers
}
sparse_instr = {
solver: f"u = mpc.solve(solver='{solver}', sparse=True)"
for solver in sparse_solvers
}
benchmark = "https://github.com/qpsolvers/qpbenchmark"
print("\nTesting QP solvers on one given model predictive control problem")
print(f"For a proper benchmark, check out {benchmark}")
print("\nDense solvers\n-------------")
for solver, instr in dense_instr.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
print("\nSparse solvers\n--------------")
for solver, instr in sparse_instr.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
================================================
FILE: examples/test_random_problems.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test all available QP solvers on random quadratic programs."""
import sys
try:
from IPython import get_ipython
except ImportError:
print("This example requires IPython, try installing ipython3")
sys.exit(-1)
from os.path import basename
from timeit import timeit
from numpy import dot, linspace, ones, random
from qpsolvers import available_solvers, solve_qp
from scipy.linalg import toeplitz
nb_iter = 10
sizes = [10, 20, 50, 100, 200, 500, 1000, 2000]
def solve_random_qp(n, solver):
M, b = random.random((n, n)), random.random(n)
P, q = dot(M.T, M), dot(b, M)
G = toeplitz(
[1.0, 0.0, 0.0] + [0.0] * (n - 3), [1.0, 2.0, 3.0] + [0.0] * (n - 3)
)
h = ones(n)
return solve_qp(P, q, G, h, solver=solver)
def plot_results(perfs):
try:
from pylab import (
clf,
get_cmap,
grid,
ion,
legend,
plot,
xlabel,
xscale,
ylabel,
yscale,
)
except ImportError:
print("Cannot plot results, try installing python3-matplotlib")
print("Results are stored in the global `perfs` dictionary")
return
cmap = get_cmap("tab10")
colors = cmap(linspace(0, 1, len(available_solvers)))
solver_color = {
solver: colors[i] for i, solver in enumerate(available_solvers)
}
ion()
clf()
for solver in perfs:
plot(sizes, perfs[solver], lw=2, color=solver_color[solver])
grid(True)
legend(list(perfs.keys()), loc="lower right")
xscale("log")
yscale("log")
xlabel("Problem size $n$")
ylabel("Time (s)")
for solver in perfs:
plot(sizes, perfs[solver], marker="o", color=solver_color[solver])
if __name__ == "__main__":
if get_ipython() is None:
print(
"This example should be run with IPython:\n\n"
f"\tipython -i {basename(__file__)}\n"
)
exit()
perfs = {}
benchmark = "https://github.com/qpsolvers/qpbenchmark"
print("\nTesting all solvers on a given set of random QPs")
print(f"For a proper benchmark, check out {benchmark}")
for solver in available_solvers:
try:
perfs[solver] = []
for size in sizes:
print(f"Running {solver} on problem size {size}...")
cum_time = timeit(
stmt=f"solve_random_qp({size}, '{solver}')",
setup="from __main__ import solve_random_qp",
number=nb_iter,
)
perfs[solver].append(cum_time / nb_iter)
except Exception as e:
print(f"Warning: {str(e)}")
if solver in perfs:
del perfs[solver]
plot_results(perfs)
================================================
FILE: examples/test_sparse_problem.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Test all available QP solvers on a sparse quadratic program."""
from os.path import basename
import numpy as np
import scipy.sparse
from IPython import get_ipython
from numpy.linalg import norm
from scipy.sparse import csc_matrix
from qpsolvers import dense_solvers, solve_qp, sparse_solvers
n = 500
M = scipy.sparse.lil_matrix(scipy.sparse.eye(n))
for i in range(1, n - 1):
M[i, i + 1] = -1
M[i, i - 1] = 1
P = csc_matrix(M.dot(M.transpose()))
q = -np.ones((n,))
G = csc_matrix(-scipy.sparse.eye(n))
h = -2 * np.ones((n,))
P_array = np.array(P.todense())
G_array = np.array(G.todense())
def check_same_solutions(tol=0.05):
sol0 = solve_qp(P, q, G, h, solver=sparse_solvers[0])
for solver in sparse_solvers:
sol = solve_qp(P, q, G, h, solver=solver)
relvar = norm(sol - sol0) / norm(sol0)
assert (
relvar < tol
), f"{solver}'s solution offset by {100.0 * relvar:.1f}%"
for solver in dense_solvers:
sol = solve_qp(P_array, q, G_array, h, solver=solver)
relvar = norm(sol - sol0) / norm(sol0)
assert (
relvar < tol
), f"{solver}'s solution offset by {100.0 * relvar:.1f}%"
def time_dense_solvers():
instructions = {
solver: f"u = solve_qp(P_array, q, G_array, h, solver='{solver}')"
for solver in dense_solvers
}
print("\nDense solvers\n-------------")
for solver, instr in instructions.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
def time_sparse_solvers():
instructions = {
solver: f"u = solve_qp(P, q, G, h, solver='{solver}')"
for solver in sparse_solvers
}
print("\nSparse solvers\n--------------")
for solver, instr in instructions.items():
print(f"{solver}: ", end="")
get_ipython().run_line_magic("timeit", instr)
if __name__ == "__main__":
if get_ipython() is None:
print(
"This example should be run with IPython:\n\n"
f"\tipython -i {basename(__file__)}\n"
)
exit()
benchmark = "https://github.com/qpsolvers/qpbenchmark"
print("\nTesting all QP solvers on one given sparse quadratic program")
print(f"For a proper benchmark, check out {benchmark}")
check_same_solutions()
time_dense_solvers()
time_sparse_solvers()
================================================
FILE: pyproject.toml
================================================
[build-system]
requires = ["flit_core >=2,<4"]
build-backend = "flit_core.buildapi"
[project]
name = "qpsolvers"
readme = "README.md"
authors = [
{name = "Stéphane Caron", email = "stephane.caron@normalesup.org"},
]
maintainers = [
{name = "Stéphane Caron", email = "stephane.caron@normalesup.org"},
]
dynamic = ['version', 'description']
requires-python = ">=3.10"
classifiers = [
"Development Status :: 5 - Production/Stable",
"Intended Audience :: Developers",
"Intended Audience :: Science/Research",
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Topic :: Scientific/Engineering :: Mathematics",
]
dependencies = [
"numpy >=1.15.4",
"scipy >=1.2.0",
]
keywords = ["quadratic programming", "solver", "numerical optimization"]
[project.optional-dependencies]
clarabel = ["clarabel >=0.4.1"]
copt = ["coptpy>=7.0.0"]
cvxopt = ["cvxopt >=1.2.6"]
kvxopt = ["kvxopt >=1.3.2"]
daqp = ["daqp >=0.8.2"]
ecos = ["ecos >=2.0.8"]
gurobi = ["gurobipy >=9.5.2"]
highs = ["highspy >=1.1.2.dev3,<1.14.0"]
jaxopt = ["jaxopt >=0.8.3"]
mosek = ["cvxopt >=1.2.6", "mosek >=10.0.40"]
osqp = ["osqp >=0.6.2"]
piqp = ["piqp >=0.2.2"]
proxqp = ["proxsuite >=0.2.9"]
qpalm = ["qpalm >=1.2.1"]
pyqpmad = ["pyqpmad >=1.4.0.post3"]
qpax = ["qpax>=0.0.9"]
qtqp = ["qtqp >=0.0.3"]
quadprog = ["quadprog >=0.1.11"]
scs = ["scs >=3.2.0"]
sip = ["sip-python >=0.0.2"]
open_source_solvers = ["qpsolvers[clarabel,cvxopt,daqp,ecos,highs,jaxopt,osqp,piqp,proxqp,pyqpmad,qpalm,qtqp,quadprog,scs,sip,qpax]"]
# Wheels-only solvers should distribute wheels that work on:
# - macOS (aarch64)
# - macOS (x86)
# - Linux (x86)
# - Windows (x86)
wheels_only = ["qpsolvers[cvxopt,daqp,ecos,highs,piqp,proxqp,qpalm,sip]"]
[project.urls]
Homepage = "https://github.com/qpsolvers/qpsolvers"
Documentation = "https://qpsolvers.github.io/qpsolvers/"
Source = "https://github.com/qpsolvers/qpsolvers"
Tracker = "https://github.com/qpsolvers/qpsolvers/issues"
Changelog = "https://github.com/qpsolvers/qpsolvers/blob/main/CHANGELOG.md"
[tool.coverage]
report.include = ["qpsolvers/*"]
report.omit = [
"qpsolvers/solvers/pdhcg_.py",
"qpsolvers/solvers/nppro_.py",
]
[tool.mypy]
ignore_missing_imports = true
[tool.pixi.workspace]
channels = ["conda-forge"]
platforms = ["linux-64", "linux-aarch64", "osx-arm64", "win-64"]
[tool.pixi.dependencies]
python = ">=3.10,<3.14"
clarabel = ">=0.4.1"
cvxopt = ">=1.2.6"
daqp = ">=0.8.2"
ecos = ">=2.0.8"
highspy = ">=1.5.3,<1.14.0"
kvxopt = ">=1.3.2"
numpy = ">=1.15.4"
osqp = ">=0.6.2"
piqp = ">=0.2.2"
proxsuite = ">=0.2.9"
quadprog = ">=0.1.11"
scipy = ">=1.2.0"
scs = ">=3.2.0"
[tool.pixi.pypi-dependencies]
coptpy = ">=7.0.0"
gurobipy = ">=9.5.2"
jaxopt = ">=0.8.3"
qpalm = ">=1.2.1"
qpax = ">=0.0.9"
qtqp = ">=0.0.3,<0.0.4"
sip-python = ">=0.0.2"
pyqpmad = ">=1.4.0.post3"
[tool.pixi.feature.coverage.dependencies]
coverage = ">=5.5"
coveralls = "*"
[tool.pixi.feature.coverage.tasks]
coverage-erase = { cmd = "coverage erase" }
coverage-run = { cmd = "coverage run -m unittest discover --failfast", depends-on = ["coverage-erase"] }
coverage = { cmd = "coverage report", depends-on = ["coverage-run"] }
coveralls = { cmd = "coveralls --service=github" }
[tool.pixi.feature.docs.dependencies]
sphinx = ">=7.2.2"
sphinx-autodoc-typehints = "*"
setuptools = ">=60.0"
[tool.pixi.feature.docs.pypi-dependencies]
furo = ">=2023.8.17"
sphinx-mathjax-offline = "*"
[tool.pixi.feature.licensed.pypi-dependencies]
mosek = ">=10.0.40"
[tool.pixi.feature.licensed.tasks]
licensed = "python -m unittest discover --failfast"
[tool.pixi.feature.lint.dependencies]
mypy = ">=0.812"
pylint = ">=2.8.2"
ruff = ">=0.5.4"
scipy-stubs = "*"
[tool.pixi.feature.py310.dependencies]
python = "3.10.*"
[tool.pixi.feature.py311.dependencies]
python = "3.11.*"
[tool.pixi.feature.py312.dependencies]
python = "3.12.*"
[tool.pixi.feature.py313.dependencies]
python = "3.13.*"
[tool.pixi.feature.test.dependencies]
[tool.pixi.feature.test.tasks]
test = "python -m unittest discover --failfast"
[tool.pixi.feature.lint.tasks]
mypy = "mypy qpsolvers --config-file=pyproject.toml"
pylint = "pylint qpsolvers --exit-zero --rcfile=pyproject.toml"
ruff = "ruff check qpsolvers && ruff format --check qpsolvers"
lint = { depends-on = ["mypy", "pylint", "ruff"] }
[tool.pixi.feature.docs.tasks]
docs-build = "sphinx-build doc _build -W"
[tool.pixi.environments]
coverage = { features = ["py310", "coverage", "licensed"], solve-group = "py310" }
docs = { features = ["py310", "docs"], solve-group = "py310" }
licensed = { features = ["py310", "licensed"], solve-group = "py310" }
lint = { features = ["py312", "lint"], solve-group = "py312" }
test-py310 = { features = ["py310", "test"], solve-group = "py310" }
test-py311 = { features = ["py311", "test"], solve-group = "py311" }
test-py312 = { features = ["py312", "test"], solve-group = "py312" }
test-py313 = { features = ["py313", "test"], solve-group = "py313" }
[tool.pylint.'MESSAGES CONTROL']
disable = [
"C0103", # Argument name doesn't conform to snake_case (we use uppercase matrices)
"E0611", # No name 'solve_qp' in module 'quadprog' (false positive)
"E1130", # Bad operand type for unary - (false positive, covered by mypy)
"R0801", # Similar lines in many files (functions share the same prototype)
"R0902", # Too many instance attributes (our QP class has 8 > 7)
"R0913", # Too many arguments (functions have > 5 args)
"R0914", # Too many local variables (functions often > 15 locals)
"import-error", # Suppress import‑error when optional back‑ends are missing
"too-many-branches", # We accept the blame
"too-many-positional-arguments", # We accept the blame
"too-many-statements", # We accept the blame
]
[tool.pylint.'TYPECHECK']
generated-members = [
"clarabel.DefaultSettings",
"clarabel.DefaultSolver",
"clarabel.NonnegativeConeT",
"clarabel.SolverStatus",
"clarabel.ZeroConeT",
"coptpy.Envr",
"coptpy.EnvrConfig",
"coptpy.MConstr",
"coptpy.Model",
"coptpy.NdArray",
"daqp.solve",
"gurobipy.MConstr",
"gurobipy.Model",
"piqp.DenseSolver",
"piqp.PIQP_SOLVED",
"piqp.SparseSolver",
"piqp.__version__",
"proxsuite.proxqp",
"qpSWIFT.run",
"qpalm.Data",
"qpalm.Settings",
"qpalm.Solver",
"sip.ModelCallbackInput",
"sip.ModelCallbackOutput",
"sip.ProblemDimensions",
"sip.QDLDLSettings",
"sip.Settings",
"sip.Solver",
"sip.Status",
"sip.Variables",
]
[tool.ruff]
line-length = 79
[tool.ruff.lint]
ignore = [
"D401", # good for methods but not for class docstrings
"D405", # British-style section names are also "proper"!
]
select = [
# pyflakes
"F",
# pycodestyle
"E",
"W",
# isort
"I001",
# pydocstyle
"D"
]
[tool.ruff.lint.pydocstyle]
convention = "numpy"
================================================
FILE: qpsolvers/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Quadratic programming solvers in Python with a unified API."""
from .active_set import ActiveSet
from .exceptions import (
NoSolverSelected,
ParamError,
ProblemError,
QPError,
SolverError,
SolverNotFound,
)
from .problem import Problem
from .solution import Solution
from .solve_ls import solve_ls
from .solve_qp import solve_problem, solve_qp
from .solve_unconstrained import solve_unconstrained
from .solvers import (
available_solvers,
cvxopt_solve_qp,
daqp_solve_qp,
dense_solvers,
ecos_solve_qp,
gurobi_solve_qp,
highs_solve_qp,
hpipm_solve_qp,
kvxopt_solve_qp,
mosek_solve_qp,
nppro_solve_qp,
osqp_solve_qp,
pdhcg_solve_qp,
piqp_solve_qp,
proxqp_solve_qp,
pyqpmad_solve_qp,
qpalm_solve_qp,
qpoases_solve_qp,
qpswift_solve_qp,
qtqp_solve_qp,
quadprog_solve_qp,
scs_solve_qp,
sip_solve_qp,
sparse_solvers,
)
from .utils import print_matrix_vector
__version__ = "4.11.0"
__all__ = [
"ActiveSet",
"NoSolverSelected",
"ParamError",
"Problem",
"ProblemError",
"QPError",
"Solution",
"SolverError",
"SolverNotFound",
"__version__",
"available_solvers",
"cvxopt_solve_qp",
"daqp_solve_qp",
"dense_solvers",
"ecos_solve_qp",
"gurobi_solve_qp",
"highs_solve_qp",
"hpipm_solve_qp",
"kvxopt_solve_qp",
"mosek_solve_qp",
"nppro_solve_qp",
"osqp_solve_qp",
"print_matrix_vector",
"pdhcg_solve_qp",
"piqp_solve_qp",
"proxqp_solve_qp",
"pyqpmad_solve_qp",
"qpalm_solve_qp",
"qpoases_solve_qp",
"qpswift_solve_qp",
"quadprog_solve_qp",
"qtqp_solve_qp",
"scs_solve_qp",
"sip_solve_qp",
"solve_ls",
"solve_problem",
"solve_qp",
"solve_unconstrained",
"sparse_solvers",
]
================================================
FILE: qpsolvers/active_set.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2023 Inria
"""Active set: indices of inequality constraints saturated at the optimum."""
from dataclasses import dataclass
from typing import Optional, Sequence
@dataclass
class ActiveSet:
"""Indices of active inequality constraints.
Attributes
----------
G_indices :
Indices of active linear inequality constraints.
lb_indices :
Indices of active lower-bound inequality constraints.
ub_indices :
Indices of active upper-bound inequality constraints.
"""
G_indices: Sequence[int]
lb_indices: Sequence[int]
ub_indices: Sequence[int]
def __init__(
self,
G_indices: Optional[Sequence[int]] = None,
lb_indices: Optional[Sequence[int]] = None,
ub_indices: Optional[Sequence[int]] = None,
) -> None:
self.G_indices = list(G_indices) if G_indices is not None else []
self.lb_indices = list(lb_indices) if lb_indices is not None else []
self.ub_indices = list(ub_indices) if ub_indices is not None else []
================================================
FILE: qpsolvers/conversions/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Convert problems from and to standard QP form."""
from .combine_linear_box_inequalities import combine_linear_box_inequalities
from .ensure_sparse_matrices import ensure_sparse_matrices
from .linear_from_box_inequalities import linear_from_box_inequalities
from .socp_from_qp import socp_from_qp
from .split_dual_linear_box import split_dual_linear_box
__all__ = [
"combine_linear_box_inequalities",
"ensure_sparse_matrices",
"linear_from_box_inequalities",
"socp_from_qp",
"split_dual_linear_box",
]
================================================
FILE: qpsolvers/conversions/combine_linear_box_inequalities.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2023 Stéphane Caron and the qpsolvers contributors
"""Combine linear and box inequalities into double-sided linear format."""
import numpy as np
import scipy.sparse as spa
from ..exceptions import ProblemError
def combine_linear_box_inequalities(G, h, lb, ub, n: int, use_csc: bool):
r"""Combine linear and box inequalities into double-sided linear format.
Input format:
.. math::
\begin{split}\begin{array}{ll}
G x & \leq h \\
lb & \leq x \leq ub
\end{array}\end{split}
Output format:
.. math::
l \leq C \leq u
Parameters
----------
G :
Linear inequality constraint matrix. Must be two-dimensional.
h :
Linear inequality constraint vector.
lb :
Lower bound constraint vector.
ub :
Upper bound constraint vector.
n :
Number of optimization variables.
use_csc :
If ``True``, use sparse rather than dense matrices.
Returns
-------
:
Linear inequality matrix :math:`C` and vectors :math:`u`, :math:`l`.
The two vector will contain :math:`\pm\infty` values on coordinates
where there is no corresponding constraint.
Raises
------
ProblemError
If the inequality matrix and vector are not consistent.
"""
if lb is None and ub is None:
C_out = G
u_out = h
l_out = np.full(h.shape, -np.inf) if h is not None else None
elif G is None:
# lb is not None or ub is not None:
C_out = spa.eye(n, format="csc") if use_csc else np.eye(n)
u_out = ub if ub is not None else np.full(n, +np.inf)
l_out = lb if lb is not None else np.full(n, -np.inf)
elif h is not None:
# G is not None and h is not None and not (lb is None and ub is None)
C_out = (
spa.vstack((G, spa.eye(n)), format="csc")
if use_csc
else np.vstack((G, np.eye(n)))
)
ub = ub if ub is not None else np.full(G.shape[1], +np.inf)
lb = lb if lb is not None else np.full(G.shape[1], -np.inf)
l_out = np.hstack((np.full(h.shape, -np.inf), lb))
u_out = np.hstack((h, ub))
else: # G is not None and h is None
raise ProblemError("Inconsistent inequalities: G is set but h is None")
return C_out, u_out, l_out
================================================
FILE: qpsolvers/conversions/ensure_sparse_matrices.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Make sure problem matrices are sparse."""
import warnings
from typing import Optional, Tuple, Union
import numpy as np
import scipy.sparse as spa
from ..warnings import SparseConversionWarning
def __warn_about_sparse_conversion(matrix_name: str, solver_name: str) -> None:
"""Warn about conversion from dense to sparse matrix.
Parameters
----------
matrix_name :
Name of matrix being converted from dense to sparse.
solver_name :
Name of the QP solver matrices will be passed to.
"""
warnings.warn(
f"Converted matrix '{matrix_name}' of your problem to "
f"scipy.sparse.csc_matrix to pass it to solver '{solver_name}'; "
f"for best performance, build your matrix as a csc_matrix directly.",
category=SparseConversionWarning,
)
def ensure_sparse_matrices(
solver_name: str,
P: Union[np.ndarray, spa.csc_matrix],
G: Optional[Union[np.ndarray, spa.csc_matrix]],
A: Optional[Union[np.ndarray, spa.csc_matrix]],
) -> Tuple[spa.csc_matrix, Optional[spa.csc_matrix], Optional[spa.csc_matrix]]:
"""
Make sure problem matrices are sparse.
Parameters
----------
solver_name :
Name of the QP solver matrices will be passed to.
P :
Cost matrix.
G :
Inequality constraint matrix, if any.
A :
Equality constraint matrix, if any.
Returns
-------
:
Tuple of all three matrices as sparse matrices.
"""
if isinstance(P, np.ndarray):
__warn_about_sparse_conversion("P", solver_name)
P = spa.csc_matrix(P)
if isinstance(G, np.ndarray):
__warn_about_sparse_conversion("G", solver_name)
G = spa.csc_matrix(G)
if isinstance(A, np.ndarray):
__warn_about_sparse_conversion("A", solver_name)
A = spa.csc_matrix(A)
return P, G, A
================================================
FILE: qpsolvers/conversions/linear_from_box_inequalities.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Functions to convert vector bounds into linear inequality constraints."""
from typing import Optional, Tuple, Union
import numpy as np
import scipy.sparse as spa
from ..exceptions import ProblemError
def concatenate_bound(
G: Optional[Union[np.ndarray, spa.csc_matrix, spa.dia_matrix]],
h: Optional[np.ndarray],
b: np.ndarray,
sign: float,
use_sparse: bool,
) -> Tuple[Optional[Union[np.ndarray, spa.csc_matrix]], Optional[np.ndarray]]:
"""Append bound constraint vectors to inequality constraints.
Parameters
----------
G :
Linear inequality matrix.
h :
Linear inequality vector.
b :
Bound constraint vector.
sign :
Sign factor: -1.0 for a lower and +1.0 for an upper bound.
use_sparse :
Use sparse matrices if true, dense matrices otherwise.
Returns
-------
G :
Updated linear inequality matrix.
h :
Updated linear inequality vector.
"""
n = len(b) # == number of optimization variables
if G is None or h is None:
G = sign * (spa.eye(n, format="csc") if use_sparse else np.eye(n))
h = sign * b
return (G, h)
h = np.concatenate((h, sign * b))
if isinstance(G, np.ndarray):
dense_G: np.ndarray = np.concatenate((G, sign * np.eye(n)), 0)
return (dense_G, h)
if isinstance(G, (spa.csc_matrix, spa.dia_matrix)):
sparse_G: spa.csc_matrix = spa.vstack(
[G, sign * spa.eye(n)], format="csc"
)
return (sparse_G, h)
# G is not an instance of a type we know
name = type(G).__name__
raise ProblemError(f"invalid type '{name}' for inequality matrix G")
def linear_from_box_inequalities(
G: Optional[Union[np.ndarray, spa.csc_matrix, spa.dia_matrix]],
h: Optional[np.ndarray],
lb: Optional[np.ndarray],
ub: Optional[np.ndarray],
use_sparse: bool,
) -> Tuple[Optional[Union[np.ndarray, spa.csc_matrix]], Optional[np.ndarray]]:
"""Append lower or upper bound vectors to inequality constraints.
Parameters
----------
G :
Linear inequality matrix.
h :
Linear inequality vector.
lb :
Lower bound constraint vector.
ub :
Upper bound constraint vector.
use_sparse :
Use sparse matrices if true, dense matrices otherwise.
Returns
-------
G :
Updated linear inequality matrix.
h :
Updated linear inequality vector.
"""
if lb is not None:
G, h = concatenate_bound(G, h, lb, -1.0, use_sparse)
if ub is not None:
G, h = concatenate_bound(G, h, ub, +1.0, use_sparse)
if isinstance(G, spa.dia_matrix): # corner case with no new box bound
return (spa.csc_matrix(G), h)
return (G, h)
================================================
FILE: qpsolvers/conversions/socp_from_qp.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Convert quadratic programs to second-order cone programs."""
from typing import Any, Dict, Optional, Tuple, Union
import numpy as np
from numpy import ndarray, sqrt
from numpy.linalg import LinAlgError, cholesky
from scipy.sparse import csc_matrix
from ..exceptions import ProblemError
def socp_from_qp(
P: ndarray, q: ndarray, G: Optional[ndarray], h: Optional[ndarray]
) -> Tuple[ndarray, csc_matrix, ndarray, Dict[str, Any]]:
r"""Convert a quadratic program to a second-order cone program.
The quadratic program is defined by:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h
\end{array}\end{split}
The equivalent second-order cone program is:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
c^T_s y \\
\mbox{subject to}
& G_s y \leq_{\cal K} h_s
\end{array}\end{split}
This function is adapted from ``ecosqp.m`` in the `ecos-matlab
<https://github.com/embotech/ecos-matlab/>`_ repository. See the
documentation in that script for details on this reformulation.
Parameters
----------
P :
Primal quadratic cost matrix.
q :
Primal quadratic cost vector.
G :
Linear inequality constraint matrix.
h :
Linear inequality constraint vector.
Returns
-------
c_socp : array
SOCP cost vector.
G_socp : array
SOCP inequality matrix.
h_socp : array
SOCP inequality vector.
dims : dict
Dimension dictionary used by SOCP solvers, where ``dims["l"]`` is the
number of inequality constraints.
Raises
------
ValueError :
If the cost matrix is not positive definite.
"""
n = P.shape[1] # dimension of QP variable
c_socp = np.hstack([np.zeros(n), 1]) # new SOCP variable stacked as [x, t]
try:
L = cholesky(P)
except LinAlgError as e:
error = str(e)
if "not positive definite" in error:
raise ProblemError("matrix P is not positive definite") from e
raise e # other linear algebraic error
scale = 1.0 / sqrt(2)
G_quad = np.vstack(
[
scale * np.hstack([q, -1.0]),
np.hstack([-L.T, np.zeros((L.shape[0], 1))]),
scale * np.hstack([-q, +1.0]),
]
)
h_quad = np.hstack([scale, np.zeros(L.shape[0]), scale])
dims: Dict[str, Any] = {"q": [L.shape[0] + 2]}
G_socp: Union[ndarray, csc_matrix]
if G is not None and h is not None:
G_socp = np.vstack([np.hstack([G, np.zeros((G.shape[0], 1))]), G_quad])
h_socp = np.hstack([h, h_quad])
dims["l"] = G.shape[0]
else: # no linear inequality constraint
G_socp = G_quad
h_socp = h_quad
dims["l"] = 0
G_socp = csc_matrix(G_socp)
return c_socp, G_socp, h_socp, dims
================================================
FILE: qpsolvers/conversions/split_dual_linear_box.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Convert stacked dual multipliers into linear and box multipliers."""
from typing import Optional, Tuple
import numpy as np
def split_dual_linear_box(
z_stacked: np.ndarray,
lb: Optional[np.ndarray],
ub: Optional[np.ndarray],
) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:
"""Separate linear and box multipliers from a stacked dual vector.
This function assumes linear and box inequalities were combined using
:func:`qpsolvers.conversions.linear_from_box_inequalities`.
Parameters
----------
z_stacked :
Stacked vector of dual multipliers.
lb :
Lower bound constraint vector.
ub :
Upper bound constraint vector.
Returns
-------
:
Pair :code:`z, z_box` of linear and box multipliers. Both can be empty
arrays if there is no corresponding constraint.
"""
z = np.empty((0,), dtype=z_stacked.dtype)
z_box = np.empty((0,), dtype=z_stacked.dtype)
if lb is not None and ub is not None:
n_lb = lb.shape[0]
n_ub = ub.shape[0]
n_box = n_lb + n_ub
z_box = z_stacked[-n_ub:] - z_stacked[-n_box:-n_ub]
z = z_stacked[:-n_box]
elif ub is not None: # lb is None
n_ub = ub.shape[0]
z_box = z_stacked[-n_ub:]
z = z_stacked[:-n_ub]
elif lb is not None: # ub is None
n_lb = lb.shape[0]
z_box = -z_stacked[-n_lb:]
z = z_stacked[:-n_lb]
else: # lb is None and ub is None
z = z_stacked
return z, z_box
================================================
FILE: qpsolvers/exceptions.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""
Exceptions from qpsolvers.
We catch all solver exceptions and re-throw them in a qpsolvers-owned exception
to avoid abstraction leakage. See this `design decision
<https://github.com/getparthenon/parthenon/wiki/Design-Decision:-Throw-Custom-Exceptions>`__
for more details on the rationale behind this choice.
"""
class QPError(Exception):
"""Base class for qpsolvers exceptions."""
class NoSolverSelected(QPError):
"""Exception raised when the `solver` keyword argument is not set."""
class ParamError(QPError):
"""Exception raised when solver parameters are incorrect."""
class ProblemError(QPError):
"""Exception raised when a quadratic program is malformed."""
class SolverNotFound(QPError):
"""Exception raised when a requested solver is not found."""
class SolverError(QPError):
"""Exception raised when a solver failed."""
================================================
FILE: qpsolvers/problem.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Model for a quadratic program."""
from typing import List, Optional, Tuple, Union
import numpy as np
import scipy.sparse as spa
from .active_set import ActiveSet
from .conversions import linear_from_box_inequalities
from .exceptions import ParamError, ProblemError
class Problem:
r"""Data structure describing a quadratic program.
The quadratic program is defined as:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}\end{split}
This class provides sanity checks and metrics such as the condition number
of a problem.
Attributes
----------
P :
Symmetric cost matrix (most solvers require it to be definite
as well).
q :
Cost vector.
G :
Linear inequality matrix.
h :
Linear inequality vector.
A :
Linear equality matrix.
b :
Linear equality vector.
lb :
Lower bound constraint vector. Can contain ``-np.inf``.
ub :
Upper bound constraint vector. Can contain ``+np.inf``.
"""
P: Union[np.ndarray, spa.csc_matrix]
q: np.ndarray
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None
h: Optional[np.ndarray] = None
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None
b: Optional[np.ndarray] = None
lb: Optional[np.ndarray] = None
ub: Optional[np.ndarray] = None
@staticmethod
def __check_matrix(
M: Union[np.ndarray, spa.csc_matrix],
) -> Union[np.ndarray, spa.csc_matrix]:
"""
Ensure a problem matrix has proper shape.
Parameters
----------
M :
Problem matrix.
name :
Matrix name.
Returns
-------
:
Same matrix with proper shape.
"""
if hasattr(M, "ndim") and M.ndim == 1: # type: ignore
M = M.reshape((1, M.shape[0])) # type: ignore
return M
@staticmethod
def __check_vector(v: np.ndarray, name: str) -> np.ndarray:
"""
Ensure a problem vector has proper shape.
Parameters
----------
M :
Problem matrix.
name :
Matrix name.
Returns
-------
:
Same matrix with proper shape.
Raises
------
ProblemError
If the vector cannot be flattened.
"""
if v.ndim <= 1:
return v
if v.shape[0] != 1 and v.shape[1] != 1 or v.ndim > 2:
raise ProblemError(
f"vector '{name}' should be flat "
f"and cannot be flattened as its shape is {v.shape}"
)
return v.flatten()
def __init__(
self,
P: Union[np.ndarray, spa.csc_matrix],
q: np.ndarray,
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
h: Optional[np.ndarray] = None,
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
b: Optional[np.ndarray] = None,
lb: Optional[np.ndarray] = None,
ub: Optional[np.ndarray] = None,
) -> None:
P = Problem.__check_matrix(P)
q = Problem.__check_vector(q, "q")
G = Problem.__check_matrix(G) if G is not None else None
h = Problem.__check_vector(h, "h") if h is not None else None
A = Problem.__check_matrix(A) if A is not None else None
b = Problem.__check_vector(b, "b") if b is not None else None
lb = Problem.__check_vector(lb, "lb") if lb is not None else None
ub = Problem.__check_vector(ub, "ub") if ub is not None else None
self.P = P
self.q = q
self.G = G
self.h = h
self.A = A
self.b = b
self.lb = lb
self.ub = ub
@property
def has_sparse(self) -> bool:
"""Check whether the problem has sparse matrices.
Returns
-------
:
True if at least one of the :math:`P`, :math:`G` or :math:`A`
matrices is sparse.
"""
sparse_types = (spa.csc_matrix, spa.dia_matrix)
return (
isinstance(self.P, sparse_types)
or isinstance(self.G, sparse_types)
or isinstance(self.A, sparse_types)
)
@property
def is_unconstrained(self) -> bool:
"""Check whether the problem has any constraint.
Returns
-------
:
True if the problem has at least one constraint.
"""
return (
self.G is None
and self.A is None
and self.lb is None
and self.ub is None
)
def unpack(
self,
) -> Tuple[
Union[np.ndarray, spa.csc_matrix],
np.ndarray,
Optional[Union[np.ndarray, spa.csc_matrix]],
Optional[np.ndarray],
Optional[Union[np.ndarray, spa.csc_matrix]],
Optional[np.ndarray],
Optional[np.ndarray],
Optional[np.ndarray],
]:
"""Get problem matrices as a tuple.
Returns
-------
:
Tuple ``(P, q, G, h, A, b, lb, ub)`` of problem matrices.
"""
return (
self.P,
self.q,
self.G,
self.h,
self.A,
self.b,
self.lb,
self.ub,
)
def unpack_as_dense(
self,
) -> Tuple[
np.ndarray,
np.ndarray,
Optional[np.ndarray],
Optional[np.ndarray],
Optional[np.ndarray],
Optional[np.ndarray],
Optional[np.ndarray],
Optional[np.ndarray],
]:
"""Get problem matrices as a tuple of dense matrices and vectors.
Returns
-------
:
Tuple ``(P, q, G, h, A, b, lb, ub)`` of problem matrices.
"""
return (
self.P.toarray() if isinstance(self.P, spa.csc_matrix) else self.P,
self.q,
self.G.toarray() if isinstance(self.G, spa.csc_matrix) else self.G,
self.h,
self.A.toarray() if isinstance(self.A, spa.csc_matrix) else self.A,
self.b,
self.lb,
self.ub,
)
def check_constraints(self):
"""Check that problem constraints are properly specified.
Raises
------
ProblemError
If the constraints are not properly defined.
"""
if self.G is None and self.h is not None:
raise ProblemError("incomplete inequality constraint (missing h)")
if self.G is not None and self.h is None:
raise ProblemError("incomplete inequality constraint (missing G)")
if self.A is None and self.b is not None:
raise ProblemError("incomplete equality constraint (missing b)")
if self.A is not None and self.b is None:
raise ProblemError("incomplete equality constraint (missing A)")
def __get_active_inequalities(
self, active_set: ActiveSet
) -> Optional[Union[np.ndarray, spa.csc_matrix]]:
r"""Combine active linear and box inequalities into a single matrix.
Parameters
----------
active_set :
Active set to evaluate the condition number with. It should contain
the set of active constraints at the optimum of the problem.
Returns
-------
:
Combined matrix of active inequalities.
"""
G_full, _ = linear_from_box_inequalities(
self.G, self.h, self.lb, self.ub, use_sparse=False
)
if G_full is None:
return None
indices: List[int] = []
offset: int = 0
if self.h is not None:
indices.extend(active_set.G_indices)
offset += self.h.size
if self.lb is not None:
indices.extend(offset + i for i in active_set.lb_indices)
offset += self.lb.size
if self.ub is not None:
indices.extend(offset + i for i in active_set.ub_indices)
G_active = G_full[indices]
return G_active
def cond(self, active_set: ActiveSet) -> float:
r"""Condition number of the problem matrix.
Compute the condition number of the symmetric matrix representing the
problem data:
.. math::
M =
\begin{bmatrix}
P & G_{act}^T & A_{act}^T \\
G_{act} & 0 & 0 \\
A_{act} & 0 & 0
\end{bmatrix}
where :math:`G_{act}` and :math:`A_{act}` denote the active inequality
and equality constraints at the optimum of the problem.
Parameters
----------
active_set :
Active set to evaluate the condition number with. It should contain
the set of active constraints at the optimum of the problem.
Returns
-------
:
Condition number of the problem.
Raises
------
ProblemError :
If the problem is sparse.
Notes
-----
Having a low condition number (say, less than 1e10) condition number is
strongly tied to the capacity of numerical solvers to solve a problem.
This is the motivation for preconditioning, as detailed for instance in
Section 5 of [Stellato2020]_.
"""
if self.has_sparse:
raise ProblemError("This function is for dense problems only")
if active_set.lb_indices and self.lb is None:
raise ProblemError("Lower bound in active set but not in problem")
if active_set.ub_indices and self.ub is None:
raise ProblemError("Upper bound in active set but not in problem")
P: np.ndarray = (
self.P.toarray() if isinstance(self.P, spa.csc_matrix) else self.P
)
G_active_full = self.__get_active_inequalities(active_set)
G_active: Optional[np.ndarray] = (
G_active_full.toarray()
if isinstance(G_active_full, spa.csc_matrix)
else G_active_full
)
A: Optional[np.ndarray] = (
self.A.toarray() if isinstance(self.A, spa.csc_matrix) else self.A
)
n_G = G_active.shape[0] if G_active is not None else 0
n_A = A.shape[0] if A is not None else 0
if G_active is not None and A is not None:
M = np.vstack(
[
np.hstack([P, G_active.T, A.T]),
np.hstack(
[
G_active,
np.zeros((n_G, n_G)),
np.zeros((n_G, n_A)),
]
),
np.hstack(
[
A,
np.zeros((n_A, n_G)),
np.zeros((n_A, n_A)),
]
),
]
)
elif G_active is not None:
M = np.vstack(
[
np.hstack([P, G_active.T]),
np.hstack([G_active, np.zeros((n_G, n_G))]),
]
)
elif A is not None:
M = np.vstack(
[
np.hstack([P, A.T]),
np.hstack([A, np.zeros((n_A, n_A))]),
]
)
else: # G_active is None and A is None
M = P
return np.linalg.cond(M)
def save(self, file: str) -> None:
"""Save problem to a compressed NumPy file.
Parameters
----------
file : str or file
Either the filename (string) or an open file (file-like object)
where the data will be saved. If file is a string or a Path, the
``.npz`` extension will be appended to the filename if it is not
already there.
"""
np.savez(
file,
P=(
self.P.toarray()
if isinstance(self.P, spa.csc_matrix)
else self.P
),
q=self.q,
G=np.array(self.G),
h=np.array(self.h),
A=np.array(self.A),
b=np.array(self.b),
lb=np.array(self.lb),
ub=np.array(self.ub),
)
@staticmethod
def load(file: str):
"""Load problem from a NumPy file.
Parameters
----------
file : file-like object, string, or pathlib.Path
The file to read. File-like objects must support the
``seek()`` and ``read()`` methods and must always
be opened in binary mode. Pickled files require that the
file-like object support the ``readline()`` method as well.
"""
problem_data = np.load(file, allow_pickle=False)
def load_optional(key):
try:
return problem_data[key]
except ValueError:
return None
return Problem(
P=load_optional("P"),
q=load_optional("q"),
G=load_optional("G"),
h=load_optional("h"),
A=load_optional("A"),
b=load_optional("b"),
lb=load_optional("lb"),
ub=load_optional("ub"),
)
def get_cute_classification(self, interest: str) -> str:
"""Get the CUTE classification string of the problem.
Parameters
----------
interest:
Either 'A', 'M' or 'R': 'A' if the problem is academic, that is,
has been constructed specifically by researchers to test one or
more algorithms; 'M' if the problem is part of a modelling exercise
where the actual value of the solution is not used in a genuine
practical application; and 'R' if the problem's solution is (or has
been) actually used in a real application for purposes other than
testing algorithms.
Returns
-------
:
CUTE classification string of the problem
Notes
-----
Check out the `CUTE classification scheme
<https://www.cuter.rl.ac.uk//Problems/classification.shtml>`__ for
details.
"""
if interest not in ("A", "M", "R"):
raise ParamError(f"interest '{interest}' not in 'A', 'M' or 'R'")
nb_var = self.P.shape[0]
nb_cons = 0
if self.G is not None:
nb_cons += self.G.shape[0]
if self.A is not None:
nb_cons += self.A.shape[0]
# NB: we don't cound bounds as constraints in this classification
return f"QLR2-{interest}N-{nb_var}-{nb_cons}"
================================================
FILE: qpsolvers/problems.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2023 Stéphane Caron and the qpsolvers contributors
"""Collection of sample problems."""
from typing import Tuple
import numpy as np
import scipy.sparse as spa
from .problem import Problem
from .solution import Solution
def get_sparse_least_squares(n):
"""
Get a sparse least squares problem.
Parameters
----------
n :
Problem size.
Notes
-----
This problem was inspired by `this question on Stack Overflow
<https://stackoverflow.com/q/73656257/3721564>`__.
"""
# minimize 1/2 || x - s ||^2
R = spa.eye(n, format="csc")
s = np.array(range(n), dtype=float)
# such that G * x <= h
G = spa.diags(
diagonals=[
[1.0 if i % 2 == 0 else 0.0 for i in range(n)],
[1.0 if i % 3 == 0 else 0.0 for i in range(n - 1)],
[1.0 if i % 5 == 0 else 0.0 for i in range(n - 1)],
],
offsets=[0, 1, -1],
format="csc",
)
h = np.ones(G.shape[0])
# such that sum(x) == 42
A = spa.csc_matrix(np.ones((1, n)))
b = np.array([42.0]).reshape((1,))
# such that x >= 0
lb = np.zeros(n)
ub = None
return R, s, G, h, A, b, lb, ub
def get_qpsut01() -> Tuple[Problem, Solution]:
"""Get QPSUT01 problem and its solution.
Returns
-------
:
Problem-solution pair.
"""
M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([3.0, 2.0, 3.0]), M)
G = np.array([[4.0, 2.0, 0.0], [-1.0, 2.0, -1.0]])
h = np.array([1.0, -2.0])
A = np.array([1.0, 1.0, 1.0]).reshape((1, 3))
b = np.array([1.0])
lb = np.array([-0.5, -0.4, -0.5])
ub = np.array([1.0, 1.0, 1.0])
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = Solution(problem)
solution.found = True
solution.x = np.array([0.4, -0.4, 1.0])
solution.z = np.array([0.0, 0.0])
solution.y = np.array([-5.8])
solution.z_box = np.array([0.0, -1.8, 3.0])
return problem, solution
def get_qpsut02() -> Tuple[Problem, Solution]:
"""Get QPSUT02 problem and its solution.
Returns
-------
:
Problem-solution pair.
"""
M = np.array(
[
[1.0, -2.0, 0.0, 8.0],
[-6.0, 3.0, 1.0, 4.0],
[-2.0, 1.0, 0.0, 1.0],
[9.0, 9.0, 5.0, 3.0],
]
)
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([-3.0, 2.0, 0.0, 9.0]), M)
G = np.array(
[
[4.0, 7.0, 0.0, -2.0],
]
)
h = np.array([30.0])
A = np.array(
[
[1.0, 1.0, 1.0, 1.0],
[1.0, -1.0, -1.0, 1.0],
]
)
b = np.array([10.0, 0.0])
lb = np.array([-2.0, -1.0, -3.0, 1.0])
ub = np.array([4.0, 2.0, 6.0, 10.0])
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = Solution(problem)
solution.found = True
solution.x = np.array([1.36597938, -1.0, 6.0, 3.63402062])
solution.z = np.array([0.0])
solution.y = np.array([-377.60314303, -62.75251185]) # YMMV
solution.z_box = np.array([0.0, -138.9585918, 37.53106937, 0.0]) # YMMV
return problem, solution
def get_qpsut03() -> Tuple[Problem, Solution]:
"""Get QPSUT03 problem and its solution.
Returns
-------
:
Problem-solution pair.
Notes
-----
This problem has partial box bounds, that is, -infinity on some lower
bounds and +infinity on some upper bounds.
"""
M = np.array(
[
[1.0, -2.0, 0.0, 8.0],
[-6.0, 3.0, 1.0, 4.0],
[-2.0, 1.0, 0.0, 1.0],
[9.0, 9.0, 5.0, 3.0],
]
)
P = np.dot(M.T, M) # this is a positive definite matrix
q = np.dot(np.array([-3.0, 2.0, 0.0, 9.0]), M)
G = None
h = None
A = None
b = None
lb = np.array([-np.inf, -0.4, -np.inf, -1.0])
ub = np.array([np.inf, np.inf, 0.5, 1.0])
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = Solution(problem)
solution.found = True
solution.x = np.array([0.18143455, 0.00843864, -2.35442995, 0.35443034])
solution.z = np.array([])
solution.y = np.array([])
solution.z_box = np.array([0.0, 0.0, 0.0, 0.0])
return problem, solution
def get_qpsut04() -> Tuple[Problem, Solution]:
"""Get QPSUT04 problem and its solution.
Returns
-------
:
Problem-solution pair.
"""
n = 3
P = np.eye(n)
q = 0.01 * np.ones(shape=(n, 1)) # non-flat vector
G = np.eye(n)
h = np.ones(shape=(n,))
A = np.ones(shape=(n,))
b = np.ones(shape=(1,))
problem = Problem(P, q, G, h, A, b)
solution = Solution(problem)
solution.found = True
solution.x = 1.0 / 3.0 * np.ones(n)
solution.y = np.array([1.0 / 3.0 + 0.01])
solution.z = np.zeros(n)
return problem, solution
def get_qpsut05() -> Tuple[Problem, Solution]:
"""Get QPSUT05 problem and its solution.
Returns
-------
:
Problem-solution pair.
"""
P = np.array([2.0])
q = np.array([-2.0])
problem = Problem(P, q)
solution = Solution(problem)
solution.found = True
solution.x = np.array([1.0])
return problem, solution
def get_qptest():
"""Get QPTEST problem from the Maros-Meszaros test set.
Returns
-------
:
Problem-solution pair.
"""
P = np.array([[8.0, 2.0], [2.0, 10.0]])
q = np.array([1.5, -2.0])
G = np.array([[-1.0, 2.0], [-2.0, -1.0]])
h = np.array([6.0, -2.0])
lb = np.array([0.0, 0.0])
ub = np.array([20.0, np.inf])
problem = Problem(P, q, G, h, lb=lb, ub=ub)
solution = Solution(problem)
solution.found = True
solution.x = np.array([0.7625, 0.475])
solution.z = np.array([0.0, 4.275])
solution.z_box = np.array([0.0, 0.0])
return problem, solution
def get_qpgurdu():
"""Get sample random problem with linear inequality constraints.
Returns
-------
:
Problem-solution pair.
"""
P = np.array(
[
[
3.57211988,
3.04767485,
2.81378189,
3.10290601,
3.70204698,
3.21624815,
3.07738552,
2.97880055,
2.87282375,
3.13101137,
],
[
3.04767485,
3.29764869,
2.96655517,
2.99532101,
3.27631229,
2.95993532,
3.36890754,
3.41940015,
2.71055468,
3.48874903,
],
[
2.81378189,
2.96655517,
4.07209512,
3.15291684,
3.25120445,
3.16570711,
3.29693401,
3.57945021,
2.38634372,
3.56010605,
],
[
3.10290601,
2.99532101,
3.15291684,
4.18950328,
3.80236382,
3.30578443,
3.86461151,
3.73403774,
2.65103423,
3.6915013,
],
[
3.70204698,
3.27631229,
3.25120445,
3.80236382,
4.49927773,
3.71882781,
3.72242148,
3.36633929,
3.07400851,
3.44904275,
],
[
3.21624815,
2.95993532,
3.16570711,
3.30578443,
3.71882781,
3.54009378,
3.3619341,
3.45111777,
2.52760157,
3.47292034,
],
[
3.07738552,
3.36890754,
3.29693401,
3.86461151,
3.72242148,
3.3619341,
4.18766506,
3.9158527,
2.73687599,
3.94376429,
],
[
2.97880055,
3.41940015,
3.57945021,
3.73403774,
3.36633929,
3.45111777,
3.9158527,
4.4180459,
2.50596495,
4.25387869,
],
[
2.87282375,
2.71055468,
2.38634372,
2.65103423,
3.07400851,
2.52760157,
2.73687599,
2.50596495,
2.74656049,
2.54212279,
],
[
3.13101137,
3.48874903,
3.56010605,
3.6915013,
3.44904275,
3.47292034,
3.94376429,
4.25387869,
2.54212279,
4.634129,
],
]
)
q = np.array(
[
[0.49318579],
[0.82113304],
[0.67851692],
[0.34081485],
[0.14826526],
[0.81974126],
[0.41957706],
[0.53118637],
[0.59189664],
[0.98775649],
]
)
G = np.array(
[
[
4.38410058e-01,
4.43204832e-01,
3.01827071e-01,
5.77725615e-02,
8.04962962e-01,
6.13555163e-01,
1.15255766e-01,
7.11331164e-01,
7.71820534e-02,
3.86074035e-01,
],
[
8.47645982e-01,
9.37475356e-01,
3.54726656e-01,
9.64635375e-01,
5.95008737e-01,
4.65424573e-01,
3.60529910e-01,
5.83149169e-01,
5.51353698e-01,
8.45823800e-01,
],
[
2.29674075e-04,
5.54870256e-02,
7.83869376e-01,
9.97727284e-01,
1.49512389e-01,
7.44775614e-01,
8.76446593e-02,
2.57348591e-01,
7.28916655e-01,
5.97511590e-01,
],
[
6.92184129e-01,
9.04600884e-01,
7.57700115e-01,
7.76548565e-01,
5.31039749e-01,
8.32203998e-01,
4.27810742e-01,
1.92236814e-01,
2.91129478e-01,
7.76195308e-01,
],
[
4.73333212e-01,
3.02129792e-02,
6.86517354e-01,
5.08992776e-01,
8.43205462e-01,
6.30402967e-01,
7.92221172e-01,
3.67768984e-01,
1.10864990e-01,
5.44828940e-01,
],
[
9.23060980e-01,
4.55743966e-01,
4.81958856e-02,
5.47614699e-02,
8.23194952e-01,
2.40526659e-01,
9.33519842e-01,
5.40430172e-01,
6.27229337e-01,
4.27829243e-01,
],
[
2.39454128e-01,
1.29688157e-01,
7.64521599e-01,
2.66943061e-01,
4.94990723e-01,
3.87798160e-01,
5.76282838e-01,
8.87340479e-01,
5.49439650e-01,
2.99596520e-01,
],
[
3.73174589e-02,
4.08407618e-01,
1.19009418e-01,
3.02572289e-02,
1.90287316e-01,
2.93975786e-01,
7.65243508e-01,
8.64670246e-02,
3.90593097e-01,
1.33870683e-01,
],
[
9.10093385e-01,
9.63382642e-02,
2.94162739e-01,
9.71178995e-01,
1.81811460e-01,
9.69904715e-02,
4.10693806e-01,
7.56873549e-01,
2.36595007e-01,
3.19756491e-01,
],
[
8.58362518e-02,
7.88161645e-02,
9.67300428e-01,
2.59894669e-01,
1.62774911e-01,
3.33859109e-01,
6.15307748e-01,
1.81164951e-02,
5.99620503e-01,
5.71512979e-01,
],
]
)
h = np.array(
[
[4.94957567],
[7.50577326],
[5.40302286],
[7.18164978],
[5.98834884],
[6.07449251],
[5.59605532],
[3.45542914],
[5.27449417],
[4.69303926],
]
)
problem = Problem(P, q, G, h)
solution = Solution(problem)
solution.found = True
return problem, solution
def get_qpgurabs():
"""Get sample random problem with box constraints.
Returns
-------
:
Problem-solution pair.
"""
qpgurdu, _ = get_qpgurdu()
box = np.abs(qpgurdu.h)
problem = Problem(qpgurdu.P, qpgurdu.q, lb=-box, ub=+box)
solution = Solution(problem)
solution.found = True
return problem, solution
def get_qpgureq():
"""Get sample random problem with equality constraints.
Returns
-------
:
Problem-solution pair.
"""
qpgurdu, _ = get_qpgurdu()
A = qpgurdu.G
b = 0.1 * qpgurdu.h
problem = Problem(qpgurdu.P, qpgurdu.q, A=A, b=b)
solution = Solution(problem)
solution.found = True
return problem, solution
================================================
FILE: qpsolvers/py.typed
================================================
================================================
FILE: qpsolvers/solution.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Output from a QP solver."""
from dataclasses import dataclass, field
from typing import Optional
import numpy as np
from .problem import Problem
@dataclass(frozen=False)
class Solution:
"""Solution returned by a QP solver for a given problem.
Attributes
----------
extras :
Other outputs, specific to each solver.
found :
True if the solution was found successfully by a solver, False if the
solver did not find a solution or detected an unfeasible problem,
``None`` if no solver was run.
problem :
Quadratic program the solution corresponds to.
obj :
Value of the primal objective at the solution (``None`` if no solution
was found).
x :
Solution vector for the primal quadratic program (``None`` if no
solution was found).
y :
Dual multipliers for equality constraints (``None`` if no solution was
found, or if there is no equality constraint). The dimension of
:math:`y` is equal to the number of equality constraints. The values
:math:`y_i` can be either positive or negative.
z :
Dual multipliers for linear inequality constraints (``None`` if no
solution was found, or if there is no inequality constraint). The
dimension of :math:`z` is equal to the number of inequalities. The
value :math:`z_i` for inequality :math:`i` is always positive.
- If :math:`z_i > 0`, the inequality is active at the solution:
:math:`G_i x = h_i`.
- If :math:`z_i = 0`, the inequality is inactive at the solution:
:math:`G_i x < h_i`.
z_box :
Dual multipliers for box inequality constraints (``None`` if no
solution was found, or if there is no box inequality). The sign of
:math:`z_{box,i}` depends on the active bound:
- If :math:`z_{box,i} < 0`, then the lower bound :math:`lb_i = x_i` is
active at the solution.
- If :math:`z_{box,i} = 0`, then neither the lower nor the upper bound
are active and :math:`lb_i < x_i < ub_i`.
- If :math:`z_{box,i} > 0`, then the upper bound :math:`x_i = ub_i` is
active at the solution.
build_time :
Time taken, during the call to `solve_problem`, to build problem
matrices in the QP solver's expected format.
solve_time :
Time taken, during the call to `solve_problem`, to call the QP solver
itself.
"""
problem: Problem
extras: dict = field(default_factory=dict)
found: Optional[bool] = None
obj: Optional[float] = None
x: Optional[np.ndarray] = None
y: Optional[np.ndarray] = None
z: Optional[np.ndarray] = None
z_box: Optional[np.ndarray] = None
build_time: Optional[float] = None
solve_time: Optional[float] = None
def is_optimal(self, eps_abs: float) -> bool:
"""Check whether the solution is indeed optimal.
Parameters
----------
eps_abs :
Absolute tolerance for the primal residual, dual residual and
duality gap.
Notes
-----
See for instance [Caron2022]_ for an overview of optimality conditions
in quadratic programming.
"""
return (
self.primal_residual() < eps_abs
and self.dual_residual() < eps_abs
and self.duality_gap() < eps_abs
)
def primal_residual(self) -> float:
r"""Compute the primal residual of the solution.
The primal residual is:
.. math::
r_p := \max(\| A x - b \|_\infty, [G x - h]^+,
[lb - x]^+, [x - ub]^+)
were :math:`v^- = \min(v, 0)` and :math:`v^+ = \max(v, 0)`.
Returns
-------
:
Primal residual if it is defined, ``np.inf`` otherwise.
Notes
-----
See for instance [Caron2022]_ for an overview of optimality conditions
and why this residual will be zero at the optimum.
"""
_, _, G, h, A, b, lb, ub = self.problem.unpack()
if not self.found or self.x is None:
return np.inf
x = self.x
return max(
[
0.0,
np.max(G.dot(x) - h) if G is not None else 0.0,
np.max(np.abs(A.dot(x) - b)) if A is not None else 0.0,
np.max(lb - x) if lb is not None else 0.0,
np.max(x - ub) if ub is not None else 0.0,
]
)
def dual_residual(self) -> float:
r"""Compute the dual residual of the solution.
The dual residual is:
.. math::
r_d := \| P x + q + A^T y + G^T z + z_{box} \|_\infty
Returns
-------
:
Dual residual if it is defined, ``np.inf`` otherwise.
Notes
-----
See for instance [Caron2022]_ for an overview of optimality conditions
and why this residual will be zero at the optimum.
"""
P, q, G, _, A, _, lb, ub = self.problem.unpack()
if not self.found or self.x is None:
return np.inf
zeros = np.zeros(self.x.shape)
Px = P.dot(self.x)
ATy = zeros
if A is not None:
if self.y is None:
return np.inf
ATy = A.T.dot(self.y)
GTz = zeros
if G is not None:
if self.z is None:
return np.inf
GTz = G.T.dot(self.z)
z_box = zeros
if lb is not None or ub is not None:
if self.z_box is None:
return np.inf
z_box = self.z_box
p = np.linalg.norm(Px + q + GTz + ATy + z_box, np.inf)
return p # type: ignore
def duality_gap(self) -> float:
r"""Compute the duality gap of the solution.
The duality gap is:
.. math::
r_g := | x^T P x + q^T x + b^T y + h^T z +
lb^T z_{box}^- + ub^T z_{box}^+ |
were :math:`v^- = \min(v, 0)` and :math:`v^+ = \max(v, 0)`.
Returns
-------
:
Duality gap if it is defined, ``np.inf`` otherwise.
Notes
-----
See for instance [Caron2022]_ for an overview of optimality conditions
and why this gap will be zero at the optimum.
"""
P, q, _, h, _, b, lb, ub = self.problem.unpack()
if not self.found or self.x is None:
return np.inf
xPx = self.x.T.dot(P.dot(self.x))
qx = q.dot(self.x)
hz = 0.0
if h is not None:
if self.z is None:
return np.inf
hz = h.dot(self.z)
by = 0.0
if b is not None:
if self.y is None:
return np.inf
by = b.dot(self.y)
lb_z_box = 0.0
ub_z_box = 0.0
if self.z_box is not None:
if lb is not None:
finite = np.asarray(lb != -np.inf).nonzero()
z_box_neg = np.minimum(self.z_box, 0.0)
lb_z_box = lb[finite].dot(z_box_neg[finite])
if ub is not None:
finite = np.asarray(ub != np.inf).nonzero()
z_box_pos = np.maximum(self.z_box, 0.0)
ub_z_box = ub[finite].dot(z_box_pos[finite])
return abs(xPx + qx + hz + by + lb_z_box + ub_z_box)
================================================
FILE: qpsolvers/solve_ls.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Solve linear least squares."""
from typing import Optional, Union
import numpy as np
import scipy.sparse as spa
from .problem import Problem
from .solve_qp import solve_qp
def __solve_dense_ls(
R: Union[np.ndarray, spa.csc_matrix],
s: np.ndarray,
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
h: Optional[np.ndarray] = None,
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
b: Optional[np.ndarray] = None,
lb: Optional[np.ndarray] = None,
ub: Optional[np.ndarray] = None,
W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
solver: Optional[str] = None,
initvals: Optional[np.ndarray] = None,
verbose: bool = False,
**kwargs,
) -> Optional[np.ndarray]:
WR: Union[np.ndarray, spa.csc_matrix] = (
R if W is None else W @ R # type: ignore[assignment]
)
P_: Union[np.ndarray, spa.csc_matrix] = (
R.T @ WR # type: ignore[assignment]
)
P: Union[np.ndarray, spa.csc_matrix] = (
P_ if isinstance(P_, np.ndarray) else P_.tocsc()
)
q = -(s.T @ WR)
return solve_qp(
P,
q,
G,
h,
A,
b,
lb,
ub,
solver=solver,
initvals=initvals,
verbose=verbose,
**kwargs,
)
def __solve_sparse_ls(
R: Union[np.ndarray, spa.csc_matrix],
s: np.ndarray,
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
h: Optional[np.ndarray] = None,
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
b: Optional[np.ndarray] = None,
lb: Optional[np.ndarray] = None,
ub: Optional[np.ndarray] = None,
W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
solver: Optional[str] = None,
initvals: Optional[np.ndarray] = None,
verbose: bool = False,
**kwargs,
) -> Optional[np.ndarray]:
m, n = R.shape
eye_m = spa.eye(m, format="csc")
q = np.zeros(n + m)
# We know the RHS of this assignment is CSC from the format kwarg
P_: spa.csc_matrix = spa.block_diag( # type: ignore[assignment]
[spa.csc_matrix((n, n)), eye_m if W is None else W],
format="csc",
)
P, q, G, h, A, b, lb, ub = Problem(P_, q, G, h, A, b, lb, ub).unpack()
if G is not None:
G = spa.hstack( # type: ignore[call-overload]
[G, spa.csc_matrix((G.shape[0], m))],
format="csc",
)
if A is not None:
A = spa.hstack( # type: ignore[call-overload]
[A, spa.csc_matrix((A.shape[0], m))],
format="csc",
)
Rx_minus_y = spa.hstack( # type: ignore[call-overload]
[R, -eye_m],
format="csc",
)
if A is not None and b is not None: # help mypy
A = spa.vstack( # type: ignore[call-overload]
[A, Rx_minus_y],
format="csc",
)
b = np.hstack([b, s])
else: # no input equality constraint
A = Rx_minus_y
b = s
if lb is not None:
lb = np.hstack([lb, np.full((m,), -np.inf)])
if ub is not None:
ub = np.hstack([ub, np.full((m,), np.inf)])
xy = solve_qp(
P,
q,
G,
h,
A,
b,
lb,
ub,
solver=solver,
initvals=initvals,
verbose=verbose,
**kwargs,
)
return xy[:n] if xy is not None else None
def solve_ls(
R: Union[np.ndarray, spa.csc_matrix],
s: np.ndarray,
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
h: Optional[np.ndarray] = None,
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
b: Optional[np.ndarray] = None,
lb: Optional[np.ndarray] = None,
ub: Optional[np.ndarray] = None,
W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
solver: Optional[str] = None,
initvals: Optional[np.ndarray] = None,
verbose: bool = False,
sparse_conversion: Optional[bool] = None,
**kwargs,
) -> Optional[np.ndarray]:
r"""Solve a constrained weighted linear Least Squares problem.
The linear least squares is defined as:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac12 \| R x - s \|^2_W
= \frac12 (R x - s)^T W (R x - s) \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}\end{split}
using the QP solver selected by the ``solver`` keyword argument.
Parameters
----------
R :
Union[np.ndarray, spa.csc_matrix] factor of the cost function (most
solvers require :math:`R^T W R` to be positive definite, which means
:math:`R` should have full row rank).
s :
Vector term of the cost function.
G :
Linear inequality matrix.
h :
Linear inequality vector.
A :
Linear equality matrix.
b :
Linear equality vector.
lb :
Lower bound constraint vector.
ub :
Upper bound constraint vector.
W :
Definite symmetric weight matrix used to define the norm of the cost
function. The standard L2 norm (W = Identity) is used by default.
solver :
Name of the QP solver, to choose in
:data:`qpsolvers.available_solvers`. This argument is mandatory.
initvals :
Vector of initial `x` values used to warm-start the solver.
verbose :
Set to `True` to print out extra information.
sparse_conversion :
Set to `True` to use a sparse conversion strategy and to `False` to use
a dense strategy. By default, the conversion strategy to follow is
determined by the sparsity of :math:`R` (sparse if CSC matrix, dense
otherwise). See Notes below.
Returns
-------
:
Optimal solution if found, otherwise ``None``.
Note
----
Some solvers (like quadprog) will require a full-rank matrix :math:`R`,
while others (like ProxQP or QPALM) can work even when :math:`R` has a
non-empty nullspace.
Notes
-----
This function implements two strategies to convert the least-squares cost
:math:`(R, s)` to a quadratic-programming cost :math:`(P, q)`: one that
assumes :math:`R` is dense, and one that assumes :math:`R` is sparse. These
two strategies are detailed in `this note
<https://scaron.info/blog/conversion-from-least-squares-to-quadratic-programming.html>`__.
The sparse strategy introduces extra variables :math;`y = R x` and will
likely perform better on sparse problems, although this may not always be
the case (for instance, it may perform worse if :math:`R` has many more
rows than columns).
Extra keyword arguments given to this function are forwarded to the
underlying solvers. For example, OSQP has a setting `eps_abs` which we can
provide by ``solve_ls(R, s, G, h, solver='osqp', eps_abs=1e-4)``.
"""
if sparse_conversion is None:
sparse_conversion = not isinstance(R, np.ndarray)
if sparse_conversion:
return __solve_sparse_ls(
R, s, G, h, A, b, lb, ub, W, solver, initvals, verbose, **kwargs
)
return __solve_dense_ls(
R, s, G, h, A, b, lb, ub, W, solver, initvals, verbose, **kwargs
)
================================================
FILE: qpsolvers/solve_problem.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Solve quadratic programs."""
from typing import Optional
import numpy as np
from .exceptions import SolverNotFound
from .problem import Problem
from .solution import Solution
from .solvers import available_solvers, solve_function
def solve_problem(
problem: Problem,
solver: str,
initvals: Optional[np.ndarray] = None,
verbose: bool = False,
**kwargs,
) -> Solution:
r"""Solve a quadratic program using a given solver.
Parameters
----------
problem :
Quadratic program to solve.
solver :
Name of the solver, to choose in :data:`qpsolvers.available_solvers`.
initvals :
Primal candidate vector :math:`x` values used to warm-start the solver.
verbose :
Set to ``True`` to print out extra information.
Note
----
In quadratic programming, the matrix :math:`P` should be symmetric. Many
solvers (including CVXOPT, OSQP and quadprog) assume this is the case and
may return unintended results when the provided matrix is not. Thus, make
sure you matrix is indeed symmetric before calling this function, for
instance by projecting it on its symmetric part :math:`S = \frac{1}{2} (P
+ P^T)`.
Returns
-------
:
Solution found by the solver, if any, along with solver-specific return
values.
Raises
------
SolverNotFound
If the requested solver is not in :data:`qpsolvers.available_solvers`.
ValueError
If the problem is not correctly defined. For instance, if the solver
requires a definite cost matrix but the provided matrix :math:`P` is
not.
Notes
-----
Extra keyword arguments given to this function are forwarded to the
underlying solver. For example, we can call OSQP with a custom absolute
feasibility tolerance by ``solve_problem(problem, solver='osqp',
eps_abs=1e-6)``. See the :ref:`Supported solvers <Supported solvers>` page
for details on the parameters available to each solver.
There is no guarantee that a ``ValueError`` is raised if the provided
problem is non-convex, as some solvers don't check for this. Rather, if the
problem is non-convex and the solver fails because of that, then a
``ValueError`` will be raised.
"""
problem.check_constraints()
kwargs["initvals"] = initvals
kwargs["verbose"] = verbose
try:
return solve_function[solver](problem, **kwargs)
except KeyError as e:
raise SolverNotFound(
f"'{solver}' does not seem to be installed "
f"(found solvers: {available_solvers}); if '{solver}' is "
"listed in https://github.com/qpsolvers/qpsolvers#solvers "
f"you can install it by `pip install qpsolvers[{solver}]`"
) from e
================================================
FILE: qpsolvers/solve_qp.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Solve quadratic programs."""
from typing import Optional, Union
import numpy as np
import scipy.sparse as spa
from .exceptions import NoSolverSelected
from .problem import Problem
from .solve_problem import solve_problem
from .solvers import available_solvers
def solve_qp(
P: Union[np.ndarray, spa.csc_matrix],
q: np.ndarray,
G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
h: Optional[np.ndarray] = None,
A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,
b: Optional[np.ndarray] = None,
lb: Optional[np.ndarray] = None,
ub: Optional[np.ndarray] = None,
solver: Optional[str] = None,
initvals: Optional[np.ndarray] = None,
verbose: bool = False,
**kwargs,
) -> Optional[np.ndarray]:
r"""Solve a quadratic program.
The quadratic program is defined as:
.. math::
\begin{split}\begin{array}{ll}
\underset{x}{\mbox{minimize}} &
\frac{1}{2} x^T P x + q^T x \\
\mbox{subject to}
& G x \leq h \\
& A x = b \\
& lb \leq x \leq ub
\end{array}\end{split}
using the QP solver selected by the ``solver`` keyword argument.
Parameters
----------
P :
Symmetric cost matrix (most solvers require it to be definite as well).
q :
Cost vector.
G :
Linear inequality matrix.
h :
Linear inequality vector.
A :
Linear equality matrix.
b :
Linear equality vector.
lb :
Lower bound constraint vector. Can contain ``-np.inf``.
ub :
Upper bound constraint vector. Can contain ``+np.inf``.
solver :
Name of the QP solver, to choose in
:data:`qpsolvers.available_solvers`. This argument is mandatory.
initvals :
Primal candidate vector :math:`x` values used to warm-start the solver.
verbose :
Set to ``True`` to print out extra information.
Note
----
In quadratic programming, the matrix :math:`P` should be symmetric. Many
solvers (including CVXOPT, OSQP and quadprog) leverage this property and
may return unintended results when it is not the case. You can set
project :math:`P` on its symmetric part by:
.. code:: python
P = 0.5 * (P + P.transpose())
Some solvers (like quadprog) will further require that :math:`P` is
definite, while other solvers (like ProxQP or QPALM) can work with
semi-definite matrices.
Returns
-------
:
Optimal solution if found, otherwise ``None``.
Raises
------
NoSolverSelected
If the ``solver`` keyword argument is not set.
ParamError
If any solver parameter is incorrect.
ProblemError
If the problem is not correctly defined. For instance, if the solver
requires a definite cost matrix but the provided matrix :math:`P` is
not.
SolverError
If the solver failed during its execution.
SolverNotFound
If the requested solver is not in :data:`qpsolvers.available_solvers`.
Notes
-----
Extra keyword arguments given to this function are forwarded to the
underlying solver. For example, we can call OSQP with a custom absolute
feasibility tolerance by ``solve_qp(P, q, G, h, solver='osqp',
eps_abs=1e-6)``. See the :ref:`Supported solvers <Supported solvers>` page
for details on the parameters available to each solver.
There is no guarantee that a ``ValueError`` is raised if the provided
problem is non-convex, as some solvers don't check for this. Rather, if the
problem is non-convex and the solver fails because of that, then a
``ValueError`` will be raised.
"""
if solver is None:
raise NoSolverSelected(
"Set the `solver` keyword argument to one of the "
f"available solvers in {available_solvers}"
)
problem = Problem(P, q, G, h, A, b, lb, ub)
solution = solve_problem(problem, solver, initvals, verbose, **kwargs)
return solution.x if solution.found else None
================================================
FILE: qpsolvers/solve_unconstrained.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2023 Inria
"""Solve an unconstrained quadratic program."""
import numpy as np
from scipy.sparse.linalg import lsqr
from .exceptions import ProblemError
from .problem import Problem
from .solution import Solution
def solve_unconstrained(problem: Problem) -> Solution:
"""Solve an unconstrained quadratic program with SciPy's LSQR.
Parameters
----------
problem :
Unconstrained quadratic program.
Returns
-------
:
Solution to the unconstrained QP, if it is bounded.
Raises
------
ValueError
If the quadratic program is not unbounded below.
"""
P, q, _, _, _, _, _, _ = problem.unpack()
solution = Solution(problem)
solution.x = lsqr(P, -q)[0]
cost_check = np.linalg.norm(P @ solution.x + q)
if cost_check > 1e-8:
raise ProblemError(
f"problem is unbounded below (cost_check={cost_check:.1e}), "
"q has component in the nullspace of P"
)
solution.found = True
return solution
================================================
FILE: qpsolvers/solvers/__init__.py
================================================
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors
"""Import available QP solvers."""
import warnings
from typing import Any, Callable, Dict, List, Optional, Union
from numpy import ndarray
from scipy.sparse import csc_matrix
from ..problem import Problem
from ..solution import Solution
available_solvers: List[str] = []
dense_solvers: List[str] = []
solve_function: Dict[str, Any] = {}
sparse_solvers: List[str] = []
# Clarabel.rs
# ===========
clarabel_solve_problem: Optional[
Callable[
[
Problem,
Optional[ndarray],
bool,
],
Solution,
]
] = None
clarabel_solve_qp: Optional[
Callable[
[
Union[ndarray, csc_matrix],
ndarray,
Optional[Union[ndarray, csc_matrix]],
Optional[ndarray],
Optional[Union[ndarray, csc_matrix]],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
bool,
],
Optional[ndarray],
]
] = None
try:
from .clarabel_ import clarabel_solve_problem, clarabel_solve_qp
solve_function["clarabel"] = clarabel_solve_problem
available_solvers.append("clarabel")
sparse_solvers.append("clarabel")
except ImportError:
pass
# CVXOPT
# ======
cvxopt_solve_problem: Optional[
Callable[
[
Problem,
Optional[str],
Optional[ndarray],
bool,
],
Solution,
]
] = None
cvxopt_solve_qp: Optional[
Callable[
[
Union[ndarray, csc_matrix],
ndarray,
Optional[Union[ndarray, csc_matrix]],
Optional[ndarray],
Optional[Union[ndarray, csc_matrix]],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[str],
Optional[ndarray],
bool,
],
Optional[ndarray],
]
] = None
try:
from .cvxopt_ import cvxopt_solve_problem, cvxopt_solve_qp
solve_function["cvxopt"] = cvxopt_solve_problem
available_solvers.append("cvxopt")
dense_solvers.append("cvxopt")
sparse_solvers.append("cvxopt")
except ImportError:
pass
# DAQP
# ========
daqp_solve_qp: Optional[
Callable[
[
ndarray,
ndarray,
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
bool,
],
Optional[ndarray],
]
] = None
daqp_solve_problem: Optional[
Callable[
[
Problem,
Optional[ndarray],
bool,
],
Solution,
]
] = None
try:
from .daqp_ import daqp_solve_problem, daqp_solve_qp
solve_function["daqp"] = daqp_solve_problem
available_solvers.append("daqp")
dense_solvers.append("daqp")
except ImportError:
pass
# ECOS
# ====
ecos_solve_problem: Optional[
Callable[
[
Problem,
Optional[ndarray],
bool,
],
Solution,
]
] = None
ecos_solve_qp: Optional[
Callable[
[
ndarray,
ndarray,
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
bool,
],
Optional[ndarray],
]
] = None
try:
from .ecos_ import ecos_solve_problem, ecos_solve_qp
solve_function["ecos"] = ecos_solve_problem
available_solvers.append("ecos")
dense_solvers.append("ecos") # considered dense as it calls cholesky(P)
except ImportError:
pass
# Gurobi
# ======
gurobi_solve_problem: Optional[
Callable[
[
Problem,
Optional[ndarray],
bool,
],
Solution,
]
] = None
gurobi_solve_qp: Optional[
Callable[
[
ndarray,
ndarray,
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
bool,
],
Optional[ndarray],
]
] = None
try:
from .gurobi_ import gurobi_solve_problem, gurobi_solve_qp
solve_function["gurobi"] = gurobi_solve_problem
available_solvers.append("gurobi")
sparse_solvers.append("gurobi")
except ImportError:
pass
# COPT
# ======
copt_solve_problem: Optional[
Callable[
[
Problem,
Optional[ndarray],
bool,
],
Solution,
]
] = None
copt_solve_qp: Optional[
Callable[
[
ndarray,
ndarray,
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
Optional[ndarray],
gitextract_sm8s1023/
├── .gitattributes
├── .github/
│ └── workflows/
│ ├── changelog.yml
│ ├── ci.yml
│ ├── docs.yml
│ └── pypi.yml
├── .gitignore
├── CHANGELOG.md
├── CITATION.cff
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── doc/
│ ├── conf.py
│ ├── developer-notes.rst
│ ├── index.rst
│ ├── installation.rst
│ ├── least-squares.rst
│ ├── quadratic-programming.rst
│ ├── references.rst
│ ├── supported-solvers.rst
│ └── unsupported-solvers.rst
├── examples/
│ ├── README.md
│ ├── box_inequalities.py
│ ├── constrained_linear_regression.py
│ ├── dual_multipliers.py
│ ├── lasso_regularization.py
│ ├── least_squares.py
│ ├── model_predictive_control.py
│ ├── quadratic_program.py
│ ├── sparse_least_squares.py
│ ├── test_dense_problem.py
│ ├── test_model_predictive_control.py
│ ├── test_random_problems.py
│ └── test_sparse_problem.py
├── pyproject.toml
├── qpsolvers/
│ ├── __init__.py
│ ├── active_set.py
│ ├── conversions/
│ │ ├── __init__.py
│ │ ├── combine_linear_box_inequalities.py
│ │ ├── ensure_sparse_matrices.py
│ │ ├── linear_from_box_inequalities.py
│ │ ├── socp_from_qp.py
│ │ └── split_dual_linear_box.py
│ ├── exceptions.py
│ ├── problem.py
│ ├── problems.py
│ ├── py.typed
│ ├── solution.py
│ ├── solve_ls.py
│ ├── solve_problem.py
│ ├── solve_qp.py
│ ├── solve_unconstrained.py
│ ├── solvers/
│ │ ├── __init__.py
│ │ ├── clarabel_.py
│ │ ├── copt_.py
│ │ ├── cvxopt_.py
│ │ ├── daqp_.py
│ │ ├── ecos_.py
│ │ ├── gurobi_.py
│ │ ├── highs_.py
│ │ ├── hpipm_.py
│ │ ├── jaxopt_osqp_.py
│ │ ├── kvxopt_.py
│ │ ├── mosek_.py
│ │ ├── nppro_.py
│ │ ├── osqp_.py
│ │ ├── pdhcg_.py
│ │ ├── piqp_.py
│ │ ├── proxqp_.py
│ │ ├── pyqpmad_.py
│ │ ├── qpalm_.py
│ │ ├── qpax_.py
│ │ ├── qpoases_.py
│ │ ├── qpswift_.py
│ │ ├── qtqp_.py
│ │ ├── quadprog_.py
│ │ ├── scs_.py
│ │ └── sip_.py
│ ├── utils.py
│ └── warnings.py
└── tests/
├── __init__.py
├── problems.py
├── test_clarabel.py
├── test_combine_linear_box_inequalities.py
├── test_conversions.py
├── test_copt.py
├── test_cvxopt.py
├── test_ecos.py
├── test_gurobi.py
├── test_highs.py
├── test_jaxopt_osqp.py
├── test_kvxopt.py
├── test_mosek.py
├── test_nppro.py
├── test_osqp.py
├── test_piqp.py
├── test_problem.py
├── test_proxqp.py
├── test_pyqpmad.py
├── test_qpax.py
├── test_qpoases.py
├── test_qpswift.py
├── test_quadprog.py
├── test_scs.py
├── test_sip.py
├── test_solution.py
├── test_solve_ls.py
├── test_solve_problem.py
├── test_solve_qp.py
├── test_timings.py
├── test_unfeasible_problem.py
└── test_utils.py
SYMBOL INDEX (280 symbols across 75 files)
FILE: examples/model_predictive_control.py
class HumanoidSteppingProblem (line 30) | class HumanoidSteppingProblem:
class LinearModelPredictiveControl (line 42) | class LinearModelPredictiveControl:
method __init__ (line 51) | def __init__(
method build (line 94) | def build(self):
method solve (line 142) | def solve(self, solver: str, sparse: bool = False, **kwargs):
method states (line 149) | def states(self):
class HumanoidModelPredictiveControl (line 158) | class HumanoidModelPredictiveControl(LinearModelPredictiveControl):
method __init__ (line 159) | def __init__(self, problem: HumanoidSteppingProblem):
function plot_mpc_solution (line 200) | def plot_mpc_solution(problem, mpc):
FILE: examples/test_random_problems.py
function solve_random_qp (line 28) | def solve_random_qp(n, solver):
function plot_results (line 38) | def plot_results(perfs):
FILE: examples/test_sparse_problem.py
function check_same_solutions (line 32) | def check_same_solutions(tol=0.05):
function time_dense_solvers (line 48) | def time_dense_solvers():
function time_sparse_solvers (line 59) | def time_sparse_solvers():
FILE: qpsolvers/active_set.py
class ActiveSet (line 14) | class ActiveSet:
method __init__ (line 31) | def __init__(
FILE: qpsolvers/conversions/combine_linear_box_inequalities.py
function combine_linear_box_inequalities (line 15) | def combine_linear_box_inequalities(G, h, lb, ub, n: int, use_csc: bool):
FILE: qpsolvers/conversions/ensure_sparse_matrices.py
function __warn_about_sparse_conversion (line 18) | def __warn_about_sparse_conversion(matrix_name: str, solver_name: str) -...
function ensure_sparse_matrices (line 36) | def ensure_sparse_matrices(
FILE: qpsolvers/conversions/linear_from_box_inequalities.py
function concatenate_bound (line 17) | def concatenate_bound(
function linear_from_box_inequalities (line 66) | def linear_from_box_inequalities(
FILE: qpsolvers/conversions/socp_from_qp.py
function socp_from_qp (line 19) | def socp_from_qp(
FILE: qpsolvers/conversions/split_dual_linear_box.py
function split_dual_linear_box (line 14) | def split_dual_linear_box(
FILE: qpsolvers/exceptions.py
class QPError (line 17) | class QPError(Exception):
class NoSolverSelected (line 21) | class NoSolverSelected(QPError):
class ParamError (line 25) | class ParamError(QPError):
class ProblemError (line 29) | class ProblemError(QPError):
class SolverNotFound (line 33) | class SolverNotFound(QPError):
class SolverError (line 37) | class SolverError(QPError):
FILE: qpsolvers/problem.py
class Problem (line 19) | class Problem:
method __check_matrix (line 69) | def __check_matrix(
method __check_vector (line 92) | def __check_vector(v: np.ndarray, name: str) -> np.ndarray:
method __init__ (line 122) | def __init__(
method has_sparse (line 151) | def has_sparse(self) -> bool:
method is_unconstrained (line 168) | def is_unconstrained(self) -> bool:
method unpack (line 183) | def unpack(
method unpack_as_dense (line 213) | def unpack_as_dense(
method check_constraints (line 243) | def check_constraints(self):
method __get_active_inequalities (line 260) | def __get_active_inequalities(
method cond (line 294) | def cond(self, active_set: ActiveSet) -> float:
method save (line 394) | def save(self, file: str) -> None:
method load (line 422) | def load(file: str):
method get_cute_classification (line 452) | def get_cute_classification(self, interest: str) -> str:
FILE: qpsolvers/problems.py
function get_sparse_least_squares (line 18) | def get_sparse_least_squares(n):
function get_qpsut01 (line 59) | def get_qpsut01() -> Tuple[Problem, Solution]:
function get_qpsut02 (line 87) | def get_qpsut02() -> Tuple[Problem, Solution]:
function get_qpsut03 (line 131) | def get_qpsut03() -> Tuple[Problem, Solution]:
function get_qpsut04 (line 171) | def get_qpsut04() -> Tuple[Problem, Solution]:
function get_qpsut05 (line 196) | def get_qpsut05() -> Tuple[Problem, Solution]:
function get_qptest (line 214) | def get_qptest():
function get_qpgurdu (line 238) | def get_qpgurdu():
function get_qpgurabs (line 529) | def get_qpgurabs():
function get_qpgureq (line 546) | def get_qpgureq():
FILE: qpsolvers/solution.py
class Solution (line 18) | class Solution:
method is_optimal (line 91) | def is_optimal(self, eps_abs: float) -> bool:
method primal_residual (line 111) | def primal_residual(self) -> float:
method dual_residual (line 147) | def dual_residual(self) -> float:
method duality_gap (line 193) | def duality_gap(self) -> float:
FILE: qpsolvers/solve_ls.py
function __solve_dense_ls (line 18) | def __solve_dense_ls(
function __solve_sparse_ls (line 59) | def __solve_sparse_ls(
function solve_ls (line 131) | def solve_ls(
FILE: qpsolvers/solve_problem.py
function solve_problem (line 19) | def solve_problem(
FILE: qpsolvers/solve_qp.py
function solve_qp (line 20) | def solve_qp(
FILE: qpsolvers/solve_unconstrained.py
function solve_unconstrained (line 17) | def solve_unconstrained(problem: Problem) -> Solution:
FILE: qpsolvers/solvers/clarabel_.py
function clarabel_solve_problem (line 39) | def clarabel_solve_problem(
function clarabel_solve_qp (line 166) | def clarabel_solve_qp(
FILE: qpsolvers/solvers/copt_.py
function copt_solve_problem (line 31) | def copt_solve_problem(
function __retrieve_dual (line 141) | def __retrieve_dual(
function copt_solve_qp (line 174) | def copt_solve_qp(
function __to_numpy (line 264) | def __to_numpy(
FILE: qpsolvers/solvers/cvxopt_.py
function __to_cvxopt (line 36) | def __to_cvxopt(
function cvxopt_solve_problem (line 61) | def cvxopt_solve_problem(
function cvxopt_solve_qp (line 207) | def cvxopt_solve_qp(
FILE: qpsolvers/solvers/daqp_.py
function daqp_solve_problem (line 27) | def daqp_solve_problem(
function daqp_solve_qp (line 141) | def daqp_solve_qp(
FILE: qpsolvers/solvers/ecos_.py
function ecos_solve_problem (line 52) | def ecos_solve_problem(
function ecos_solve_qp (line 184) | def ecos_solve_qp(
FILE: qpsolvers/solvers/gurobi_.py
function gurobi_solve_problem (line 34) | def gurobi_solve_problem(
function __retrieve_dual (line 147) | def __retrieve_dual(
function gurobi_solve_qp (line 172) | def gurobi_solve_qp(
FILE: qpsolvers/solvers/highs_.py
function __set_hessian (line 32) | def __set_hessian(model: highspy.HighsModel, P: spa.csc_matrix) -> None:
function __set_columns (line 49) | def __set_columns(
function __set_rows (line 81) | def __set_rows(
function highs_solve_problem (line 131) | def highs_solve_problem(
function highs_solve_qp (line 233) | def highs_solve_qp(
FILE: qpsolvers/solvers/hpipm_.py
function hpipm_solve_problem (line 29) | def hpipm_solve_problem(
function hpipm_solve_qp (line 184) | def hpipm_solve_qp(
FILE: qpsolvers/solvers/jaxopt_osqp_.py
function jaxopt_osqp_solve_problem (line 32) | def jaxopt_osqp_solve_problem(
function jaxopt_osqp_solve_qp (line 113) | def jaxopt_osqp_solve_qp(
FILE: qpsolvers/solvers/kvxopt_.py
function __to_cvxopt (line 35) | def __to_cvxopt(
function kvxopt_solve_problem (line 60) | def kvxopt_solve_problem(
function kvxopt_solve_qp (line 212) | def kvxopt_solve_qp(
FILE: qpsolvers/solvers/mosek_.py
function mosek_solve_problem (line 31) | def mosek_solve_problem(
function mosek_solve_qp (line 84) | def mosek_solve_qp(
FILE: qpsolvers/solvers/nppro_.py
function nppro_solve_problem (line 25) | def nppro_solve_problem(
function nppro_solve_qp (line 150) | def nppro_solve_qp(
FILE: qpsolvers/solvers/osqp_.py
function osqp_solve_problem (line 34) | def osqp_solve_problem(
function osqp_solve_qp (line 168) | def osqp_solve_qp(
FILE: qpsolvers/solvers/pdhcg_.py
function pdhcg_solve_problem (line 35) | def pdhcg_solve_problem(
function pdhcg_solve_qp (line 184) | def pdhcg_solve_qp(
FILE: qpsolvers/solvers/piqp_.py
function __select_backend (line 35) | def __select_backend(backend: Optional[str], use_csc: bool):
function piqp_solve_problem (line 65) | def piqp_solve_problem(
function piqp_solve_qp (line 266) | def piqp_solve_qp(
FILE: qpsolvers/solvers/proxqp_.py
function __select_backend (line 33) | def __select_backend(backend: Optional[str], use_csc: bool):
function proxqp_solve_problem (line 63) | def proxqp_solve_problem(
function proxqp_solve_qp (line 188) | def proxqp_solve_qp(
FILE: qpsolvers/solvers/pyqpmad_.py
function pyqpmad_solve_problem (line 30) | def pyqpmad_solve_problem(
function pyqpmad_solve_qp (line 205) | def pyqpmad_solve_qp(
FILE: qpsolvers/solvers/qpalm_.py
function qpalm_solve_problem (line 35) | def qpalm_solve_problem(
function qpalm_solve_qp (line 160) | def qpalm_solve_qp(
FILE: qpsolvers/solvers/qpax_.py
function qpax_solve_problem (line 32) | def qpax_solve_problem(
function qpax_solve_qp (line 158) | def qpax_solve_qp(
FILE: qpsolvers/solvers/qpoases_.py
function __clamp_infinities (line 53) | def __clamp_infinities(v: Optional[np.ndarray]):
function __prepare_options (line 66) | def __prepare_options(
function __convert_inequalities (line 117) | def __convert_inequalities(
function qpoases_solve_problem (line 162) | def qpoases_solve_problem(
function qpoases_solve_qp (line 302) | def qpoases_solve_qp(
FILE: qpsolvers/solvers/qpswift_.py
function qpswift_solve_problem (line 33) | def qpswift_solve_problem(
function qpswift_solve_qp (line 169) | def qpswift_solve_qp(
FILE: qpsolvers/solvers/qtqp_.py
function qtqp_solve_problem (line 34) | def qtqp_solve_problem(
function qtqp_solve_qp (line 221) | def qtqp_solve_qp(
FILE: qpsolvers/solvers/quadprog_.py
function quadprog_solve_problem (line 30) | def quadprog_solve_problem(
function quadprog_solve_qp (line 135) | def quadprog_solve_qp(
FILE: qpsolvers/solvers/scs_.py
function __add_box_cone (line 47) | def __add_box_cone(
function scs_solve_problem (line 86) | def scs_solve_problem(
function scs_solve_qp (line 209) | def scs_solve_qp(
FILE: qpsolvers/solvers/sip_.py
function sip_solve_problem (line 33) | def sip_solve_problem(
function sip_solve_qp (line 321) | def sip_solve_qp(
FILE: qpsolvers/utils.py
function print_matrix_vector (line 15) | def print_matrix_vector(
FILE: qpsolvers/warnings.py
class QPWarning (line 10) | class QPWarning(UserWarning):
class SparseConversionWarning (line 14) | class SparseConversionWarning(QPWarning):
FILE: tests/problems.py
function get_sd3310_problem (line 12) | def get_sd3310_problem() -> Problem:
function get_qpmad_demo_problem (line 27) | def get_qpmad_demo_problem():
FILE: tests/test_clarabel.py
class TestClarabel (line 19) | class TestClarabel(unittest.TestCase):
method test_time_limit (line 22) | def test_time_limit(self):
method test_status (line 31) | def test_status(self):
FILE: tests/test_combine_linear_box_inequalities.py
class TestCombineLinearBoxInequalities (line 18) | class TestCombineLinearBoxInequalities(unittest.TestCase):
method setUp (line 19) | def setUp(self):
method get_dense_problem (line 24) | def get_dense_problem(self):
method get_test_all_shapes (line 52) | def get_test_all_shapes(solver: str):
FILE: tests/test_conversions.py
class TestConversions (line 17) | class TestConversions(unittest.TestCase):
method __test_linear_from_box_inequalities (line 20) | def __test_linear_from_box_inequalities(self, G, h, lb, ub):
method test_concatenate_bounds (line 29) | def test_concatenate_bounds(self):
method test_pure_bounds (line 36) | def test_pure_bounds(self):
method test_skip_infinite_bounds (line 41) | def test_skip_infinite_bounds(self):
method test_skip_partial_infinite_bounds (line 54) | def test_skip_partial_infinite_bounds(self):
method test_sparse_conversion (line 67) | def test_sparse_conversion(self):
FILE: tests/test_copt.py
class TestCOPT (line 17) | class TestCOPT(unittest.TestCase):
method test_copt_params (line 20) | def test_copt_params(self):
FILE: tests/test_cvxopt.py
class TestCVXOPT (line 25) | class TestCVXOPT(unittest.TestCase):
method setUp (line 28) | def setUp(self):
method get_sparse_problem (line 32) | def get_sparse_problem(
method test_sparse (line 59) | def test_sparse(self):
method test_extra_kwargs (line 71) | def test_extra_kwargs(self):
method test_infinite_linear_bounds (line 89) | def test_infinite_linear_bounds(self):
method test_infinite_box_bounds (line 96) | def test_infinite_box_bounds(self):
FILE: tests/test_ecos.py
class TestECOS (line 21) | class TestECOS(unittest.TestCase):
method test_problem (line 24) | def test_problem(self):
method test_infinite_inequality (line 29) | def test_infinite_inequality(self):
FILE: tests/test_gurobi.py
class TestGurobi (line 17) | class TestGurobi(unittest.TestCase):
method test_gurobi_params (line 20) | def test_gurobi_params(self):
FILE: tests/test_highs.py
class TestHiGHS (line 17) | class TestHiGHS(unittest.TestCase):
method setUp (line 20) | def setUp(self):
method test_highs_tolerances (line 24) | def test_highs_tolerances(self):
FILE: tests/test_jaxopt_osqp.py
class TestKVXOPT (line 17) | class TestKVXOPT(unittest.TestCase):
method setUp (line 20) | def setUp(self):
method test_jax_array_input (line 24) | def test_jax_array_input(self):
FILE: tests/test_kvxopt.py
class TestKVXOPT (line 25) | class TestKVXOPT(unittest.TestCase):
method setUp (line 28) | def setUp(self):
method get_sparse_problem (line 32) | def get_sparse_problem(
method test_sparse (line 59) | def test_sparse(self):
method test_extra_kwargs (line 71) | def test_extra_kwargs(self):
method test_infinite_linear_bounds (line 89) | def test_infinite_linear_bounds(self):
method test_infinite_box_bounds (line 96) | def test_infinite_box_bounds(self):
FILE: tests/test_mosek.py
class TestMOSEK (line 17) | class TestMOSEK(unittest.TestCase):
method test_problem (line 20) | def test_problem(self):
FILE: tests/test_nppro.py
class TestNPPro (line 15) | class TestNPPro(unittest.TestCase):
method test_problem (line 18) | def test_problem(self):
FILE: tests/test_osqp.py
class TestOSQP (line 17) | class TestOSQP(unittest.TestCase):
method test_problem (line 20) | def test_problem(self):
FILE: tests/test_piqp.py
class TestPIQP (line 19) | class TestPIQP(unittest.TestCase):
method test_dense_backend (line 22) | def test_dense_backend(self):
method test_sparse_backend (line 36) | def test_sparse_backend(self):
method test_invalid_backend (line 50) | def test_invalid_backend(self):
method test_invalid_problems (line 64) | def test_invalid_problems(self):
FILE: tests/test_problem.py
class TestProblem (line 20) | class TestProblem(unittest.TestCase):
method setUp (line 23) | def setUp(self):
method test_unpack (line 26) | def test_unpack(self):
method test_check_inequality_constraints (line 37) | def test_check_inequality_constraints(self):
method test_check_equality_constraints (line 45) | def test_check_equality_constraints(self):
method test_cond (line 53) | def test_cond(self):
method test_cond_unconstrained (line 61) | def test_cond_unconstrained(self):
method test_cond_no_equality (line 68) | def test_cond_no_equality(self):
method test_cond_sparse (line 75) | def test_cond_sparse(self):
method test_check_matrix_shapes (line 81) | def test_check_matrix_shapes(self):
method test_check_vector_shapes (line 85) | def test_check_vector_shapes(self):
method test_save_load (line 96) | def test_save_load(self):
FILE: tests/test_proxqp.py
class TestProxQP (line 19) | class TestProxQP(unittest.TestCase):
method test_dense_backend (line 22) | def test_dense_backend(self):
method test_sparse_backend (line 35) | def test_sparse_backend(self):
method test_invalid_backend (line 48) | def test_invalid_backend(self):
method test_double_warm_start (line 62) | def test_double_warm_start(self):
method test_invalid_inequalities (line 77) | def test_invalid_inequalities(self):
FILE: tests/test_pyqpmad.py
class TestPyqpmad (line 22) | class TestPyqpmad(unittest.TestCase):
method setUp (line 25) | def setUp(self):
method test_not_sparse (line 29) | def test_not_sparse(self):
method test_box_constraints (line 37) | def test_box_constraints(self):
FILE: tests/test_qpax.py
class TestQpax (line 17) | class TestQpax(unittest.TestCase):
method test_problem (line 20) | def test_problem(self):
FILE: tests/test_qpoases.py
class TestQpOASES (line 22) | class TestQpOASES(unittest.TestCase):
method test_initvals (line 25) | def test_initvals(self):
method test_params (line 38) | def test_params(self):
method test_unfeasible (line 58) | def test_unfeasible(self):
method test_not_sparse (line 75) | def test_not_sparse(self):
FILE: tests/test_qpswift.py
class TestQpSwift (line 21) | class TestQpSwift(unittest.TestCase):
method test_problem (line 24) | def test_problem(self):
method test_not_sparse (line 29) | def test_not_sparse(self):
FILE: tests/test_quadprog.py
class TestQuadprog (line 22) | class TestQuadprog(unittest.TestCase):
method setUp (line 25) | def setUp(self):
method test_non_psd_cost (line 29) | def test_non_psd_cost(self):
method test_quadprog_value_error (line 36) | def test_quadprog_value_error(self):
method test_not_sparse (line 42) | def test_not_sparse(self):
FILE: tests/test_scs.py
class TestSCS (line 21) | class TestSCS(unittest.TestCase):
method test_problem (line 24) | def test_problem(self):
method test_unbounded_below (line 29) | def test_unbounded_below(self):
FILE: tests/test_sip.py
class TestSIP (line 21) | class TestSIP(unittest.TestCase):
method test_problem (line 24) | def test_problem(self):
FILE: tests/test_solution.py
class TestSolution (line 18) | class TestSolution(unittest.TestCase):
method test_found_default (line 21) | def test_found_default(self):
method test_residuals (line 25) | def test_residuals(self):
method test_undefined_optimality (line 40) | def test_undefined_optimality(self):
FILE: tests/test_solve_ls.py
class TestSolveLS (line 21) | class TestSolveLS(unittest.TestCase):
method setUp (line 22) | def setUp(self):
method get_problem_and_solution (line 27) | def get_problem_and_solution(self):
method get_test (line 57) | def get_test(solver: str):
method test_no_solver_selected (line 124) | def test_no_solver_selected(self):
method test_solver_not_found (line 130) | def test_solver_not_found(self):
method get_test_mixed_sparse_args (line 137) | def get_test_mixed_sparse_args(solver: str):
method get_test_medium_sparse (line 189) | def get_test_medium_sparse(solver: str, sparse_conversion: bool, **kwa...
method get_test_large_sparse (line 223) | def get_test_large_sparse(
FILE: tests/test_solve_problem.py
class TestSolveProblem (line 28) | class TestSolveProblem(unittest.TestCase):
method get_test_qpsut01 (line 40) | def get_test_qpsut01(solver: str):
method get_test_qpsut02 (line 104) | def get_test_qpsut02(solver: str):
method get_test_qpsut03 (line 159) | def get_test_qpsut03(solver: str):
method get_test_qpsut04 (line 190) | def get_test_qpsut04(solver: str):
method get_test_qpsut05 (line 224) | def get_test_qpsut05(solver: str):
method get_test_qptest (line 251) | def get_test_qptest(solver: str):
method get_test_infinite_box_bounds (line 314) | def get_test_infinite_box_bounds(solver: str):
method get_test_infinite_linear_bounds (line 340) | def get_test_infinite_linear_bounds(solver: str):
method get_test_qpgurdu (line 365) | def get_test_qpgurdu(solver: str):
method get_test_qpgurabs (line 402) | def get_test_qpgurabs(solver: str):
method get_test_qpgureq (line 433) | def get_test_qpgureq(solver: str):
FILE: tests/test_solve_qp.py
class TestSolveQP (line 39) | class TestSolveQP(unittest.TestCase):
method setUp (line 48) | def setUp(self):
method get_dense_problem (line 53) | def get_dense_problem(self):
method get_sparse_problem (line 80) | def get_sparse_problem(self):
method test_no_solver_selected (line 105) | def test_no_solver_selected(self):
method test_solver_not_found (line 111) | def test_solver_not_found(self):
method get_test (line 118) | def get_test(solver: str):
method get_test_all_shapes (line 170) | def get_test_all_shapes(solver: str):
method get_test_bounds (line 284) | def get_test_bounds(solver: str):
method get_test_no_cons (line 339) | def get_test_no_cons(solver: str):
method get_test_no_eq (line 372) | def get_test_no_eq(solver: str):
method get_test_no_ineq (line 423) | def get_test_no_ineq(solver: str):
method get_test_one_ineq (line 475) | def get_test_one_ineq(solver: str):
method get_test_sparse (line 547) | def get_test_sparse(solver: str):
method get_test_sparse_bounds (line 606) | def get_test_sparse_bounds(solver: str):
method get_test_sparse_unfeasible (line 650) | def get_test_sparse_unfeasible(solver: str):
method get_test_warmstart (line 681) | def get_test_warmstart(solver: str):
method get_test_raise_on_unbounded_below (line 745) | def get_test_raise_on_unbounded_below(solver: str):
method get_test_qpmad_demo (line 778) | def get_test_qpmad_demo(solver: str):
FILE: tests/test_timings.py
class TestTimings (line 6) | class TestTimings(unittest.TestCase):
method test_timings_recorded (line 7) | def test_timings_recorded(self):
FILE: tests/test_unfeasible_problem.py
class UnfeasibleProblem (line 16) | class UnfeasibleProblem(unittest.TestCase):
method setUp (line 22) | def setUp(self):
method get_unfeasible_problem (line 28) | def get_unfeasible_problem(self):
method get_test (line 57) | def get_test(solver: str):
FILE: tests/test_utils.py
class TestUtils (line 18) | class TestUtils(unittest.TestCase):
method setUp (line 21) | def setUp(self):
method test_print_matrix_vector (line 25) | def test_print_matrix_vector(self):
Condensed preview — 111 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (563K chars).
[
{
"path": ".gitattributes",
"chars": 76,
"preview": "pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff\n"
},
{
"path": ".github/workflows/changelog.yml",
"chars": 488,
"preview": "name: Changelog\n\non:\n pull_request:\n branches: [ main ]\n\njobs:\n changelog:\n name: \"Check changelog u"
},
{
"path": ".github/workflows/ci.yml",
"chars": 4181,
"preview": "name: CI\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n workflow_dispatch:\n\n"
},
{
"path": ".github/workflows/docs.yml",
"chars": 1370,
"preview": "name: Documentation\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n\njobs:\n do"
},
{
"path": ".github/workflows/pypi.yml",
"chars": 1526,
"preview": "name: PyPI\n\non:\n push:\n branches: [ main ]\n pull_request:\n branches: [ main ]\n\njobs:\n pypi:\n "
},
{
"path": ".gitignore",
"chars": 128,
"preview": "*.pyc\n*.pyo\n.coverage\n.ropeproject\n.tox\nMANIFEST\n_build/\nbuild/\ndist/\nexamples/qpsolvers\ngurobi.log\nhtmlcov/\nqpsolvers.e"
},
{
"path": "CHANGELOG.md",
"chars": 28788,
"preview": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Change"
},
{
"path": "CITATION.cff",
"chars": 2171,
"preview": "cff-version: 1.2.0\nmessage: \"If you find this code helpful, please cite it as below.\"\ntitle: \"qpsolvers: Quadratic Progr"
},
{
"path": "CONTRIBUTING.md",
"chars": 996,
"preview": "# 👷 Contributing\n\nThere are many ways you can contribute to qpsolvers. Here are some ideas:\n\n- Add a [new solver](https:"
},
{
"path": "LICENSE",
"chars": 7652,
"preview": " GNU LESSER GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n\n Copyright (C) 2007"
},
{
"path": "README.md",
"chars": 11210,
"preview": "# Quadratic Programming Solvers in Python\n\n[ solvers ava"
},
{
"path": "doc/installation.rst",
"chars": 4871,
"preview": "************\nInstallation\n************\n\nLinux\n=====\n\nConda\n-----\n\nTo install the library from `conda-forge <https://cond"
},
{
"path": "doc/least-squares.rst",
"chars": 1012,
"preview": ".. _Least squares:\n\n*************\nLeast squares\n*************\n\nTo solve a linear least-squares problem, simply build the"
},
{
"path": "doc/quadratic-programming.rst",
"chars": 5670,
"preview": ".. _Quadratic programming:\n\n*********************\nQuadratic programming\n*********************\n\nPrimal problem\n=========="
},
{
"path": "doc/references.rst",
"chars": 3329,
"preview": "**********\nReferences\n**********\n\n.. [Tracy2024] `On the Differentiability of the Primal-Dual Interior-Point Method <htt"
},
{
"path": "doc/supported-solvers.rst",
"chars": 1820,
"preview": ".. _Supported solvers:\n\n*****************\nSupported solvers\n*****************\n\nSolvers that are detected as installed on"
},
{
"path": "doc/unsupported-solvers.rst",
"chars": 465,
"preview": "*******************\nUnsupported solvers\n*******************\n\nUnsupported solvers will be made available if they are dete"
},
{
"path": "examples/README.md",
"chars": 667,
"preview": "# Quadratic programming examples\n\nExamples are roughly sorted from simple to complex. The basic ones are:\n\n- [Quadratic "
},
{
"path": "examples/box_inequalities.py",
"chars": 1386,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/constrained_linear_regression.py",
"chars": 957,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/dual_multipliers.py",
"chars": 2144,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/lasso_regularization.py",
"chars": 1625,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/least_squares.py",
"chars": 1223,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/model_predictive_control.py",
"chars": 7656,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/quadratic_program.py",
"chars": 1401,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/sparse_least_squares.py",
"chars": 1405,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/test_dense_problem.py",
"chars": 1994,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/test_model_predictive_control.py",
"chars": 1576,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/test_random_problems.py",
"chars": 2949,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "examples/test_sparse_problem.py",
"chars": 2522,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "pyproject.toml",
"chars": 7342,
"preview": "[build-system]\nrequires = [\"flit_core >=2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n\n[project]\nname = \"qpsolvers\"\nreadme"
},
{
"path": "qpsolvers/__init__.py",
"chars": 2002,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/active_set.py",
"chars": 1138,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\""
},
{
"path": "qpsolvers/conversions/__init__.py",
"chars": 688,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/conversions/combine_linear_box_inequalities.py",
"chars": 2457,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéph"
},
{
"path": "qpsolvers/conversions/ensure_sparse_matrices.py",
"chars": 2026,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/conversions/linear_from_box_inequalities.py",
"chars": 2939,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/conversions/socp_from_qp.py",
"chars": 3202,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/conversions/split_dual_linear_box.py",
"chars": 1686,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/exceptions.py",
"chars": 1033,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/problem.py",
"chars": 15107,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/problems.py",
"chars": 14260,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéph"
},
{
"path": "qpsolvers/py.typed",
"chars": 0,
"preview": ""
},
{
"path": "qpsolvers/solution.py",
"chars": 7527,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solve_ls.py",
"chars": 7440,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solve_problem.py",
"chars": 2952,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solve_qp.py",
"chars": 4276,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solve_unconstrained.py",
"chars": 1123,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\""
},
{
"path": "qpsolvers/solvers/__init__.py",
"chars": 22517,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/clarabel_.py",
"chars": 6848,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\""
},
{
"path": "qpsolvers/solvers/copt_.py",
"chars": 11204,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/cvxopt_.py",
"chars": 8921,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/daqp_.py",
"chars": 6410,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/ecos_.py",
"chars": 9117,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/gurobi_.py",
"chars": 8039,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/highs_.py",
"chars": 9324,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/hpipm_.py",
"chars": 8137,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/jaxopt_osqp_.py",
"chars": 5223,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov"
},
{
"path": "qpsolvers/solvers/kvxopt_.py",
"chars": 9115,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/mosek_.py",
"chars": 4193,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/nppro_.py",
"chars": 6113,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéph"
},
{
"path": "qpsolvers/solvers/osqp_.py",
"chars": 9480,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/pdhcg_.py",
"chars": 7991,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/piqp_.py",
"chars": 11068,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/proxqp_.py",
"chars": 8114,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/pyqpmad_.py",
"chars": 8301,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/qpalm_.py",
"chars": 6463,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/qpax_.py",
"chars": 6137,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov"
},
{
"path": "qpsolvers/solvers/qpoases_.py",
"chars": 13281,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/qpswift_.py",
"chars": 10347,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/qtqp_.py",
"chars": 8810,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n\n\"\"\"Solver interface for `Q"
},
{
"path": "qpsolvers/solvers/quadprog_.py",
"chars": 5897,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/scs_.py",
"chars": 9727,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/solvers/sip_.py",
"chars": 12750,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/utils.py",
"chars": 1746,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "qpsolvers/warnings.py",
"chars": 349,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2025 Inria\n\n\"\"\""
},
{
"path": "tests/__init__.py",
"chars": 59,
"preview": "# Make sure Python treats the test directory as a package.\n"
},
{
"path": "tests/problems.py",
"chars": 1968,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_clarabel.py",
"chars": 1569,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_combine_linear_box_inequalities.py",
"chars": 3997,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_conversions.py",
"chars": 3037,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_copt.py",
"chars": 899,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_cvxopt.py",
"chars": 3437,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_ecos.py",
"chars": 1119,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_gurobi.py",
"chars": 866,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_highs.py",
"chars": 1112,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_jaxopt_osqp.py",
"chars": 1276,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2025 Stéph"
},
{
"path": "tests/test_kvxopt.py",
"chars": 3436,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_mosek.py",
"chars": 716,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_nppro.py",
"chars": 687,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_osqp.py",
"chars": 709,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_piqp.py",
"chars": 3294,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_problem.py",
"chars": 3881,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_proxqp.py",
"chars": 2909,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_pyqpmad.py",
"chars": 1504,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2024 Stéph"
},
{
"path": "tests/test_qpax.py",
"chars": 670,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov"
},
{
"path": "tests/test_qpoases.py",
"chars": 2530,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_qpswift.py",
"chars": 1072,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_quadprog.py",
"chars": 1635,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_scs.py",
"chars": 1034,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_sip.py",
"chars": 758,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_solution.py",
"chars": 2420,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_solve_ls.py",
"chars": 11221,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_solve_problem.py",
"chars": 17043,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\""
},
{
"path": "tests/test_solve_qp.py",
"chars": 29731,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_timings.py",
"chars": 1220,
"preview": "import unittest\nimport warnings\nfrom qpsolvers import available_solvers, solve_problem\nfrom .problems import get_qpmad_d"
},
{
"path": "tests/test_unfeasible_problem.py",
"chars": 2350,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
},
{
"path": "tests/test_utils.py",
"chars": 1221,
"preview": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéph"
}
]
About this extraction
This page contains the full source code of the stephane-caron/qpsolvers GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 111 files (520.4 KB), approximately 142.6k tokens, and a symbol index with 280 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.