-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprocessNMR.py
More file actions
365 lines (315 loc) · 16.2 KB
/
Copy pathprocessNMR.py
File metadata and controls
365 lines (315 loc) · 16.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
import templates
import textwrap
import os
import pickle
from MELD_NEF_Classes import *
import argparse
import copy
def parse_args(): #in line argument parser with help
'''
Parse arguments for the program from command line
'''
parser = argparse.ArgumentParser()
parser.add_argument('-nef', type=str, help='NEF input file or object')
parser.add_argument('-directory', type=str, help='Where to place the files',default='.')
parser.add_argument('-name', type=str, help='Name for job to submit to queues',default='NMR')
return(parser.parse_args())
map_to_heavy = {
('ARG','HG%'):(['CG'],1),
('ARG','HGx'):(['CG'],1),
('ARG','HGy'):(['CG'],1),
('ARG','HD%'):(['CD'],1),
('ARG','HDx'):(['CD'],1),
('ARG','HDy'):(['CD'],1),
('ARG','HH1%'):(['NH1'],1),
('ARG','HH2%'):(['NH2'],1),
('ARG','HH%'):(['NH1','NH2'],1),
('ARG','NZ%'):(['NH1','NH2'],0),
('ASN','HD2%'):(['ND2'],1),
('ASN','HD2x'):(['ND2'],1),
('ASN','HD2y'):(['ND2'],1),
('ASP','OD%'):(['OD1','OD2'],0),
('GLN','HG%'):(['CG'],1),
('GLN','HE%'):(['NE2'],1),
('GLN','HE2%'):(['NE2'],1),
('GLN','HE2x'):(['NE2'],1),
('GLN','HE2y'):(['NE2'],1),
('GLN','HGx'):(['CG'],1),
('GLN','HGy'):(['CG'],1,),
('GLU','HG%'):(['CG'],1),
('GLU','HGx'):(['CG'],1),
('GLU','HGy'):(['CG'],1),
('GLU','OE%'):(['OE1','OE2'],0),
('GLY','HA%'):(['CA'],1),
('GLY','HAx'):(['CA'],1),
('GLY','HAy'):(['CA'],1),
('ILE','HG1%'):(['CG1'],1),
('ILE','HG2%'):(['CG2'],1),
('ILE','HD1%'):(['CD1'],1),
('ILE','HG1x'):(['CG1'],1),
('ILE','HG1y'):(['CG1'],1),
('LEU','HD1%'):(['CD1'],1),
('LEU','HD2%'):(['CD2'],1),
('LEU','HD%'):(['CD1','CD2'],1),
('LEU','HDx%'):(['CD1','CD2'],1),
('LEU','HDy%'):(['CD1','CD2'],1),
('LYS','HG%'):(['CG'],1),
('LYS','HD%'):(['CD'],1),
('LYS','HE%'):(['CE'],1),
('LYS','HZ%'):(['NZ'],1),
('LYS','HDx'):(['CD'],1),
('LYS','HDy'):(['CD'],1),
('LYS','HEx'):(['CE'],1),
('LYS','HEy'):(['CE'],1),
('LYS','HGx'):(['CG'],1),
('LYS','HGy'):(['CG'],1),
('MET','HG%'):(['CG'],1),
('MET','HE'):(['CE'],1),
('MET','HE%'):(['CE'],1),
('MET','HGx'):(['CG'],1),
('MET','HGy'):(['CG'],1),
('PHE','HD%'):(['CD1','CD2'],1),
('PHE','HE%'):(['CE1','CE2'],1),
('PHE','CD%'):(['CD1','CD2'],0),
('PHE','CE%'):(['CE1','CE2'],0),
('PRO','HG%'):(['CG'],1),
('PRO','HD%'):(['CD'],1),
('PRO','HDx'):(['CD'],1),
('PRO','HDy'):(['CD'],1),
('PRO','HGx'):(['CG'],1),
('PRO','HGy'):(['CG'],1),
('THR','HG2%'):(['CG2'],1),
('TYR','HD%'):(['CD'],1),
('TYR','HE%'):(['CE'],1),
('TYR','CD%'):(['CD1','CD2'],0),
('TYR','CD'):(['CD1','CD2'],0),
('TYR','CE'):(['CE1','CE2'],0),
('TYR','CE%'):(['CE1','CE2'],0),
('VAL','HG1%'):(['CG1'],1),
('VAL','HG2%'):(['CG2'],1),
('VAL','HGx%'):(['CG1','CG2'],1),
('VAL','HGy%'):(['CG1','CG2'],1),
('VAL','HG%'):(['CG1','CG2'],1),
}
aminoacids = ["ALA","ARG","ASN","ASP","CYS","GLN","GLU","GLY","HIS","ILE","LEU","LYS","MET","PHE","PRO","SER","THR","TRP","TYR","VAL"]
for aa in aminoacids:
map_to_heavy[(aa,'HB%')] = (['CB'],1)
map_to_heavy[(aa,'HBx')] = (['CB'],1)
map_to_heavy[(aa,'HBy')] = (['CB'],1)
def write_TALOS_deprecated(dihedrals):
'''Dihedrals will be phi or psi. Separate them. Characterize by their resid. Output maximum/minimum values for the dihedral.
It is possible that a dihedral could be seen in two conformations--> two NMR peaks, satisfy one according to chemical shfits.
I'm going to assume that a restraint_id will never correspond to phi and psi at the same time. This is something to confirm with
NMR experts
'''
phi = ''
psi = ''
for one_peak in dihedrals.restraint_id.unique():
selection = dihedrals.loc[ (dihedrals['restraint_id'] == one_peak) & (dihedrals['name'] == 'PHI'), ['sequence_code_2','lower_limit','upper_limit'] ]
if len(selection) > 0:
phi += selection.to_string(index=False,header=False)
phi += "\n\n"
selection = dihedrals.loc[ (dihedrals['restraint_id'] == one_peak) & (dihedrals['name'] == 'PSI'), ['sequence_code_2','lower_limit','upper_limit'] ]
if len(selection) > 0:
psi += selection.to_string(index=False,header=False)
psi += "\n\n"
return (phi,psi)
def write_TALOS(dihedrals):
'''Dihedrals will be phi, psi or side chain rotamers. Use general approach keeping each residue number and atom involved.
It is possible that a dihedral could be seen in two conformations--> two NMR peaks, satisfy one according to chemical shfits.
'''
rotamers = ''
for one_peak in dihedrals.restraint_id.unique():
selection = dihedrals.loc[ dihedrals['restraint_id'] == one_peak, ['sequence_code_1','atom_name_1','sequence_code_2','atom_name_2','sequence_code_3','atom_name_3','sequence_code_4','atom_name_4','lower_limit','upper_limit'] ]
if len(selection) > 0:
rotamers += selection.to_string(index=False,header=False)
rotamers += "\n\n"
return (rotamers)
def write_peaks(peaks,min_CO=4):
'''We get peaks list and write it out. If we did process_peaks before then the peaks
will be mapped to heavy atoms. if we used process_sequence, atom ordering will be amber-like
We cannot separate in chains, some restraints could be ambiguos and satisfied in either monomer
or dimer
Peaks that are trivial to satisfy (e.g. same residue) should remove the same ambiguous peak that defines it.
If any possible contact in a peak is trivial, remove the whole peak.
'''
output_noe = ''
local_noe = ''
for one_peak in peaks.restraint_id.unique():
selection = peaks.loc[ peaks['restraint_id'] == one_peak, ['sequence_code_1','atom_name_1','sequence_code_2','atom_name_2','upper_limit'] ]
trivial = selection.loc[ abs(selection['sequence_code_1'] - selection['sequence_code_2']) < min_CO]
if len(trivial) < 1:
output_noe += selection.to_string(index=False,header=False)
output_noe += "\n\n"
else:
local_noe += selection.to_string(index=False,header=False)
local_noe += "\n\n"
return (output_noe,local_noe)
def process_sequence(NEF,peaks,TALOS=False):
'''USe mapping of residues from molecular system block to pass into amber-like topology numbering.
First residue starts at 1, new chains keep numbering intead of starting over
Here we run into an issue: pandas has dataframe.index as their internal numbering...starts at 0
NEF sets the name of the column index which would start at 1 as we would like
TALOS data has the same keywords but 4 instances to change instead of two. IF the instances exist
in the data frame --> change them. Otherwise do not
'''
data = NEF.block_content['molecular_system'].loop_type_data['_nef_sequence']
numbering = {}
for seq,chain,index in zip(data.sequence_code,data.chain_code,data.index):
numbering[(seq,chain)] = index + 1
for (seq,chain) in numbering.keys():
peaks.loc[ (peaks['chain_code_1'] == chain) & (peaks['sequence_code_1'] == seq),['sequence_code_1'] ] = numbering[(seq,chain)]
peaks.loc[ (peaks['chain_code_2'] == chain) & (peaks['sequence_code_2'] == seq),['sequence_code_2'] ] = numbering[(seq,chain)]
if TALOS:
peaks.loc[ (peaks['chain_code_3'] == chain) & (peaks['sequence_code_3'] == seq),['sequence_code_3'] ] = numbering[(seq,chain)]
peaks.loc[ (peaks['chain_code_3'] == chain) & (peaks['sequence_code_4'] == seq),['sequence_code_4'] ] = numbering[(seq,chain)]
return(peaks)
def process_peaks(peaks):
''' Translate the peaks into MELD restraints. Put ambiguous H distances on the heavy atom
add one to the upper distance treshold
We will use a dictionary with all possible cases and replace by the heavy atom counterparts
in cases whereabmiguity leads to more than one heavy atom, we will duplicate the row in the
pandas dataframe'''
#Correct first atom
for (amino, atom) in map_to_heavy.keys():
(heavy,distance_correction) = map_to_heavy[(amino,atom)]
#If have more than one heavy atom need to duplicate (or more) data
aa = peaks.loc[(peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom)]
if len(aa) < 1:
continue
if len(heavy)== 1:
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['atom_name_1'] ] = heavy[0]
else:
'''First pass changes the first ambiguos atom to heavy. Then adds the original ones to teh data frame and substitutes by the second atom'''
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['atom_name_1'] ] = heavy[0]
peaks.append(aa)
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_1'] == amino) & (peaks['atom_name_1'] == atom),['atom_name_1'] ] = heavy[1]
#Correct second atom
#TO DO: This is duplicated code, should create routine to handle
for (amino, atom) in map_to_heavy.keys():
(heavy,distance_correction) = map_to_heavy[(amino,atom)]
#If have more than one heavy atom need to duplicate (or more) data
aa = peaks.loc[(peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom)]
if len(aa) < 1:
continue
if len(heavy)== 1:
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['atom_name_2'] ] = heavy[0]
else:
'''First pass changes the first ambiguos atom to heavy. Then adds the original ones to teh data frame and substitutes by the second atom'''
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['atom_name_2'] ] = heavy[0]
peaks.append(aa)
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['upper_limit'] ] = peaks.upper_limit + distance_correction
peaks.loc[ (peaks['residue_name_2'] == amino) & (peaks['atom_name_2'] == atom),['atom_name_2'] ] = heavy[1]
return(peaks)
def write_out_meld_version_restraints(restraint_block): # NOE block as the input
restraint_list = list(restraint_block._raw) # has headers and list of restraints
for i,line in enumerate(restraint_list):
if 'sf_framecode' in line:
restraint_list[i] = '{}_meld\n'.format(restraint_list[i].strip())
return (NEF_block(restraint_list))
'''Examples of how to use this module
NEF = pickle.load(open('/ufrc/alberto.perezant/alberto.perezant/NEF/Forked_NEF/NEF/data_1_1/PDBStat_developers/Perez/trial.nef.pkl','rb'))
NEF.peaks = {}
for i in NEF.chains:
for j in NEF.chains:
NEF.peaks[(i,j)] = []
NEF.active = ['molecular_system']
for i,NOE in enumerate(NEF.block_types['distance_restraint_list']):
print(dir(NOE))
print(NOE.type,NOE.name)
distances = NOE.loop_type_data['_nef_distance_restraint']
print(NEF.sequence_names)
distances = process_peaks(distances)
distances = process_sequence(NEF,distances)
peaks_to_write = write_peaks(distances)
#Add the dataframe back into the NEF object, this will be a MELD modified one
#ToDO: make a block routine that adds a new block with MELD output NEF
NOE.loop_type_data['_nef_distance_restraint'] = distances
with open('{}/NOE_{}.dat'.format('.',i),'w') as fo:
fo.write(peaks_to_write)
NEF.active.append('_'.join([NOE.type,NOE.name]))
#dihedrals just need to be renumbered and then written to NEF/MELD output
for i,TALOS in enumerate(NEF.block_types['dihedral_restraint_list']):
print(dir(TALOS))
print(TALOS.type,TALOS.name)
dihedrals = TALOS.loop_type_data['_nef_dihedral_restraint']
dihedrals = process_sequence(NEF,dihedrals,TALOS=True)
rotamers2write = write_TALOS(dihedrals)
with open('{}/rotamers_{}.dat'.format('.',i),'w') as fo:
fo.write(rotamers2write)
TALOS.loop_type_data['_nef_distance_restraint'] = dihedrals
NEF.active.append('_'.join([TALOS.type,TALOS.name]))
NEF.write()
with open('MELD_NMR_setup.py','w') as fo:
fo.write(templates.meld_NMR_script)
with open('MELD_job.sh','w') as fo:
fo.write(templates.meld_gpu_job.format(args.name))
'''
def main():
args = parse_args()
#Work in a temporary directory
if args.directory == '.':
args.directory = os.getcwd()
try:
NEF = pickle.load(open(args.nef,'rb'))
except:
NEF = NEF_system(args.nef,args.directory)
NEF.sequence()
#NEF.active = ['molecular_system']
n_blocks = len(NEF.block_types['distance_restraint_list'])
for i,NOE in enumerate(NEF.block_types['distance_restraint_list']):
if i >= n_blocks:
#Otherwise non-ending loop where NEF.block_types keeps growing
continue
#We want to keep original data and crteate new MELD data. We will duplicate objects
#myNOE = copy.deepcopy(NOE)
myNOE = write_out_meld_version_restraints(copy.deepcopy(NOE))
distances = myNOE.loop_type_data['_nef_distance_restraint']
distances = process_peaks(distances)
distances = process_sequence(NEF,distances)
peaks_to_write,local_peaks = write_peaks(distances,min_CO=8)
#Add the dataframe back into the NEF object, this will be a MELD modified one
#ToDO: make a block routine that adds a new block with MELD output NEF
myNOE.loop_type_data['_nef_distance_restraint'] = distances
with open('{}/local_NOE_{}.dat'.format(args.directory,i),'w') as fo:
fo.write(local_peaks)
with open('{}/NOE_{}.dat'.format(args.directory,i),'w') as fo:
fo.write(peaks_to_write)
myNOE.name = '{}_meld'.format(myNOE.name)
myNOE.header = '_'.join(['save',myNOE.type,myNOE.name])
NEF.add_block(myNOE)
#dihedrals just need to be renumbered and then written to NEF/MELD output
try:
NEF.block_types['dihedral_restraint_list']
ok = 1
except:
ok = 0
if ok:
n_blocks = len(NEF.block_types['dihedral_restraint_list'])
for i,TALOS in enumerate(NEF.block_types['dihedral_restraint_list']):
if i >= n_blocks:
continue
#myTALOS = copy.deepcopy(TALOS)
myTALOS = write_out_meld_version_restraints(copy.deepcopy(TALOS))
dihedrals = myTALOS.loop_type_data['_nef_dihedral_restraint']
dihedrals = process_sequence(NEF,dihedrals,TALOS=True)
rotamers2write = write_TALOS(dihedrals)
with open('{}/rotamers_{}.dat'.format(args.directory,i),'w') as fo:
fo.write(rotamers2write)
myTALOS.loop_type_data['_nef_distance_restraint'] = dihedrals
myTALOS.name = '{}_meld'.format(myTALOS.name)
myTALOS.header = '_'.join(['save',myTALOS.type,myTALOS.name])
#NEF.active.append('_'.join([myTALOS.type,myTALOS.name]))
NEF.add_block(myTALOS)
NEF.write()
with open('{}/MELD_NMR_setup.py'.format(args.directory),'w') as fo:
fo.write(templates.meld_NMR_script)
with open('{}/MELD_job.sh'.format(args.directory),'w') as fo:
fo.write(templates.meld_gpu_job.format(args.name))
if __name__ == '__main__': #Python way to execute main()
main()