spinlab 0.0.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.
Files changed (71) hide show
  1. spinlab/__init__.py +27 -0
  2. spinlab/analysis/__init__.py +6 -0
  3. spinlab/analysis/hydration.py +555 -0
  4. spinlab/analysis/peaks.py +249 -0
  5. spinlab/analysis/relaxation_fit.py +29 -0
  6. spinlab/analysis/simulate_enhancement_profiles.py +109 -0
  7. spinlab/config/__init__.py +0 -0
  8. spinlab/config/config.py +81 -0
  9. spinlab/constants/__init__.py +5 -0
  10. spinlab/constants/constants.py +27 -0
  11. spinlab/constants/mrProperties.py +236 -0
  12. spinlab/constants/radicalProperties.py +147 -0
  13. spinlab/core/__init__.py +5 -0
  14. spinlab/core/base.py +1268 -0
  15. spinlab/core/coord.py +225 -0
  16. spinlab/core/data.py +335 -0
  17. spinlab/core/ufunc.py +67 -0
  18. spinlab/core/util.py +159 -0
  19. spinlab/fitting/__init__.py +3 -0
  20. spinlab/fitting/general.py +106 -0
  21. spinlab/io/__init__.py +19 -0
  22. spinlab/io/bes3t.py +453 -0
  23. spinlab/io/cnsi.py +139 -0
  24. spinlab/io/delta.py +523 -0
  25. spinlab/io/h5.py +215 -0
  26. spinlab/io/load.py +334 -0
  27. spinlab/io/load_csv.py +98 -0
  28. spinlab/io/logs.py +84 -0
  29. spinlab/io/mat.py +153 -0
  30. spinlab/io/power.py +109 -0
  31. spinlab/io/prospa.py +340 -0
  32. spinlab/io/random.py +36 -0
  33. spinlab/io/rs2d.py +111 -0
  34. spinlab/io/save.py +52 -0
  35. spinlab/io/specman.py +358 -0
  36. spinlab/io/tnmr.py +178 -0
  37. spinlab/io/topspin.py +625 -0
  38. spinlab/io/vna.py +76 -0
  39. spinlab/io/vnmrj.py +236 -0
  40. spinlab/io/winepr.py +205 -0
  41. spinlab/math/__init__.py +6 -0
  42. spinlab/math/lineshape.py +142 -0
  43. spinlab/math/pulses.py +255 -0
  44. spinlab/math/relaxation.py +133 -0
  45. spinlab/math/window.py +161 -0
  46. spinlab/plotting/__init__.py +9 -0
  47. spinlab/plotting/colors.py +9 -0
  48. spinlab/plotting/general.py +285 -0
  49. spinlab/plotting/image.py +68 -0
  50. spinlab/plotting/stack_plot.py +69 -0
  51. spinlab/processing/__init__.py +12 -0
  52. spinlab/processing/align.py +76 -0
  53. spinlab/processing/apodization.py +96 -0
  54. spinlab/processing/average.py +28 -0
  55. spinlab/processing/conversion.py +247 -0
  56. spinlab/processing/fft.py +182 -0
  57. spinlab/processing/helpers.py +577 -0
  58. spinlab/processing/integration.py +130 -0
  59. spinlab/processing/interpolation.py +70 -0
  60. spinlab/processing/offset.py +133 -0
  61. spinlab/processing/phase.py +508 -0
  62. spinlab/reporting/__init__.py +1 -0
  63. spinlab/version.py +1 -0
  64. spinlab/widgets/__init__.py +4 -0
  65. spinlab/widgets/align_widget.py +77 -0
  66. spinlab/widgets/phase_widget.py +118 -0
  67. spinlab-0.0.0.dist-info/METADATA +56 -0
  68. spinlab-0.0.0.dist-info/RECORD +71 -0
  69. spinlab-0.0.0.dist-info/WHEEL +5 -0
  70. spinlab-0.0.0.dist-info/licenses/LICENSE.txt +21 -0
  71. spinlab-0.0.0.dist-info/top_level.txt +1 -0
spinlab/__init__.py ADDED
@@ -0,0 +1,27 @@
1
+ """SpinLab - Bringing the Power of Python to Spin-NMR Spectroscopy"""
2
+
3
+ from .core.data import SpinData
4
+ from .core.ufunc import *
5
+ from .core.util import *
6
+
7
+ from .constants import *
8
+ from .fitting import *
9
+ from .math import *
10
+
11
+ from .io import *
12
+ from .io.save import save
13
+ from .io.load import load
14
+
15
+ from .analysis.relaxation_fit import *
16
+ from .analysis.hydration import hydration
17
+ from .analysis.simulate_enhancement_profiles import *
18
+ from .analysis.peaks import *
19
+
20
+ from .processing import *
21
+ from .widgets import *
22
+ from .plotting import *
23
+ from .reporting import *
24
+ from .version import __version__
25
+
26
+ # config
27
+ from .config.config import SpinLAB_CONFIG
@@ -0,0 +1,6 @@
1
+ """Modules which provide a workflow for analyzing slData objects"""
2
+
3
+ from . import hydration
4
+ from . import relaxation_fit
5
+ from . import simulate_enhancement_profiles
6
+ from . import peaks
@@ -0,0 +1,555 @@
1
+ import numpy as _np
2
+ from scipy import optimize
3
+ import warnings
4
+ from ..constants import constants as _const
5
+
6
+
7
+ def calculate_smax(spin_C=False):
8
+ r"""Returns maximal saturation factor.
9
+
10
+ Args:
11
+ spin_C (float): unpaired spin concentration (M)
12
+
13
+ Returns:
14
+ smax (float): maximal saturation factor (unitless)
15
+
16
+ .. math::
17
+ \mathrm{s_{max}} = 1 - (2 / (3 + (3 * (\mathrm{spin\_C} * 198.7))))
18
+
19
+ M.T. Türke, M. Bennati, Phys. Chem. Chem. Phys. 13 (2011) 3630. & J. Hyde, J. Chien, J. Freed, J. Chem. Phys. 48 (1968) 4211.
20
+ """
21
+
22
+ return 1 - (2 / (3 + (3 * (spin_C * 198.7))))
23
+
24
+
25
+ def interpolate_T1(
26
+ E_powers=False,
27
+ T1_powers=False,
28
+ T1_array=False,
29
+ interpolate_method="linear",
30
+ delta_T1_water=False,
31
+ T1_water=False,
32
+ macro_C=False,
33
+ spin_C=1,
34
+ T10=2.0,
35
+ T100=2.5,
36
+ ):
37
+ """Returns interpolated T1 data.
38
+
39
+ Args:
40
+ E_powers (numpy.array): The microwave powers at which to evaluate
41
+ T1_powers (numpy.array): The microwave powers of the T1s to interpolate
42
+ T1_array (numpy.array): The original T1s (s)
43
+ interpolate_method (str): "second_order" or "linear"
44
+ spin_C (float): unpaired electron spin concentration (M)
45
+ T10 (float): T1 measured with unpaired electrons (s)
46
+ T100 (float): T1 measured without unpaired electrons (s)
47
+ delta_T1_water (optional) (float): change in T1 of water at max microwave power (s)
48
+ T1_water (optional) (float): T1 of pure water (s)
49
+ macro_C (optional) (float): concentration of macromolecule (M)
50
+
51
+ Returns:
52
+ interpolated_T1 (numpy.array): Array of T1 values same shape as E_powers and E_array
53
+
54
+ T1 data is interpolated using Eq. 39 of http://dx.doi.org/10.1016/j.pnmrs.2013.06.001 for "linear" or Eq. 22 of https://doi.org/10.1016/bs.mie.2018.09.024 for "second_order"
55
+ """
56
+
57
+ # 2nd order fit, Franck and Han MIE (Eq. 22) and (Eq. 23)
58
+ if interpolate_method == "second_order":
59
+ if not macro_C:
60
+ macro_C = spin_C
61
+
62
+ if not delta_T1_water:
63
+ delta_T1_water = T1_array[-1] - T1_array[0]
64
+ if not T1_water:
65
+ T1_water = T100
66
+
67
+ kHH = (1.0 / T10 - 1.0 / T1_water) / macro_C
68
+ krp = (
69
+ (1.0 / T1_array)
70
+ - (1.0 / (T1_water + delta_T1_water * T1_powers))
71
+ - (kHH * (macro_C))
72
+ ) / (spin_C)
73
+
74
+ p = _np.polyfit(T1_powers, krp, 2)
75
+ T1_fit_2order = _np.polyval(p, E_powers)
76
+
77
+ interpolated_T1 = 1.0 / (
78
+ ((spin_C) * T1_fit_2order)
79
+ + (1.0 / (T1_water + delta_T1_water * E_powers))
80
+ + (kHH * (macro_C))
81
+ )
82
+
83
+ # linear fit, Franck et al. PNMRS (Eq. 39)
84
+ elif interpolate_method == "linear":
85
+ linear_t1 = 1.0 / ((1.0 / T1_array) - (1.0 / T10) + (1.0 / T100))
86
+
87
+ p = _np.polyfit(T1_powers, linear_t1, 1)
88
+ T1_fit_linear = _np.polyval(p, E_powers)
89
+
90
+ interpolated_T1 = T1_fit_linear / (
91
+ 1.0 + (T1_fit_linear / T10) - (T1_fit_linear / T100)
92
+ )
93
+
94
+ else:
95
+ raise Exception("invalid interpolate_method")
96
+
97
+ return interpolated_T1
98
+
99
+
100
+ def calculate_ksigma_array(powers=False, ksigma_smax=95.4, p_12=False):
101
+ """Function to calcualte ksig array for any given ksigma and p_12
102
+
103
+ Args:
104
+ powers (numpy.array): Array of powers
105
+ ksigma_smax (float): product of ksigma and smax (s^-1 * M^-1)
106
+ p_12 (float): power at half max for ksigma fit
107
+
108
+ Returns:
109
+ ksig_fit (numpy.array): calculated ksigma array
110
+
111
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
112
+ """
113
+
114
+ # Right side of Eq. 42. This function should fit to ksig_sp
115
+ ksig_fit = (ksigma_smax * powers) / (p_12 + powers)
116
+
117
+ return ksig_fit
118
+
119
+
120
+ def calculate_ksigma(ksigma_sp=False, powers=False, smax=1):
121
+ """Get ksigma and E_power at half max of ksig
122
+
123
+ Args:
124
+ ksig (numpy.array): Array of ksigmas
125
+ powers (numpy.array): Array of E_powers
126
+
127
+ Returns:
128
+ ksigma (float): calculated ksigma
129
+ ksigma_stdd (float): standard deviation in ksigma
130
+ p_12 (float): power at half max for ksigma fit
131
+
132
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
133
+ """
134
+
135
+ # curve fitting
136
+ # see https://docs.scipy.org/doc/scipy/reference/optimize.html
137
+ popt, pcov = optimize.curve_fit(
138
+ calculate_ksigma_array,
139
+ powers,
140
+ ksigma_sp,
141
+ p0=[95.4 / 2, (max(powers) * 0.1)],
142
+ method="lm",
143
+ )
144
+
145
+ assert popt[0] > 0, "Unexpected ksigma value: %d < 0" % popt[0]
146
+
147
+ ksigma_smax = popt[0]
148
+ p_12 = popt[1]
149
+ ksigma_std = _np.sqrt(_np.diag(pcov))
150
+ ksigma_stdd = ksigma_std[0] / smax
151
+
152
+ ksigma_fit = calculate_ksigma_array(powers, ksigma_smax, p_12)
153
+
154
+ ksigma = ksigma_smax / smax
155
+
156
+ return ksigma, ksigma_stdd, ksigma_fit
157
+
158
+
159
+ def calculate_xi(tcorr=54e-12, omega_e=0.0614, omega_H=9.3231e-05):
160
+ """Returns coupling_factor for any given tcorr
161
+
162
+ Args:
163
+ tcorr (float): translational diffusion correlation time (s)
164
+ omega_e (float): electron gyromagnetic ratio
165
+ omega_H (float): proton gyromagnetic ratio
166
+
167
+ Returns:
168
+ xi (float): coupling factor
169
+
170
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
171
+ """
172
+
173
+ # Using Franck et al. PNMRS (2013)
174
+ if tcorr < 0.1:
175
+ tcorr *= 1e12
176
+
177
+ zdiff = _np.sqrt(1j * (omega_e - omega_H) * tcorr)
178
+ zsum = _np.sqrt(1j * (omega_e + omega_H) * tcorr)
179
+ zH = _np.sqrt(1j * omega_H * tcorr)
180
+
181
+ # (Eq. 2)
182
+ Jdiff = (1 + (zdiff / 4)) / (1 + zdiff + ((4 * (zdiff**2)) / 9) + ((zdiff**3) / 9))
183
+
184
+ Jsum = (1 + (zsum / 4)) / (1 + zsum + ((4 * (zsum**2)) / 9) + ((zsum**3) / 9))
185
+
186
+ JH = (1 + (zH / 4)) / (1 + zH + ((4 * (zH**2)) / 9) + ((zH**3) / 9))
187
+
188
+ # (Eq. 23) calculation of coupling_factor from the spectral density functions
189
+ xi = ((6 * _np.real(Jdiff)) - _np.real(Jsum)) / (
190
+ (6 * _np.real(Jdiff)) + (3 * _np.real(JH)) + _np.real(Jsum)
191
+ )
192
+
193
+ return xi
194
+
195
+
196
+ def calculate_tcorr(coupling_factor=0.27, omega_e=0.0614, omega_H=9.3231e-05):
197
+ """Returns translational correlation time (tcorr) in pico second
198
+
199
+ Args:
200
+ coupling_factor (float): coupling factor
201
+ omega_e (float): electron gyromagnetic ratio
202
+ omega_H (float): proton gyromagnetic ratio
203
+
204
+ Returns:
205
+ tcorr (float): translational diffusion correlation time (s)
206
+
207
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
208
+ """
209
+
210
+ # root finding
211
+ # see https://docs.scipy.org/doc/scipy/reference/optimize.html
212
+ result = optimize.root_scalar(
213
+ lambda t_corr: calculate_xi(t_corr, omega_e=omega_e, omega_H=omega_H)
214
+ - coupling_factor,
215
+ method="brentq",
216
+ bracket=[1, 1e5],
217
+ )
218
+
219
+ if not result.converged:
220
+ raise ValueError("Could not find tcorr")
221
+
222
+ tcorr = result.root
223
+ return tcorr * 1e-12
224
+
225
+
226
+ def calculate_uncorrected_Ep(
227
+ uncorrected_xi=0.33,
228
+ p_12_unc=0,
229
+ E_powers=False,
230
+ T10=2.0,
231
+ T100=2.5,
232
+ omega_ratio=658.5792,
233
+ smax=1,
234
+ ):
235
+ """Function for E(p) for any given xi and p_12
236
+
237
+ Args:
238
+ uncorrected_xi (float): uncorrected coupling factor
239
+ p_12_unc (float): power at half max for uncorrected_xi fit
240
+ E_array (numpy.array): Array of enhancements
241
+ E_powers (numpy.array): Array of E_powers
242
+ T10 (float): T1(0), proton T1 with microwave power=0 (s)
243
+ T100 (float): T10(0), proton T1 with spin_C=0 and microwave power=0 (s)
244
+ omega_ratio (float): ratio of electron & proton gyromagnetic ratios
245
+ smax (float): maximal saturation factor
246
+
247
+ Returns:
248
+ Ep_fit (numpy.array): uncorrected enhancement curve
249
+
250
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
251
+ """
252
+
253
+ # Right side of Eq. 42. This function should fit to ksig_sp
254
+ Ep_fit = 1 - (
255
+ (uncorrected_xi * (1 - (T10 / T100)) * omega_ratio)
256
+ * ((E_powers * smax) / (p_12_unc + E_powers))
257
+ )
258
+
259
+ return Ep_fit
260
+
261
+
262
+ def _residual_Ep(
263
+ x,
264
+ E_array: _np.array,
265
+ E_powers: _np.array,
266
+ T10: float,
267
+ T100: float,
268
+ omega_ratio: float,
269
+ smax: float,
270
+ ):
271
+ """Function for residuals between E(p) for any given xi and p_12 and the experimental E_array
272
+
273
+ Args:
274
+ x (list): [uncorrected coupling factor, power at half max for uncorrected_xi fit]
275
+ E_array (numpy.array): Array of enhancements
276
+ E_powers (numpy.array): Array of E_power
277
+ T10 (float): T1(0), proton T1 with microwave power=0 (s)
278
+ T100 (float): T10(0), proton T1 with spin_C=0 and microwave power=0 (s)
279
+ omega_ratio (float): ratio of electron & proton gyromagnetic ratios
280
+ smax (float): maximal saturation factor
281
+
282
+ Returns:
283
+ Ep_fit (numpy.array): uncorrected enhancement curve
284
+
285
+ J.M. Franck et al. / Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
286
+ """
287
+
288
+ return E_array - calculate_uncorrected_Ep(
289
+ uncorrected_xi=x[0],
290
+ p_12_unc=x[1],
291
+ E_powers=E_powers,
292
+ T10=T10,
293
+ T100=T100,
294
+ omega_ratio=omega_ratio,
295
+ smax=smax,
296
+ )
297
+
298
+
299
+ def calculate_uncorrected_xi(
300
+ E_array=False,
301
+ E_powers=False,
302
+ T10=2.0,
303
+ T100=2.5,
304
+ omega_ratio=658.5792,
305
+ smax=1,
306
+ ):
307
+ """Get coupling_factor and E_power at half saturation
308
+
309
+ Args:
310
+ E_array (numpy.array): Array of enhancements
311
+ E_powers (numpy.array): Array of powers
312
+ T10 (float): T1(0), proton T1 with microwave power=0 (s)
313
+ T100 (float): T10(0), proton T1 with spin_C=0 and microwave power=0 (s)
314
+ omega_ratio (float): ratio of electron & proton gyromagnetic ratios
315
+ smax (float): maximal saturation factor
316
+
317
+ Returns:
318
+ uncorrected_xi (float): uncorrected coupling factor
319
+ p_12_unc (float): power at half max for uncorrected_xi fit
320
+
321
+ J.M. Franck et al.; Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
322
+ """
323
+
324
+ # least-squares fitting.
325
+ # see https://docs.scipy.org/doc/scipy/reference/optimize.html
326
+ results = optimize.least_squares(
327
+ fun=_residual_Ep,
328
+ x0=[0.27, (max(E_powers) * 0.1)],
329
+ args=(E_array, E_powers, T10, T100, omega_ratio, smax),
330
+ jac="2-point",
331
+ method="lm",
332
+ )
333
+ if not results.success:
334
+ raise ValueError("Could not fit Ep")
335
+ assert results.x[0] > 0, "Unexpected coupling_factor value: %d < 0" % results.x[0]
336
+
337
+ uncorrected_xi = results.x[0]
338
+ p_12_unc = results.x[1]
339
+
340
+ return uncorrected_xi, p_12_unc
341
+
342
+
343
+ def hydration(data={}, constants={}):
344
+ """Function for performing ODNP calculations
345
+
346
+ Args:
347
+ data (dict) : keys and values are described in the example
348
+ constants (dict) : (optional) keys and values are described in the example
349
+
350
+ Returns:
351
+ (dict) : keys and values are described in the example
352
+
353
+ J.M. Franck et al.; Progress in Nuclear Magnetic Resonance Spectroscopy 74 (2013) 33–56
354
+ https://www.sciencedirect.com/science/article/abs/pii/S0079656513000629
355
+
356
+ J.M. Franck, S. Han; Methods in Enzymology, Chapter 5, Volume 615, (2019) 131-175
357
+ https://www.sciencedirect.com/science/article/abs/pii/S0076687918303872
358
+ """
359
+
360
+ if not data:
361
+ raise ValueError("Please supply a valid data dictionary, see example")
362
+
363
+ if "hydration_inputs" in data.keys():
364
+ warnings.warn(
365
+ "The workspace concept is depreciated, see the example in the docs for the new syntax"
366
+ )
367
+ _data = data["hydration_inputs"]
368
+ if "hydration_constants" in data.keys():
369
+ constants = data["hydration_constants"]
370
+ data = _data
371
+
372
+ if "tcorr_bulk" in constants.keys() and constants["tcorr_bulk"] > 0.1:
373
+ warnings.warn(
374
+ "tcorr_bulk should be given in seconds, support for picoseconds will be removed in a future release"
375
+ )
376
+ constants["tcorr_bulk"] *= 1e-12
377
+
378
+ if "macro_C" in constants.keys() and constants["macro_C"] > 0.1:
379
+ warnings.warn(
380
+ "macro_C should be given in molar, support for micromolar will be removed in a future release"
381
+ )
382
+ constants["macro_C"] *= 1e-6
383
+
384
+ if data["spin_C"] > 0.1:
385
+ warnings.warn(
386
+ "spin_C should be given in molar, support for micromolar will be removed in a future release"
387
+ )
388
+ data["spin_C"] *= 1e-6
389
+
390
+ if "field" in data.keys():
391
+ warnings.warn(
392
+ "keyword 'field' is depreciated, please use 'magnetic_field' from now on"
393
+ )
394
+ if "magnetic_field" in data.keys():
395
+ warnings.warn(
396
+ "you supplied both 'field' and 'magnetic_field', only 'magnetic_field' will be used"
397
+ )
398
+ else:
399
+ data["magnetic_field"] = data["field"]
400
+ data.pop("field")
401
+
402
+ if data["magnetic_field"] > 3:
403
+ warnings.warn(
404
+ "magnetic_field should be given in T, support for mT will be removed in a future release"
405
+ )
406
+ data["magnetic_field"] *= 1e-3
407
+
408
+ standard_constants = {
409
+ "ksigma_bulk": 95.4,
410
+ "krho_bulk": 353.4,
411
+ "klow_bulk": 366,
412
+ "tcorr_bulk": 54e-12,
413
+ "D_H2O": 2.3e-9,
414
+ "D_SL": 4.1e-10,
415
+ "delta_T1_water": False,
416
+ "T1_water": False,
417
+ "macro_C": False,
418
+ }
419
+ # these constants have been compiled from the various ODNP literature
420
+
421
+ odnp_constants = {**standard_constants, **constants}
422
+
423
+ if data["smax_model"] == "tethered":
424
+ # Option 1, tether spin label
425
+ s_max = 1 # (section 2.2) maximal saturation factor
426
+
427
+ elif data["smax_model"] == "free":
428
+ # Option 2, free spin probe
429
+ s_max = calculate_smax(data["spin_C"]) # from:
430
+ # M.T. Türke, M. Bennati, Phys. Chem. Chem. Phys. 13 (2011) 3630. &
431
+ # J. Hyde, J. Chien, J. Freed, J. Chem. Phys. 48 (1968) 4211.
432
+
433
+ elif isinstance(data["smax_model"], float):
434
+ # Option 3, manual input of smax
435
+ if not (data["smax_model"] <= 1 and data["smax_model"] > 0):
436
+ raise ValueError(
437
+ "if given directly, smax must be type float between 0 and 1"
438
+ )
439
+ s_max = data["smax_model"]
440
+ else:
441
+ raise ValueError(
442
+ "'smax_model' must be 'tethered', 'free', or a float between 0 and 1"
443
+ )
444
+
445
+ omega_e = 1.76085963023e-1 * data["magnetic_field"]
446
+ # gamma_e in 1/ps for the tcorr unit, then correct by magnetic_field in T.
447
+ # gamma_e is from NIST. The magnetic_field cancels in the following omega_ratio but you
448
+ # need these individually for the spectral density functions later.
449
+
450
+ omega_H = 2.6752218744e-4 * data["magnetic_field"]
451
+ # gamma_H in 1/ps for the tcorr unit, then correct by magnetic_field in T.
452
+ # gamma_H is from NIST. The magnetic_field cancels in the following omega_ratio but you
453
+ # need these individually for the spectral density functions later.
454
+
455
+ omega_ratio = (omega_e / (2 * _const.pi)) / (omega_H / (2 * _const.pi))
456
+ # (Eq. 4-6) ratio of omega_e and omega_H, divide by (2*pi) to get angular
457
+ # frequency units in order to correspond to S_0/I_0, this is also ~= to the
458
+ # ratio of the resonance frequencies for the experiment, i.e. MW freq/RF freq
459
+
460
+ if "T1_powers" in data.keys():
461
+ T1p = interpolate_T1(
462
+ E_powers=data["E_powers"],
463
+ T1_powers=data["T1_powers"],
464
+ T1_array=data["T1_array"],
465
+ interpolate_method=data["interpolate_method"],
466
+ delta_T1_water=odnp_constants["delta_T1_water"],
467
+ T1_water=odnp_constants["T1_water"],
468
+ macro_C=odnp_constants["macro_C"],
469
+ spin_C=data["spin_C"],
470
+ T10=data["T10"],
471
+ T100=data["T100"],
472
+ )
473
+ else:
474
+ if len(data["T1_array"]) == len(data["E_array"]):
475
+ T1p = data["T1_array"]
476
+ else:
477
+ raise ValueError(
478
+ "'T1_array' must be equal in length to 'E_array'. Otherwise give 'T1_powers' equal in length to 'T1_array' in order to interpolate."
479
+ )
480
+
481
+ ksigma_array = (1 - data["E_array"]) / (data["spin_C"] * omega_ratio * T1p)
482
+ # (Eq. 41) this calculates the array of ksigma*s(p) from the enhancement array,
483
+ # dividing by the T1 array for the "corrected" analysis
484
+
485
+ ksigma, ksigma_stdd, ksigma_fit = calculate_ksigma(
486
+ ksigma_array, data["E_powers"], s_max
487
+ )
488
+ # fit to the right side of Eq. 42 to get (ksigma*smax) and half of the E_power at s_max, called p_12 here
489
+
490
+ krho = ((1 / data["T10"]) - (1 / data["T100"])) / (
491
+ data["spin_C"]
492
+ ) # (Eq. 36) "self" relaxivity, unit is s^-1 M^-1
493
+
494
+ coupling_factor = ksigma / krho # coupling factor, unitless
495
+
496
+ tcorr = calculate_tcorr(coupling_factor, omega_e, omega_H)
497
+ # (Eq. 21-23) this calls the fit to the spectral density functions. The fit
498
+ # optimizes the value of tcorr in the calculation of coupling_factor, the correct tcorr
499
+ # is the one for which the calculation of coupling_factor from the spectral density
500
+ # functions matches the coupling_factor found experimentally. tcorr unit is ps
501
+
502
+ Dlocal = (odnp_constants["tcorr_bulk"] / tcorr) * (
503
+ odnp_constants["D_H2O"] + odnp_constants["D_SL"]
504
+ )
505
+ # (Eq. 19-20) local diffusivity, i.e. diffusivity of the water near the spin label
506
+
507
+ klow = ((5 * krho) - (7 * ksigma)) / 3
508
+ # section 6, (Eq. 13). this describes the relatively slowly diffusing water
509
+ # near the spin label, sometimes called "bound" water.
510
+ # This is defined in its most compact form in:
511
+ # Frank, JM and Han, SI; Chapter Five - Overhauser Dynamic Nuclear Polarization
512
+ # for the Study of Hydration Dynamics, Explained. Methods in Enzymology, Volume 615, 2019
513
+ # But also explained well in:
514
+ # Franck, JM, et. al.; "Anomalously Rapid Hydration Water Diffusion Dynamics
515
+ # Near DNA Surfaces" J. Am. Chem. Soc. 2015, 137, 12013−12023.
516
+
517
+ xi_unc, p_12_unc = calculate_uncorrected_xi(
518
+ data["E_array"],
519
+ data["E_powers"],
520
+ data["T10"],
521
+ data["T100"],
522
+ omega_ratio,
523
+ s_max,
524
+ )
525
+ # (Eqs. 7 and 44) this calculates the coupling factor using the "uncorrected" analysis
526
+
527
+ uncorrected_Ep = calculate_uncorrected_Ep(
528
+ xi_unc,
529
+ p_12_unc,
530
+ data["E_powers"],
531
+ data["T10"],
532
+ data["T100"],
533
+ omega_ratio,
534
+ s_max,
535
+ )
536
+ # (Eqs. 7 and 44) this calculates the "uncorrected" enhancement array using xi_unc
537
+
538
+ return {
539
+ "uncorrected_Ep": uncorrected_Ep,
540
+ "uncorrected_xi": xi_unc,
541
+ "interpolated_T1": T1p,
542
+ "ksigma_array": ksigma_array,
543
+ "ksigma_fit": ksigma_fit,
544
+ "ksigma": ksigma,
545
+ "ksigma_stdd": ksigma_stdd,
546
+ "ksigma_bulk_ratio": ksigma / odnp_constants["ksigma_bulk"],
547
+ "krho": krho,
548
+ "krho_bulk_ratio": krho / odnp_constants["krho_bulk"],
549
+ "klow": klow,
550
+ "klow_bulk_ratio": klow / odnp_constants["klow_bulk"],
551
+ "coupling_factor": coupling_factor,
552
+ "tcorr": tcorr,
553
+ "tcorr_bulk_ratio": tcorr / odnp_constants["tcorr_bulk"],
554
+ "Dlocal": Dlocal,
555
+ }