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.
- {t1t2ne-0.0.2.dev0/t1t2ne.egg-info → t1t2ne-0.0.2.dev1}/PKG-INFO +2 -2
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/README.md +1 -1
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/pyproject.toml +3 -3
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/cli.py +2 -2
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_ParaMeters_relax.py +22 -3
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/fun_hetrelax_models.py +8 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_interactive.py +67 -27
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_ns.py +20 -16
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_setuptract.py +57 -1
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_solventpre.py +30 -3
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_tract.py +34 -22
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_utils.py +37 -6
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/tract_extra.py +8 -3
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1/t1t2ne.egg-info}/PKG-INFO +2 -2
- t1t2ne-0.0.2.dev1/t1t2ne.egg-info/entry_points.txt +2 -0
- t1t2ne-0.0.2.dev0/t1t2ne.egg-info/entry_points.txt +0 -2
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/LICENSE.txt +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/setup.cfg +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/__init__.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/__init__.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/base.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/create_registry.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_findfs.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/f_fit.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/hydrodynamics_utils.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_configure.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/t1t2ne_makelists.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne/scripts/textcolor.py +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/SOURCES.txt +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/dependency_links.txt +0 -0
- {t1t2ne-0.0.2.dev0 → t1t2ne-0.0.2.dev1}/t1t2ne.egg-info/requires.txt +0 -0
- {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.
|
|
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.
|
|
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
|
-
|
|
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="
|
|
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 '
|
|
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
|
|
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(
|
|
79
|
-
T2_30 = -np.log(
|
|
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
|
-
|
|
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.
|
|
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 {
|
|
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',
|
|
155
|
-
tb = S_t1.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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 =
|
|
187
|
-
d21 =
|
|
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
|
|
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.
|
|
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
|
-
|
|
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',
|
|
242
|
-
tb = S_t2.
|
|
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
|
|
248
|
-
l1_30_est = int((-np.log(
|
|
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
|
|
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(
|
|
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 {
|
|
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 {
|
|
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-
|
|
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',
|
|
25
|
-
parser.add_argument('--tract',
|
|
26
|
-
parser.add_argument('--hsqc',
|
|
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
|
-
|
|
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
|
-
*
|
|
65
|
+
* hsqc : str, optional
|
|
68
66
|
The experiment number of the HSQC reference spectrum to use for the SNR estimation.
|
|
69
|
-
*
|
|
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
|
|
82
|
+
if hasattr(CO, 'MW') and CO.MW is not None:
|
|
85
83
|
MW = CO.MW
|
|
86
84
|
else:
|
|
87
|
-
if
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
126
|
-
path_hsqc = os.path.join(CO.basedir, f'{CO.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
307
|
+
|
|
302
308
|
# Connect the name of the file to the path
|
|
303
|
-
|
|
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
|
-
|
|
323
|
-
|
|
324
|
-
|
|
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
|
-
|
|
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, [
|
|
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(
|
|
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(
|
|
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(
|
|
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.
|
|
511
|
-
sigma_Ra_avg = np.
|
|
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
|
|
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
|
-
|
|
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.
|
|
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["
|
|
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
|
-
|
|
722
|
-
|
|
723
|
-
|
|
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
|
-
|
|
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
|
-
|
|
23
|
-
|
|
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.
|
|
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/).
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|