Coverage for tests/test_models.py: 100%

473 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 `models.py` module. 

2 

3This module contains unit tests for the functions implemented in the `models.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.models import ( 

14 BTDModel, 

15 BTDModelTRMC, 

16 BTDModelTRPL, 

17 BTD_KWARGS, 

18 BTModel, 

19 BTModelTRMC, 

20 BTModelTRPL, 

21 BT_KWARGS, 

22 Model, 

23) 

24from app.utility.data import are_close 

25 

26# Time array 

27T = np.linspace(0, 100, 101) 

28 

29 

30def assert_fit( 

31 fit: dict, 

32 popt_expected: dict, 

33 contribution_expected: dict, 

34 cod_expected: float, 

35) -> None: 

36 """Check the result of a fit. 

37 :param fit: fit result 

38 :param popt_expected: expected optimised fit popt 

39 :param contribution_expected: expected contributions 

40 :param cod_expected: expected cod""" 

41 

42 assert are_close(fit["popts"][0], popt_expected, rtol=0.05) 

43 assert are_close(fit["contributions"], contribution_expected, rtol=0.025) 

44 assert are_close(fit["cod"], cod_expected, rtol=0.01) 

45 

46 

47class TestModel: 

48 

49 @pytest.fixture 

50 def model(self) -> Model: 

51 """Example Model object""" 

52 

53 param_ids = ["k_B", "k_T", "k_A", "y_0"] 

54 units = {"k_B": "cm^3/ns", "k_T": "1/ns", "k_A": "cm^6/ns"} 

55 units_html = {"k_B": "cm<sup>3</sup>/ns", "k_T": "1/ns", "k_A": "cm<sup>6</sup>/ns"} 

56 factors = {"k_B": 1e-20, "k_T": 1e-3, "k_A": 1e-40} 

57 fvalues = {"k_B": 1e-18, "k_T": None, "k_A": None} 

58 gvalues = {"k_B": 1e-19, "k_T": 1e-3, "k_A": 1e-40} 

59 gvalues_range = {"k_B": [1e-20, 1e-19], "k_T": [1e-3, 1e-2], "k_A": [1e-40]} 

60 n_keys = ["n"] 

61 conc_ca_ids = ["n"] 

62 param_filters = [] 

63 

64 def n_init(N_0) -> dict[str, float]: # pragma: no cover 

65 """Return the photoexcited carrier concentration""" 

66 return {"n": N_0} 

67 

68 return Model( 

69 param_ids, 

70 units, 

71 units_html, 

72 factors, 

73 fvalues, 

74 gvalues, 

75 gvalues_range, 

76 n_keys, 

77 n_init, 

78 conc_ca_ids, 

79 param_filters, 

80 ) 

81 

82 def test_initialisation(self, model) -> None: 

83 

84 assert model.param_ids == ["k_B", "k_T", "k_A", "y_0"] 

85 assert model.units["k_B"] == "cm^3/ns" 

86 assert model.n_keys == ["n"] 

87 assert model.fvalues["k_B"] == 1e-18 

88 

89 def test_get_parameter_label(self, model) -> None: 

90 

91 assert model.get_parameter_label("k_B") == "k_B (cm^3/ns)" 

92 assert model.get_parameter_label("y_0") == "y_0" 

93 

94 def test_fixed_values(self, model) -> None: 

95 

96 assert model.fixed_values == {"k_B": 1e-18, "y_0": 0.0} 

97 

98 def test_eq(self, model) -> None: 

99 

100 model2 = model 

101 assert model == model2 

102 

103 model3 = Model( 

104 model.param_ids, 

105 model.units, 

106 model.units_html, 

107 model.factors, 

108 {"k_B": 1}, 

109 model.gvalues, 

110 model.gvalues_range, 

111 model.n_keys, 

112 model.n_init, 

113 model.conc_ca_ids, 

114 model.param_filters, 

115 ) 

116 assert model != model3 

117 

118 def test_get_rec_string(self, model) -> None: 

119 

120 expected = "This fit predicts low Auger. The values associated with this process may be inaccurate." 

121 expected2 = ( 

122 "This fit predicts low Auger. The values associated with this process may be inaccurate.\nIt is " 

123 "recommended to measure your sample under higher excitation fluence for this process to become significant" 

124 ) 

125 assert model.get_contribution_recommendation("Auger") == expected 

126 assert model.get_contribution_recommendation("Auger", "higher") == expected2 

127 

128 def test_rate_equations(self, model) -> None: 

129 

130 assert model._rate_equations() == {} 

131 

132 def test_calculate_contributions(self, model) -> None: 

133 

134 assert model.calculate_contributions() == {} 

135 

136 def test_calculate_fit_quantity(self, model) -> None: 

137 

138 assert model.calculate_fit_quantity() == np.array([0]) 

139 

140 def test_get_contribution_recommendations(self, model) -> None: 

141 

142 assert model.get_contribution_recommendations() == [""] 

143 

144 

145class TestBTModel: 

146 

147 @pytest.fixture 

148 def model(self) -> BTModel: 

149 """Example BTModel object""" 

150 

151 return BTModel(["k_T", "k_B", "k_A", "mu", "y_0"]) 

152 

153 def test_initialization(self, model) -> None: 

154 

155 assert model.param_ids == ["k_T", "k_B", "k_A", "mu", "y_0"] 

156 assert model.units["k_B"] == "cm3/ns" 

157 assert model.factors["k_T"] == 1e-3 

158 

159 def test_rate_equations(self, model) -> None: 

160 

161 rates = model._rate_equations(n=1e17, **BT_KWARGS) 

162 assert np.isclose(rates["n"], -6000100000000000.0) 

163 

164 def test_calculate_concentrations(self, model) -> None: 

165 

166 # Single pulse 

167 output = model._calculate_concentrations(T, 1e17, **BT_KWARGS) 

168 assert np.allclose(output["n"][0][:3], np.array([1.00000000e17, 9.43127550e16, 8.91893621e16])) 

169 

170 # Multiple pulses 

171 output = model._calculate_concentrations(T, 1e17, **BT_KWARGS, p=1000) 

172 assert np.allclose(output["n"][-1][:3], np.array([1.09021346e17, 1.02383279e17, 9.64515430e16])) 

173 assert len(output["n"]) == 5 

174 

175 # Multiple pulses - No stabilisation 

176 with pytest.raises(AssertionError): 

177 model._calculate_concentrations(T, 1e17, **BT_KWARGS, p=3) 

178 

179 def test_get_carrier_concentrations(self, model) -> None: 

180 

181 popts = [{"I": 1.0, "N_0": 1e17, **BT_KWARGS}] 

182 

183 # Period provided 

184 output = model.get_carrier_concentrations([T], popts, 100) 

185 assert np.allclose(output[2][0]["n"][:3], np.array([1.00000000e17, 9.70802526e16, 9.43127550e16])) 

186 assert np.allclose(output[0][0][:3], np.array([0.0, 0.00498008, 0.00996016])) 

187 

188 # Period not provided 

189 output2 = model.get_carrier_concentrations([T], popts, 0) 

190 assert np.allclose(output2[2][0]["n"][:3], np.array([1.00000000e17, 9.43127550e16, 8.91893621e16])) 

191 assert np.allclose(output2[0][0][:3], np.array([0.0, 1.0, 2.0])) 

192 

193 def test_get_contribution_recommendations(self, model) -> None: 

194 

195 contributions = {"T": np.array([5.0]), "B": np.array([5.0]), "A": np.array([0])} 

196 recs = model.get_contribution_recommendations(contributions) 

197 expected = [ 

198 "This fit predicts low trapping. The values associated with this process may be inaccurate.\nIt is " 

199 "recommended to measure your sample under lower excitation fluence for this process to become significant", 

200 "This fit predicts low bimolecular. The values associated with this process may be inaccurate.\nIt is " 

201 "recommended to measure your sample under higher excitation fluence for this process to become significant", 

202 ] 

203 assert recs == expected 

204 

205 model.fvalues["k_A"] = None 

206 recs = model.get_contribution_recommendations(contributions) 

207 expected = [ 

208 "This fit predicts low trapping. The values associated with this process may be inaccurate.\nIt is " 

209 "recommended to measure your sample under lower excitation fluence for this process to become significant", 

210 "This fit predicts low bimolecular. The values associated with this process may be inaccurate.\nIt is " 

211 "recommended to measure your sample under higher excitation fluence for this process to become significant", 

212 "This fit predicts low Auger. The values associated with this process may be inaccurate.\nIt is " 

213 "recommended to measure your sample under higher excitation fluence for this process to become significant", 

214 ] 

215 assert recs == expected 

216 

217 def test_calculate_trpl(self, model) -> None: 

218 

219 result = model.calculate_trpl(T, N_0=1e17, **BT_KWARGS) 

220 assert np.allclose(result[:3], np.array([1.0, 0.88948958, 0.79547423])) 

221 

222 def test_calculate_trmc(self, model) -> None: 

223 

224 result = model.calculate_trmc(T, N_0=1e17, **BT_KWARGS) 

225 assert np.allclose(result[:3], np.array([20.0, 18.86255101, 17.83787242])) 

226 

227 

228class TestBTModelTRPL: 

229 

230 def test_calculate_fit_quantity(self) -> None: 

231 

232 result = BTModelTRPL().calculate_fit_quantity(T, N_0=1e17, **BT_KWARGS) 

233 assert np.allclose(result[:3], np.array([1.0, 0.88948958, 0.79547423])) 

234 

235 def test_calculate_contributions(self) -> None: 

236 

237 concentrations = BTModelTRPL()._calculate_concentrations(T, 1e16, **BT_KWARGS) 

238 concentrations = {key: value[0] for key, value in concentrations.items()} 

239 contributions = BTModelTRPL().calculate_contributions(T, **concentrations, **BT_KWARGS) 

240 expected = { 

241 "T": np.float64(74.27571672613342), 

242 "B": np.float64(25.724244681350395), 

243 "A": np.float64(3.859251618686695e-05), 

244 } 

245 assert are_close(contributions, expected) 

246 

247 def test_get_carrier_accumulation(self) -> None: 

248 

249 N0s = [1e17, 1e18] 

250 popts = [{"N_0": n, **BT_KWARGS} for n in N0s] 

251 

252 # 100 ns period 

253 output = BTModelTRPL().get_carrier_accumulation(popts, 100) 

254 ca_expected = [np.float64(2.1474605461112906), np.float64(0.3260927825203319)] 

255 decay_expected = [1.0, 9.99896859e-01, 9.99896716e-01] 

256 assert are_close(output["CA"], ca_expected) 

257 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

258 

259 # 50 ns period 

260 output = BTModelTRPL().get_carrier_accumulation(popts, 50) 

261 ca_expected = [np.float64(4.931657664085842), np.float64(0.8414419278757801)] 

262 decay_expected = [1.0, 0.99989505, 0.99989491] 

263 assert are_close(output["CA"], ca_expected) 

264 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

265 

266 def test_generate_decays(self) -> None: 

267 

268 # Without noise 

269 xs_data, ys_data, N0s = BTModelTRPL().generate_decays() 

270 assert are_close(ys_data[0][:3], [1.0, 0.99476408, 0.98955622]) 

271 assert are_close(ys_data[-1][:3], [1.0, 0.9706254, 0.94245754]) 

272 

273 # With noise 

274 xs_data, ys_data, N0s = BTModelTRPL().generate_decays(noise=0.02) 

275 assert are_close(ys_data[0][:3], [0.96980343, 1.0, 0.98891807]) 

276 assert are_close(ys_data[-1][:3], [1.0, 0.95023039, 0.95215302]) 

277 

278 def test_fit(self) -> None: 

279 

280 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

281 

282 test_data = BTModelTRPL().generate_decays() 

283 fit = BTModelTRPL().fit(*test_data) 

284 popt_expected = { 

285 "k_T": np.float64(0.009999986902280364), 

286 "k_B": np.float64(5.000059540779067e-19), 

287 "k_A": 0.0, 

288 "y_0": 0.0, 

289 "I": 1.0, 

290 "N_0": 1000000000000000.0, 

291 } 

292 contribution_expected = { 

293 "T": np.array([96.76851866, 75.55601245, 25.64917742]), 

294 "B": np.array([3.23148134, 24.44398755, 74.35082258]), 

295 "A": np.array([0.0, 0.0, 0.0]), 

296 } 

297 assert_fit(fit, popt_expected, contribution_expected, 0.9999999999984733) 

298 

299 # ---------------------------------------------------- NOISY --------------------------------------------------- 

300 

301 test_data = BTModelTRPL().generate_decays(0.05) 

302 fit = BTModelTRPL().fit(*test_data) 

303 popt_expected = { 

304 "k_T": np.float64(0.010968908554455715), 

305 "k_B": np.float64(5.169626461951155e-19), 

306 "k_A": 0.0, 

307 "y_0": 0.0, 

308 "I": 1.0, 

309 "N_0": 1000000000000000.0, 

310 } 

311 contribution_expected = { 

312 "T": np.array([96.95417309, 76.62826407, 26.73269065]), 

313 "B": np.array([3.04582691, 23.37173593, 73.26730935]), 

314 "A": np.array([0.0, 0.0, 0.0]), 

315 } 

316 assert_fit(fit, popt_expected, contribution_expected, 0.9419815552329127) 

317 

318 # --------------------------------------------- NOISY / NON-FIXED I -------------------------------------------- 

319 

320 test_data = BTModelTRPL().generate_decays(0.05) 

321 model = BTModelTRPL() 

322 model.fvalues["I"] = None 

323 fit = model.fit(*test_data) 

324 

325 popt_expected = { 

326 "k_T": np.float64(0.010076397709080696), 

327 "k_B": np.float64(4.901882357446167e-19), 

328 "I": np.float64(0.9320239638447994), 

329 "k_A": 0.0, 

330 "y_0": 0.0, 

331 "N_0": 1000000000000000.0, 

332 } 

333 contribution_expected = { 

334 "T": np.array([96.85353317, 76.04748899, 26.1462359]), 

335 "B": np.array([3.14646683, 23.95251101, 73.8537641]), 

336 "A": np.array([0.0, 0.0, 0.0]), 

337 } 

338 assert_fit(fit, popt_expected, contribution_expected, 0.9464096079674329) 

339 

340 # ------------------------------------------- NO NOISE / AUGER GUESS ------------------------------------------- 

341 

342 test_data = BTModelTRPL().generate_decays() 

343 model = BTModelTRPL() 

344 model.fvalues["k_A"] = None 

345 fit = model.fit(*test_data) 

346 popt_expected = { 

347 "k_T": np.float64(0.010000000000022824), 

348 "k_B": np.float64(4.999999999757379e-19), 

349 "k_A": np.float64(1.0000045458996652e-40), 

350 "y_0": 0.0, 

351 "I": 1.0, 

352 "N_0": 1000000000000000.0, 

353 } 

354 contribution_expected = { 

355 "T": np.array([96.76855947, 75.55622408, 25.64918984]), 

356 "B": np.array([3.23144005, 24.44373996, 74.34977381]), 

357 "A": np.array([4.83765434e-07, 3.59589010e-05, 1.03635229e-03]), 

358 } 

359 assert_fit(fit, popt_expected, contribution_expected, 1.0) 

360 

361 def test_grid_fitting(self) -> None: 

362 

363 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

364 

365 xs_data, ys_data, N0s = BTModelTRPL().generate_decays() 

366 analysis = BTModelTRPL().grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

367 popt_expected = { 

368 "k_B": np.float64(5.000059541427541e-19), 

369 "k_T": np.float64(0.009999986899992526), 

370 "k_A": 0.0, 

371 "y_0": 0.0, 

372 "I": 1.0, 

373 "N_0": 1000000000000000.0, 

374 } 

375 contributions_expected = { 

376 "T": np.array([96.76851866, 75.55601244, 25.64917742]), 

377 "B": np.array([3.23148134, 24.44398756, 74.35082258]), 

378 "A": np.array([0.0, 0.0, 0.0]), 

379 } 

380 assert_fit(analysis[0], popt_expected, contributions_expected, 0.9999999999984733) 

381 assert_fit(analysis[-1], popt_expected, contributions_expected, 0.9999999999984733) 

382 

383 # ---------------------------------------------------- NOISY --------------------------------------------------- 

384 

385 xs_data, ys_data, N0s = BTModelTRPL().generate_decays(0.05) 

386 analysis = BTModelTRPL().grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

387 popt_expected = { 

388 "k_B": np.float64(5.169646462205936e-19), 

389 "k_T": np.float64(0.010968900882386862), 

390 "k_A": 0.0, 

391 "y_0": 0.0, 

392 "I": 1.0, 

393 "N_0": 1000000000000000.0, 

394 } 

395 contributions_expected = { 

396 "T": np.array([96.95415961, 76.62818434, 26.73260624]), 

397 "B": np.array([3.04584039, 23.37181566, 73.26739376]), 

398 "A": np.array([0.0, 0.0, 0.0]), 

399 } 

400 assert_fit(analysis[0], popt_expected, contributions_expected, 0.9419815552335047) 

401 assert_fit(analysis[-1], popt_expected, contributions_expected, 0.9419815552345455) 

402 

403 # -------------------------------------------- NO NOISE/ NON-FIXED I ------------------------------------------- 

404 

405 xs_data, ys_data, N0s = BTModelTRPL().generate_decays(0.05) 

406 model = BTModelTRPL() 

407 model.fvalues["I"] = None 

408 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

409 popt_expected = { 

410 "k_B": np.float64(4.901881281164643e-19), 

411 "k_T": np.float64(0.0100763981602312), 

412 "I": np.float64(0.9320239815477664), 

413 "k_A": 0.0, 

414 "y_0": 0.0, 

415 "N_0": 1000000000000000.0, 

416 } 

417 contributions_expected = { 

418 "T": np.array([96.85353398, 76.04749368, 26.14624072]), 

419 "B": np.array([3.14646602, 23.95250632, 73.85375928]), 

420 "A": np.array([0.0, 0.0, 0.0]), 

421 } 

422 assert_fit(analysis[0], popt_expected, contributions_expected, 0.9464096079674144) 

423 assert_fit(analysis[-1], popt_expected, contributions_expected, 0.9464096079674144) 

424 

425 # ------------------------------------------- NO NOISE / AUGER GUESS ------------------------------------------- 

426 

427 xs_data, ys_data, N0s = BTModelTRPL().generate_decays() 

428 model = BTModelTRPL() 

429 model.fvalues["k_A"] = None 

430 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

431 popt_expected = { 

432 "k_B": np.float64(4.999999999517231e-19), 

433 "k_T": np.float64(0.010000000000056396), 

434 "k_A": np.float64(9.99999839030493e-41), 

435 "y_0": 0.0, 

436 "I": 1.0, 

437 "N_0": 1000000000000000.0, 

438 } 

439 popt_expected_2 = { 

440 "k_B": np.float64(3.661606705835427e-19), 

441 "k_T": np.float64(0.010497516613971638), 

442 "k_A": np.float64(1.7842548483988976e-36), 

443 "y_0": 0.0, 

444 "I": 1.0, 

445 "N_0": 1000000000000000.0, 

446 } 

447 contributions_expected = { 

448 "T": np.array([96.76855947, 75.55622408, 25.64918984]), 

449 "B": np.array([3.23144005, 24.44373996, 74.34977381]), 

450 "A": np.array([4.83763157e-07, 3.59587318e-05, 1.03634741e-03]), 

451 } 

452 contribution_expected_2 = { 

453 "T": np.array([97.7152958, 80.94098053, 27.43533307]), 

454 "B": np.array([2.27639629, 18.39690108, 54.37465037]), 

455 "A": np.array([8.30790588e-03, 6.62118386e-01, 1.81900166e01]), 

456 } 

457 assert_fit(analysis[0], popt_expected, contributions_expected, 1.0) 

458 assert_fit(analysis[-1], popt_expected_2, contribution_expected_2, 0.9992469395390701) 

459 

460 # ------------------------------------------------- FAILED FIT ------------------------------------------------- 

461 

462 xs_data, ys_data, N0s = BTModelTRPL().generate_decays() 

463 model = BTModelTRPL() 

464 model.gvalues_range["k_B"] = [1e-20, -1e-20] 

465 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

466 assert len(analysis) == 2 

467 

468 

469class TestBTModelTRMC: 

470 

471 def test_calculate_fit_quantity(self) -> None: 

472 

473 result = BTModelTRMC().calculate_trmc(T, N_0=1e17, **BT_KWARGS) 

474 assert np.allclose(result[:3], np.array([20.0, 18.86255101, 17.83787242])) 

475 

476 def test_calculate_contributions(self) -> None: 

477 

478 concentrations = BTModelTRMC()._calculate_concentrations(T, 1e16, **BT_KWARGS) 

479 concentrations = {key: value[0] for key, value in concentrations.items()} 

480 contributions = BTModelTRMC().calculate_contributions(T, **concentrations, **BT_KWARGS) 

481 expected = { 

482 "T": np.float64(76.23900060688833), 

483 "B": np.float64(23.760966476140023), 

484 "A": np.float64(3.291697165313421e-05), 

485 } 

486 assert are_close(contributions, expected) 

487 

488 def test_get_carrier_accumulation(self) -> None: 

489 

490 N0s = [1e17, 1e18] 

491 popts = [{"N_0": n, **BT_KWARGS} for n in N0s] 

492 

493 # 100 ns period 

494 output = BTModelTRMC().get_carrier_accumulation(popts, 100) 

495 ca_expected = [np.float64(1.8119795431245034), np.float64(0.2751318097141131)] 

496 decay_expected = [20.22572435, 20.22468127, 20.22467983] 

497 assert are_close(output["CA"], ca_expected) 

498 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

499 

500 # 50 ns period 

501 output = BTModelTRMC().get_carrier_accumulation(popts, 50) 

502 ca_expected = [np.float64(4.161872397435568), np.float64(0.7099465316118103)] 

503 decay_expected = [20.58756773, 20.58648736, 20.58648594] 

504 assert are_close(output["CA"], ca_expected) 

505 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

506 

507 def test_generate_decays(self) -> None: 

508 

509 # Without noise 

510 xs_data, ys_data, N0s = BTModelTRMC().generate_decays() 

511 assert are_close(ys_data[0][:3], [20.0, 19.79115011, 19.58458361]) 

512 assert are_close(ys_data[-1][:3], [20.0, 18.86255101, 17.83787242]) 

513 

514 # With noise 

515 xs_data, ys_data, N0s = BTModelTRMC().generate_decays(noise=0.02) 

516 assert are_close(ys_data[0][:3], [19.57021361, 20.07543598, 19.74939803]) 

517 assert are_close(ys_data[-1][:3], [20.14851545, 18.5957746, 18.1731914]) 

518 

519 def test_fit(self) -> None: 

520 

521 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

522 

523 test_data = BTModelTRMC().generate_decays() 

524 fit = BTModelTRMC().fit(*test_data) 

525 popt_expected = { 

526 "k_T": 0.009999990976444148, 

527 "k_B": 5.000049109826438e-19, 

528 "mu": 9.999999708991059, 

529 "k_A": 0.0, 

530 "y_0": 0.0, 

531 "N_0": 1000000000000000.0, 

532 } 

533 contribution_expected = { 

534 "T": np.array([97.58013435402015, 81.09151713962103, 35.81980709285807]), 

535 "B": np.array([2.419865645979866, 18.90848286037897, 64.18019290714193]), 

536 "A": np.array([0.0, 0.0, 0.0]), 

537 } 

538 expected_cod = 0.9999999999993268 

539 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

540 

541 # ---------------------------------------------------- NOISY --------------------------------------------------- 

542 

543 test_data = BTModelTRMC().generate_decays(0.05) 

544 fit = BTModelTRMC().fit(*test_data) 

545 popt_expected = { 

546 "k_T": 0.010116290480286557, 

547 "k_B": 4.802023013869001e-19, 

548 "mu": 10.05269417362719, 

549 "k_A": 0.0, 

550 "y_0": 0.0, 

551 "N_0": 1000000000000000.0, 

552 } 

553 contribution_expected = { 

554 "T": np.array([97.69896938291939, 81.83087367000402, 36.82379525515391]), 

555 "B": np.array([2.3010306170806087, 18.16912632999597, 63.17620474484609]), 

556 "A": np.array([0.0, 0.0, 0.0]), 

557 } 

558 expected_cod = 0.9184744027161614 

559 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

560 

561 # ------------------------------------------- NO NOISE / AUGER GUESS ------------------------------------------ 

562 

563 test_data = BTModelTRMC().generate_decays() 

564 model = BTModelTRMC() 

565 model.fvalues["k_A"] = None 

566 fit = model.fit(*test_data) 

567 popt_expected = { 

568 "k_T": 0.009999999999996585, 

569 "k_B": 5.000000000015119e-19, 

570 "k_A": 9.99999711161973e-41, 

571 "mu": 9.999999999997167, 

572 "y_0": 0.0, 

573 "N_0": 1000000000000000.0, 

574 } 

575 contribution_expected = { 

576 "T": np.array([97.58015916566838, 81.09165394240388, 35.819832181412764]), 

577 "B": np.array([2.4198405129812923, 18.908321681647912, 64.1794233913077]), 

578 "A": np.array([3.2135031959224633e-07, 2.4375948198990817e-05, 0.0007444272795414486]), 

579 } 

580 expected_cod = 1.0 

581 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

582 

583 def test_grid_fitting(self) -> None: 

584 

585 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

586 

587 xs_data, ys_data, N0s = BTModelTRMC().generate_decays() 

588 analysis = BTModelTRMC().grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

589 popt_expected = { 

590 "k_B": 5.000049117261053e-19, 

591 "k_T": 0.009999990976318993, 

592 "mu": 9.999999709727632, 

593 "k_A": 0.0, 

594 "y_0": 0.0, 

595 "N_0": 1000000000000000.0, 

596 } 

597 contribution_expected = { 

598 "T": np.array([97.58013435051565, 81.09151711799227, 35.81980706408841]), 

599 "B": np.array([2.4198656494843624, 18.908482882007725, 64.18019293591158]), 

600 "A": np.array([0.0, 0.0, 0.0]), 

601 } 

602 expected_cod = 0.9999999999993268 

603 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

604 

605 popt_expected = { 

606 "k_B": 5.000049117289082e-19, 

607 "k_T": 0.00999999097631548, 

608 "mu": 9.999999709742937, 

609 "k_A": 0.0, 

610 "y_0": 0.0, 

611 "N_0": 1000000000000000.0, 

612 } 

613 contribution_expected = { 

614 "T": np.array([97.5801343504991, 81.09151711790633, 35.81980706397423]), 

615 "B": np.array([2.419865649500915, 18.90848288209365, 64.18019293602576]), 

616 "A": np.array([0.0, 0.0, 0.0]), 

617 } 

618 expected_cod = 0.9999999999993268 

619 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

620 

621 # ---------------------------------------------------- NOISY --------------------------------------------------- 

622 

623 xs_data, ys_data, N0s = BTModelTRMC().generate_decays(0.05) 

624 analysis = BTModelTRMC().grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

625 popt_expected = { 

626 "k_B": 4.802021953300244e-19, 

627 "k_T": 0.010116289289924205, 

628 "mu": 10.052693492580108, 

629 "k_A": 0.0, 

630 "y_0": 0.0, 

631 "N_0": 1000000000000000.0, 

632 } 

633 contribution_expected = { 

634 "T": np.array([97.6989696130472, 81.83087511653866, 36.82379726558527]), 

635 "B": np.array([2.3010303869527995, 18.169124883461336, 63.17620273441473]), 

636 "A": np.array([0.0, 0.0, 0.0]), 

637 } 

638 expected_cod = 0.9184744027161531 

639 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

640 

641 popt_expected = { 

642 "k_B": 4.802026294974155e-19, 

643 "k_T": 0.010116289206037831, 

644 "mu": 10.052694327271345, 

645 "k_A": 0.0, 

646 "y_0": 0.0, 

647 "N_0": 1000000000000000.0, 

648 } 

649 contribution_expected = { 

650 "T": np.array([97.69896757743622, 81.83086232245338, 36.82377951378882]), 

651 "B": np.array([2.301032422563783, 18.169137677546612, 63.176220486211186]), 

652 "A": np.array([0.0, 0.0, 0.0]), 

653 } 

654 expected_cod = 0.9184744027161734 

655 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

656 

657 # ------------------------------------------- NO NOISE / AUGER GUESS ------------------------------------------- 

658 

659 xs_data, ys_data, N0s = BTModelTRMC().generate_decays() 

660 model = BTModelTRMC() 

661 model.fvalues["k_A"] = None 

662 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

663 popt_expected = { 

664 "k_B": 4.999999999957749e-19, 

665 "k_T": 0.010000000000002705, 

666 "k_A": 9.999973258931717e-41, 

667 "mu": 9.999999999984738, 

668 "y_0": 0.0, 

669 "N_0": 1000000000000000.0, 

670 } 

671 contribution_expected = { 

672 "T": np.array([97.58015916568964, 81.09165394262986, 35.819832182075714]), 

673 "B": np.array([2.4198405129608025, 18.908321681480082, 64.17942339242039]), 

674 "A": np.array([3.213495530872035e-07, 2.4375890055846062e-05, 0.0007444255039074176]), 

675 } 

676 expected_cod = 1.0 

677 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

678 

679 popt_expected = { 

680 "k_B": 3.898375046878196e-19, 

681 "k_T": 0.010363531958922218, 

682 "k_A": 2.3293175945341933e-36, 

683 "mu": 10.106211747446103, 

684 "y_0": 0.0, 

685 "N_0": 1000000000000000.0, 

686 } 

687 contribution_expected = { 

688 "T": np.array([98.15786554403202, 84.42608368385423, 36.67047807117695]), 

689 "B": np.array([1.8348477970950738, 14.993304359125895, 47.42593643224026]), 

690 "A": np.array([0.007286658872892999, 0.5806119570198793, 15.903585496582778]), 

691 } 

692 expected_cod = 0.9995910035912059 

693 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

694 

695 

696# ------------------------------------------------------ BTD MODEL ----------------------------------------------------- 

697 

698 

699class TestBTDModel: 

700 

701 @pytest.fixture 

702 def model(self) -> BTDModel: 

703 """Example BTDModel""" 

704 

705 return BTDModel(["k_B", "k_T", "k_D", "N_T", "p_0", "mu_e", "mu_h"]) 

706 

707 def test_initialization(self, model) -> None: 

708 

709 assert model.units["k_B"] == "cm3/ns" 

710 assert model.factors["k_B"] == 1e-20 

711 assert model.n_keys == ["n_e", "n_t", "n_h"] 

712 

713 def test_rate_equations(self, model) -> None: 

714 

715 result = model._rate_equations(n_e=1e17, n_t=1e12, n_h=1e17, **BTD_KWARGS) 

716 assert result == {"n_e": -5711250000000000.0, "n_t": 707919948000000.0, "n_h": -5003330052000000.0} 

717 

718 def test_calculate_concentrations(self, model) -> None: 

719 

720 # Single pulse 

721 output = model._calculate_concentrations(T, 1e17, **BTD_KWARGS) 

722 assert np.allclose(output["n_e"][0][:3], np.array([1.00000000e17, 9.51739441e16, 9.08408676e16])) 

723 assert np.allclose(output["n_t"][0][:3], np.array([0.00000000e00, 5.96016786e13, 5.96021105e13])) 

724 assert np.allclose(output["n_h"][0][:3], np.array([1.00000000e17, 9.52335458e16, 9.09004697e16])) 

725 assert np.allclose(output["n_e"][0][:3] + output["n_t"][0][:3], output["n_h"][0][:3]) 

726 

727 # Multiple pulses 

728 output = model._calculate_concentrations(T, 1e17, **BTD_KWARGS, p=1000) 

729 assert np.allclose(output["n_e"][-1][:3], np.array([1.16972060e17, 1.10497012e17, 1.04700620e17])) 

730 assert np.allclose(output["n_t"][-1][:3], np.array([5.95997634e13, 5.96022059e13, 5.96021815e13])) 

731 assert np.allclose(output["n_h"][-1][:3], np.array([1.17031660e17, 1.10556615e17, 1.04760222e17])) 

732 assert np.allclose(output["n_e"][-1][:3] + output["n_t"][-1][:3], output["n_h"][-1][:3]) 

733 assert len(output["n_e"]) == 5 

734 

735 # Multiple pulses - No stabilisation 

736 with pytest.raises(AssertionError): 

737 model._calculate_concentrations(T, 1e17, **BTD_KWARGS, p=3) 

738 

739 def test_get_carrier_concentrations(self, model) -> None: 

740 

741 popts = [{"I0": 1.0, "N_0": 1e17, **BTD_KWARGS}] 

742 

743 # Period provided 

744 output = model.get_carrier_concentrations([T], popts, 100) 

745 assert np.allclose(output[2][0]["n_e"][:3], np.array([1.00000000e17, 9.74992405e16, 9.51739441e16])) 

746 assert np.allclose(output[2][0]["n_t"][:3], np.array([0.00000000e00, 5.94487983e13, 5.96016786e13])) 

747 assert np.allclose(output[2][0]["n_h"][:3], np.array([1.00000000e17, 9.75586893e16, 9.52335458e16])) 

748 assert np.allclose(output[0][0][:3], np.array([0.0, 0.00498008, 0.00996016])) 

749 

750 # Period not provided 

751 output = model.get_carrier_concentrations([T], popts, 0) 

752 assert np.allclose(output[2][0]["n_e"][:3], np.array([1.00000000e17, 9.51739441e16, 9.08408676e16])) 

753 assert np.allclose(output[2][0]["n_t"][:3], np.array([0.00000000e00, 5.96016786e13, 5.96021105e13])) 

754 assert np.allclose(output[2][0]["n_h"][:3], np.array([1.00000000e17, 9.52335458e16, 9.09004697e16])) 

755 assert np.allclose(output[0][0][:3], np.array([0.0, 1.0, 2.0])) 

756 

757 def test_get_contribution_recommendations(self, model) -> None: 

758 

759 contributions = {"B": np.array([5]), "T": np.array([5]), "D": np.array([8])} 

760 recs = model.get_contribution_recommendations(contributions) 

761 expected = [ 

762 "This fit predicts low bimolecular. The values associated with this process may be inaccurate.\nIt " 

763 "is recommended to measure your sample under higher excitation fluence for this process to become significant", 

764 "This fit predicts low trapping. The values associated with this process may be inaccurate.\nIt is recommended to " 

765 "measure your sample under lower excitation fluence for this process to become significant", 

766 "This fit predicts low detrapping. The values associated with this process may be inaccurate.", 

767 "Note: For the bimolecular-trapping-detrapping model, although a low contribution suggests that the " 

768 "parameter associated with the process are not be accurate, a non-negligible contribution does not " 

769 "automatically indicate that the parameters retrieved are accurate due to the complex nature of the " 

770 "model. It is recommended to perform a grid fitting analysis with this model.", 

771 ] 

772 assert recs == expected 

773 

774 def test_calculate_trpl(self, model) -> None: 

775 

776 result = model.calculate_trpl(T, N_0=1e15, **BTD_KWARGS) 

777 assert np.allclose(result[:3], np.array([1.0, 0.99221134, 0.98523199])) 

778 

779 def test_calculate_trmc(self, model) -> None: 

780 

781 result = model.calculate_trmc(T, N_0=1e17, **BTD_KWARGS) 

782 assert np.allclose(result[:3], np.array([50.0, 47.60485258, 45.43831441])) 

783 

784 

785class TestBTDModelTRPL: 

786 

787 def test_calculate_fit_quantity(self) -> None: 

788 

789 result = BTDModelTRPL().calculate_fit_quantity(T, N_0=1e15, **BTD_KWARGS) 

790 assert np.allclose(result[:3], np.array([1.0, 0.99221134, 0.98523199])) 

791 

792 def test_calculate_contributions(self) -> None: 

793 

794 concentrations = BTDModelTRPL()._calculate_concentrations(T, 1e15, **BTD_KWARGS) 

795 concentrations = {key: value[0] for key, value in concentrations.items()} 

796 contributions = BTDModelTRPL().calculate_contributions(T, **concentrations, **BTD_KWARGS) 

797 expected = { 

798 "T": np.float64(41.01551259497901), 

799 "B": np.float64(56.48980101942542), 

800 "D": np.float64(2.494686385595578), 

801 } 

802 assert contributions == expected 

803 

804 def test_get_carrier_accumulation(self) -> None: 

805 

806 N0s = [1e17, 1e18] 

807 popts = [{"N_0": n, **BTD_KWARGS} for n in N0s] 

808 

809 # 100 ns period 

810 output = BTDModelTRPL().get_carrier_accumulation(popts, 100) 

811 ca_expected = [np.float64(4.624820971652416), np.float64(0.5713873355827237)] 

812 decay_expected = [1.0, 0.99989804, 0.9998979] 

813 assert are_close(output["CA"], ca_expected) 

814 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

815 

816 # 50 ns period 

817 output = BTDModelTRPL().get_carrier_accumulation(popts, 50) 

818 ca_expected = [np.float64(7.852802160625521), np.float64(1.1155396319428357)] 

819 decay_expected = [1.0, 0.99989615, 0.99989602] 

820 assert are_close(output["CA"], ca_expected) 

821 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

822 

823 def test_generate_decays(self) -> None: 

824 

825 # Without noise 

826 xs_data, ys_data, N0s = BTDModelTRPL().generate_decays() 

827 assert are_close(ys_data[0][:3], [1.0, 0.93171234, 0.87145003]) 

828 assert are_close(ys_data[-1][:3], [1.0, 0.9437801, 0.90364939]) 

829 

830 # With noise 

831 xs_data, ys_data, N0s = BTDModelTRPL().generate_decays(noise=0.02) 

832 assert are_close(ys_data[0][:3], [1.0, 0.96670037, 0.89900986]) 

833 assert are_close(ys_data[-1][:3], [1.0, 0.98174779, 0.93649283]) 

834 

835 def test_fit(self) -> None: 

836 

837 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

838 

839 test_data = BTDModelTRPL().generate_decays() 

840 fit = BTDModelTRPL().fit(*test_data) 

841 popt_expected = { 

842 "k_B": 5.000000052524678e-19, 

843 "k_T": 1.2000000020860115e-16, 

844 "k_D": 7.99999965517838e-19, 

845 "N_T": 59999999908205.234, 

846 "p_0": 65000005482068.65, 

847 "y_0": 0.0, 

848 "I": 1.0, 

849 "N_0": 51000000000000.0, 

850 } 

851 contribution_expected = { 

852 "T": np.array([97.55966, 67.41144, 28.74199, 10.17702, 5.60928]), 

853 "B": np.array([1.80154, 23.92938, 61.86576, 85.42388, 92.5306]), 

854 "D": np.array([0.6388, 8.65918, 9.39225, 4.3991, 1.86012]), 

855 } 

856 expected_cod = 1.0 

857 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

858 

859 # ---------------------------------------------------- NOISY --------------------------------------------------- 

860 

861 test_data = BTDModelTRPL().generate_decays(0.05) 

862 fit = BTDModelTRPL().fit(*test_data) 

863 popt_expected = { 

864 "k_B": 5.281696267903158e-19, 

865 "k_T": 1.2005725792145259e-16, 

866 "k_D": 5.907688024451682e-19, 

867 "N_T": 58111233208509.96, 

868 "p_0": 108786247871822.42, 

869 "y_0": 0.0, 

870 "I": 1.0, 

871 "N_0": 51000000000000.0, 

872 } 

873 contribution_expected = { 

874 "T": np.array([96.85071, 63.90628, 25.56837, 8.67334, 4.91477]), 

875 "B": np.array([2.65371, 29.80912, 67.7983, 88.26982, 93.80589]), 

876 "D": np.array([0.49558, 6.2846, 6.63334, 3.05684, 1.27934]), 

877 } 

878 expected_cod = 0.9219066553006975 

879 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

880 

881 # --------------------------------------------- NOISY / NON-FIXED I -------------------------------------------- 

882 

883 test_data = BTDModelTRPL().generate_decays(0.05) 

884 model = BTDModelTRPL() 

885 model.fvalues["I"] = None 

886 fit = model.fit(*test_data) 

887 popt_expected = { 

888 "k_B": 5.231708686847702e-19, 

889 "k_T": 1.187069255529963e-16, 

890 "k_D": 6.120441223117572e-19, 

891 "N_T": 60333138581717.695, 

892 "p_0": 97515750573609.22, 

893 "I": 1.0431146652007544, 

894 "y_0": 0.0, 

895 "N_0": 51000000000000.0, 

896 } 

897 contribution_expected = { 

898 "T": np.array([97.19901, 65.60995, 26.60699, 9.08448, 5.11529]), 

899 "B": np.array([2.31978, 27.78817, 66.25586, 87.61144, 93.49959]), 

900 "D": np.array([0.48121, 6.60188, 7.13715, 3.30408, 1.38512]), 

901 } 

902 expected_cod = 0.9221545665271867 

903 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

904 

905 def test_grid_fitting(self) -> None: 

906 

907 model = BTDModelTRPL() 

908 model.gvalues_range["k_B"] = [1e-20] 

909 model.gvalues_range["k_T"] = [1e-16] 

910 model.gvalues_range["p_0"] = [1e14] 

911 

912 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

913 

914 xs_data, ys_data, N0s = model.generate_decays() 

915 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

916 popt_expected = { 

917 "k_B": 5.044092519858126e-19, 

918 "k_T": 1.221507144388288e-16, 

919 "k_D": 3.2627489522773756e-19, 

920 "p_0": 201606458996472.16, 

921 "N_T": 58686486048362.56, 

922 "y_0": 0.0, 

923 "I": 1.0, 

924 "N_0": 51000000000000.0, 

925 } 

926 contribution_expected = { 

927 "T": np.array([96.06959, 59.97392, 22.63214, 7.46053, 4.46979]), 

928 "B": np.array([3.67138, 36.55816, 73.6027, 90.75852, 94.78018]), 

929 "D": np.array([0.25903, 3.46791, 3.76516, 1.78095, 0.75003]), 

930 } 

931 expected_cod = 0.9999689786967758 

932 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

933 popt_expected = { 

934 "k_B": 5.0000000437758285e-19, 

935 "k_T": 1.200000008803637e-16, 

936 "k_D": 8.000000823783076e-19, 

937 "p_0": 64999983771959.49, 

938 "N_T": 59999999918573.16, 

939 "y_0": 0.0, 

940 "I": 1.0, 

941 "N_0": 51000000000000.0, 

942 } 

943 contribution_expected = { 

944 "T": np.array([97.55966, 67.41144, 28.74199, 10.17702, 5.60928]), 

945 "B": np.array([1.80154, 23.92937, 61.86576, 85.42388, 92.5306]), 

946 "D": np.array([0.6388, 8.65918, 9.39225, 4.3991, 1.86012]), 

947 } 

948 expected_cod = 0.9999999999999998 

949 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

950 

951 # ---------------------------------------------------- NOISY --------------------------------------------------- 

952 

953 xs_data, ys_data, N0s = model.generate_decays(0.05) 

954 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

955 popt_expected = { 

956 "k_B": 5.281625280355903e-19, 

957 "k_T": 1.2005402935900586e-16, 

958 "k_D": 5.911623649351193e-19, 

959 "p_0": 108688274641093.72, 

960 "N_T": 58112578119929.21, 

961 "y_0": 0.0, 

962 "I": 1.0, 

963 "N_0": 51000000000000.0, 

964 } 

965 contribution_expected = { 

966 "T": np.array([96.85187, 63.91184, 25.5731, 8.67546, 4.91564]), 

967 "B": np.array([2.65221, 29.79925, 67.78904, 88.26562, 93.80414]), 

968 "D": np.array([0.49592, 6.28891, 6.63786, 3.05892, 1.28022]), 

969 } 

970 expected_cod = 0.9219066554530871 

971 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

972 popt_expected = { 

973 "k_B": 5.281014612329833e-19, 

974 "k_T": 1.2004301649017149e-16, 

975 "k_D": 5.952924043139263e-19, 

976 "p_0": 107673071106750.62, 

977 "N_T": 58122054681198.03, 

978 "y_0": 0.0, 

979 "I": 1.0, 

980 "N_0": 51000000000000.0, 

981 } 

982 contribution_expected = { 

983 "T": np.array([96.86386, 63.9668, 25.62092, 8.69737, 4.92498]), 

984 "B": np.array([2.63673, 29.69927, 67.6943, 88.22221, 93.78573]), 

985 "D": np.array([0.49942, 6.33393, 6.68478, 3.08042, 1.28929]), 

986 } 

987 expected_cod = 0.9219066470404309 

988 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

989 

990 # -------------------------------------------- NO NOISE/ NON-FIXED I ------------------------------------------- 

991 

992 xs_data, ys_data, N0s = model.generate_decays(0.05) 

993 model.fvalues["I"] = None 

994 model.gvalues_range["k_D"] = [1e-18] 

995 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

996 popt_expected = { 

997 "k_B": 5.233892964974858e-19, 

998 "k_T": 1.1867745425973867e-16, 

999 "k_D": 6.055792534709746e-19, 

1000 "p_0": 98983350486299.22, 

1001 "N_T": 60330106371418.1, 

1002 "I": 1.0430840799173018, 

1003 "y_0": 0.0, 

1004 "N_0": 51000000000000.0, 

1005 } 

1006 contribution_expected = { 

1007 "T": np.array([97.18283, 65.53522, 26.53384, 9.04918, 5.09949]), 

1008 "B": np.array([2.34114, 27.93346, 66.40415, 87.68164, 93.53021]), 

1009 "D": np.array([0.47603, 6.53132, 7.06201, 3.26918, 1.3703]), 

1010 } 

1011 expected_cod = 0.9221545717141318 

1012 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

1013 popt_expected = { 

1014 "k_B": 5.231318664511353e-19, 

1015 "k_T": 1.1868317014121706e-16, 

1016 "k_D": 6.1288804671095715e-19, 

1017 "p_0": 97302985162596.94, 

1018 "N_T": 60340004786445.39, 

1019 "I": 1.0431027851890577, 

1020 "y_0": 0.0, 

1021 "N_0": 51000000000000.0, 

1022 } 

1023 contribution_expected = { 

1024 "T": np.array([97.20144, 65.62315, 26.61848, 9.08953, 5.11716]), 

1025 "B": np.array([2.31668, 27.76541, 66.23378, 87.60143, 93.49561]), 

1026 "D": np.array([0.48187, 6.61144, 7.14774, 3.30904, 1.38723]), 

1027 } 

1028 expected_cod = 0.9221545624359501 

1029 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

1030 

1031 

1032class TestBTDModelTRMC: 

1033 

1034 def test_calculate_fit_quantity(self) -> None: 

1035 

1036 result = BTDModelTRMC().calculate_fit_quantity(T, N_0=1e17, **BTD_KWARGS) 

1037 assert np.allclose(result[:3], np.array([50.0, 47.60485258, 45.43831441])) 

1038 

1039 def test_calculate_contributions(self) -> None: 

1040 

1041 concentrations = BTDModelTRMC()._calculate_concentrations(T, 1e15, **BTD_KWARGS) 

1042 concentrations = {key: value[0] for key, value in concentrations.items()} 

1043 contributions = BTDModelTRMC().calculate_contributions(T, **concentrations, **BTD_KWARGS) 

1044 expected = { 

1045 "T": np.float64(33.7114972857804), 

1046 "B": np.float64(62.7295706296424), 

1047 "D": np.float64(3.558932084577181), 

1048 } 

1049 assert contributions == expected 

1050 

1051 def test_get_carrier_accumulation(self) -> None: 

1052 

1053 N0s = [1e17, 1e18] 

1054 popts = [{"N_0": n, **BTD_KWARGS} for n in N0s] 

1055 

1056 # 100 ns period 

1057 output = BTDModelTRMC().get_carrier_accumulation(popts, 100) 

1058 ca_expected = [np.float64(3.912967640970455), np.float64(0.48314189311882694)] 

1059 decay_expected = [50.97705122, 50.97445232, 50.97444872] 

1060 assert are_close(output["CA"], ca_expected) 

1061 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

1062 

1063 # 50 ns period 

1064 output = BTDModelTRMC().get_carrier_accumulation(popts, 50) 

1065 ca_expected = [np.float64(6.63910507746735), np.float64(0.9422743536986633)] 

1066 decay_expected = [51.92211319, 51.91941704, 51.9194135] 

1067 assert are_close(output["CA"], ca_expected) 

1068 assert are_close(output["Pulse S"][-1][:3], decay_expected) 

1069 

1070 def test_generate_decays(self) -> None: 

1071 

1072 # Without noise 

1073 xs_data, ys_data, N0s = BTDModelTRMC().generate_decays() 

1074 assert are_close(ys_data[0][:3], [50.0, 44.46133618, 41.08877032]) 

1075 assert are_close(ys_data[-1][:3], [50.0, 44.68626895, 40.55753199]) 

1076 

1077 # With noise 

1078 xs_data, ys_data, N0s = BTDModelTRMC().generate_decays(noise=0.02) 

1079 assert are_close(ys_data[0][:3], [48.92553402, 45.17205085, 41.50080638]) 

1080 assert are_close(ys_data[-1][:3], [49.25755246, 45.85575692, 41.50440705]) 

1081 

1082 def test_fit(self) -> None: 

1083 

1084 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

1085 

1086 test_data = BTDModelTRMC().generate_decays() 

1087 fit = BTDModelTRMC().fit(*test_data) 

1088 popt_expected = { 

1089 "k_B": 5.000000164708902e-19, 

1090 "k_T": 1.200000031835309e-16, 

1091 "k_D": 7.999999808965121e-19, 

1092 "N_T": 59999999376176.54, 

1093 "p_0": 65000001818720.9, 

1094 "mu_e": 20.000000281407857, 

1095 "mu_h": 29.99999994350886, 

1096 "y_0": 0.0, 

1097 "N_0": 51000000000000.0, 

1098 } 

1099 contribution_expected = { 

1100 "T": np.array([40.90276, 32.92583, 20.94199, 13.13185, 9.81368]), 

1101 "B": np.array([1.59628, 21.39947, 52.96585, 75.52489, 84.90275]), 

1102 "D": np.array([57.50096, 45.6747, 26.09216, 11.34326, 5.28357]), 

1103 } 

1104 expected_cod = 1.0 

1105 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

1106 

1107 # ---------------------------------------------------- NOISY --------------------------------------------------- 

1108 

1109 test_data = BTDModelTRMC().generate_decays(0.05) 

1110 fit = BTDModelTRMC().fit(*test_data) 

1111 popt_expected = { 

1112 "k_B": 5.170253809634543e-19, 

1113 "k_T": 1.1017602528332628e-16, 

1114 "k_D": 8.109404996553888e-19, 

1115 "N_T": 57522615670991.11, 

1116 "p_0": 63326777368258.42, 

1117 "mu_e": 20.819525615904908, 

1118 "mu_h": 29.662688836182774, 

1119 "y_0": 0.0, 

1120 "N_0": 51000000000000.0, 

1121 } 

1122 contribution_expected = { 

1123 "T": np.array([41.93316, 33.03053, 20.50887, 12.50261, 9.1934]), 

1124 "B": np.array([1.91827, 23.1565, 54.71674, 76.79712, 85.84359]), 

1125 "D": np.array([56.14857, 43.81296, 24.7744, 10.70027, 4.96301]), 

1126 } 

1127 expected_cod = 0.9072435319931186 

1128 assert_fit(fit, popt_expected, contribution_expected, expected_cod) 

1129 

1130 def test_grid_fitting(self) -> None: 

1131 

1132 model = BTDModelTRMC() 

1133 model.gvalues_range["k_B"] = [1e-20] 

1134 model.gvalues_range["k_T"] = [1e-16] 

1135 model.gvalues_range["k_D"] = [1e-18] 

1136 model.gvalues_range["p_0"] = [1e14] 

1137 

1138 # -------------------------------------------------- NO NOISE -------------------------------------------------- 

1139 

1140 xs_data, ys_data, N0s = model.generate_decays() 

1141 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

1142 popt_expected = { 

1143 "k_B": 5.000000263245968e-19, 

1144 "k_T": 1.200000041725577e-16, 

1145 "k_D": 7.999999617651796e-19, 

1146 "p_0": 65000003889440.46, 

1147 "N_T": 59999999181832.375, 

1148 "mu_e": 20.000000542628477, 

1149 "mu_h": 29.9999999067198, 

1150 "y_0": 0.0, 

1151 "N_0": 51000000000000.0, 

1152 } 

1153 contribution_expected = { 

1154 "T": np.array([40.90276, 32.92583, 20.94199, 13.13185, 9.81368]), 

1155 "B": np.array([1.59628, 21.39947, 52.96585, 75.52489, 84.90275]), 

1156 "D": np.array([57.50096, 45.6747, 26.09216, 11.34326, 5.28357]), 

1157 } 

1158 expected_cod = 0.9999999999999999 

1159 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

1160 

1161 popt_expected = { 

1162 "k_B": 5.000000344817324e-19, 

1163 "k_T": 1.2000000694970784e-16, 

1164 "k_D": 7.999999442799611e-19, 

1165 "p_0": 65000005667179.016, 

1166 "N_T": 59999998470043.97, 

1167 "mu_e": 20.00000063847375, 

1168 "mu_h": 29.999999859906556, 

1169 "y_0": 0.0, 

1170 "N_0": 51000000000000.0, 

1171 } 

1172 contribution_expected = { 

1173 "T": np.array([40.90276, 32.92583, 20.94199, 13.13185, 9.81368]), 

1174 "B": np.array([1.59628, 21.39947, 52.96585, 75.52489, 84.90275]), 

1175 "D": np.array([57.50096, 45.6747, 26.09216, 11.34326, 5.28357]), 

1176 } 

1177 expected_cod = 0.9999999999999994 

1178 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod) 

1179 

1180 # ---------------------------------------------------- NOISY --------------------------------------------------- 

1181 

1182 xs_data, ys_data, N0s = model.generate_decays(0.05) 

1183 analysis = model.grid_fitting(None, N0s, xs_data=xs_data, ys_data=ys_data) 

1184 popt_expected = { 

1185 "k_B": 5.169968590421277e-19, 

1186 "k_T": 1.1017306106751546e-16, 

1187 "k_D": 8.120500700336253e-19, 

1188 "p_0": 63209041226835.805, 

1189 "N_T": 57509469769832.9, 

1190 "mu_e": 20.816413593836856, 

1191 "mu_h": 29.663528347986045, 

1192 "y_0": 0.0, 

1193 "N_0": 51000000000000.0, 

1194 } 

1195 contribution_expected = { 

1196 "T": np.array([41.92978, 33.03175, 20.51353, 12.50502, 9.19378]), 

1197 "B": np.array([1.91722, 23.14519, 54.69963, 76.78697, 85.83906]), 

1198 "D": np.array([56.153, 43.82306, 24.78684, 10.70801, 4.96716]), 

1199 } 

1200 expected_cod = 0.9072435361184141 

1201 assert_fit(analysis[0], popt_expected, contribution_expected, expected_cod) 

1202 popt_expected = { 

1203 "k_B": 5.170343293253923e-19, 

1204 "k_T": 1.1018194467749615e-16, 

1205 "k_D": 8.121236402177761e-19, 

1206 "p_0": 63200380749480.586, 

1207 "N_T": 57501357284825.3, 

1208 "mu_e": 20.817089249169957, 

1209 "mu_h": 29.662940880799475, 

1210 "y_0": 0.0, 

1211 "N_0": 51000000000000.0, 

1212 } 

1213 contribution_expected = { 

1214 "T": np.array([41.93091, 33.03105, 20.51274, 12.50452, 9.19342]), 

1215 "B": np.array([1.91766, 23.14931, 54.70297, 76.78871, 85.84002]), 

1216 "D": np.array([56.15143, 43.81964, 24.78429, 10.70677, 4.96656]), 

1217 } 

1218 expected_cod = 0.9072435358895351 

1219 assert_fit(analysis[-1], popt_expected, contribution_expected, expected_cod)