1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 """Module for the manipulation of pseudocontact shift data."""
24
25
26 from copy import deepcopy
27 from math import pi, sqrt
28 from numpy import array, float64, ones, zeros
29 from numpy.linalg import norm
30 import sys
31 from warnings import warn
32
33
34 from generic_fns import grace, pipes
35 from generic_fns.align_tensor import get_tensor_index
36 from generic_fns.mol_res_spin import exists_mol_res_spin_data, generate_spin_id, return_spin, spin_loop
37 from maths_fns.pcs import ave_pcs_tensor
38 from physical_constants import g1H, pcs_constant
39 from relax_errors import RelaxError, RelaxNoPdbError, RelaxNoSequenceError
40 from relax_io import open_write_file, read_spin_data, write_spin_data
41 from relax_warnings import RelaxWarning, RelaxNoSpinWarning
42
43
45 """Back calculate the PCS from the alignment tensor.
46
47 @keyword align_id: The alignment tensor ID string.
48 @type align_id: str
49 """
50
51
52 if align_id and align_id not in cdp.align_ids:
53 raise RelaxError("The alignment ID '%s' is not in the alignment ID list %s." % (align_id, cdp.align_ids))
54
55
56 if align_id:
57 align_ids = [align_id]
58 else:
59 align_ids = cdp.align_ids
60
61
62 for align_id in align_ids:
63
64 if not hasattr(cdp, 'pcs_ids'):
65 cdp.pcs_ids = []
66
67
68 if align_id not in cdp.pcs_ids:
69 cdp.pcs_ids.append(align_id)
70
71
72 weights = ones(cdp.N, float64) / cdp.N
73
74
75 unit_vect = zeros((cdp.N, 3), float64)
76
77
78 for spin in spin_loop():
79
80 if not hasattr(spin, 'pos'):
81 continue
82
83
84 pos = spin.pos
85 if type(pos[0]) in [float, float64]:
86 pos = [pos] * cdp.N
87
88
89 for id in align_ids:
90
91 vect = zeros((cdp.N, 3), float64)
92 r = zeros(cdp.N, float64)
93 dj = zeros(cdp.N, float64)
94 for c in range(cdp.N):
95
96 vect[c] = pos[c] - cdp.paramagnetic_centre
97
98
99 r[c] = norm(vect[c])
100
101
102 if r[c]:
103 vect[c] = vect[c] / r[c]
104
105
106 dj[c] = pcs_constant(cdp.temperature[id], cdp.frq[id] * 2.0 * pi / g1H, r[c]/1e10)
107
108
109 if not hasattr(spin, 'pcs_bc'):
110 spin.pcs_bc = {}
111
112
113 spin.pcs_bc[id] = ave_pcs_tensor(dj, vect, cdp.N, cdp.align_tensors[get_tensor_index(id)].A, weights=weights) * 1e6
114
115
116 -def centre(pos=None, atom_id=None, pipe=None, verbosity=1, ave_pos=False, force=False):
117 """Specify the atom in the loaded structure corresponding to the paramagnetic centre.
118
119 @keyword pos: The atomic position. If set, the atom_id string will be ignored.
120 @type pos: list of float
121 @keyword atom_id: The atom identification string.
122 @type atom_id: str
123 @keyword pipe: An alternative data pipe to extract the paramagnetic centre from.
124 @type pipe: None or str
125 @keyword verbosity: The amount of information to print out. The bigger the number, the more information.
126 @type verbosity: int
127 @keyword ave_pos: A flag which if True causes the atomic positions from multiple models to be averaged.
128 @type ave_pos: bool
129 @keyword force: A flag which if True will cause the current PCS centre to be overwritten.
130 """
131
132
133 if pipe == None:
134 pipe = pipes.cdp_name()
135
136
137 pipes.test(pipe)
138
139
140 source_dp = pipes.get_pipe(pipe)
141
142
143 if not hasattr(source_dp, 'structure'):
144 raise RelaxNoPdbError
145
146
147 if not force and hasattr(cdp, 'paramagnetic_centre'):
148 raise RelaxError("The paramagnetic centre has already been set to the coordinates " + repr(cdp.paramagnetic_centre) + ".")
149
150
151 if pos != None:
152 centre = array(pos)
153 num_pos = 1
154 full_pos_list = []
155
156
157 else:
158
159 centre = zeros(3, float64)
160 full_pos_list = []
161 num_pos = 0
162 for spin, spin_id in spin_loop(atom_id, pipe=pipe, return_id=True):
163
164 if not hasattr(spin, 'pos'):
165 continue
166
167
168 if isinstance(spin.pos[0], float) or isinstance(spin.pos[0], float64):
169 pos_list = [spin.pos]
170 else:
171 pos_list = spin.pos
172
173
174 for pos in pos_list:
175 full_pos_list.append(pos)
176 centre = centre + array(pos)
177 num_pos = num_pos + 1
178
179
180 if not num_pos:
181 raise RelaxError("No positional information could be found for the spin '%s'." % atom_id)
182
183
184 centre = centre / float(num_pos)
185
186
187 if verbosity:
188 print("Paramagnetic centres located at:")
189 for pos in full_pos_list:
190 print(" [%8.3f, %8.3f, %8.3f]" % (pos[0], pos[1], pos[2]))
191 print("\nAverage paramagnetic centre located at:")
192 print(" [%8.3f, %8.3f, %8.3f]" % (centre[0], centre[1], centre[2]))
193
194
195 if ave_pos:
196 if verbosity:
197 print("\nUsing the average paramagnetic position.")
198 cdp.paramagnetic_centre = centre
199 else:
200 if verbosity:
201 print("\nUsing all paramagnetic positions.")
202 cdp.paramagnetic_centre = full_pos_list
203
204
205 -def corr_plot(format=None, file=None, dir=None, force=False):
206 """Generate a correlation plot of the measured vs. back-calculated PCSs.
207
208 @keyword format: The format for the plot file. The following values are accepted: 'grace', a Grace plot; None, a plain text file.
209 @type format: str or None
210 @keyword file: The file name or object to write to.
211 @type file: str or file object
212 @keyword dir: The name of the directory to place the file into (defaults to the current directory).
213 @type dir: str
214 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
215 @type force: bool
216 """
217
218
219 pipes.test()
220
221
222 if not exists_mol_res_spin_data():
223 raise RelaxNoSequenceError
224
225
226 if not hasattr(cdp, 'pcs_ids') or not cdp.pcs_ids:
227 warn(RelaxWarning("No PCS data exists, skipping file creation."))
228 return
229
230
231 file = open_write_file(file, dir, force)
232
233
234 data = []
235
236
237 data.append([[-100, -100, 0], [100, 100, 0]])
238
239
240 types = []
241 for spin in spin_loop():
242 if spin.element not in types:
243 types.append(spin.element)
244
245
246 for align_id in cdp.pcs_ids:
247
248 for i in range(len(types)):
249
250 data.append([])
251
252
253 err_flag = False
254 for spin in spin_loop():
255
256 if not spin.select:
257 continue
258
259
260 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
261 err_flag = True
262 break
263
264
265 for spin, spin_id in spin_loop(return_id=True):
266
267 if not spin.select:
268 continue
269
270
271 if spin.element != types[i]:
272 continue
273
274
275 if not hasattr(spin, 'pcs') or not hasattr(spin, 'pcs_bc') or not align_id in spin.pcs.keys() or not align_id in spin.pcs_bc.keys():
276 continue
277
278
279 data[-1].append([spin.pcs_bc[align_id], spin.pcs[align_id]])
280
281
282 if err_flag:
283 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
284 data[-1][-1].append(spin.pcs_err[align_id])
285 else:
286 data[-1][-1].append(None)
287
288
289 data[-1][-1].append(spin_id)
290
291
292 size = len(data)
293
294
295 data = [data]
296
297
298 if err_flag:
299 graph_type = 'xydy'
300 else:
301 graph_type = 'xy'
302
303
304 if format == 'grace':
305
306 set_names = [None]
307 for i in range(len(cdp.pcs_ids)):
308 for j in range(len(types)):
309 set_names.append("%s (%s)" % (cdp.pcs_ids[i], types[j]))
310
311
312 grace.write_xy_header(file=file, title="PCS correlation plot", sets=size, set_names=set_names, linestyle=[2]+[0]*size, data_type=['pcs_bc', 'pcs'], axis_min=[-0.5, -0.5], axis_max=[0.5, 0.5], legend_pos=[1, 0.5])
313
314
315 grace.write_xy_data(data=data, file=file, graph_type=graph_type)
316
317
319 """Delete the PCS data corresponding to the alignment ID.
320
321 @keyword align_id: The alignment tensor ID string. If not specified, all data will be deleted.
322 @type align_id: str or None
323 """
324
325
326 pipes.test()
327
328
329 if not exists_mol_res_spin_data():
330 raise RelaxNoSequenceError
331
332
333 if align_id and not align_id in cdp.pcs_ids:
334 raise RelaxError("The PCS alignment id '%s' does not exist" % align_id)
335
336
337 if not align_id:
338 ids = deepcopy(cdp.pcs_ids)
339 else:
340 ids = [align_id]
341
342
343 for id in ids:
344
345 cdp.pcs_ids.pop(cdp.pcs_ids.index(id))
346
347
348 if hasattr(cdp, 'pcs_data_types') and id in cdp.pcs_data_types:
349 cdp.pcs_data_types.pop(id)
350
351
352 for spin in spin_loop():
353
354 if hasattr(spin, 'pcs') and id in spin.pcs:
355 spin.pcs.pop(id)
356
357
358 if hasattr(spin, 'pcs_err') and id in spin.pcs_err:
359 spin.pcs_err.pop(id)
360
361
362 if not hasattr(cdp, 'rdc_ids') or id not in cdp.rdc_ids:
363 cdp.align_ids.pop(id)
364
365
366 -def display(align_id=None, bc=False):
367 """Display the PCS data corresponding to the alignment ID.
368
369 @keyword align_id: The alignment tensor ID string.
370 @type align_id: str
371 @keyword bc: The back-calculation flag which if True will cause the back-calculated rather than measured data to be displayed.
372 @type bc: bool
373 """
374
375
376 write(align_id=align_id, file=sys.stdout, bc=bc)
377
378
380 """Calculate the Q-factors for the PCS data.
381
382 @keyword spin_id: The spin ID string used to restrict the Q-factor calculation to a subset of all spins.
383 @type spin_id: None or str
384 """
385
386
387 if not hasattr(cdp, 'pcs_ids') or not len(cdp.pcs_ids):
388 warn(RelaxWarning("No PCS data exists, Q factors cannot be calculated."))
389 return
390
391
392 cdp.q_factors_pcs = {}
393
394
395 for align_id in cdp.pcs_ids:
396
397 pcs2_sum = 0.0
398 sse = 0.0
399
400
401 spin_count = 0
402 pcs_data = False
403 pcs_bc_data = False
404 for spin in spin_loop(spin_id):
405
406 if not spin.select:
407 continue
408
409
410 spin_count += 1
411
412
413 if hasattr(spin, 'pcs') and align_id in spin.pcs:
414 pcs_data = True
415 if hasattr(spin, 'pcs_bc') and align_id in spin.pcs_bc:
416 pcs_bc_data = True
417
418
419 if not hasattr(spin, 'pcs') or not hasattr(spin, 'pcs_bc') or not align_id in spin.pcs or spin.pcs[align_id] == None or not align_id in spin.pcs_bc or spin.pcs_bc[align_id] == None:
420 continue
421
422
423 sse = sse + (spin.pcs[align_id] - spin.pcs_bc[align_id])**2
424
425
426 pcs2_sum = pcs2_sum + spin.pcs[align_id]**2
427
428
429 if pcs2_sum:
430 Q = sqrt(sse / pcs2_sum)
431 cdp.q_factors_pcs[align_id] = Q
432
433
434 if not spin_count:
435 warn(RelaxWarning("No spins have been used in the calculation."))
436 return
437 if not pcs_data:
438 warn(RelaxWarning("No PCS data can be found."))
439 return
440 if not pcs_bc_data:
441 warn(RelaxWarning("No back-calculated PCS data can be found."))
442 return
443
444
445 cdp.q_pcs = 0.0
446 for id in cdp.q_factors_pcs:
447 cdp.q_pcs = cdp.q_pcs + cdp.q_factors_pcs[id]**2
448 cdp.q_pcs = cdp.q_pcs / len(cdp.q_factors_pcs)
449 cdp.q_pcs = sqrt(cdp.q_pcs)
450
451
452 -def read(align_id=None, file=None, dir=None, file_data=None, spin_id_col=None, mol_name_col=None, res_num_col=None, res_name_col=None, spin_num_col=None, spin_name_col=None, data_col=None, error_col=None, sep=None, spin_id=None):
453 """Read the PCS data from file.
454
455 @param align_id: The alignment tensor ID string.
456 @type align_id: str
457 @param file: The name of the file to open.
458 @type file: str
459 @param dir: The directory containing the file (defaults to the current directory if None).
460 @type dir: str or None
461 @param file_data: An alternative opening a file, if the data already exists in the correct format. The format is a list of lists where the first index corresponds to the row and the second the column.
462 @type file_data: list of lists
463 @keyword spin_id_col: The column containing the spin ID strings. If supplied, the mol_name_col, res_name_col, res_num_col, spin_name_col, and spin_num_col arguments must be none.
464 @type spin_id_col: int or None
465 @keyword mol_name_col: The column containing the molecule name information. If supplied, spin_id_col must be None.
466 @type mol_name_col: int or None
467 @keyword res_name_col: The column containing the residue name information. If supplied, spin_id_col must be None.
468 @type res_name_col: int or None
469 @keyword res_num_col: The column containing the residue number information. If supplied, spin_id_col must be None.
470 @type res_num_col: int or None
471 @keyword spin_name_col: The column containing the spin name information. If supplied, spin_id_col must be None.
472 @type spin_name_col: int or None
473 @keyword spin_num_col: The column containing the spin number information. If supplied, spin_id_col must be None.
474 @type spin_num_col: int or None
475 @keyword data_col: The column containing the PCS data in Hz.
476 @type data_col: int or None
477 @keyword error_col: The column containing the PCS errors.
478 @type error_col: int or None
479 @keyword sep: The column separator which, if None, defaults to whitespace.
480 @type sep: str or None
481 @keyword spin_id: The spin ID string used to restrict data loading to a subset of all spins.
482 @type spin_id: None or str
483 """
484
485
486 pipes.test()
487
488
489 if not exists_mol_res_spin_data():
490 raise RelaxNoSequenceError
491
492
493 if data_col == None and error_col == None:
494 raise RelaxError("One of either the data or error column must be supplied.")
495
496
497
498
499
500
501 mol_names = []
502 res_nums = []
503 res_names = []
504 spin_nums = []
505 spin_names = []
506 values = []
507 errors = []
508 for data in read_spin_data(file=file, dir=dir, file_data=file_data, spin_id_col=spin_id_col, mol_name_col=mol_name_col, res_num_col=res_num_col, res_name_col=res_name_col, spin_num_col=spin_num_col, spin_name_col=spin_name_col, data_col=data_col, error_col=error_col, sep=sep, spin_id=spin_id):
509
510 if data_col and error_col:
511 mol_name, res_num, res_name, spin_num, spin_name, value, error = data
512 elif data_col:
513 mol_name, res_num, res_name, spin_num, spin_name, value = data
514 error = None
515 else:
516 mol_name, res_num, res_name, spin_num, spin_name, error = data
517 value = None
518
519
520 if error == 0.0:
521 raise RelaxError("An invalid error value of zero has been encountered.")
522
523
524 id = generate_spin_id(mol_name=mol_name, res_num=res_num, res_name=res_name, spin_num=spin_num, spin_name=spin_name)
525 spin = return_spin(id)
526 if spin == None:
527 warn(RelaxNoSpinWarning(id))
528 continue
529
530
531 if data_col:
532
533 if not hasattr(spin, 'pcs'):
534 spin.pcs = {}
535
536
537 spin.pcs[align_id] = value
538
539
540 if error_col:
541
542 if not hasattr(spin, 'pcs_err'):
543 spin.pcs_err = {}
544
545
546 spin.pcs_err[align_id] = error
547
548
549 mol_names.append(mol_name)
550 res_nums.append(res_num)
551 res_names.append(res_name)
552 spin_nums.append(spin_num)
553 spin_names.append(spin_name)
554 values.append(value)
555 errors.append(error)
556
557
558 write_spin_data(file=sys.stdout, mol_names=mol_names, res_nums=res_nums, res_names=res_names, spin_nums=spin_nums, spin_names=spin_names, data=values, data_name='PCSs', error=errors, error_name='PCS_error')
559
560
561
562
563
564
565 if not len(values):
566 return
567
568
569 if not hasattr(cdp, 'align_ids'):
570 cdp.align_ids = []
571 if not hasattr(cdp, 'pcs_ids'):
572 cdp.pcs_ids = []
573
574
575 if align_id not in cdp.align_ids:
576 cdp.align_ids.append(align_id)
577 if align_id not in cdp.pcs_ids:
578 cdp.pcs_ids.append(align_id)
579
580
581 -def weight(align_id=None, spin_id=None, weight=1.0):
582 """Set optimisation weights on the PCS data.
583
584 @keyword align_id: The alignment tensor ID string.
585 @type align_id: str
586 @keyword spin_id: The spin ID string.
587 @type spin_id: None or str
588 @keyword weight: The optimisation weight. The higher the value, the more importance the PCS will have.
589 @type weight: float or int.
590 """
591
592
593 if not exists_mol_res_spin_data():
594 raise RelaxNoSequenceError
595
596
597 if not hasattr(cdp, 'pcs_ids') or align_id not in cdp.pcs_ids:
598 raise RelaxNoPCSError(align_id)
599
600
601 for spin in spin_loop(spin_id):
602
603 if not hasattr(spin, 'pcs_weight'):
604 spin.pcs_weight = {}
605
606
607 spin.pcs_weight[align_id] = weight
608
609
610 -def write(align_id=None, file=None, dir=None, bc=False, force=False):
611 """Display the PCS data corresponding to the alignment ID.
612
613 @keyword align_id: The alignment tensor ID string.
614 @type align_id: str
615 @keyword file: The file name or object to write to.
616 @type file: str or file object
617 @keyword dir: The name of the directory to place the file into (defaults to the current directory).
618 @type dir: str
619 @keyword bc: The back-calculation flag which if True will cause the back-calculated rather than measured data to be written.
620 @type bc: bool
621 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
622 @type force: bool
623 """
624
625
626 pipes.test()
627
628
629 if not exists_mol_res_spin_data():
630 raise RelaxNoSequenceError
631
632
633 if not hasattr(cdp, 'pcs_ids') or align_id not in cdp.pcs_ids:
634 raise RelaxNoPCSError(align_id)
635
636
637 file = open_write_file(file, dir, force)
638
639
640 mol_names = []
641 res_nums = []
642 res_names = []
643 spin_nums = []
644 spin_names = []
645 values = []
646 errors = []
647 for spin, mol_name, res_num, res_name in spin_loop(full_info=True):
648
649 if not bc and (not hasattr(spin, 'pcs') or not align_id in spin.pcs.keys()):
650 continue
651 elif bc and (not hasattr(spin, 'pcs_bc') or align_id not in spin.pcs_bc.keys()):
652 continue
653
654
655 mol_names.append(mol_name)
656 res_nums.append(res_num)
657 res_names.append(res_name)
658 spin_nums.append(spin.num)
659 spin_names.append(spin.name)
660
661
662 if bc:
663 values.append(spin.pcs_bc[align_id])
664 else:
665 values.append(spin.pcs[align_id])
666
667
668 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
669 errors.append(spin.pcs_err[align_id])
670 else:
671 errors.append(None)
672
673
674 write_spin_data(file=file, mol_names=mol_names, res_nums=res_nums, res_names=res_names, spin_nums=spin_nums, spin_names=spin_names, data=values, data_name='PCSs', error=errors, error_name='PCS_error')
675