[
  {
    "path": ".gitattributes",
    "content": "pixi.lock merge=binary linguist-language=YAML linguist-generated=true -diff\n"
  },
  {
    "path": ".github/workflows/changelog.yml",
    "content": "name: Changelog\n\non:\n    pull_request:\n        branches: [ main ]\n\njobs:\n    changelog:\n        name: \"Check changelog update\"\n        runs-on: ubuntu-latest\n        steps:\n            - uses: tarides/changelog-check-action@v2\n              with:\n                  changelog: CHANGELOG.md\n\n    changelog_success:\n        name: \"Changelog success\"\n        runs-on: ubuntu-latest\n        needs: [changelog]\n        steps:\n            - run: echo \"Changelog workflow completed successfully\"\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n    push:\n        branches: [ main ]\n    pull_request:\n        branches: [ main ]\n    workflow_dispatch:\n\njobs:\n    check-secrets:\n        name: \"Check availability of GitHub secrets\"\n        runs-on: ubuntu-latest\n        outputs:\n            has-secrets: ${{ steps.secret-check.outputs.available }}\n        steps:\n          - name: Check whether GitHub secrets are available\n            id: secret-check\n            shell: bash\n            run: |\n                if [ '${{ secrets.MSK_LICENSE }}' != '' ]; then\n                    echo \"available=true\" >> ${GITHUB_OUTPUT};\n                else\n                    echo \"available=false\" >> ${GITHUB_OUTPUT};\n                fi\n\n    coverage:\n        name: \"Coverage\"\n        runs-on: ubuntu-latest\n        needs: [check-secrets]\n        if: needs.check-secrets.outputs.has-secrets == 'true'\n\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Pixi\"\n              uses: prefix-dev/setup-pixi@v0.8.8\n              with:\n                  pixi-version: v0.59.0\n                  cache: true\n\n            - name: \"Prepare license files\"\n              env:\n                  MSK_LICENSE: ${{ secrets.MSK_LICENSE }}\n              run: |\n                  echo \"${MSK_LICENSE}\" > ${{ github.workspace }}/mosek.lic\n\n            - name: \"Check code coverage\"\n              env:\n                  MOSEKLM_LICENSE_FILE: ${{ github.workspace }}/mosek.lic\n              run: |\n                  pixi run coverage\n\n            - name: \"Upload coverage results\"\n              env:\n                  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n              run: |\n                  pixi run coveralls\n\n    licensed:\n        name: \"Test licensed solvers on ${{ matrix.os }}\"\n        runs-on: ${{ matrix.os }}\n        needs: [check-secrets]\n        if: needs.check-secrets.outputs.has-secrets == 'true'\n\n        strategy:\n            matrix:\n                os: [ubuntu-latest, macos-latest]\n\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Pixi\"\n              uses: prefix-dev/setup-pixi@v0.8.8\n              with:\n                  pixi-version: v0.59.0\n                  environments: licensed\n                  cache: true\n\n            - name: \"Prepare license files\"\n              env:\n                  MSK_LICENSE: ${{ secrets.MSK_LICENSE }}\n              run: |\n                  echo \"${MSK_LICENSE}\" > ${{ github.workspace }}/mosek.lic\n\n            - name: \"Test licensed solvers\"\n              env:\n                  MOSEKLM_LICENSE_FILE: ${{ github.workspace }}/mosek.lic\n              run: |\n                  pixi run -e licensed licensed\n\n    lint:\n        name: \"Code style\"\n        runs-on: ubuntu-latest\n\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Pixi\"\n              uses: prefix-dev/setup-pixi@v0.8.8\n              with:\n                  pixi-version: v0.59.0\n                  environments: lint\n                  cache: true\n\n            - name: \"Lint qpsolvers\"\n              run: |\n                  pixi run lint\n\n    test:\n        name: \"Test ${{ matrix.os }} with ${{ matrix.pyenv }}\"\n        runs-on: ${{ matrix.os }}\n\n        strategy:\n            matrix:\n                os: [ubuntu-latest, macos-latest, windows-latest]\n                pyenv: [py310, py311, py312, py313]\n\n        env:\n            PIXI_ENV: test-${{ matrix.pyenv }}\n\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Pixi\"\n              uses: prefix-dev/setup-pixi@v0.8.8\n              with:\n                  pixi-version: v0.59.0\n                  environments: ${{ env.PIXI_ENV }}\n                  cache: true\n\n            - name: \"Run unit tests\"\n              run: |\n                  pixi run --environment ${{ env.PIXI_ENV }} test\n\n    ci_success:\n        name: \"CI success\"\n        runs-on: ubuntu-latest\n        needs: [coverage, licensed, lint, test]\n        steps:\n            - run: echo \"CI workflow completed successfully\"\n"
  },
  {
    "path": ".github/workflows/docs.yml",
    "content": "name: Documentation\n\non:\n    push:\n        branches: [ main ]\n    pull_request:\n        branches: [ main ]\n\njobs:\n    docs:\n        name: \"GitHub Pages\"\n        runs-on: ubuntu-latest\n        permissions:\n            contents: write\n        steps:\n            - name: \"Checkout Git repository\"\n              uses: actions/checkout@v4\n\n            - name: \"Setup Pixi\"\n              uses: prefix-dev/setup-pixi@v0.8.8\n              with:\n                  pixi-version: v0.59.0\n                  environments: docs\n                  cache: true\n\n            - name: \"Checkout qpSWIFT\"\n              uses: actions/checkout@v4\n              with:\n                  repository: qpSWIFT/qpSWIFT\n                  path: qpSWIFT\n\n            - name: \"Install qpSWIFT\"\n              run: |\n                  cd qpSWIFT/python\n                  pixi run -e docs python setup.py install\n\n            - name: \"Build documentation\"\n              run: |\n                  pixi run -e docs docs-build\n\n            - name: \"Deploy to GitHub Pages\"\n              uses: peaceiris/actions-gh-pages@v3\n              if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }}\n              with:\n                  publish_branch: gh-pages\n                  github_token: ${{ secrets.GITHUB_TOKEN }}\n                  publish_dir: _build/\n                  force_orphan: true\n"
  },
  {
    "path": ".github/workflows/pypi.yml",
    "content": "name: PyPI\n\non:\n    push:\n        branches: [ main ]\n    pull_request:\n        branches: [ main ]\n\njobs:\n    pypi:\n        name: \"Install from PyPI\"\n        runs-on: ubuntu-latest\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Install dependencies\"\n              run: |\n                  python -m pip install --upgrade pip\n\n            - name: \"Install package\"\n              run: python -m pip install qpsolvers\n\n            - name: \"Install at least one solver\"\n              run: python -m pip install quadprog\n\n            - name: \"Test module import\"\n              run: python -c \"import qpsolvers\"\n\n    testpypi:\n        name: \"Install from TestPyPI\"\n        runs-on: ubuntu-latest\n        steps:\n            - name: \"Checkout sources\"\n              uses: actions/checkout@v4\n\n            - name: \"Install dependencies\"\n              run: |\n                  python -m pip install --upgrade pip\n\n            - name: \"Install package\"\n              run: python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple/ qpsolvers\n\n            - name: \"Install at least one solver\"\n              run: python -m pip install quadprog\n\n            - name: \"Test module import\"\n              run: python -c \"import qpsolvers\"\n\n    pypi_success:\n        name: \"PyPI success\"\n        runs-on: ubuntu-latest\n        needs: [pypi, testpypi]\n        steps:\n            - run: echo \"PyPI workflow completed successfully\"\n"
  },
  {
    "path": ".gitignore",
    "content": "*.pyc\n*.pyo\n.coverage\n.ropeproject\n.tox\nMANIFEST\n_build/\nbuild/\ndist/\nexamples/qpsolvers\ngurobi.log\nhtmlcov/\nqpsolvers.egg-info\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Changelog\n\nAll notable changes to this project will be documented in this file.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- Add build and solve timing info (thanks to @ahoarau)\n- DAQP: Warm starts and time limit are now enabled in DAQP (thanks to @darnstrom)\n- New solver: qpmad (thanks to @ahoarau)\n- Support Python 3.13\n\n### Fixed\n\n- HiGHS: update interface following code changes in HiGHS v1.14.0\n\n## [4.11.0] - 2026-03-16\n\n### Added\n\n- New solver: PDHCG (thanks to @Lhongpei)\n\n### Changed\n\n- Change params in copt test to avoid unexpected timeout fail (thanks to @Salancelot)\n- Merge unsupported solvers submodule within the main solvers submodule\n- Remove unintended copied copyright information in copt_.py (thanks to @Salancelot)\n\n### Fixed\n\n- CICD: Clean up OS-specific features and reduce environments (thanks to @ahoarau)\n\n## [4.10.0] - 2026-03-10\n\n### Added\n\n- New solver: [COPT](https://guide.coap.online/copt/en-doc/index.html) (thanks to @Salancelot)\n\n### Changed\n\n- Gurobi: update to newer Gurobi API\n\n### Fixed\n\n- CICD: Add Gurobi to pixi PyPI dependencies\n\n## [4.9.0] - 2026-03-04\n\n### Added\n\n- Add `unpack_as_dense` function to problems\n- Add type annotations to internal `__solve_sparse_ls` function\n- New solver: [QTQP](https://github.com/google-deepmind/qtqp)\n\n### Changed\n\n- Bump minimum Python version to 3.10\n- CICD: Configure Git attributes for the pixi.lock file\n- CICD: Switch from tox to pixi\n- KVXOPT: Update type annotations\n- SIP: Ensure returned primal and dual vectors are NumPy arrays\n- SIP: Remove unused CVXOPT-related code from solver interface\n\n### Fixed\n\n- Correct type annotation of SOCP conversion function\n- Correct type annotation of solver function prototypes\n- ECOS: Rename internal intermediate variables to improve type checking\n- ECOS: Revise type annotations in solver interface\n- Fix sparse type annotations in `concatenate_bound`\n- Fix type annotation corner case in `linear_from_box_inequalities`\n- Fix type annotations in internal `__solve_dense_ls` function\n- Fix variable name re-use errors raised by newer versions of mypy\n- SIP: Forward allow-non-PSD argument in `solve_qp` variant of the interface\n- Unpack problems as NumPy arrays for dense solver APIs\n\n### Removed\n\n- Remove support for Python 3.9\n- CICD: Remove Python 3.9\n\n## [4.8.2] - 2025-11-25\n\n### Added\n\n- docs: Document warm-starting for all solver interfaces\n\n### Changed\n\n- Prevent deprecation warnings from OSQP related to solver status (thanks to @jkeust)\n- Prevent warning message from OSQP about conversion to a CSC matrix (thanks to @jkeust)\n- Slightly more explicit error message when a solver is not found\n\n## [4.8.1] - 2025-08-07\n\n### Changed\n\n- Clarabel: Warn when problem is unconstrained and solved by `lsqr` (thanks to @proyan)\n\n## [4.8.0] - 2025-07-02\n\n### Added\n\n- PIQP: Add support for new API in v0.6.0 (thanks to @RSchwan)\n- PIQP: Add new solver options to the documentation (thanks to @RSchwan)\n\n### Fixed\n\n- CICD: Enable macOS tests for KVXOPT (thanks to @sanurielf)\n\n## [4.7.1] - 2025-06-03\n\n### Added\n\n- `py.typed` file to indicate tools like `mypy` to use type annotations (thanks to @ValerianRey)\n\n## [4.7.0] - 2025-05-13\n\n### Added\n\n- New solver: [SIP](https://github.com/joaospinto/sip_python) (thanks to @joaospinto)\n- warnings: Add `SparseConversionWarning` to filter the corresponding warning\n- warnings: Base class `QPWarning` for all qpsolvers-related warnings\n- warnings: Recall solver name when issuing conversion warnings\n\n### Changed\n\n- Add solver name argument to internal `ensure_sparse_matrices` function\n- CICD: Update Python version to 3.10 in coverage job\n\n### Fixed\n\n- docs: Add jaxopt.OSQP to the list of supported solvers\n\n### Removed\n\n- OSQP: Remove pre-1.0 version pin\n- OSQP: Update interface after relase of v1.0.4\n- Warning that was issued every time an unsupported solver is available\n\n## [4.6.0] - 2025-04-17\n\n### Added\n\n- New solver: [KVXOPT](https://github.com/sanurielf/kvxopt/) (thanks to @agroudiev)\n- jaxopt.OSQP: Support JAX array inputs when jaxopt.OSQP is the selected solver\n\n## [4.5.1] - 2025-04-10\n\n### Changed\n\n- CICD: Update checkout action to v4\n\n### Fixed\n\n- OSQP: Temporary fix in returning primal-dual infeasibility certificates\n\n## [4.5.0] - 2025-03-04\n\n### Added\n\n- HPIPM: Document new `tol_dual_gap` parameter\n- New solver: [jaxopt.OSQP](https://jaxopt.github.io/stable/_autosummary/jaxopt.OSQP.html)\n- Support Python 3.12\n\n### Changed\n\n- Bump minimum Python version to 3.8\n- CICD: Remove Python 3.8 from continuous integration\n- Fix output datatypes when splitting linear-box dual multipliers\n- OSQP: version-pin to < 1.0.0 pending an interface update\n- Warn when solving unconstrained problem by SciPy's LSQR rather than QP solver\n\n### Fixed\n\n- Fix mypy error in `Solution.primal_residual`\n\n## [4.4.0] - 2024-09-24\n\n### Added\n\n- HPIPM: Link to reference paper for details on solver modes\n- New solver: [qpax](https://github.com/kevin-tracy/qpax) (thanks to @lvjonok)\n\n## [4.3.3] - 2024-08-06\n\n### Changed\n\n- CICD: Remove Gurobi from macOS continuous integration\n- CICD: Remove Python 3.7 from continuous integration\n- CICD: Update ruff to 0.4.3\n\n### Fixed\n\n- CICD: Fix coverage and licensed-solver workflows\n- CICD: Install missing dependency in licensed solver test environment\n- Clarabel: Catch pyO3 panics that can happen when building a problem\n- Default arguments to active set dataclass to `None` rather than empty list\n- PIQP: Warning message about CSC matrix conversions (thanks to @itsahmedkhalil)\n- Update all instances of `np.infty` to `np.inf`\n\n## [4.3.2] - 2024-03-25\n\n### Added\n\n- Optional dependency: `wheels_only` for solvers with pre-compiled binaries\n\n### Changed\n\n- Update developer notes in the documentation\n- Update some solver tolerances in unit tests\n- Warn rather than raise when there is no solver detected\n\n### Fixed\n\n- CICD: Update micromamba setup action\n\n## [4.3.1] - 2024-02-06\n\n### Fixed\n\n- Gurobi: sign of inequality multipliers (thanks to @563925743)\n\n## [4.3.0] - 2024-01-23\n\n### Added\n\n- Extend continuous integration to Python 3.11\n- Function to get the CUTE classification string of the problem\n- Optional dependencies for all solvers in the list available on PyPI\n\n### Changed\n\n- **Breaking:** no default QP solver installed along with the library\n- NPPro: update exit flag value to match new solver API (thanks to @ottapav)\n\n### Fixed\n\n- Documentation: Add Clarabel to the list of supported solvers (thanks to @ogencoglu)\n- Documentation: Correct note in `solve_ls` documentation (thanks to @ogencoglu)\n- Documentation: Correct output of LS example (thanks to @ogencoglu)\n\n## [4.2.0] - 2023-12-21\n\n### Added\n\n- Example: [lasso regularization](https://scaron.info/blog/lasso-regularization-in-quadratic-programming.html)\n- `Problem.load` function\n- `Problem.save` function\n\n## [4.1.1] - 2023-12-05\n\n### Changed\n\n- Mark QPALM as a sparse solver only\n\n## [4.1.0] - 2023-12-04\n\n### Added\n\n- New solver: [QPALM](https://kul-optec.github.io/QPALM/Doxygen/)\n- Unit test for internal linear-box inequality combination\n\n### Changed\n\n- Internal: refactor linear-box inequality combination function\n- Renamed main branch of the repository from `master` to `main`\n\n### Fixed\n\n- Fix combination of box inequalities with empty linear inequalities\n- Gurobi: Account for a slight regression in QPSUT01 performance\n\n## [4.0.1] - 2023-11-01\n\n### Added\n\n- Allow installation of a subset of QP solvers from PyPI\n\n## [4.0.0] - 2023-08-30\n\n### Added\n\n- New solver: [PIQP](https://github.com/PREDICT-EPFL/piqp) (thanks to @shaoanlu)\n- Type for active set of equality and inequality constraints\n\n### Changed\n\n- **Breaking:** condition number requires an active set (thanks to @aescande)\n\n## [3.5.0] - 2023-08-16\n\n### Added\n\n- New solver: [HPIPM](https://github.com/giaf/hpipm) (thanks to @adamheins)\n\n### Changed\n\n- MOSEK: Disable CI test on QPSUT03 due to regression with 10.1.8\n- MOSEK: Relax test tolerances as latest version is less accurate with defaults\n\n## [3.4.0] - 2023-04-28\n\n### Changed\n\n- Converted THANKS file to [CFF](https://citation-file-format.github.io/)\n- ECOS: raise a ProblemError if inequality vectors contain infinite values\n- ECOS: raise a ProblemError if the cost matrix is not positive definite\n- MOSEK is now a supported solver (thanks to @uricohen and @aszekMosek)\n\n### Fixed\n\n- Residual and duality gap computations when solution is not found\n- Update OSQP version to 0.6.2.post9 for testing\n\n## [3.3.1] - 2023-04-12\n\n### Fixed\n\n- DAQP: Update to 0.5.1 to fix installation of arm64 wheels\n\n## [3.3.0] - 2023-04-11\n\n### Added\n\n- New sample problems in `qpsolvers.problems`\n- New solver: [DAQP](https://darnstrom.github.io/daqp/) (thanks to @darnstrom)\n\n### Changed\n\n- Dual multipliers are empty arrays rather than None when no constraint\n- Store solver results even when solution is not found\n- Switch to `Solution.found` as solver success status (thanks to @rxian)\n\n### Fixed\n\n- Unit test on actual solution to QPSUT03 problem\n\n## [3.2.0] - 2023-03-29\n\n### Added\n\n- Sparse strategy to convert LS problems to QP (thanks to @bodono)\n- Start `problems` submodule to collect sample test problems\n\n### Fixed\n\n- Clarabel: upstream handling of infinite values in inequalities\n- CVXOPT: option passing\n\n## [3.1.0] - 2023-03-07\n\n### Added\n\n- New solver: NPPro\n\n### Changed\n\n- Documentation: separate support and unsupported solver lists\n- Exclude unsupported solvers from code coverage report\n- Move unsupported solvers to a separate submodule\n- Remove CVXOPT from dependencies as it doesn't have arm64 wheels\n- Remove quadprog from dependencies as it doesn't have arm64 wheels\n\n## [3.0.0] - 2023-02-28\n\n### Added\n\n- Exception `ParamError` for incorrect solver parameters\n- Exception `SolverError` for solver failures\n\n### Changed\n\n- All functions throw only qpsolvers-owned exceptions\n- CVXOPT: rethrow `ValueError` as either `ProblemError` or `SolverError`\n- Checking `Solution.is_empty` becomes `not Solution.found`\n- Install open source solvers with wheels by default\n- Remove `solve_safer_qp`\n- Remove `sym_proj` parameter\n\n## [2.8.1] - 2023-02-28\n\n### Changed\n\n- Expose `solve_unconstrained` function from main module\n\n### Fixed\n\n- Clarabel: handle unconstrained problems\n- README: correct and improve FAQ on non-convex problems (thanks to @nrontsis)\n\n## [2.8.0] - 2023-02-27\n\n### Added\n\n- New solver: [Clarabel](https://github.com/oxfordcontrol/Clarabel.rs)\n\n### Changed\n\n- Move documentation to [GitHub Pages](https://qpsolvers.github.io/qpsolvers/)\n- Remove Python 2 installation instructions\n\n## [2.7.4] - 2023-01-31\n\n### Fixed\n\n- Check vector shapes in problem constructor\n\n## [2.7.3] - 2023-01-16\n\n### Added\n\n- qpOASES: return number of WSR in solution extra info\n\n### Fixed\n\n- CVXOPT: fix domain errors when some bounds are infinite\n- qpOASES: fix missing lower bound when there is no equality constraint\n- qpOASES: handle infinite bounds\n- qpOASES: segmentation fault with conda feedstock\n\n## [2.7.2] - 2023-01-02\n\n### Added\n\n- ECOS: handle two more exit flags\n- Exception `ProblemError` for problem formulation errors\n- Exception `QPError` as a base class for exceptions\n- Property to check if a Problem has sparse matrices\n- qpOASES: raise a ProblemError when matrices are not dense\n- qpSWIFT: raise a ProblemError when matrices are not dense\n- quadprog: raise a ProblemError when matrices are not dense\n\n### Changed\n\n- Add `use_sparse` argument to internal linear-from-box conversion\n- Restrict condition number calculation to dense problems for now\n\n## [2.7.1] - 2022-12-23\n\n### Added\n\n- Document problem conversion functions in developer notes\n- ECOS: handle more exit flags\n\n### Changed\n\n- quadprog: use internal `split_dual_linear_box` conversion function\n\n### Fixed\n\n- SCS: require at least version 3.2\n- Solution: duality gap computation under infinite box bounds\n\n## [2.7.0] - 2022-12-15\n\n### Added\n\n- Continuous integration for macOS\n- CVXOPT: return dual multipliers\n- ECOS: return dual multipliers\n- Example: dual multipliers\n- Gurobi: return dual multipliers\n- HiGHS: return dual multipliers\n- MOSEK: return dual multipliers\n- OSQP: return dual multipliers\n- Problem class with utility metrics on quadratic programs\n- Problem: condition number\n- ProxQP: return dual multipliers\n- qpOASES: return dual multipliers\n- qpOASES: return objective value\n- qpSWIFT: return dual multipliers\n- qpSWIFT: return objective value\n- quadprog: return dual multipliers\n- SCS: return dual multipliers\n\n### Changed\n\n- Code: move `solve_safer_qp` to a separate source file\n- Code: refactor location of internal conversions submodule\n- ProxQP: bump minimum supported version to 0.2.9\n\n### Fixed\n\n- qpOASES: eliminate redundant equality constraints\n\n## [2.6.0] - 2022-11-14\n\n### Added\n\n- Example: constrained linear regression\n- Example: sparse linear least squares\n- Gurobi: forward keyword arguments as solver parameters\n- Handle diagonal matrices when combining linear and box inequalities\n- qpOASES: pre-defined options parameter\n- qpOASES: time limit parameter\n\n### Changed\n\n- CVXOPT: forward all keyword arguments as solver options\n- Deprecate `solve_safer_qp` and warn about future removal\n- Example: disable verbose output in least squares example\n- HiGHS: forward all keyword arguments as solver options\n- OSQP: drop support for versions <= 0.5.0\n- OSQP: streamline stacking of box inequalities\n- ProxQP: also consider constraint matrices to select backend\n- qpOASES: forward all keyword arguments as solver options\n- qpOASES: forward box inequalities directly\n- Remove CVXPY which is not a solver\n- SCS: `SOLVED_INACCURATE` is now considered a failure\n\n### Fixed\n\n- Dot product bug in `solve_ls` with sparse matrices\n- MOSEK: restore CVXOPT options after calling MOSEK\n- ProxQP: fix box inequality shapes when combining bounds\n- qpOASES: non-persistent solver options between calls\n- qpOASES: return failure on `RET_INIT_FAILED*` return codes\n\n## [2.5.0] - 2022-11-04\n\n### Added\n\n- CVXOPT: absolute tolerance parameter\n- CVXOPT: feasibility tolerance parameter\n- CVXOPT: limit maximum number of iterations\n- CVXOPT: refinement parameter\n- CVXOPT: relative tolerance parameter\n- Documentation: reference solver papers\n- ECOS: document additional parameters\n- Gurobi: time limit parameter\n- HiGHS: dual feasibility tolerance parameter\n- HiGHS: primal feasibility tolerance parameter\n- HiGHS: time limit parameter\n\n### Changed\n\n- CVXOPT matrices are not valid types for qpsolvers any more\n- CVXOPT: improve documentation\n- CVXOPT: solver is now listed as sparse as well\n- ECOS: type annotations allow sparse input matrices\n- OSQP: don't override default solver tolerances\n- Remove internal CVXOPT-specific type annotation\n- Restrict matrix types to NumPy arrays and SciPy CSC matrices\n- SCS: don't override default solver tolerances\n- Simplify intermediate internal type annotations\n\n### Fixed\n\n- CVXOPT: pass warm-start primal properly\n- ECOS: forward keyword arguments\n- OSQP: dense arrays for vectors in type annotations\n- SCS: fix handling of problems with only box inequalities\n\n## [2.4.1] - 2022-10-21\n\n### Changed\n\n- Update ProxQP to version 0.2.2\n\n## [2.4.0] - 2022-09-29\n\n### Added\n\n- New solver: [HiGHS](https://github.com/ERGO-Code/HiGHS)\n- Raise error when there is no available solver\n\n### Changed\n\n- Make sure plot is shown in MPC example\n- Print expected solutions in QP, LS and box-inequality examples\n- Renamed starter solvers optional deps to `open_source_solvers`\n\n### Fixed\n\n- Correct documentation of `R` argument to `solve_ls`\n\n## [2.3.0] - 2022-09-06\n\n### Added\n\n- New solver: [ProxQP](https://github.com/Simple-Robotics/proxsuite)\n\n### Changed\n\n- Clean up unused dependencies in GitHub workflow\n- Non-default solver parameters in unit tests to test their precision\n\n### Fixed\n\n- Configuration of `tox-gh-actions` for Python 3.7\n- Enforce `USING_COVERAGE` in GitHub workflow configuration\n- Remove redundant solver loop from `test_all_shapes`\n\n## [2.2.0] - 2022-08-15\n\n### Added\n\n- Add `lb` and `ub` arguments to all `<solver>_solve_qp` functions\n- Internal `conversions` submodule\n\n### Changed\n\n- Moved `concatenate_bounds` to internal `conversions` submodule\n- Moved `convert_to_socp` to internal `conversions` submodule\n- Renamed `concatenate_bounds` to `linear_from_box_inequalities`\n- Renamed internal `convert_to_socp` function to `socp_from_qp`\n\n## [2.1.0] - 2022-07-25\n\n### Added\n\n- Document how to add a new QP solver to the library\n- Example with (box) lower and upper bounds\n- Test case where `lb` XOR `ub` is set\n\n### Changed\n\n- SCS: use the box cone API when lower/upper bounds are set\n\n## [2.0.0] - 2022-07-05\n\n### Added\n\n- Exception `NoSolverSelected` raised when the solver kwarg is missing\n- Starter set of QP solvers as optional dependencies\n- Test exceptions raised by `solve_ls` and `solve_qp`\n\n### Changed\n\n- **Breaking:** `solver` keyword argument is now mandatory for `solve_ls`\n- **Breaking:** `solver` keyword argument is now mandatory for `solve_qp`\n- Quadratic programming example now randomly selects an available solver\n\n## [1.10.0] - 2022-06-25\n\n### Changed\n\n- qpSWIFT: forward solver options as keywords arguments as with other solvers\n\n## [1.9.1] - 2022-05-02\n\n### Fixed\n\n- OSQP: pass extra keyword arguments properly (thanks to @urob)\n\n## [1.9.0] - 2022-04-03\n\n### Added\n\n- Benchmark on model predictive control problem\n- Model predictive control example\n- qpSWIFT 0.0.2 solver interface\n\n### Changed\n\n- Compute colors automatically in benchmark example\n\n### Fixed\n\n- Bounds concatenation for CVXOPT sparse matrices\n\n## [1.8.1] - 2022-03-05\n\n### Added\n\n- Setup instructions for Microsoft Visual Studio\n- Unit tests where the problem is unbounded below\n\n### Changed\n\n- Minimum supported Python version is now 3.7\n\n### Fixed\n\n- Clear all Pylint warnings\n- Disable Pylint false positives that are covered by mypy\n- ECOS: raise a ValueError when the cost matrix is not positive definite\n\n## [1.8.0] - 2022-01-13\n\n### Added\n\n- Build and test for Python 3.10 in GitHub Actions\n\n### Changed\n\n- Moved SCS to sparse solvers\n- Re-run solver benchmark reported to the README\n- Removed `requirements2.txt` and update Python 2 installation instructions\n- Updated SCS to new 3.0 version\n\n### Fixed\n\n- Handle sparse matrices in `print_matrix_vector`\n- Match `__all__` in model and top-level `__init__.py`\n- Run unit tests in GitHub Actions\n- Typing error in bound concatenation\n\n## [1.7.2] - 2021-11-24\n\n### Added\n\n- Convenience function to prettyprint a matrix and vector side by side\n\n### Changed\n\n- Move old tests from the examples folder to the unit test suite\n- Removed deprecated `requirements.txt` installation file\n- Renamed `solvers` optional dependencies to `all_pypi_solvers`\n\n## [1.7.1] - 2021-10-02\n\n### Fixed\n\n- Make CVXOPT optional again (thanks to @adamoppenheimer)\n\n## [1.7.0] - 2021-09-19\n\n### Added\n\n- Example script corresponding exactly to the README\n- Handle lower and upper bounds with sparse matrices (thanks to @MeindertHH)\n- SCS 2.0 solver interface\n- Type annotations to all solve functions\n- Unit tests: package coverage is now 94%\n\n### Changed\n\n- ECOS: simplify sparse matrix conversions\n- Ignore warnings when running unit tests\n- Inequality tolerance is now 1e-10 when validating solvers on README example\n- Refactor QP to SOCP conversion to use more than one SOCP solver\n- Rename \"example problem\" for testing to \"README problem\" (less ambiguous)\n- Rename `sw` parameter of `solve_safer_qp` to `sr` for \"slack repulsion\"\n- Reorganize code with a qpsolvers/solvers submodule\n- quadprog: warning when `initvals is not None` is now verbose\n\n### Fixed\n\n- OSQP: forward keyword arguments to solver properly\n- quadprog: forward keyword arguments to solver properly\n\n## [1.6.1] - 2021-04-09\n\n### Fixed\n\n- Add quadprog dependency properly in `pyproject.toml`\n\n## [1.6.0] - 2021-04-09\n\n### Added\n\n- Add `__version__` to main module\n- First unit tests to check all solvers over a pre-defined set of problems\n- GitHub Actions now make sure the project is built and tested upon updates\n- Type hints now decorate all function definitions\n\n### Changed\n\n- Code formatting now applies [Black](https://github.com/psf/black)\n- ECOS: refactor SOCP conversion to improve function readability\n- Gurobi: performance significantly improved by new matrix API (thanks to @DKenefake)\n\n### Fixed\n\n- CVXPY: properly return `None` on unfeasible problems\n- Consistently warn when `initvals` is passed but ignored by solver interface\n- ECOS: properly return `None` on unfeasible problems\n- Fix `None` case in `solve_safer_qp` (found by static type checking)\n- Fix warnings in repository-level `__init__.py`\n- OSQP: properly return `None` on unfeasible problems\n- Pass Flake8 validation for overall code style\n- Reduce complexity of entry `solve_qp` via a module-level solve-function index\n- Remove Python 2 compatibility line from examples\n- quadprog: properly return `None` on unfeasible problems (thanks to @DKenefake)\n\n## [1.5.0] - 2020-12-05\n\n### Added\n\n- Upgrade to Python 3 and deprecate Python 2\n- Saved Python 2 package versions to `requirements2.txt`\n\n### Fixed\n\n- Deprecation warning in CVXPY\n\n## [1.4.1] - 2020-11-29\n\n### Added\n\n- New `solve_ls` function to solve linear Least Squares problems\n\n### Fixed\n\n- Call to `print` in PyPI description\n- Handling of quadprog ValueError exceptions\n\n## [1.4.0] - 2020-07-04\n\n### Added\n\n- Solver settings can now by passed to `solve_qp` as keyword arguments\n- Started an [API documentation](https://scaron.info/doc/qpsolvers/)\n\n### Changed\n\n- Made `verbose` an explicit keyword argument of all internal functions\n- OSQP settings now match precision of other solvers (thanks to @Neotriple)\n\n## [1.3.1] - 2020-06-13\n\n### Fixed\n\n- Equation of quadratic program on [PyPI page](https://pypi.org/project/qpsolvers/)\n\n## [1.3.0] - 2020-05-16\n\n### Added\n\n- Lower and upper bound keyword arguments `lb` and `ub`\n\n### Fixed\n\n- Check that equality/inequality matrices/vectors are provided consistently\n- Relaxed offset check in [test\\_solvers.py](examples/test_solvers.py)\n\n## [1.2.1] - 2020-05-16\n\n### Added\n\n- CVXPY: verbose keyword argument\n- ECOS: verbose keyword argument\n- Gurobi: verbose keyword argument\n- OSQP: verbose keyword argument\n\n### Fixed\n\n- Ignore verbosity argument when solver is not available\n\n## [1.2.0] - 2020-05-16\n\n### Added\n\n- cvxopt: verbose keyword argument\n- mosek: verbose keyword argument\n- qpoases: verbose keyword argument\n\n## [1.1.2] - 2020-05-15\n\n### Fixed\n\n- osqp: handle both old and more recent versions\n\n## [1.1.1] - 2020-05-15\n\n### Fixed\n\n- Avoid variable name clash in OSQP\n- Handle quadprog exception to avoid confusion on cost matrix notation\n\n## [1.1.0] - 2020-03-07\n\n### Added\n\n- ECOS solver interface (no need to go through CVXPY any more)\n- Update ECOS performance in benchmark (much better than before!)\n\n### Fixed\n\n- Fix link to ECOS in setup.py\n- Remove ned for IPython in solver test\n- Update notes on P matrix\n\n## [1.0.7] - 2019-10-26\n\n### Changed\n\n- Always reshape A or G vectors into one-line matrices\n\n### Fixed\n\n- cvxopt: handle case where G and h are None but not A and b\n- osqp: handle case where G and h are None\n- osqp: handle case where both G and A are one-line matrices\n- qpoases: handle case where G and h are None but not A and b\n\n## [1.0.6] - 2019-10-26\n\nThanks to Brian Delhaisse and Soeren Wolfers who contributed fixes to this\nrelease!\n\n### Fixed\n\n- quadprog: handle case where G and h are None\n- quadprog: handle cas where A.ndim == 1\n- Make examples compatible with both Python 2 and Python 3\n\n## [1.0.5] - 2019-04-10\n\n### Added\n\n- Equality constraint shown in the README example\n- Installation file `requirements.txt`\n- Installation instructions for qpOASES\n- OSQP: automatic CSC matrix conversions (with performance warnings)\n- This change log\n\n### Fixed\n\n- CVXOPT: case where A is one-dimensional\n- qpOASES: case where both G and A are not None\n- quadprog: wrapper for one-dimensional A matrix (thanks to @nvitucci)\n\n### Changed\n\n- CVXOPT version is now 1.1.8 due to [this issue](https://github.com/urinieto/msaf-gpl/issues/2)\n- Examples now in a [separate folder](examples)\n\n## [1.0.4] - 2018-07-05\n\n### Added\n\n- A changelog :)\n\n[unreleased]: https://github.com/qpsolvers/qpsolvers/compare/v4.11.0...HEAD\n[4.11.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.11.0\n[4.10.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.10.0\n[4.9.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.9.0\n[4.8.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.2\n[4.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.1\n[4.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.8.0\n[4.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.7.1\n[4.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.7.0\n[4.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.6.0\n[4.5.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.5.1\n[4.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.5.0\n[4.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.4.0\n[4.3.3]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.3\n[4.3.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.2\n[4.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.1\n[4.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.3.0\n[4.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.2.0\n[4.1.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.1.1\n[4.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.1.0\n[4.0.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.0.1\n[4.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v4.0.0\n[3.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.5.0\n[3.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.4.0\n[3.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.3.1\n[3.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.3.0\n[3.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.2.0\n[3.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.1.0\n[3.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v3.0.0\n[2.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.8.1\n[2.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.8.0\n[2.7.3]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.3\n[2.7.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.2\n[2.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.1\n[2.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.7.0\n[2.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.6.0\n[2.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.5.0\n[2.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.4.0\n[2.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.3.0\n[2.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.2.0\n[2.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.1.0\n[2.0.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v2.0.0\n[1.10.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.10.0\n[1.9.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.9.1\n[1.9.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.9.0\n[1.8.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.8.1\n[1.8.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.8.0\n[1.7.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.2\n[1.7.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.1\n[1.7.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.7.0\n[1.6.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.6.1\n[1.6.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.6.0\n[1.5.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.5.0\n[1.4.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.4.1\n[1.4.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.4.0\n[1.3.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.3.1\n[1.3.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.3.0\n[1.2.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.2.1\n[1.2.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.2.0\n[1.1.2]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.2\n[1.1.1]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.1\n[1.1.0]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.1.0\n[1.0.7]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.7\n[1.0.6]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.6\n[1.0.5]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.5\n[1.0.4]: https://github.com/qpsolvers/qpsolvers/releases/tag/v1.0.4\n"
  },
  {
    "path": "CITATION.cff",
    "content": "cff-version: 1.2.0\nmessage: \"If you find this code helpful, please cite it as below.\"\ntitle: \"qpsolvers: Quadratic Programming Solvers in Python\"\nversion: 4.11.0\ndate-released: 2026-03-10\nurl: \"https://github.com/qpsolvers/qpsolvers\"\nlicense: \"LGPL-3.0\"\nauthors:\n- family-names: \"Caron\"\n  given-names: \"Stéphane\"\n  orcid: \"https://orcid.org/0000-0003-2906-692X\"\n- family-names: \"Arnström\"\n  given-names: \"Daniel\"\n- family-names: \"Bonagiri\"\n  given-names: \"Suraj\"\n- family-names: \"Dechaume\"\n  given-names: \"Antoine\"\n- family-names: \"Flowers\"\n  given-names: \"Nikolai\"\n- family-names: \"Heins\"\n  given-names: \"Adam\"\n- family-names: \"Ishikawa\"\n  given-names: \"Takuma\"\n- family-names: \"Kenefake\"\n  given-names: \"Dustin\"\n- family-names: \"Mazzamuto\"\n  given-names: \"Giacomo\"\n- family-names: \"Meoli\"\n  given-names: \"Donato\"\n- family-names: \"O'Donoghue\"\n  given-names: \"Brendan\"\n- family-names: \"Oppenheimer\"\n  given-names: \"Adam A.\"\n- family-names: \"Otta\"\n  given-names: \"Pavel\"\n- family-names: \"Pandala\"\n  given-names: \"Abhishek\"\n- family-names: \"Quiroz Omaña\"\n  given-names: \"Juan José\"\n- family-names: \"Rontsis\"\n  given-names: \"Nikitas\"\n- family-names: \"Shah\"\n  given-names: \"Paarth\"\n- family-names: \"St-Jean\"\n  given-names: \"Samuel\"\n- family-names: \"Vitucci\"\n  given-names: \"Nicola\"\n- family-names: \"Wolfers\"\n  given-names: \"Soeren\"\n- family-names: \"Yang\"\n  given-names: \"Fengyu\"\n- family-names: \"Delhaisse\"\n  given-names: \"Brian\"\n- family-names: \"MeindertHH\"\n- family-names: \"rimaddo\"\n- family-names: \"urob\"\n- family-names: \"shaoanlu\"\n- family-names: \"Sandoval\"\n  given-names: \"Uriel\"\n- family-names: \"Khalil\"\n  given-names: \"Ahmed\"\n- family-names: \"Kozlov\"\n  given-names: \"Lev\"\n- family-names: \"Groudiev\"\n  given-names: \"Antoine\"\n- family-names: \"Sousa Pinto\"\n  given-names: \"João\"\n  orcid: \"https://orcid.org/0000-0003-2469-2809\"\n- family-names: \"Rey\"\n  given-names: \"Valérian\"\n- family-names: \"Schwan\"\n  given-names: \"Roland\"\n- family-names: \"Budhiraja\"\n  given-names: \"Rohan\"\n- family-names: \"Keustermans\"\n  given-names: \"Johannes\"\n- family-names: \"Wu\"\n  given-names: \"Yubin\"\n- family-names: \"Li\"\n  given-names: \"Hongpei\"\n  orcid: \"https://orcid.org/0009-0000-3001-8883\"\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# 👷 Contributing\n\nThere are many ways you can contribute to qpsolvers. Here are some ideas:\n\n- 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)\n- Describe your use case in [Show and tell](https://github.com/qpsolvers/qpsolvers/discussions/categories/show-and-tell)\n- Suggest improvements to the library, see for instance [these contributions](https://github.com/qpsolvers/qpsolvers/pulls?q=is%3Apr+-author%3Astephane-caron+is%3Amerged)\n- Find code that is [not covered](https://coveralls.io/github/qpsolvers/qpsolvers?branch=main) by unit tests, and add a test for it\n- Address one of the [open issues](https://github.com/qpsolvers/qpsolvers/issues?q=is%3Aissue+is%3Aopen)\n\nWhen you contribute a PR to the library, make sure to add yourself to `CITATION.cff` and to the BibTeX citation in the readme.\n"
  },
  {
    "path": "LICENSE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "README.md",
    "content": "# Quadratic Programming Solvers in Python\n\n[![CI](https://img.shields.io/github/actions/workflow/status/qpsolvers/qpsolvers/ci.yml?branch=main)](https://github.com/qpsolvers/qpsolvers/actions)\n[![Documentation](https://img.shields.io/github/actions/workflow/status/qpsolvers/qpsolvers/docs.yml?branch=main&label=docs)](https://qpsolvers.github.io/qpsolvers/)\n[![Coverage](https://coveralls.io/repos/github/qpsolvers/qpsolvers/badge.svg?branch=main)](https://coveralls.io/github/qpsolvers/qpsolvers?branch=main)\n[![Conda version](https://img.shields.io/conda/vn/conda-forge/qpsolvers.svg?color=blue)](https://anaconda.org/conda-forge/qpsolvers)\n[![PyPI version](https://img.shields.io/pypi/v/qpsolvers?color=blue)](https://pypi.org/project/qpsolvers/)\n[![PyPI downloads](https://img.shields.io/pypi/dm/qpsolvers?color=blue)](https://pypistats.org/packages/qpsolvers)\n\nThis library provides a [`solve_qp`](https://qpsolvers.github.io/qpsolvers/quadratic-programming.html#qpsolvers.solve_qp) function to solve convex quadratic programs:\n\n$$\n\\begin{split}\n\\begin{array}{ll}\n\\underset{x}{\\mbox{minimize}}\n    & \\frac{1}{2} x^T P x + q^T x \\\\\n\\mbox{subject to}\n    & G x \\leq h \\\\\n    & A x = b \\\\\n    & lb \\leq x \\leq ub\n\\end{array}\n\\end{split}\n$$\n\nVector 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.\n\n**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.\n\n## Example\n\nTo solve a quadratic program, build the matrices that define it and call ``solve_qp``, selecting the backend QP solver via the ``solver`` keyword argument:\n\n```python\nimport numpy as np\nfrom qpsolvers import solve_qp\n\nM = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\nP = M.T @ M  # this is a positive definite matrix\nq = np.array([3.0, 2.0, 3.0]) @ M\nG = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\nh = np.array([3.0, 2.0, -2.0])\nA = np.array([1.0, 1.0, 1.0])\nb = np.array([1.0])\n\nx = solve_qp(P, q, G, h, A, b, solver=\"proxqp\")\nprint(f\"QP solution: {x = }\")\n```\n\nThis 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).\n\n## Installation\n\n### From conda-forge\n\n```console\nconda install -c conda-forge qpsolvers\n```\n\n### From PyPI\n\nTo install the library with open source QP solvers:\n\n```console\npip install qpsolvers[open_source_solvers]\n```\n\nThis 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:\n\n- ``pip install qpsolvers[wheels_only]`` will only install solvers with pre-compiled binaries,\n- ``pip install qpsolvers[clarabel,daqp,proxqp,scs]`` (for instance) will install the listed set of QP solvers,\n- ``pip install qpsolvers`` will only install the library itself.\n\nWhen imported, qpsolvers loads all the solvers it can find and lists them in ``qpsolvers.available_solvers``.\n\n## Solvers\n\n| Solver                                                                       | Keyword         | Algorithm | API | License |\n|------------------------------------------------------------------------------|-----------------| --------- | --- | ------- |\n| [Clarabel](https://github.com/oxfordcontrol/Clarabel.rs)                     | ``clarabel``    | Interior point | Sparse | Apache-2.0 |\n| [COPT](https://www.shanshu.ai/copt)                                          | ``copt``        | Interior point | Sparse | Commercial |\n| [CVXOPT](http://cvxopt.org/)                                                 | ``cvxopt``      | Interior point | Dense | GPL-3.0 |\n| [DAQP](https://github.com/darnstrom/daqp)                                    | ``daqp``        | Active set | Dense | MIT |\n| [ECOS](https://web.stanford.edu/~boyd/papers/ecos.html)                      | ``ecos``        | Interior point | Sparse | GPL-3.0 |\n| [Gurobi](https://www.gurobi.com/)                                            | ``gurobi``      | Interior point | Sparse | Commercial |\n| [HiGHS](https://highs.dev/)                                                  | ``highs``       | Active set | Sparse | MIT |\n| [HPIPM](https://github.com/giaf/hpipm)                                       | ``hpipm``       | Interior point | Dense | BSD-2-Clause |\n| [jaxopt.OSQP](https://jaxopt.github.io/stable/_autosummary/jaxopt.OSQP.html) | ``jaxopt_osqp`` | Augmented Lagrangian | Dense | Apache-2.0 |\n| [KVXOPT](https://github.com/sanurielf/kvxopt)                                | ``kvxopt``      | Interior point | Dense & Sparse | GPL-3.0 |\n| [MOSEK](https://mosek.com/)                                                  | ``mosek``       | Interior point | Sparse | Commercial |\n| NPPro                                                                        | ``nppro``       | Active set | Dense | Commercial |\n| [OSQP](https://osqp.org/)                                                    | ``osqp``        | Augmented Lagrangian | Sparse | Apache-2.0 |\n| [PDHCG](https://github.com/Lhongpei/PDHCG-II)                                | ``pdhcg``       | Primal-dual hybrid gradient | Dense & Sparse | Apache-2.0 |\n| [PIQP](https://github.com/PREDICT-EPFL/piqp)                                 | ``piqp``        | Proximal interior point | Dense & Sparse | BSD-2-Clause |\n| [ProxQP](https://github.com/Simple-Robotics/proxsuite)                       | ``proxqp``      | Augmented Lagrangian | Dense & Sparse | BSD-2-Clause |\n| [QPALM](https://github.com/kul-optec/QPALM)                                  | ``qpalm``       | Augmented Lagrangian | Sparse | LGPL-3.0 |\n| [qpmad](https://github.com/asherikov/qpmad)                                  | ``qpmad``       | Active set | Dense | Apache-2.0 |\n| [QTQP](https://github.com/google-deepmind/qtqp)                              | ``qtqp``        | Interior point | Sparse | Apache-2.0 |\n| [qpax](https://github.com/kevin-tracy/qpax/)                                 | ``qpax``        | Interior point | Dense | MIT |\n| [qpOASES](https://github.com/coin-or/qpOASES)                                | ``qpoases``     | Active set | Dense | LGPL-2.1 |\n| [qpSWIFT](https://github.com/qpSWIFT/qpSWIFT)                                | ``qpswift``     | Interior point | Sparse | GPL-3.0 |\n| [quadprog](https://github.com/quadprog/quadprog)                             | ``quadprog``    | Active set | Dense | GPL-2.0 |\n| [SCS](https://www.cvxgrp.org/scs/)                                           | ``scs``         | Augmented Lagrangian | Sparse | MIT |\n| [SIP](https://github.com/joaospinto/sip_python)                              | ``sip``         | Barrier Augmented Lagrangian | Sparse | MIT |\n\nMatrix arguments are NumPy arrays for dense solvers and SciPy Compressed Sparse Column (CSC) matrices for sparse ones.\n\n## Frequently Asked Questions\n\n- [Can I print the list of solvers available on my machine?](https://github.com/qpsolvers/qpsolvers/discussions/37)\n- [Is it possible to solve a least squares rather than a quadratic program?](https://github.com/qpsolvers/qpsolvers/discussions/223)\n- [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)\n- [I have a non-convex quadratic program, is there a solver I can use?](https://github.com/qpsolvers/qpsolvers/discussions/240)\n- [I have quadratic equality constraints, is there a solver I can use?](https://github.com/qpsolvers/qpsolvers/discussions/241)\n- [Error: Mircrosoft Visual C++ 14.0 or greater is required on Windows](https://github.com/qpsolvers/qpsolvers/discussions/257)\n- [Can I add penalty terms as in ridge regression or LASSO?](https://github.com/qpsolvers/qpsolvers/discussions/272)\n\n## Benchmark\n\nQP 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, ...):\n\n- 📈 [Free-for-all test set](https://github.com/qpsolvers/free_for_all_qpbenchmark): open to all problems submitted by the community.\n- 📈 [Maros-Meszaros test set](https://github.com/qpsolvers/maros_meszaros_qpbenchmark): hard problems curated by the numerical optimization community.\n- 📈 [MPC test set](https://github.com/qpsolvers/mpc_qpbenchmark): convex model predictive control problems arising in robotics.\n\n## Citing qpsolvers\n\nIf you find this project useful, please consider giving it a :star: or citing it if your work is scientific:\n\n```bibtex\n@software{qpsolvers,\n  title = {{qpsolvers: Quadratic Programming Solvers in Python}},\n  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},\n  license = {LGPL-3.0},\n  url = {https://github.com/qpsolvers/qpsolvers},\n  version = {4.11.0},\n  year = {2026}\n}\n```\n\nDon't forget to add yourself to the BibTeX above and to `CITATION.cff` if you contribute to this repository.\n\n## Contributing\n\nWe 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.\n\n## See also\n\n- [qpbenchmark](https://github.com/qpsolvers/qpbenchmark/): Benchmark for quadratic programming solvers available in Python.\n- [qpsolvers-eigen](https://github.com/ami-iit/qpsolvers-eigen): C++ abstraction layer for quadratic programming solvers using Eigen.\n"
  },
  {
    "path": "doc/conf.py",
    "content": "# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2024 Stéphane Caron and the qpsolvers contributors\n\nimport re\nimport sys\nfrom os.path import abspath, dirname, join\n\nsys.path.insert(0, abspath(\"..\"))\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.\nextensions = [\n    \"sphinx.ext.autodoc\",\n    \"sphinx.ext.coverage\",\n    \"sphinx-mathjax-offline\",\n    \"sphinx.ext.napoleon\",  # before sphinx_autodoc_typehints\n    \"sphinx_autodoc_typehints\",\n]\n\n# List of modules to be mocked up\nautodoc_mock_imports = [\n    \"coptpy\",\n    \"ecos\",\n    \"gurobipy\",\n    \"hpipm_python\",\n    \"mosek\",\n    \"nppro\",\n    \"osqp\",\n    \"pdhcg\",\n    \"qpoases\",\n    \"qpSWIFT\",\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = []\n\n# The suffix(es) of source filenames.\nsource_suffix = {\".rst\": \"restructuredtext\"}\n\n# The master toctree document.\nmaster_doc = \"index\"\n\n# General information about the project.\nproject = \"qpsolvers\"\ncopyright = \"2016-2024 Stéphane Caron and the qpsolvers contributors\"\nauthor = \"Stéphane Caron\"\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n\n# The short X.Y version.\nversion = None\n\n# The full version, including alpha/beta/rc tags.\nrelease = None\n\n# Read version info directly from the module's __init__.py\ninit_path = join(dirname(dirname(str(abspath(__file__)))), \"qpsolvers\")\nwith open(f\"{init_path}/__init__.py\", \"r\") as fh:\n    for line in fh:\n        match = re.match(\n            r'__version__ = \"((\\d+)\\.(\\d+)\\.\\d+)[a-z0-9\\-]*\".*',\n            line,\n        )\n        if match is not None:\n            release = f\"{match.group(2)}.{match.group(3)}\"\n            version = match.group(1)\n            assert len(release.split(\".\")) == 2, f\"{release=}\"\n            assert len(version.split(\".\")) == 3, f\"{version=}\"\n            break\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = \"en\"\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\n# This patterns also effect to html_static_path and html_extra_path\nexclude_patterns = [\"build\", \"Thumbs.db\", \".DS_Store\"]\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\n#\nhtml_theme = \"furo\"\n\n# Override Pygments style.\npygments_style = \"sphinx\"\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#\nhtml_theme_options = {}\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = \"qpsolversdoc\"\n"
  },
  {
    "path": "doc/developer-notes.rst",
    "content": "***************\nDeveloper notes\n***************\n\nAdding a new solver\n===================\n\nLet'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\"``.\n\nThe process to add AwesomeQP to *qpsolvers* goes as follows:\n\n1. Create a new file ``qpsolvers/solvers/awesomeqp_.py`` (named after the solver keyword, with a trailing underscore)\n2. Implement in this file a function ``awesomeqp_solve_problem`` that returns a :class:`.Solution`\n3. Implement in the same file a function ``awesomeqp_solve_qp`` to connect it to the historical API, typically as follows:\n\n.. code:: python\n\n    def awesomeqp_solve_qp(P, q, G, h, A, b, lb, ub, initvals=None, verbose=False, **kwargs):\n    ) -> Optional[np.ndarray]:\n        r\"\"\"Solve a quadratic program using AwesomeQP.\n\n        [document parameters and return values here]\n        \"\"\"\n        problem = Problem(P, q, G, h, A, b, lb, ub)\n        solution = awesomeqp_solve_problem(\n            problem, initvals, verbose, backend, **kwargs\n        )\n        return solution.x if solution.found else None\n\n4. Define the two function prototypes for ``awesomeqp_solve_problem`` and ``awesomeqp_solve_qp`` in ``qpsolvers/solvers/__init__.py``:\n\n.. code:: python\n\n    # AwesomeQP\n    # ========\n\n    awesome_solve_qp: Optional[\n        Callable[\n            [\n                ndarray,\n                ndarray,\n                Optional[ndarray],\n                Optional[ndarray],\n                Optional[ndarray],\n                Optional[ndarray],\n                Optional[ndarray],\n                Optional[str],\n                bool,\n            ],\n            Optional[ndarray],\n        ]\n    ] = None\n\n.. note::\n\n    The prototype needs to match the actual function. You can check its correctness by running ``tox -e py`` in the repository.\n\n5. Below the prototype, import the function into the ``solve_function`` dictionary:\n\n.. code:: python\n\n    try:\n        from .awesomeqp_ import awesomeqp_solve_qp\n\n        solve_function[\"awesomeqp\"] = awesomeqp_solve_qp\n        available_solvers.append(\"awesomeqp\")\n        # dense_solvers.append(\"awesomeqp\")   if applicable\n        # sparse_solvers.append(\"awesomeqp\")  if applicable\n    except ImportError:\n        pass\n\n6. Append the solver identifier to ``dense_solvers`` or ``sparse_solvers``, if applicable\n7. Import ``awesomeqp_solve_qp`` from ``qpsolvers/__init__.py`` and add it to ``__all__``\n8. Add the solver to ``doc/src/supported-solvers.rst``\n9. Add the solver to the *Solvers* section of the README\n10. Assuming AwesomeQP is distributed on `PyPI <https://pypi.org/>`__, add it to the ``[testenv]`` and ``[testenv:coverage]`` environments of ``tox.ini`` for unit testing\n11. Assuming AwesomeQP is distributed on Conda or PyPI, add it to the list of dependencies in ``doc/environment.yml``\n12. Log the new solver as an addition in the changelog\n13. If you are a new contributor, feel free to add your name to ``CITATION.cff``.\n\nProblem conversions\n===================\n\n.. automodule:: qpsolvers.conversions\n    :members:\n\nTesting locally\n===============\n\nTo run all CI checks locally, go to the repository folder and run\n\n.. code:: bash\n\n    tox -e py\n\nThis will run linters and unit tests.\n"
  },
  {
    "path": "doc/index.rst",
    "content": ".. title:: Table of Contents\n\n#########\nqpsolvers\n#########\n\nUnified interface to Quadratic Programming (QP) solvers available in Python.\n\nThe 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:\n\n.. math::\n\n    \\begin{split}\\begin{array}{ll}\n        \\underset{x}{\\mbox{minimize}} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                    \\\\\n            & lb \\leq x \\leq ub\n    \\end{array}\\end{split}\n\nA similar function is provided for :ref:`least squares <Least squares>`.\n\n.. toctree::\n    :maxdepth: 1\n\n    installation.rst\n    quadratic-programming.rst\n    least-squares.rst\n    supported-solvers.rst\n    unsupported-solvers.rst\n    developer-notes.rst\n    references.rst\n"
  },
  {
    "path": "doc/installation.rst",
    "content": "************\nInstallation\n************\n\nLinux\n=====\n\nConda\n-----\n\nTo install the library from `conda-forge <https://conda-forge.org/>`__, simply run:\n\n.. code:: bash\n\n    conda install -c conda-forge qpsolvers\n\nPyPI\n----\n\nFirst, install the pip package manager, for example on a recent Debian-based distribution with Python 3:\n\n.. code:: bash\n\n    sudo apt install python3-dev\n\nYou can then install the library by:\n\n.. code:: bash\n\n    pip install qpsolvers\n\nAdd the ``--user`` parameter for a user-only installation.\n\nWindows\n=======\n\nAnaconda\n--------\n\n- First, install the `Visual C++ Build Tools <https://visualstudio.microsoft.com/visual-cpp-build-tools/>`_\n- Install your Python environment, for instance `Anaconda <https://docs.anaconda.com/anaconda/install/windows/>`_\n- Install the library from conda-forge, for instance in a terminal opened from the Anaconda Navigator:\n\n.. code:: bash\n\n    conda install -c conda-forge qpsolvers\n\nMicrosoft Visual Studio\n-----------------------\n\n- Open Microsoft Visual Studio\n- Create a new project:\n    - Select a new \"Python Application\" project template\n    - Click \"Next\"\n    - Give a name to your project\n    - Click \"Create\"\n- Go to Tools → Python → Python Environments:\n    - To the left of the \"Python Environments\" tab that opens, select a Python version >= 3.8\n    - Click on \"Packages (PyPI)\"\n    - In the search box, type \"qpsolvers\"\n    - Below the search box, click on \"Run command: pip install qpsolvers\"\n    - A window pops up asking for administrator privileges: grant them\n    - Check the text messages in the \"Output\" pane at the bottom of the window\n- Go to the main code tab (it should be your project name followed by the \".py\" extension)\n- Copy the `example code <https://github.com/qpsolvers/qpsolvers#example>`_ from the README and paste it there\n- Click on the \"Run\" icon in the toolbar to execute this program\n\nAt this point a ``python.exe`` window should open with the following output:\n\n.. code:: bash\n\n    QP solution: x = [0.30769231, -0.69230769, 1.38461538]\n    Press any key to continue . . .\n\nSolvers\n=======\n\nOpen source solvers\n-------------------\n\nTo install at once all open source QP solvers available from the `Python\nPackage Index <https://pypi.org/>`_, run the ``pip`` command as follows:\n\n.. code:: bash\n\n    pip install \"qpsolvers[open_source_solvers]\"\n\nYou can also install a subset of QP solvers of your liking, for instance:\n\n.. code:: bash\n\n    pip install qpsolvers[clarabel,daqp,proxqp,scs]\n\n.. _gurobi-install:\n\nGurobi\n------\n\nGurobi comes with a `one-line pip installation\n<https://www.gurobi.com/documentation/9.1/quickstart_linux/cs_using_pip_to_install_gr.html>`_\nwhere you can fetch the solver directly from the company servers:\n\n.. code:: bash\n\n    python -m pip install -i https://pypi.gurobi.com gurobipy\n\nThis version comes with limitations. For instance, trying to solve a problem\nwith 200 optimization variables fails with the following warning:\n\n.. code:: python\n\n    Warning: Model too large for size-limited license; visit https://www.gurobi.com/free-trial for a full license\n\n.. _copt-install:\n\nCOPT\n------\n\nCOPT comes with an installation doc at  `COPT installation\n<https://guide.coap.online/copt/en-doc/install.html>`_\nwhere you can install by pip:\n\n.. code:: bash\n\n    python -m pip install coptpy\n\nThis version comes with limitations. For instance, trying to solve a problem\nwith 200 optimization variables fails with the following warning:\n\n.. code:: python\n\n    No license found. Starting COPT with size limitations for non-commercial use\n    Please apply for a license from www.shanshu.ai/copt\n\n\n.. _qpoases-install:\n\nHiGHS\n-----\n\nThe simplest way to install HiGHS is:\n\n.. code:: bash\n\n    pip install highspy\n\nIf this solution doesn't work for you, follow the `Python installation\ninstructions <https://github.com/ERGO-Code/HiGHS#python>`__ from the README.\n\nPDHCG\n-----\n\nYou can install the GPU-accelerated PDHCG solver directly from PyPI:\n\n.. code:: bash\n\n    pip install pdhcg\n\nNote 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:\n\n.. code:: bash\n\n    export CUDACXX=/your/path/to/nvcc\n    export SKBUILD_CMAKE_ARGS=\"-DCMAKE_CUDA_COMPILER=/your/path/to/nvcc\"\n    pip install pdhcg\n\nquadprog\n--------\n\nYou can install the quadprog solver from PyPI:\n\n.. code:: bash\n\n    pip install quadprog\n\nThis package comes with wheels to avoid recompiling the solver from source.\n\nqpOASES\n-------\n\nThe simplest way to install qpOASES is via conda-forge:\n\n.. code:: bash\n\n    conda install -c conda-forge qpoases\n\nYou can also check out the `official qpOASES installation page\n<https://projects.coin-or.org/qpOASES/wiki/QpoasesInstallation>`_ for the\nlatest release.\n"
  },
  {
    "path": "doc/least-squares.rst",
    "content": ".. _Least squares:\n\n*************\nLeast squares\n*************\n\nTo solve a linear least-squares problem, simply build the matrices that define\nit and call the :func:`.solve_ls` function:\n\n.. code:: python\n\n    from numpy import array, dot\n    from qpsolvers import solve_ls\n\n    R = array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])\n    s = array([3., 2., 3.])\n    G = array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])\n    h = array([3., 2., -2.]).reshape((3,))\n\n    x_sol = solve_ls(R, s, G, h, solver=\"osqp\")\n    print(f\"LS solution: {x_sol = }\")\n\nThe backend QP solver is selected among :ref:`supported solvers <Supported\nsolvers>` via the ``solver`` keyword argument. This example outputs the\nsolution ``[0.12997217, -0.06498019, 1.74004125]``.\n\n.. autofunction:: qpsolvers.solve_ls\n\nSee the ``examples/`` folder in the repository for more advanced use cases. For\na more general introduction you can also check out this post on `least squares\nin Python <https://scaron.info/robotics/least-squares.html>`_.\n"
  },
  {
    "path": "doc/quadratic-programming.rst",
    "content": ".. _Quadratic programming:\n\n*********************\nQuadratic programming\n*********************\n\nPrimal problem\n==============\n\nA quadratic program is defined in standard form as:\n\n.. math::\n\n    \\begin{split}\\begin{array}{ll}\n        \\underset{x}{\\mbox{minimize}} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n    \\end{array}\\end{split}\n\nThe vectors :math:`lb` and :math:`ub` can contain :math:`\\pm \\infty` values to\ndisable bounds on some coordinates. To solve such a problem, build the matrices\nthat define it and call the :func:`.solve_qp` function:\n\n.. code:: python\n\n    from numpy import array, dot\n    from qpsolvers import solve_qp\n\n    M = array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])\n    P = dot(M.T, M)  # quick way to build a symmetric matrix\n    q = dot(array([3., 2., 3.]), M).reshape((3,))\n    G = array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])\n    h = array([3., 2., -2.]).reshape((3,))\n    A = array([1., 1., 1.])\n    b = array([1.])\n\n    x = solve_qp(P, q, G, h, A, b, solver=\"osqp\")\n    print(f\"QP solution: x = {x}\")\n\nThe backend QP solver is selected among :ref:`supported solvers <Supported\nsolvers>` via the ``solver`` keyword argument. This example outputs the\nsolution ``[0.30769231, -0.69230769,  1.38461538]``.\n\n.. autofunction:: qpsolvers.solve_qp\n\nSee the ``examples/`` folder in the repository for more use cases. For a more\ngeneral introduction you can also check out this post on `quadratic programming\nin Python <https://scaron.info/blog/quadratic-programming-in-python.html>`_.\n\nProblem class\n=============\n\nAlternatively, we can define the matrices and vectors using the :class:`.Problem` class:\n\n.. autoclass:: qpsolvers.problem.Problem\n   :members:\n\nThe solve function corresponding to :class:`.Problem` is :func:`.solve_problem`\nrather than :func:`.solve_qp`.\n\nDual multipliers\n================\n\nThe dual of the quadratic program defined above can be written as:\n\n.. math::\n\n    \\begin{split}\\begin{array}{ll}\n    \\underset{x, z, y, z_{\\mathit{box}}}{\\mbox{maximize}} &\n        -\\frac{1}{2} x^T P x - h^T z - b^T y\n        - lb^T z_{\\mathit{box}}^- - ub^T z_{\\mathit{box}}^+ \\\\\n    \\mbox{subject to}\n        & P x + G^T z + A^T y + z_{\\mathit{box}} + q = 0 \\\\\n        & z \\geq 0\n    \\end{array}\\end{split}\n\nwere :math:`v^- = \\min(v, 0)` and :math:`v^+ = \\max(v, 0)`. To solve both a\nproblem and its dual, getting a full primal-dual solution :math:`(x^*, z^*,\ny^*, z_\\mathit{box}^*)`, build a :class:`.Problem` and call the\n:func:`.solve_problem` function:\n\n.. code:: python\n\n    import numpy as np\n    from qpsolvers import Problem, solve_problem\n\n    M = np.array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])\n    P = M.T.dot(M)  # quick way to build a symmetric matrix\n    q = np.array([3., 2., 3.]).dot(M).reshape((3,))\n    G = np.array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])\n    h = np.array([3., 2., -2.]).reshape((3,))\n    A = np.array([1., 1., 1.])\n    b = np.array([1.])\n    lb = -0.6 * np.ones(3)\n    ub = +0.7 * np.ones(3)\n\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = solve_problem(problem, solver=\"proxqp\")\n\n    print(f\"Primal: x = {solution.x}\")\n    print(f\"Dual (Gx <= h): z = {solution.z}\")\n    print(f\"Dual (Ax == b): y = {solution.y}\")\n    print(f\"Dual (lb <= x <= ub): z_box = {solution.z_box}\")\n\nThe function returns a :class:`.Solution` with both primal and dual vectors. This example outputs the following solution:\n\n.. code::\n\n    Primal: x = [ 0.63333169 -0.33333307  0.70000137]\n    Dual (Gx <= h): z = [0.         0.         7.66660538]\n    Dual (Ax == b): y = [-16.63326017]\n    Dual (lb <= x <= ub): z_box = [ 0.          0.         26.26649724]\n\n.. autofunction:: qpsolvers.solve_problem\n\nSee the ``examples/`` folder in the repository for more use cases. For an\nintroduction to dual multipliers you can also check out this post on\n`optimality conditions and numerical tolerances in QP solvers\n<https://scaron.info/blog/optimality-conditions-and-numerical-tolerances-in-qp-solvers.html>`_.\n\nOptimality of a solution\n========================\n\nThe :class:`.Solution` class describes the solution found by a solver to a\ngiven problem. It is linked to the corresponding :class:`.Problem`, which it\ncan use for instance to check residuals. We can for instance check the\noptimality of the solution returned by a solver with:\n\n.. code:: python\n\n    import numpy as np\n    from qpsolvers import Problem, solve_problem\n\n    M = np.array([[1., 2., 0.], [-8., 3., 2.], [0., 1., 1.]])\n    P = M.T.dot(M)  # quick way to build a symmetric matrix\n    q = np.array([3., 2., 3.]).dot(M).reshape((3,))\n    G = np.array([[1., 2., 1.], [2., 0., 1.], [-1., 2., -1.]])\n    h = np.array([3., 2., -2.]).reshape((3,))\n    A = np.array([1., 1., 1.])\n    b = np.array([1.])\n    lb = -0.6 * np.ones(3)\n    ub = +0.7 * np.ones(3)\n\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = solve_problem(problem, solver=\"qpalm\")\n\n    print(f\"- Solution is{'' if solution.is_optimal(1e-8) else ' NOT'} optimal\")\n    print(f\"- Primal residual: {solution.primal_residual():.1e}\")\n    print(f\"- Dual residual: {solution.dual_residual():.1e}\")\n    print(f\"- Duality gap: {solution.duality_gap():.1e}\")\n\nThis example prints:\n\n.. code::\n\n    - Solution is optimal\n    - Primal residual: 1.1e-16\n    - Dual residual: 1.4e-14\n    - Duality gap: 0.0e+00\n\nYou can check out [Caron2022]_ for an overview of optimality conditions and why\na solution is optimal if and only if these three residuals are zero.\n\n.. autoclass:: qpsolvers.solution.Solution\n   :members:\n"
  },
  {
    "path": "doc/references.rst",
    "content": "**********\nReferences\n**********\n\n.. [Tracy2024] `On the Differentiability of the Primal-Dual Interior-Point Method <https://arxiv.org/abs/2406.11749>`_, K. Tracy and Z. Manchester. ArXiv, 2024.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [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.\n\n.. [Huangfu2018] *Parallelizing the dual revised simplex method*. Q. Huangfu and J. Hall. Mathematical Programming Computation, 2018, vol. 10, no 1, p. 119-142.\n\n.. [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.\n\n.. [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.\n\n.. [Vandenberghe2010] `The CVXOPT linear and quadratic cone program solvers <https://www.seas.ucla.edu/~vandenbe/publications/coneprog.pdf>`_, L. Vandenberghe. 2010.\n\n.. [Goldfarb1983] *A numerically stable dual method for solving strictly convex quadratic programs*. D. Goldfarb and A. Idnani. Mathematical Programming, vol. 27, p. 1-33.\n"
  },
  {
    "path": "doc/supported-solvers.rst",
    "content": ".. _Supported solvers:\n\n*****************\nSupported solvers\n*****************\n\nSolvers that are detected as installed on your machine are listed in:\n\n.. autodata:: qpsolvers.available_solvers\n\nClarabel\n========\n\n.. automodule:: qpsolvers.solvers.clarabel_\n    :members:\n\nCOPT\n========\n\n.. automodule:: qpsolvers.solvers.copt_\n    :members:\n\nCVXOPT\n======\n\n.. automodule:: qpsolvers.solvers.cvxopt_\n    :members:\n\nDAQP\n======\n\n.. automodule:: qpsolvers.solvers.daqp_\n    :members:\n\nECOS\n====\n\n.. automodule:: qpsolvers.solvers.ecos_\n    :members:\n\nGurobi\n======\n\n.. automodule:: qpsolvers.solvers.gurobi_\n    :members:\n\nHiGHS\n=====\n\n.. automodule:: qpsolvers.solvers.highs_\n    :members:\n\nHPIPM\n=====\n\n.. automodule:: qpsolvers.solvers.hpipm_\n    :members:\n\njaxopt.OSQP\n===========\n\n.. automodule:: qpsolvers.solvers.jaxopt_osqp_\n    :members:\n\nKVXOPT\n======\n\n.. automodule:: qpsolvers.solvers.kvxopt_\n    :members:\n\nMOSEK\n=====\n\n.. automodule:: qpsolvers.solvers.mosek_\n    :members:\n\nOSQP\n====\n\n.. automodule:: qpsolvers.solvers.osqp_\n    :members:\n\n\nPIQP\n======\n\n.. automodule:: qpsolvers.solvers.piqp_\n    :members:\n\nProxQP\n======\n\n.. automodule:: qpsolvers.solvers.proxqp_\n    :members:\n\npyqpmad\n=======\n\n.. automodule:: qpsolvers.solvers.pyqpmad_\n    :members:\n\nQPALM\n=====\n\n.. automodule:: qpsolvers.solvers.qpalm_\n    :members:\n\nqpOASES\n=======\n\n.. automodule:: qpsolvers.solvers.qpoases_\n    :members:\n\nqpSWIFT\n=======\n\n.. automodule:: qpsolvers.solvers.qpswift_\n    :members:\n\nqpax\n===\n\n.. automodule:: qpsolvers.solvers.qpax_\n    :members:\n\nQTQP\n====\n\n.. automodule:: qpsolvers.solvers.qtqp_\n    :members:\n\nquadprog\n========\n\n.. automodule:: qpsolvers.solvers.quadprog_\n    :members:\n\nSCS\n===\n\n.. automodule:: qpsolvers.solvers.scs_\n    :members:\n\nSIP\n===\n\n.. automodule:: qpsolvers.solvers.sip_\n    :members:\n"
  },
  {
    "path": "doc/unsupported-solvers.rst",
    "content": "*******************\nUnsupported solvers\n*******************\n\nUnsupported 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).\n\nPDHCG\n=====\n\n.. automodule:: qpsolvers.solvers.pdhcg_\n    :members:\n\nNPPro\n=====\n\n.. automodule:: qpsolvers.solvers.nppro_\n    :members:\n"
  },
  {
    "path": "examples/README.md",
    "content": "# Quadratic programming examples\n\nExamples are roughly sorted from simple to complex. The basic ones are:\n\n- [Quadratic program](quadratic_program.py)\n- [Linear least squares](least_squares.py)\n- [Box inequalities](box_inequalities.py)\n- [Sparse linear least squares](sparse_least_squares.py)\n\nFor more advance use cases, check out:\n\n- [Dual multipliers](dual_multipliers.py)\n\n## Applications\n\nFeel free to share yours in [Show and tell](https://github.com/qpsolvers/qpsolvers/discussions/categories/show-and-tell)!\n\n- [Constrained linear regression](constrained_linear_regression.py)\n- [Model predictive control for humanoid locomotion](model_predictive_control.py)\n"
  },
  {
    "path": "examples/box_inequalities.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test an available QP solvers on a small problem with box inequalities.\"\"\"\n\nimport random\nfrom time import perf_counter\n\nimport numpy as np\n\nfrom qpsolvers import available_solvers, print_matrix_vector, solve_qp\n\nM = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\nP = np.dot(M.T, M)  # this is a positive definite matrix\nq = np.dot(np.array([3.0, 2.0, 3.0]), M)\nA = np.array([1.0, 1.0, 1.0])\nb = np.array([1.0])\nlb = -0.5 * np.ones(3)\nub = 1.0 * np.ones(3)\n\nx_sol = np.array([0.41463414566726164, -0.41463414566726164, 1.0])\n\nif __name__ == \"__main__\":\n    start_time = perf_counter()\n    solver = random.choice(available_solvers)\n    x = solve_qp(P, q, A=A, b=b, lb=lb, ub=ub, solver=solver)\n    end_time = perf_counter()\n\n    print(\"\")\n    print(\"    min. 1/2 x^T P x + q^T x\")\n    print(\"    s.t.   A * x == b\")\n    print(\"         lb <= x <= ub\")\n    print(\"\")\n    print_matrix_vector(P, \"P\", q, \"q\")\n    print(\"\")\n    print_matrix_vector(A, \"A\", b, \"b\")\n    print(\"\")\n    print_matrix_vector(lb.reshape((3, 1)), \"lb\", ub, \"ub\")\n    print(\"\")\n    print(f\"Solution: x = {x}\")\n    print(f\"It should be close to x* = {x_sol}\")\n    print(f\"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}\")\n"
  },
  {
    "path": "examples/constrained_linear_regression.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test a random QP solver on a constrained linear regression problem.\n\nThis example originates from:\n    https://stackoverflow.com/a/74422084\n\nSee also:\n    https://scaron.info/blog/simple-linear-regression-with-online-updates.html\n\"\"\"\n\nimport random\n\nimport numpy as np\n\nimport qpsolvers\nfrom qpsolvers import solve_ls\n\na = np.array([1.2, 2.3, 4.2])\nb = np.array([1.0, 5.0, 6.0])\nc = np.array([5.4, 6.2, 1.9])\nm = np.vstack([a, b, c])\ny = np.array([5.3, 0.9, 5.6])\n\n# Objective: || [a b c] x - y ||^2\nR = m.T\ns = y\n\n# Constraint: sum(x) = 1\nA = np.ones((1, 3))\nb = np.array([1.0])\n\n# Constraint: x >= 0\nlb = np.zeros(3)\n\nif __name__ == \"__main__\":\n    solver = random.choice(qpsolvers.available_solvers)\n    x = solve_ls(R, s, A=A, b=b, lb=lb, solver=solver)\n    print(f\"Found solution {x=}\")\n"
  },
  {
    "path": "examples/dual_multipliers.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Get both primal and dual solutions to a quadratic program.\"\"\"\n\nimport random\n\nimport numpy as np\n\nfrom qpsolvers import (\n    Problem,\n    available_solvers,\n    print_matrix_vector,\n    solve_problem,\n)\n\nM = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\nP = np.dot(M.T, M)  # this is a positive definite matrix\nq = np.dot(np.array([3.0, 2.0, 3.0]), M)\nG = np.array([[4.0, 2.0, 0.0], [-1.0, 2.0, -1.0]])\nh = np.array([1.0, -2.0])\nA = np.array([1.0, 1.0, 1.0]).reshape((1, 3))\nb = np.array([1.0])\nlb = np.array([-0.5, -0.4, -0.5])\nub = np.array([1.0, 1.0, 1.0])\n\n\nif __name__ == \"__main__\":\n    solver = random.choice(available_solvers)\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = solve_problem(problem, solver)\n\n    print(\"========================= PRIMAL PROBLEM =========================\")\n    print(\"\")\n    print(\"    min. 1/2 x^T P x + q^T x\")\n    print(\"    s.t.     G x <= h\")\n    print(\"             A x == b\")\n    print(\"         lb <= x <= ub\")\n    print(\"\")\n    print_matrix_vector(P, \"P\", q, \"q\")\n    print(\"\")\n    print_matrix_vector(G, \"G\", h, \"h\")\n    print(\"\")\n    print_matrix_vector(A, \"A\", b, \"b\")\n    print(\"\")\n    print_matrix_vector(lb.reshape((3, 1)), \"lb\", ub, \"ub\")\n    print(\"\")\n\n    print(\"============================ SOLUTION ============================\")\n    print(\"\")\n    print(f'Found with solver=\"{solver}\"')\n    print(\"\")\n    print_matrix_vector(\n        solution.x.reshape((3, 1)),\n        \"Primal x*\",\n        solution.z,\n        \"Dual (Gx <= h) z*\",\n    )\n    print(\"\")\n    print_matrix_vector(\n        solution.y.reshape((1, 1)),\n        \"Dual (Ax == b) y*\",\n        solution.z_box.reshape((3, 1)),\n        \"Dual (lb <= x <= ub) z_box*\",\n    )\n    print(\"\")\n\n    print(\"=== Optimality  checks ===\")\n    print(f\"- Primal residual: {solution.primal_residual():.1e}\")\n    print(f\"- Dual residual:   {solution.dual_residual():.1e}\")\n    print(f\"- Duality gap:     {solution.duality_gap():.1e}\")\n    print(\"\")\n"
  },
  {
    "path": "examples/lasso_regularization.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Apply lasso regularization to a quadratic program.\n\nDetails:\n    https://scaron.info/blog/lasso-regularization-in-quadratic-programming.html\n\"\"\"\n\nimport numpy as np\n\nfrom qpsolvers import solve_qp\n\n# Objective: || R x - s ||^2\nn = 6\nR = np.diag(range(1, n + 1))\ns = np.ones(n)\n\n# Convert our least-squares objective to quadratic programming\nP = np.dot(R.transpose(), R)\nq = -np.dot(s.transpose(), R)\n\n# Linear inequality constraints: G x <= h\nG = np.array(\n    [\n        [1.0, 0.0] * (n // 2),\n        [0.0, 1.0] * (n // 2),\n    ]\n)\nh = np.array([10.0, -10.0])\n\n# Lasso parameter\nt: float = 10.0\n\n# Lasso: inequality constraints\nG_lasso = np.vstack(\n    [\n        np.hstack([G, np.zeros((G.shape[0], n))]),\n        np.hstack([+np.eye(n), -np.eye(n)]),\n        np.hstack([-np.eye(n), -np.eye(n)]),\n        np.hstack([np.zeros((1, n)), np.ones((1, n))]),\n    ]\n)\nh_lasso = np.hstack([h, np.zeros(n), np.zeros(n), t])\n\n# Lasso: objective\nP_lasso = np.vstack(\n    [\n        np.hstack([P, np.zeros((n, n))]),\n        np.zeros((n, 2 * n)),\n    ]\n)\nq_lasso = np.hstack([q, np.ones(n)])\n\nif __name__ == \"__main__\":\n    x_unreg = solve_qp(P, q, G, h, solver=\"proxqp\")\n    print(f\"Solution without lasso: {x_unreg = }\")\n\n    lasso_res = solve_qp(P_lasso, q_lasso, G_lasso, h_lasso, solver=\"proxqp\")\n    x_lasso = lasso_res[:n]\n    z_lasso = lasso_res[n:]\n    print(f\"Solution with lasso ({t=}): {x_lasso = }\")\n    print(f\"We can check that abs(x_lasso) = {z_lasso = }\")\n"
  },
  {
    "path": "examples/least_squares.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test a random available QP solver on a small least-squares problem.\"\"\"\n\nimport random\nfrom time import perf_counter\n\nimport numpy as np\n\nimport qpsolvers\nfrom qpsolvers import print_matrix_vector, solve_ls\n\nR = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\ns = np.array([3.0, 2.0, 3.0])\nG = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\nh = np.array([3.0, 2.0, -2.0])\n\nx_sol = np.array([0.1299734765610818, -0.0649867382805409, 1.7400530468778364])\n\nif __name__ == \"__main__\":\n    start_time = perf_counter()\n    solver = random.choice(qpsolvers.available_solvers)\n    x = solve_ls(R, s, G, h, solver=solver, verbose=False)\n    end_time = perf_counter()\n\n    print(\"\")\n    print(\"    min. || R * x - s ||^2\")\n    print(\"    s.t. G * x <= h\")\n    print(\"\")\n    print_matrix_vector(R, \"R\", s, \"s\")\n    print(\"\")\n    print_matrix_vector(G, \"G\", h, \"h\")\n    print(\"\")\n    print(f\"Solution: x = {x}\")\n    print(f\"It should be close to x* = {x_sol}\")\n    print(f\"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}\")\n"
  },
  {
    "path": "examples/model_predictive_control.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test the \"quadprog\" QP solver on a model predictive control problem.\n\nThe problem is to balance a humanoid robot walking on a flat horizontal floor.\nSee the following post for context:\n\n    https://scaron.info/robot-locomotion/prototyping-a-walking-pattern-generator.html\n\"\"\"\n\nimport random\nfrom dataclasses import dataclass\nfrom typing import Optional\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom scipy.sparse import csc_matrix\n\nimport qpsolvers\nfrom qpsolvers import solve_qp\n\ngravity = 9.81  # [m] / [s]^2\n\n\n@dataclass\nclass HumanoidSteppingProblem:\n\n    com_height: float = 0.8\n    dsp_duration: float = 0.1\n    end_pos: float = 0.3\n    foot_length: float = 0.1\n    horizon_duration: float = 2.5\n    nb_timesteps: int = 16\n    ssp_duration: float = 0.7\n    start_pos: float = 0.0\n\n\nclass LinearModelPredictiveControl:\n\n    \"\"\"\n    Linear model predictive control for a system with linear dynamics and\n    linear constraints. This class is fully documented at:\n\n        https://scaron.info/doc/pymanoid/walking-pattern-generation.html#pymanoid.mpc.LinearPredictiveControl\n    \"\"\"\n\n    def __init__(\n        self,\n        A,\n        B,\n        C,\n        D,\n        e,\n        x_init,\n        x_goal,\n        nb_timesteps: int,\n        wxt: Optional[float],\n        wxc: Optional[float],\n        wu: float,\n    ):\n        assert C is not None or D is not None, \"use LQR for unconstrained case\"\n        assert (\n            wu > 0.0\n        ), \"non-negative control weight needed for regularization\"\n        assert wxt is not None or wxc is not None, \"set either wxt or wxc\"\n        u_dim = B.shape[1]\n        x_dim = A.shape[1]\n        self.A = A\n        self.B = B\n        self.C = C\n        self.D = D\n        self.G = None\n        self.P = None\n        self.U = None\n        self.U_dim = u_dim * nb_timesteps\n        self.e = e\n        self.h = None\n        self.nb_timesteps = nb_timesteps\n        self.q = None\n        self.u_dim = u_dim\n        self.wu = wu\n        self.wxc = wxc\n        self.wxt = wxt\n        self.x_dim = x_dim\n        self.x_goal = x_goal\n        self.x_init = x_init\n        #\n        self.build()\n\n    def build(self):\n        phi = np.eye(self.x_dim)\n        psi = np.zeros((self.x_dim, self.U_dim))\n        G_list, h_list = [], []\n        phi_list, psi_list = [], []\n        for k in range(self.nb_timesteps):\n            # Loop invariant: x == psi * U + phi * x_init\n            if self.wxc is not None:\n                phi_list.append(phi)\n                psi_list.append(psi)\n            C = self.C[k] if type(self.C) is list else self.C\n            D = self.D[k] if type(self.D) is list else self.D\n            e = self.e[k] if type(self.e) is list else self.e\n            G = np.zeros((e.shape[0], self.U_dim))\n            h = e if C is None else e - np.dot(C.dot(phi), self.x_init)\n            if D is not None:\n                # we rely on G == 0 to avoid a slower +=\n                G[:, k * self.u_dim : (k + 1) * self.u_dim] = D\n            if C is not None:\n                G += C.dot(psi)\n            if k == 0 and D is None:  # corner case, input has no effect\n                assert np.all(h >= 0.0)\n            else:  # regular case\n                G_list.append(G)\n                h_list.append(h)\n            phi = self.A.dot(phi)\n            psi = self.A.dot(psi)\n            psi[:, self.u_dim * k : self.u_dim * (k + 1)] = self.B\n        P = self.wu * np.eye(self.U_dim)\n        q = np.zeros(self.U_dim)\n        if self.wxt is not None and self.wxt > 1e-10:\n            c = np.dot(phi, self.x_init) - self.x_goal\n            P += self.wxt * np.dot(psi.T, psi)\n            q += self.wxt * np.dot(c.T, psi)\n        if self.wxc is not None and self.wxc > 1e-10:\n            Phi = np.vstack(phi_list)\n            Psi = np.vstack(psi_list)\n            X_goal = np.hstack([self.x_goal] * self.nb_timesteps)\n            c = np.dot(Phi, self.x_init) - X_goal\n            P += self.wxc * np.dot(Psi.T, Psi)\n            q += self.wxc * np.dot(c.T, Psi)\n        self.P = P\n        self.q = q\n        self.G = np.vstack(G_list)\n        self.h = np.hstack(h_list)\n        self.P_csc = csc_matrix(self.P)\n        self.G_csc = csc_matrix(self.G)\n\n    def solve(self, solver: str, sparse: bool = False, **kwargs):\n        P = self.P_csc if sparse else self.P\n        G = self.G_csc if sparse else self.G\n        U = solve_qp(P, self.q, G, self.h, solver=solver, **kwargs)\n        self.U = U.reshape((self.nb_timesteps, self.u_dim))\n\n    @property\n    def states(self):\n        assert self.U is not None, \"you need to solve() the MPC problem first\"\n        X = np.zeros((self.nb_timesteps + 1, self.x_dim))\n        X[0] = self.x_init\n        for k in range(self.nb_timesteps):\n            X[k + 1] = self.A.dot(X[k]) + self.B.dot(self.U[k])\n        return X\n\n\nclass HumanoidModelPredictiveControl(LinearModelPredictiveControl):\n    def __init__(self, problem: HumanoidSteppingProblem):\n        T = problem.horizon_duration / problem.nb_timesteps\n        nb_init_dsp_steps = int(round(problem.dsp_duration / T))\n        nb_init_ssp_steps = int(round(problem.ssp_duration / T))\n        nb_dsp_steps = int(round(problem.dsp_duration / T))\n        state_matrix = np.array(\n            [[1.0, T, T ** 2 / 2.0], [0.0, 1.0, T], [0.0, 0.0, 1.0]]\n        )\n        control_matrix = np.array([T ** 3 / 6.0, T ** 2 / 2.0, T])\n        control_matrix = control_matrix.reshape((3, 1))\n        zmp_from_state = np.array([1.0, 0.0, -problem.com_height / gravity])\n        ineq_matrix = np.array([+zmp_from_state, -zmp_from_state])\n        cur_max = problem.start_pos + 0.5 * problem.foot_length\n        cur_min = problem.start_pos - 0.5 * problem.foot_length\n        next_max = problem.end_pos + 0.5 * problem.foot_length\n        next_min = problem.end_pos - 0.5 * problem.foot_length\n        ineq_vector = [\n            np.array([+1000.0, +1000.0])\n            if i < nb_init_dsp_steps\n            else np.array([+cur_max, -cur_min])\n            if i - nb_init_dsp_steps <= nb_init_ssp_steps\n            else np.array([+1000.0, +1000.0])\n            if i - nb_init_dsp_steps - nb_init_ssp_steps < nb_dsp_steps\n            else np.array([+next_max, -next_min])\n            for i in range(problem.nb_timesteps)\n        ]\n        super().__init__(\n            state_matrix,\n            control_matrix,\n            ineq_matrix,\n            None,\n            ineq_vector,\n            x_init=np.array([problem.start_pos, 0.0, 0.0]),\n            x_goal=np.array([problem.end_pos, 0.0, 0.0]),\n            nb_timesteps=problem.nb_timesteps,\n            wxt=1.0,\n            wxc=None,\n            wu=1e-3,\n        )\n\n\ndef plot_mpc_solution(problem, mpc):\n    t = np.linspace(0.0, problem.horizon_duration, problem.nb_timesteps + 1)\n    X = mpc.states\n    zmp_from_state = np.array([1.0, 0.0, -problem.com_height / gravity])\n    zmp = X.dot(zmp_from_state)\n    pos = X[:, 0]\n    zmp_min = [x[0] if abs(x[0]) < 10 else None for x in mpc.e]\n    zmp_max = [-x[1] if abs(x[1]) < 10 else None for x in mpc.e]\n    zmp_min.append(zmp_min[-1])\n    zmp_max.append(zmp_max[-1])\n    plt.ion()\n    plt.clf()\n    plt.plot(t, pos)\n    plt.plot(t, zmp, \"r-\")\n    plt.plot(t, zmp_min, \"g:\")\n    plt.plot(t, zmp_max, \"b:\")\n    plt.grid(True)\n    plt.show(block=True)\n\n\nif __name__ == \"__main__\":\n    problem = HumanoidSteppingProblem()\n    mpc = HumanoidModelPredictiveControl(problem)\n    mpc.solve(solver=random.choice(qpsolvers.available_solvers))\n    plot_mpc_solution(problem, mpc)\n"
  },
  {
    "path": "examples/quadratic_program.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test the \"quadprog\" QP solver on a small dense problem.\"\"\"\n\nimport random\nfrom time import perf_counter\n\nimport numpy as np\n\nfrom qpsolvers import available_solvers, print_matrix_vector, solve_qp\n\nM = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\nP = np.dot(M.T, M)  # this is a positive definite matrix\nq = np.dot(np.array([3.0, 2.0, 3.0]), M)\nG = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\nh = np.array([3.0, 2.0, -2.0])\nA = np.array([1.0, 1.0, 1.0])\nb = np.array([1.0])\n\nx_sol = np.array([0.3076923111580727, -0.6923076888419274, 1.3846153776838548])\n\nif __name__ == \"__main__\":\n    start_time = perf_counter()\n    solver = random.choice(available_solvers)\n    x = solve_qp(P, q, G, h, A, b, solver=solver)\n    end_time = perf_counter()\n\n    print(\"\")\n    print(\"    min. 1/2 x^T P x + q^T x\")\n    print(\"    s.t. G * x <= h\")\n    print(\"         A * x == b\")\n    print(\"\")\n    print_matrix_vector(P, \"P\", q, \"q\")\n    print(\"\")\n    print_matrix_vector(G, \"G\", h, \"h\")\n    print(\"\")\n    print_matrix_vector(A, \"A\", b, \"b\")\n    print(\"\")\n    print(f\"Solution: x = {x}\")\n    print(f\"It should be close to x* = {x_sol}\")\n    print(f\"Found in {1e6 * (end_time - start_time):.0f} [us] with {solver}\")\n"
  },
  {
    "path": "examples/sparse_least_squares.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test a random sparse QP solver on a sparse least-squares problem.\n\nSee also: https://stackoverflow.com/a/74415546/3721564\n\"\"\"\n\nimport random\nfrom time import perf_counter\n\nimport qpsolvers\nfrom qpsolvers import solve_ls\nfrom qpsolvers.problems import get_sparse_least_squares\n\nif __name__ == \"__main__\":\n    solver = random.choice(qpsolvers.sparse_solvers)\n\n    R, s, G, h, A, b, lb, ub = get_sparse_least_squares(n=150_000)\n    start_time = perf_counter()\n    x = solve_ls(\n        R,\n        s,\n        G,\n        h,\n        A,\n        b,\n        lb,\n        ub,\n        solver=solver,\n        verbose=False,\n        sparse_conversion=True,\n    )\n    end_time = perf_counter()\n    duration_ms = 1e3 * (end_time - start_time)\n    tol = 1e-6  # tolerance for checks\n\n    print(\"\")\n    print(\"    min.  || x - s ||^2\")\n    print(\"    s.t.  G * x <= h\")\n    print(\"          sum(x) = 42\")\n    print(\"          0 <= x\")\n    print(\"\")\n    print(f\"Found solution in {duration_ms:.0f} milliseconds with {solver}\")\n    print(\"\")\n    print(f\"- Objective: {0.5 * (x - s).dot(x - s):.1f}\")\n    print(f\"- G * x <= h: {(G.dot(x) <= h + tol).all()}\")\n    print(f\"- x >= 0: {(x + tol >= 0.0).all()}\")\n    print(f\"- sum(x) = {x.sum():.1f}\")\n    print(\"\")\n"
  },
  {
    "path": "examples/test_dense_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test all available QP solvers on a dense quadratic program.\"\"\"\n\nfrom os.path import basename\n\nfrom IPython import get_ipython\nfrom numpy import array, dot\nfrom qpsolvers import dense_solvers, solve_qp, sparse_solvers\nfrom scipy.sparse import csc_matrix\n\nM = array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\nP = dot(M.T, M)\nq = dot(array([3.0, 2.0, 3.0]), M)\nG = array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\nh = array([3.0, 2.0, -2.0])\nP_csc = csc_matrix(P)\nG_csc = csc_matrix(G)\n\n\nif __name__ == \"__main__\":\n    if get_ipython() is None:\n        print(\n            \"This example should be run with IPython:\\n\\n\"\n            f\"\\tipython -i {basename(__file__)}\\n\"\n        )\n        exit()\n\n    dense_instr = {\n        solver: f\"u = solve_qp(P, q, G, h, solver='{solver}')\"\n        for solver in dense_solvers\n    }\n    sparse_instr = {\n        solver: f\"u = solve_qp(P_csc, q, G_csc, h, solver='{solver}')\"\n        for solver in sparse_solvers\n    }\n\n    benchmark = \"https://github.com/qpsolvers/qpbenchmark\"\n    print(\"\\nTesting all QP solvers on one given dense quadratic program\")\n    print(f\"For a proper benchmark, check out {benchmark}\")\n\n    sol0 = solve_qp(P, q, G, h, solver=dense_solvers[0])\n    abstol = 2e-4  # tolerance on absolute solution error\n    for solver in dense_solvers:\n        sol = solve_qp(P, q, G, h, solver=solver)\n    for solver in sparse_solvers:\n        sol = solve_qp(P_csc, q, G_csc, h, solver=solver)\n\n    print(\"\\nDense solvers\\n-------------\")\n    for solver, instr in dense_instr.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n\n    print(\"\\nSparse solvers\\n--------------\")\n    for solver, instr in sparse_instr.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n"
  },
  {
    "path": "examples/test_model_predictive_control.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test all available QP solvers on a model predictive control problem.\"\"\"\n\nfrom os.path import basename\n\nfrom IPython import get_ipython\nfrom qpsolvers import dense_solvers, sparse_solvers\n\nfrom model_predictive_control import (\n    HumanoidModelPredictiveControl,\n    HumanoidSteppingProblem,\n)\n\nproblem = HumanoidSteppingProblem()\nmpc = HumanoidModelPredictiveControl(problem)\n\n\nif __name__ == \"__main__\":\n    if get_ipython() is None:\n        print(\n            \"This example should be run with IPython:\\n\\n\"\n            f\"\\tipython -i {basename(__file__)}\\n\"\n        )\n        exit()\n\n    dense_instr = {\n        solver: f\"u = mpc.solve(solver='{solver}', sparse=False)\"\n        for solver in dense_solvers\n    }\n    sparse_instr = {\n        solver: f\"u = mpc.solve(solver='{solver}', sparse=True)\"\n        for solver in sparse_solvers\n    }\n\n    benchmark = \"https://github.com/qpsolvers/qpbenchmark\"\n    print(\"\\nTesting QP solvers on one given model predictive control problem\")\n    print(f\"For a proper benchmark, check out {benchmark}\")\n\n    print(\"\\nDense solvers\\n-------------\")\n    for solver, instr in dense_instr.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n\n    print(\"\\nSparse solvers\\n--------------\")\n    for solver, instr in sparse_instr.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n"
  },
  {
    "path": "examples/test_random_problems.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test all available QP solvers on random quadratic programs.\"\"\"\n\nimport sys\n\ntry:\n    from IPython import get_ipython\nexcept ImportError:\n    print(\"This example requires IPython, try installing ipython3\")\n    sys.exit(-1)\n\nfrom os.path import basename\nfrom timeit import timeit\n\nfrom numpy import dot, linspace, ones, random\nfrom qpsolvers import available_solvers, solve_qp\nfrom scipy.linalg import toeplitz\n\nnb_iter = 10\nsizes = [10, 20, 50, 100, 200, 500, 1000, 2000]\n\n\ndef solve_random_qp(n, solver):\n    M, b = random.random((n, n)), random.random(n)\n    P, q = dot(M.T, M), dot(b, M)\n    G = toeplitz(\n        [1.0, 0.0, 0.0] + [0.0] * (n - 3), [1.0, 2.0, 3.0] + [0.0] * (n - 3)\n    )\n    h = ones(n)\n    return solve_qp(P, q, G, h, solver=solver)\n\n\ndef plot_results(perfs):\n    try:\n        from pylab import (\n            clf,\n            get_cmap,\n            grid,\n            ion,\n            legend,\n            plot,\n            xlabel,\n            xscale,\n            ylabel,\n            yscale,\n        )\n    except ImportError:\n        print(\"Cannot plot results, try installing python3-matplotlib\")\n        print(\"Results are stored in the global `perfs` dictionary\")\n        return\n\n    cmap = get_cmap(\"tab10\")\n    colors = cmap(linspace(0, 1, len(available_solvers)))\n    solver_color = {\n        solver: colors[i] for i, solver in enumerate(available_solvers)\n    }\n    ion()\n    clf()\n    for solver in perfs:\n        plot(sizes, perfs[solver], lw=2, color=solver_color[solver])\n    grid(True)\n    legend(list(perfs.keys()), loc=\"lower right\")\n    xscale(\"log\")\n    yscale(\"log\")\n    xlabel(\"Problem size $n$\")\n    ylabel(\"Time (s)\")\n    for solver in perfs:\n        plot(sizes, perfs[solver], marker=\"o\", color=solver_color[solver])\n\n\nif __name__ == \"__main__\":\n    if get_ipython() is None:\n        print(\n            \"This example should be run with IPython:\\n\\n\"\n            f\"\\tipython -i {basename(__file__)}\\n\"\n        )\n        exit()\n    perfs = {}\n\n    benchmark = \"https://github.com/qpsolvers/qpbenchmark\"\n    print(\"\\nTesting all solvers on a given set of random QPs\")\n    print(f\"For a proper benchmark, check out {benchmark}\")\n\n    for solver in available_solvers:\n        try:\n            perfs[solver] = []\n            for size in sizes:\n                print(f\"Running {solver} on problem size {size}...\")\n                cum_time = timeit(\n                    stmt=f\"solve_random_qp({size}, '{solver}')\",\n                    setup=\"from __main__ import solve_random_qp\",\n                    number=nb_iter,\n                )\n                perfs[solver].append(cum_time / nb_iter)\n        except Exception as e:\n            print(f\"Warning: {str(e)}\")\n            if solver in perfs:\n                del perfs[solver]\n    plot_results(perfs)\n"
  },
  {
    "path": "examples/test_sparse_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Test all available QP solvers on a sparse quadratic program.\"\"\"\n\nfrom os.path import basename\n\nimport numpy as np\nimport scipy.sparse\nfrom IPython import get_ipython\nfrom numpy.linalg import norm\nfrom scipy.sparse import csc_matrix\n\nfrom qpsolvers import dense_solvers, solve_qp, sparse_solvers\n\nn = 500\nM = scipy.sparse.lil_matrix(scipy.sparse.eye(n))\nfor i in range(1, n - 1):\n    M[i, i + 1] = -1\n    M[i, i - 1] = 1\nP = csc_matrix(M.dot(M.transpose()))\nq = -np.ones((n,))\nG = csc_matrix(-scipy.sparse.eye(n))\nh = -2 * np.ones((n,))\nP_array = np.array(P.todense())\nG_array = np.array(G.todense())\n\n\ndef check_same_solutions(tol=0.05):\n    sol0 = solve_qp(P, q, G, h, solver=sparse_solvers[0])\n    for solver in sparse_solvers:\n        sol = solve_qp(P, q, G, h, solver=solver)\n        relvar = norm(sol - sol0) / norm(sol0)\n        assert (\n            relvar < tol\n        ), f\"{solver}'s solution offset by {100.0 * relvar:.1f}%\"\n    for solver in dense_solvers:\n        sol = solve_qp(P_array, q, G_array, h, solver=solver)\n        relvar = norm(sol - sol0) / norm(sol0)\n        assert (\n            relvar < tol\n        ), f\"{solver}'s solution offset by {100.0 * relvar:.1f}%\"\n\n\ndef time_dense_solvers():\n    instructions = {\n        solver: f\"u = solve_qp(P_array, q, G_array, h, solver='{solver}')\"\n        for solver in dense_solvers\n    }\n    print(\"\\nDense solvers\\n-------------\")\n    for solver, instr in instructions.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n\n\ndef time_sparse_solvers():\n    instructions = {\n        solver: f\"u = solve_qp(P, q, G, h, solver='{solver}')\"\n        for solver in sparse_solvers\n    }\n    print(\"\\nSparse solvers\\n--------------\")\n    for solver, instr in instructions.items():\n        print(f\"{solver}: \", end=\"\")\n        get_ipython().run_line_magic(\"timeit\", instr)\n\n\nif __name__ == \"__main__\":\n    if get_ipython() is None:\n        print(\n            \"This example should be run with IPython:\\n\\n\"\n            f\"\\tipython -i {basename(__file__)}\\n\"\n        )\n        exit()\n\n    benchmark = \"https://github.com/qpsolvers/qpbenchmark\"\n    print(\"\\nTesting all QP solvers on one given sparse quadratic program\")\n    print(f\"For a proper benchmark, check out {benchmark}\")\n\n    check_same_solutions()\n    time_dense_solvers()\n    time_sparse_solvers()\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[build-system]\nrequires = [\"flit_core >=2,<4\"]\nbuild-backend = \"flit_core.buildapi\"\n\n[project]\nname = \"qpsolvers\"\nreadme = \"README.md\"\nauthors = [\n    {name = \"Stéphane Caron\", email = \"stephane.caron@normalesup.org\"},\n]\nmaintainers = [\n    {name = \"Stéphane Caron\", email = \"stephane.caron@normalesup.org\"},\n]\ndynamic = ['version', 'description']\nrequires-python = \">=3.10\"\nclassifiers = [\n    \"Development Status :: 5 - Production/Stable\",\n    \"Intended Audience :: Developers\",\n    \"Intended Audience :: Science/Research\",\n    \"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)\",\n    \"Operating System :: OS Independent\",\n    \"Programming Language :: Python :: 3.10\",\n    \"Programming Language :: Python :: 3.11\",\n    \"Programming Language :: Python :: 3.12\",\n    \"Programming Language :: Python :: 3.13\",\n    \"Topic :: Scientific/Engineering :: Mathematics\",\n]\ndependencies = [\n    \"numpy >=1.15.4\",\n    \"scipy >=1.2.0\",\n]\nkeywords = [\"quadratic programming\", \"solver\", \"numerical optimization\"]\n\n[project.optional-dependencies]\nclarabel = [\"clarabel >=0.4.1\"]\ncopt = [\"coptpy>=7.0.0\"]\ncvxopt = [\"cvxopt >=1.2.6\"]\nkvxopt = [\"kvxopt >=1.3.2\"]\ndaqp = [\"daqp >=0.8.2\"]\necos = [\"ecos >=2.0.8\"]\ngurobi = [\"gurobipy >=9.5.2\"]\nhighs = [\"highspy >=1.1.2.dev3,<1.14.0\"]\njaxopt = [\"jaxopt >=0.8.3\"]\nmosek = [\"cvxopt >=1.2.6\", \"mosek >=10.0.40\"]\nosqp = [\"osqp >=0.6.2\"]\npiqp = [\"piqp >=0.2.2\"]\nproxqp = [\"proxsuite >=0.2.9\"]\nqpalm = [\"qpalm >=1.2.1\"]\npyqpmad = [\"pyqpmad >=1.4.0.post3\"]\nqpax = [\"qpax>=0.0.9\"]\nqtqp = [\"qtqp >=0.0.3\"]\nquadprog = [\"quadprog >=0.1.11\"]\nscs = [\"scs >=3.2.0\"]\nsip = [\"sip-python >=0.0.2\"]\nopen_source_solvers = [\"qpsolvers[clarabel,cvxopt,daqp,ecos,highs,jaxopt,osqp,piqp,proxqp,pyqpmad,qpalm,qtqp,quadprog,scs,sip,qpax]\"]\n\n# Wheels-only solvers should distribute wheels that work on:\n# - macOS (aarch64)\n# - macOS (x86)\n# - Linux (x86)\n# - Windows (x86)\nwheels_only = [\"qpsolvers[cvxopt,daqp,ecos,highs,piqp,proxqp,qpalm,sip]\"]\n\n[project.urls]\nHomepage = \"https://github.com/qpsolvers/qpsolvers\"\nDocumentation = \"https://qpsolvers.github.io/qpsolvers/\"\nSource = \"https://github.com/qpsolvers/qpsolvers\"\nTracker = \"https://github.com/qpsolvers/qpsolvers/issues\"\nChangelog = \"https://github.com/qpsolvers/qpsolvers/blob/main/CHANGELOG.md\"\n\n[tool.coverage]\nreport.include = [\"qpsolvers/*\"]\nreport.omit = [\n    \"qpsolvers/solvers/pdhcg_.py\",\n    \"qpsolvers/solvers/nppro_.py\",\n]\n\n[tool.mypy]\nignore_missing_imports = true\n\n[tool.pixi.workspace]\nchannels = [\"conda-forge\"]\nplatforms = [\"linux-64\", \"linux-aarch64\", \"osx-arm64\", \"win-64\"]\n\n[tool.pixi.dependencies]\npython = \">=3.10,<3.14\"\nclarabel = \">=0.4.1\"\ncvxopt = \">=1.2.6\"\ndaqp = \">=0.8.2\"\necos = \">=2.0.8\"\nhighspy = \">=1.5.3,<1.14.0\"\nkvxopt = \">=1.3.2\"\nnumpy = \">=1.15.4\"\nosqp = \">=0.6.2\"\npiqp = \">=0.2.2\"\nproxsuite = \">=0.2.9\"\nquadprog = \">=0.1.11\"\nscipy = \">=1.2.0\"\nscs = \">=3.2.0\"\n\n[tool.pixi.pypi-dependencies]\ncoptpy = \">=7.0.0\"\ngurobipy = \">=9.5.2\"\njaxopt = \">=0.8.3\"\nqpalm = \">=1.2.1\"\nqpax = \">=0.0.9\"\nqtqp = \">=0.0.3,<0.0.4\"\nsip-python = \">=0.0.2\"\npyqpmad = \">=1.4.0.post3\"\n\n[tool.pixi.feature.coverage.dependencies]\ncoverage = \">=5.5\"\ncoveralls = \"*\"\n\n[tool.pixi.feature.coverage.tasks]\ncoverage-erase = { cmd = \"coverage erase\" }\ncoverage-run = { cmd = \"coverage run -m unittest discover --failfast\", depends-on = [\"coverage-erase\"] }\ncoverage = { cmd = \"coverage report\", depends-on = [\"coverage-run\"] }\ncoveralls = { cmd = \"coveralls --service=github\" }\n\n[tool.pixi.feature.docs.dependencies]\nsphinx = \">=7.2.2\"\nsphinx-autodoc-typehints = \"*\"\nsetuptools = \">=60.0\"\n\n[tool.pixi.feature.docs.pypi-dependencies]\nfuro = \">=2023.8.17\"\nsphinx-mathjax-offline = \"*\"\n\n[tool.pixi.feature.licensed.pypi-dependencies]\nmosek = \">=10.0.40\"\n\n[tool.pixi.feature.licensed.tasks]\nlicensed = \"python -m unittest discover --failfast\"\n\n[tool.pixi.feature.lint.dependencies]\nmypy = \">=0.812\"\npylint = \">=2.8.2\"\nruff = \">=0.5.4\"\nscipy-stubs = \"*\"\n\n[tool.pixi.feature.py310.dependencies]\npython = \"3.10.*\"\n\n[tool.pixi.feature.py311.dependencies]\npython = \"3.11.*\"\n\n[tool.pixi.feature.py312.dependencies]\npython = \"3.12.*\"\n\n[tool.pixi.feature.py313.dependencies]\npython = \"3.13.*\"\n\n[tool.pixi.feature.test.dependencies]\n\n[tool.pixi.feature.test.tasks]\ntest = \"python -m unittest discover --failfast\"\n\n[tool.pixi.feature.lint.tasks]\nmypy = \"mypy qpsolvers --config-file=pyproject.toml\"\npylint = \"pylint qpsolvers --exit-zero --rcfile=pyproject.toml\"\nruff = \"ruff check qpsolvers && ruff format --check qpsolvers\"\nlint = { depends-on = [\"mypy\", \"pylint\", \"ruff\"] }\n\n[tool.pixi.feature.docs.tasks]\ndocs-build = \"sphinx-build doc _build -W\"\n\n[tool.pixi.environments]\ncoverage = { features = [\"py310\", \"coverage\", \"licensed\"], solve-group = \"py310\" }\ndocs = { features = [\"py310\", \"docs\"], solve-group = \"py310\" }\nlicensed = { features = [\"py310\", \"licensed\"], solve-group = \"py310\" }\nlint = { features = [\"py312\", \"lint\"], solve-group = \"py312\" }\ntest-py310 = { features = [\"py310\", \"test\"], solve-group = \"py310\" }\ntest-py311 = { features = [\"py311\", \"test\"], solve-group = \"py311\" }\ntest-py312 = { features = [\"py312\", \"test\"], solve-group = \"py312\" }\ntest-py313 = { features = [\"py313\", \"test\"], solve-group = \"py313\" }\n\n[tool.pylint.'MESSAGES CONTROL']\ndisable = [\n    \"C0103\",                          # Argument name doesn't conform to snake_case (we use uppercase matrices)\n    \"E0611\",                          # No name 'solve_qp' in module 'quadprog' (false positive)\n    \"E1130\",                          # Bad operand type for unary - (false positive, covered by mypy)\n    \"R0801\",                          # Similar lines in many files (functions share the same prototype)\n    \"R0902\",                          # Too many instance attributes (our QP class has 8 > 7)\n    \"R0913\",                          # Too many arguments (functions have > 5 args)\n    \"R0914\",                          # Too many local variables (functions often > 15 locals)\n    \"import-error\",                   # Suppress import‑error when optional back‑ends are missing\n    \"too-many-branches\",              # We accept the blame\n    \"too-many-positional-arguments\",  # We accept the blame\n    \"too-many-statements\",            # We accept the blame\n]\n\n[tool.pylint.'TYPECHECK']\ngenerated-members = [\n    \"clarabel.DefaultSettings\",\n    \"clarabel.DefaultSolver\",\n    \"clarabel.NonnegativeConeT\",\n    \"clarabel.SolverStatus\",\n    \"clarabel.ZeroConeT\",\n    \"coptpy.Envr\",\n    \"coptpy.EnvrConfig\",\n    \"coptpy.MConstr\",\n    \"coptpy.Model\",\n    \"coptpy.NdArray\",\n    \"daqp.solve\",\n    \"gurobipy.MConstr\",\n    \"gurobipy.Model\",\n    \"piqp.DenseSolver\",\n    \"piqp.PIQP_SOLVED\",\n    \"piqp.SparseSolver\",\n    \"piqp.__version__\",\n    \"proxsuite.proxqp\",\n    \"qpSWIFT.run\",\n    \"qpalm.Data\",\n    \"qpalm.Settings\",\n    \"qpalm.Solver\",\n    \"sip.ModelCallbackInput\",\n    \"sip.ModelCallbackOutput\",\n    \"sip.ProblemDimensions\",\n    \"sip.QDLDLSettings\",\n    \"sip.Settings\",\n    \"sip.Solver\",\n    \"sip.Status\",\n    \"sip.Variables\",\n]\n\n[tool.ruff]\nline-length = 79\n\n[tool.ruff.lint]\nignore = [\n    \"D401\",  # good for methods but not for class docstrings\n    \"D405\",  # British-style section names are also \"proper\"!\n]\nselect = [\n    # pyflakes\n    \"F\",\n    # pycodestyle\n    \"E\",\n    \"W\",\n    # isort\n    \"I001\",\n    # pydocstyle\n    \"D\"\n]\n\n[tool.ruff.lint.pydocstyle]\nconvention = \"numpy\"\n"
  },
  {
    "path": "qpsolvers/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Quadratic programming solvers in Python with a unified API.\"\"\"\n\nfrom .active_set import ActiveSet\nfrom .exceptions import (\n    NoSolverSelected,\n    ParamError,\n    ProblemError,\n    QPError,\n    SolverError,\n    SolverNotFound,\n)\nfrom .problem import Problem\nfrom .solution import Solution\nfrom .solve_ls import solve_ls\nfrom .solve_qp import solve_problem, solve_qp\nfrom .solve_unconstrained import solve_unconstrained\nfrom .solvers import (\n    available_solvers,\n    cvxopt_solve_qp,\n    daqp_solve_qp,\n    dense_solvers,\n    ecos_solve_qp,\n    gurobi_solve_qp,\n    highs_solve_qp,\n    hpipm_solve_qp,\n    kvxopt_solve_qp,\n    mosek_solve_qp,\n    nppro_solve_qp,\n    osqp_solve_qp,\n    pdhcg_solve_qp,\n    piqp_solve_qp,\n    proxqp_solve_qp,\n    pyqpmad_solve_qp,\n    qpalm_solve_qp,\n    qpoases_solve_qp,\n    qpswift_solve_qp,\n    qtqp_solve_qp,\n    quadprog_solve_qp,\n    scs_solve_qp,\n    sip_solve_qp,\n    sparse_solvers,\n)\nfrom .utils import print_matrix_vector\n\n__version__ = \"4.11.0\"\n\n__all__ = [\n    \"ActiveSet\",\n    \"NoSolverSelected\",\n    \"ParamError\",\n    \"Problem\",\n    \"ProblemError\",\n    \"QPError\",\n    \"Solution\",\n    \"SolverError\",\n    \"SolverNotFound\",\n    \"__version__\",\n    \"available_solvers\",\n    \"cvxopt_solve_qp\",\n    \"daqp_solve_qp\",\n    \"dense_solvers\",\n    \"ecos_solve_qp\",\n    \"gurobi_solve_qp\",\n    \"highs_solve_qp\",\n    \"hpipm_solve_qp\",\n    \"kvxopt_solve_qp\",\n    \"mosek_solve_qp\",\n    \"nppro_solve_qp\",\n    \"osqp_solve_qp\",\n    \"print_matrix_vector\",\n    \"pdhcg_solve_qp\",\n    \"piqp_solve_qp\",\n    \"proxqp_solve_qp\",\n    \"pyqpmad_solve_qp\",\n    \"qpalm_solve_qp\",\n    \"qpoases_solve_qp\",\n    \"qpswift_solve_qp\",\n    \"quadprog_solve_qp\",\n    \"qtqp_solve_qp\",\n    \"scs_solve_qp\",\n    \"sip_solve_qp\",\n    \"solve_ls\",\n    \"solve_problem\",\n    \"solve_qp\",\n    \"solve_unconstrained\",\n    \"sparse_solvers\",\n]\n"
  },
  {
    "path": "qpsolvers/active_set.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\"Active set: indices of inequality constraints saturated at the optimum.\"\"\"\n\nfrom dataclasses import dataclass\nfrom typing import Optional, Sequence\n\n\n@dataclass\nclass ActiveSet:\n    \"\"\"Indices of active inequality constraints.\n\n    Attributes\n    ----------\n    G_indices :\n        Indices of active linear inequality constraints.\n    lb_indices :\n        Indices of active lower-bound inequality constraints.\n    ub_indices :\n        Indices of active upper-bound inequality constraints.\n    \"\"\"\n\n    G_indices: Sequence[int]\n    lb_indices: Sequence[int]\n    ub_indices: Sequence[int]\n\n    def __init__(\n        self,\n        G_indices: Optional[Sequence[int]] = None,\n        lb_indices: Optional[Sequence[int]] = None,\n        ub_indices: Optional[Sequence[int]] = None,\n    ) -> None:\n        self.G_indices = list(G_indices) if G_indices is not None else []\n        self.lb_indices = list(lb_indices) if lb_indices is not None else []\n        self.ub_indices = list(ub_indices) if ub_indices is not None else []\n"
  },
  {
    "path": "qpsolvers/conversions/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Convert problems from and to standard QP form.\"\"\"\n\nfrom .combine_linear_box_inequalities import combine_linear_box_inequalities\nfrom .ensure_sparse_matrices import ensure_sparse_matrices\nfrom .linear_from_box_inequalities import linear_from_box_inequalities\nfrom .socp_from_qp import socp_from_qp\nfrom .split_dual_linear_box import split_dual_linear_box\n\n__all__ = [\n    \"combine_linear_box_inequalities\",\n    \"ensure_sparse_matrices\",\n    \"linear_from_box_inequalities\",\n    \"socp_from_qp\",\n    \"split_dual_linear_box\",\n]\n"
  },
  {
    "path": "qpsolvers/conversions/combine_linear_box_inequalities.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Combine linear and box inequalities into double-sided linear format.\"\"\"\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..exceptions import ProblemError\n\n\ndef combine_linear_box_inequalities(G, h, lb, ub, n: int, use_csc: bool):\n    r\"\"\"Combine linear and box inequalities into double-sided linear format.\n\n    Input format:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            G x & \\leq h \\\\\n            lb & \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    Output format:\n\n    .. math::\n\n        l \\leq C \\leq u\n\n    Parameters\n    ----------\n    G :\n        Linear inequality constraint matrix. Must be two-dimensional.\n    h :\n        Linear inequality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    n :\n        Number of optimization variables.\n    use_csc :\n        If ``True``, use sparse rather than dense matrices.\n\n    Returns\n    -------\n    :\n        Linear inequality matrix :math:`C` and vectors :math:`u`, :math:`l`.\n        The two vector will contain :math:`\\pm\\infty` values on coordinates\n        where there is no corresponding constraint.\n\n    Raises\n    ------\n    ProblemError\n        If the inequality matrix and vector are not consistent.\n    \"\"\"\n    if lb is None and ub is None:\n        C_out = G\n        u_out = h\n        l_out = np.full(h.shape, -np.inf) if h is not None else None\n    elif G is None:\n        # lb is not None or ub is not None:\n        C_out = spa.eye(n, format=\"csc\") if use_csc else np.eye(n)\n        u_out = ub if ub is not None else np.full(n, +np.inf)\n        l_out = lb if lb is not None else np.full(n, -np.inf)\n    elif h is not None:\n        # G is not None and h is not None and not (lb is None and ub is None)\n        C_out = (\n            spa.vstack((G, spa.eye(n)), format=\"csc\")\n            if use_csc\n            else np.vstack((G, np.eye(n)))\n        )\n        ub = ub if ub is not None else np.full(G.shape[1], +np.inf)\n        lb = lb if lb is not None else np.full(G.shape[1], -np.inf)\n        l_out = np.hstack((np.full(h.shape, -np.inf), lb))\n        u_out = np.hstack((h, ub))\n    else:  # G is not None and h is None\n        raise ProblemError(\"Inconsistent inequalities: G is set but h is None\")\n    return C_out, u_out, l_out\n"
  },
  {
    "path": "qpsolvers/conversions/ensure_sparse_matrices.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Make sure problem matrices are sparse.\"\"\"\n\nimport warnings\nfrom typing import Optional, Tuple, Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..warnings import SparseConversionWarning\n\n\ndef __warn_about_sparse_conversion(matrix_name: str, solver_name: str) -> None:\n    \"\"\"Warn about conversion from dense to sparse matrix.\n\n    Parameters\n    ----------\n    matrix_name :\n        Name of matrix being converted from dense to sparse.\n    solver_name :\n        Name of the QP solver matrices will be passed to.\n    \"\"\"\n    warnings.warn(\n        f\"Converted matrix '{matrix_name}' of your problem to \"\n        f\"scipy.sparse.csc_matrix to pass it to solver '{solver_name}'; \"\n        f\"for best performance, build your matrix as a csc_matrix directly.\",\n        category=SparseConversionWarning,\n    )\n\n\ndef ensure_sparse_matrices(\n    solver_name: str,\n    P: Union[np.ndarray, spa.csc_matrix],\n    G: Optional[Union[np.ndarray, spa.csc_matrix]],\n    A: Optional[Union[np.ndarray, spa.csc_matrix]],\n) -> Tuple[spa.csc_matrix, Optional[spa.csc_matrix], Optional[spa.csc_matrix]]:\n    \"\"\"\n    Make sure problem matrices are sparse.\n\n    Parameters\n    ----------\n    solver_name :\n        Name of the QP solver matrices will be passed to.\n    P :\n        Cost matrix.\n    G :\n        Inequality constraint matrix, if any.\n    A :\n        Equality constraint matrix, if any.\n\n    Returns\n    -------\n    :\n        Tuple of all three matrices as sparse matrices.\n    \"\"\"\n    if isinstance(P, np.ndarray):\n        __warn_about_sparse_conversion(\"P\", solver_name)\n        P = spa.csc_matrix(P)\n    if isinstance(G, np.ndarray):\n        __warn_about_sparse_conversion(\"G\", solver_name)\n        G = spa.csc_matrix(G)\n    if isinstance(A, np.ndarray):\n        __warn_about_sparse_conversion(\"A\", solver_name)\n        A = spa.csc_matrix(A)\n    return P, G, A\n"
  },
  {
    "path": "qpsolvers/conversions/linear_from_box_inequalities.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Functions to convert vector bounds into linear inequality constraints.\"\"\"\n\nfrom typing import Optional, Tuple, Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..exceptions import ProblemError\n\n\ndef concatenate_bound(\n    G: Optional[Union[np.ndarray, spa.csc_matrix, spa.dia_matrix]],\n    h: Optional[np.ndarray],\n    b: np.ndarray,\n    sign: float,\n    use_sparse: bool,\n) -> Tuple[Optional[Union[np.ndarray, spa.csc_matrix]], Optional[np.ndarray]]:\n    \"\"\"Append bound constraint vectors to inequality constraints.\n\n    Parameters\n    ----------\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    b :\n        Bound constraint vector.\n    sign :\n        Sign factor: -1.0 for a lower and +1.0 for an upper bound.\n    use_sparse :\n        Use sparse matrices if true, dense matrices otherwise.\n\n    Returns\n    -------\n    G :\n        Updated linear inequality matrix.\n    h :\n        Updated linear inequality vector.\n    \"\"\"\n    n = len(b)  # == number of optimization variables\n    if G is None or h is None:\n        G = sign * (spa.eye(n, format=\"csc\") if use_sparse else np.eye(n))\n        h = sign * b\n        return (G, h)\n\n    h = np.concatenate((h, sign * b))\n    if isinstance(G, np.ndarray):\n        dense_G: np.ndarray = np.concatenate((G, sign * np.eye(n)), 0)\n        return (dense_G, h)\n    if isinstance(G, (spa.csc_matrix, spa.dia_matrix)):\n        sparse_G: spa.csc_matrix = spa.vstack(\n            [G, sign * spa.eye(n)], format=\"csc\"\n        )\n        return (sparse_G, h)\n    # G is not an instance of a type we know\n    name = type(G).__name__\n    raise ProblemError(f\"invalid type '{name}' for inequality matrix G\")\n\n\ndef linear_from_box_inequalities(\n    G: Optional[Union[np.ndarray, spa.csc_matrix, spa.dia_matrix]],\n    h: Optional[np.ndarray],\n    lb: Optional[np.ndarray],\n    ub: Optional[np.ndarray],\n    use_sparse: bool,\n) -> Tuple[Optional[Union[np.ndarray, spa.csc_matrix]], Optional[np.ndarray]]:\n    \"\"\"Append lower or upper bound vectors to inequality constraints.\n\n    Parameters\n    ----------\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    use_sparse :\n        Use sparse matrices if true, dense matrices otherwise.\n\n    Returns\n    -------\n    G :\n        Updated linear inequality matrix.\n    h :\n        Updated linear inequality vector.\n    \"\"\"\n    if lb is not None:\n        G, h = concatenate_bound(G, h, lb, -1.0, use_sparse)\n    if ub is not None:\n        G, h = concatenate_bound(G, h, ub, +1.0, use_sparse)\n    if isinstance(G, spa.dia_matrix):  # corner case with no new box bound\n        return (spa.csc_matrix(G), h)\n    return (G, h)\n"
  },
  {
    "path": "qpsolvers/conversions/socp_from_qp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Convert quadratic programs to second-order cone programs.\"\"\"\n\nfrom typing import Any, Dict, Optional, Tuple, Union\n\nimport numpy as np\nfrom numpy import ndarray, sqrt\nfrom numpy.linalg import LinAlgError, cholesky\nfrom scipy.sparse import csc_matrix\n\nfrom ..exceptions import ProblemError\n\n\ndef socp_from_qp(\n    P: ndarray, q: ndarray, G: Optional[ndarray], h: Optional[ndarray]\n) -> Tuple[ndarray, csc_matrix, ndarray, Dict[str, Any]]:\n    r\"\"\"Convert a quadratic program to a second-order cone program.\n\n    The quadratic program is defined by:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h\n        \\end{array}\\end{split}\n\n    The equivalent second-order cone program is:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                c^T_s y \\\\\n            \\mbox{subject to}\n                & G_s y \\leq_{\\cal K} h_s\n        \\end{array}\\end{split}\n\n    This function is adapted from ``ecosqp.m`` in the `ecos-matlab\n    <https://github.com/embotech/ecos-matlab/>`_ repository. See the\n    documentation in that script for details on this reformulation.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n\n    Returns\n    -------\n    c_socp : array\n        SOCP cost vector.\n    G_socp : array\n        SOCP inequality matrix.\n    h_socp : array\n        SOCP inequality vector.\n    dims : dict\n        Dimension dictionary used by SOCP solvers, where ``dims[\"l\"]`` is the\n        number of inequality constraints.\n\n    Raises\n    ------\n    ValueError :\n        If the cost matrix is not positive definite.\n    \"\"\"\n    n = P.shape[1]  # dimension of QP variable\n    c_socp = np.hstack([np.zeros(n), 1])  # new SOCP variable stacked as [x, t]\n    try:\n        L = cholesky(P)\n    except LinAlgError as e:\n        error = str(e)\n        if \"not positive definite\" in error:\n            raise ProblemError(\"matrix P is not positive definite\") from e\n        raise e  # other linear algebraic error\n\n    scale = 1.0 / sqrt(2)\n    G_quad = np.vstack(\n        [\n            scale * np.hstack([q, -1.0]),\n            np.hstack([-L.T, np.zeros((L.shape[0], 1))]),\n            scale * np.hstack([-q, +1.0]),\n        ]\n    )\n    h_quad = np.hstack([scale, np.zeros(L.shape[0]), scale])\n\n    dims: Dict[str, Any] = {\"q\": [L.shape[0] + 2]}\n    G_socp: Union[ndarray, csc_matrix]\n    if G is not None and h is not None:\n        G_socp = np.vstack([np.hstack([G, np.zeros((G.shape[0], 1))]), G_quad])\n        h_socp = np.hstack([h, h_quad])\n        dims[\"l\"] = G.shape[0]\n    else:  # no linear inequality constraint\n        G_socp = G_quad\n        h_socp = h_quad\n        dims[\"l\"] = 0\n\n    G_socp = csc_matrix(G_socp)\n    return c_socp, G_socp, h_socp, dims\n"
  },
  {
    "path": "qpsolvers/conversions/split_dual_linear_box.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Convert stacked dual multipliers into linear and box multipliers.\"\"\"\n\nfrom typing import Optional, Tuple\n\nimport numpy as np\n\n\ndef split_dual_linear_box(\n    z_stacked: np.ndarray,\n    lb: Optional[np.ndarray],\n    ub: Optional[np.ndarray],\n) -> Tuple[Optional[np.ndarray], Optional[np.ndarray]]:\n    \"\"\"Separate linear and box multipliers from a stacked dual vector.\n\n    This function assumes linear and box inequalities were combined using\n    :func:`qpsolvers.conversions.linear_from_box_inequalities`.\n\n    Parameters\n    ----------\n    z_stacked :\n        Stacked vector of dual multipliers.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n\n    Returns\n    -------\n    :\n        Pair :code:`z, z_box` of linear and box multipliers. Both can be empty\n        arrays if there is no corresponding constraint.\n    \"\"\"\n    z = np.empty((0,), dtype=z_stacked.dtype)\n    z_box = np.empty((0,), dtype=z_stacked.dtype)\n    if lb is not None and ub is not None:\n        n_lb = lb.shape[0]\n        n_ub = ub.shape[0]\n        n_box = n_lb + n_ub\n        z_box = z_stacked[-n_ub:] - z_stacked[-n_box:-n_ub]\n        z = z_stacked[:-n_box]\n    elif ub is not None:  # lb is None\n        n_ub = ub.shape[0]\n        z_box = z_stacked[-n_ub:]\n        z = z_stacked[:-n_ub]\n    elif lb is not None:  # ub is None\n        n_lb = lb.shape[0]\n        z_box = -z_stacked[-n_lb:]\n        z = z_stacked[:-n_lb]\n    else:  # lb is None and ub is None\n        z = z_stacked\n    return z, z_box\n"
  },
  {
    "path": "qpsolvers/exceptions.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"\nExceptions from qpsolvers.\n\nWe catch all solver exceptions and re-throw them in a qpsolvers-owned exception\nto avoid abstraction leakage. See this `design decision\n<https://github.com/getparthenon/parthenon/wiki/Design-Decision:-Throw-Custom-Exceptions>`__\nfor more details on the rationale behind this choice.\n\"\"\"\n\n\nclass QPError(Exception):\n    \"\"\"Base class for qpsolvers exceptions.\"\"\"\n\n\nclass NoSolverSelected(QPError):\n    \"\"\"Exception raised when the `solver` keyword argument is not set.\"\"\"\n\n\nclass ParamError(QPError):\n    \"\"\"Exception raised when solver parameters are incorrect.\"\"\"\n\n\nclass ProblemError(QPError):\n    \"\"\"Exception raised when a quadratic program is malformed.\"\"\"\n\n\nclass SolverNotFound(QPError):\n    \"\"\"Exception raised when a requested solver is not found.\"\"\"\n\n\nclass SolverError(QPError):\n    \"\"\"Exception raised when a solver failed.\"\"\"\n"
  },
  {
    "path": "qpsolvers/problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Model for a quadratic program.\"\"\"\n\nfrom typing import List, Optional, Tuple, Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom .active_set import ActiveSet\nfrom .conversions import linear_from_box_inequalities\nfrom .exceptions import ParamError, ProblemError\n\n\nclass Problem:\n    r\"\"\"Data structure describing a quadratic program.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                    \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    This class provides sanity checks and metrics such as the condition number\n    of a problem.\n\n    Attributes\n    ----------\n    P :\n        Symmetric cost matrix (most solvers require it to be definite\n        as well).\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality matrix.\n    b :\n        Linear equality vector.\n    lb :\n        Lower bound constraint vector. Can contain ``-np.inf``.\n    ub :\n        Upper bound constraint vector. Can contain ``+np.inf``.\n    \"\"\"\n\n    P: Union[np.ndarray, spa.csc_matrix]\n    q: np.ndarray\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None\n    h: Optional[np.ndarray] = None\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None\n    b: Optional[np.ndarray] = None\n    lb: Optional[np.ndarray] = None\n    ub: Optional[np.ndarray] = None\n\n    @staticmethod\n    def __check_matrix(\n        M: Union[np.ndarray, spa.csc_matrix],\n    ) -> Union[np.ndarray, spa.csc_matrix]:\n        \"\"\"\n        Ensure a problem matrix has proper shape.\n\n        Parameters\n        ----------\n        M :\n            Problem matrix.\n        name :\n            Matrix name.\n\n        Returns\n        -------\n        :\n            Same matrix with proper shape.\n        \"\"\"\n        if hasattr(M, \"ndim\") and M.ndim == 1:  # type: ignore\n            M = M.reshape((1, M.shape[0]))  # type: ignore\n        return M\n\n    @staticmethod\n    def __check_vector(v: np.ndarray, name: str) -> np.ndarray:\n        \"\"\"\n        Ensure a problem vector has proper shape.\n\n        Parameters\n        ----------\n        M :\n            Problem matrix.\n        name :\n            Matrix name.\n\n        Returns\n        -------\n        :\n            Same matrix with proper shape.\n\n        Raises\n        ------\n        ProblemError\n            If the vector cannot be flattened.\n        \"\"\"\n        if v.ndim <= 1:\n            return v\n        if v.shape[0] != 1 and v.shape[1] != 1 or v.ndim > 2:\n            raise ProblemError(\n                f\"vector '{name}' should be flat \"\n                f\"and cannot be flattened as its shape is {v.shape}\"\n            )\n        return v.flatten()\n\n    def __init__(\n        self,\n        P: Union[np.ndarray, spa.csc_matrix],\n        q: np.ndarray,\n        G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n        h: Optional[np.ndarray] = None,\n        A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n        b: Optional[np.ndarray] = None,\n        lb: Optional[np.ndarray] = None,\n        ub: Optional[np.ndarray] = None,\n    ) -> None:\n        P = Problem.__check_matrix(P)\n        q = Problem.__check_vector(q, \"q\")\n        G = Problem.__check_matrix(G) if G is not None else None\n        h = Problem.__check_vector(h, \"h\") if h is not None else None\n        A = Problem.__check_matrix(A) if A is not None else None\n        b = Problem.__check_vector(b, \"b\") if b is not None else None\n        lb = Problem.__check_vector(lb, \"lb\") if lb is not None else None\n        ub = Problem.__check_vector(ub, \"ub\") if ub is not None else None\n        self.P = P\n        self.q = q\n        self.G = G\n        self.h = h\n        self.A = A\n        self.b = b\n        self.lb = lb\n        self.ub = ub\n\n    @property\n    def has_sparse(self) -> bool:\n        \"\"\"Check whether the problem has sparse matrices.\n\n        Returns\n        -------\n        :\n            True if at least one of the :math:`P`, :math:`G` or :math:`A`\n            matrices is sparse.\n        \"\"\"\n        sparse_types = (spa.csc_matrix, spa.dia_matrix)\n        return (\n            isinstance(self.P, sparse_types)\n            or isinstance(self.G, sparse_types)\n            or isinstance(self.A, sparse_types)\n        )\n\n    @property\n    def is_unconstrained(self) -> bool:\n        \"\"\"Check whether the problem has any constraint.\n\n        Returns\n        -------\n        :\n            True if the problem has at least one constraint.\n        \"\"\"\n        return (\n            self.G is None\n            and self.A is None\n            and self.lb is None\n            and self.ub is None\n        )\n\n    def unpack(\n        self,\n    ) -> Tuple[\n        Union[np.ndarray, spa.csc_matrix],\n        np.ndarray,\n        Optional[Union[np.ndarray, spa.csc_matrix]],\n        Optional[np.ndarray],\n        Optional[Union[np.ndarray, spa.csc_matrix]],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n    ]:\n        \"\"\"Get problem matrices as a tuple.\n\n        Returns\n        -------\n        :\n            Tuple ``(P, q, G, h, A, b, lb, ub)`` of problem matrices.\n        \"\"\"\n        return (\n            self.P,\n            self.q,\n            self.G,\n            self.h,\n            self.A,\n            self.b,\n            self.lb,\n            self.ub,\n        )\n\n    def unpack_as_dense(\n        self,\n    ) -> Tuple[\n        np.ndarray,\n        np.ndarray,\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n        Optional[np.ndarray],\n    ]:\n        \"\"\"Get problem matrices as a tuple of dense matrices and vectors.\n\n        Returns\n        -------\n        :\n            Tuple ``(P, q, G, h, A, b, lb, ub)`` of problem matrices.\n        \"\"\"\n        return (\n            self.P.toarray() if isinstance(self.P, spa.csc_matrix) else self.P,\n            self.q,\n            self.G.toarray() if isinstance(self.G, spa.csc_matrix) else self.G,\n            self.h,\n            self.A.toarray() if isinstance(self.A, spa.csc_matrix) else self.A,\n            self.b,\n            self.lb,\n            self.ub,\n        )\n\n    def check_constraints(self):\n        \"\"\"Check that problem constraints are properly specified.\n\n        Raises\n        ------\n        ProblemError\n            If the constraints are not properly defined.\n        \"\"\"\n        if self.G is None and self.h is not None:\n            raise ProblemError(\"incomplete inequality constraint (missing h)\")\n        if self.G is not None and self.h is None:\n            raise ProblemError(\"incomplete inequality constraint (missing G)\")\n        if self.A is None and self.b is not None:\n            raise ProblemError(\"incomplete equality constraint (missing b)\")\n        if self.A is not None and self.b is None:\n            raise ProblemError(\"incomplete equality constraint (missing A)\")\n\n    def __get_active_inequalities(\n        self, active_set: ActiveSet\n    ) -> Optional[Union[np.ndarray, spa.csc_matrix]]:\n        r\"\"\"Combine active linear and box inequalities into a single matrix.\n\n        Parameters\n        ----------\n        active_set :\n            Active set to evaluate the condition number with. It should contain\n            the set of active constraints at the optimum of the problem.\n\n        Returns\n        -------\n        :\n            Combined matrix of active inequalities.\n        \"\"\"\n        G_full, _ = linear_from_box_inequalities(\n            self.G, self.h, self.lb, self.ub, use_sparse=False\n        )\n        if G_full is None:\n            return None\n        indices: List[int] = []\n        offset: int = 0\n        if self.h is not None:\n            indices.extend(active_set.G_indices)\n            offset += self.h.size\n        if self.lb is not None:\n            indices.extend(offset + i for i in active_set.lb_indices)\n            offset += self.lb.size\n        if self.ub is not None:\n            indices.extend(offset + i for i in active_set.ub_indices)\n        G_active = G_full[indices]\n        return G_active\n\n    def cond(self, active_set: ActiveSet) -> float:\n        r\"\"\"Condition number of the problem matrix.\n\n        Compute the condition number of the symmetric matrix representing the\n        problem data:\n\n        .. math::\n\n            M =\n            \\begin{bmatrix}\n                P & G_{act}^T & A_{act}^T \\\\\n                G_{act} & 0 & 0 \\\\\n                A_{act} & 0 & 0\n            \\end{bmatrix}\n\n        where :math:`G_{act}` and :math:`A_{act}` denote the active inequality\n        and equality constraints at the optimum of the problem.\n\n        Parameters\n        ----------\n        active_set :\n            Active set to evaluate the condition number with. It should contain\n            the set of active constraints at the optimum of the problem.\n\n        Returns\n        -------\n        :\n            Condition number of the problem.\n\n        Raises\n        ------\n        ProblemError :\n            If the problem is sparse.\n\n        Notes\n        -----\n        Having a low condition number (say, less than 1e10) condition number is\n        strongly tied to the capacity of numerical solvers to solve a problem.\n        This is the motivation for preconditioning, as detailed for instance in\n        Section 5 of [Stellato2020]_.\n        \"\"\"\n        if self.has_sparse:\n            raise ProblemError(\"This function is for dense problems only\")\n        if active_set.lb_indices and self.lb is None:\n            raise ProblemError(\"Lower bound in active set but not in problem\")\n        if active_set.ub_indices and self.ub is None:\n            raise ProblemError(\"Upper bound in active set but not in problem\")\n\n        P: np.ndarray = (\n            self.P.toarray() if isinstance(self.P, spa.csc_matrix) else self.P\n        )\n        G_active_full = self.__get_active_inequalities(active_set)\n        G_active: Optional[np.ndarray] = (\n            G_active_full.toarray()\n            if isinstance(G_active_full, spa.csc_matrix)\n            else G_active_full\n        )\n        A: Optional[np.ndarray] = (\n            self.A.toarray() if isinstance(self.A, spa.csc_matrix) else self.A\n        )\n        n_G = G_active.shape[0] if G_active is not None else 0\n        n_A = A.shape[0] if A is not None else 0\n        if G_active is not None and A is not None:\n            M = np.vstack(\n                [\n                    np.hstack([P, G_active.T, A.T]),\n                    np.hstack(\n                        [\n                            G_active,\n                            np.zeros((n_G, n_G)),\n                            np.zeros((n_G, n_A)),\n                        ]\n                    ),\n                    np.hstack(\n                        [\n                            A,\n                            np.zeros((n_A, n_G)),\n                            np.zeros((n_A, n_A)),\n                        ]\n                    ),\n                ]\n            )\n        elif G_active is not None:\n            M = np.vstack(\n                [\n                    np.hstack([P, G_active.T]),\n                    np.hstack([G_active, np.zeros((n_G, n_G))]),\n                ]\n            )\n        elif A is not None:\n            M = np.vstack(\n                [\n                    np.hstack([P, A.T]),\n                    np.hstack([A, np.zeros((n_A, n_A))]),\n                ]\n            )\n        else:  # G_active is None and A is None\n            M = P\n        return np.linalg.cond(M)\n\n    def save(self, file: str) -> None:\n        \"\"\"Save problem to a compressed NumPy file.\n\n        Parameters\n        ----------\n        file : str or file\n            Either the filename (string) or an open file (file-like object)\n            where the data will be saved. If file is a string or a Path, the\n            ``.npz`` extension will be appended to the filename if it is not\n            already there.\n        \"\"\"\n        np.savez(\n            file,\n            P=(\n                self.P.toarray()\n                if isinstance(self.P, spa.csc_matrix)\n                else self.P\n            ),\n            q=self.q,\n            G=np.array(self.G),\n            h=np.array(self.h),\n            A=np.array(self.A),\n            b=np.array(self.b),\n            lb=np.array(self.lb),\n            ub=np.array(self.ub),\n        )\n\n    @staticmethod\n    def load(file: str):\n        \"\"\"Load problem from a NumPy file.\n\n        Parameters\n        ----------\n        file : file-like object, string, or pathlib.Path\n            The file to read. File-like objects must support the\n            ``seek()`` and ``read()`` methods and must always\n            be opened in binary mode.  Pickled files require that the\n            file-like object support the ``readline()`` method as well.\n        \"\"\"\n        problem_data = np.load(file, allow_pickle=False)\n\n        def load_optional(key):\n            try:\n                return problem_data[key]\n            except ValueError:\n                return None\n\n        return Problem(\n            P=load_optional(\"P\"),\n            q=load_optional(\"q\"),\n            G=load_optional(\"G\"),\n            h=load_optional(\"h\"),\n            A=load_optional(\"A\"),\n            b=load_optional(\"b\"),\n            lb=load_optional(\"lb\"),\n            ub=load_optional(\"ub\"),\n        )\n\n    def get_cute_classification(self, interest: str) -> str:\n        \"\"\"Get the CUTE classification string of the problem.\n\n        Parameters\n        ----------\n        interest:\n            Either 'A', 'M' or 'R': 'A' if the problem is academic, that is,\n            has been constructed specifically by researchers to test one or\n            more algorithms; 'M' if the problem is part of a modelling exercise\n            where the actual value of the solution is not used in a genuine\n            practical application; and 'R' if the problem's solution is (or has\n            been) actually used in a real application for purposes other than\n            testing algorithms.\n\n        Returns\n        -------\n        :\n            CUTE classification string of the problem\n\n        Notes\n        -----\n        Check out the `CUTE classification scheme\n        <https://www.cuter.rl.ac.uk//Problems/classification.shtml>`__ for\n        details.\n        \"\"\"\n        if interest not in (\"A\", \"M\", \"R\"):\n            raise ParamError(f\"interest '{interest}' not in 'A', 'M' or 'R'\")\n        nb_var = self.P.shape[0]\n        nb_cons = 0\n        if self.G is not None:\n            nb_cons += self.G.shape[0]\n        if self.A is not None:\n            nb_cons += self.A.shape[0]\n        # NB: we don't cound bounds as constraints in this classification\n        return f\"QLR2-{interest}N-{nb_var}-{nb_cons}\"\n"
  },
  {
    "path": "qpsolvers/problems.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Collection of sample problems.\"\"\"\n\nfrom typing import Tuple\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom .problem import Problem\nfrom .solution import Solution\n\n\ndef get_sparse_least_squares(n):\n    \"\"\"\n    Get a sparse least squares problem.\n\n    Parameters\n    ----------\n    n :\n        Problem size.\n\n    Notes\n    -----\n    This problem was inspired by `this question on Stack Overflow\n    <https://stackoverflow.com/q/73656257/3721564>`__.\n    \"\"\"\n    # minimize 1/2 || x - s ||^2\n    R = spa.eye(n, format=\"csc\")\n    s = np.array(range(n), dtype=float)\n\n    # such that G * x <= h\n    G = spa.diags(\n        diagonals=[\n            [1.0 if i % 2 == 0 else 0.0 for i in range(n)],\n            [1.0 if i % 3 == 0 else 0.0 for i in range(n - 1)],\n            [1.0 if i % 5 == 0 else 0.0 for i in range(n - 1)],\n        ],\n        offsets=[0, 1, -1],\n        format=\"csc\",\n    )\n    h = np.ones(G.shape[0])\n\n    # such that sum(x) == 42\n    A = spa.csc_matrix(np.ones((1, n)))\n    b = np.array([42.0]).reshape((1,))\n\n    # such that x >= 0\n    lb = np.zeros(n)\n    ub = None\n\n    return R, s, G, h, A, b, lb, ub\n\n\ndef get_qpsut01() -> Tuple[Problem, Solution]:\n    \"\"\"Get QPSUT01 problem and its solution.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n    P = np.dot(M.T, M)  # this is a positive definite matrix\n    q = np.dot(np.array([3.0, 2.0, 3.0]), M)\n    G = np.array([[4.0, 2.0, 0.0], [-1.0, 2.0, -1.0]])\n    h = np.array([1.0, -2.0])\n    A = np.array([1.0, 1.0, 1.0]).reshape((1, 3))\n    b = np.array([1.0])\n    lb = np.array([-0.5, -0.4, -0.5])\n    ub = np.array([1.0, 1.0, 1.0])\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = np.array([0.4, -0.4, 1.0])\n    solution.z = np.array([0.0, 0.0])\n    solution.y = np.array([-5.8])\n    solution.z_box = np.array([0.0, -1.8, 3.0])\n    return problem, solution\n\n\ndef get_qpsut02() -> Tuple[Problem, Solution]:\n    \"\"\"Get QPSUT02 problem and its solution.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    M = np.array(\n        [\n            [1.0, -2.0, 0.0, 8.0],\n            [-6.0, 3.0, 1.0, 4.0],\n            [-2.0, 1.0, 0.0, 1.0],\n            [9.0, 9.0, 5.0, 3.0],\n        ]\n    )\n    P = np.dot(M.T, M)  # this is a positive definite matrix\n    q = np.dot(np.array([-3.0, 2.0, 0.0, 9.0]), M)\n    G = np.array(\n        [\n            [4.0, 7.0, 0.0, -2.0],\n        ]\n    )\n    h = np.array([30.0])\n    A = np.array(\n        [\n            [1.0, 1.0, 1.0, 1.0],\n            [1.0, -1.0, -1.0, 1.0],\n        ]\n    )\n    b = np.array([10.0, 0.0])\n    lb = np.array([-2.0, -1.0, -3.0, 1.0])\n    ub = np.array([4.0, 2.0, 6.0, 10.0])\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = np.array([1.36597938, -1.0, 6.0, 3.63402062])\n    solution.z = np.array([0.0])\n    solution.y = np.array([-377.60314303, -62.75251185])  # YMMV\n    solution.z_box = np.array([0.0, -138.9585918, 37.53106937, 0.0])  # YMMV\n    return problem, solution\n\n\ndef get_qpsut03() -> Tuple[Problem, Solution]:\n    \"\"\"Get QPSUT03 problem and its solution.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n\n    Notes\n    -----\n    This problem has partial box bounds, that is, -infinity on some lower\n    bounds and +infinity on some upper bounds.\n    \"\"\"\n    M = np.array(\n        [\n            [1.0, -2.0, 0.0, 8.0],\n            [-6.0, 3.0, 1.0, 4.0],\n            [-2.0, 1.0, 0.0, 1.0],\n            [9.0, 9.0, 5.0, 3.0],\n        ]\n    )\n    P = np.dot(M.T, M)  # this is a positive definite matrix\n    q = np.dot(np.array([-3.0, 2.0, 0.0, 9.0]), M)\n    G = None\n    h = None\n    A = None\n    b = None\n    lb = np.array([-np.inf, -0.4, -np.inf, -1.0])\n    ub = np.array([np.inf, np.inf, 0.5, 1.0])\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = np.array([0.18143455, 0.00843864, -2.35442995, 0.35443034])\n    solution.z = np.array([])\n    solution.y = np.array([])\n    solution.z_box = np.array([0.0, 0.0, 0.0, 0.0])\n    return problem, solution\n\n\ndef get_qpsut04() -> Tuple[Problem, Solution]:\n    \"\"\"Get QPSUT04 problem and its solution.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    n = 3\n    P = np.eye(n)\n    q = 0.01 * np.ones(shape=(n, 1))  # non-flat vector\n    G = np.eye(n)\n    h = np.ones(shape=(n,))\n    A = np.ones(shape=(n,))\n    b = np.ones(shape=(1,))\n    problem = Problem(P, q, G, h, A, b)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = 1.0 / 3.0 * np.ones(n)\n    solution.y = np.array([1.0 / 3.0 + 0.01])\n    solution.z = np.zeros(n)\n    return problem, solution\n\n\ndef get_qpsut05() -> Tuple[Problem, Solution]:\n    \"\"\"Get QPSUT05 problem and its solution.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    P = np.array([2.0])\n    q = np.array([-2.0])\n    problem = Problem(P, q)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = np.array([1.0])\n    return problem, solution\n\n\ndef get_qptest():\n    \"\"\"Get QPTEST problem from the Maros-Meszaros test set.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    P = np.array([[8.0, 2.0], [2.0, 10.0]])\n    q = np.array([1.5, -2.0])\n    G = np.array([[-1.0, 2.0], [-2.0, -1.0]])\n    h = np.array([6.0, -2.0])\n    lb = np.array([0.0, 0.0])\n    ub = np.array([20.0, np.inf])\n    problem = Problem(P, q, G, h, lb=lb, ub=ub)\n\n    solution = Solution(problem)\n    solution.found = True\n    solution.x = np.array([0.7625, 0.475])\n    solution.z = np.array([0.0, 4.275])\n    solution.z_box = np.array([0.0, 0.0])\n    return problem, solution\n\n\ndef get_qpgurdu():\n    \"\"\"Get sample random problem with linear inequality constraints.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    P = np.array(\n        [\n            [\n                3.57211988,\n                3.04767485,\n                2.81378189,\n                3.10290601,\n                3.70204698,\n                3.21624815,\n                3.07738552,\n                2.97880055,\n                2.87282375,\n                3.13101137,\n            ],\n            [\n                3.04767485,\n                3.29764869,\n                2.96655517,\n                2.99532101,\n                3.27631229,\n                2.95993532,\n                3.36890754,\n                3.41940015,\n                2.71055468,\n                3.48874903,\n            ],\n            [\n                2.81378189,\n                2.96655517,\n                4.07209512,\n                3.15291684,\n                3.25120445,\n                3.16570711,\n                3.29693401,\n                3.57945021,\n                2.38634372,\n                3.56010605,\n            ],\n            [\n                3.10290601,\n                2.99532101,\n                3.15291684,\n                4.18950328,\n                3.80236382,\n                3.30578443,\n                3.86461151,\n                3.73403774,\n                2.65103423,\n                3.6915013,\n            ],\n            [\n                3.70204698,\n                3.27631229,\n                3.25120445,\n                3.80236382,\n                4.49927773,\n                3.71882781,\n                3.72242148,\n                3.36633929,\n                3.07400851,\n                3.44904275,\n            ],\n            [\n                3.21624815,\n                2.95993532,\n                3.16570711,\n                3.30578443,\n                3.71882781,\n                3.54009378,\n                3.3619341,\n                3.45111777,\n                2.52760157,\n                3.47292034,\n            ],\n            [\n                3.07738552,\n                3.36890754,\n                3.29693401,\n                3.86461151,\n                3.72242148,\n                3.3619341,\n                4.18766506,\n                3.9158527,\n                2.73687599,\n                3.94376429,\n            ],\n            [\n                2.97880055,\n                3.41940015,\n                3.57945021,\n                3.73403774,\n                3.36633929,\n                3.45111777,\n                3.9158527,\n                4.4180459,\n                2.50596495,\n                4.25387869,\n            ],\n            [\n                2.87282375,\n                2.71055468,\n                2.38634372,\n                2.65103423,\n                3.07400851,\n                2.52760157,\n                2.73687599,\n                2.50596495,\n                2.74656049,\n                2.54212279,\n            ],\n            [\n                3.13101137,\n                3.48874903,\n                3.56010605,\n                3.6915013,\n                3.44904275,\n                3.47292034,\n                3.94376429,\n                4.25387869,\n                2.54212279,\n                4.634129,\n            ],\n        ]\n    )\n    q = np.array(\n        [\n            [0.49318579],\n            [0.82113304],\n            [0.67851692],\n            [0.34081485],\n            [0.14826526],\n            [0.81974126],\n            [0.41957706],\n            [0.53118637],\n            [0.59189664],\n            [0.98775649],\n        ]\n    )\n    G = np.array(\n        [\n            [\n                4.38410058e-01,\n                4.43204832e-01,\n                3.01827071e-01,\n                5.77725615e-02,\n                8.04962962e-01,\n                6.13555163e-01,\n                1.15255766e-01,\n                7.11331164e-01,\n                7.71820534e-02,\n                3.86074035e-01,\n            ],\n            [\n                8.47645982e-01,\n                9.37475356e-01,\n                3.54726656e-01,\n                9.64635375e-01,\n                5.95008737e-01,\n                4.65424573e-01,\n                3.60529910e-01,\n                5.83149169e-01,\n                5.51353698e-01,\n                8.45823800e-01,\n            ],\n            [\n                2.29674075e-04,\n                5.54870256e-02,\n                7.83869376e-01,\n                9.97727284e-01,\n                1.49512389e-01,\n                7.44775614e-01,\n                8.76446593e-02,\n                2.57348591e-01,\n                7.28916655e-01,\n                5.97511590e-01,\n            ],\n            [\n                6.92184129e-01,\n                9.04600884e-01,\n                7.57700115e-01,\n                7.76548565e-01,\n                5.31039749e-01,\n                8.32203998e-01,\n                4.27810742e-01,\n                1.92236814e-01,\n                2.91129478e-01,\n                7.76195308e-01,\n            ],\n            [\n                4.73333212e-01,\n                3.02129792e-02,\n                6.86517354e-01,\n                5.08992776e-01,\n                8.43205462e-01,\n                6.30402967e-01,\n                7.92221172e-01,\n                3.67768984e-01,\n                1.10864990e-01,\n                5.44828940e-01,\n            ],\n            [\n                9.23060980e-01,\n                4.55743966e-01,\n                4.81958856e-02,\n                5.47614699e-02,\n                8.23194952e-01,\n                2.40526659e-01,\n                9.33519842e-01,\n                5.40430172e-01,\n                6.27229337e-01,\n                4.27829243e-01,\n            ],\n            [\n                2.39454128e-01,\n                1.29688157e-01,\n                7.64521599e-01,\n                2.66943061e-01,\n                4.94990723e-01,\n                3.87798160e-01,\n                5.76282838e-01,\n                8.87340479e-01,\n                5.49439650e-01,\n                2.99596520e-01,\n            ],\n            [\n                3.73174589e-02,\n                4.08407618e-01,\n                1.19009418e-01,\n                3.02572289e-02,\n                1.90287316e-01,\n                2.93975786e-01,\n                7.65243508e-01,\n                8.64670246e-02,\n                3.90593097e-01,\n                1.33870683e-01,\n            ],\n            [\n                9.10093385e-01,\n                9.63382642e-02,\n                2.94162739e-01,\n                9.71178995e-01,\n                1.81811460e-01,\n                9.69904715e-02,\n                4.10693806e-01,\n                7.56873549e-01,\n                2.36595007e-01,\n                3.19756491e-01,\n            ],\n            [\n                8.58362518e-02,\n                7.88161645e-02,\n                9.67300428e-01,\n                2.59894669e-01,\n                1.62774911e-01,\n                3.33859109e-01,\n                6.15307748e-01,\n                1.81164951e-02,\n                5.99620503e-01,\n                5.71512979e-01,\n            ],\n        ]\n    )\n    h = np.array(\n        [\n            [4.94957567],\n            [7.50577326],\n            [5.40302286],\n            [7.18164978],\n            [5.98834884],\n            [6.07449251],\n            [5.59605532],\n            [3.45542914],\n            [5.27449417],\n            [4.69303926],\n        ]\n    )\n    problem = Problem(P, q, G, h)\n\n    solution = Solution(problem)\n    solution.found = True\n    return problem, solution\n\n\ndef get_qpgurabs():\n    \"\"\"Get sample random problem with box constraints.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    qpgurdu, _ = get_qpgurdu()\n    box = np.abs(qpgurdu.h)\n    problem = Problem(qpgurdu.P, qpgurdu.q, lb=-box, ub=+box)\n\n    solution = Solution(problem)\n    solution.found = True\n    return problem, solution\n\n\ndef get_qpgureq():\n    \"\"\"Get sample random problem with equality constraints.\n\n    Returns\n    -------\n    :\n        Problem-solution pair.\n    \"\"\"\n    qpgurdu, _ = get_qpgurdu()\n    A = qpgurdu.G\n    b = 0.1 * qpgurdu.h\n    problem = Problem(qpgurdu.P, qpgurdu.q, A=A, b=b)\n\n    solution = Solution(problem)\n    solution.found = True\n    return problem, solution\n"
  },
  {
    "path": "qpsolvers/py.typed",
    "content": ""
  },
  {
    "path": "qpsolvers/solution.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Output from a QP solver.\"\"\"\n\nfrom dataclasses import dataclass, field\nfrom typing import Optional\n\nimport numpy as np\n\nfrom .problem import Problem\n\n\n@dataclass(frozen=False)\nclass Solution:\n    \"\"\"Solution returned by a QP solver for a given problem.\n\n    Attributes\n    ----------\n    extras :\n        Other outputs, specific to each solver.\n\n    found :\n        True if the solution was found successfully by a solver, False if the\n        solver did not find a solution or detected an unfeasible problem,\n        ``None`` if no solver was run.\n\n    problem :\n        Quadratic program the solution corresponds to.\n\n    obj :\n        Value of the primal objective at the solution (``None`` if no solution\n        was found).\n\n    x :\n        Solution vector for the primal quadratic program (``None`` if no\n        solution was found).\n\n    y :\n        Dual multipliers for equality constraints (``None`` if no solution was\n        found, or if there is no equality constraint). The dimension of\n        :math:`y` is equal to the number of equality constraints. The values\n        :math:`y_i` can be either positive or negative.\n\n    z :\n        Dual multipliers for linear inequality constraints (``None`` if no\n        solution was found, or if there is no inequality constraint). The\n        dimension of :math:`z` is equal to the number of inequalities. The\n        value :math:`z_i` for inequality :math:`i` is always positive.\n\n        - If :math:`z_i > 0`, the inequality is active at the solution:\n          :math:`G_i x = h_i`.\n        - If :math:`z_i = 0`, the inequality is inactive at the solution:\n          :math:`G_i x < h_i`.\n\n    z_box :\n        Dual multipliers for box inequality constraints (``None`` if no\n        solution was found, or if there is no box inequality). The sign of\n        :math:`z_{box,i}` depends on the active bound:\n\n        - If :math:`z_{box,i} < 0`, then the lower bound :math:`lb_i = x_i` is\n          active at the solution.\n        - If :math:`z_{box,i} = 0`, then neither the lower nor the upper bound\n          are active and :math:`lb_i < x_i < ub_i`.\n        - If :math:`z_{box,i} > 0`, then the upper bound :math:`x_i = ub_i` is\n          active at the solution.\n\n    build_time :\n        Time taken, during the call to `solve_problem`, to build problem\n        matrices in the QP solver's expected format.\n\n    solve_time :\n        Time taken, during the call to `solve_problem`, to call the QP solver\n        itself.\n    \"\"\"\n\n    problem: Problem\n    extras: dict = field(default_factory=dict)\n    found: Optional[bool] = None\n    obj: Optional[float] = None\n    x: Optional[np.ndarray] = None\n    y: Optional[np.ndarray] = None\n    z: Optional[np.ndarray] = None\n    z_box: Optional[np.ndarray] = None\n    build_time: Optional[float] = None\n    solve_time: Optional[float] = None\n\n    def is_optimal(self, eps_abs: float) -> bool:\n        \"\"\"Check whether the solution is indeed optimal.\n\n        Parameters\n        ----------\n        eps_abs :\n            Absolute tolerance for the primal residual, dual residual and\n            duality gap.\n\n        Notes\n        -----\n        See for instance [Caron2022]_ for an overview of optimality conditions\n        in quadratic programming.\n        \"\"\"\n        return (\n            self.primal_residual() < eps_abs\n            and self.dual_residual() < eps_abs\n            and self.duality_gap() < eps_abs\n        )\n\n    def primal_residual(self) -> float:\n        r\"\"\"Compute the primal residual of the solution.\n\n        The primal residual is:\n\n        .. math::\n\n            r_p := \\max(\\| A x - b \\|_\\infty, [G x - h]^+,\n            [lb - x]^+, [x - ub]^+)\n\n        were :math:`v^- = \\min(v, 0)` and :math:`v^+ = \\max(v, 0)`.\n\n        Returns\n        -------\n        :\n            Primal residual if it is defined, ``np.inf`` otherwise.\n\n        Notes\n        -----\n        See for instance [Caron2022]_ for an overview of optimality conditions\n        and why this residual will be zero at the optimum.\n        \"\"\"\n        _, _, G, h, A, b, lb, ub = self.problem.unpack()\n        if not self.found or self.x is None:\n            return np.inf\n        x = self.x\n        return max(\n            [\n                0.0,\n                np.max(G.dot(x) - h) if G is not None else 0.0,\n                np.max(np.abs(A.dot(x) - b)) if A is not None else 0.0,\n                np.max(lb - x) if lb is not None else 0.0,\n                np.max(x - ub) if ub is not None else 0.0,\n            ]\n        )\n\n    def dual_residual(self) -> float:\n        r\"\"\"Compute the dual residual of the solution.\n\n        The dual residual is:\n\n        .. math::\n\n            r_d := \\| P x + q + A^T y + G^T z + z_{box} \\|_\\infty\n\n        Returns\n        -------\n        :\n            Dual residual if it is defined, ``np.inf`` otherwise.\n\n        Notes\n        -----\n        See for instance [Caron2022]_ for an overview of optimality conditions\n        and why this residual will be zero at the optimum.\n        \"\"\"\n        P, q, G, _, A, _, lb, ub = self.problem.unpack()\n        if not self.found or self.x is None:\n            return np.inf\n        zeros = np.zeros(self.x.shape)\n        Px = P.dot(self.x)\n\n        ATy = zeros\n        if A is not None:\n            if self.y is None:\n                return np.inf\n            ATy = A.T.dot(self.y)\n\n        GTz = zeros\n        if G is not None:\n            if self.z is None:\n                return np.inf\n            GTz = G.T.dot(self.z)\n\n        z_box = zeros\n        if lb is not None or ub is not None:\n            if self.z_box is None:\n                return np.inf\n            z_box = self.z_box\n\n        p = np.linalg.norm(Px + q + GTz + ATy + z_box, np.inf)\n        return p  # type: ignore\n\n    def duality_gap(self) -> float:\n        r\"\"\"Compute the duality gap of the solution.\n\n        The duality gap is:\n\n        .. math::\n\n            r_g := | x^T P x + q^T x + b^T y + h^T z +\n            lb^T z_{box}^- + ub^T z_{box}^+ |\n\n        were :math:`v^- = \\min(v, 0)` and :math:`v^+ = \\max(v, 0)`.\n\n        Returns\n        -------\n        :\n            Duality gap if it is defined, ``np.inf`` otherwise.\n\n        Notes\n        -----\n        See for instance [Caron2022]_ for an overview of optimality conditions\n        and why this gap will be zero at the optimum.\n        \"\"\"\n        P, q, _, h, _, b, lb, ub = self.problem.unpack()\n        if not self.found or self.x is None:\n            return np.inf\n        xPx = self.x.T.dot(P.dot(self.x))\n        qx = q.dot(self.x)\n\n        hz = 0.0\n        if h is not None:\n            if self.z is None:\n                return np.inf\n            hz = h.dot(self.z)\n\n        by = 0.0\n        if b is not None:\n            if self.y is None:\n                return np.inf\n            by = b.dot(self.y)\n\n        lb_z_box = 0.0\n        ub_z_box = 0.0\n        if self.z_box is not None:\n            if lb is not None:\n                finite = np.asarray(lb != -np.inf).nonzero()\n                z_box_neg = np.minimum(self.z_box, 0.0)\n                lb_z_box = lb[finite].dot(z_box_neg[finite])\n            if ub is not None:\n                finite = np.asarray(ub != np.inf).nonzero()\n                z_box_pos = np.maximum(self.z_box, 0.0)\n                ub_z_box = ub[finite].dot(z_box_pos[finite])\n        return abs(xPx + qx + hz + by + lb_z_box + ub_z_box)\n"
  },
  {
    "path": "qpsolvers/solve_ls.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solve linear least squares.\"\"\"\n\nfrom typing import Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom .problem import Problem\nfrom .solve_qp import solve_qp\n\n\ndef __solve_dense_ls(\n    R: Union[np.ndarray, spa.csc_matrix],\n    s: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    WR: Union[np.ndarray, spa.csc_matrix] = (\n        R if W is None else W @ R  # type: ignore[assignment]\n    )\n    P_: Union[np.ndarray, spa.csc_matrix] = (\n        R.T @ WR  # type: ignore[assignment]\n    )\n    P: Union[np.ndarray, spa.csc_matrix] = (\n        P_ if isinstance(P_, np.ndarray) else P_.tocsc()\n    )\n    q = -(s.T @ WR)\n    return solve_qp(\n        P,\n        q,\n        G,\n        h,\n        A,\n        b,\n        lb,\n        ub,\n        solver=solver,\n        initvals=initvals,\n        verbose=verbose,\n        **kwargs,\n    )\n\n\ndef __solve_sparse_ls(\n    R: Union[np.ndarray, spa.csc_matrix],\n    s: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    m, n = R.shape\n    eye_m = spa.eye(m, format=\"csc\")\n    q = np.zeros(n + m)\n\n    # We know the RHS of this assignment is CSC from the format kwarg\n    P_: spa.csc_matrix = spa.block_diag(  # type: ignore[assignment]\n        [spa.csc_matrix((n, n)), eye_m if W is None else W],\n        format=\"csc\",\n    )\n\n    P, q, G, h, A, b, lb, ub = Problem(P_, q, G, h, A, b, lb, ub).unpack()\n    if G is not None:\n        G = spa.hstack(  # type: ignore[call-overload]\n            [G, spa.csc_matrix((G.shape[0], m))],\n            format=\"csc\",\n        )\n    if A is not None:\n        A = spa.hstack(  # type: ignore[call-overload]\n            [A, spa.csc_matrix((A.shape[0], m))],\n            format=\"csc\",\n        )\n    Rx_minus_y = spa.hstack(  # type: ignore[call-overload]\n        [R, -eye_m],\n        format=\"csc\",\n    )\n    if A is not None and b is not None:  # help mypy\n        A = spa.vstack(  # type: ignore[call-overload]\n            [A, Rx_minus_y],\n            format=\"csc\",\n        )\n        b = np.hstack([b, s])\n    else:  # no input equality constraint\n        A = Rx_minus_y\n        b = s\n    if lb is not None:\n        lb = np.hstack([lb, np.full((m,), -np.inf)])\n    if ub is not None:\n        ub = np.hstack([ub, np.full((m,), np.inf)])\n\n    xy = solve_qp(\n        P,\n        q,\n        G,\n        h,\n        A,\n        b,\n        lb,\n        ub,\n        solver=solver,\n        initvals=initvals,\n        verbose=verbose,\n        **kwargs,\n    )\n\n    return xy[:n] if xy is not None else None\n\n\ndef solve_ls(\n    R: Union[np.ndarray, spa.csc_matrix],\n    s: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    W: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    sparse_conversion: Optional[bool] = None,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a constrained weighted linear Least Squares problem.\n\n    The linear least squares is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac12 \\| R x - s \\|^2_W\n                = \\frac12 (R x - s)^T W (R x - s) \\\\\n            \\mbox{subject to}\n                & G x \\leq h          \\\\\n                & A x = b             \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    using the QP solver selected by the ``solver`` keyword argument.\n\n    Parameters\n    ----------\n    R :\n        Union[np.ndarray, spa.csc_matrix] factor of the cost function (most\n        solvers require :math:`R^T W R` to be positive definite, which means\n        :math:`R` should have full row rank).\n    s :\n        Vector term of the cost function.\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality matrix.\n    b :\n        Linear equality vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    W :\n        Definite symmetric weight matrix used to define the norm of the cost\n        function. The standard L2 norm (W = Identity) is used by default.\n    solver :\n        Name of the QP solver, to choose in\n        :data:`qpsolvers.available_solvers`. This argument is mandatory.\n    initvals :\n        Vector of initial `x` values used to warm-start the solver.\n    verbose :\n        Set to `True` to print out extra information.\n    sparse_conversion :\n        Set to `True` to use a sparse conversion strategy and to `False` to use\n        a dense strategy. By default, the conversion strategy to follow is\n        determined by the sparsity of :math:`R` (sparse if CSC matrix, dense\n        otherwise). See Notes below.\n\n    Returns\n    -------\n    :\n        Optimal solution if found, otherwise ``None``.\n\n    Note\n    ----\n    Some solvers (like quadprog) will require a full-rank matrix :math:`R`,\n    while others (like ProxQP or QPALM) can work even when :math:`R` has a\n    non-empty nullspace.\n\n    Notes\n    -----\n    This function implements two strategies to convert the least-squares cost\n    :math:`(R, s)` to a quadratic-programming cost :math:`(P, q)`: one that\n    assumes :math:`R` is dense, and one that assumes :math:`R` is sparse. These\n    two strategies are detailed in `this note\n    <https://scaron.info/blog/conversion-from-least-squares-to-quadratic-programming.html>`__.\n    The sparse strategy introduces extra variables :math;`y = R x` and will\n    likely perform better on sparse problems, although this may not always be\n    the case (for instance, it may perform worse if :math:`R` has many more\n    rows than columns).\n\n    Extra keyword arguments given to this function are forwarded to the\n    underlying solvers. For example, OSQP has a setting `eps_abs` which we can\n    provide by ``solve_ls(R, s, G, h, solver='osqp', eps_abs=1e-4)``.\n    \"\"\"\n    if sparse_conversion is None:\n        sparse_conversion = not isinstance(R, np.ndarray)\n    if sparse_conversion:\n        return __solve_sparse_ls(\n            R, s, G, h, A, b, lb, ub, W, solver, initvals, verbose, **kwargs\n        )\n    return __solve_dense_ls(\n        R, s, G, h, A, b, lb, ub, W, solver, initvals, verbose, **kwargs\n    )\n"
  },
  {
    "path": "qpsolvers/solve_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solve quadratic programs.\"\"\"\n\nfrom typing import Optional\n\nimport numpy as np\n\nfrom .exceptions import SolverNotFound\nfrom .problem import Problem\nfrom .solution import Solution\nfrom .solvers import available_solvers, solve_function\n\n\ndef solve_problem(\n    problem: Problem,\n    solver: str,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using a given solver.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    solver :\n        Name of the solver, to choose in :data:`qpsolvers.available_solvers`.\n    initvals :\n        Primal candidate vector :math:`x` values used to warm-start the solver.\n    verbose :\n        Set to ``True`` to print out extra information.\n\n    Note\n    ----\n    In quadratic programming, the matrix :math:`P` should be symmetric. Many\n    solvers (including CVXOPT, OSQP and quadprog) assume this is the case and\n    may return unintended results when the provided matrix is not. Thus, make\n    sure you matrix is indeed symmetric before calling this function, for\n    instance by projecting it on its symmetric part :math:`S = \\frac{1}{2} (P\n    + P^T)`.\n\n    Returns\n    -------\n    :\n        Solution found by the solver, if any, along with solver-specific return\n        values.\n\n    Raises\n    ------\n    SolverNotFound\n        If the requested solver is not in :data:`qpsolvers.available_solvers`.\n\n    ValueError\n        If the problem is not correctly defined. For instance, if the solver\n        requires a definite cost matrix but the provided matrix :math:`P` is\n        not.\n\n    Notes\n    -----\n    Extra keyword arguments given to this function are forwarded to the\n    underlying solver. For example, we can call OSQP with a custom absolute\n    feasibility tolerance by ``solve_problem(problem, solver='osqp',\n    eps_abs=1e-6)``. See the :ref:`Supported solvers <Supported solvers>` page\n    for details on the parameters available to each solver.\n\n    There is no guarantee that a ``ValueError`` is raised if the provided\n    problem is non-convex, as some solvers don't check for this. Rather, if the\n    problem is non-convex and the solver fails because of that, then a\n    ``ValueError`` will be raised.\n    \"\"\"\n    problem.check_constraints()\n    kwargs[\"initvals\"] = initvals\n    kwargs[\"verbose\"] = verbose\n    try:\n        return solve_function[solver](problem, **kwargs)\n    except KeyError as e:\n        raise SolverNotFound(\n            f\"'{solver}' does not seem to be installed \"\n            f\"(found solvers: {available_solvers}); if '{solver}' is \"\n            \"listed in https://github.com/qpsolvers/qpsolvers#solvers \"\n            f\"you can install it by `pip install qpsolvers[{solver}]`\"\n        ) from e\n"
  },
  {
    "path": "qpsolvers/solve_qp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solve quadratic programs.\"\"\"\n\nfrom typing import Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom .exceptions import NoSolverSelected\nfrom .problem import Problem\nfrom .solve_problem import solve_problem\nfrom .solvers import available_solvers\n\n\ndef solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    using the QP solver selected by the ``solver`` keyword argument.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix (most solvers require it to be definite as well).\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality matrix.\n    b :\n        Linear equality vector.\n    lb :\n        Lower bound constraint vector. Can contain ``-np.inf``.\n    ub :\n        Upper bound constraint vector. Can contain ``+np.inf``.\n    solver :\n        Name of the QP solver, to choose in\n        :data:`qpsolvers.available_solvers`. This argument is mandatory.\n    initvals :\n        Primal candidate vector :math:`x` values used to warm-start the solver.\n    verbose :\n        Set to ``True`` to print out extra information.\n\n    Note\n    ----\n    In quadratic programming, the matrix :math:`P` should be symmetric. Many\n    solvers (including CVXOPT, OSQP and quadprog) leverage this property and\n    may return unintended results when it is not the case. You can set\n    project :math:`P` on its symmetric part by:\n\n    .. code:: python\n\n        P = 0.5 * (P + P.transpose())\n\n    Some solvers (like quadprog) will further require that :math:`P` is\n    definite, while other solvers (like ProxQP or QPALM) can work with\n    semi-definite matrices.\n\n    Returns\n    -------\n    :\n        Optimal solution if found, otherwise ``None``.\n\n    Raises\n    ------\n    NoSolverSelected\n        If the ``solver`` keyword argument is not set.\n\n    ParamError\n        If any solver parameter is incorrect.\n\n    ProblemError\n        If the problem is not correctly defined. For instance, if the solver\n        requires a definite cost matrix but the provided matrix :math:`P` is\n        not.\n\n    SolverError\n        If the solver failed during its execution.\n\n    SolverNotFound\n        If the requested solver is not in :data:`qpsolvers.available_solvers`.\n\n    Notes\n    -----\n    Extra keyword arguments given to this function are forwarded to the\n    underlying solver. For example, we can call OSQP with a custom absolute\n    feasibility tolerance by ``solve_qp(P, q, G, h, solver='osqp',\n    eps_abs=1e-6)``. See the :ref:`Supported solvers <Supported solvers>` page\n    for details on the parameters available to each solver.\n\n    There is no guarantee that a ``ValueError`` is raised if the provided\n    problem is non-convex, as some solvers don't check for this. Rather, if the\n    problem is non-convex and the solver fails because of that, then a\n    ``ValueError`` will be raised.\n    \"\"\"\n    if solver is None:\n        raise NoSolverSelected(\n            \"Set the `solver` keyword argument to one of the \"\n            f\"available solvers in {available_solvers}\"\n        )\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = solve_problem(problem, solver, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solve_unconstrained.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\"Solve an unconstrained quadratic program.\"\"\"\n\nimport numpy as np\nfrom scipy.sparse.linalg import lsqr\n\nfrom .exceptions import ProblemError\nfrom .problem import Problem\nfrom .solution import Solution\n\n\ndef solve_unconstrained(problem: Problem) -> Solution:\n    \"\"\"Solve an unconstrained quadratic program with SciPy's LSQR.\n\n    Parameters\n    ----------\n    problem :\n        Unconstrained quadratic program.\n\n    Returns\n    -------\n    :\n        Solution to the unconstrained QP, if it is bounded.\n\n    Raises\n    ------\n    ValueError\n        If the quadratic program is not unbounded below.\n    \"\"\"\n    P, q, _, _, _, _, _, _ = problem.unpack()\n    solution = Solution(problem)\n    solution.x = lsqr(P, -q)[0]\n    cost_check = np.linalg.norm(P @ solution.x + q)\n    if cost_check > 1e-8:\n        raise ProblemError(\n            f\"problem is unbounded below (cost_check={cost_check:.1e}), \"\n            \"q has component in the nullspace of P\"\n        )\n    solution.found = True\n    return solution\n"
  },
  {
    "path": "qpsolvers/solvers/__init__.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Import available QP solvers.\"\"\"\n\nimport warnings\nfrom typing import Any, Callable, Dict, List, Optional, Union\n\nfrom numpy import ndarray\nfrom scipy.sparse import csc_matrix\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\navailable_solvers: List[str] = []\ndense_solvers: List[str] = []\nsolve_function: Dict[str, Any] = {}\nsparse_solvers: List[str] = []\n\n# Clarabel.rs\n# ===========\n\nclarabel_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nclarabel_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .clarabel_ import clarabel_solve_problem, clarabel_solve_qp\n\n    solve_function[\"clarabel\"] = clarabel_solve_problem\n    available_solvers.append(\"clarabel\")\n    sparse_solvers.append(\"clarabel\")\nexcept ImportError:\n    pass\n\n\n# CVXOPT\n# ======\n\ncvxopt_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[str],\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ncvxopt_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[str],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .cvxopt_ import cvxopt_solve_problem, cvxopt_solve_qp\n\n    solve_function[\"cvxopt\"] = cvxopt_solve_problem\n    available_solvers.append(\"cvxopt\")\n    dense_solvers.append(\"cvxopt\")\n    sparse_solvers.append(\"cvxopt\")\nexcept ImportError:\n    pass\n\n\n# DAQP\n# ========\n\ndaqp_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ndaqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .daqp_ import daqp_solve_problem, daqp_solve_qp\n\n    solve_function[\"daqp\"] = daqp_solve_problem\n    available_solvers.append(\"daqp\")\n    dense_solvers.append(\"daqp\")\nexcept ImportError:\n    pass\n\n\n# ECOS\n# ====\n\necos_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\necos_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .ecos_ import ecos_solve_problem, ecos_solve_qp\n\n    solve_function[\"ecos\"] = ecos_solve_problem\n    available_solvers.append(\"ecos\")\n    dense_solvers.append(\"ecos\")  # considered dense as it calls cholesky(P)\nexcept ImportError:\n    pass\n\n\n# Gurobi\n# ======\n\ngurobi_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ngurobi_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .gurobi_ import gurobi_solve_problem, gurobi_solve_qp\n\n    solve_function[\"gurobi\"] = gurobi_solve_problem\n    available_solvers.append(\"gurobi\")\n    sparse_solvers.append(\"gurobi\")\nexcept ImportError:\n    pass\n\n\n# COPT\n# ======\n\ncopt_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ncopt_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .copt_ import copt_solve_problem, copt_solve_qp\n\n    solve_function[\"copt\"] = copt_solve_problem\n    available_solvers.append(\"copt\")\n    sparse_solvers.append(\"copt\")\nexcept ImportError:\n    pass\n\n\n# HiGHS\n# =====\n\nhighs_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nhighs_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .highs_ import highs_solve_problem, highs_solve_qp\n\n    solve_function[\"highs\"] = highs_solve_problem\n    available_solvers.append(\"highs\")\n    sparse_solvers.append(\"highs\")\nexcept ImportError:\n    pass\n\n\n# HPIPM\n# =====\n\nhpipm_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            str,\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nhpipm_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            str,\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .hpipm_ import hpipm_solve_problem, hpipm_solve_qp\n\n    solve_function[\"hpipm\"] = hpipm_solve_problem\n    available_solvers.append(\"hpipm\")\n    dense_solvers.append(\"hpipm\")\nexcept ImportError:\n    pass\n\n# jaxopt.OSQP\n# ==========\n\njaxopt_osqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\njaxopt_osqp_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .jaxopt_osqp_ import jaxopt_osqp_solve_problem, jaxopt_osqp_solve_qp\n\n    solve_function[\"jaxopt_osqp\"] = jaxopt_osqp_solve_problem\n    available_solvers.append(\"jaxopt_osqp\")\n    dense_solvers.append(\"jaxopt_osqp\")\nexcept ImportError:\n    pass\n\n# KVXOPT\n# ======\n\nkvxopt_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[str],\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nkvxopt_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[str],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .kvxopt_ import kvxopt_solve_problem, kvxopt_solve_qp\n\n    solve_function[\"kvxopt\"] = kvxopt_solve_problem\n    available_solvers.append(\"kvxopt\")\n    dense_solvers.append(\"kvxopt\")\n    sparse_solvers.append(\"kvxopt\")\nexcept ImportError:\n    pass\n\n# MOSEK\n# =====\n\nmosek_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nmosek_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .mosek_ import mosek_solve_problem, mosek_solve_qp\n\n    solve_function[\"mosek\"] = mosek_solve_problem\n    available_solvers.append(\"mosek\")\n    sparse_solvers.append(\"mosek\")\nexcept ImportError:\n    pass\n\n\n# NPPro\n# =====\n\nnppro_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n        ],\n        Solution,\n    ]\n] = None\n\nnppro_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .nppro_ import nppro_solve_problem, nppro_solve_qp\n\n    solve_function[\"nppro\"] = nppro_solve_problem\n    available_solvers.append(\"nppro\")\n    dense_solvers.append(\"nppro\")\nexcept ImportError:\n    pass\n\n\n# OSQP\n# ====\n\nosqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nosqp_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .osqp_ import osqp_solve_problem, osqp_solve_qp\n\n    solve_function[\"osqp\"] = osqp_solve_problem\n    available_solvers.append(\"osqp\")\n    sparse_solvers.append(\"osqp\")\nexcept ImportError:\n    pass\n\n# PDHCG\n# =====\n\npdhcg_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\npdhcg_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .pdhcg_ import pdhcg_solve_problem, pdhcg_solve_qp\n\n    solve_function[\"pdhcg\"] = pdhcg_solve_problem\n    available_solvers.append(\"pdhcg\")\n    dense_solvers.append(\"pdhcg\")\n    sparse_solvers.append(\"pdhcg\")\nexcept ImportError:\n    pass\n\n# PIQP\n# =======\n\npiqp_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n            Optional[str],\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\npiqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n            Optional[str],\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .piqp_ import piqp_solve_problem, piqp_solve_qp\n\n    solve_function[\"piqp\"] = piqp_solve_problem\n    available_solvers.append(\"piqp\")\n    dense_solvers.append(\"piqp\")\n    sparse_solvers.append(\"piqp\")\nexcept ImportError:\n    pass\n\n\n# ProxQP\n# =======\n\nproxqp_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n            Optional[str],\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\nproxqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n            Optional[str],\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .proxqp_ import proxqp_solve_problem, proxqp_solve_qp\n\n    solve_function[\"proxqp\"] = proxqp_solve_problem\n    available_solvers.append(\"proxqp\")\n    dense_solvers.append(\"proxqp\")\n    sparse_solvers.append(\"proxqp\")\nexcept ImportError:\n    pass\n\n\n# QPALM\n# =====\n\nqpalm_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\nqpalm_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .qpalm_ import qpalm_solve_problem, qpalm_solve_qp\n\n    solve_function[\"qpalm\"] = qpalm_solve_problem\n    available_solvers.append(\"qpalm\")\n    sparse_solvers.append(\"qpalm\")\nexcept ImportError:\n    pass\n\n# qpax\n# ========\n\nqpax_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nqpax_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .qpax_ import qpax_solve_problem, qpax_solve_qp\n\n    solve_function[\"qpax\"] = qpax_solve_problem\n    available_solvers.append(\"qpax\")\n    dense_solvers.append(\"qpax\")\nexcept ImportError:\n    pass\n\n\n# qpOASES\n# =======\n\nqpoases_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n            int,\n            Optional[float],\n        ],\n        Solution,\n    ]\n] = None\n\nqpoases_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n            int,\n            Optional[float],\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .qpoases_ import qpoases_solve_problem, qpoases_solve_qp\n\n    solve_function[\"qpoases\"] = qpoases_solve_problem\n    available_solvers.append(\"qpoases\")\n    dense_solvers.append(\"qpoases\")\nexcept ImportError:\n    pass\n\n\n# qpSWIFT\n# =======\n\nqpswift_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nqpswift_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .qpswift_ import qpswift_solve_problem, qpswift_solve_qp\n\n    solve_function[\"qpswift\"] = qpswift_solve_problem\n    available_solvers.append(\"qpswift\")\n    dense_solvers.append(\"qpswift\")\nexcept ImportError:\n    pass\n\n\n# QTQP\n# ====\n\nqtqp_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nqtqp_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .qtqp_ import qtqp_solve_problem, qtqp_solve_qp\n\n    solve_function[\"qtqp\"] = qtqp_solve_problem\n    available_solvers.append(\"qtqp\")\n    sparse_solvers.append(\"qtqp\")\nexcept ImportError:\n    pass\n\n\n# quadprog\n# ========\n\nquadprog_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\nquadprog_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .quadprog_ import quadprog_solve_problem, quadprog_solve_qp\n\n    solve_function[\"quadprog\"] = quadprog_solve_problem\n    available_solvers.append(\"quadprog\")\n    dense_solvers.append(\"quadprog\")\nexcept ImportError:\n    pass\n\n\n# pyqpmad\n# =======\n\npyqpmad_solve_qp: Optional[\n    Callable[\n        [\n            ndarray,\n            ndarray,\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\npyqpmad_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\ntry:\n    from .pyqpmad_ import pyqpmad_solve_problem, pyqpmad_solve_qp\n\n    solve_function[\"pyqpmad\"] = pyqpmad_solve_problem\n    available_solvers.append(\"pyqpmad\")\n    dense_solvers.append(\"pyqpmad\")\nexcept ImportError:\n    pass\n\n\n# SCS\n# ========\n\nscs_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nscs_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .scs_ import scs_solve_problem, scs_solve_qp\n\n    solve_function[\"scs\"] = scs_solve_problem\n    available_solvers.append(\"scs\")\n    sparse_solvers.append(\"scs\")\nexcept ImportError:\n    pass\n\n\n# SIP\n# ========\n\nsip_solve_problem: Optional[\n    Callable[\n        [\n            Problem,\n            Optional[ndarray],\n            bool,\n            bool,\n        ],\n        Solution,\n    ]\n] = None\n\nsip_solve_qp: Optional[\n    Callable[\n        [\n            Union[ndarray, csc_matrix],\n            ndarray,\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[Union[ndarray, csc_matrix]],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            Optional[ndarray],\n            bool,\n            bool,\n        ],\n        Optional[ndarray],\n    ]\n] = None\n\ntry:\n    from .sip_ import sip_solve_problem, sip_solve_qp\n\n    solve_function[\"sip\"] = sip_solve_problem\n    available_solvers.append(\"sip\")\n    sparse_solvers.append(\"sip\")\nexcept ImportError:\n    pass\n\n\nif not available_solvers:\n    warnings.warn(\n        \"no QP solver found on your system, \"\n        \"you can install solvers from PyPI by \"\n        \"``pip install qpsolvers[open_source_solvers]``\"\n    )\n\n\n__all__ = [\n    \"available_solvers\",\n    \"clarabel_solve_qp\",\n    \"copt_solve_qp\",\n    \"cvxopt_solve_qp\",\n    \"daqp_solve_qp\",\n    \"dense_solvers\",\n    \"ecos_solve_qp\",\n    \"gurobi_solve_qp\",\n    \"highs_solve_qp\",\n    \"hpipm_solve_qp\",\n    \"jaxopt_osqp_solve_qp\",\n    \"kvxopt_solve_qp\",\n    \"mosek_solve_qp\",\n    \"nppro_solve_qp\",\n    \"osqp_solve_qp\",\n    \"pdhcg_solve_qp\",\n    \"piqp_solve_qp\",\n    \"proxqp_solve_qp\",\n    \"pyqpmad_solve_qp\",\n    \"qpalm_solve_qp\",\n    \"qpax_solve_qp\",\n    \"qpoases_solve_qp\",\n    \"qpswift_solve_qp\",\n    \"qtqp_solve_qp\",\n    \"quadprog_solve_qp\",\n    \"scs_solve_qp\",\n    \"sip_solve_qp\",\n    \"solve_function\",\n    \"sparse_solvers\",\n]\n"
  },
  {
    "path": "qpsolvers/solvers/clarabel_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\"Solver interface for `Clarabel.rs`_.\n\n.. _Clarabel.rs: https://github.com/oxfordcontrol/Clarabel.rs\n\nClarabel.rs is a Rust implementation of an interior point numerical solver for\nconvex optimization problems using a novel homogeneous embedding. A paper\ndescribing the Clarabel solver algorithm and implementation will be forthcoming\nsoon (retrieved: 2023-02-06). Until then, the authors ask that you cite its\ndocumentation if you have found Clarabel.rs useful in your work.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport clarabel\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..conversions import (\n    ensure_sparse_matrices,\n    linear_from_box_inequalities,\n    split_dual_linear_box,\n)\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\nfrom ..solve_unconstrained import solve_unconstrained\n\n\ndef clarabel_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using Clarabel.rs.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by Clarabel.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded as options to Clarabel.rs. For instance, we\n    can call ``clarabel_solve_qp(P, q, G, h, u, tol_feas=1e-6)``. Clarabel\n    options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``max_iter``\n         - Maximum number of iterations.\n       * - ``time_limit``\n         - Time limit for solve run in seconds (can be fractional).\n       * - ``tol_gap_abs``\n         - absolute duality-gap tolerance\n       * - ``tol_gap_rel``\n         - relative duality-gap tolerance\n       * - ``tol_feas``\n         - feasibility check tolerance (primal and dual)\n\n    Check out the `API reference\n    <https://oxfordcontrol.github.io/ClarabelDocs/stable/api_settings/#Clarabel.Settings>`_\n    for details.\n\n    Lower values for absolute or relative tolerances yield more precise\n    solutions at the cost of computation time. See *e.g.* [Caron2022]_ for a\n    primer on solver tolerances and residuals.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"Clarabel: warm-start values are ignored\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    P, G, A = ensure_sparse_matrices(\"clarabel\", P, G, A)\n    if lb is not None or ub is not None:\n        G, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=True)\n\n    cones = []\n    A_list: list = []\n    b_list = []\n    if A is not None and b is not None:\n        A_list.append(A)\n        b_list.append(b)\n        cones.append(clarabel.ZeroConeT(b.shape[0]))\n    if G is not None and h is not None:\n        A_list.append(G)\n        b_list.append(h)\n        cones.append(clarabel.NonnegativeConeT(h.shape[0]))\n    if not A_list:\n        warnings.warn(\n            \"QP is unconstrained: \"\n            \"solving with SciPy's LSQR rather than clarabel\"\n        )\n        return solve_unconstrained(problem)\n\n    settings = clarabel.DefaultSettings()\n    settings.verbose = verbose\n    for key, value in kwargs.items():\n        setattr(settings, key, value)\n\n    A_stack = spa.vstack(A_list, format=\"csc\")\n    b_stack = np.concatenate(b_list)\n    try:\n        solver = clarabel.DefaultSolver(\n            P, q, A_stack, b_stack, cones, settings\n        )\n    except BaseException as exn:\n        # The one we want to catch is a pyo3_runtime.PanicException\n        # But see https://github.com/PyO3/pyo3/issues/2880\n        raise ProblemError(\"Solver failed to build problem\") from exn\n\n    solve_start_time = time.perf_counter()\n    result = solver.solve()\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.obj = result.obj_val\n    solution.extras = {\n        \"s\": result.s,\n        \"status\": result.status,\n        \"solve_time\": result.solve_time,\n    }\n\n    solution.found = result.status == clarabel.SolverStatus.Solved\n    if not solution.found:\n        warnings.warn(f\"Clarabel.rs terminated with status {result.status}\")\n\n    solution.x = np.array(result.x)\n    meq = A.shape[0] if A is not None else 0\n    solution.y = result.z[:meq] if meq > 0 else np.empty((0,))\n    if G is not None:\n        z, z_box = split_dual_linear_box(np.array(result.z[meq:]), lb, ub)\n        solution.z = z\n        solution.z_box = z_box\n    else:  # G is None\n        solution.z = np.empty((0,))\n        solution.z_box = np.empty((0,))\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef clarabel_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using Clarabel.rs.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `Clarabel.rs`_.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        This argument is not used by Clarabel.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = clarabel_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/copt_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `COPT <https://www.shanshu.ai/solver>`__.\n\nThe COPT Optimizer suite ships several solvers for mathematical programming,\nincluding problems that have linear constraints, bound constraints, integrality\nconstraints, cone constraints, or quadratic constraints.\nIt targets modern CPU/GPU architectures and multi-core processors,\n\nSee the :ref:`installation page <copt-install>` for additional instructions\non installing this solver.\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Sequence, Union\n\nimport coptpy\nimport numpy as np\nimport scipy.sparse as spa\nfrom coptpy import COPT\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef copt_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using COPT.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by COPT.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to COPT as parameters. For instance, we\n    can call ``copt_solve_qp(P, q, G, h, u, FeasTol=1e-8,\n    DualTol=1e-8)``. COPT settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``FeasTol``\n         - Primal feasibility tolerance.\n       * - ``DualTol``\n         - Dual feasibility tolerance.\n       * - ``TimeLimit``\n         - Run time limit in seconds, 0 to disable.\n\n    Check out the `Parameter Descriptions\n    <https://guide.coap.online/copt/en-doc/parameter.html>`_\n    documentation for all available COPT parameters.\n\n    Lower values for primal or dual tolerances yield more precise solutions at\n    the cost of computation time. See *e.g.* [Caron2022]_ for a primer of\n    solver tolerances.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\"warm-start values are ignored by this wrapper\")\n\n    env_config = coptpy.EnvrConfig()\n    if not verbose:\n        env_config.set(\"nobanner\", \"1\")\n\n    env = coptpy.Envr(env_config)\n    model = env.createModel()\n\n    if not verbose:\n        model.setParam(COPT.Param.Logging, 0)\n    for param, value in kwargs.items():\n        model.setParam(param, value)\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    num_vars = P.shape[0]\n    identity = np.eye(num_vars)\n    x = model.addMVar(\n        num_vars, lb=-COPT.INFINITY, ub=COPT.INFINITY, vtype=COPT.CONTINUOUS\n    )\n    ineq_constr, eq_constr, lb_constr, ub_constr = None, None, None, None\n    if G is not None and h is not None:\n        ineq_constr = model.addMConstr(\n            G,  # type: ignore[arg-type]\n            x,\n            COPT.LESS_EQUAL,\n            h,\n        )\n    if A is not None and b is not None:\n        eq_constr = model.addMConstr(\n            A,  # type: ignore[arg-type]\n            x,\n            COPT.EQUAL,\n            b,\n        )\n    if lb is not None:\n        lb_constr = model.addMConstr(identity, x, COPT.GREATER_EQUAL, lb)\n    if ub is not None:\n        ub_constr = model.addMConstr(identity, x, COPT.LESS_EQUAL, ub)\n    objective = 0.5 * (x @ P @ x) + q @ x  # type: ignore[operator]\n    model.setObjective(objective, sense=COPT.MINIMIZE)\n    solve_start_time = time.perf_counter()\n    model.solve()\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras[\"status\"] = model.status\n    solution.found = model.status in (COPT.OPTIMAL, COPT.IMPRECISE)\n    if solution.found:\n        # COPT v8.0.0+ Changed the default Python matrix modeling API from\n        # `numpy` to its own implementation. `coptpy.NdArray` does not support\n        # operators such as \">=\", so convert to `np.ndarray`\n        solution.x = __to_numpy(x.X)  # type: ignore[attr-defined]\n        __retrieve_dual(solution, ineq_constr, eq_constr, lb_constr, ub_constr)\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef __retrieve_dual(\n    solution: Solution,\n    ineq_constr: Optional[coptpy.MConstr],\n    eq_constr: Optional[coptpy.MConstr],\n    lb_constr: Optional[coptpy.MConstr],\n    ub_constr: Optional[coptpy.MConstr],\n) -> None:\n    solution.z = (\n        __to_numpy(-ineq_constr.Pi)  # type: ignore[attr-defined]\n        if ineq_constr is not None\n        else np.empty((0,))\n    )\n    solution.y = (\n        __to_numpy(-eq_constr.Pi)  # type: ignore[attr-defined]\n        if eq_constr is not None\n        else np.empty((0,))\n    )\n    if lb_constr is not None and ub_constr is not None:\n        solution.z_box = __to_numpy(\n            -ub_constr.Pi - lb_constr.Pi  # type: ignore[attr-defined]\n        )\n    elif ub_constr is not None:  # lb_constr is None\n        solution.z_box = __to_numpy(\n            -ub_constr.Pi  # type: ignore[attr-defined]\n        )\n    elif lb_constr is not None:  # ub_constr is None\n        solution.z_box = __to_numpy(\n            -lb_constr.Pi  # type: ignore[attr-defined]\n        )\n    else:  # lb_constr is None and ub_constr is None\n        solution.z_box = np.empty((0,))\n\n\ndef copt_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using COPT.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `COPT <https://www.shanshu.ai/solver>`__.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        This argument is not used by COPT.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to COPT as parameters. For instance, we\n    can call ``copt_solve_qp(P, q, G, h, u, FeasTol=1e-8,\n    DualTol=1e-8)``. COPT settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``FeasTol``\n         - Primal feasibility tolerance.\n       * - ``DualTol``\n         - Dual feasibility tolerance.\n       * - ``TimeLimit``\n         - Run time limit in seconds, 0 to disable.\n\n    Check out the `Parameter Descriptions\n    <https://guide.coap.online/copt/en-doc/parameter.html>`_\n    documentation for all available COPT parameters.\n\n    Lower values for primal or dual tolerances yield more precise solutions at\n    the cost of computation time. See *e.g.* [Caron2022]_ for a primer of\n    solver tolerances.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = copt_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n\n\ndef __to_numpy(\n    array_like: Union[\n        coptpy.NdArray, np.ndarray, float, int, Sequence[Union[float, int]]\n    ],\n) -> np.ndarray:\n    \"\"\"Convert COPT NdArray or array-like objects to numpy ndarray.\n\n    This function ensures compatibility with COPT v8+, which changed the\n    default Python matrix modeling API from numpy to its own implementation\n    (``coptpy.NdArray``).\n\n    Parameters\n    ----------\n    array_like :\n        Input array to convert. Supported types:\n        - ``coptpy.NdArray`` from COPT v8+ (converted via ``tonumpy()``)\n        - ``np.ndarray`` (returned as-is to avoid redundant copy)\n        - Scalar values (float, int) → converted to 1-element 1D numpy array\n        - Sequence types (list, tuple) of floats/ints → converted to 1D numpy\n          array\n\n    Returns\n    -------\n    :\n        Numpy array representation of the input (1D for scalars/sequences, same\n        shape for COPT/numpy arrays).\n\n    Raises\n    ------\n    TypeError\n        If the input type is not supported (e.g., dict, None, non-numeric\n        sequence).\n    RuntimeError\n        If conversion from coptpy.NdArray to numpy fails (e.g., invalid COPT\n        array).\n\n    Notes\n    -----\n    COPT v8.0.0+ uses ``coptpy.NdArray`` by default, which does not support\n    operators such as ``>=``. This function converts such arrays to\n    ``np.ndarray`` for further processing.\n    Numpy arrays are returned as-is to avoid unnecessary memory copies.\n\n    Examples\n    --------\n    >>> # Convert COPT NdArray to numpy (when coptpy is available)\n    >>> # copt_array = coptpy.NdArray([1.0, 2.0, 3.0])  # doctest: +SKIP\n    >>> # np_array = __to_numpy(copt_array)  # doctest: +SKIP\n    >>> # isinstance(np_array, np.ndarray)  # doctest: +SKIP\n    >>> # True  # doctest: +SKIP\n\n    >>> # Convert scalar to 1D numpy array\n    >>> __to_numpy(5.0).shape\n    (1,)\n\n    >>> # Convert list to numpy array\n    >>> __to_numpy([1, 2, 3]).shape\n    (3,)\n    \"\"\"\n    if array_like is None:\n        raise TypeError(\n            \"Input 'array_like' cannot be None. Supported types: \"\n            \"coptpy.NdArray, np.ndarray, float, int, list/tuple of numbers.\"\n        )\n\n    if isinstance(array_like, np.ndarray):\n        return array_like\n\n    if isinstance(array_like, coptpy.NdArray):\n        try:\n            return array_like.tonumpy()\n        except Exception as e:\n            raise RuntimeError(\n                f\"Failed to convert coptpy.NdArray to numpy array: {str(e)}\"\n            ) from e\n\n    try:\n        if isinstance(array_like, (int, float)):\n            return np.asarray([array_like])\n        if isinstance(array_like, (list, tuple)):\n            return np.asarray(array_like)\n    except Exception as e:\n        raise RuntimeError(\n            \"Failed to convert input to numpy array. Input type: \"\n            f\"{type(array_like).__name__}, error: {str(e)}\"\n        ) from e\n\n    raise TypeError(\n        f\"Unsupported type '{type(array_like).__name__}' for 'array_like'. \"\n        f\"Supported types: coptpy.NdArray, np.ndarray, float, int, list/tuple \"\n        \"of numbers.\"\n    )\n"
  },
  {
    "path": "qpsolvers/solvers/cvxopt_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `CVXOPT <https://cvxopt.org/>`__.\n\nCVXOPT is a free software package for convex optimization in Python. Its main\npurpose is to make the development of software for convex optimization\napplications straightforward by building on Python’s extensive standard library\nand on the strengths of Python as a high-level programming language. If you are\nusing CVXOPT in a scientific work, consider citing the corresponding report\n[Vandenberghe2010]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Dict, Optional, Union\n\nimport cvxopt\nimport numpy as np\nimport scipy.sparse as spa\nfrom cvxopt.solvers import qp\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError, SolverError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\ncvxopt.solvers.options[\"show_progress\"] = False  # disable default verbosity\n\n\ndef __to_cvxopt(\n    M: Union[np.ndarray, spa.csc_matrix],\n) -> Union[cvxopt.matrix, cvxopt.spmatrix]:\n    \"\"\"Convert matrix to CVXOPT format.\n\n    Parameters\n    ----------\n    M :\n        Matrix in NumPy or CVXOPT format.\n\n    Returns\n    -------\n    :\n        Matrix in CVXOPT format.\n    \"\"\"\n    if isinstance(M, np.ndarray):\n        __infty__ = 1e10  # 1e20 tends to yield division-by-zero errors\n        M_noinf = np.nan_to_num(M, posinf=__infty__, neginf=-__infty__)\n        return cvxopt.matrix(M_noinf)\n    coo = M.tocoo()\n    return cvxopt.spmatrix(\n        coo.data.tolist(), coo.row.tolist(), coo.col.tolist(), size=M.shape\n    )\n\n\ndef cvxopt_solve_problem(\n    problem: Problem,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using CVXOPT.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    solver :\n        Set to 'mosek' to run MOSEK rather than CVXOPT.\n    initvals :\n        Warm-start guess vector.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError\n        If the CVXOPT rank assumption is not satisfied.\n\n    SolverError\n        If CVXOPT failed with an error.\n\n    Note\n    ----\n    .. _CVXOPT rank assumptions:\n\n    **Rank assumptions:** CVXOPT requires the QP matrices to satisfy the\n\n    .. math::\n\n        \\begin{split}\\begin{array}{cc}\n        \\mathrm{rank}(A) = p\n        &\n        \\mathrm{rank}([P\\ A^T\\ G^T]) = n\n        \\end{array}\\end{split}\n\n    where :math:`p` is the number of rows of :math:`A` and :math:`n` is the\n    number of optimization variables. See the \"Rank assumptions\" paragraph in\n    the report `The CVXOPT linear and quadratic cone program solvers\n    <http://www.ee.ucla.edu/~vandenbe/publications/coneprog.pdf>`_ for details.\n\n    Notes\n    -----\n    CVXOPT only considers the lower entries of :math:`P`, therefore it will use\n    a different cost than the one intended if a non-symmetric matrix is\n    provided.\n\n    Keyword arguments are forwarded as options to CVXOPT. For instance, we can\n    call ``cvxopt_solve_qp(P, q, G, h, u, abstol=1e-4, reltol=1e-4)``. CVXOPT\n    options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``abstol``\n         - Absolute tolerance on the duality gap.\n       * - ``feastol``\n         - Tolerance on feasibility conditions, that is, on the primal\n           residual.\n       * - ``maxiters``\n         - Maximum number of iterations.\n       * - ``refinement``\n         - Number of iterative refinement steps when solving KKT equations\n       * - ``reltol``\n         - Relative tolerance on the duality gap.\n\n    Check out `Algorithm Parameters\n    <https://cvxopt.org/userguide/coneprog.html#algorithm-parameters>`_ section\n    of the solver documentation for details and default values of all solver\n    parameters. See also [Caron2022]_ for a primer on the duality gap, primal\n    and dual residuals.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    if lb is not None or ub is not None:\n        G, h = linear_from_box_inequalities(\n            G, h, lb, ub, use_sparse=problem.has_sparse\n        )\n\n    args = [__to_cvxopt(P), __to_cvxopt(q)]\n    constraints = {\"G\": None, \"h\": None, \"A\": None, \"b\": None}\n    if G is not None and h is not None:\n        constraints[\"G\"] = __to_cvxopt(G)\n        constraints[\"h\"] = __to_cvxopt(h)\n    if A is not None and b is not None:\n        constraints[\"A\"] = __to_cvxopt(A)\n        constraints[\"b\"] = __to_cvxopt(b)\n    initvals_dict: Optional[Dict[str, cvxopt.matrix]] = None\n    if initvals is not None:\n        if \"mosek\" in kwargs:\n            warnings.warn(\"MOSEK: warm-start values are ignored\")\n        initvals_dict = {\"x\": __to_cvxopt(initvals)}\n    kwargs[\"show_progress\"] = verbose\n\n    try:\n        solve_start_time = time.perf_counter()\n        res = qp(\n            *args,\n            solver=solver,\n            initvals=initvals_dict,\n            options=kwargs,\n            **constraints,\n        )\n        solve_end_time = time.perf_counter()\n    except ValueError as exception:\n        error = str(exception)\n        if \"Rank(A)\" in error:\n            raise ProblemError(error) from exception\n        raise SolverError(error) from exception\n\n    solution = Solution(problem)\n    solution.extras = res\n    solution.found = \"optimal\" in res[\"status\"]\n    solution.x = np.array(res[\"x\"]).flatten()\n    solution.y = (\n        np.array(res[\"y\"]).flatten() if b is not None else np.empty((0,))\n    )\n    if h is not None and res[\"z\"] is not None:\n        z_cvxopt = np.array(res[\"z\"]).flatten()\n        if z_cvxopt.size == h.size:\n            z, z_box = split_dual_linear_box(z_cvxopt, lb, ub)\n            solution.z = z\n            solution.z_box = z_box\n    else:  # h is None\n        solution.z = np.empty((0,))\n        solution.z_box = np.empty((0,))\n    solution.obj = res[\"primal objective\"]\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef cvxopt_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using CVXOPT.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `CVXOPT <http://cvxopt.org/>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix. Together with :math:`A` and :math:`G`, it should\n        satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`, see the rank\n        assumptions below.\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix. Together with :math:`P` and :math:`A`, it\n        should satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`, see the\n        rank assumptions below.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality constraint matrix. It needs to be full row rank, and\n        together with :math:`P` and :math:`G` satisfy\n        :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`. See the rank assumptions\n        below.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    solver :\n        Set to 'mosek' to run MOSEK rather than CVXOPT.\n    initvals :\n        Warm-start guess vector.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError\n        If the CVXOPT rank assumption is not satisfied.\n\n    SolverError\n        If CVXOPT failed with an error.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = cvxopt_solve_problem(\n        problem, solver, initvals, verbose, **kwargs\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/daqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `DAQP <https://github.com/darnstrom/daqp>`__.\n\nDAQP is a dual active-set algorithm implemented in C [Arnstrom2022]_.\nIt has been developed to solve small/medium scale dense problems.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom ctypes import c_int\nfrom typing import Optional\n\nimport daqp\nimport numpy as np\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef daqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using DAQP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to DAQP. For instance, we can call\n    ``daqp_solve_qp(P, q, G, h, u, primal_tol=1e-6, iter_limit=1000)``. DAQP\n    settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``iter_limit``\n         - Maximum number of iterations.\n       * - ``primal_tol``\n         - Primal feasibility tolerance.\n       * - ``dual_tol``\n         - Dual feasibility tolerance.\n\n    Check out the `DAQP settings\n    <https://darnstrom.github.io/daqp/parameters>`_ documentation for\n    all available settings.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"warm-start values are ignored by DAQP\")\n\n    H, f, G, h, A, b, lb, ub = problem.unpack_as_dense()\n\n    # Determine constraint counts upfront to allow single pre-allocation\n    meq = A.shape[0] if A is not None else 0\n    mineq = G.shape[0] if G is not None else 0\n    if ub is not None:\n        ms = ub.size\n    elif lb is not None:\n        ms = lb.size\n    else:\n        ms = 0\n    mtot = ms + mineq + meq\n\n    # Build bupper/blower. When there are no box constraints and only one\n    # constraint block, reuse the existing arrays directly (zero extra copy).\n    if ms == 0 and (mineq == 0 or meq == 0):\n        bupper = h if (mineq > 0) else (b if meq > 0 else np.zeros(0))\n        blower = np.full(mineq + meq, -1e30)\n    else:\n        bupper = np.empty(mtot)\n        blower = np.full(mtot, -1e30)\n        if ms > 0:\n            bupper[:ms] = ub if ub is not None else 1e30\n            if lb is not None:\n                blower[:ms] = lb\n        if mineq > 0:\n            bupper[ms : ms + mineq] = h\n        if meq > 0:\n            bupper[ms + mineq :] = b\n\n    # Build constraint matrix; stack only when both blocks are present\n    if mineq > 0 and meq > 0:\n        Atot = np.empty((mineq + meq, f.size))\n        Atot[:mineq] = G\n        Atot[mineq:] = A\n    elif mineq > 0:\n        Atot = G  # type: ignore[assignment]\n    elif meq > 0:\n        Atot = A  # type: ignore[assignment]\n    else:\n        Atot = np.zeros((0, f.size))\n\n    sense = np.zeros(mtot, dtype=c_int)\n    sense[ms + mineq :] = 5\n\n    solve_start_time = time.perf_counter()\n    x, obj, exitflag, info = daqp.solve(\n        H, f, Atot, bupper, blower, sense, primal_start=initvals, **kwargs\n    )\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.found = exitflag > 0\n    if exitflag > 0:\n        solution.x = x\n        solution.obj = obj\n\n        solution.z_box = info[\"lam\"][:ms]\n        solution.z = info[\"lam\"][ms : ms + mineq]\n        solution.y = info[\"lam\"][ms + mineq :]\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef daqp_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using DAQP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `DAQP <https://pypi.python.org/pypi/daqp/>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to DAQP. For instance, we can call\n    ``daqp_solve_qp(P, q, G, h, u, primal_tol=1e-6, iter_limit=1000)``. DAQP\n    settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``iter_limit``\n         - Maximum number of iterations.\n       * - ``primal_tol``\n         - Primal feasibility tolerance.\n       * - ``dual_tol``\n         - Dual feasibility tolerance.\n       * - ``time_limit``\n         - Time limit for solve run in seconds.\n\n    Check out the `DAQP settings\n    <https://darnstrom.github.io/daqp/parameters>`_ documentation for\n    all available settings.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = daqp_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/ecos_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `ECOS <https://github.com/embotech/ecos>`__.\n\nECOS is an interior-point solver for convex second-order cone programs (SOCPs).\ndesigned specifically for embedded applications. ECOS is written in low\nfootprint, single-threaded, library-free ANSI-C and so runs on most embedded\nplatforms. For small problems, ECOS is faster than most existing SOCP solvers;\nit is still competitive for medium-sized problems up to tens of thousands of\nvariables. If you are using ECOS in a scientific work, consider citing the\ncorresponding paper [Domahidi2013]_.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport ecos\nimport numpy as np\nfrom scipy import sparse as spa\n\nfrom ..conversions import (\n    linear_from_box_inequalities,\n    socp_from_qp,\n    split_dual_linear_box,\n)\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n__exit_flag_meaning__ = {\n    0: \"OPTIMAL\",\n    1: \"PINF: found certificate of primal infeasibility\",\n    2: \"DING: found certificate of dual infeasibility\",\n    10: \"INACC_OFFSET: inaccurate results\",\n    11: \"PINF_INACC: found inaccurate certificate of primal infeasibility\",\n    12: \"DING_INACC: found inaccurate certificate of dual infeasibility\",\n    -1: \"MAXIT: maximum number of iterations reached\",\n    -2: \"NUMERICS: search direction is unreliable\",\n    -3: \"OUTCONE: primal or dual variables got outside of cone\",\n    -4: \"SIGINT: solver interrupted\",\n    -7: \"FATAL: unknown solver problem\",\n}\n\n\ndef ecos_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using ECOS.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    initvals :\n        This argument is not used by ECOS.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If inequality constraints contain infinite values that the solver\n        doesn't handle.\n\n    ValueError :\n        If the cost matrix is not positive definite.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to the ECOS solver.\n    For instance, you can call ``qpswift_solve_qp(P, q, G, h, abstol=1e-5)``.\n\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``feastol``\n         -  Tolerance on the primal and dual residual.\n       * - ``abstol``\n         -  Absolute tolerance on the duality gap.\n       * - ``reltol``\n         -  Relative tolerance on the duality gap.\n       * - ``feastol_inacc``\n         -  Tolerance on the primal and dual residual if reduced precisions.\n       * - ``abstol_inacc``\n         - Absolute tolerance on the duality gap if reduced precision.\n       * - ``reltolL_inacc``\n         - Relative tolerance on the duality gap if reduced precision.\n       * - ``max_iters``\n         - Maximum numer of iterations.\n       * - ``nitref``\n         - Number of iterative refinement steps.\n\n    See the `ECOS Python wrapper documentation\n    <https://github.com/embotech/ecos-python#calling-ecos-from-python>`_ for\n    more details. You can also check out [Caron2022]_ for a primer on\n    primal-dual residuals or the duality gap.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\"warm-start values are ignored by this wrapper\")\n\n    P_, q, G_, h, A, b, lb, ub = problem.unpack()\n    # P and G should be dense for socp_from_qp, but A should be sparse\n    P: np.ndarray = P_.toarray() if isinstance(P_, spa.csc_matrix) else P_\n    G: Optional[np.ndarray] = (\n        G_.toarray() if isinstance(G_, spa.csc_matrix) else G_\n    )\n\n    if lb is not None or ub is not None:\n        G2, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n        G = G2.toarray() if isinstance(G2, spa.csc_matrix) else G2  # for mypy\n    kwargs.update({\"verbose\": verbose})\n\n    c_socp, G_socp, h_socp, dims = socp_from_qp(P, q, G, h)\n    if A is not None:\n        A_sparse = (\n            spa.csc_matrix(A) if not isinstance(A, spa.csc_matrix) else A\n        )\n        A_socp = spa.hstack(\n            [A_sparse, spa.csc_matrix((A.shape[0], 1))], format=\"csc\"\n        )\n        solve_start_time = time.perf_counter()\n        result = ecos.solve(c_socp, G_socp, h_socp, dims, A_socp, b, **kwargs)\n        solve_end_time = time.perf_counter()\n    else:\n        solve_start_time = time.perf_counter()\n        result = ecos.solve(c_socp, G_socp, h_socp, dims, **kwargs)\n        solve_end_time = time.perf_counter()\n    flag = result[\"info\"][\"exitFlag\"]\n    solution = Solution(problem)\n    solution.extras = result[\"info\"]\n    solution.found = flag == 0\n    if not solution.found:\n        if h is not None and not np.isfinite(h).all():\n            raise ProblemError(\n                \"ECOS does not handle infinite values in inequality vectors, \"\n                \"try clipping them to a finite value suitable to your problem\"\n            )\n        meaning = __exit_flag_meaning__.get(flag, \"unknown exit flag\")\n        warnings.warn(f\"ECOS returned exit flag {flag} ({meaning})\")\n    solution.x = result[\"x\"][:-1]\n    if A is not None:\n        solution.y = result[\"y\"]\n    if G is not None:\n        z_ecos = result[\"z\"][: G.shape[0]]\n        z, z_box = split_dual_linear_box(z_ecos, lb, ub)\n        solution.z = z\n        solution.z_box = z_box\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef ecos_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using ECOS.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b\n        \\end{array}\\end{split}\n\n    It is solved using `ECOS <https://github.com/embotech/ecos>`__.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    initvals :\n        This argument is not used by ECOS.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to the ECOS solver.\n    For instance, you can call ``ecos_solve_qp(P, q, G, h, abstol=1e-5)``.\n\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``feastol``\n         -  Tolerance on the primal and dual residual.\n       * - ``abstol``\n         -  Absolute tolerance on the duality gap.\n       * - ``reltol``\n         -  Relative tolerance on the duality gap.\n       * - ``feastol_inacc``\n         -  Tolerance on the primal and dual residual if reduced precisions.\n       * - ``abstol_inacc``\n         - Absolute tolerance on the duality gap if reduced precision.\n       * - ``reltolL_inacc``\n         - Relative tolerance on the duality gap if reduced precision.\n       * - ``max_iters``\n         - Maximum numer of iterations.\n       * - ``nitref``\n         - Number of iterative refinement steps.\n\n    See the `ECOS Python wrapper documentation\n    <https://github.com/embotech/ecos-python#calling-ecos-from-python>`_ for\n    more details. You can also check out [Caron2022]_ for a primer on\n    primal-dual residuals or the duality gap.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = ecos_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/gurobi_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n# Copyright 2021 Dustin Kenefake\n\n\"\"\"Solver interface for `Gurobi <https://www.gurobi.com/>`__.\n\nThe Gurobi Optimizer suite ships several solvers for mathematical programming,\nincluding problems that have linear constraints, bound constraints, integrality\nconstraints, cone constraints, or quadratic constraints. It targets modern CPU\narchitectures and multi-core processors,\n\nSee the :ref:`installation page <gurobi-install>` for additional instructions\non installing this solver.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport gurobipy\nimport numpy as np\nimport scipy.sparse as spa\nfrom gurobipy import GRB\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef gurobi_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using Gurobi.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by Gurobi.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to Gurobi as parameters. For instance, we\n    can call ``gurobi_solve_qp(P, q, G, h, u, FeasibilityTol=1e-8,\n    OptimalityTol=1e-8)``. Gurobi settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``FeasibilityTol``\n         - Primal feasibility tolerance.\n       * - ``OptimalityTol``\n         - Dual feasibility tolerance.\n       * - ``PSDTol``\n         - Positive semi-definite tolerance.\n       * - ``TimeLimit``\n         - Run time limit in seconds, 0 to disable.\n\n    Check out the `Parameter Descriptions\n    <https://www.gurobi.com/documentation/9.5/refman/parameter_descriptions.html>`__\n    documentation for all available Gurobi parameters.\n\n    Lower values for primal or dual tolerances yield more precise solutions at\n    the cost of computation time. See *e.g.* [Caron2022]_ for a primer of\n    solver tolerances.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\"warm-start values are ignored by this wrapper\")\n\n    model = gurobipy.Model()\n    if not verbose:\n        model.setParam(GRB.Param.OutputFlag, 0)\n    for param, value in kwargs.items():\n        model.setParam(param, value)\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    num_vars = P.shape[0]\n    identity = spa.eye(num_vars)\n    x = model.addMVar(\n        num_vars, lb=-GRB.INFINITY, ub=GRB.INFINITY, vtype=GRB.CONTINUOUS\n    )\n    ineq_constr, eq_constr, lb_constr, ub_constr = None, None, None, None\n    if G is not None and h is not None:\n        ineq_constr = model.addMConstr(\n            G,  # type: ignore[arg-type]\n            x,\n            GRB.LESS_EQUAL,\n            h,\n        )\n    if A is not None and b is not None:\n        eq_constr = model.addMConstr(\n            A,  # type: ignore[arg-type]\n            x,\n            GRB.EQUAL,\n            b,\n        )\n    if lb is not None:\n        lb_constr = model.addMConstr(\n            identity,  # type: ignore[call-overload]\n            x,\n            GRB.GREATER_EQUAL,\n            lb,\n        )\n    if ub is not None:\n        ub_constr = model.addMConstr(\n            identity,  # type: ignore[call-overload]\n            x,\n            GRB.LESS_EQUAL,\n            ub,\n        )\n    objective = 0.5 * (x @ P @ x) + q @ x  # type: ignore[operator]\n    model.setObjective(objective, sense=GRB.MINIMIZE)\n    solve_start_time = time.perf_counter()\n    model.optimize()\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras[\"status\"] = model.status\n    solution.found = model.status in (GRB.OPTIMAL, GRB.SUBOPTIMAL)\n    if solution.found:\n        solution.x = x.getAttr(\"X\")\n        __retrieve_dual(solution, ineq_constr, eq_constr, lb_constr, ub_constr)\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef __retrieve_dual(\n    solution: Solution,\n    ineq_constr: Optional[gurobipy.MConstr],\n    eq_constr: Optional[gurobipy.MConstr],\n    lb_constr: Optional[gurobipy.MConstr],\n    ub_constr: Optional[gurobipy.MConstr],\n) -> None:\n    solution.z = (\n        -ineq_constr.getAttr(\"Pi\")\n        if ineq_constr is not None\n        else np.empty((0,))\n    )\n    solution.y = (\n        -eq_constr.getAttr(\"Pi\") if eq_constr is not None else np.empty((0,))\n    )\n    if lb_constr is not None and ub_constr is not None:\n        solution.z_box = -ub_constr.getAttr(\"Pi\") - lb_constr.getAttr(\"Pi\")\n    elif ub_constr is not None:  # lb_constr is None\n        solution.z_box = -ub_constr.getAttr(\"Pi\")\n    elif lb_constr is not None:  # ub_constr is None\n        solution.z_box = -lb_constr.getAttr(\"Pi\")\n    else:  # lb_constr is None and ub_constr is None\n        solution.z_box = np.empty((0,))\n\n\ndef gurobi_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using Gurobi.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `Gurobi <http://www.gurobi.com/>`__.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        This argument is not used by Gurobi.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to Gurobi as parameters. For instance, we\n    can call ``gurobi_solve_qp(P, q, G, h, u, FeasibilityTol=1e-8,\n    OptimalityTol=1e-8)``. Gurobi settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``FeasibilityTol``\n         - Primal feasibility tolerance.\n       * - ``OptimalityTol``\n         - Dual feasibility tolerance.\n       * - ``PSDTol``\n         - Positive semi-definite tolerance.\n       * - ``TimeLimit``\n         - Run time limit in seconds, 0 to disable.\n\n    Check out the `Parameter Descriptions\n    <https://www.gurobi.com/documentation/9.5/refman/parameter_descriptions.html>`__\n    documentation for all available Gurobi parameters.\n\n    Lower values for primal or dual tolerances yield more precise solutions at\n    the cost of computation time. See *e.g.* [Caron2022]_ for a primer of\n    solver tolerances.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = gurobi_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/highs_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `HiGHS <https://github.com/ERGO-Code/HiGHS>`__.\n\nHiGHS is an open source, serial and parallel solver for large scale sparse\nlinear programming (LP), mixed-integer programming (MIP), and quadratic\nprogramming (QP). It is written mostly in C++11 and available under the MIT\nlicence. HiGHS's QP solver implements a Nullspace Active Set method. It works\nbest on moderately-sized dense problems. If you are using HiGHS in a scientific\nwork, consider citing the corresponding paper [Huangfu2018]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport highspy\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..conversions import ensure_sparse_matrices\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef __set_hessian(model: highspy.HighsModel, P: spa.csc_matrix) -> None:\n    \"\"\"Set Hessian :math:`Q` of the cost :math:`(1/2) x^T Q x + c^T x`.\n\n    Parameters\n    ----------\n    model :\n        HiGHS model.\n    P :\n        Positive semidefinite cost matrix.\n    \"\"\"\n    P_lower = spa.tril(P, format=\"csc\")\n    model.hessian_.dim_ = P_lower.shape[0]\n    model.hessian_.start_ = P_lower.indptr\n    model.hessian_.index_ = P_lower.indices\n    model.hessian_.value_ = P_lower.data\n\n\ndef __set_columns(\n    model: highspy.HighsModel,\n    q: np.ndarray,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n) -> None:\n    r\"\"\"Set columns of the model.\n\n    Columns consist of:\n\n    - Linear part :math:`c` of the cost :math:`(1/2) x^T Q x + c^T x`\n    - Box inequalities :math:`l \\leq x \\leq u``\n\n    Parameters\n    ----------\n    model :\n        HiGHS model.\n    q :\n        Cost vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    \"\"\"\n    n = q.shape[0]\n    lp = model.lp_\n    lp.num_col_ = n\n    lp.col_cost_ = q\n    lp.col_lower_ = lb if lb is not None else np.full((n,), -highspy.kHighsInf)\n    lp.col_upper_ = ub if ub is not None else np.full((n,), highspy.kHighsInf)\n\n\ndef __set_rows(\n    model: highspy.HighsModel,\n    G: Optional[spa.csc_matrix] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[spa.csc_matrix] = None,\n    b: Optional[np.ndarray] = None,\n) -> None:\n    r\"\"\"Set rows :math:`L \\leq A x \\leq U`` of the model.\n\n    Parameters\n    ----------\n    model :\n        HiGHS model.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    \"\"\"\n    lp = model.lp_\n    lp.num_row_ = 0\n    row_list: list = []\n    row_lower: list = []\n    row_upper: list = []\n    if G is not None:\n        lp.num_row_ += G.shape[0]\n        row_list.append(G)\n        row_lower.append(np.full((G.shape[0],), -highspy.kHighsInf))\n        row_upper.append(h)\n    if A is not None:\n        lp.num_row_ += A.shape[0]\n        row_list.append(A)\n        row_lower.append(b)\n        row_upper.append(b)\n    if not row_list:\n        return\n    row_matrix = spa.vstack(row_list, format=\"csc\")\n    lp.a_matrix_.format_ = highspy.MatrixFormat.kColwise\n    lp.a_matrix_.start_ = row_matrix.indptr\n    lp.a_matrix_.index_ = row_matrix.indices\n    lp.a_matrix_.value_ = row_matrix.data\n    lp.a_matrix_.num_row_ = row_matrix.shape[0]\n    lp.a_matrix_.num_col_ = row_matrix.shape[1]\n    lp.row_lower_ = np.hstack(row_lower)\n    lp.row_upper_ = np.hstack(row_upper)\n\n\ndef highs_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using HiGHS.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to HiGHS as options. For instance, we\n    can call ``highs_solve_qp(P, q, G, h, u, primal_feasibility_tolerance=1e-8,\n    dual_feasibility_tolerance=1e-8)``. HiGHS settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``dual_feasibility_tolerance``\n         - Dual feasibility tolerance.\n       * - ``primal_feasibility_tolerance``\n         - Primal feasibility tolerance.\n       * - ``time_limit``\n         - Run time limit in seconds.\n\n    Check out the `HiGHS documentation <https://ergo-code.github.io/HiGHS/>`_\n    for more information on the solver.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\n            \"HiGHS: warm-start values are not available for this solver, \"\n            \"see: https://github.com/qpsolvers/qpsolvers/issues/94\"\n        )\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    P, G, A = ensure_sparse_matrices(\"highs\", P, G, A)\n    model = highspy.HighsModel()\n    __set_hessian(model, P)\n    __set_columns(model, q, lb, ub)\n    __set_rows(model, G, h, A, b)\n\n    solver = highspy.Highs()\n    if verbose:\n        solver.setOptionValue(\"log_to_console\", True)\n        solver.setOptionValue(\"log_dev_level\", highspy.HighsLogType.kVerbose)\n        solver.setOptionValue(\n            \"highs_debug_level\", highspy.HighsLogType.kVerbose\n        )\n    else:  # not verbose\n        solver.setOptionValue(\"log_to_console\", False)\n    for option, value in kwargs.items():\n        solver.setOptionValue(option, value)\n    solver.passModel(model)\n    solve_start_time = time.perf_counter()\n    solver.run()\n    solve_end_time = time.perf_counter()\n\n    result = solver.getSolution()\n    model_status = solver.getModelStatus()\n\n    solution = Solution(problem)\n    solution.found = model_status == highspy.HighsModelStatus.kOptimal\n    solution.x = np.array(result.col_value)\n    if G is not None:\n        solution.z = -np.array(result.row_dual[: G.shape[0]])\n        solution.y = (\n            -np.array(result.row_dual[G.shape[0] :])\n            if A is not None\n            else np.empty((0,))\n        )\n    else:  # G is None\n        solution.z = np.empty((0,))\n        solution.y = (\n            -np.array(result.row_dual) if A is not None else np.empty((0,))\n        )\n    solution.z_box = (\n        -np.array(result.col_dual)\n        if lb is not None or ub is not None\n        else np.empty((0,))\n    )\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef highs_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using HiGHS.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `HiGHS <https://github.com/ERGO-Code/HiGHS>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to HiGHS as options. For instance, we\n    can call ``highs_solve_qp(P, q, G, h, u, primal_feasibility_tolerance=1e-8,\n    dual_feasibility_tolerance=1e-8)``. HiGHS settings include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``dual_feasibility_tolerance``\n         - Dual feasibility tolerance.\n       * - ``primal_feasibility_tolerance``\n         - Primal feasibility tolerance.\n       * - ``time_limit``\n         - Run time limit in seconds.\n\n    Check out the `HiGHS documentation <https://ergo-code.github.io/HiGHS/>`_\n    for more information on the solver.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = highs_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/hpipm_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `HPIPM <https://github.com/giaf/hpipm>`__.\n\nHPIPM is a high-performance interior point method for solving convex quadratic\nprograms. It is designed to be efficient for small to medium-size problems\narising in model predictive control and embedded optmization. If you are using\nHPIPM in a scientific work, consider citing the corresponding paper\n[Frison2020]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport hpipm_python.common as hpipm\nimport numpy as np\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef hpipm_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    mode: str = \"balance\",\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using HPIPM.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    mode :\n        Solver mode, which provides a set of default solver arguments. Pick one\n        of [\"speed_abs\", \"speed\", \"balance\", \"robust\"]. These modes are\n        documented in section 4.2 *IPM implementation choices* of the reference\n        paper [Frison2020]_. The default one is \"balance\".\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to HPIPM. For instance, we can call\n    ``hpipm_solve_qp(P, q, G, h, u, tol_eq=1e-5)``. HPIPM settings include the\n    following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``iter_max``\n         - Maximum number of iterations.\n       * - ``tol_eq``\n         - Equality constraint tolerance.\n       * - ``tol_ineq``\n         - Inequality constraint tolerance.\n       * - ``tol_comp``\n         - Complementarity condition tolerance.\n       * - ``tol_dual_gap``\n         - Duality gap tolerance.\n       * - ``tol_stat``\n         - Stationarity condition tolerance.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    if verbose:\n        warnings.warn(\"verbose keyword argument is ignored by HPIPM\")\n\n    # setup the problem dimensions\n    nv = q.shape[0]\n    ne = b.shape[0] if b is not None else 0\n    ng = h.shape[0] if h is not None else 0\n\n    nlb = lb.shape[0] if lb is not None else 0\n    nub = ub.shape[0] if ub is not None else 0\n    nb = max(nlb, nub)\n\n    dim = hpipm.hpipm_dense_qp_dim()\n    dim.set(\"nv\", nv)\n    dim.set(\"nb\", nb)\n    dim.set(\"ne\", ne)\n    dim.set(\"ng\", ng)\n\n    # setup the problem data\n    qp = hpipm.hpipm_dense_qp(dim)\n    qp.set(\"H\", P)\n    qp.set(\"g\", q)\n\n    if ng > 0:\n        qp.set(\"C\", G)\n        qp.set(\"ug\", h)\n        # mask out the lower bound\n        qp.set(\"lg_mask\", np.zeros_like(h, dtype=bool))\n\n    if ne > 0:\n        qp.set(\"A\", A)\n        qp.set(\"b\", b)\n\n    if nb > 0:\n        # mark all variables as box-constrained\n        qp.set(\"idxb\", np.arange(nv))\n\n        # need to mask out lb or ub if the box constraints are only one-sided\n        # we also mask out infinities (and set the now-irrelevant value to\n        # zero), since HPIPM doesn't like infinities\n        if nlb > 0 and lb is not None:  # help mypy\n            lb_mask = np.isinf(lb)\n            lb[lb_mask] = 0.0\n            qp.set(\"lb\", lb)\n            qp.set(\"lb_mask\", ~lb_mask)\n        else:\n            qp.set(\"lb_mask\", np.zeros(nb, dtype=bool))\n\n        if nub > 0 and ub is not None:  # help mypy\n            ub_mask = np.isinf(ub)\n            ub[ub_mask] = 0.0\n            qp.set(\"ub\", ub)\n            qp.set(\"ub_mask\", ~ub_mask)\n        else:\n            qp.set(\"ub_mask\", np.zeros(nb, dtype=bool))\n\n    solver_args = hpipm.hpipm_dense_qp_solver_arg(dim, mode)\n    for key, val in kwargs.items():\n        solver_args.set(key, val)\n\n    sol = hpipm.hpipm_dense_qp_sol(dim)\n    if initvals is not None:\n        solver_args.set(\"warm_start\", 1)\n        sol.set(\"v\", initvals)\n\n    solver = hpipm.hpipm_dense_qp_solver(dim, solver_args)\n    solve_start_time = time.perf_counter()\n    solver.solve(qp, sol)\n    solve_end_time = time.perf_counter()\n\n    status = solver.get(\"status\")\n\n    solution = Solution(problem)\n    solution.extras = {\n        \"status\": status,\n        \"max_res_stat\": solver.get(\"max_res_stat\"),\n        \"max_res_eq\": solver.get(\"max_res_eq\"),\n        \"max_res_ineq\": solver.get(\"max_res_ineq\"),\n        \"max_res_comp\": solver.get(\"max_res_comp\"),\n        \"iter\": solver.get(\"iter\"),\n        \"stat\": solver.get(\"stat\"),\n    }\n    solution.found = status == 0\n    if not solution.found:\n        warnings.warn(f\"HPIPM exited with status '{status}'\")\n\n    # the equality multipliers in HPIPM are opposite in sign compared to what\n    # we expect here\n    solution.x = sol.get(\"v\").flatten()\n    solution.y = -sol.get(\"pi\").flatten() if ne > 0 else np.empty((0,))\n    solution.z = sol.get(\"lam_ug\").flatten() if ng > 0 else np.empty((0,))\n    if nb > 0:\n        solution.z_box = (sol.get(\"lam_ub\") - sol.get(\"lam_lb\")).flatten()\n    else:\n        solution.z_box = np.empty((0,))\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef hpipm_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    mode: str = \"balance\",\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using HPIPM.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `HPIPM <https://github.com/giaf/hpipm>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    mode :\n        Solver mode, which provides a set of default solver arguments. Pick one\n        of [\"speed_abs\", \"speed\", \"balance\", \"robust\"]. Default is \"balance\".\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to HPIPM. For instance, we can call\n    ``hpipm_solve_qp(P, q, G, h, u, tol_eq=1e-5)``. HPIPM settings include the\n    following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``iter_max``\n         - Maximum number of iterations.\n       * - ``tol_eq``\n         - Equality constraint tolerance.\n       * - ``tol_ineq``\n         - Inequality constraint tolerance.\n       * - ``tol_comp``\n         - Complementarity condition tolerance.\n       * - ``tol_dual_gap``\n         - Duality gap tolerance.\n       * - ``tol_stat``\n         - Stationarity condition tolerance.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = hpipm_solve_problem(problem, initvals, mode, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/jaxopt_osqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov\n\n\"\"\"\nSolver interface for jaxopt's implementation of the OSQP algorithm.\n\nJAXopt is a library of hardware-accelerated, batchable and differentiable\noptimizers implemented with JAX. JAX itself is a library for array-oriented\nnumerical computation that provides automatic differentiation and just-in-time\ncompilation.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport jax.numpy as jnp\nimport jaxopt\nimport numpy as np\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..problem import Problem\nfrom ..solution import Solution\nfrom ..solve_unconstrained import solve_unconstrained\n\n\ndef jaxopt_osqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program with the OSQP algorithm implemented in jaxopt.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by jaxopt.OSQP.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as keyword arguments to\n    jaxopt.OSQP. For instance, you can call ``jaxopt_osqp_solve_qp(P, q, G,\n    h, sigma=1e-5, momentum=0.9)``.\n\n    Note that JAX by default uses 32-bit floating point numbers, which can lead\n    to numerical instability. If you encounter numerical issues, consider using\n    64-bit floating point numbers by setting its `jax_enable_x64`\n    configuration.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if problem.is_unconstrained:\n        warnings.warn(\n            \"QP is unconstrained: \"\n            \"solving with SciPy's LSQR rather than jaxopt's OSQP\"\n        )\n        return solve_unconstrained(problem)\n    if initvals is not None and verbose:\n        warnings.warn(\"warm-start values are ignored by this wrapper\")\n\n    P, q, G_0, h_0, A, b, lb, ub = problem.unpack()\n    G, h = linear_from_box_inequalities(G_0, h_0, lb, ub, use_sparse=False)\n\n    osqp = jaxopt.OSQP(**kwargs)\n    solve_start_time = time.perf_counter()\n    result = osqp.run(\n        params_obj=(jnp.array(P), jnp.array(q)),\n        params_eq=(jnp.array(A), jnp.array(b)) if A is not None else None,\n        params_ineq=(jnp.array(G), jnp.array(h)) if G is not None else None,\n    )\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.x = np.array(result.params.primal)\n    solution.found = result.state.status == jaxopt.BoxOSQP.SOLVED\n    solution.y = (\n        np.array(result.params.dual_eq)\n        if result.params.dual_eq is not None\n        else np.empty((0,))\n    )\n\n    # split the dual variables into\n    # the box constraints and the linear constraints\n    solution.z, solution.z_box = split_dual_linear_box(\n        np.array(result.params.dual_ineq), problem.lb, problem.ub\n    )\n\n    solution.extras = {\n        \"iter_num\": int(result.state.iter_num),\n        \"error\": float(result.state.error),\n        \"status\": int(result.state.status),\n    }\n\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef jaxopt_osqp_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a QP with the OSQP algorithm implemented in jaxopt.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `jaxopt.OSQP\n    <https://jaxopt.github.io/stable/_autosummary/jaxopt.OSQP.html>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    verbose :\n        Set to `True` to print out extra information.\n    initvals :\n        This argument is not used by jaxopt.OSQP.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = jaxopt_osqp_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/kvxopt_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `KVXOPT <https://github.com/sanurielf/kvxopt>`__.\n\nKVXOPT is a fork from CVXOPT including more SuiteSparse functions\nand KLU sparse matrix solver. As CVXOPT, it is a free, open-source\ninterior-point solver.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Dict, Optional, Union\n\nimport kvxopt\nimport numpy as np\nimport scipy.sparse as spa\nfrom kvxopt.base import matrix as KVXOPTMatrix\nfrom kvxopt.base import spmatrix as KVXOPTSpmatrix\nfrom kvxopt.solvers import qp\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError, SolverError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\nkvxopt.solvers.options[\"show_progress\"] = False  # disable default verbosity\n\n\ndef __to_cvxopt(\n    M: Union[np.ndarray, spa.csc_matrix],\n) -> Union[KVXOPTMatrix, KVXOPTSpmatrix]:\n    \"\"\"Convert matrix to CVXOPT format.\n\n    Parameters\n    ----------\n    M :\n        Matrix in NumPy or CVXOPT format.\n\n    Returns\n    -------\n    :\n        Matrix in CVXOPT format.\n    \"\"\"\n    if isinstance(M, np.ndarray):\n        __infty__ = 1e10  # 1e20 tends to yield division-by-zero errors\n        M_noinf = np.nan_to_num(M, posinf=__infty__, neginf=-__infty__)\n        return KVXOPTMatrix(M_noinf)\n    coo = M.tocoo()\n    return KVXOPTSpmatrix(\n        coo.data.tolist(), coo.row.tolist(), coo.col.tolist(), size=M.shape\n    )\n\n\ndef kvxopt_solve_problem(\n    problem: Problem,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using KVXOPT.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    solver :\n        Set to 'mosek' to run MOSEK rather than KVXOPT.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError\n        If the KVXOPT rank assumption is not satisfied.\n\n    SolverError\n        If KVXOPT failed with an error.\n\n    Note\n    ----\n    .. _KVXOPT rank assumptions:\n\n    **Rank assumptions:** KVXOPT requires the QP matrices to satisfy the\n\n    .. math::\n\n        \\begin{split}\\begin{array}{cc}\n        \\mathrm{rank}(A) = p\n        &\n        \\mathrm{rank}([P\\ A^T\\ G^T]) = n\n        \\end{array}\\end{split}\n\n    where :math:`p` is the number of rows of :math:`A` and :math:`n` is the\n    number of optimization variables. See the \"Rank assumptions\" paragraph in\n    the report `The CVXOPT linear and quadratic cone program solvers\n    <http://www.ee.ucla.edu/~vandenbe/publications/coneprog.pdf>`_ for details.\n\n    Notes\n    -----\n    KVXOPT only considers the lower entries of :math:`P`, therefore it will use\n    a different cost than the one intended if a non-symmetric matrix is\n    provided.\n\n    Keyword arguments are forwarded as options to KVXOPT. For instance, we can\n    call ``kvxopt_solve_qp(P, q, G, h, u, abstol=1e-4, reltol=1e-4)``. KVXOPT\n    options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``abstol``\n         - Absolute tolerance on the duality gap.\n       * - ``feastol``\n         - Tolerance on feasibility conditions, that is, on the primal\n           residual.\n       * - ``maxiters``\n         - Maximum number of iterations.\n       * - ``refinement``\n         - Number of iterative refinement steps when solving KKT equations\n       * - ``reltol``\n         - Relative tolerance on the duality gap.\n\n    Check out `Algorithm Parameters\n    <https://cvxopt.org/userguide/coneprog.html#algorithm-parameters>`_ section\n    of the solver documentation for details and default values of all solver\n    parameters. See also [Caron2022]_ for a primer on the duality gap, primal\n    and dual residuals.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    if lb is not None or ub is not None:\n        G, h = linear_from_box_inequalities(\n            G, h, lb, ub, use_sparse=problem.has_sparse\n        )\n\n    args = [__to_cvxopt(P), __to_cvxopt(q)]\n    constraints: Dict[str, Optional[Union[KVXOPTMatrix, KVXOPTSpmatrix]]] = {\n        \"G\": None,\n        \"h\": None,\n        \"A\": None,\n        \"b\": None,\n    }\n    if G is not None and h is not None:\n        constraints[\"G\"] = __to_cvxopt(G)\n        constraints[\"h\"] = __to_cvxopt(h)\n    if A is not None and b is not None:\n        constraints[\"A\"] = __to_cvxopt(A)\n        constraints[\"b\"] = __to_cvxopt(b)\n    initvals_dict: Optional[Dict[str, Union[KVXOPTMatrix, KVXOPTSpmatrix]]]\n    initvals_dict = None\n    if initvals is not None:\n        if \"mosek\" in kwargs:\n            warnings.warn(\"MOSEK: warm-start values are ignored\")\n        initvals_dict = {\"x\": __to_cvxopt(initvals)}\n    kwargs[\"show_progress\"] = verbose\n\n    try:\n        solve_start_time = time.perf_counter()\n        res = qp(\n            *args,\n            solver=solver,  # type: ignore[arg-type]\n            initvals=initvals_dict,  # type: ignore[arg-type]\n            options=kwargs,\n            **constraints,  # type: ignore[arg-type]\n        )\n        solve_end_time = time.perf_counter()\n    except ValueError as exception:\n        error = str(exception)\n        if \"Rank(A)\" in error:\n            raise ProblemError(error) from exception\n        raise SolverError(error) from exception\n\n    solution = Solution(problem)\n    solution.extras = res\n    solution.found = \"optimal\" in res[\"status\"]  # type: ignore[operator]\n    solution.x = np.array(res[\"x\"]).flatten()\n    solution.y = (\n        np.array(res[\"y\"]).flatten() if b is not None else np.empty((0,))\n    )\n    if h is not None and res[\"z\"] is not None:\n        z_cvxopt = np.array(res[\"z\"]).flatten()\n        if z_cvxopt.size == h.size:\n            z, z_box = split_dual_linear_box(z_cvxopt, lb, ub)\n            solution.z = z\n            solution.z_box = z_box\n    else:  # h is None\n        solution.z = np.empty((0,))\n        solution.z_box = np.empty((0,))\n    solution.obj = res[\"primal objective\"]  # type: ignore[assignment]\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef kvxopt_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    solver: Optional[str] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using KVXOPT.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `KVXOPT <https://github.com/sanurielf/kvxopt>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix. Together with :math:`A` and :math:`G`, it should\n        satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`, see the rank\n        assumptions below.\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix. Together with :math:`P` and :math:`A`, it\n        should satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`, see the\n        rank assumptions below.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality constraint matrix. It needs to be full row rank, and\n        together with :math:`P` and :math:`G` satisfy\n        :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`. See the rank assumptions\n        below.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    solver :\n        Set to 'mosek' to run MOSEK rather than KVXOPT.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError\n        If the KVXOPT rank assumption is not satisfied.\n\n    SolverError\n        If KVXOPT failed with an error.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = kvxopt_solve_problem(\n        problem, solver, initvals, verbose, **kwargs\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/mosek_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `MOSEK <https://www.mosek.com/>`__.\n\nMOSEK is a solver for linear, mixed-integer linear, quadratic, mixed-integer\nquadratic, quadratically constraint, conic and convex nonlinear mathematical\noptimization problems. Its interior-point method is geared towards large scale\nsparse problems, in particular for linear or conic quadratic programs.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport mosek\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom ..problem import Problem\nfrom ..solution import Solution\nfrom ..solve_unconstrained import solve_unconstrained\nfrom ..solvers.cvxopt_ import cvxopt_solve_problem\n\n\ndef mosek_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using MOSEK.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if problem.is_unconstrained:\n        warnings.warn(\n            \"QP is unconstrained: solving with SciPy's LSQR rather than MOSEK\"\n        )\n        return solve_unconstrained(problem)\n    if \"mosek\" not in kwargs:\n        kwargs[\"mosek\"] = {}\n    kwargs[\"mosek\"][mosek.iparam.log] = 1 if verbose else 0\n    solve_start_time = time.perf_counter()\n    solution = cvxopt_solve_problem(problem, \"mosek\", initvals, **kwargs)\n    solve_end_time = time.perf_counter()\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef mosek_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using MOSEK.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using the `MOSEK interface from CVXOPT\n    <https://cvxopt.org/userguide/coneprog.html#optional-solvers>`_.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = mosek_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/nppro_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2023 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for NPPro.\n\nThe NPPro solver implements an enhanced Newton Projection with Proportioning\nmethod for strictly convex quadratic programming. Currently, it is designed for\ndense problems only.\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport nppro\nimport numpy as np\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef nppro_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using NPPro.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to NPPro. For\n    instance, you can call ``nppro_solve_qp(P, q, G, h, MaxIter=15)``.\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``MaxIter``\n         - Maximum number of iterations.\n       * - ``SkipPreprocessing``\n         - Skip preprocessing phase or not.\n       * - ``SkipPhaseOne``\n         - Skip feasible starting point finding or not.\n       * - ``InfVal``\n         - Values are assumed to be infinite above this threshold.\n       * - ``HessianUpdates``\n         - Enable Hessian updates or not.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack_as_dense()\n\n    n = P.shape[0]\n    m_iq = G.shape[0] if G is not None else 0\n    m_eq = A.shape[0] if A is not None else 0\n    m = m_iq + m_eq\n\n    A_ = None\n    l_ = None\n    u_ = None\n    lb_ = np.full(q.shape, -np.inf)\n    ub_ = np.full(q.shape, +np.inf)\n    if G is not None and h is not None:\n        A_ = G\n        l_ = np.full(h.shape, -np.inf)\n        u_ = h\n    if A is not None and b is not None:\n        A_ = A if A_ is None else np.vstack([A_, A])\n        l_ = b if l_ is None else np.hstack([l_, b])\n        u_ = b if u_ is None else np.hstack([u_, b])\n    if lb is not None:\n        lb_ = lb\n    if ub is not None:\n        ub_ = ub\n\n    # Create solver object\n    solver = nppro.CreateSolver(n, m)\n\n    # Use options from input if provided, defaults otherwise\n    max_iter = kwargs.get(\"MaxIter\", 100)\n    skip_preprocessing = kwargs.get(\"SkipPreprocessing\", False)\n    skip_phase_one = kwargs.get(\"SkipPhaseOne\", False)\n    inf_val = kwargs.get(\"InfVal\", 1e16)\n    hessian_updates = kwargs.get(\"HessianUpdates\", True)\n\n    # Set options\n    solver.setOption_MaxIter(max_iter)\n    solver.setOption_SkipPreprocessing(skip_preprocessing)\n    solver.setOption_SkipPhaseOne(skip_phase_one)\n    solver.setOption_InfVal(inf_val)\n    solver.setOption_HessianUpdates(hessian_updates)\n\n    x0 = np.full(q.shape, 0)\n    if initvals is not None:\n        x0 = initvals\n\n    # Conversion to datatype supported by the solver's C++ interface\n    P = np.asarray(P, order=\"C\", dtype=np.float64)\n    q = np.asarray(q, order=\"C\", dtype=np.float64)\n    A_ = np.asarray(A_, order=\"C\", dtype=np.float64)\n    l_ = np.asarray(l_, order=\"C\", dtype=np.float64)\n    u_ = np.asarray(u_, order=\"C\", dtype=np.float64)\n    lb_ = np.asarray(lb_, order=\"C\", dtype=np.float64)\n    ub_ = np.asarray(ub_, order=\"C\", dtype=np.float64)\n    x0 = np.asarray(x0, order=\"C\", dtype=np.float64)\n\n    # Call solver\n    solve_start_time = time.perf_counter()\n    x, fval, exitflag, iter_ = solver.solve(P, q, A_, l_, u_, lb_, ub_, x0)\n    solve_end_time = time.perf_counter()\n\n    # Store solution\n    exitflag_success = 1\n    solution = Solution(problem)\n    solution.found = exitflag == exitflag_success and not np.isnan(x).any()\n    if not solution.found:\n        # The second condition typically handle positive semi-definite cases\n        # that are not catched by the solver yet\n        warnings.warn(f\"NPPro exited with status {exitflag}\")\n    solution.x = x\n    solution.z = None  # not available yet\n    solution.y = None  # not available yet\n    solution.z_box = None  # not available yet\n    solution.extras = {\n        \"cost\": fval,\n        \"iter\": iter_,\n    }\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef nppro_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using NPPro.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using NPPro.\n\n    Parameters\n    ----------\n    P :\n        Positive definite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    See the Notes section in :func:`nppro_solve_problem`.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = nppro_solve_problem(problem, initvals, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/osqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `OSQP <https://osqp.org/>`__.\n\nThe OSQP solver implements an operator-splitting method, more precisely an\nalternating direction method of multipliers (ADMM). It is designed for both\ndense and sparse problems, and convexity is the only assumption it makes on\nproblem data (for instance, it does not make any rank assumption, contrary to\nsolvers such as :ref:`CVXOPT <CVXOPT rank assumptions>` or :ref:`qpSWIFT\n<qpSWIFT rank assumptions>`). If you are using OSQP in a scientific work,\nconsider citing the corresponding paper [Stellato2020]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom osqp import OSQP, SolverStatus\nfrom scipy.sparse import csc_matrix\n\nfrom ..conversions import ensure_sparse_matrices\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef osqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using OSQP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Raises\n    ------\n    ValueError\n        If the problem is clearly non-convex. See `this recommendation\n        <https://osqp.org/docs/interfaces/status_values.html#status-values>`_.\n        Note that OSQP may find the problem unfeasible if the problem is\n        slightly non-convex (in this context, the meaning of \"clearly\" and\n        \"slightly\" depends on how close the negative eigenvalues of :math:`P`\n        are to zero).\n\n    Note\n    ----\n    OSQP requires a symmetric `P` and won't check for errors otherwise. Check\n    out this point if you `get nan values\n    <https://github.com/oxfordcontrol/osqp/issues/10>`_ in your solutions.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to OSQP. For instance, we can call\n    ``osqp_solve_qp(P, q, G, h, u, eps_abs=1e-8, eps_rel=0.0)``. OSQP settings\n    include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``max_iter``\n         - Maximum number of iterations.\n       * - ``time_limit``\n         - Run time limit in seconds, 0 to disable.\n       * - ``eps_abs``\n         - Absolute feasibility tolerance. See `Convergence\n           <https://osqp.org/docs/solver/index.html#convergence>`__.\n       * - ``eps_rel``\n         - Relative feasibility tolerance. See `Convergence\n           <https://osqp.org/docs/solver/index.html#convergence>`__.\n       * - ``eps_prim_inf``\n         - Primal infeasibility tolerance.\n       * - ``eps_dual_inf``\n         - Dual infeasibility tolerance.\n       * - ``polish``\n         - Perform polishing. See `Polishing\n           <https://osqp.org/docs/solver/#polishing>`_.\n\n    Check out the `OSQP settings\n    <https://osqp.org/docs/interfaces/solver_settings.html>`_ documentation for\n    all available settings.\n\n    Lower values for absolute or relative tolerances yield more precise\n    solutions at the cost of computation time. See *e.g.* [Caron2022]_ for an\n    overview of solver tolerances.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    P, G, A = ensure_sparse_matrices(\"osqp\", P, G, A)\n\n    A_osqp = None\n    l_osqp = None\n    u_osqp = None\n    if G is not None and h is not None:\n        A_osqp = G\n        l_osqp = np.full(h.shape, -np.inf)\n        u_osqp = h\n    if A is not None and b is not None:\n        A_osqp = A if A_osqp is None else spa.vstack([A_osqp, A], format=\"csc\")\n        l_osqp = b if l_osqp is None else np.hstack([l_osqp, b])\n        u_osqp = b if u_osqp is None else np.hstack([u_osqp, b])\n    if lb is not None or ub is not None:\n        lb = lb if lb is not None else np.full(q.shape, -np.inf)\n        ub = ub if ub is not None else np.full(q.shape, +np.inf)\n        E = spa.eye(q.shape[0], format=\"csc\")\n        A_osqp = E if A_osqp is None else spa.vstack([A_osqp, E], format=\"csc\")\n        l_osqp = lb if l_osqp is None else np.hstack([l_osqp, lb])\n        u_osqp = ub if u_osqp is None else np.hstack([u_osqp, ub])\n\n    kwargs[\"verbose\"] = verbose\n    solver = OSQP()\n    solver.setup(P=P, q=q, A=A_osqp, l=l_osqp, u=u_osqp, **kwargs)\n    if initvals is not None:\n        solver.warm_start(x=initvals)\n\n    solve_start_time = time.perf_counter()\n    res = solver.solve()\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras = {\n        \"info\": res.info,\n        \"dual_inf_cert\": res.dual_inf_cert,\n        \"prim_inf_cert\": res.prim_inf_cert,\n    }\n\n    solution.found = res.info.status_val == SolverStatus.OSQP_SOLVED\n    if not solution.found:\n        warnings.warn(f\"OSQP exited with status '{res.info.status}'\")\n    solution.x = res.x\n    m = G.shape[0] if G is not None else 0\n    meq = A.shape[0] if A is not None else 0\n    solution.z = res.y[:m] if G is not None else np.empty((0,))\n    solution.y = res.y[m : m + meq] if A is not None else np.empty((0,))\n    solution.z_box = (\n        res.y[m + meq :]\n        if lb is not None or ub is not None\n        else np.empty((0,))\n    )\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef osqp_solve_qp(\n    P: Union[np.ndarray, csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using OSQP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `OSQP <https://github.com/oxfordcontrol/osqp>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ValueError\n        If the problem is clearly non-convex. See `this recommendation\n        <https://osqp.org/docs/interfaces/status_values.html#status-values>`_.\n        Note that OSQP may find the problem unfeasible if the problem is\n        slightly non-convex (in this context, the meaning of \"clearly\" and\n        \"slightly\" depends on how close the negative eigenvalues of :math:`P`\n        are to zero).\n\n    Note\n    ----\n    OSQP requires a symmetric `P` and won't check for errors otherwise. Check\n    out this point if you `get nan values\n    <https://github.com/oxfordcontrol/osqp/issues/10>`_ in your solutions.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to OSQP. For instance, we can call\n    ``osqp_solve_qp(P, q, G, h, u, eps_abs=1e-8, eps_rel=0.0)``. OSQP settings\n    include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``max_iter``\n         - Maximum number of iterations.\n       * - ``time_limit``\n         - Run time limit in seconds, 0 to disable.\n       * - ``eps_abs``\n         - Absolute feasibility tolerance. See `Convergence\n           <https://osqp.org/docs/solver/index.html#convergence>`__.\n       * - ``eps_rel``\n         - Relative feasibility tolerance. See `Convergence\n           <https://osqp.org/docs/solver/index.html#convergence>`__.\n       * - ``eps_prim_inf``\n         - Primal infeasibility tolerance.\n       * - ``eps_dual_inf``\n         - Dual infeasibility tolerance.\n       * - ``polish``\n         - Perform polishing. See `Polishing\n           <https://osqp.org/docs/solver/#polishing>`_.\n\n    Check out the `OSQP settings\n    <https://osqp.org/docs/interfaces/solver_settings.html>`_ documentation for\n    all available settings.\n\n    Lower values for absolute or relative tolerances yield more precise\n    solutions at the cost of computation time. See *e.g.* [Caron2022]_ for an\n    overview of solver tolerances.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = osqp_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/pdhcg_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for PDHCG.\n\nPDHCG (Primal-Dual Hybrid Conjugate Gradient) is a high-performance,\nGPU-accelerated solver designed for large-scale convex Quadratic\nProgramming (QP). It is particularly efficient for huge-scale problems\nby fully leveraging NVIDIA CUDA architectures.\n\nNote:\n    To use this solver, you need an NVIDIA GPU and the ``pdhcg`` package\n    installed via ``pip install pdhcg``. For advanced installation (e.g.,\n    custom CUDA paths), please refer to the\n    `official PDHCG-II repository <https://github.com/Lhongpei/PDHCG-II>`_.\n\nReferences\n----------\n- `PDHCG-II <https://arxiv.org/abs/2602.23967>`_\n\"\"\"\n\nfrom typing import Any, List, Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom pdhcg import Model\n\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef pdhcg_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs: Any,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using PDHCG.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to PDHCG as solver parameters.\n    For instance, you can call ``pdhcg_solve_qp(..., TimeLimit=60)``.\n    Common PDHCG parameters include:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``TimeLimit``\n         - Maximum wall-clock time in seconds (default: 3600.0).\n       * - ``IterationLimit``\n         - Maximum number of iterations.\n       * - ``OptimalityTol``\n         - Relative tolerance for optimality gap (default: 1e-4).\n       * - ``FeasibilityTol``\n         - Relative feasibility tolerance for residuals (default: 1e-4).\n       * - ``OutputFlag``\n         - Enable (True) or disable (False) console logging output.\n\n    For advanced parameters, please refer to the\n    `PDHCG Documentation <https://github.com/Lhongpei/PDHCG-II>`_.\n    \"\"\"\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n\n    C_mats: List[Any] = []\n    l_bounds: List[Any] = []\n    u_bounds: List[Any] = []\n\n    if G is not None and h is not None:\n        C_mats.append(G)\n        l_bounds.append(np.full(h.shape, -np.inf))\n        u_bounds.append(h)\n\n    if A is not None and b is not None:\n        C_mats.append(A)\n        l_bounds.append(b)\n        u_bounds.append(b)\n\n    constraint_matrix: Optional[\n        Union[np.ndarray, spa.csr_matrix, spa.csc_matrix]\n    ] = None\n    constraint_lower_bound: Optional[np.ndarray] = None\n    constraint_upper_bound: Optional[np.ndarray] = None\n\n    if C_mats:\n        if any(spa.issparse(mat) for mat in C_mats):\n            constraint_matrix = spa.vstack(C_mats, format=\"csr\")  # type: ignore\n        else:\n            constraint_matrix = np.vstack(C_mats)  # type: ignore\n        constraint_lower_bound = np.concatenate(l_bounds)  # type: ignore\n        constraint_upper_bound = np.concatenate(u_bounds)  # type: ignore\n\n    model = Model(\n        objective_matrix=P,\n        objective_vector=q,\n        constraint_matrix=constraint_matrix,\n        constraint_lower_bound=constraint_lower_bound,\n        constraint_upper_bound=constraint_upper_bound,\n        variable_lower_bound=lb,\n        variable_upper_bound=ub,\n    )\n\n    if verbose:\n        model.setParam(\"OutputFlag\", 2)\n    else:\n        model.setParam(\"OutputFlag\", 0)\n\n    if kwargs:\n        model.setParams(**kwargs)\n\n    if initvals is not None:\n        model.setWarmStart(primal=initvals)\n\n    model.optimize()\n\n    solution = Solution(problem)\n\n    status_str = str(model.Status).upper() if model.Status else \"\"\n    solution.found = status_str == \"OPTIMAL\"\n\n    if solution.found and model.X is not None:\n        solution.x = np.array(model.X)\n        solution.obj = model.ObjVal\n\n    solution.extras[\"runtime\"] = model.Runtime\n    solution.extras[\"iter\"] = model.IterCount\n    solution.extras[\"status\"] = status_str\n\n    if solution.found and model.Pi is not None and C_mats:\n        pi = np.array(model.Pi)\n        idx = 0\n        if G is not None:\n            num_g = G.shape[0]\n            solution.z = -pi[idx : idx + num_g]\n            idx += num_g\n        else:\n            solution.z = np.empty((0,))\n\n        if A is not None:\n            num_a = A.shape[0]\n            solution.y = -pi[idx : idx + num_a]\n        else:\n            solution.y = np.empty((0,))\n    else:\n        solution.z = np.empty((0,)) if G is None else np.empty(G.shape[0])\n        solution.y = np.empty((0,)) if A is None else np.empty(A.shape[0])\n\n    return solution\n\n\ndef pdhcg_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs: Any,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using PDHCG.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `PDHCG <https://github.com/Lhongpei/PDHCG-II>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to PDHCG as solver parameters.\n    For instance, you can call ``pdhcg_solve_qp(..., TimeLimit=60)``.\n    Common PDHCG parameters include:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``TimeLimit``\n         - Maximum wall-clock time in seconds (default: 3600.0).\n       * - ``IterationLimit``\n         - Maximum number of iterations.\n       * - ``OptimalityTol``\n         - Relative tolerance for optimality gap (default: 1e-4).\n       * - ``FeasibilityTol``\n         - Relative feasibility tolerance for residuals (default: 1e-4).\n       * - ``OutputFlag``\n         - Enable (True) or disable (False) console logging output.\n\n    For advanced parameters, please refer to the\n    `PDHCG Documentation <https://github.com/Lhongpei/PDHCG-II>`_.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = pdhcg_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/piqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `PIQP`_.\n\n.. _PIQP: https://github.com/PREDICT-EPFL/piqp\n\nPIQP is a proximal interior-point quadratic programming solver for dense and\nsparse problems. Its algorithm combines an infeasible interior-point method\nwith the proximal method of multipliers, and is designed to handle\nill-conditioned convex problems without the need for linear independence of\nconstraints. If you are using PIQP in a scientific work, consider citing the\ncorresponding paper [Schwan2023]_.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport numpy as np\nimport piqp\nimport scipy.sparse as spa\n\nfrom ..conversions import ensure_sparse_matrices\nfrom ..exceptions import ParamError, ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef __select_backend(backend: Optional[str], use_csc: bool):\n    \"\"\"Select backend function for PIQP.\n\n    Parameters\n    ----------\n    backend :\n        PIQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    use_csc :\n        If ``True``, use sparse matrices if the backend is not specified.\n\n    Returns\n    -------\n    :\n        Backend solve function.\n\n    Raises\n    ------\n    ParamError\n        If the required backend is not a valid PIQP backend.\n    \"\"\"\n    if backend is None:\n        return piqp.SparseSolver() if use_csc else piqp.DenseSolver()\n    if backend == \"dense\":\n        return piqp.DenseSolver()\n    if backend == \"sparse\":\n        return piqp.SparseSolver()\n    raise ParamError(f'Unknown PIQP backend \"{backend}')\n\n\ndef piqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    backend: Optional[str] = None,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using PIQP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by PIQP.\n    backend :\n        PIQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to PIQP. For\n    instance, you can call ``piqp_solve_qp(P, q, G, h, eps_abs=1e-6)``.\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``rho_init``\n         - Initial value for the primal proximal penalty parameter rho.\n       * - ``delta_init``\n         - Initial value for the augmented lagrangian penalty parameter delta.\n       * - ``eps_abs``\n         - Absolute tolerance.\n       * - ``eps_rel``\n         - Relative tolerance.\n       * - ``check_duality_gap``\n         - Check terminal criterion on duality gap.\n       * - ``eps_duality_gap_abs``\n         - Absolute tolerance on duality gap.\n       * - ``eps_duality_gap_rel``\n         - Threshold value for infeasibility detection.\n       * - ``infeasibility_threshold``\n         - Relative tolerance on duality gap.\n       * - ``reg_lower_limit``\n         - Lower limit for regularization.\n       * - ``reg_finetune_lower_limit``\n         - Fine tune lower limit regularization.\n       * - ``reg_finetune_primal_update_threshold``\n         - Threshold of number of no primal updates to transition to fine\n           tune mode.\n       * - ``reg_finetune_dual_update_threshold``\n         - Threshold of number of no dual updates to transition to fine\n           tune mode.\n       * - ``max_iter``\n         - Maximum number of iterations.\n       * - ``max_factor_retires``\n         - Maximum number of factorization retires before failure.\n       * - ``preconditioner_scale_cost``\n         - \tScale cost in Ruiz preconditioner.\n       * - ``preconditioner_reuse_on_update``\n         - \tReuse the preconditioner from previous setup/update.\n       * - ``preconditioner_iter``\n         - Maximum of preconditioner iterations.\n       * - ``tau``\n         - Maximum interior point step length.\n       * - ``kkt_solver``\n         - KKT solver backend.\n       * - ``iterative_refinement_always_enabled``\n         - Always run iterative refinement and not only on factorization\n           failure.\n       * - ``iterative_refinement_eps_abs``\n         - Iterative refinement absolute tolerance.\n       * - ``iterative_refinement_eps_rel``\n         - Iterative refinement relative tolerance.\n       * - ``iterative_refinement_max_iter``\n         - Maximum number of iterations for iterative refinement.\n       * - ``iterative_refinement_min_improvement_rate``\n         - Minimum improvement rate for iterative refinement.\n       * - ``iterative_refinement_static_regularization_eps``\n         - Static regularization for KKT system for iterative refinement.\n       * - ``iterative_refinement_static_regularization_rel``\n         - Static regularization w.r.t. the maximum abs diagonal term of\n           KKT system.\n       * - ``verbose``\n         - Verbose printing.\n       * - ``compute_timings``\n         - Measure timing information internally.\n\n    This list is not exhaustive. Check out the `solver documentation\n    <https://predict-epfl.github.io/piqp/interfaces/settings>`__ for details.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"warm-start values are ignored by PIQP\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    n: int = q.shape[0]\n    if G is None and h is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: G is not set but h is set\"\n        )\n    if G is not None and h is None:\n        raise ProblemError(\"Inconsistent inequalities: G is set but h is None\")\n    if A is None and b is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: A is not set but b is set\"\n        )\n    if A is not None and b is None:\n        raise ProblemError(\"Inconsistent inequalities: A is set but b is None\")\n    # PIQP does not support A, b, G, and h to be None.\n    use_csc: bool = (\n        not isinstance(P, np.ndarray)\n        or (G is not None and not isinstance(G, np.ndarray))\n        or (A is not None and not isinstance(A, np.ndarray))\n    )\n    G_piqp: Union[np.ndarray, spa.csc_matrix] = (\n        G\n        if G is not None\n        else spa.csc_matrix(np.zeros((0, n)))\n        if use_csc\n        else np.zeros((0, n))\n    )\n    A_piqp: Union[np.ndarray, spa.csc_matrix] = (\n        A\n        if A is not None\n        else spa.csc_matrix(np.zeros((0, n)))\n        if use_csc\n        else np.zeros((0, n))\n    )\n    h_piqp = np.zeros((0,)) if h is None else h\n    b_piqp = np.zeros((0,)) if b is None else b\n    if use_csc:\n        P, G_piqp_sparse, A_piqp_sparse = ensure_sparse_matrices(\n            \"piqp\", P, G_piqp, A_piqp\n        )\n        assert G_piqp_sparse is not None and A_piqp_sparse is not None\n        G_piqp = G_piqp_sparse\n        A_piqp = A_piqp_sparse\n\n    solver = __select_backend(backend, use_csc)\n    solver.settings.verbose = verbose\n    for key, value in kwargs.items():\n        try:\n            setattr(solver.settings, key, value)\n        except AttributeError:\n            if verbose:\n                warnings.warn(\n                    f\"Received an undefined solver setting {key}\\\n                    with value {value}\"\n                )\n    old_interface = (\n        float(piqp.__version__.split(\".\")[0]) == 0\n        and float(piqp.__version__.split(\".\")[1]) <= 5\n    )\n    if old_interface:\n        solver.setup(P, q, A_piqp, b_piqp, G_piqp, h_piqp, lb, ub)\n    else:\n        solver.setup(P, q, A_piqp, b_piqp, G_piqp, None, h_piqp, lb, ub)\n    solve_start_time = time.perf_counter()\n    status = solver.solve()\n    solve_end_time = time.perf_counter()\n    success_status = piqp.PIQP_SOLVED\n\n    solution = Solution(problem)\n    solution.extras = {\"info\": solver.result.info}\n    solution.found = status == success_status\n    solution.x = solver.result.x\n    if A is None:\n        solution.y = np.empty((0,))\n    else:\n        solution.y = solver.result.y\n    if G is None:\n        solution.z = np.empty((0,))\n    else:\n        if old_interface:\n            solution.z = solver.result.z\n        else:\n            solution.z = solver.result.z_u\n    if lb is not None or ub is not None:\n        if old_interface:\n            solution.z_box = solver.result.z_ub - solver.result.z_lb\n        else:\n            solution.z_box = solver.result.z_bu - solver.result.z_bl\n    else:\n        solution.z_box = np.empty((0,))\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef piqp_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    backend: Optional[str] = None,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using PIQP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `PIQP\n    <https://github.com/PREDICT-EPFL/piqp>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    backend :\n        PIQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    verbose :\n        Set to `True` to print out extra information.\n    initvals :\n        This argument is not used by PIQP.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = piqp_solve_problem(\n        problem, initvals, verbose, backend, **kwargs\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/proxqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `ProxQP`_.\n\n.. _ProxQP: https://github.com/Simple-Robotics/proxsuite#proxqp\n\nProxQP is a primal-dual augmented Lagrangian method with proximal heuristics.\nIt converges to the solution of feasible problems, or to the solution to the\nclosest feasible one if the input problem is unfeasible. ProxQP is part of the\nProxSuite collection of open-source solvers. If you use ProxQP in a scientific\nwork, consider citing the corresponding paper [Bambade2022]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nfrom typing import Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom proxsuite import proxqp\n\nfrom ..conversions import combine_linear_box_inequalities\nfrom ..exceptions import ParamError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef __select_backend(backend: Optional[str], use_csc: bool):\n    \"\"\"Select backend function for ProxQP.\n\n    Parameters\n    ----------\n    backend :\n        ProxQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    use_csc :\n        If ``True``, use sparse matrices if the backend is not specified.\n\n    Returns\n    -------\n    :\n        Backend solve function.\n\n    Raises\n    ------\n    ParamError\n        If the required backend is not a valid ProxQP backend.\n    \"\"\"\n    if backend is None:\n        return proxqp.sparse.solve if use_csc else proxqp.dense.solve\n    if backend == \"dense\":\n        return proxqp.dense.solve\n    if backend == \"sparse\":\n        return proxqp.sparse.solve\n    raise ParamError(f'Unknown ProxQP backend \"{backend}')\n\n\ndef proxqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    backend: Optional[str] = None,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using ProxQP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    backend :\n        ProxQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Raises\n    ------\n    ParamError\n        If a warm-start value is given both in `initvals` and the `x` keyword\n        argument.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to ProxQP. For\n    instance, you can call ``proxqp_solve_qp(P, q, G, h, eps_abs=1e-6)``.\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``x``\n         - Warm start value for the primal variable.\n       * - ``y``\n         - Warm start value for the dual Lagrange multiplier for equality\n           constraints.\n       * - ``z``\n         - Warm start value for the dual Lagrange multiplier for inequality\n           constraints.\n       * - ``eps_abs``\n         - Asbolute stopping criterion of the solver (default: 1e-3, note that\n           this is a laxer default than other solvers). See *e.g.*\n           [Caron2022]_ for an overview of solver tolerances.\n       * - ``eps_rel``\n         - Relative stopping criterion of the solver. See *e.g.* [Caron2022]_\n           for an overview of solver tolerances.\n       * - ``check_duality_gap``\n         - If set to true (false by default), ProxQP will include the duality\n           gap in absolute and relative stopping criteria.\n       * - ``mu_eq``\n         - Proximal step size wrt equality constraints multiplier.\n       * - ``mu_in``\n         - Proximal step size wrt inequality constraints multiplier.\n       * - ``rho``\n         - Proximal step size wrt primal variable.\n       * - ``compute_preconditioner``\n         - If ``True`` (default), the preconditioner will be derived.\n       * - ``compute_timings``\n         - If ``True`` (default), timings will be computed by the solver (setup\n           time, solving time, and run time = setup time + solving time).\n       * - ``max_iter``\n         - Maximal number of authorized outer iterations.\n       * - ``initial_guess``\n         - Sets the initial guess option for initilizing x, y and z.\n\n    This list is not exhaustive. Check out the `solver documentation\n    <https://simple-robotics.github.io/proxsuite/>`__ for details.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        if \"x\" in kwargs:\n            raise ParamError(\n                \"Warm-start value specified in both `initvals` and `x` kwargs\"\n            )\n        kwargs[\"x\"] = initvals\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    n: int = q.shape[0]\n    use_csc: bool = (\n        not isinstance(P, np.ndarray)\n        or (G is not None and not isinstance(G, np.ndarray))\n        or (A is not None and not isinstance(A, np.ndarray))\n    )\n    Cx, ux, lx = combine_linear_box_inequalities(G, h, lb, ub, n, use_csc)\n    solve = __select_backend(backend, use_csc)\n    solve_start_time = time.perf_counter()\n    result = solve(\n        P,\n        q,\n        A,\n        b,\n        Cx,\n        lx,\n        ux,\n        verbose=verbose,\n        **kwargs,\n    )\n    solve_end_time = time.perf_counter()\n    solution = Solution(problem)\n    solution.extras = {\"info\": result.info}\n    solution.found = result.info.status == proxqp.QPSolverOutput.PROXQP_SOLVED\n    solution.x = result.x\n    solution.y = result.y\n    if lb is not None or ub is not None:\n        solution.z = result.z[:-n]\n        solution.z_box = result.z[-n:]\n    else:  # lb is None and ub is None\n        solution.z = result.z\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef proxqp_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    backend: Optional[str] = None,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using ProxQP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `ProxQP\n    <https://github.com/Simple-Robotics/proxsuite#proxqp>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    backend :\n        ProxQP backend to use in ``[None, \"dense\", \"sparse\"]``. If ``None``\n        (default), the backend is selected based on the type of ``P``.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = proxqp_solve_problem(\n        problem, initvals, verbose, backend, **kwargs\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/pyqpmad_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `pyqpmad <https://github.com/ahoarau/pyqpmad>`__.\n\npyqpmad is a Python wrapper for qpmad, a C++ implementation of\nGoldfarb-Idnani's dual active-set method [Goldfarb1983]_. It works best on\nwell-conditioned dense problems with a positive-definite Hessian.\n\n`qpmad <https://github.com/asherikov/qpmad>`\n\n**Warm-start:** this solver interface supports warm starting 🌡️\n\"\"\"\n\nimport warnings\nfrom typing import Optional\n\nimport numpy as np\nimport pyqpmad\nfrom numpy import hstack, vstack\n\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef pyqpmad_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using pyqpmad.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Initial guess for the primal solution. pyqpmad uses this as a\n        warm-start if provided.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If the problem has sparse matrices (pyqpmad is a dense solver).\n\n    Notes\n    -----\n    Keyword arguments are forwarded as attributes of a\n    ``pyqpmad.SolverParameters`` object. Supported settings include:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``tolerance``\n         - Solver feasibility/optimality tolerance.\n       * - ``max_iter``\n         - Maximum number of iterations.\n\n    Check out the `qpmad documentation\n    <https://asherikov.github.io/qpmad/>`_ for all available settings.\n    \"\"\"\n    if verbose:\n        warnings.warn(\"pyqpmad does not support verbose output\")\n\n    if problem.has_sparse:\n        raise ProblemError(\"pyqpmad does not support sparse matrices\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack_as_dense()\n    n = q.shape[0]\n\n    A_qpmad = None\n    Alb_qpmad = None\n    Aub_qpmad = None\n\n    if A is not None and b is not None:\n        A_qpmad = A\n        Alb_qpmad = b\n        Aub_qpmad = b\n\n    if G is not None and h is not None:\n        A_qpmad = G if A_qpmad is None else vstack([A_qpmad, G])\n        lb_G = np.full(h.shape, -np.inf)\n        Alb_qpmad = lb_G if Alb_qpmad is None else hstack([Alb_qpmad, lb_G])\n        Aub_qpmad = h if Aub_qpmad is None else hstack([Aub_qpmad, h])\n\n    # qpmad requires the Hessian in Fortran (column-major) order.\n    H_qpmad = np.asfortranarray(P, dtype=np.float64)\n    if A_qpmad is not None:\n        A_qpmad = np.asfortranarray(A_qpmad, dtype=np.float64)\n\n    # Build solver parameters from keyword arguments.\n    params = pyqpmad.SolverParameters()\n    for key, val in kwargs.items():\n        if hasattr(params, key):\n            setattr(params, key, val)\n        elif verbose:\n            warnings.warn(f\"pyqpmad ignoring unknown parameter: {key!r}\")\n\n    # Initialize primal variable; warm start if initvals is provided.\n    x = (\n        np.array(initvals, dtype=np.float64)\n        if initvals is not None\n        else np.zeros(n, dtype=np.float64)\n    )\n\n    lb_qpmad = None\n    ub_qpmad = None\n    if lb is not None or ub is not None:\n        lb_qpmad = (\n            np.asarray(lb, dtype=np.float64)\n            if lb is not None\n            else np.full(n, -np.inf)\n        )\n        ub_qpmad = (\n            np.asarray(ub, dtype=np.float64)\n            if ub is not None\n            else np.full(n, np.inf)\n        )\n\n    solver = pyqpmad.Solver()\n    try:\n        status = solver.solve(\n            x,\n            H_qpmad,\n            np.asarray(q, dtype=np.float64),\n            lb_qpmad,\n            ub_qpmad,\n            A_qpmad,\n            Alb_qpmad,\n            Aub_qpmad,\n            params,\n        )\n    except Exception:\n        status = None  # To make solution.found = False\n\n    solution = Solution(problem)\n    solution.found = status == pyqpmad.ReturnStatus.OK\n    if solution.found:\n        solution.x = x\n\n        n_simple = n if (lb is not None or ub is not None) else 0\n        n_eq_orig = A.shape[0] if A is not None else 0\n        n_ineq_orig = G.shape[0] if G is not None else 0\n\n        # Initialise dual arrays (zeros covers inactive constraints).\n        # z is always a non-None array (possibly empty) after a successful\n        # solve.\n        solution.y = np.zeros(n_eq_orig)\n        solution.z = np.zeros(n_ineq_orig)\n        if n_simple > 0:\n            solution.z_box = np.zeros(n)\n\n        # Reconstruct z and z_box from the active-set inequality duals.\n        # qpmad index ordering:\n        # - 0..n_simple-1 are simple bounds (lb/ub),\n        # - n_simple..n_simple+n_eq_orig-1 are equality rows of A_qpmad,\n        # - n_simple+n_eq_orig.. are inequality rows of A_qpmad (from G).\n        ineq_dual = solver.get_inequality_dual()\n        for i in range(len(ineq_dual.dual)):\n            ci = int(ineq_dual.indices[i])\n            d = float(ineq_dual.dual[i])\n            if ci < n_simple:\n                # Active box bound: sign follows qpsolvers convention\n                # (negative for lower, positive for upper)\n                solution.z_box[ci] = (  # type: ignore[index]\n                    # z_box is set to np.zeros(n) above when n_simple > 0\n                    -d if ineq_dual.is_lower[i] else d\n                )\n            else:\n                j = ci - n_simple  # row in A_qpmad\n                if j >= n_eq_orig:  # inequality row from G\n                    solution.z[j - n_eq_orig] = d\n\n        # Compute equality duals y from KKT stationarity to avoid any\n        # sign-convention ambiguity with qpmad's internal dual storage:\n        #   P x + q + A' y + G' z + z_box = 0\n        #   =>  A' y = -(P x + q + G' z + z_box)\n        if A is not None and n_eq_orig > 0:\n            residual = P @ x + q\n            if G is not None and solution.z is not None:\n                residual = residual + G.T @ solution.z\n            if solution.z_box is not None:\n                residual = residual + solution.z_box\n            solution.y, _, _, _ = np.linalg.lstsq(A.T, -residual, rcond=None)\n\n        solution.extras = {\"num_iterations\": solver.get_num_iterations()}\n    return solution\n\n\ndef pyqpmad_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using pyqpmad.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `pyqpmad <https://pypi.org/project/pyqpmad/>`__, a\n    Python wrapper for the `qpmad\n    <https://github.com/asherikov/qpmad>`__ C++ solver.\n\n    Parameters\n    ----------\n    P :\n        Symmetric positive-definite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Initial guess for the primal solution (warm start).\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = pyqpmad_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/qpalm_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `QPALM`_.\n\n.. _QPALM: https://github.com/kul-optec/QPALM\n\nQPALM is a proximal augmented-Lagrangian solver for (possibly nonconvex)\nquadratic programs, implemented in the C programming language. If you use QPALM\nin a scientific work, consider citing the corresponding paper [Hermans2022]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport numpy as np\nimport qpalm\nimport scipy.sparse as spa\n\nfrom ..conversions import (\n    combine_linear_box_inequalities,\n    ensure_sparse_matrices,\n)\nfrom ..exceptions import ParamError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef qpalm_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using QPALM.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Raises\n    ------\n    ParamError\n        If a warm-start value is given both in `initvals` and the `x` keyword\n        argument.\n\n    Note\n    ----\n    QPALM internally only uses the upper-triangular part of the cost matrix\n    :math:`P`.\n\n    Notes\n    -----\n    Keyword arguments are forwarded as \"settings\" to QPALM. For instance, we\n    can call ``qpalm_solve_qp(P, q, G, h, u, eps_abs=1e-4, eps_rel=1e-4)``.\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``max_iter``\n         - Maximum number of iterations.\n       * - ``eps_abs``\n         - Asbolute stopping criterion of the solver. See *e.g.* [Caron2022]_\n           for an overview of solver tolerances.\n       * - ``eps_rel``\n         - Relative stopping criterion of the solver. See *e.g.* [Caron2022]_\n           for an overview of solver tolerances.\n       * - ``rho``\n         - Tolerance scaling factor.\n       * - ``theta``\n         - Penalty update criterion parameter.\n       * - ``delta``\n         - Penalty update factor.\n       * - ``sigma_max``\n         - Penalty factor cap.\n       * - ``proximal``\n         - Boolean, use proximal method of multipliers or not.\n\n    This list is not exhaustive. Check out the `solver documentation\n    <https://kul-optec.github.io/QPALM/Doxygen/structqpalm_1_1Settings.html>`__\n    for details.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        if \"x\" in kwargs:\n            raise ParamError(\n                \"Warm-start value specified in both `initvals` and `x` kwargs\"\n            )\n        kwargs[\"x\"] = initvals\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    P, G, A = ensure_sparse_matrices(\"qpalm\", P, G, A)\n    n: int = q.shape[0]\n\n    Cx, ux, lx = combine_linear_box_inequalities(G, h, lb, ub, n, use_csc=True)\n    if A is not None and b is not None:\n        Cx = spa.vstack((A, Cx), format=\"csc\") if Cx is not None else A\n        lx = np.hstack((b, lx)) if lx is not None else b\n        ux = np.hstack((b, ux)) if ux is not None else b\n    m: int = Cx.shape[0] if Cx is not None else 0\n\n    data = qpalm.Data(n, m)\n    if Cx is not None:\n        data.A = Cx\n        data.bmax = ux\n        data.bmin = lx\n    data.Q = P\n    data.q = q\n\n    settings = qpalm.Settings()\n    settings.verbose = verbose\n    for key, value in kwargs.items():\n        try:\n            setattr(settings, key, value)\n        except AttributeError:\n            if verbose:\n                warnings.warn(\n                    f\"Received an undefined solver setting {key}\\\n                    with value {value}\"\n                )\n\n    solver = qpalm.Solver(data, settings)\n    solve_start_time = time.perf_counter()\n    solver.solve()\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras = {\"info\": solver.info}\n    solution.found = solver.info.status == \"solved\"\n    solution.x = solver.solution.x\n    m_eq: int = A.shape[0] if A is not None else 0\n    m_leq: int = G.shape[0] if G is not None else 0\n    solution.y = solver.solution.y[0:m_eq]\n    solution.z = solver.solution.y[m_eq : m_eq + m_leq]\n    solution.z_box = solver.solution.y[m_eq + m_leq :]\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef qpalm_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using QPALM.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `QPALM <https://github.com/kul-optec/QPALM>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = qpalm_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/qpax_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov\n\n\"\"\"\nSolver interface for `qpax <https://github.com/kevin-tracy/qpax>`__.\n\nqpax is an open-source QP solver that can be combined with JAX's jit and vmap\nfunctionality, as well as differentiated with reverse-mode differentiation. It\nis based on a primal-dual interior point algorithm. If you are using qpax in\na scientific work, consider citing the corresponding paper [Tracy2024]_.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport numpy as np\nimport qpax\nimport scipy.sparse as spa\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef qpax_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using qpax.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by qpax.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to qpax. For\n    instance, you can call ``qpax_solve_qp(P, q, G, h, solver_tol=1e-5)``.\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``solver_tol``\n         - Tolerance for the solver.\n\n    Note that `jax` by default uses 32-bit floating point numbers, which can\n    lead to numerical instability. If you encounter numerical issues, consider\n    using 64-bit floating point numbers by setting\n    ```python\n    import jax\n    jax.config.update(\"jax_enable_x64\", True)\n    ```\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"warm-start values are ignored by qpax\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    n: int = q.shape[0]\n    if G is None and h is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: G is not set but h is set\"\n        )\n    if G is not None and h is None:\n        raise ProblemError(\"Inconsistent inequalities: G is set but h is None\")\n    if A is None and b is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: A is not set but b is set\"\n        )\n    if A is not None and b is None:\n        raise ProblemError(\"Inconsistent inequalities: A is set but b is None\")\n\n    # construct the qpax problem\n    G, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n    if G is None:\n        G = np.zeros((0, n))\n        h = np.zeros((0,))\n\n    # qpax does not support A, b to be None.\n    A_qpax = np.zeros((0, n)) if A is None else A\n    b_qpax = np.zeros((0)) if b is None else b\n\n    # qpax does not support sparse matrices,\n    # so we need to convert them to dense\n    if isinstance(P, spa.csc_matrix):\n        P = P.toarray()\n    if isinstance(A_qpax, spa.csc_matrix):\n        A_qpax = A_qpax.toarray()\n    if isinstance(G, spa.csc_matrix):\n        G = G.toarray()\n\n    solve_start_time = time.perf_counter()\n    x, s, z, y, converged, iters1 = qpax.solve_qp(\n        P,\n        q,\n        A_qpax,\n        b_qpax,\n        G,\n        h,\n        **kwargs,\n    )\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.x = x\n    solution.found = converged\n    solution.y = y\n\n    # split the dual variables into\n    # the box constraints and the linear constraints\n    solution.z, solution.z_box = split_dual_linear_box(\n        z, problem.lb, problem.ub\n    )\n\n    # store information about the solution\n    # and the resulted raw variables in the extras\n    solution.extras = {\n        \"info\": {\n            \"iterations\": iters1,\n            \"converged\": converged,\n        },\n        \"variables\": {\n            \"x\": x,\n            \"y\": y,\n            \"z\": z,\n            \"s\": s,\n        },\n    }\n\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef qpax_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using qpax.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `qpax\n    <https://github.com/kevin-tracy/qpax>`__.\n    `Paper: <https://arxiv.org/pdf/2406.11749>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    verbose :\n        Set to `True` to print out extra information.\n    initvals :\n        This argument is not used by qpax.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = qpax_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/qpoases_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `qpOASES <https://github.com/coin-or/qpOASES>`__.\n\nqpOASES is an open-source C++ implementation of the online active set strategy,\nwhich was inspired by observations from the field of parametric quadratic\nprogramming. It has theoretical features that make it suitable to model\npredictive control. Further numerical modifications have made qpOASES a\nreliable QP solver, even when tackling semi-definite, ill-posed or degenerated\nQP problems. If you are using qpOASES in a scientific work, consider citing the\ncorresponding paper [Ferreau2014]_.\n\nSee the :ref:`installation page <qpoases-install>` for additional instructions\non installing this solver.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Any, List, Optional, Tuple\n\nimport numpy as np\nfrom numpy import array, hstack, vstack\nfrom qpoases import PyOptions as Options\nfrom qpoases import PyPrintLevel as PrintLevel\nfrom qpoases import PyQProblem as QProblem\nfrom qpoases import PyQProblemB as QProblemB\nfrom qpoases import PyReturnValue as ReturnValue\n\nfrom ..exceptions import ParamError, ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n# See qpOASES/include/Constants.hpp\n__infty__ = 1.0e20\n\n\n# Return codes not wrapped in qpoases.PyReturnValue\nRET_INIT_FAILED = 33\nRET_INIT_FAILED_TQ = 34\nRET_INIT_FAILED_CHOLESKY = 35\nRET_INIT_FAILED_HOTSTART = 36\nRET_INIT_FAILED_INFEASIBILITY = 37\nRET_INIT_FAILED_UNBOUNDEDNESS = 38\nRET_INIT_FAILED_REGULARISATION = 39\n\n\ndef __clamp_infinities(v: Optional[np.ndarray]):\n    \"\"\"Replace infinite values in an array by big finite ones.\n\n    Note\n    ----\n    qpOASES requires large bounds instead of infinite float values. See the\n    following issue: https://github.com/coin-or/qpOASES/issues/126\n    \"\"\"\n    if v is not None:\n        v = np.nan_to_num(v, posinf=__infty__, neginf=-__infty__)\n    return v\n\n\ndef __prepare_options(\n    verbose: bool,\n    predefined_options: Optional[str],\n    **kwargs,\n) -> Options:\n    \"\"\"Prepare options for qpOASES.\n\n    Parameters\n    ----------\n    verbose :\n        Set to `True` to print out extra information.\n    predefined_options :\n        Set solver options to one of the pre-defined choices provided by\n        qpOASES: ``[\"default\", \"fast\", \"mpc\", \"reliable\"]``.\n\n    Returns\n    -------\n    :\n        Options for qpOASES.\n\n    Raises\n    ------\n    ParamError\n        If predefined options are not a valid choice for qpOASES.\n    \"\"\"\n    options = Options()\n\n    # Start from pre-defined options\n    if predefined_options is None:\n        pass\n    elif predefined_options == \"fast\":\n        options.setToFast()\n    elif predefined_options == \"default\":\n        options.setToDefault()\n    elif predefined_options == \"mpc\":\n        options.setToMPC()\n    elif predefined_options == \"reliable\":\n        options.setToReliable()\n    else:\n        raise ParamError(\n            f\"unknown qpOASES pre-defined options {predefined_options}'\"\n        )\n\n    # Override options with explicit ones\n    options.printLevel = PrintLevel.MEDIUM if verbose else PrintLevel.NONE\n    for param, value in kwargs.items():\n        setattr(options, param, value)\n\n    return options\n\n\ndef __convert_inequalities(\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n) -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:\n    \"\"\"Convert linear constraints to qpOASES format.\n\n    Parameters\n    ----------\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n\n    Returns\n    -------\n    :\n        Tuple ``(C, lb_C, ub_C)`` for qpOASES.\n    \"\"\"\n    C: np.ndarray = np.array([])\n    lb_C: Optional[np.ndarray] = None\n    ub_C: np.ndarray = np.array([])\n    if G is not None and h is not None:\n        h_neginf = np.full(h.shape, -__infty__)\n        if A is not None and b is not None:\n            C = vstack([G, A])\n            lb_C = hstack([h_neginf, b])\n            ub_C = hstack([h, b])\n        else:  # no equality constraint\n            C = G\n            lb_C = h_neginf\n            ub_C = h\n    else:  # no inequality constraint\n        if A is not None and b is not None:\n            C = A\n            lb_C = b\n            ub_C = b\n    return C, lb_C, ub_C\n\n\ndef qpoases_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    max_wsr: int = 1000,\n    time_limit: Optional[float] = None,\n    predefined_options: Optional[str] = None,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using qpOASES.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n    max_wsr :\n        Maximum number of Working-Set Recalculations given to qpOASES.\n    time_limit :\n        Set a run time limit in seconds.\n    predefined_options :\n        Set solver options to one of the pre-defined choices provided by\n        qpOASES, to pick in ``[\"default\", \"fast\", \"mpc\", \"reliable\"]``.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If the problem is ill-formed in some way, for instance if some matrices\n        are not dense.\n    ValueError :\n        If ``predefined_options`` is not a valid choice.\n\n    Notes\n    -----\n    This function relies on an update to qpOASES to allow empty bounds (`lb`,\n    `ub`, `lbA` or `ubA`) in Python. This is possible in the C++ API but not by\n    the Python API (as of version 3.2.0). If using them, be sure to update the\n    Cython file (`qpoases.pyx`) in your distribution of qpOASES to convert\n    ``None`` to the null pointer. Check out the `installation instructions\n    <https://scaron.info/doc/qpsolvers/installation.html#qpoases>`_.\n\n    Keyword arguments are forwarded as options to qpOASES. For instance, we can\n    call ``qpoases_solve_qp(P, q, G, h, u, terminationTolerance=1e-14)``.\n    qpOASES options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``boundRelaxation``\n         - Initial relaxation of bounds to start homotopy and initial value for\n           far bounds.\n       * - ``epsNum``\n         - Numerator tolerance for ratio tests.\n       * - ``epsDen``\n         - Denominator tolerance for ratio tests.\n       * - ``numRefinementSteps``\n         - Maximum number of iterative refinement steps.\n       * - ``terminationTolerance``\n         - Relative termination tolerance to stop homotopy.\n\n    Check out pages 28 to 30 of `qpOASES User's Manual\n    <https://www.coin-or.org/qpOASES/doc/3.1/manual.pdf>`_. for all available\n    options.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\"qpOASES: warm-start values are ignored\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack_as_dense()\n    n = P.shape[0]\n    lb = np.full((n,), -np.inf) if lb is None else lb\n    ub = np.full((n,), +np.inf) if ub is None else ub\n    C, lb_C, ub_C = __convert_inequalities(G, h, A, b)\n    lb_C = __clamp_infinities(lb_C)\n    ub_C = __clamp_infinities(ub_C)\n    lb = __clamp_infinities(lb)\n    ub = __clamp_infinities(ub)\n\n    args: List[Any] = []\n    n_wsr = np.array([max_wsr])\n    if C.shape[0] > 0:\n        qp = QProblem(n, C.shape[0])\n        args = [P, q, C, lb, ub, lb_C, ub_C, n_wsr]\n    else:  # at most box constraints\n        qp = QProblemB(n)\n        args = [P, q, lb, ub, n_wsr]\n    if time_limit is not None:\n        args.append(array([time_limit]))\n\n    options = __prepare_options(verbose, predefined_options, **kwargs)\n    qp.setOptions(options)\n\n    solve_start_time = time.perf_counter()\n    try:\n        return_value = qp.init(*args)\n    except TypeError as error:\n        raise ProblemError(\"problem has sparse matrices\") from error\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras = {\n        \"nWSR\": n_wsr[0],\n    }\n    solution.found = True\n    if RET_INIT_FAILED <= return_value <= RET_INIT_FAILED_REGULARISATION:\n        solution.found = False\n    if return_value == ReturnValue.MAX_NWSR_REACHED:\n        warnings.warn(f\"qpOASES reached the maximum number of WSR ({max_wsr})\")\n        solution.found = False\n\n    x_opt = np.empty((n,))\n    z_opt = np.empty((n + C.shape[0],))\n    qp.getPrimalSolution(x_opt)  # can't return RET_QP_NOT_SOLVED at this point\n    qp.getDualSolution(z_opt)\n    solution.x = x_opt\n    solution.obj = qp.getObjVal()\n    m = G.shape[0] if G is not None else 0\n    solution.y = (\n        -z_opt[n + m : n + m + A.shape[0]] if A is not None else np.empty((0,))\n    )\n    solution.z = -z_opt[n : n + m] if G is not None else np.empty((0,))\n    solution.z_box = (\n        -z_opt[:n] if lb is not None or ub is not None else np.empty((0,))\n    )\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef qpoases_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    max_wsr: int = 1000,\n    time_limit: Optional[float] = None,\n    predefined_options: Optional[str] = None,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using qpOASES.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `qpOASES <https://github.com/coin-or/qpOASES>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n    max_wsr :\n        Maximum number of Working-Set Recalculations given to qpOASES.\n    time_limit :\n        Set a run time limit in seconds.\n    predefined_options :\n        Set solver options to one of the pre-defined choices provided by\n        qpOASES, to pick in ``[\"default\", \"fast\", \"mpc\", \"reliable\"]``.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If the problem is ill-formed in some way, for instance if some matrices\n        are not dense.\n    ValueError :\n        If ``predefined_options`` is not a valid choice.\n\n    Notes\n    -----\n    This function relies on an update to qpOASES to allow empty bounds (`lb`,\n    `ub`, `lbA` or `ubA`) in Python. This is possible in the C++ API but not by\n    the Python API (as of version 3.2.0). If using them, be sure to update the\n    Cython file (`qpoases.pyx`) in your distribution of qpOASES to convert\n    ``None`` to the null pointer. Check out the `installation instructions\n    <https://scaron.info/doc/qpsolvers/installation.html#qpoases>`_.\n\n    Keyword arguments are forwarded as options to qpOASES. For instance, we can\n    call ``qpoases_solve_qp(P, q, G, h, u, terminationTolerance=1e-14)``.\n    qpOASES options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``boundRelaxation``\n         - Initial relaxation of bounds to start homotopy and initial value for\n           far bounds.\n       * - ``epsNum``\n         - Numerator tolerance for ratio tests.\n       * - ``epsDen``\n         - Denominator tolerance for ratio tests.\n       * - ``numRefinementSteps``\n         - Maximum number of iterative refinement steps.\n       * - ``terminationTolerance``\n         - Relative termination tolerance to stop homotopy.\n\n    Check out pages 28 to 30 of `qpOASES User's Manual\n    <https://www.coin-or.org/qpOASES/doc/3.1/manual.pdf>`_. for all available\n    options.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = qpoases_solve_problem(\n        problem,\n        initvals,\n        verbose,\n        max_wsr,\n        time_limit,\n        predefined_options,\n        **kwargs,\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/qpswift_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `qpSWIFT <https://github.com/qpSWIFT/qpSWIFT>`__.\n\nqpSWIFT is a light-weight sparse Quadratic Programming solver targeted for\nembedded and robotic applications. It employs Primal-Dual Interior Point method\nwith Mehrotra Predictor corrector step and Nesterov Todd scaling. For solving\nthe linear system of equations, sparse LDL' factorization is used along with\napproximate minimum degree heuristic to minimize fill-in of the factorizations.\nIf you use qpSWIFT in your research, consider citing the corresponding paper\n[Pandala2019]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport numpy as np\nimport qpSWIFT\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef qpswift_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using qpSWIFT.\n\n    Note\n    ----\n    This solver does not handle problems without inequality constraints.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Raises\n    ------\n    ProblemError :\n        If the problem is ill-formed in some way, for instance if some matrices\n        are not dense or the problem has no inequality constraint.\n\n    Note\n    ----\n    **Rank assumptions:** qpSWIFT requires the QP matrices to satisfy the\n\n    .. math::\n\n        \\begin{split}\\begin{array}{cc}\n        \\mathrm{rank}(A) = p\n        &\n        \\mathrm{rank}([P\\ A^T\\ G^T]) = n\n        \\end{array}\\end{split}\n\n    where :math:`p` is the number of rows of :math:`A` and :math:`n` is the\n    number of optimization variables. This is the same requirement as\n    :func:`cvxopt_solve_qp`, however qpSWIFT does not perform rank checks as it\n    prioritizes performance. If the solver fails on your problem, try running\n    CVXOPT on it for rank checks.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to the qpSWIFT solver.\n    For instance, you can call ``qpswift_solve_qp(P, q, G, h, ABSTOL=1e-5)``.\n    See the solver documentation for details.\n\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - ``MAXITER``\n         - Maximum number of iterations needed.\n       * - ``ABSTOL``\n         - Absolute tolerance on the duality gap. See *e.g.* [Caron2022]_ for\n           a primer on the duality gap and solver tolerances.\n       * - ``RELTOL``\n         - Relative tolerance on the residuals :math:`r_x = P x + G^T z + q`\n           (dual residual), :math:`r_y = A x - b` (primal residual on equality\n           constraints) and :math:`r_z = h - G x - s` (primal residual on\n           inequality constraints). See equation (21) in [Pandala2019]_.\n       * - ``SIGMA``\n         - Maximum centering allowed.\n\n    If a verbose output shows that the maximum number of iterations is reached,\n    check e.g. (1) the rank of your equality constraint matrix and (2) that\n    your inequality constraint matrix does not have zero rows.\n\n    As qpSWIFT does not sanity check its inputs, it should be used with a\n    little more care than the other solvers. For instance, make sure you don't\n    have zero rows in your input matrices, as it can `make the solver\n    numerically unstable <https://github.com/qpSWIFT/qpSWIFT/issues/3>`_.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None:\n        warnings.warn(\"qpSWIFT: warm-start values are ignored\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    if lb is not None or ub is not None:\n        G, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n    result: dict = {}\n    kwargs.update(\n        {\n            \"OUTPUT\": 2,  # include \"sol\", \"basicInfo\" and \"advInfo\"\n            \"VERBOSE\": 1 if verbose else 0,\n        }\n    )\n\n    solve_start_time = time.perf_counter()\n    try:\n        if G is not None and h is not None:\n            if A is not None and b is not None:\n                result = qpSWIFT.run(q, h, P, G, A, b, kwargs)\n            else:  # no equality constraint\n                result = qpSWIFT.run(q, h, P, G, opts=kwargs)\n        else:  # no inequality constraint\n            # See https://github.com/qpSWIFT/qpSWIFT/issues/2\n            raise ProblemError(\"problem has no inequality constraint\")\n    except TypeError as error:\n        raise ProblemError(\"problem has sparse matrices\") from error\n    solve_end_time = time.perf_counter()\n\n    basic_info = result[\"basicInfo\"]\n    adv_info = result[\"advInfo\"]\n\n    solution = Solution(problem)\n    solution.extras = {\n        \"basicInfo\": basic_info,\n        \"advInfo\": adv_info,\n    }\n    solution.obj = adv_info[\"fval\"]\n    exit_flag = basic_info[\"ExitFlag\"]\n    solution.found = exit_flag == 0\n    solution.x = result[\"sol\"]\n    solution.y = adv_info[\"y\"] if A is not None else np.empty((0,))\n    z, z_box = split_dual_linear_box(adv_info[\"z\"], lb, ub)\n    solution.z = z\n    solution.z_box = z_box\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef qpswift_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using qpSWIFT.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `qpSWIFT <https://github.com/qpSWIFT/qpSWIFT>`__.\n\n    Note\n    ----\n    This solver does not handle problems without inequality constraints yet.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix. Together with :math:`A` and :math:`G`, it should\n        satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`, see the rank\n        assumptions below.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix. Together with :math:`P` and\n        :math:`A`, it should satisfy :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) =\n        n`, see the rank assumptions below.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix. It needs to be full row rank, and\n        together with :math:`P` and :math:`G` satisfy\n        :math:`\\mathrm{rank}([P\\ A^T\\ G^T]) = n`. See the rank assumptions\n        below.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If the problem is ill-formed in some way, for instance if some matrices\n        are not dense or the problem has no inequality constraint.\n\n    Note\n    ----\n    .. _qpSWIFT rank assumptions:\n\n    **Rank assumptions:** qpSWIFT requires the QP matrices to satisfy the\n\n    .. math::\n\n        \\begin{split}\\begin{array}{cc}\n        \\mathrm{rank}(A) = p\n        &\n        \\mathrm{rank}([P\\ A^T\\ G^T]) = n\n        \\end{array}\\end{split}\n\n    where :math:`p` is the number of rows of :math:`A` and :math:`n` is the\n    number of optimization variables. This is the same requirement as\n    :func:`cvxopt_solve_qp`, however qpSWIFT does not perform rank checks as it\n    prioritizes performance. If the solver fails on your problem, try running\n    CVXOPT on it for rank checks.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to the qpSWIFT solver.\n    For instance, you can call ``qpswift_solve_qp(P, q, G, h, ABSTOL=1e-5)``.\n    See the solver documentation for details.\n\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - MAXITER\n         - Maximum number of iterations needed.\n       * - ABSTOL\n         - Absolute tolerance on the duality gap. See *e.g.* [Caron2022]_ for\n           a primer on the duality gap and solver tolerances.\n       * - RELTOL\n         - Relative tolerance on the residuals :math:`r_x = P x + G^T z + q`\n           (dual residual), :math:`r_y = A x - b` (primal residual on equality\n           constraints) and :math:`r_z = h - G x - s` (primal residual on\n           inequality constraints). See equation (21) in [Pandala2019]_.\n       * - SIGMA\n         - Maximum centering allowed.\n\n    If a verbose output shows that the maximum number of iterations is reached,\n    check e.g. (1) the rank of your equality constraint matrix and (2) that\n    your inequality constraint matrix does not have zero rows.\n\n    As qpSWIFT does not sanity check its inputs, it should be used with a\n    little more care than the other solvers. For instance, make sure you don't\n    have zero rows in your input matrices, as it can `make the solver\n    numerically unstable <https://github.com/qpSWIFT/qpSWIFT/issues/3>`_.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = qpswift_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/qtqp_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n\n\"\"\"Solver interface for `QTQP`_.\n\n.. _QTQP: https://github.com/google-deepmind/qtqp\n\nQTQP is a primal-dual interior point method for solving convex quadratic\nprograms (QPs), implemented in pure Python. It is developed by Google DeepMind.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import List, Optional, Union\n\nimport numpy as np\nimport qtqp\nimport scipy.sparse as spa\n\nfrom ..conversions import (\n    ensure_sparse_matrices,\n    linear_from_box_inequalities,\n    split_dual_linear_box,\n)\nfrom ..problem import Problem\nfrom ..solution import Solution\nfrom ..solve_unconstrained import solve_unconstrained\n\n\ndef qtqp_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    r\"\"\"Solve a quadratic program using QTQP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by QTQP.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded as options to QTQP. For instance, we\n    can call ``qtqp_solve_qp(P, q, G, h, atol=1e-8, max_iter=200)``. QTQP\n    options include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``atol``\n         - Absolute tolerance for optimality convergence (default: 1e-7).\n       * - ``rtol``\n         - Relative tolerance for optimality convergence (default: 1e-8).\n       * - ``atol_infeas``\n         - Absolute tolerance for infeasibility detection (default: 1e-8).\n       * - ``rtol_infeas``\n         - Relative tolerance for infeasibility detection (default: 1e-9).\n       * - ``max_iter``\n         - Maximum number of iterations (default: 100).\n       * - ``step_size_scale``\n         - Scale factor in (0,1) for line search step size (default: 0.99).\n       * - ``min_static_regularization``\n         - Diagonal regularization on KKT for robustness (default: 1e-7).\n       * - ``max_iterative_refinement_steps``\n         - Maximum steps for iterative refinement (default: 50).\n       * - ``linear_solver_atol``\n         - Absolute tolerance for iterative refinement (default: 1e-12).\n       * - ``linear_solver_rtol``\n         - Relative tolerance for iterative refinement (default: 1e-12).\n       * - ``linear_solver``\n         - KKT solver backend (default: qtqp.LinearSolver.SCIPY).\n       * - ``equilibrate``\n         - Scale/equilibrate data for numerical stability (default: True).\n\n    Check out the `QTQP repository\n    <https://github.com/google-deepmind/qtqp>`_ for details.\n\n    Lower values for absolute or relative tolerances yield more precise\n    solutions at the cost of computation time.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"QTQP: warm-start values are ignored\")\n\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n\n    # Convert box constraints to linear inequalities\n    if lb is not None or ub is not None:\n        G, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=True)\n\n    # Convert to CSC format as required by QTQP\n    P, G, A = ensure_sparse_matrices(\"qtqp\", P, G, A)\n\n    # Check for unconstrained case\n    if G is None and A is None:\n        warnings.warn(\n            \"QP is unconstrained: solving with SciPy's LSQR rather than QTQP\"\n        )\n        return solve_unconstrained(problem)\n\n    # Build the constraint matrix in QTQP format\n    # QTQP expects: a @ x + s = b\n    # where s[:z] == 0 (equality constraints)\n    #       s[z:] >= 0 (inequality constraints)\n\n    constraint_matrices: List[spa.csc_matrix] = []\n    constraint_vectors = []\n\n    # Add equality constraints first (these form the zero cone)\n    z = 0\n    if A is not None and b is not None:\n        constraint_matrices.append(A)\n        constraint_vectors.append(b)\n        z = A.shape[0]\n\n    # Add inequality constraints (these form the nonnegative cone)\n    if G is not None and h is not None:\n        constraint_matrices.append(G)\n        constraint_vectors.append(h)\n\n    # QTQP requires at least one inequality constraint (z < m)\n    if G is None and A is not None:\n        warnings.warn(\n            \"QTQP cannot solve problems with only equality constraints; \"\n            \"at least one inequality constraint is required\"\n        )\n        solution = Solution(problem)\n        solution.found = False\n        return solution\n\n    # Stack all constraints\n    a_qtqp = spa.vstack(constraint_matrices, format=\"csc\")\n    b_qtqp = np.concatenate(constraint_vectors)\n\n    # QTQP uses 'c' for the cost vector (qpsolvers uses 'q')\n    c_qtqp = q\n\n    # QTQP uses 'p' for the cost matrix (qpsolvers uses 'P')\n    p_qtqp = P if P is not None else None\n\n    # Create QTQP solver instance\n    solver = qtqp.QTQP(\n        p=p_qtqp,\n        a=a_qtqp,\n        b=b_qtqp,\n        c=c_qtqp,\n        z=z,\n    )\n\n    # Solve the problem\n    solve_start_time = time.perf_counter()\n    result = solver.solve(verbose=verbose, **kwargs)\n    solve_end_time = time.perf_counter()\n\n    # Build solution\n    solution = Solution(problem)\n    solution.extras = {\n        \"status\": result.status,\n        \"stats\": result.stats,\n    }\n\n    # Check solution status\n    solution.found = result.status == qtqp.SolutionStatus.SOLVED\n    if not solution.found:\n        warnings.warn(f\"QTQP terminated with status {result.status.value}\")\n\n    # Extract primal solution\n    solution.x = result.x\n\n    # Extract dual variables\n    # result.y contains dual variables for all constraints\n    # First z entries correspond to equality constraints (y)\n    # Remaining entries correspond to inequality constraints (z)\n    meq = z\n    if meq > 0:\n        solution.y = result.y[:meq]\n    else:\n        solution.y = np.empty((0,))\n\n    # Extract inequality duals\n    if G is not None:\n        z_ineq = result.y[meq:]\n        # Split dual variables for linear and box inequalities\n        z_linear, z_box = split_dual_linear_box(z_ineq, lb, ub)\n        solution.z = z_linear\n        solution.z_box = z_box\n    else:\n        solution.z = np.empty((0,))\n        solution.z_box = np.empty((0,))\n\n    # Compute objective value\n    if solution.found and solution.x is not None:\n        solution.obj = (\n            0.5 * solution.x @ (P @ solution.x) if P is not None else 0.0\n        )\n        solution.obj += q @ solution.x\n\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef qtqp_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using QTQP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `QTQP`_.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality matrix.\n    h :\n        Linear inequality vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        This argument is not used by QTQP.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n\n    Notes\n    -----\n    Keyword arguments are forwarded to the solver. For example, we can call\n    ``qtqp_solve_qp(P, q, G, h, atol=1e-8, max_iter=200)``.\n\n    QTQP is a primal-dual interior point method that solves convex quadratic\n    programs. It requires the cost matrix P to be positive semidefinite.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = qtqp_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/quadprog_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `quadprog <https://github.com/quadprog/quadprog>`__.\n\nquadprog is a C implementation of the Goldfarb-Idnani dual algorithm\n[Goldfarb1983]_. It works best on well-conditioned dense problems.\n\n**Warm-start:** this solver interface does not support warm starting ❄️\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom numpy import hstack, vstack\nfrom quadprog import solve_qp\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef quadprog_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using quadprog.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        This argument is not used by quadprog.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ProblemError :\n        If the cost matrix of the quadratic program if not positive definite,\n        or if the problem is ill-formed in some way, for instance if some\n        matrices are not dense.\n\n    Note\n    ----\n    The quadprog solver only considers the lower entries of :math:`P`,\n    therefore it will use a different cost than the one intended if a\n    non-symmetric matrix is provided.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded to the quadprog solver. For\n    instance, you can call ``quadprog_solve_qp(P, q, G, h, factorized=True)``.\n    See the solver documentation for details.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    if initvals is not None and verbose:\n        warnings.warn(\"warm-start values are ignored by quadprog\")\n\n    if problem.has_sparse:\n        raise ProblemError(\"problem has sparse matrices\")\n    P, q, G, h, A, b, lb, ub = problem.unpack_as_dense()\n    if lb is not None or ub is not None:\n        G_, h = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n        G = G_.toarray() if isinstance(G_, spa.csc_matrix) else G_  # for mypy\n    qp_G = P\n    qp_a = -q\n    qp_C: Optional[np.ndarray] = None\n    qp_b: Optional[np.ndarray] = None\n    if A is not None and b is not None:\n        if G is not None and h is not None:\n            qp_C = -vstack([A, G]).T\n            qp_b = -hstack([b, h])\n        else:\n            qp_C = -A.T\n            qp_b = -b\n        meq = A.shape[0]\n    else:  # no equality constraint\n        if G is not None and h is not None:\n            qp_C = -G.T\n            qp_b = -h\n        meq = 0\n\n    solution = Solution(problem)\n    try:\n        solve_start_time = time.perf_counter()\n        x, obj, xu, iterations, y, iact = solve_qp(\n            qp_G, qp_a, qp_C, qp_b, meq, **kwargs\n        )\n        solve_end_time = time.perf_counter()\n        solution.found = True\n        solution.x = x\n        solution.obj = obj\n\n        z, z_box = split_dual_linear_box(y[meq:], lb, ub)\n        solution.y = y[:meq] if meq > 0 else np.empty((0,))\n        solution.z = z\n        solution.z_box = z_box\n\n        solution.extras = {\n            \"iact\": iact,\n            \"iterations\": iterations,\n            \"xu\": xu,\n        }\n    except ValueError as error:\n        solve_end_time = time.perf_counter()\n        solution.found = False\n        error_message = str(error)\n        if \"matrix G is not positive definite\" in error_message:\n            # quadprog writes G the cost matrix that we write P in this package\n            raise ProblemError(\"matrix P is not positive definite\") from error\n        if \"no solution\" not in error_message:\n            warnings.warn(f\"quadprog raised a ValueError: {error_message}\")\n\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef quadprog_solve_qp(\n    P: np.ndarray,\n    q: np.ndarray,\n    G: Optional[np.ndarray] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[np.ndarray] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using quadprog.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `quadprog <https://pypi.python.org/pypi/quadprog/>`__.\n\n    Parameters\n    ----------\n    P :\n        Symmetric cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        This argument is not used by quadprog.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = quadprog_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/scs_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `SCS <https://www.cvxgrp.org/scs/>`__.\n\nSCS (Splitting Conic Solver) is a numerical optimization package for solving\nlarge-scale convex quadratic cone problems, which is a general class of\nproblems that includes quadratic programming. If you use SCS in a scientific\nwork, consider citing the corresponding paper [ODonoghue2021]_.\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Any, Dict, Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom numpy import ndarray\nfrom scipy.sparse import csc_matrix\nfrom scs import solve\n\nfrom ..conversions import ensure_sparse_matrices\nfrom ..problem import Problem\nfrom ..solution import Solution\nfrom ..solve_unconstrained import solve_unconstrained\n\n# See https://www.cvxgrp.org/scs/api/exit_flags.html#exit-flags\n__status_val_meaning__ = {\n    -7: \"INFEASIBLE_INACCURATE\",\n    -6: \"UNBOUNDED_INACCURATE\",\n    -5: \"SIGINT\",\n    -4: \"FAILED\",\n    -3: \"INDETERMINATE\",\n    -2: \"INFEASIBLE (primal infeasible, dual unbounded)\",\n    -1: \"UNBOUNDED (primal unbounded, dual infeasible)\",\n    0: \"UNFINISHED (never returned, used as placeholder)\",\n    1: \"SOLVED\",\n    2: \"SOLVED_INACCURATE\",\n}\n\n\ndef __add_box_cone(\n    n: int,\n    lb: Optional[ndarray],\n    ub: Optional[ndarray],\n    cone: Dict[str, Any],\n    data: Dict[str, Any],\n) -> None:\n    \"\"\"Add box cone to the problem.\n\n    Parameters\n    ----------\n    n :\n        Number of optimization variables.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    cone :\n        SCS cone dictionary.\n    data :\n        SCS data dictionary.\n\n    Notes\n    -----\n    See the `SCS Cones <https://www.cvxgrp.org/scs/api/cones.html>`__\n    documentation for details.\n    \"\"\"\n    cone[\"bl\"] = lb if lb is not None else np.full((n,), -np.inf)\n    cone[\"bu\"] = ub if ub is not None else np.full((n,), +np.inf)\n    zero_row = csc_matrix((1, n))\n    data[\"A\"] = spa.vstack(\n        ((data[\"A\"],) if \"A\" in data else ()) + (zero_row, -spa.eye(n)),\n        format=\"csc\",\n    )\n    data[\"b\"] = np.hstack(\n        ((data[\"b\"],) if \"b\" in data else ()) + (1.0, np.zeros(n))\n    )\n\n\ndef scs_solve_problem(\n    problem: Problem,\n    initvals: Optional[ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using SCS.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution returned by the solver.\n\n    Raises\n    ------\n    ValueError\n        If the quadratic program is not unbounded below.\n\n    Notes\n    -----\n    Keyword arguments are forwarded as is to SCS. For instance, we can call\n    ``scs_solve_qp(P, q, G, h, eps_abs=1e-6, eps_rel=1e-4)``. SCS settings\n    include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``max_iters``\n         - Maximum number of iterations to run.\n       * - ``time_limit_secs``\n         - Time limit for solve run in seconds (can be fractional). 0 is\n           interpreted as no limit.\n       * - ``eps_abs``\n         - Absolute feasibility tolerance. See `Termination criteria\n           <https://www.cvxgrp.org/scs/algorithm/index.html#termination>`__.\n       * - ``eps_rel``\n         - Relative feasibility tolerance. See `Termination criteria\n           <https://www.cvxgrp.org/scs/algorithm/index.html#termination>`__.\n       * - ``eps_infeas``\n         - Infeasibility tolerance (primal and dual), see `Certificate of\n           infeasibility\n           <https://www.cvxgrp.org/scs/algorithm/index.html#certificate-of-infeasibility>`_.\n       * - ``normalize``\n         - Whether to perform heuristic data rescaling. See `Data equilibration\n           <https://www.cvxgrp.org/scs/algorithm/equilibration.html#equilibration>`__.\n\n    Check out the `SCS settings\n    <https://www.cvxgrp.org/scs/api/settings.html#settings>`_ documentation for\n    all available settings.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G, h, A, b, lb, ub = problem.unpack()\n    P, G, A = ensure_sparse_matrices(\"scs\", P, G, A)\n    n = P.shape[0]\n\n    data: Dict[str, Any] = {\"P\": P, \"c\": q}\n    cone: Dict[str, Any] = {}\n    if initvals is not None:\n        data[\"x\"] = initvals\n    if A is not None and b is not None:\n        if G is not None and h is not None:\n            data[\"A\"] = spa.vstack([A, G], format=\"csc\")\n            data[\"b\"] = np.hstack([b, h])\n            cone[\"z\"] = b.shape[0]  # zero cone\n            cone[\"l\"] = h.shape[0]  # positive cone\n        else:  # G is None and h is None\n            data[\"A\"] = A\n            data[\"b\"] = b\n            cone[\"z\"] = b.shape[0]  # zero cone\n    elif G is not None and h is not None:\n        data[\"A\"] = G\n        data[\"b\"] = h\n        cone[\"l\"] = h.shape[0]  # positive cone\n    elif lb is None and ub is None:  # no constraint\n        warnings.warn(\n            \"QP is unconstrained: solving with SciPy's LSQR rather than SCS\"\n        )\n        return solve_unconstrained(problem)\n    if lb is not None or ub is not None:\n        __add_box_cone(n, lb, ub, cone, data)\n    kwargs[\"verbose\"] = verbose\n    solve_start_time = time.perf_counter()\n    result = solve(data, cone, **kwargs)\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras = result[\"info\"]\n    status_val = result[\"info\"][\"status_val\"]\n    solution.found = status_val == 1\n    if not solution.found:\n        warnings.warn(\n            f\"SCS returned {status_val}: {__status_val_meaning__[status_val]}\"\n        )\n    solution.x = result[\"x\"]\n    meq = A.shape[0] if A is not None else 0\n    solution.y = result[\"y\"][:meq] if A is not None else np.empty((0,))\n    solution.z = (\n        result[\"y\"][meq : meq + G.shape[0]]\n        if G is not None\n        else np.empty((0,))\n    )\n    solution.z_box = (\n        -result[\"y\"][-n:]\n        if lb is not None or ub is not None\n        else np.empty((0,))\n    )\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef scs_solve_qp(\n    P: Union[ndarray, csc_matrix],\n    q: ndarray,\n    G: Optional[Union[ndarray, csc_matrix]] = None,\n    h: Optional[ndarray] = None,\n    A: Optional[Union[ndarray, csc_matrix]] = None,\n    b: Optional[ndarray] = None,\n    lb: Optional[ndarray] = None,\n    ub: Optional[ndarray] = None,\n    initvals: Optional[ndarray] = None,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[ndarray]:\n    r\"\"\"Solve a quadratic program using SCS.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n            \\underset{x}{\\mbox{minimize}} &\n                \\frac{1}{2} x^T P x + q^T x \\\\\n            \\mbox{subject to}\n                & G x \\leq h                \\\\\n                & A x = b                   \\\\\n                & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `SCS <https://github.com/cvxgrp/scs>`__.\n\n    Parameters\n    ----------\n    P :\n        Primal quadratic cost matrix.\n    q :\n        Primal quadratic cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP, if found, otherwise ``None``.\n\n    Raises\n    ------\n    ValueError\n        If the quadratic program is not unbounded below.\n\n    Notes\n    -----\n    Keyword arguments are forwarded as is to SCS. For instance, we can call\n    ``scs_solve_qp(P, q, G, h, eps_abs=1e-6, eps_rel=1e-4)``. SCS settings\n    include the following:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Description\n       * - ``max_iters``\n         - Maximum number of iterations to run.\n       * - ``time_limit_secs``\n         - Time limit for solve run in seconds (can be fractional). 0 is\n           interpreted as no limit.\n       * - ``eps_abs``\n         - Absolute feasibility tolerance. See `Termination criteria\n           <https://www.cvxgrp.org/scs/algorithm/index.html#termination>`__.\n       * - ``eps_rel``\n         - Relative feasibility tolerance. See `Termination criteria\n           <https://www.cvxgrp.org/scs/algorithm/index.html#termination>`__.\n       * - ``eps_infeas``\n         - Infeasibility tolerance (primal and dual), see `Certificate of\n           infeasibility\n           <https://www.cvxgrp.org/scs/algorithm/index.html#certificate-of-infeasibility>`_.\n       * - ``normalize``\n         - Whether to perform heuristic data rescaling. See `Data equilibration\n           <https://www.cvxgrp.org/scs/algorithm/equilibration.html#equilibration>`__.\n\n    Check out the `SCS settings\n    <https://www.cvxgrp.org/scs/api/settings.html#settings>`_ documentation for\n    all available settings.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = scs_solve_problem(problem, initvals, verbose, **kwargs)\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/solvers/sip_.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Solver interface for `SIP`_.\n\n.. _SIP: https://github.com/joaospinto/sip_python\n\nSIP is a general NLP solver based. It is based on the barrier augmented\nLagrangian method, which combines the interior point and augmented Lagrangian\nmethods. If you are using SIP in a scientific work, consider citing the\ncorresponding GitHub repository (or paper, if one has been released).\n\n**Warm-start:** this solver interface supports warm starting 🔥\n\"\"\"\n\nimport time\nimport warnings\nfrom typing import Optional, Union\n\nimport numpy as np\nimport scipy.sparse as spa\nimport sip_python as sip\n\nfrom ..conversions import linear_from_box_inequalities, split_dual_linear_box\nfrom ..exceptions import ProblemError\nfrom ..problem import Problem\nfrom ..solution import Solution\n\n\ndef sip_solve_problem(\n    problem: Problem,\n    initvals: Optional[np.ndarray] = None,\n    verbose: bool = False,\n    allow_non_psd_P: bool = False,\n    **kwargs,\n) -> Solution:\n    \"\"\"Solve a quadratic program using SIP.\n\n    Parameters\n    ----------\n    problem :\n        Quadratic program to solve.\n    initvals :\n        Warm-start guess vector for the primal solution.\n    verbose :\n        Set to `True` to print out extra information.\n\n    Returns\n    -------\n    :\n        Solution to the QP returned by the solver.\n\n    Notes\n    -----\n    All other keyword arguments are forwarded as options to SIP. For\n    instance, you can call ``sip_solve_qp(P, q, G, h, eps_abs=1e-6)``.\n    For a quick overview, the solver accepts the following settings:\n\n    .. list-table::\n       :widths: 30 70\n       :header-rows: 1\n\n       * - Name\n         - Effect\n       * - max_iterations\n         - The maximum number of iterations the solver can do.\n       * - max_ls_iterations\n         - The maximum cumulative number of line search iterations.\n       * - min_iterations_for_convergence\n         - The least number of iterations until we can declare convergence.\n       * - num_iterative_refinement_steps\n         - The number of iterative refinement steps.\n       * - max_kkt_violation\n         - The maximum allowed violation of the KKT system.\n       * - max_merit_slope\n         - The maximum allowed merit function slope.\n       * - initial_regularization\n         - The initial x-regularizatino to be applied on the LHS.\n       * - regularization_decay_factor\n         - The multiplicative decay of the x-regularization coefficient.\n       * - tau\n         - A parameter of the fraction-to-the-boundary rule.\n       * - start_ls_with_alpha_s_max\n         - Determines whether we start with alpha=alpha_s_max or alpha=1.\n       * - initial_mu\n         - The initial barrier function coefficient.\n       * - mu_update_factor\n         - Determines how much mu decreases per iteration.\n       * - mu_min\n         - The minimum barrier coefficient.\n       * - initial_penalty_parameter\n         - The initial penalty parameter of the Augmented Lagrangian.\n       * - min_acceptable_constraint_violation_ratio\n         - Least acceptable constraint violation ratio to not increase eta.\n       * - penalty_parameter_increase_factor\n         - By what factor to increase eta.\n       * - penalty_parameter_decrease_factor\n         - By what factor to decrease eta.\n       * - max_penalty_parameter\n         - The maximum allowed penalty parameter in the AL merit function.\n       * - armijo_factor\n         - Determines when we accept a line search step.\n       * - line_search_factor\n         - Determines how much to backtrack at each line search iteration.\n       * - line_search_min_step_size\n         - Determines when we declare a line search failure.\n       * - min_merit_slope_to_skip_line_search\n         - Min merit slope to skip the line search.\n       * - dual_armijo_factor\n         - Fraction of the primal merit decrease to allow on the dual update.\n       * - min_allowed_merit_increase\n         - The minimum allowed merit function increase in the dual update.\n       * - enable_elastics\n         - Whether to enable the usage of elastic variables.\n       * - elastic_var_cost_coeff\n         - Elastic variables cost coefficient.\n       * - enable_line_search_failures\n         - Halts the optimization process if a good step is not found.\n       * - print_logs\n         - Determines whether we should print the solver logs.\n       * - print_line_search_logs\n         - Determines whether we should print the line search logs.\n       * - print_search_direction_logs\n         - Whether we should print the search direction computation logs.\n       * - print_derivative_check_logs\n         - Whether to print derivative check logs when something looks off.\n       * - only_check_search_direction_slope\n         - Only derivative-check the search direction.\n       * - assert_checks_pass\n         - Handle checks with assert calls.\n\n    This list may not be exhaustive.\n    Check the `Settings` struct in the `solver code\n    <https://github.com/joaospinto/sip/blob/main/sip/types.hpp>`__ for details.\n    \"\"\"\n    build_start_time = time.perf_counter()\n    P, q, G_, h, A_, b, lb, ub = problem.unpack()\n    if lb is not None or ub is not None:\n        G_, h = linear_from_box_inequalities(\n            G_, h, lb, ub, use_sparse=problem.has_sparse\n        )\n    n: int = q.shape[0]\n\n    # SIP does not support A, b, G, and h to be None.\n    G: Union[np.ndarray, spa.csc_matrix, spa.csr_matrix] = (\n        G_ if G_ is not None else spa.csr_matrix(np.zeros((0, n)))\n    )\n    A: Union[np.ndarray, spa.csc_matrix, spa.csr_matrix] = (\n        A_ if A_ is not None else spa.csr_matrix(np.zeros((0, n)))\n    )\n    h = np.zeros((0,)) if h is None else h\n    b = np.zeros((0,)) if b is None else b\n\n    # Remove any infs from h.\n    G[np.isinf(h), :] = 0.0\n    h[np.isinf(h)] = 1.0\n\n    if not isinstance(P, spa.csr_matrix):\n        P = spa.csc_matrix(P)\n        if verbose:\n            warnings.warn(\"Converted P to a csc_matrix.\")\n    if not isinstance(G, spa.csr_matrix):\n        G = spa.csr_matrix(G)\n        if verbose:\n            warnings.warn(\"Converted G to a csr_matrix.\")\n    if not isinstance(A, spa.csr_matrix):\n        A = spa.csr_matrix(A)\n        if verbose:\n            warnings.warn(\"Converted A to a csr_matrix.\")\n\n    P.eliminate_zeros()\n    G.eliminate_zeros()\n    A.eliminate_zeros()\n\n    P_T = spa.csc_matrix(P.T)\n    if (\n        (P.indices != P_T.indices).any()\n        or (P.indptr != P_T.indptr).any()\n        or (P.data != P_T.data).any()\n    ):\n        raise ProblemError(\"P should be symmetric.\")\n\n    if G is None and h is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: G is not set but h is set\"\n        )\n    if G is not None and h is None:\n        raise ProblemError(\"Inconsistent inequalities: G is set but h is None\")\n    if A is None and b is not None:\n        raise ProblemError(\n            \"Inconsistent inequalities: A is not set but b is set\"\n        )\n    if A is not None and b is None:\n        raise ProblemError(\"Inconsistent inequalities: A is set but b is None\")\n\n    k = None\n    if allow_non_psd_P:\n        eigenvalues, _eigenvectors = spa.linalg.eigsh(P, k=1, which=\"SM\")\n        k = -min(eigenvalues[0], 0.0) + 1e-3\n    else:\n        k = 1e-6\n\n    # hess_L = P + k * spa.eye(n);\n    # the code below avoids potential index cancellations.\n    hess_L = spa.coo_matrix(P)\n    upp_hess_L_rows = np.concatenate([hess_L.row, np.arange(n)])\n    upp_hess_L_cols = np.concatenate([hess_L.col, np.arange(n)])\n    upp_hess_L_data = np.concatenate([hess_L.data, k * np.ones(n)])\n    hess_L = spa.coo_matrix(\n        (upp_hess_L_data, (upp_hess_L_rows, upp_hess_L_cols)), shape=P.shape\n    )\n    hess_L.sum_duplicates()\n    upp_hess_L = spa.triu(hess_L.tocsc())\n\n    qs = sip.QDLDLSettings()\n    qs.permute_kkt_system = True\n    qs.kkt_pinv = sip.get_kkt_perm_inv(\n        P=hess_L,\n        A=A,\n        G=G,\n    )\n\n    pd = sip.ProblemDimensions()\n    pd.x_dim = n\n    pd.s_dim = h.shape[0]\n    pd.y_dim = b.shape[0]\n    pd.upper_hessian_lagrangian_nnz = upp_hess_L.nnz\n    pd.jacobian_c_nnz = A.nnz\n    pd.jacobian_g_nnz = G.nnz\n    pd.kkt_nnz, pd.kkt_L_nnz = sip.get_kkt_and_L_nnzs(\n        P=hess_L,\n        A=A,\n        G=G,\n        perm_inv=qs.kkt_pinv,\n    )\n    pd.is_jacobian_c_transposed = True\n    pd.is_jacobian_g_transposed = True\n\n    vars_ = sip.Variables(pd)\n\n    if initvals is not None:\n        vars_.x[:] = initvals  # type: ignore[index]\n    else:\n        vars_.x[:] = 0.0  # type: ignore[index]\n\n    vars_.s[:] = 1.0  # type: ignore[index]\n    vars_.y[:] = 0.0  # type: ignore[index]\n    vars_.e[:] = 0.0  # type: ignore[index]\n    vars_.z[:] = 1.0  # type: ignore[index]\n\n    ss = sip.Settings()\n    ss.max_iterations = 100\n    ss.max_ls_iterations = 1000\n    ss.max_kkt_violation = 1e-8\n    ss.max_merit_slope = 1e-16\n    ss.penalty_parameter_increase_factor = 2.0\n    ss.mu_update_factor = 0.5\n    ss.mu_min = 1e-16\n    ss.max_penalty_parameter = 1e16\n    ss.assert_checks_pass = True\n\n    ss.print_logs = verbose\n    ss.print_line_search_logs = verbose\n    ss.print_search_direction_logs = verbose\n    ss.print_derivative_check_logs = False\n\n    for key, value in kwargs.items():\n        try:\n            setattr(ss, key, value)\n        except AttributeError:\n            if verbose:\n                warnings.warn(\n                    f\"Received an undefined solver setting {key}\\\n                    with value {value}\"\n                )\n\n    def mc(mci: sip.ModelCallbackInput) -> sip.ModelCallbackOutput:\n        mco = sip.ModelCallbackOutput()\n\n        Px = P.T @ mci.x  # type: ignore[operator]\n\n        mco.f = 0.5 * np.dot(Px, mci.x) + np.dot(q, mci.x)\n        mco.c = A @ mci.x - b  # type: ignore[operator]\n        mco.g = G @ mci.x - h  # type: ignore[operator]\n\n        mco.gradient_f = Px + q\n        mco.jacobian_c = A\n        mco.jacobian_g = G\n        mco.upper_hessian_lagrangian = upp_hess_L\n\n        return mco\n\n    solver = sip.Solver(ss, qs, pd, mc)\n\n    solve_start_time = time.perf_counter()\n    output = solver.solve(vars_)\n    solve_end_time = time.perf_counter()\n\n    solution = Solution(problem)\n    solution.extras = {\"sip_output\": output, \"sip_vars\": vars_}\n    solution.found = output.exit_status == sip.Status.SOLVED\n    solution.obj = 0.5 * np.dot(\n        P.T @ vars_.x,  # type: ignore[operator]\n        vars_.x,\n    ) + np.dot(q, vars_.x)\n    solution.x = np.array(vars_.x)\n    solution.y = np.array(vars_.y)\n    if h is not None and vars_.z is not None:\n        z_sip = np.array(vars_.z)\n        z, z_box = split_dual_linear_box(z_sip, lb, ub)\n        solution.z = z\n        solution.z_box = z_box\n    solution.build_time = solve_start_time - build_start_time\n    solution.solve_time = solve_end_time - solve_start_time\n    return solution\n\n\ndef sip_solve_qp(\n    P: Union[np.ndarray, spa.csc_matrix],\n    q: np.ndarray,\n    G: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    h: Optional[np.ndarray] = None,\n    A: Optional[Union[np.ndarray, spa.csc_matrix]] = None,\n    b: Optional[np.ndarray] = None,\n    lb: Optional[np.ndarray] = None,\n    ub: Optional[np.ndarray] = None,\n    initvals: Optional[np.ndarray] = None,\n    allow_non_psd_P: bool = False,\n    verbose: bool = False,\n    **kwargs,\n) -> Optional[np.ndarray]:\n    r\"\"\"Solve a quadratic program using SIP.\n\n    The quadratic program is defined as:\n\n    .. math::\n\n        \\begin{split}\\begin{array}{ll}\n        \\underset{\\mbox{minimize}}{x} &\n            \\frac{1}{2} x^T P x + q^T x \\\\\n        \\mbox{subject to}\n            & G x \\leq h                \\\\\n            & A x = b                   \\\\\n            & lb \\leq x \\leq ub\n        \\end{array}\\end{split}\n\n    It is solved using `SIP\n    <https://github.com/joaospinto/sip>`__.\n\n    Parameters\n    ----------\n    P :\n        Positive semidefinite cost matrix.\n    q :\n        Cost vector.\n    G :\n        Linear inequality constraint matrix.\n    h :\n        Linear inequality constraint vector.\n    A :\n        Linear equality constraint matrix.\n    b :\n        Linear equality constraint vector.\n    lb :\n        Lower bound constraint vector.\n    ub :\n        Upper bound constraint vector.\n    verbose :\n        Set to `True` to print out extra information.\n    initvals :\n        Warm-start guess vector for the primal solution.\n\n    Returns\n    -------\n    :\n        Primal solution to the QP, if found, otherwise ``None``.\n    \"\"\"\n    problem = Problem(P, q, G, h, A, b, lb, ub)\n    solution = sip_solve_problem(\n        problem,\n        initvals,\n        verbose,\n        allow_non_psd_P,\n        **kwargs,\n    )\n    return solution.x if solution.found else None\n"
  },
  {
    "path": "qpsolvers/utils.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Utility functions.\"\"\"\n\nfrom typing import Union\n\nimport numpy as np\nimport scipy.sparse as spa\n\n\ndef print_matrix_vector(\n    A: Union[np.ndarray, spa.csc_matrix],\n    A_label: str,\n    b: np.ndarray,\n    b_label: str,\n    column_width: int = 24,\n) -> None:\n    \"\"\"Print a matrix and vector side by side to the terminal.\n\n    Parameters\n    ----------\n    A :\n        Union[np.ndarray, spa.csc_matrix] to print.\n    A_label :\n        Label for A.\n    b :\n        np.ndarray to print.\n    b_label :\n        Label for b.\n    column_width :\n        Number of characters for the matrix and vector text columns.\n    \"\"\"\n    if isinstance(A, np.ndarray) and A.ndim == 1:\n        A = A.reshape((1, A.shape[0]))\n    if isinstance(A, spa.csc_matrix):\n        A = A.toarray()\n    if A.shape[0] == b.shape[0]:\n        A_string = f\"{A_label} =\\n{A}\"\n        b_string = f\"{b_label} =\\n{b.reshape((A.shape[0], 1))}\"\n    elif A.shape[0] > b.shape[0]:\n        m = b.shape[0]\n        A_string = f\"{A_label} =\\n{A[:m]}\"\n        b_string = f\"{b_label} =\\n{b.reshape(m, 1)}\"\n        A_string += f\"\\n{A[m:]}\"\n        b_string += \"\\n \" * (A.shape[0] - m)\n    else:  # A.shape[0] < b.shape[0]\n        n = A.shape[0]\n        k = b.shape[0] - n\n        A_string = f\"{A_label} =\\n{A}\"\n        b_string = f\"{b_label} =\\n{b[:n].reshape(n, 1)}\"\n        A_string += \"\\n \" * k\n        b_string += f\"\\n{b[n:].reshape(k, 1)}\"\n    A_lines = A_string.splitlines()\n    b_lines = b_string.splitlines()\n    for i, A_line in enumerate(A_lines):\n        print(A_line.ljust(column_width) + b_lines[i].ljust(column_width))\n"
  },
  {
    "path": "qpsolvers/warnings.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2025 Inria\n\n\"\"\"Warnings from qpsolvers.\"\"\"\n\n\nclass QPWarning(UserWarning):\n    \"\"\"Base class for qpsolvers warnings.\"\"\"\n\n\nclass SparseConversionWarning(QPWarning):\n    \"\"\"Warning issued when converting NumPy arrays to SciPy sparse matrices.\"\"\"\n"
  },
  {
    "path": "tests/__init__.py",
    "content": "# Make sure Python treats the test directory as a package.\n"
  },
  {
    "path": "tests/problems.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\nimport numpy as np\n\nfrom qpsolvers import Problem\n\n\ndef get_sd3310_problem() -> Problem:\n    \"\"\"\n    Get a small dense problem with 3 optimization variables, 3 inequality\n    constraints, 1 equality constraint and 0 box constraint.\n    \"\"\"\n    M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n    P = np.dot(M.T, M)  # this is a positive definite matrix\n    q = np.dot(np.array([3.0, 2.0, 3.0]), M).reshape((3,))\n    G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n    h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n    A = np.array([1.0, 1.0, 1.0])\n    b = np.array([1.0])\n    return Problem(P, q, G, h, A, b)\n\n\ndef get_qpmad_demo_problem():\n    \"\"\"\n    Problem from qpmad's `demo.cpp\n    <https://github.com/asherikov/qpmad/blob/5e4038f15d85a2a396bb062599f9d7a06d0b0764/test/dependency/demo.cpp>`__.\n    \"\"\"\n    P = np.eye(20)\n    q = np.ones((20,))\n    G = np.vstack([np.ones((1, 20)), -np.ones((1, 20))])\n    h = np.hstack([1.5, 1.5])\n    lb = np.array(\n        [\n            1.0,\n            2.0,\n            3.0,\n            4.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n            -5.0,\n        ]\n    )\n    ub = np.array(\n        [\n            1.0,\n            2.0,\n            3.0,\n            4.0,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n            0.5,\n        ]\n    )\n    return Problem(P, q, G, h, lb=lb, ub=ub)\n"
  },
  {
    "path": "tests/test_clarabel.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for Clarabel.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom qpsolvers.problems import get_qpsut01\n\ntry:\n    import clarabel\n\n    from qpsolvers.solvers.clarabel_ import clarabel_solve_problem\n\n    class TestClarabel(unittest.TestCase):\n        \"\"\"Test fixture for the Clarabel.rs solver.\"\"\"\n\n        def test_time_limit(self):\n            \"\"\"Call Clarabel.rs with an infeasibly low time limit.\"\"\"\n            problem, ref_solution = get_qpsut01()\n            solution = clarabel_solve_problem(problem, time_limit=1e-10)\n            status = solution.extras[\"status\"]\n            self.assertEqual(status, clarabel.SolverStatus.MaxTime)\n            # See https://github.com/oxfordcontrol/Clarabel.rs/issues/10\n            self.assertFalse(status != clarabel.SolverStatus.MaxTime)\n\n        def test_status(self):\n            \"\"\"Check that result status is consistent with its string repr.\n\n            Context: https://github.com/oxfordcontrol/Clarabel.rs/issues/10\n            \"\"\"\n            problem, _ = get_qpsut01()\n            solution = clarabel_solve_problem(problem)\n            status = solution.extras[\"status\"]\n            check_1 = str(status) != \"Solved\"\n            check_2 = status != clarabel.SolverStatus.Solved\n            self.assertEqual(check_1, check_2)\n\nexcept ImportError as exn:  # in case the solver is not installed\n    warnings.warn(f\"Skipping Clarabel.rs tests: {exn}\")\n"
  },
  {
    "path": "tests/test_combine_linear_box_inequalities.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for the `solve_qp` function.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\n\nfrom qpsolvers import available_solvers\nfrom qpsolvers.conversions import combine_linear_box_inequalities\n\n\nclass TestCombineLinearBoxInequalities(unittest.TestCase):\n    def setUp(self):\n        \"\"\"Prepare test fixture.\"\"\"\n        warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n        warnings.simplefilter(\"ignore\", category=UserWarning)\n\n    def get_dense_problem(self):\n        \"\"\"Get dense problem as a sextuple of values to unpack.\n\n        Returns\n        -------\n        P : numpy.ndarray\n            Symmetric cost matrix .\n        q : numpy.ndarray\n            Cost vector.\n        G : numpy.ndarray\n            Linear inequality matrix.\n        h : numpy.ndarray\n            Linear inequality vector.\n        A : numpy.ndarray, scipy.sparse.csc_matrix or cvxopt.spmatrix\n            Linear equality matrix.\n        b : numpy.ndarray\n            Linear equality vector.\n        \"\"\"\n        M = np.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n        P = np.dot(M.T, M)  # this is a positive definite matrix\n        q = np.dot(np.array([3.0, 2.0, 3.0]), M).reshape((3,))\n        G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n        A = np.array([1.0, 1.0, 1.0])\n        b = np.array([1.0])\n        return P, q, G, h, A, b\n\n    @staticmethod\n    def get_test_all_shapes(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant tries all possible shapes for matrix and vector\n        parameters.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, _, _ = self.get_dense_problem()\n            A = np.array([[1.0, 0.0, 0.0], [0.0, 0.4, 0.5]])\n            b = np.array([-0.5, -1.2])\n            lb = np.array([-0.5, -2, -0.8])\n            ub = np.array([+1.0, +1.0, +1.0])\n\n            ineq_variants = ((None, None), (G, h), (G[0], np.array([h[0]])))\n            eq_variants = ((None, None), (A, b), (A[0], np.array([b[0]])))\n            box_variants = ((None, None), (lb, None), (None, ub), (lb, ub))\n            cases = [\n                {\n                    \"P\": P,\n                    \"q\": q,\n                    \"G\": G_case,\n                    \"h\": h_case,\n                    \"A\": A_case,\n                    \"b\": b_case,\n                    \"lb\": lb_case,\n                    \"ub\": ub_case,\n                }\n                for (G_case, h_case) in ineq_variants\n                for (A_case, b_case) in eq_variants\n                for (lb_case, ub_case) in box_variants\n            ]\n\n            for i, test_case in enumerate(cases):\n                G = test_case[\"G\"]\n                h = test_case[\"h\"]\n                lb = test_case[\"lb\"]\n                ub = test_case[\"ub\"]\n                n = test_case[\"q\"].shape[0]\n                if G is None and lb is None and ub is None:\n                    continue\n                elif isinstance(G, np.ndarray) and G.ndim == 1:\n                    G = G.reshape((1, G.shape[0]))\n                C, u, l = combine_linear_box_inequalities(\n                    G, h, lb, ub, n, use_csc=False\n                )\n                self.assertTrue(isinstance(C, np.ndarray))\n                self.assertTrue(isinstance(u, np.ndarray))\n                self.assertTrue(isinstance(l, np.ndarray))\n\n        return test\n\n\n# Generate test fixtures for each solver\nfor solver in available_solvers:\n    setattr(\n        TestCombineLinearBoxInequalities,\n        f\"test_all_shapes_{solver}\",\n        TestCombineLinearBoxInequalities.get_test_all_shapes(solver),\n    )\n"
  },
  {
    "path": "tests/test_conversions.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for internal conversion functions.\"\"\"\n\nimport unittest\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom qpsolvers.conversions import linear_from_box_inequalities\n\n\nclass TestConversions(unittest.TestCase):\n    \"\"\"Test fixture for box to linear inequality conversion.\"\"\"\n\n    def __test_linear_from_box_inequalities(self, G, h, lb, ub):\n        G2, h2 = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n        m = G.shape[0] if G is not None else 0\n        k = lb.shape[0]\n        self.assertTrue(np.allclose(G2[m : m + k, :], -np.eye(k)))\n        self.assertTrue(np.allclose(h2[m : m + k], -lb))\n        self.assertTrue(np.allclose(G2[m + k : m + 2 * k, :], np.eye(k)))\n        self.assertTrue(np.allclose(h2[m + k : m + 2 * k], ub))\n\n    def test_concatenate_bounds(self):\n        G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n        lb = np.array([-1.0, -1.0, -1.0])\n        ub = np.array([1.0, 1.0, 1.0])\n        self.__test_linear_from_box_inequalities(G, h, lb, ub)\n\n    def test_pure_bounds(self):\n        lb = np.array([-1.0, -1.0, -1.0])\n        ub = np.array([1.0, 1.0, 1.0])\n        self.__test_linear_from_box_inequalities(None, None, lb, ub)\n\n    def test_skip_infinite_bounds(self):\n        \"\"\"\n        TODO(scaron): infinite box bounds are skipped by the conversion.\n        \"\"\"\n        G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n        lb = np.array([-np.inf, -np.inf, -np.inf])\n        ub = np.array([np.inf, np.inf, np.inf])\n        G2, h2 = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n        if False:  # TODO(scaron): update behavior\n            self.assertTrue(np.allclose(G2, G))\n            self.assertTrue(np.allclose(h2, h))\n\n    def test_skip_partial_infinite_bounds(self):\n        \"\"\"\n        TODO(scaron): all values in the combined constraint vector are finite,\n        even if some input box bounds are infinite.\n        \"\"\"\n        G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n        lb = np.array([-1.0, -np.inf, -1.0])\n        ub = np.array([np.inf, 1.0, 1.0])\n        G2, h2 = linear_from_box_inequalities(G, h, lb, ub, use_sparse=False)\n        if False:  # TODO(scaron): update behavior\n            self.assertTrue(np.isfinite(h2).all())\n\n    def test_sparse_conversion(self):\n        \"\"\"\n        Box concatenation on a sparse problem without linear inequality\n        constraints yields a sparse problem.\n        \"\"\"\n        n = 1000\n        lb = np.full((n,), -1.0)\n        ub = np.full((n,), +1.0)\n        G, h = linear_from_box_inequalities(\n            None, None, lb, ub, use_sparse=True\n        )\n        self.assertTrue(isinstance(G, spa.csc_matrix))\n"
  },
  {
    "path": "tests/test_copt.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for COPT.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.copt_ import copt_solve_qp\n\n    class TestCOPT(unittest.TestCase):\n        \"\"\"Test fixture for the COPT solver.\"\"\"\n\n        def test_copt_params(self):\n            problem = get_sd3310_problem()\n            x = copt_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                verbose=True,\n                FeasTol=1e-8,\n                DualTol=1e-8,\n                Presolve=0\n            )\n            self.assertIsNotNone(x)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping COPT tests: {exn}\")\n"
  },
  {
    "path": "tests/test_cvxopt.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for CVXOPT.\"\"\"\n\nimport unittest\nimport warnings\nfrom typing import Tuple\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom numpy import array, ones\nfrom numpy.linalg import norm\nfrom qpsolvers.problems import get_qpsut01\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    import cvxopt\n    from qpsolvers.solvers.cvxopt_ import cvxopt_solve_problem, cvxopt_solve_qp\n\n    class TestCVXOPT(unittest.TestCase):\n        \"\"\"Test fixture for the CVXOPT solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def get_sparse_problem(\n            self,\n        ) -> Tuple[cvxopt.matrix, np.ndarray, cvxopt.matrix, np.ndarray]:\n            \"\"\"Get sparse problem as a quadruplet of values to unpack.\n\n            Returns\n            -------\n            P :\n                Symmetric cost matrix.\n            q :\n                Cost vector.\n            G :\n                Linear inequality matrix.\n            h :\n                Linear inequality vector.\n            \"\"\"\n            n = 150\n            M = spa.lil_matrix(spa.eye(n))\n            for i in range(1, n - 1):\n                M[i, i + 1] = -1\n                M[i, i - 1] = 1\n            P = spa.csc_matrix(M.dot(M.transpose()))\n            q = -ones((n,))\n            G = spa.csc_matrix(-spa.eye(n))\n            h = -2.0 * ones((n,))\n            return P, q, G, h\n\n        def test_sparse(self):\n            \"\"\"Test CVXOPT on a sparse problem.\"\"\"\n            P, q, G, h = self.get_sparse_problem()\n            x = cvxopt_solve_qp(P, q, G, h)\n            self.assertIsNotNone(x, 'solver=\"cvxopt\"')\n            known_solution = array([2.0] * 149 + [3.0])\n            sol_tolerance = 1e-2  # aouch, not great!\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, 'solver=\"cvxopt\"'\n            )\n            self.assertLess(max(G.dot(x) - h), 1e-10, 'solver=\"cvxopt\"')\n\n        def test_extra_kwargs(self):\n            \"\"\"Call CVXOPT with various solver-specific settings.\"\"\"\n            problem = get_sd3310_problem()\n            x = cvxopt_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                maxiters=10,\n                abstol=1e-1,\n                reltol=1e-1,\n                feastol=1e-2,\n                refinement=3,\n            )\n            self.assertIsNotNone(x, 'solver=\"cvxopt\"')\n\n        def test_infinite_linear_bounds(self):\n            \"\"\"CVXOPT does not yield a domain error on infinite bounds.\"\"\"\n            problem, _ = get_qpsut01()\n            problem.h[1] = +np.inf\n            x = cvxopt_solve_problem(problem)\n            self.assertIsNotNone(x, 'solver=\"cvxopt\"')\n\n        def test_infinite_box_bounds(self):\n            \"\"\"CVXOPT does not yield a domain error infinite box bounds.\"\"\"\n            problem, _ = get_qpsut01()\n            problem.lb[1] = -np.inf\n            problem.ub[1] = +np.inf\n            x = cvxopt_solve_problem(problem)\n            self.assertIsNotNone(x, 'solver=\"cvxopt\"')\n\nexcept ImportError as exn:  # in case the solver is not installed\n    warnings.warn(f\"Skipping CVXOPT tests: {exn}\")\n"
  },
  {
    "path": "tests/test_ecos.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for ECOS.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\n\nfrom qpsolvers.exceptions import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.ecos_ import ecos_solve_qp\n\n    class TestECOS(unittest.TestCase):\n        \"\"\"Tests specific to ECOS.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(ecos_solve_qp(P, q, G, h, A, b, lb, ub))\n\n        def test_infinite_inequality(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            lb = np.array([-1.0, -np.inf, -1.0])\n            ub = np.array([np.inf, 1.0, 1.0])\n            with self.assertRaises(ProblemError):\n                ecos_solve_qp(P, q, G, h, lb=lb, ub=ub)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping ECOS tests: {exn}\")\n"
  },
  {
    "path": "tests/test_gurobi.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for Gurobi.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.gurobi_ import gurobi_solve_qp\n\n    class TestGurobi(unittest.TestCase):\n        \"\"\"Test fixture for the Gurobi solver.\"\"\"\n\n        def test_gurobi_params(self):\n            problem = get_sd3310_problem()\n            x = gurobi_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                TimeLimit=0.1,\n                FeasibilityTol=1e-8,\n            )\n            self.assertIsNotNone(x)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping Gurobi tests: {exn}\")\n"
  },
  {
    "path": "tests/test_highs.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for HiGHS.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.highs_ import highs_solve_qp\n\n    class TestHiGHS(unittest.TestCase):\n        \"\"\"Test fixture for the HiGHS solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def test_highs_tolerances(self):\n            problem = get_sd3310_problem()\n            x = highs_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                time_limit=0.1,\n                primal_feasibility_tolerance=1e-1,\n                dual_feasibility_tolerance=1e-1,\n            )\n            self.assertIsNotNone(x)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping HiGHS tests: {exn}\")\n"
  },
  {
    "path": "tests/test_jaxopt_osqp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2025 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for jaxopt.OSQP.\"\"\"\n\nimport unittest\nimport warnings\n\ntry:\n    import jax.numpy as jnp\n\n    from qpsolvers import solve_qp\n\n    class TestKVXOPT(unittest.TestCase):\n        \"\"\"Test fixture for the KVXOPT solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def test_jax_array_input(self):\n            \"\"\"We can call ``solve_qp`` with jax.Array matrices.\"\"\"\n            M = jnp.array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n            P = M.T @ M  # this is a positive definite matrix\n            q = jnp.array([3.0, 2.0, 3.0]) @ M\n            G = jnp.array(\n                [[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]]\n            )\n            h = jnp.array([3.0, 2.0, -2.0])\n            A = jnp.array([1.0, 1.0, 1.0])\n            b = jnp.array([1.0])\n            x = solve_qp(P, q, G, h, A, b, solver=\"jaxopt_osqp\")\n            self.assertIsNotNone(x)\n\nexcept ImportError as exn:  # in case the solver is not installed\n    warnings.warn(f\"Skipping jaxopt.OSQP tests: {exn}\")\n"
  },
  {
    "path": "tests/test_kvxopt.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for KVXOPT.\"\"\"\n\nimport unittest\nimport warnings\nfrom typing import Tuple\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom numpy import array, ones\nfrom numpy.linalg import norm\nfrom qpsolvers.problems import get_qpsut01\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    import kvxopt\n    from qpsolvers.solvers.kvxopt_ import kvxopt_solve_problem, kvxopt_solve_qp\n\n    class TestKVXOPT(unittest.TestCase):\n        \"\"\"Test fixture for the KVXOPT solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def get_sparse_problem(\n            self,\n        ) -> Tuple[kvxopt.matrix, np.ndarray, kvxopt.matrix, np.ndarray]:\n            \"\"\"Get sparse problem as a quadruplet of values to unpack.\n\n            Returns\n            -------\n            P :\n                Symmetric cost matrix.\n            q :\n                Cost vector.\n            G :\n                Linear inequality matrix.\n            h :\n                Linear inequality vector.\n            \"\"\"\n            n = 150\n            M = spa.lil_matrix(spa.eye(n))\n            for i in range(1, n - 1):\n                M[i, i + 1] = -1\n                M[i, i - 1] = 1\n            P = spa.csc_matrix(M.dot(M.transpose()))\n            q = -ones((n,))\n            G = spa.csc_matrix(-spa.eye(n))\n            h = -2.0 * ones((n,))\n            return P, q, G, h\n\n        def test_sparse(self):\n            \"\"\"Test KVXOPT on a sparse problem.\"\"\"\n            P, q, G, h = self.get_sparse_problem()\n            x = kvxopt_solve_qp(P, q, G, h)\n            self.assertIsNotNone(x, 'solver=\"kvxopt\"')\n            known_solution = array([2.0] * 149 + [3.0])\n            sol_tolerance = 1e-2  # aouch, not great!\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, 'solver=\"kvxopt\"'\n            )\n            self.assertLess(max(G.dot(x) - h), 1e-10, 'solver=\"kvxopt\"')\n\n        def test_extra_kwargs(self):\n            \"\"\"Call KVXOPT with various solver-specific settings.\"\"\"\n            problem = get_sd3310_problem()\n            x = kvxopt_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                maxiters=10,\n                abstol=1e-1,\n                reltol=1e-1,\n                feastol=1e-2,\n                refinement=3,\n            )\n            self.assertIsNotNone(x, 'solver=\"kvxopt\"')\n\n        def test_infinite_linear_bounds(self):\n            \"\"\"KVXOPT does not yield a domain error on infinite bounds.\"\"\"\n            problem, _ = get_qpsut01()\n            problem.h[1] = +np.inf\n            x = kvxopt_solve_problem(problem)\n            self.assertIsNotNone(x, 'solver=\"kvxopt\"')\n\n        def test_infinite_box_bounds(self):\n            \"\"\"KVXOPT does not yield a domain error infinite box bounds.\"\"\"\n            problem, _ = get_qpsut01()\n            problem.lb[1] = -np.inf\n            problem.ub[1] = +np.inf\n            x = kvxopt_solve_problem(problem)\n            self.assertIsNotNone(x, 'solver=\"kvxopt\"')\n\nexcept ImportError as exn:  # in case the solver is not installed\n    warnings.warn(f\"Skipping KVXOPT tests: {exn}\")"
  },
  {
    "path": "tests/test_mosek.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for MOSEK.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.mosek_ import mosek_solve_qp\n\n    class TestMOSEK(unittest.TestCase):\n        \"\"\"Tests specific to MOSEK.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(mosek_solve_qp(P, q, G, h, A, b, lb, ub))\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping MOSEK tests: {exn}\")\n"
  },
  {
    "path": "tests/test_nppro.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.nppro_ import nppro_solve_qp\n\n    class TestNPPro(unittest.TestCase):\n        \"\"\"Tests specific to NPPro.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(nppro_solve_qp(P, q, G, h, A, b, lb, ub))\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping NPPro tests: {exn}\")\n"
  },
  {
    "path": "tests/test_osqp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for OSQP.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.osqp_ import osqp_solve_qp\n\n    class TestOSQP(unittest.TestCase):\n        \"\"\"Tests specific to OSQP.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(osqp_solve_qp(P, q, G, h, A, b, lb, ub))\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping OSQP tests: {exn}\")\n"
  },
  {
    "path": "tests/test_piqp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for PIQP.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom qpsolvers.exceptions import ParamError, ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.piqp_ import piqp_solve_qp\n\n    class TestPIQP(unittest.TestCase):\n        \"\"\"Test fixture specific to the PIQP solver.\"\"\"\n\n        def test_dense_backend(self):\n            \"\"\"Try the dense backend.\"\"\"\n            problem = get_sd3310_problem()\n            sol = piqp_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                backend=\"dense\",\n            )\n            self.assertIsNotNone(sol)\n\n        def test_sparse_backend(self):\n            \"\"\"Try the sparse backend.\"\"\"\n            problem = get_sd3310_problem()\n            sol = piqp_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                backend=\"sparse\",\n            )\n            self.assertIsNotNone(sol)\n\n        def test_invalid_backend(self):\n            \"\"\"Exception raised when asking for an invalid backend.\"\"\"\n            problem = get_sd3310_problem()\n            with self.assertRaises(ParamError):\n                piqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                    problem.A,\n                    problem.b,\n                    backend=\"invalid\",\n                )\n\n        def test_invalid_problems(self):\n            \"\"\"Exception raised when asking for an invalid backend.\"\"\"\n            problem = get_sd3310_problem()\n            with self.assertRaises(ProblemError):\n                piqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    None,\n                    problem.h,\n                    problem.A,\n                    problem.b,\n                    backend=\"sparse\",\n                )\n            with self.assertRaises(ProblemError):\n                piqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    None,\n                    problem.A,\n                    problem.b,\n                    backend=\"sparse\",\n                )\n            with self.assertRaises(ProblemError):\n                piqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                    None,\n                    problem.b,\n                    backend=\"sparse\",\n                )\n            with self.assertRaises(ProblemError):\n                piqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                    problem.A,\n                    None,\n                    backend=\"sparse\",\n                )\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping PIQP tests: {exn}\")\n"
  },
  {
    "path": "tests/test_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for Problem class.\"\"\"\n\nimport tempfile\nimport unittest\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom qpsolvers import ActiveSet, Problem, ProblemError\n\nfrom .problems import get_sd3310_problem\n\n\nclass TestProblem(unittest.TestCase):\n    \"\"\"Test fixture for problems.\"\"\"\n\n    def setUp(self):\n        self.problem = get_sd3310_problem()\n\n    def test_unpack(self):\n        P, q, G, h, A, b, lb, ub = self.problem.unpack()\n        self.assertEqual(P.shape, self.problem.P.shape)\n        self.assertEqual(q.shape, self.problem.q.shape)\n        self.assertEqual(G.shape, self.problem.G.shape)\n        self.assertEqual(h.shape, self.problem.h.shape)\n        self.assertEqual(A.shape, self.problem.A.shape)\n        self.assertEqual(b.shape, self.problem.b.shape)\n        self.assertIsNone(lb)\n        self.assertIsNone(ub)\n\n    def test_check_inequality_constraints(self):\n        problem = get_sd3310_problem()\n        P, q, G, h, A, b, _, _ = problem.unpack()\n        with self.assertRaises(ProblemError):\n            Problem(P, q, G, None, A, b).check_constraints()\n        with self.assertRaises(ProblemError):\n            Problem(P, q, None, h, A, b).check_constraints()\n\n    def test_check_equality_constraints(self):\n        problem = get_sd3310_problem()\n        P, q, G, h, A, b, _, _ = problem.unpack()\n        with self.assertRaises(ProblemError):\n            Problem(P, q, G, h, A, None).check_constraints()\n        with self.assertRaises(ProblemError):\n            Problem(P, q, G, h, None, b).check_constraints()\n\n    def test_cond(self):\n        active_set = ActiveSet(\n            G_indices=range(self.problem.G.shape[0]),\n            lb_indices=[],\n            ub_indices=[],\n        )\n        self.assertGreater(self.problem.cond(active_set), 200.0)\n\n    def test_cond_unconstrained(self):\n        unconstrained = Problem(self.problem.P, self.problem.q)\n        active_set = ActiveSet()\n        self.assertAlmostEqual(\n            unconstrained.cond(active_set), 124.257, places=4\n        )\n\n    def test_cond_no_equality(self):\n        no_equality = Problem(\n            self.problem.P, self.problem.q, self.problem.G, self.problem.h\n        )\n        active_set = ActiveSet(G_indices=range(self.problem.G.shape[0]))\n        self.assertGreater(no_equality.cond(active_set), 200.0)\n\n    def test_cond_sparse(self):\n        sparse = Problem(spa.csc_matrix(self.problem.P), self.problem.q)\n        active_set = ActiveSet()\n        with self.assertRaises(ProblemError):\n            sparse.cond(active_set)\n\n    def test_check_matrix_shapes(self):\n        Problem(np.eye(1), np.ones(1))\n        Problem(np.array([1.0]), np.ones(1))\n\n    def test_check_vector_shapes(self):\n        Problem(np.eye(3), np.ones(shape=(3, 1)))\n        Problem(np.eye(3), np.ones(shape=(1, 3)))\n        Problem(np.eye(3), np.ones(shape=(3,)))\n        with self.assertRaises(ProblemError):\n            Problem(np.eye(3), np.ones(shape=(3, 2)))\n        with self.assertRaises(ProblemError):\n            Problem(np.eye(3), np.ones(shape=(3, 1, 1)))\n        with self.assertRaises(ProblemError):\n            Problem(np.eye(3), np.ones(shape=(1, 3, 1)))\n\n    def test_save_load(self):\n        problem = Problem(np.eye(3), np.ones(shape=(3, 1)))\n        save_path = tempfile.mktemp() + \".npz\"\n        problem.save(save_path)\n        reloaded = Problem.load(save_path)\n        self.assertTrue(np.allclose(reloaded.P, problem.P))\n        self.assertTrue(np.allclose(reloaded.q, problem.q))\n        self.assertIsNone(reloaded.G)\n        self.assertIsNone(reloaded.h)\n        self.assertIsNone(reloaded.A)\n        self.assertIsNone(reloaded.b)\n        self.assertIsNone(reloaded.lb)\n        self.assertIsNone(reloaded.ub)\n"
  },
  {
    "path": "tests/test_proxqp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for ProxQP.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom qpsolvers.exceptions import ParamError, ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.proxqp_ import proxqp_solve_qp\n\n    class TestProxQP(unittest.TestCase):\n        \"\"\"Test fixture specific to the ProxQP solver.\"\"\"\n\n        def test_dense_backend(self):\n            \"\"\"Try the dense backend.\"\"\"\n            problem = get_sd3310_problem()\n            proxqp_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                backend=\"dense\",\n            )\n\n        def test_sparse_backend(self):\n            \"\"\"Try the sparse backend.\"\"\"\n            problem = get_sd3310_problem()\n            proxqp_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                backend=\"sparse\",\n            )\n\n        def test_invalid_backend(self):\n            \"\"\"Exception raised when asking for an invalid backend.\"\"\"\n            problem = get_sd3310_problem()\n            with self.assertRaises(ParamError):\n                proxqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                    problem.A,\n                    problem.b,\n                    backend=\"invalid\",\n                )\n\n        def test_double_warm_start(self):\n            \"\"\"Exception when two warm-start values are provided.\"\"\"\n            problem = get_sd3310_problem()\n            with self.assertRaises(ParamError):\n                proxqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                    problem.A,\n                    problem.b,\n                    initvals=problem.q,\n                    x=problem.q,\n                )\n\n        def test_invalid_inequalities(self):\n            \"\"\"Check for inconsistent parameters.\n\n            Raise an exception in an implementation-dependent inconsistent set\n            of parameters. This may happen when :func:`proxqp_solve_qp` it is\n            called directly.\n            \"\"\"\n            problem = get_sd3310_problem()\n            with self.assertRaises(ProblemError):\n                proxqp_solve_qp(\n                    problem.P,\n                    problem.q,\n                    G=problem.G,\n                    h=None,\n                    lb=problem.q,\n                )\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping ProxQP tests: {exn}\")\n"
  },
  {
    "path": "tests/test_pyqpmad.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2024 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for pyqpmad.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom qpsolvers.exceptions import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.pyqpmad_ import pyqpmad_solve_qp\n\n    class TestPyqpmad(unittest.TestCase):\n        \"\"\"Test fixture for the pyqpmad solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def test_not_sparse(self):\n            \"\"\"Raise a ProblemError on sparse problems.\"\"\"\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            P = spa.csc_matrix(P)\n            with self.assertRaises(ProblemError):\n                pyqpmad_solve_qp(P, q, G, h, A, b)\n\n        def test_box_constraints(self):\n            \"\"\"Test solving a problem with only box constraints.\"\"\"\n            P = np.array([[1.0, 0.0], [0.0, 1.0]])\n            q = np.array([-1.0, -1.0])\n            lb = np.array([0.0, 0.0])\n            ub = np.array([0.5, 0.5])\n            x = pyqpmad_solve_qp(P, q, lb=lb, ub=ub)\n            self.assertIsNotNone(x)\n            self.assertTrue(np.allclose(x, [0.5, 0.5]))\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping pyqpmad tests: {exn}\")\n"
  },
  {
    "path": "tests/test_qpax.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2024 Lev Kozlov\n\n\"\"\"Unit tests for qpax.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.qpax_ import qpax_solve_qp\n\n    class TestQpax(unittest.TestCase):\n        \"\"\"Tests specific to qpax.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(qpax_solve_qp(P, q, G, h, A, b, lb, ub))\n\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping qpax tests: {exn}\")\n"
  },
  {
    "path": "tests/test_qpoases.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for qpOASES.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom qpsolvers import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.qpoases_ import qpoases_solve_qp\n\n    class TestQpOASES(unittest.TestCase):\n        \"\"\"Test fixture specific to the qpOASES solver.\"\"\"\n\n        def test_initvals(self):\n            \"\"\"Call the solver with a warm-start guess.\"\"\"\n            problem = get_sd3310_problem()\n            qpoases_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                initvals=problem.q,\n            )\n\n        def test_params(self):\n            \"\"\"Call the solver with a time limit and other parameters.\"\"\"\n            problem = get_sd3310_problem()\n            qpoases_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                time_limit=0.1,\n                terminationTolerance=1e-7,\n            )\n            qpoases_solve_qp(\n                problem.P,\n                problem.q,\n                time_limit=0.1,\n                terminationTolerance=1e-7,\n            )\n\n        def test_unfeasible(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            custom_lb = np.ones(q.shape)\n            custom_ub = -np.ones(q.shape)\n            x = qpoases_solve_qp(\n                problem.P,\n                problem.q,\n                problem.G,\n                problem.h,\n                problem.A,\n                problem.b,\n                custom_lb,\n                custom_ub,\n            )\n            self.assertIsNone(x)\n\n        def test_not_sparse(self):\n            \"\"\"Raise a ProblemError on sparse problems.\"\"\"\n            problem = get_sd3310_problem()\n            problem.P = spa.csc_matrix(problem.P)\n            with self.assertRaises(ProblemError):\n                qpoases_solve_qp(\n                    problem.P,\n                    problem.q,\n                    problem.G,\n                    problem.h,\n                )\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping qpOASES tests: {exn}\")\n"
  },
  {
    "path": "tests/test_qpswift.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for qpSWIFT.\"\"\"\n\nimport unittest\nimport warnings\n\nimport scipy.sparse as spa\n\nfrom qpsolvers import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.qpswift_ import qpswift_solve_qp\n\n    class TestQpSwift(unittest.TestCase):\n        \"\"\"Tests specific to qpSWIFT.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(qpswift_solve_qp(P, q, G, h, A, b, lb, ub))\n\n        def test_not_sparse(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            P = spa.csc_matrix(P)\n            with self.assertRaises(ProblemError):\n                qpswift_solve_qp(P, q, G, h, A, b, lb, ub)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping qpSWIFT tests: {exn}\")\n"
  },
  {
    "path": "tests/test_quadprog.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for quadprog.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\nimport scipy.sparse as spa\n\nfrom qpsolvers.exceptions import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.quadprog_ import quadprog_solve_qp\n\n    class TestQuadprog(unittest.TestCase):\n        \"\"\"Test fixture for the quadprog solver.\"\"\"\n\n        def setUp(self):\n            \"\"\"Prepare test fixture.\"\"\"\n            warnings.simplefilter(\"ignore\", category=UserWarning)\n\n        def test_non_psd_cost(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            P -= np.eye(3)\n            with self.assertRaises(ProblemError):\n                quadprog_solve_qp(P, q, G, h, A, b)\n\n        def test_quadprog_value_error(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            q = q[1:]  # raise \"G and a must have the same dimension\"\n            self.assertIsNone(quadprog_solve_qp(P, q, G, h, A, b))\n\n        def test_not_sparse(self):\n            \"\"\"Raise a ProblemError on sparse problems.\"\"\"\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, _, _ = problem.unpack()\n            P = spa.csc_matrix(P)\n            with self.assertRaises(ProblemError):\n                quadprog_solve_qp(P, q, G, h, A, b)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping quadprog tests: {exn}\")\n"
  },
  {
    "path": "tests/test_scs.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for SCS.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\n\nfrom qpsolvers import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.scs_ import scs_solve_qp\n\n    class TestSCS(unittest.TestCase):\n        \"\"\"Tests specific to SCS.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(scs_solve_qp(P, q, G, h, A, b, lb, ub))\n\n        def test_unbounded_below(self):\n            problem = get_sd3310_problem()\n            P, q, _, _, _, _, _, _ = problem.unpack()\n            P -= np.eye(3)  # make problem unbounded\n            with self.assertRaises(ProblemError):\n                scs_solve_qp(P, q)\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping SCS tests: {exn}\")\n"
  },
  {
    "path": "tests/test_sip.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for SIP.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\n\nfrom qpsolvers import ProblemError\n\nfrom .problems import get_sd3310_problem\n\ntry:\n    from qpsolvers.solvers.sip_ import sip_solve_qp\n\n    class TestSIP(unittest.TestCase):\n        \"\"\"Tests specific to SIP.\"\"\"\n\n        def test_problem(self):\n            problem = get_sd3310_problem()\n            P, q, G, h, A, b, lb, ub = problem.unpack()\n            self.assertIsNotNone(sip_solve_qp(P, q, G, h, A, b, lb, ub))\n\nexcept ImportError as exn:  # solver not installed\n    warnings.warn(f\"Skipping SIP tests: {exn}\")\n"
  },
  {
    "path": "tests/test_solution.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for Solution class.\"\"\"\n\nimport unittest\n\nimport numpy as np\n\nfrom qpsolvers import Solution, solve_problem\n\nfrom .problems import get_sd3310_problem\n\n\nclass TestSolution(unittest.TestCase):\n    \"\"\"Test fixture for solutions.\"\"\"\n\n    def test_found_default(self):\n        solution = Solution(get_sd3310_problem())\n        self.assertFalse(solution.found)\n\n    def test_residuals(self):\n        \"\"\"Test residuals at the solution of the SD3310 problem.\n\n        Note\n        ----\n        This function uses DAQP to find a solution.\n        \"\"\"\n        problem = get_sd3310_problem()\n        solution = solve_problem(problem, solver=\"daqp\")\n        eps_abs = 1e-10\n        self.assertLess(solution.primal_residual(), eps_abs)\n        self.assertLess(solution.dual_residual(), eps_abs)\n        self.assertLess(solution.duality_gap(), eps_abs)\n        self.assertTrue(solution.is_optimal(eps_abs))\n\n    def test_undefined_optimality(self):\n        solution = Solution(get_sd3310_problem())\n\n        # solution is fully undefined\n        self.assertEqual(solution.primal_residual(), np.inf)\n        self.assertEqual(solution.dual_residual(), np.inf)\n        self.assertEqual(solution.duality_gap(), np.inf)\n\n        # solution was not found\n        solution.found = False\n        self.assertEqual(solution.primal_residual(), np.inf)\n        self.assertEqual(solution.dual_residual(), np.inf)\n        self.assertEqual(solution.duality_gap(), np.inf)\n\n        solution.found = True\n        solution.x = np.array([1.0, 2.0, 3.0])\n        self.assertNotEqual(solution.primal_residual(), np.inf)\n        self.assertEqual(solution.dual_residual(), np.inf)\n        self.assertEqual(solution.duality_gap(), np.inf)\n\n        solution.z = np.array([-1.0, -2.0, -3.0])\n        self.assertEqual(solution.dual_residual(), np.inf)\n        self.assertEqual(solution.duality_gap(), np.inf)\n\n        solution.y = np.array([0.0])\n        self.assertNotEqual(solution.dual_residual(), np.inf)\n        self.assertNotEqual(solution.duality_gap(), np.inf)\n\n        # solution is now fully defined\n        self.assertGreater(solution.primal_residual(), 1.0)\n        self.assertGreater(solution.dual_residual(), 10.0)\n        self.assertGreater(solution.duality_gap(), 10.0)\n"
  },
  {
    "path": "tests/test_solve_ls.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for the `solve_ls` function.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\nimport scipy.sparse as spa\nfrom numpy.linalg import norm\n\nfrom qpsolvers import available_solvers, solve_ls, sparse_solvers\nfrom qpsolvers.exceptions import NoSolverSelected, SolverNotFound\nfrom qpsolvers.problems import get_sparse_least_squares\n\n\nclass TestSolveLS(unittest.TestCase):\n    def setUp(self):\n        \"\"\"Prepare test fixture.\"\"\"\n        warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n        warnings.simplefilter(\"ignore\", category=UserWarning)\n\n    def get_problem_and_solution(self):\n        \"\"\"Get least-squares problem and its primal solution.\n\n        Returns\n        -------\n        R :\n            Least-squares matrix.\n        s :\n            Least-squares vector.\n        G :\n            Linear inequality matrix.\n        h :\n            Linear inequality vector.\n        A :\n            Linear equality matrix.\n        b :\n            Linear equality vector.\n        solution :\n            Known solution.\n        \"\"\"\n        R = np.array([[1.0, 2.0, 0.0], [2.0, 3.0, 4.0], [0.0, 4.0, 1.0]])\n        s = np.array([3.0, 2.0, 3.0])\n        G = np.array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = np.array([3.0, 2.0, -2.0]).reshape((3,))\n        A = np.array([1.0, 1.0, 1.0])\n        b = np.array([1.0])\n        solution = np.array([2.0 / 3, -1.0 / 3, 2.0 / 3])\n        return R, s, G, h, A, b, solution\n\n    @staticmethod\n    def get_test(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            R, s, G, h, A, b, solution = self.get_problem_and_solution()\n            x = solve_ls(\n                R, s, G, h, A, b, solver=solver, sparse_conversion=False\n            )\n            x_sp = solve_ls(\n                R, s, G, h, A, b, solver=solver, sparse_conversion=False\n            )\n            self.assertIsNotNone(x, f\"{solver=}\")\n            self.assertIsNotNone(x_sp, f\"{solver=}\")\n            sol_tolerance = (\n                5e-3\n                if solver in [\"jaxopt_osqp\", \"osqp\"]\n                else (\n                    2e-5\n                    if solver == \"proxqp\"\n                    else (\n                        1e-5\n                        if solver in [\"ecos\", \"qpalm\", \"qpax\", \"sip\"]\n                        else 1e-6\n                    )\n                )\n            )\n            eq_tolerance = (\n                1e-4\n                if solver == \"jaxopt_osqp\"\n                else (\n                    2e-6\n                    if solver in [\"qpalm\", \"qpax\"]\n                    else 1e-7 if solver in [\"osqp\", \"qtqp\", \"sip\"] else 1e-9\n                )\n            )\n            ineq_tolerance = (\n                1e-3\n                if solver == \"osqp\"\n                else (\n                    1e-5\n                    if solver == \"proxqp\"\n                    else (\n                        2e-7\n                        if solver in [\"scs\", \"qpax\"]\n                        else 1e-8 if solver == \"qtqp\" else 1e-9\n                    )\n                )\n            )\n            self.assertLess(norm(x - solution), sol_tolerance, f\"{solver=}\")\n            self.assertLess(norm(x_sp - solution), sol_tolerance, f\"{solver=}\")\n            self.assertLess(max(G.dot(x) - h), ineq_tolerance, f\"{solver=}\")\n            self.assertLess(max(A.dot(x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(A.dot(x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    def test_no_solver_selected(self):\n        \"\"\"Check that NoSolverSelected is raised when applicable.\"\"\"\n        R, s, G, h, A, b, _ = self.get_problem_and_solution()\n        with self.assertRaises(NoSolverSelected):\n            solve_ls(R, s, G, h, A, b, solver=None)\n\n    def test_solver_not_found(self):\n        \"\"\"SolverNotFound is raised when the solver does not exist.\"\"\"\n        R, s, G, h, A, b, _ = self.get_problem_and_solution()\n        with self.assertRaises(SolverNotFound):\n            solve_ls(R, s, G, h, A, b, solver=\"ideal\")\n\n    @staticmethod\n    def get_test_mixed_sparse_args(solver: str):\n        \"\"\"Get test function for mixed sparse problems with a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            _, s, G, h, A, b, _ = self.get_problem_and_solution()\n            n = len(s)\n\n            R_csc = spa.eye(n, format=\"csc\")\n            x_csc = solve_ls(\n                R_csc, s, G, h, A, b, solver=solver, sparse_conversion=False\n            )\n            self.assertIsNotNone(x_csc, f\"{solver=}\")\n\n            R_dia = spa.eye(n)\n            x_dia = solve_ls(\n                R_dia, s, G, h, A, b, solver=solver, sparse_conversion=False\n            )\n            self.assertIsNotNone(x_dia, f\"{solver=}\")\n\n            x_np_dia = solve_ls(\n                R_dia,\n                s,\n                G,\n                h,\n                A,\n                b,\n                W=np.eye(n),\n                solver=solver,\n                sparse_conversion=False,\n            )\n            self.assertIsNotNone(x_np_dia, f\"{solver=}\")\n\n            sol_tolerance = 1e-8\n            self.assertLess(norm(x_csc - x_dia), sol_tolerance, f\"{solver=}\")\n            self.assertLess(\n                norm(x_csc - x_np_dia), sol_tolerance, f\"{solver=}\"\n            )\n\n        return test\n\n    @staticmethod\n    def get_test_medium_sparse(solver: str, sparse_conversion: bool, **kwargs):\n        \"\"\"Get test function for a large sparse problem with a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n        sparse_conversion :\n            Conversion strategy boolean.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            R, s, G, h, A, b, _, _ = get_sparse_least_squares(n=1500)\n            x = solve_ls(\n                R,\n                s,\n                G,\n                h,\n                A,\n                b,\n                solver=solver,\n                sparse_conversion=sparse_conversion,\n                **kwargs,\n            )\n            self.assertIsNotNone(x, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_large_sparse(\n        solver: str,\n        sparse_conversion: bool,\n        obj_scaling: float = 1.0,\n        **kwargs,\n    ):\n        \"\"\"Get test function for a large sparse problem with a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n        sparse_conversion :\n            Whether to perform sparse or dense LS-to-QP conversion.\n        obj_scaling:\n            Scale objective matrices by this factor. Suitable values can help\n            solvers that don't compute a preconditioner internally.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            R, s, G, h, A, b, _, _ = get_sparse_least_squares(n=15_000)\n            x = solve_ls(\n                obj_scaling * R,\n                obj_scaling * s,\n                G,\n                h,\n                A,\n                b,\n                solver=solver,\n                sparse_conversion=sparse_conversion,\n                **kwargs,\n            )\n            self.assertIsNotNone(x, f\"{solver=}\")\n\n        return test\n\n\n# Generate test fixtures for each solver\nfor solver in available_solvers:\n    setattr(\n        TestSolveLS, \"test_{}\".format(solver), TestSolveLS.get_test(solver)\n    )\n\nfor solver in sparse_solvers:\n    setattr(\n        TestSolveLS,\n        \"test_mixed_sparse_args_{}\".format(solver),\n        TestSolveLS.get_test_mixed_sparse_args(solver),\n    )\n\nfor solver in sparse_solvers:  # loop complexity warning ;p\n    if solver not in [\"gurobi\", \"qtqp\"]:\n        # Gurobi: model too large for size-limited license\n        # QTQP: slow convergence on medium/large problems (pure Python)\n        kwargs = {}\n        if solver == \"mosek\":\n            try:\n                import mosek\n\n                kwargs[\"mosek\"] = {mosek.dparam.intpnt_qo_tol_rel_gap: 1e-6}\n            except ImportError:\n                pass\n        if solver != \"copt\":\n            # COPT: Size limitation for non-commercial use\n            setattr(\n                TestSolveLS,\n                \"test_medium_sparse_dense_conversion_{}\".format(solver),\n                TestSolveLS.get_test_medium_sparse(\n                    solver, sparse_conversion=False, **kwargs\n                ),\n            )\n\nfor solver in sparse_solvers:  # loop complexity warning ;p\n    if solver not in [\"copt\", \"cvxopt\", \"kvxopt\", \"gurobi\", \"qtqp\"]:\n        # COPT: Size limitation for non-commercial use\n        # CVXOPT and KVXOPT: sparse conversion breaks rank assumption\n        # Gurobi: model too large for size-limited license\n        # QTQP: slow convergence on medium/large problems (pure Python)\n        setattr(\n            TestSolveLS,\n            \"test_medium_sparse_sparse_conversion_{}\".format(solver),\n            TestSolveLS.get_test_medium_sparse(solver, sparse_conversion=True),\n        )\n\nfor solver in sparse_solvers:  # loop complexity warning ;p\n    if solver not in [\"gurobi\", \"highs\", \"qtqp\"]:\n        # Gurobi: model too large for size-limited license\n        # HiGHS: model too large https://github.com/ERGO-Code/HiGHS/issues/992\n        # QTQP: slow convergence on large problems (pure Python implementation)\n        kwargs = {\"eps_infeas\": 1e-12} if solver == \"scs\" else {}\n        if solver == \"mosek\":\n            try:\n                import mosek\n\n                kwargs[\"mosek\"] = {mosek.dparam.intpnt_qo_tol_rel_gap: 1e-7}\n            except ImportError:\n                pass\n        if solver != \"copt\":\n            # COPT: Size limitation for non-commercial use\n            setattr(\n                TestSolveLS,\n                \"test_large_sparse_problem_dense_conversion_{}\".format(solver),\n                TestSolveLS.get_test_large_sparse(\n                    solver,\n                    sparse_conversion=False,\n                    obj_scaling=1e-3 if solver == \"mosek\" else 1.0,\n                    **kwargs,\n                ),\n            )\n        if solver not in [\"copt\", \"cvxopt\", \"kvxopt\"]:\n            # COPT: Size limitation for non-commercial use\n            # CVXOPT and KVXOPT: sparse conversion breaks rank assumption\n            setattr(\n                TestSolveLS,\n                \"test_large_sparse_problem_sparse_conversion_{}\".format(\n                    solver\n                ),\n                TestSolveLS.get_test_large_sparse(\n                    solver, sparse_conversion=True, **kwargs\n                ),\n            )\n"
  },
  {
    "path": "tests/test_solve_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2023 Inria\n\n\"\"\"Unit tests for the `solve_problem` function.\"\"\"\n\nimport unittest\n\nimport numpy as np\nfrom numpy.linalg import norm\n\nfrom qpsolvers import available_solvers, solve_problem\nfrom qpsolvers.problems import (\n    get_qpgurabs,\n    get_qpgurdu,\n    get_qpgureq,\n    get_qpsut01,\n    get_qpsut02,\n    get_qpsut03,\n    get_qpsut04,\n    get_qpsut05,\n    get_qptest,\n)\n\n\nclass TestSolveProblem(unittest.TestCase):\n    \"\"\"Test fixture for primal and dual solutions of a variety of problems.\n\n    Notes\n    -----\n    Solver-specific tests are implemented in static methods called\n    ``get_test_{foo}`` that return the test function for a given solver. The\n    corresponding test function ``test_{foo}_{solver}`` is then added to the\n    fixture below the class definition.\n    \"\"\"\n\n    @staticmethod\n    def get_test_qpsut01(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, ref_solution = get_qpsut01()\n            solution = solve_problem(problem, solver=solver)\n            eps_abs = (\n                5e-1\n                if solver in [\"jaxopt_osqp\", \"osqp\", \"qpalm\"]\n                else (\n                    5e-3\n                    if solver == \"proxqp\"\n                    else (\n                        1e-4\n                        if solver == \"ecos\"\n                        else (\n                            5e-5\n                            if solver in [\"mosek\", \"qpax\", \"sip\"]\n                            else (\n                                5e-6\n                                if solver == \"qtqp\"\n                                else (\n                                    1e-6\n                                    if solver\n                                    in [\"copt\", \"cvxopt\", \"kvxopt\", \"qpswift\", \"scs\"]\n                                    else 5e-7 if solver in [\"gurobi\"] else 1e-7\n                                )\n                            )\n                        )\n                    )\n                )\n            )\n            self.assertLess(\n                norm(solution.x - ref_solution.x), eps_abs, f\"{solver=}\"\n            )\n            # NB: in general the dual solution is not unique (that's why the\n            # other tests check residuals). This test only works because the\n            # dual solution is unique in this particular problem.\n            self.assertLess(\n                norm(solution.y - ref_solution.y), eps_abs, f\"{solver=}\"\n            )\n            self.assertLess(\n                norm(solution.z - ref_solution.z), eps_abs, f\"{solver=}\"\n            )\n            self.assertLess(\n                norm(solution.z_box - ref_solution.z_box),\n                eps_abs,\n                f\"{solver=}\",\n            )\n\n        return test\n\n    @staticmethod\n    def get_test_qpsut02(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, ref_solution = get_qpsut02()\n            solution = solve_problem(problem, solver=solver)\n            eps_abs = (\n                5e-2\n                if solver in [\"ecos\", \"jaxopt_osqp\", \"qpalm\"]\n                else (\n                    5e-4\n                    if solver in [\"proxqp\", \"scs\", \"qpax\"]\n                    else (\n                        1e-4\n                        if solver in [\"cvxopt\", \"kvxopt\", \"qpax\", \"qtqp\"]\n                        else (\n                            1e-5\n                            if solver in [\"copt\", \"highs\", \"osqp\"]\n                            else (\n                                5e-7\n                                if solver\n                                in [\n                                    \"clarabel\",\n                                    \"mosek\",\n                                    \"qpswift\",\n                                    \"piqp\",\n                                    \"sip\",\n                                ]\n                                else 1e-7 if solver in [\"gurobi\"] else 1e-8\n                            )\n                        )\n                    )\n                )\n            )\n            self.assertLess(\n                norm(solution.x - ref_solution.x), eps_abs, f\"{solver=}\"\n            )\n            self.assertLess(solution.primal_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(solution.dual_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(solution.duality_gap(), eps_abs, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpsut03(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, ref_solution = get_qpsut03()\n            solution = solve_problem(problem, solver=solver)\n            self.assertEqual(solution.x.shape, (4,), f\"{solver=}\")\n            self.assertEqual(solution.y.shape, (0,), f\"{solver=}\")\n            self.assertEqual(solution.z.shape, (0,), f\"{solver=}\")\n            self.assertEqual(solution.z_box.shape, (4,), f\"{solver=}\")\n            tolerance = (\n                1e-1\n                if solver in [\"osqp\", \"qtqp\"]\n                else 1e-2 if solver == \"scs\" else 1e-3\n            )\n            self.assertTrue(solution.is_optimal(tolerance), f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpsut04(solver: str):\n        \"\"\"\n        Get test function for the QPSUT04 problem.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, ref_solution = get_qpsut04()\n            solution = solve_problem(problem, solver=solver)\n            eps_abs = (\n                2e-4\n                if solver in [\"jaxopt_osqp\", \"osqp\", \"qpalm\", \"qpax\", \"sip\"]\n                else 1e-6\n            )\n            self.assertLess(\n                norm(solution.x - ref_solution.x), eps_abs, f\"{solver=}\"\n            )\n            self.assertLess(\n                norm(solution.z - ref_solution.z), eps_abs, f\"{solver=}\"\n            )\n            self.assertTrue(np.isfinite(solution.duality_gap()), f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpsut05(solver: str):\n        \"\"\"\n        Get test function for the QPSUT04 problem.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, ref_solution = get_qpsut05()\n            solution = solve_problem(problem, solver=solver)\n            eps_abs = 2e-5 if solver == \"ecos\" else 1e-6\n            self.assertLess(\n                norm(solution.x - ref_solution.x), eps_abs, f\"{solver=}\"\n            )\n            self.assertTrue(np.isfinite(solution.duality_gap()), f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qptest(solver: str):\n        \"\"\"Get test function for the QPTEST problem.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n\n        Note\n        ----\n        ECOS fails to solve this problem.\n        \"\"\"\n\n        def test(self):\n            problem, solution = get_qptest()\n            result = solve_problem(problem, solver=solver)\n            tolerance = (\n                1e1\n                if solver == \"gurobi\"\n                else (\n                    1.0\n                    if solver == \"proxqp\"\n                    else (\n                        2e-3\n                        if solver == \"osqp\"\n                        else (\n                            5e-5\n                            if solver in [\"qpalm\", \"scs\", \"qpax\"]\n                            else (\n                                1e-6\n                                if solver == \"mosek\"\n                                else (\n                                    1e-7\n                                    if solver == \"highs\"\n                                    else (\n                                        5e-7\n                                        if solver == \"cvxopt\"\n                                        or solver == \"kvxopt\"\n                                        else (\n                                            5e-8\n                                            if solver == \"clarabel\"\n                                            else 1e-8\n                                        )\n                                    )\n                                )\n                            )\n                        )\n                    )\n                )\n            )\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            self.assertIsNotNone(result.z_box, f\"{solver=}\")\n            self.assertTrue(solution.is_optimal(tolerance), f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_infinite_box_bounds(solver: str):\n        \"\"\"Problem with some infinite box bounds.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, _ = get_qpsut01()\n            problem.lb[1] = -np.inf\n            problem.ub[1] = +np.inf\n            result = solve_problem(problem, solver=solver)\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            self.assertIsNotNone(result.z_box, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_infinite_linear_bounds(solver: str):\n        \"\"\"Problem with some infinite linear bounds.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, _ = get_qpsut01()\n            problem.h[0] = +np.inf\n            result = solve_problem(problem, solver=solver)\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            self.assertIsNotNone(result.z_box, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpgurdu(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, _ = get_qpgurdu()\n            result = solve_problem(problem, solver)\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            eps_abs = (\n                6e-3\n                if solver in (\"jaxopt_osqp\", \"osqp\", \"qpax\")\n                else (\n                    1e-3\n                    if solver in (\"scs\", \"sip\")\n                    else (\n                        1e-4 if solver in (\"ecos\", \"highs\", \"proxqp\") else 1e-5\n                    )\n                )\n            )\n            self.assertLess(result.primal_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.dual_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.duality_gap(), eps_abs, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpgurabs(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem, _ = get_qpgurabs()\n            result = solve_problem(problem, solver)\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            eps_abs = (\n                0.2\n                if solver == \"osqp\"\n                else 3e-3 if solver in [\"jaxopt_osqp\", \"proxqp\"] else 1e-4\n            )\n            self.assertLess(result.primal_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.dual_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.duality_gap(), eps_abs, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_qpgureq(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            if solver == \"ecos\":\n                return\n            problem, _ = get_qpgureq()\n            result = solve_problem(problem, solver)\n            self.assertIsNotNone(result.x, f\"{solver=}\")\n            self.assertIsNotNone(result.z, f\"{solver=}\")\n            eps_abs = (\n                0.01\n                if solver in [\"osqp\", \"qpax\"]\n                else 5e-3 if solver in [\"jaxopt_osqp\", \"proxqp\"] else 1e-4\n            )\n            self.assertLess(result.primal_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.dual_residual(), eps_abs, f\"{solver=}\")\n            self.assertLess(result.duality_gap(), eps_abs, f\"{solver=}\")\n\n        return test\n\n\n# Generate test fixtures for each solver\nfor solver in available_solvers:\n    setattr(\n        TestSolveProblem,\n        f\"test_qpsut01_{solver}\",\n        TestSolveProblem.get_test_qpsut01(solver),\n    )\n    setattr(\n        TestSolveProblem,\n        f\"test_qpsut02_{solver}\",\n        TestSolveProblem.get_test_qpsut02(solver),\n    )\n    if solver not in [\"ecos\", \"mosek\", \"qpswift\", \"qtqp\"]:\n        # ECOS: https://github.com/embotech/ecos-python/issues/49\n        # MOSEK: https://github.com/qpsolvers/qpsolvers/issues/229\n        # qpSWIFT: https://github.com/qpsolvers/qpsolvers/issues/159\n        # qpax: https://github.com/kevin-tracy/qpax/issues/5\n        # qtqp: does not handle problems without inequalities\n        setattr(\n            TestSolveProblem,\n            f\"test_qpsut03_{solver}\",\n            TestSolveProblem.get_test_qpsut03(solver),\n        )\n    setattr(\n        TestSolveProblem,\n        f\"test_qpsut04_{solver}\",\n        TestSolveProblem.get_test_qpsut04(solver),\n    )\n    if solver not in [\"osqp\", \"qpswift\"]:\n        # OSQP: see https://github.com/osqp/osqp-python/issues/104\n        setattr(\n            TestSolveProblem,\n            f\"test_qpsut05_{solver}\",\n            TestSolveProblem.get_test_qpsut05(solver),\n        )\n    if solver not in [\"ecos\", \"qpswift\"]:\n        # ECOS: https://github.com/qpsolvers/qpsolvers/issues/160\n        # qpSWIFT: https://github.com/qpsolvers/qpsolvers/issues/159\n        # qpax: https://github.com/kevin-tracy/qpax/issues/4\n        setattr(\n            TestSolveProblem,\n            f\"test_qptest_{solver}\",\n            TestSolveProblem.get_test_qptest(solver),\n        )\n    if solver not in [\"ecos\", \"qpswift\"]:\n        # See https://github.com/qpsolvers/qpsolvers/issues/159\n        # See https://github.com/qpsolvers/qpsolvers/issues/160\n        setattr(\n            TestSolveProblem,\n            f\"test_infinite_box_bounds_{solver}\",\n            TestSolveProblem.get_test_infinite_box_bounds(solver),\n        )\n    if solver not in [\"ecos\", \"qpswift\", \"scs\"]:\n        # See https://github.com/qpsolvers/qpsolvers/issues/159\n        # See https://github.com/qpsolvers/qpsolvers/issues/160\n        # See https://github.com/qpsolvers/qpsolvers/issues/161\n        setattr(\n            TestSolveProblem,\n            f\"test_infinite_linear_bounds_{solver}\",\n            TestSolveProblem.get_test_infinite_linear_bounds(solver),\n        )\n    setattr(\n        TestSolveProblem,\n        f\"test_qpgurdu_{solver}\",\n        TestSolveProblem.get_test_qpgurdu(solver),\n    )\n    setattr(\n        TestSolveProblem,\n        f\"test_qpgurabs_{solver}\",\n        TestSolveProblem.get_test_qpgurabs(solver),\n    )\n    if solver != \"qtqp\":  # QTQP requires at least one inequality constraint\n        setattr(\n            TestSolveProblem,\n            f\"test_qpgureq_{solver}\",\n            TestSolveProblem.get_test_qpgureq(solver),\n        )\n"
  },
  {
    "path": "tests/test_solve_qp.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for the `solve_qp` function.\"\"\"\n\nimport unittest\nimport warnings\n\nimport numpy as np\nimport scipy\nfrom numpy import array, dot, ones, random\nfrom numpy.linalg import norm\nfrom scipy.sparse import csc_matrix\n\nfrom qpsolvers import (\n    NoSolverSelected,\n    ProblemError,\n    SolverNotFound,\n    available_solvers,\n    solve_qp,\n    sparse_solvers,\n)\n\nfrom .problems import get_qpmad_demo_problem\n\n# Raising a ValueError when the problem is unbounded below is desired but not\n# achieved by some solvers. Here are the behaviors observed as of March 2022.\n# Unit tests only cover solvers that raise successfully:\nbehavior_on_unbounded = {\n    \"raise\": [\"cvxopt\", \"kvxopt\", \"ecos\", \"quadprog\", \"scs\"],\n    \"return_crazy_solution\": [\"qpoases\"],\n    \"return_none\": [\"osqp\"],\n}\n\n\nclass TestSolveQP(unittest.TestCase):\n    \"\"\"Test fixture for a variety of quadratic programs.\n\n    Solver-specific tests are implemented in static methods called\n    ``get_test_{foo}`` that return the test function for a given solver. The\n    corresponding test function ``test_{foo}_{solver}`` is then added to the\n    fixture below the class definition.\n    \"\"\"\n\n    def setUp(self):\n        \"\"\"Prepare test fixture.\"\"\"\n        warnings.simplefilter(\"ignore\", category=DeprecationWarning)\n        warnings.simplefilter(\"ignore\", category=UserWarning)\n\n    def get_dense_problem(self):\n        \"\"\"Get dense problem as a sextuple of values to unpack.\n\n        Returns\n        -------\n        P : numpy.ndarray\n            Symmetric cost matrix .\n        q : numpy.ndarray\n            Cost vector.\n        G : numpy.ndarray\n            Linear inequality matrix.\n        h : numpy.ndarray\n            Linear inequality vector.\n        A : numpy.ndarray, scipy.sparse.csc_matrix or cvxopt.spmatrix\n            Linear equality matrix.\n        b : numpy.ndarray\n            Linear equality vector.\n        \"\"\"\n        M = array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n        P = dot(M.T, M)  # this is a positive definite matrix\n        q = dot(array([3.0, 2.0, 3.0]), M).reshape((3,))\n        G = array([[1.0, 2.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = array([3.0, 2.0, -2.0]).reshape((3,))\n        A = array([1.0, 1.0, 1.0])\n        b = array([1.0])\n        return P, q, G, h, A, b\n\n    def get_sparse_problem(self):\n        \"\"\"Get sparse problem as a quadruplet of values to unpack.\n\n        Returns\n        -------\n        P : scipy.sparse.csc_matrix\n            Symmetric cost matrix.\n        q : numpy.ndarray\n            Cost vector.\n        G : scipy.sparse.csc_matrix\n            Linear inequality matrix.\n        h : numpy.ndarray\n            Linear inequality vector.\n        \"\"\"\n        n = 150\n        M = scipy.sparse.lil_matrix(scipy.sparse.eye(n))\n        for i in range(1, n - 1):\n            M[i, i + 1] = -1\n            M[i, i - 1] = 1\n        P = csc_matrix(M.dot(M.transpose()))\n        q = -ones((n,))\n        G = csc_matrix(-scipy.sparse.eye(n))\n        h = -2.0 * ones((n,))\n        return P, q, G, h\n\n    def test_no_solver_selected(self):\n        \"\"\"Check that NoSolverSelected is raised when applicable.\"\"\"\n        P, q, G, h, A, b = self.get_dense_problem()\n        with self.assertRaises(NoSolverSelected):\n            solve_qp(P, q, G, h, A, b, solver=None)\n\n    def test_solver_not_found(self):\n        \"\"\"SolverNotFound is raised when the solver does not exist.\"\"\"\n        P, q, G, h, A, b = self.get_dense_problem()\n        with self.assertRaises(SolverNotFound):\n            solve_qp(P, q, G, h, A, b, solver=\"ideal\")\n\n    @staticmethod\n    def get_test(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            x = solve_qp(P, q, G, h, A, b, solver=solver)\n            x_sp = solve_qp(P, q, G, h, A, b, solver=solver)\n            self.assertIsNotNone(x, f\"{solver=}\")\n            self.assertIsNotNone(x_sp, f\"{solver=}\")\n            known_solution = array([0.30769231, -0.69230769, 1.38461538])\n            sol_tolerance = (\n                5e-4\n                if solver in [\"osqp\", \"qpalm\", \"scs\"]\n                else (\n                    1e-4\n                    if solver in [\"ecos\", \"jaxopt_osqp\"]\n                    else 5e-6 if solver in [\"proxqp\", \"qpax\", \"sip\"] else 1e-8\n                )\n            )\n            eq_tolerance = (\n                1e-5 if solver in [\"jaxopt_osqp\", \"sip\"]\n                else 1e-7 if solver in [\"qpax\"] else 1e-10\n            )\n            ineq_tolerance = (\n                2e-4\n                if solver in [\"jaxopt_osqp\", \"qpalm\", \"scs\"]\n                else 5e-6 if solver in [\"proxqp\", \"qpax\"] else 1e-10\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(\n                norm(x_sp - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(G, x) - h), ineq_tolerance, f\"{solver=}\")\n            self.assertLess(max(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_all_shapes(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant tries all possible shapes for matrix and vector\n        parameters.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n\n        Note\n        ----\n        This function uses DAQP to find groundtruth solutions.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, _, _ = self.get_dense_problem()\n            A = array([[1.0, 0.0, 0.0], [0.0, 0.4, 0.5]])\n            b = array([-0.5, -1.2])\n            lb = array([-0.5, -2, -0.8])\n            ub = array([+1.0, +1.0, +1.0])\n\n            ineq_variants = ((None, None), (G, h), (G[0], array([h[0]])))\n            eq_variants = ((None, None), (A, b), (A[0], array([b[0]])))\n            box_variants = ((None, None), (lb, None), (None, ub), (lb, ub))\n            cases = [\n                {\n                    \"P\": P,\n                    \"q\": q,\n                    \"G\": G_case,\n                    \"h\": h_case,\n                    \"A\": A_case,\n                    \"b\": b_case,\n                    \"lb\": lb_case,\n                    \"ub\": ub_case,\n                }\n                for (G_case, h_case) in ineq_variants\n                for (A_case, b_case) in eq_variants\n                for (lb_case, ub_case) in box_variants\n            ]\n\n            for i, test_case in enumerate(cases):\n                no_inequality = \"G\" not in test_case or test_case[\"G\"] is None\n                if no_inequality and solver in [\"qpswift\", \"qpax\"]:\n                    # QPs without inequality constraints are not handled by\n                    # qpSWIFT or qpax\n                    continue\n                no_equality = \"A\" not in test_case or test_case[\"A\"] is None\n                if no_equality and solver in [\"qpax\"]:\n                    # QPs without equality constraints not handled by qpax\n                    continue\n                no_box = (\n                    test_case.get(\"lb\") is None and test_case.get(\"ub\") is None\n                )\n                if (\n                    no_inequality\n                    and no_box\n                    and not no_equality\n                    and solver == \"qtqp\"\n                ):\n                    # QTQP requires at least one inequality constraint, so\n                    # equality-only problems (no G, no box bounds) are not\n                    # supported\n                    continue\n                has_one_equality = (\n                    \"A\" in test_case\n                    and test_case[\"A\"] is not None\n                    and test_case[\"A\"].ndim == 1\n                )\n                has_lower_box = (\n                    \"lb\" in test_case and test_case[\"lb\"] is not None\n                )\n                if has_one_equality and has_lower_box and solver == \"hpipm\":\n                    # Skipping this test for HPIPM for now\n                    # See https://github.com/giaf/hpipm/issues/136\n                    continue\n                test_comp = {\n                    k: v.shape if v is not None else \"None\"\n                    for k, v in test_case.items()\n                }\n                daqp_solution = solve_qp(solver=\"daqp\", **test_case)\n                self.assertIsNotNone(\n                    daqp_solution,\n                    f\"Baseline failed on parameters: {test_comp}\",\n                )\n                solver_solution = solve_qp(solver=solver, **test_case)\n                sol_tolerance = (\n                    2e-2\n                    if solver == \"proxqp\"\n                    else (\n                        5e-3\n                        if solver in [\"jaxopt_osqp\"]\n                        else (\n                            2e-3\n                            if solver in [\"osqp\", \"qpalm\", \"scs\"]\n                            else 5e-4 if solver == \"ecos\" else 2e-4\n                        )\n                    )\n                )\n                self.assertLess(\n                    norm(solver_solution - daqp_solution),\n                    sol_tolerance,\n                    f\"Solver failed on parameters: {test_comp}\",\n                )\n\n        return test\n\n    @staticmethod\n    def get_test_bounds(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant adds vector bounds.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            lb = array([-1.0, -2.0, -0.5])\n            ub = array([1.0, -0.2, 1.0])\n            x = solve_qp(P, q, G, h, A, b, lb, ub, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([0.41463415, -0.41463415, 1.0])\n            sol_tolerance = (\n                2e-3\n                if solver == \"proxqp\"\n                else (\n                    5e-3\n                    if solver in [\"jaxopt_osqp\", \"osqp\"]\n                    else (\n                        5e-5\n                        if solver == \"scs\"\n                        else (\n                            1e-6\n                            if solver in [\"qpalm\", \"ecos\", \"qpax\", \"sip\"]\n                            else 1e-8\n                        )\n                    )\n                )\n            )\n            eq_tolerance = (\n                1e-5 if solver in [\"proxqp\", \"sip\"]\n                else 1e-7 if solver in [\"qpax\"] else 1e-10\n            )\n            ineq_tolerance = 1e-5 if solver == \"proxqp\" else 1e-10\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(G, x) - h), ineq_tolerance, f\"{solver=}\")\n            self.assertLess(max(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_no_cons(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        In this variant, there is no equality nor inequality constraint.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            x = solve_qp(P, q, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([-0.64705882, -1.17647059, -1.82352941])\n            sol_tolerance = (\n                1e-3\n                if solver == \"ecos\"\n                else 1e-5 if solver in [\"osqp\", \"qpalm\"] else 1e-6\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n\n        return test\n\n    @staticmethod\n    def get_test_no_eq(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        In this variant, there is no equality constraint.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            x = solve_qp(P, q, G, h, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([-0.49025721, -1.57755261, -0.66484801])\n            sol_tolerance = (\n                1e-3\n                if solver in [\"ecos\", \"jaxopt_osqp\"]\n                else (\n                    1e-4\n                    if solver in [\"qpalm\", \"sip\"]\n                    else 1e-5 if solver == \"osqp\" else 1e-6\n                )\n            )\n            ineq_tolerance = (\n                1e-3\n                if solver == \"jaxopt_osqp\"\n                else (\n                    1e-5\n                    if solver == \"osqp\"\n                    else (\n                        1e-6\n                        if solver == \"proxqp\"\n                        else 1e-7 if solver == \"scs\" else 1e-10\n                    )\n                )\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(G, x) - h), ineq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_no_ineq(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        In this variant, there is no inequality constraint.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            x = solve_qp(P, q, A=A, b=b, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([0.28026906, -1.55156951, 2.27130045])\n            sol_tolerance = (\n                1e-3\n                if solver in [\"jaxopt_osqp\", \"osqp\", \"qpalm\"]\n                else (\n                    1e-5\n                    if solver in [\"ecos\", \"scs\", \"qpax\"]\n                    else (\n                        1e-6\n                        if solver == \"highs\"\n                        else 1e-7 if solver == \"proxqp\" else 1e-8\n                    )\n                )\n            )\n            eq_tolerance = (\n                1e-5\n                if solver == \"jaxopt_osqp\"\n                else (\n                    1e-6\n                    if solver in [\"qpax\", \"sip\"]\n                    else 1e-7 if solver == \"osqp\" else 1e-9\n                )\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_one_ineq(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        In this variant, there is only one inequality constraint.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            G, h = G[1], h[1].reshape((1,))\n            x = solve_qp(P, q, G, h, A, b, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([0.30769231, -0.69230769, 1.38461538])\n            sol_tolerance = (\n                1e-3\n                if solver in [\"jaxopt_osqp\", \"qpalm\"]\n                else (\n                    5e-4\n                    if solver == \"osqp\"\n                    else (\n                        1e-5\n                        if solver == \"scs\"\n                        else (\n                            5e-6\n                            if solver in [\"proxqp\", \"sip\"]\n                            else (\n                                1e-6\n                                if solver\n                                in [\"copt\", \"cvxopt\", \"kvxopt\", \"ecos\", \"qpax\"]\n                                else 5e-8 if solver == \"qpswift\" else 1e-8\n                            )\n                        )\n                    )\n                )\n            )\n            eq_tolerance = (\n                1e-3\n                if solver in [\"jaxopt_osqp\"]\n                else (\n                    1e-4\n                    if solver in [\"qpalm\", \"qpax\", \"sip\"]\n                    else 1e-8 if solver in [\"osqp\", \"qtqp\", \"scs\"] else 1e-10\n                )\n            )\n            ineq_tolerance = (\n                1e-5\n                if solver == \"proxqp\"\n                else (\n                    1e-6\n                    if solver == \"qpax\"\n                    else (1e-7 if solver in [\"qpswift\", \"scs\"] else 1e-8)\n                )\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(G, x) - h), ineq_tolerance, f\"{solver=}\")\n            self.assertLess(max(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_sparse(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant tests a sparse problem.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h = self.get_sparse_problem()\n            kwargs = {}\n            tol_solvers = (\"osqp\", \"proxqp\", \"qpalm\", \"scs\")\n            if solver in tol_solvers:\n                kwargs[\"eps_abs\"] = 2e-4\n            x = solve_qp(P, q, G, h, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([2.0] * 149 + [3.0])\n            sol_tolerance = (\n                5e-3\n                if solver == \"cvxopt\" or solver == \"kvxopt\"\n                else (\n                    2e-3\n                    if solver in [\"osqp\", \"qpalm\", \"qpax\", \"qtqp\"]\n                    else (\n                        1e-3\n                        if solver in [\"gurobi\", \"piqp\"]\n                        else (\n                            5e-4\n                            if solver in [\"clarabel\", \"mosek\"]\n                            else (\n                                1e-4\n                                if solver in [\"scs\", \"sip\"]\n                                else (\n                                    2e-5\n                                    if solver in [\"copt\", \"proxqp\"]\n                                    else 1e-6 if solver == \"highs\" else 1e-7\n                                )\n                            )\n                        )\n                    )\n                )\n            )\n            ineq_tolerance = 1e-4 if solver in tol_solvers else 1e-7\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(G * x - h), ineq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_sparse_bounds(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant tests a sparse problem with additional vector lower and\n        upper bounds.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h = self.get_sparse_problem()\n            lb = +2.2 * ones(q.shape)\n            ub = +2.4 * ones(q.shape)\n            x = solve_qp(P, q, G, h, lb=lb, ub=ub, solver=solver)\n            self.assertIsNotNone(x)\n            known_solution = array([2.2] * 149 + [2.4])\n            sol_tolerance = (\n                1e-3\n                if solver in [\"gurobi\", \"copt\", \"osqp\", \"qpalm\", \"sip\"]\n                else (\n                    5e-6\n                    if solver in [\"mosek\", \"proxqp\"]\n                    else (\n                        1e-7 if solver in [\"cvxopt\", \"kvxopt\", \"scs\"] else 1e-8\n                    )\n                )\n            )\n            ineq_tolerance = 1e-10\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(G * x - h), ineq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_sparse_unfeasible(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant tests an unfeasible sparse problem with additional vector\n        lower and upper bounds.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h = self.get_sparse_problem()\n            lb = +0.5 * ones(q.shape)\n            ub = +1.5 * ones(q.shape)\n            if solver == \"cvxopt\" or solver == \"kvxopt\":\n                # Skipping this test for CVXOPT and KVXOPT for now\n                # See https://github.com/cvxopt/cvxopt/issues/229\n                return\n            x = solve_qp(P, q, G, h, lb=lb, ub=ub, solver=solver)\n            self.assertIsNone(x)\n\n        return test\n\n    @staticmethod\n    def get_test_warmstart(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        This variant warm starts.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_dense_problem()\n            known_solution = array([0.30769231, -0.69230769, 1.38461538])\n            initvals = known_solution + 0.1 * random.random(3)\n            x = solve_qp(\n                P,\n                q,\n                G,\n                h,\n                A,\n                b,\n                solver=solver,\n                initvals=initvals,\n                verbose=True,  # increases coverage\n            )\n            self.assertIsNotNone(x)\n            sol_tolerance = (\n                1e-3\n                if solver in [\"osqp\", \"qpalm\", \"scs\"]\n                else (\n                    1e-4\n                    if solver in [\"ecos\", \"jaxopt_osqp\"]\n                    else 5e-6 if solver in [\"proxqp\", \"qpax\", \"sip\"] else 1e-8\n                )\n            )\n            eq_tolerance = (\n                1e-4 if solver in [\"jaxopt_osqp\", \"osqp\", \"sip\"] else\n                1e-7 if solver == \"qpax\" else 1e-10\n            )\n            ineq_tolerance = (\n                1e-3\n                if solver in [\"osqp\", \"qpalm\", \"scs\"]\n                else (\n                    1e-5\n                    if solver in [\"jaxopt_osqp\", \"proxqp\", \"qpax\"]\n                    else 1e-10\n                )\n            )\n            self.assertLess(\n                norm(x - known_solution), sol_tolerance, f\"{solver=}\"\n            )\n            self.assertLess(max(dot(G, x) - h), ineq_tolerance, f\"{solver=}\")\n            self.assertLess(max(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n            self.assertLess(min(dot(A, x) - b), eq_tolerance, f\"{solver=}\")\n\n        return test\n\n    @staticmethod\n    def get_test_raise_on_unbounded_below(solver: str):\n        \"\"\"ValueError is raised when the problem is unbounded below.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n\n        Notes\n        -----\n        Detecting non-convexity is not a trivial problem and most solvers leave\n        it to the user. See for instance the `recommendation from OSQP\n        <https://osqp.org/docs/interfaces/status_values.html#status-values>`_.\n        We only run this test for functions that successfully detect unbounded\n        problems when the eigenvalues of :math:`P` are close to zero.\n        \"\"\"\n\n        def test(self):\n            v = array([5.4, -1.2, -1e-2, 1e4])\n            P = dot(v.reshape(4, 1), v.reshape(1, 4))\n            q = array([-1.0, -2, 0, 3e-4])\n            # q is in the nullspace of P, so the problem is unbounded below\n            with self.assertRaises(ProblemError):\n                solve_qp(P, q, solver=solver)\n\n        return test\n\n    @staticmethod\n    def get_test_qpmad_demo(solver: str):\n        \"\"\"Get test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            problem = get_qpmad_demo_problem()\n            P, q, G, h, _, _, lb, ub = problem.unpack()\n            x = solve_qp(P, q, G, h, lb=lb, ub=ub, solver=solver)\n            known_solution = array(\n                [\n                    1.0,\n                    2.0,\n                    3.0,\n                    4.0,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                    -0.71875,\n                ]\n            )\n            sol_tolerance = (\n                1e-2\n                if solver in [\"jaxopt_osqp\", \"osqp\"]\n                else (\n                    5e-4\n                    if solver in [\"qpalm\", \"scs\", \"qpax\"]\n                    else (\n                        2e-5\n                        if solver == \"proxqp\"\n                        else (\n                            1e-6\n                            if solver\n                            in [\n                                \"cvxopt\",\n                                \"kvxopt\",\n                                \"mosek\",\n                                \"piqp\",\n                                \"qpswift\",\n                                \"qtqp\",\n                                \"sip\",\n                            ]\n                            else 1e-8\n                        )\n                    )\n                )\n            )\n            self.assertIsNotNone(x)\n            self.assertLess(np.linalg.norm(x - known_solution), sol_tolerance)\n\n        return test\n\n\n# Generate test fixtures for each solver\nfor solver in available_solvers:\n    setattr(TestSolveQP, f\"test_{solver}\", TestSolveQP.get_test(solver))\n    setattr(\n        TestSolveQP,\n        f\"test_all_shapes_{solver}\",\n        TestSolveQP.get_test_all_shapes(solver),\n    )\n    setattr(\n        TestSolveQP,\n        f\"test_bounds_{solver}\",\n        TestSolveQP.get_test_bounds(solver),\n    )\n    if solver not in [\"qpswift\"]:\n        # qpSWIFT: https://github.com/qpSWIFT/qpSWIFT/issues/2\n        setattr(\n            TestSolveQP,\n            f\"test_no_cons_{solver}\",\n            TestSolveQP.get_test_no_cons(solver),\n        )\n    setattr(\n        TestSolveQP,\n        f\"test_no_eq_{solver}\",\n        TestSolveQP.get_test_no_eq(solver),\n    )\n    if solver not in [\"qpswift\", \"qtqp\"]:\n        # qpSWIFT: https://github.com/qpSWIFT/qpSWIFT/issues/2\n        # QTQP: requires at least one inequality constraint\n        setattr(\n            TestSolveQP,\n            f\"test_no_ineq_{solver}\",\n            TestSolveQP.get_test_no_ineq(solver),\n        )\n    setattr(\n        TestSolveQP,\n        f\"test_one_ineq_{solver}\",\n        TestSolveQP.get_test_one_ineq(solver),\n    )\n    if solver in sparse_solvers:\n        setattr(\n            TestSolveQP,\n            f\"test_sparse_{solver}\",\n            TestSolveQP.get_test_sparse(solver),\n        )\n        setattr(\n            TestSolveQP,\n            f\"test_sparse_bounds_{solver}\",\n            TestSolveQP.get_test_sparse_bounds(solver),\n        )\n        setattr(\n            TestSolveQP,\n            f\"test_sparse_unfeasible_{solver}\",\n            TestSolveQP.get_test_sparse_unfeasible(solver),\n        )\n    setattr(\n        TestSolveQP,\n        f\"test_warmstart_{solver}\",\n        TestSolveQP.get_test_warmstart(solver),\n    )\n    if solver in behavior_on_unbounded[\"raise\"]:\n        setattr(\n            TestSolveQP,\n            f\"test_raise_on_unbounded_below_{solver}\",\n            TestSolveQP.get_test_raise_on_unbounded_below(solver),\n        )\n    setattr(\n        TestSolveQP,\n        f\"test_qpmad_demo_{solver}\",\n        TestSolveQP.get_test_qpmad_demo(solver),\n    )\n"
  },
  {
    "path": "tests/test_timings.py",
    "content": "import unittest\nimport warnings\nfrom qpsolvers import available_solvers, solve_problem\nfrom .problems import get_qpmad_demo_problem\n\nclass TestTimings(unittest.TestCase):\n    def test_timings_recorded(self):\n        problem = get_qpmad_demo_problem()\n        \n        for solver in available_solvers:\n            with self.subTest(solver=solver):\n                try:\n                    with warnings.catch_warnings():\n                        warnings.simplefilter(\"ignore\")\n                        solution = solve_problem(problem, solver=solver)\n                    \n                    self.assertTrue(solution.found, f\"Solver {solver} did not find a solution\")\n                    self.assertIsNotNone(solution.build_time)\n                    self.assertIsNotNone(solution.solve_time)\n                    self.assertGreaterEqual(solution.build_time, 0.0)\n                    self.assertGreaterEqual(solution.solve_time, 0.0)\n                    \n                    print(f\"[{solver}] build_time: {solution.build_time:.6f}s, solve_time: {solution.solve_time:.6f}s\")\n                except Exception as e:\n                    print(f\"[{solver}] Failed to solve: {e}\")\n\nif __name__ == \"__main__\":\n    unittest.main()\n"
  },
  {
    "path": "tests/test_unfeasible_problem.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests with unfeasible problems.\"\"\"\n\nimport unittest\nimport warnings\n\nfrom numpy import array, dot\nfrom qpsolvers import available_solvers, solve_qp\n\n\nclass UnfeasibleProblem(unittest.TestCase):\n    \"\"\"\n    Test fixture for an unfeasible quadratic program (inequality and equality\n    constraints are inconsistent).\n    \"\"\"\n\n    def setUp(self):\n        \"\"\"\n        Prepare test fixture.\n        \"\"\"\n        warnings.simplefilter(\"ignore\", category=UserWarning)\n\n    def get_unfeasible_problem(self):\n        \"\"\"\n        Get problem as a sextuple of values to unpack.\n\n        Returns\n        -------\n        P :\n            Symmetric cost matrix.\n        q :\n            Cost vector.\n        G :\n            Linear inequality matrix.\n        h :\n            Linear inequality vector.\n        A :\n            Linear equality matrix.\n        b :\n            Linear equality vector.\n        \"\"\"\n        M = array([[1.0, 2.0, 0.0], [-8.0, 3.0, 2.0], [0.0, 1.0, 1.0]])\n        P = dot(M.T, M)  # this is a positive definite matrix\n        q = dot(array([3.0, 2.0, 3.0]), M).reshape((3,))\n        G = array([[1.0, 1.0, 1.0], [2.0, 0.0, 1.0], [-1.0, 2.0, -1.0]])\n        h = array([3.0, 2.0, -2.0]).reshape((3,))\n        A = array([1.0, 1.0, 1.0])\n        b = array([42.0])\n        return P, q, G, h, A, b\n\n    @staticmethod\n    def get_test(solver: str):\n        \"\"\"\n        Closure of test function for a given solver.\n\n        Parameters\n        ----------\n        solver :\n            Name of the solver to test.\n\n        Returns\n        -------\n        :\n            Test function for that solver.\n        \"\"\"\n\n        def test(self):\n            P, q, G, h, A, b = self.get_unfeasible_problem()\n            x = solve_qp(P, q, G, h, A, b, solver=solver)\n            self.assertIsNone(x)\n\n        return test\n\n\n# Generate test fixtures for each solver\nfor solver in available_solvers:\n    if solver == \"qpoases\":\n        # Unfortunately qpOASES returns an invalid solution in the face of this\n        # problem being unfeasible. Skipping it.\n        continue\n    setattr(\n        UnfeasibleProblem,\n        \"test_{}\".format(solver),\n        UnfeasibleProblem.get_test(solver),\n    )\n"
  },
  {
    "path": "tests/test_utils.py",
    "content": "#!/usr/bin/env python\n# -*- coding: utf-8 -*-\n#\n# SPDX-License-Identifier: LGPL-3.0-or-later\n# Copyright 2016-2022 Stéphane Caron and the qpsolvers contributors\n\n\"\"\"Unit tests for utility functions.\"\"\"\n\nimport io\nimport sys\nimport unittest\n\nimport numpy as np\n\nfrom qpsolvers.utils import print_matrix_vector\n\n\nclass TestUtils(unittest.TestCase):\n    \"\"\"Test fixture for utility functions.\"\"\"\n\n    def setUp(self):\n        self.G = np.array([[1.3, 2.1], [2.6, 0.3], [2.2, -1.6]])\n        self.h = np.array([3.4, 1.8, -2.7]).reshape((3,))\n\n    def test_print_matrix_vector(self):\n        \"\"\"Printing a matrix-vector pair outputs the proper labels.\"\"\"\n        def run_test(G, h):\n            stdout_capture = io.StringIO()\n            sys.stdout = stdout_capture\n            print_matrix_vector(G, \"ineq_matrix\", h, \"ineq_vector\")\n            sys.stdout = sys.__stdout__\n            output = stdout_capture.getvalue()\n            self.assertIn(\"ineq_matrix =\", output)\n            self.assertIn(str(G[0][1]), output)\n            self.assertIn(\"ineq_vector =\", output)\n            self.assertIn(str(h[1]), output)\n\n        run_test(self.G, self.h)\n        run_test(self.G, self.h[:-1])\n        run_test(self.G[:-1], self.h)\n"
  }
]