Developer notes

Adding a new solver

Let’s imagine we want to add a new solver called AwesomeQP. The solver keyword, the string passed via the solver keyword argument, is the lowercase version of the vernacular name of a QP solver. For our imaginary solver, the keyword is therefore "awesomeqp".

The process to add AwesomeQP to qpsolvers goes as follows:

  1. Create a new file qpsolvers/solvers/awesomeqp_.py (named after the solver keyword, with a trailing underscore)

  2. Implement in this file a function awesomeqp_solve_problem that returns a Solution

  3. Implement in the same file a function awesomeqp_solve_qp to connect it to the historical API, typically as follows:

def awesomeqp_solve_qp(P, q, G, h, A, b, lb, ub, initvals=None, verbose=False, **kwargs):
) -> Optional[np.ndarray]:
    r"""Solve a quadratic program using AwesomeQP.

    [document parameters and return values here]
    """
    problem = Problem(P, q, G, h, A, b, lb, ub)
    solution = awesomeqp_solve_problem(
        problem, initvals, verbose, backend, **kwargs
    )
    return solution.x if solution.found else None
  1. Define the two function prototypes for awesomeqp_solve_problem and awesomeqp_solve_qp in qpsolvers/solvers/__init__.py:

# AwesomeQP
# ========

awesome_solve_qp: Optional[
    Callable[
        [
            ndarray,
            ndarray,
            Optional[ndarray],
            Optional[ndarray],
            Optional[ndarray],
            Optional[ndarray],
            Optional[ndarray],
            Optional[str],
            bool,
        ],
        Optional[ndarray],
    ]
] = None

Note

The prototype needs to match the actual function. You can check its correctness by running tox -e py in the repository.

  1. Below the prototype, import the function into the solve_function dictionary:

try:
    from .awesomeqp_ import awesomeqp_solve_qp

    solve_function["awesomeqp"] = awesomeqp_solve_qp
    available_solvers.append("awesomeqp")
    # dense_solvers.append("awesomeqp")   if applicable
    # sparse_solvers.append("awesomeqp")  if applicable
except ImportError:
    pass
  1. Append the solver identifier to dense_solvers or sparse_solvers, if applicable

  2. Import awesomeqp_solve_qp from qpsolvers/__init__.py and add it to __all__

  3. Add the solver to doc/src/supported-solvers.rst

  4. Add the solver to the Solvers section of the README

  5. Assuming AwesomeQP is distributed on PyPI, add it to the [testenv] and [testenv:coverage] environments of tox.ini for unit testing

  6. Assuming AwesomeQP is distributed on Conda or PyPI, add it to the list of dependencies in doc/environment.yml

  7. Log the new solver as an addition in the changelog

  8. If you are a new contributor, feel free to add your name to CITATION.cff.

Problem conversions

Convert problems from and to standard QP form.

qpsolvers.conversions.combine_linear_box_inequalities(G, h, lb, ub, n, use_csc)

Combine linear and box inequalities into double-sided linear format.

Input format:

\[\begin{split}\begin{split}\begin{array}{ll} G x & \leq h \\ lb & \leq x \leq ub \end{array}\end{split}\end{split}\]

Output format:

\[l \leq C \leq u\]
Parameters:
  • G – Linear inequality constraint matrix. Must be two-dimensional.

  • h – Linear inequality constraint vector.

  • lb – Lower bound constraint vector.

  • ub – Upper bound constraint vector.

  • n (int) – Number of optimization variables.

  • use_csc (bool) – If True, use sparse rather than dense matrices.

Returns:

Linear inequality matrix \(C\) and vectors \(u\), \(l\). The two vector will contain \(\pm\infty\) values on coordinates where there is no corresponding constraint.

Raises:

ProblemError – If the inequality matrix and vector are not consistent.

qpsolvers.conversions.ensure_sparse_matrices(P, G, A)

Make sure problem matrices are sparse.

Parameters:
  • P (Union[ndarray, csc_matrix]) – Cost matrix.

  • G (Union[ndarray, csc_matrix, None]) – Inequality constraint matrix, if any.

  • A (Union[ndarray, csc_matrix, None]) – Equality constraint matrix, if any.

Return type:

Tuple[csc_matrix, Optional[csc_matrix], Optional[csc_matrix]]

Returns:

Tuple of all three matrices as sparse matrices.

qpsolvers.conversions.linear_from_box_inequalities(G, h, lb, ub, use_sparse)

Append lower or upper bound vectors to inequality constraints.

Parameters:
  • G (Union[ndarray, csc_matrix, None]) – Linear inequality matrix.

  • h (Optional[ndarray]) – Linear inequality vector.

  • lb (Optional[ndarray]) – Lower bound constraint vector.

  • ub (Optional[ndarray]) – Upper bound constraint vector.

  • use_sparse (bool) – Use sparse matrices if true, dense matrices otherwise.

Return type:

Tuple[Union[ndarray, csc_matrix, None], Optional[ndarray]]

Returns:

  • G (np.ndarray, spa.csc_matrix or None) – Updated linear inequality matrix.

  • h (np.ndarray or None) – Updated linear inequality vector.

qpsolvers.conversions.socp_from_qp(P, q, G, h)

Convert a quadratic program to a second-order cone program.

The quadratic program is defined by:

\[\begin{split}\begin{split}\begin{array}{ll} \underset{x}{\mbox{minimize}} & \frac{1}{2} x^T P x + q^T x \\ \mbox{subject to} & G x \leq h \end{array}\end{split}\end{split}\]

The equivalent second-order cone program is:

\[\begin{split}\begin{split}\begin{array}{ll} \underset{x}{\mbox{minimize}} & c^T_s y \\ \mbox{subject to} & G_s y \leq_{\cal K} h_s \end{array}\end{split}\end{split}\]

This function is adapted from ecosqp.m in the ecos-matlab repository. See the documentation in that script for details on this reformulation.

Parameters:
  • P (ndarray) – Primal quadratic cost matrix.

  • q (ndarray) – Primal quadratic cost vector.

  • G (Optional[ndarray]) – Linear inequality constraint matrix.

  • h (Optional[ndarray]) – Linear inequality constraint vector.

Return type:

Tuple[ndarray, ndarray, ndarray, Dict[str, Any]]

Returns:

  • c_socp (array) – SOCP cost vector.

  • G_socp (array) – SOCP inequality matrix.

  • h_socp (array) – SOCP inequality vector.

  • dims (dict) – Dimension dictionary used by SOCP solvers, where dims["l"] is the number of inequality constraints.

Raises:

ValueError : – If the cost matrix is not positive definite.

qpsolvers.conversions.split_dual_linear_box(z_stacked, lb, ub)

Separate linear and box multipliers from a stacked dual vector.

This function assumes linear and box inequalities were combined using qpsolvers.conversions.linear_from_box_inequalities().

Parameters:
  • z_stacked (ndarray) – Stacked vector of dual multipliers.

  • lb (Optional[ndarray]) – Lower bound constraint vector.

  • ub (Optional[ndarray]) – Upper bound constraint vector.

Return type:

Tuple[Optional[ndarray], Optional[ndarray]]

Returns:

Pair z, z_box of linear and box multipliers. Both can be empty arrays if there is no corresponding constraint.

Testing locally

To run all CI checks locally, go to the repository folder and run

tox -e py

This will run linters and unit tests.