-
Notifications
You must be signed in to change notification settings - Fork 147
/
gromacstop.py
2078 lines (1996 loc) · 95.1 KB
/
gromacstop.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
This module contains functionality relevant to loading a GROMACS topology file
and building a Structure from it
"""
from __future__ import print_function, division, absolute_import
from collections import OrderedDict, defaultdict
from contextlib import closing
import copy
from datetime import datetime
import math
import os
import re
try:
from string import letters
except ImportError:
from string import ascii_letters as letters
import sys
import warnings
from parmed.constants import TINY, DEG_TO_RAD
from parmed.exceptions import GromacsError, GromacsWarning, ParameterError
from parmed.formats.registry import FileFormatType
from parmed.parameters import ParameterSet, _find_ureybrad_key
from parmed.gromacs._gromacsfile import GromacsFile
from parmed.structure import Structure
from parmed.topologyobjects import (Atom, Bond, Angle, Dihedral, Improper,
NonbondedException, ExtraPoint, BondType, Cmap, NoUreyBradley,
AngleType, DihedralType, DihedralTypeList, ImproperType, CmapType,
RBTorsionType, ThreeParticleExtraPointFrame, AtomType, UreyBradley,
TwoParticleExtraPointFrame, OutOfPlaneExtraPointFrame,
NonbondedExceptionType, UnassignedAtomType)
from parmed.periodic_table import element_by_mass, AtomicNum
from parmed import unit as u
from parmed.utils.io import genopen
from parmed.utils.six import add_metaclass, string_types, iteritems
from parmed.utils.six.moves import range
try:
import pwd
_username = pwd.getpwuid(os.getuid())[0]
_userid = os.getuid()
_uname = os.uname()[1]
except ImportError:
import getpass
_username = getpass.getuser() # pragma: no cover
_userid = 0 # pragma: no cover
import platform # pragma: no cover
_uname = platform.node() # pragma: no cover
# Gromacs uses "funct" flags in its parameter files to indicate what kind of
# functional form is used for each of its different parameter types. This is
# taken from the topdirs.c source code file along with a table in the Gromacs
# user manual. The table below summarizes my findings, for reference:
# Bonds
# -----
# 1 - F_BONDS : simple harmonic potential
# 2 - F_G96BONDS : fourth-power potential
# 3 - F_MORSE : morse potential
# 4 - F_CUBICBONDS : cubic potential
# 5 - F_CONNBONDS : not even implemented in GROMACS
# 6 - F_HARMONIC : seems to be the same as (1) ??
# 7 - F_FENEBONDS : finietely-extensible-nonlinear-elastic (FENE) potential
# 8 - F_TABBONDS : bond function from tabulated function
# 9 - F_TABBONDSNC : bond function from tabulated function (no exclusions)
# 10 - F_RESTRBONDS : restraint bonds
# Angles
# ------
# 1 - F_ANGLES : simple harmonic potential
# 2 - F_G96ANGLES : cosine-based angle potential
# 3 - F_CROSS_BOND_BONDS : bond-bond cross term potential
# 4 - F_CROSS_BOND_ANGLES : bond-angle cross term potential
# 5 - F_UREY_BRADLEY : Urey-Bradley angle-bond potential
# 6 - F_QUARTIC_ANGLES : 4th-order polynomial potential
# 7 - F_TABANGLES : angle function from tabulated function
# 8 - F_LINEAR_ANGLES : angle function from tabulated function
# 9 - F_RESTRANGLES : restricted bending potential
# Dihedrals
# ---------
# 1 - F_PDIHS : periodic proper torsion potential [ k(1+cos(n*phi-phase)) ]
# 2 - F_IDIHS : harmonic improper torsion potential
# 3 - F_RBDIHS : Ryckaert-Bellemans torsion potential
# 4 - F_PIDIHS : periodic harmonic improper torsion potential (same as 1)
# 5 - F_FOURDIHS : Fourier dihedral torsion potential
# 8 - F_TABDIHS : dihedral potential from tabulated function
# 9 - F_PDIHS : Same as 1, but can be multi-term
# 10 - F_RESTRDIHS : Restricted torsion potential
# 11 - F_CBTDIHS : combined bending-torsion potential
_sectionre = re.compile(r'\[ (\w+) \]\s*$')
class _Defaults(object):
""" Global properties of force fields as implemented in GROMACS """
def __init__(self, nbfunc=1, comb_rule=2, gen_pairs='no',
fudgeLJ=1.0, fudgeQQ=1.0):
if int(nbfunc) not in (1, 2):
raise ValueError('nbfunc must be 1 (L-J) or 2 (Buckingham)')
if int(comb_rule) not in (1, 2, 3):
raise ValueError('comb_rule must be 1, 2, or 3')
if gen_pairs not in ('yes', 'no'):
raise ValueError("gen_pairs must be 'yes' or 'no'")
if float(fudgeLJ) < 0:
raise ValueError('fudgeLJ must be non-negative')
if float(fudgeQQ) < 0:
raise ValueError('fudgeQQ must be non-negative')
self.nbfunc = int(nbfunc)
self.comb_rule = int(comb_rule)
self.gen_pairs = gen_pairs
self.fudgeLJ = float(fudgeLJ)
self.fudgeQQ = float(fudgeQQ)
def __repr__(self):
return ('<_Defaults: nbfunc=%d, comb-rule=%d, gen-pairs="%s", '
'fudgeLJ=%g, fudgeQQ=%g>' % (self.nbfunc, self.comb_rule,
self.gen_pairs, self.fudgeLJ, self.fudgeQQ))
def __getitem__(self, idx):
# Treat it like the array that it is in the topology file
if idx < 0: idx += 5
if idx == 0: return self.nbfunc
if idx == 1: return self.comb_rule
if idx == 2: return self.gen_pairs
if idx == 3: return self.fudgeLJ
if idx == 4: return self.fudgeQQ
raise IndexError('Index %d out of range' % idx)
def __eq__(self, other):
return (self.nbfunc == other.nbfunc and
self.comb_rule == other.comb_rule and
self.gen_pairs == other.gen_pairs and
self.fudgeLJ == other.fudgeLJ and
self.fudgeQQ == other.fudgeQQ)
def __setitem__(self, idx, value):
if idx < 0: idx += 5
if idx == 0:
if int(value) not in (1, 2):
raise ValueError('nbfunc must be 1 or 2')
self.nbfunc = int(value)
elif idx == 1:
if int(value) not in (1, 2, 3):
raise ValueError('comb_rule must be 1, 2, or 3')
self.comb_rule = int(value)
elif idx == 2:
if value not in ('yes', 'no'):
raise ValueError('gen_pairs must be "yes" or "no"')
self.gen_pairs = value
elif idx == 3:
if float(value) < 0:
raise ValueError('fudgeLJ must be non-negative')
self.fudgeLJ = float(value)
elif idx == 4:
if float(value) < 0:
raise ValueError('fudgeQQ must be non-negative')
self.fudgeQQ = value
else:
raise IndexError('Index %d out of range' % idx)
@add_metaclass(FileFormatType)
class GromacsTopologyFile(Structure):
""" Class providing a parser and writer for a GROMACS topology file
Parameters
----------
fname : str
The name of the file to read
defines : list of str=None
If specified, this is the set of defines to use when parsing the
topology file
parametrized : bool, optional
If True, parameters are assigned after parsing is done from the
parametertypes sections. If False, only parameter types defined in the
parameter sections themselves are loaded (i.e., on the same line as the
parameter was defined). Default is True
xyz : str or array, optional
The source of atomic coordinates. It can be a string containing the name
of a coordinate file from which to fill the coordinates (and optionally
the unit cell information), or it can be an array with the coordinates.
Default is None
box : array, optional
If provided, the unit cell information will be set from this variable.
If provided, it must be a collection of 6 floats representing the unit
cell dimensions a, b, c, alpha, beta, and gamma, respectively. Default
is None.
Notes
-----
If the ``xyz`` argument is a file name that contains the unit cell
information, this unit cell information is set. However, the ``box``
argument takes precedence and will override values given in the coordinate
file unless it has its default value of ``None``.
"""
#===================================================
@staticmethod
def id_format(filename):
""" Identifies the file as a GROMACS topology file
Parameters
----------
filename : str
Name of the file to check if it is a gromacs topology file
Returns
-------
is_fmt : bool
If it is identified as a gromacs topology, return True. False
otherwise
"""
with closing(genopen(filename)) as f:
for line in f:
if line.startswith(';'):
line = line[:line.index(';')]
if not line.strip(): continue
if line.startswith('#'):
if line.startswith('#if'): continue
if line.startswith('#define'): continue
if line.startswith('#include'): continue
if line.startswith('#undef'): continue
if line.startswith('#endif'): continue
return False
rematch = _sectionre.match(line)
if not rematch:
return False
sec, = rematch.groups()
return sec in ('atoms', 'atomtypes', 'defaults', 'moleculetype',
'system', 'bondtypes', 'angletypes', 'cmaptypes',
'dihedraltypes', 'bonds', 'angles', 'dihedrals',
'cmaps', 'molecules', 'exclusions',
'nonbond_params', 'position_restraints')
return False
#===================================================
def __init__(self, fname=None, defines=None, parametrize=True,
xyz=None, box=None):
from parmed import load_file
super(GromacsTopologyFile, self).__init__()
self.parameterset = None
self.defaults = _Defaults(gen_pairs='yes') # make ParmEd's default yes
if fname is not None:
self.read(fname, defines, parametrize)
# Fill in coordinates and unit cell information if appropriate
if xyz is not None:
if isinstance(xyz, string_types):
f = load_file(xyz, skip_bonds=True)
if not hasattr(f, 'coordinates') or f.coordinates is None:
raise TypeError('File %s does not have coordinates' %
xyz)
self.coordinates = f.coordinates
if box is None and hasattr(f, 'box'):
self.box = f.box
else:
self.coordinates = xyz
if box is not None:
self.box = box
self.unchange()
elif xyz is not None or box is not None:
raise ValueError('Cannot provide coordinates/box and NOT a top')
#===================================================
def read(self, fname, defines=None, parametrize=True):
""" Reads the topology file into the current instance """
from parmed import gromacs as gmx
params = self.parameterset = ParameterSet()
molecules = self.molecules = dict()
bond_types = dict()
angle_types = dict()
ub_types = dict()
dihedral_types = dict()
exc_types = dict()
structure_contents = []
if defines is None:
defines = OrderedDict(FLEXIBLE=1)
proper_multiterm_dihedrals = dict()
with closing(GromacsFile(fname, includes=[gmx.GROMACS_TOPDIR],
defines=defines)) as f:
current_section = None
for line in f:
line = line.strip()
if not line: continue
if line[0] == '[':
current_section = line[1:-1].strip()
elif current_section == 'moleculetype':
molname, nrexcl = line.split()
nrexcl = int(nrexcl)
if molname in molecules:
raise GromacsError('Duplicate definition of molecule %s'
% molname)
molecule = Structure()
molecules[molname] = (molecule, nrexcl)
molecule.nrexcl = nrexcl
bond_types = dict()
angle_types = dict()
ub_types = dict()
dihedral_types = dict()
exc_types = dict()
elif current_section == 'atoms':
molecule.add_atom(*self._parse_atoms(line, params))
elif current_section == 'bonds':
bond, bond_type = self._parse_bonds(line, bond_types,
molecule.atoms)
molecule.bonds.append(bond)
if bond_type is not None:
molecule.bond_types.append(bond_type)
bond_type.list = molecule.bond_types
elif current_section == 'pairs':
nbe, nbet = self._parse_pairs(line, exc_types,
molecule.atoms)
molecule.adjusts.append(nbe)
if nbet is not None:
molecule.adjust_types.append(nbet)
nbet.list = molecule.adjust_types
elif current_section == 'angles':
ang, ub, angt, ubt = self._parse_angles(line, angle_types,
ub_types, molecule.atoms)
molecule.angles.append(ang)
if ub is not None:
molecule.urey_bradleys.append(ub)
if angt is not None:
molecule.angle_types.append(angt)
angt.list = molecule.angle_types
if ubt is not None and ubt is not NoUreyBradley:
molecule.urey_bradley_types.append(ubt)
ubt.list = molecule.urey_bradley_types
elif current_section == 'dihedrals':
self._parse_dihedrals(line, dihedral_types,
proper_multiterm_dihedrals, molecule)
elif current_section == 'cmap':
cmap = self._parse_cmaps(line, molecule.atoms)
molecule.cmaps.append(cmap)
elif current_section == 'system':
self.title = line
elif current_section == 'defaults':
words = line.split()
if len(words) < 2: # 3, 4, and 5 fields are optional
raise GromacsError('Too few fields in [ defaults ]')
if words[0] != '1':
warnings.warn('Unsupported nonbonded type; unknown '
'functional', GromacsWarning)
self.unknown_functional = True
if words[1] in ('1', '3'):
self.combining_rule = 'geometric'
self.defaults = _Defaults(*words)
elif current_section == 'molecules':
name, num = line.split()
num = int(num)
structure_contents.append((name, num))
elif current_section == 'settles':
bnds, bndts = self._parse_settles(line, molecule.atoms)
molecule.bonds.extend(bnds)
molecule.bond_types.extend(bndts)
molecule.bond_types.claim()
elif current_section in ('virtual_sites3', 'dummies3'):
try:
b, bt = self._parse_vsites3(line, molecule.atoms, params)
except KeyError:
raise GromacsError('Cannot determine vsite geometry'
'without parameter types')
molecule.bonds.append(b)
molecule.bond_types.append(bt)
bt.list = molecule.bond_types
elif current_section == 'exclusions':
atoms = [molecule.atoms[int(w)-1] for w in line.split()]
for a in atoms[1:]:
atoms[0].exclude(a)
elif current_section == 'atomtypes':
attype, typ = self._parse_atomtypes(line)
params.atom_types[attype] = typ
elif current_section == 'nonbond_params':
words = line.split()
a1, a2 = words[:2]
# func = int(words[2]) #... unused
sig, eps = (float(x) for x in words[3:5])
sig *= 10 # Convert to Angstroms
eps *= u.kilojoule.conversion_factor_to(u.kilocalorie)
params.nbfix_types[(a1, a2)] = (eps, sig*2**(1/6))
params.nbfix_types[(a2, a1)] = (eps, sig*2**(1/6))
params.atom_types[a1].add_nbfix(a2, sig*2**(1/6), eps)
params.atom_types[a2].add_nbfix(a1, sig*2**(1/6), eps)
elif current_section == 'bondtypes':
a, b, t = self._parse_bondtypes(line)
params.bond_types[(a, b)] = t
params.bond_types[(b, a)] = t
elif current_section == 'angletypes':
a, b, c, t, ut = self._parse_angletypes(line)
params.angle_types[(a, b, c)] = t
params.angle_types[(c, b, a)] = t
if ut is not None:
params.urey_bradley_types[(a, b, c)] = ut
params.urey_bradley_types[(c, b, a)] = ut
elif current_section == 'dihedraltypes':
key, knd, t, replace = self._parse_dihedraltypes(line)
rkey = tuple(reversed(key))
if knd == 'normal':
if replace or key not in params.dihedral_types:
t = DihedralTypeList([t])
params.dihedral_types[key] = t
params.dihedral_types[rkey] = t
elif key in params.dihedral_types:
params.dihedral_types[key].append(t, override=True)
elif knd == 'improper':
params.improper_types[key] = t
elif knd == 'improper_periodic':
params.improper_periodic_types[key] = t
params.improper_periodic_types[rkey] = t
elif knd == 'rbtorsion':
params.rb_torsion_types[key] = t
params.rb_torsion_types[rkey] = t
elif current_section == 'cmaptypes':
a1, a2, a3, a4, a5, t = self._parse_cmaptypes(line)
params.cmap_types[(a1, a2, a3, a4, a2, a3, a4, a5)] = t
params.cmap_types[(a5, a4, a3, a2, a4, a3, a2, a1)] = t
elif current_section == 'pairtypes':
a, b, t = self._parse_pairtypes(line)
params.pair_types[(a, b)] = params.pair_types[(b, a)] = t
itplist = f.included_files
# Combine first, then parametrize. That way, we don't have to create
# copies of the ParameterType instances in self.parameterset
for molname, num in structure_contents:
if molname not in molecules:
raise GromacsError('Structure contains %s molecules, but no '
'template defined' % molname)
molecule, nrexcl = molecules[molname]
if nrexcl < 3 and _any_atoms_farther_than(molecule, nrexcl):
warnings.warn('nrexcl %d not currently supported' % nrexcl,
GromacsWarning)
elif nrexcl > 3 and _any_atoms_farther_than(molecule, 3):
warnings.warn('nrexcl %d not currently supported' % nrexcl,
GromacsWarning)
if num == 0:
warnings.warn('Detected addition of 0 %s molecules in topology '
'file' % molname, GromacsWarning)
if num == 1:
self += molecules[molname][0]
elif num > 1:
self += molecules[molname][0] * num
else:
raise GromacsError("Can't add %d %s molecules" % (num, molname))
self.itps = itplist
if parametrize:
self.parametrize()
#===================================================
# Private parsing helper functions
def _parse_atoms(self, line, params):
""" Parses an atom line. Returns an Atom, resname, resnum """
words = line.split()
try:
attype = params.atom_types[words[1]]
except KeyError:
attype = None
if len(words) < 8:
if attype is not None:
mass = attype.mass
atomic_number = attype.atomic_number
else:
mass = -1
atomic_number = -1
else:
mass = float(words[7])
if attype is not None and attype.atomic_number >= 0:
atomic_number = attype.atomic_number
else:
atomic_number = AtomicNum[element_by_mass(mass)]
if len(words) < 7:
charge = None
else:
charge = float(words[6])
if atomic_number == 0:
atom = ExtraPoint(name=words[4], type=words[1],
charge=charge)
else:
atom = Atom(atomic_number=atomic_number, name=words[4],
type=words[1], charge=charge, mass=mass)
return atom, words[3], int(words[2])
def _parse_bonds(self, line, bond_types, atoms):
""" Parses a bond line. Returns a Bond, BondType/None """
words = line.split()
i, j = int(words[0])-1, int(words[1])-1
funct = int(words[2])
if funct != 1:
warnings.warn('bond funct != 1; unknown functional',
GromacsWarning)
self.unknown_functional = True
bond = Bond(atoms[i], atoms[j])
bond.funct = funct
bond_type = None
if len(words) >= 5 and funct == 1:
req, k = (float(x) for x in words[3:5])
if (req, k) in bond_types:
bond.type = bond_types[(req, k)]
else:
bond_type = BondType(
k*u.kilojoule_per_mole/u.nanometer**2/2,
req*u.nanometer
)
bond_types[(req, k)] = bond.type = bond_type
return bond, bond_type
def _parse_pairs(self, line, exc_types, atoms):
""" Parses a pairs line. Returns NonbondedException, NEType/None """
words = line.split()
i, j = int(words[0])-1, int(words[1])-1
funct = int(words[2])
if funct != 1:
# This is not even supported in Gromacs
warnings.warn('pairs funct != 1; unknown functional',
GromacsWarning)
self.unknown_functional = True
nbe = NonbondedException(atoms[i], atoms[j])
nbe.funct = funct
nbet = None
if funct == 1 and len(words) >= 5:
sig = float(words[3]) * 2**(1/6)
eps = float(words[4])
if (sig, eps) in exc_types:
nbe.type = exc_types[(sig, eps)]
else:
nbet = NonbondedExceptionType(
sig*u.nanometers,
eps*u.kilojoules_per_mole,
self.defaults.fudgeQQ,
)
exc_types[(sig, eps)] = nbe.type = nbet
return nbe, nbet
def _parse_angles(self, line, angle_types, ub_types, atoms):
""" Parse an angles line, Returns Angle, UB/None, and types """
words = line.split()
i, j, k = [int(w)-1 for w in words[:3]]
funct = int(words[3])
if funct not in (1, 5):
warnings.warn('angles funct != 1 or 5; unknown '
'functional', GromacsWarning)
self.unknown_functional = True
angt = ub = ubt = None
ang = Angle(atoms[i], atoms[j], atoms[k])
ang.funct = funct
if funct == 5:
ub = UreyBradley(atoms[i], atoms[k])
if (funct == 1 and len(words) >= 6) or (funct == 5 and len(words) >= 8):
theteq, k = (float(x) for x in words[4:6])
if (theteq, k) in angle_types:
ang.type = angle_types[(theteq, k)]
else:
angt = AngleType(k*u.kilojoule_per_mole/u.radian**2/2,
theteq*u.degree)
angle_types[(theteq, k)] = ang.type = angt
if funct == 5 and len(words) >= 8:
ubreq, ubk = (float(x) for x in words[6:8])
if ubk > 0:
if (ubreq, ubk) in ub_types:
ub.type = ub_types[(ubreq, ubk)]
else:
ubt = BondType(
ubk*u.kilojoule_per_mole/u.nanometer**2/2,
ubreq*u.nanometer,
)
ub_types[(ubreq, ubk)] = ub.type = ubt
else:
ub.type = NoUreyBradley
return ang, ub, angt, ubt
def _parse_dihedrals(self, line, dihedral_types, PMD, molecule):
""" Processes a dihedrals line, returns None """
words = line.split()
i, j, k, l = [int(x)-1 for x in words[:4]]
funct = int(words[4])
if funct in (1, 4) or (funct == 9 and len(words) < 8):
dih, diht = self._process_normal_dihedral(words, molecule.atoms, i,
j, k, l, dihedral_types,
funct==4)
molecule.dihedrals.append(dih)
if diht is not None:
molecule.dihedral_types.append(diht)
diht.list = molecule.dihedral_types
elif funct == 2:
dih, impt = self._process_improper(words, i, j, k, l,
molecule.atoms, dihedral_types)
molecule.impropers.append(dih)
if impt is not None:
molecule.improper_types.append(impt)
impt.list = molecule.improper_types
elif funct == 3:
dih, rbt = self._process_rbtorsion(words, i, j, k, l, molecule.atoms,
dihedral_types)
molecule.rb_torsions.append(dih)
if rbt is not None:
molecule.rb_torsion_types.append(rbt)
rbt.list = molecule.rb_torsion_types
elif funct == 9:
# in-line parameters, since len(words) must be >= 8
key = (molecule.atoms[i], molecule.atoms[j],
molecule.atoms[k], molecule.atoms[l])
if key in PMD:
diht = PMD[key]
self._process_dihedral_series(words, diht)
dih = None
else:
dih = Dihedral(*key)
diht = self._process_dihedral_series(words)
dih.type = PMD[key] = PMD[tuple(reversed(key))] = diht
molecule.dihedrals.append(dih)
molecule.dihedral_types.append(diht)
diht.list = molecule.dihedral_types
else:
# ??? unknown funct
warnings.warn('torsions funct != 1, 2, 3, 4, 9; unknown'
' functional', GromacsWarning)
dih = Dihedral(molecule.atoms[i], molecule.atoms[j],
molecule.atoms[k], molecule.atoms[l])
molecule.dihedrals.append(dih)
self.unknown_functional = True
if dih is not None:
dih.funct = funct
def _parse_cmaps(self, line, atoms):
""" Parses cmap terms, returns cmap """
words = line.split()
i, j, k, l, m = (int(w)-1 for w in words[:5])
funct = int(words[5])
if funct != 1:
warnings.warn('cmap funct != 1; unknown functional',
GromacsWarning)
self.unknown_functional = True
cmap = Cmap(atoms[i], atoms[j], atoms[k], atoms[l], atoms[m])
cmap.funct = funct
return cmap
def _parse_settles(self, line, atoms):
""" Parses settles line; returns list of Bonds, list of BondTypes """
# Instead of adding bonds that get constrained for waters (or other
# 3-atom molecules), GROMACS uses a "settles" section to specify the
# constraint geometry. We have to translate that into bonds.
natoms = len([a for a in atoms if not isinstance(a, ExtraPoint)])
if natoms != 3:
raise GromacsError("Cannot SETTLE a %d-atom molecule" % natoms)
try:
oxy, = [atom for atom in atoms if atom.atomic_number == 8]
hyd1, hyd2 = [atom for atom in atoms if atom.atomic_number == 1]
except ValueError:
raise GromacsError('Can only SETTLE water; wrong atoms')
#TODO see if there's a bond_type entry in the parameter set
# that we can fill in? Wait until this is needed...
try:
i, funct, doh, dhh = line.split()
doh, dhh = float(doh), float(dhh)
except ValueError:
raise GromacsError('Bad [ settles ] line')
nm = u.nanometers
bt_oh = BondType(5e5*u.kilojoules_per_mole/nm**2, doh*nm)
bt_hh = BondType(5e5*u.kilojoules_per_mole/nm**2, dhh*nm)
return [Bond(oxy, hyd1, bt_oh), Bond(oxy, hyd2, bt_oh),
Bond(hyd1, hyd2, bt_hh)], [bt_oh, bt_hh]
def _parse_vsites3(self, line, all_atoms, params):
""" Parse vsites3/dummy3 line; returns Bond, BondType """
words = line.split()
vsite = all_atoms[int(words[0])-1]
atoms = [all_atoms[int(i)-1] for i in words[1:4]]
funct = int(words[4])
if funct == 1:
a, b = float(words[5]), float(words[6])
if abs(a - b) > TINY:
raise GromacsError("No vsite frames with different weights")
else:
raise GromacsError('Only 3-point vsite type 1 is supported')
# We need to know the geometry of the frame in order to
# determine the bond length between the virtual site and its
# parent atom
parent = atoms[0]
if vsite in parent.bond_partners:
raise GromacsError('Unexpected bond b/w vsite and its parent')
kws = dict()
for bond in parent.bonds:
if atoms[1] in bond:
key = (_gettype(parent), _gettype(atoms[1]))
kws['dp1'] = (bond.type or params.bond_types[key]).req
if atoms[2] in bond:
key = (_gettype(bond.atom1), _gettype(bond.atom2))
kws['dp2'] = (bond.type or params.bond_types[key]).req
for angle in parent.angles:
if parent is not angle.atom2: continue
if atoms[0] not in angle or atoms[1] not in angle: continue
key = (_gettype(angle.atom1), _gettype(angle.atom2),
_gettype(angle.atom3))
kws['theteq'] = (angle.type or params.angle_types[key]).theteq
break
else: # Did not break, no theta found
for bond in atoms[1].bonds:
if atoms[2] in bond:
key = (_gettype(bond.atom1), _gettype(bond.atom2))
kws['d12'] = (bond.type or params.bond_types[key]).req
bondlen = ThreeParticleExtraPointFrame.from_weights(parent, atoms[1],
atoms[2], a, b, **kws)
bt_vs = BondType(0, bondlen*u.angstroms)
return Bond(vsite, parent, bt_vs), bt_vs
def _parse_atomtypes(self, line):
""" Parses line from atomtypes section, returns str, AtomType """
words = line.split()
# Support the following spec, found in the Gromacs source
# code:
# Field 0 (mandatory) : nonbonded type name (string)
# Field 1 (optional) : bonded type (string)
# Field 2 (optional) : atomic number (int)
# Field 3 (mandatory) : mass (float)
# Field 4 (mandatory) : charge (float)
# Field 5 (mandatory) : particle type (single character)
attype = words[0]
if len(words[3]) == 1 and words[3] in letters:
atnum = -1
sigidx = 4
# ptypeidx = 3 # ... unused
massidx = 1
bond_type = None
elif len(words[5]) == 1 and words[5] in letters:
sigidx = 6
# ptypeidx = 5 # ... unused
massidx = 3
atnum = int(words[2])
bond_type = words[1]
else:
# ptypeidx = 4 # ... unused
massidx = 2
sigidx = 5
try:
atnum = int(words[1])
bond_type = None
except ValueError:
# This must be a bonded type string
bond_type = words[1]
atnum = -1
mass = float(words[massidx])
if mass > 0 and atnum == -1:
atnum = AtomicNum[element_by_mass(mass)]
chg = float(words[massidx+1])
# ptype = words[ptypeidx] # ... unused
sig = float(words[sigidx]) * u.nanometers
eps = float(words[sigidx+1]) * u.kilojoules_per_mole
typ = AtomType(attype, None, mass, atnum,
bond_type=bond_type, charge=chg)
typ.set_lj_params(eps, sig*2**(1/6)/2)
return attype, typ
def _parse_bondtypes(self, line):
""" Parse bondtypes line. Returns str, str, BondType """
words = line.split()
r = float(words[3]) * u.nanometers
k = (float(words[4]) / 2) * (u.kilojoules_per_mole / u.nanometers**2)
if words[2] != '1':
warnings.warn('bondtypes funct != 1; unknown functional',
GromacsWarning)
self.unknown_functional = True
return words[0], words[1], BondType(k, r)
def _parse_angletypes(self, line):
"""
Parses angletypes line. Returns str, str, str, AngleType, BondType/None
"""
words = line.split()
theta = float(words[4]) * u.degrees
k = (float(words[5]) / 2) * (u.kilojoules_per_mole / u.radians**2)
if words[3] != '1' and words[3] != '5':
warnings.warn('angletypes funct != 1 or 5; unknown functional',
GromacsWarning)
self.unknown_functional = True
ub = None
if words[3] == '5':
# Contains the angle with urey-bradley
ub0 = float(words[6])
cub = float(words[7]) / 2
if cub == 0:
ub = NoUreyBradley
else:
ub0 *= u.nanometers
cub *= u.kilojoules_per_mole / u.nanometers**2
ub = BondType(cub, ub0)
return words[0], words[1], words[2], AngleType(k, theta), ub
def _parse_dihedraltypes(self, line):
""" Parse dihedraltypes, returns (str,str,str,str), str, Type, bool """
words = line.split()
replace = False
dtype = 'normal'
# Ugh. Gromacs allows only two atom types (the middle atom types) to be
# specified. This signifies wild-cards
if words[2] in ('1', '2', '3', '4', '5', '8', '9', '10', '11'):
a1 = a4 = 'X'
a2, a3 = words[:2]
si = 2
else:
a1, a2, a3, a4 = words[:4]
si = 4
improper_periodic = False
replace = words[si] in ('1', '2', '3', '4')
improper_periodic = words[si] == '4'
if words[si] == '2':
dtype = 'improper'
elif words[si] == '3':
dtype = 'rbtorsion'
elif words[si] not in ('1', '4', '9'):
warnings.warn('dihedraltypes funct not supported', GromacsWarning)
self.unknown_functional = True
# Do the proper types
if dtype == 'normal':
phase = float(words[si+1]) * u.degrees
phi_k = float(words[si+2]) * u.kilojoules_per_mole
per = int(words[si+3])
ptype = DihedralType(phi_k, per, phase,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
if improper_periodic:
# must do this here, since dtype has to be 'normal' above
dtype = 'improper_periodic'
elif dtype == 'improper':
theta = float(words[si+1])*u.degrees
k = float(words[si+2])*u.kilojoules_per_mole/u.radians**2/2
a1, a2, a3, a4 = sorted([a1, a2, a3, a4])
ptype = ImproperType(k, theta)
elif dtype == 'rbtorsion':
a1, a2, a3, a4 = words[:4]
c0, c1, c2, c3, c4, c5 = (float(x)*u.kilojoules_per_mole
for x in words[si+1:si+7])
ptype = RBTorsionType(c0, c1, c2, c3, c4, c5,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
return (a1, a2, a3, a4), dtype, ptype, replace
def _parse_cmaptypes(self, line):
words = line.split()
a1, a2, a3, a4, a5 = words[:5]
# funct = int(words[5]) # ... unused
res1, res2 = int(words[6]), int(words[7])
grid = [float(w) for w in words[8:]] * u.kilojoules_per_mole
if len(grid) != res1 * res2:
raise GromacsError('CMAP grid dimensions do not match resolution')
if res1 != res2:
raise GromacsError('Only square CMAPs are supported')
return a1, a2, a3, a4, a5, CmapType(res1, grid)
def _parse_pairtypes(self, line):
words = line.split()
a1, a2 = words[:2]
# funct = int(words[2]) # ... unused
cs6, cs12 = (float(x) for x in words[3:5])
cs6 *= u.nanometers * 2**(1/6)
cs12 *= u.kilojoules_per_mole
return a1, a2, NonbondedExceptionType(cs6, cs12, self.defaults.fudgeQQ)
#===================================================
# Internal Dihedral processing routines for different kinds of dihedrals
def _process_normal_dihedral(self, words, atoms, i, j, k, l,
dihedral_types, imp):
dih = Dihedral(atoms[i], atoms[j], atoms[k], atoms[l], improper=imp)
diht = None
if len(words) >= 8:
phase, phi_k, per = (float(x) for x in words[5:8])
if (phase, phi_k, per) in dihedral_types:
dih.type = dihedral_types[(phase, phi_k, per)]
else:
diht = DihedralType(phi_k*u.kilojoule_per_mole,
per, phase*u.degrees,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
dihedral_types[(phase, phi_k, per)] = dih.type = diht
return dih, diht
def _process_dihedral_series(self, words, dihtype=None):
phase, phi_k, per = (float(x) for x in words[5:8])
dt = DihedralType(phi_k*u.kilojoule_per_mole,
per, phase*u.degrees,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
if dihtype is not None:
dihtype.append(dt)
dtl = None
else:
dt = DihedralType(phi_k*u.kilojoule_per_mole,
per, phase*u.degrees,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
dtl = DihedralTypeList()
dtl.append(dt)
return dtl
def _process_improper(self, words, i, j, k, l, atoms, dihedral_types):
""" Processes an improper, returns Improper, ImproperType """
# Improper
imp = Improper(atoms[i], atoms[j], atoms[k], atoms[l])
impt = None
if len(words) >= 7:
psieq, k = (float(x) for x in words[5:7])
if (psieq, k) in dihedral_types:
imp.type = dihedral_types[(psieq, k)]
else:
impt = ImproperType(k*u.kilojoule_per_mole/u.radian**2/2,
psieq*u.degree)
imp.type = dihedral_types[(psieq, k)] = impt
return imp, impt
def _process_rbtorsion(self, words, i, j, k, l, atoms, dihedral_types):
rb = Dihedral(atoms[i], atoms[j], atoms[k], atoms[l])
rbt = None
if len(words) >= 11:
c0, c1, c2, c3, c4, c5 = (float(x) for x in words[5:11])
if (c0, c1, c2, c3, c4, c5) in dihedral_types:
rb.type = dihedral_types[(c0, c1, c2, c3, c4, c5)]
else:
kjpm = u.kilojoules_per_mole
rbt = RBTorsionType(c0*kjpm, c1*kjpm, c2*kjpm,
c3*kjpm, c4*kjpm, c5*kjpm,
scee=1/self.defaults.fudgeQQ,
scnb=1/self.defaults.fudgeLJ)
dihedral_types[(c0, c1, c2, c3, c4, c5)] = rb.type = rbt
return rb, rbt
#===================================================
def parametrize(self):
"""
Assign parameters to the current structure. This should be called
*after* `read`
"""
if self.parameterset is None:
raise RuntimeError('parametrize called before read')
params = copy.copy(self.parameterset)
def update_typelist_from(ptypes, types):
added_types = set(id(typ) for typ in types)
for k, typ in iteritems(ptypes):
if not typ.used: continue
if id(typ) in added_types: continue
added_types.add(id(typ))
types.append(typ)
types.claim()
# Assign all of the parameters. If they've already been assigned (i.e.,
# on the parameter line itself) keep the existing parameters
for atom in self.atoms:
atom.atom_type = params.atom_types[atom.type]
# The list of ordered 2-tuples of atoms explicitly specified in [ pairs ].
# Under most circumstances, this is the list of 1-4 pairs.
gmx_pair = set()
for pair in self.adjusts:
if pair.atom1 > pair.atom2:
gmx_pair.add((pair.atom2, pair.atom1))
else:
gmx_pair.add((pair.atom1, pair.atom2))
if pair.type is not None: continue
key = (_gettype(pair.atom1), _gettype(pair.atom2))
if key in params.pair_types:
pair.type = params.pair_types[key]
pair.type.used = True
elif self.defaults.gen_pairs == 'yes':
assert self.combining_rule in ('geometric', 'lorentz'), \
'Unrecognized combining rule'
if self.combining_rule == 'geometric':
eps = math.sqrt(pair.atom1.epsilon * pair.atom2.epsilon)
sig = math.sqrt(pair.atom1.sigma * pair.atom2.sigma)
elif self.combining_rule == 'lorentz':
eps = math.sqrt(pair.atom1.epsilon * pair.atom2.epsilon)
sig = 0.5 * (pair.atom1.sigma + pair.atom2.sigma)
eps *= self.defaults.fudgeLJ
pairtype = NonbondedExceptionType(sig*2**(1/6), eps,
self.defaults.fudgeQQ, list=self.adjust_types)
self.adjust_types.append(pairtype)
pair.type = pairtype
pair.type.used = True
else:
raise ParameterError('Not all pair parameters can be found')
update_typelist_from(params.pair_types, self.adjust_types)
# This is the list of 1-4 pairs determined from the bond graph.
# If this is different from what's in [ pairs ], we print a warning
# and make some adjustments (specifically, other programs assume
# the 1-4 list is complete, so we zero out the parameters for
# 1-4 pairs that aren't in [ pairs ].
true_14 = set()
for bond in self.bonds:
for bpi in bond.atom1.bond_partners:
for bpj in bond.atom2.bond_partners:
if len(set([bpi, bond.atom1, bond.atom2, bpj])) < 4:
continue
if bpi in bpj.bond_partners or bpi in bpj.angle_partners:
continue
if bpi > bpj:
true_14.add((bpj, bpi))