Differentiable QP solver in JAX.
Project description
qpax
Differentiable QP solver in JAX.
This package can be used for solving convex quadratic programs of the following form:
$$ \begin{align*} \underset{x}{\text{minimize}} & \quad \frac{1}{2}x^TQx + q^Tx \ \text{subject to} & \quad Ax = b, \ & \quad Gx \leq h \end{align*} $$
where $Q \succeq 0$. This solver can be combined with JAX's jit
and vmap
functionality, as well as differentiated with reverse-mode grad
.
The QP is solved with a primal-dual interior point algorithm detailed in cvxgen, with the solution to the linear systems computed with reduction techniques from cvxopt. At an approximate primal-dual solution, the the primal variable $x$ is differentiated with respect to the problem parameters using the implicit function theorem as shown in optnet, and their pytorch-based qp solver qpth.
Installation
To install directly from github using pip
:
$ pip install qpax
Alternatively, to install from source in editable mode:
$ pip install -e .
Usage
Solving a QP
We can solve QPs with qpax in a way that plays nice with JAX's jit
and vmap
:
import qpax
# solve QP (this can be combined with jit or vmap)
x, s, z, y, converged, iters = qpax.solve_qp(Q, q, A, b, G, h)
Solving a batch of QP's
Here let's solve a batch of nonnegative least squares problems as QPs. This outlines two bits of functionality from qpax
, first is the ability to solve QPs without any equality constraints, and second is the ability to vmap
over a batch of QPs.
import numpy as np
import jax
import jax.numpy as jnp
from jax import jit, grad, vmap
import qpax
import timeit
"""
solve batched non-negative least squares (nnls) problems
min_x |Fx - g|^2
st x >= 0
"""
n = 5 # size of x
m = 10 # rows in F
# create data for N_qps random nnls problems
N_qps = 10000
Fs = jnp.array(np.random.randn(N_qps, m, n))
gs = jnp.array(np.random.randn(N_qps, m))
@jit
def form_qp(F, g):
# convert the least squares to qp form
n = F.shape[1]
Q = F.T @ F
q = -F.T @ g
G = -jnp.eye(n)
h = jnp.zeros(n)
A = jnp.zeros((0, n))
b = jnp.zeros(0)
return Q, q, A, b, G, h
# create the QPs in a batched fashion
Qs, qs, As, bs, Gs, hs = vmap(form_qp, in_axes = (0, 0))(Fs, gs)
# create function for solving a batch of QPs
batch_qp = jit(vmap(qpax.solve_qp_primal, in_axes = (0, 0, 0, 0, 0, 0)))
xs = batch_qp(Qs, qs, As, bs, Gs, hs)
Differentiating a QP
Alternatively, if we are only looking to use the primal variable x
, we can use solve_qp_primal
which enables automatic differenation:
import jax
import jax.numpy as jnp
import qpax
def loss(Q, q, A, b, G, h):
x = qpax.solve_qp_primal(Q, q, A, b, G, h)
x_bar = jnp.ones(len(q))
return jnp.dot(x - x_bar, x - x_bar)
# gradient of loss function
loss_grad = jax.grad(loss, argnums = (0, 1, 2, 3, 4, 5))
# compatible with jit
loss_grad_jit = jax.jit(loss_grad)
# calculate derivatives
derivs = loss_grad_jit(Q, q, A, b, G, h)
dl_dQ, dl_dq, dl_dA, dl_db, dl_dG, dl_dh = derivs
Citation
@misc{tracy2024differentiability,
title={On the Differentiability of the Primal-Dual Interior-Point Method},
author={Kevin Tracy and Zachary Manchester},
year={2024},
eprint={2406.11749},
archivePrefix={arXiv},
primaryClass={math.OC}
}
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
File details
Details for the file qpax-0.0.9-py3-none-any.whl
.
File metadata
- Download URL: qpax-0.0.9-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.9.20
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 55d24b0ac30c95984a14f218ebdacddb434a94ea88eceb95a11f94e91fd0ce30 |
|
MD5 | c763c9ba19ed235b1b16638031994f75 |
|
BLAKE2b-256 | 4caa61e68bfbfdc7cfbe9a9760637d4a914188b85277cfa0208aa69b1c8a7b2b |