Coverage for src/cvx/linalg/norm/norm.py: 100%

38 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-15 07:08 +0000

1"""Norm utilities for vectors with optional NaN-aware matrix weighting.""" 

2 

3from __future__ import annotations 

4 

5from typing import Literal 

6 

7import numpy as np 

8 

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 

21 

22 

23def _validate_square(vector: Vector, matrix: Matrix) -> None: 

24 """Check *matrix* is square and its dimension matches *vector*'s length.""" 

25 if matrix.shape[0] != matrix.shape[1]: 

26 raise NonSquareMatrixError(matrix.shape[0], matrix.shape[1]) 

27 if vector.size != matrix.shape[0]: 

28 raise DimensionMismatchError(vector.size, matrix.shape[0]) 

29 

30 

31def norm( 

32 x: Vector | Matrix, 

33 ord: int | float | Literal["fro", "nuc"] | None = None, # noqa: A002 # mirrors np.linalg.norm public API 

34) -> float: 

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

36 

37 Non-finite entries (NaN, inf) are treated as zero before computing the norm, 

38 so they contribute nothing to the result. Supports all ``ord`` values accepted 

39 by ``np.linalg.norm``. 

40 

41 Args: 

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

43 ord: Order of the norm. See ``np.linalg.norm`` for valid values. 

44 Common choices: ``None`` (default 2-norm for vectors, Frobenius for 

45 matrices), ``1``, ``2``, ``np.inf``, ``'fro'``, ``'nuc'``. 

46 

47 Returns: 

48 The norm as a float. 

49 

50 Example: 

51 >>> import numpy as np 

52 >>> from cvx.linalg import norm 

53 >>> norm(np.array([3.0, np.nan, 4.0])) 

54 5.0 

55 >>> norm(np.array([[1.0, np.nan], [np.nan, 1.0]]), ord='fro') 

56 1.4142135623730951 

57 """ 

58 return float(np.linalg.norm(np.where(np.isfinite(x), x, 0.0), ord=ord)) 

59 

60 

61def a_norm(vector: Vector, matrix: Matrix | None = None) -> float: 

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

63 

64 Args: 

65 vector: The input vector. 

66 matrix: Optional square matrix defining the quadratic form. 

67 

68 Returns: 

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

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

71 

72 Raises: 

73 NonSquareMatrixError: If the matrix is not square. 

74 DimensionMismatchError: If the vector length does not match the matrix dimension. 

75 

76 Example: 

77 >>> import numpy as np 

78 >>> from cvx.linalg import a_norm 

79 >>> a_norm(np.array([3.0, 4.0])) 

80 5.0 

81 """ 

82 if matrix is None: 

83 return float(np.linalg.norm(vector[np.isfinite(vector)], 2)) 

84 

85 _validate_square(vector, matrix) 

86 

87 mask, submatrix = valid(matrix) 

88 if mask.any(): 

89 filtered_vector = vector[mask] 

90 return float(np.sqrt(filtered_vector @ submatrix @ filtered_vector)) 

91 

92 return float("nan") 

93 

94 

95def inv_a_norm( 

96 vector: Vector, 

97 matrix: Matrix | None = None, 

98 cond_threshold: float = DEFAULT_COND_THRESHOLD, 

99) -> float: 

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

101 

102 If ``matrix`` is ``None``, returns the Euclidean norm of finite entries. 

103 Otherwise computes ``sqrt(v.T @ A^{-1} @ v)`` on the valid submatrix, 

104 attempting Cholesky decomposition first for numerical stability and 

105 falling back to LU decomposition for non-positive-definite matrices. 

106 When the condition number of the valid sub-matrix exceeds *cond_threshold*, 

107 an ``IllConditionedMatrixWarning`` is emitted. 

108 

109 Args: 

110 vector: The input vector. 

111 matrix: Optional square matrix defining the quadratic form. 

112 cond_threshold: Condition-number threshold above which a warning is 

113 emitted. Defaults to ``1e12``. 

114 

115 Returns: 

116 The Euclidean norm of the finite vector entries, or 

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

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

119 exist. 

120 

121 Raises: 

122 NonSquareMatrixError: If the matrix is not square. 

123 DimensionMismatchError: If the vector length does not match the matrix dimension. 

124 SingularMatrixError: If the valid sub-matrix is singular. 

125 

126 Example: 

127 >>> import numpy as np 

128 >>> from cvx.linalg import inv_a_norm 

129 >>> inv_a_norm(np.array([3.0, 4.0])) 

130 5.0 

131 """ 

132 if matrix is None: 

133 return float(np.linalg.norm(vector[np.isfinite(vector)], 2)) 

134 

135 _validate_square(vector, matrix) 

136 

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))) 

146 

147 return float("nan")