libra-toolbox 0.6.1.post1__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.
- libra_toolbox/__init__.py +21 -0
- libra_toolbox/_version.py +21 -0
- libra_toolbox/neutron_detection/__init__.py +1 -0
- libra_toolbox/neutron_detection/activation_foils/__init__.py +1 -0
- libra_toolbox/neutron_detection/activation_foils/calculations.py +188 -0
- libra_toolbox/neutron_detection/activation_foils/calibration.py +39 -0
- libra_toolbox/neutron_detection/activation_foils/compass.py +26 -0
- libra_toolbox/neutron_detection/activation_foils/explicit.py +110 -0
- libra_toolbox/neutron_detection/activation_foils/settings.py +17 -0
- libra_toolbox/neutron_detection/diamond/__init__.py +2 -0
- libra_toolbox/neutron_detection/diamond/process_data.py +128 -0
- libra_toolbox/neutron_detection/diamond/prt.py +745 -0
- libra_toolbox/neutronics/A325_generator_diamond.h5 +0 -0
- libra_toolbox/neutronics/__init__.py +2 -0
- libra_toolbox/neutronics/neutron_source.py +89 -0
- libra_toolbox/neutronics/vault.py +773 -0
- libra_toolbox/tritium/__init__.py +7 -0
- libra_toolbox/tritium/lsc_measurements.py +304 -0
- libra_toolbox/tritium/model.py +340 -0
- libra_toolbox/tritium/plotting.py +320 -0
- libra_toolbox-0.6.1.post1.dist-info/METADATA +45 -0
- libra_toolbox-0.6.1.post1.dist-info/RECORD +25 -0
- libra_toolbox-0.6.1.post1.dist-info/WHEEL +5 -0
- libra_toolbox-0.6.1.post1.dist-info/licenses/LICENSE +21 -0
- libra_toolbox-0.6.1.post1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
try:
|
|
2
|
+
# Python 3.8+
|
|
3
|
+
from importlib import metadata
|
|
4
|
+
except ImportError:
|
|
5
|
+
try:
|
|
6
|
+
# For older Python versions
|
|
7
|
+
import importlib_metadata as metadata
|
|
8
|
+
except ImportError:
|
|
9
|
+
# If both imports fail, set version to unknown
|
|
10
|
+
__version__ = "unknown"
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
# Attempt to get the version of the package
|
|
14
|
+
__version__ = metadata.version("libra_toolbox")
|
|
15
|
+
except Exception:
|
|
16
|
+
# If it fails, set version to unknown
|
|
17
|
+
__version__ = "unknown"
|
|
18
|
+
|
|
19
|
+
from . import tritium
|
|
20
|
+
from . import neutron_detection
|
|
21
|
+
from . import neutronics
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.6.1.post1'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 6, 1)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import activation_foils, diamond
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import explicit, settings, calculations
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
from .settings import *
|
|
4
|
+
import datetime
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
t = sp.Symbol("t")
|
|
8
|
+
neutron_flux = sp.Symbol("\Gamma_n")
|
|
9
|
+
number_of_Nb93 = sp.Symbol(r"N_{Nb93}")
|
|
10
|
+
decay_constant = sp.Symbol("\lambda", positive=True)
|
|
11
|
+
cross_section = sp.Symbol("\sigma")
|
|
12
|
+
overall_efficiency = sp.Symbol("\eta")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def n93_number(foil_mass: float):
|
|
16
|
+
n93_molar_mass = 92.90637 * ureg.g / ureg.mol
|
|
17
|
+
return (foil_mass / n93_molar_mass).to(ureg.particle)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def delay_time(start_time: str, end_time: str):
|
|
21
|
+
# convert string to time with datetime
|
|
22
|
+
start_time = datetime.datetime.strptime(start_time, "%m/%d/%Y %H:%M:%S")
|
|
23
|
+
end_time = datetime.datetime.strptime(end_time, "%m/%d/%Y %H:%M:%S")
|
|
24
|
+
return (end_time - start_time).total_seconds() * ureg.s
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def N_during_irradiation(
|
|
28
|
+
N0, t, decay_constant, neutron_flux, cross_section, nb_Nb93, t_0, mod=sp
|
|
29
|
+
):
|
|
30
|
+
A = nb_Nb93 * neutron_flux * cross_section / decay_constant
|
|
31
|
+
return A + (N0 - A) * mod.exp(-decay_constant * (t - t_0))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def N_during_rest(N0, t, decay_constant, t_0, mod=sp):
|
|
35
|
+
return N0 * mod.exp(-decay_constant * (t - t_0))
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_number_of_Nb92m_after_last_irradiations(irradiations: list):
|
|
39
|
+
"""
|
|
40
|
+
Returns the number of Nb92m at the end of the last irradiation
|
|
41
|
+
as a symbolic expression with sympy
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
irradiations (list): list of dictionaries with keys "t_on" and "t_off"
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
sp.Expression: symbolic expression for the number of Nb92m at the end of the last irradiation
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# there is a series of irradiation and rest, get the number of Nb92m at the end of the last irradiation
|
|
52
|
+
t_on = irradiations[0]["t_on"]
|
|
53
|
+
t_off = irradiations[0]["t_off"]
|
|
54
|
+
N_after_irradiation = N_during_irradiation(
|
|
55
|
+
0, t_off, decay_constant, neutron_flux, cross_section, number_of_Nb93, t_on
|
|
56
|
+
)
|
|
57
|
+
for irr in irradiations[1:]:
|
|
58
|
+
N_at_end_of_rest = N_during_rest(
|
|
59
|
+
N_after_irradiation, irr["t_on"], decay_constant, t_0=t_off
|
|
60
|
+
)
|
|
61
|
+
N_after_irradiation = N_during_irradiation(
|
|
62
|
+
N0=N_at_end_of_rest,
|
|
63
|
+
t=irr["t_off"],
|
|
64
|
+
decay_constant=decay_constant,
|
|
65
|
+
neutron_flux=neutron_flux,
|
|
66
|
+
cross_section=cross_section,
|
|
67
|
+
nb_Nb93=number_of_Nb93,
|
|
68
|
+
t_0=irr["t_on"],
|
|
69
|
+
)
|
|
70
|
+
t_off = irr["t_off"]
|
|
71
|
+
|
|
72
|
+
return N_after_irradiation
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def get_number_ofNb92m_numpy(
|
|
76
|
+
irradiations: list, times: np.ndarray, neutron_flux: float, nb_Nb93: float
|
|
77
|
+
):
|
|
78
|
+
"""
|
|
79
|
+
Returns the number of Nb92m at different times as a numpy array
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
irradiations (list): list of dictionaries with keys "t_on" and "t_off"
|
|
83
|
+
times (np.ndarray): array of times
|
|
84
|
+
neutron_flux (float): neutron flux
|
|
85
|
+
nb_Nb93 (float): number of Nb93 atoms in the foil
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
np.ndarray: array of the number of Nb92m at different times
|
|
89
|
+
"""
|
|
90
|
+
N = np.zeros_like(times)
|
|
91
|
+
N[0] = 0
|
|
92
|
+
decay_constant = Nb92m_decay_constant.to(1 / ureg.s).magnitude
|
|
93
|
+
cross_section = Nb93_n_2n_Nb92m_cross_section_at_14Mev.to(ureg.cm**2).magnitude
|
|
94
|
+
t_on = irradiations[0]["t_on"]
|
|
95
|
+
t_off = irradiations[0]["t_off"]
|
|
96
|
+
previous_irradiation_off = t_off
|
|
97
|
+
idx = np.where(
|
|
98
|
+
np.logical_and(
|
|
99
|
+
(times >= irradiations[0]["t_on"]), (times <= irradiations[0]["t_off"])
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
N[idx] = N_during_irradiation(
|
|
103
|
+
0,
|
|
104
|
+
times[idx],
|
|
105
|
+
decay_constant,
|
|
106
|
+
neutron_flux,
|
|
107
|
+
cross_section,
|
|
108
|
+
nb_Nb93,
|
|
109
|
+
t_on,
|
|
110
|
+
mod=np,
|
|
111
|
+
)
|
|
112
|
+
current_N = N[idx][-1]
|
|
113
|
+
|
|
114
|
+
for irr in irradiations[1:]:
|
|
115
|
+
|
|
116
|
+
idx_rest = np.where((times > previous_irradiation_off) & (times <= irr["t_on"]))
|
|
117
|
+
N[idx_rest] = N_during_rest(
|
|
118
|
+
current_N,
|
|
119
|
+
times[idx_rest],
|
|
120
|
+
decay_constant,
|
|
121
|
+
t_0=previous_irradiation_off,
|
|
122
|
+
mod=np,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
idx_irr = np.where(
|
|
126
|
+
np.logical_and((times > irr["t_on"]), (times <= irr["t_off"]))
|
|
127
|
+
)
|
|
128
|
+
N[idx_irr] = N_during_irradiation(
|
|
129
|
+
N[idx_rest][-1],
|
|
130
|
+
times[idx_irr],
|
|
131
|
+
decay_constant,
|
|
132
|
+
neutron_flux,
|
|
133
|
+
cross_section,
|
|
134
|
+
nb_Nb93,
|
|
135
|
+
t_0=irr["t_on"],
|
|
136
|
+
mod=np,
|
|
137
|
+
)
|
|
138
|
+
previous_irradiation_off = irr["t_off"]
|
|
139
|
+
current_N = N[idx_irr][-1]
|
|
140
|
+
|
|
141
|
+
if times[-1] not in irradiations[-1].values():
|
|
142
|
+
idx_rest = np.where((times > previous_irradiation_off))
|
|
143
|
+
N[idx_rest] = N_during_rest(
|
|
144
|
+
current_N,
|
|
145
|
+
times[idx_rest],
|
|
146
|
+
decay_constant,
|
|
147
|
+
t_0=previous_irradiation_off,
|
|
148
|
+
mod=np,
|
|
149
|
+
)
|
|
150
|
+
return N
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
if __name__ == "__main__":
|
|
154
|
+
|
|
155
|
+
irradiations = [
|
|
156
|
+
{"t_on": 0, "t_off": 12 * 3600},
|
|
157
|
+
{"t_on": 24 * 3600, "t_off": 36 * 3600},
|
|
158
|
+
]
|
|
159
|
+
|
|
160
|
+
times = np.linspace(0, 48 * 3600, 1000)
|
|
161
|
+
neutron_flux_val = 3.51e9 * ureg["1/(cm^2 h)"] # 1/(cm^2 h)
|
|
162
|
+
nb_Nb93 = 1.78e21
|
|
163
|
+
N = get_number_ofNb92m_numpy(
|
|
164
|
+
irradiations, times, neutron_flux_val.to(ureg["1/(cm^2 s)"]).magnitude, nb_Nb93
|
|
165
|
+
)
|
|
166
|
+
plt.plot(times / 3600, N)
|
|
167
|
+
|
|
168
|
+
N_analytical = get_number_of_Nb92m_after_last_irradiations(irradiations)
|
|
169
|
+
|
|
170
|
+
N_analytical = (
|
|
171
|
+
N_analytical.subs(
|
|
172
|
+
cross_section,
|
|
173
|
+
Nb93_n_2n_Nb92m_cross_section_at_14Mev.to(ureg.cm**2).magnitude,
|
|
174
|
+
)
|
|
175
|
+
.subs(decay_constant, Nb92m_decay_constant.to(1 / ureg.s).magnitude)
|
|
176
|
+
.subs(neutron_flux, neutron_flux_val.to(ureg["1/(cm^2 s)"]).magnitude)
|
|
177
|
+
.subs(number_of_Nb93, nb_Nb93)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
plt.scatter([irradiations[-1]["t_off"] / 3600], [N_analytical], color="red")
|
|
181
|
+
plt.xlabel("Time (h)")
|
|
182
|
+
plt.ylabel("Number of Nb92m")
|
|
183
|
+
|
|
184
|
+
for irr in irradiations:
|
|
185
|
+
plt.gca().axvspan(
|
|
186
|
+
irr["t_on"] / 3600, irr["t_off"] / 3600, alpha=0.2, color="red"
|
|
187
|
+
)
|
|
188
|
+
plt.show()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
|
|
3
|
+
def get_decay_lines(nuclides:list[str])->dict:
|
|
4
|
+
""" Creates dictionary of check source data
|
|
5
|
+
given a list of check source nuclides. """
|
|
6
|
+
# energy is the gamma energy in units of eV
|
|
7
|
+
# intensity is the percentage of decays that result in this energy gamma
|
|
8
|
+
all_decay_lines = {'Ba133':{'energy':[80.9979, 276.3989, 302.8508, 356.0129, 383.8485],
|
|
9
|
+
'intensity':[0.329, 0.0716, 0.1834, 0.6205, 0.0894],
|
|
10
|
+
'half_life':[10.551*365.25*24*3600],
|
|
11
|
+
'activity_date':datetime.date(2014, 3, 19),
|
|
12
|
+
'activity':1 * 3.7e4},
|
|
13
|
+
'Co60':{'energy':[1173.228, 1332.492],
|
|
14
|
+
'intensity':[0.9985, 0.999826],
|
|
15
|
+
'half_life':[1925.28*24*3600],
|
|
16
|
+
'actvity_date':datetime.date(2014, 3, 19),
|
|
17
|
+
'activity':0.872 * 3.7e4},
|
|
18
|
+
'Na22':{'energy':[511, 1274.537],
|
|
19
|
+
'intensity':[1.80, 0.9994],
|
|
20
|
+
'half_life':[2.6018*365.25*24*3600],
|
|
21
|
+
'actvity_date':datetime.date(2014, 3, 19),
|
|
22
|
+
'activity': 5 * 3.7e4},
|
|
23
|
+
'Cs137':{'energy':[661.657],
|
|
24
|
+
'intensity':[0.851],
|
|
25
|
+
'half_life':[30.08*365.25*24*3600],
|
|
26
|
+
'actvity_date':datetime.date(2014, 3, 19),
|
|
27
|
+
'activity':4.66 * 3.7e4},
|
|
28
|
+
'Mn54':{'energy':[834.848],
|
|
29
|
+
'intensity':[0.99976],
|
|
30
|
+
'half_life':[312.20*24*3600],
|
|
31
|
+
'actvity_date':datetime.date(2016, 5, 2),
|
|
32
|
+
'activity':6.27 * 3.7e4}}
|
|
33
|
+
decay_lines = {}
|
|
34
|
+
for nuclide in nuclides:
|
|
35
|
+
if nuclide in all_decay_lines.keys():
|
|
36
|
+
decay_lines[nuclide] = all_decay_lines[nuclide]
|
|
37
|
+
else:
|
|
38
|
+
raise ValueError(f'{nuclide} not yet added to get_decay_lines()')
|
|
39
|
+
return decay_lines
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
def get_channel(filename):
|
|
4
|
+
"""
|
|
5
|
+
Extract the channel number from a given filename string.
|
|
6
|
+
|
|
7
|
+
Parameters
|
|
8
|
+
----------
|
|
9
|
+
filename : str
|
|
10
|
+
The input filename string containing the channel information.
|
|
11
|
+
Should look something like : "Data_CH<channel_number>@V...CSV"
|
|
12
|
+
|
|
13
|
+
Returns
|
|
14
|
+
-------
|
|
15
|
+
int
|
|
16
|
+
The extracted channel number.
|
|
17
|
+
|
|
18
|
+
Example
|
|
19
|
+
-------
|
|
20
|
+
>>> get_channel("Data_CH4@V1725_292_Background_250322.CSV")
|
|
21
|
+
4
|
|
22
|
+
"""
|
|
23
|
+
return int(filename.split('@')[0][7:])
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from .settings import *
|
|
2
|
+
from .calculations import n93_number, delay_time
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_chain(irradiations):
|
|
7
|
+
"""
|
|
8
|
+
Returns the value of
|
|
9
|
+
(1 - exp(-\lambda * \Delta t_1)) * (1 - exp(-\lambda * \Delta t_2)) * ... * (1 - exp(-\lambda * \Delta t_n))
|
|
10
|
+
where \Delta t_i is the time between the end of the i-th period (rest or irradiation) and the start of the next one
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
irradiations (list): list of dictionaries with keys "t_on" and "t_off" for irradiations
|
|
14
|
+
|
|
15
|
+
Returns:
|
|
16
|
+
float or pint.Quantity: the value of the chain
|
|
17
|
+
"""
|
|
18
|
+
result = 1
|
|
19
|
+
periods = [{"start": irradiations[0]["t_on"], "end": irradiations[0]["t_off"]}]
|
|
20
|
+
for irr in irradiations[1:]:
|
|
21
|
+
periods.append({"start": periods[-1]["end"], "end": irr["t_on"]})
|
|
22
|
+
periods.append({"start": irr["t_on"], "end": irr["t_off"]})
|
|
23
|
+
|
|
24
|
+
for period in periods:
|
|
25
|
+
delta_t = period["end"] - period["start"]
|
|
26
|
+
result = 1 - result * np.exp(-Nb92m_decay_constant * delta_t)
|
|
27
|
+
return result
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def get_neutron_flux(experiment: dict, irradiations: list):
|
|
31
|
+
"""calculates the neutron flux during the irradiation
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
experiment (dict): dictionary containing the experiment data
|
|
35
|
+
irradiations (list): list of dictionaries with keys "t_on" and "t_off" for irradiations
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
pint.Quantity: neutron flux
|
|
39
|
+
"""
|
|
40
|
+
overall_efficiency = (
|
|
41
|
+
(geometric_efficiency * nal_gamma_efficiency * branching_ratio)
|
|
42
|
+
* ureg.count
|
|
43
|
+
/ ureg.particle
|
|
44
|
+
)
|
|
45
|
+
number_of_Nb92m_decays_measured = experiment["photon_counts"] / overall_efficiency
|
|
46
|
+
|
|
47
|
+
number_of_Nb92m_decays_measured *= 0.5 # times by 0.5 because of double counting
|
|
48
|
+
|
|
49
|
+
flux = (
|
|
50
|
+
number_of_Nb92m_decays_measured
|
|
51
|
+
/ n93_number(experiment["foil_mass"])
|
|
52
|
+
/ Nb93_n_2n_Nb92m_cross_section_at_14Mev
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
flux *= get_chain(irradiations) ** -1
|
|
56
|
+
time_between_generator_off_and_start_of_counting = delay_time(
|
|
57
|
+
experiment["time_generator_off"], experiment["start_time_counting"]
|
|
58
|
+
)
|
|
59
|
+
flux *= (
|
|
60
|
+
-1
|
|
61
|
+
/ Nb92m_decay_constant
|
|
62
|
+
* (
|
|
63
|
+
np.exp(
|
|
64
|
+
-Nb92m_decay_constant
|
|
65
|
+
* (
|
|
66
|
+
time_between_generator_off_and_start_of_counting
|
|
67
|
+
+ experiment["counting_time"]
|
|
68
|
+
)
|
|
69
|
+
)
|
|
70
|
+
- np.exp(
|
|
71
|
+
-Nb92m_decay_constant * time_between_generator_off_and_start_of_counting
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
) ** -1
|
|
75
|
+
|
|
76
|
+
# convert n/cm2/s to n/s
|
|
77
|
+
area_of_sphere = 4 * np.pi * experiment["distance_from_center_of_target_plane"] ** 2
|
|
78
|
+
|
|
79
|
+
flux *= area_of_sphere
|
|
80
|
+
|
|
81
|
+
return flux
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_neutron_flux_error(experiment: dict):
|
|
85
|
+
"""
|
|
86
|
+
Returns the uncertainty of the neutron flux as a pint.Quantity
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
experiment (dict): dictionary containing the experiment data
|
|
90
|
+
irradiations (list): list of dictionaries with keys "t_on" and "t_off" for irradiations
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
pint.Quantity: uncertainty of the neutron flux
|
|
94
|
+
"""
|
|
95
|
+
error_counts = experiment["photon_counts_uncertainty"] / experiment["photon_counts"]
|
|
96
|
+
error_mass = 0.0001 * ureg.g / experiment["foil_mass"]
|
|
97
|
+
error_geometric_eff = 0.025 / geometric_efficiency
|
|
98
|
+
error_intrinsic_eff = 0.025 / nal_gamma_efficiency
|
|
99
|
+
|
|
100
|
+
error = np.sqrt(
|
|
101
|
+
error_counts**2
|
|
102
|
+
+ error_mass**2
|
|
103
|
+
+ error_geometric_eff**2
|
|
104
|
+
+ error_intrinsic_eff**2
|
|
105
|
+
)
|
|
106
|
+
return error.to(ureg.dimensionless).magnitude
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
if __name__ == "__main__":
|
|
110
|
+
pass
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import pint
|
|
2
|
+
import numpy as np
|
|
3
|
+
|
|
4
|
+
ureg = pint.UnitRegistry()
|
|
5
|
+
|
|
6
|
+
geometric_efficiency = 0.5
|
|
7
|
+
nal_gamma_efficiency = 0.344917296922981
|
|
8
|
+
branching_ratio = 0.999
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# source: https://scipub.euro-fusion.org/wp-content/uploads/eurofusion/WPJET3PR17_16948_submitted.pdf
|
|
12
|
+
# See Figure 3
|
|
13
|
+
Nb93_n_2n_Nb92m_cross_section_at_14Mev = 0.46 * ureg.barn
|
|
14
|
+
|
|
15
|
+
# source https://gammaray.inl.gov/SiteAssets/catalogs/ge/pdf/nb92m.pdf
|
|
16
|
+
Nb92m_half_life = 10.25 * ureg.day
|
|
17
|
+
Nb92m_decay_constant = np.log(2) / Nb92m_half_life
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class DataProcessor:
|
|
5
|
+
"""
|
|
6
|
+
A class for reading and processing data from diamond detectors as text files
|
|
7
|
+
|
|
8
|
+
Attributes:
|
|
9
|
+
files (list of str): List of text filenames that have been read
|
|
10
|
+
time_values (np.array): Array of time values from all files
|
|
11
|
+
energy_values (np.array): Array of energy values from all files
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def __init__(self) -> None:
|
|
15
|
+
self.files = []
|
|
16
|
+
|
|
17
|
+
self.time_values = np.array([])
|
|
18
|
+
self.energy_values = np.array([])
|
|
19
|
+
|
|
20
|
+
def add_file(
|
|
21
|
+
self,
|
|
22
|
+
filename: str,
|
|
23
|
+
time_column: int,
|
|
24
|
+
energy_column: int,
|
|
25
|
+
scale_time: bool = True,
|
|
26
|
+
**kwargs,
|
|
27
|
+
):
|
|
28
|
+
"""
|
|
29
|
+
Adds a file to the data processor, reading the time and energy values from the file
|
|
30
|
+
and appending them to the existing data (``time_values`` and ``energy_values`` attributes).
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
filename (str): the name of the file to read
|
|
34
|
+
time_column (int): the column index of the time values in the file
|
|
35
|
+
energy_column (int): the column index of the energy values in the file
|
|
36
|
+
scale_time (bool, optional): if True, the time values are scaled from ps to s. Defaults to True.
|
|
37
|
+
"""
|
|
38
|
+
self.files.append(filename)
|
|
39
|
+
|
|
40
|
+
# Should we store the data for each file separately too?
|
|
41
|
+
data = np.genfromtxt(filename, **kwargs)
|
|
42
|
+
|
|
43
|
+
time_values = data[:, time_column]
|
|
44
|
+
if scale_time:
|
|
45
|
+
# convert times from ps to s
|
|
46
|
+
time_values *= 1 / 1e12
|
|
47
|
+
energy_values = data[:, energy_column]
|
|
48
|
+
|
|
49
|
+
# Append time and energy values to the list
|
|
50
|
+
self.time_values = np.concatenate((self.time_values, time_values))
|
|
51
|
+
self.energy_values = np.concatenate((self.energy_values, energy_values))
|
|
52
|
+
|
|
53
|
+
# sort time and energy values
|
|
54
|
+
inds = np.argsort(self.time_values)
|
|
55
|
+
self.time_values = np.array(self.time_values)[inds]
|
|
56
|
+
self.energy_values = np.array(self.energy_values)[inds]
|
|
57
|
+
|
|
58
|
+
print(f"Added file: {filename} containing {len(time_values)} events")
|
|
59
|
+
|
|
60
|
+
def get_count_rate(self, bin_time: float, energy_window: tuple = None):
|
|
61
|
+
"""
|
|
62
|
+
Calculate the count rate in a given time bin for the
|
|
63
|
+
time values stored in the data processor.
|
|
64
|
+
|
|
65
|
+
Args:
|
|
66
|
+
bin_time (float): the time bin width in seconds
|
|
67
|
+
energy_window (tuple, optional): If provided, the rate
|
|
68
|
+
will be computed only on this energy window. Defaults to None.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
np.array: Array of count rates (counts per second)
|
|
72
|
+
np.array: Array of time bin edges (in seconds)
|
|
73
|
+
"""
|
|
74
|
+
time_values = self.time_values.copy()
|
|
75
|
+
energy_values = self.energy_values.copy()
|
|
76
|
+
|
|
77
|
+
time_bins = np.arange(time_values.min(), time_values[-2], bin_time)
|
|
78
|
+
|
|
79
|
+
if energy_window is not None:
|
|
80
|
+
peak_mask = (energy_values > energy_window[0]) & (
|
|
81
|
+
energy_values < energy_window[1]
|
|
82
|
+
)
|
|
83
|
+
time_values = time_values[peak_mask]
|
|
84
|
+
|
|
85
|
+
count_rates, count_rate_bins = np.histogram(time_values, bins=time_bins)
|
|
86
|
+
count_rates = count_rates / bin_time
|
|
87
|
+
|
|
88
|
+
return count_rates, count_rate_bins
|
|
89
|
+
|
|
90
|
+
def get_avg_rate(self, t_min: float, t_max: float, energy_window: tuple = None):
|
|
91
|
+
"""
|
|
92
|
+
Calculate the average count rate in a given time window for the
|
|
93
|
+
time values stored in the data processor.
|
|
94
|
+
Similar to ``get_count_rate`` but returns a single value for a time window.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
t_min (float): start time of the time window
|
|
98
|
+
t_max (float): end time of the time window
|
|
99
|
+
energy_window (tuple, optional): If provided, the rate
|
|
100
|
+
will be computed only on this energy window. Defaults to None.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
float: Average count rate (counts per second)
|
|
104
|
+
float: Error on the average count rate (counts per second)
|
|
105
|
+
"""
|
|
106
|
+
time_values = self.time_values.copy()
|
|
107
|
+
energy_values = self.energy_values.copy()
|
|
108
|
+
|
|
109
|
+
if energy_window is not None:
|
|
110
|
+
peak_mask = (energy_values > energy_window[0]) & (
|
|
111
|
+
energy_values < energy_window[1]
|
|
112
|
+
)
|
|
113
|
+
time_values = time_values[peak_mask]
|
|
114
|
+
|
|
115
|
+
# Create mask to only count pulses of any energy in section time window
|
|
116
|
+
idx = np.logical_and(time_values > t_min, time_values < t_max)
|
|
117
|
+
|
|
118
|
+
counts = len(time_values[idx])
|
|
119
|
+
error = np.sqrt(len(time_values[idx]))
|
|
120
|
+
delta_t = t_max - t_min
|
|
121
|
+
count_rate = counts / delta_t
|
|
122
|
+
count_rate_err = error / delta_t
|
|
123
|
+
|
|
124
|
+
return count_rate, count_rate_err
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
if __name__ == "__main__":
|
|
128
|
+
pass
|