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

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

28 

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``. 

32 

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'``. 

38 

39 Returns: 

40 The norm as a float. 

41 

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

51 

52 

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

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

55 

56 Args: 

57 vector: The input vector. 

58 matrix: Optional square matrix defining the quadratic form. 

59 

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. 

63 

64 Raises: 

65 NonSquareMatrixError: If the matrix is not square. 

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

67 

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

76 

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

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

79 

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

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

82 

83 mask, submatrix = valid(matrix) 

84 if mask.any(): 

85 filtered_vector = vector[mask] 

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

87 

88 return float("nan") 

89 

90 

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. 

97 

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. 

104 

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``. 

110 

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. 

116 

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. 

121 

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

130 

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

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

133 

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

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

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