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, read_spin_data, write_spin_data
39 from lib.physical_constants import g1H, pcs_constant
40 from lib.warnings import RelaxWarning, RelaxNoSpinWarning
41 from pipe_control import grace, pipes
42 from pipe_control.align_tensor import get_tensor_index, get_tensor_object, opt_uses_align_data, opt_uses_tensor
43 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
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.spectrometer_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(align_id=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", data_type=['pcs_bc', 'pcs'], sets=[size], set_names=[set_names], linestyle=[[2]+[0]*size], 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 """Determine if the PCS data for the given alignment ID is needed for optimisation.
509
510 @param align_id: The alignment ID string.
511 @type align_id: str
512 @return: True if the PCS data is to be used for optimisation, False otherwise.
513 @rtype: bool
514 """
515
516
517 if not hasattr(cdp, 'pcs_ids'):
518 return False
519
520
521 if align_id not in cdp.pcs_ids:
522 return False
523
524
525 tensor_flag = opt_uses_tensor(get_tensor_object(align_id))
526
527
528 pos_flag = False
529 if hasattr(cdp, 'paramag_centre_fixed') and not cdp.paramag_centre_fixed:
530 pos_flag = True
531
532
533 prob_flag = False
534 if cdp.model == 'population':
535 prob_flag = True
536
537
538 if not tensor_flag and not pos_flag and not prob_flag:
539 return False
540
541
542 return True
543
544
546 """Calculate the Q-factors for the PCS data.
547
548 @keyword spin_id: The spin ID string used to restrict the Q-factor calculation to a subset of all spins.
549 @type spin_id: None or str
550 """
551
552
553 check_pipe_setup(sequence=True)
554
555
556 if not hasattr(cdp, 'pcs_ids') or not len(cdp.pcs_ids):
557 warn(RelaxWarning("No PCS data exists, Q factors cannot be calculated."))
558 return
559
560
561 cdp.q_factors_pcs = {}
562
563
564 for align_id in cdp.pcs_ids:
565
566 pcs2_sum = 0.0
567 sse = 0.0
568
569
570 spin_count = 0
571 pcs_data = False
572 pcs_bc_data = False
573 for spin in spin_loop(spin_id):
574
575 if not spin.select:
576 continue
577
578
579 spin_count += 1
580
581
582 if hasattr(spin, 'pcs') and align_id in spin.pcs:
583 pcs_data = True
584 if hasattr(spin, 'pcs_bc') and align_id in spin.pcs_bc:
585 pcs_bc_data = True
586
587
588 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:
589 continue
590
591
592 sse = sse + (spin.pcs[align_id] - spin.pcs_bc[align_id])**2
593
594
595 pcs2_sum = pcs2_sum + spin.pcs[align_id]**2
596
597
598 if pcs2_sum:
599 Q = sqrt(sse / pcs2_sum)
600 cdp.q_factors_pcs[align_id] = Q
601
602
603 if not spin_count:
604 warn(RelaxWarning("No spins have been used in the calculation, skipping the PCS Q factor calculation."))
605 return
606 if not pcs_data:
607 warn(RelaxWarning("No PCS data can be found for the alignment ID '%s', skipping the PCS Q factor calculation for this alignment." % align_id))
608 continue
609 if not pcs_bc_data:
610 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))
611 continue
612
613
614 cdp.q_pcs = 0.0
615 for id in cdp.q_factors_pcs:
616 cdp.q_pcs = cdp.q_pcs + cdp.q_factors_pcs[id]**2
617 cdp.q_pcs = cdp.q_pcs / len(cdp.q_factors_pcs)
618 cdp.q_pcs = sqrt(cdp.q_pcs)
619
620
621 -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):
622 """Read the PCS data from file.
623
624 @param align_id: The alignment tensor ID string.
625 @type align_id: str
626 @param file: The name of the file to open.
627 @type file: str
628 @param dir: The directory containing the file (defaults to the current directory if None).
629 @type dir: str or None
630 @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.
631 @type file_data: list of lists
632 @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.
633 @type spin_id_col: int or None
634 @keyword mol_name_col: The column containing the molecule name information. If supplied, spin_id_col must be None.
635 @type mol_name_col: int or None
636 @keyword res_name_col: The column containing the residue name information. If supplied, spin_id_col must be None.
637 @type res_name_col: int or None
638 @keyword res_num_col: The column containing the residue number information. If supplied, spin_id_col must be None.
639 @type res_num_col: int or None
640 @keyword spin_name_col: The column containing the spin name information. If supplied, spin_id_col must be None.
641 @type spin_name_col: int or None
642 @keyword spin_num_col: The column containing the spin number information. If supplied, spin_id_col must be None.
643 @type spin_num_col: int or None
644 @keyword data_col: The column containing the PCS data in Hz.
645 @type data_col: int or None
646 @keyword error_col: The column containing the PCS errors.
647 @type error_col: int or None
648 @keyword sep: The column separator which, if None, defaults to whitespace.
649 @type sep: str or None
650 @keyword spin_id: The spin ID string used to restrict data loading to a subset of all spins.
651 @type spin_id: None or str
652 """
653
654
655 check_pipe_setup(sequence=True)
656
657
658 if not exists_mol_res_spin_data():
659 raise RelaxNoSequenceError
660
661
662 if data_col == None and error_col == None:
663 raise RelaxError("One of either the data or error column must be supplied.")
664
665
666
667
668
669
670 mol_names = []
671 res_nums = []
672 res_names = []
673 spin_nums = []
674 spin_names = []
675 values = []
676 errors = []
677 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):
678
679 if data_col and error_col:
680 mol_name, res_num, res_name, spin_num, spin_name, value, error = data
681 elif data_col:
682 mol_name, res_num, res_name, spin_num, spin_name, value = data
683 error = None
684 else:
685 mol_name, res_num, res_name, spin_num, spin_name, error = data
686 value = None
687
688
689 if error == 0.0:
690 raise RelaxError("An invalid error value of zero has been encountered.")
691
692
693 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)
694 spin = return_spin(id)
695 if spin == None and spin_id[0] == '@':
696 spin = return_spin(id+spin_id)
697 if spin == None:
698 warn(RelaxNoSpinWarning(id))
699 continue
700
701
702 if data_col:
703
704 if not hasattr(spin, 'pcs'):
705 spin.pcs = {}
706
707
708 spin.pcs[align_id] = value
709
710
711 if error_col:
712
713 if not hasattr(spin, 'pcs_err'):
714 spin.pcs_err = {}
715
716
717 spin.pcs_err[align_id] = error
718
719
720 mol_names.append(mol_name)
721 res_nums.append(res_num)
722 res_names.append(res_name)
723 spin_nums.append(spin_num)
724 spin_names.append(spin_name)
725 values.append(value)
726 errors.append(error)
727
728
729 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')
730
731
732
733
734
735
736 if not len(values):
737 return
738
739
740 if not hasattr(cdp, 'align_ids'):
741 cdp.align_ids = []
742 if not hasattr(cdp, 'pcs_ids'):
743 cdp.pcs_ids = []
744
745
746 if align_id not in cdp.align_ids:
747 cdp.align_ids.append(align_id)
748 if align_id not in cdp.pcs_ids:
749 cdp.pcs_ids.append(align_id)
750
751
753 """Set up the data structures for optimisation using PCSs as base data sets.
754
755 @keyword sim_index: The index of the simulation to optimise. This should be None if normal optimisation is desired.
756 @type sim_index: None or int
757 @return: The assembled data structures for using PCSs as the base data for optimisation. These include:
758 - the PCS values.
759 - the unit vectors connecting the paramagnetic centre (the electron spin) to the spin.
760 - the PCS weight.
761 - the experimental temperatures.
762 - the spectrometer frequencies.
763 - pseudo_flags, the list of flags indicating if the interatomic data contains a pseudo-atom (as 1's and 0's).
764 @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)
765 """
766
767
768 if not hasattr(cdp, 'paramagnetic_centre') and (hasattr(cdp, 'paramag_centre_fixed') and cdp.paramag_centre_fixed):
769 raise RelaxError("The paramagnetic centre has not yet been specified.")
770 if not hasattr(cdp, 'temperature'):
771 raise RelaxError("The experimental temperatures have not been set.")
772 if not hasattr(cdp, 'spectrometer_frq'):
773 raise RelaxError("The spectrometer frequencies of the experiments have not been set.")
774
775
776 setup_pseudoatom_pcs()
777
778
779 pcs = []
780 pcs_err = []
781 pcs_weight = []
782 temp = []
783 frq = []
784 pseudo_flags = []
785
786
787 for i in range(len(cdp.align_ids)):
788
789 align_id = cdp.align_ids[i]
790
791
792 if not opt_uses_align_data(align_id):
793 continue
794
795
796 pcs.append([])
797 pcs_err.append([])
798 pcs_weight.append([])
799
800
801 if align_id in cdp.temperature:
802 temp.append(cdp.temperature[align_id])
803
804
805 else:
806 raise RelaxError("The experimental temperature for the alignment ID '%s' has not been set." % align_id)
807
808
809 if align_id in cdp.spectrometer_frq:
810 frq.append(cdp.spectrometer_frq[align_id] * 2.0 * pi / g1H)
811
812
813 else:
814 raise RelaxError("The spectrometer frequency for the alignment ID '%s' has not been set." % align_id)
815
816
817 j = 0
818 for spin in spin_loop():
819
820 if not spin.select:
821 continue
822
823
824 if not hasattr(spin, 'pcs'):
825 continue
826
827
828 if align_id in spin.pcs.keys():
829 if sim_index != None:
830 pcs[-1].append(spin.pcs_sim[align_id][sim_index])
831 else:
832 pcs[-1].append(spin.pcs[align_id])
833 else:
834 pcs[-1].append(None)
835
836
837 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
838 pcs_err[-1].append(spin.pcs_err[align_id])
839 else:
840 pcs_err[-1].append(None)
841
842
843 if hasattr(spin, 'pcs_weight') and align_id in spin.pcs_weight.keys():
844 pcs_weight[-1].append(spin.pcs_weight[align_id])
845 else:
846 pcs_weight[-1].append(1.0)
847
848
849 j = j + 1
850
851
852 for spin in spin_loop():
853 if is_pseudoatom(spin):
854 pseudo_flags.append(1)
855 else:
856 pseudo_flags.append(0)
857
858
859 pcs = array(pcs, float64)
860 pcs_err = array(pcs_err, float64)
861 pcs_weight = array(pcs_weight, float64)
862 pseudo_flags = array(pseudo_flags, int32)
863
864
865 pcs = pcs * 1e-6
866 pcs_err = pcs_err * 1e-6
867
868
869 return pcs, pcs_err, pcs_weight, temp, frq, pseudo_flags
870
871
872 -def set_errors(align_id=None, spin_id=None, sd=None):
873 """Set the PCS errors if not already present.
874
875 @keyword align_id: The optional alignment tensor ID string.
876 @type align_id: str
877 @keyword spin_id: The optional spin ID string.
878 @type spin_id: None or str
879 @keyword sd: The PCS standard deviation in ppm.
880 @type sd: float or int.
881 """
882
883
884 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
885
886
887 if align_id:
888 align_ids = [align_id]
889 else:
890 align_ids = cdp.pcs_ids
891
892
893 for spin in spin_loop(spin_id):
894
895 if not spin.select:
896 continue
897
898
899 if not hasattr(spin, 'pcs') or (align_id and not align_id in spin.pcs):
900 continue
901
902
903 if not hasattr(spin, 'pcs_err'):
904 spin.pcs_err = {}
905
906
907 for id in align_ids:
908 spin.pcs_err[id] = sd
909
910
912 """Make sure that the spin systems are properly set up for pseudo-atoms and PCSs.
913
914 All spin data containers which are a member of a pseudo-atom will be deselected.
915 """
916
917
918 for pseudospin, pseudospin_id in spin_loop(return_id=True):
919
920 if not is_pseudoatom(pseudospin):
921 return
922
923
924 for spin, spin_id in pseudoatom_loop(pseudospin, return_id=True):
925
926 if spin.select:
927 warn(RelaxWarning("Deselecting the '%s' spin as it is a member of the '%s' pseudo-atom system." % (spin_id, pseudospin_id)))
928 spin.select = False
929
930
931 -def structural_noise(align_id=None, rmsd=0.2, sim_num=1000, file=None, dir=None, force=False):
932 """Determine the PCS error due to structural noise via simulation.
933
934 For the simulation the following must already be set up in the current data pipe:
935
936 - The position of the paramagnetic centre.
937 - The alignment and magnetic susceptibility tensor.
938
939 The protocol for the simulation is as follows:
940
941 - 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.
942 - 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.
943 - The PCS for the randomised position will be back calculated.
944 - The PCS standard deviation will be calculated from the N randomised PCS values.
945
946 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.
947
948 If the alignment ID string is not supplied, the procedure will be applied to the PCS data from all alignments.
949
950
951 @keyword align_id: The alignment tensor ID string.
952 @type align_id: str
953 @keyword rmsd: The atomic position RMSD, in Angstrom, to randomise the spin positions with for the simulations.
954 @type rmsd: float
955 @keyword sim_num: The number of simulations, N, to perform to determine the structural noise component of the PCS errors.
956 @type sim_num: int
957 @keyword file: The optional name of the Grace file to plot the structural errors verses the paramagnetic centre to spin distances.
958 @type file: None or str
959 @keyword dir: The directory name to place the Grace file into.
960 @type dir: None or str
961 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
962 @type force: bool
963 """
964
965
966 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True, paramag_centre=True)
967
968
969 if align_id:
970 align_ids = [align_id]
971 else:
972 align_ids = cdp.align_ids
973
974
975 grace_data = []
976 unit_vect = zeros(3, float64)
977 pcs = {}
978 for id in align_ids:
979 grace_data.append([])
980 pcs[id] = zeros(sim_num, float64)
981
982
983 print("Executing %i simulations for each spin system." % sim_num)
984
985
986 for spin, spin_id in spin_loop(return_id=True):
987
988 if not spin.select:
989 continue
990
991
992 if not hasattr(spin, 'pcs'):
993 continue
994 if not hasattr(spin, 'pos'):
995 continue
996
997
998 print(spin_id)
999
1000
1001 if type(spin.pos[0]) in [float, float64]:
1002 pos = spin.pos
1003 else:
1004 pos = zeros(3, float64)
1005 for i in range(len(spin.pos)):
1006 pos += spin.pos[i]
1007 pos = pos / len(spin.pos)
1008
1009
1010 orig_vect = pos - cdp.paramagnetic_centre
1011 orig_r = norm(orig_vect)
1012
1013
1014 for i in range(sim_num):
1015
1016 random_unit_vector(unit_vect)
1017
1018
1019 disp = gauss(0, rmsd)
1020
1021
1022 new_pos = pos + disp*unit_vect
1023
1024
1025 vect = new_pos - cdp.paramagnetic_centre
1026 r = norm(vect)
1027 vect = vect / r
1028
1029
1030 for id in align_ids:
1031
1032 if id not in spin.pcs:
1033 continue
1034
1035
1036 dj = pcs_constant(cdp.temperature[id], cdp.spectrometer_frq[id] * 2.0 * pi / g1H, r/1e10)
1037
1038
1039 pcs[id][i] = pcs_tensor(dj, vect, cdp.align_tensors[get_tensor_index(id)].A) * 1e6
1040
1041
1042 if not hasattr(spin, 'pcs_struct_err'):
1043 spin.pcs_struct_err = {}
1044
1045
1046 align_index = 0
1047 for id in align_ids:
1048
1049 if id not in spin.pcs:
1050 align_index += 1
1051 continue
1052
1053
1054 sd = std(pcs[id])
1055
1056
1057 if id in spin.pcs_struct_err:
1058 warn(RelaxWarning("Removing the previous structural error value from the PCS error of the spin '%s' for the alignment ID '%s'." % (spin_id, id)))
1059 spin.pcs_err[id] = sqrt(spin.pcs_err[id]**2 - spin.pcs_struct_err[id]**2)
1060
1061
1062 spin.pcs_struct_err[id] = sd
1063
1064
1065 spin.pcs_err[id] = sqrt(spin.pcs_err[id]**2 + sd**2)
1066
1067
1068 grace_data[align_index].append([orig_r, sd])
1069
1070
1071 align_index += 1
1072
1073
1074 if file:
1075
1076 file = open_write_file(file, dir, force)
1077
1078
1079 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)"]])
1080
1081
1082 grace.write_xy_data(data=[grace_data], file=file, graph_type='xy')
1083
1084
1085 -def weight(align_id=None, spin_id=None, weight=1.0):
1086 """Set optimisation weights on the PCS data.
1087
1088 @keyword align_id: The alignment tensor ID string.
1089 @type align_id: str
1090 @keyword spin_id: The spin ID string.
1091 @type spin_id: None or str
1092 @keyword weight: The optimisation weight. The higher the value, the more importance the PCS will have.
1093 @type weight: float or int.
1094 """
1095
1096
1097 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
1098
1099
1100 for spin in spin_loop(spin_id):
1101
1102 if not hasattr(spin, 'pcs_weight'):
1103 spin.pcs_weight = {}
1104
1105
1106 spin.pcs_weight[align_id] = weight
1107
1108
1109 -def write(align_id=None, file=None, dir=None, bc=False, force=False):
1110 """Display the PCS data corresponding to the alignment ID.
1111
1112 @keyword align_id: The alignment tensor ID string.
1113 @type align_id: str
1114 @keyword file: The file name or object to write to.
1115 @type file: str or file object
1116 @keyword dir: The name of the directory to place the file into (defaults to the current directory).
1117 @type dir: str
1118 @keyword bc: The back-calculation flag which if True will cause the back-calculated rather than measured data to be written.
1119 @type bc: bool
1120 @keyword force: A flag which if True will cause any pre-existing file to be overwritten.
1121 @type force: bool
1122 """
1123
1124
1125 check_pipe_setup(sequence=True, pcs_id=align_id, pcs=True)
1126
1127
1128 file = open_write_file(file, dir, force)
1129
1130
1131 mol_names = []
1132 res_nums = []
1133 res_names = []
1134 spin_nums = []
1135 spin_names = []
1136 values = []
1137 errors = []
1138 for spin, mol_name, res_num, res_name in spin_loop(full_info=True):
1139
1140 if not spin.select:
1141 continue
1142
1143
1144 if not bc and (not hasattr(spin, 'pcs') or not align_id in spin.pcs.keys()):
1145 continue
1146 elif bc and (not hasattr(spin, 'pcs_bc') or align_id not in spin.pcs_bc.keys()):
1147 continue
1148
1149
1150 mol_names.append(mol_name)
1151 res_nums.append(res_num)
1152 res_names.append(res_name)
1153 spin_nums.append(spin.num)
1154 spin_names.append(spin.name)
1155
1156
1157 if bc:
1158 values.append(spin.pcs_bc[align_id])
1159 else:
1160 values.append(spin.pcs[align_id])
1161
1162
1163 if hasattr(spin, 'pcs_err') and align_id in spin.pcs_err.keys():
1164 errors.append(spin.pcs_err[align_id])
1165 else:
1166 errors.append(None)
1167
1168
1169 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')
1170