Skip to content

API Reference

NaN-aware linear algebra utilities for risk models. All public symbols are importable directly from the top-level package:

from cvx.linalg import cholesky, eigh, eigvalsh, eigvals, qr, svd, pca
from cvx.linalg import solve, lstsq, inv
from cvx.linalg import norm, a_norm, inv_a_norm, cond, det
from cvx.linalg import rand_cov, valid, is_positive_definite
from cvx.linalg.ewm_cov import ewm_covariance  # requires polars

Decompositions

cvx.linalg.cholesky.cholesky(cov, rhs=None)

Compute the upper triangular Cholesky factor, or solve a linear system.

When called without rhs, returns the upper triangular factor R such that R.T @ R = cov. When rhs is given, solves cov @ x = rhs using the Cholesky decomposition for numerical stability, falling back to LU decomposition if cov is not positive-definite.

Parameters:

Name Type Description Default
cov ndarray

A positive definite covariance matrix of shape (n, n).

required
rhs ndarray | None

Optional right-hand side vector or matrix. When provided the system cov @ x = rhs is solved and x is returned.

None

Returns:

Type Description
ndarray

The upper triangular Cholesky factor R when rhs is None, or the

ndarray

solution x to cov @ x = rhs otherwise.

Raises:

Type Description
LinAlgError

When rhs is None and cov is not positive-definite, or when rhs is given and both Cholesky and LU-based solves fail.

Example

Decomposition (no rhs):

import numpy as np from cvx.linalg import cholesky cov = np.array([[4.0, 2.0], [2.0, 5.0]]) R = cholesky(cov) np.allclose(R.T @ R, cov) True

Solve (with rhs):

cholesky(np.eye(2), np.array([1.0, 2.0])).tolist() [1.0, 2.0] cholesky(np.array([[4.0, 0.0], [0.0, 9.0]]), np.array([8.0, 27.0])).tolist() [2.0, 3.0]

Source code in src/cvx/linalg/cholesky.py
def cholesky(cov: np.ndarray, rhs: np.ndarray | None = None) -> np.ndarray:
    """Compute the upper triangular Cholesky factor, or solve a linear system.

    When called without *rhs*, returns the upper triangular factor R such that
    R.T @ R = cov.  When *rhs* is given, solves ``cov @ x = rhs`` using the
    Cholesky decomposition for numerical stability, falling back to LU
    decomposition if *cov* is not positive-definite.

    Args:
        cov: A positive definite covariance matrix of shape (n, n).
        rhs: Optional right-hand side vector or matrix. When provided the
            system ``cov @ x = rhs`` is solved and *x* is returned.

    Returns:
        The upper triangular Cholesky factor R when *rhs* is ``None``, or the
        solution x to ``cov @ x = rhs`` otherwise.

    Raises:
        np.linalg.LinAlgError: When *rhs* is ``None`` and *cov* is not
            positive-definite, or when *rhs* is given and both Cholesky and
            LU-based solves fail.

    Example:
        Decomposition (no rhs):

        >>> import numpy as np
        >>> from cvx.linalg import cholesky
        >>> cov = np.array([[4.0, 2.0], [2.0, 5.0]])
        >>> R = cholesky(cov)
        >>> np.allclose(R.T @ R, cov)
        True

        Solve (with rhs):

        >>> cholesky(np.eye(2), np.array([1.0, 2.0])).tolist()
        [1.0, 2.0]
        >>> cholesky(np.array([[4.0, 0.0], [0.0, 9.0]]), np.array([8.0, 27.0])).tolist()
        [2.0, 3.0]
    """
    if rhs is None:
        return _cholesky(cov).transpose()
    try:
        upper = _cholesky(cov).transpose()
        return np.linalg.solve(upper, np.linalg.solve(upper.T, rhs))
    except np.linalg.LinAlgError:
        return np.linalg.solve(cov, rhs)

cvx.linalg.cholesky.is_positive_definite(matrix)

Return True if matrix is symmetric positive-definite, False otherwise.

The check is performed via an attempted Cholesky decomposition — the most numerically reliable way to test positive-definiteness for symmetric matrices.

This function is side-effect-free: it raises no exceptions and emits no warnings. It is suitable for use as a guard before passing a matrix to a linear solver.

Parameters:

Name Type Description Default
matrix ndarray

Square matrix to test.

required

Returns:

Type Description
bool

True if the matrix is positive-definite, False otherwise.

Example

import numpy as np from cvx.linalg import is_positive_definite is_positive_definite(np.eye(3)) True is_positive_definite(np.array([[1.0, 2.0], [2.0, 1.0]])) False is_positive_definite(np.array([[1.0, 0.5], [0.5, 1.0]])) True

Source code in src/cvx/linalg/cholesky.py
def is_positive_definite(matrix: np.ndarray) -> bool:
    """Return True if *matrix* is symmetric positive-definite, False otherwise.

    The check is performed via an attempted Cholesky decomposition — the most
    numerically reliable way to test positive-definiteness for symmetric matrices.

    This function is side-effect-free: it raises no exceptions and emits no
    warnings.  It is suitable for use as a guard before passing a matrix to a
    linear solver.

    Args:
        matrix: Square matrix to test.

    Returns:
        ``True`` if the matrix is positive-definite, ``False`` otherwise.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import is_positive_definite
        >>> is_positive_definite(np.eye(3))
        True
        >>> is_positive_definite(np.array([[1.0, 2.0], [2.0, 1.0]]))
        False
        >>> is_positive_definite(np.array([[1.0, 0.5], [0.5, 1.0]]))
        True
    """
    try:
        cholesky(matrix)
    except np.linalg.LinAlgError:
        return False
    else:
        return True

cvx.linalg.eigh.eigh(matrix)

Compute eigendecomposition of a real symmetric or Hermitian matrix.

Rows and columns with non-finite diagonal entries are excluded before decomposition.

Parameters:

Name Type Description Default
matrix ndarray

Square symmetric/Hermitian input matrix.

required

Returns:

Type Description
ndarray

A tuple (eigenvalues, eigenvectors) as returned by np.linalg.eigh

ndarray

on the valid submatrix. Eigenvalues are sorted in ascending order.

Source code in src/cvx/linalg/eigh.py
def eigh(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """Compute eigendecomposition of a real symmetric or Hermitian matrix.

    Rows and columns with non-finite diagonal entries are excluded before
    decomposition.

    Args:
        matrix: Square symmetric/Hermitian input matrix.

    Returns:
        A tuple ``(eigenvalues, eigenvectors)`` as returned by ``np.linalg.eigh``
        on the valid submatrix. Eigenvalues are sorted in ascending order.
    """
    _, submatrix = valid(matrix)
    return np.linalg.eigh(submatrix)

cvx.linalg.eigh.eigvalsh(matrix)

Return eigenvalues of a real symmetric or Hermitian matrix.

Rows and columns with non-finite diagonal entries are excluded before decomposition.

Parameters:

Name Type Description Default
matrix ndarray

Square symmetric/Hermitian input matrix.

required

Returns:

Type Description
ndarray

Eigenvalues of the valid submatrix in ascending order.

Source code in src/cvx/linalg/eigh.py
def eigvalsh(matrix: np.ndarray) -> np.ndarray:
    """Return eigenvalues of a real symmetric or Hermitian matrix.

    Rows and columns with non-finite diagonal entries are excluded before
    decomposition.

    Args:
        matrix: Square symmetric/Hermitian input matrix.

    Returns:
        Eigenvalues of the valid submatrix in ascending order.
    """
    eigenvalues, _ = eigh(matrix)
    return eigenvalues

cvx.linalg.eigvals.eigvals(matrix)

Return the eigenvalues of a square matrix.

This routine supports general (non-symmetric) square matrices and may return complex eigenvalues.

Parameters:

Name Type Description Default
matrix ndarray

Square input matrix.

required

Returns:

Type Description
ndarray

Eigenvalues of matrix as returned by numpy.linalg.eigvals.

Raises:

Type Description
NotAMatrixError

If matrix is not two-dimensional.

NonSquareMatrixError

If matrix is not square.

Source code in src/cvx/linalg/eigvals.py
def eigvals(matrix: np.ndarray) -> np.ndarray:
    """Return the eigenvalues of a square matrix.

    This routine supports general (non-symmetric) square matrices and may
    return complex eigenvalues.

    Args:
        matrix: Square input matrix.

    Returns:
        Eigenvalues of ``matrix`` as returned by ``numpy.linalg.eigvals``.

    Raises:
        NotAMatrixError: If *matrix* is not two-dimensional.
        NonSquareMatrixError: If *matrix* is not square.
    """
    if matrix.ndim != 2:
        raise NotAMatrixError(matrix.ndim)

    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    return np.linalg.eigvals(matrix)

cvx.linalg.qr.qr(matrix)

Compute the reduced QR decomposition of a 2-D matrix.

Parameters:

Name Type Description Default
matrix ndarray

Input matrix with shape (m, n).

required

Returns:

Type Description
tuple[ndarray, ndarray]

A tuple (Q, R) matching np.linalg.qr(matrix, mode="reduced").

Raises:

Type Description
DimensionMismatchError

If matrix is not two-dimensional.

Example

import numpy as np from cvx.linalg import qr q, r = qr(np.array([[1.0, 2.0], [3.0, 4.0]])) np.allclose(q @ r, np.array([[1.0, 2.0], [3.0, 4.0]])) True

Source code in src/cvx/linalg/qr.py
def qr(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """Compute the reduced QR decomposition of a 2-D matrix.

    Args:
        matrix: Input matrix with shape ``(m, n)``.

    Returns:
        A tuple ``(Q, R)`` matching ``np.linalg.qr(matrix, mode="reduced")``.

    Raises:
        DimensionMismatchError: If ``matrix`` is not two-dimensional.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import qr
        >>> q, r = qr(np.array([[1.0, 2.0], [3.0, 4.0]]))
        >>> np.allclose(q @ r, np.array([[1.0, 2.0], [3.0, 4.0]]))
        True
    """
    if matrix.ndim != 2:
        raise DimensionMismatchError(matrix.ndim, 2)

    return np.linalg.qr(matrix, mode="reduced")

cvx.linalg.svd.svd(matrix)

Compute the compact singular value decomposition of a matrix.

This is a thin wrapper around numpy.linalg.svd with full_matrices=False.

Parameters:

Name Type Description Default
matrix Matrix

Input matrix of shape (m, n).

required

Returns:

Type Description
tuple[Matrix, Matrix, Matrix]

Tuple (u, s, vt) such that matrix == u @ np.diag(s) @ vt.

Source code in src/cvx/linalg/svd.py
def svd(matrix: Matrix) -> tuple[Matrix, Matrix, Matrix]:
    """Compute the compact singular value decomposition of a matrix.

    This is a thin wrapper around ``numpy.linalg.svd`` with
    ``full_matrices=False``.

    Args:
        matrix: Input matrix of shape ``(m, n)``.

    Returns:
        Tuple ``(u, s, vt)`` such that ``matrix == u @ np.diag(s) @ vt``.
    """
    return np.linalg.svd(matrix, full_matrices=False)

cvx.linalg.pca.pca(returns, n_components=10)

Compute the first n principal components for a return matrix using SVD.

Parameters:

Name Type Description Default
returns Matrix

Array of asset returns with shape (n_samples, n_assets).

required
n_components int

Number of principal components to extract. Defaults to 10.

10

Returns:

Type Description
PCA

PCA named tuple containing: - explained_variance: Ratio of variance explained by each component - factors: Factor returns (scores) - exposure: Factor exposures (loadings) - cov: Factor covariance matrix - systematic: Returns explained by factors - idiosyncratic: Residual returns

Example

import numpy as np from cvx.linalg import pca np.random.seed(42) returns = np.random.randn(100, 10) result = pca(returns, n_components=3) bool(result.explained_variance[0] > result.explained_variance[1]) True factor_corr = np.corrcoef(result.factors.T) bool(np.allclose(factor_corr, np.eye(3), atol=0.1)) True VtV = result.exposure @ result.exposure.T bool(np.allclose(VtV, np.eye(3), atol=1e-10)) True all(result.explained_variance[i] >= result.explained_variance[i+1] ... for i in range(len(result.explained_variance)-1)) True reconstructed = result.factors @ result.exposure centered_systematic = result.systematic - returns.mean(axis=0) bool(np.allclose(reconstructed, centered_systematic, atol=1e-10)) True

Source code in src/cvx/linalg/pca.py
def pca(returns: Matrix, n_components: int = 10) -> PCA:
    """Compute the first n principal components for a return matrix using SVD.

    Args:
        returns: Array of asset returns with shape (n_samples, n_assets).
        n_components: Number of principal components to extract. Defaults to 10.

    Returns:
        PCA named tuple containing:
            - explained_variance: Ratio of variance explained by each component
            - factors: Factor returns (scores)
            - exposure: Factor exposures (loadings)
            - cov: Factor covariance matrix
            - systematic: Returns explained by factors
            - idiosyncratic: Residual returns

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import pca
        >>> np.random.seed(42)
        >>> returns = np.random.randn(100, 10)
        >>> result = pca(returns, n_components=3)
        >>> bool(result.explained_variance[0] > result.explained_variance[1])
        True
        >>> factor_corr = np.corrcoef(result.factors.T)
        >>> bool(np.allclose(factor_corr, np.eye(3), atol=0.1))
        True
        >>> VtV = result.exposure @ result.exposure.T
        >>> bool(np.allclose(VtV, np.eye(3), atol=1e-10))
        True
        >>> all(result.explained_variance[i] >= result.explained_variance[i+1]
        ...     for i in range(len(result.explained_variance)-1))
        True
        >>> reconstructed = result.factors @ result.exposure
        >>> centered_systematic = result.systematic - returns.mean(axis=0)
        >>> bool(np.allclose(reconstructed, centered_systematic, atol=1e-10))
        True

    """
    x_mean = returns.mean(axis=0)
    x_centered = returns - x_mean

    u, s_full, vt = svd(x_centered)

    u = u[:, :n_components]
    s = s_full[:n_components]
    vt = vt[:n_components, :]

    factors: Matrix = u * s
    exposure: Matrix = vt
    explained_variance: Matrix = (s**2) / np.sum(s_full**2)
    cov: Matrix = np.cov(factors.T)
    systematic: Matrix = factors @ vt + x_mean
    idiosyncratic: Matrix = x_centered - factors @ vt

    return PCA(
        explained_variance=explained_variance,
        factors=factors,
        exposure=exposure,
        cov=cov,
        systematic=systematic,
        idiosyncratic=idiosyncratic,
    )

Solvers

cvx.linalg.solve.solve(matrix, rhs, cond_threshold=_DEFAULT_COND_THRESHOLD)

Solve a linear system restricted to the valid submatrix.

Rows and columns with non-finite diagonal entries are excluded from the solve; the corresponding positions in the result are set to NaN. Cholesky decomposition is attempted first for numerical stability and falls back to LU decomposition for non-positive-definite matrices. When the condition number of the valid sub-matrix exceeds cond_threshold, an IllConditionedMatrixWarning is emitted.

Parameters:

Name Type Description Default
matrix ndarray

Square coefficient matrix.

required
rhs ndarray

Right-hand side vector.

required
cond_threshold float

Condition-number threshold above which a warning is emitted. Defaults to 1e12.

_DEFAULT_COND_THRESHOLD

Returns:

Type Description
ndarray

A solution vector with the same shape as rhs. Entries mapped to

ndarray

invalid rows or columns are returned as NaN.

Raises:

Type Description
NonSquareMatrixError

If the matrix is not square.

DimensionMismatchError

If rhs size does not match the matrix dimension.

SingularMatrixError

If the valid sub-matrix is singular.

Example

import numpy as np from cvx.linalg import solve solve(np.eye(2), np.array([1.0, 2.0])).tolist() [1.0, 2.0]

NaN-masked entries are skipped:

matrix = np.array([[4.0, 0.0], [0.0, np.nan]]) solve(matrix, np.array([8.0, 1.0])).tolist() [2.0, nan]

Source code in src/cvx/linalg/solve.py
def solve(
    matrix: np.ndarray,
    rhs: np.ndarray,
    cond_threshold: float = _DEFAULT_COND_THRESHOLD,
) -> np.ndarray:
    """Solve a linear system restricted to the valid submatrix.

    Rows and columns with non-finite diagonal entries are excluded from the
    solve; the corresponding positions in the result are set to NaN.  Cholesky
    decomposition is attempted first for numerical stability and falls back to
    LU decomposition for non-positive-definite matrices.  When the condition
    number of the valid sub-matrix exceeds *cond_threshold*, an
    ``IllConditionedMatrixWarning`` is emitted.

    Args:
        matrix: Square coefficient matrix.
        rhs: Right-hand side vector.
        cond_threshold: Condition-number threshold above which a warning is
            emitted. Defaults to ``1e12``.

    Returns:
        A solution vector with the same shape as ``rhs``. Entries mapped to
        invalid rows or columns are returned as ``NaN``.

    Raises:
        NonSquareMatrixError: If the matrix is not square.
        DimensionMismatchError: If ``rhs`` size does not match the matrix dimension.
        SingularMatrixError: If the valid sub-matrix is singular.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import solve
        >>> solve(np.eye(2), np.array([1.0, 2.0])).tolist()
        [1.0, 2.0]

        NaN-masked entries are skipped:

        >>> matrix = np.array([[4.0, 0.0], [0.0, np.nan]])
        >>> solve(matrix, np.array([8.0, 1.0])).tolist()
        [2.0, nan]
    """
    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    if rhs.size != matrix.shape[0]:
        raise DimensionMismatchError(rhs.size, matrix.shape[0])

    solution = np.nan * np.ones(rhs.size)
    mask, submatrix = valid(matrix)

    if mask.any():
        _check_and_warn_condition(submatrix, cond_threshold)
        try:
            solution[mask] = _cholesky(submatrix, rhs[mask])
        except np.linalg.LinAlgError as exc:
            raise SingularMatrixError(str(exc)) from exc

    return solution

cvx.linalg.lstsq.lstsq(matrix, rhs, cond_threshold=_DEFAULT_COND_THRESHOLD)

Solve an overdetermined or underdetermined system in the least-squares sense.

Rows where any entry in matrix or the corresponding entry in rhs is non-finite are excluded before solving. The returned solution vector always has length equal to the number of columns in matrix. When the effective condition number of the valid sub-matrix exceeds cond_threshold, an IllConditionedMatrixWarning is emitted.

Parameters:

Name Type Description Default
matrix ndarray

Coefficient matrix of shape (m, n).

required
rhs ndarray

Right-hand side vector of length m.

required
cond_threshold float

Condition-number threshold above which a warning is emitted. Defaults to 1e12.

_DEFAULT_COND_THRESHOLD

Returns:

Type Description
ndarray

A four-tuple (x, residuals, rank, sv) matching the convention of

ndarray

func:numpy.linalg.lstsq:

int
  • x — least-squares solution of shape (n,).
ndarray
  • residuals — sum of squared residuals; empty when the solution is not unique or all rows are invalid.
tuple[ndarray, ndarray, int, ndarray]
  • rank — effective rank of the valid sub-matrix.
tuple[ndarray, ndarray, int, ndarray]
  • sv — singular values of the valid sub-matrix in descending order.

Raises:

Type Description
DimensionMismatchError

If rhs length does not match the number of rows in matrix.

Example

import numpy as np from cvx.linalg import lstsq A = np.array([[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]]) b = np.array([6.0, 5.0, 7.0]) x, res, rank, sv = lstsq(A, b) int(rank) 2

NaN rows are silently dropped:

A_nan = np.array([[1.0, 1.0], [np.nan, 2.0], [1.0, 3.0]]) b_nan = np.array([6.0, 5.0, 7.0]) x2, _, rank2, _ = lstsq(A_nan, b_nan) int(rank2) 2

Source code in src/cvx/linalg/lstsq.py
def lstsq(
    matrix: np.ndarray,
    rhs: np.ndarray,
    cond_threshold: float = _DEFAULT_COND_THRESHOLD,
) -> tuple[np.ndarray, np.ndarray, int, np.ndarray]:
    """Solve an overdetermined or underdetermined system in the least-squares sense.

    Rows where any entry in *matrix* or the corresponding entry in *rhs* is
    non-finite are excluded before solving.  The returned solution vector
    always has length equal to the number of columns in *matrix*.  When the
    effective condition number of the valid sub-matrix exceeds
    *cond_threshold*, an ``IllConditionedMatrixWarning`` is emitted.

    Args:
        matrix: Coefficient matrix of shape ``(m, n)``.
        rhs: Right-hand side vector of length ``m``.
        cond_threshold: Condition-number threshold above which a warning is
            emitted. Defaults to ``1e12``.

    Returns:
        A four-tuple ``(x, residuals, rank, sv)`` matching the convention of
        :func:`numpy.linalg.lstsq`:

        - ``x`` — least-squares solution of shape ``(n,)``.
        - ``residuals`` — sum of squared residuals; empty when the solution is
          not unique or all rows are invalid.
        - ``rank`` — effective rank of the valid sub-matrix.
        - ``sv`` — singular values of the valid sub-matrix in descending order.

    Raises:
        DimensionMismatchError: If ``rhs`` length does not match the number of
            rows in *matrix*.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import lstsq
        >>> A = np.array([[1.0, 1.0], [1.0, 2.0], [1.0, 3.0]])
        >>> b = np.array([6.0, 5.0, 7.0])
        >>> x, res, rank, sv = lstsq(A, b)
        >>> int(rank)
        2

        NaN rows are silently dropped:

        >>> A_nan = np.array([[1.0, 1.0], [np.nan, 2.0], [1.0, 3.0]])
        >>> b_nan = np.array([6.0, 5.0, 7.0])
        >>> x2, _, rank2, _ = lstsq(A_nan, b_nan)
        >>> int(rank2)
        2
    """
    if rhs.shape[0] != matrix.shape[0]:
        raise DimensionMismatchError(rhs.shape[0], matrix.shape[0])

    n_cols = matrix.shape[1]

    # Filter rows that contain any non-finite value in matrix or rhs.
    row_mask = np.isfinite(matrix).all(axis=1) & np.isfinite(rhs)
    sub_matrix = matrix[row_mask]
    sub_rhs = rhs[row_mask]

    if sub_matrix.shape[0] == 0:
        return np.full(n_cols, np.nan), np.array([]), 0, np.array([])

    x, residuals, rank, sv = np.linalg.lstsq(sub_matrix, sub_rhs, rcond=None)

    # Compute condition number from singular values.
    if sv.size > 0 and sv[-1] > 0:
        cond = float(sv[0] / sv[-1])
    elif sv.size > 0:
        cond = float("inf")
    else:
        cond = 1.0

    if cond > cond_threshold:
        warnings.warn(
            f"Matrix condition number {cond:.3e} exceeds threshold {cond_threshold:.3e}; "
            "results may be numerically unreliable.",
            IllConditionedMatrixWarning,
            stacklevel=2,
        )

    return x, residuals, rank, sv

cvx.linalg.inv.inv(matrix, cond_threshold=_DEFAULT_COND_THRESHOLD)

Invert a matrix restricted to the valid submatrix.

Rows and columns with non-finite diagonal entries are excluded from the inversion; the corresponding rows and columns in the result are set to NaN. When the condition number of the valid sub-matrix exceeds cond_threshold, an IllConditionedMatrixWarning is emitted.

Parameters:

Name Type Description Default
matrix ndarray

Square matrix to invert.

required
cond_threshold float

Condition-number threshold above which a warning is emitted. Defaults to 1e12.

_DEFAULT_COND_THRESHOLD

Returns:

Type Description
ndarray

An inverted matrix with the same shape as matrix. Rows and columns

ndarray

mapped to invalid entries are returned as NaN.

Raises:

Type Description
NonSquareMatrixError

If the matrix is not square.

SingularMatrixError

If the valid sub-matrix is singular.

Example

import numpy as np from cvx.linalg import inv np.allclose(inv(np.eye(2)), np.eye(2)) True

NaN-masked entries are skipped:

matrix = np.array([[4.0, 0.0], [0.0, np.nan]]) result = inv(matrix) float(result[0, 0]) 0.25 bool(np.isnan(result[0, 1]) and np.isnan(result[1, 0]) and np.isnan(result[1, 1])) True

Source code in src/cvx/linalg/inv.py
def inv(
    matrix: np.ndarray,
    cond_threshold: float = _DEFAULT_COND_THRESHOLD,
) -> np.ndarray:
    """Invert a matrix restricted to the valid submatrix.

    Rows and columns with non-finite diagonal entries are excluded from the
    inversion; the corresponding rows and columns in the result are set to NaN.
    When the condition number of the valid sub-matrix exceeds *cond_threshold*,
    an ``IllConditionedMatrixWarning`` is emitted.

    Args:
        matrix: Square matrix to invert.
        cond_threshold: Condition-number threshold above which a warning is
            emitted. Defaults to ``1e12``.

    Returns:
        An inverted matrix with the same shape as *matrix*. Rows and columns
        mapped to invalid entries are returned as ``NaN``.

    Raises:
        NonSquareMatrixError: If the matrix is not square.
        SingularMatrixError: If the valid sub-matrix is singular.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import inv
        >>> np.allclose(inv(np.eye(2)), np.eye(2))
        True

        NaN-masked entries are skipped:

        >>> matrix = np.array([[4.0, 0.0], [0.0, np.nan]])
        >>> result = inv(matrix)
        >>> float(result[0, 0])
        0.25
        >>> bool(np.isnan(result[0, 1]) and np.isnan(result[1, 0]) and np.isnan(result[1, 1]))
        True
    """
    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    n = matrix.shape[0]
    result = np.full((n, n), np.nan)
    mask, submatrix = valid(matrix)

    if mask.any():
        _check_and_warn_condition(submatrix, cond_threshold)
        try:
            sub_inv = np.linalg.inv(submatrix)
        except np.linalg.LinAlgError as exc:
            raise SingularMatrixError(str(exc)) from exc

        idx = np.where(mask)[0]
        result[np.ix_(idx, idx)] = sub_inv

    return result

Norms & Metrics

cvx.linalg.norm.norm(x, ord=None)

Compute the norm of a vector or matrix, ignoring non-finite entries.

Non-finite entries (NaN, inf) are treated as zero before computing the norm, so they contribute nothing to the result. Supports all ord values accepted by np.linalg.norm.

Parameters:

Name Type Description Default
x ndarray

Input array (1-D vector or 2-D matrix).

required
ord int | float | Literal['fro', 'nuc'] | None

Order of the norm. See np.linalg.norm for valid values. Common choices: None (default 2-norm for vectors, Frobenius for matrices), 1, 2, np.inf, 'fro', 'nuc'.

None

Returns:

Type Description
float

The norm as a float.

Example

import numpy as np from cvx.linalg import norm norm(np.array([3.0, np.nan, 4.0])) 5.0 norm(np.array([[1.0, np.nan], [np.nan, 1.0]]), ord='fro') 1.4142135623730951

Source code in src/cvx/linalg/norm.py
def norm(
    x: np.ndarray,
    ord: int | float | Literal["fro", "nuc"] | None = None,
) -> float:
    """Compute the norm of a vector or matrix, ignoring non-finite entries.

    Non-finite entries (NaN, inf) are treated as zero before computing the norm,
    so they contribute nothing to the result. Supports all ``ord`` values accepted
    by ``np.linalg.norm``.

    Args:
        x: Input array (1-D vector or 2-D matrix).
        ord: Order of the norm. See ``np.linalg.norm`` for valid values.
            Common choices: ``None`` (default 2-norm for vectors, Frobenius for
            matrices), ``1``, ``2``, ``np.inf``, ``'fro'``, ``'nuc'``.

    Returns:
        The norm as a float.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import norm
        >>> norm(np.array([3.0, np.nan, 4.0]))
        5.0
        >>> norm(np.array([[1.0, np.nan], [np.nan, 1.0]]), ord='fro')
        1.4142135623730951
    """
    return float(np.linalg.norm(np.where(np.isfinite(x), x, 0.0), ord=ord))

cvx.linalg.norm.a_norm(vector, matrix=None)

Calculate the generalized norm of a vector with respect to a matrix.

Parameters:

Name Type Description Default
vector ndarray

The input vector.

required
matrix ndarray | None

Optional square matrix defining the quadratic form.

None

Returns:

Type Description
float

The Euclidean norm of the finite vector entries, or sqrt(v.T @ A @ v)

float

after dropping rows and columns whose diagonal entries are not finite.

Raises:

Type Description
NonSquareMatrixError

If the matrix is not square.

DimensionMismatchError

If the vector length does not match the matrix dimension.

Example

import numpy as np from cvx.linalg import a_norm a_norm(np.array([3.0, 4.0])) 5.0

Source code in src/cvx/linalg/norm.py
def a_norm(vector: np.ndarray, matrix: np.ndarray | None = None) -> float:
    """Calculate the generalized norm of a vector with respect to a matrix.

    Args:
        vector: The input vector.
        matrix: Optional square matrix defining the quadratic form.

    Returns:
        The Euclidean norm of the finite vector entries, or ``sqrt(v.T @ A @ v)``
        after dropping rows and columns whose diagonal entries are not finite.

    Raises:
        NonSquareMatrixError: If the matrix is not square.
        DimensionMismatchError: If the vector length does not match the matrix dimension.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import a_norm
        >>> a_norm(np.array([3.0, 4.0]))
        5.0
    """
    if matrix is None:
        return float(np.linalg.norm(vector[np.isfinite(vector)], 2))

    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    if vector.size != matrix.shape[0]:
        raise DimensionMismatchError(vector.size, matrix.shape[0])

    mask, submatrix = valid(matrix)
    if mask.any():
        filtered_vector = vector[mask]
        return float(np.sqrt(filtered_vector @ submatrix @ filtered_vector))

    return float("nan")

cvx.linalg.norm.inv_a_norm(vector, matrix=None, cond_threshold=_DEFAULT_COND_THRESHOLD)

Calculate the inverse A-norm of a vector using an optional matrix.

If matrix is None, returns the Euclidean norm of finite entries. Otherwise computes sqrt(v.T @ A^{-1} @ v) on the valid submatrix, attempting Cholesky decomposition first for numerical stability and falling back to LU decomposition for non-positive-definite matrices. When the condition number of the valid sub-matrix exceeds cond_threshold, an IllConditionedMatrixWarning is emitted.

Parameters:

Name Type Description Default
vector ndarray

The input vector.

required
matrix ndarray | None

Optional square matrix defining the quadratic form.

None
cond_threshold float

Condition-number threshold above which a warning is emitted. Defaults to 1e12.

_DEFAULT_COND_THRESHOLD

Returns:

Type Description
float

The Euclidean norm of the finite vector entries, or

float

sqrt(v.T @ A^{-1} @ v) after dropping rows and columns whose

float

diagonal entries are not finite. Returns nan when no valid entries

float

exist.

Raises:

Type Description
NonSquareMatrixError

If the matrix is not square.

DimensionMismatchError

If the vector length does not match the matrix dimension.

SingularMatrixError

If the valid sub-matrix is singular.

Example

import numpy as np from cvx.linalg import inv_a_norm inv_a_norm(np.array([3.0, 4.0])) 5.0

Source code in src/cvx/linalg/norm.py
def inv_a_norm(
    vector: np.ndarray,
    matrix: np.ndarray | None = None,
    cond_threshold: float = _DEFAULT_COND_THRESHOLD,
) -> float:
    """Calculate the inverse A-norm of a vector using an optional matrix.

    If ``matrix`` is ``None``, returns the Euclidean norm of finite entries.
    Otherwise computes ``sqrt(v.T @ A^{-1} @ v)`` on the valid submatrix,
    attempting Cholesky decomposition first for numerical stability and
    falling back to LU decomposition for non-positive-definite matrices.
    When the condition number of the valid sub-matrix exceeds *cond_threshold*,
    an ``IllConditionedMatrixWarning`` is emitted.

    Args:
        vector: The input vector.
        matrix: Optional square matrix defining the quadratic form.
        cond_threshold: Condition-number threshold above which a warning is
            emitted. Defaults to ``1e12``.

    Returns:
        The Euclidean norm of the finite vector entries, or
        ``sqrt(v.T @ A^{-1} @ v)`` after dropping rows and columns whose
        diagonal entries are not finite. Returns ``nan`` when no valid entries
        exist.

    Raises:
        NonSquareMatrixError: If the matrix is not square.
        DimensionMismatchError: If the vector length does not match the matrix dimension.
        SingularMatrixError: If the valid sub-matrix is singular.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import inv_a_norm
        >>> inv_a_norm(np.array([3.0, 4.0]))
        5.0
    """
    if matrix is None:
        return float(np.linalg.norm(vector[np.isfinite(vector)], 2))

    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    if vector.size != matrix.shape[0]:
        raise DimensionMismatchError(vector.size, matrix.shape[0])

    mask, submatrix = valid(matrix)
    if mask.any():
        _check_and_warn_condition(submatrix, cond_threshold)
        filtered_vector = vector[mask]
        try:
            solved = _cholesky(submatrix, filtered_vector)
        except np.linalg.LinAlgError as exc:
            raise SingularMatrixError(str(exc)) from exc
        return float(np.sqrt(np.dot(filtered_vector, solved)))

    return float("nan")

cvx.linalg.exceptions.cond(matrix, p=None)

Return the condition number of a matrix.

Returns nan if the matrix contains any non-finite (NaN or inf) entries. Otherwise delegates to :func:numpy.linalg.cond.

Parameters:

Name Type Description Default
matrix ndarray

Input matrix.

required
p int | float | Literal['fro', 'nuc'] | None

Order of the norm used to compute the condition number. Accepts the same values as :func:numpy.linalg.cond (None, 1, -1, 2, -2, numpy.inf, -numpy.inf, 'fro'). Defaults to None which corresponds to the 2-norm (largest singular value divided by the smallest).

None

Returns:

Type Description
float

The condition number as a float, or nan when the matrix

float

contains non-finite entries.

Examples:

>>> import numpy as np
>>> cond(np.eye(3))
1.0
>>> import math
>>> math.isnan(cond(np.array([[float('nan'), 1.0], [1.0, 2.0]])))
True
>>> cond(np.diag([1.0, 1e10]), p=1)
10000000000.0
Source code in src/cvx/linalg/exceptions.py
def cond(matrix: np.ndarray, p: int | float | Literal["fro", "nuc"] | None = None) -> float:
    """Return the condition number of a matrix.

    Returns ``nan`` if the matrix contains any non-finite (NaN or inf) entries.
    Otherwise delegates to :func:`numpy.linalg.cond`.

    Args:
        matrix: Input matrix.
        p: Order of the norm used to compute the condition number.
            Accepts the same values as :func:`numpy.linalg.cond`
            (``None``, ``1``, ``-1``, ``2``, ``-2``, ``numpy.inf``,
            ``-numpy.inf``, ``'fro'``).  Defaults to ``None`` which
            corresponds to the 2-norm (largest singular value divided by
            the smallest).

    Returns:
        The condition number as a ``float``, or ``nan`` when the matrix
        contains non-finite entries.

    Examples:
        >>> import numpy as np
        >>> cond(np.eye(3))
        1.0
        >>> import math
        >>> math.isnan(cond(np.array([[float('nan'), 1.0], [1.0, 2.0]])))
        True
        >>> cond(np.diag([1.0, 1e10]), p=1)
        10000000000.0
    """
    if not np.all(np.isfinite(matrix)):
        return float("nan")
    return float(np.linalg.cond(matrix, p=p))

cvx.linalg.det.det(matrix, cond_threshold=_DEFAULT_COND_THRESHOLD)

Return the determinant of a square matrix.

Rows and columns with non-finite diagonal entries are excluded before the computation; when no valid rows or columns remain the function returns nan. When the condition number of the valid sub-matrix exceeds cond_threshold, an IllConditionedMatrixWarning is emitted.

Parameters:

Name Type Description Default
matrix ndarray

Square input matrix.

required
cond_threshold float

Condition-number threshold above which a warning is emitted. Defaults to 1e12.

_DEFAULT_COND_THRESHOLD

Returns:

Type Description
float

The determinant of the valid sub-matrix, or nan when no valid

float

entries exist.

Raises:

Type Description
NonSquareMatrixError

If the matrix is not square.

Example

import numpy as np from cvx.linalg import det det(np.eye(3)) 1.0

NaN-masked entries are skipped:

matrix = np.array([[2.0, 0.0], [0.0, np.nan]]) det(matrix) 2.0

Source code in src/cvx/linalg/det.py
def det(
    matrix: np.ndarray,
    cond_threshold: float = _DEFAULT_COND_THRESHOLD,
) -> float:
    """Return the determinant of a square matrix.

    Rows and columns with non-finite diagonal entries are excluded before the
    computation; when no valid rows or columns remain the function returns
    ``nan``.  When the condition number of the valid sub-matrix exceeds
    *cond_threshold*, an ``IllConditionedMatrixWarning`` is emitted.

    Args:
        matrix: Square input matrix.
        cond_threshold: Condition-number threshold above which a warning is
            emitted. Defaults to ``1e12``.

    Returns:
        The determinant of the valid sub-matrix, or ``nan`` when no valid
        entries exist.

    Raises:
        NonSquareMatrixError: If the matrix is not square.

    Example:
        >>> import numpy as np
        >>> from cvx.linalg import det
        >>> det(np.eye(3))
        1.0

        NaN-masked entries are skipped:

        >>> matrix = np.array([[2.0, 0.0], [0.0, np.nan]])
        >>> det(matrix)
        2.0
    """
    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    mask, submatrix = valid(matrix)

    if not mask.any():
        return _SENTINEL

    _check_and_warn_condition(submatrix, cond_threshold)
    return float(np.linalg.det(submatrix))

Covariance

cvx.linalg.ewm_cov.ewm_covariance(data, assets, index_col, window=30, is_halflife=False, warmup=0)

Compute the exponentially weighted covariance matrix of returns.

EWM covariance uses the identity Cov(X, Y) = EWM(X*Y) - EWM(X)*EWM(Y) applied to the common non-null observations of each pair, which is equivalent to pandas.DataFrame.ewm(span).cov(bias=True).

Each date is included in the result as long as at least one matrix entry is non-NaN. Cells involving a late-starting asset are NaN until that asset has enough observations; the date is never dropped on account of a single asset being unavailable. Dates where every cell is NaN (before the warmup period is met for any asset) are omitted.

Parameters:

Name Type Description Default
data DataFrame

Polars DataFrame containing the index column and asset columns.

required
assets list[str]

Ordered list of asset column names.

required
index_col str

Name of the index (e.g. date) column in data.

required
window int

Span (default) or half-life (when is_halflife is True) of the exponential decay. Defaults to 30.

30
is_halflife bool

When True window is interpreted as the half-life; otherwise it is the EWMA span. Defaults to False.

False
warmup int

Minimum number of common observations required before a pair's cell is non-NaN. Defaults to 0 (cells are non-NaN from the first shared observation).

0

Returns:

Type Description
dict[Hashable, ndarray]

Dictionary keyed by index value (date or integer) mapping to

dict[Hashable, ndarray]

a square symmetric numpy.ndarray of shape (n, n)

dict[Hashable, ndarray]

where n is the number of assets. Row/column order

dict[Hashable, ndarray]

matches assets. Unavailable cells are NaN.

Source code in src/cvx/linalg/ewm_cov.py
def ewm_covariance(
    data: pl.DataFrame,
    assets: list[str],
    index_col: str,
    window: int = 30,
    is_halflife: bool = False,
    warmup: int = 0,
) -> dict[Hashable, np.ndarray]:
    """Compute the exponentially weighted covariance matrix of returns.

    EWM covariance uses the identity
    ``Cov(X, Y) = EWM(X*Y) - EWM(X)*EWM(Y)`` applied to the
    *common non-null observations* of each pair, which is equivalent
    to ``pandas.DataFrame.ewm(span).cov(bias=True)``.

    Each date is included in the result as long as at least one
    matrix entry is non-NaN.  Cells involving a late-starting asset
    are ``NaN`` until that asset has enough observations; the date is
    never dropped on account of a single asset being unavailable.
    Dates where every cell is NaN (before the warmup period is met
    for any asset) are omitted.

    Args:
        data: Polars DataFrame containing the index column and asset columns.
        assets: Ordered list of asset column names.
        index_col: Name of the index (e.g. date) column in *data*.
        window: Span (default) or half-life (when *is_halflife* is
            ``True``) of the exponential decay.  Defaults to ``30``.
        is_halflife: When ``True`` *window* is interpreted as the
            half-life; otherwise it is the EWMA span.  Defaults to
            ``False``.
        warmup: Minimum number of common observations required before
            a pair's cell is non-NaN.  Defaults to ``0`` (cells are
            non-NaN from the first shared observation).

    Returns:
        Dictionary keyed by index value (date or integer) mapping to
        a square symmetric ``numpy.ndarray`` of shape ``(n, n)``
        where ``n`` is the number of assets.  Row/column order
        matches *assets*.  Unavailable cells are ``NaN``.

    """
    if isinstance(warmup, bool) or not isinstance(warmup, int):
        raise TypeError
    if warmup < 0:
        raise NegativeWarmupError

    n = len(assets)
    min_samples = 1 if warmup == 0 else warmup

    def _ewm(expr: pl.Expr) -> pl.Expr:
        """Apply EWM mean with the configured span or half-life."""
        if is_halflife:
            return expr.ewm_mean(half_life=window, min_samples=min_samples)
        return expr.ewm_mean(span=window, min_samples=min_samples)

    cov_exprs = [
        (
            _ewm(pl.col(a) * pl.col(b))
            - _ewm(pl.when(pl.col(b).is_null()).then(None).otherwise(pl.col(a)))
            * _ewm(pl.when(pl.col(a).is_null()).then(None).otherwise(pl.col(b)))
        ).alias(f"{a}_{b}")
        for i, a in enumerate(assets)
        for b in assets[i:]
    ]

    pair_df = data.with_columns(cov_exprs).drop(assets)
    all_keys = pair_df[index_col].to_list()
    pair_arr = pair_df.drop(index_col).to_numpy()

    ii, jj = np.triu_indices(n)
    cube = np.full((len(all_keys), n, n), np.nan)
    cube[:, ii, jj] = pair_arr
    cube[:, jj, ii] = pair_arr

    has_data = ~np.all(np.isnan(cube), axis=(1, 2))
    return {k: cube[t] for t, k in enumerate(all_keys) if has_data[t]}

cvx.linalg.rand_cov.rand_cov(n, seed=None)

Construct a random positive semi-definite covariance matrix of size n x n.

The matrix is constructed as A^T @ A where A is a random n x n matrix with elements drawn from a standard normal distribution. This ensures the result is symmetric and positive semi-definite.

Parameters:

Name Type Description Default
n int

Size of the covariance matrix (n x n).

required
seed int | None

Random seed for reproducibility. If None, uses the current random state.

None

Returns:

Type Description
ndarray

A random positive semi-definite n x n covariance matrix.

Example

Generate a reproducible random covariance matrix:

import numpy as np from cvx.linalg import rand_cov cov1 = rand_cov(3, seed=42) cov2 = rand_cov(3, seed=42) np.allclose(cov1, cov2) True

Verify positive definiteness via Cholesky decomposition:

cov = rand_cov(5, seed=123)

If Cholesky succeeds without error, matrix is positive definite

L = np.linalg.cholesky(cov) bool(np.allclose(L @ L.T, cov)) True

Eigenvalue verification:

cov = rand_cov(3, seed=99) eigenvalues = np.linalg.eigvalsh(cov)

All eigenvalues should be positive for PD matrix

bool(np.all(eigenvalues > 0)) True

Different seeds produce different matrices:

cov1 = rand_cov(3, seed=1) cov2 = rand_cov(3, seed=2) bool(not np.allclose(cov1, cov2)) True

Without seed, consecutive calls may differ (random state):

These may or may not be equal depending on random state

cov_a = rand_cov(2, seed=None) cov_b = rand_cov(2, seed=None) cov_a.shape == cov_b.shape == (2, 2) True

Note

The generated matrix is guaranteed to be positive semi-definite because it is constructed as A^T @ A. In practice, it will typically be positive definite (all eigenvalues strictly positive) unless n is very large.

Source code in src/cvx/linalg/rand_cov.py
def rand_cov(n: int, seed: int | None = None) -> np.ndarray:
    """Construct a random positive semi-definite covariance matrix of size n x n.

    The matrix is constructed as A^T @ A where A is a random n x n matrix with
    elements drawn from a standard normal distribution. This ensures the result
    is symmetric and positive semi-definite.

    Args:
        n: Size of the covariance matrix (n x n).
        seed: Random seed for reproducibility. If None, uses the current
            random state.

    Returns:
        A random positive semi-definite n x n covariance matrix.

    Example:
        Generate a reproducible random covariance matrix:

        >>> import numpy as np
        >>> from cvx.linalg import rand_cov
        >>> cov1 = rand_cov(3, seed=42)
        >>> cov2 = rand_cov(3, seed=42)
        >>> np.allclose(cov1, cov2)
        True

        Verify positive definiteness via Cholesky decomposition:

        >>> cov = rand_cov(5, seed=123)
        >>> # If Cholesky succeeds without error, matrix is positive definite
        >>> L = np.linalg.cholesky(cov)
        >>> bool(np.allclose(L @ L.T, cov))
        True

        Eigenvalue verification:

        >>> cov = rand_cov(3, seed=99)
        >>> eigenvalues = np.linalg.eigvalsh(cov)
        >>> # All eigenvalues should be positive for PD matrix
        >>> bool(np.all(eigenvalues > 0))
        True

        Different seeds produce different matrices:

        >>> cov1 = rand_cov(3, seed=1)
        >>> cov2 = rand_cov(3, seed=2)
        >>> bool(not np.allclose(cov1, cov2))
        True

        Without seed, consecutive calls may differ (random state):

        >>> # These may or may not be equal depending on random state
        >>> cov_a = rand_cov(2, seed=None)
        >>> cov_b = rand_cov(2, seed=None)
        >>> cov_a.shape == cov_b.shape == (2, 2)
        True

    Note:
        The generated matrix is guaranteed to be positive semi-definite because
        it is constructed as A^T @ A. In practice, it will typically be positive
        definite (all eigenvalues strictly positive) unless n is very large.

    """
    rng = np.random.default_rng(seed)
    a = rng.standard_normal((n, n))
    return np.transpose(a) @ a

Validation

cvx.linalg.valid.valid(matrix)

Extract the valid subset of a matrix by removing rows/columns with non-finite values.

This function identifies rows and columns in a square matrix that contain non-finite values (NaN or infinity) on the diagonal and removes them, returning both the indicator vector and the resulting valid submatrix.

This is useful when working with covariance matrices where some assets may have missing or invalid data.

Parameters:

Name Type Description Default
matrix ndarray

A square n x n matrix to be validated. Typically a covariance or correlation matrix.

required

Returns:

Type Description
tuple[ndarray, ndarray]

A tuple containing: - v: Boolean vector of shape (n,) indicating which rows/columns are valid (True for valid, False for invalid). - submatrix: The valid submatrix with invalid rows/columns removed. Shape is (k, k) where k is the number of True values in v.

Raises:

Type Description
NonSquareMatrixError

If the input matrix is not square (n x n).

Example

Basic usage with a covariance matrix:

import numpy as np from cvx.linalg import valid

Create a 3x3 matrix with one invalid entry

cov = np.array([[1.0, 0.5, 0.2], ... [0.5, np.nan, 0.3], ... [0.2, 0.3, 1.0]]) v, submatrix = valid(cov) v array([ True, False, True]) submatrix array([[1. , 0.2], [0.2, 1. ]])

Handling a fully valid matrix:

cov = np.array([[1.0, 0.5], [0.5, 1.0]]) v, submatrix = valid(cov) v array([ True, True]) np.allclose(submatrix, cov) True

Handling infinity values:

cov = np.array([[1.0, 0.5, 0.2], ... [0.5, np.inf, 0.3], ... [0.2, 0.3, 1.0]]) v, submatrix = valid(cov) v array([ True, False, True]) submatrix array([[1. , 0.2], [0.2, 1. ]])

Multiple invalid entries:

cov = np.array([[np.nan, 0.1, 0.2, 0.3], ... [0.1, 2.0, 0.4, 0.5], ... [0.2, 0.4, np.nan, 0.6], ... [0.3, 0.5, 0.6, 3.0]]) v, submatrix = valid(cov) v array([False, True, False, True]) submatrix.shape (2, 2) submatrix array([[2. , 0.5], [0.5, 3. ]])

Non-square matrix raises NonSquareMatrixError:

try: ... valid(np.array([[1, 2, 3], [4, 5, 6]])) ... except NonSquareMatrixError: ... print("Caught NonSquareMatrixError for non-square matrix") Caught NonSquareMatrixError for non-square matrix

Note

The function checks only the diagonal elements for validity. It assumes that if the diagonal is finite, the entire row/column is valid. This is a common assumption for covariance matrices.

Source code in src/cvx/linalg/valid.py
def valid(matrix: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """Extract the valid subset of a matrix by removing rows/columns with non-finite values.

    This function identifies rows and columns in a square matrix that contain
    non-finite values (NaN or infinity) on the diagonal and removes them,
    returning both the indicator vector and the resulting valid submatrix.

    This is useful when working with covariance matrices where some assets
    may have missing or invalid data.

    Args:
        matrix: A square n x n matrix to be validated. Typically a covariance
            or correlation matrix.

    Returns:
        A tuple containing:
            - v: Boolean vector of shape (n,) indicating which rows/columns are
              valid (True for valid, False for invalid).
            - submatrix: The valid submatrix with invalid rows/columns removed.
              Shape is (k, k) where k is the number of True values in v.

    Raises:
        NonSquareMatrixError: If the input matrix is not square (n x n).

    Example:
        Basic usage with a covariance matrix:

        >>> import numpy as np
        >>> from cvx.linalg import valid
        >>> # Create a 3x3 matrix with one invalid entry
        >>> cov = np.array([[1.0, 0.5, 0.2],
        ...                 [0.5, np.nan, 0.3],
        ...                 [0.2, 0.3, 1.0]])
        >>> v, submatrix = valid(cov)
        >>> v
        array([ True, False,  True])
        >>> submatrix
        array([[1. , 0.2],
               [0.2, 1. ]])

        Handling a fully valid matrix:

        >>> cov = np.array([[1.0, 0.5], [0.5, 1.0]])
        >>> v, submatrix = valid(cov)
        >>> v
        array([ True,  True])
        >>> np.allclose(submatrix, cov)
        True

        Handling infinity values:

        >>> cov = np.array([[1.0, 0.5, 0.2],
        ...                 [0.5, np.inf, 0.3],
        ...                 [0.2, 0.3, 1.0]])
        >>> v, submatrix = valid(cov)
        >>> v
        array([ True, False,  True])
        >>> submatrix
        array([[1. , 0.2],
               [0.2, 1. ]])

        Multiple invalid entries:

        >>> cov = np.array([[np.nan, 0.1, 0.2, 0.3],
        ...                 [0.1, 2.0, 0.4, 0.5],
        ...                 [0.2, 0.4, np.nan, 0.6],
        ...                 [0.3, 0.5, 0.6, 3.0]])
        >>> v, submatrix = valid(cov)
        >>> v
        array([False,  True, False,  True])
        >>> submatrix.shape
        (2, 2)
        >>> submatrix
        array([[2. , 0.5],
               [0.5, 3. ]])

        Non-square matrix raises NonSquareMatrixError:

        >>> try:
        ...     valid(np.array([[1, 2, 3], [4, 5, 6]]))
        ... except NonSquareMatrixError:
        ...     print("Caught NonSquareMatrixError for non-square matrix")
        Caught NonSquareMatrixError for non-square matrix

    Note:
        The function checks only the diagonal elements for validity. It assumes
        that if the diagonal is finite, the entire row/column is valid. This is
        a common assumption for covariance matrices.

    """
    if matrix.shape[0] != matrix.shape[1]:
        raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])

    v = np.isfinite(np.diag(matrix))
    return v, matrix[:, v][v]

Exceptions & Warnings

cvx.linalg.exceptions.SingularMatrixError

Bases: ValueError

Raised when a matrix is (numerically) singular and cannot be inverted.

Parameters:

Name Type Description Default
detail str

Optional extra detail string to append to the message.

''

Examples:

>>> raise SingularMatrixError()
Traceback (most recent call last):
    ...
cvx.linalg.exceptions.SingularMatrixError: Matrix is singular and cannot be solved.
Source code in src/cvx/linalg/exceptions.py
class SingularMatrixError(ValueError):
    """Raised when a matrix is (numerically) singular and cannot be inverted.

    Args:
        detail: Optional extra detail string to append to the message.

    Examples:
        >>> raise SingularMatrixError()
        Traceback (most recent call last):
            ...
        cvx.linalg.exceptions.SingularMatrixError: Matrix is singular and cannot be solved.
    """

    def __init__(self, detail: str = "") -> None:
        """Initialize with an optional extra detail string."""
        msg = "Matrix is singular and cannot be solved."
        if detail:
            msg = f"{msg} {detail}"
        super().__init__(msg)

__init__(detail='')

Initialize with an optional extra detail string.

Source code in src/cvx/linalg/exceptions.py
def __init__(self, detail: str = "") -> None:
    """Initialize with an optional extra detail string."""
    msg = "Matrix is singular and cannot be solved."
    if detail:
        msg = f"{msg} {detail}"
    super().__init__(msg)

cvx.linalg.exceptions.IllConditionedMatrixWarning

Bases: UserWarning

Emitted when a matrix condition number exceeds a configurable threshold.

Examples:

>>> import warnings
>>> warnings.warn("condition number 1e13", IllConditionedMatrixWarning)
Source code in src/cvx/linalg/exceptions.py
class IllConditionedMatrixWarning(UserWarning):
    """Emitted when a matrix condition number exceeds a configurable threshold.

    Examples:
        >>> import warnings
        >>> warnings.warn("condition number 1e13", IllConditionedMatrixWarning)
    """

cvx.linalg.exceptions.DimensionMismatchError

Bases: ValueError

Raised when vector and matrix dimensions are incompatible.

Parameters:

Name Type Description Default
vector_size int

Length of the offending vector.

required
matrix_size int

Expected dimension inferred from the matrix.

required

Examples:

>>> raise DimensionMismatchError(3, 2)
Traceback (most recent call last):
    ...
cvx.linalg.exceptions.DimensionMismatchError: Vector length 3 does not match matrix dimension 2.
Source code in src/cvx/linalg/exceptions.py
class DimensionMismatchError(ValueError):
    """Raised when vector and matrix dimensions are incompatible.

    Args:
        vector_size: Length of the offending vector.
        matrix_size: Expected dimension inferred from the matrix.

    Examples:
        >>> raise DimensionMismatchError(3, 2)
        Traceback (most recent call last):
            ...
        cvx.linalg.exceptions.DimensionMismatchError: Vector length 3 does not match matrix dimension 2.
    """

    def __init__(self, vector_size: int, matrix_size: int) -> None:
        """Initialize with the offending vector and matrix sizes."""
        super().__init__(f"Vector length {vector_size} does not match matrix dimension {matrix_size}.")
        self.vector_size = vector_size
        self.matrix_size = matrix_size

__init__(vector_size, matrix_size)

Initialize with the offending vector and matrix sizes.

Source code in src/cvx/linalg/exceptions.py
def __init__(self, vector_size: int, matrix_size: int) -> None:
    """Initialize with the offending vector and matrix sizes."""
    super().__init__(f"Vector length {vector_size} does not match matrix dimension {matrix_size}.")
    self.vector_size = vector_size
    self.matrix_size = matrix_size

cvx.linalg.exceptions.NonSquareMatrixError

Bases: ValueError

Raised when a square matrix is required but the input is not square.

Parameters:

Name Type Description Default
rows int

Number of rows in the offending matrix.

required
cols int

Number of columns in the offending matrix.

required

Examples:

>>> raise NonSquareMatrixError(3, 2)
Traceback (most recent call last):
    ...
cvx.linalg.exceptions.NonSquareMatrixError: Matrix must be square, got shape (3, 2).
Source code in src/cvx/linalg/exceptions.py
class NonSquareMatrixError(ValueError):
    """Raised when a square matrix is required but the input is not square.

    Args:
        rows: Number of rows in the offending matrix.
        cols: Number of columns in the offending matrix.

    Examples:
        >>> raise NonSquareMatrixError(3, 2)
        Traceback (most recent call last):
            ...
        cvx.linalg.exceptions.NonSquareMatrixError: Matrix must be square, got shape (3, 2).
    """

    def __init__(self, rows: int, cols: int) -> None:
        """Initialize with the offending matrix shape."""
        super().__init__(f"Matrix must be square, got shape ({rows}, {cols}).")
        self.rows = rows
        self.cols = cols

__init__(rows, cols)

Initialize with the offending matrix shape.

Source code in src/cvx/linalg/exceptions.py
def __init__(self, rows: int, cols: int) -> None:
    """Initialize with the offending matrix shape."""
    super().__init__(f"Matrix must be square, got shape ({rows}, {cols}).")
    self.rows = rows
    self.cols = cols

cvx.linalg.exceptions.NotAMatrixError

Bases: TypeError

Raised when a 2-D matrix is required but the input has a different number of dimensions.

Parameters:

Name Type Description Default
ndim int

Actual number of dimensions of the offending array.

required

Examples:

>>> raise NotAMatrixError(3)
Traceback (most recent call last):
    ...
cvx.linalg.exceptions.NotAMatrixError: eigvals() expected a 2-D matrix, got 3-D input.
Source code in src/cvx/linalg/exceptions.py
class NotAMatrixError(TypeError):
    """Raised when a 2-D matrix is required but the input has a different number of dimensions.

    Args:
        ndim: Actual number of dimensions of the offending array.

    Examples:
        >>> raise NotAMatrixError(3)
        Traceback (most recent call last):
            ...
        cvx.linalg.exceptions.NotAMatrixError: eigvals() expected a 2-D matrix, got 3-D input.
    """

    def __init__(self, ndim: int) -> None:
        """Initialize with the actual number of dimensions."""
        super().__init__(f"eigvals() expected a 2-D matrix, got {ndim}-D input.")
        self.ndim = ndim

__init__(ndim)

Initialize with the actual number of dimensions.

Source code in src/cvx/linalg/exceptions.py
def __init__(self, ndim: int) -> None:
    """Initialize with the actual number of dimensions."""
    super().__init__(f"eigvals() expected a 2-D matrix, got {ndim}-D input.")
    self.ndim = ndim

cvx.linalg.ewm_cov.NegativeWarmupError

Bases: ValueError

Raised when warmup is a negative integer.

Source code in src/cvx/linalg/ewm_cov.py
class NegativeWarmupError(ValueError):
    """Raised when warmup is a negative integer."""