nncg solves the strictly convex non-negative quadratic program
and its equality-augmented variant with a general linear system
This is the reference implementation of the paper Non-Negative Conjugate
Gradients (Schmelzer & Stoll), developed in
Jebel-Quant/mean_variance_solvers.
The paper's numerical study doubles as this package's test suite: planted-optimum
recovery across condition numbers, the equality-augmented solve for
The quadratic term enters as a cvx.linalg.SymmetricOperator: wrap an
explicit SPD array in DenseOperator. When GramOperator(M, ridge) and the inner solves need only products with
pip install nncgimport numpy as np
from cvx.linalg import DenseOperator, GramOperator
from nncg import kkt_violation, solve_nnqp, solve_nnqp_eq
# a random SPD problem with condition number 1e4
rng = np.random.default_rng(0)
Q, _ = np.linalg.qr(rng.standard_normal((200, 200)))
A = (Q * np.geomspace(1.0, 1e4, 200)) @ Q.T
b = rng.standard_normal(200)
op = DenseOperator(A) # the solvers take a SymmetricOperator
res = solve_nnqp(op, b)
assert res.converged # stopped on the KKT certificate
assert kkt_violation(op, b, res.x) < 1e-6 # zero certifies the global minimiser
# equality-augmented: minimise subject to x >= 0 and B x = c
B = np.ones((1, 200)) # p = 1: the budget 1'x = 1
res_eq = solve_nnqp_eq(op, b, B, np.array([1.0]))
assert res_eq.lam.shape == (1,) # multiplier, via a p-by-p Schur solve
# warm-start a parametric sweep: support-stable steps take ONE outer step
res2 = solve_nnqp(op, b + 1e-4, warm=(res.free, res.x))
# Gram-structured: A = M'M + I only through products with M — never formed
M = np.random.default_rng(1).standard_normal((50, 200))
res_g = solve_nnqp(GramOperator(M, ridge=1.0), M.T @ np.ones(50))
assert res_g.convergedFix a working set of free variables and solve the unconstrained reduced SPD
system by CG (matrix-free, make_adversarial family in the test suite's
tests/problems.py) the unguarded batch path revisits a previously seen
working set and loops forever.
If you use this package in academic work, please cite the paper:
@techreport{schmelzer2026nncg,
title = {Non-Negative Conjugate Gradients},
author = {Schmelzer, Thomas and Stoll, Martin},
year = {2026},
institution = {Jebel Quant Research and TU Chemnitz},
url = {https://github.com/Jebel-Quant/mean_variance_solvers},
}MIT — see LICENSE.