t1t2ne 0.0.2.dev0__tar.gz → 0.0.2.dev1__tar.gz

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 (32) hide show
  1. {t1t2ne-0.0.2.dev0/t1t2ne.egg-info → t1t2ne-0.0.2.dev1}/PKG-INFO +2 -2
  2. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/README.md +1 -1
  3. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/pyproject.toml +3 -3
  4. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/cli.py +2 -2
  5. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_ParaMeters_relax.py +22 -3
  6. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/fun_hetrelax_models.py +8 -0
  7. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_interactive.py +67 -27
  8. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_ns.py +20 -16
  9. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_setuptract.py +57 -1
  10. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_solventpre.py +30 -3
  11. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_tract.py +34 -22
  12. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_utils.py +37 -6
  13. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/tract_extra.py +8 -3
  14. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1/t1t2ne.egg-info}/PKG-INFO +2 -2
  15. t1t2ne-0.0.2.dev1/t1t2ne.egg-info/entry_points.txt +2 -0
  16. t1t2ne-0.0.2.dev0/t1t2ne.egg-info/entry_points.txt +0 -2
  17. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/LICENSE.txt +0 -0
  18. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/setup.cfg +0 -0
  19. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/__init__.py +0 -0
  20. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/__init__.py +0 -0
  21. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/base.py +0 -0
  22. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/create_registry.py +0 -0
  23. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_findfs.py +0 -0
  24. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_fit.py +0 -0
  25. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/hydrodynamics_utils.py +0 -0
  26. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_configure.py +0 -0
  27. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_makelists.py +0 -0
  28. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/textcolor.py +0 -0
  29. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/SOURCES.txt +0 -0
  30. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/dependency_links.txt +0 -0
  31. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/requires.txt +0 -0
  32. {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: t1t2ne
3
- Version: 0.0.2.dev0
3
+ Version: 0.0.2.dev1
4
4
  Project-URL: Homepage, https://github.com/enricoravera/t1ttune
5
5
  Project-URL: Repository, https://github.com/enricoravera/t1ttune.git
6
6
  Project-URL: Documentation, https://t1ttune.readthedocs.io
@@ -19,7 +19,7 @@ Requires-Dist: csaps
19
19
  Dynamic: license-file
20
20
 
21
21
  # **T1-T2-ne**
22
-
22
+ <img src='https://github.com/enricoravera/T1T2ne/blob/main/docs/source/_static/t1t2ne_logo.png' alt='logo' width=300 />
23
23
  This software computes the optimal delays for protein dynamics experiments.
24
24
 
25
25
  Full documentation is available at [https://t1ttune.readthedocs.io/en/latest/](https://t1ttune.readthedocs.io/en/latest/).
@@ -1,5 +1,5 @@
1
1
  # **T1-T2-ne**
2
-
2
+ <img src='https://github.com/enricoravera/T1T2ne/blob/main/docs/source/_static/t1t2ne_logo.png' alt='logo' width=300 />
3
3
  This software computes the optimal delays for protein dynamics experiments.
4
4
 
5
5
  Full documentation is available at [https://t1ttune.readthedocs.io/en/latest/](https://t1ttune.readthedocs.io/en/latest/).
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "t1t2ne"
7
- version = "0.0.2.dev0"
7
+ version = "0.0.2.dev1"
8
8
  description = ""
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -29,12 +29,12 @@ where = ['.']
29
29
  include = ['t1t2ne*']
30
30
 
31
31
  [tool.setuptools.package-data]
32
- t1ttune = ['*.txt', '*.md', '*.yaml']
32
+ t1ttune = ['*.txt', '*.md', '*.yaml', 'examples/**']
33
33
 
34
34
  [project.readme]
35
35
  file = "README.md"
36
36
  content-type = "text/markdown"
37
37
 
38
38
  [project.scripts]
39
- t1ttune = "t1t2ne.cli:main"
39
+ t1t2ne = "t1t2ne.cli:main"
40
40
 
@@ -9,7 +9,7 @@ from .scripts.__init__ import registry
9
9
  def main():
10
10
  # Top-level parser — mostra i sottocomandi disponibili nell'help
11
11
  parser = argparse.ArgumentParser(
12
- prog="t1ttune",
12
+ prog="t1t2ne",
13
13
  description="Main program. Select a subcommand to start.",
14
14
  formatter_class=argparse.RawDescriptionHelpFormatter,
15
15
  epilog=_build_commands_epilog(),
@@ -47,5 +47,5 @@ def _build_commands_epilog() -> str:
47
47
  for name, cmd in registry.items():
48
48
  lines.append(f" {name:<12} {cmd.SHORT_HELP}")
49
49
  lines.append("")
50
- lines.append("Use 't1ttune <command> --help' for details on each command.")
50
+ lines.append("Use 't1t2ne <command> --help' for details on each command.")
51
51
  return "\n".join(lines)
@@ -237,7 +237,12 @@ def SBM(r, B, taue=1e-9, taur=1e-9, tau_M=np.inf, S=0.5, tauv=None, deltat=None,
237
237
 
238
238
  def transient_zfs(deltat, B, tauv, S):
239
239
  """
240
- Calculate the transient zero-field splitting (ZFS) contributions to the electron relaxation rates T1e and T2e using the Bloembergen-Morgan model.
240
+ Calculate the transient zero-field splitting (ZFS) contributions to the electron relaxation rates T1e and T2e using the Bloembergen-Morgan model:
241
+
242
+ .. math::
243
+
244
+ R_{1e} = \frac{2}{15} \Delta_t^2 \left(4S(S+1) - 3\right) \left(J(\omega_e) + 2J(2\omega_e)\right) \\
245
+ R_{2e} = \frac{1}{15} \Delta_t^2 \left(4S(S+1) - 3\right) \left(3J(0) + 5J(\omega_e) + J(2\omega_e)\right)
241
246
 
242
247
  Parameters
243
248
  ----------
@@ -373,7 +378,21 @@ def rotational_taue(g, B, tauv, A=None, I=None):
373
378
 
374
379
  def OuterSphere(B, c=1, d=3.6e-10, D_target = 1e-10, D_cosolute = 2.6e-10, f=0.5, taue=1e-9, tauv=2.6e-11, deltat=0.014, AMe=None, I=None, g=None, S=3.5, nuc='1H'):
375
380
  """
376
- Calculate the outer sphere relaxation rates R1 and R2 using the Freed model, with optional transient zero-field splitting (ZFS) contributions.
381
+ Calculate the outer sphere relaxation rates R1 and R2 using the Freed model:
382
+
383
+ .. math::
384
+
385
+ k_{outer} = \frac{16\pi}{81} \left( \frac{\mu_0}{4\pi} \right)^2 h^2 \gamma_n^2 \gamma_e^2 S(S+1) N_A f \frac{c}{d(D_{target}+D_{cosolute})}\\
386
+ R_1 = k_{outer} [7 J_{outer} (\omega_S) + 3 J_{outer} (\omega_I)]\\
387
+ R_2 = \frac{1}{2} k_{outer} [13 J_{outer} (\omega_S) + 3 J_{outer} (\omega_I) + 4 J_{outer} (0)]
388
+
389
+ Optionally, the transient zero-field splitting (ZFS) contribution to electron relaxation is included with the Bloembergen-Morgan model:
390
+
391
+ .. math::
392
+
393
+ R_{1e} = \frac{2}{15} \Delta_t^2 \left(4S(S+1) - 3\right) \left(J(\omega_e) + 2J(2\omega_e)\right)\\
394
+ R_{2e} = \frac{1}{15} \Delta_t^2 \left(4S(S+1) - 3\right) \left(3J(0) + 5J(\omega_e) + J(2\omega_e)\right)
395
+
377
396
  Default values are for 1 mM Gd-DOTA and a protein of 10 kDa at room temperature. Values for Gd-DOTA are taken from `Li et al. 2002`_.
378
397
 
379
398
  .. _Li et al. 2002: https://pubs.acs.org/doi/full/10.1021/ic0200390
@@ -423,7 +442,7 @@ def OuterSphere(B, c=1, d=3.6e-10, D_target = 1e-10, D_cosolute = 2.6e-10, f=0.5
423
442
  mu_0 = constants.mu_0
424
443
  gamma_nuc = kz.sim.gamma[nuc]*(2*np.pi)*1e6 # in rad/s/T
425
444
  gamma_e = -1 * constants.physical_constants['electron gyromag. ratio'][0]
426
- k_outer = (16./81.) * np.pi * (mu_0/(4*np.pi))**2 * (h**2) * (gamma_nuc**2) * (gamma_e**2 / (4*np.pi**2)) * S*(S+1.) * N_A * c / (d * (D_target + D_cosolute))
445
+ k_outer = (16./81.) * np.pi * (mu_0/(4*np.pi))**2 * (h**2) * (gamma_nuc**2) * (gamma_e**2 / (4*np.pi**2)) * S*(S+1.) * N_A * c * f / (d * (D_target + D_cosolute))
427
446
 
428
447
  omegai = gamma_nuc * B
429
448
  omegas = gamma_e * B
@@ -217,6 +217,14 @@ def J_Freed(w, d, D_target, D_cosolute, tau_1=1e-9, tau_2=None):
217
217
  Freed spectral density function for outer sphere relaxation. Equations 6.42, 6.48 and 6.50 in `Bertini et al. 2016`_.
218
218
 
219
219
  .. _Bertini et al. 2016: https://www.sciencedirect.com/science/chapter/monograph/pii/B9780444634368000065
220
+
221
+ .. math::
222
+
223
+ \tau_D = \frac{d^2}{D_{target} + D_{cosolute}}
224
+ z = \sqrt{2 |\omega| \tau_D + \frac{\tau_D}{\tau_1}}
225
+ J(\omega) = \frac{2}{5} \frac{1 + \frac{5 z}{8} + \frac{z^2}{8}}{1 + z + \frac{z^2}{2} + \frac{z^3}{6} + \frac{4 z^4}{81} + \frac{z^5}{81} + \frac{z^6}{648}}
226
+
227
+
220
228
 
221
229
  Parameters
222
230
  -----------
@@ -8,6 +8,7 @@ from .textcolor import textcolor
8
8
  import klassez as kz
9
9
  from . import t1t2ne_utils, fun_hetrelax_models, f_findfs
10
10
  import random
11
+ from scipy.special import lambertw
11
12
 
12
13
  class InteractiveCmd(BaseCommand):
13
14
  SHORT_HELP = "Interactive setup of the experiment"
@@ -36,7 +37,7 @@ class InteractiveCmd(BaseCommand):
36
37
  parser.add_argument('--Deltasigma', type=float, default=-160, help='The chemical shift anisotropy of the 15N nucleus in ppm. Default is -160 ppm.')
37
38
  parser.add_argument('--theta', type=float, default=17, help='The angle between the 1H-15N bond and the principal axis of the CSA tensor in degrees. Default is 17 degrees.')
38
39
  parser.add_argument('--B0', nargs='*', help='The magnetic field strength in Tesla. If not provided, the script will try to load it from the config file. If it is not found in the config file, an error will be raised.')
39
- parser.add_argument('--Larmor', type=float, help='The Larmor frequency of the nucleus in MHz. If not provided, the script will try to load it from the config file. If it is not found in the config file, an error will be raised.')
40
+ parser.add_argument('--Larmor', nargs=1, type=float, help='The Larmor frequency of the nucleus in MHz. If not provided, the script will try to load it from the config file. If it is not found in the config file, an error will be raised.')
40
41
  parser.add_argument('--logscale', action='store_true', help='Whether to use a logarithmic scale for the vdlist and vclist. Default is False, which means a linear scale will be used.')
41
42
  parser.add_argument('--nucs', nargs='*', default=['1H', '15N'], help='The nuclei to use for the calculation of the relaxation rates. Default is 1H and 15N.')
42
43
  parser.add_argument('--large', action='store_true', help='Whether to create the lists for the "large" sequence, which is optimized for short T2 times. If True, the d21 value is set to 450 us and only 8 cycles per CPMG block are used instead of 16. Default is False.')
@@ -70,13 +71,15 @@ def interactive_setup(CO):
70
71
  CO.add_ref('klassez')
71
72
 
72
73
  CO.get_B0()
74
+
75
+ expectedratio = np.real(np.exp(-(1+lambertw(1/np.e)))) # maximum reduction achievable with the optimal CPMG block, which is 1+lambertw(1/e) times the T2 time of the system
73
76
 
74
77
  if hasattr(CO, 'tau') and CO.tau is not None:
75
78
  CO.add_ref('fushman')
76
79
  R1, R2, nOe = fun_hetrelax_models.R1R2nOe(CO.B_0, r=CO.r, nuc1=CO.nucs[0], nuc2=CO.nucs[1], Deltasigma=CO.Deltasigma, func=fun_hetrelax_models.LS_iso, f_args=(CO.S2, CO.tau))
77
80
  print(textcolor('Based on the provided parameters, the estimated R1 is {:.2f} s^-1 and R2 is {:.2f} s^-1.'.format(R1, R2), 'green'))
78
- T1_30 = -np.log(0.3)/R1
79
- T2_30 = -np.log(0.3)/R2
81
+ T1_30 = -np.log(expectedratio)/R1
82
+ T2_30 = -np.log(expectedratio)/R2
80
83
  else:
81
84
  T1_30 = 0.5
82
85
  T2_30 = 0.065
@@ -92,6 +95,7 @@ def interactive_setup(CO):
92
95
  else:
93
96
  CO.T1red = float(input('Enter percent residual signal for the longest delay of the T1 experiment (default 5%): ').strip() or "5")*0.01
94
97
  CO.T2red = float(input('Enter percent residual signal for the longest CPMG block of the T2 experiment (default 10%): ').strip() or "10")*0.01
98
+
95
99
  logscale = CO.options['logscale']
96
100
  nT1 = CO.nT[0]
97
101
  if len(CO.nT) == 1:
@@ -109,7 +113,8 @@ def interactive_setup(CO):
109
113
 
110
114
 
111
115
  print('\nStarting the interactive setup of the T1 experiment...')
112
- print('Acquire the reference spectrum first with d7 = 20u.')
116
+ d29 = -np.log(CO.T1red)/R1
117
+ print(f'Acquire the reference spectrum first with d31 = 20u and d29 = {d29:.3f} s.')
113
118
  refno = input('Please enter the reference spectrum number (default is 1): ') or '1'
114
119
 
115
120
  path_ref = os.path.join(CO.basedir, refno)
@@ -119,6 +124,9 @@ def interactive_setup(CO):
119
124
  S_ref = kz.Spectrum_1D(path_ref)
120
125
  except Exception as e:
121
126
  raise RuntimeError(f'Error occurred while loading the reference spectrum: {e}')
127
+ version = t1t2ne_utils.fs_version(S_ref)
128
+ if version == 'topspin3':
129
+ S_ref.acqus['GRPDLY'] += 1
122
130
  S_ref.procs['wf']['mode'] = 'em'
123
131
  S_ref.procs['wf']['lb'] = 1/S_ref.acqus['AQ']
124
132
  print(f'Line broadening applied: {S_ref.procs["wf"]["lb"]:.2g} Hz')
@@ -132,11 +140,11 @@ def interactive_setup(CO):
132
140
  S_ref.integrate(filename='refspec')
133
141
  limits = kz.misc.key_to_limits(list(S_ref.integrals.keys()))
134
142
 
135
- ta = S_ref.ngdict['acqus']['D'][7]
143
+ ta = S_ref.ngdic['acqus']['D'][31]
136
144
  whilecontrol = True
137
145
  iteration = 0
138
146
  while whilecontrol:
139
- print(textcolor(f'To achieve a reduction of the signal intensity of about {(1-CO.T1red)*100:.0f}%, try setting d7 to ' + t1t2ne_utils.f4(T1_30), 'blue'))
147
+ print(textcolor(f'To achieve a reduction of the signal intensity of about {expectedratio*100:.0f}%, try setting d31 to ' + t1t2ne_utils.f4(T1_30), 'blue'))
140
148
  expno = input('Please enter the experiment number for the T1 experiment once it is done (default is 2): ') or '2'
141
149
  path_t1 = os.path.join(CO.basedir, expno)
142
150
  if not os.path.exists(path_t1):
@@ -145,27 +153,29 @@ def interactive_setup(CO):
145
153
  S_t1 = kz.Spectrum_1D(path_t1)
146
154
  except Exception as e:
147
155
  raise RuntimeError(f'Error occurred while loading the T1 spectrum: {e}')
156
+ if version == 'topspin3':
157
+ S_t1.acqus['GRPDLY'] += 1
148
158
  S_t1.procs['wf']['mode'] = 'em'
149
159
  S_t1.procs['wf']['lb'] = 1/S_t1.acqus['AQ']
150
160
  S_t1.procs['zf'] = 2 * S_t1.fid.shape[-1]
151
161
  S_t1.process()
152
162
  S_t1.pknl()
153
163
  S_t1.adjph()
154
- S_t1.integrate(filename='t1spec', limits=limits)
155
- tb = S_t1.ngdict['acqus']['D'][7]
164
+ S_t1.integrate(filename='t1spec', lims=limits)
165
+ tb = S_t1.ngdic['acqus']['D'][31]
156
166
  ratio = 0
157
167
  for key in S_t1.integrals.keys():
158
168
  ratio += S_t1.integrals[key] / S_ref.integrals[key] * (1/len(S_t1.integrals.keys()))
159
169
  R1_ave = -np.log(ratio) / (tb-ta)
160
- print(f'Iteration {iteration}: Estimated R1 from the reference and T1 spectra: {R1_ave:.2f} s^-1')
161
- T1_30_est = -np.log(0.3)/R1_ave
170
+ print(f'Iteration {iteration}: Estimated R1 from the reference ({ta*1e6:.2f} u) and T1 spectra ({tb:.3f} s) with a ratio of {ratio:.2f}: {R1_ave:.2f} s^-1')
171
+ T1_30_est = -np.log(expectedratio)/R1_ave
162
172
  if abs(T1_30_est - T1_30) / T1_30 > 0.1:
163
173
  T1_30 = T1_30_est
164
174
  else:
165
175
  whilecontrol = False
166
176
  iteration += 1
167
177
  print(textcolor(f'Convergence achieved for T1 estimation. Estimated $R1_{{ave}}$ is {R1_ave:.2f} s.', 'green'))
168
- print(textcolor(f'Set the recovery delay for the hetnOe experiment to at least {6/R1_ave:.3f}.\n', 'blue', bold=False))
178
+ print(textcolor(f'Set the recovery delay for the hetnOe experiment in the range {4/R1_ave:.3f} - {6/R1_ave:.3f}.\n', 'blue', bold=False))
169
179
  T1max = -np.log(CO.T1red)/R1_ave
170
180
  if logscale:
171
181
  vdlist_T1 = [2e-5 - 1/R1_ave * np.log(1-(1-CO.T1red)*i/(nT1-1)) for i in range(nT1)] #logarithmically spaced list from 20u to to T1max
@@ -179,18 +189,35 @@ def interactive_setup(CO):
179
189
 
180
190
 
181
191
  print('\nStarting the interactive setup of the T2 experiment...')
182
- print('Acquire the reference spectrum first with l1 = 0.')
192
+ T2max = -np.log(CO.T2red)/R2
193
+
183
194
  if CO.options['idp'] and not CO.options['small']:
184
195
  CO.options['small'] = True
196
+ if T2max > 0.250:
197
+ print(textcolor(f'Alert: the longest CPMG block for a residual signal of {T2red*100:.0f}% is {T2max:.2f} s, which is likely too long for any equipment.', 'red', bold=True))
198
+ print('Setting the maximum CPMG block to to 250ms')
199
+ T2max = 0.250
200
+ T2red = 1 - np.exp(-R2*T2max)
201
+ print(textcolor(f'With this maximum CPMG block, the expected residual signal is {T2red*100:.0f}%.', 'yellow', bold=True))
202
+ if CO.options['large']:
203
+ print(textcolor('You chose the "large" sequence, which is optimized for short T2 times, this option will be disabled.', 'yellow', bold=True))
204
+ CO.options['large'] = False
205
+
185
206
  if CO.options['small']:
186
- print(textcolor('Using ".idp" sequence, which is optimized for long T2 times. d21 = 600u', 'blue'))
187
- d21 = 600
207
+ print(textcolor('Using ".idp" sequence, which is optimized for long T2 times. d21 = 750u', 'blue'))
208
+ d21 = 750
188
209
  else:
189
210
  d21 = float(input('Enter the d21 value in microseconds (default 450): ').strip() or "450")
190
- p30 = float(input('Enter the p30 value in microseconds (default 80): ').strip() or "80")
211
+ p30 = float(input('Enter the p30 value in microseconds (default 160): ').strip() or "160")
191
212
  d31 = (p30*16+d21*32)
192
213
  if CO.options['large']:
193
214
  d31 = d31/2
215
+ T2red_max = 1 - np.exp(-R2*0.250)
216
+ T2_30 = min(T2_30, 0.250)
217
+ l29 = int(T2_30/(d31*1e-6))
218
+ l31_hl = int(0.250/(d31*1e-6))
219
+
220
+ print(f'Acquire the reference spectrum first with l31 = 0 and l29={l29:d}.')
194
221
 
195
222
 
196
223
  refno = input('Please enter the reference spectrum number (default is 1): ') or '1'
@@ -202,6 +229,9 @@ def interactive_setup(CO):
202
229
  S_ref = kz.Spectrum_1D(path_ref)
203
230
  except Exception as e:
204
231
  raise RuntimeError(f'Error occurred while loading the reference spectrum: {e}')
232
+ version = t1t2ne_utils.fs_version(S_ref)
233
+ if version == 'topspin3':
234
+ S_ref.acqus['GRPDLY'] += 1
205
235
  S_ref.procs['wf']['mode'] = 'em'
206
236
  S_ref.procs['wf']['lb'] = 1/S_ref.acqus['AQ']
207
237
  print(f'Line broadening applied: {S_ref.procs["wf"]["lb"]:.2g} Hz')
@@ -215,15 +245,18 @@ def interactive_setup(CO):
215
245
  S_ref.integrate(filename='refspec')
216
246
  limits = kz.misc.key_to_limits(list(S_ref.integrals.keys()))
217
247
 
218
- ta = S_ref.ngdict['acqus']['L'][1]*d31*1e-6
248
+ ta = S_ref.ngdic['acqus']['L'][31]*d31*1e-6
219
249
  whilecontrol = True
220
250
  iteration = 0
221
251
  l1_30 = int(T2_30/(d31*1e-6))
222
252
  if l1_30 <= 0:
223
253
  l1_30 = 1
224
-
254
+ if l1_30 >= l31_hl:
255
+ l1_30 = l31_hl
256
+ R2ave = R2
225
257
  while whilecontrol:
226
- print(textcolor(f'To achieve a reduction of the signal intensity of about {(1-CO.T2red)*100:.0f}%, try setting l1 to {l1_30:.0f}'))
258
+ T2red_30 = max(1 - np.exp(-R2ave*l1_30*d31*1e-6), T2red_max)
259
+ print(textcolor(f'To achieve a reduction of the signal intensity of about {T2red_30*100:.0f}%, try setting l31 to {l1_30:.0f}', 'blue'))
227
260
  expno = input('Please enter the experiment number for the T2 experiment once it is done (default is 2): ') or '2'
228
261
  path_t2 = os.path.join(CO.basedir, expno)
229
262
  if not os.path.exists(path_t2):
@@ -232,47 +265,54 @@ def interactive_setup(CO):
232
265
  S_t2 = kz.Spectrum_1D(path_t2)
233
266
  except Exception as e:
234
267
  raise RuntimeError(f'Error occurred while loading the T2 spectrum: {e}')
268
+ if version == 'topspin3':
269
+ S_t2.acqus['GRPDLY'] += 1
235
270
  S_t2.procs['wf']['mode'] = 'em'
236
271
  S_t2.procs['wf']['lb'] = 1/S_t2.acqus['AQ']
237
272
  S_t2.procs['zf'] = 2 * S_t2.fid.shape[-1]
238
273
  S_t2.process()
239
274
  S_t2.pknl()
240
275
  S_t2.adjph()
241
- S_t2.integrate(filename='t2spec', limits=limits)
242
- tb = S_t2.ngdict['acqus']['L'][1]*d31*1e-6
276
+ S_t2.integrate(filename='t2spec', lims=limits)
277
+ tb = S_t2.ngdic['acqus']['L'][31]*d31*1e-6
243
278
  ratio = 0
244
279
  for key in S_t2.integrals.keys():
245
280
  ratio += S_t2.integrals[key] / S_ref.integrals[key] * (1/len(S_t2.integrals.keys()))
246
281
  R2_ave = -np.log(ratio) / (tb-ta)
247
- print(f'Iteration {iteration}: Estimated R2 from the reference and T2 spectra: {R2_ave:.2f} s^-1')
248
- l1_30_est = int((-np.log(0.3)/R2_ave)/(d31*1e-6))
282
+ print(f'Iteration {iteration}: Estimated R2 from the reference ({ta*1e6:.2f} us) and T2 ({tb*1e3:.2f} ms), with ratio of {ratio:.3f}: {R2_ave:.2f} s^-1')
283
+ l1_30_est = int((-np.log(expectedratio)/R2_ave)/(d31*1e-6))
249
284
  if l1_30_est == 0:
250
285
  l1_30_est = 1
251
- if l1_30_est != l1_30:
286
+ if l1_30_est >= l31_hl:
287
+ l1_30_est = l31_hl
288
+ if np.abs(l1_30_est - l1_30) > 1:
252
289
  l1_30 = l1_30_est
253
290
  else:
254
291
  whilecontrol = False
255
292
  iteration += 1
256
293
 
257
294
  print(textcolor(f'Convergence achieved for T2 estimation. Estimated $R2_{{ave}}$ is {R2_ave:.2f} s.', 'green'))
258
- T2max = -np.log(CO.T2red)/R2_ave
295
+ T2max = -np.log(T2red)/R2_ave
259
296
  nmax = int(T2max/(d31*1e-6))
260
297
  if T2max > 0.250:
261
- print(textcolor(f'Alert: the longest CPMG block for a residual signal of {CO.T2red*100:.0f}% is {T2max:.2f} s, which is likely too long for any equipment.', 'red', bold=True))
298
+ print(textcolor(f'Alert: the longest CPMG block for a residual signal of {T2red*100:.0f}% is {T2max:.2f} s, which is likely too long for any equipment.', 'red', bold=True))
262
299
  print('Setting the maximum CPMG block to to 250ms')
263
300
  T2max = 0.250
301
+ T2red = 1 - np.exp(-R2*T2max)
302
+ print(textcolor(f'With this maximum CPMG block, the expected residual signal is {T2red*100:.0f}%.', 'yellow', bold=True))
303
+
264
304
  if CO.options['large']:
265
305
  print(textcolor('You chose the "large" sequence, which is optimized for short T2 times, this option will be disabled.', 'yellow', bold=True))
266
306
  CO.options['large'] = False
267
307
 
268
308
  #print(f'd21 = {d21} us, p30 = {p30} us, cpmgblock = {d31} us')
269
309
  print('\n')
270
- print(textcolor(f'The longest CPMG block for T2 for a residual signal of {CO.T2red*100:.0f}% should be {T2max:.2f} s, with {nmax} loops.' , 'default', bold=True)) #grassetto
310
+ print(textcolor(f'The longest CPMG block for T2 for a residual signal of {T2red*100:.0f}% should be {T2max:.2f} s, with {nmax} loops.' , 'default', bold=True)) #grassetto
271
311
  print(textcolor('Check if this is too long for your equipment before running the experiment', 'red')) #rosso
272
312
  if nT2 > nmax:
273
313
  print(textcolor(f'Warning: the number of increments you chose for the CPMG experiment is {nT2}, which is more than the recommended number of loops {nmax} for a longest CPMG block of {T2max:.2f} s. The list will contain duplicates.', 'yellow')) #giallo
274
314
  if logscale:
275
- vclist_T2 = [-1/R2 * np.log(1-(1-CO.T2red)*i/(nT2-1))/(d31*1e-6) for i in range(nT2)]
315
+ vclist_T2 = [-1/R2 * np.log(1-(1-T2red)*i/(nT2-1))/(d31*1e-6) for i in range(nT2)]
276
316
  vclist_T2 = np.array(vclist_T2, dtype=np.uint64) #convert to np.uint64
277
317
  else:
278
318
  vclist_T2 = np.linspace(0,nmax, num=int(nT2), dtype=np.uint64) #linearly spaced list from 0 to nmax
@@ -21,9 +21,9 @@ class NSCmd(BaseCommand):
21
21
 
22
22
  @staticmethod
23
23
  def add_arguments(parser):
24
- parser.add_argument('--basedir', nargs=1, help='Base directory for the experiment (where the reference spectrum is located)')
25
- parser.add_argument('--tract', nargs=1, help='expno of the TRACT experiment (used to estimate the SNR). Either this or --hsqc should be provided')
26
- parser.add_argument('--hsqc', nargs=1, help='expno of the reference HSQC spectrum (used to estimate the SNR). Either this or --tract should be provided')
24
+ parser.add_argument('--basedir', type=str, help='Base directory for the experiment (where the reference spectrum is located)')
25
+ parser.add_argument('--tract', type=str, help='expno of the TRACT experiment (used to estimate the SNR). Either this or --hsqc should be provided')
26
+ parser.add_argument('--hsqc', type=str, help='expno of the reference HSQC spectrum (used to estimate the SNR). Either this or --tract should be provided')
27
27
  parser.add_argument('--nres', type=int, help='The number of non-proline residues in the protein. If not provided, it will be estimated from the molecular weight.')
28
28
  parser.add_argument('--lw', type=float, help='The linewidth of the peaks in the reference spectrum in Hz. If not provided, it will be estimated from the reference spectrum.')
29
29
  parser.add_argument('--xred', nargs='*', help='The percent residual signal for the longest delay of the experiment, or NOE efficiency as percentage. Accepts a list, computes the optimal number of scans for each value')
@@ -39,9 +39,7 @@ class NSCmd(BaseCommand):
39
39
  @staticmethod
40
40
  def run(args: argparse.Namespace) -> None:
41
41
  CO = t1t2ne_utils.Conf_Optns(args, module='NS')
42
- CO.get_B0(config_p=t1t2ne_utils.load_config())
43
- CO.get_experiments(config_p=t1t2ne_utils.load_config())
44
- SNR = estimate_snr(args)
42
+ SNR = estimate_snr(CO)
45
43
  suggest_scans(CO, SNR)
46
44
  t1t2ne_utils.the_end(CO)
47
45
 
@@ -64,9 +62,9 @@ def estimate_snr(CO):
64
62
  The order parameters to use for the calculation of tau_c. In IDP mode, two values should be provide, else only one.
65
63
  * tau : list of float
66
64
  The correlation times to use for the calculation of tau_c. In IDP mode, two values should be provide, else only one.
67
- * hsqcexpno : str, optional
65
+ * hsqc : str, optional
68
66
  The experiment number of the HSQC reference spectrum to use for the SNR estimation.
69
- * tractexpno : str, optional
67
+ * tract : str, optional
70
68
  The experiment number of the TRACT spectrum to use for the SNR estimation.
71
69
  * T : float, optional
72
70
  The temperature in Kelvin, used to recompute tau_c based on temperature.
@@ -81,23 +79,23 @@ def estimate_snr(CO):
81
79
  """
82
80
 
83
81
 
84
- if CO.hasattr('MW'):
82
+ if hasattr(CO, 'MW') and CO.MW is not None:
85
83
  MW = CO.MW
86
84
  else:
87
- if CO.hasattr('lw'):
85
+ if hasattr(CO, 'lw') and CO.lw is not None:
88
86
  print('No value provided for the molecular weight. Estimating it from the linewidth provided. This is a rough estimate, but it should be sufficient for our purposes.')
89
87
  MW = (CO.lw*np.pi/7.42 - 0.1674)/0.5998
90
88
  CO.add_ref('bermel')
91
89
  CO.add_ref('cavanagh')
92
- elif CO.hasattr('nres'):
90
+ elif hasattr(CO, 'nres') and CO.nres is not None:
93
91
  MW = CO.nres*0.110/0.937 #average mass of an amino acid is 110 Da, and we multiply by 0.937 to account for the fact that prolines do not have an amide proton and therefore do not contribute to the signal in the HSQC spectrum. This is a rough estimate, but it should be sufficient for our purposes.
94
- elif CO.hasattr('tau'):
92
+ elif hasattr(CO, 'tau') and CO.tau is not None:
95
93
  CO.add_ref('cavanagh')
96
94
  MW = (hydrodynamics_utils.tau(CO.tau[0]*1e9, 298.15, T_old=CO.T)- 0.1674)/0.5998
97
95
  else:
98
96
  raise ValueError('No value provided for the molecular weight or linewidth. Cannot estimate molecular weight.')
99
97
 
100
- if CO.hasattr('nres'):
98
+ if hasattr(CO, 'nres') and CO.nres is not None:
101
99
  nres = CO.nres
102
100
  else:
103
101
  nres = np.rint(MW/0.110)*(0.937) #average mass of an amino acid is 110 Da, and we multiply by 0.937 to account for the fact that prolines do not have an amide proton and therefore do not contribute to the signal in the HSQC spectrum. This is a rough estimate, but it should be sufficient for our purposes.
@@ -113,7 +111,7 @@ def estimate_snr(CO):
113
111
  tau_average = S2_slow*tau_slow + S2_int*tau_int
114
112
  else:
115
113
  tau_average = CO.tau[0]
116
- if CO.lw is None:
114
+ if not hasattr(CO, 'lw') or CO.lw is None:
117
115
  R2H = tau_average*7.42e9
118
116
  CO.add_ref('bermel')
119
117
  amidelinewidth = R2H/(np.pi)
@@ -122,9 +120,12 @@ def estimate_snr(CO):
122
120
  amidelinewidth_p = kz.misc.freq2ppm(amidelinewidth, CO.B_0*kz.sim.gamma['1H'])
123
121
 
124
122
  #estimate signal to noise ratio. if HSQC is provided, use it. Else, use the TRACT
125
- if CO.hsqcexpno is not None:
126
- path_hsqc = os.path.join(CO.basedir, f'{CO.hsqcexpno}')
123
+ if CO.hsqc is not None:
124
+ path_hsqc = os.path.join(CO.basedir, f'{CO.hsqc}')
127
125
  S_hsqc = kz.Spectrum_2D(path_hsqc)
126
+ version = t1t2ne_utils.fs_version(S_hsqc)
127
+ if version == 'topspin3':
128
+ S_hsqc.acqus['GRPDLY'] += 1
128
129
  S_hsqc.procs['wf'][-1]['mode'] = 'em'
129
130
  S_hsqc.procs['wf'][-1]['lb'] = 1/S_hsqc.acqus['AQ2']
130
131
  S_hsqc.procs['zf'][-1] = 2 * S_hsqc.fid.shape[-1]
@@ -151,6 +152,9 @@ def estimate_snr(CO):
151
152
  S = kz.Pseudo_2D(path)
152
153
  if not t1t2ne_utils.istract(S):
153
154
  raise NameError(f'Experiment {CO.tract} is not a TRACT experiment')
155
+ version = t1t2ne_utils.fs_version(S_hsqc)
156
+ if version == 'topspin3':
157
+ S_hsqc.acqus['GRPDLY'] += 1
154
158
  Sa, Sb = split_tract(S)
155
159
  if CO.options['phase']:
156
160
  Sb.adjph(ref=0)
@@ -1,11 +1,14 @@
1
1
  #! /usr/bin/env python3
2
2
 
3
+ from unittest import result
3
4
  import numpy as np
4
5
 
5
6
  from .base import BaseCommand
6
7
  from .textcolor import textcolor
7
8
  from . import t1t2ne_utils, fun_hetrelax_models
8
9
  import random
10
+ from scipy.optimize import differential_evolution
11
+ from scipy.special import lambertw
9
12
 
10
13
  class SetupTractCmd(BaseCommand):
11
14
  SHORT_HELP = "Setup the vdlist for a TRACT experiment"
@@ -44,6 +47,23 @@ class SetupTractCmd(BaseCommand):
44
47
  suggest_tract_vdlist(CO)
45
48
  t1t2ne_utils.the_end(CO)
46
49
  exit()
50
+
51
+
52
+
53
+
54
+ def d_optimal_criterion(t, lam1, lam2):
55
+ """Returns -log det(FIM); minimize this."""
56
+ t = np.asarray(t)
57
+ F11 = np.sum(t**2 * np.exp(-2 * lam1 * t))
58
+ F22 = np.sum(t**2 * np.exp(-2 * lam2 * t))
59
+ F12 = np.sum(t**2 * np.exp(-(lam1 + lam2) * t))
60
+ return -(F11 * F22 - F12**2) # negative det (we minimise)
61
+
62
+
63
+ def decode(u, t_max, n, min_gap):
64
+ """Map unconstrained u in [0,1]^n to sorted times with min_gap enforced."""
65
+ gaps = min_gap + u * (t_max - n * min_gap) / n # each gap >= min_gap
66
+ return np.cumsum(gaps)
47
67
 
48
68
  def suggest_tract_vdlist(CO):
49
69
  r"""
@@ -77,12 +97,48 @@ def suggest_tract_vdlist(CO):
77
97
  if CO.options['idp']:
78
98
  CO.add_ref('rezaei-ghaleh')
79
99
  CO.add_ref('fushman')
100
+ print(textcolor('\nCalculating the optimal vdlist for the TRACT experiment...', 'blue'))
80
101
  R1, R2, nOe = fun_hetrelax_models.R1R2nOe(CO.B_0, r=CO.r, nuc1=CO.nucs[0], nuc2=CO.nucs[1], Deltasigma=CO.Deltasigma, func=fun_hetrelax_models.LS_iso, f_args=(CO.S2, CO.tau))
81
102
  CO.add_ref('salvi')
82
103
  eta_z, eta_xy = fun_hetrelax_models.eta_z_eta_xy(CO.B_0, r=CO.r, nuc1=CO.nucs[0], nuc2=CO.nucs[1], Deltasigma=CO.Deltasigma, theta=CO.theta, func=fun_hetrelax_models.LS_iso, f_args=(CO.S2, CO.tau))
83
104
  Rb = R2 + eta_xy
84
105
  Ra = R2 - eta_xy
85
- vdlist_TRACT = np.geomspace(2e-5, 2/Rb, num=nT) #geometrically spaced list from 20us to 2*tau_average
106
+ print(textcolor('\nProvided parameters:', 'blue'))
107
+ print(f'Correlation time(s): {CO.tau} s')
108
+ print(f'Order parameter(s) S2: {CO.S2}')
109
+
110
+ print(textcolor('\nEstimated relaxation rates:', 'blue'))
111
+ print(f'R1: {R1:.2f} s^-1, R2: {R2:.2f} s^-1, nOe: {nOe:.2f}')
112
+ print(f'Ra: {Ra:.2f} s^-1')
113
+ print(f'Rb: {Rb:.2f} s^-1')
114
+
115
+ factor = np.real(1 + lambertw(1/np.e))
116
+
117
+ t_a = factor / Ra
118
+ t_b = factor / Rb
119
+
120
+ # Time range
121
+ t_min = 2e-5
122
+ t_max = 2 * max(t_a, t_b)
123
+
124
+ # Logarithmically spaced grid (covers both fast and slow scales)
125
+ log_times = np.logspace(np.log10(t_min), np.log10(t_max), nT)
126
+
127
+ # Force the two exact optimal points into the grid
128
+ # (this guarantees the schedule "touches" the FIM-optimal locations)
129
+ idx_a = np.argmin(np.abs(log_times - t_a))
130
+ idx_b = np.argmin(np.abs(log_times - t_b))
131
+
132
+ # Avoid overwriting the same index if t_a ≈ t_b
133
+ if idx_a == idx_b:
134
+ idx_b = (idx_b + 1) % nT
135
+
136
+ log_times[idx_a] = t_a
137
+ log_times[idx_b] = t_b
138
+
139
+ # Return sorted distinct times
140
+ vdlist_TRACT = np.sort(log_times)
141
+
86
142
  vdlist_TRACT /= 2
87
143
  if CO.options['randomize']:
88
144
  random.shuffle(vdlist_TRACT)
@@ -7,6 +7,7 @@ from .textcolor import textcolor
7
7
  from . import t1t2ne_utils, f_ParaMeters_relax
8
8
  import klassez as kz
9
9
  import random
10
+ from scipy.special import lambertw
10
11
 
11
12
  class SPREListsCmd(BaseCommand):
12
13
  SHORT_HELP = "Setup the time increment for solvent PRE experiments"
@@ -18,7 +19,7 @@ class SPREListsCmd(BaseCommand):
18
19
  def add_arguments(parser):
19
20
  parser.add_argument('--MW', type=float, help='The molecular weight of the protein in kDa, to be used for estimating the linewidth.')
20
21
  parser.add_argument('--lw', type=float, default=10, help='The expected linewidth in Hz, to be used for estimating the tau_slow in the IDP model. Default is 10 Hz.')
21
- parser.add_argument('--T1', type=float, default=1, help='The expected T1 in seconds, to be used for estimating the tau_slow in the IDP model. If not provided, it will be estimated from the molecular weight if provided, otherwise a default value of 1 s will be used.')
22
+ parser.add_argument('--T1', type=float, default=1, help='The expected intrinsic T1 in seconds.')
22
23
  parser.add_argument('--T', type=float, help='The Temperature in Kelvin')
23
24
  parser.add_argument('--nT', nargs='*', help='The number of increments in the suggested vdlist. For this module only one value must be provided. Default is 8.')
24
25
  parser.add_argument('--r', type=float, default=3.6, help='The distance of closest approach in Angstroms. Default is 3.6e-10 m, corresponding to the sum of the ionic radius of Gd^3+ and the van der Waals radius of water.')
@@ -110,7 +111,28 @@ def solventpre(CO):
110
111
  R2 = Gamma2 + amidelinewidth/np.pi
111
112
  print(textcolor(f"\nEstimated R1 at the closest approach distance: {R1:.2f} s^-1", "blue"))
112
113
  print(textcolor(f"Estimated R2 at the closest approach distance: {R2:.2f} s^-1", "blue"))
113
- vdlist_PRE = np.geomspace(2e-5, 2/R1, num=nT1) #geometrically spaced list from 20us to 2*tau_average
114
+
115
+ factor = np.real(1 + lambertw(1/np.e))
116
+
117
+ t_1 = factor / R1
118
+ t_2 = factor / R2
119
+
120
+ # Time range
121
+ t_1_min = 2e-5
122
+ t_1_max = 2 * t_1
123
+
124
+ # Logarithmically spaced grid (covers both fast and slow scales)
125
+ log_times = np.logspace(np.log10(t_1_min), np.log10(t_1_max), nT1)
126
+
127
+ # Force the two exact optimal points into the grid
128
+ # (this guarantees the schedule "touches" the FIM-optimal locations)
129
+ idx_a = np.argmin(np.abs(log_times - t_1))
130
+
131
+ log_times[idx_a] = t_1
132
+
133
+ # Return sorted distinct times
134
+ vdlist_PRE = np.sort(log_times)
135
+
114
136
  if CO.options['randomize']:
115
137
  random.shuffle(vdlist_PRE)
116
138
  print(textcolor('\nSuggested vdlist for T1 experiment:', 'blue'))
@@ -119,7 +141,12 @@ def solventpre(CO):
119
141
  if nT2 == 2:
120
142
  print(textcolor(f"Optimal Tb-Ta difference for optimizing Gamma2 measurement: {1.15/(R2):.5f} s", "blue"))
121
143
  else:
122
- vdlist_PRE_R2 = np.geomspace(2e-5, 2/(R2), num=nT2)
144
+ t_2_min = 2e-5
145
+ t_2_max = 2 * t_2
146
+ log_times = np.logspace(np.log10(t_2_min), np.log10(t_2_max), nT2)
147
+ idx_b = np.argmin(np.abs(log_times - t_2))
148
+ log_times[idx_b] = t_2
149
+ vdlist_PRE_R2 = np.sort(log_times)
123
150
  if CO.options['randomize']:
124
151
  random.shuffle(vdlist_PRE_R2)
125
152
  print(textcolor('\nSuggested vdlist for R2 experiment:', 'blue'))
@@ -13,6 +13,7 @@ from matplotlib.widgets import MultiCursor
13
13
 
14
14
  from .base import BaseCommand
15
15
  from . import f_fit, t1t2ne_utils, fun_hetrelax_models
16
+ from .tract_extra import split_tract
16
17
 
17
18
  class TractCmd(BaseCommand):
18
19
  SHORT_HELP = "Fit a TRACT experiment to extract the average tau_c of the system"
@@ -135,7 +136,7 @@ def make_plot(CO, xaxis, y_rec, yerr_rec, y_ave, yerr_ave, Ra, Rb, sigma_Ra, sig
135
136
  ax.set_yscale('log')
136
137
  else:
137
138
  ax.set_ylabel(r'$S^2$ estimate')
138
- ax.text(0.05, 0.95, f'expected $T_1$ = {1/R1:.2f} s\nexpected $T_2$ = {1/R2:.2f} s\nexpected hetnOe = {nOe:.2f}\nexpected $\eta_z$ = {eta_z:.2f}\nexpected $\eta_{{xy}}$ = {eta_xy:.2f}', transform=ax.transAxes, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
139
+ ax.text(0.05, 0.95, f'expected $T_1$ = {1/R1:.2f} s\nexpected $T_2$ = {1/R2:.2f} s\nexpected hetnOe = {nOe:.2f}\nexpected $\\eta_z$ = {eta_z:.2f}\nexpected $\\eta_{{xy}}$ = {eta_xy:.2f}', transform=ax.transAxes, verticalalignment='top', bbox=dict(boxstyle='round', facecolor='white', alpha=0.8))
139
140
 
140
141
  kz.misc.pretty_scale(axr, (0, R2+4*np.abs(eta_xy)), 'y')
141
142
  axr.set_ylabel('Relaxation rates (Hz)')
@@ -177,6 +178,11 @@ def s2(w_N, c, tau_slow, S2_slow = 0.15, tau_int = 1.6e-9):
177
178
  The slow motion is assumed to correspond to the tumbling of a protein of the same size of the one under study, and the order parameter of the slow motion is assumed to be 0.15.
178
179
  The intermediate motion is assumed to have a correlation time of a peptide of 20 residues (1.6 ns).
179
180
 
181
+ .. math::
182
+
183
+ S^2 = \frac{|\eta_{xy}/(d*c*B_0*P_2(\cos(\theta)))| - 4(S^2_{slow} J(0, \tau_{slow}) + (1-S^2_{slow}) J(0, \tau_{fast})) - 3(S^2_{slow} J(\omega_N, \tau_{slow}) + (1-S^2_{slow}) J(\omega_N, \tau_{fast}))}{4(1-S^2_{slow})(J(0, \tau_{int}) - J(0, \tau_{fast})) + 3(1-S^2_{slow})(J(\omega_N, \tau_{int}) - J(\omega_N, \tau_{fast}))}
184
+
185
+
180
186
  Parameters
181
187
  ----------
182
188
  w_N : float
@@ -298,9 +304,16 @@ def tract_fit_Ra_Rb(CO):
298
304
 
299
305
  path = os.path.join(CO.basedir, f'{CO.tract}')
300
306
  # find vdlist in path
301
- file = os.listdir(os.path.join(path, 'lists', 'vd'))[0]
307
+
302
308
  # Connect the name of the file to the path
303
- vdlistpath = os.path.join(path, 'lists', 'vd', file)
309
+ S = kz.Pseudo_2D(path)
310
+ if not t1t2ne_utils.istract(S):
311
+ raise NameError(f'Experiment {CO.tract} is not a TRACT experiment')
312
+ try:
313
+ file = os.listdir(os.path.join(path, 'lists', 'vd'))[0]
314
+ vdlistpath = os.path.join(path, 'lists', 'vd', file)
315
+ except:
316
+ vdlistpath = os.path.join(path, 'vdlist')
304
317
  # Print a notification
305
318
  print(f'Found {vdlistpath} to be imported as VDLIST')
306
319
  # Actual loading and storage in an attribute
@@ -319,18 +332,11 @@ def tract_fit_Ra_Rb(CO):
319
332
  slw = CO.options['slw']
320
333
 
321
334
  #load the dataset and check if it's a TRACT experiment
322
- S = kz.Pseudo_2D(path)
323
- if not t1t2ne_utils.istract(S):
324
- raise NameError(f'Experiment {CO.tract} is not a TRACT experiment')
325
-
335
+
336
+ CO.B0 = S.acqus['SFO1'] / kz.sim.gamma[CO.nucs[0]]
337
+ print(f'Magnetic field strength: {CO.B0:.2f} T')
326
338
  #splitcomb-like operation to separate the two interleaved datasets (TROSY and ANTITROSY)
327
- S.fid = np.reshape(S.fid.flatten(), (2*S.fid.shape[0], -1))
328
- Sa = deepcopy(S)
329
- Sb = deepcopy(S)
330
- Sb.fid = S.fid[::2]
331
- Sa.fid = S.fid[1::2]
332
- Sa.acqus['TD1'] = Sa.fid.shape[0]
333
- Sb.acqus['TD1'] = Sb.fid.shape[0]
339
+ Sa, Sb = split_tract(S)
334
340
  if smooth_data or smooth_rates:
335
341
  slw_windowlength = int(S.acqus['TD'] * slw[0] / 100)
336
342
 
@@ -344,18 +350,24 @@ def tract_fit_Ra_Rb(CO):
344
350
  # Zerofill to twice the size
345
351
  s.procs['zf'] = 2 * s.fid.shape[-1]
346
352
  # Apply and do FT
353
+ if phase:
354
+ s.procs['p0'] = 0
355
+ s.procs['p1'] = 0
347
356
  s.process()
348
357
 
349
358
  # Remove digital filter
350
359
  s.pknl()
360
+
351
361
  # Phase the spectrum
352
362
  if phase:
363
+
353
364
  if s == Sa:
354
365
  s.adjph()
355
366
  else:
356
- s.adjph(p0=Sa.procs['p0'], p1=Sa.procs['p1'], pv=Sa.procs['pv'])
367
+ s.adjph(p0=Sa.procs['p0'], p1=Sa.procs['p1'], pv=Sa.procs['pv'], update=False)
357
368
  # Qfil the solvent peak
358
369
  s.acqus['FnMODE'] = 'No'
370
+ #s.abs_v2()
359
371
  if s==Sa:
360
372
  s.qfil()
361
373
  else:
@@ -375,7 +387,7 @@ def tract_fit_Ra_Rb(CO):
375
387
  filenames = 's_b', 's_a'
376
388
 
377
389
  # Integrals
378
- for k, (filename, s) in enumerate(zip(filenames, [Sb, Sa])):
390
+ for k, (filename, s) in enumerate(zip(filenames, [Sa, Sb])):
379
391
  if k == 0: # => TROSY component
380
392
  if not readints: # compute the integrals
381
393
  s.integrate(filename=filename)
@@ -383,7 +395,7 @@ def tract_fit_Ra_Rb(CO):
383
395
  s.read_integrals(filename=f'{filename}.igrl')
384
396
  else: # => ANTITROSY component
385
397
  # Get the integration regions from the TROSY spectrum
386
- limits = kz.misc.key_to_limits(list(Sb.integrals.keys()))
398
+ limits = kz.misc.key_to_limits(list(Sa.integrals.keys()))
387
399
  if not readints: # integrate in the same regions of the TROSY
388
400
  s.integrate(filename=filename, lims=limits)
389
401
  else: # read an integrals file
@@ -405,7 +417,7 @@ def tract_fit_Ra_Rb(CO):
405
417
  else:
406
418
  xaxis = []
407
419
  if selectregion:
408
- signalregion = kz.fit.get_region(Sb.ppm_f2, Sb.rr[0])
420
+ signalregion = kz.fit.get_region(Sa.ppm_f2, Sa.rr[0])
409
421
  else:
410
422
  if CO.options['idp']:
411
423
  signalregion = [[8.6, 7.4]]
@@ -414,7 +426,7 @@ def tract_fit_Ra_Rb(CO):
414
426
  # Sb.strip(signalregion)
415
427
  # Sa.strip(signalregion)
416
428
  for sreg in signalregion:
417
- start, end = [kz.misc.ppmfind(Sb.ppm_f2, w)[0] for w in sreg]
429
+ start, end = [kz.misc.ppmfind(Sa.ppm_f2, w)[0] for w in sreg]
418
430
  for i in range(start, end):
419
431
  Rb.append(f_fit.fit_exponential(vdlist, Sb.rr[:, i], multi=1).params['k'].value)
420
432
  Ra.append(f_fit.fit_exponential(vdlist, Sa.rr[:, i], multi=1).params['k'].value)
@@ -507,8 +519,8 @@ def tract_compute_tau(B0, Ra, Rb, sigma_Ra, sigma_Rb, S2=0.9, r=1.02e-10, Deltas
507
519
  print('angle theta = {:.2f} degrees'.format(theta*180/np.pi))
508
520
  Rb_avg = np.mean(Rb)
509
521
  Ra_avg = np.mean(Ra)
510
- sigma_Rb_avg = np.std(Rb)
511
- sigma_Ra_avg = np.std(Ra)
522
+ sigma_Rb_avg = np.mean(Rb)
523
+ sigma_Ra_avg = np.mean(Ra)
512
524
  c_avg = np.average(c, weights=c**2/sigma_c**2) #(Rb_avg - Ra_avg)/(dN*p*(3*np.cos(theta)**2-1))
513
525
  sigma_eta_avg = 0.5 * np.sqrt(sigma_Rb_avg**2 + sigma_Ra_avg**2)
514
526
  sigma_c_avg = np.abs(dc_deta) * sigma_eta_avg
@@ -645,4 +657,4 @@ def tract(CO):
645
657
  make_plot(CO, xaxis, y, yerr, y_ave, yerr_ave, Ra, Rb, sigma_Ra, sigma_Rb, values, name=name, idx = idx)
646
658
 
647
659
  print(textcolor('Use this command to create the lists for your system', 'green'))
648
- print(f't1t2ne makelists --tau {CO.tau[0]*1e9:.2e} {CO.tau[1]*1e9:.2e} --S2 {CO.S2[0]:.2f} {CO.S2[1]:.2f} --idp' if CO.options['idp'] else f't1t2ne makelist --tau {CO.tau[0]*1e9:.2e} --S2 {CO.S2[0]:.2f}')
660
+ print(f't1t2ne makelists --tau {CO.tau[0]*1e9:.2e} {CO.tau[1]*1e9:.2e} --S2 {CO.S2[0]:.2f} {CO.S2[1]:.2f} --idp' if CO.options['idp'] else f't1t2ne makelists --tau {CO.tau[0]*1e9:.2e} --S2 {CO.S2[0]:.2f}')
@@ -5,11 +5,34 @@ import socket
5
5
  import configparser
6
6
  import numpy as np
7
7
  import klassez as kz
8
+ from importlib.resources import files, as_file
9
+ from pathlib import Path
8
10
 
9
11
  from . import hydrodynamics_utils, f_findfs
10
12
  from .textcolor import textcolor
11
13
 
12
14
 
15
+ def fs_version(S):
16
+ """
17
+ Determines the version of TopSpin used to acquire the spectrum S by looking at the '_coreheader' parameter in the acqus dictionary of the spectrum.
18
+
19
+ Parameters
20
+ ----------
21
+ S : kz.Pseudo_2D
22
+ The spectrum for which to determine the TopSpin version.
23
+
24
+ Returns
25
+ -------
26
+ str
27
+ The version of TopSpin used to acquire the spectrum if the '_coreheader' parameter is found, otherwise None.
28
+ """
29
+ if 'TopSpin 4' in S.ngdic['acqus']['_coreheader'][0]:
30
+ return 'topspin4'
31
+ elif 'TopSpin 3' in S.ngdic['acqus']['_coreheader'][0]:
32
+ return 'topspin3'
33
+ else:
34
+ return None
35
+
13
36
  def config_exists():
14
37
  """
15
38
  Checks if the config file exists in the curdir folder of topspin for any of the users in the nmrsuperuser list.
@@ -440,7 +463,10 @@ class Conf_Optns:
440
463
  if self.T != 298.15:
441
464
  self.tau[0] = hydrodynamics_utils.recompute_tau(self.tau[0], self.T)
442
465
  if len(self.tau) == 1:
443
- self.tau.append((self.MWi * 0.5998 + 0.1674) * 1e-9 if hasattr(self, 'MWi') else 1.6e-9) # default value for the correlation time of the intermediate motion in the IDP model is 1.6 ns
466
+ taui = (self.MWi * 0.5998 + 0.1674) * 1e-9 if hasattr(self, 'MWi') else 1.6e-9
467
+ if self.T != 298.15:
468
+ taui = hydrodynamics_utils.recompute_tau(taui, self.T)
469
+ self.tau.append(taui) # default value for the correlation time of the intermediate motion in the IDP model is 1.6 ns
444
470
  self.add_ref('rezaei-ghaleh')
445
471
  if hasattr(self, 'tau'):
446
472
  self.tau.append(1e-11) # the fast motion is always set to 10 ps.
@@ -634,7 +660,10 @@ class Conf_Optns:
634
660
  elif hasattr(parser, 'Larmor') and parser.Larmor is not None:
635
661
  self.B_0 = float(parser.Larmor[0]) / (42.57747892) # convert from MHz to Tesla using the gyromagnetic ratio of the first nucleus in the list
636
662
  else:
637
- self.get_B0()
663
+ if self.module in ['tract', 'interactive']:
664
+ pass
665
+ else:
666
+ self.get_B0()
638
667
 
639
668
  def get_B0(self):
640
669
  """
@@ -654,7 +683,7 @@ class Conf_Optns:
654
683
 
655
684
  findfs_info = f_findfs.find_topspin()
656
685
  if findfs_info["spectrometer"]:
657
- fspath = findfs_info["fspath"]
686
+ fspath = findfs_info["install_path"]
658
687
  else:
659
688
  fspath = None
660
689
  if self.module == 'interactive':
@@ -718,9 +747,11 @@ class Conf_Optns:
718
747
  self.tract = None
719
748
  self.hsqc = None
720
749
  else:
721
- self.basedir = 'tract'
722
- self.tract = '36'
723
- self.hsqc = '411'
750
+ example_resource = files('t1t2ne') / 'examples'
751
+ with as_file(example_resource) as example_path:
752
+ self.basedir = example_path
753
+ self.tract = '11'
754
+ self.hsqc = '4'
724
755
  print(f'Experiment parameters set for internal test environment: basedir={self.basedir}, tract={self.tract}, hsqc={self.hsqc}')
725
756
  else:
726
757
  if hasattr(self, 'MW') and self.MW is not None:
@@ -2,6 +2,7 @@
2
2
 
3
3
  import numpy as np
4
4
  from copy import deepcopy
5
+ from . import t1t2ne_utils
5
6
 
6
7
  def split_tract(S):
7
8
  """Split the interleaved TROSY and ANTITROSY datasets of a TRACT experiment.
@@ -16,11 +17,15 @@ def split_tract(S):
16
17
  Sa, Sb : Spectrum objects
17
18
  The two Spectrum objects containing the TROSY and ANTITROSY datasets, respectively.
18
19
  """
19
- S.fid = np.reshape(S.fid.flatten(), (2*S.fid.shape[0], -1))
20
+ version = t1t2ne_utils.fs_version(S)
21
+ if version == 'topspin3':
22
+ S.acqus['GRPDLY'] += 1
23
+ else:
24
+ S.fid = np.reshape(S.fid.flatten(), (2*S.fid.shape[0], -1))
20
25
  Sa = deepcopy(S)
21
26
  Sb = deepcopy(S)
22
- Sa.fid = S.fid[::2]
23
- Sb.fid = S.fid[1::2]
27
+ Sb.fid = S.fid[::2]
28
+ Sa.fid = S.fid[1::2]
24
29
  Sa.acqus['TD1'] = Sa.fid.shape[0]
25
30
  Sb.acqus['TD1'] = Sb.fid.shape[0]
26
31
  return Sa, Sb
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: t1t2ne
3
- Version: 0.0.2.dev0
3
+ Version: 0.0.2.dev1
4
4
  Project-URL: Homepage, https://github.com/enricoravera/t1ttune
5
5
  Project-URL: Repository, https://github.com/enricoravera/t1ttune.git
6
6
  Project-URL: Documentation, https://t1ttune.readthedocs.io
@@ -19,7 +19,7 @@ Requires-Dist: csaps
19
19
  Dynamic: license-file
20
20
 
21
21
  # **T1-T2-ne**
22
-
22
+ <img src='https://github.com/enricoravera/T1T2ne/blob/main/docs/source/_static/t1t2ne_logo.png' alt='logo' width=300 />
23
23
  This software computes the optimal delays for protein dynamics experiments.
24
24
 
25
25
  Full documentation is available at [https://t1ttune.readthedocs.io/en/latest/](https://t1ttune.readthedocs.io/en/latest/).
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ t1t2ne = t1t2ne.cli:main
@@ -1,2 +0,0 @@
1
- [console_scripts]
2
- t1ttune = t1t2ne.cli:main
File without changes
File without changes