TDCRPy 0.0.6__py3-none-any.whl → 0.0.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
TDCRPy/TDCRPy.py ADDED
@@ -0,0 +1,425 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created on Mon Jan 23 16:01:49 2023
4
+
5
+ A Monte-Carlo code to calculate detection efficiency in TDCR measurements
6
+
7
+ @author: Romain Coulon, Jialin Hu
8
+ Bureau International des Poids et Mesures
9
+ """
10
+
11
+ ## IMPORT PYTHON MODULES
12
+ import TDCR_model_lib as tl
13
+ import numpy as np
14
+
15
+ def TDCRPy(L, TD, TAB, TBC, TAC, Rad, pmf_1, N, kB, RHO, nE, mode, mode2, Display=False):
16
+ """
17
+ This is a Monte-Carlo TDCR model
18
+
19
+ Parameters
20
+ ----------
21
+ L : Float (if mode2="sym") or a tuple (if mode2="asym")
22
+ Free parameter.
23
+ TD : Float
24
+ Measured TDCR.
25
+ Rad : string
26
+ List of radionuclides.
27
+ pmf_1 : string
28
+ list of probability of each radionuclide.
29
+ N : integer
30
+ Number of Monte-Carlo trials.
31
+ kB : float
32
+ Birks constant.
33
+ RHO : float
34
+ Density of the scintillator.
35
+ nE : integer
36
+ Number of bins for the quenching function.
37
+ mode : string
38
+ "res" to return the residual, "eff" to return efficiencies.
39
+ mode2 : string
40
+ "sym" for symetrical model, "asym" for symetrical model.
41
+ Display : Boolean, optional
42
+ "True" to display details on the decay sampling. The default is False.
43
+
44
+ Returns
45
+ -------
46
+ List
47
+ if mode=="res", the residual (float).
48
+ if mode=="eff", the efficiencies (list)
49
+
50
+ """
51
+
52
+
53
+ Rad=Rad.replace(" ","")
54
+ Rad=Rad.split(",")
55
+ pmf_1=pmf_1.split(",")
56
+ pmf_1 = [float(x) for x in pmf_1]
57
+
58
+ if np.size(pmf_1) > 1:
59
+ if sum(pmf_1) !=1: print("warning p not equal to 1")
60
+ elif pmf_1[0] != 1: print("warning")
61
+
62
+ """
63
+ Read PenNuc File
64
+ """
65
+ out_PenNuc = []
66
+ particle = [] # Particle(s) from the Mother -- indice 3
67
+ p_branch = [] # Probablity of the different decay of branch -- indice 5
68
+ e_branch = [] # Energy of the different decay of branch -- indice 4
69
+ LevelDaughter = [] # Level of the Daughter nucleus just after the particle emission -- indice 6
70
+ levelNumber = [] # The vector of level of the daughter to get information of all possible isomeric transitions -- indice 11
71
+ prob_trans = [] # Probability for each transition -- indice 10
72
+ prob_branch = [] # Probability for each branch -- indice 7
73
+ levelEnergy = [] # Energy of each level -- indice 13
74
+ transitionType = [] # type of each possible transitions (internal transitions or gamma emission) -- indice 8
75
+ e_trans = [] # Energy of the transition -- indice 9
76
+ next_level = [] # Next level on the daughter nucleus -- indice 12
77
+ Q_value = [] # Energy of the reaction -- indice 2
78
+ DaughterVec = [] # Daughters -- indice 0
79
+ Pdaughter = [] # Probabiblity related to daughters -- indice 1
80
+ Transition_prob_sum = []
81
+
82
+ for rad_i in Rad:
83
+ out_PenNuc = tl.readPenNuc2(rad_i)
84
+ particle.append(out_PenNuc[3]) # Particle(s) from the Mother -- indice 3
85
+ p_branch.append(out_PenNuc[5]) # Probablity of the different decay of branch -- indice 5
86
+ e_branch.append(out_PenNuc[4]) # Energy of the different decay of branch -- indice 4
87
+ LevelDaughter.append(out_PenNuc[6]) # Level of the Daughter nucleus just after the particle emission -- indice 6
88
+ levelNumber.append(out_PenNuc[11]) # The vector of level of the daughter to get information of all possible isomeric transitions -- indice 11
89
+ prob_trans.append(out_PenNuc[10]) # Probability for each transition -- indice 10
90
+ prob_branch.append(out_PenNuc[7]) # Probability for each branch -- indice 7
91
+ levelEnergy.append(out_PenNuc[13]) # Energy of each level -- indice 13
92
+ transitionType.append(out_PenNuc[8]) # type of each possible transitions (internal transitions or gamma emission) -- indice 8
93
+ e_trans.append(out_PenNuc[9]) # Energy of the transition -- indice 9
94
+ next_level.append(out_PenNuc[12]) # Next level on the daughter nucleus -- indice 12
95
+ Q_value.append(out_PenNuc[2]) # Energy of the reaction -- indice 2
96
+ DaughterVec.append(out_PenNuc[0]) # Daughters -- indice 0
97
+ Pdaughter.append(out_PenNuc[1]) # Probabiblity related to daughters -- indice 1
98
+ Transition_prob_sum.append(out_PenNuc[14])
99
+
100
+ efficiency_S = []
101
+ efficiency_D = []
102
+ efficiency_T = []
103
+ efficiency_AB = []
104
+ efficiency_BC = []
105
+ efficiency_AC = []
106
+ for i in range(N): # Main Loop - Monte Carlo trials
107
+ particle_vec=[]
108
+ energy_vec=[]
109
+ '''
110
+ ===============================
111
+ 0. SAMPLING OF THE RADIONUCLIDE
112
+ ===============================
113
+ '''
114
+ index_rad = tl.sampling(pmf_1)
115
+ rad_i = Rad[index_rad]
116
+ if Display: print("\n Sampled radionuclide = ", rad_i, "- L = ", L, ' keV-1 - kB = ', kB, ' cm/keV')
117
+
118
+ '''
119
+ ===========================
120
+ I. DESINTEGRATION NUCLEAIRE
121
+ ===========================
122
+ '''
123
+ #=========================
124
+ # Sampling of the daughter
125
+ #=========================
126
+ iDaughter=tl.sampling(np.asarray(Pdaughter[index_rad])/sum(np.asarray(Pdaughter[index_rad])))
127
+ Daughter = DaughterVec[index_rad][iDaughter]
128
+ if Display: print("\t Sampled daughter:")
129
+ if Display: print("\t\t Daughter = ", Daughter)
130
+ #=============================
131
+ # Sampling of the decay branch
132
+ #=============================
133
+ branch_i = tl.normalise(prob_branch[index_rad][iDaughter]) # normalise la proba de branch
134
+ i_branch=tl.sampling(branch_i) # indice de la branche globale
135
+ if p_branch[index_rad][iDaughter][i_branch] != []:
136
+ branch_proba = tl.normalise(p_branch[index_rad][iDaughter][i_branch])
137
+ index_subBranch = tl.sampling(branch_proba) # indice de la branch precise
138
+ particle_branch = particle[index_rad][iDaughter][i_branch][index_subBranch] # sampled particle emitted by the mother
139
+ energy_branch = e_branch[index_rad][iDaughter][i_branch][index_subBranch] # energy of the particle emitted by the mother
140
+ # probability_branch = p_branch[index_rad][iDaughter][i_branch][index_subBranch] # probability of the sampled branch
141
+ levelOftheDaughter = LevelDaughter[index_rad][iDaughter][i_branch][index_subBranch] # Level of the daughter just after the particle emission from the mother
142
+ level_before_trans = LevelDaughter[index_rad][iDaughter][i_branch][index_subBranch]
143
+ if Display: print("\t Sampled decay branch:")
144
+ if Display: print("\t\t Particle = ", particle_branch)
145
+ if Display: print("\t\t Energy of the particle = ", energy_branch, " keV")
146
+ if Display: print("\t\t Level of the daughter nucleus = ", levelOftheDaughter)
147
+ #========
148
+ # Scoring
149
+ #========
150
+ e_sum = energy_branch # Update the Energy summary
151
+ particle_vec.append(particle_branch) # Update of the particle vector
152
+ energy_vec.append(energy_branch) # Update of the energy of the particle
153
+ else:
154
+ transition_prob = tl.normalise(Transition_prob_sum[index_rad][iDaughter])
155
+ index_transition_level = tl.sampling(transition_prob)
156
+ levelOftheDaughter = levelNumber[index_rad][iDaughter][index_transition_level][0]
157
+ if Display: print("\t Sampled decay branch:")
158
+ if Display: print("\t\t Particle = isomeric transition, no particle")
159
+ if Display: print("\t\t Level of the nucleus : ",levelOftheDaughter)
160
+ e_sum = 0
161
+
162
+ '''
163
+ ==============
164
+ I-1 Transition
165
+ ==============
166
+ '''
167
+
168
+ if Display: print("\t Subsequent isomeric transition") # finish with the mother / now with the daughter
169
+ while levelOftheDaughter > 0: # Go on the loop while the daughter nucleus is a its fundamental level (energy 0)
170
+ i_level = levelNumber[index_rad][iDaughter].index([levelOftheDaughter]) # Find the position in the daughter level vector
171
+ if transitionType[index_rad][iDaughter][i_level] != []:
172
+ #====================================================================
173
+ # Sampling of the transition in energy levels of the daughter nucleus
174
+ #====================================================================
175
+ probability_tran = tl.normalise(prob_trans[index_rad][iDaughter][i_level]) # normaliser la proba de transition
176
+ index_t = tl.sampling(probability_tran) # indice de la transition
177
+ if Display: print("\t\t Energy of the level = ", levelEnergy[index_rad][iDaughter][i_level], " keV")
178
+ if Display: print("\t\t Transition type = ", transitionType[index_rad][iDaughter][i_level][index_t])
179
+ if Display: print("\t\t Energy of the transition = ", e_trans[index_rad][iDaughter][i_level][index_t], "keV")
180
+ if Display: print("\t\t next level = ", next_level[index_rad][iDaughter][i_level][index_t])
181
+ #========
182
+ # Scoring
183
+ #========
184
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "GA": # if it is a gamma that has been emitted
185
+ particle_vec.append("gamma") # Update of the particle vector
186
+ energy_vec.append(e_trans[index_rad][iDaughter][i_level][index_t]) # Update the energy vector
187
+ else: # if not, it is a internal conversion, so an electron
188
+ particle_vec.append("electron") # !!!!!!!!! it is OK for our model? Does the electron leave with the kinetic enegy of the transition
189
+ energy_vec.append(e_trans[index_rad][iDaughter][i_level][index_t]) # Update the energy vector
190
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EK": # record that an electron is missing on the K shell of the dughter nucleus
191
+ particle_vec.append("Atom_K")
192
+ energy_vec.append(0)
193
+
194
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EL": # record that an electron is missing on the L1 shell of the dughter nucleus
195
+ particle_vec.append("Atom_L")
196
+ energy_vec.append(0)
197
+
198
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EL1": # record that an electron is missing on the L1 shell of the dughter nucleus
199
+ particle_vec.append("Atom_L1")
200
+ energy_vec.append(0)
201
+
202
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EL2": # record that an electron is missing on the L2 shell of the dughter nucleus
203
+ particle_vec.append("Atom_L2")
204
+ energy_vec.append(0)
205
+
206
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EL3": # record that an electron is missing on the L3 shell of the dughter nucleus
207
+ particle_vec.append("Atom_L3")
208
+ energy_vec.append(0)
209
+
210
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EM": # record that an electron is missing on the M shell of the dughter nucleus
211
+ particle_vec.append("Atom_M")
212
+ energy_vec.append(0)
213
+
214
+ if transitionType[index_rad][iDaughter][i_level][index_t] == "EN": # record that an electron is missing on the N shell of the dughter nucleus
215
+ particle_vec.append("Atom_N")
216
+ energy_vec.append(0)
217
+
218
+ e_sum += e_trans[index_rad][iDaughter][i_level][index_t] # Energy summary
219
+ levelOftheDaughter = next_level[index_rad][iDaughter][i_level][index_t] # set the next level
220
+ else:
221
+ i_level = levelNumber[index_rad][iDaughter].index([levelOftheDaughter])
222
+ print("warning:pas de données de transition:daughter,niveau,niveau d'énergie",DaughterVec[index_rad][iDaughter],levelOftheDaughter,levelEnergy[index_rad][iDaughter][i_level] )
223
+ levelOftheDaughter = 0 # set the next level
224
+
225
+ if Display: print("\t Summary of the nuclear decay")
226
+ if Display: print("\t\t particles : ", particle_vec)
227
+ if Display: print("\t\t energy : ", energy_vec, "keV")
228
+
229
+ '''
230
+ ==========================
231
+ II. LA RELAXATION ATOMIQUE
232
+ ==========================
233
+ '''
234
+ daughter_relax = DaughterVec[index_rad][iDaughter]
235
+ for i_part in range(len(particle_vec)):
236
+ relaxation = False
237
+ if "Atom_K" in particle_vec[i_part] or "Atom_L" in particle_vec[i_part] or "Atom_M" in particle_vec[i_part]:
238
+ relaxation = True
239
+ while relaxation:
240
+ tf,ef = tl.relaxation_atom(daughter_relax,Rad[index_rad],particle_vec[i_part])
241
+ if tf == "XKA":
242
+ particle_vec[i_part] = "Atom_L"
243
+ particle_vec.append(tf)
244
+ energy_vec.append(ef)
245
+ relaxation = True
246
+ elif tf == "XKB":
247
+ particle_vec[i_part] = "Atom_M"
248
+ particle_vec.append(tf)
249
+ energy_vec.append(ef)
250
+ relaxation = False
251
+ elif tf == "XL":
252
+ particle_vec[i_part] = "Atom_M"
253
+ particle_vec.append(tf)
254
+ energy_vec.append(ef)
255
+ relaxation = False
256
+ elif tf == "Auger K":
257
+ particle_vec[i_part] = "Atom_L"
258
+ particle_vec.append(tf)
259
+ energy_vec.append(ef)
260
+ relaxation = True
261
+ elif tf == "Auger L":
262
+ particle_vec[i_part] = "Atom_M"
263
+ particle_vec.append(tf)
264
+ energy_vec.append(ef)
265
+ relaxation = False
266
+ else:
267
+ if Display: print("untermined x or Auger")
268
+ relaxation = False
269
+ e_sum += ef
270
+ if Display: print("\t Summary of the atomic relaxation")
271
+ if Display: print("\t\t particles : ", particle_vec)
272
+ if Display: print("\t\t energy : ", energy_vec, "keV")
273
+
274
+ '''
275
+ ==========================================================
276
+ III. INTERACTION RAYONNEMENT/MATIERE + SPECTRES D'EMISSION
277
+ ==========================================================
278
+ '''
279
+ for i, p in enumerate(particle_vec):
280
+ if p == "beta":
281
+ e_b,p_b = tl.readBetaShape(rad_i,"beta-",level_before_trans) # read the data of BetaShape
282
+ index_beta_energy = tl.sampling(p_b) # sampling energy of beta
283
+ particle_vec[i] = "electron"
284
+ energy_vec[i] = e_b[index_beta_energy]
285
+ energy_vec[i] = tl.energie_dep_beta(energy_vec[i])
286
+
287
+ if p == "beta+":
288
+ e_b,p_b = tl.readBetaShape(rad_i,"beta+",level_before_trans)
289
+ index_beta_energy = tl.sampling(p_b)
290
+ particle_vec[i] = "positron"
291
+ energy_vec[i] = e_b[index_beta_energy]
292
+ energy_vec[i] = tl.energie_dep_beta(energy_vec[i])
293
+ particle_vec.append("gamma")
294
+ particle_vec.append("gamma")
295
+ energy_vec.append(511)
296
+ energy_vec.append(511)
297
+
298
+ if p == "gamma" or p == "XKA" or p == "XKB" or p == "XL":
299
+ energy_vec[i] = tl.energie_dep_gamma(energy_vec[i],v=10) # sampling energy free from photon
300
+ particle_vec[i] = "electron"
301
+ if p == "Auger K" or p == "Auger L":
302
+ particle_vec[i] = "electron"
303
+ energy_vec[i] = tl.energie_dep_beta(energy_vec[i])
304
+ if Display: print("\t Summary of the final charged particles")
305
+ if Display: print("\t\t particles : ", particle_vec)
306
+ if Display: print("\t\t energy : ", energy_vec, "keV")
307
+
308
+ '''
309
+ ====================
310
+ IV. LA SCINTILLATION
311
+ Calculation of the scintillation quenching with the Birks Model
312
+ ====================
313
+ '''
314
+ e_quenching=[]
315
+ for i, p in enumerate(particle_vec):
316
+ e_discrete = np.linspace(0,energy_vec[i],nE) # vector for the quenched energy calculation keV
317
+ delta_e = e_discrete[2]-e_discrete[1] #keV
318
+ if p == "alpha":
319
+ energy_vec[i] = np.cumsum(delta_e/(1+kB*tl.stoppingpowerA(e_discrete)))[-1]
320
+ e_quenching.append(energy_vec[i])
321
+ elif p == "electron" or p == "positron":
322
+ energy_vec[i] = tl.E_quench_e(energy_vec[i]*1e3,kB*1e3,nE)*1e-3
323
+ e_quenching.append(energy_vec[i])
324
+ else:
325
+ e_quenching.append(0)
326
+ if Display: print("\t Summary of the estimation of quenched energies")
327
+
328
+ if Display: print("\t\t energy_vec : ", energy_vec, "keV")
329
+ if Display: print("\t\t quenched energy : ", e_quenching, "keV")
330
+
331
+ '''
332
+ ====================
333
+ V. LE MESURE TDCR
334
+ ====================
335
+ '''
336
+
337
+ if mode2=="sym":
338
+ p_nosingle = np.exp(-L*np.sum(np.asarray(e_quenching))/3) # probability to have 0 electrons in a PMT
339
+ p_single = 1-p_nosingle # probability to have at least 1 electrons in a PMT
340
+ efficiency_S.append(p_single)
341
+ efficiency_T.append(p_single**3)
342
+ efficiency_D.append(3*(p_single)**2-2*efficiency_T[-1])
343
+ if Display: print("\t Summary of TDCR measurement")
344
+ if Display: print("\t\t Efficiency of single events: ", efficiency_S[-1])
345
+ if Display: print("\t\t Efficiency of double events: ", efficiency_D[-1])
346
+ if Display: print("\t\t Efficiency of triple events: ", efficiency_D[-1])
347
+ elif mode2=="asym":
348
+ pA_nosingle = np.exp(-L[0]*np.sum(np.asarray(e_quenching))/3) # probability to have 0 electrons in a PMT
349
+ pA_single = 1-pA_nosingle # probability to have at least 1 electrons in a PMT
350
+ pB_nosingle = np.exp(-L[1]*np.sum(np.asarray(e_quenching))/3) # probability to have 0 electrons in a PMT
351
+ pB_single = 1-pB_nosingle # probability to have at least 1 electrons in a PMT
352
+ pC_nosingle = np.exp(-L[2]*np.sum(np.asarray(e_quenching))/3) # probability to have 0 electrons in a PMT
353
+ pC_single = 1-pC_nosingle # probability to have at least 1 electrons in a PMT
354
+
355
+ efficiency_AB.append(pA_single*pB_single)
356
+ efficiency_BC.append(pB_single*pC_single)
357
+ efficiency_AC.append(pA_single*pC_single)
358
+ efficiency_T.append(pA_single*pB_single*pC_single)
359
+
360
+ efficiency_D.append(efficiency_AB[-1]+efficiency_BC[-1]+efficiency_AC[-1]-2*efficiency_T[-1])
361
+ efficiency_S.append(pA_single+pB_single+pC_single-efficiency_D[-1]-efficiency_T[-1])
362
+
363
+
364
+
365
+ '''
366
+ ====================
367
+ VI. CALCULATION OF THE FINAL ESTIMATORS
368
+ ====================
369
+ '''
370
+ if mode2=="sym":
371
+ mean_efficiency_T = np.mean(efficiency_T) # average
372
+ std_efficiency_T = np.std(efficiency_T)/np.sqrt(N) # standard deviation
373
+ mean_efficiency_D = np.mean(efficiency_D)
374
+ std_efficiency_D = np.std(efficiency_D)/np.sqrt(N)
375
+ mean_efficiency_S = np.mean(efficiency_S)
376
+ std_efficiency_S = np.std(efficiency_S)/np.sqrt(N)
377
+ TDCR_calcul = mean_efficiency_T/mean_efficiency_D
378
+ elif mode2=="asym":
379
+ mean_efficiency_T = np.mean(efficiency_T) # average
380
+ std_efficiency_T = np.std(efficiency_T)/np.sqrt(N) # standard deviation
381
+ mean_efficiency_D = np.mean(efficiency_D)
382
+ std_efficiency_D = np.std(efficiency_D)/np.sqrt(N)
383
+ mean_efficiency_S = np.mean(efficiency_S)
384
+ std_efficiency_S = np.std(efficiency_S)/np.sqrt(N)
385
+ mean_efficiency_AB = np.mean(efficiency_AB)
386
+ std_efficiency_AB = np.std(efficiency_AB)/np.sqrt(N)
387
+ mean_efficiency_BC = np.mean(efficiency_BC)
388
+ std_efficiency_BC = np.std(efficiency_BC)/np.sqrt(N)
389
+ mean_efficiency_AC = np.mean(efficiency_AC)
390
+ std_efficiency_AC = np.std(efficiency_AC)/np.sqrt(N)
391
+ TDCR_calcul = mean_efficiency_T/mean_efficiency_D
392
+ TABmodel = mean_efficiency_T/mean_efficiency_AB
393
+ TBCmodel = mean_efficiency_T/mean_efficiency_BC
394
+ TACmodel = mean_efficiency_T/mean_efficiency_AC
395
+
396
+
397
+ # x = np.arange(np.mean(efficiency_T),1.001,0.001)
398
+ # plt.figure("efficiency distribution")
399
+ # plt.clf()
400
+ # plt.hist(np.asarray(efficiency_D),bins=x,label="Efficiency of double coincidences")[0]
401
+ # plt.hist(np.asarray(efficiency_T),bins=x,label="Efficiency of triple coincidences")[0]
402
+ # plt.xlabel("Efficiency", fontsize = 14)
403
+ # plt.ylabel(r"Number of counts", fontsize = 14)
404
+ # plt.legend(fontsize = 12)
405
+ # plt.savefig('Effdistribution.png')
406
+
407
+ # x = np.arange(np.mean(tdcr),1.001,0.001)
408
+ # plt.figure("TDCR distribution")
409
+ # plt.clf()
410
+ # plt.hist(np.asarray(tdcr),bins=x,label="Calculated TDCR")[0]
411
+ # plt.plot(x,st.norm.pdf(x, TDCR_measure, u_TDCR_measure),label="measured TDCR")[0]
412
+ # plt.xlabel("Efficiency", fontsize = 14)
413
+ # plt.ylabel(r"Number of counts", fontsize = 14)
414
+ # plt.legend(fontsize = 12)
415
+ # plt.savefig('TDCRdistribution.png')
416
+
417
+ if mode2=="sym":
418
+ res=(TDCR_calcul-TD)**2
419
+ elif mode2=="asym":
420
+ res=(TAB-TABmodel)**2+(TBC-TBCmodel)**2+(TAC-TACmodel)**2
421
+
422
+ if mode == "res":
423
+ return res
424
+ if mode == "eff":
425
+ return mean_efficiency_S, std_efficiency_S, mean_efficiency_D, std_efficiency_D, mean_efficiency_T, std_efficiency_T