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

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

20 

21 

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. 

27 

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

31 

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

37 

38 Returns: 

39 The norm as a float. 

40 

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

50 

51 

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. 

54 

55 Args: 

56 vector: The input vector. 

57 matrix: Optional square matrix defining the quadratic form. 

58 

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. 

62 

63 Raises: 

64 NonSquareMatrixError: If the matrix is not square. 

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

66 

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

75 

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

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

78 

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

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

81 

82 mask, submatrix = valid(matrix) 

83 if mask.any(): 

84 filtered_vector = vector[mask] 

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

86 

87 return float("nan") 

88 

89 

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. 

96 

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. 

103 

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

109 

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. 

115 

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. 

120 

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

129 

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

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

132 

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

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

135 

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

145 

146 return float("nan")