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