Coverage for src / cvx / linalg / exceptions.py: 100%
33 statements
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 05:40 +0000
« prev ^ index » next coverage.py v7.14.0, created at 2026-05-19 05:40 +0000
1"""Domain-specific exceptions and warnings for cvx.linalg."""
3from __future__ import annotations
5import warnings
6from typing import Literal
8import numpy as np
11class NotAMatrixError(TypeError):
12 """Raised when a 2-D matrix is required but the input has a different number of dimensions.
14 Args:
15 ndim: Actual number of dimensions of the offending array.
17 Examples:
18 >>> raise NotAMatrixError(3)
19 Traceback (most recent call last):
20 ...
21 cvx.linalg.exceptions.NotAMatrixError: eigvals() expected a 2-D matrix, got 3-D input.
22 """
24 def __init__(self, ndim: int) -> None:
25 """Initialize with the actual number of dimensions."""
26 super().__init__(f"eigvals() expected a 2-D matrix, got {ndim}-D input.")
27 self.ndim = ndim
30class NonSquareMatrixError(ValueError):
31 """Raised when a square matrix is required but the input is not square.
33 Args:
34 rows: Number of rows in the offending matrix.
35 cols: Number of columns in the offending matrix.
37 Examples:
38 >>> raise NonSquareMatrixError(3, 2)
39 Traceback (most recent call last):
40 ...
41 cvx.linalg.exceptions.NonSquareMatrixError: Matrix must be square, got shape (3, 2).
42 """
44 def __init__(self, rows: int, cols: int) -> None:
45 """Initialize with the offending matrix shape."""
46 super().__init__(f"Matrix must be square, got shape ({rows}, {cols}).")
47 self.rows = rows
48 self.cols = cols
51class DimensionMismatchError(ValueError):
52 """Raised when vector and matrix dimensions are incompatible.
54 Args:
55 vector_size: Length of the offending vector.
56 matrix_size: Expected dimension inferred from the matrix.
58 Examples:
59 >>> raise DimensionMismatchError(3, 2)
60 Traceback (most recent call last):
61 ...
62 cvx.linalg.exceptions.DimensionMismatchError: Vector length 3 does not match matrix dimension 2.
63 """
65 def __init__(self, vector_size: int, matrix_size: int) -> None:
66 """Initialize with the offending vector and matrix sizes."""
67 super().__init__(f"Vector length {vector_size} does not match matrix dimension {matrix_size}.")
68 self.vector_size = vector_size
69 self.matrix_size = matrix_size
72class SingularMatrixError(ValueError):
73 """Raised when a matrix is (numerically) singular and cannot be inverted.
75 Args:
76 detail: Optional extra detail string to append to the message.
78 Examples:
79 >>> raise SingularMatrixError()
80 Traceback (most recent call last):
81 ...
82 cvx.linalg.exceptions.SingularMatrixError: Matrix is singular and cannot be solved.
83 """
85 def __init__(self, detail: str = "") -> None:
86 """Initialize with an optional extra detail string."""
87 msg = "Matrix is singular and cannot be solved."
88 if detail:
89 msg = f"{msg} {detail}"
90 super().__init__(msg)
93class IllConditionedMatrixWarning(UserWarning):
94 """Emitted when a matrix condition number exceeds a configurable threshold.
96 Examples:
97 >>> import warnings
98 >>> warnings.warn("condition number 1e13", IllConditionedMatrixWarning)
99 """
102def cond(matrix: np.ndarray, p: int | float | Literal["fro", "nuc"] | None = None) -> float:
103 """Return the condition number of a matrix.
105 Returns ``nan`` if the matrix contains any non-finite (NaN or inf) entries.
106 Otherwise delegates to :func:`numpy.linalg.cond`.
108 Args:
109 matrix: Input matrix.
110 p: Order of the norm used to compute the condition number.
111 Accepts the same values as :func:`numpy.linalg.cond`
112 (``None``, ``1``, ``-1``, ``2``, ``-2``, ``numpy.inf``,
113 ``-numpy.inf``, ``'fro'``). Defaults to ``None`` which
114 corresponds to the 2-norm (largest singular value divided by
115 the smallest).
117 Returns:
118 The condition number as a ``float``, or ``nan`` when the matrix
119 contains non-finite entries.
121 Examples:
122 >>> import numpy as np
123 >>> cond(np.eye(3))
124 1.0
125 >>> import math
126 >>> math.isnan(cond(np.array([[float('nan'), 1.0], [1.0, 2.0]])))
127 True
128 >>> cond(np.diag([1.0, 1e10]), p=1)
129 10000000000.0
130 """
131 if not np.all(np.isfinite(matrix)):
132 return float("nan")
133 return float(np.linalg.cond(matrix, p=p))
136def check_and_warn_condition(matrix: np.ndarray, threshold: float) -> None:
137 """Emit IllConditionedMatrixWarning when the condition number exceeds threshold.
139 Args:
140 matrix: Square matrix whose condition number is checked.
141 threshold: Upper bound before a warning is issued.
143 Example:
144 >>> import numpy as np
145 >>> import warnings
146 >>> with warnings.catch_warnings(record=True) as w:
147 ... warnings.simplefilter("always")
148 ... check_and_warn_condition(np.eye(2), 0.5)
149 ... len(w)
150 1
151 """
152 c = cond(matrix)
153 if c > threshold:
154 warnings.warn(
155 f"Matrix condition number {c:.3e} exceeds threshold {threshold:.3e}; "
156 "results may be numerically unreliable.",
157 IllConditionedMatrixWarning,
158 stacklevel=3,
159 )