Coverage for tests/test_fitting.py: 100%

107 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-28 09:13 +0000

1"""Test module for the functions in the `fitting.py` module. 

2 

3This module contains unit tests for the functions implemented in the `fitting.py` module. The purpose of these tests is to 

4ensure the correct functionality of each function in different scenarios and to validate that the expected outputs are 

5returned. 

6 

7Tests should cover various edge cases, valid inputs, and any other conditions that are necessary to confirm the 

8robustness of the functions.""" 

9 

10import numpy as np 

11import pytest 

12 

13from app.utility.data import are_close 

14from app.fitting import Fit, normalize_to_unit 

15 

16 

17class TestNormalizeToUnit: 

18 

19 def test_zero(self) -> None: 

20 """Test normalization of zero.""" 

21 value, exponent = normalize_to_unit(0.0) 

22 assert value == 0.0 

23 assert exponent == 0 

24 

25 def test_one(self) -> None: 

26 """Test normalization of 1.""" 

27 value, exponent = normalize_to_unit(1.0) 

28 assert value == 1.0 

29 assert exponent == 0 

30 

31 def test_negative_one(self) -> None: 

32 """Test normalization of -1.""" 

33 value, exponent = normalize_to_unit(-1.0) 

34 assert value == -1.0 

35 assert exponent == 0 

36 

37 def test_smaller_than_one(self) -> None: 

38 """Test normalization of number smaller than 1 but greater than 0.1.""" 

39 value, exponent = normalize_to_unit(0.5) 

40 assert value == 0.5 

41 assert exponent == 0 

42 

43 def test_smaller_than_point_one(self) -> None: 

44 """Test normalization of number smaller than 0.1.""" 

45 value, exponent = normalize_to_unit(0.05) 

46 assert value == 0.5 

47 assert exponent == -1 

48 

49 def test_larger_than_one(self) -> None: 

50 """Test normalization of number larger than 1.""" 

51 value, exponent = normalize_to_unit(42.0) 

52 assert are_close(value, 0.42) 

53 assert exponent == 2 

54 

55 def test_very_large_number(self) -> None: 

56 """Test normalization of a very large number.""" 

57 value, exponent = normalize_to_unit(1.433364345e9) 

58 assert are_close(value, 0.1433364345) 

59 assert exponent == 10 

60 

61 def test_very_small_number(self) -> None: 

62 """Test normalization of a very small number.""" 

63 value, exponent = normalize_to_unit(3.5e-8) 

64 assert are_close(value, 0.35) 

65 assert exponent == -7 

66 

67 def test_negative_small_number(self) -> None: 

68 """Test normalization of a negative small number.""" 

69 value, exponent = normalize_to_unit(-0.0025) 

70 assert are_close(value, -0.25) 

71 assert exponent == -2 

72 

73 def test_negative_large_number(self) -> None: 

74 """Test normalization of a negative large number.""" 

75 value, exponent = normalize_to_unit(-12345.0) 

76 assert are_close(value, -0.12345) 

77 assert exponent == 5 

78 

79 def test_exactly_point_one(self) -> None: 

80 """Test normalization of exactly 0.1.""" 

81 value, exponent = normalize_to_unit(0.1) 

82 assert value == 0.1 

83 assert exponent == 0 

84 

85 def test_almost_point_one(self) -> None: 

86 """Test normalization of a number very close to 0.1.""" 

87 value, exponent = normalize_to_unit(0.099999) 

88 assert are_close(value, 0.99999) 

89 assert exponent == -1 

90 

91 def test_scientific_notation_positive(self) -> None: 

92 """Test with number in scientific notation (positive exponent).""" 

93 value, exponent = normalize_to_unit(2.5e4) 

94 assert are_close(value, 0.25) 

95 assert exponent == 5 

96 

97 def test_scientific_notation_negative(self) -> None: 

98 """Test with number in scientific notation (negative exponent).""" 

99 value, exponent = normalize_to_unit(14e-6) 

100 assert are_close(value, 0.14) 

101 assert exponent == -4 

102 

103 

104class TestFit: 

105 

106 @pytest.fixture 

107 def gaussian_data( 

108 self, 

109 ) -> tuple[list[np.ndarray], list[np.ndarray], callable, dict[str, float], list[str], list[dict[str, float]]]: 

110 """Gaussian data""" 

111 

112 def gaussian(x: np.ndarray, a: float, x0: float, c: float) -> np.ndarray: 

113 """Gaussian function""" 

114 return a * np.exp(-((x - x0) ** 2) / (2 * c)) 

115 

116 xs_data = [np.linspace(-2, 5, 101)] * 3 

117 ys_data = [ 

118 gaussian(xs_data[0], 1e8, 1, 0.5), 

119 gaussian(xs_data[0], 3e8, 1, 0.25), 

120 gaussian(xs_data[0], 5e8, 1, 0.25), 

121 ] 

122 p0 = dict(a=1e8, x0=0, c=0.3) 

123 detached_parameters = ["a"] 

124 fixed_parameters = [dict(c=0.5), dict(c=0.25), dict(c=0.25)] 

125 return xs_data, ys_data, gaussian, p0, detached_parameters, fixed_parameters 

126 

127 @pytest.fixture 

128 def gaussian_fit(self, gaussian_data) -> Fit: 

129 """Gaussian fit object""" 

130 

131 xs_data, ys_data, function, p0, detached_parameters, fixed_parameters = gaussian_data 

132 return Fit(xs_data, ys_data, function, p0, detached_parameters, fixed_parameters) 

133 

134 def test_gaussian_creation(self, gaussian_fit) -> None: 

135 

136 assert gaussian_fit.p0_mantissa == {"a": 1, "x0": 0.0} 

137 assert gaussian_fit.p0_factors == {"a": 8, "x0": 0} 

138 assert gaussian_fit.p0_list == [1.0, 0.0, 1.0, 1.0] 

139 assert gaussian_fit.bounds == {"a": [0, np.inf], "x0": [0, np.inf]} 

140 assert gaussian_fit.bounds_list == [[0, np.inf], [0, np.inf], [0, np.inf], [0, np.inf]] 

141 

142 def test_gaussian_incorrect_fixed_parameters(self, gaussian_data) -> None: 

143 

144 xs_data, ys_data, gaussian, p0, detached_parameters = gaussian_data[:-1] 

145 fixed_parameters = [dict(c=0.5), dict()] 

146 with pytest.raises(AssertionError): 

147 Fit(xs_data, ys_data, gaussian, p0, detached_parameters, fixed_parameters) 

148 

149 def test_gaussian_list_to_dicts(self, gaussian_fit) -> None: 

150 

151 expected = [ 

152 {"a": 100000000.0, "x0": 0.0}, 

153 {"a": 100000000.0, "x0": 0.0}, 

154 {"a": 100000000.0, "x0": 0.0}, 

155 ] 

156 assert are_close(gaussian_fit.list_to_dicts(gaussian_fit.p0_list), expected) 

157 

158 def test_gaussian_list_to_dicts_no_fixed(self, gaussian_data) -> None: 

159 

160 expected = [{"a": 100000000.0, "x0": 0.0}, {"a": 100000000.0, "x0": 0.0}, {"a": 100000000.0, "x0": 0.0}] 

161 xs_data, ys_data, function, p0, detached_parameters, fixed_parameters = gaussian_data 

162 fit = Fit(xs_data, ys_data, function, p0, [], fixed_parameters) 

163 assert are_close(fit.list_to_dicts(fit.p0_list), expected) 

164 

165 def test_gaussian_error_function(self, gaussian_fit) -> None: 

166 

167 expected = np.array([1819222.90846475, 2392860.49712516]) 

168 assert are_close(gaussian_fit.error_function(gaussian_fit.p0_list)[:2], expected) 

169 

170 def test_gaussian_fit(self, gaussian_fit) -> None: 

171 

172 expected = [ 

173 {"a": 100000000.0, "x0": 1.0, "c": 0.5}, 

174 {"a": 300000000.0, "x0": 1.0, "c": 0.25}, 

175 {"a": 500000000.0, "x0": 1.0, "c": 0.25}, 

176 ] 

177 assert are_close(gaussian_fit.fit(), expected) 

178 

179 def test_gaussian_calculate_fits(self, gaussian_fit) -> None: 

180 

181 expected = np.array([12340.98040867, 18690.68861775]) 

182 assert are_close(gaussian_fit.calculate_fits(gaussian_fit.fit())[0][:2], expected) 

183 

184 def test_gaussian_calculate_rss(self, gaussian_fit) -> None: 

185 

186 fits = gaussian_fit.calculate_fits(gaussian_fit.fit()) 

187 assert are_close(gaussian_fit.calculate_rss(fits), 1)