Linear algebra utilities for portfolio optimization, part of the jebel-quant ecosystem.
pip install cvx-linalgThe ewm_covariance function requires the optional Polars dependency:
pip install 'cvx-linalg[ewm]'The entire public API is re-exported at the top level, so a flat import is all you ever need:
from cvx.linalg import (
a_norm, cholesky, cholesky_solve, cov_to_corr,
inv, inv_a_norm, is_positive_definite, lstsq, pca, rand_cov, solve, valid,
GramOperator, bordered_solve,
)
from cvx.linalg.covariance.ewm_cov import ewm_covariance # requires the 'ewm' extra (polars)Internally the package is organized into subpackages (which can also be
imported directly, e.g. from cvx.linalg.decomposition import qr):
| Subpackage | Contents |
|---|---|
cvx.linalg.core |
Type aliases, exceptions/warnings, condition-number helpers, matrix validation |
cvx.linalg.covariance |
Covariance utilities: PCA, random covariance, correlation conversion, EWM covariance |
cvx.linalg.decomposition |
Matrix decompositions: Cholesky, QR, SVD, eigenvalue routines, power iteration |
cvx.linalg.kkt |
Equality-constrained solves: bordered KKT systems, affine projections |
cvx.linalg.norm |
NaN-aware vector and matrix norms, A-norms and their inverses |
cvx.linalg.operators |
Symmetric linear operators (dense, Gram, factor, incremental) for active-set and Krylov solvers |
cvx.linalg.solve |
NaN-aware linear-system solvers: solve, lstsq, inv, det |
The one exception to the flat-import rule is ewm_covariance: it lives in
cvx.linalg.covariance.ewm_cov and is deliberately not re-exported because it
requires the optional polars dependency.
valid(matrix)— Return a boolean mask and valid submatrix by removing rows/columns with non-finite diagonal entriescond(matrix, p=None)— Condition number of a matrix (NaN-aware); accepts the samepnorm values asnumpy.linalg.condcheck_and_warn_condition(matrix, threshold)— EmitIllConditionedMatrixWarningwhen the condition number exceeds the thresholdwarn_ill_conditioned(cond_value, threshold)— EmitIllConditionedMatrixWarningfor an already-computed condition numberDEFAULT_COND_THRESHOLD— Default condition-number threshold (1e12) used by the ill-conditioning checks
Matrix— Type alias for a 2-Dnumpy.ndarraywithfloat64dtypeVector— Type alias for a 1-Dnumpy.ndarraywithfloat64dtype
The package ships a py.typed marker; all public signatures are precisely annotated and verified with ty in CI.
All exceptions and warnings live in core/exceptions.py:
DimensionMismatchError— Raised when vector length does not match matrix dimensionIllConditionedMatrixWarning— Emitted when the condition number exceeds a configurable thresholdInvalidComponentsError— Raised whenpcais asked for fewer than 1 or more components than the data supportsNegativeWarmupError— Raised when a negative warmup period is passed toewm_covarianceNonIntegerWarmupError— Raised when a non-integer warmup is passed toewm_covarianceNonSquareMatrixError— Raised when a square matrix is required but the input is not squareNotAMatrixError— Raised when a 2-D matrix is required but the input has different dimensionalitySingularMatrixError— Raised when a matrix is numerically singular
cov_to_corr(cov, min_var=1e-14)— Convert a covariance matrix to a correlation matrixewm_covariance(data, assets, index_col, window, is_halflife, warmup)— Exponentially weighted covariance matrices from a Polars DataFrame (requires theewmextra)pca(returns, n_components=10)— Principal Component Analysis via SVDrand_cov(n, seed=None)— Random positive semi-definite covariance matrix
cholesky(cov)— Upper triangular Cholesky factor R such that R.T @ R = covcholesky_solve(cov, rhs)— Solve cov @ x = rhs via Cholesky decomposition (falls back to LU for non-positive-definite matrices)is_positive_definite(matrix)— Return True if the matrix is symmetric positive-definiteeigh(matrix)— Eigenvalues/eigenvectors of the valid symmetric/Hermitian submatrix in ascending eigenvalue ordereigvalsh(matrix)— Eigenvalues-only convenience wrapper aroundeigheigvals(matrix)— Eigenvalues of a general square matrix (supports complex output for non-symmetric matrices)power_iteration(matrix, n_iter=1000, tol=1e-9, seed=None)— Estimate the dominant (largest-magnitude) eigenpair of a symmetric matrix via power iterationqr(matrix)— Reduced QR decomposition, matchingnp.linalg.qr(mode='reduced')svd(matrix)— Raw compact singular value decomposition vianp.linalg.svd(full_matrices=False)svd_k(matrix, k)— Exact truncated rank-kSVD (the best rank-kapproximation; leading triplets of the compact SVD)
norm(x, ord=None)— Norm of a vector or matrix, ignoring non-finite entries; supports allordvalues ofnp.linalg.norma_norm(vector, matrix=None)— Euclidean norm or NaN-aware matrix norminv_a_norm(vector, matrix=None)— Euclidean norm or inverse NaN-aware matrix norm
solve(matrix, rhs, cond_threshold=1e12)— Solve a linear system (vector or matrix rhs) restricted to valid rows/columns; NaN entries are returned for invalid positionslstsq(matrix, rhs, cond_threshold=1e12)— Solve a least-squares system with NaN-aware row filtering; returns(x, residuals, rank, sv)consistent withnumpy.linalg.lstsqinv(matrix, cond_threshold=1e12)— Invert a matrix restricted to valid rows/columns; NaN rows/columns are returned for invalid positionsdet(matrix, cond_threshold=1e12)— Determinant of a square matrix with NaN-aware submatrix handling; emitsIllConditionedMatrixWarningwhen near-singular
Active-set and Krylov solvers never need a symmetric matrix A as an explicit
array — they touch it through a handful of products: a full matrix-vector
product, sub-block products, and a direct solve against a principal ("free")
sub-block. The SymmetricOperator
protocol captures exactly that contract (including rcond_free for detecting
rank-deficient free blocks and restricted(free) for a pre-sliced free-block
view), and the backends implement it at very different cost:
SymmetricOperator— Protocol exposing a symmetric matrix throughmatvec,block_matvec,solve_free,apply_free,rcond_free,restricted, anddiagDenseOperator— Backend wrapping an explicit densen x nmatrixIncrementalDenseOperator—DenseOperatormaintaining the free-block inverse across single-index flips for active-set sweepsGramOperator— Matrix-free backend forA = M.T @ M, represented by its factorM; never forms the Gram matrixFactorOperator— Diagonal-plus-low-rank backendA = diag(d) + U @ Delta @ U.Twith Woodbury free-block solvesSumOperator— Weighted sum of symmetric operators (forward-only; feedapply_freeto a Krylov solver)
Building blocks for active-set and path-tracing solvers, built on top of the operator protocol:
bordered_solve(operator, free, c_free, rhs, d)— Range-space (Schur complement) solve of the bordered KKT system[[H_FF, C.T], [C, 0]] @ [x; nu] = [rhs; d], reaching the Hessian block only through aSymmetricOperatorAffineProjection(c, d)— Euclidean projection onto the affine set{x : C x = d}, caching the Gram matrixC C.Tfor repeated use
This package follows semantic versioning. The public API
is everything importable from cvx.linalg (plus cvx.linalg.covariance.ewm_cov):
- Breaking changes only occur in major releases.
- Deprecations are announced at least one minor release before removal and
emit a
DeprecationWarningin the meantime (currently: the two-argumentcholesky(cov, rhs)form — usecholesky_solve— slated for removal in 1.0). - Supported environments: Python 3.11–3.14, NumPy ≥ 2.0. The optional
ewmextra requires Polars ≥ 1.40.
Numerical conventions (NaN handling, condition-number warnings, dtype contract) are documented in Numerical Behavior.