A Python implementation of the CVQP solver for CVaR-constrained quadratic programs
Project description
CVQP
A Python implementation of an operator splitting method for solving large-scale CVaR-constrained quadratic programs, as described in our paper.
Installation
From PyPI (recommended)
pip install cvqp
From Source
Clone the repository and install using Poetry:
# Clone the repository
git clone https://github.com/cvxgrp/cvqp.git
cd cvqp
# Install dependencies
poetry install
# Compile C++ extensions
poetry run pip install -e .
CVaR-Constrained Quadratic Programs
CVaR-constrained quadratic programs (CVQPs) are optimization problems of the form
$$ \begin{align} \text{minimize} & \quad \frac{1}{2}x^TPx + q^Tx \ \text{subject to} & \quad \phi_\beta(Ax) \leq \kappa \ & \quad l \leq Bx \leq u \end{align} $$
where $x \in \mathbb{R}^n$ is the variable, $P \in \mathbb{R}^{n \times n}$ is positive semidefinite, $q \in \mathbb{R}^n$, $\phi_\beta$ is the conditional value-at-risk (CVaR) with parameter $\beta \in (0,1)$, $A \in \mathbb{R}^{m \times n}$, $B \in \mathbb{R}^{p \times n}$, and $l, u \in \mathbb{R}^p$ are lower and upper bounds (which can include infinity).
If $Ax$ represents a vector of scenario losses, the CVaR constraint $\phi_\beta(Ax) \leq \kappa$ bounds the average loss in the worst $(1-\beta)$ fraction of scenarios. This type of constraint is useful in risk-sensitive applications such as portfolio optimization, where we want to limit the expected loss in the worst-case scenarios.
Example: Using CVQP Solver
import numpy as np
from cvqp import CVQP, CVQPParams, CVQPConfig
# Define problem parameters
params = CVQPParams(
P=np.eye(10), # Quadratic cost matrix
q=np.ones(10) * -0.1, # Linear cost vector
A=np.random.randn(100, 10) * 0.2 + 0.1, # CVaR constraint matrix
B=np.eye(10), # Box constraint matrix
l=-np.ones(10), # Lower bounds
u=np.ones(10), # Upper bounds
beta=0.9, # CVaR confidence level
kappa=0.1, # CVaR limit
)
# Create solver with custom configuration
config = CVQPConfig(
verbose=True, # Print detailed progress
max_iter=1000, # Maximum iterations
abstol=1e-4, # Absolute tolerance
reltol=1e-3, # Relative tolerance
)
# Initialize and solve
cvqp = CVQP(params, config)
results = cvqp.solve()
# Access solution
print(f"Optimal value: {results.objval[-1]:.6f}")
print(f"Solver status: {results.problem_status}")
print(f"Solve time: {results.solve_time:.2f} seconds")
print(f"Iterations: {results.iter_count}")
The Same Problem in CVXPY
For comparison, here's how to solve the same problem using CVXPY:
import cvxpy as cp
import numpy as np
# Define parameters (same as above)
n = 10
m = 100
P = np.eye(n)
q = np.ones(n) * -0.1
A = np.random.randn(m, n) * 0.2 + 0.1
beta = 0.9
kappa = 0.1
# Define variables
x = cp.Variable(n)
# Define objective
objective = cp.Minimize(0.5 * cp.quad_form(x, P) + q.T @ x)
# Define constraints
cvar_constraint = cp.cvar(A @ x, beta) <= kappa
box_constraints = [-np.ones(n) <= x, x <= np.ones(n)]
# Define and solve problem
prob = cp.Problem(objective, [cvar_constraint] + box_constraints)
prob.solve(solver=cp.MOSEK)
print(f"Optimal value: {prob.value:.6f}")
print(f"Solver status: {prob.status}")
While CVXPY makes it easy to model this problem, our specialized CVQP solver is orders of magnitude faster than general-purpose solvers (like MOSEK or CLARABEL) for large-scale problems with many scenarios.
Sum-k-Largest Projection
The CVQP package also provides an efficient function for projecting onto the set where the sum of k largest elements is at most d:
$$ \begin{align} \text{minimize} & \quad \lVert v - z \rVert_2^2 \ \text{subject to} & \quad f_k(z) \leq d \end{align} $$
where $v \in \mathbb{R}^m$ is the vector to be projected, $z \in \mathbb{R}^m$ is the optimization variable, $f_k(z) = \sum_{i=1}^k z_{[i]}$ is the sum of the $k$ largest elements of $z$, and $z_{[1]} \geq z_{[2]} \geq \cdots \geq z_{[m]}$ are the components of $z$ in non-increasing order.
This projection problem is closely related to the CVaR constraint, as we can express the CVaR constraint equivalently using the sum-k-largest function with $k = (1-\beta)m$ and $d = \kappa k$.
Example: Using Sum-k-Largest Projection
from cvqp import proj_sum_largest
import numpy as np
# Create a vector to project
v = np.array([6.0, 2.0, 5.0, 4.0, 1.0])
k = 2 # Number of largest elements to constrain
d = 7.0 # Upper bound on sum
# Apply projection
z = proj_sum_largest(v, k, d)
print(f"Original vector: {v}")
print(f"Projected vector: {z}")
print(f"Sum of {k} largest elements after projection: {sum(sorted(z, reverse=True)[:k]):.6f}")
The Same Problem in CVXPY
For comparison, here's the same projection using CVXPY:
import cvxpy as cp
import numpy as np
# Create a vector to project
v = np.array([6.0, 2.0, 5.0, 4.0, 1.0])
k = 2 # Number of largest elements to constrain
d = 7.0 # Upper bound on sum
# Define variable and problem
z = cp.Variable(len(v))
objective = cp.Minimize(cp.sum_squares(z - v))
constraint = cp.sum_largest(z, k) <= d
# Solve the problem
prob = cp.Problem(objective, [constraint])
prob.solve(solver=cp.MOSEK)
print(f"Original vector: {v}")
print(f"Projected vector: {z.value}")
print(f"Sum of {k} largest elements after projection: {sum(sorted(z.value, reverse=True)[:k]):.6f}")
Both approaches solve the same mathematical problem, but our specialized projection algorithm is much faster and scales to vectors with millions of elements, where general-purpose solvers become impractical.
Examples and Benchmarks
The examples directory contains notebooks and benchmark scripts for:
- Portfolio optimization
- Quantile regression
See the examples README for detailed benchmark results from our paper and instructions for reproducing them.
Citation
If you use CVQP in your research, please consider citing our paper:
@misc{cvqp2025,
title={An Operator Splitting Method for Large-Scale {CVaR}-Constrained Quadratic Programs},
author={Luxenberg, Eric and P\'erez-Pi\~neiro, David and Diamond, Steven and Boyd, Stephen},
year={2025},
eprint={2504.10814},
archivePrefix={arXiv},
primaryClass={math.OC},
url={https://arxiv.org/abs/2504.10814}
}
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cvqp-0.1.0.tar.gz.
File metadata
- Download URL: cvqp-0.1.0.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
01a840fbf82f2877531a4aa93ab398372983fb29e5640eb1e0ec012d661cf145
|
|
| MD5 |
8796c98f60abb46a3a29ae7a9023a925
|
|
| BLAKE2b-256 |
1d596487e95f0738096530595ba5a87866eca3d391104bcd702516134812f942
|
File details
Details for the file cvqp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: cvqp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 13.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c91607f33ab18d6f9ff61d5bb4dd80820399c5f84294575e79b29928b9061940
|
|
| MD5 |
e430dfb1a3f754a819af813bffb9582f
|
|
| BLAKE2b-256 |
4e2c535caab58314a5ed7a153bc7e51fb5d4f4e716d388bc04f9bb6ceb854050
|
File details
Details for the file cvqp-0.1.0-cp312-cp312-macosx_14_0_x86_64.whl.
File metadata
- Download URL: cvqp-0.1.0-cp312-cp312-macosx_14_0_x86_64.whl
- Upload date:
- Size: 84.5 kB
- Tags: CPython 3.12, macOS 14.0+ x86-64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.1.0 CPython/3.12.8
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d539e0ec08c858ab9f72c38b0321015af1ce414cec250e3457329b6bc8fc3788
|
|
| MD5 |
69a9bbce93eee58c00b76bf6ef226f42
|
|
| BLAKE2b-256 |
75a01cd74d8d9656247ca52d6f5cbd664e03af282f4bedebbb95955febc85713
|