Coverage for src \ pavpen \ geometry_calculator \ faults.py: 100%
31 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-27 03:59 -0400
1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com>
2#
3# SPDX-License-Identifier: MIT
6class CalculationPrecisionExceededError(ValueError):
7 """Raised when a computation in algorithm step exceeds the maximum allowed
8 calculation error (floating point tolerance)
10 For example, if you try to normalize a vector whose norm is sufficiently
11 close to zero using float operations, the result may be a vector whose
12 coordinates have 1 significant digit, or compute as infinite.
13 """
15 def __init__(self, tolerance: float, calculation_error: float, value_name: str | None = None) -> None:
16 self._tolerance = tolerance
17 self._calculation_error = calculation_error
18 self._value_name = value_name
19 message = f"Maximum calculation tolerance ({tolerance}) exceeded (calculation error: {calculation_error})"
20 if value_name is not None:
21 message += f" for value {value_name!r}"
22 message += "!"
23 super().__init__(message)
25 @property
26 def tolerance(self) -> float:
27 """Maximum numerical error in caculation approximations
29 This is the value that was exceeded in the exception.
30 """
32 return self._tolerance
34 @property
35 def calculation_error(self) -> float:
36 """The numerical error in the calculation that caused this exception"""
38 return self._calculation_error
40 @property
41 def value_name(self) -> str | None:
42 """Name of the value whose calculation exceeded the calculation
43 tolerance
44 """
46 return self._value_name
49class ImpossibleOutputGeometryError(ValueError):
50 """Raised when the output of a calculation is physically impossible
52 E.g., a line with a negative length, a sphere with a negative radius, etc.
53 """
55 def __init__(self, message: str) -> None:
56 super().__init__(message)
59class AmbiguousComputationDueToPrecisionWarning(RuntimeWarning):
60 """Raised when a calculation may produce more than possible output (e.g.,
61 having the center of an output circle on one side of a given line, or the
62 other), but the calculation failed to unambiguously distinguish which of
63 the possible alternatives to return, because the difference between the
64 distinguishing factors is within the calculation error (floating point
65 tolerance).
67 As an example of such a calculation, you can consider determining the
68 the point, diametrically opposite the second of 3 input points, which
69 define the circle. If none of the 3 input points coincide, but they lie on
70 a line, the radius of the circle becomes infinite, and there are two
71 solutions, one on each side of the line, on a perpendicular to the second
72 of the 3 input points.
74 If the 3 input points given to the calculation are on a line, or
75 sufficiently close to a line (within the floating point tolerance), the
76 calculation may return one of the possible results, but raise this warning,
77 because the other possible solution, is also within the floating point
78 tolerance of the input.
79 """
81 def __init__(
82 self,
83 ambiguous_value_names: list[str],
84 tolerance: float,
85 difference_from_alternate_solution: float,
86 causing_exception: Exception | None = None,
87 ):
88 """
89 :param ambiguous_value_names: List of variables whose computed values
90 are ambigous due to computation precision. Each name in the list can be in the form "object.name", e.g. "RoundedCornerCalculator.center".
91 """
93 suffix = "" if causing_exception is None else f"\nCaused by: {causing_exception}"
94 message = (
95 f"{self.__class__.__name__}: The solution was ambiguous while "
96 f"computing values {ambiguous_value_names!r} because the difference "
97 f"from an alternate solution "
98 f"({difference_from_alternate_solution}) < the computational "
99 f"tolerance ({tolerance}){suffix}"
100 )
101 super().__init__(message)
102 self.ambiguous_value_names = ambiguous_value_names
103 self.tolerance = tolerance
104 self.difference_from_alternate_solution = difference_from_alternate_solution
105 self.causing_exception = causing_exception