Skip to main content

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]'

The optional SciPy extra makes cholesky_solve use triangular solves on the Cholesky factor and lets the condition-number check screen with a cheap LAPACK estimate instead of a full SVD (see Solvers):

pip install 'cvx-linalg[scipy]'

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())
valid assets     : [True, False, True]
>>> print("clean submatrix  :", submatrix.tolist())
clean submatrix  : [[4.0, 0.0], [0.0, 9.0]]
>>> print("cvx.linalg.solve :", solve(cov, rhs).tolist())
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)

  • valid(matrix) — Return a boolean mask and valid submatrix by removing rows/columns with non-finite diagonal entries
  • cond(matrix, p=None) — Condition number of a matrix (NaN-aware); accepts the same p norm values as numpy.linalg.cond
  • check_and_warn_condition(matrix, threshold) — Emit IllConditionedMatrixWarning when the condition number exceeds the threshold; threshold=None skips the check (and its SVD) entirely
  • warn_ill_conditioned(cond_value, threshold) — Emit IllConditionedMatrixWarning for an already-computed condition number; threshold=None never warns
  • DEFAULT_COND_THRESHOLD — Default condition-number threshold (1e12) used by the ill-conditioning checks

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
  • SupportsMatvec — Structural protocol for a matrix-free operator: anything exposing a dimension n and matvec(x) -> A @ x (every SymmetricOperator conforms)

The package ships a py.typed marker; all public signatures are precisely annotated and verified with ty and mypy --strict 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(operator, *, n=None, n_iter=1000, tol=1e-9, seed=None) — Estimate the dominant (largest-magnitude) eigenpair of a symmetric operator; accepts a dense array, any SymmetricOperator, or a callable v -> A @ v plus n, so it runs matrix-free
  • 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)

solve, inv, det (and inv_a_norm) warn when the 2-norm condition number of the valid submatrix exceeds cond_threshold. With the scipy extra installed, a symmetric positive-definite submatrix is first screened by a LAPACK estimate from its Cholesky factor (about the cost of one more solve), and the full SVD runs only when that estimate cannot rule out a warning. Otherwise the check costs a full SVD on every call, often far more than the solve itself. Pass cond_threshold=None to skip the check entirely; cond_threshold=np.inf only silences the warning. lstsq accepts None too, but gets its condition number from singular values it already has.

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(matrix) — Backend wrapping an explicit dense n x n matrix
  • IncrementalDenseOperator(matrix) — DenseOperator maintaining the free-block inverse across single-index flips for active-set sweeps
  • GramOperator(factor, ridge=0.0) — Matrix-free backend for A = M.T @ M + ridge * I, represented by its factor M; never forms the Gram matrix. GramOperator.regularized(factor, alpha, root) builds the shrinkage target (1 - alpha) M.T M + alpha R.T R as a stacked-factor Gram operator
  • FactorOperator(diagonal, loadings, inner) — Diagonal-plus-low-rank backend A = diag(d) + U @ Delta @ U.T with Woodbury free-block solves
  • SumOperator(terms) — 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; the optional scipy extra requires SciPy ≥ 1.11.

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

Metadata

Release files for cvx-linalg 1.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cvx-linalg 1.1.2
File Size Uploaded
cvx_linalg-1.1.2.tar.gz 43.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cvx-linalg 1.1.2
File Interpreter ABI Platform
cvx_linalg-1.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 101.2 kB

Release files / cvx_linalg-1.1.2.tar.gz

Download URL cvx_linalg-1.1.2.tar.gz
Size 43.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4c1497147af11986a34bfeb5e2972052730952e167679ef0aea6d7b630ba7c56
BLAKE2b-256 checksum
How to use checksums
99859315f75dfb1f901f8c224c5d9bdff455c5c4a3a7ae4c5847126e29ddd93f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release files / cvx_linalg-1.1.2-py3-none-any.whl

Download URL cvx_linalg-1.1.2-py3-none-any.whl
Size 57.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a852a5a73566cd92fca8d36c0230c19e9e14cde30c6e60097293aef446785b49
BLAKE2b-256 checksum
How to use checksums
141c9be411ebfce2a73c09644cdc459456ac876ea3c1599b0ebe000ba7ba07ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

1.1.2 This release

2 release files

1.1.1

2 release files

1.1.0

2 release files

1.0.0

2 release files

0.9.6

2 release files

0.9.5

2 release files

0.9.4

2 release files

0.9.3

2 release files

0.9.2

2 release files

0.9.1

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.0

2 release files

0.6.2

2 release files

0.6.1

2 release files

0.6.0

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page