Skip to content

Repository files navigation

cvx-linalg

PyPI version Python Downloads License CodeFactor Rhiza Coverage

Linear algebra utilities for portfolio optimization, part of the jebel-quant ecosystem.

Installation

pip install cvx-linalg

The ewm_covariance function requires the optional Polars dependency:

pip install 'cvx-linalg[ewm]'

Usage

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)

Everything is NaN-aware

This is what sets cvx-linalg apart from numpy.linalg. Financial data arrives with gaps: an asset that was not yet listed, a name that stopped trading, a covariance estimate that never converged.

Hand such a matrix to np.linalg.solve and you get back nothing you can use. LAPACK's pivoting smears the missing entry across the solution, and how far it spreads depends on the BLAS your NumPy wheel was built against — the same call returns [nan, nan, nan] under Accelerate on macOS and [nan, nan, 2.375] under OpenBLAS on Linux. Neither is an answer you can act on.

Every function here instead restricts the computation to the valid sub-problem and returns NaN only where the input was missing — the same result on every platform:

import numpy as np

from cvx.linalg import solve, valid

# A covariance matrix in which the second asset has no usable data.
cov = np.array(
    [
        [4.0, 1.0, 0.0],
        [1.0, np.nan, 1.0],
        [0.0, 1.0, 9.0],
    ]
)
rhs = np.array([8.0, 1.0, 18.0])

mask, submatrix = valid(cov)
print("valid assets     :", mask.tolist())
print("clean submatrix  :", submatrix.tolist())
print("cvx.linalg.solve :", solve(cov, rhs).tolist())
valid assets     : [True, False, True]
clean submatrix  : [[4.0, 0.0], [0.0, 9.0]]
cvx.linalg.solve : [2.0, nan, 2.0]

valid reports which rows and columns survive and hands back the clean submatrix; solve uses that mask internally, solves the 2x2 system for the assets that do have data, and marks the missing asset NaN rather than destroying the other two answers.

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.

Core (cvx.linalg.core)

Types

  • Matrix — Type alias for a 2-D numpy.ndarray with float64 dtype
  • Vector — Type alias for a 1-D numpy.ndarray with float64 dtype

The package ships a py.typed marker; all public signatures are precisely annotated and verified with ty in CI.

Exceptions & Warnings

All exceptions and warnings live in core/exceptions.py:

  • DimensionMismatchError — Raised when vector length does not match matrix dimension
  • IllConditionedMatrixWarning — Emitted when the condition number exceeds a configurable threshold
  • InvalidComponentsError — Raised when pca is asked for fewer than 1 or more components than the data supports
  • NegativeWarmupError — Raised when a negative warmup period is passed to ewm_covariance
  • NonIntegerWarmupError — Raised when a non-integer warmup is passed to ewm_covariance
  • NonSquareMatrixError — Raised when a square matrix is required but the input is not square
  • NotAMatrixError — Raised when a 2-D matrix is required but the input has different dimensionality
  • SingularMatrixError — Raised when a matrix is numerically singular

Covariance (cvx.linalg.covariance)

Decompositions (cvx.linalg.decomposition)

  • cholesky(cov) — Upper triangular Cholesky factor R such that R.T @ R = cov
  • cholesky_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-definite
  • eigh(matrix) — Eigenvalues/eigenvectors of the valid symmetric/Hermitian submatrix in ascending eigenvalue order
  • eigvalsh(matrix) — Eigenvalues-only convenience wrapper around eigh
  • eigvals(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 iteration
  • qr(matrix) — Reduced QR decomposition, matching np.linalg.qr(mode='reduced')
  • svd(matrix) — Raw compact singular value decomposition via np.linalg.svd(full_matrices=False)
  • svd_k(matrix, k) — Exact truncated rank-k SVD (the best rank-k approximation; leading triplets of the compact SVD)

Norms (cvx.linalg.norm)

Solvers (cvx.linalg.solve)

Operators (cvx.linalg.operators)

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 through matvec, block_matvec, solve_free, apply_free, rcond_free, restricted, and diag
  • DenseOperator — Backend wrapping an explicit dense n x n matrix
  • IncrementalDenseOperatorDenseOperator maintaining the free-block inverse across single-index flips for active-set sweeps
  • GramOperator — Matrix-free backend for A = M.T @ M, represented by its factor M; never forms the Gram matrix
  • FactorOperator — Diagonal-plus-low-rank backend A = diag(d) + U @ Delta @ U.T with Woodbury free-block solves
  • SumOperator — Weighted sum of symmetric operators (forward-only; feed apply_free to a Krylov solver)

Constrained solves (cvx.linalg.kkt)

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 a SymmetricOperator
  • AffineProjection(c, d) — Euclidean projection onto the affine set {x : C x = d}, caching the Gram matrix C C.T for repeated use

Stability policy

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 DeprecationWarning in the meantime (currently: the two-argument cholesky(cov, rhs) form — use cholesky_solve — slated for removal in 2.0).
  • Supported environments: Python 3.11–3.14, NumPy ≥ 2.0. The optional ewm extra requires Polars ≥ 1.40.

Numerical conventions (NaN handling, condition-number warnings, dtype contract) are documented in Numerical Behavior.

About

Linear algebra utilities for portfolio optimization

Resources

Contributing

Security policy

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages