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:
Create a new file
qpsolvers/solvers/awesomeqp_.py
(named after the solver keyword, with a trailing underscore)Implement in this file a function
awesomeqp_solve_problem
that returns aSolution
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
Define the two function prototypes for
awesomeqp_solve_problem
andawesomeqp_solve_qp
inqpsolvers/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.
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
Append the solver identifier to
dense_solvers
orsparse_solvers
, if applicableImport
awesomeqp_solve_qp
fromqpsolvers/__init__.py
and add it to__all__
Add the solver to
doc/src/supported-solvers.rst
Add the solver to the Solvers section of the README
Assuming AwesomeQP is distributed on PyPI, add it to the
[testenv]
and[testenv:coverage]
environments oftox.ini
for unit testingAssuming AwesomeQP is distributed on Conda or PyPI, add it to the list of dependencies in
doc/environment.yml
Log the new solver as an addition in the changelog
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
) – IfTrue
, 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.