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
« 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."""
3from __future__ import annotations
6class NotAMatrixError(TypeError):
7 """Raised when a 2-D matrix is required but the input has a different number of dimensions.
9 Args:
10 ndim: Actual number of dimensions of the offending array.
11 func: Name of the function that rejected the input.
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 """
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
31class NonSquareMatrixError(ValueError):
32 """Raised when a square matrix is required but the input is not square.
34 Args:
35 rows: Number of rows in the offending matrix.
36 cols: Number of columns in the offending matrix.
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 """
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
52class DimensionMismatchError(ValueError):
53 """Raised when vector and matrix dimensions are incompatible.
55 Args:
56 vector_size: Length of the offending vector.
57 matrix_size: Expected dimension inferred from the matrix.
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 """
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
73class SingularMatrixError(ValueError):
74 """Raised when a matrix is (numerically) singular and cannot be inverted.
76 Args:
77 detail: Optional extra detail string to append to the message.
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 """
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)
94class NegativeWarmupError(ValueError):
95 """Raised when a negative warmup period is requested.
97 Args:
98 warmup: The offending warmup value.
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 """
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
116class NonIntegerWarmupError(TypeError):
117 """Raised when warmup is not an integer (booleans are rejected as well).
119 Args:
120 value: The offending warmup value.
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 """
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
135class InvalidComponentsError(ValueError):
136 """Raised when the requested number of principal components is out of range.
138 Args:
139 n_components: The requested number of components.
140 max_components: The largest number of components supported by the data.
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 """
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
156class IllConditionedMatrixWarning(UserWarning):
157 """Emitted when a matrix condition number exceeds a configurable threshold.
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 """