Skip to main content

A Python implementation of the CVQP solver for CVaR-constrained quadratic programs

Project description

CVQP

PyPI version License

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

cvqp-0.1.0.tar.gz (21.5 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

cvqp-0.1.0-py3-none-any.whl (13.5 kB view details)

Uploaded Python 3

cvqp-0.1.0-cp312-cp312-macosx_14_0_x86_64.whl (84.5 kB view details)

Uploaded CPython 3.12macOS 14.0+ x86-64

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

Hashes for cvqp-0.1.0.tar.gz
Algorithm Hash digest
SHA256 01a840fbf82f2877531a4aa93ab398372983fb29e5640eb1e0ec012d661cf145
MD5 8796c98f60abb46a3a29ae7a9023a925
BLAKE2b-256 1d596487e95f0738096530595ba5a87866eca3d391104bcd702516134812f942

See more details on using hashes here.

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

Hashes for cvqp-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 c91607f33ab18d6f9ff61d5bb4dd80820399c5f84294575e79b29928b9061940
MD5 e430dfb1a3f754a819af813bffb9582f
BLAKE2b-256 4e2c535caab58314a5ed7a153bc7e51fb5d4f4e716d388bc04f9bb6ceb854050

See more details on using hashes here.

File details

Details for the file cvqp-0.1.0-cp312-cp312-macosx_14_0_x86_64.whl.

File metadata

File hashes

Hashes for cvqp-0.1.0-cp312-cp312-macosx_14_0_x86_64.whl
Algorithm Hash digest
SHA256 d539e0ec08c858ab9f72c38b0321015af1ce414cec250e3457329b6bc8fc3788
MD5 69a9bbce93eee58c00b76bf6ef226f42
BLAKE2b-256 75a01cd74d8d9656247ca52d6f5cbd664e03af282f4bedebbb95955febc85713

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page