screamlab 0.1.0__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.
- screamlab/__init__.py +1 -0
- screamlab/__pycache__/__init__.cpython-310.pyc +0 -0
- screamlab/__pycache__/__init__.cpython-312.pyc +0 -0
- screamlab/__pycache__/__init__.cpython-313.pyc +0 -0
- screamlab/__pycache__/__init__.cpython-38.pyc +0 -0
- screamlab/__pycache__/dataset.cpython-310.pyc +0 -0
- screamlab/__pycache__/dataset.cpython-312.pyc +0 -0
- screamlab/__pycache__/dataset.cpython-313.pyc +0 -0
- screamlab/__pycache__/functions.cpython-310.pyc +0 -0
- screamlab/__pycache__/functions.cpython-312.pyc +0 -0
- screamlab/__pycache__/io.cpython-310.pyc +0 -0
- screamlab/__pycache__/io.cpython-312.pyc +0 -0
- screamlab/__pycache__/settings.cpython-310.pyc +0 -0
- screamlab/__pycache__/settings.cpython-312.pyc +0 -0
- screamlab/__pycache__/settings.cpython-313.pyc +0 -0
- screamlab/__pycache__/utils.cpython-310.pyc +0 -0
- screamlab/__pycache__/utils.cpython-312.pyc +0 -0
- screamlab/dataset.py +655 -0
- screamlab/functions.py +415 -0
- screamlab/io.py +984 -0
- screamlab/settings.py +281 -0
- screamlab/utils.py +729 -0
- screamlab-0.1.0.dist-info/METADATA +90 -0
- screamlab-0.1.0.dist-info/RECORD +27 -0
- screamlab-0.1.0.dist-info/WHEEL +5 -0
- screamlab-0.1.0.dist-info/licenses/LICENSE +24 -0
- screamlab-0.1.0.dist-info/top_level.txt +1 -0
screamlab/utils.py
ADDED
|
@@ -0,0 +1,729 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Spectral Analysis/Fitting Module
|
|
3
|
+
|
|
4
|
+
This module provides tools for fitting spectral data and analyzing buildup behaviors
|
|
5
|
+
using the `lmfit` package. It includes several classes designed for spectral deconvolution
|
|
6
|
+
and dynamic nuclear polarization (DNP) buildup kinetic analysis.
|
|
7
|
+
|
|
8
|
+
Classes:
|
|
9
|
+
Spectral Fitting/Deconvolution Classes:
|
|
10
|
+
- Fitter: The base class for fitting spectral data.
|
|
11
|
+
- Prefitter: A specialized fitter that fits a preselected spectrum.
|
|
12
|
+
- GlobalFitter: A fitter that applies parameter constraints across multiple spectra.
|
|
13
|
+
- IndependentFitter: A simple extension of `Fitter` with no additional functionality.
|
|
14
|
+
|
|
15
|
+
DNP Buildup Kinetic Fitting Classes:
|
|
16
|
+
- BuildupFitter: The parent class for fitting DNP buildup kinetics.
|
|
17
|
+
- ExpFitter: A fitter for single-exponential buildup behavior.
|
|
18
|
+
- ExpFitterWithOffset: A variant of `ExpFitter` with an additional offset parameter.
|
|
19
|
+
- BiexpFitter: A fitter for biexponential buildup behavior.
|
|
20
|
+
- BiexpFitterWithOffset: A variant of `BiexpFitter`
|
|
21
|
+
with an additional offset parameter.
|
|
22
|
+
- StretchedExponentialFitter: A fitter for stretched exponential buildup behavior.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
import copy
|
|
26
|
+
import numpy as np
|
|
27
|
+
import lmfit
|
|
28
|
+
from pyDOE3 import lhs
|
|
29
|
+
from screamlab import functions
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Fitter:
|
|
33
|
+
"""
|
|
34
|
+
Base class for spectral fitting using `lmfit`.
|
|
35
|
+
|
|
36
|
+
This class handles parameter initialization and spectral fitting for a ds.
|
|
37
|
+
|
|
38
|
+
Attributes
|
|
39
|
+
----------
|
|
40
|
+
dataset : :obj:`screamlab.ds.Dataset`
|
|
41
|
+
Containing spectra and peak information.
|
|
42
|
+
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
def __init__(self, dataset):
|
|
46
|
+
"""
|
|
47
|
+
Initializes the Fitter with a ds.
|
|
48
|
+
|
|
49
|
+
Args
|
|
50
|
+
----
|
|
51
|
+
dataset : An object containing spectral data and peak list.
|
|
52
|
+
|
|
53
|
+
"""
|
|
54
|
+
self.dataset = dataset
|
|
55
|
+
|
|
56
|
+
def fit(self):
|
|
57
|
+
"""
|
|
58
|
+
Performs spectral fitting using the `lmfit.minimize` function.
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
lmfit.MinimizerResult
|
|
63
|
+
The result of the fitting process.
|
|
64
|
+
|
|
65
|
+
"""
|
|
66
|
+
x_axis, y_axis = self._generate_axis_list()
|
|
67
|
+
params = self._generate_params_list()
|
|
68
|
+
params = self._set_param_expr(params)
|
|
69
|
+
|
|
70
|
+
return self._start_minimize(x_axis, y_axis, params)
|
|
71
|
+
|
|
72
|
+
def _start_minimize(self, x_axis, y_axis, params):
|
|
73
|
+
return lmfit.minimize(
|
|
74
|
+
self._spectral_fitting, params, args=(x_axis, y_axis)
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
def _set_param_expr(self, params):
|
|
78
|
+
"""
|
|
79
|
+
Modifies parameter expressions if needed.
|
|
80
|
+
|
|
81
|
+
Default implementation returns parameters unchanged.
|
|
82
|
+
|
|
83
|
+
Args
|
|
84
|
+
----
|
|
85
|
+
params : lmfit.Parameters
|
|
86
|
+
The parameters to be modified.
|
|
87
|
+
|
|
88
|
+
Returns
|
|
89
|
+
-------
|
|
90
|
+
lmfit.Parameters
|
|
91
|
+
The modified parameters.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
return params
|
|
95
|
+
|
|
96
|
+
def _generate_axis_list(self):
|
|
97
|
+
"""
|
|
98
|
+
Generates lists of x-axis and y-axis values for all spectra in the ds.
|
|
99
|
+
|
|
100
|
+
Returns
|
|
101
|
+
-------
|
|
102
|
+
tuple
|
|
103
|
+
Two lists containing x-axis and y-axis values for each spectrum.
|
|
104
|
+
|
|
105
|
+
"""
|
|
106
|
+
x_axis, y_axis = [], []
|
|
107
|
+
for spectrum in self.dataset.spectra:
|
|
108
|
+
x_axis.append(spectrum.x_axis)
|
|
109
|
+
y_axis.append(spectrum.y_axis)
|
|
110
|
+
return x_axis, y_axis
|
|
111
|
+
|
|
112
|
+
def _generate_params_list(self):
|
|
113
|
+
"""
|
|
114
|
+
Generates initial fitting parameters based on peak information in the ds.
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
lmfit.Parameters
|
|
119
|
+
The initialized parameters for fitting.
|
|
120
|
+
|
|
121
|
+
"""
|
|
122
|
+
params = lmfit.Parameters()
|
|
123
|
+
spectra = self._get_spectra_list()
|
|
124
|
+
lw_types = {
|
|
125
|
+
"voigt": ["sigma", "gamma"],
|
|
126
|
+
"gauss": ["sigma"],
|
|
127
|
+
"lorentz": ["gamma"],
|
|
128
|
+
}
|
|
129
|
+
for spectrum_nr, _ in enumerate(spectra):
|
|
130
|
+
for peak in self.dataset.peak_list:
|
|
131
|
+
params.add(**self._get_amplitude_dict(peak, spectrum_nr))
|
|
132
|
+
params.add(**self._get_center_dict(peak, spectrum_nr))
|
|
133
|
+
|
|
134
|
+
for lw_type in lw_types.get(peak.fitting_type, []):
|
|
135
|
+
params.add(
|
|
136
|
+
**self._get_lw_dict(peak, spectrum_nr, lw_type)
|
|
137
|
+
)
|
|
138
|
+
return params
|
|
139
|
+
|
|
140
|
+
def _get_spectra_list(self):
|
|
141
|
+
"""
|
|
142
|
+
Retrieves the appropriate spectra for fitting.
|
|
143
|
+
|
|
144
|
+
Returns
|
|
145
|
+
-------
|
|
146
|
+
list
|
|
147
|
+
A list of spectra to be fitted.
|
|
148
|
+
|
|
149
|
+
"""
|
|
150
|
+
return (
|
|
151
|
+
[self.dataset.spectra[self.dataset.props.spectrum_for_prefit]]
|
|
152
|
+
if isinstance(self, Prefitter)
|
|
153
|
+
else self.dataset.spectra
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _get_amplitude_dict(self, peak, nr):
|
|
157
|
+
"""
|
|
158
|
+
Generates an amplitude parameter dictionary for a given peak.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
peak: A peak object containing fitting information.
|
|
162
|
+
nr (int): The spectrum index.
|
|
163
|
+
|
|
164
|
+
Returns
|
|
165
|
+
-------
|
|
166
|
+
dict
|
|
167
|
+
A dictionary defining the amplitude parameter.
|
|
168
|
+
|
|
169
|
+
"""
|
|
170
|
+
return {
|
|
171
|
+
"name": f"{peak.peak_label}_amp_{nr}",
|
|
172
|
+
"value": 200 if peak.peak_sign == "+" else -200,
|
|
173
|
+
"min": 0 if peak.peak_sign == "+" else -np.inf,
|
|
174
|
+
"max": np.inf if peak.peak_sign == "+" else 0,
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
def _get_center_dict(self, peak, nr):
|
|
178
|
+
"""
|
|
179
|
+
Generates a center parameter dictionary for a given peak.
|
|
180
|
+
|
|
181
|
+
Args
|
|
182
|
+
----
|
|
183
|
+
peak
|
|
184
|
+
A peak object containing fitting information.
|
|
185
|
+
nr (int)
|
|
186
|
+
The spectrum index.
|
|
187
|
+
|
|
188
|
+
Returns
|
|
189
|
+
-------
|
|
190
|
+
dict
|
|
191
|
+
A dictionary defining the center parameter.
|
|
192
|
+
|
|
193
|
+
"""
|
|
194
|
+
return {
|
|
195
|
+
"name": f"{peak.peak_label}_cen_{nr}",
|
|
196
|
+
"value": peak.peak_center,
|
|
197
|
+
"min": peak.peak_center - 20,
|
|
198
|
+
"max": peak.peak_center + 20,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
def _get_lw_dict(self, peak, nr, lw):
|
|
202
|
+
"""
|
|
203
|
+
Generates a linewidth parameter dictionary for a given peak.
|
|
204
|
+
|
|
205
|
+
Args
|
|
206
|
+
----
|
|
207
|
+
peak: A peak object containing fitting information.
|
|
208
|
+
nr (int): The spectrum index.
|
|
209
|
+
lw (str): The linewidth type (e.g., 'sigma', 'gamma').
|
|
210
|
+
|
|
211
|
+
Returns
|
|
212
|
+
-------
|
|
213
|
+
dict: A dictionary defining the linewidth parameter.
|
|
214
|
+
|
|
215
|
+
"""
|
|
216
|
+
return {
|
|
217
|
+
"name": f"{peak.peak_label}_{lw}_{nr}",
|
|
218
|
+
"value": (
|
|
219
|
+
peak.line_broadening[lw]["min"]
|
|
220
|
+
+ peak.line_broadening[lw]["max"]
|
|
221
|
+
)
|
|
222
|
+
/ 2,
|
|
223
|
+
"min": peak.line_broadening[lw]["min"],
|
|
224
|
+
"max": peak.line_broadening[lw]["max"],
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
def _spectral_fitting(self, params, x_axis, y_axis):
|
|
228
|
+
"""
|
|
229
|
+
Computes the residual between the fitted and experimental spectra.
|
|
230
|
+
|
|
231
|
+
Args
|
|
232
|
+
----
|
|
233
|
+
params (lmfit.Parameters): The fitting parameters.
|
|
234
|
+
x_axis (list): List of x-axis values.
|
|
235
|
+
y_axis (list): List of y-axis values.
|
|
236
|
+
|
|
237
|
+
Returns
|
|
238
|
+
-------
|
|
239
|
+
np.ndarray: The residual between the fitted and experimental spectra.
|
|
240
|
+
|
|
241
|
+
"""
|
|
242
|
+
residual = copy.deepcopy(y_axis)
|
|
243
|
+
params_dict_list = functions.generate_spectra_param_dict(params)
|
|
244
|
+
for key, val_list in params_dict_list.items():
|
|
245
|
+
for val in val_list:
|
|
246
|
+
simspec = [0 for _ in range(len(x_axis[key]))]
|
|
247
|
+
simspec = functions.calc_peak(x_axis[key], simspec, val)
|
|
248
|
+
residual[key] -= simspec
|
|
249
|
+
return np.concatenate(residual)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
class Prefitter(Fitter):
|
|
253
|
+
"""
|
|
254
|
+
A subclass of Fitter that performs a preliminary fit on a preselected spectrum.
|
|
255
|
+
|
|
256
|
+
By fitting the spectrum first, it estimates optimal parameters, particularly for
|
|
257
|
+
linewidths, and narrows down the parameter intervals. The pre-fit parameters define bounds
|
|
258
|
+
(±10%) for the linewidths. These refined intervals are then used in the global fit,
|
|
259
|
+
significantly reducing computational time by limiting the parameter range.
|
|
260
|
+
"""
|
|
261
|
+
|
|
262
|
+
def _generate_axis_list(self):
|
|
263
|
+
"""
|
|
264
|
+
Generate the x and y axes for prefit spectrum.
|
|
265
|
+
|
|
266
|
+
This function retrieves the x and y axes from the spectrum
|
|
267
|
+
specified in the ds properties for prefit.
|
|
268
|
+
|
|
269
|
+
:return: Tuple containing lists of x and y axes.
|
|
270
|
+
"""
|
|
271
|
+
spectrum_for_prefit = self.dataset.props.spectrum_for_prefit
|
|
272
|
+
x_axis, y_axis = [], []
|
|
273
|
+
x_axis.append(self.dataset.spectra[spectrum_for_prefit].x_axis)
|
|
274
|
+
y_axis.append(self.dataset.spectra[spectrum_for_prefit].y_axis)
|
|
275
|
+
return x_axis, y_axis
|
|
276
|
+
|
|
277
|
+
def _start_minimize(self, x_axis, y_axis, params):
|
|
278
|
+
result = lmfit.minimize(
|
|
279
|
+
self._spectral_fitting, params, args=(x_axis, y_axis)
|
|
280
|
+
)
|
|
281
|
+
return result
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
class GlobalFitter(Fitter):
|
|
285
|
+
"""
|
|
286
|
+
Global fit over all spectra at different polarization times.
|
|
287
|
+
|
|
288
|
+
For SCREAM-DNP data, it can be assumed that the line broadening did not vary over all
|
|
289
|
+
polarization times in cases where a homogeneous polarization buildup on protons exists.
|
|
290
|
+
|
|
291
|
+
Same goes for the center of each peak since the chemical shift is not depending on the
|
|
292
|
+
polarization time. For this, it is recommended to carefully reference all spectra during
|
|
293
|
+
post-processing. With this, the number of fitting parameters can drastically be reduced,
|
|
294
|
+
yielding a shorter calculation time. In this case, all spectra from a SCREAM-DNP buildup
|
|
295
|
+
series can be described by two lineshape parameters (sigma and gamma), one variable for
|
|
296
|
+
the peak center (µ), and n amplitude variables per resonance, where n stands for the
|
|
297
|
+
number of spectra within one series.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
def _set_param_expr(self, params):
|
|
301
|
+
"""
|
|
302
|
+
Set parameter expressions to enforce global constraints.
|
|
303
|
+
|
|
304
|
+
This function modifies parameters such that all parameters
|
|
305
|
+
except for amplitudes ("amp") share the same global parameter
|
|
306
|
+
value across multiple spectra by setting their expressions.
|
|
307
|
+
|
|
308
|
+
:param params: lmfit Parameters object containing the parameters to be modified.
|
|
309
|
+
:return: Modified lmfit Parameters object with parameter expressions set.
|
|
310
|
+
"""
|
|
311
|
+
for keys in params.keys():
|
|
312
|
+
splitted_keys = keys.split("_")
|
|
313
|
+
if splitted_keys[-1] != "0" and splitted_keys[-2] != "amp":
|
|
314
|
+
splitted_keys[-1] = "0"
|
|
315
|
+
params[keys].expr = "_".join(splitted_keys)
|
|
316
|
+
return params
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
class IndependentFitter(Fitter):
|
|
320
|
+
"""
|
|
321
|
+
Fit of each spectrum with individual parameter set.
|
|
322
|
+
|
|
323
|
+
In some cases it might be necessary to simulate each spectrum from one series with its own
|
|
324
|
+
parameter set.
|
|
325
|
+
|
|
326
|
+
This option is also provided. Each resonance in each spectrum will be fitted to two
|
|
327
|
+
lineshape parameters, an amplitude and a globally determined peak center. Note that this
|
|
328
|
+
yields higher run times. A prefit can be combined with this case to save time. However, it
|
|
329
|
+
must be ensured that all spectra can be fitted by conditions given in point two.
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class BuildupFitter:
|
|
334
|
+
"""
|
|
335
|
+
Base class for fitting buildup data using `lmfit`.
|
|
336
|
+
|
|
337
|
+
This class is responsible for performing a fitting procedure on a ds
|
|
338
|
+
of peaks with time-dependent intensities.
|
|
339
|
+
|
|
340
|
+
Attributes
|
|
341
|
+
----------
|
|
342
|
+
dataset: :obj:`screamlab.ds.Dataset` containing peak
|
|
343
|
+
intensity and polarization time information.
|
|
344
|
+
|
|
345
|
+
"""
|
|
346
|
+
|
|
347
|
+
def __init__(self, dataset):
|
|
348
|
+
"""
|
|
349
|
+
Initialize the BuildupFitter with a ds.
|
|
350
|
+
|
|
351
|
+
:param dataset: The ds containing peak list information.
|
|
352
|
+
"""
|
|
353
|
+
self.dataset = dataset
|
|
354
|
+
|
|
355
|
+
def perform_fit(self):
|
|
356
|
+
"""
|
|
357
|
+
Perform the fitting procedure on the ds's peak list.
|
|
358
|
+
|
|
359
|
+
:return: List of best fit results for each peak.
|
|
360
|
+
"""
|
|
361
|
+
result_list = []
|
|
362
|
+
for peak in self.dataset.peak_list:
|
|
363
|
+
default_param_dict = self._get_default_param_dict(peak)
|
|
364
|
+
lhs_init_params = self._get_lhs_init_params(default_param_dict)
|
|
365
|
+
best_result = None
|
|
366
|
+
best_chisqr = np.inf
|
|
367
|
+
for init_params in lhs_init_params:
|
|
368
|
+
params = self._set_params(default_param_dict, init_params)
|
|
369
|
+
try:
|
|
370
|
+
result = self._start_minimize(params, peak.buildup_vals)
|
|
371
|
+
best_result, best_chisqr = self._check_result_quality(
|
|
372
|
+
best_result, best_chisqr, result
|
|
373
|
+
)
|
|
374
|
+
except (ValueError, RuntimeError): # nosec B110
|
|
375
|
+
pass
|
|
376
|
+
result_list.append(best_result)
|
|
377
|
+
return result_list
|
|
378
|
+
|
|
379
|
+
def _get_lhs_init_params(self, default_param_dict, n_samples=1):
|
|
380
|
+
"""
|
|
381
|
+
Generate Latin Hypercube Sampling (LHS) initial parameters.
|
|
382
|
+
|
|
383
|
+
:param default_param_dict: Dictionary of default parameter values and bounds.
|
|
384
|
+
:param n_samples: Number of LHS samples to generate.
|
|
385
|
+
:return: List of sampled parameters.
|
|
386
|
+
"""
|
|
387
|
+
param_bounds = [
|
|
388
|
+
self._get_param_bounds(default_param_dict[key])
|
|
389
|
+
for key in default_param_dict
|
|
390
|
+
]
|
|
391
|
+
if n_samples == 1:
|
|
392
|
+
n_samples = len(default_param_dict.keys()) * 100
|
|
393
|
+
lhs_samples = lhs(len(default_param_dict.keys()), samples=n_samples)
|
|
394
|
+
return self._set_sample_params(lhs_samples, param_bounds)
|
|
395
|
+
|
|
396
|
+
def _start_minimize(self, params, args):
|
|
397
|
+
"""
|
|
398
|
+
Start the minimization process using lmfit.
|
|
399
|
+
|
|
400
|
+
:param params: Parameters for fitting.
|
|
401
|
+
:param args: Arguments containing time delays and intensities.
|
|
402
|
+
:return: Minimization result.
|
|
403
|
+
"""
|
|
404
|
+
return lmfit.minimize(
|
|
405
|
+
self._fitting_function,
|
|
406
|
+
params,
|
|
407
|
+
args=(args.tpol, args.intensity),
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
def _check_result_quality(self, best_result, best_chisqr, result):
|
|
411
|
+
"""
|
|
412
|
+
Check if the new result is better than the current best result.
|
|
413
|
+
|
|
414
|
+
:param best_result: The current best fitting result.
|
|
415
|
+
:param best_chisqr: The chi-squared value of the best result.
|
|
416
|
+
:param result: The new fitting result.
|
|
417
|
+
:return: The best result and its chi-squared value.
|
|
418
|
+
"""
|
|
419
|
+
if result.chisqr < best_chisqr:
|
|
420
|
+
return result, result.chisqr
|
|
421
|
+
return best_result, best_chisqr
|
|
422
|
+
|
|
423
|
+
def _get_param_bounds(self, params):
|
|
424
|
+
"""
|
|
425
|
+
Retrieve parameter bounds.
|
|
426
|
+
|
|
427
|
+
:param params: Dictionary containing parameter min and max values.
|
|
428
|
+
:return: Tuple containing (min, max) bounds.
|
|
429
|
+
"""
|
|
430
|
+
return (params["min"], params["max"])
|
|
431
|
+
|
|
432
|
+
def _set_sample_params(self, lhs_samples, param_bounds):
|
|
433
|
+
"""
|
|
434
|
+
Scale LHS samples according to parameter bounds.
|
|
435
|
+
|
|
436
|
+
:param lhs_samples: LHS-generated samples.
|
|
437
|
+
:param param_bounds: List of parameter bounds.
|
|
438
|
+
:return: List of sampled parameters.
|
|
439
|
+
"""
|
|
440
|
+
sampled_params = []
|
|
441
|
+
for sample in lhs_samples:
|
|
442
|
+
scaled_sample = [
|
|
443
|
+
low + sample[i] * (high - low)
|
|
444
|
+
for i, (low, high) in enumerate(param_bounds)
|
|
445
|
+
]
|
|
446
|
+
sampled_params.append(scaled_sample)
|
|
447
|
+
return sampled_params
|
|
448
|
+
|
|
449
|
+
def _set_params(self, default_param_dict, init_params):
|
|
450
|
+
"""
|
|
451
|
+
Set up lmfit Parameters object using initial parameters.
|
|
452
|
+
|
|
453
|
+
:param default_param_dict: Default parameter dictionary.
|
|
454
|
+
:param init_params: Initial parameter values.
|
|
455
|
+
:return: lmfit Parameters object.
|
|
456
|
+
"""
|
|
457
|
+
params = lmfit.Parameters()
|
|
458
|
+
for key_nr, key in enumerate(default_param_dict.keys()):
|
|
459
|
+
default_param_dict[key]["value"] = init_params[key_nr]
|
|
460
|
+
params.add(key, **default_param_dict[key])
|
|
461
|
+
return params
|
|
462
|
+
|
|
463
|
+
def _fitting_function(self, params, tdel, intensity):
|
|
464
|
+
"""
|
|
465
|
+
Define the residual function for fitting.
|
|
466
|
+
|
|
467
|
+
:param params: Parameters for fitting.
|
|
468
|
+
:param tdel: Time delays.
|
|
469
|
+
:param intensity: Measured intensities.
|
|
470
|
+
:return: Residuals between observed and simulated intensities.
|
|
471
|
+
"""
|
|
472
|
+
residual = copy.deepcopy(intensity)
|
|
473
|
+
param_list = self._generate_param_list(params)
|
|
474
|
+
intensity_sim = self._calc_intensity(tdel, param_list)
|
|
475
|
+
return [a - b for a, b in zip(residual, intensity_sim)]
|
|
476
|
+
|
|
477
|
+
def _generate_param_list(self, params):
|
|
478
|
+
"""
|
|
479
|
+
Generate a list of parameter values from lmfit Parameters.
|
|
480
|
+
|
|
481
|
+
:param params: lmfit Parameters object.
|
|
482
|
+
:return: List of parameter values.
|
|
483
|
+
"""
|
|
484
|
+
return [params[key].value for key in params]
|
|
485
|
+
|
|
486
|
+
def _get_intensity_dict(self, peak):
|
|
487
|
+
"""
|
|
488
|
+
Generate intensity parameter dictionary.
|
|
489
|
+
|
|
490
|
+
:param peak: Peak object containing buildup values.
|
|
491
|
+
:return: Dictionary with default intensity parameter values.
|
|
492
|
+
"""
|
|
493
|
+
return (
|
|
494
|
+
{
|
|
495
|
+
"value": 10,
|
|
496
|
+
"min": 0,
|
|
497
|
+
"max": max(peak.buildup_vals.intensity) * 3,
|
|
498
|
+
}
|
|
499
|
+
if peak.peak_sign == "+"
|
|
500
|
+
else {
|
|
501
|
+
"value": 10,
|
|
502
|
+
"max": 0,
|
|
503
|
+
"min": min(peak.buildup_vals.intensity) * 3,
|
|
504
|
+
}
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def _get_time_dict(self, peak):
|
|
508
|
+
"""
|
|
509
|
+
Generate time delay parameter dictionary.
|
|
510
|
+
|
|
511
|
+
:param peak: Peak object containing buildup values.
|
|
512
|
+
:return: Dictionary with default time parameter values.
|
|
513
|
+
"""
|
|
514
|
+
return {"value": 5, "min": 0, "max": max(peak.buildup_vals.tpol) * 3}
|
|
515
|
+
|
|
516
|
+
def _get_beta_dict(self):
|
|
517
|
+
return {"value": 0, "min": 0, "max": 1}
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
class BiexpFitter(BuildupFitter):
|
|
521
|
+
"""
|
|
522
|
+
Class for fitting biexponential models to buildup data.
|
|
523
|
+
|
|
524
|
+
The biexponential model fits buildup curves using two exponential terms
|
|
525
|
+
characterized by amplitudes (Af, As) and time constants (tf, ts).
|
|
526
|
+
|
|
527
|
+
The model function is defined as:
|
|
528
|
+
I(t) = Af * (1 - exp(-t_pol / tf)) + As * (1 - exp(-t_pol / ts))
|
|
529
|
+
|
|
530
|
+
where:
|
|
531
|
+
- Af, As : amplitudes of the exponential components
|
|
532
|
+
- tf, ts : time constants of the exponential components (tf, ts > 0)
|
|
533
|
+
- t_pol : polarization time (independent variable)
|
|
534
|
+
- I(t_pol) : peak intensity at polarization time t_pol
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
def _get_default_param_dict(self, peak):
|
|
538
|
+
"""
|
|
539
|
+
Define default parameters for biexponential fitting.
|
|
540
|
+
|
|
541
|
+
:param peak: Peak object containing peak_sign and buildup values.
|
|
542
|
+
:return: Dictionary of default parameters with keys: Af, As, tf, ts.
|
|
543
|
+
"""
|
|
544
|
+
return {
|
|
545
|
+
"Af": self._get_intensity_dict(peak),
|
|
546
|
+
"As": self._get_intensity_dict(peak),
|
|
547
|
+
"tf": self._get_time_dict(peak),
|
|
548
|
+
"ts": self._get_time_dict(peak),
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
def _calc_intensity(self, tdel, param):
|
|
552
|
+
"""
|
|
553
|
+
Calculate biexponential intensity.
|
|
554
|
+
|
|
555
|
+
:param tdel: Time delays.
|
|
556
|
+
:param param: List of parameters.
|
|
557
|
+
:return: Calculated intensity values.
|
|
558
|
+
"""
|
|
559
|
+
return functions.calc_biexponential(tdel, param)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
class BiexpFitterWithOffset(BuildupFitter):
|
|
563
|
+
"""
|
|
564
|
+
Class for fitting biexponential models with offset to buildup data.
|
|
565
|
+
|
|
566
|
+
This fits buildup curves using two exponential terms
|
|
567
|
+
characterized by amplitudes (Af, As), time constants (tf, ts) and offset (t_off).
|
|
568
|
+
|
|
569
|
+
The model function is defined as:
|
|
570
|
+
I(t) = Af * (1 - exp(-(t_pol-t_off) / tf)) + As * (1 - exp(-(t_pol-t_off) / ts))
|
|
571
|
+
|
|
572
|
+
where:
|
|
573
|
+
- Af, As : amplitudes of the exponential components
|
|
574
|
+
- tf, ts : time constants of the exponential components (tf, ts > 0)
|
|
575
|
+
- t_off : offset in polarization time
|
|
576
|
+
- t_pol : polarization time (independent variable)
|
|
577
|
+
- I(t_pol) : peak intensity at polarization time t_pol
|
|
578
|
+
"""
|
|
579
|
+
|
|
580
|
+
def _get_default_param_dict(self, peak):
|
|
581
|
+
"""
|
|
582
|
+
Define default parameters for biexponential fitting.
|
|
583
|
+
|
|
584
|
+
:param peak: Peak object containing peak_sign and buildup values.
|
|
585
|
+
:return: Dictionary of default parameters with keys: Af, As, tf, ts.
|
|
586
|
+
"""
|
|
587
|
+
return {
|
|
588
|
+
"Af": self._get_intensity_dict(peak),
|
|
589
|
+
"As": self._get_intensity_dict(peak),
|
|
590
|
+
"tf": self._get_time_dict(peak),
|
|
591
|
+
"ts": self._get_time_dict(peak),
|
|
592
|
+
"t_off": {"value": 0, "min": -5, "max": 5},
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
def _calc_intensity(self, tdel, param):
|
|
596
|
+
"""
|
|
597
|
+
Calculate biexponential intensity with x axis offset.
|
|
598
|
+
|
|
599
|
+
:param tdel: Time delays.
|
|
600
|
+
:param param: List of parameters.
|
|
601
|
+
:return: Calculated intensity values.
|
|
602
|
+
"""
|
|
603
|
+
return functions.calc_biexponential_with_offset(tdel, param)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
class ExpFitter(BuildupFitter):
|
|
607
|
+
"""
|
|
608
|
+
Class for fitting exponential models to buildup data.
|
|
609
|
+
|
|
610
|
+
This fits buildup curves using an exponential term
|
|
611
|
+
characterized by amplitude (Af) and time constant (tf).
|
|
612
|
+
|
|
613
|
+
The model function is defined as:
|
|
614
|
+
I(t) = Af * (1 - exp(-t_pol / tf))
|
|
615
|
+
|
|
616
|
+
where:
|
|
617
|
+
- Af : amplitudes of the exponential components
|
|
618
|
+
- tf : time constants of the exponential components (tf > 0)
|
|
619
|
+
- t_pol : polarization time (independent variable)
|
|
620
|
+
- I(t_pol) : peak intensity at polarization time t_pol
|
|
621
|
+
"""
|
|
622
|
+
|
|
623
|
+
def _get_default_param_dict(self, peak):
|
|
624
|
+
"""
|
|
625
|
+
Define default parameters for exponential fitting.
|
|
626
|
+
|
|
627
|
+
:param peak: Peak object containing peak_sign and buildup values.
|
|
628
|
+
:return: Dictionary of default parameters with keys: Af, tf.
|
|
629
|
+
"""
|
|
630
|
+
return {
|
|
631
|
+
"Af": self._get_intensity_dict(peak),
|
|
632
|
+
"tf": self._get_time_dict(peak),
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
def _calc_intensity(self, tdel, param):
|
|
636
|
+
"""
|
|
637
|
+
Calculate exponential intensity.
|
|
638
|
+
|
|
639
|
+
:param tdel: Time delays.
|
|
640
|
+
:param param: List of parameters.
|
|
641
|
+
:return: Calculated intensity values.
|
|
642
|
+
"""
|
|
643
|
+
return functions.calc_exponential(tdel, param)
|
|
644
|
+
|
|
645
|
+
|
|
646
|
+
class ExpFitterWithOffset(BuildupFitter):
|
|
647
|
+
"""
|
|
648
|
+
Class for fitting exponential models with offset to buildup data.
|
|
649
|
+
|
|
650
|
+
This fits buildup curves using an exponential term
|
|
651
|
+
characterized by amplitude (Af), time constant (tf) and offset (t_off).
|
|
652
|
+
|
|
653
|
+
The model function is defined as:
|
|
654
|
+
I(t) = Af * (1 - exp(-(t_pol-t_off) / tf))
|
|
655
|
+
|
|
656
|
+
where:
|
|
657
|
+
- Af : amplitudes of the exponential components
|
|
658
|
+
- tf : time constants of the exponential components (tf > 0)
|
|
659
|
+
- t_off : offset in polarization time
|
|
660
|
+
- t_pol : polarization time (independent variable)
|
|
661
|
+
- I(t_pol) : peak intensity at polarization time t_pol
|
|
662
|
+
"""
|
|
663
|
+
|
|
664
|
+
def _get_default_param_dict(self, peak):
|
|
665
|
+
"""
|
|
666
|
+
Define default parameters for exponential with offset in x fitting.
|
|
667
|
+
|
|
668
|
+
:param peak: Peak object containing peak_sign and buildup values.
|
|
669
|
+
:return: Dictionary of default parameters with keys: Af, tf, t_off.
|
|
670
|
+
"""
|
|
671
|
+
return {
|
|
672
|
+
"Af": self._get_intensity_dict(peak),
|
|
673
|
+
"tf": self._get_time_dict(peak),
|
|
674
|
+
"t_off": {"value": 0, "min": -5, "max": 5},
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
def _calc_intensity(self, tdel, param):
|
|
678
|
+
"""
|
|
679
|
+
Calculate exponential intensity with x axis offset.
|
|
680
|
+
|
|
681
|
+
:param tdel: Time delays.
|
|
682
|
+
:param param: List of parameters.
|
|
683
|
+
:return: Calculated intensity values.
|
|
684
|
+
"""
|
|
685
|
+
return functions.calc_exponential_with_offset(tdel, param)
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class StrechedExponentialFitter(BuildupFitter):
|
|
689
|
+
"""
|
|
690
|
+
Class for fitting streched exponential models to buildup data.
|
|
691
|
+
|
|
692
|
+
This fits buildup curves using an streched exponential term
|
|
693
|
+
characterized by amplitude (Af), time constant (tf), and stretching factor (beta)..
|
|
694
|
+
|
|
695
|
+
The model function is defined as:
|
|
696
|
+
I(t) = Af * (1 - exp(-(t_pol / tf)^beta))
|
|
697
|
+
|
|
698
|
+
where:
|
|
699
|
+
- Af : amplitudes of the exponential components
|
|
700
|
+
- tf : time constants of the exponential components (tf > 0)
|
|
701
|
+
- beta : stretching factor (beta > 0, controls deviation from a simple exponential)
|
|
702
|
+
- t_pol : polarization time (independent variable)
|
|
703
|
+
- I(t_pol): peak intensity at polarization time t_pol
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
"""
|
|
707
|
+
|
|
708
|
+
def _get_default_param_dict(self, peak):
|
|
709
|
+
"""
|
|
710
|
+
Define default parameters for strechted exponential fitting.
|
|
711
|
+
|
|
712
|
+
:param peak: Peak object containing peak_sign and buildup values.
|
|
713
|
+
:return: Dictionary of default parameters with keys: Af, tf, beta.
|
|
714
|
+
"""
|
|
715
|
+
return {
|
|
716
|
+
"Af": self._get_intensity_dict(peak),
|
|
717
|
+
"tf": self._get_time_dict(peak),
|
|
718
|
+
"beta": self._get_beta_dict(),
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
def _calc_intensity(self, tdel, param):
|
|
722
|
+
"""
|
|
723
|
+
Calculate streched exponential intensity.
|
|
724
|
+
|
|
725
|
+
:param tdel: Time delays.
|
|
726
|
+
:param param: List of parameters.
|
|
727
|
+
:return: Calculated intensity values.
|
|
728
|
+
"""
|
|
729
|
+
return functions.calc_stretched_exponential(tdel, param)
|