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 the interatomic data structures in the relax data store."""
24
25
26 from copy import deepcopy
27 from numpy import float64, zeros
28 from numpy.linalg import norm
29 from re import search
30 import sys
31 from warnings import warn
32
33
34 from lib.arg_check import is_float
35 from lib.errors import RelaxError, RelaxInteratomInconsistentError, RelaxNoInteratomError, RelaxNoSpinError
36 from lib.io import extract_data, strip, write_data
37 from lib.warnings import RelaxWarning, RelaxZeroVectorWarning
38 from pipe_control import pipes
39 from pipe_control.mol_res_spin import Selection, count_spins, exists_mol_res_spin_data, return_spin, spin_loop
40
41
42 -def copy(pipe_from=None, pipe_to=None, spin_id1=None, spin_id2=None, verbose=True):
43 """Copy the interatomic data from one data pipe to another.
44
45 @keyword pipe_from: The data pipe to copy the interatomic data from. This defaults to the current data pipe.
46 @type pipe_from: str
47 @keyword pipe_to: The data pipe to copy the interatomic data to. This defaults to the current data pipe.
48 @type pipe_to: str
49 @keyword spin_id1: The spin ID string of the first atom.
50 @type spin_id1: str
51 @keyword spin_id2: The spin ID string of the second atom.
52 @type spin_id2: str
53 @keyword verbose: A flag which if True will cause info about each spin pair to be printed out.
54 @type verbose: bool
55 """
56
57
58 if pipe_from == None and pipe_to == None:
59 raise RelaxError("The pipe_from and pipe_to arguments cannot both be set to None.")
60 elif pipe_from == None:
61 pipe_from = pipes.cdp_name()
62 elif pipe_to == None:
63 pipe_to = pipes.cdp_name()
64
65
66 pipes.test(pipe_from)
67 pipes.test(pipe_to)
68
69
70 if spin_id1:
71 if count_spins(selection=spin_id1, pipe=pipe_from, skip_desel=False) == 0:
72 raise RelaxNoSpinError(spin_id1, pipe_from)
73 if count_spins(selection=spin_id1, pipe=pipe_to, skip_desel=False) == 0:
74 raise RelaxNoSpinError(spin_id1, pipe_to)
75 if spin_id2:
76 if count_spins(selection=spin_id2, pipe=pipe_from, skip_desel=False) == 0:
77 raise RelaxNoSpinError(spin_id2, pipe_from)
78 if count_spins(selection=spin_id2, pipe=pipe_to, skip_desel=False) == 0:
79 raise RelaxNoSpinError(spin_id2, pipe_to)
80
81
82 if not spin_id1 and not spin_id2:
83 for spin, spin_id in spin_loop(pipe=pipe_from, return_id=True):
84 if not return_spin(spin_id, pipe=pipe_to):
85 raise RelaxNoSpinError(spin_id, pipe_to)
86
87
88 if not exists_data(pipe_from):
89 return
90
91
92 ids = []
93 for interatom in interatomic_loop(selection1=spin_id1, selection2=spin_id2, pipe=pipe_from):
94
95 new_interatom = create_interatom(spin_id1=interatom.spin_id1, spin_id2=interatom.spin_id2, pipe=pipe_to)
96
97
98 for name in dir(interatom):
99
100 if search('^_', name):
101 continue
102
103
104 if name in ['spin_id1', 'spin_id2']:
105 continue
106
107
108 if name in list(interatom.__class__.__dict__.keys()):
109 continue
110
111
112 obj = deepcopy(getattr(interatom, name))
113 setattr(new_interatom, name, obj)
114
115
116 ids.append([repr(interatom.spin_id1), repr(interatom.spin_id2)])
117
118
119 if verbose:
120 write_data(out=sys.stdout, headings=["Spin_ID_1", "Spin_ID_2"], data=ids)
121
122
152
153
155 """Create and return the interatomic data container for the two spins.
156
157 @keyword spin_id1: The spin ID string of the first atom.
158 @type spin_id1: str
159 @keyword spin_id2: The spin ID string of the second atom.
160 @type spin_id2: str
161 @keyword pipe: The data pipe to create the interatomic data container for. This defaults to the current data pipe if not supplied.
162 @type pipe: str or None
163 @keyword verbose: A flag which if True will result printouts.
164 @type verbose: bool
165 @return: The newly created interatomic data container.
166 @rtype: data.interatomic.InteratomContainer instance
167 """
168
169
170 if verbose:
171 print("Creating an interatomic data container between the spins '%s' and '%s'." % (spin_id1, spin_id2))
172
173
174 if pipe == None:
175 pipe = pipes.cdp_name()
176
177
178 dp = pipes.get_pipe(pipe)
179
180
181 spin = return_spin(spin_id1, pipe)
182 if spin == None:
183 raise RelaxNoSpinError(spin_id1)
184 spin = return_spin(spin_id2, pipe)
185 if spin == None:
186 raise RelaxNoSpinError(spin_id2)
187
188
189 for i in range(len(dp.interatomic)):
190 if id_match(spin_id=spin_id1, interatom=dp.interatomic[i], pipe=pipe) and id_match(spin_id=spin_id2, interatom=dp.interatomic[i], pipe=pipe):
191 raise RelaxError("The spin pair %s and %s have already been added." % (spin_id1, spin_id2))
192
193
194 return dp.interatomic.add_item(spin_id1=spin_id1, spin_id2=spin_id2)
195
196
197 -def define(spin_id1=None, spin_id2=None, pipe=None, direct_bond=False, verbose=True):
198 """Set up the magnetic dipole-dipole interaction.
199
200 @keyword spin_id1: The spin identifier string of the first spin of the pair.
201 @type spin_id1: str
202 @keyword spin_id2: The spin identifier string of the second spin of the pair.
203 @type spin_id2: str
204 @param pipe: The data pipe to operate on. Defaults to the current data pipe.
205 @type pipe: str
206 @keyword direct_bond: A flag specifying if the two spins are directly bonded.
207 @type direct_bond: bool
208 @keyword verbose: A flag which if True will result in printouts of the created interatomoic data containers.
209 @type verbose: bool
210 """
211
212
213 if pipe == None:
214 pipe = pipes.cdp_name()
215
216
217 dp = pipes.get_pipe(pipe)
218
219
220 ids = []
221 for spin1, mol_name1, res_num1, res_name1, id1 in spin_loop(spin_id1, pipe=pipe, full_info=True, return_id=True):
222 for spin2, mol_name2, res_num2, res_name2, id2 in spin_loop(spin_id2, pipe=pipe, full_info=True, return_id=True):
223
224 if direct_bond:
225
226 if mol_name1 != mol_name2:
227 continue
228
229
230 if hasattr(dp, 'structure') and dp.structure.get_molecule(mol_name1, model=1):
231 if not dp.structure.are_bonded(atom_id1=id1, atom_id2=id2):
232 continue
233
234
235 else:
236
237 if not hasattr(spin1, 'element'):
238 raise RelaxError("The spin '%s' does not have the element type set." % id1)
239 if not hasattr(spin2, 'element'):
240 raise RelaxError("The spin '%s' does not have the element type set." % id2)
241
242
243 pair = False
244 if (spin1.element == 'N' and spin2.element == 'H') or (spin2.element == 'N' and spin1.element == 'H'):
245 pair = True
246 elif (spin1.element == 'C' and spin2.element == 'H') or (spin2.element == 'C' and spin1.element == 'H'):
247 pair = True
248
249
250 if pair and res_num1 != None and res_num1 != res_num2:
251 continue
252 elif pair and res_num1 == None and res_name1 != res_name2:
253 continue
254
255
256 interatom = return_interatom(id1, id2, pipe=pipe)
257
258
259 if interatom == None:
260 interatom = create_interatom(spin_id1=id1, spin_id2=id2, pipe=pipe)
261
262
263 if interatom.dipole_pair:
264 raise RelaxError("The magnetic dipole-dipole interaction already exists between the spins '%s' and '%s'." % (id1, id2))
265
266
267 interatom.dipole_pair = True
268
269
270 ids.append([repr(id1), repr(id2)])
271
272
273 if not len(ids):
274
275 count1 = 0
276 count2 = 0
277 for spin in spin_loop(spin_id1):
278 count1 += 1
279 for spin in spin_loop(spin_id2):
280 count2 += 1
281
282
283 if count1 == 0 and count2 == 0:
284 raise RelaxError("Neither spin IDs '%s' and '%s' match any spins." % (spin_id1, spin_id2))
285 elif count1 == 0:
286 raise RelaxError("The spin ID '%s' matches no spins." % spin_id1)
287 elif count2 == 0:
288 raise RelaxError("The spin ID '%s' matches no spins." % spin_id2)
289 else:
290 raise RelaxError("Unknown error.")
291
292
293 if verbose:
294 print("Interatomic interactions are now defined for the following spins:\n")
295 write_data(out=sys.stdout, headings=["Spin_ID_1", "Spin_ID_2"], data=ids)
296
297
299 """Determine if any interatomic data exists.
300
301 @keyword pipe: The data pipe in which the interatomic data will be checked for.
302 @type pipe: str
303 @return: The answer to the question about the existence of data.
304 @rtype: bool
305 """
306
307
308 if pipe == None:
309 pipe = pipes.cdp_name()
310
311
312 pipes.test(pipe)
313
314
315 dp = pipes.get_pipe(pipe)
316
317
318 if dp.interatomic.is_empty():
319 return False
320
321
322 return True
323
324
325 -def id_match(spin_id=None, interatom=None, pipe=None):
326 """Test if the spin ID matches one of the two spins of the given container.
327
328 @keyword spin_id: The spin ID string of the first atom.
329 @type spin_id: str
330 @keyword interatom: The interatomic data container.
331 @type interatom: InteratomContainer instance
332 @keyword pipe: The data pipe containing the interatomic data container. Defaults to the current data pipe.
333 @type pipe: str or None
334 @return: True if the spin ID matches one of the two spins, False otherwise.
335 @rtype: bool
336 """
337
338
339 spin1 = return_spin(interatom.spin_id1, pipe=pipe)
340 spin2 = return_spin(interatom.spin_id2, pipe=pipe)
341
342
343 if spin1 == None or spin2 == None:
344 return False
345
346
347 if spin_id in spin1._spin_ids or spin_id in spin2._spin_ids:
348 return True
349
350
351 return False
352
353
354 -def interatomic_loop(selection1=None, selection2=None, pipe=None, skip_desel=True):
355 """Generator function for looping over all the interatomic data containers.
356
357 @keyword selection1: The optional spin ID selection of the first atom.
358 @type selection1: str
359 @keyword selection2: The optional spin ID selection of the second atom.
360 @type selection2: str
361 @keyword pipe: The data pipe containing the spin. Defaults to the current data pipe.
362 @type pipe: str
363 @keyword skip_desel: A flag which if True will cause only selected interatomic data containers to be returned.
364 @type skip_desel: bool
365 """
366
367
368 if pipe == None:
369 pipe = pipes.cdp_name()
370
371
372 dp = pipes.get_pipe(pipe)
373
374
375 select_obj = None
376 select_obj1 = None
377 select_obj2 = None
378 if selection1 and selection2:
379 select_obj1 = Selection(selection1)
380 select_obj2 = Selection(selection2)
381 elif selection1:
382 select_obj = Selection(selection1)
383 elif selection2:
384 select_obj = Selection(selection2)
385
386
387 for i in range(len(dp.interatomic)):
388
389 if skip_desel and not dp.interatomic[i].select:
390 continue
391
392
393 interatom = dp.interatomic[i]
394 mol_index1, res_index1, spin_index1 = cdp.mol._spin_id_lookup[interatom.spin_id1]
395 mol_index2, res_index2, spin_index2 = cdp.mol._spin_id_lookup[interatom.spin_id2]
396 mol1 = cdp.mol[mol_index1]
397 res1 = cdp.mol[mol_index1].res[res_index1]
398 spin1 = cdp.mol[mol_index1].res[res_index1].spin[spin_index1]
399 mol2 = cdp.mol[mol_index2]
400 res2 = cdp.mol[mol_index2].res[res_index2]
401 spin2 = cdp.mol[mol_index2].res[res_index2].spin[spin_index2]
402
403
404 if select_obj:
405 sel1 = select_obj.contains_spin(spin_name=spin1.name, spin_num=spin1.num, res_name=res1.name, res_num=res1.num, mol=mol1.name)
406 sel2 = select_obj.contains_spin(spin_name=spin2.name, spin_num=spin2.num, res_name=res2.name, res_num=res2.num, mol=mol2.name)
407 if select_obj1:
408 sel11 = select_obj1.contains_spin(spin_name=spin1.name, spin_num=spin1.num, res_name=res1.name, res_num=res1.num, mol=mol1.name)
409 sel12 = select_obj1.contains_spin(spin_name=spin2.name, spin_num=spin2.num, res_name=res2.name, res_num=res2.num, mol=mol2.name)
410 if select_obj2:
411 sel21 = select_obj2.contains_spin(spin_name=spin1.name, spin_num=spin1.num, res_name=res1.name, res_num=res1.num, mol=mol1.name)
412 sel22 = select_obj2.contains_spin(spin_name=spin2.name, spin_num=spin2.num, res_name=res2.name, res_num=res2.num, mol=mol2.name)
413
414
415 if select_obj:
416 if not sel1 and not sel2:
417 continue
418 if select_obj1:
419 if not (sel11 or sel12):
420 continue
421 if select_obj2:
422 if not (sel21 or sel22):
423 continue
424
425
426 yield interatom
427
428
429 -def read_dist(file=None, dir=None, unit='meter', spin_id1_col=None, spin_id2_col=None, data_col=None, sep=None):
430 """Set up the magnetic dipole-dipole interaction.
431
432 @keyword file: The name of the file to open.
433 @type file: str
434 @keyword dir: The directory containing the file (defaults to the current directory if None).
435 @type dir: str or None
436 @keyword unit: The measurement unit. This can be either 'meter' or 'Angstrom'.
437 @type unit: str
438 @keyword spin_id1_col: The column containing the spin ID strings of the first spin.
439 @type spin_id1_col: int
440 @keyword spin_id2_col: The column containing the spin ID strings of the second spin.
441 @type spin_id2_col: int
442 @keyword data_col: The column containing the averaged distances in meters.
443 @type data_col: int or None
444 @keyword sep: The column separator which, if None, defaults to whitespace.
445 @type sep: str or None
446 """
447
448
449 if unit not in ['meter', 'Angstrom']:
450 raise RelaxError("The measurement unit of '%s' must be one of 'meter' or 'Angstrom'." % unit)
451
452
453 pipes.test()
454
455
456 if not exists_mol_res_spin_data():
457 raise RelaxNoSequenceError
458
459
460 file_data = extract_data(file, dir, sep=sep)
461 file_data = strip(file_data, comments=True)
462
463
464 data = []
465 for line in file_data:
466
467 if spin_id1_col > len(line):
468 warn(RelaxWarning("The data %s is invalid, no first spin ID column can be found." % line))
469 continue
470 if spin_id2_col > len(line):
471 warn(RelaxWarning("The data %s is invalid, no second spin ID column can be found." % line))
472 continue
473 if data_col and data_col > len(line):
474 warn(RelaxWarning("The data %s is invalid, no data column can be found." % line))
475 continue
476
477
478 spin_id1 = line[spin_id1_col-1]
479 spin_id2 = line[spin_id2_col-1]
480 ave_dist = None
481 if data_col:
482 ave_dist = line[data_col-1]
483
484
485 if ave_dist != None:
486 try:
487 ave_dist = float(ave_dist)
488 except ValueError:
489 warn(RelaxWarning("The averaged distance of '%s' from the line %s is invalid." % (ave_dist, line)))
490 continue
491
492
493 if unit == 'Angstrom':
494 ave_dist = ave_dist * 1e-10
495
496
497 interatom = return_interatom(spin_id1, spin_id2)
498
499
500 if interatom == None:
501 interatom = create_interatom(spin_id1=spin_id1, spin_id2=spin_id2, verbose=True)
502
503
504 interatom.r = ave_dist
505
506
507 data.append([repr(interatom.spin_id1), repr(interatom.spin_id2), repr(ave_dist)])
508
509
510 if not len(data):
511 raise RelaxError("No data could be extracted from the file.")
512
513
514 print("The following averaged distances have been read:\n")
515 write_data(out=sys.stdout, headings=["Spin_ID_1", "Spin_ID_2", "Ave_distance(meters)"], data=data)
516
517
519 """Return the list of interatomic data containers for the two spins.
520
521 @keyword spin_id1: The spin ID string of the first atom.
522 @type spin_id1: str
523 @keyword spin_id2: The spin ID string of the second atom.
524 @type spin_id2: str
525 @keyword pipe: The data pipe holding the container. Defaults to the current data pipe.
526 @type pipe: str or None
527 @return: The matching interatomic data container, if it exists.
528 @rtype: data.interatomic.InteratomContainer instance or None
529 """
530
531
532 if spin_id1 == None:
533 raise RelaxError("The first spin ID must be supplied.")
534 if spin_id2 == None:
535 raise RelaxError("The second spin ID must be supplied.")
536
537
538 if pipe == None:
539 pipe = pipes.cdp_name()
540
541
542 dp = pipes.get_pipe(pipe)
543
544
545 for i in range(len(dp.interatomic)):
546 if id_match(spin_id=spin_id1, interatom=dp.interatomic[i], pipe=pipe) and id_match(spin_id=spin_id2, interatom=dp.interatomic[i], pipe=pipe):
547 return dp.interatomic[i]
548
549
550 return None
551
552
585
586
587 -def set_dist(spin_id1=None, spin_id2=None, ave_dist=None, unit='meter'):
588 """Set up the magnetic dipole-dipole interaction.
589
590 @keyword spin_id1: The spin identifier string of the first spin of the pair.
591 @type spin_id1: str
592 @keyword spin_id2: The spin identifier string of the second spin of the pair.
593 @type spin_id2: str
594 @keyword ave_dist: The r^-3 averaged interatomic distance.
595 @type ave_dist: float
596 @keyword unit: The measurement unit. This can be either 'meter' or 'Angstrom'.
597 @type unit: str
598 """
599
600
601 if unit not in ['meter', 'Angstrom']:
602 raise RelaxError("The measurement unit of '%s' must be one of 'meter' or 'Angstrom'." % unit)
603
604
605 if unit == 'Angstrom':
606 ave_dist = ave_dist * 1e-10
607
608
609 sel_obj1 = Selection(spin_id1)
610 sel_obj2 = Selection(spin_id2)
611
612
613 data = []
614 for interatom in interatomic_loop():
615
616 mol_name1, res_num1, res_name1, spin1 = return_spin(interatom.spin_id1, full_info=True)
617 mol_name2, res_num2, res_name2, spin2 = return_spin(interatom.spin_id2, full_info=True)
618
619
620 if not (sel_obj1.contains_spin(spin_num=spin1.num, spin_name=spin1.name, res_num=res_num1, res_name=res_name1, mol=mol_name1) and sel_obj2.contains_spin(spin_num=spin2.num, spin_name=spin2.name, res_num=res_num2, res_name=res_name2, mol=mol_name2)) and not (sel_obj2.contains_spin(spin_num=spin1.num, spin_name=spin1.name, res_num=res_num1, res_name=res_name1, mol=mol_name1) and sel_obj1.contains_spin(spin_num=spin2.num, spin_name=spin2.name, res_num=res_num2, res_name=res_name2, mol=mol_name2)):
621 continue
622
623
624 interatom.r = ave_dist
625
626
627 data.append([repr(interatom.spin_id1), repr(interatom.spin_id2), repr(ave_dist)])
628
629
630 if not len(data):
631 raise RelaxError("No data could be set.")
632
633
634 print("The following averaged distances have been set:\n")
635 write_data(out=sys.stdout, headings=["Spin_ID_1", "Spin_ID_2", "Ave_distance(meters)"], data=data)
636
637
639 """Extract the bond vectors from the loaded structures and store them in the spin container.
640
641 @keyword ave: A flag which if True will cause the average of all vectors to be calculated.
642 @type ave: bool
643 """
644
645
646 pipes.test()
647
648
649 if not exists_data():
650 raise RelaxNoInteratomError
651
652
653 if ave:
654 print("Averaging all vectors.")
655 else:
656 print("No averaging of the vectors.")
657
658
659 no_vectors = True
660 pos_info = False
661 for interatom in interatomic_loop(skip_desel=False):
662
663 spin1 = return_spin(interatom.spin_id1)
664 spin2 = return_spin(interatom.spin_id2)
665
666
667 if not hasattr(spin1, 'pos'):
668 continue
669 if not hasattr(spin2, 'pos'):
670 continue
671
672
673 pos_info = True
674
675
676 if is_float(spin1.pos[0], raise_error=False) and is_float(spin2.pos[0], raise_error=False):
677
678 vector_list = [spin2.pos - spin1.pos]
679
680
681 elif is_float(spin1.pos[0], raise_error=False) or is_float(spin2.pos[0], raise_error=False):
682
683 if is_float(spin2.pos[0], raise_error=False):
684 vector_list = []
685 for i in range(len(spin1.pos)):
686 vector_list.append(spin2.pos - spin1.pos[i])
687
688
689 else:
690 vector_list = []
691 for i in range(len(spin2.pos)):
692 vector_list.append(spin2.pos[i] - spin1.pos)
693
694
695 else:
696
697 if len(spin1.pos) != len(spin2.pos):
698 raise RelaxError("The spin '%s' consists of %s positions whereas the spin '%s' consists of %s - these numbers must match." % (interatom.spin_id1, len(spin1.pos), interatom.spin_id1, len(spin1.pos)))
699
700
701 vector_list = []
702 for i in range(len(spin1.pos)):
703 vector_list.append(spin2.pos[i] - spin1.pos[i])
704
705
706 for i in range(len(vector_list)):
707
708 norm_factor = norm(vector_list[i])
709
710
711 if norm_factor == 0.0:
712 warn(RelaxZeroVectorWarning(spin_id1=interatom.spin_id1, spin_id2=interatom.spin_id2))
713
714
715 else:
716 vector_list[i] = vector_list[i] / norm_factor
717
718
719 if ave:
720 ave_vector = zeros(3, float64)
721 for i in range(len(vector_list)):
722 ave_vector = ave_vector + vector_list[i]
723 vector_list = [ave_vector / len(vector_list)]
724
725
726 if len(vector_list) == 1:
727 vector_list = vector_list[0]
728
729
730 setattr(interatom, 'vector', vector_list)
731
732
733 no_vectors = False
734
735
736 num = 1
737 if not is_float(vector_list[0], raise_error=False):
738 num = len(vector_list)
739 plural = 's'
740 if num == 1:
741 plural = ''
742 if spin1.name:
743 spin1_str = spin1.name
744 else:
745 spin1_str = spin1.num
746 if spin2.name:
747 spin2_str = spin2.name
748 else:
749 spin2_str = spin2.num
750 print("Calculated %s %s-%s unit vector%s between the spins '%s' and '%s'." % (num, spin1_str, spin2_str, plural, interatom.spin_id1, interatom.spin_id2))
751
752
753 if not pos_info:
754 raise RelaxError("Positional information could not be found for any spins.")
755
756
757 if no_vectors:
758 raise RelaxError("No vectors could be extracted.")
759