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 pseudo-contact shift data."""
24
25
26 from copy import deepcopy
27 from math import pi, sqrt
28 from numpy import array, float64, ones, std, zeros
29 from numpy.linalg import norm
30 from random import gauss
31 import sys
32 from warnings import warn
33
34
35 from generic_fns import grace, pipes
36 from generic_fns.align_tensor import get_tensor_index
37 from generic_fns.mol_res_spin import exists_mol_res_spin_data, generate_spin_id_unique, return_spin, spin_index_loop, spin_loop
38 from maths_fns.pcs import ave_pcs_tensor, pcs_tensor
39 from maths_fns.vectors import random_unit_vector
40 from physical_constants import g1H, pcs_constant
41 from relax_errors import RelaxError, RelaxAlignError, RelaxNoAlignError, RelaxNoPdbError, RelaxNoPCSError, RelaxNoSequenceError, RelaxPCSError
42 from relax_io import open_write_file, read_spin_data, write_spin_data
43 from relax_warnings import RelaxWarning, RelaxNoSpinWarning
44
45
47 """Back calculate the PCS from the alignment tensor.
48
49 @keyword align_id: The alignment tensor ID string.
50 @type align_id: str
51 """
52
53
54 check_pipe_setup(pcs_id=align_id, sequence=True, N=True, tensors=True, paramag_centre=True)
55
56
57 if align_id:
58 align_ids = [align_id]
59 else:
60 align_ids = cdp.align_ids
61
62
63 for align_id in align_ids:
64
65 if not hasattr(cdp, 'pcs_ids'):
66 cdp.pcs_ids = []
67
68
69 if align_id not in cdp.pcs_ids:
70 cdp.pcs_ids.append(align_id)
71
72
73 weights = ones(cdp.N, float64) / cdp.N
74
75
76 unit_vect = zeros((cdp.N, 3), float64)
77
78
79 count = 0
80 for spin in spin_loop():
81
82 if not hasattr(spin, 'pos'):
83 continue
84
85
86 pos = spin.pos
87 if type(pos[0]) in [float, float64]:
88 pos = [pos] * cdp.N
89
90
91 for id in align_ids:
92
93 vect = zeros((cdp.N, 3), float64)
94 r = zeros(cdp.N, float64)
95 dj = zeros(cdp.N, float64)
96 for c in range(cdp.N):
97
98 vect[c] = pos[c] - cdp.paramagnetic_centre
99
100
101 r[c] = norm(vect[c])
102
103
104 if r[c]:
105 vect[c] = vect[c] / r[c]
106
107
108 dj[c] = pcs_constant(cdp.temperature[id], cdp.frq[id] * 2.0 * pi / g1H, r[c]/1e10)
109
110
111 if not hasattr(spin, 'pcs_bc'):
112 spin.pcs_bc = {}
113
114
115 spin.pcs_bc[id] = ave_pcs_tensor(dj, vect, cdp.N, cdp.align_tensors[get_tensor_index(id)].A, weights=weights) * 1e6
116
117
118 count += 1
119
120
121 if not count:
122 warn(RelaxWarning("No PCSs have been back calculated, probably due to missing spin position information."))
123
124
125 -def centre(pos=None, atom_id=None, pipe=None, verbosity=1, ave_pos=False, force=False):
126 """Specify the atom in the loaded structure corresponding to the paramagnetic centre.
127
128 @keyword pos: The atomic position. If set, the atom_id string will be ignored.
129 @type pos: list of float
130 @keyword atom_id: The atom identification string.
131 @type atom_id: str
132 @keyword pipe: An alternative data pipe to extract the paramagnetic centre from.
133 @type pipe: None or str
134 @keyword verbosity: The amount of information to print out. The bigger the number, the more information.
135 @type verbosity: int
136 @keyword ave_pos: A flag which if True causes the atomic positions from multiple models to be averaged.
137 @type ave_pos: bool
138 @keyword force: A flag which if True will cause the current PCS centre to be overwritten.
139 """
140
141
142 if pipe == None:
143 pipe = pipes.cdp_name()
144
145
146 check_pipe_setup(pipe=pipe)
147
148
149 source_dp = pipes.get_pipe(pipe)
150
151
152 if not hasattr(source_dp, 'structure'):
153 raise RelaxNoPdbError
154
155
156 if not force and hasattr(cdp, 'paramagnetic_centre'):
157 raise RelaxError("The paramagnetic centre has already been set to the coordinates " + repr(cdp.paramagnetic_centre) + ".")
158
159
160 if pos != None:
161 centre = array(pos)
162 num_pos = 1
163 full_pos_list = []
164
165
166 else:
167
168 centre = zeros(3, float64)
169 full_pos_list = []
170 num_pos = 0
171 for spin, spin_id in spin_loop(atom_id, pipe=pipe, return_id=True):
172
173 if not hasattr(spin, 'pos'):
174 continue
175
176
177 if isinstance(spin.pos[0], float) or isinstance(spin.pos[0], float64):
178 pos_list = [spin.pos]
179 else:
180 pos_list = spin.pos
181
182
183 for pos in pos_list:
184 full_pos_list.append(pos)
185 centre = centre + array(pos)
186 num_pos = num_pos + 1
187
188
189 if not num_pos:
190 raise RelaxError("No positional information could be found for the spin '%s'." % atom_id)
191
192
193 centre = centre / float(num_pos)
194
195
196 if verbosity:
197 print("Paramagnetic centres located at:")
198 for pos in full_pos_list:
199 print(" [%8.3f, %8.3f, %8.3f]" % (pos[0], pos[1], pos[2]))
200 print("\nAverage paramagnetic centre located at:")
201 print(" [%8.3f, %8.3f, %8.3f]" % (centre[0], centre[1], centre[2]))
202
203
204 if ave_pos:
205 if verbosity:
206 print("\nUsing the average paramagnetic position.")
207 cdp.paramagnetic_centre = centre
208 else:
209 if verbosity:
210 print("\nUsing all paramagnetic positions.")
211 cdp.paramagnetic_centre = full_pos_list
212
213
214 -def check_pipe_setup(pipe=None, pcs_id=None, sequence=False, N=False, tensors=False, pcs=False, paramag_centre=False):
215 """Check that the current data pipe has been setup sufficiently.
216
217 @keyword pipe: The data pipe to check, defaulting to the current pipe.
218 @type pipe: None or str
219 @keyword pcs_id: The PCS ID string to check for in cdp.pcs_ids.
220 @type pcs_id: None or str
221 @keyword sequence: A flag which when True will invoke the sequence data check.
222 @type sequence: bool
223 @keyword N: A flag which if True will check that cdp.N is set.
224 @type N: bool
225 @keyword tensors: A flag which if True will check that alignment tensors exist.
226 @type tensors: bool
227 @keyword pcs: A flag which if True will check that PCSs exist.
228 @type pcs: bool
229 @keyword paramag_centre: A flag which if True will check that the paramagnetic centre has been set.
230 @type paramag_centre: bool
231 """
232
233
234 if pipe == None:
235 pipe = pipes.cdp_name()
236
237
238 dp = pipes.get_pipe(pipe)
239
240
241 pipes.test(pipe)
242
243
244 if sequence and not exists_mol_res_spin_data(pipe):
245 raise RelaxNoSequenceError(pipe)
246
247
248 if N and not hasattr(dp, 'N'):
249 raise RelaxError("The number of states N has not been set.")
250
251
252 if tensors and (not hasattr(dp, 'align_tensors') or len(dp.align_tensors) == 0):
253 raise RelaxNoTensorError('alignment')
254
255
256 if pcs_id and (not hasattr(dp, 'align_ids') or pcs_id not in dp.align_ids):
257 raise RelaxNoAlignError(pcs_id, pipe)
258
259
260 if pcs and not hasattr(dp, 'align_ids'):
261 raise RelaxNoAlignError()
262 if pcs and not hasattr(dp, 'pcs_ids'):
263 raise RelaxNoPCSError()
264 elif pcs and pcs_id and pcs_id not in dp.pcs_ids:
265 raise RelaxNoPCSError(pcs_id)
266
267
268 if paramag_centre and not hasattr(cdp, 'paramagnetic_centre'):
269 raise RelaxError("The paramagnetic centre has not been defined.")
270
271
272 -def copy(pipe_from=None, pipe_to=None, align_id=None):
273 """Copy the PCS data from one data pipe to another.
274
275 @keyword pipe_from: The data pipe to copy the PCS data from. This defaults to the current data pipe.
276 @type pipe_from: str
277 @keyword pipe_to: The data pipe to copy the PCS data to. This defaults to the current data pipe.
278 @type pipe_to: str
279 @param align_id: The alignment ID string.
280 @type align_id: str
281 """
282
283
284 if pipe_from == None and pipe_to == None:
285 raise RelaxError("The pipe_from and pipe_to arguments cannot both be set to None.")
286 elif pipe_from == None:
287 pipe_from = pipes.cdp_name()
288 elif pipe_to == None:
289 pipe_to = pipes.cdp_name()
290
291
292 check_pipe_setup(pipe=pipe_from, pcs_id=align_id, sequence=True, pcs=True)
293 check_pipe_setup(pipe=pipe_to, sequence=True)
294
295
296 dp_from = pipes.get_pipe(pipe_from)
297 dp_to = pipes.get_pipe(pipe_to)
298
299
300 if align_id == None:
301 align_ids = dp_from.align_ids
302 else:
303 align_ids = [align_id]
304
305
306 if not hasattr(dp_to, 'align_ids'):
307 dp_to.align_ids = []
308 if not hasattr(dp_to, 'pcs_ids'):
309 dp_to.pcs_ids = []
310
311
312 for align_id in align_ids:
313
314 if align_id not in dp_to.align_ids and align_id not in dp_to.align_ids:
315 dp_to.align_ids.append(align_id)
316 if align_id in dp_from.pcs_ids and align_id not in dp_to.pcs_ids:
317 dp_to.pcs_ids.append(align_id)
318
319
320 for mol_index, res_index, spin_index in spin_index_loop():
321
322 spin_from = dp_from.mol[mol_index].res[res_index].spin[spin_index]
323 spin_to = dp_to.mol[mol_index].res[res_index].spin[spin_index]
324
325
326 if (not hasattr(spin_from, 'pcs') or not align_id in spin_from.pcs) and (not hasattr(spin_from, 'pcs_err') or not align_id in spin_from.pcs_err):
327 continue
328
329
330 if hasattr(spin_from, 'pcs') and not hasattr(spin_to, 'pcs'):
331 spin_to.pcs = {}
332 if hasattr(spin_from, 'pcs_err') and not hasattr(spin_to, 'pcs_err'):
333 spin_to.pcs_err = {}
334
335
336 if hasattr(spin_from, 'pcs'):
337 spin_to.pcs[align_id] = spin_from.pcs[align_id]
338 if hasattr(spin_from, 'pcs_err'):
339 spin_to.pcs_err[align_id] = spin_from.pcs_err[align_id]
340
341
342 -def corr_plot(format=None, file=None, dir=None, force=False):
343 """Generate a correlation plot of the measured vs. back-calculated PCSs.
344
345 @keyword format: The format for the plot file. The following values are accepted: 'grace', a Grace plot; None, a plain text file.
346 @type format: str or None
347 @keyword file: The file name or object to write to.
348 @type file: str or file object
349 @keyword dir: The name of the directory to place the file into (defaults to the current directory).
350 @type dir: str
351 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
352 @type force: bool
353 """
354
355
356 check_pipe_setup(sequence=True)
357
358
359 if not hasattr(cdp, 'pcs_ids') or not cdp.pcs_ids:
360 warn(RelaxWarning("No PCS data exists, skipping file creation."))
361 return
362
363
364 file = open_write_file(file, dir, force)
365
366
367 data = []
368
369
370 data.append([[-100, -100, 0], [100, 100, 0]])
371
372
373 types = []
374 for spin in spin_loop():
375 if spin.element not in types:
376 types.append(spin.element)
377
378
379 for align_id in cdp.pcs_ids:
380
381 for i in range(len(types)):
382
383 data.append([])
384
385
386 err_flag = False
387 for spin in spin_loop():
388
389 if not spin.select:
390 continue
391
392
393 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
394 err_flag = True
395 break
396
397
398 for spin, spin_id in spin_loop(return_id=True):
399
400 if not spin.select:
401 continue
402
403
404 if spin.element != types[i]:
405 continue
406
407
408 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():
409 continue
410
411
412 data[-1].append([spin.pcs_bc[align_id], spin.pcs[align_id]])
413
414
415 if err_flag:
416 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
417 data[-1][-1].append(spin.pcs_err[align_id])
418 else:
419 data[-1][-1].append(None)
420
421
422 data[-1][-1].append(spin_id)
423
424
425 size = len(data)
426
427
428 data = [data]
429
430
431 if err_flag:
432 graph_type = 'xydy'
433 else:
434 graph_type = 'xy'
435
436
437 if format == 'grace':
438
439 set_names = [None]
440 for i in range(len(cdp.pcs_ids)):
441 for j in range(len(types)):
442 set_names.append("%s (%s)" % (cdp.pcs_ids[i], types[j]))
443
444
445 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])
446
447
448 grace.write_xy_data(data=data, file=file, graph_type=graph_type)
449
450
452 """Delete the PCS data corresponding to the alignment ID.
453
454 @keyword align_id: The alignment tensor ID string. If not specified, all data will be deleted.
455 @type align_id: str or None
456 """
457
458
459 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
460
461
462 if not align_id:
463 ids = deepcopy(cdp.pcs_ids)
464 else:
465 ids = [align_id]
466
467
468 for id in ids:
469
470 cdp.pcs_ids.pop(cdp.pcs_ids.index(id))
471
472
473 if hasattr(cdp, 'pcs_data_types') and id in cdp.pcs_data_types:
474 cdp.pcs_data_types.pop(id)
475
476
477 for spin in spin_loop():
478
479 if hasattr(spin, 'pcs') and id in spin.pcs:
480 spin.pcs.pop(id)
481
482
483 if hasattr(spin, 'pcs_err') and id in spin.pcs_err:
484 spin.pcs_err.pop(id)
485
486
487 if not hasattr(cdp, 'rdc_ids') or id not in cdp.rdc_ids:
488 cdp.align_ids.pop(cdp.align_ids.index(id))
489
490
491 -def display(align_id=None, bc=False):
492 """Display the PCS data corresponding to the alignment ID.
493
494 @keyword align_id: The alignment tensor ID string.
495 @type align_id: str
496 @keyword bc: The back-calculation flag which if True will cause the back-calculated rather than measured data to be displayed.
497 @type bc: bool
498 """
499
500
501 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
502
503
504 write(align_id=align_id, file=sys.stdout, bc=bc)
505
506
508 """Calculate the Q-factors for the PCS data.
509
510 @keyword spin_id: The spin ID string used to restrict the Q-factor calculation to a subset of all spins.
511 @type spin_id: None or str
512 """
513
514
515 check_pipe_setup(sequence=True)
516
517
518 if not hasattr(cdp, 'pcs_ids') or not len(cdp.pcs_ids):
519 warn(RelaxWarning("No PCS data exists, Q factors cannot be calculated."))
520 return
521
522
523 cdp.q_factors_pcs = {}
524
525
526 for align_id in cdp.pcs_ids:
527
528 pcs2_sum = 0.0
529 sse = 0.0
530
531
532 spin_count = 0
533 pcs_data = False
534 pcs_bc_data = False
535 for spin in spin_loop(spin_id):
536
537 if not spin.select:
538 continue
539
540
541 spin_count += 1
542
543
544 if hasattr(spin, 'pcs') and align_id in spin.pcs:
545 pcs_data = True
546 if hasattr(spin, 'pcs_bc') and align_id in spin.pcs_bc:
547 pcs_bc_data = True
548
549
550 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:
551 continue
552
553
554 sse = sse + (spin.pcs[align_id] - spin.pcs_bc[align_id])**2
555
556
557 pcs2_sum = pcs2_sum + spin.pcs[align_id]**2
558
559
560 if pcs2_sum:
561 Q = sqrt(sse / pcs2_sum)
562 cdp.q_factors_pcs[align_id] = Q
563
564
565 if not spin_count:
566 warn(RelaxWarning("No spins have been used in the calculation, skipping the PCS Q factor calculation."))
567 return
568 if not pcs_data:
569 warn(RelaxWarning("No PCS data can be found for the alignment ID '%s', skipping the PCS Q factor calculation for this alignment." % align_id))
570 continue
571 if not pcs_bc_data:
572 warn(RelaxWarning("No back-calculated PCS data can be found for the alignment ID '%s', skipping the PCS Q factor calculation for this alignment." % align_id))
573 continue
574
575
576 cdp.q_pcs = 0.0
577 for id in cdp.q_factors_pcs:
578 cdp.q_pcs = cdp.q_pcs + cdp.q_factors_pcs[id]**2
579 cdp.q_pcs = cdp.q_pcs / len(cdp.q_factors_pcs)
580 cdp.q_pcs = sqrt(cdp.q_pcs)
581
582
583 -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):
584 """Read the PCS data from file.
585
586 @param align_id: The alignment tensor ID string.
587 @type align_id: str
588 @param file: The name of the file to open.
589 @type file: str
590 @param dir: The directory containing the file (defaults to the current directory if None).
591 @type dir: str or None
592 @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.
593 @type file_data: list of lists
594 @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.
595 @type spin_id_col: int or None
596 @keyword mol_name_col: The column containing the molecule name information. If supplied, spin_id_col must be None.
597 @type mol_name_col: int or None
598 @keyword res_name_col: The column containing the residue name information. If supplied, spin_id_col must be None.
599 @type res_name_col: int or None
600 @keyword res_num_col: The column containing the residue number information. If supplied, spin_id_col must be None.
601 @type res_num_col: int or None
602 @keyword spin_name_col: The column containing the spin name information. If supplied, spin_id_col must be None.
603 @type spin_name_col: int or None
604 @keyword spin_num_col: The column containing the spin number information. If supplied, spin_id_col must be None.
605 @type spin_num_col: int or None
606 @keyword data_col: The column containing the PCS data in Hz.
607 @type data_col: int or None
608 @keyword error_col: The column containing the PCS errors.
609 @type error_col: int or None
610 @keyword sep: The column separator which, if None, defaults to whitespace.
611 @type sep: str or None
612 @keyword spin_id: The spin ID string used to restrict data loading to a subset of all spins.
613 @type spin_id: None or str
614 """
615
616
617 check_pipe_setup(sequence=True)
618
619
620 if not exists_mol_res_spin_data():
621 raise RelaxNoSequenceError
622
623
624 if data_col == None and error_col == None:
625 raise RelaxError("One of either the data or error column must be supplied.")
626
627
628
629
630
631
632 mol_names = []
633 res_nums = []
634 res_names = []
635 spin_nums = []
636 spin_names = []
637 values = []
638 errors = []
639 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):
640
641 if data_col and error_col:
642 mol_name, res_num, res_name, spin_num, spin_name, value, error = data
643 elif data_col:
644 mol_name, res_num, res_name, spin_num, spin_name, value = data
645 error = None
646 else:
647 mol_name, res_num, res_name, spin_num, spin_name, error = data
648 value = None
649
650
651 if error == 0.0:
652 raise RelaxError("An invalid error value of zero has been encountered.")
653
654
655 id = generate_spin_id_unique(mol_name=mol_name, res_num=res_num, res_name=res_name, spin_num=spin_num, spin_name=spin_name)
656 spin = return_spin(id)
657 if spin == None and spin_id[0] == '@':
658 spin = return_spin(id+spin_id)
659 if spin == None:
660 warn(RelaxNoSpinWarning(id))
661 continue
662
663
664 if data_col:
665
666 if not hasattr(spin, 'pcs'):
667 spin.pcs = {}
668
669
670 spin.pcs[align_id] = value
671
672
673 if error_col:
674
675 if not hasattr(spin, 'pcs_err'):
676 spin.pcs_err = {}
677
678
679 spin.pcs_err[align_id] = error
680
681
682 mol_names.append(mol_name)
683 res_nums.append(res_num)
684 res_names.append(res_name)
685 spin_nums.append(spin_num)
686 spin_names.append(spin_name)
687 values.append(value)
688 errors.append(error)
689
690
691 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')
692
693
694
695
696
697
698 if not len(values):
699 return
700
701
702 if not hasattr(cdp, 'align_ids'):
703 cdp.align_ids = []
704 if not hasattr(cdp, 'pcs_ids'):
705 cdp.pcs_ids = []
706
707
708 if align_id not in cdp.align_ids:
709 cdp.align_ids.append(align_id)
710 if align_id not in cdp.pcs_ids:
711 cdp.pcs_ids.append(align_id)
712
713
714 -def set_errors(align_id=None, spin_id=None, sd=None):
715 """Set the PCS errors if not already present.
716
717 @keyword align_id: The optional alignment tensor ID string.
718 @type align_id: str
719 @keyword spin_id: The optional spin ID string.
720 @type spin_id: None or str
721 @keyword sd: The PCS standard deviation in ppm.
722 @type sd: float or int.
723 """
724
725
726 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
727
728
729 if align_id:
730 align_ids = [align_id]
731 else:
732 align_ids = cdp.pcs_ids
733
734
735 for spin in spin_loop(spin_id):
736
737 if not spin.select:
738 continue
739
740
741 if not hasattr(spin, 'pcs') or (align_id and not align_id in spin.pcs):
742 continue
743
744
745 if not hasattr(spin, 'pcs_err'):
746 spin.pcs_err = {}
747
748
749 for id in align_ids:
750 spin.pcs_err[id] = sd
751
752
753 -def structural_noise(align_id=None, rmsd=0.2, sim_num=1000, file=None, dir=None, force=False):
754 """Determine the PCS error due to structural noise via simulation.
755
756 For the simulation the following must already be set up in the current data pipe:
757
758 - The position of the paramagnetic centre.
759 - The alignment and magnetic susceptibility tensor.
760
761 The protocol for the simulation is as follows:
762
763 - The lanthanide or paramagnetic centre position will be fixed. Its motion is assumed to be on the femto- to pico- and nanosecond timescales. Hence the motion is averaged over the evolution of the PCS and can be ignored.
764 - The positions of the nuclear spins will be randomised N times. For each simulation a random unit vector will be generated. Then a random distance along the unit vector will be generated by sampling from a Gaussian distribution centered at zero, the original spin position, with a standard deviation set to the given RMSD. Both positive and negative displacements will be used.
765 - The PCS for the randomised position will be back calculated.
766 - The PCS standard deviation will be calculated from the N randomised PCS values.
767
768 The standard deviation will both be stored in the spin container data structure in the relax data store as well as being added to the already present PCS error (using variance addition). This will then be used in any optimisations involving the PCS.
769
770 If the alignment ID string is not supplied, the procedure will be applied to the PCS data from all alignments.
771
772
773 @keyword align_id: The alignment tensor ID string.
774 @type align_id: str
775 @keyword rmsd: The atomic position RMSD, in Angstrom, to randomise the spin positions with for the simulations.
776 @type rmsd: float
777 @keyword sim_num: The number of simulations, N, to perform to determine the structural noise component of the PCS errors.
778 @type sim_num: int
779 @keyword file: The optional name of the Grace file to plot the structural errors verses the paramagnetic centre to spin distances.
780 @type file: None or str
781 @keyword dir: The directory name to place the Grace file into.
782 @type dir: None or str
783 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
784 @type force: bool
785 """
786
787
788 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True, paramag_centre=True)
789
790
791 if align_id:
792 align_ids = [align_id]
793 else:
794 align_ids = cdp.align_ids
795
796
797 grace_data = []
798 unit_vect = zeros(3, float64)
799 pcs = {}
800 for id in align_ids:
801 grace_data.append([])
802 pcs[id] = zeros(sim_num, float64)
803
804
805 print("Executing %i simulations for each spin system." % sim_num)
806
807
808 for spin, spin_id in spin_loop(return_id=True):
809
810 if not spin.select:
811 continue
812
813
814 if not hasattr(spin, 'pcs'):
815 continue
816 if not hasattr(spin, 'pos'):
817 continue
818
819
820 print(spin_id)
821
822
823 if type(spin.pos[0]) in [float, float64]:
824 pos = spin.pos
825 else:
826 pos = zeros(3, float64)
827 for i in range(len(spin.pos)):
828 pos += spin.pos[i]
829 pos = pos / len(spin.pos)
830
831
832 orig_vect = pos - cdp.paramagnetic_centre
833 orig_r = norm(orig_vect)
834
835
836 for i in range(sim_num):
837
838 random_unit_vector(unit_vect)
839
840
841 disp = gauss(0, rmsd)
842
843
844 new_pos = pos + disp*unit_vect
845
846
847 vect = new_pos - cdp.paramagnetic_centre
848 r = norm(vect)
849 vect = vect / r
850
851
852 for id in align_ids:
853
854 if id not in spin.pcs:
855 continue
856
857
858 dj = pcs_constant(cdp.temperature[id], cdp.frq[id] * 2.0 * pi / g1H, r/1e10)
859
860
861 pcs[id][i] = pcs_tensor(dj, vect, cdp.align_tensors[get_tensor_index(id)].A) * 1e6
862
863
864 if not hasattr(spin, 'pcs_struct_err'):
865 spin.pcs_struct_err = {}
866
867
868 align_index = 0
869 for id in align_ids:
870
871 if id not in spin.pcs:
872 align_index += 1
873 continue
874
875
876 sd = std(pcs[id])
877
878
879 if id in spin.pcs_struct_err:
880 warn(RelaxWarning("Removing the previous structural error value from the PCS error of the spin '%s' for the alignment ID '%s'." % (spin_id, id)))
881 spin.pcs_err[id] = sqrt(spin.pcs_err[id]**2 - spin.pcs_struct_err[id]**2)
882
883
884 spin.pcs_struct_err[id] = sd
885
886
887 spin.pcs_err[id] = sqrt(spin.pcs_err[id]**2 + sd**2)
888
889
890 grace_data[align_index].append([orig_r, sd])
891
892
893 align_index += 1
894
895
896 if file:
897
898 file = open_write_file(file, dir, force)
899
900
901 grace.write_xy_header(file=file, title="PCS structural noise", subtitle="%s Angstrom structural noise"%rmsd, sets=len(align_ids), set_names=align_ids, symbol_sizes=[0.5]*len(align_ids), linetype=[0]*len(align_ids), data_type=['pcs_bc', 'pcs'], axis_labels=["Ln\\S3+\\N to spin distance (Angstrom)", "PCS standard deviation (ppm)"])
902
903
904 grace.write_xy_data(data=[grace_data], file=file, graph_type='xy')
905
906
907 -def weight(align_id=None, spin_id=None, weight=1.0):
908 """Set optimisation weights on the PCS data.
909
910 @keyword align_id: The alignment tensor ID string.
911 @type align_id: str
912 @keyword spin_id: The spin ID string.
913 @type spin_id: None or str
914 @keyword weight: The optimisation weight. The higher the value, the more importance the PCS will have.
915 @type weight: float or int.
916 """
917
918
919 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
920
921
922 for spin in spin_loop(spin_id):
923
924 if not hasattr(spin, 'pcs_weight'):
925 spin.pcs_weight = {}
926
927
928 spin.pcs_weight[align_id] = weight
929
930
931 -def write(align_id=None, file=None, dir=None, bc=False, force=False):
932 """Display the PCS data corresponding to the alignment ID.
933
934 @keyword align_id: The alignment tensor ID string.
935 @type align_id: str
936 @keyword file: The file name or object to write to.
937 @type file: str or file object
938 @keyword dir: The name of the directory to place the file into (defaults to the current directory).
939 @type dir: str
940 @keyword bc: The back-calculation flag which if True will cause the back-calculated rather than measured data to be written.
941 @type bc: bool
942 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
943 @type force: bool
944 """
945
946
947 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
948
949
950 file = open_write_file(file, dir, force)
951
952
953 mol_names = []
954 res_nums = []
955 res_names = []
956 spin_nums = []
957 spin_names = []
958 values = []
959 errors = []
960 for spin, mol_name, res_num, res_name in spin_loop(full_info=True):
961
962 if not spin.select:
963 continue
964
965
966 if not bc and (not hasattr(spin, 'pcs') or not align_id in spin.pcs.keys()):
967 continue
968 elif bc and (not hasattr(spin, 'pcs_bc') or align_id not in spin.pcs_bc.keys()):
969 continue
970
971
972 mol_names.append(mol_name)
973 res_nums.append(res_num)
974 res_names.append(res_name)
975 spin_nums.append(spin.num)
976 spin_names.append(spin.name)
977
978
979 if bc:
980 values.append(spin.pcs_bc[align_id])
981 else:
982 values.append(spin.pcs[align_id])
983
984
985 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
986 errors.append(spin.pcs_err[align_id])
987 else:
988 errors.append(None)
989
990
991 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')
992