Coverage for src/cvx/linalg/norm/norm.py: 100%
39 statements
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 18:56 +0000
« prev ^ index » next coverage.py v7.15.0, created at 2026-07-03 18:56 +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 ..core.exceptions import (
10 DEFAULT_COND_THRESHOLD,
11 DimensionMismatchError,
12 NonSquareMatrixError,
13 SingularMatrixError,
14)
15from ..core.exceptions import (
16 check_and_warn_condition as _check_and_warn_condition,
17)
18from ..core.types import Matrix, Vector
19from ..core.valid import valid
20from ..decomposition.cholesky import cholesky_solve as _cholesky_solve
23def norm(
24 x: Vector | Matrix,
25 ord: int | float | Literal["fro", "nuc"] | None = None, # noqa: A002 # mirrors np.linalg.norm public API
26) -> float:
27 """Compute the norm of a vector or matrix, ignoring non-finite entries.
29 Non-finite entries (NaN, inf) are treated as zero before computing the norm,
30 so they contribute nothing to the result. Supports all ``ord`` values accepted
31 by ``np.linalg.norm``.
33 Args:
34 x: Input array (1-D vector or 2-D matrix).
35 ord: Order of the norm. See ``np.linalg.norm`` for valid values.
36 Common choices: ``None`` (default 2-norm for vectors, Frobenius for
37 matrices), ``1``, ``2``, ``np.inf``, ``'fro'``, ``'nuc'``.
39 Returns:
40 The norm as a float.
42 Example:
43 >>> import numpy as np
44 >>> from cvx.linalg import norm
45 >>> norm(np.array([3.0, np.nan, 4.0]))
46 5.0
47 >>> norm(np.array([[1.0, np.nan], [np.nan, 1.0]]), ord='fro')
48 1.4142135623730951
49 """
50 return float(np.linalg.norm(np.where(np.isfinite(x), x, 0.0), ord=ord))
53def a_norm(vector: Vector, matrix: Matrix | None = None) -> float:
54 """Calculate the generalized norm of a vector with respect to a matrix.
56 Args:
57 vector: The input vector.
58 matrix: Optional square matrix defining the quadratic form.
60 Returns:
61 The Euclidean norm of the finite vector entries, or ``sqrt(v.T @ A @ v)``
62 after dropping rows and columns whose diagonal entries are not finite.
64 Raises:
65 NonSquareMatrixError: If the matrix is not square.
66 DimensionMismatchError: If the vector length does not match the matrix dimension.
68 Example:
69 >>> import numpy as np
70 >>> from cvx.linalg import a_norm
71 >>> a_norm(np.array([3.0, 4.0]))
72 5.0
73 """
74 if matrix is None:
75 return float(np.linalg.norm(vector[np.isfinite(vector)], 2))
77 if matrix.shape[0] != matrix.shape[1]:
78 raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])
80 if vector.size != matrix.shape[0]:
81 raise DimensionMismatchError(vector.size, matrix.shape[0])
83 mask, submatrix = valid(matrix)
84 if mask.any():
85 filtered_vector = vector[mask]
86 return float(np.sqrt(filtered_vector @ submatrix @ filtered_vector))
88 return float("nan")
91def inv_a_norm(
92 vector: Vector,
93 matrix: Matrix | None = None,
94 cond_threshold: float = DEFAULT_COND_THRESHOLD,
95) -> float:
96 """Calculate the inverse A-norm of a vector using an optional matrix.
98 If ``matrix`` is ``None``, returns the Euclidean norm of finite entries.
99 Otherwise computes ``sqrt(v.T @ A^{-1} @ v)`` on the valid submatrix,
100 attempting Cholesky decomposition first for numerical stability and
101 falling back to LU decomposition for non-positive-definite matrices.
102 When the condition number of the valid sub-matrix exceeds *cond_threshold*,
103 an ``IllConditionedMatrixWarning`` is emitted.
105 Args:
106 vector: The input vector.
107 matrix: Optional square matrix defining the quadratic form.
108 cond_threshold: Condition-number threshold above which a warning is
109 emitted. Defaults to ``1e12``.
111 Returns:
112 The Euclidean norm of the finite vector entries, or
113 ``sqrt(v.T @ A^{-1} @ v)`` after dropping rows and columns whose
114 diagonal entries are not finite. Returns ``nan`` when no valid entries
115 exist.
117 Raises:
118 NonSquareMatrixError: If the matrix is not square.
119 DimensionMismatchError: If the vector length does not match the matrix dimension.
120 SingularMatrixError: If the valid sub-matrix is singular.
122 Example:
123 >>> import numpy as np
124 >>> from cvx.linalg import inv_a_norm
125 >>> inv_a_norm(np.array([3.0, 4.0]))
126 5.0
127 """
128 if matrix is None:
129 return float(np.linalg.norm(vector[np.isfinite(vector)], 2))
131 if matrix.shape[0] != matrix.shape[1]:
132 raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1])
134 if vector.size != matrix.shape[0]:
135 raise DimensionMismatchError(vector.size, matrix.shape[0])
137 mask, submatrix = valid(matrix)
138 if mask.any():
139 _check_and_warn_condition(submatrix, cond_threshold)
140 filtered_vector = vector[mask]
141 try:
142 solved = _cholesky_solve(submatrix, filtered_vector)
143 except np.linalg.LinAlgError as exc:
144 raise SingularMatrixError(str(exc)) from exc
145 return float(np.sqrt(np.dot(filtered_vector, solved)))
147 return float("nan")