Coverage for src/cvx/linalg/core/exceptions.py: 100%

39 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-01 07:23 +0000

1"""Domain-specific exceptions and warnings for cvx.linalg.""" 

2 

3from __future__ import annotations 

4 

5 

6class NotAMatrixError(TypeError): 

7 """Raised when a 2-D matrix is required but the input has a different number of dimensions. 

8 

9 Args: 

10 ndim: Actual number of dimensions of the offending array. 

11 func: Name of the function that rejected the input. 

12 

13 Examples: 

14 >>> raise NotAMatrixError(3) 

15 Traceback (most recent call last): 

16 ... 

17 cvx.linalg.core.exceptions.NotAMatrixError: eigvals() expected a 2-D matrix, got 3-D input. 

18 >>> raise NotAMatrixError(3, func="qr") 

19 Traceback (most recent call last): 

20 ... 

21 cvx.linalg.core.exceptions.NotAMatrixError: qr() expected a 2-D matrix, got 3-D input. 

22 """ 

23 

24 def __init__(self, ndim: int, func: str = "eigvals") -> None: 

25 """Initialize with the actual number of dimensions and the rejecting function.""" 

26 super().__init__(f"{func}() expected a 2-D matrix, got {ndim}-D input.") 

27 self.ndim = ndim 

28 self.func = func 

29 

30 

31class NonSquareMatrixError(ValueError): 

32 """Raised when a square matrix is required but the input is not square. 

33 

34 Args: 

35 rows: Number of rows in the offending matrix. 

36 cols: Number of columns in the offending matrix. 

37 

38 Examples: 

39 >>> raise NonSquareMatrixError(3, 2) 

40 Traceback (most recent call last): 

41 ... 

42 cvx.linalg.core.exceptions.NonSquareMatrixError: Matrix must be square, got shape (3, 2). 

43 """ 

44 

45 def __init__(self, rows: int, cols: int) -> None: 

46 """Initialize with the offending matrix shape.""" 

47 super().__init__(f"Matrix must be square, got shape ({rows}, {cols}).") 

48 self.rows = rows 

49 self.cols = cols 

50 

51 

52class DimensionMismatchError(ValueError): 

53 """Raised when vector and matrix dimensions are incompatible. 

54 

55 Args: 

56 vector_size: Length of the offending vector. 

57 matrix_size: Expected dimension inferred from the matrix. 

58 

59 Examples: 

60 >>> raise DimensionMismatchError(3, 2) 

61 Traceback (most recent call last): 

62 ... 

63 cvx.linalg.core.exceptions.DimensionMismatchError: Vector length 3 does not match matrix dimension 2. 

64 """ 

65 

66 def __init__(self, vector_size: int, matrix_size: int) -> None: 

67 """Initialize with the offending vector and matrix sizes.""" 

68 super().__init__(f"Vector length {vector_size} does not match matrix dimension {matrix_size}.") 

69 self.vector_size = vector_size 

70 self.matrix_size = matrix_size 

71 

72 

73class SingularMatrixError(ValueError): 

74 """Raised when a matrix is (numerically) singular and cannot be inverted. 

75 

76 Args: 

77 detail: Optional extra detail string to append to the message. 

78 

79 Examples: 

80 >>> raise SingularMatrixError() 

81 Traceback (most recent call last): 

82 ... 

83 cvx.linalg.core.exceptions.SingularMatrixError: Matrix is singular and cannot be solved. 

84 """ 

85 

86 def __init__(self, detail: str = "") -> None: 

87 """Initialize with an optional extra detail string.""" 

88 msg = "Matrix is singular and cannot be solved." 

89 if detail: 

90 msg = f"{msg} {detail}" 

91 super().__init__(msg) 

92 

93 

94class NegativeWarmupError(ValueError): 

95 """Raised when a negative warmup period is requested. 

96 

97 Args: 

98 warmup: The offending warmup value. 

99 

100 Examples: 

101 >>> raise NegativeWarmupError(-3) 

102 Traceback (most recent call last): 

103 ... 

104 cvx.linalg.core.exceptions.NegativeWarmupError: warmup must be non-negative, got -3. 

105 """ 

106 

107 def __init__(self, warmup: int | None = None) -> None: 

108 """Initialize with the offending warmup value.""" 

109 msg = "warmup must be non-negative." 

110 if warmup is not None: 

111 msg = f"warmup must be non-negative, got {warmup}." 

112 super().__init__(msg) 

113 self.warmup = warmup 

114 

115 

116class NonIntegerWarmupError(TypeError): 

117 """Raised when warmup is not an integer (booleans are rejected as well). 

118 

119 Args: 

120 value: The offending warmup value. 

121 

122 Examples: 

123 >>> raise NonIntegerWarmupError(True) 

124 Traceback (most recent call last): 

125 ... 

126 cvx.linalg.core.exceptions.NonIntegerWarmupError: warmup must be an integer, got bool. 

127 """ 

128 

129 def __init__(self, value: object) -> None: 

130 """Initialize with the offending warmup value.""" 

131 super().__init__(f"warmup must be an integer, got {type(value).__name__}.") 

132 self.value = value 

133 

134 

135class InvalidComponentsError(ValueError): 

136 """Raised when the requested number of principal components is out of range. 

137 

138 Args: 

139 n_components: The requested number of components. 

140 max_components: The largest number of components supported by the data. 

141 

142 Examples: 

143 >>> raise InvalidComponentsError(10, 5) 

144 Traceback (most recent call last): 

145 ... 

146 cvx.linalg.core.exceptions.InvalidComponentsError: n_components must be between 1 and 5, got 10. 

147 """ 

148 

149 def __init__(self, n_components: int, max_components: int) -> None: 

150 """Initialize with the requested and maximum number of components.""" 

151 super().__init__(f"n_components must be between 1 and {max_components}, got {n_components}.") 

152 self.n_components = n_components 

153 self.max_components = max_components 

154 

155 

156class IllConditionedMatrixWarning(UserWarning): 

157 """Emitted when a matrix condition number exceeds a configurable threshold. 

158 

159 Examples: 

160 >>> import warnings 

161 >>> with warnings.catch_warnings(record=True) as w: 

162 ... warnings.simplefilter("always") 

163 ... warnings.warn("condition number 1e13", IllConditionedMatrixWarning) 

164 ... issubclass(w[-1].category, IllConditionedMatrixWarning) 

165 True 

166 """