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

1# SPDX-FileCopyrightText: 2026-present Pavel M. Penev <pavpen@gmail.com> 

2# 

3# SPDX-License-Identifier: MIT 

4 

5 

6class CalculationPrecisionExceededError(ValueError): 

7 """Raised when a computation in algorithm step exceeds the maximum allowed 

8 calculation error (floating point tolerance) 

9 

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

14 

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) 

24 

25 @property 

26 def tolerance(self) -> float: 

27 """Maximum numerical error in caculation approximations 

28 

29 This is the value that was exceeded in the exception. 

30 """ 

31 

32 return self._tolerance 

33 

34 @property 

35 def calculation_error(self) -> float: 

36 """The numerical error in the calculation that caused this exception""" 

37 

38 return self._calculation_error 

39 

40 @property 

41 def value_name(self) -> str | None: 

42 """Name of the value whose calculation exceeded the calculation 

43 tolerance 

44 """ 

45 

46 return self._value_name 

47 

48 

49class ImpossibleOutputGeometryError(ValueError): 

50 """Raised when the output of a calculation is physically impossible 

51 

52 E.g., a line with a negative length, a sphere with a negative radius, etc. 

53 """ 

54 

55 def __init__(self, message: str) -> None: 

56 super().__init__(message) 

57 

58 

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

66 

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. 

73 

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

80 

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

92 

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