Solvers¶
All solver classes share the setup / solve / update workflow and use the same
Settings. See
Re-solving with new data for the
fixed-structure update pattern and Differentiation for
the backward() workflow.
They differ only in the accepted storage format for P, A, G and the KKT
factorization used. See Backends for guidance on choosing one.
DenseSolver¶
DenseSolver
¶
Bases: SolverBase
GPU solver for general dense convex quadratic programs that solves a QP - or a whole batch of QPs - of the form
using the proximal interior-point method, running entirely on the GPU.
Inputs. P, A, G and every vector (c, b, h_l,
h_u, x_l, x_u) must be dense arrays that live on the GPU.
Any object exposing the __cuda_array_interface__ protocol is
accepted - a cupy.ndarray, a CUDA torch.Tensor, a CUDA JAX
array, a Numba device array, and so on.
cuPIQP is GPU-only: CPU data (numpy.ndarray, CPU torch tensors,
CPU JAX arrays) is rejected with a TypeError rather than copied to
the device behind your back.
Batching. DenseSolver is natively batched: solve B
independent QPs in a single GPU call by giving every array a leading
batch dimension - P of shape (B, n, n), c of shape
(B, n), and so on. A single problem is simply B = 1, and
solver.result.x then has shape (B, n).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dtype
|
(float64, float32)
|
Floating-point precision used throughout the solve. |
"float64"
|
Examples:
A small inequality-constrained QP (one row is one-sided via -inf):
import cupy as cp
from cupiqp import DenseSolver
P = cp.eye(2)
c = cp.array([-1.0, -4.0])
G = cp.array([[1.0, 1.0]]) # constrain x1 + x2
h_l = cp.array([-cp.inf]) # no lower bound on the row
h_u = cp.array([1.0]) # x1 + x2 <= 1
solver = DenseSolver()
solver.setup(P=P, c=c, G=G, h_l=h_l, h_u=h_u)
solver.solve()
print(solver.result.info.status[0].name) # CUPIQP_SOLVED
x = solver.result.x.get()[0] # bring the solution to the host
See Also
SparseSolver: solver for general sparse problems.
MultistageSolver: structure-exploiting solver for multistage optimization (e.g. optimal-control) problems.
Notes
The problem structure - array shapes, which constraint blocks are
present, and which bounds are finite vs. +/-inf - is fixed by
setup and can only be set up once per instance. To re-solve with new
numerical values of the same structure (e.g. a moving target b in
receding-horizon control), call update and then solve again,
which reuses all GPU allocations; for a different structure, create a
new DenseSolver. Solver behaviour (tolerances, verbosity, iteration
cap, ...) is configured through solver.settings.
Source code in cupiqp/dense/dense_solver.py
setup
¶
setup(
P: CudaArray,
c: CudaArray,
A: Optional[CudaArray] = None,
b: Optional[CudaArray] = None,
G: Optional[CudaArray] = None,
h_u: Optional[CudaArray] = None,
h_l: Optional[CudaArray] = None,
x_u: Optional[CudaArray] = None,
x_l: Optional[CudaArray] = None,
) -> None
Bind the problem data and prepare the solver for solve().
Fixes the problem structure - array shapes, which constraint
blocks are present, the sparsity pattern (sparse backend), and the
finite/infinite pattern of the bounds - and allocates all GPU
buffers, the KKT system, and the preconditioner. Call this once
per solver instance, then call solve().
Pass a single problem (2D P) or a batch (3D P with a leading
batch axis, or a list of matrices); the batch size is inferred here
and sets the shape of the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
Quadratic cost, shape |
required |
c
|
GPU array
|
Linear cost, shape |
required |
A
|
GPU array
|
Equality constraints |
None
|
b
|
GPU array
|
Equality constraints |
None
|
G
|
GPU array
|
Two-sided inequalities |
None
|
h_l
|
GPU array
|
Two-sided inequalities |
None
|
h_u
|
GPU array
|
Two-sided inequalities |
None
|
x_l
|
GPU array
|
Element-wise box bounds |
None
|
x_u
|
GPU array
|
Element-wise box bounds |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
TypeError
|
If an input is not a GPU array of the kind this backend expects
(e.g. a CPU |
See Also
solve : run the solver after setup. update : change numerical data without a full re-setup.
Source code in cupiqp/dense/dense_solver.py
solve
¶
Solve the QP set up by setup() and return the solve status.
Runs the proximal interior-point iterations on the GPU. The full
solution (primal x, dual, and slack variables) and per-problem
diagnostics are written to solver.result; this method returns the
status for convenience.
Returns:
| Type | Description |
|---|---|
Status or list of Status
|
For a single problem, the |
Notes
Read the solution from solver.result after solving - e.g.
solver.result.x (shape (B, n)) and
solver.result.info.status. Set solver.settings.verbose = True
to print a per-iteration log. After setup() you may solve()
repeatedly, optionally calling update() in between to change the
numerical data.
Source code in cupiqp/solver.py
update
¶
update(
P: Optional[Any] = None,
c: Optional[Any] = None,
A: Optional[Any] = None,
b: Optional[Any] = None,
G: Optional[Any] = None,
h_u: Optional[Any] = None,
h_l: Optional[Any] = None,
x_u: Optional[Any] = None,
x_l: Optional[Any] = None,
check_validity: bool = False,
)
Change the numerical problem data, then solve() again.
The fast path for re-solving a problem of the same structure -
for example a moving target b or a re-linearized P in
receding-horizon control. It reuses every GPU allocation from
setup(), so only the values change; shapes, sparsity patterns,
and which blocks are present must stay the same (create a new solver
for a structural change). Bound values may change freely, including
which entries are +/-inf - a bound can flip between finite and
infinite without re-setup().
Any argument left as None keeps its current value. After
update(), call solve() to get the new solution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
New values for the corresponding problem block. |
None
|
c
|
GPU array
|
New values for the corresponding problem block. |
None
|
A
|
GPU array
|
New values for the corresponding problem block. |
None
|
b
|
GPU array
|
New values for the corresponding problem block. |
None
|
G
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
check_validity
|
bool
|
If |
False
|
Source code in cupiqp/solver.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
backward
¶
backward(
grad_x=None,
grad_y=None,
grad_z_u=None,
grad_z_l=None,
grad_z_bu=None,
grad_z_bl=None,
grad_s_u=None,
grad_s_l=None,
grad_s_bu=None,
grad_s_bl=None,
)
Compute gradients of an outer scalar :math:L w.r.t. problem
data, given upstream cotangents on the solution variables.
Orchestration (backend-agnostic):
- Pack the per-field cotangent kwargs into
self._grad_in(a pre-allocatedVariables); missing kwargs are treated as zeros. - Solve the adjoint KKT system via :meth:
_compute_adjoint, producing user-space adjoint vectors. - Scatter the four active-size lambda groups (
z_u, z_l, z_bu, z_bl) and the two active-size ineq result groups into full-m/ full-nbuffers (self._lam_z*_full,self._z*_full). Both the dG outer product and thedh_*/dx_*vector gradients consume these full-layout buffers. - Delegate to :meth:
_compute_data_gradientsfor backend- specific matrix-gradient assembly +Datasubclass construction.
Returns the backend's Data subclass populated with the
gradients in user space. Cotangents and returned gradients are
interpreted in user (un-scaled) space throughout — the
adjoint solve and scatter chain handles all preconditioner
bookkeeping internally.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If :meth: |
Source code in cupiqp/solver.py
1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 | |
SparseSolver¶
SparseSolver
¶
Bases: SolverBase
GPU solver for general sparse convex quadratic programs that solves a QP - or a whole batch of QPs - of the form
using the proximal interior-point method with a sparse LDL^T
factorization, running entirely on the GPU. It is built for large,
structurally sparse P / A / G.
Inputs - CSR matrices, dense vectors. P, A, G must be
GPU sparse matrices in CSR layout; the vectors (c, b,
h_l, h_u, x_l, x_u) are dense GPU arrays. Accepted
matrix types are:
cupyx.scipy.sparse.csr_matrix(a GPU CSR matrix),- a CUDA
torch.sparse_csr_tensor, or - a
UniformBatchedCsrMatrix- cuPIQP's own container holding a batch of CSR matrices that share one sparsity pattern (the efficient way to pass a batch).
A list / tuple of cupyx.scipy.sparse.csr_matrix (one per batch
element, all sharing the same sparsity pattern) is also accepted, but
discouraged: separate matrix objects cannot be organized with the
uniform stride that batched linear-algebra routines require, so cuPIQP
must copy them into a single contiguous
UniformBatchedCsrMatrix at
setup. Build and pass one of those yourself to avoid the copy.
cuPIQP is GPU-only and CSR-only, and never converts formats behind your back:
- CPU sparse matrices (
scipy.sparse.*, a CPUtorch.sparse_csr_tensor) are rejected - lift them onto the GPU first, e.g.cupyx.scipy.sparse.csr_matrix(P_scipy). - Non-CSR GPU layouts (CSC, BSR, BSC, COO) are rejected - convert with
.tocsr()before passing.
Batching. Solve B independent QPs in a single GPU call by passing
P, A, G as a
UniformBatchedCsrMatrix (the
preferred, fastest batched input), with the vectors stacked along a
leading batch dimension (c of shape (B, n), etc.). A single
problem is simply B = 1, and solver.result.x then has shape
(B, n). (A list of per-element CSR matrices also works but is
slower - see above.)
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dtype
|
(float64, float32)
|
Floating-point precision used throughout the solve. |
"float64"
|
Examples:
A sparse problem assembled on the host with SciPy, then lifted to the GPU:
import scipy.sparse as sp
import cupy as cp
from cupyx.scipy.sparse import csr_matrix
from cupiqp import SparseSolver
P = csr_matrix(sp.eye(4, format="csr")) # lift scipy -> GPU CSR
c = cp.zeros(4)
solver = SparseSolver()
solver.setup(P=P, c=c)
solver.solve()
print(solver.result.info.status[0].name) # CUPIQP_SOLVED
See Also
DenseSolver: solver for general dense problems.
MultistageSolver: structure-exploiting solver for multistage optimization (e.g. optimal-control) problems.
Notes
The problem structure - array shapes, the sparsity pattern of
each matrix, which constraint blocks are present, and which bounds are
finite vs. +/-inf - is fixed by setup and can only be set up
once per instance. To re-solve after changing only the numerical
values (keeping the same sparsity pattern), call update and then
solve again, which reuses all GPU allocations; for a different
structure, create a new SparseSolver. Solver behaviour (tolerances,
verbosity, iteration cap, ...) is configured through solver.settings.
Source code in cupiqp/sparse/sparse_solver.py
setup
¶
setup(
P: CsrMatrixInput,
c: CudaArray,
A: Optional[CsrMatrixInput] = None,
b: Optional[CudaArray] = None,
G: Optional[CsrMatrixInput] = None,
h_u: Optional[CudaArray] = None,
h_l: Optional[CudaArray] = None,
x_u: Optional[CudaArray] = None,
x_l: Optional[CudaArray] = None,
) -> None
Bind the problem data and prepare the solver for solve().
Fixes the problem structure - array shapes, which constraint
blocks are present, the sparsity pattern (sparse backend), and the
finite/infinite pattern of the bounds - and allocates all GPU
buffers, the KKT system, and the preconditioner. Call this once
per solver instance, then call solve().
Pass a single problem (2D P) or a batch (3D P with a leading
batch axis, or a list of matrices); the batch size is inferred here
and sets the shape of the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
Quadratic cost, shape |
required |
c
|
GPU array
|
Linear cost, shape |
required |
A
|
GPU array
|
Equality constraints |
None
|
b
|
GPU array
|
Equality constraints |
None
|
G
|
GPU array
|
Two-sided inequalities |
None
|
h_l
|
GPU array
|
Two-sided inequalities |
None
|
h_u
|
GPU array
|
Two-sided inequalities |
None
|
x_l
|
GPU array
|
Element-wise box bounds |
None
|
x_u
|
GPU array
|
Element-wise box bounds |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
TypeError
|
If an input is not a GPU array of the kind this backend expects
(e.g. a CPU |
See Also
solve : run the solver after setup. update : change numerical data without a full re-setup.
Source code in cupiqp/sparse/sparse_solver.py
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 | |
solve
¶
Solve the QP set up by setup() and return the solve status.
Runs the proximal interior-point iterations on the GPU. The full
solution (primal x, dual, and slack variables) and per-problem
diagnostics are written to solver.result; this method returns the
status for convenience.
Returns:
| Type | Description |
|---|---|
Status or list of Status
|
For a single problem, the |
Notes
Read the solution from solver.result after solving - e.g.
solver.result.x (shape (B, n)) and
solver.result.info.status. Set solver.settings.verbose = True
to print a per-iteration log. After setup() you may solve()
repeatedly, optionally calling update() in between to change the
numerical data.
Source code in cupiqp/solver.py
update
¶
update(
P: Optional[Any] = None,
c: Optional[Any] = None,
A: Optional[Any] = None,
b: Optional[Any] = None,
G: Optional[Any] = None,
h_u: Optional[Any] = None,
h_l: Optional[Any] = None,
x_u: Optional[Any] = None,
x_l: Optional[Any] = None,
check_validity: bool = False,
)
Change the numerical problem data, then solve() again.
The fast path for re-solving a problem of the same structure -
for example a moving target b or a re-linearized P in
receding-horizon control. It reuses every GPU allocation from
setup(), so only the values change; shapes, sparsity patterns,
and which blocks are present must stay the same (create a new solver
for a structural change). Bound values may change freely, including
which entries are +/-inf - a bound can flip between finite and
infinite without re-setup().
Any argument left as None keeps its current value. After
update(), call solve() to get the new solution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
New values for the corresponding problem block. |
None
|
c
|
GPU array
|
New values for the corresponding problem block. |
None
|
A
|
GPU array
|
New values for the corresponding problem block. |
None
|
b
|
GPU array
|
New values for the corresponding problem block. |
None
|
G
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
check_validity
|
bool
|
If |
False
|
Source code in cupiqp/solver.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
backward
¶
backward(
grad_x=None,
grad_y=None,
grad_z_u=None,
grad_z_l=None,
grad_z_bu=None,
grad_z_bl=None,
grad_s_u=None,
grad_s_l=None,
grad_s_bu=None,
grad_s_bl=None,
)
Compute gradients of an outer scalar :math:L w.r.t. problem
data, given upstream cotangents on the solution variables.
Orchestration (backend-agnostic):
- Pack the per-field cotangent kwargs into
self._grad_in(a pre-allocatedVariables); missing kwargs are treated as zeros. - Solve the adjoint KKT system via :meth:
_compute_adjoint, producing user-space adjoint vectors. - Scatter the four active-size lambda groups (
z_u, z_l, z_bu, z_bl) and the two active-size ineq result groups into full-m/ full-nbuffers (self._lam_z*_full,self._z*_full). Both the dG outer product and thedh_*/dx_*vector gradients consume these full-layout buffers. - Delegate to :meth:
_compute_data_gradientsfor backend- specific matrix-gradient assembly +Datasubclass construction.
Returns the backend's Data subclass populated with the
gradients in user space. Cotangents and returned gradients are
interpreted in user (un-scaled) space throughout — the
adjoint solve and scatter chain handles all preconditioner
bookkeeping internally.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If :meth: |
Source code in cupiqp/solver.py
1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 | |
SparseSolver takes a batch of sparse matrices as a single
UniformBatchedCsrMatrix — cuPIQP's own batched CSR container (the preferred,
fastest batched input; see setup above):
UniformBatchedCsrMatrix
¶
UniformBatchedCsrMatrix(
batch_size: int,
indices: Sequence[int],
indptr: Sequence[int],
data: ndarray,
shape: Optional[Tuple[int, int]] = None,
dtype=cp.float64,
)
A batch of CSR matrices that share one sparsity pattern.
A batched extension of cupy's cupyx.scipy.sparse.csr_matrix: where a
single CSR matrix stores indptr, indices, and a 1-D data array
of length nnz, this type stores one shared indptr / indices
pair plus a 2-D data buffer of shape (batch_size, nnz) - the
values of all matrices stacked along a leading batch axis. Every
matrix in the batch therefore has the same nonzero structure and differs
only in its values.
This is the storage cuPIQP uses internally for batched sparse problems, and
the preferred input for solving a batch with SparseSolver: because the
values are contiguous with a uniform per-matrix stride, batched sparse
linear-algebra routines can sweep the whole batch with no copy (unlike a
Python list of separate csr_matrix objects, which must be copied into
this layout first).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
batch_size
|
int
|
Number of matrices in the batch, |
required |
indices
|
sequence of int
|
The shared CSR column-index and row-pointer arrays - the single sparsity pattern used by every matrix in the batch. |
required |
indptr
|
sequence of int
|
The shared CSR column-index and row-pointer arrays - the single sparsity pattern used by every matrix in the batch. |
required |
data
|
ndarray
|
Values of shape |
required |
shape
|
tuple of (int, int)
|
Dense |
None
|
dtype
|
data - type
|
Value dtype. |
``cupy.float64``
|
Attributes:
| Name | Type | Description |
|---|---|---|
batch_size, nnz, rows, cols, shape |
Batch size |
|
indices, indptr |
ndarray
|
The shared CSR sparsity pattern. |
data |
ndarray
|
The |
Source code in cupiqp/sparse/batched_csr.py
MultistageSolver¶
MultistageSolver
¶
Bases: SolverBase
GPU solver for multistage (block-structured) convex quadratic programs that solves a QP - or a whole batch of QPs - of the form
using the proximal interior-point method with a block-Cholesky factorization that exploits block-tridiagonal / block-tridiagonal-arrow KKT structure, running entirely on the GPU. It is built for multistage problems such as optimal control (OCPs / MPC), where that structure arises from the stage-by-stage dynamics and costs.
Inputs - block-structured, end to end. Unlike the dense and sparse
solvers, MultistageSolver takes its problem data as pre-built block
objects (from cupiqp.multistage.multistage_utils), not arrays or CSR
matrices - generic CSR is not auto-promoted to block form, because
the structure can only be exploited if you build it explicitly:
P: aBlockTridiagMat,A,G: aBlockBidiagMat(orNone),c,b,h_l,h_u,x_l,x_u: aBlockVec(orNonewhere the block is absent).
Only P and c are required. As with the other backends, +/-inf
entries in the bound blocks mark one-sided or free bounds and are dropped
at no numerical cost.
Batching. Build the block objects with batch_size=B and the
solver processes all B problems in one GPU call; B = 1 is a
single problem, and solver.result.x then has shape (B, n).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dtype
|
(float64, float32)
|
Floating-point precision used throughout the solve. |
"float64"
|
Examples:
from cupiqp import MultistageSolver
from cupiqp.multistage.multistage_utils import (
BlockTridiagMat, BlockBidiagMat, BlockVec,
)
P = BlockTridiagMat(num_diag_blocks=N, block_size=d)
A = BlockBidiagMat(rows_of_blocks=d, cols_of_blocks=d, N=N)
c = BlockVec(num_blocks=N, rows=d)
b = BlockVec(num_blocks=N, rows=d)
# ... fill block data ...
s = MultistageSolver()
s.setup(P=P, c=c, A=A, b=b)
s.solve()
See Also
DenseSolver: solver for general dense problems.
SparseSolver: solver for general sparse problems.
Notes
Requires the socu block-solver package (install the multistage
extra: pip install ".[cuda13,multistage]"). The problem structure -
the block sizes and which blocks are present - is fixed by setup and
can only be set up once per instance; to re-solve with new numerical
values of the same structure, call update and then solve again,
which reuses all GPU allocations. Solver behaviour (tolerances, verbosity,
iteration cap, ...) is configured through solver.settings.
Source code in cupiqp/multistage/multistage_solver.py
setup
¶
setup(
P: BlockTridiagMat,
c: BlockVec,
A: Optional[BlockBidiagMat] = None,
b: Optional[BlockVec] = None,
G: Optional[BlockBidiagMat] = None,
h_u: Optional[BlockVec] = None,
h_l: Optional[BlockVec] = None,
x_u: Optional[BlockVec] = None,
x_l: Optional[BlockVec] = None,
) -> None
Bind the problem data and prepare the solver for solve().
Fixes the problem structure - array shapes, which constraint
blocks are present, the sparsity pattern (sparse backend), and the
finite/infinite pattern of the bounds - and allocates all GPU
buffers, the KKT system, and the preconditioner. Call this once
per solver instance, then call solve().
Pass a single problem (2D P) or a batch (3D P with a leading
batch axis, or a list of matrices); the batch size is inferred here
and sets the shape of the result.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
Quadratic cost, shape |
required |
c
|
GPU array
|
Linear cost, shape |
required |
A
|
GPU array
|
Equality constraints |
None
|
b
|
GPU array
|
Equality constraints |
None
|
G
|
GPU array
|
Two-sided inequalities |
None
|
h_l
|
GPU array
|
Two-sided inequalities |
None
|
h_u
|
GPU array
|
Two-sided inequalities |
None
|
x_l
|
GPU array
|
Element-wise box bounds |
None
|
x_u
|
GPU array
|
Element-wise box bounds |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If |
TypeError
|
If an input is not a GPU array of the kind this backend expects
(e.g. a CPU |
See Also
solve : run the solver after setup. update : change numerical data without a full re-setup.
Source code in cupiqp/multistage/multistage_solver.py
solve
¶
Solve the QP set up by setup() and return the solve status.
Runs the proximal interior-point iterations on the GPU. The full
solution (primal x, dual, and slack variables) and per-problem
diagnostics are written to solver.result; this method returns the
status for convenience.
Returns:
| Type | Description |
|---|---|
Status or list of Status
|
For a single problem, the |
Notes
Read the solution from solver.result after solving - e.g.
solver.result.x (shape (B, n)) and
solver.result.info.status. Set solver.settings.verbose = True
to print a per-iteration log. After setup() you may solve()
repeatedly, optionally calling update() in between to change the
numerical data.
Source code in cupiqp/solver.py
update
¶
update(
P: Optional[Any] = None,
c: Optional[Any] = None,
A: Optional[Any] = None,
b: Optional[Any] = None,
G: Optional[Any] = None,
h_u: Optional[Any] = None,
h_l: Optional[Any] = None,
x_u: Optional[Any] = None,
x_l: Optional[Any] = None,
check_validity: bool = False,
)
Change the numerical problem data, then solve() again.
The fast path for re-solving a problem of the same structure -
for example a moving target b or a re-linearized P in
receding-horizon control. It reuses every GPU allocation from
setup(), so only the values change; shapes, sparsity patterns,
and which blocks are present must stay the same (create a new solver
for a structural change). Bound values may change freely, including
which entries are +/-inf - a bound can flip between finite and
infinite without re-setup().
Any argument left as None keeps its current value. After
update(), call solve() to get the new solution.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
P
|
GPU array
|
New values for the corresponding problem block. |
None
|
c
|
GPU array
|
New values for the corresponding problem block. |
None
|
A
|
GPU array
|
New values for the corresponding problem block. |
None
|
b
|
GPU array
|
New values for the corresponding problem block. |
None
|
G
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
h_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_u
|
GPU array
|
New values for the corresponding problem block. |
None
|
x_l
|
GPU array
|
New values for the corresponding problem block. |
None
|
check_validity
|
bool
|
If |
False
|
Source code in cupiqp/solver.py
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 | |
backward
¶
backward(
grad_x=None,
grad_y=None,
grad_z_u=None,
grad_z_l=None,
grad_z_bu=None,
grad_z_bl=None,
grad_s_u=None,
grad_s_l=None,
grad_s_bu=None,
grad_s_bl=None,
)
Compute gradients of an outer scalar :math:L w.r.t. problem
data, given upstream cotangents on the solution variables.
Orchestration (backend-agnostic):
- Pack the per-field cotangent kwargs into
self._grad_in(a pre-allocatedVariables); missing kwargs are treated as zeros. - Solve the adjoint KKT system via :meth:
_compute_adjoint, producing user-space adjoint vectors. - Scatter the four active-size lambda groups (
z_u, z_l, z_bu, z_bl) and the two active-size ineq result groups into full-m/ full-nbuffers (self._lam_z*_full,self._z*_full). Both the dG outer product and thedh_*/dx_*vector gradients consume these full-layout buffers. - Delegate to :meth:
_compute_data_gradientsfor backend- specific matrix-gradient assembly +Datasubclass construction.
Returns the backend's Data subclass populated with the
gradients in user space. Cotangents and returned gradients are
interpreted in user (un-scaled) space throughout — the
adjoint solve and scatter chain handles all preconditioner
bookkeeping internally.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If :meth: |
Source code in cupiqp/solver.py
1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918 1919 1920 1921 1922 1923 1924 1925 1926 | |
MultistageSolver takes its problem data as the block-structured objects below —
build them, fill in their data, and pass them to setup (see above for which
argument expects which type):
BlockTridiagMat
¶
BlockTridiagMat(
num_diag_blocks: int,
block_size: int,
dtype=wp.float64,
device="cuda",
batch_size: int = 1,
)
Symmetric block-tridiagonal matrix, batched.
Layout::
D shape (B, N, d, d) - diagonal blocks
E shape (B, N-1, d, d) - lower-diagonal blocks
The matrix is symmetric; the upper off-diagonal is implicitly the transpose of the lower one. batch_size defaults to 1.
diag_blocks and off_diag_blocks_lower may be assigned a Warp array or any CUDA array implementing cuda_array_interface (CuPy, dense CUDA PyTorch, JAX, Numba, ...). Non-Warp arrays are wrapped zero-copy; shape and dtype must match the construction arguments.
Source code in cupiqp/multistage/multistage_utils.py
BlockBidiagMat
¶
BlockBidiagMat(
rows_of_blocks: int,
cols_of_blocks: int,
N: int,
dtype=wp.float64,
device="cuda",
batch_size: int = 1,
)
Block lower-bidiagonal matrix, batched.
Stores A or G of the multistage problem with shape::
D shape (B, N, rows_of_blocks, cols_of_blocks)
E shape (B, N, rows_of_blocks, cols_of_blocks)
Logical structure (per batch)::
A =
[ D0
E0 D1
E1 D2
...
E_{N-2} D_{N-1}
E_{N-1} ]
D and E may be assigned a warp array or any CUDA array
implementing __cuda_array_interface__ (cupy, dense CUDA torch,
jax, numba, ...). Non-warp arrays are wrapped zero-copy, so the
container aliases the assigned buffer; shape and dtype must match
the construction arguments.
Source code in cupiqp/multistage/multistage_utils.py
BlockVec
¶
Block vector, batched. data shape (B, num_blocks, rows).
data may be assigned a warp array or any CUDA array implementing
__cuda_array_interface__ (cupy, dense CUDA torch, jax, numba, ...).
Non-warp arrays are wrapped zero-copy, so the container aliases the
assigned buffer; shape and dtype must match the construction arguments.