Coverage for src / cvx / linalg / norm.py: 100%
39 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 05:40 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 05:40 +0000
1"""Norm utilities for vectors with optional NaN-aware matrix weighting."""
3from __future__ import annotations
5from typing import Literal
7import numpy as np
9from .cholesky import cholesky as _cholesky
10from .exceptions import (
11 DimensionMismatchError,
12 NonSquareMatrixError,
13 SingularMatrixError,
14)
15from .exceptions import (
16 check_and_warn_condition as _check_and_warn_condition,
17)
18from .solve import _DEFAULT_COND_THRESHOLD
19from .valid import valid
22def norm(
23 x: np.ndarray,
24 ord: int | float | Literal["fro", "nuc"] | None = None,
25) -> float:
26 """Compute the norm of a vector or matrix, ignoring non-finite entries.
28 Non-finite entries (NaN, inf) are treated as zero before computing the norm,
29 so they contribute nothing to the result. Supports all ``ord`` values accepted
30 by ``np.linalg.norm``.
32 Args:
33 x: Input array (1-D vector or 2-D matrix).
34 ord: Order of the norm. See ``np.linalg.norm`` for valid values.
35 Common choices: ``None`` (default 2-norm for vectors, Frobenius for
36 matrices), ``1``, ``2``, ``np.inf``, ``'fro'``, ``'nuc'``.
38 Returns:
39 The norm as a float.
41 Example:
42 >>> import numpy as np
43 >>> from cvx.linalg import norm
44 >>> norm(np.array([3.0, np.nan, 4.0]))
45 5.0
46 >>> norm(np.array([[1.0, np.nan], [np.nan, 1.0]]), ord='fro')
47 1.4142135623730951
48 """
49 return float(np.linalg.norm(np.where(np.isfinite(x), x, 0.0), ord=ord))
52def a_norm(vector: np.ndarray, matrix: np.ndarray | None = None) -> float:
53 """Calculate the generalized norm of a vector with respect to a matrix.
55 Args:
56 vector: The input vector.
57 matrix: Optional square matrix defining the quadratic form.
59 Returns:
60 The Euclidean norm of the finite vector entries, or ``sqrt(v.T @ A @ v)``
61 after dropping rows and columns whose diagonal entries are not finite.
63 Raises:
64 NonSquareMatrixError: If the matrix is not square.
65 DimensionMismatchError: If the vector length does not match the matrix dimension.
67 Example:
68 >>> import numpy as np
69 >>> from cvx.linalg import a_norm
70 >>> a_norm(np.array([3.0, 4.0]))
71 5.0
72 """
73 if matrix is None:
74 return float(np.linalg.norm(vector[np.isfinite(vector)], 2))
76 if matrix.shape[0] != matrix.shape[1]:
77 raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])
79 if vector.size != matrix.shape[0]:
80 raise DimensionMismatchError(vector.size, matrix.shape[0])
82 mask, submatrix = valid(matrix)
83 if mask.any():
84 filtered_vector = vector[mask]
85 return float(np.sqrt(filtered_vector @ submatrix @ filtered_vector))
87 return float("nan")
90def inv_a_norm(
91 vector: np.ndarray,
92 matrix: np.ndarray | None = None,
93 cond_threshold: float = _DEFAULT_COND_THRESHOLD,
94) -> float:
95 """Calculate the inverse A-norm of a vector using an optional matrix.
97 If ``matrix`` is ``None``, returns the Euclidean norm of finite entries.
98 Otherwise computes ``sqrt(v.T @ A^{-1} @ v)`` on the valid submatrix,
99 attempting Cholesky decomposition first for numerical stability and
100 falling back to LU decomposition for non-positive-definite matrices.
101 When the condition number of the valid sub-matrix exceeds *cond_threshold*,
102 an ``IllConditionedMatrixWarning`` is emitted.
104 Args:
105 vector: The input vector.
106 matrix: Optional square matrix defining the quadratic form.
107 cond_threshold: Condition-number threshold above which a warning is
108 emitted. Defaults to ``1e12``.
110 Returns:
111 The Euclidean norm of the finite vector entries, or
112 ``sqrt(v.T @ A^{-1} @ v)`` after dropping rows and columns whose
113 diagonal entries are not finite. Returns ``nan`` when no valid entries
114 exist.
116 Raises:
117 NonSquareMatrixError: If the matrix is not square.
118 DimensionMismatchError: If the vector length does not match the matrix dimension.
119 SingularMatrixError: If the valid sub-matrix is singular.
121 Example:
122 >>> import numpy as np
123 >>> from cvx.linalg import inv_a_norm
124 >>> inv_a_norm(np.array([3.0, 4.0]))
125 5.0
126 """
127 if matrix is None:
128 return float(np.linalg.norm(vector[np.isfinite(vector)], 2))
130 if matrix.shape[0] != matrix.shape[1]:
131 raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])
133 if vector.size != matrix.shape[0]:
134 raise DimensionMismatchError(vector.size, matrix.shape[0])
136 mask, submatrix = valid(matrix)
137 if mask.any():
138 _check_and_warn_condition(submatrix, cond_threshold)
139 filtered_vector = vector[mask]
140 try:
141 solved = _cholesky(submatrix, filtered_vector)
142 except np.linalg.LinAlgError as exc:
143 raise SingularMatrixError(str(exc)) from exc
144 return float(np.sqrt(np.dot(filtered_vector, solved)))
146 return float("nan")