Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
tsoc
openmm
Commits
ccc0ba85
Commit
ccc0ba85
authored
Nov 07, 2011
by
Peter Eastman
Browse files
Moved OpenMM app into the main OpenMM project
parent
7bf291e0
Changes
16
Show whitespace changes
Inline
Side-by-side
Showing
16 changed files
with
6828 additions
and
2 deletions
+6828
-2
wrappers/python/CMakeLists.txt
wrappers/python/CMakeLists.txt
+1
-0
wrappers/python/setup.py
wrappers/python/setup.py
+6
-2
wrappers/python/simtk/openmm/app/__init__.py
wrappers/python/simtk/openmm/app/__init__.py
+35
-0
wrappers/python/simtk/openmm/app/amberinpcrdfile.py
wrappers/python/simtk/openmm/app/amberinpcrdfile.py
+37
-0
wrappers/python/simtk/openmm/app/amberprmtopfile.py
wrappers/python/simtk/openmm/app/amberprmtopfile.py
+107
-0
wrappers/python/simtk/openmm/app/dcdfile.py
wrappers/python/simtk/openmm/app/dcdfile.py
+84
-0
wrappers/python/simtk/openmm/app/dcdreporter.py
wrappers/python/simtk/openmm/app/dcdreporter.py
+51
-0
wrappers/python/simtk/openmm/app/element.py
wrappers/python/simtk/openmm/app/element.py
+147
-0
wrappers/python/simtk/openmm/app/forcefield.py
wrappers/python/simtk/openmm/app/forcefield.py
+3957
-0
wrappers/python/simtk/openmm/app/internal/__init__.py
wrappers/python/simtk/openmm/app/internal/__init__.py
+0
-0
wrappers/python/simtk/openmm/app/internal/amber_file_parser.py
...ers/python/simtk/openmm/app/internal/amber_file_parser.py
+836
-0
wrappers/python/simtk/openmm/app/internal/pdbstructure.py
wrappers/python/simtk/openmm/app/internal/pdbstructure.py
+990
-0
wrappers/python/simtk/openmm/app/pdbfile.py
wrappers/python/simtk/openmm/app/pdbfile.py
+214
-0
wrappers/python/simtk/openmm/app/pdbreporter.py
wrappers/python/simtk/openmm/app/pdbreporter.py
+56
-0
wrappers/python/simtk/openmm/app/simulation.py
wrappers/python/simtk/openmm/app/simulation.py
+91
-0
wrappers/python/simtk/openmm/app/topology.py
wrappers/python/simtk/openmm/app/topology.py
+216
-0
No files found.
wrappers/python/CMakeLists.txt
View file @
ccc0ba85
...
@@ -36,6 +36,7 @@ foreach(SUBDIR ${SUBDIRS})
...
@@ -36,6 +36,7 @@ foreach(SUBDIR ${SUBDIRS})
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.py"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.py"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.i"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.i"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.sh"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.sh"
"
${
CMAKE_CURRENT_SOURCE_DIR
}
/
${
SUBDIR
}
/*.xml"
)
)
foreach
(
file
${
STAGING_INPUT_FILES1
}
)
foreach
(
file
${
STAGING_INPUT_FILES1
}
)
set
(
STAGING_INPUT_FILES
${
STAGING_INPUT_FILES
}
"
${
file
}
"
)
set
(
STAGING_INPUT_FILES
${
STAGING_INPUT_FILES
}
"
${
file
}
"
)
...
...
wrappers/python/setup.py
View file @
ccc0ba85
...
@@ -81,11 +81,15 @@ def buildKeywordDictionary(major_version_num=MAJOR_VERSION_NUM,
...
@@ -81,11 +81,15 @@ def buildKeywordDictionary(major_version_num=MAJOR_VERSION_NUM,
setupKeywords
[
"download_url"
]
=
"https://simtk.org/home/openmm"
setupKeywords
[
"download_url"
]
=
"https://simtk.org/home/openmm"
setupKeywords
[
"packages"
]
=
[
"simtk"
,
setupKeywords
[
"packages"
]
=
[
"simtk"
,
"simtk.unit"
,
"simtk.unit"
,
"simtk.openmm"
]
"simtk.openmm"
,
"simtk.openmm.app"
,
"simtk.openmm.app.internal"
]
setupKeywords
[
"data_files"
]
=
[]
setupKeywords
[
"data_files"
]
=
[]
setupKeywords
[
"package_data"
]
=
{
"simtk"
:
[],
setupKeywords
[
"package_data"
]
=
{
"simtk"
:
[],
"simtk.unit"
:
[],
"simtk.unit"
:
[],
"simtk.openmm"
:
[]}
"simtk.openmm"
:
[],
"simtk.openmm.app"
:
[
'data/*.xml'
],
"simtk.openmm.app.internal"
:
[]}
setupKeywords
[
"platforms"
]
=
[
"Linux"
,
"Mac OS X"
,
"Windows"
]
setupKeywords
[
"platforms"
]
=
[
"Linux"
,
"Mac OS X"
,
"Windows"
]
setupKeywords
[
"description"
]
=
\
setupKeywords
[
"description"
]
=
\
"Python wrapper for OpenMM (a C++ MD package)"
"Python wrapper for OpenMM (a C++ MD package)"
...
...
wrappers/python/simtk/openmm/app/__init__.py
0 → 100644
View file @
ccc0ba85
"""
OpenMM Application
"""
__docformat__
=
"epytext en"
__author__
=
"Peter Eastman"
__copyright__
=
"Copyright 2011, Stanford University and Peter Eastman"
__credits__
=
[]
__license__
=
"MIT"
__maintainer__
=
"Peter Eastman"
__email__
=
"peastman@stanford.edu"
from
topology
import
Topology
,
Chain
,
Residue
,
Atom
from
pdbfile
import
PDBFile
from
forcefield
import
ForceField
from
simulation
import
Simulation
from
pdbreporter
import
PDBReporter
from
amberprmtopfile
import
AmberPrmtopFile
from
amberinpcrdfile
import
AmberInpcrdFile
from
dcdfile
import
DCDFile
from
dcdreporter
import
DCDReporter
# Enumerated values
NoCutoff
=
forcefield
.
NoCutoff
CutoffNonPeriodic
=
forcefield
.
CutoffNonPeriodic
CutoffPeriodic
=
forcefield
.
CutoffPeriodic
Ewald
=
forcefield
.
Ewald
PME
=
forcefield
.
PME
HBonds
=
forcefield
.
HBonds
AllBonds
=
forcefield
.
AllBonds
HAngles
=
forcefield
.
HAngles
OBC
=
amberprmtopfile
.
OBC
wrappers/python/simtk/openmm/app/amberinpcrdfile.py
0 → 100644
View file @
ccc0ba85
"""
armberinpcrdfile.py: Used for loading AMBER inpcrd files.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
from
simtk.openmm.app.internal
import
amber_file_parser
class
AmberInpcrdFile
(
object
):
"""AmberInpcrdFile parses an AMBER inpcrd file and loads the data stored in it."""
def
__init__
(
self
,
file
,
loadVelocities
=
False
,
loadBoxVectors
=
False
):
"""Load an inpcrd file.
An inpcrd file contains atom positions and, optionally, velocities and periodic box dimensions.
Unfortunately, it is sometimes impossible to determine from the file itself exactly what data
it contains. You therefore must specify in advance what data to load. It is stored into this
object's "positions", "velocities", and "boxVectors" fields.
Parameters:
- file (string) the name of the file to load
- loadVelocities (boolean=False) whether to load velocities from the file
- loadBoxVectors (boolean=False) whether to load the periodic box vectors
"""
results
=
amber_file_parser
.
readAmberCoordinates
(
file
,
read_velocities
=
loadVelocities
,
read_box
=
loadBoxVectors
)
if
loadVelocities
:
self
.
positions
=
results
[
0
]
if
loadBoxVectors
:
self
.
boxVectors
=
results
[
1
]
self
.
velocities
=
results
[
2
]
else
:
self
.
velocities
=
results
[
1
]
elif
loadBoxVectors
:
self
.
positions
=
results
[
0
]
self
.
boxVectors
=
results
[
1
]
else
:
self
.
positions
=
results
wrappers/python/simtk/openmm/app/amberprmtopfile.py
0 → 100644
View file @
ccc0ba85
"""
armberprmtopfile.py: Used for loading AMBER prmtop files.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
from
simtk.openmm.app
import
Topology
from
simtk.openmm.app.internal
import
amber_file_parser
import
forcefield
as
ff
import
element
as
elem
import
simtk.unit
as
unit
# Enumerated values for implicit solvent model
OBC
=
object
()
class
AmberPrmtopFile
(
object
):
"""AmberPrmtopFile parses an AMBER prmtop file and constructs a Topology and (optionally) an OpenMM System from it."""
def
__init__
(
self
,
file
):
"""Load a prmtop file."""
top
=
Topology
()
self
.
topology
=
top
# Load the prmtop file
prmtop
=
amber_file_parser
.
PrmtopLoader
(
file
)
self
.
prmtop
=
prmtop
# Add atoms to the topology
lastResidue
=
None
c
=
top
.
addChain
()
for
index
in
range
(
prmtop
.
getNumAtoms
()):
resNumber
=
prmtop
.
getResidueNumber
(
index
)
if
resNumber
!=
lastResidue
:
lastResidue
=
resNumber
resName
=
prmtop
.
getResidueLabel
(
iAtom
=
index
)
r
=
top
.
addResidue
(
resName
,
c
)
atomName
=
prmtop
.
getAtomName
(
index
)
# Try to guess the element.
upper
=
atomName
.
upper
()
if
upper
.
startswith
(
'CL'
):
element
=
elem
.
chlorine
elif
upper
.
startswith
(
'NA'
):
element
=
elem
.
sodium
elif
upper
.
startswith
(
'MG'
):
element
=
elem
.
magnesium
else
:
try
:
element
=
elem
.
get_by_symbol
(
atomName
[
0
])
except
KeyError
:
pass
top
.
addAtom
(
atomName
,
element
,
r
)
# Add bonds to the topology
for
bond
in
prmtop
.
getBondsWithH
():
top
.
addBond
(
bond
[
0
],
bond
[
1
])
for
bond
in
prmtop
.
getBondsNoH
():
top
.
addBond
(
bond
[
0
],
bond
[
1
])
def
createSystem
(
self
,
nonbondedMethod
=
ff
.
NoCutoff
,
nonbondedCutoff
=
1.0
*
unit
.
nanometer
,
constraints
=
None
,
rigidWater
=
True
,
implicitSolvent
=
None
):
"""Construct an OpenMM System representing the topology described by this prmtop file.
Parameters:
- nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
- nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions
- constraints (object=None) Specifies which bonds angles should be implemented with constraints.
Allowed values are None, HBonds, AllBonds, or HAngles.
- rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument
- implicitSolvent (object=None) If not None, the implicit solvent model to use
Returns: the newly created System
"""
methodMap
=
{
ff
.
NoCutoff
:
'NoCutoff'
,
ff
.
CutoffNonPeriodic
:
'CutoffNonPeriodic'
,
ff
.
CutoffPeriodic
:
'CutoffPeriodic'
,
ff
.
Ewald
:
'Ewald'
,
ff
.
PME
:
'PME'
}
if
nonbondedMethod
not
in
methodMap
:
raise
ValueError
(
'Illegal value for nonbonded method'
)
if
not
self
.
prmtop
.
getIfBox
()
and
nonbondedMethod
in
(
ff
.
CutoffPeriodic
,
ff
.
Ewald
,
ff
.
PME
):
raise
ValueError
(
'Illegal nonbonded method for a non-periodic system'
)
if
nonbondedMethod
==
ff
.
NoCutoff
:
nonbondedCutoff
=
None
constraintMap
=
{
None
:
None
,
ff
.
HBonds
:
'h-bonds'
,
ff
.
AllBonds
:
'all-bonds'
,
ff
.
HAngles
:
'h-angles'
}
if
constraints
==
ff
.
NoConstraints
:
constraintString
=
None
elif
constraints
in
constraintMap
:
constraintString
=
constraintMap
[
constraints
]
else
:
raise
ValueError
(
'Illegal value for constraints'
)
if
implicitSolvent
is
None
:
implicitString
=
None
elif
implicitSolvent
==
OBC
:
implicitString
=
'OBC'
else
:
raise
ValueError
(
'Illegal value for implicit solvent model'
)
return
amber_file_parser
.
readAmberSystem
(
prmtop_loader
=
self
.
prmtop
,
shake
=
constraintString
,
nonbondedCutoff
=
nonbondedCutoff
,
nonbondedMethod
=
methodMap
[
nonbondedMethod
],
flexibleConstraints
=
False
,
gbmodel
=
implicitString
)
\ No newline at end of file
wrappers/python/simtk/openmm/app/dcdfile.py
0 → 100644
View file @
ccc0ba85
"""
dcdfile.py: Used for writing DCD files.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
array
import
os
import
time
import
struct
from
simtk.unit
import
picoseconds
,
nanometers
,
angstroms
,
is_quantity
class
DCDFile
(
object
):
"""DCDFile provides methods for creating DCD files.
DCD is a file format for storing simulation trajectories. It is supported by many programs, such
as CHARMM, NAMD, and X-PLOR. Note, however, that different programs produce subtly different
versions of the format. This class generates the CHARMM version. Also note that there is no
standard byte ordering (big-endian or little-endian) for this format. This class always generates
files with little-endian ordering.
To use this class, create a DCDFile object, then call writeModel() once for each model in the file."""
def
__init__
(
self
,
file
,
topology
,
dt
,
firstStep
=
0
,
interval
=
1
):
"""Create a DCD file and write out the header.
Parameters:
- file (file) A file to write to
- topology (Topology) The Topology defining the molecular system being written
- dt (time) The time step used in the trajectory
- firstStep (int=0) The index of the first step in the trajectory
- interval (int=1) The frequency (measured in time steps) at which states are written to the trajectory
"""
self
.
_file
=
file
self
.
_topology
=
topology
self
.
_firstStep
=
firstStep
self
.
_interval
=
interval
self
.
_modelCount
=
0
if
is_quantity
(
dt
):
dt
=
dt
.
value_in_unit
(
picoseconds
)
dt
/=
0.04888821
boxFlag
=
0
if
topology
.
getUnitCellDimensions
()
is
not
None
:
boxFlag
=
1
header
=
struct
.
pack
(
'<i4c9if'
,
84
,
'C'
,
'O'
,
'R'
,
'D'
,
0
,
firstStep
,
interval
,
0
,
0
,
0
,
0
,
0
,
0
,
dt
)
header
+=
struct
.
pack
(
'<13i'
,
boxFlag
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
0
,
24
,
84
,
164
,
2
)
header
+=
struct
.
pack
(
'<80s'
,
'Created by OpenMM'
)
header
+=
struct
.
pack
(
'<80s'
,
'Created '
+
time
.
asctime
(
time
.
localtime
(
time
.
time
())))
header
+=
struct
.
pack
(
'<4i'
,
164
,
4
,
len
(
list
(
topology
.
atoms
())),
4
)
file
.
write
(
header
)
def
writeModel
(
self
,
positions
):
"""Write out a model to the DCD file.
Parameters:
- positions (list) The list of atomic positions to write
"""
if
len
(
list
(
self
.
_topology
.
atoms
()))
!=
len
(
positions
):
raise
ValueError
(
'The number of positions must match the number of atoms'
)
if
is_quantity
(
positions
):
positions
=
positions
.
value_in_unit
(
nanometers
)
file
=
self
.
_file
# Update the header.
self
.
_modelCount
+=
1
file
.
seek
(
8
,
os
.
SEEK_SET
)
file
.
write
(
struct
.
pack
(
'<i'
,
self
.
_modelCount
))
file
.
seek
(
20
,
os
.
SEEK_SET
)
file
.
write
(
struct
.
pack
(
'<i'
,
self
.
_firstStep
+
self
.
_modelCount
*
self
.
_interval
))
# Write the data.
file
.
seek
(
0
,
os
.
SEEK_END
)
boxSize
=
self
.
_topology
.
getUnitCellDimensions
()
if
boxSize
is
not
None
:
size
=
boxSize
.
value_in_unit
(
angstroms
)
file
.
write
(
struct
.
pack
(
'<i6di'
,
48
,
size
[
0
],
0
,
size
[
1
],
0
,
0
,
size
[
2
],
48
))
length
=
struct
.
pack
(
'<i'
,
4
*
len
(
positions
))
for
i
in
range
(
3
):
file
.
write
(
length
)
data
=
array
.
array
(
'f'
,
(
10
*
x
[
i
]
for
x
in
positions
))
data
.
tofile
(
file
)
file
.
write
(
length
)
wrappers/python/simtk/openmm/app/dcdreporter.py
0 → 100644
View file @
ccc0ba85
"""
dcdreporter.py: Outputs simulation trajectories in DCD format
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
simtk.openmm
as
mm
from
simtk.openmm.app
import
DCDFile
class
DCDReporter
(
object
):
"""DCDReporter outputs a series of frames from a Simulation to a DCD file.
To use it, create a DCDReporter, than add it to the Simulation's list of reporters.
"""
def
__init__
(
self
,
file
,
reportInterval
):
"""Create a DCDReporter.
Parameters:
- file (string) The file to write to
- reportInterval (int) The interval (in time steps) at which to write frames
"""
self
.
_reportInterval
=
reportInterval
self
.
_out
=
open
(
file
,
'wb'
)
self
.
_dcd
=
None
def
describeNextReport
(
self
,
simulation
):
"""Get information about the next report this object will generate.
Parameters:
- simulation (Simulation) The Simulation to generate a report for
Returns: A five element tuple. The first element is the number of steps until the
next report. The remaining elements specify whether that report will require
positions, velocities, forces, and energies respectively.
"""
steps
=
self
.
_reportInterval
-
simulation
.
currentStep
%
self
.
_reportInterval
return
(
steps
,
True
,
False
,
False
,
False
)
def
report
(
self
,
simulation
,
state
):
"""Generate a report.
Parameters:
- simulation (Simulation) The Simulation to generate a report for
- state (State) The current state of the simulation
"""
if
self
.
_dcd
is
None
:
self
.
_dcd
=
DCDFile
(
self
.
_out
,
simulation
.
topology
,
simulation
.
integrator
.
getStepSize
(),
0
,
self
.
_reportInterval
)
self
.
_dcd
.
writeModel
(
state
.
getPositions
())
def
__del__
(
self
):
self
.
_out
.
close
()
wrappers/python/simtk/openmm/app/element.py
0 → 100644
View file @
ccc0ba85
#!/bin/env python
"""
element.py: Used for managing elements.
"""
__author__
=
"Christopher M. Bruns"
__version__
=
"1.0"
from
simtk.unit
import
daltons
class
Element
:
elements_by_symbol
=
{}
def
__init__
(
self
,
number
,
name
,
symbol
,
mass
):
self
.
atomic_number
=
number
self
.
name
=
name
self
.
symbol
=
symbol
self
.
mass
=
mass
# Index this element in a global table
s
=
symbol
.
strip
().
upper
()
assert
s
not
in
Element
.
elements_by_symbol
Element
.
elements_by_symbol
[
s
]
=
self
def
get_by_symbol
(
symbol
):
s
=
symbol
.
strip
().
upper
()
return
Element
.
elements_by_symbol
[
s
]
hydrogen
=
Element
(
1
,
"hydrogen"
,
"H"
,
1.007947
*
daltons
)
deuterium
=
Element
(
1
,
"deuterium"
,
"D"
,
2.01355321270
*
daltons
)
helium
=
Element
(
2
,
"helium"
,
"He"
,
4.003
*
daltons
)
lithium
=
Element
(
3
,
"lithium"
,
"Li"
,
6.9412
*
daltons
)
beryllium
=
Element
(
4
,
"beryllium"
,
"Be"
,
9.0121823
*
daltons
)
boron
=
Element
(
5
,
"boron"
,
"B"
,
10.8117
*
daltons
)
carbon
=
Element
(
6
,
"carbon"
,
"C"
,
12.01078
*
daltons
)
nitrogen
=
Element
(
7
,
"nitrogen"
,
"N"
,
14.00672
*
daltons
)
oxygen
=
Element
(
8
,
"oxygen"
,
"O"
,
15.99943
*
daltons
)
fluorine
=
Element
(
9
,
"fluorine"
,
"F"
,
18.99840325
*
daltons
)
neon
=
Element
(
10
,
"neon"
,
"Ne"
,
20.17976
*
daltons
)
sodium
=
Element
(
11
,
"sodium"
,
"Na"
,
22.989769282
*
daltons
)
magnesium
=
Element
(
12
,
"magnesium"
,
"Mg"
,
24.30506
*
daltons
)
aluminum
=
Element
(
13
,
"aluminum"
,
"Al"
,
26.98153868
*
daltons
)
silicon
=
Element
(
14
,
"silicon"
,
"Si"
,
28.08553
*
daltons
)
phosphorus
=
Element
(
15
,
"phosphorus"
,
"P"
,
30.9737622
*
daltons
)
sulfur
=
Element
(
16
,
"sulfur"
,
"S"
,
32.0655
*
daltons
)
chlorine
=
Element
(
17
,
"chlorine"
,
"Cl"
,
35.4532
*
daltons
)
argon
=
Element
(
18
,
"argon"
,
"Ar"
,
39.9481
*
daltons
)
potassium
=
Element
(
19
,
"potassium"
,
"K"
,
39.09831
*
daltons
)
calcium
=
Element
(
20
,
"calcium"
,
"Ca"
,
40.0784
*
daltons
)
scandium
=
Element
(
21
,
"scandium"
,
"Sc"
,
44.9559126
*
daltons
)
titanium
=
Element
(
22
,
"titanium"
,
"Ti"
,
47.8671
*
daltons
)
vanadium
=
Element
(
23
,
"vanadium"
,
"V"
,
50.94151
*
daltons
)
chromium
=
Element
(
24
,
"chromium"
,
"Cr"
,
51.99616
*
daltons
)
manganese
=
Element
(
25
,
"manganese"
,
"Mn"
,
54.9380455
*
daltons
)
iron
=
Element
(
26
,
"iron"
,
"Fe"
,
55.8452
*
daltons
)
cobalt
=
Element
(
27
,
"cobalt"
,
"Co"
,
58.9331955
*
daltons
)
nickel
=
Element
(
28
,
"nickel"
,
"Ni"
,
58.69342
*
daltons
)
copper
=
Element
(
29
,
"copper"
,
"Cu"
,
63.5463
*
daltons
)
zinc
=
Element
(
30
,
"zinc"
,
"Zn"
,
65.4094
*
daltons
)
gallium
=
Element
(
31
,
"gallium"
,
"Ga"
,
69.7231
*
daltons
)
germanium
=
Element
(
32
,
"germanium"
,
"Ge"
,
72.641
*
daltons
)
arsenic
=
Element
(
33
,
"arsenic"
,
"As"
,
74.921602
*
daltons
)
selenium
=
Element
(
34
,
"selenium"
,
"Se"
,
78.963
*
daltons
)
bromine
=
Element
(
35
,
"bromine"
,
"Br"
,
79.9041
*
daltons
)
krypton
=
Element
(
36
,
"krypton"
,
"Kr"
,
83.7982
*
daltons
)
rubidium
=
Element
(
37
,
"rubidium"
,
"Rb"
,
85.46783
*
daltons
)
strontium
=
Element
(
38
,
"strontium"
,
"Sr"
,
87.621
*
daltons
)
yttrium
=
Element
(
39
,
"yttrium"
,
"Y"
,
88.905852
*
daltons
)
zirconium
=
Element
(
40
,
"zirconium"
,
"Zr"
,
91.2242
*
daltons
)
niobium
=
Element
(
41
,
"niobium"
,
"Nb"
,
92.906382
*
daltons
)
molybdenum
=
Element
(
42
,
"molybdenum"
,
"Mo"
,
95.942
*
daltons
)
technetium
=
Element
(
43
,
"technetium"
,
"Tc"
,
98
*
daltons
)
ruthenium
=
Element
(
44
,
"ruthenium"
,
"Ru"
,
101.072
*
daltons
)
rhodium
=
Element
(
45
,
"rhodium"
,
"Rh"
,
102.905502
*
daltons
)
palladium
=
Element
(
46
,
"palladium"
,
"Pd"
,
106.421
*
daltons
)
silver
=
Element
(
47
,
"silver"
,
"Ag"
,
107.86822
*
daltons
)
cadmium
=
Element
(
48
,
"cadmium"
,
"Cd"
,
112.4118
*
daltons
)
indium
=
Element
(
49
,
"indium"
,
"In"
,
114.8183
*
daltons
)
tin
=
Element
(
50
,
"tin"
,
"Sn"
,
118.7107
*
daltons
)
antimony
=
Element
(
51
,
"antimony"
,
"Sb"
,
121.7601
*
daltons
)
tellurium
=
Element
(
52
,
"tellurium"
,
"Te"
,
127.603
*
daltons
)
iodine
=
Element
(
53
,
"iodine"
,
"I"
,
126.904473
*
daltons
)
xenon
=
Element
(
54
,
"xenon"
,
"Xe"
,
131.2936
*
daltons
)
cesium
=
Element
(
55
,
"cesium"
,
"Cs"
,
132.90545192
*
daltons
)
barium
=
Element
(
56
,
"barium"
,
"Ba"
,
137.3277
*
daltons
)
lanthanum
=
Element
(
57
,
"lanthanum"
,
"La"
,
138.905477
*
daltons
)
cerium
=
Element
(
58
,
"cerium"
,
"Ce"
,
140.1161
*
daltons
)
praseodymium
=
Element
(
59
,
"praseodymium"
,
"Pr"
,
140.907652
*
daltons
)
neodymium
=
Element
(
60
,
"neodymium"
,
"Nd"
,
144.2423
*
daltons
)
promethium
=
Element
(
61
,
"promethium"
,
"Pm"
,
145
*
daltons
)
samarium
=
Element
(
62
,
"samarium"
,
"Sm"
,
150.362
*
daltons
)
europium
=
Element
(
63
,
"europium"
,
"Eu"
,
151.9641
*
daltons
)
gadolinium
=
Element
(
64
,
"gadolinium"
,
"Gd"
,
157.253
*
daltons
)
terbium
=
Element
(
65
,
"terbium"
,
"Tb"
,
158.925352
*
daltons
)
dysprosium
=
Element
(
66
,
"dysprosium"
,
"Dy"
,
162.5001
*
daltons
)
holmium
=
Element
(
67
,
"holmium"
,
"Ho"
,
164.930322
*
daltons
)
erbium
=
Element
(
68
,
"erbium"
,
"Er"
,
167.2593
*
daltons
)
thulium
=
Element
(
69
,
"thulium"
,
"Tm"
,
168.934212
*
daltons
)
ytterbium
=
Element
(
70
,
"ytterbium"
,
"Yb"
,
173.043
*
daltons
)
lutetium
=
Element
(
71
,
"lutetium"
,
"Lu"
,
174.9671
*
daltons
)
hafnium
=
Element
(
72
,
"hafnium"
,
"Hf"
,
178.492
*
daltons
)
tantalum
=
Element
(
73
,
"tantalum"
,
"Ta"
,
180.947882
*
daltons
)
tungsten
=
Element
(
74
,
"tungsten"
,
"W"
,
183.841
*
daltons
)
rhenium
=
Element
(
75
,
"rhenium"
,
"Re"
,
186.2071
*
daltons
)
osmium
=
Element
(
76
,
"osmium"
,
"Os"
,
190.233
*
daltons
)
iridium
=
Element
(
77
,
"iridium"
,
"Ir"
,
192.2173
*
daltons
)
platinum
=
Element
(
78
,
"platinum"
,
"Pt"
,
195.0849
*
daltons
)
gold
=
Element
(
79
,
"gold"
,
"Au"
,
196.9665694
*
daltons
)
mercury
=
Element
(
80
,
"mercury"
,
"Hg"
,
200.592
*
daltons
)
thallium
=
Element
(
81
,
"thallium"
,
"Tl"
,
204.38332
*
daltons
)
lead
=
Element
(
82
,
"lead"
,
"Pb"
,
207.21
*
daltons
)
bismuth
=
Element
(
83
,
"bismuth"
,
"Bi"
,
208.980401
*
daltons
)
polonium
=
Element
(
84
,
"polonium"
,
"Po"
,
209
*
daltons
)
astatine
=
Element
(
85
,
"astatine"
,
"At"
,
210
*
daltons
)
radon
=
Element
(
86
,
"radon"
,
"Rn"
,
222.018
*
daltons
)
francium
=
Element
(
87
,
"francium"
,
"Fr"
,
223
*
daltons
)
radium
=
Element
(
88
,
"radium"
,
"Ra"
,
226
*
daltons
)
actinium
=
Element
(
89
,
"actinium"
,
"Ac"
,
227
*
daltons
)
thorium
=
Element
(
90
,
"thorium"
,
"Th"
,
232.038062
*
daltons
)
protactinium
=
Element
(
91
,
"protactinium"
,
"Pa"
,
231.035882
*
daltons
)
uranium
=
Element
(
92
,
"uranium"
,
"U"
,
238.028913
*
daltons
)
neptunium
=
Element
(
93
,
"neptunium"
,
"Np"
,
237
*
daltons
)
plutonium
=
Element
(
94
,
"plutonium"
,
"Pu"
,
244
*
daltons
)
americium
=
Element
(
95
,
"americium"
,
"Am"
,
243
*
daltons
)
curium
=
Element
(
96
,
"curium"
,
"Cm"
,
247
*
daltons
)
berkelium
=
Element
(
97
,
"berkelium"
,
"Bk"
,
247
*
daltons
)
californium
=
Element
(
98
,
"californium"
,
"Cf"
,
251
*
daltons
)
einsteinium
=
Element
(
99
,
"einsteinium"
,
"Es"
,
252
*
daltons
)
fermium
=
Element
(
100
,
"fermium"
,
"Fm"
,
257
*
daltons
)
mendelevium
=
Element
(
101
,
"mendelevium"
,
"Md"
,
258
*
daltons
)
nobelium
=
Element
(
102
,
"nobelium"
,
"No"
,
259
*
daltons
)
lawrencium
=
Element
(
103
,
"lawrencium"
,
"Lr"
,
262
*
daltons
)
rutherfordium
=
Element
(
104
,
"rutherfordium"
,
"Rf"
,
261
*
daltons
)
dubnium
=
Element
(
105
,
"dubnium"
,
"Db"
,
262
*
daltons
)
seaborgium
=
Element
(
106
,
"seaborgium"
,
"Sg"
,
266
*
daltons
)
bohrium
=
Element
(
107
,
"bohrium"
,
"Bh"
,
264
*
daltons
)
hassium
=
Element
(
108
,
"hassium"
,
"Hs"
,
269
*
daltons
)
meitnerium
=
Element
(
109
,
"meitnerium"
,
"Mt"
,
268
*
daltons
)
darmstadtium
=
Element
(
110
,
"darmstadtium"
,
"Ds"
,
281
*
daltons
)
roentgenium
=
Element
(
111
,
"roentgenium"
,
"Rg"
,
272
*
daltons
)
ununbium
=
Element
(
112
,
"ununbium"
,
"Uub"
,
285
*
daltons
)
ununtrium
=
Element
(
113
,
"ununtrium"
,
"Uut"
,
284
*
daltons
)
ununquadium
=
Element
(
114
,
"ununquadium"
,
"Uuq"
,
289
*
daltons
)
ununpentium
=
Element
(
115
,
"ununpentium"
,
"Uup"
,
288
*
daltons
)
ununhexium
=
Element
(
116
,
"ununhexium"
,
"Uuh"
,
292
*
daltons
)
wrappers/python/simtk/openmm/app/forcefield.py
0 → 100644
View file @
ccc0ba85
"""
forcefield.py: Constructs OpenMM System objects based on a Topology and an XML force field description
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
os
import
itertools
import
xml.etree.ElementTree
as
etree
from
math
import
sqrt
,
cos
import
simtk.openmm
as
mm
import
simtk.unit
as
unit
import
element
as
elem
from
simtk.openmm.app
import
Topology
# Enumerated values for nonbonded method
NoCutoff
=
object
()
CutoffNonPeriodic
=
object
()
CutoffPeriodic
=
object
()
Ewald
=
object
()
PME
=
object
()
# Enumerated values for constraint type
HBonds
=
object
()
AllBonds
=
object
()
HAngles
=
object
()
# A map of functions to parse elements of the XML file.
parsers
=
{}
class
ForceField
(
object
):
"""A ForceField constructs OpenMM System objects based on a Topology."""
def
__init__
(
self
,
*
files
):
"""Load one or more XML files and create a ForceField object based on them.
Parameters:
- A list of XML files defining the force field. Each entry may be an absolute file path, a path relative to the
current working directory, or a path relative to this module's data subdirectory (for built in force fields).
"""
self
.
_atomTypes
=
{}
self
.
_templates
=
{}
self
.
_templateSignatures
=
{}
self
.
_atomClasses
=
{}
self
.
_forces
=
[]
for
file
in
files
:
try
:
tree
=
etree
.
parse
(
file
)
except
IOError
:
tree
=
etree
.
parse
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'data'
,
file
))
root
=
tree
.
getroot
()
# Load the atom types.
if
tree
.
getroot
().
find
(
'AtomTypes'
)
is
not
None
:
for
type
in
tree
.
getroot
().
find
(
'AtomTypes'
).
findall
(
'Type'
):
self
.
_atomTypes
[
type
.
attrib
[
'name'
]]
=
(
type
.
attrib
[
'class'
],
float
(
type
.
attrib
[
'mass'
]),
elem
.
get_by_symbol
(
type
.
attrib
[
'element'
]))
# Load the residue templates.
if
tree
.
getroot
().
find
(
'Residues'
)
is
not
None
:
for
residue
in
root
.
find
(
'Residues'
).
findall
(
'Residue'
):
resName
=
residue
.
attrib
[
'name'
]
template
=
ForceField
.
_TemplateData
(
resName
)
self
.
_templates
[
resName
]
=
template
for
atom
in
residue
.
findall
(
'Atom'
):
template
.
atoms
.
append
(
ForceField
.
_TemplateAtomData
(
atom
.
attrib
[
'name'
],
atom
.
attrib
[
'type'
],
self
.
_atomTypes
[
atom
.
attrib
[
'type'
]][
2
]))
for
bond
in
residue
.
findall
(
'Bond'
):
b
=
(
int
(
bond
.
attrib
[
'from'
]),
int
(
bond
.
attrib
[
'to'
]))
template
.
bonds
.
append
(
b
)
template
.
atoms
[
b
[
0
]].
bondedTo
.
append
(
b
[
1
])
template
.
atoms
[
b
[
1
]].
bondedTo
.
append
(
b
[
0
])
for
bond
in
residue
.
findall
(
'ExternalBond'
):
b
=
int
(
bond
.
attrib
[
'from'
])
template
.
externalBonds
.
append
(
b
)
template
.
atoms
[
b
].
externalBonds
+=
1
for
template
in
self
.
_templates
.
values
():
template
.
signature
=
_createResidueSignature
([
atom
.
element
for
atom
in
template
.
atoms
])
sigString
=
_signatureToString
(
template
.
signature
)
if
sigString
in
self
.
_templateSignatures
:
self
.
_templateSignatures
[
sigString
].
append
(
template
)
else
:
self
.
_templateSignatures
[
sigString
]
=
[
template
]
# Build sets of every atom type belonging to each class
for
type
in
self
.
_atomTypes
:
atomClass
=
self
.
_atomTypes
[
type
][
0
]
if
atomClass
in
self
.
_atomClasses
:
typeSet
=
self
.
_atomClasses
[
atomClass
]
else
:
typeSet
=
set
()
self
.
_atomClasses
[
atomClass
]
=
typeSet
typeSet
.
add
(
type
)
self
.
_atomClasses
[
''
]
=
self
.
_atomTypes
.
keys
()
# Load force definitions
for
child
in
root
:
if
child
.
tag
in
parsers
:
parsers
[
child
.
tag
](
child
,
self
)
def
_findAtomTypes
(
self
,
node
,
num
):
"""Parse the attributes on an XML tag to find the set of atom types for each atom it involves."""
types
=
[]
attrib
=
node
.
attrib
for
i
in
range
(
num
):
if
num
==
1
:
suffix
=
''
else
:
suffix
=
str
(
i
+
1
)
classAttrib
=
'class'
+
suffix
if
classAttrib
in
attrib
:
if
attrib
[
classAttrib
]
not
in
self
.
_atomClasses
:
return
None
# Unknown atom class
types
.
append
(
self
.
_atomClasses
[
attrib
[
classAttrib
]])
else
:
typeAttrib
=
'type'
+
suffix
if
typeAttrib
not
in
attrib
or
attrib
[
typeAttrib
]
not
in
self
.
_atomTypes
:
return
None
# Unknown atom type
types
.
append
([
attrib
[
typeAttrib
]])
return
types
def
_parseTorsion
(
self
,
node
):
"""Parse the node defining a torsion."""
types
=
self
.
_findAtomTypes
(
node
,
4
)
if
types
is
None
:
return
None
torsion
=
PeriodicTorsion
(
types
)
attrib
=
node
.
attrib
index
=
1
while
'phase%d'
%
index
in
attrib
:
torsion
.
periodicity
.
append
(
int
(
attrib
[
'periodicity%d'
%
index
]))
torsion
.
phase
.
append
(
float
(
attrib
[
'phase%d'
%
index
]))
torsion
.
k
.
append
(
float
(
attrib
[
'k%d'
%
index
]))
index
+=
1
return
torsion
class
_SystemData
:
"""Inner class used to encapsulate data about the system being created."""
def
__init__
(
self
):
self
.
atomType
=
{}
self
.
atoms
=
[]
self
.
bonds
=
[]
self
.
angles
=
[]
self
.
propers
=
[]
self
.
impropers
=
[]
self
.
atomBonds
=
[]
self
.
isAngleConstrained
=
[]
class
_TemplateData
:
"""Inner class used to encapsulate data about a residue template definition."""
def
__init__
(
self
,
name
):
self
.
name
=
name
self
.
atoms
=
[]
self
.
bonds
=
[]
self
.
externalBonds
=
[]
class
_TemplateAtomData
:
"""Inner class used to encapsulate data about an atom in a residue template definition."""
def
__init__
(
self
,
name
,
type
,
element
):
self
.
name
=
name
self
.
type
=
type
self
.
element
=
element
self
.
bondedTo
=
[]
self
.
externalBonds
=
0
class
_BondData
:
"""Inner class used to encapsulate data about a bond."""
def
__init__
(
self
,
atom1
,
atom2
):
self
.
atom1
=
atom1
self
.
atom2
=
atom2
self
.
isConstrained
=
False
self
.
length
=
0.0
def
createSystem
(
self
,
topology
,
nonbondedMethod
=
NoCutoff
,
nonbondedCutoff
=
1.0
*
unit
.
nanometer
,
constraints
=
None
,
rigidWater
=
True
,
**
args
):
"""Construct an OpenMM System representing a Topology with this force field.
Parameters:
- topology (Topology) The Topology for which to create a System
- nonbondedMethod (object=NoCutoff) The method to use for nonbonded interactions. Allowed values are
NoCutoff, CutoffNonPeriodic, CutoffPeriodic, Ewald, or PME.
- nonbondedCutoff (distance=1*nanometer) The cutoff distance to use for nonbonded interactions
- constraints (object=None) Specifies which bonds angles should be implemented with constraints.
Allowed values are None, HBonds, AllBonds, or HAngles.
- rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the constraints argument
- Arbitrary additional keyword arguments may also be specified. This allows extra parameters to be specified that are specific to
particular force fields.
Returns: the newly created System
"""
# Record atom indices
data
=
ForceField
.
_SystemData
()
atomIndices
=
{}
for
index
,
atom
in
enumerate
(
topology
.
atoms
()):
data
.
atoms
.
append
(
atom
)
atomIndices
[
atom
]
=
index
# Make a list of all bonds
for
bond
in
topology
.
bonds
():
if
bond
[
0
]
in
atomIndices
and
bond
[
1
]
in
atomIndices
:
data
.
bonds
.
append
(
ForceField
.
_BondData
(
atomIndices
[
bond
[
0
]],
atomIndices
[
bond
[
1
]]))
# Record which atoms are bonded to each other atom
bondedToAtom
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bondedToAtom
.
append
(
set
())
data
.
atomBonds
.
append
([])
for
i
in
range
(
len
(
data
.
bonds
)):
bond
=
data
.
bonds
[
i
]
bondedToAtom
[
bond
.
atom1
].
add
(
bond
.
atom2
)
bondedToAtom
[
bond
.
atom2
].
add
(
bond
.
atom1
)
data
.
atomBonds
[
bond
.
atom1
].
append
(
i
)
data
.
atomBonds
[
bond
.
atom2
].
append
(
i
)
# Find the template matching each residue and assign atom types.
for
chain
in
topology
.
chains
():
for
res
in
chain
.
residues
():
signature
=
_signatureToString
(
_createResidueSignature
([
atom
.
element
for
atom
in
res
.
atoms
()]))
template
=
None
matches
=
None
if
signature
in
self
.
_templateSignatures
:
for
t
in
self
.
_templateSignatures
[
signature
]:
matches
=
_matchResidue
(
res
,
t
,
bondedToAtom
,
atomIndices
)
if
matches
is
not
None
:
template
=
t
break
if
matches
is
None
:
raise
ValueError
(
'No template found for residue %d (%s)'
%
(
res
.
index
+
1
,
res
.
name
))
for
atom
,
match
in
zip
(
res
.
atoms
(),
matches
):
data
.
atomType
[
atom
]
=
template
.
atoms
[
match
].
type
# Create the System and add atoms
sys
=
mm
.
System
()
for
atom
in
topology
.
atoms
():
sys
.
addParticle
(
self
.
_atomTypes
[
data
.
atomType
[
atom
]][
1
])
# Set periodic boundary conditions.
boxSize
=
topology
.
getUnitCellDimensions
()
if
boxSize
is
not
None
:
sys
.
setDefaultPeriodicBoxVectors
((
boxSize
[
0
],
0
,
0
),
(
0
,
boxSize
[
1
],
0
),
(
0
,
0
,
boxSize
[
2
]))
elif
nonbondedMethod
is
not
NoCutoff
and
nonbondedMethod
is
not
CutoffNonPeriodic
:
raise
ValueError
(
'Requested periodic boundary conditions for a Topology that does not specify periodic box dimensions'
)
# Make a list of all unique angles
uniqueAngles
=
set
()
for
bond
in
data
.
bonds
:
for
atom
in
bondedToAtom
[
bond
.
atom1
]:
if
atom
!=
bond
.
atom2
:
if
atom
<
bond
.
atom2
:
uniqueAngles
.
add
((
atom
,
bond
.
atom1
,
bond
.
atom2
))
else
:
uniqueAngles
.
add
((
bond
.
atom2
,
bond
.
atom1
,
atom
))
for
atom
in
bondedToAtom
[
bond
.
atom2
]:
if
atom
!=
bond
.
atom1
:
if
atom
>
bond
.
atom1
:
uniqueAngles
.
add
((
bond
.
atom1
,
bond
.
atom2
,
atom
))
else
:
uniqueAngles
.
add
((
atom
,
bond
.
atom2
,
bond
.
atom1
))
data
.
angles
=
sorted
(
list
(
uniqueAngles
))
# Make a list of all unique proper torsions
uniquePropers
=
set
()
for
angle
in
data
.
angles
:
for
atom
in
bondedToAtom
[
angle
[
0
]]:
if
atom
!=
angle
[
1
]:
if
atom
<
angle
[
2
]:
uniquePropers
.
add
((
atom
,
angle
[
0
],
angle
[
1
],
angle
[
2
]))
else
:
uniquePropers
.
add
((
angle
[
2
],
angle
[
1
],
angle
[
0
],
atom
))
for
atom
in
bondedToAtom
[
angle
[
2
]]:
if
atom
!=
angle
[
1
]:
if
atom
>
angle
[
0
]:
uniquePropers
.
add
((
angle
[
0
],
angle
[
1
],
angle
[
2
],
atom
))
else
:
uniquePropers
.
add
((
atom
,
angle
[
2
],
angle
[
1
],
angle
[
0
]))
data
.
propers
=
sorted
(
list
(
uniquePropers
))
# Make a list of all unique improper torsions
for
atom
in
range
(
len
(
bondedToAtom
)):
bondedTo
=
bondedToAtom
[
atom
]
if
len
(
bondedTo
)
>
2
:
for
subset
in
itertools
.
combinations
(
bondedTo
,
3
):
data
.
impropers
.
append
((
atom
,
subset
[
0
],
subset
[
1
],
subset
[
2
]))
# Identify bonds that should be implemented with constraints
if
constraints
==
AllBonds
or
constraints
==
HAngles
:
for
bond
in
data
.
bonds
:
bond
.
isConstrained
=
True
elif
constraints
==
HBonds
:
for
bond
in
data
.
bonds
:
atom1
=
data
.
atoms
[
bond
.
atom1
]
atom2
=
data
.
atoms
[
bond
.
atom2
]
bond
.
isConstrained
=
atom1
.
name
.
startswith
(
'H'
)
or
atom2
.
name
.
startswith
(
'H'
)
if
rigidWater
:
for
bond
in
data
.
bonds
:
atom1
=
data
.
atoms
[
bond
.
atom1
]
atom2
=
data
.
atoms
[
bond
.
atom2
]
if
atom1
.
residue
.
name
==
'HOH'
and
atom2
.
residue
.
name
==
'HOH'
:
bond
.
isConstrained
=
True
# Identify angles that should be implemented with constraints
if
constraints
==
HAngles
:
for
angle
in
data
.
angles
:
atom1
=
data
.
atoms
[
angle
[
0
]]
atom2
=
data
.
atoms
[
angle
[
1
]]
atom3
=
data
.
atoms
[
angle
[
2
]]
numH
=
0
if
atom1
.
name
.
startswith
(
'H'
):
numH
+=
1
if
atom3
.
name
.
startswith
(
'H'
):
numH
+=
1
data
.
isAngleConstrained
.
append
(
numH
==
2
or
(
numH
==
1
and
atom2
.
name
.
startswith
(
'O'
)))
else
:
data
.
isAngleConstrained
=
len
(
data
.
angles
)
*
[
False
]
if
rigidWater
:
for
i
in
range
(
len
(
data
.
angles
)):
angle
=
data
.
angles
[
i
]
atom1
=
data
.
atoms
[
angle
[
0
]]
atom2
=
data
.
atoms
[
angle
[
1
]]
atom3
=
data
.
atoms
[
angle
[
2
]]
if
atom1
.
residue
.
name
==
'HOH'
and
atom2
.
residue
.
name
==
'HOH'
and
atom3
.
residue
.
name
==
'HOH'
:
data
.
isAngleConstrained
[
i
]
=
True
# Add forces to the System
for
force
in
self
.
_forces
:
force
.
createForce
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
)
return
sys
def
_createResidueSignature
(
elements
):
"""Create a signature for a residue based on the elements of the atoms it contains."""
counts
=
{}
for
element
in
elements
:
if
element
in
counts
:
counts
[
element
]
+=
1
else
:
counts
[
element
]
=
1
sig
=
[]
for
c
in
counts
:
sig
.
append
((
c
,
counts
[
c
]))
sig
.
sort
(
key
=
lambda
x
:
-
x
[
0
].
mass
)
return
sig
def
_signatureToString
(
signature
):
"""Convert the signature returned by _createResidueSignature() to a string."""
s
=
''
for
element
,
count
in
signature
:
s
+=
element
.
symbol
+
str
(
count
)
return
s
def
_matchResidue
(
res
,
template
,
bondedToAtom
,
atomIndices
):
"""Determine whether a residue matches a template and return a list of corresponding atoms.
Parameters:
- res (Residue) The residue to check
- template (_TemplateData) The template to compare it to
- bondedToAtom (list) Enumerates which other atoms each atom is bonded to
- atomIndices (map) Maps from atoms to their indices in the System
Returns: a list specifying which atom of the template each atom of the residue corresponds to,
or None if it does not match the template
"""
atoms
=
list
(
res
.
atoms
())
residueAtomBonds
=
[]
templateAtomBonds
=
[]
matches
=
len
(
atoms
)
*
[
0
]
hasMatch
=
len
(
atoms
)
*
[
False
]
# Translate from global to local atom indices, and record the bonds for each atom.
renumberAtoms
=
{}
for
i
in
range
(
len
(
atoms
)):
renumberAtoms
[
atomIndices
[
atoms
[
i
]]]
=
i
bondedTo
=
[]
externalBonds
=
[]
for
atom
in
atoms
:
bonds
=
[
renumberAtoms
[
x
]
for
x
in
bondedToAtom
[
atomIndices
[
atom
]]
if
x
in
renumberAtoms
]
bondedTo
.
append
(
bonds
)
externalBonds
.
append
(
len
([
x
for
x
in
bondedToAtom
[
atomIndices
[
atom
]]
if
x
not
in
renumberAtoms
]))
if
_findAtomMatches
(
atoms
,
template
,
bondedTo
,
externalBonds
,
matches
,
hasMatch
,
0
):
return
matches
return
None
def
_findAtomMatches
(
atoms
,
template
,
bondedTo
,
externalBonds
,
matches
,
hasMatch
,
position
):
"""This is called recursively from inside _matchResidue() to identify matching atoms."""
if
position
==
len
(
atoms
):
return
True
elem
=
atoms
[
position
].
element
for
i
in
range
(
len
(
atoms
)):
atom
=
template
.
atoms
[
i
]
if
atom
.
element
==
elem
and
not
hasMatch
[
i
]
and
len
(
atom
.
bondedTo
)
==
len
(
bondedTo
[
position
])
and
atom
.
externalBonds
==
externalBonds
[
position
]:
# See if the bonds for this identification are consistent
allBondsMatch
=
all
((
bonded
>
position
or
matches
[
bonded
]
in
atom
.
bondedTo
for
bonded
in
bondedTo
[
position
]))
if
allBondsMatch
:
# This is a possible match, so trying matching the rest of the residue.
matches
[
position
]
=
i
hasMatch
[
i
]
=
True
if
_findAtomMatches
(
atoms
,
template
,
bondedTo
,
externalBonds
,
matches
,
hasMatch
,
position
+
1
):
return
True
hasMatch
[
i
]
=
False
return
False
# The following classes are generators that know how to create Force subclasses and add them to a System that is being
# created. Each generator class must define two methods: 1) a static method that takes an etree Element and a ForceField,
# and returns the corresponding generator object; 2) a createForce() method that constructs the Force object and adds it
# to the System. The static method should be added to the parsers map.
class
HarmonicBondGenerator
:
"""A HarmonicBondGenerator constructs a HarmonicBondForce."""
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
length
=
[]
self
.
k
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
HarmonicBondGenerator
()
ff
.
_forces
.
append
(
generator
)
for
bond
in
element
.
findall
(
'Bond'
):
types
=
ff
.
_findAtomTypes
(
bond
,
2
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
length
.
append
(
float
(
bond
.
attrib
[
'length'
]))
generator
.
k
.
append
(
float
(
bond
.
attrib
[
'k'
]))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
HarmonicBondForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
HarmonicBondForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
for
bond
in
data
.
bonds
:
type1
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom1
]]
type2
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom2
]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
if
(
type1
in
types1
and
type2
in
types2
)
or
(
type1
in
types2
and
type2
in
types1
):
bond
.
length
=
self
.
length
[
i
]
if
bond
.
isConstrained
:
sys
.
addConstraint
(
bond
.
atom1
,
bond
.
atom2
,
self
.
length
[
i
])
elif
self
.
k
[
i
]
!=
0
:
force
.
addBond
(
bond
.
atom1
,
bond
.
atom2
,
self
.
length
[
i
],
self
.
k
[
i
])
break
parsers
[
"HarmonicBondForce"
]
=
HarmonicBondGenerator
.
parseElement
class
HarmonicAngleGenerator
:
"""A HarmonicAngleGenerator constructs a HarmonicAngleForce."""
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
angle
=
[]
self
.
k
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
HarmonicAngleGenerator
()
ff
.
_forces
.
append
(
generator
)
for
angle
in
element
.
findall
(
'Angle'
):
types
=
ff
.
_findAtomTypes
(
angle
,
3
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
angle
.
append
(
float
(
angle
.
attrib
[
'angle'
]))
generator
.
k
.
append
(
float
(
angle
.
attrib
[
'k'
]))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
HarmonicAngleForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
HarmonicAngleForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
for
(
angle
,
isConstrained
)
in
zip
(
data
.
angles
,
data
.
isAngleConstrained
):
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
)
or
(
type1
in
types3
and
type2
in
types2
and
type3
in
types1
):
if
isConstrained
:
# Find the two bonds that make this angle.
bond1
=
None
bond2
=
None
for
bond
in
data
.
atomBonds
[
angle
[
1
]]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
atom1
==
angle
[
0
]
or
atom2
==
angle
[
0
]:
bond1
=
bond
elif
atom1
==
angle
[
2
]
or
atom2
==
angle
[
2
]:
bond2
=
bond
# Compute the distance between atoms and add a constraint
if
bond1
is
not
None
and
bond2
is
not
None
:
l1
=
data
.
bonds
[
bond1
].
length
l2
=
data
.
bonds
[
bond2
].
length
if
l1
is
not
None
and
l2
is
not
None
:
length
=
sqrt
(
l1
*
l1
+
l2
*
l2
-
2
*
l1
*
l2
*
cos
(
self
.
angle
[
i
]))
sys
.
addConstraint
(
angle
[
0
],
angle
[
2
],
length
)
elif
self
.
k
[
i
]
!=
0
:
force
.
addAngle
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
self
.
angle
[
i
],
self
.
k
[
i
])
break
parsers
[
"HarmonicAngleForce"
]
=
HarmonicAngleGenerator
.
parseElement
class
PeriodicTorsion
:
"""A PeriodicTorsion records the information for a periodic torsion definition."""
def
__init__
(
self
,
types
):
self
.
types1
=
types
[
0
]
self
.
types2
=
types
[
1
]
self
.
types3
=
types
[
2
]
self
.
types4
=
types
[
3
]
self
.
periodicity
=
[]
self
.
phase
=
[]
self
.
k
=
[]
class
PeriodicTorsionGenerator
:
"""A PeriodicTorsionGenerator constructs a PeriodicTorsionForce."""
def
__init__
(
self
):
self
.
proper
=
[]
self
.
improper
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
PeriodicTorsionGenerator
()
generator
.
ff
=
ff
ff
.
_forces
.
append
(
generator
)
for
torsion
in
element
.
findall
(
'Proper'
):
torsion
=
ff
.
_parseTorsion
(
torsion
)
if
torsion
is
not
None
:
generator
.
proper
.
append
(
torsion
)
for
torsion
in
element
.
findall
(
'Improper'
):
torsion
=
ff
.
_parseTorsion
(
torsion
)
if
torsion
is
not
None
:
generator
.
improper
.
append
(
torsion
)
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
PeriodicTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
PeriodicTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
wildcard
=
self
.
ff
.
_atomClasses
[
''
]
for
torsion
in
data
.
propers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
match
=
None
for
tordef
in
self
.
proper
:
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
(
type2
in
types2
and
type3
in
types3
and
type4
in
types4
and
type1
in
types1
)
or
(
type2
in
types3
and
type3
in
types2
and
type4
in
types1
and
type1
in
types4
):
hasWildcard
=
(
wildcard
in
(
types1
,
types2
,
types3
,
types4
))
if
match
is
None
or
not
hasWildcard
:
# Prefer specific definitions over ones with wildcards
match
=
tordef
if
not
hasWildcard
:
break
if
match
is
not
None
:
for
i
in
range
(
len
(
match
.
phase
)):
if
match
.
k
[
i
]
!=
0
:
force
.
addTorsion
(
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
match
.
periodicity
[
i
],
match
.
phase
[
i
],
match
.
k
[
i
])
for
torsion
in
data
.
impropers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
done
=
False
for
tordef
in
self
.
improper
:
if
done
:
break
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
type1
in
types1
:
for
(
t2
,
t3
,
t4
)
in
itertools
.
permutations
(((
type2
,
1
),
(
type3
,
2
),
(
type4
,
3
))):
if
t2
[
0
]
in
types2
and
t3
[
0
]
in
types3
and
t4
[
0
]
in
types4
:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambigous. It then follows some bizarre rules
# to pick the order.
a1
=
torsion
[
t2
[
1
]]
a2
=
torsion
[
t3
[
1
]]
e1
=
data
.
atoms
[
a1
].
element
e2
=
data
.
atoms
[
a2
].
element
if
e1
==
e2
and
a1
>
a2
:
(
a1
,
a2
)
=
(
a2
,
a1
)
elif
e1
!=
elem
.
carbon
and
(
e2
==
elem
.
carbon
or
e1
.
mass
<
e2
.
mass
):
(
a1
,
a2
)
=
(
a2
,
a1
)
for
i
in
range
(
len
(
tordef
.
phase
)):
if
tordef
.
k
[
i
]
!=
0
:
force
.
addTorsion
(
a1
,
a2
,
torsion
[
0
],
torsion
[
t4
[
1
]],
tordef
.
periodicity
[
i
],
tordef
.
phase
[
i
],
tordef
.
k
[
i
])
done
=
True
break
parsers
[
"PeriodicTorsionForce"
]
=
PeriodicTorsionGenerator
.
parseElement
class
RBTorsion
:
"""An RBTorsion records the information for a Ryckaert-Bellemans torsion definition."""
def
__init__
(
self
,
types
,
c
):
self
.
types1
=
types
[
0
]
self
.
types2
=
types
[
1
]
self
.
types3
=
types
[
2
]
self
.
types4
=
types
[
3
]
self
.
c
=
c
class
RBTorsionGenerator
:
"""An RBTorsionGenerator constructs an RBTorsionForce."""
def
__init__
(
self
):
self
.
proper
=
[]
self
.
improper
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
RBTorsionGenerator
()
generator
.
ff
=
ff
ff
.
_forces
.
append
(
generator
)
for
torsion
in
element
.
findall
(
'Proper'
):
types
=
ff
.
_findAtomTypes
(
torsion
,
4
)
if
types
is
not
None
:
generator
.
proper
.
append
(
RBTorsion
(
types
,
[
float
(
torsion
.
attrib
[
'c'
+
str
(
i
)])
for
i
in
range
(
6
)]))
for
torsion
in
element
.
findall
(
'Improper'
):
types
=
ff
.
_findAtomTypes
(
torsion
,
4
)
if
types
is
not
None
:
generator
.
improper
.
append
(
RBTorsion
(
types
,
[
float
(
torsion
.
attrib
[
'c'
+
str
(
i
)])
for
i
in
range
(
6
)]))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
RBTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
RBTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
wildcard
=
self
.
ff
.
_atomClasses
[
''
]
for
torsion
in
data
.
propers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
match
=
None
for
tordef
in
self
.
proper
:
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
(
type2
in
types2
and
type3
in
types3
and
type4
in
types4
and
type1
in
types1
)
or
(
type2
in
types3
and
type3
in
types2
and
type4
in
types1
and
type1
in
types4
):
hasWildcard
=
(
wildcard
in
(
types1
,
types2
,
types3
,
types4
))
if
match
is
None
or
not
hasWildcard
:
# Prefer specific definitions over ones with wildcards
match
=
tordef
if
not
hasWildcard
:
break
if
match
is
not
None
:
force
.
addTorsion
(
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
match
.
c
[
0
],
match
.
c
[
1
],
match
.
c
[
2
],
match
.
c
[
3
],
match
.
c
[
4
],
match
.
c
[
5
])
for
torsion
in
data
.
impropers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
done
=
False
for
tordef
in
self
.
improper
:
if
done
:
break
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
type1
in
types1
:
for
(
t2
,
t3
,
t4
)
in
itertools
.
permutations
(((
type2
,
1
),
(
type3
,
2
),
(
type4
,
3
))):
if
t2
[
0
]
in
types2
and
t3
[
0
]
in
types3
and
t4
[
0
]
in
types4
:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambigous. It then follows some bizarre rules
# to pick the order.
a1
=
torsion
[
t2
[
1
]]
a2
=
torsion
[
t3
[
1
]]
e1
=
data
.
atoms
[
a1
].
element
e2
=
data
.
atoms
[
a2
].
element
if
e1
==
e2
and
a1
>
a2
:
(
a1
,
a2
)
=
(
a2
,
a1
)
elif
e1
!=
elem
.
carbon
and
(
e2
==
elem
.
carbon
or
e1
.
mass
<
e2
.
mass
):
(
a1
,
a2
)
=
(
a2
,
a1
)
force
.
addTorsion
(
a1
,
a2
,
torsion
[
0
],
torsion
[
t4
[
1
]],
tordef
.
c
[
0
],
tordef
.
c
[
1
],
tordef
.
c
[
2
],
tordef
.
c
[
3
],
tordef
.
c
[
4
],
tordef
.
c
[
5
])
done
=
True
break
parsers
[
"RBTorsionForce"
]
=
RBTorsionGenerator
.
parseElement
class
CMAPTorsion
:
"""A CMAPTorsion records the information for a CMAP torsion definition."""
def
__init__
(
self
,
types
,
map
):
self
.
types1
=
types
[
0
]
self
.
types2
=
types
[
1
]
self
.
types3
=
types
[
2
]
self
.
types4
=
types
[
3
]
self
.
types5
=
types
[
4
]
self
.
map
=
map
class
CMAPTorsionGenerator
:
"""A CMAPTorsionGenerator constructs a CMAPTorsionForce."""
def
__init__
(
self
):
self
.
torsions
=
[]
self
.
maps
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
CMAPTorsionGenerator
()
generator
.
ff
=
ff
ff
.
_forces
.
append
(
generator
)
for
map
in
element
.
findall
(
'Map'
):
values
=
[
float
(
x
)
for
x
in
map
.
text
.
split
()]
size
=
sqrt
(
len
(
values
))
if
size
*
size
!=
len
(
values
):
raise
ValueError
(
'CMAP must have the same number of elements along each dimension'
)
generator
.
maps
.
append
(
values
)
for
torsion
in
element
.
findall
(
'Torsion'
):
types
=
ff
.
_findAtomTypes
(
torsion
,
5
)
if
types
is
not
None
:
generator
.
torsions
.
append
(
CMAPTorsion
(
types
,
int
(
torsion
.
attrib
[
'map'
])))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
CMAPTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
CMAPTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
for
map
in
self
.
maps
:
force
.
addMap
(
int
(
sqrt
(
len
(
map
))),
map
)
# Find all chains of length 5
uniqueTorsions
=
set
()
for
torsion
in
data
.
propers
:
for
bond
in
(
data
.
bonds
[
x
]
for
x
in
data
.
atomBonds
[
torsion
[
0
]]):
if
bond
.
atom1
==
torsion
[
0
]:
atom
=
bond
.
atom2
else
:
atom
=
bond
.
atom1
if
atom
!=
torsion
[
1
]:
uniqueTorsions
.
add
((
atom
,
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
]))
for
bond
in
(
data
.
bonds
[
x
]
for
x
in
data
.
atomBonds
[
torsion
[
3
]]):
if
bond
.
atom1
==
torsion
[
3
]:
atom
=
bond
.
atom2
else
:
atom
=
bond
.
atom1
if
atom
!=
torsion
[
2
]:
uniqueTorsions
.
add
((
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
atom
))
torsions
=
sorted
(
list
(
uniqueTorsions
))
wildcard
=
self
.
ff
.
_atomClasses
[
''
]
for
torsion
in
torsions
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
type5
=
data
.
atomType
[
data
.
atoms
[
torsion
[
4
]]]
match
=
None
for
tordef
in
self
.
torsions
:
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
types5
=
tordef
.
types5
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
and
type4
in
types4
and
type5
in
types5
)
or
(
type1
in
types5
and
type2
in
types4
and
type3
in
types3
and
type4
in
types2
and
type5
in
types1
):
hasWildcard
=
(
wildcard
in
(
types1
,
types2
,
types3
,
types4
,
types5
))
if
match
is
None
or
not
hasWildcard
:
# Prefer specific definitions over ones with wildcards
match
=
tordef
if
not
hasWildcard
:
break
if
match
is
not
None
:
force
.
addTorsion
(
match
.
map
,
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
torsion
[
4
])
parsers
[
"CMAPTorsionForce"
]
=
CMAPTorsionGenerator
.
parseElement
class
NonbondedGenerator
:
"""A NonbondedGenerator constructs a NonbondedForce."""
def
__init__
(
self
,
coulomb14scale
,
lj14scale
):
self
.
coulomb14scale
=
coulomb14scale
self
.
lj14scale
=
lj14scale
self
.
typeMap
=
{}
@
staticmethod
def
parseElement
(
element
,
ff
):
existing
=
[
f
for
f
in
ff
.
_forces
if
isinstance
(
f
,
NonbondedGenerator
)]
if
len
(
existing
)
==
0
:
generator
=
NonbondedGenerator
(
float
(
element
.
attrib
[
'coulomb14scale'
]),
float
(
element
.
attrib
[
'lj14scale'
]))
ff
.
_forces
.
append
(
generator
)
else
:
# Multiple <NonbondedForce> tags were found, probably in different files. Simply add more types to the existing one.
generator
=
existing
[
0
]
if
generator
.
coulomb14scale
!=
float
(
element
.
attrib
[
'coulomb14scale'
])
or
generator
.
lj14scale
!=
float
(
element
.
attrib
[
'lj14scale'
]):
raise
ValueError
(
'Found multiple NonbondedForce tags with different 1-4 scales'
)
for
atom
in
element
.
findall
(
'Atom'
):
types
=
ff
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
(
float
(
atom
.
attrib
[
'charge'
]),
float
(
atom
.
attrib
[
'sigma'
]),
float
(
atom
.
attrib
[
'epsilon'
]))
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
methodMap
=
{
NoCutoff
:
mm
.
NonbondedForce
.
NoCutoff
,
CutoffNonPeriodic
:
mm
.
NonbondedForce
.
CutoffNonPeriodic
,
CutoffPeriodic
:
mm
.
NonbondedForce
.
CutoffPeriodic
,
Ewald
:
mm
.
NonbondedForce
.
Ewald
,
PME
:
mm
.
NonbondedForce
.
PME
}
if
nonbondedMethod
not
in
methodMap
:
raise
ValueError
(
'Illegal nonbonded method for NonbondedForce'
)
force
=
mm
.
NonbondedForce
()
for
atom
in
data
.
atoms
:
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
force
.
addParticle
(
values
[
0
],
values
[
1
],
values
[
2
])
else
:
raise
ValueError
(
'No nonbonded parameters defined for atom type '
+
t
)
bondIndices
=
[]
for
bond
in
data
.
bonds
:
bondIndices
.
append
((
bond
.
atom1
,
bond
.
atom2
))
force
.
createExceptionsFromBonds
(
bondIndices
,
self
.
coulomb14scale
,
self
.
lj14scale
)
force
.
setNonbondedMethod
(
methodMap
[
nonbondedMethod
])
force
.
setCutoffDistance
(
nonbondedCutoff
)
if
'ewaldErrorTolerance'
in
args
:
force
.
setEwaldErrorTolerance
(
args
[
'ewaldErrorTolerance'
])
sys
.
addForce
(
force
)
parsers
[
"NonbondedForce"
]
=
NonbondedGenerator
.
parseElement
class
GBSAOBCGenerator
:
"""A GBSAOBCGenerator constructs a GBSAOBCForce."""
def
__init__
(
self
):
self
.
typeMap
=
{}
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
GBSAOBCGenerator
()
ff
.
_forces
.
append
(
generator
)
for
atom
in
element
.
findall
(
'Atom'
):
types
=
ff
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
(
float
(
atom
.
attrib
[
'charge'
]),
float
(
atom
.
attrib
[
'radius'
]),
float
(
atom
.
attrib
[
'scale'
]))
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
methodMap
=
{
NoCutoff
:
mm
.
NonbondedForce
.
NoCutoff
,
CutoffNonPeriodic
:
mm
.
NonbondedForce
.
CutoffNonPeriodic
,
CutoffPeriodic
:
mm
.
NonbondedForce
.
CutoffPeriodic
}
if
nonbondedMethod
not
in
methodMap
:
raise
ValueError
(
'Illegal nonbonded method for GBSAOBCForce'
)
force
=
mm
.
GBSAOBCForce
()
for
atom
in
data
.
atoms
:
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
force
.
addParticle
(
values
[
0
],
values
[
1
],
values
[
2
])
else
:
raise
ValueError
(
'No GBSAOBC parameters defined for atom type '
+
t
)
force
.
setNonbondedMethod
(
methodMap
[
nonbondedMethod
])
force
.
setCutoffDistance
(
nonbondedCutoff
)
sys
.
addForce
(
force
)
parsers
[
"GBSAOBCForce"
]
=
GBSAOBCGenerator
.
parseElement
class
GBVIGenerator
:
"""A GBVIGenerator constructs a GBVIForce."""
def
__init__
(
self
,
ff
):
self
.
ff
=
ff
self
.
fixedParameters
=
{}
self
.
fixedParameters
[
'soluteDielectric'
]
=
1.0
self
.
fixedParameters
[
'solventDielectric'
]
=
78.3
self
.
fixedParameters
[
'scalingMethod'
]
=
1
self
.
fixedParameters
[
'quinticUpperBornRadiusLimit'
]
=
5.0
self
.
fixedParameters
[
'quinticLowerLimitFactor'
]
=
0.8
self
.
typeMap
=
{}
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
GBVIGenerator
(
ff
)
for
key
in
generator
.
fixedParameters
.
iterkeys
():
if
(
key
in
element
.
attrib
):
generator
.
fixedParameters
[
key
]
=
float
(
element
.
attrib
[
key
]
)
ff
.
_forces
.
append
(
generator
)
for
atom
in
element
.
findall
(
'Atom'
):
types
=
ff
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
(
float
(
atom
.
attrib
[
'charge'
]),
float
(
atom
.
attrib
[
'radius'
]),
float
(
atom
.
attrib
[
'gamma'
]))
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
methodMap
=
{
NoCutoff
:
mm
.
NonbondedForce
.
NoCutoff
,
CutoffNonPeriodic
:
mm
.
NonbondedForce
.
CutoffNonPeriodic
,
CutoffPeriodic
:
mm
.
NonbondedForce
.
CutoffPeriodic
}
if
nonbondedMethod
not
in
methodMap
:
raise
ValueError
(
'Illegal nonbonded method for GB/VI Force'
)
# add particles
force
=
mm
.
GBVIForce
()
for
atom
in
data
.
atoms
:
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
force
.
addParticle
(
values
[
0
],
values
[
1
],
values
[
2
])
else
:
raise
ValueError
(
'No GB/VI parameters defined for atom type '
+
t
)
# get HarmonicBond generator -- exit if not found
hbGenerator
=
0
for
generator
in
self
.
ff
.
_forces
:
if
(
generator
.
__class__
.
__name__
==
'HarmonicBondGenerator'
):
hbGenerator
=
generator
break
if
(
hbGenerator
==
0
):
raise
ValueError
(
'HarmonicBondGenerator not found.'
)
# add bonds
for
bond
in
data
.
bonds
:
type1
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom1
]]
type2
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom2
]]
hit
=
0
for
i
in
range
(
len
(
hbGenerator
.
types1
)):
types1
=
hbGenerator
.
types1
[
i
]
types2
=
hbGenerator
.
types2
[
i
]
if
(
type1
in
types1
and
type2
in
types2
)
or
(
type1
in
types2
and
type2
in
types1
):
#bond.length = hbGenerator.length[i]
force
.
addBond
(
bond
.
atom1
,
bond
.
atom2
,
hbGenerator
.
length
[
i
])
force
.
setNonbondedMethod
(
methodMap
[
nonbondedMethod
])
force
.
setCutoffDistance
(
nonbondedCutoff
)
force
.
setSolventDielectric
(
self
.
fixedParameters
[
'solventDielectric'
]
)
force
.
setSoluteDielectric
(
self
.
fixedParameters
[
'soluteDielectric'
]
)
force
.
setBornRadiusScalingMethod
(
self
.
fixedParameters
[
'scalingMethod'
]
)
force
.
setQuinticLowerLimitFactor
(
self
.
fixedParameters
[
'quinticLowerLimitFactor'
]
)
force
.
setQuinticUpperBornRadiusLimit
(
self
.
fixedParameters
[
'quinticUpperBornRadiusLimit'
]
)
sys
.
addForce
(
force
)
parsers
[
"GBVIForce"
]
=
GBVIGenerator
.
parseElement
class
CustomBondGenerator
:
"""A CustomBondGenerator constructs a CustomBondForce."""
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
globalParams
=
{}
self
.
perBondParams
=
[]
self
.
paramValues
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
CustomBondGenerator
()
ff
.
_forces
.
append
(
generator
)
generator
.
energy
=
element
.
attrib
[
'energy'
]
for
param
in
element
.
findall
(
'GlobalParameter'
):
generator
.
globalParams
[
param
.
attrib
[
'name'
]]
=
float
(
param
.
attrib
[
'defaultValue'
])
for
param
in
element
.
findall
(
'PerBondParameter'
):
generator
.
perBondParams
.
append
(
param
.
attrib
[
'name'
])
for
bond
in
element
.
findall
(
'Bond'
):
types
=
ff
.
_findAtomTypes
(
bond
,
2
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
paramValues
.
append
([
float
(
bond
.
attrib
[
param
])
for
param
in
generator
.
perBondParams
])
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
force
=
mm
.
CustomBondForce
(
self
.
energy
)
sys
.
addForce
(
force
)
for
param
in
self
.
globalParams
:
force
.
addGlobalParameter
(
param
,
self
.
globalParams
[
param
])
for
param
in
self
.
perBondParams
:
force
.
addPerBondParameter
(
param
)
for
bond
in
data
.
bonds
:
type1
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom1
]]
type2
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom2
]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
if
(
type1
in
types1
and
type2
in
types2
)
or
(
type1
in
types2
and
type2
in
types1
):
force
.
addBond
(
bond
.
atom1
,
bond
.
atom2
,
self
.
paramValues
[
i
])
break
parsers
[
"CustomBondForce"
]
=
CustomBondGenerator
.
parseElement
class
CustomAngleGenerator
:
"""A CustomAngleGenerator constructs a CustomAngleForce."""
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
globalParams
=
{}
self
.
perAngleParams
=
[]
self
.
paramValues
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
CustomAngleGenerator
()
ff
.
_forces
.
append
(
generator
)
generator
.
energy
=
element
.
attrib
[
'energy'
]
for
param
in
element
.
findall
(
'GlobalParameter'
):
generator
.
globalParams
[
param
.
attrib
[
'name'
]]
=
float
(
param
.
attrib
[
'defaultValue'
])
for
param
in
element
.
findall
(
'PerAngleParameter'
):
generator
.
perAngleParams
.
append
(
param
.
attrib
[
'name'
])
for
angle
in
element
.
findall
(
'Angle'
):
types
=
ff
.
_findAtomTypes
(
angle
,
3
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
paramValues
.
append
([
float
(
angle
.
attrib
[
param
])
for
param
in
generator
.
perAngleParams
])
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
force
=
mm
.
CustomAngleForce
(
self
.
energy
)
sys
.
addForce
(
force
)
for
param
in
self
.
globalParams
:
force
.
addGlobalParameter
(
param
,
self
.
globalParams
[
param
])
for
param
in
self
.
perAngleParams
:
force
.
addPerAngleParameter
(
param
)
for
angle
in
data
.
angles
:
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
)
or
(
type1
in
types3
and
type2
in
types2
and
type3
in
types1
):
force
.
addAngle
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
self
.
paramValues
[
i
])
break
parsers
[
"CustomAngleForce"
]
=
CustomAngleGenerator
.
parseElement
class
CustomTorsion
:
"""A CustomTorsion records the information for a custom torsion definition."""
def
__init__
(
self
,
types
,
paramValues
):
self
.
types1
=
types
[
0
]
self
.
types2
=
types
[
1
]
self
.
types3
=
types
[
2
]
self
.
types4
=
types
[
3
]
self
.
paramValues
=
paramValues
class
CustomTorsionGenerator
:
"""A CustomTorsionGenerator constructs a CustomTorsionForce."""
def
__init__
(
self
):
self
.
proper
=
[]
self
.
improper
=
[]
self
.
globalParams
=
{}
self
.
perTorsionParams
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
CustomTorsionGenerator
()
generator
.
ff
=
ff
ff
.
_forces
.
append
(
generator
)
generator
.
energy
=
element
.
attrib
[
'energy'
]
for
param
in
element
.
findall
(
'GlobalParameter'
):
generator
.
globalParams
[
param
.
attrib
[
'name'
]]
=
float
(
param
.
attrib
[
'defaultValue'
])
for
param
in
element
.
findall
(
'PerTorsionParameter'
):
generator
.
perTorsionParams
.
append
(
param
.
attrib
[
'name'
])
for
torsion
in
element
.
findall
(
'Proper'
):
types
=
ff
.
_findAtomTypes
(
torsion
,
4
)
if
types
is
not
None
:
generator
.
proper
.
append
(
CustomTorsion
(
types
,
[
float
(
torsion
.
attrib
[
param
])
for
param
in
generator
.
perTorsionParams
]))
for
torsion
in
element
.
findall
(
'Improper'
):
types
=
ff
.
_findAtomTypes
(
torsion
,
4
)
if
types
is
not
None
:
generator
.
improper
.
append
(
CustomTorsion
(
types
,
[
float
(
torsion
.
attrib
[
param
])
for
param
in
generator
.
perTorsionParams
]))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
force
=
mm
.
CustomTorsionForce
(
self
.
energy
)
sys
.
addForce
(
force
)
for
param
in
self
.
globalParams
:
force
.
addGlobalParameter
(
param
,
self
.
globalParams
[
param
])
for
param
in
self
.
perTorsionParams
:
force
.
addPerTorsionParameter
(
param
)
wildcard
=
self
.
ff
.
_atomClasses
[
''
]
for
torsion
in
data
.
propers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
match
=
None
for
tordef
in
self
.
proper
:
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
(
type2
in
types2
and
type3
in
types3
and
type4
in
types4
and
type1
in
types1
)
or
(
type2
in
types3
and
type3
in
types2
and
type4
in
types1
and
type1
in
types4
):
hasWildcard
=
(
wildcard
in
(
types1
,
types2
,
types3
,
types4
))
if
match
is
None
or
not
hasWildcard
:
# Prefer specific definitions over ones with wildcards
match
=
tordef
if
not
hasWildcard
:
break
if
match
is
not
None
:
force
.
addTorsion
(
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
match
.
paramValues
)
for
torsion
in
data
.
impropers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
done
=
False
for
tordef
in
self
.
improper
:
if
done
:
break
types1
=
tordef
.
types1
types2
=
tordef
.
types2
types3
=
tordef
.
types3
types4
=
tordef
.
types4
if
type1
in
types1
:
for
(
t2
,
t3
,
t4
)
in
itertools
.
permutations
(((
type2
,
1
),
(
type3
,
2
),
(
type4
,
3
))):
if
t2
[
0
]
in
types2
and
t3
[
0
]
in
types3
and
t4
[
0
]
in
types4
:
# Workaround to be more consistent with AMBER. It uses wildcards to define most of its
# impropers, which leaves the ordering ambigous. It then follows some bizarre rules
# to pick the order.
a1
=
torsion
[
t2
[
1
]]
a2
=
torsion
[
t3
[
1
]]
e1
=
data
.
atoms
[
a1
].
element
e2
=
data
.
atoms
[
a2
].
element
if
e1
==
e2
and
a1
>
a2
:
(
a1
,
a2
)
=
(
a2
,
a1
)
elif
e1
!=
elem
.
carbon
and
(
e2
==
elem
.
carbon
or
e1
.
mass
<
e2
.
mass
):
(
a1
,
a2
)
=
(
a2
,
a1
)
force
.
addTorsion
(
a1
,
a2
,
torsion
[
0
],
torsion
[
t4
[
1
]],
tordef
.
paramValues
)
done
=
True
break
parsers
[
"CustomTorsionForce"
]
=
CustomTorsionGenerator
.
parseElement
class
CustomGBGenerator
:
"""A CustomGBGenerator constructs a CustomGBForce."""
def
__init__
(
self
):
self
.
typeMap
=
{}
self
.
globalParams
=
{}
self
.
perParticleParams
=
[]
self
.
paramValues
=
[]
self
.
computedValues
=
[]
self
.
energyTerms
=
[]
self
.
functions
=
[]
@
staticmethod
def
parseElement
(
element
,
ff
):
generator
=
CustomGBGenerator
()
ff
.
_forces
.
append
(
generator
)
for
param
in
element
.
findall
(
'GlobalParameter'
):
generator
.
globalParams
[
param
.
attrib
[
'name'
]]
=
float
(
param
.
attrib
[
'defaultValue'
])
for
param
in
element
.
findall
(
'PerParticleParameter'
):
generator
.
perParticleParams
.
append
(
param
.
attrib
[
'name'
])
for
atom
in
element
.
findall
(
'Atom'
):
types
=
ff
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
[
float
(
atom
.
attrib
[
param
])
for
param
in
generator
.
perParticleParams
]
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
computationMap
=
{
"SingleParticle"
:
mm
.
CustomGBForce
.
SingleParticle
,
"ParticlePair"
:
mm
.
CustomGBForce
.
ParticlePair
,
"ParticlePairNoExclusions"
:
mm
.
CustomGBForce
.
ParticlePairNoExclusions
}
for
value
in
element
.
findall
(
'ComputedValue'
):
generator
.
computedValues
.
append
((
value
.
attrib
[
'name'
],
value
.
text
,
computationMap
[
value
.
attrib
[
'type'
]]))
for
term
in
element
.
findall
(
'EnergyTerm'
):
generator
.
energyTerms
.
append
((
term
.
text
,
computationMap
[
term
.
attrib
[
'type'
]]))
for
function
in
element
.
findall
(
"Function"
):
values
=
[
float
(
x
)
for
x
in
function
.
text
.
split
()]
generator
.
functions
.
append
((
function
.
attrib
[
'name'
],
values
,
float
(
function
.
attrib
[
'min'
]),
float
(
function
.
attrib
[
'max'
])))
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
methodMap
=
{
NoCutoff
:
mm
.
CustomGBForce
.
NoCutoff
,
CutoffNonPeriodic
:
mm
.
CustomGBForce
.
CutoffNonPeriodic
,
CutoffPeriodic
:
mm
.
CustomGBForce
.
CutoffPeriodic
}
if
nonbondedMethod
not
in
methodMap
:
raise
ValueError
(
'Illegal nonbonded method for CustomGBForce'
)
force
=
mm
.
CustomGBForce
()
for
param
in
self
.
globalParams
:
force
.
addGlobalParameter
(
param
,
self
.
globalParams
[
param
])
for
param
in
self
.
perParticleParams
:
force
.
addPerParticleParameter
(
param
)
for
value
in
self
.
computedValues
:
force
.
addComputedValue
(
value
[
0
],
value
[
1
],
value
[
2
])
for
term
in
self
.
energyTerms
:
force
.
addEnergyTerm
(
term
[
0
],
term
[
1
])
for
function
in
self
.
functions
:
force
.
addFunction
(
function
[
0
],
function
[
1
],
function
[
2
],
function
[
3
])
for
atom
in
data
.
atoms
:
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
force
.
addParticle
(
self
.
typeMap
[
t
])
else
:
raise
ValueError
(
'No CustomGB parameters defined for atom type '
+
t
)
force
.
setNonbondedMethod
(
methodMap
[
nonbondedMethod
])
force
.
setCutoffDistance
(
nonbondedCutoff
)
sys
.
addForce
(
force
)
parsers
[
"CustomGBForce"
]
=
CustomGBGenerator
.
parseElement
def
getAtomPrint
(
data
,
atomIndex
):
if
(
atomIndex
<
len
(
data
.
atoms
)
):
atom
=
data
.
atoms
[
atomIndex
]
returnString
=
"%4s %4s %5d"
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
else
:
returnString
=
"NA"
return
returnString
#=============================================================================================
def
countConstraint
(
data
):
bondCount
=
0
angleCount
=
0
for
bond
in
data
.
bonds
:
if
bond
.
isConstrained
:
bondCount
+=
1
angleCount
=
0
for
(
angle
,
isConstrained
)
in
zip
(
data
.
angles
,
data
.
isAngleConstrained
):
if
(
isConstrained
):
angleCount
+=
1
print
"Constraints bond=%d angle=%d total=%d"
%
(
bondCount
,
angleCount
,
(
bondCount
+
angleCount
)
)
class
AmoebaHarmonicBondGenerator
:
#=============================================================================================
"""An AmoebaHarmonicBondGenerator constructs a AmoebaHarmonicBondForce."""
#=============================================================================================
def
__init__
(
self
,
cubic
,
quartic
):
self
.
cubic
=
cubic
self
.
quartic
=
quartic
self
.
types1
=
[]
self
.
types2
=
[]
self
.
length
=
[]
self
.
k
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaHarmonicBondForce bond-cubic="-25.5" bond-quartic="379.3125">
# <Bond class1="1" class2="2" length="0.1437" k="156900.0"/>
generator
=
AmoebaHarmonicBondGenerator
(
float
(
element
.
attrib
[
'bond-cubic'
]),
float
(
element
.
attrib
[
'bond-quartic'
])
)
forceField
.
_forces
.
append
(
generator
)
for
bond
in
element
.
findall
(
'Bond'
):
types
=
forceField
.
_findAtomTypes
(
bond
,
2
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
length
.
append
(
float
(
bond
.
attrib
[
'length'
]))
generator
.
k
.
append
(
float
(
bond
.
attrib
[
'k'
]))
else
:
outputString
=
"AmoebaHarmonicBondGenerator: error getting types: %s %s"
%
(
bond
.
attrib
[
'class1'
],
bond
.
attrib
[
'class2'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
if
(
verbose
):
countConstraint
(
data
)
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaHarmonicBondForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaHarmonicBondForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
force
.
setAmoebaGlobalHarmonicBondCubic
(
self
.
cubic
)
force
.
setAmoebaGlobalHarmonicBondQuartic
(
self
.
quartic
)
if
(
verbose
):
print
"In AmoebaHarmonicBondGenerator bonds=%d "
%
(
len
(
data
.
bonds
))
count
=
0
for
bond
in
data
.
bonds
:
type1
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom1
]]
type2
=
data
.
atomType
[
data
.
atoms
[
bond
.
atom2
]]
hit
=
0
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
if
(
type1
in
types1
and
type2
in
types2
)
or
(
type1
in
types2
and
type2
in
types1
):
bond
.
length
=
self
.
length
[
i
]
hit
=
1
if
bond
.
isConstrained
:
sys
.
addConstraint
(
bond
.
atom1
,
bond
.
atom2
,
self
.
length
[
i
])
if
(
verbose
):
atomS1
=
getAtomPrint
(
data
,
bond
.
atom1
)
atomS2
=
getAtomPrint
(
data
,
bond
.
atom2
)
print
"AmoebaHarmonicBondGenerator %5d %5d %5d [%s %s] [%5s %5s] %15.6f %15.6f Constraint"
%
(
count
,
bond
.
atom1
,
bond
.
atom2
,
atomS1
,
atomS2
,
type1
,
type2
,
self
.
length
[
i
],
self
.
k
[
i
])
elif
self
.
k
[
i
]
!=
0
:
if
(
verbose
):
atomS1
=
getAtomPrint
(
data
,
bond
.
atom1
)
atomS2
=
getAtomPrint
(
data
,
bond
.
atom2
)
print
"AmoebaHarmonicBondGenerator %5d %5d %5d [%s %s] [%5s %5s] %15.6f %15.6f"
%
(
count
,
bond
.
atom1
,
bond
.
atom2
,
atomS1
,
atomS2
,
type1
,
type2
,
self
.
length
[
i
],
self
.
k
[
i
])
force
.
addBond
(
bond
.
atom1
,
bond
.
atom2
,
self
.
length
[
i
],
self
.
k
[
i
])
break
if
(
hit
==
0
):
print
"AmoebaHarmonicBondGenerator missing: %5d types=[%5s %5s] atoms=[%6d %6d] "
%
(
count
,
type1
,
type2
,
bond
.
atom1
,
bond
.
atom2
)
count
+=
1
parsers
[
"AmoebaHarmonicBondForce"
]
=
AmoebaHarmonicBondGenerator
.
parseElement
#=============================================================================================
# Add angle constraint
#=============================================================================================
def
addAngleConstraint
(
angle
,
idealAngle
,
data
,
sys
):
# Find the two bonds that make this angle.
bond1
=
None
bond2
=
None
for
bond
in
data
.
atomBonds
[
angle
[
1
]]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
atom1
==
angle
[
0
]
or
atom2
==
angle
[
0
]:
bond1
=
bond
elif
atom1
==
angle
[
2
]
or
atom2
==
angle
[
2
]:
bond2
=
bond
# Compute the distance between atoms and add a constraint
if
bond1
is
not
None
and
bond2
is
not
None
:
l1
=
data
.
bonds
[
bond1
].
length
l2
=
data
.
bonds
[
bond2
].
length
if
l1
is
not
None
and
l2
is
not
None
:
length
=
sqrt
(
l1
*
l1
+
l2
*
l2
-
2
*
l1
*
l2
*
cos
(
idealAngle
))
sys
.
addConstraint
(
angle
[
0
],
angle
[
2
],
length
)
return
#=============================================================================================
class
AmoebaHarmonicAngleGenerator
:
#=============================================================================================
"""An AmoebaHarmonicAngleGenerator constructs a AmoebaHarmonicAngleForce."""
#=============================================================================================
def
__init__
(
self
,
forceField
,
cubic
,
quartic
,
pentic
,
sextic
):
self
.
forceField
=
forceField
self
.
cubic
=
cubic
self
.
quartic
=
quartic
self
.
pentic
=
pentic
self
.
sextic
=
sextic
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
angle
=
[]
self
.
k
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaHarmonicAngleForce angle-cubic="-0.014" angle-quartic="5.6e-05" angle-pentic="-7e-07" angle-sextic="2.2e-08">
# <Angle class1="2" class2="1" class3="3" k="0.0637259642196" angle1="122.00" />
generator
=
AmoebaHarmonicAngleGenerator
(
forceField
,
float
(
element
.
attrib
[
'angle-cubic'
]),
float
(
element
.
attrib
[
'angle-quartic'
]),
float
(
element
.
attrib
[
'angle-pentic'
]),
float
(
element
.
attrib
[
'angle-sextic'
]
)
)
forceField
.
_forces
.
append
(
generator
)
for
angle
in
element
.
findall
(
'Angle'
):
types
=
forceField
.
_findAtomTypes
(
angle
,
3
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
angleList
=
[]
angleList
.
append
(
float
(
angle
.
attrib
[
'angle1'
])
)
try
:
angleList
.
append
(
float
(
angle
.
attrib
[
'angle2'
])
)
try
:
angleList
.
append
(
float
(
angle
.
attrib
[
'angle3'
])
)
except
:
pass
except
:
pass
generator
.
angle
.
append
(
angleList
)
generator
.
k
.
append
(
float
(
angle
.
attrib
[
'k'
]))
else
:
outputString
=
"AmoebaHarmonicAngleGenerator: error getting types: %s %s %s"
%
(
angle
.
attrib
[
'class1'
],
angle
.
attrib
[
'class2'
],
angle
.
attrib
[
'class3'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
# createForce is bypassed here since the AmoebaOutOfPlaneBendForce generator must first execute
# and partition angles into in-plane and non-in-plane angles
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
pass
#=============================================================================================
# createForcePostOpBendAngle is called by AmoebaOutOfPlaneBendForce with the list of
# non-in-plane angles
#=============================================================================================
def
createForcePostOpBendAngle
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
angleList
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
+=
1
# get force
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaHarmonicAngleForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaHarmonicAngleForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
# scalars
force
.
setAmoebaGlobalHarmonicAngleCubic
(
self
.
cubic
)
force
.
setAmoebaGlobalHarmonicAngleQuartic
(
self
.
quartic
)
force
.
setAmoebaGlobalHarmonicAnglePentic
(
self
.
pentic
)
force
.
setAmoebaGlobalHarmonicAngleSextic
(
self
.
sextic
)
if
(
verbose
):
print
"In AmoebaHarmonicAngleGenerator angles=%d "
%
(
len
(
data
.
angles
))
count
=
0
for
angleDict
in
angleList
:
angle
=
angleDict
[
'angle'
]
isConstrained
=
angleDict
[
'isConstrained'
]
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
hit
=
0
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
)
or
(
type1
in
types3
and
type2
in
types2
and
type3
in
types1
):
hit
=
1
if
isConstrained
and
self
.
k
[
i
]
!=
0.0
:
angleDict
[
'idealAngle'
]
=
self
.
angle
[
i
][
0
]
addAngleConstraint
(
angle
,
self
.
angle
[
i
][
0
],
data
,
sys
)
elif
self
.
k
[
i
]
!=
0
:
# print "AmoebaHarmonicAngleGenerator %5d %5d %5d %5d [%5s %5s %5s] %15.6f %15.6f" % (count, angle[0], angle[1], angle[2], type1, type2, type3, self.angle[i], self.k[i])
lenAngle
=
len
(
self
.
angle
[
i
]
)
if
(
lenAngle
>
1
):
# get k-index by counting number of non-angle hydrogens on the central atom
# based on kangle.f
numberOfHydrogens
=
0
for
bond
in
data
.
atomBonds
[
angle
[
1
]]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
(
atom1
==
angle
[
1
]
and
atom2
!=
angle
[
0
]
and
atom2
!=
angle
[
2
]
and
(
sys
.
getParticleMass
(
atom2
)
/
unit
.
dalton
)
<
1.90
):
numberOfHydrogens
+=
1
if
(
atom2
==
angle
[
1
]
and
atom1
!=
angle
[
0
]
and
atom1
!=
angle
[
2
]
and
(
sys
.
getParticleMass
(
atom1
)
/
unit
.
dalton
)
<
1.90
):
numberOfHydrogens
+=
1
if
(
numberOfHydrogens
<
lenAngle
):
angleValue
=
self
.
angle
[
i
][
numberOfHydrogens
]
else
:
print
"Error: AmoebaHarmonicAngleGenerator angle index=%d is out of range: [0, %5d] "
%
(
numberOfHydrogens
,
lenAngle
)
sys
.
exit
(
-
1
)
else
:
angleValue
=
self
.
angle
[
i
][
0
]
angleDict
[
'idealAngle'
]
=
angleValue
force
.
addAngle
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
angleValue
,
self
.
k
[
i
]
)
break
if
(
hit
==
0
):
print
"AmoebaHarmonicAngleGenerator missing: %5d %s %s %s "
%
(
count
,
type1
,
type2
,
type3
)
count
+=
1
#=============================================================================================
# createForcePostOpBendInPlaneAngle is called by AmoebaOutOfPlaneBendForce with the list of
# in-plane angles
#=============================================================================================
def
createForcePostOpBendInPlaneAngle
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
angleList
,
args
):
verbose
=
0
self
.
hasBeenCalled
+=
1
# get force
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaHarmonicInPlaneAngleForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaHarmonicInPlaneAngleForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
# scalars
force
.
setAmoebaGlobalHarmonicInPlaneAngleCubic
(
self
.
cubic
)
force
.
setAmoebaGlobalHarmonicInPlaneAngleQuartic
(
self
.
quartic
)
force
.
setAmoebaGlobalHarmonicInPlaneAnglePentic
(
self
.
pentic
)
force
.
setAmoebaGlobalHarmonicInPlaneAngleSextic
(
self
.
sextic
)
if
(
verbose
):
print
"In AmoebaHarmonicAngleGenerator angles=%d "
%
(
len
(
data
.
angles
))
count
=
0
for
angleDict
in
angleList
:
angle
=
angleDict
[
'angle'
]
isConstrained
=
angleDict
[
'isConstrained'
]
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
hit
=
0
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
)
or
(
type1
in
types3
and
type2
in
types2
and
type3
in
types1
):
if
(
verbose
):
print
"AmoebaHarmonicInPlaneAngleGenerator %5d %5d %5d %5d len=%d [%5s %5s %5s] %15.6f %15.6f"
%
(
count
,
angle
[
0
],
angle
[
1
],
angle
[
2
],
len
(
angle
),
type1
,
type2
,
type3
,
self
.
angle
[
i
][
0
],
self
.
k
[
i
])
hit
=
1
angleDict
[
'idealAngle'
]
=
self
.
angle
[
i
][
0
]
if
(
isConstrained
and
self
.
k
[
i
]
!=
0.0
):
addAngleConstraint
(
angle
,
self
.
angle
[
i
][
0
],
data
,
sys
)
else
:
force
.
addAngle
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
angle
[
3
],
self
.
angle
[
i
][
0
],
self
.
k
[
i
])
break
if
(
hit
==
0
):
print
"AmoebaHarmonicInPlaneAngleGenerator missing: %5d %s %s "
%
(
count
,
type1
,
type2
,
type3
)
count
+=
1
parsers
[
"AmoebaHarmonicAngleForce"
]
=
AmoebaHarmonicAngleGenerator
.
parseElement
#=============================================================================================
# Generator for the AmoebaOutOfPlaneBend covalent force; also calls methods in the
# AmoebaHarmonicAngleGenerator to generate the AmoebaHarmonicAngleForce and
# AmoebaHarmonicInPlaneAngleForce
#=============================================================================================
class
AmoebaOutOfPlaneBendGenerator
:
#=============================================================================================
"""An AmoebaOutOfPlaneBendGenerator constructs a AmoebaOutOfPlaneBendForce."""
#=============================================================================================
def
__init__
(
self
,
forceField
,
type
,
cubic
,
quartic
,
pentic
,
sextic
):
self
.
forceField
=
forceField
self
.
type
=
type
self
.
cubic
=
cubic
self
.
quartic
=
quartic
self
.
pentic
=
pentic
self
.
sextic
=
sextic
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
types4
=
[]
self
.
ks
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
# Local version of findAtomTypes needed since class indices are 0 (i.e., not recognized)
# for types3 and 4
#=============================================================================================
def
findAtomTypes
(
self
,
forceField
,
node
,
num
):
"""Parse the attributes on an XML tag to find the set of atom types for each atom it involves."""
types
=
[]
attrib
=
node
.
attrib
for
i
in
range
(
num
):
if
num
==
1
:
suffix
=
''
else
:
suffix
=
str
(
i
+
1
)
classAttrib
=
'class'
+
suffix
if
classAttrib
in
attrib
:
if
attrib
[
classAttrib
]
in
forceField
.
_atomClasses
:
types
.
append
(
forceField
.
_atomClasses
[
attrib
[
classAttrib
]])
else
:
types
.
append
(
set
())
return
types
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaOutOfPlaneBendForce type="ALLINGER" opbend-cubic="-0.014" opbend-quartic="5.6e-05" opbend-pentic="-7e-07" opbend-sextic="2.2e-08">
# <Angle class1="2" class2="1" class3="0" class4="0" k="0.0531474541591"/>
# <Angle class1="3" class2="1" class3="0" class4="0" k="0.0898536095496"/>
# get global scalar parameters
generator
=
AmoebaOutOfPlaneBendGenerator
(
forceField
,
element
.
attrib
[
'type'
],
float
(
element
.
attrib
[
'opbend-cubic'
]),
float
(
element
.
attrib
[
'opbend-quartic'
]),
float
(
element
.
attrib
[
'opbend-pentic'
]),
float
(
element
.
attrib
[
'opbend-sextic'
]
)
)
forceField
.
_forces
.
append
(
generator
)
for
angle
in
element
.
findall
(
'Angle'
):
types
=
generator
.
findAtomTypes
(
forceField
,
angle
,
4
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
]
)
generator
.
types2
.
append
(
types
[
1
]
)
generator
.
types3
.
append
(
types
[
2
]
)
generator
.
types4
.
append
(
types
[
3
]
)
generator
.
ks
.
append
(
float
(
angle
.
attrib
[
'k'
]
)
)
else
:
outputString
=
"AmoebaOutOfPlaneBendGenerator: error getting types: %s %s %s %s."
%
(
angle
.
attrib
[
'class1'
],
angle
.
attrib
[
'class2'
],
angle
.
attrib
[
'class3'
],
angle
.
attrib
[
'class4'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
# Get middle atom in a angle
# return index of middle atom or -1 if no middle is found
# This method appears not to be needed since the angle[1] entry appears to always
# be the middle atom. However, was unsure if this is guaranteed
#=============================================================================================
def
getMiddleAtom
(
self
,
angle
,
data
):
# find atom shared by both bonds making up the angle
middleAtom
=
-
1
for
atomIndex
in
angle
:
isMiddle
=
0
for
bond
in
data
.
atomBonds
[
atomIndex
]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
(
atom1
!=
atomIndex
):
partner
=
atom1
else
:
partner
=
atom2
if
(
partner
==
angle
[
0
]
or
partner
==
angle
[
1
]
or
partner
==
angle
[
2
]
):
isMiddle
+=
1
if
(
isMiddle
==
2
):
return
atomIndex
return
-
1
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
# get force
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaOutOfPlaneBendForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaOutOfPlaneBendForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
# set scalars
force
.
setAmoebaGlobalOutOfPlaneBendCubic
(
self
.
cubic
)
force
.
setAmoebaGlobalOutOfPlaneBendQuartic
(
self
.
quartic
)
force
.
setAmoebaGlobalOutOfPlaneBendPentic
(
self
.
pentic
)
force
.
setAmoebaGlobalOutOfPlaneBendSextic
(
self
.
sextic
)
count
=
0
opBondCount
=
0
# this hash is used to insure the out-of-plane-bend bonds
# are only added once
skipAtoms
=
dict
()
# these lists are used in the partitioning of the angles into
# angle and inPlane angles
inPlaneAngles
=
[]
nonInPlaneAngles
=
[]
nonInPlaneAnglesConstrained
=
[]
idealAngles
=
[]
*
len
(
data
.
angles
)
for
(
angle
,
isConstrained
)
in
zip
(
data
.
angles
,
data
.
isAngleConstrained
):
middleAtom
=
self
.
getMiddleAtom
(
angle
,
data
)
if
(
middleAtom
>
-
1
):
middleType
=
data
.
atomType
[
data
.
atoms
[
middleAtom
]]
middleCovalency
=
len
(
data
.
atomBonds
[
middleAtom
])
else
:
middleType
=
-
1
middleCovalency
=
-
1
# if middle atom has covalency of 3 and
# the types of the middle atom and the partner atom (atom bonded to
# middle atom, but not in angle) match types2 and types2, then
# three out-of-plane bend angles are generated. Three in-plane angle
# are also generated. If the conditions are not satisfied the angle is generic angle (not a in-plane angle)
if
(
middleAtom
>
-
1
and
middleCovalency
==
3
and
middleAtom
not
in
skipAtoms
):
partners
=
[]
partnerSet
=
set
()
partnerTypes
=
[]
partnerK
=
[]
for
bond
in
data
.
atomBonds
[
middleAtom
]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
(
atom1
!=
middleAtom
):
partner
=
atom1
else
:
partner
=
atom2
partnerType
=
data
.
atomType
[
data
.
atoms
[
partner
]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
if
(
middleType
in
types2
and
partnerType
in
types1
):
partners
.
append
(
partner
)
partnerSet
.
add
(
partner
)
partnerTypes
.
append
(
partnerType
)
partnerK
.
append
(
self
.
ks
[
i
]
)
if
(
len
(
partners
)
==
3
):
opBondCount
+=
3
if
(
verbose
):
print
"%5d Opbend: %d type=%s cov=%d "
%
(
opBondCount
,
middleAtom
,
middleType
,
middleCovalency
)
force
.
addOutOfPlaneBend
(
partners
[
0
],
middleAtom
,
partners
[
1
],
partners
[
2
],
partnerK
[
2
]
)
force
.
addOutOfPlaneBend
(
partners
[
0
],
middleAtom
,
partners
[
2
],
partners
[
1
],
partnerK
[
1
]
)
force
.
addOutOfPlaneBend
(
partners
[
1
],
middleAtom
,
partners
[
2
],
partners
[
0
],
partnerK
[
0
]
)
skipAtoms
[
middleAtom
]
=
set
()
skipAtoms
[
middleAtom
].
add
(
partners
[
0
]
)
skipAtoms
[
middleAtom
].
add
(
partners
[
1
]
)
skipAtoms
[
middleAtom
].
add
(
partners
[
2
]
)
angleDict
=
{}
angleList
=
[]
angleList
.
append
(
angle
[
0
]
)
angleList
.
append
(
angle
[
1
]
)
angleList
.
append
(
angle
[
2
]
)
angleDict
[
'angle'
]
=
angleList
angleDict
[
'isConstrained'
]
=
0
angleSet
=
set
()
angleSet
.
add
(
angle
[
0
]
)
angleSet
.
add
(
angle
[
1
]
)
angleSet
.
add
(
angle
[
2
]
)
for
atomIndex
in
partnerSet
:
if
(
atomIndex
not
in
angleSet
):
angleList
.
append
(
atomIndex
)
if
(
verbose
):
print
"%5d Opbend: middle=%d inPlane1 %d %s"
%
(
opBondCount
,
middleAtom
,
len
(
angleList
),
str
(
angleList
)
)
inPlaneAngles
.
append
(
angleDict
)
else
:
if
(
verbose
):
print
"%5d Opbend: %d type=%s cov=%d xxx"
%
(
opBondCount
,
middleAtom
,
middleType
,
middleCovalency
)
print
"%5d Opbend: middle=%d inPlane1 %d %s"
%
(
opBondCount
,
middleAtom
,
len
(
angleList
),
str
(
angleList
)
)
angleDict
=
{}
angleDict
[
'angle'
]
=
angle
angleDict
[
'isConstrained'
]
=
isConstrained
nonInPlaneAngles
.
append
(
angleDict
)
else
:
if
(
middleAtom
>
-
1
and
middleCovalency
==
3
and
middleAtom
in
skipAtoms
):
partnerSet
=
skipAtoms
[
middleAtom
]
angleDict
=
{}
angleList
=
[]
angleList
.
append
(
angle
[
0
]
)
angleList
.
append
(
angle
[
1
]
)
angleList
.
append
(
angle
[
2
]
)
angleDict
[
'angle'
]
=
angleList
angleDict
[
'isConstrained'
]
=
isConstrained
angleSet
=
set
()
angleSet
.
add
(
angle
[
0
]
)
angleSet
.
add
(
angle
[
1
]
)
angleSet
.
add
(
angle
[
2
]
)
for
atomIndex
in
partnerSet
:
if
(
atomIndex
not
in
angleSet
):
angleList
.
append
(
atomIndex
)
if
(
verbose
):
print
"%5d Opbend: middle=%d inPlane2 %d angleList=%s partnerSet=%s"
%
(
opBondCount
,
middleAtom
,
len
(
angleList
),
str
(
angleList
),
str
(
partnerSet
)
)
inPlaneAngles
.
append
(
angleDict
)
else
:
angleDict
=
{}
angleDict
[
'angle'
]
=
angle
angleDict
[
'isConstrained'
]
=
isConstrained
nonInPlaneAngles
.
append
(
angleDict
)
count
+=
1
# get AmoebaHarmonicAngleGenerator and add AmoebaHarmonicAngle and AmoebaHarmonicInPlaneAngle forces
for
force
in
self
.
forceField
.
_forces
:
if
(
force
.
__class__
.
__name__
==
'AmoebaHarmonicAngleGenerator'
):
force
.
createForcePostOpBendAngle
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
nonInPlaneAngles
,
args
)
force
.
createForcePostOpBendInPlaneAngle
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
inPlaneAngles
,
args
)
if
(
force
.
__class__
.
__name__
==
'AmoebaHarmonicBondGenerator'
):
force
.
createForce
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
)
for
force
in
self
.
forceField
.
_forces
:
if
(
force
.
__class__
.
__name__
==
'AmoebaStretchBendGenerator'
):
for
angleDict
in
inPlaneAngles
:
nonInPlaneAngles
.
append
(
angleDict
)
force
.
createForcePostAmoebaHarmonicBondForce
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
nonInPlaneAngles
,
args
)
parsers
[
"AmoebaOutOfPlaneBendForce"
]
=
AmoebaOutOfPlaneBendGenerator
.
parseElement
#=============================================================================================
class
AmoebaTorsionGenerator
:
#=============================================================================================
"""An AmoebaTorsionGenerator constructs a AmoebaTorsionForce."""
#=============================================================================================
def
__init__
(
self
,
torsionUnit
):
self
.
torsionUnit
=
torsionUnit
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
types4
=
[]
self
.
t1
=
[]
self
.
t2
=
[]
self
.
t3
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaTorsionForce torsionUnit="0.5">
# <Torsion class1="3" class2="1" class3="2" class4="3" amp1="0.0" angle1="0.0" amp2="0.0" angle2="3.14159265359" amp3="0.0" angle3="0.0" />
# <Torsion class1="3" class2="1" class3="2" class4="6" amp1="0.0" angle1="0.0" amp2="0.0" angle2="3.14159265359" amp3="-0.263592" angle3="0.0" />
generator
=
AmoebaTorsionGenerator
(
float
(
element
.
attrib
[
'torsionUnit'
])
)
forceField
.
_forces
.
append
(
generator
)
# collect particle classes and t1,t2,t3,
# where ti=[amplitude_i,angle_i]
for
torsion
in
element
.
findall
(
'Torsion'
):
types
=
forceField
.
_findAtomTypes
(
torsion
,
4
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
types4
.
append
(
types
[
3
])
for
ii
in
range
(
1
,
4
):
tInfo
=
[]
suffix
=
str
(
ii
)
ampName
=
'amp'
+
suffix
tInfo
.
append
(
float
(
torsion
.
attrib
[
ampName
]))
angName
=
'angle'
+
suffix
tInfo
.
append
(
float
(
torsion
.
attrib
[
angName
]))
if
(
ii
==
1
):
generator
.
t1
.
append
(
tInfo
)
elif
(
ii
==
2
):
generator
.
t2
.
append
(
tInfo
)
elif
(
ii
==
3
):
generator
.
t3
.
append
(
tInfo
)
else
:
outputString
=
"AmoebaTorsionGenerator: error getting types: %s %s %s %s"
%
(
stretchBend
.
attrib
[
'class1'
],
stretchBend
.
attrib
[
'class2'
],
stretchBend
.
attrib
[
'class3'
],
stretchBend
.
attrib
[
'class4'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nontorsionedMethod
,
nontorsionedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
if
(
verbose
):
print
"In AmoebaTorsionGenerator torsions=%d "
%
(
len
(
data
.
propers
))
count
=
0
for
torsion
in
data
.
propers
:
type1
=
data
.
atomType
[
data
.
atoms
[
torsion
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
torsion
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
torsion
[
2
]]]
type4
=
data
.
atomType
[
data
.
atoms
[
torsion
[
3
]]]
hit
=
0
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
types4
=
self
.
types4
[
i
]
# match types in forward or reverse direction
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
and
type4
in
types4
)
or
(
type4
in
types1
and
type3
in
types2
and
type2
in
types3
and
type1
in
types4
):
if
(
verbose
):
print
"AmoebaTorsionGenerator %5d [%5d %5d %5d %5d] [%5s %5s %5s %5s]"
%
(
count
,
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
type1
,
type2
,
type3
,
type4
)
hit
=
1
force
.
addTorsion
(
torsion
[
0
],
torsion
[
1
],
torsion
[
2
],
torsion
[
3
],
self
.
t1
[
i
],
self
.
t2
[
i
],
self
.
t3
[
i
]
)
break
if
(
hit
==
0
):
print
"AmoebaTorsionGenerator missing: %5d %s %s "
%
(
count
,
type1
,
type2
,
type3
,
type4
)
count
+=
1
if
(
verbose
):
print
"AmoebaTorsionGenerator number of torsions added=%d "
%
(
force
.
getNumTorsions
())
parsers
[
"AmoebaTorsionForce"
]
=
AmoebaTorsionGenerator
.
parseElement
#=============================================================================================
class
AmoebaPiTorsionGenerator
:
#=============================================================================================
"""An AmoebaPiTorsionGenerator constructs a AmoebaPiTorsionForce."""
#=============================================================================================
def
__init__
(
self
,
piTorsionUnit
):
self
.
piTorsionUnit
=
piTorsionUnit
self
.
types1
=
[]
self
.
types2
=
[]
self
.
k
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaPiTorsionForce piTorsionUnit="1.0">
# <PiTorsion class1="1" class2="3" k="28.6604" />
generator
=
AmoebaPiTorsionGenerator
(
float
(
element
.
attrib
[
'piTorsionUnit'
])
)
forceField
.
_forces
.
append
(
generator
)
for
piTorsion
in
element
.
findall
(
'PiTorsion'
):
types
=
forceField
.
_findAtomTypes
(
piTorsion
,
2
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
k
.
append
(
float
(
piTorsion
.
attrib
[
'k'
]))
else
:
outputString
=
"AmoebaPiTorsionGenerator: error getting types: %s %s "
%
(
piTorsion
.
attrib
[
'class1'
],
piTorsion
.
attrib
[
'class2'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonpiTorsionedMethod
,
nonpiTorsionedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaPiTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaPiTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
count
=
0
for
bond
in
data
.
bonds
:
# search for bonds with both atoms in bond having covalency == 3
atom1
=
bond
.
atom1
atom2
=
bond
.
atom2
if
(
len
(
data
.
atomBonds
[
atom1
])
==
3
and
len
(
data
.
atomBonds
[
atom1
])
==
3
):
type1
=
data
.
atomType
[
data
.
atoms
[
atom1
]]
type2
=
data
.
atomType
[
data
.
atoms
[
atom2
]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
if
(
type1
in
types1
and
type2
in
types2
)
or
(
type1
in
types2
and
type2
in
types1
):
if
(
verbose
):
print
"AmoebaPiTorsionGenerator %5d %5d %5d [%5s %5s] %15.6f"
%
(
count
,
atom1
,
atom2
,
type1
,
type2
,
self
.
k
[
i
])
# piTorsionAtom1, piTorsionAtom2 are the atoms bonded to atom1, excluding atom2
# piTorsionAtom5, piTorsionAtom6 are the atoms bonded to atom2, excluding atom1
piTorsionAtom1
=
-
1
piTorsionAtom2
=
-
1
piTorsionAtom3
=
atom1
piTorsionAtom4
=
atom2
piTorsionAtom5
=
-
1
piTorsionAtom6
=
-
1
for
bond
in
data
.
atomBonds
[
atom1
]:
bondedAtom1
=
data
.
bonds
[
bond
].
atom1
bondedAtom2
=
data
.
bonds
[
bond
].
atom2
if
(
bondedAtom1
!=
atom1
):
b1
=
bondedAtom1
else
:
b1
=
bondedAtom2
if
(
b1
!=
atom2
):
if
(
piTorsionAtom1
==
-
1
):
piTorsionAtom1
=
b1
else
:
piTorsionAtom2
=
b1
for
bond
in
data
.
atomBonds
[
atom2
]:
bondedAtom1
=
data
.
bonds
[
bond
].
atom1
bondedAtom2
=
data
.
bonds
[
bond
].
atom2
if
(
bondedAtom1
!=
atom2
):
b1
=
bondedAtom1
else
:
b1
=
bondedAtom2
if
(
b1
!=
atom1
):
if
(
piTorsionAtom5
==
-
1
):
piTorsionAtom5
=
b1
else
:
piTorsionAtom6
=
b1
force
.
addPiTorsion
(
piTorsionAtom1
,
piTorsionAtom2
,
piTorsionAtom3
,
piTorsionAtom4
,
piTorsionAtom5
,
piTorsionAtom6
,
self
.
k
[
i
])
count
+=
1
if
(
verbose
):
print
"AmoebaPiTorsionGenerator number of pi-torsions added=%d "
%
(
force
.
getNumPiTorsions
())
parsers
[
"AmoebaPiTorsionForce"
]
=
AmoebaPiTorsionGenerator
.
parseElement
#=============================================================================================
class
AmoebaTorsionTorsionGenerator
:
#=============================================================================================
"""An AmoebaTorsionTorsionGenerator constructs a AmoebaTorsionTorsionForce."""
#=============================================================================================
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
types4
=
[]
self
.
types5
=
[]
self
.
gridIndex
=
[]
self
.
grids
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
generator
=
AmoebaTorsionTorsionGenerator
(
)
forceField
.
_forces
.
append
(
generator
)
maxGridIndex
=
-
1
# <AmoebaTorsionTorsionForce >
# <TorsionTorsion class1="3" class2="1" class3="2" class4="3" class5="1" grid="0" nx="25" ny="25" />
for
torsionTorsion
in
element
.
findall
(
'TorsionTorsion'
):
types
=
forceField
.
_findAtomTypes
(
torsionTorsion
,
5
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
types4
.
append
(
types
[
3
])
generator
.
types5
.
append
(
types
[
4
])
gridIndex
=
int
(
torsionTorsion
.
attrib
[
'grid'
])
if
(
gridIndex
>
maxGridIndex
):
maxGridIndex
=
gridIndex
generator
.
gridIndex
.
append
(
gridIndex
)
else
:
outputString
=
"AmoebaTorsionTorsionGenerator: error getting types: %s %s %s %s %s"
%
(
torsionTorsion
.
attrib
[
'class1'
],
torsionTorsion
.
attrib
[
'class2'
],
torsionTorsion
.
attrib
[
'class3'
],
torsionTorsion
.
attrib
[
'class4'
],
torsionTorsion
.
attrib
[
'class5'
]
)
raise
ValueError
(
outputString
)
# load grid
# xml source
# <TorsionTorsionGrid grid="0" nx="25" ny="25" >
# <Grid angle1="-180.00" angle2="-180.00" f="0.0" fx="2.31064374824e-05" fy="0.0" fxy="-0.0052801799672" />
# <Grid angle1="-165.00" angle2="-180.00" f="-0.66600912" fx="-0.06983370052" fy="-0.075058725744" fxy="-0.0044462732032" />
# output grid:
# grid[x][y][0] = x value
# grid[x][y][1] = y value
# grid[x][y][2] = function value
# grid[x][y][3] = dfdx value
# grid[x][y][4] = dfdy value
# grid[x][y][5] = dfd(xy) value
maxGridIndex
+=
1
generator
.
grids
=
maxGridIndex
*
[]
for
torsionTorsionGrid
in
element
.
findall
(
'TorsionTorsionGrid'
):
gridIndex
=
int
(
torsionTorsionGrid
.
attrib
[
"grid"
]
)
nx
=
int
(
torsionTorsionGrid
.
attrib
[
"nx"
]
)
ny
=
int
(
torsionTorsionGrid
.
attrib
[
"ny"
]
)
grid
=
[]
gridCol
=
[]
gridColIndex
=
0
for
gridEntry
in
torsionTorsionGrid
.
findall
(
'Grid'
):
gridRow
=
[]
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'angle1'
]
)
)
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'angle2'
]
)
)
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'f'
]
)
)
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'fx'
]
)
)
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'fy'
]
)
)
gridRow
.
append
(
float
(
gridEntry
.
attrib
[
'fxy'
]
)
)
gridCol
.
append
(
gridRow
)
gridColIndex
+=
1
if
(
gridColIndex
==
nx
):
grid
.
append
(
gridCol
)
gridCol
=
[]
gridColIndex
=
0
if
(
gridIndex
==
len
(
generator
.
grids
)
):
generator
.
grids
.
append
(
grid
)
else
:
while
(
len
(
generator
.
grids
)
<
gridIndex
):
generator
.
grids
.
append
(
[]
)
generator
.
grids
[
gridIndex
]
=
grid
#=============================================================================================
def
getChiralAtomIndex
(
self
,
data
,
sys
,
atomB
,
atomC
,
atomD
):
chiralAtomIndex
=
-
1
# if atomC has four bonds, find the
# two bonds that do not include atomB and atomD
# set chiralAtomIndex to one of these, if they are
# not the same atom(type/mass)
if
(
len
(
data
.
atomBonds
[
atomC
])
==
4
):
atomE
=
-
1
atomF
=
-
1
for
bond
in
data
.
atomBonds
[
atomC
]:
bondedAtom1
=
data
.
bonds
[
bond
].
atom1
bondedAtom2
=
data
.
bonds
[
bond
].
atom2
hit
=
-
1
if
(
bondedAtom1
==
atomC
and
bondedAtom2
!=
atomB
and
bondedAtom2
!=
atomD
):
hit
=
bondedAtom2
elif
(
bondedAtom2
==
atomC
and
bondedAtom1
!=
atomB
and
bondedAtom1
!=
atomD
):
hit
=
bondedAtom1
if
(
hit
>
-
1
):
if
(
atomE
==
-
1
):
atomE
=
hit
else
:
atomF
=
hit
# raise error if atoms E or F not found
if
(
atomE
==
-
1
or
atomF
==
-
1
):
outputString
=
"getChiralAtomIndex: error getting bonded partners of atomC=%s %d %s"
%
(
atomC
.
name
,
atomC
.
resiude
.
index
,
atomC
.
resiude
.
name
,
)
raise
ValueError
(
outputString
)
# check for different type/mass between atoms E & F
typeE
=
int
(
data
.
atomType
[
data
.
atoms
[
atomE
]])
typeF
=
int
(
data
.
atomType
[
data
.
atoms
[
atomF
]])
if
(
typeE
>
typeF
):
chiralAtomIndex
=
atomE
if
(
typeF
>
typeE
):
chiralAtomIndex
=
atomF
massE
=
sys
.
getParticleMass
(
atomE
)
/
unit
.
dalton
massF
=
sys
.
getParticleMass
(
atomE
)
/
unit
.
dalton
if
(
massE
>
massF
):
chiralAtomIndex
=
massE
if
(
massF
>
massE
):
chiralAtomIndex
=
massF
return
chiralAtomIndex
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonpiTorsionedMethod
,
nonpiTorsionedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaTorsionTorsionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaTorsionTorsionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
count
=
0
for
angle
in
data
.
angles
:
# search for bitorsions; based on TINKER subroutine bitors()
ib
=
angle
[
0
]
ic
=
angle
[
1
]
id
=
angle
[
2
]
for
bondIndex
in
data
.
atomBonds
[
ib
]:
bondedAtom1
=
data
.
bonds
[
bondIndex
].
atom1
bondedAtom2
=
data
.
bonds
[
bondIndex
].
atom2
if
(
bondedAtom1
!=
ib
):
ia
=
bondedAtom1
else
:
ia
=
bondedAtom2
if
(
ia
!=
ic
and
ia
!=
id
):
for
bondIndex
in
data
.
atomBonds
[
id
]:
bondedAtom1
=
data
.
bonds
[
bondIndex
].
atom1
bondedAtom2
=
data
.
bonds
[
bondIndex
].
atom2
if
(
bondedAtom1
!=
id
):
ie
=
bondedAtom1
else
:
ie
=
bondedAtom2
if
(
ie
!=
ic
and
ie
!=
ib
and
ie
!=
ia
):
# found candidate set of atoms
# check if types match in order or reverse order
type1
=
data
.
atomType
[
data
.
atoms
[
ia
]]
type2
=
data
.
atomType
[
data
.
atoms
[
ib
]]
type3
=
data
.
atomType
[
data
.
atoms
[
ic
]]
type4
=
data
.
atomType
[
data
.
atoms
[
id
]]
type5
=
data
.
atomType
[
data
.
atoms
[
ie
]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
types4
=
self
.
types4
[
i
]
types5
=
self
.
types5
[
i
]
# match in order
if
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
and
type4
in
types4
and
type5
in
types5
):
chiralAtomIndex
=
self
.
getChiralAtomIndex
(
data
,
sys
,
ib
,
ic
,
id
)
force
.
addTorsionTorsion
(
ia
,
ib
,
ic
,
id
,
ie
,
chiralAtomIndex
,
self
.
gridIndex
[
i
])
# match in reverse order
if
(
type5
in
types1
and
type4
in
types2
and
type3
in
types3
and
type2
in
types4
and
type1
in
types5
):
chiralAtomIndex
=
self
.
getChiralAtomIndex
(
data
,
sys
,
ib
,
ic
,
id
)
force
.
addTorsionTorsion
(
ie
,
id
,
ic
,
ib
,
ia
,
chiralAtomIndex
,
self
.
gridIndex
[
i
])
# set grids
for
(
index
,
grid
)
in
enumerate
(
self
.
grids
):
force
.
setTorsionTorsionGrid
(
index
,
grid
)
if
(
verbose
):
print
"AmoebaTorsionTorsionGenerator number of bitorsions added=%d "
%
(
force
.
getNumTorsionTorsions
())
print
"AmoebaTorsionTorsionGenerator number of grids added=%d "
%
(
force
.
getNumTorsionTorsionGrids
())
parsers
[
"AmoebaTorsionTorsionForce"
]
=
AmoebaTorsionTorsionGenerator
.
parseElement
#=============================================================================================
class
AmoebaStretchBendGenerator
:
"""An AmoebaStretchBendGenerator constructs a AmoebaStretchBendForce."""
#=============================================================================================
def
__init__
(
self
):
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
k1
=
[]
self
.
k2
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
generator
=
AmoebaStretchBendGenerator
(
)
forceField
.
_forces
.
append
(
generator
)
# <AmoebaStretchBendForce stretchBendUnit="1.0">
# <StretchBend class1="2" class2="1" class3="3" k1="5.25776946506" k2="5.25776946506" />
# <StretchBend class1="2" class2="1" class3="4" k1="3.14005676385" k2="3.14005676385" />
for
stretchBend
in
element
.
findall
(
'StretchBend'
):
types
=
forceField
.
_findAtomTypes
(
stretchBend
,
3
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
k1
.
append
(
float
(
stretchBend
.
attrib
[
'k1'
]))
generator
.
k2
.
append
(
float
(
stretchBend
.
attrib
[
'k2'
]))
else
:
outputString
=
"AmoebaStretchBendGenerator : error getting types: %s %s %s"
%
(
stretchBend
.
attrib
[
'class1'
],
stretchBend
.
attrib
[
'class2'
],
stretchBend
.
attrib
[
'class3'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
# The setup of this force is dependent on AmoebaHarmonicBondForce and AmoebaHarmonicAngleForce
# having been called since the ideal bond lengths and angle are needed here.
# As a conseqeunce, createForce() is not implemented since it is not guaranteed that the generator for
# AmoebaHarmonicBondForce and AmoebaHarmonicAngleForce have been called prior to AmoebaStretchBendGenerator().
# Instead, createForcePostAmoebaHarmonicBondForce() is called
# after the generators for AmoebaHarmonicBondForce and AmoebaHarmonicAngleForce have been called
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
pass
#=============================================================================================
# Note: request for constrained bonds is ignored.
#=============================================================================================
def
createForcePostAmoebaHarmonicBondForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
angleList
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaStretchBendForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaStretchBendForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
if
(
verbose
):
print
"In AmoebaStretchBendGenerator bonds=%d "
%
(
len
(
data
.
bonds
))
count
=
0
for
angleDict
in
angleList
:
angle
=
angleDict
[
'angle'
]
if
(
'isConstrained'
in
angleDict
):
isConstrained
=
angleDict
[
'isConstrained'
]
else
:
isConstrained
=
0
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
hit
=
0
radian
=
57.2957795130
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
# match types
# get ideal bond lengths, bondAB, bondCB
# get ideal angle
if
(
type2
in
types2
and
(
(
type1
in
types1
and
type3
in
types3
)
or
(
type3
in
types1
and
type1
in
types3
)
)
):
if
isConstrained
:
hit
=
1
# sys.addConstraint(bond.atom1, bond.atom2, self.length[i])
# print message
else
:
hit
=
1
bondAB
=
-
1.0
bondCB
=
-
1.0
swap
=
0
for
bond
in
data
.
atomBonds
[
angle
[
1
]]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
length
=
data
.
bonds
[
bond
].
length
if
(
atom1
==
angle
[
0
]
):
bondAB
=
length
if
(
atom1
==
angle
[
2
]
):
bondCB
=
length
if
(
atom2
==
angle
[
2
]
):
bondCB
=
length
if
(
atom2
==
angle
[
0
]
):
bondAB
=
length
# check that ideal angle and bonds are set
if
(
'idealAngle'
not
in
angleDict
):
outputString
=
"AmoebaStretchBendGenerator: ideal angle is not set for following entry:
\n
"
outputString
+=
" %5d [%5d %5d %5d] [%5s %5s %5s]"
%
(
count
,
angle
[
0
],
angle
[
1
],
angle
[
2
],
type1
,
type2
,
type3
)
raise
ValueError
(
outputString
)
elif
(
bondAB
<
0
or
bondCB
<
0
):
outputString
=
"AmoebaStretchBendGenerator: bonds not set: %15.7e %15.7e. for following entry:"
%
(
bondAB
,
bondCB
)
outputString
+=
" %5d [%5d %5d %5d] [%5s %5s %5s]"
%
(
count
,
angle
[
0
],
angle
[
1
],
angle
[
2
],
type1
,
type2
,
type3
)
raise
ValueError
(
outputString
)
else
:
if
(
verbose
):
print
"AmoebaStretchBendGenerator %5d [%5d %5d %5d] [%5s %5s %5s] %15.6f %15.6f %15.6f %15.6f"
%
(
count
,
angle
[
0
],
angle
[
1
],
angle
[
2
],
type1
,
type2
,
type3
,
bondAB
,
bondCB
,
angleDict
[
'idealAngle'
],
self
.
k1
[
i
])
force
.
addStretchBend
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
bondAB
,
bondCB
,
angleDict
[
'idealAngle'
]
/
radian
,
self
.
k1
[
i
])
break
if
(
hit
==
0
and
verbose
):
print
"AmoebaStretchBendGenerator missing: %5d missing [%5d %5d %5d] [%5s %5s %5s] "
%
(
count
,
angle
[
0
],
angle
[
1
],
angle
[
2
],
type1
,
type2
,
type3
)
count
+=
1
parsers
[
"AmoebaStretchBendForce"
]
=
AmoebaStretchBendGenerator
.
parseElement
#=============================================================================================
class
AmoebaVdwGenerator
:
"""A AmoebaVdwGenerator constructs a AmoebaVdwForce."""
#=============================================================================================
def
__init__
(
self
,
type
,
radiusrule
,
radiustype
,
radiussize
,
epsilonrule
,
vdw13Scale
,
vdw14Scale
,
vdw15Scale
):
self
.
type
=
type
self
.
radiusrule
=
radiusrule
self
.
radiustype
=
radiustype
self
.
radiussize
=
radiussize
self
.
epsilonrule
=
epsilonrule
self
.
vdw13Scale
=
vdw13Scale
self
.
vdw14Scale
=
vdw14Scale
self
.
vdw15Scale
=
vdw15Scale
self
.
typeMap
=
{}
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaVdwForce type="BUFFERED-14-7" radiusrule="CUBIC-MEAN" radiustype="R-MIN" radiussize="DIAMETER" epsilonrule="HHG" vdw-13-scale="0.0" vdw-14-scale="1.0" vdw-15-scale="1.0" >
# <Vdw class="1" sigma="0.371" epsilon="0.46024" reduction="1.0" />
# <Vdw class="2" sigma="0.382" epsilon="0.422584" reduction="1.0" />
generator
=
AmoebaVdwGenerator
(
element
.
attrib
[
'type'
],
element
.
attrib
[
'radiusrule'
],
element
.
attrib
[
'radiustype'
],
element
.
attrib
[
'radiussize'
],
element
.
attrib
[
'epsilonrule'
],
float
(
element
.
attrib
[
'vdw-13-scale'
]),
float
(
element
.
attrib
[
'vdw-14-scale'
]),
float
(
element
.
attrib
[
'vdw-15-scale'
])
)
forceField
.
_forces
.
append
(
generator
)
two_six
=
1.122462048309372
# types[] = [ sigma, epsilon, reductionFactor, class ]
# sigma is modified based on radiustype and radiussize
for
atom
in
element
.
findall
(
'Vdw'
):
types
=
forceField
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
[
float
(
atom
.
attrib
[
'sigma'
]),
float
(
atom
.
attrib
[
'epsilon'
]),
float
(
atom
.
attrib
[
'reduction'
])]
classType
=
atom
.
attrib
[
'class'
]
if
(
generator
.
radiustype
==
'SIGMA'
):
values
[
0
]
*=
two_six
if
(
generator
.
radiussize
==
'DIAMETER'
):
values
[
0
]
*=
0.5
values
.
append
(
classType
)
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
else
:
outputString
=
"AmoebaVdwGenerator: error getting type: %s"
%
(
atom
.
attrib
[
'class'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
# Return a set containing the indices of particles bonded to particle with index=particleIndex
#=============================================================================================
@
staticmethod
def
getBondedParticleSet
(
particleIndex
,
data
):
bondedParticleSet
=
set
()
for
bond
in
data
.
atomBonds
[
particleIndex
]:
atom1
=
data
.
bonds
[
bond
].
atom1
atom2
=
data
.
bonds
[
bond
].
atom2
if
(
atom1
!=
particleIndex
):
bondedParticleSet
.
add
(
atom1
)
else
:
bondedParticleSet
.
add
(
atom2
)
return
bondedParticleSet
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
sigmaMap
=
{
'ARITHMETIC'
:
1
,
'GEOMETRIC'
:
1
,
'CUBIC-MEAN'
:
1
}
epsilonMap
=
{
'ARITHMETIC'
:
1
,
'GEOMETRIC'
:
1
,
'HARMONIC'
:
1
,
'HHG'
:
1
}
verbose
=
0
# get or create force depending on whether it has already been added to the system
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaVdwForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaVdwForce
()
sys
.
addForce
(
force
)
# sigma and epsilon combining rules
if
(
'sigmaCombiningRule'
in
args
):
sigmaRule
=
args
[
'sigmaCombiningRule'
].
upper
()
if
(
sigmaRule
.
upper
()
in
sigmaMap
):
force
.
setSigmaCombiningRule
(
sigmaRule
.
upper
()
)
else
:
stringList
=
' '
.
join
(
str
(
x
)
for
x
in
sigmaMap
.
keys
())
print
"sigma combining rule %s not recognized; valid values are %s; using default."
%
(
sigmaRule
,
stringList
)
else
:
force
.
setSigmaCombiningRule
(
self
.
radiusrule
)
if
(
'epsilonCombiningRule'
in
args
):
epsilonRule
=
args
[
'epsilonCombiningRule'
].
upper
()
if
(
epsilonRule
.
upper
()
in
epsilonMap
):
force
.
setEpsilonCombiningRule
(
epsilonRule
.
upper
()
)
else
:
stringList
=
' '
.
join
(
str
(
x
)
for
x
in
epsilonMap
.
keys
())
print
"epsilon combining rule %s not recognized; valid values are %s; using default."
%
(
epsilonRule
,
stringList
)
else
:
force
.
setEpsilonCombiningRule
(
self
.
epsilonrule
)
# cutoff
if
(
'vdwCutoff'
in
args
):
force
.
setCutoff
(
float
(
args
[
'vdwCutoff'
]
)
)
if
(
nonbondedMethod
==
PME
):
force
.
setPBC
(
1
)
force
.
setUseNeighborList
(
1
)
else
:
force
=
existing
[
0
]
# add particles to force
# throw error if particle type not available
for
(
i
,
atom
)
in
enumerate
(
data
.
atoms
):
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
classIndex
=
int
(
values
[
3
])
# ivIndex = index of bonded partner for hydrogens; otherwise ivIndex = particle index
ivIndex
=
i
mass
=
sys
.
getParticleMass
(
i
)
/
unit
.
dalton
if
(
mass
<
1.9
and
len
(
data
.
atomBonds
[
i
])
==
1
):
bondIndex
=
data
.
atomBonds
[
i
][
0
]
if
(
data
.
bonds
[
bondIndex
].
atom1
==
i
):
ivIndex
=
data
.
bonds
[
bondIndex
].
atom2
else
:
ivIndex
=
data
.
bonds
[
bondIndex
].
atom1
force
.
addParticle
(
ivIndex
,
classIndex
,
values
[
0
],
values
[
1
],
values
[
2
])
if
(
verbose
):
print
"Vdw %5d %5d %2d %15.7e %15.7e %15.7e"
%
(
i
,
ivIndex
,
classIndex
,
values
[
0
],
values
[
1
],
values
[
2
])
else
:
raise
ValueError
(
'No vdw type for atom %s'
%
(
atom
.
name
))
# set combining rules
# set particle exclusions: self, 1-2 and 1-3 bonds
# (1) collect in bondedParticleSets[i], 1-2 indices for all bonded partners of particle i
# (2) add 1-2,1-3 and self to exclusion set
bondedParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bondedParticleSets
.
append
(
AmoebaVdwGenerator
.
getBondedParticleSet
(
i
,
data
)
)
for
(
i
,
atom
)
in
enumerate
(
data
.
atoms
):
# 1-2 partners
exclusionSet
=
bondedParticleSets
[
i
].
copy
()
# 1-3 partners
if
(
self
.
vdw13Scale
==
0.0
):
for
bondedParticle
in
bondedParticleSets
[
i
]:
exclusionSet
=
exclusionSet
.
union
(
bondedParticleSets
[
bondedParticle
]
)
# self
exclusionSet
.
add
(
i
)
if
(
verbose
):
print
"VdwExcl %5d %s || %s"
%
(
i
,
str
(
exclusionSet
),
str
(
bondedParticleSets
[
i
]))
for
atomIndex
in
sorted
(
exclusionSet
):
atom
=
data
.
atoms
[
atomIndex
]
print
" %5d [%s %s %d]"
%
(
atomIndex
,
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
print
"
\n
"
force
.
setParticleExclusions
(
i
,
exclusionSet
)
parsers
[
"AmoebaVdwForce"
]
=
AmoebaVdwGenerator
.
parseElement
#=============================================================================================
class
AmoebaMultipoleGenerator
:
#=============================================================================================
"""A AmoebaMultipoleGenerator constructs a AmoebaMultipoleForce."""
#=============================================================================================
def
__init__
(
self
,
forceField
,
direct11Scale
,
direct12Scale
,
direct13Scale
,
direct14Scale
,
mpole12Scale
,
mpole13Scale
,
mpole14Scale
,
mpole15Scale
,
mutual11Scale
,
mutual12Scale
,
mutual13Scale
,
mutual14Scale
,
polar12Scale
,
polar13Scale
,
polar14Scale
,
polar15Scale
):
self
.
forceField
=
forceField
self
.
direct11Scale
=
direct11Scale
self
.
direct12Scale
=
direct12Scale
self
.
direct13Scale
=
direct13Scale
self
.
direct14Scale
=
direct14Scale
self
.
mpole12Scale
=
mpole12Scale
self
.
mpole13Scale
=
mpole13Scale
self
.
mpole14Scale
=
mpole14Scale
self
.
mpole15Scale
=
mpole15Scale
self
.
mutual11Scale
=
mutual11Scale
self
.
mutual12Scale
=
mutual12Scale
self
.
mutual13Scale
=
mutual13Scale
self
.
mutual14Scale
=
mutual14Scale
self
.
polar12Scale
=
polar12Scale
self
.
polar13Scale
=
polar13Scale
self
.
polar14Scale
=
polar14Scale
self
.
polar15Scale
=
polar15Scale
self
.
typeMap
=
{}
self
.
hasBeenCalled
=
0
#=============================================================================================
# Set axis type
#=============================================================================================
@
staticmethod
def
setAxisType
(
kIndices
):
# set axis type
kIndicesLen
=
len
(
kIndices
)
if
(
kIndicesLen
>
3
):
ky
=
kIndices
[
3
]
else
:
ky
=
0
if
(
kIndicesLen
>
2
):
kx
=
kIndices
[
2
]
else
:
kx
=
0
if
(
kIndicesLen
>
1
):
kz
=
kIndices
[
1
]
else
:
kz
=
0
while
(
len
(
kIndices
)
<
4
):
kIndices
.
append
(
0
)
axisType
=
mm
.
AmoebaMultipoleForce
.
ZThenX
if
(
kz
==
0
):
axisType
=
mm
.
AmoebaMultipoleForce
.
NoAxisType
if
(
kz
!=
0
and
kx
==
0
):
axisType
=
mm
.
AmoebaMultipoleForce
.
ZOnly
if
(
kz
<
0
or
kx
<
0
):
axisType
=
mm
.
AmoebaMultipoleForce
.
Bisector
if
(
kx
<
0
and
ky
<
0
):
axisType
=
mm
.
AmoebaMultipoleForce
.
ZBisect
if
(
kz
<
0
and
kx
<
0
and
ky
<
0
):
axisType
=
mm
.
AmoebaMultipoleForce
.
ThreeFold
kIndices
[
1
]
=
abs
(
kz
)
kIndices
[
2
]
=
abs
(
kx
)
kIndices
[
3
]
=
abs
(
ky
)
return
axisType
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaMultipoleForce direct11Scale="0.0" direct12Scale="1.0" direct13Scale="1.0" direct14Scale="1.0" mpole12Scale="0.0" mpole13Scale="0.0" mpole14Scale="0.4" mpole15Scale="0.8" mutual11Scale="1.0" mutual12Scale="1.0" mutual13Scale="1.0" mutual14Scale="1.0" polar12Scale="0.0" polar13Scale="0.0" polar14Intra="0.5" polar14Scale="1.0" polar15Scale="1.0" >
# <Multipole class="1" kz="2" kx="4" c0="-0.22620" d1="0.08214" d2="0.00000" d3="0.34883" q11="0.11775" q21="0.00000" q22="-1.02185" q31="-0.17555" q32="0.00000" q33="0.90410" />
# <Multipole class="2" kz="1" kx="3" c0="-0.15245" d1="0.19517" d2="0.00000" d3="0.19687" q11="-0.20677" q21="0.00000" q22="-0.48084" q31="-0.01672" q32="0.00000" q33="0.68761" />
generator
=
AmoebaMultipoleGenerator
(
forceField
,
element
.
attrib
[
'direct11Scale'
],
element
.
attrib
[
'direct12Scale'
],
element
.
attrib
[
'direct13Scale'
],
element
.
attrib
[
'direct14Scale'
],
element
.
attrib
[
'mpole12Scale'
],
element
.
attrib
[
'mpole13Scale'
],
element
.
attrib
[
'mpole14Scale'
],
element
.
attrib
[
'mpole15Scale'
],
element
.
attrib
[
'mutual11Scale'
],
element
.
attrib
[
'mutual12Scale'
],
element
.
attrib
[
'mutual13Scale'
],
element
.
attrib
[
'mutual14Scale'
],
element
.
attrib
[
'polar12Scale'
],
element
.
attrib
[
'polar13Scale'
],
element
.
attrib
[
'polar14Scale'
],
element
.
attrib
[
'polar15Scale'
]
)
forceField
.
_forces
.
append
(
generator
)
# set type map: [ kIndices, multipoles, AMOEBA/OpenMM axis type]
for
atom
in
element
.
findall
(
'Multipole'
):
types
=
forceField
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
# print "Multipole Atom %s types=%s." %( atom.attrib['type'], str(types) )
# k-indices not provided default to 0
kIndices
=
[
int
(
atom
.
attrib
[
'type'
])]
kStrings
=
[
'kz'
,
'kx'
,
'ky'
]
for
kString
in
kStrings
:
try
:
if
(
atom
.
attrib
[
kString
]
):
kIndices
.
append
(
int
(
atom
.
attrib
[
kString
]
))
except
:
pass
# set axis type based on k-Indices
axisType
=
AmoebaMultipoleGenerator
.
setAxisType
(
kIndices
)
# set multipole
charge
=
float
(
atom
.
attrib
[
'c0'
])
conversion
=
1.0
dipole
=
[
conversion
*
float
(
atom
.
attrib
[
'd1'
]),
conversion
*
float
(
atom
.
attrib
[
'd2'
]),
conversion
*
float
(
atom
.
attrib
[
'd3'
])]
quadrupole
=
[]
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q11'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q21'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q31'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q21'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q22'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q32'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q31'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q32'
]))
quadrupole
.
append
(
conversion
*
float
(
atom
.
attrib
[
'q33'
]))
for
t
in
types
[
0
]:
if
(
t
not
in
generator
.
typeMap
):
generator
.
typeMap
[
t
]
=
[]
valueMap
=
dict
()
valueMap
[
'classIndex'
]
=
atom
.
attrib
[
'type'
]
valueMap
[
'kIndices'
]
=
kIndices
valueMap
[
'charge'
]
=
charge
valueMap
[
'dipole'
]
=
dipole
valueMap
[
'quadrupole'
]
=
quadrupole
valueMap
[
'axisType'
]
=
axisType
generator
.
typeMap
[
t
].
append
(
valueMap
)
else
:
outputString
=
"AmoebaMultipoleGenerator: error getting type for multipole: %s"
%
(
atom
.
attrib
[
'class'
]
)
raise
ValueError
(
outputString
)
# polarization parameters
for
atom
in
element
.
findall
(
'Polarize'
):
types
=
forceField
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
classIndex
=
atom
.
attrib
[
'type'
]
polarizability
=
float
(
atom
.
attrib
[
'polarizability'
]
)
thole
=
float
(
atom
.
attrib
[
'thole'
]
)
if
(
thole
==
0
):
pdamp
=
0
else
:
pdamp
=
pow
(
polarizability
,
1.0
/
6.0
)
pgrpMap
=
dict
()
for
index
in
range
(
1
,
7
):
pgrp
=
'pgrp'
+
str
(
index
)
if
(
pgrp
in
atom
.
attrib
):
pgrpMap
[
int
(
atom
.
attrib
[
pgrp
])]
=
-
1
for
t
in
types
[
0
]:
if
(
t
not
in
generator
.
typeMap
):
outputString
=
"AmoebaMultipoleGenerator: polarize type not present: %s"
%
(
atom
.
attrib
[
'type'
]
)
raise
ValueError
(
outputString
)
else
:
typeMapList
=
generator
.
typeMap
[
t
]
hit
=
0
for
(
ii
,
typeMap
)
in
enumerate
(
typeMapList
):
if
(
typeMap
[
'classIndex'
]
==
classIndex
):
typeMap
[
'polarizability'
]
=
polarizability
typeMap
[
'thole'
]
=
thole
typeMap
[
'pdamp'
]
=
pdamp
typeMap
[
'pgrpMap'
]
=
pgrpMap
typeMapList
[
ii
]
=
typeMap
hit
=
1
#print "Adding polarize for %s map=%d len=%d keys=%s" % ( classIndex, ii, len( typeMapList), str( typeMap.keys() ) )
if
(
hit
==
0
):
outputString
=
"AmoebaMultipoleGenerator: error getting type for polarize: class index=%s not in multipole list?"
%
(
atom
.
attrib
[
'class'
]
)
raise
ValueError
(
outputString
)
else
:
outputString
=
"AmoebaMultipoleGenerator: error getting type for polarize: %s"
%
(
atom
.
attrib
[
'class'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
def
setPolarGroups
(
self
,
data
,
bonded12ParticleSets
,
force
):
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
# assign multipole parameters via only 1-2 connected atoms
multipoleDict
=
atom
.
multipoleDict
pgrpMap
=
multipoleDict
[
'pgrpMap'
]
bondedAtomIndices
=
bonded12ParticleSets
[
atomIndex
]
atom
.
stage
=
-
1
atom
.
polarizationGroupSet
=
list
()
atom
.
polarizationGroups
[
atomIndex
]
=
1
for
bondedAtomIndex
in
bondedAtomIndices
:
bondedAtomType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomIndex
]])
bondedAtom
=
data
.
atoms
[
bondedAtomIndex
]
if
(
bondedAtomType
in
pgrpMap
):
atom
.
polarizationGroups
[
bondedAtomIndex
]
=
1
bondedAtom
.
polarizationGroups
[
atomIndex
]
=
1
# pgrp11
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
if
(
len
(
data
.
atoms
[
atomIndex
].
polarizationGroupSet
)
>
0
):
continue
group
=
set
()
visited
=
set
()
notVisited
=
set
()
for
pgrpAtomIndex
in
atom
.
polarizationGroups
:
group
.
add
(
pgrpAtomIndex
)
notVisited
.
add
(
pgrpAtomIndex
)
visited
.
add
(
atomIndex
)
while
(
len
(
notVisited
)
>
0
):
nextAtom
=
notVisited
.
pop
()
if
(
nextAtom
not
in
visited
):
visited
.
add
(
nextAtom
)
for
ii
in
data
.
atoms
[
nextAtom
].
polarizationGroups
:
group
.
add
(
ii
)
if
(
ii
not
in
visited
):
notVisited
.
add
(
ii
)
pGroup
=
group
for
pgrpAtomIndex
in
group
:
data
.
atoms
[
pgrpAtomIndex
].
polarizationGroupSet
.
append
(
pGroup
)
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
atom
.
polarizationGroupSet
[
0
]
=
sorted
(
atom
.
polarizationGroupSet
[
0
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
PolarizationCovalent11
,
atom
.
polarizationGroupSet
[
0
]
)
# pgrp12
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
if
(
len
(
data
.
atoms
[
atomIndex
].
polarizationGroupSet
)
>
1
):
continue
pgrp11
=
set
(
atom
.
polarizationGroupSet
[
0
]
)
pgrp12
=
set
()
for
pgrpAtomIndex
in
pgrp11
:
for
bonded12
in
bonded12ParticleSets
[
pgrpAtomIndex
]:
pgrp12
=
pgrp12
.
union
(
data
.
atoms
[
bonded12
].
polarizationGroupSet
[
0
]
)
pgrp12
=
pgrp12
-
pgrp11
for
pgrpAtomIndex
in
pgrp11
:
data
.
atoms
[
pgrpAtomIndex
].
polarizationGroupSet
.
append
(
pgrp12
)
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
atom
.
polarizationGroupSet
[
1
]
=
sorted
(
atom
.
polarizationGroupSet
[
1
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
PolarizationCovalent12
,
atom
.
polarizationGroupSet
[
1
]
)
# pgrp13
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
if
(
len
(
data
.
atoms
[
atomIndex
].
polarizationGroupSet
)
>
2
):
continue
pgrp11
=
set
(
atom
.
polarizationGroupSet
[
0
]
)
pgrp12
=
set
(
atom
.
polarizationGroupSet
[
1
]
)
pgrp13
=
set
()
for
pgrpAtomIndex
in
pgrp12
:
for
bonded12
in
bonded12ParticleSets
[
pgrpAtomIndex
]:
pgrp13
=
pgrp13
.
union
(
data
.
atoms
[
bonded12
].
polarizationGroupSet
[
0
]
)
pgrp13
=
pgrp13
-
pgrp12
pgrp13
=
pgrp13
-
set
(
pgrp11
)
for
pgrpAtomIndex
in
pgrp11
:
data
.
atoms
[
pgrpAtomIndex
].
polarizationGroupSet
.
append
(
pgrp13
)
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
atom
.
polarizationGroupSet
[
2
]
=
sorted
(
atom
.
polarizationGroupSet
[
2
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
PolarizationCovalent13
,
atom
.
polarizationGroupSet
[
2
]
)
# pgrp14
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
if
(
len
(
data
.
atoms
[
atomIndex
].
polarizationGroupSet
)
>
3
):
continue
pgrp11
=
set
(
atom
.
polarizationGroupSet
[
0
]
)
pgrp12
=
set
(
atom
.
polarizationGroupSet
[
1
]
)
pgrp13
=
set
(
atom
.
polarizationGroupSet
[
2
]
)
pgrp14
=
set
()
for
pgrpAtomIndex
in
pgrp13
:
for
bonded12
in
bonded12ParticleSets
[
pgrpAtomIndex
]:
pgrp14
=
pgrp14
.
union
(
data
.
atoms
[
bonded12
].
polarizationGroupSet
[
0
]
)
pgrp14
=
pgrp14
-
pgrp13
pgrp14
=
pgrp14
-
pgrp12
pgrp14
=
pgrp14
-
set
(
pgrp11
)
for
pgrpAtomIndex
in
pgrp11
:
data
.
atoms
[
pgrpAtomIndex
].
polarizationGroupSet
.
append
(
pgrp14
)
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
atom
.
polarizationGroupSet
[
3
]
=
sorted
(
atom
.
polarizationGroupSet
[
3
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
PolarizationCovalent14
,
atom
.
polarizationGroupSet
[
3
]
)
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
methodMap
=
{
NoCutoff
:
mm
.
AmoebaMultipoleForce
.
NoCutoff
,
PME
:
mm
.
AmoebaMultipoleForce
.
PME
}
verbose
=
0
# get or create force depending on whether it has already been added to the system
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaMultipoleForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaMultipoleForce
()
sys
.
addForce
(
force
)
if
(
nonbondedMethod
not
in
methodMap
):
print
"Warning: AmoebaMultipoleForce: cutoff method not available using NoCutoff."
force
.
setNonbondedMethod
(
mm
.
AmoebaMultipoleForce
.
NoCutoff
)
else
:
force
.
setNonbondedMethod
(
methodMap
[
nonbondedMethod
]
)
force
.
setCutoffDistance
(
nonbondedCutoff
)
if
(
'ewaldErrorTolerance'
in
args
):
force
.
setEwaldErrorTolerance
(
float
(
args
[
'ewaldErrorTolerance'
]))
if
(
'polarization'
in
args
):
polarizationType
=
args
[
'polarization'
]
if
(
polarizationType
.
lower
()
==
'direct'
):
force
.
setPolarizationType
(
mm
.
AmoebaMultipoleForce
.
Direct
)
else
:
force
.
setPolarizationType
(
mm
.
AmoebaMultipoleForce
.
Mutual
)
if
(
'aEwald'
in
args
):
force
.
setAEwald
(
float
(
args
[
'aEwald'
]))
if
(
'pmeGridDimensions'
in
args
):
force
.
setPmeGridDimensions
(
args
[
'pmeGridDimensions'
])
if
(
'mutualInducedMaxIterations'
in
args
):
force
.
setMutualInducedMaxIterations
(
int
(
args
[
'mutualInducedMaxIterations'
]
))
if
(
'mutualInducedTargetEpsilon'
in
args
):
force
.
setMutualInducedTargetEpsilon
(
float
(
args
[
'mutualInducedTargetEpsilon'
]))
else
:
force
=
existing
[
0
]
# OpenMM interface call
# /**
# * Add multipole-related info for a particle
# *
# * @param charge the particle's charge
# * @param molecularDipole the particle's molecular dipole (vector of size 3)
# * @param molecularQuadrupole the particle's molecular quadrupole (vector of size 9)
# * @param axisType the particle's axis type ( ZThenX, Bisector )
# * @param multipoleAtomZ index of first atom used in constructing lab<->molecular frames
# * @param multipoleAtomX index of second atom used in constructing lab<->molecular frames
# * @param multipoleAtomY index of second atom used in constructing lab<->molecular frames
# * @param thole Thole parameter
# * @param dampingFactor dampingFactor parameter
# * @param polarity polarity parameter
# *
# * @return the index of the particle that was added
# */
# int addParticle( double charge, const std::vector<double>& molecularDipole, const std::vector<double>& molecularQuadrupole, int axisType,
# int multipoleAtomZ, int multipoleAtomX, int multipoleAtomY, double thole, double dampingFactor, double polarity )
#
# add particles to force
# throw error if particle type not available
# get 1-2, 1-3, 1-4, 1-5 bonded sets
# 1-2
bonded12ParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bonded12ParticleSet
=
AmoebaVdwGenerator
.
getBondedParticleSet
(
i
,
data
)
bonded12ParticleSet
=
set
(
sorted
(
bonded12ParticleSet
)
)
bonded12ParticleSets
.
append
(
bonded12ParticleSet
)
# 1-3
bonded13ParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bonded13Set
=
set
()
bonded12ParticleSet
=
bonded12ParticleSets
[
i
]
for
j
in
bonded12ParticleSet
:
bonded13Set
=
bonded13Set
.
union
(
bonded12ParticleSets
[
j
]
)
# remove 1-2 and self from set
bonded13Set
=
bonded13Set
-
bonded12ParticleSet
selfSet
=
set
()
selfSet
.
add
(
i
)
bonded13Set
=
bonded13Set
-
selfSet
bonded13Set
=
set
(
sorted
(
bonded13Set
)
)
bonded13ParticleSets
.
append
(
bonded13Set
)
# 1-4
bonded14ParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bonded14Set
=
set
()
bonded13ParticleSet
=
bonded13ParticleSets
[
i
]
for
j
in
bonded13ParticleSet
:
bonded14Set
=
bonded14Set
.
union
(
bonded12ParticleSets
[
j
]
)
# remove 1-3, 1-2 and self from set
bonded14Set
=
bonded14Set
-
bonded12ParticleSets
[
i
]
bonded14Set
=
bonded14Set
-
bonded13ParticleSet
selfSet
=
set
()
selfSet
.
add
(
i
)
bonded14Set
=
bonded14Set
-
selfSet
bonded14Set
=
set
(
sorted
(
bonded14Set
)
)
bonded14ParticleSets
.
append
(
bonded14Set
)
# 1-5
bonded15ParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bonded15Set
=
set
()
bonded14ParticleSet
=
bonded14ParticleSets
[
i
]
for
j
in
bonded14ParticleSet
:
bonded15Set
=
bonded15Set
.
union
(
bonded12ParticleSets
[
j
]
)
# remove 1-4, 1-3, 1-2 and self from set
bonded15Set
=
bonded15Set
-
bonded12ParticleSets
[
i
]
bonded15Set
=
bonded15Set
-
bonded13ParticleSets
[
i
]
bonded15Set
=
bonded15Set
-
bonded14ParticleSet
selfSet
=
set
()
selfSet
.
add
(
i
)
bonded15Set
=
bonded15Set
-
selfSet
bonded15Set
=
set
(
sorted
(
bonded15Set
)
)
bonded15ParticleSets
.
append
(
bonded15Set
)
for
(
atomIndex
,
atom
)
in
enumerate
(
data
.
atoms
):
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
multipoleList
=
self
.
typeMap
[
t
]
hit
=
0
savedMultipoleDict
=
0
# assign multipole parameters via only 1-2 connected atoms
for
multipoleDict
in
multipoleList
:
if
(
hit
!=
0
):
break
kIndices
=
multipoleDict
[
'kIndices'
]
kz
=
kIndices
[
1
]
kx
=
kIndices
[
2
]
ky
=
kIndices
[
3
]
#print "Atom %s of %s %d k: %d %d %d keys=%s." %( atom.name, atom.residue.name, atom.residue.index, kz, kx, ky, sorted( multipoleDict.keys() ) )
# assign multipole parameters
# (1) get bonded partners
# (2) match parameter types
bondedAtomIndices
=
bonded12ParticleSets
[
atomIndex
]
zaxis
=
-
1
xaxis
=
-
1
yaxis
=
-
1
for
bondedAtomZIndex
in
bondedAtomIndices
:
if
(
hit
!=
0
):
break
bondedAtomZType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomZIndex
]])
bondedAtomZ
=
data
.
atoms
[
bondedAtomZIndex
]
if
(
bondedAtomZType
==
kz
):
for
bondedAtomXIndex
in
bondedAtomIndices
:
if
(
bondedAtomXIndex
==
bondedAtomZIndex
or
hit
!=
0
):
continue
bondedAtomXType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomXIndex
]])
if
(
bondedAtomXType
==
kx
):
if
(
ky
==
0
):
zaxis
=
bondedAtomZIndex
xaxis
=
bondedAtomXIndex
savedMultipoleDict
=
multipoleDict
hit
=
1
else
:
for
bondedAtomYIndex
in
bondedAtomIndices
:
if
(
bondedAtomYIndex
==
bondedAtomZIndex
or
bondedAtomYIndex
==
bondedAtomXIndex
or
hit
!=
0
):
continue
bondedAtomYType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomYIndex
]])
if
(
bondedAtomYType
==
ky
):
zaxis
=
bondedAtomZIndex
xaxis
=
bondedAtomXIndex
yaxis
=
bondedAtomYIndex
savedMultipoleDict
=
multipoleDict
hit
=
2
# assign multipole parameters via 1-2 and 1-3 connected atoms
for
multipoleDict
in
multipoleList
:
if
(
hit
!=
0
):
break
kIndices
=
multipoleDict
[
'kIndices'
]
kz
=
kIndices
[
1
]
kx
=
kIndices
[
2
]
ky
=
kIndices
[
3
]
#print "Atom %s of %s %d k: %d %d %d keys=%s." %( atom.name, atom.residue.name, atom.residue.index, kz, kx, ky, sorted( multipoleDict.keys() ) )
# assign multipole parameters
# (1) get bonded partners
# (2) match parameter types
bondedAtom12Indices
=
bonded12ParticleSets
[
atomIndex
]
bondedAtom13Indices
=
bonded13ParticleSets
[
atomIndex
]
zaxis
=
-
1
xaxis
=
-
1
yaxis
=
-
1
for
bondedAtomZIndex
in
bondedAtom12Indices
:
if
(
hit
!=
0
):
break
bondedAtomZType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomZIndex
]])
bondedAtomZ
=
data
.
atoms
[
bondedAtomZIndex
]
if
(
bondedAtomZType
==
kz
):
for
bondedAtomXIndex
in
bondedAtom13Indices
:
if
(
bondedAtomXIndex
==
bondedAtomZIndex
or
hit
!=
0
):
continue
bondedAtomXType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomXIndex
]])
if
(
bondedAtomXType
==
kx
and
bondedAtomZIndex
in
bonded12ParticleSets
[
bondedAtomXIndex
]
):
if
(
ky
==
0
):
zaxis
=
bondedAtomZIndex
xaxis
=
bondedAtomXIndex
savedMultipoleDict
=
multipoleDict
hit
=
3
else
:
for
bondedAtomYIndex
in
bondedAtom13Indices
:
if
(
bondedAtomYIndex
==
bondedAtomZIndex
or
bondedAtomYIndex
==
bondedAtomXIndex
or
hit
!=
0
):
continue
bondedAtomYType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomYIndex
]])
if
(
bondedAtomYType
==
ky
and
bondedAtomZIndex
in
bonded12ParticleSets
[
bondedAtomYIndex
]):
zaxis
=
bondedAtomZIndex
xaxis
=
bondedAtomXIndex
yaxis
=
bondedAtomYIndex
savedMultipoleDict
=
multipoleDict
hit
=
4
# assign multipole parameters via only a z-defining atom
for
multipoleDict
in
multipoleList
:
if
(
hit
!=
0
):
break
kIndices
=
multipoleDict
[
'kIndices'
]
kz
=
kIndices
[
1
]
kx
=
kIndices
[
2
]
zaxis
=
-
1
xaxis
=
-
1
yaxis
=
-
1
for
bondedAtomZIndex
in
bondedAtom12Indices
:
if
(
hit
!=
0
):
break
bondedAtomZType
=
int
(
data
.
atomType
[
data
.
atoms
[
bondedAtomZIndex
]])
bondedAtomZ
=
data
.
atoms
[
bondedAtomZIndex
]
if
(
kx
==
0
and
kz
==
bondedAtomZType
):
kz
=
bondedAtomZIndex
savedMultipoleDict
=
multipoleDict
hit
=
5
# assign multipole parameters via no connected atoms
for
multipoleDict
in
multipoleList
:
if
(
hit
!=
0
):
break
kIndices
=
multipoleDict
[
'kIndices'
]
kz
=
kIndices
[
1
]
zaxis
=
-
1
xaxis
=
-
1
yaxis
=
-
1
if
(
kz
==
0
):
savedMultipoleDict
=
multipoleDict
hit
=
6
# add particle if there was a hit
if
(
hit
!=
0
):
if
(
verbose
):
print
"Multipole hit for Atom %5d %4s of %4s %4d type=%5s hitBranch=%d axes: %4d %4d %4d axisType=%1d."
%
(
atomIndex
,
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
,
t
,
hit
,
zaxis
,
xaxis
,
yaxis
,
savedMultipoleDict
[
'axisType'
]
)
atom
.
multipoleDict
=
savedMultipoleDict
atom
.
polarizationGroups
=
dict
()
newIndex
=
force
.
addParticle
(
savedMultipoleDict
[
'charge'
],
savedMultipoleDict
[
'dipole'
],
savedMultipoleDict
[
'quadrupole'
],
savedMultipoleDict
[
'axisType'
],
zaxis
,
xaxis
,
yaxis
,
savedMultipoleDict
[
'thole'
],
savedMultipoleDict
[
'pdamp'
],
savedMultipoleDict
[
'polarizability'
])
if
(
atomIndex
==
newIndex
):
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
Covalent12
,
bonded12ParticleSets
[
atomIndex
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
Covalent13
,
bonded13ParticleSets
[
atomIndex
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
Covalent14
,
bonded14ParticleSets
[
atomIndex
]
)
force
.
setCovalentMap
(
atomIndex
,
mm
.
AmoebaMultipoleForce
.
Covalent15
,
bonded15ParticleSets
[
atomIndex
]
)
else
:
raise
ValueError
(
"Atom %s of %s %d is out of synch!."
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
)
else
:
raise
ValueError
(
"Atom %s of %s %d was not assigned."
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
)
else
:
raise
ValueError
(
'No multipole type for atom %s %s %d'
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
)
# set polar groups
self
.
setPolarGroups
(
data
,
bonded12ParticleSets
,
force
)
parsers
[
"AmoebaMultipoleForce"
]
=
AmoebaMultipoleGenerator
.
parseElement
#=============================================================================================
class
AmoebaWcaDispersionGenerator
:
"""A AmoebaWcaDispersionGenerator constructs a AmoebaWcaDispersionForce."""
#=========================================================================================
def
__init__
(
self
,
epso
,
epsh
,
rmino
,
rminh
,
awater
,
slevy
,
dispoff
,
shctd
):
self
.
epso
=
epso
self
.
epsh
=
epsh
self
.
rmino
=
rmino
self
.
rminh
=
rminh
self
.
awater
=
awater
self
.
slevy
=
slevy
self
.
dispoff
=
dispoff
self
.
shctd
=
shctd
self
.
typeMap
=
{}
#=========================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaWcaDispersionForce epso="0.46024" epsh="0.056484" rmino="0.17025" rminh="0.13275" awater="33.428" slevy="1.0" dispoff="0.026" shctd="0.81" >
# <WcaDispersion class="1" radius="0.1855" epsilon="0.46024" />
# <WcaDispersion class="2" radius="0.191" epsilon="0.422584" />
generator
=
AmoebaWcaDispersionGenerator
(
element
.
attrib
[
'epso'
],
element
.
attrib
[
'epsh'
],
element
.
attrib
[
'rmino'
],
element
.
attrib
[
'rminh'
],
element
.
attrib
[
'awater'
],
element
.
attrib
[
'slevy'
],
element
.
attrib
[
'dispoff'
],
element
.
attrib
[
'shctd'
]
)
forceField
.
_forces
.
append
(
generator
)
# typeMap[] = [ radius, epsilon ]
for
atom
in
element
.
findall
(
'WcaDispersion'
):
types
=
forceField
.
_findAtomTypes
(
atom
,
1
)
if
types
is
not
None
:
values
=
[
float
(
atom
.
attrib
[
'radius'
]),
float
(
atom
.
attrib
[
'epsilon'
])]
for
t
in
types
[
0
]:
generator
.
typeMap
[
t
]
=
values
else
:
outputString
=
"AmoebaWcaDispersionGenerator: error getting type: %s"
%
(
atom
.
attrib
[
'class'
]
)
raise
ValueError
(
outputString
)
#=========================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
verbose
=
0
# get or create force depending on whether it has already been added to the system
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaWcaDispersionForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaWcaDispersionForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
# add particles to force
# throw error if particle type not available
force
.
setEpso
(
float
(
self
.
epso
)
)
force
.
setEpsh
(
float
(
self
.
epsh
)
)
force
.
setRmino
(
float
(
self
.
rmino
)
)
force
.
setRminh
(
float
(
self
.
rminh
)
)
force
.
setDispoff
(
float
(
self
.
dispoff
)
)
force
.
setSlevy
(
float
(
self
.
slevy
)
)
force
.
setAwater
(
float
(
self
.
awater
)
)
force
.
setShctd
(
float
(
self
.
shctd
)
)
for
(
i
,
atom
)
in
enumerate
(
data
.
atoms
):
t
=
data
.
atomType
[
atom
]
if
t
in
self
.
typeMap
:
values
=
self
.
typeMap
[
t
]
force
.
addParticle
(
values
[
0
],
values
[
1
])
if
(
verbose
):
print
"WcaDispersion %5d %15.7e %15.7e"
%
(
i
,
values
[
0
],
values
[
1
])
else
:
raise
ValueError
(
'No WcaDispersion type for atom %s of %s %d'
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
))
parsers
[
"AmoebaWcaDispersionForce"
]
=
AmoebaWcaDispersionGenerator
.
parseElement
#=============================================================================================
class
AmoebaGeneralizedKirkwoodGenerator
:
"""A AmoebaGeneralizedKirkwoodGenerator constructs a AmoebaGeneralizedKirkwoodForce."""
#=========================================================================================
def
__init__
(
self
,
forceField
,
solventDielectric
,
soluteDielectric
,
includeCavityTerm
,
probeRadius
,
surfaceAreaFactor
):
self
.
forceField
=
forceField
self
.
solventDielectric
=
solventDielectric
self
.
soluteDielectric
=
soluteDielectric
self
.
includeCavityTerm
=
includeCavityTerm
self
.
probeRadius
=
probeRadius
self
.
surfaceAreaFactor
=
surfaceAreaFactor
self
.
hasBeenCalled
=
0
self
.
radiusTypeMap
=
{}
self
.
radiusTypeMap
[
'Bondi'
]
=
{}
bondiMap
=
self
.
radiusTypeMap
[
'Bondi'
]
rscale
=
1.03
bondiMap
[
0
]
=
0.00
bondiMap
[
1
]
=
0.12
*
rscale
bondiMap
[
2
]
=
0.14
*
rscale
bondiMap
[
5
]
=
0.18
*
rscale
bondiMap
[
6
]
=
0.170
*
rscale
bondiMap
[
7
]
=
0.155
*
rscale
bondiMap
[
8
]
=
0.152
*
rscale
bondiMap
[
9
]
=
0.147
*
rscale
bondiMap
[
10
]
=
0.154
*
rscale
bondiMap
[
14
]
=
0.210
*
rscale
bondiMap
[
15
]
=
0.180
*
rscale
bondiMap
[
16
]
=
0.180
*
rscale
bondiMap
[
17
]
=
0.175
*
rscale
bondiMap
[
18
]
=
0.188
*
rscale
bondiMap
[
34
]
=
0.190
*
rscale
bondiMap
[
35
]
=
0.185
*
rscale
bondiMap
[
36
]
=
0.202
*
rscale
bondiMap
[
53
]
=
0.198
*
rscale
bondiMap
[
54
]
=
0.216
*
rscale
#=========================================================================================
def
getObcShct
(
self
,
data
,
atomIndex
):
atom
=
data
.
atoms
[
atomIndex
]
atomicNumber
=
atom
.
element
.
atomic_number
shct
=
-
1.0
# shct
if
(
atomicNumber
==
1
):
# H(1)
shct
=
0.85
elif
(
atomicNumber
==
6
):
# C(6)
shct
=
0.72
elif
(
atomicNumber
==
7
):
# N(7)
shct
=
0.79
elif
(
atomicNumber
==
8
):
# O(8)
shct
=
0.85
elif
(
atomicNumber
==
9
):
# F(9)
shct
=
0.88
elif
(
atomicNumber
==
15
):
# P(15)
shct
=
0.86
elif
(
atomicNumber
==
16
):
# S(16)
shct
=
0.96
elif
(
atomicNumber
==
26
):
# Fe(26)
shct
=
0.88
if
(
shct
<
0.0
):
shct
=
0.80
print
"getObcShct: Warning no GK overlap scale factor for atom %s of %s %d using default value=%f"
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
,
shct
)
return
shct
#=========================================================================================
def
getAmoebaTypeRadius
(
self
,
data
,
bondedAtomIndices
,
atomIndex
):
atom
=
data
.
atoms
[
atomIndex
]
atomicNumber
=
atom
.
element
.
atomic_number
#print "CCC atomIndex %d ele=%d %s %s %d" % ( atomIndex, atom.element.atomic_number, atom.name, atom.residue.name, atom.residue.index)
radius
=
-
1.0
if
(
atomicNumber
==
1
):
# H(1)
radius
=
0.132
if
(
len
(
bondedAtomIndices
)
<
1
):
outputString
=
"AmoebaGeneralizedKirkwoodGenerator: error getting atom bonded to %s of %s %d "
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
)
raise
ValueError
(
outputString
)
for
bondedAtomIndex
in
bondedAtomIndices
:
bondedAtomAtomicNumber
=
data
.
atoms
[
bondedAtomIndex
].
element
.
atomic_number
if
(
bondedAtomAtomicNumber
==
7
):
radius
=
0.11
if
(
bondedAtomAtomicNumber
==
8
):
radius
=
0.105
elif
(
atomicNumber
==
3
):
# Li(3)
radius
=
0.15
elif
(
atomicNumber
==
6
):
# C(6)
radius
=
0.20
if
(
len
(
bondedAtomIndices
)
==
3
):
radius
=
0.205
elif
(
len
(
bondedAtomIndices
)
==
4
):
for
bondedAtomIndex
in
bondedAtomIndices
:
bondedAtomAtomicNumber
=
data
.
atoms
[
bondedAtomIndex
].
element
.
atomic_number
if
(
bondedAtomAtomicNumber
==
7
or
bondedAtomAtomicNumber
==
8
):
radius
=
0.175
elif
(
atomicNumber
==
7
):
# N(7)
radius
=
0.16
elif
(
atomicNumber
==
8
):
# O(8)
radius
=
0.155
if
(
len
(
bondedAtomIndices
)
==
2
):
radius
=
0.145
elif
(
atomicNumber
==
9
):
# F(9)
radius
=
0.154
elif
(
atomicNumber
==
10
):
radius
=
0.146
elif
(
atomicNumber
==
11
):
radius
=
0.209
elif
(
atomicNumber
==
12
):
radius
=
0.179
elif
(
atomicNumber
==
14
):
radius
=
0.189
elif
(
atomicNumber
==
15
):
# P(15)
radius
=
0.196
elif
(
atomicNumber
==
16
):
# S(16)
radius
=
0.186
elif
(
atomicNumber
==
17
):
radius
=
0.182
elif
(
atomicNumber
==
18
):
radius
=
0.179
elif
(
atomicNumber
==
19
):
radius
=
0.223
elif
(
atomicNumber
==
20
):
radius
=
0.191
elif
(
atomicNumber
==
35
):
radius
=
2.00
elif
(
atomicNumber
==
36
):
radius
=
0.190
elif
(
atomicNumber
==
37
):
radius
=
0.226
elif
(
atomicNumber
==
53
):
radius
=
0.237
elif
(
atomicNumber
==
54
):
radius
=
0.207
elif
(
atomicNumber
==
55
):
radius
=
0.263
elif
(
atomicNumber
==
56
):
radius
=
0.230
if
(
radius
<
0.0
):
radius
=
2.0
print
"Warning no GK radius for atom %s of %s %d using default value=%f"
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
,
radius
)
return
radius
#=========================================================================================
def
getBondiTypeRadius
(
self
,
data
,
bondedAtomIndices
,
atomIndex
):
bondiMap
=
self
.
radiusTypeMap
[
'Bondi'
]
atom
=
data
.
atoms
[
atomIndex
]
atomicNumber
=
atom
.
element
.
atomic_number
if
(
atomicNumber
in
bondiMap
):
radius
=
bondiMap
[
atomicNumber
]
else
:
radius
=
0.206
print
"Warning no Bondi radius for atom %s of %s %d using default value=%f"
%
(
atom
.
name
,
atom
.
residue
.
name
,
atom
.
residue
.
index
,
radius
)
return
radius
#=========================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaGeneralizedKirkwoodForce solventDielectric="78.3" soluteDielectric="1.0" includeCavityTerm="1" probeRadius="0.14" surfaceAreaFactor="-170.351730663">
# <GeneralizedKirkwood type="1" charge="-0.22620" shct="0.79" />
# <GeneralizedKirkwood type="2" charge="-0.15245" shct="0.72" />
generator
=
AmoebaGeneralizedKirkwoodGenerator
(
forceField
,
element
.
attrib
[
'solventDielectric'
],
element
.
attrib
[
'soluteDielectric'
],
element
.
attrib
[
'includeCavityTerm'
],
element
.
attrib
[
'probeRadius'
],
element
.
attrib
[
'surfaceAreaFactor'
]
)
forceField
.
_forces
.
append
(
generator
)
#=========================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
if
(
self
.
hasBeenCalled
):
return
verbose
=
0
# check if AmoebaMultipoleForce exists since charges needed
# if it has not been created, raise an error
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
amoebaMultipoleForceList
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaMultipoleForce
]
if
(
len
(
amoebaMultipoleForceList
)
>
0
):
amoebaMultipoleForce
=
amoebaMultipoleForceList
[
0
]
else
:
# call AmoebaMultipoleForceGenerator.createForce() to ensure charges have been set
for
force
in
self
.
forceField
.
_forces
:
if
(
force
.
__class__
.
__name__
==
'AmoebaMultipoleGenerator'
):
force
.
createForce
(
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
)
# get or create force depending on whether it has already been added to the system
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaGeneralizedKirkwoodForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaGeneralizedKirkwoodForce
()
sys
.
addForce
(
force
)
if
(
'solventDielectric'
in
args
):
force
.
setSolventDielectric
(
float
(
args
[
'solventDielectric'
]
)
)
else
:
force
.
setSolventDielectric
(
float
(
self
.
solventDielectric
)
)
if
(
'soluteDielectric'
in
args
):
force
.
setSoluteDielectric
(
float
(
args
[
'soluteDielectric'
]
)
)
else
:
force
.
setSoluteDielectric
(
float
(
self
.
soluteDielectric
)
)
if
(
'includeCavityTerm'
in
args
):
force
.
setIncludeCavityTerm
(
int
(
args
[
'includeCavityTerm'
]
)
)
else
:
force
.
setIncludeCavityTerm
(
int
(
self
.
includeCavityTerm
)
)
else
:
print
"AmoebaGeneralizedKirkwoodForce exists"
force
=
existing
[
0
]
self
.
hasBeenCalled
=
1
# add particles to force
# throw error if particle type not available
force
.
setProbeRadius
(
float
(
self
.
probeRadius
)
)
force
.
setSurfaceAreaFactor
(
float
(
self
.
surfaceAreaFactor
)
)
# 1-2
bonded12ParticleSets
=
[]
for
i
in
range
(
len
(
data
.
atoms
)):
bonded12ParticleSet
=
AmoebaVdwGenerator
.
getBondedParticleSet
(
i
,
data
)
bonded12ParticleSet
=
set
(
sorted
(
bonded12ParticleSet
)
)
bonded12ParticleSets
.
append
(
bonded12ParticleSet
)
radiusType
=
'Bondi'
for
atomIndex
in
range
(
0
,
amoebaMultipoleForce
.
getNumMultipoles
()
):
multipoleParameters
=
amoebaMultipoleForce
.
getMultipoleParameters
(
atomIndex
)
if
(
radiusType
==
'Amoeba'
):
radius
=
self
.
getAmoebaTypeRadius
(
data
,
bonded12ParticleSets
[
atomIndex
],
atomIndex
)
else
:
radius
=
self
.
getBondiTypeRadius
(
data
,
bonded12ParticleSets
[
atomIndex
],
atomIndex
)
shct
=
self
.
getObcShct
(
data
,
atomIndex
)
force
.
addParticle
(
multipoleParameters
[
0
],
radius
,
shct
)
if
(
verbose
):
print
"GeneralizedKirkwood %5d %15.7e %15.7e"
%
(
atomIndex
,
multipoleParameters
[
0
],
radius
,
shct
)
parsers
[
"AmoebaGeneralizedKirkwoodForce"
]
=
AmoebaGeneralizedKirkwoodGenerator
.
parseElement
#=============================================================================================
class
AmoebaUreyBradleyGenerator
:
#=============================================================================================
"""An AmoebaUreyBradleyGenerator constructs a AmoebaUreyBradleyForce."""
#=============================================================================================
def
__init__
(
self
,
cubic
,
quartic
):
self
.
cubic
=
cubic
self
.
quartic
=
quartic
self
.
types1
=
[]
self
.
types2
=
[]
self
.
types3
=
[]
self
.
length
=
[]
self
.
k
=
[]
self
.
hasBeenCalled
=
0
#=============================================================================================
@
staticmethod
def
parseElement
(
element
,
forceField
):
# <AmoebaUreyBradleyForce cubic="0.0" quartic="0.0" >
# <UreyBradley class1="74" class2="73" class3="74" k="16003.8" d="0.15537" />
generator
=
AmoebaUreyBradleyGenerator
(
float
(
element
.
attrib
[
'cubic'
]),
float
(
element
.
attrib
[
'quartic'
])
)
forceField
.
_forces
.
append
(
generator
)
for
bond
in
element
.
findall
(
'UreyBradley'
):
types
=
forceField
.
_findAtomTypes
(
bond
,
3
)
if
types
is
not
None
:
generator
.
types1
.
append
(
types
[
0
])
generator
.
types2
.
append
(
types
[
1
])
generator
.
types3
.
append
(
types
[
2
])
generator
.
length
.
append
(
float
(
bond
.
attrib
[
'd'
]))
generator
.
k
.
append
(
float
(
bond
.
attrib
[
'k'
]))
else
:
outputString
=
"AmoebaUreyBradleyGenerator: error getting types: %s %s %s"
%
(
bond
.
attrib
[
'class1'
],
bond
.
attrib
[
'class2'
],
bond
.
attrib
[
'class3'
]
)
raise
ValueError
(
outputString
)
#=============================================================================================
def
createForce
(
self
,
sys
,
data
,
nonbondedMethod
,
nonbondedCutoff
,
args
):
verbose
=
0
if
(
self
.
hasBeenCalled
):
return
self
.
hasBeenCalled
=
1
existing
=
[
sys
.
getForce
(
i
)
for
i
in
range
(
sys
.
getNumForces
())]
existing
=
[
f
for
f
in
existing
if
type
(
f
)
==
mm
.
AmoebaUreyBradleyForce
]
if
len
(
existing
)
==
0
:
force
=
mm
.
AmoebaUreyBradleyForce
()
sys
.
addForce
(
force
)
else
:
force
=
existing
[
0
]
force
.
setAmoebaGlobalUreyBradleyCubic
(
self
.
cubic
)
force
.
setAmoebaGlobalUreyBradleyQuartic
(
self
.
quartic
)
for
(
angle
,
isConstrained
)
in
zip
(
data
.
angles
,
data
.
isAngleConstrained
):
if
(
isConstrained
):
continue
type1
=
data
.
atomType
[
data
.
atoms
[
angle
[
0
]]]
type2
=
data
.
atomType
[
data
.
atoms
[
angle
[
1
]]]
type3
=
data
.
atomType
[
data
.
atoms
[
angle
[
2
]]]
for
i
in
range
(
len
(
self
.
types1
)):
types1
=
self
.
types1
[
i
]
types2
=
self
.
types2
[
i
]
types3
=
self
.
types3
[
i
]
if
(
(
type1
in
types1
and
type2
in
types2
and
type3
in
types3
)
or
(
type3
in
types1
and
type2
in
types2
and
type1
in
types3
)
):
if
(
verbose
):
print
"AmoebaUreyBradleyGenerator %5d %5d %5d [%5s %5s %5s] %15.6f %15.6f"
%
(
angle
[
0
],
angle
[
1
],
angle
[
2
],
type1
,
type2
,
type3
,
self
.
length
[
i
],
self
.
k
[
i
])
force
.
addUreyBradley
(
angle
[
0
],
angle
[
2
],
self
.
length
[
i
],
self
.
k
[
i
])
break
parsers
[
"AmoebaUreyBradleyForce"
]
=
AmoebaUreyBradleyGenerator
.
parseElement
#=============================================================================================
wrappers/python/simtk/openmm/app/internal/__init__.py
0 → 100644
View file @
ccc0ba85
wrappers/python/simtk/openmm/app/internal/amber_file_parser.py
0 → 100644
View file @
ccc0ba85
#!/bin/env python
#=============================================================================================
# MODULE DOCSTRING
#=============================================================================================
"""
Tools for constructing systems from AMBER prmtop/crd files.
This module incorporates parts of 'zander', but Randall J. Radmer.
Licensing to be worked out.
@author Randall J. Radmer
@author John D. Chodera <jchodera@gmail.com>
"""
#=============================================================================================
# GLOBAL IMPORTS
#=============================================================================================
import
os
import
os.path
import
copy
import
re
import
math
try
:
import
numpy
except
:
pass
import
simtk.unit
as
units
import
simtk.openmm
#=============================================================================================
# AMBER parmtop loader (from 'zander', by Randall J. Radmer)
#=============================================================================================
# A regex for extracting print format info from the FORMAT lines.
FORMAT_RE_PATTERN
=
re
.
compile
(
"([0-9]+)([a-zA-Z]+)([0-9]+)\.?([0-9]*)"
)
# Pointer labels which map to pointer numbers at top of prmtop files
POINTER_LABELS
=
"""
NATOM, NTYPES, NBONH, MBONA, NTHETH, MTHETA,
NPHIH, MPHIA, NHPARM, NPARM, NEXT, NRES,
NBONA, NTHETA, NPHIA, NUMBND, NUMANG, NPTRA,
NATYP, NPHB, IFPERT, NBPER, NGPER, NDPER,
MBPER, MGPER, MDPER, IFBOX, NMXRS, IFCAP
"""
# Pointer labels (above) as a list, not string.
POINTER_LABEL_LIST
=
POINTER_LABELS
.
replace
(
','
,
''
).
split
()
class
PrmtopLoader
(
object
):
"""Parsed AMBER prmtop file.
ParmtopLoader reads, parses and manages content from a AMBER prmtop file.
EXAMPLES
Parse a prmtop file of alanine dipeptide in implicit solvent.
>>> import os, os.path
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-gbsa')
>>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop')
>>> prmtop = PrmtopLoader(prmtop_filename)
Parse a prmtop file of alanine dipeptide in explicit solvent.
>>> import os, os.path
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-explicit')
>>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop')
>>> prmtop = PrmtopLoader(prmtop_filename)
"""
def
__init__
(
self
,
inFilename
):
"""
Create a PrmtopLoader object from an AMBER prmtop file.
ARGUMENTS
inFilename (string) - AMBER 'new-style' prmtop file, probably generated with one of the AMBER tleap/xleap/sleap
"""
self
.
_prmtopVersion
=
None
self
.
_flags
=
[]
self
.
_raw_format
=
{}
self
.
_raw_data
=
{}
fIn
=
open
(
inFilename
)
for
line
in
fIn
:
if
line
.
startswith
(
'%VERSION'
):
tag
,
self
.
_prmtopVersion
=
line
.
rstrip
().
split
(
None
,
1
)
elif
line
.
startswith
(
'%FLAG'
):
tag
,
flag
=
line
.
rstrip
().
split
(
None
,
1
)
self
.
_flags
.
append
(
flag
)
self
.
_raw_data
[
flag
]
=
[]
elif
line
.
startswith
(
'%FORMAT'
):
format
=
line
.
rstrip
()
index0
=
format
.
index
(
'('
)
index1
=
format
.
index
(
')'
)
self
.
_raw_format
[
self
.
_flags
[
-
1
]]
=
format
[
index0
+
1
:
index1
]
elif
self
.
_flags
\
and
'TITLE'
==
self
.
_flags
[
-
1
]
\
and
not
self
.
_raw_data
[
'TITLE'
]:
self
.
_raw_data
[
'TITLE'
]
=
line
.
rstrip
()
else
:
flag
=
self
.
_flags
[
-
1
]
(
format
,
numItems
,
itemType
,
itemLength
,
itemPrecision
)
=
self
.
_getFormat
(
flag
)
iLength
=
int
(
itemLength
)
for
index
in
range
(
0
,
len
(
line
),
iLength
):
item
=
line
.
rstrip
()[
index
:
index
+
iLength
]
if
item
:
self
.
_raw_data
[
flag
].
append
(
item
)
fIn
.
close
()
def
_getFormat
(
self
,
flag
=
None
):
if
not
flag
:
flag
=
self
.
_flags
[
-
1
]
format
=
self
.
_raw_format
[
flag
]
m
=
FORMAT_RE_PATTERN
.
search
(
format
)
return
(
format
,
m
.
group
(
1
),
m
.
group
(
2
),
m
.
group
(
3
),
m
.
group
(
4
))
def
_getPointerValue
(
self
,
pointerLabel
):
"""Return pointer value given pointer label
Parameter:
- pointerLabel: a string matching one of the following:
NATOM : total number of atoms
NTYPES : total number of distinct atom types
NBONH : number of bonds containing hydrogen
MBONA : number of bonds not containing hydrogen
NTHETH : number of angles containing hydrogen
MTHETA : number of angles not containing hydrogen
NPHIH : number of dihedrals containing hydrogen
MPHIA : number of dihedrals not containing hydrogen
NHPARM : currently not used
NPARM : currently not used
NEXT : number of excluded atoms
NRES : number of residues
NBONA : MBONA + number of constraint bonds
NTHETA : MTHETA + number of constraint angles
NPHIA : MPHIA + number of constraint dihedrals
NUMBND : number of unique bond types
NUMANG : number of unique angle types
NPTRA : number of unique dihedral types
NATYP : number of atom types in parameter file, see SOLTY below
NPHB : number of distinct 10-12 hydrogen bond pair types
IFPERT : set to 1 if perturbation info is to be read in
NBPER : number of bonds to be perturbed
NGPER : number of angles to be perturbed
NDPER : number of dihedrals to be perturbed
MBPER : number of bonds with atoms completely in perturbed group
MGPER : number of angles with atoms completely in perturbed group
MDPER : number of dihedrals with atoms completely in perturbed groups
IFBOX : set to 1 if standard periodic box, 2 when truncated octahedral
NMXRS : number of atoms in the largest residue
IFCAP : set to 1 if the CAP option from edit was specified
"""
index
=
POINTER_LABEL_LIST
.
index
(
pointerLabel
)
return
float
(
self
.
_raw_data
[
'POINTERS'
][
index
])
def
getNumAtoms
(
self
):
"""Return the number of atoms in the system"""
return
int
(
self
.
_getPointerValue
(
'NATOM'
))
def
getNumTypes
(
self
):
"""Return the number of AMBER atoms types in the system"""
return
int
(
self
.
_getPointerValue
(
'NTYPES'
))
def
getIfBox
(
self
):
"""Return True if the system was build with periodic boundary conditions (PBC)"""
return
int
(
self
.
_getPointerValue
(
'IFBOX'
))
def
getIfCap
(
self
):
"""Return True if the system was build with the cap option)"""
return
int
(
self
.
_getPointerValue
(
'IFCAP'
))
def
getIfPert
(
self
):
"""Return True if the system was build with the perturbation parameters)"""
return
int
(
self
.
_getPointerValue
(
'IFPERT'
))
def
getMasses
(
self
):
"""Return a list of atomic masses in the system"""
try
:
return
self
.
_massList
except
AttributeError
:
pass
self
.
_massList
=
[]
raw_masses
=
self
.
_raw_data
[
'MASS'
]
for
ii
in
range
(
self
.
getNumAtoms
()):
self
.
_massList
.
append
(
float
(
raw_masses
[
ii
]))
self
.
_massList
=
units
.
Quantity
(
self
.
_massList
,
units
.
amu
)
return
self
.
_massList
def
getCharges
(
self
):
"""Return a list of atomic charges in the system"""
try
:
return
self
.
_chargeList
except
AttributeError
:
pass
self
.
_chargeList
=
[]
raw_charges
=
self
.
_raw_data
[
'CHARGE'
]
for
ii
in
range
(
self
.
getNumAtoms
()):
self
.
_chargeList
.
append
(
float
(
raw_charges
[
ii
])
/
18.2223
)
self
.
_chargeList
=
units
.
Quantity
(
self
.
_chargeList
,
units
.
elementary_charge
)
return
self
.
_chargeList
def
getAtomName
(
self
,
iAtom
):
"""Return the atom name for iAtom"""
atomNames
=
self
.
getAtomNames
()
return
atomNames
[
iAtom
]
def
getAtomNames
(
self
):
"""Return the list of the system atom names"""
return
self
.
_raw_data
[
'ATOM_NAME'
]
def
_getAtomTypeIndexes
(
self
):
try
:
return
self
.
_atomTypeIndexes
except
AttributeError
:
pass
self
.
_atomTypeIndexes
=
[]
for
atomTypeIndex
in
self
.
_raw_data
[
'ATOM_TYPE_INDEX'
]:
self
.
_atomTypeIndexes
.
append
(
int
(
atomTypeIndex
))
return
self
.
_atomTypeIndexes
def
getAtomType
(
self
,
iAtom
):
"""Return the AMBER atom type for iAtom"""
atomTypes
=
self
.
getAtomTypes
()
return
atomTypes
[
iAtom
]
def
getAtomTypes
(
self
):
"""Return the list of the AMBER atom types"""
return
self
.
_raw_data
[
'AMBER_ATOM_TYPE'
]
def
getResidueNumber
(
self
,
iAtom
):
"""Return iAtom's residue number"""
return
self
.
_getResiduePointer
(
iAtom
)
+
1
def
getResidueLabel
(
self
,
iAtom
=
None
,
iRes
=
None
):
"""Return residue label for iAtom OR iRes"""
if
iRes
==
None
and
iAtom
==
None
:
raise
Exception
(
"only specify iRes or iAtom, not both"
)
if
iRes
!=
None
and
iAtom
!=
None
:
raise
Exception
(
"iRes or iAtom must be set"
)
if
iRes
!=
None
:
return
self
.
_raw_data
[
'RESIDUE_LABEL'
][
iRes
]
else
:
return
self
.
getResidueLabel
(
iRes
=
self
.
_getResiduePointer
(
iAtom
))
def
_getResiduePointer
(
self
,
iAtom
):
try
:
return
self
.
residuePointerDict
[
iAtom
]
except
:
pass
resPointers
=
self
.
_raw_data
[
'RESIDUE_POINTER'
]
iRes
=
len
(
resPointers
)
for
ii
in
range
(
1
,
len
(
resPointers
)):
firstAtom
=
int
(
resPointers
[
ii
])
-
1
if
firstAtom
>
iAtom
:
iRes
=
ii
break
try
:
self
.
residuePointerDict
[
iAtom
]
=
iRes
-
1
except
AttributeError
:
self
.
residuePointerDict
=
{
iAtom
:
iRes
-
1
}
return
self
.
residuePointerDict
[
iAtom
]
def
getNonbondTerms
(
self
):
"""Return list of all rVdw, epsilon pairs for each atom"""
try
:
return
self
.
_nonbondTerms
except
AttributeError
:
pass
self
.
_nonbondTerms
=
[]
for
iAtom
in
range
(
self
.
getNumAtoms
()):
numTypes
=
self
.
getNumTypes
()
atomTypeIndexes
=
self
.
_getAtomTypeIndexes
()
index
=
(
numTypes
+
1
)
*
(
atomTypeIndexes
[
iAtom
]
-
1
)
nbIndex
=
int
(
self
.
_raw_data
[
'NONBONDED_PARM_INDEX'
][
index
])
-
1
if
nbIndex
<
0
:
raise
Exception
(
"10-12 interactions are not supported"
)
acoef
=
float
(
self
.
_raw_data
[
'LENNARD_JONES_ACOEF'
][
nbIndex
])
bcoef
=
float
(
self
.
_raw_data
[
'LENNARD_JONES_BCOEF'
][
nbIndex
])
try
:
rMin
=
(
2
*
acoef
/
bcoef
)
**
(
1
/
6.0
)
epsilon
=
0.25
*
bcoef
*
bcoef
/
acoef
except
ZeroDivisionError
:
rMin
=
1.0
epsilon
=
0.0
rVdw
=
units
.
Quantity
(
rMin
/
2.0
,
units
.
angstrom
)
epsilon
=
units
.
Quantity
(
epsilon
,
units
.
kilocalorie_per_mole
)
self
.
_nonbondTerms
.
append
(
(
rVdw
,
epsilon
)
)
return
self
.
_nonbondTerms
def
_getBonds
(
self
,
bondPointers
):
forceConstant
=
self
.
_raw_data
[
"BOND_FORCE_CONSTANT"
]
bondEquil
=
self
.
_raw_data
[
"BOND_EQUIL_VALUE"
]
returnList
=
[]
for
ii
in
range
(
0
,
len
(
bondPointers
),
3
):
if
int
(
bondPointers
[
ii
])
<
0
or
\
int
(
bondPointers
[
ii
+
1
])
<
0
:
raise
Exception
(
"Found negative bonded atom pointers %s"
%
((
bondPointers
[
ii
],
bondPointers
[
ii
+
1
]),))
iType
=
int
(
bondPointers
[
ii
+
2
])
-
1
forceConstUnit
=
units
.
kilocalorie_per_mole
/
(
units
.
angstrom
*
units
.
angstrom
)
returnList
.
append
((
int
(
bondPointers
[
ii
])
/
3
,
int
(
bondPointers
[
ii
+
1
])
/
3
,
units
.
Quantity
(
float
(
forceConstant
[
iType
]),
forceConstUnit
),
units
.
Quantity
(
float
(
bondEquil
[
iType
]),
units
.
angstrom
)))
return
returnList
def
getBondsWithH
(
self
):
"""Return list of bonded atom pairs, K, and Rmin for each bond with a hydrogen"""
try
:
return
self
.
_bondListWithH
except
AttributeError
:
pass
bondPointers
=
self
.
_raw_data
[
"BONDS_INC_HYDROGEN"
]
self
.
_bondListWithH
=
self
.
_getBonds
(
bondPointers
)
return
self
.
_bondListWithH
def
getBondsNoH
(
self
):
"""Return list of bonded atom pairs, K, and Rmin for each bond with no hydrogen"""
try
:
return
self
.
_bondListNoH
except
AttributeError
:
pass
bondPointers
=
self
.
_raw_data
[
"BONDS_WITHOUT_HYDROGEN"
]
self
.
_bondListNoH
=
self
.
_getBonds
(
bondPointers
)
return
self
.
_bondListNoH
def
getAngles
(
self
):
"""Return list of atom triplets, K, and ThetaMin for each bond angle"""
try
:
return
self
.
_angleList
except
AttributeError
:
pass
forceConstant
=
self
.
_raw_data
[
"ANGLE_FORCE_CONSTANT"
]
angleEquil
=
self
.
_raw_data
[
"ANGLE_EQUIL_VALUE"
]
anglePointers
=
self
.
_raw_data
[
"ANGLES_INC_HYDROGEN"
]
\
+
self
.
_raw_data
[
"ANGLES_WITHOUT_HYDROGEN"
]
self
.
_angleList
=
[]
for
ii
in
range
(
0
,
len
(
anglePointers
),
4
):
if
int
(
anglePointers
[
ii
])
<
0
or
\
int
(
anglePointers
[
ii
+
1
])
<
0
or
\
int
(
anglePointers
[
ii
+
2
])
<
0
:
raise
Exception
(
"Found negative angle atom pointers %s"
%
((
anglePointers
[
ii
],
anglePointers
[
ii
+
1
],
anglePointers
[
ii
+
2
]),))
iType
=
int
(
anglePointers
[
ii
+
3
])
-
1
forceConstUnit
=
units
.
kilocalorie_per_mole
/
(
units
.
radian
*
units
.
radian
)
self
.
_angleList
.
append
((
int
(
anglePointers
[
ii
])
/
3
,
int
(
anglePointers
[
ii
+
1
])
/
3
,
int
(
anglePointers
[
ii
+
2
])
/
3
,
units
.
Quantity
(
float
(
forceConstant
[
iType
]),
forceConstUnit
),
units
.
Quantity
(
float
(
angleEquil
[
iType
])
*
180
/
math
.
pi
,
units
.
degree
)))
return
self
.
_angleList
def
getDihedrals
(
self
):
"""Return list of atom quads, K, phase and periodicity for each dihedral angle"""
try
:
return
self
.
_dihedralList
except
AttributeError
:
pass
forceConstant
=
self
.
_raw_data
[
"DIHEDRAL_FORCE_CONSTANT"
]
phase
=
self
.
_raw_data
[
"DIHEDRAL_PHASE"
]
periodicity
=
self
.
_raw_data
[
"DIHEDRAL_PERIODICITY"
]
dihedralPointers
=
self
.
_raw_data
[
"DIHEDRALS_INC_HYDROGEN"
]
\
+
self
.
_raw_data
[
"DIHEDRALS_WITHOUT_HYDROGEN"
]
self
.
_dihedralList
=
[]
for
ii
in
range
(
0
,
len
(
dihedralPointers
),
5
):
if
int
(
dihedralPointers
[
ii
])
<
0
or
int
(
dihedralPointers
[
ii
+
1
])
<
0
:
raise
Exception
(
"Found negative dihedral atom pointers %s"
%
((
dihedralPointers
[
ii
],
dihedralPointers
[
ii
+
1
],
dihedralPointers
[
ii
+
2
],
dihedralPointers
[
ii
+
3
]),))
iType
=
int
(
dihedralPointers
[
ii
+
4
])
-
1
self
.
_dihedralList
.
append
((
int
(
dihedralPointers
[
ii
])
/
3
,
int
(
dihedralPointers
[
ii
+
1
])
/
3
,
abs
(
int
(
dihedralPointers
[
ii
+
2
]))
/
3
,
abs
(
int
(
dihedralPointers
[
ii
+
3
]))
/
3
,
units
.
Quantity
(
float
(
forceConstant
[
iType
]),
units
.
kilocalorie_per_mole
),
units
.
Quantity
(
float
(
phase
[
iType
])
*
180
/
math
.
pi
,
units
.
degree
),
int
(
0.5
+
float
(
periodicity
[
iType
]))))
return
self
.
_dihedralList
def
get14Interactions
(
self
):
"""Return list of atom pairs, chargeProduct, rMin and epsilon for each 1-4 interaction"""
dihedralPointers
=
self
.
_raw_data
[
"DIHEDRALS_INC_HYDROGEN"
]
\
+
self
.
_raw_data
[
"DIHEDRALS_WITHOUT_HYDROGEN"
]
returnList
=
[]
charges
=
self
.
getCharges
()
nonbondTerms
=
self
.
getNonbondTerms
()
for
ii
in
range
(
0
,
len
(
dihedralPointers
),
5
):
if
int
(
dihedralPointers
[
ii
+
2
])
>
0
and
int
(
dihedralPointers
[
ii
+
3
])
>
0
:
iAtom
=
int
(
dihedralPointers
[
ii
])
/
3
lAtom
=
int
(
dihedralPointers
[
ii
+
3
])
/
3
chargeProd
=
charges
[
iAtom
]
*
charges
[
lAtom
]
(
rVdwI
,
epsilonI
)
=
nonbondTerms
[
iAtom
]
(
rVdwL
,
epsilonL
)
=
nonbondTerms
[
lAtom
]
rMin
=
(
rVdwI
+
rVdwL
)
epsilon
=
units
.
sqrt
(
epsilonI
*
epsilonL
)
returnList
.
append
((
iAtom
,
lAtom
,
chargeProd
,
rMin
,
epsilon
))
return
returnList
def
getExcludedAtoms
(
self
):
"""Return list of lists, giving all pairs of atoms that should have no non-bond interactions"""
try
:
return
self
.
_excludedAtoms
except
AttributeError
:
pass
self
.
_excludedAtoms
=
[]
numExcludedAtomsList
=
self
.
_raw_data
[
"NUMBER_EXCLUDED_ATOMS"
]
excludedAtomsList
=
self
.
_raw_data
[
"EXCLUDED_ATOMS_LIST"
]
total
=
0
for
iAtom
in
range
(
self
.
getNumAtoms
()):
index0
=
total
n
=
int
(
numExcludedAtomsList
[
iAtom
])
total
+=
n
index1
=
total
atomList
=
[]
for
jAtom
in
excludedAtomsList
[
index0
:
index1
]:
j
=
int
(
jAtom
)
if
j
>
0
:
atomList
.
append
(
j
-
1
)
self
.
_excludedAtoms
.
append
(
atomList
)
return
self
.
_excludedAtoms
def
getGBSA_OBC
(
self
):
"""Return list giving GB params, Radius and screening factor"""
try
:
return
self
.
_gbsa_obcList
except
AttributeError
:
pass
self
.
_gbsa_obcList
=
[]
radii
=
self
.
_raw_data
[
"RADII"
]
screen
=
self
.
_raw_data
[
"SCREEN"
]
for
iAtom
in
range
(
len
(
radii
)):
self
.
_gbsa_obcList
.
append
(
(
units
.
Quantity
(
float
(
radii
[
iAtom
]),
units
.
angstrom
),
units
.
Quantity
(
float
(
screen
[
iAtom
]),
units
.
dimensionless
))
)
return
self
.
_gbsa_obcList
def
getBoxBetaAndDimensions
(
self
):
"""Return periodic boundary box beta angle and dimensions"""
beta
=
float
(
self
.
_raw_data
[
"BOX_DIMENSIONS"
][
0
])
x
=
float
(
self
.
_raw_data
[
"BOX_DIMENSIONS"
][
1
])
y
=
float
(
self
.
_raw_data
[
"BOX_DIMENSIONS"
][
2
])
z
=
float
(
self
.
_raw_data
[
"BOX_DIMENSIONS"
][
3
])
return
(
units
.
Quantity
(
beta
,
units
.
degree
),
units
.
Quantity
(
x
,
units
.
angstrom
),
units
.
Quantity
(
y
,
units
.
angstrom
),
units
.
Quantity
(
z
,
units
.
angstrom
))
#=============================================================================================
# AMBER System builder (based on, but not identical to, systemManager from 'zander')
#=============================================================================================
def
readAmberSystem
(
prmtop_filename
=
None
,
prmtop_loader
=
None
,
shake
=
None
,
gbmodel
=
None
,
nonbondedCutoff
=
None
,
nonbondedMethod
=
'NoCutoff'
,
scee
=
1.2
,
scnb
=
2.0
,
mm
=
None
,
verbose
=
False
,
EwaldErrorTolerance
=
None
,
flexibleConstraints
=
True
):
"""
Create an OpenMM System from an Amber prmtop file.
ARGUMENTS (specify one or the other, but not both)
prmtop_filename (String) - name of Amber prmtop file (new-style only)
prmtop_loader (PrmtopLoader) - the loaded prmtop file
OPTIONAL ARGUMENTS
shake (String) - if 'h-bonds', will SHAKE all bonds to hydrogen and water; if 'all-bonds', will SHAKE all bonds and water (default: None)
gbmodel (String) - if 'OBC', OBC GBSA will be used; if 'GBVI', GB/VI will be used (default: None)
nonbondedCutoff (float) - if specified, will set nonbondedCutoff (default: None)
scnb (float) - 1-4 Lennard-Jones scaling factor (default: 1.2)
scee (float) - 1-4 electrostatics scaling factor (default: 2.0)
mm - if specified, this module will be used in place of pyopenmm (default: None)
verbose (boolean) - if True, print out information on progress (default: False)
flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds
NOTES
Even if bonds are SHAKEn, their harmonic stretch terms are still included in the potential.
TODO
Should these option names be changed to reflect their 'sander' counterparts?
EXAMPLES
Create a system of alanine dipeptide in implicit solvent.
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-gbsa')
>>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop')
>>> system = readAmberSystem(prmtop_filename)
Parse a prmtop file of alanine dipeptide in explicit solvent.
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-explicit')
>>> prmtop_filename = os.path.join(directory, 'alanine-dipeptide.prmtop')
>>> system = readAmberSystem(prmtop_filename)
"""
if
prmtop_filename
is
None
and
prmtop_loader
is
None
:
raise
Exception
(
"Must specify a filename or loader"
)
if
prmtop_filename
is
not
None
and
prmtop_loader
is
not
None
:
raise
Exception
(
"Cannot specify both a filename and a loader"
)
if
prmtop_filename
is
not
None
:
# Load prmtop file.
if
verbose
:
print
"Reading prmtop file '%s'..."
%
prmtop_filename
prmtop
=
PrmtopLoader
(
prmtop_filename
)
else
:
prmtop
=
prmtop_loader
if
prmtop
.
getIfCap
()
>
0
:
raise
Exception
(
"CAP option not currently supported"
)
if
prmtop
.
getIfPert
()
>
0
:
raise
Exception
(
"perturbation not currently supported"
)
if
prmtop
.
getIfBox
()
>
1
:
raise
Exception
(
"only standard periodic boxes are currently supported"
)
# Use pyopenmm implementation of OpenMM by default.
if
mm
is
None
:
mm
=
simtk
.
openmm
# Create OpenMM System.
if
verbose
:
print
"Creating OpenMM system..."
system
=
mm
.
System
()
# Populate system with atomic masses.
if
verbose
:
print
"Adding particles..."
for
mass
in
prmtop
.
getMasses
():
system
.
addParticle
(
mass
)
# Add constraints.
if
shake
in
(
'h-bonds'
,
'all-bonds'
,
'h-angles'
):
for
(
iAtom
,
jAtom
,
k
,
rMin
)
in
prmtop
.
getBondsWithH
():
system
.
addConstraint
(
iAtom
,
jAtom
,
rMin
)
if
shake
in
(
'all-bonds'
,
'h-angles'
):
for
(
iAtom
,
jAtom
,
k
,
rMin
)
in
prmtop
.
getBondsNoH
():
system
.
addConstraint
(
iAtom
,
jAtom
,
rMin
)
# Add harmonic bonds.
if
verbose
:
print
"Adding bonds..."
force
=
mm
.
HarmonicBondForce
()
if
flexibleConstraints
or
(
shake
not
in
(
'h-bonds'
,
'all-bonds'
,
'h-angles'
)):
for
(
iAtom
,
jAtom
,
k
,
rMin
)
in
prmtop
.
getBondsWithH
():
force
.
addBond
(
iAtom
,
jAtom
,
rMin
,
2
*
k
)
if
flexibleConstraints
or
(
shake
not
in
(
'all-bonds'
,
'h-angles'
)):
for
(
iAtom
,
jAtom
,
k
,
rMin
)
in
prmtop
.
getBondsNoH
():
force
.
addBond
(
iAtom
,
jAtom
,
rMin
,
2
*
k
)
system
.
addForce
(
force
)
# Add harmonic angles.
if
verbose
:
print
"Adding angles..."
force
=
mm
.
HarmonicAngleForce
()
if
shake
==
'h-angles'
:
numConstrainedBonds
=
system
.
getNumConstraints
()
atomConstraints
=
[[]]
*
system
.
getNumParticles
()
for
i
in
range
(
system
.
getNumConstraints
()):
c
=
system
.
getConstraintParameters
(
i
)
atomConstraints
[
c
[
0
]].
append
((
c
[
1
],
c
[
2
]))
atomConstraints
[
c
[
1
]].
append
((
c
[
0
],
c
[
2
]))
for
(
iAtom
,
jAtom
,
kAtom
,
k
,
aMin
)
in
prmtop
.
getAngles
():
if
shake
==
'h-angles'
:
type1
=
prmtop
.
getAtomType
(
iAtom
)
type2
=
prmtop
.
getAtomType
(
jAtom
)
type3
=
prmtop
.
getAtomType
(
kAtom
)
numH
=
len
([
type
for
type
in
(
type1
,
type3
)
if
type
.
startswith
(
'H'
)])
constrained
=
(
numH
==
2
or
(
numH
==
1
and
type2
.
startswith
(
'O'
)))
else
:
constrained
=
False
if
constrained
:
# Find the two bonds that make this angle.
l1
=
None
l2
=
None
for
bond
in
atomConstraints
[
jAtom
]:
if
bond
[
0
]
==
iAtom
:
l1
=
bond
[
1
]
elif
bond
[
0
]
==
jAtom
:
l2
=
bond
[
1
]
# Compute the distance between atoms and add a constraint
length
=
units
.
sqrt
(
l1
*
l1
+
l2
*
l2
-
2
*
l1
*
l2
*
units
.
cos
(
aMin
))
system
.
addConstraint
(
iAtom
,
kAtom
,
length
)
if
flexibleConstraints
or
not
constrained
:
force
.
addAngle
(
iAtom
,
jAtom
,
kAtom
,
aMin
,
2
*
k
)
system
.
addForce
(
force
)
# Add torsions.
if
verbose
:
print
"Adding torsions..."
force
=
mm
.
PeriodicTorsionForce
()
for
(
iAtom
,
jAtom
,
kAtom
,
lAtom
,
forceConstant
,
phase
,
periodicity
)
in
prmtop
.
getDihedrals
():
force
.
addTorsion
(
iAtom
,
jAtom
,
kAtom
,
lAtom
,
periodicity
,
phase
,
forceConstant
)
system
.
addForce
(
force
)
# Add nonbonded interactions.
if
verbose
:
print
"Adding nonbonded interactions..."
force
=
mm
.
NonbondedForce
()
if
(
prmtop
.
getIfBox
()
==
0
):
# System is non-periodic.
if
nonbondedMethod
==
'NoCutoff'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
NoCutoff
)
elif
nonbondedMethod
==
'CutoffNonPeriodic'
:
if
nonbondedCutoff
is
None
:
raise
Exception
(
"No cutoff value specified"
)
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
CutoffNonPeriodic
)
force
.
setCutoffDistance
(
nonbondedCutoff
)
else
:
raise
Exception
(
"Illegal nonbonded method for a non-periodic system"
)
else
:
# System is periodic.
# Set periodic box vectors for periodic system
(
boxBeta
,
boxX
,
boxY
,
boxZ
)
=
prmtop
.
getBoxBetaAndDimensions
()
d0
=
units
.
Quantity
(
0.0
,
units
.
angstroms
)
xVec
=
units
.
Quantity
((
boxX
,
d0
,
d0
))
yVec
=
units
.
Quantity
((
d0
,
boxY
,
d0
))
zVec
=
units
.
Quantity
((
d0
,
d0
,
boxZ
))
system
.
setDefaultPeriodicBoxVectors
(
xVec
,
yVec
,
zVec
)
# Set cutoff.
if
nonbondedCutoff
is
None
:
# Compute cutoff automatically.
min_box_width
=
min
([
boxX
/
units
.
nanometers
,
boxY
/
units
.
nanometers
,
boxZ
/
units
.
nanometers
])
CLEARANCE_FACTOR
=
0.97
# reduce the cutoff to be a bit smaller than 1/2 smallest box length
nonbondedCutoff
=
units
.
Quantity
((
min_box_width
*
CLEARANCE_FACTOR
)
/
2.0
,
units
.
nanometers
)
force
.
setCutoffDistance
(
nonbondedCutoff
)
# Set nonbonded method.
if
nonbondedMethod
==
'NoCutoff'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
NoCutoff
)
elif
nonbondedMethod
==
'CutoffNonPeriodic'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
CutoffNonPeriodic
)
elif
nonbondedMethod
==
'CutoffPeriodic'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
CutoffPeriodic
)
elif
nonbondedMethod
==
'Ewald'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
Ewald
)
elif
nonbondedMethod
==
'PME'
:
force
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
PME
)
else
:
raise
Exception
(
"Cutoff method not understood."
)
if
EwaldErrorTolerance
is
not
None
:
force
.
setEwaldErrorTolerance
(
EwaldErrorTolerance
)
# Add per-particle nonbonded parameters.
for
(
charge
,
(
rVdw
,
epsilon
))
in
zip
(
prmtop
.
getCharges
(),
prmtop
.
getNonbondTerms
()):
sigma
=
rVdw
*
2
**
(
-
1.
/
6.
)
*
2.0
force
.
addParticle
(
charge
,
sigma
,
epsilon
)
# Add 1-4 Interactions
excludedAtomPairs
=
set
()
for
(
iAtom
,
lAtom
,
chargeProd
,
rMin
,
epsilon
)
in
prmtop
.
get14Interactions
():
chargeProd
/=
scee
epsilon
/=
scnb
sigma
=
rMin
*
2
**
(
-
1.
/
6.
)
force
.
addException
(
iAtom
,
lAtom
,
chargeProd
,
sigma
,
epsilon
)
excludedAtomPairs
.
add
(
min
((
iAtom
,
lAtom
),
(
lAtom
,
iAtom
)))
# Add Excluded Atoms
excludedAtoms
=
prmtop
.
getExcludedAtoms
()
for
iAtom
in
range
(
prmtop
.
getNumAtoms
()):
for
jAtom
in
excludedAtoms
[
iAtom
]:
if
min
((
iAtom
,
jAtom
),
(
jAtom
,
iAtom
))
in
excludedAtomPairs
:
continue
force
.
addException
(
iAtom
,
jAtom
,
0.0
*
units
.
elementary_charge
**
2
,
1.0
*
units
.
angstroms
,
0.0
*
units
.
kilocalories_per_mole
)
system
.
addForce
(
force
)
# Add GBSA-OBC model.
if
gbmodel
==
'OBC'
:
if
verbose
:
print
"Adding GB parameters..."
gb
=
mm
.
GBSAOBCForce
()
charges
=
prmtop
.
getCharges
()
gbsa_obc
=
prmtop
.
getGBSA_OBC
()
#for charge, radius, scalingFactor in prmtop.getGBSA_OBC():
for
iAtom
in
range
(
prmtop
.
getNumAtoms
()):
gb
.
addParticle
(
charges
[
iAtom
],
gbsa_obc
[
iAtom
][
0
],
gbsa_obc
[
iAtom
][
1
])
system
.
addForce
(
gb
)
if
nonbondedMethod
==
'NoCutoff'
:
gb
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
NoCutoff
)
elif
nonbondedMethod
==
'CutoffNonPeriodic'
:
gb
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
CutoffNonPeriodic
)
gb
.
setCutoffDistance
(
nonbondedCutoff
)
elif
nonbondedMethod
==
'CutoffPeriodic'
:
gb
.
setNonbondedMethod
(
mm
.
NonbondedForce
.
CutoffPeriodic
)
gb
.
setCutoffDistance
(
nonbondedCutoff
)
else
:
raise
Exception
(
"Illegal nonbonded method for use with GBSA"
)
# TODO: Add GBVI terms?
return
system
#=============================================================================================
# AMBER INPCRD loader
#=============================================================================================
def
readAmberCoordinates
(
filename
,
read_box
=
False
,
read_velocities
=
False
,
verbose
=
False
,
asNumpy
=
False
):
"""
Read atomic coordinates (and optionally, box vectors) from Amber formatted coordinate file.
ARGUMENTS
filename (string) - name of Amber coordinates file to be read in
system (simtk.openmm.System) - System object for which coordinates are to be read
OPTIONAL ARGUMENTS
verbose (boolean) - if True, will print out verbose information about the file being read
asNumpy (boolean) - if True, results will be returned as Numpy arrays instead of lists of Vec3s
EXAMPLES
Read coordinates in vacuum.
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-gbsa')
>>> crd_filename = os.path.join(directory, 'alanine-dipeptide.inpcrd')
>>> coordinates = readAmberCoordinates(crd_filename)
Read coordinates in solvent.
>>> directory = os.path.join(os.getenv('YANK_INSTALL_DIR'), 'test', 'systems', 'alanine-dipeptide-explicit')
>>> crd_filename = os.path.join(directory, 'alanine-dipeptide.inpcrd')
>>> [coordinates, box_vectors] = readAmberCoordinates(crd_filename, read_box=True)
"""
# Open coordinate file for reading.
infile
=
open
(
filename
,
'r'
)
# Read title
title
=
infile
.
readline
().
strip
()
if
verbose
:
print
"title: '%s'"
%
title
# Read number of atoms
natoms
=
int
(
infile
.
readline
().
strip
())
if
verbose
:
print
"%d atoms"
%
natoms
# Allocate storage for coordinates
coordinates
=
[]
# Read coordinates
mm
=
simtk
.
openmm
natoms_read
=
0
while
(
natoms_read
<
natoms
):
line
=
infile
.
readline
().
strip
()
elements
=
line
.
split
()
while
(
len
(
elements
)
>
0
):
coordinates
.
append
(
mm
.
Vec3
(
float
(
elements
.
pop
(
0
)),
float
(
elements
.
pop
(
0
)),
float
(
elements
.
pop
(
0
))))
natoms_read
+=
1
if
asNumpy
:
newcoords
=
numpy
.
zeros
([
natoms
,
3
],
numpy
.
float32
)
for
i
in
range
(
len
(
coordinates
)):
for
j
in
range
(
3
):
newcoords
[
i
,
j
]
=
coordinates
[
i
][
j
]
coordinates
=
newcoords
# Assign units.
coordinates
=
units
.
Quantity
(
coordinates
,
units
.
angstroms
)
# Read velocities if requested.
velocities
=
None
if
(
read_velocities
):
# Read velocities
velocities
=
[]
natoms_read
=
0
while
(
natoms_read
<
natoms
):
line
=
infile
.
readline
().
strip
()
elements
=
line
.
split
()
while
(
len
(
elements
)
>
0
):
velocities
.
append
(
mm
.
Vec3
(
float
(
elements
.
pop
(
0
)),
float
(
elements
.
pop
(
0
)),
float
(
elements
.
pop
(
0
))))
natoms_read
+=
1
if
asNumpy
:
newvel
=
numpy
.
zeros
([
natoms
,
3
],
numpy
.
float32
)
for
i
in
range
(
len
(
velocities
)):
for
j
in
range
(
3
):
newvel
[
i
,
j
]
=
velocities
[
i
][
j
]
velocities
=
newvel
# Assign units.
velocities
=
units
.
Quantity
(
velocities
,
units
.
angstroms
)
# Read box size if present
box_vectors
=
None
if
(
read_box
):
line
=
infile
.
readline
().
strip
()
elements
=
line
.
split
()
nelements
=
len
(
elements
)
box_dimensions
=
[
0.0
]
*
nelements
for
i
in
range
(
nelements
):
box_dimensions
[
i
]
=
float
(
elements
[
i
])
# TODO: Deal with non-standard box sizes.
if
nelements
==
6
:
if
asNumpy
:
a
=
units
.
Quantity
(
numpy
.
array
([
box_dimensions
[
0
],
0.0
,
0.0
]),
units
.
angstroms
)
b
=
units
.
Quantity
(
numpy
.
array
([
0.0
,
box_dimensions
[
1
],
0.0
]),
units
.
angstroms
)
c
=
units
.
Quantity
(
numpy
.
array
([
0.0
,
0.0
,
box_dimensions
[
2
]]),
units
.
angstroms
)
else
:
a
=
units
.
Quantity
(
mm
.
Vec3
(
box_dimensions
[
0
],
0.0
,
0.0
),
units
.
angstroms
)
b
=
units
.
Quantity
(
mm
.
Vec3
(
0.0
,
box_dimensions
[
1
],
0.0
),
units
.
angstroms
)
c
=
units
.
Quantity
(
mm
.
Vec3
(
0.0
,
0.0
,
box_dimensions
[
2
]),
units
.
angstroms
)
box_vectors
=
[
a
,
b
,
c
]
else
:
raise
Exception
(
"Don't know what to do with box vectors: %s"
%
line
)
# Close file
infile
.
close
()
if
box_vectors
and
velocities
:
return
(
coordinates
,
box_vectors
,
velocities
)
if
box_vectors
:
return
(
coordinates
,
box_vectors
)
if
velocities
:
return
(
coordinates
,
velocities
)
return
coordinates
#=============================================================================================
# MAIN AND TESTS
#=============================================================================================
if
__name__
==
"__main__"
:
import
doctest
doctest
.
testmod
()
wrappers/python/simtk/openmm/app/internal/pdbstructure.py
0 → 100644
View file @
ccc0ba85
#!/bin/env python
"""
pdbstructure.py: Used for managing PDB formated files.
"""
__author__
=
"Christopher M. Bruns"
__version__
=
"1.0"
from
simtk.openmm.vec3
import
Vec3
import
simtk.unit
as
unit
from
..
import
element
import
warnings
import
sys
class
PdbStructure
(
object
):
"""
PdbStructure object holds a parsed Protein Data Bank format file.
Examples:
Load a pdb structure from a file:
> pdb = PdbStructure(open("1ARJ.pdb"))
Fetch the first atom of the structure:
> print pdb.iter_atoms().next()
ATOM 1 O5' G N 17 13.768 -8.431 11.865 1.00 0.00 O
Loop over all of the atoms of the structure
> for atom in pdb.iter_atoms():
> print atom
ATOM 1 O5' G N 17 13.768 -8.431 11.865 1.00 0.00 O
...
Get a list of all atoms in the structure:
> atoms = list(pdb.iter_atoms())
also:
residues = list(pdb.iter_residues())
positions = list(pdb.iter_positions())
chains = list(pdb.iter_chains())
models = list(pdb.iter_models())
Fetch atomic coordinates of first atom:
> print pdb.iter_positions().next()
[13.768, -8.431, 11.865] A
or
> print pdb.iter_atoms().next().position
[13.768, -8.431, 11.865] A
Strip the length units from an atomic position:
> import simtk.unit
> pos = pdb.iter_positions().next()
> print pos
[13.768, -8.431, 11.865] A
> print pos / simtk.unit.angstroms
[13.768, -8.431, 11.865]
> print pos / simtk.unit.nanometers
[1.3768, -0.8431, 1.1865]
The hierarchical structure of the parsed PDB structure is as follows:
PdbStructure
Model
Chain
Residue
Atom
Location
Model - A PDB structure consists of one or more Models. Each model corresponds to one version of
an NMR structure, or to one frame of a molecular dynamics trajectory.
Chain - A Model contains one or more Chains. Each chain corresponds to one molecule, although multiple
water molecules are frequently included in the same chain.
Residue - A Chain contains one or more Residues. One Residue corresponds to one of the repeating
unit that constitutes a polymer such as protein or DNA. For non-polymeric molecules, one Residue
represents one molecule.
Atom - A Residue contains one or more Atoms. Atoms are chemical atoms.
Location - An atom can sometimes have more that one position, due to static disorder in X-ray
crystal structures. To see all of the atom positions, use the atom.iter_positions() method,
or pass the parameter "include_alt_loc=True" to one of the other iter_positions() methods.
> for pos in pdb.iter_positions(include_alt_loc=True):
> ...
Will loop over all atom positions, including multiple alternate locations for atoms that have
multiple positions. The default value of include_alt_loc is False for the iter_positions()
methods.
"""
def
__init__
(
self
,
input_stream
,
load_all_models
=
False
):
"""Create a PDB model from a PDB file stream.
Parameters:
- self (PdbStructure) The new object that is created.
- input_stream (stream) An input file stream, probably created with
open().
- load_all_models (bool) Whether to load every model of an NMR
structure or trajectory, or just load the first model, to save memory.
"""
# initialize models
self
.
load_all_models
=
load_all_models
self
.
models
=
[]
self
.
_current_model
=
None
self
.
default_model
=
None
self
.
models_by_number
=
{}
self
.
_unit_cell_dimensions
=
None
# read file
self
.
_load
(
input_stream
)
def
_load
(
self
,
input_stream
):
# Read one line at a time
for
pdb_line
in
input_stream
:
# Look for atoms
if
(
pdb_line
.
find
(
"ATOM "
)
==
0
)
or
(
pdb_line
.
find
(
"HETATM"
)
==
0
):
self
.
_add_atom
(
Atom
(
pdb_line
))
# Notice MODEL punctuation, for the next level of detail
# in the structure->model->chain->residue->atom->position hierarchy
elif
(
pdb_line
.
find
(
"MODEL"
)
==
0
):
model_number
=
int
(
pdb_line
[
10
:
14
])
self
.
_add_model
(
Model
(
model_number
))
elif
(
pdb_line
.
find
(
"ENDMDL"
)
==
0
):
self
.
_current_model
.
_finalize
()
if
not
self
.
load_all_models
:
break
elif
(
pdb_line
.
find
(
"END"
)
==
0
):
self
.
_current_model
.
_finalize
()
if
not
self
.
load_all_models
:
break
elif
(
pdb_line
.
find
(
"TER "
)
==
0
):
self
.
_current_model
.
_current_chain
.
_add_ter_record
()
elif
(
pdb_line
.
find
(
"CRYST1"
)
==
0
):
self
.
_unit_cell_dimensions
=
(
float
(
pdb_line
[
6
:
15
]),
float
(
pdb_line
[
15
:
24
]),
float
(
pdb_line
[
24
:
33
]))
*
unit
.
angstroms
self
.
_finalize
()
def
write
(
self
,
output_stream
=
sys
.
stdout
):
"""Write out structure in PDB format"""
for
model
in
self
.
models
:
if
len
(
model
.
chains
)
==
0
:
continue
if
len
(
self
.
models
)
>
1
:
print
>>
output_stream
,
"MODEL %4d"
%
(
model
.
number
)
model
.
write
(
output_stream
)
if
len
(
self
.
models
)
>
1
:
print
>>
output_stream
,
"ENDMDL"
print
>>
output_stream
,
"END"
def
_add_model
(
self
,
model
):
if
self
.
default_model
==
None
:
self
.
default_model
=
model
self
.
models
.
append
(
model
)
self
.
_current_model
=
model
if
model
.
number
not
in
self
.
models_by_number
:
self
.
models_by_number
[
model
.
number
]
=
model
def
get_model
(
self
,
model_number
):
return
self
.
models_by_number
[
model_number
]
def
model_numbers
(
self
):
return
self
.
models_by_number
.
keys
()
def
__contains__
(
self
,
model_number
):
return
self
.
models_by_number
.
__contains__
(
model_number
)
def
__getitem__
(
self
,
model_number
):
return
self
.
models_by_number
[
model_number
]
def
__iter__
(
self
):
for
model
in
self
.
models
:
yield
model
def
iter_models
(
self
,
use_all_models
=
False
):
if
use_all_models
:
for
model
in
self
:
yield
model
elif
len
(
self
.
models
)
>
0
:
yield
self
.
models
[
0
]
def
iter_chains
(
self
,
use_all_models
=
False
):
for
model
in
self
.
iter_models
(
use_all_models
):
for
chain
in
model
.
iter_chains
():
yield
chain
def
iter_residues
(
self
,
use_all_models
=
False
):
for
model
in
self
.
iter_models
(
use_all_models
):
for
res
in
model
.
iter_residues
():
yield
res
def
iter_atoms
(
self
,
use_all_models
=
False
):
for
model
in
self
.
iter_models
(
use_all_models
):
for
atom
in
model
.
iter_atoms
():
yield
atom
def
iter_positions
(
self
,
use_all_models
=
False
,
include_alt_loc
=
False
):
"""
Iterate over atomic positions.
Parameters
- use_all_models (bool=False) Get positions from all models or just the first one.
- include_alt_loc (bool=False) Get all positions for each atom, or just the first one.
"""
for
model
in
self
.
iter_models
(
use_all_models
):
for
loc
in
model
.
iter_positions
(
include_alt_loc
):
yield
loc
def
__len__
(
self
):
return
len
(
self
.
models
)
def
_add_atom
(
self
,
atom
):
"""
"""
if
self
.
_current_model
==
None
:
self
.
_add_model
(
Model
(
0
))
atom
.
model_number
=
self
.
_current_model
.
number
# Atom might be alternate position for existing atom
self
.
_current_model
.
_add_atom
(
atom
)
def
_finalize
(
self
):
"""Establish first and last residues, atoms, etc."""
for
model
in
self
.
models
:
model
.
_finalize
()
def
get_unit_cell_dimensions
(
self
):
"""Get the dimensions of the crystallographic unit cell (may be None)."""
return
self
.
_unit_cell_dimensions
class
Model
(
object
):
"""Model holds one model of a PDB structure.
NMR structures usually have multiple models. This represents one
of them.
"""
def
__init__
(
self
,
model_number
=
1
):
self
.
number
=
model_number
self
.
chains
=
[]
self
.
_current_chain
=
None
self
.
chains_by_id
=
{}
def
_add_atom
(
self
,
atom
):
"""
"""
if
len
(
self
.
chains
)
==
0
:
self
.
_add_chain
(
Chain
(
atom
.
chain_id
))
# Create a new chain if the chain id has changed
if
self
.
_current_chain
.
chain_id
!=
atom
.
chain_id
:
self
.
_add_chain
(
Chain
(
atom
.
chain_id
))
# Create a new chain after TER record, even if ID is the same
elif
self
.
_current_chain
.
has_ter_record
:
self
.
_add_chain
(
Chain
(
atom
.
chain_id
))
self
.
_current_chain
.
_add_atom
(
atom
)
def
_add_chain
(
self
,
chain
):
self
.
chains
.
append
(
chain
)
self
.
_current_chain
=
chain
if
not
chain
.
chain_id
in
self
.
chains_by_id
:
self
.
chains_by_id
[
chain
.
chain_id
]
=
chain
def
get_chain
(
self
,
chain_id
):
return
self
.
chains_by_id
[
chain_id
]
def
chain_ids
(
self
):
return
self
.
chains_by_id
.
keys
()
def
__contains__
(
self
,
chain_id
):
return
self
.
chains_by_id
.
__contains__
(
chain_id
)
def
__getitem__
(
self
,
chain_id
):
return
self
.
chains_by_id
[
chain_id
]
def
__iter__
(
self
):
return
iter
(
self
.
chains
)
def
iter_chains
(
self
):
for
chain
in
self
:
yield
chain
def
iter_residues
(
self
):
for
chain
in
self
:
for
res
in
chain
.
iter_residues
():
yield
res
def
iter_atoms
(
self
):
for
chain
in
self
:
for
atom
in
chain
.
iter_atoms
():
yield
atom
def
iter_positions
(
self
,
include_alt_loc
=
False
):
for
chain
in
self
:
for
loc
in
chain
.
iter_positions
(
include_alt_loc
):
yield
loc
def
__len__
(
self
):
return
len
(
self
.
chains
)
def
write
(
self
,
output_stream
=
sys
.
stdout
):
# Start atom serial numbers at 1
sn
=
Model
.
AtomSerialNumber
(
1
)
for
chain
in
self
.
chains
:
chain
.
write
(
sn
,
output_stream
)
def
_finalize
(
self
):
for
chain
in
self
.
chains
:
chain
.
_finalize
()
class
AtomSerialNumber
(
object
):
"""pdb.Model inner class for pass-by-reference incrementable serial number"""
def
__init__
(
self
,
val
):
self
.
val
=
val
def
increment
(
self
):
self
.
val
+=
1
class
Chain
(
object
):
def
__init__
(
self
,
chain_id
=
' '
):
self
.
chain_id
=
chain_id
self
.
residues
=
[]
self
.
has_ter_record
=
False
self
.
_current_residue
=
None
self
.
residues_by_num_icode
=
{}
self
.
residues_by_number
=
{}
def
_add_atom
(
self
,
atom
):
"""
"""
# Create a residue if none have been created
if
len
(
self
.
residues
)
==
0
:
self
.
_add_residue
(
Residue
(
atom
.
residue_name_with_spaces
,
atom
.
residue_number
,
atom
.
insertion_code
,
atom
.
alternate_location_indicator
))
# Create a residue if the residue information has changed
elif
self
.
_current_residue
.
number
!=
atom
.
residue_number
:
self
.
_add_residue
(
Residue
(
atom
.
residue_name_with_spaces
,
atom
.
residue_number
,
atom
.
insertion_code
,
atom
.
alternate_location_indicator
))
elif
self
.
_current_residue
.
insertion_code
!=
atom
.
insertion_code
:
self
.
_add_residue
(
Residue
(
atom
.
residue_name_with_spaces
,
atom
.
residue_number
,
atom
.
insertion_code
,
atom
.
alternate_location_indicator
))
elif
self
.
_current_residue
.
name_with_spaces
==
atom
.
residue_name_with_spaces
:
# This is a normal case: number, name, and iCode have not changed
pass
elif
atom
.
alternate_location_indicator
!=
' '
:
# OK - this is a point mutation, Residue._add_atom will know what to do
pass
else
:
# Residue name does not match
# Only residue name does not match
warnings
.
warn
(
"WARNING: two consecutive residues with same number (%s, %s)"
%
(
atom
,
self
.
_current_residue
.
atoms
[
-
1
]))
self
.
_add_residue
(
Residue
(
atom
.
residue_name_with_spaces
,
atom
.
residue_number
,
atom
.
insertion_code
,
atom
.
alternate_location_indicator
))
self
.
_current_residue
.
_add_atom
(
atom
)
def
_add_residue
(
self
,
residue
):
if
len
(
self
.
residues
)
==
0
:
residue
.
is_first_in_chain
=
True
self
.
residues
.
append
(
residue
)
self
.
_current_residue
=
residue
key
=
str
(
residue
.
number
)
+
residue
.
insertion_code
# only store the first residue with a particular key
if
key
not
in
self
.
residues_by_num_icode
:
self
.
residues_by_num_icode
[
key
]
=
residue
if
residue
.
number
not
in
self
.
residues_by_number
:
self
.
residues_by_number
[
residue
.
number
]
=
residue
def
write
(
self
,
next_serial_number
,
output_stream
=
sys
.
stdout
):
for
residue
in
self
.
residues
:
residue
.
write
(
next_serial_number
,
output_stream
)
if
self
.
has_ter_record
:
r
=
self
.
residues
[
-
1
]
print
>>
output_stream
,
"TER %5d %3s %1s%4d%1s"
%
(
next_serial_number
.
val
,
r
.
name_with_spaces
,
self
.
chain_id
,
r
.
number
,
r
.
insertion_code
)
next_serial_number
.
increment
()
def
_add_ter_record
(
self
):
self
.
has_ter_record
=
True
self
.
_finalize
()
def
get_residue
(
self
,
residue_number
,
insertion_code
=
' '
):
return
residues_by_num_icode
[
str
(
residue_number
)
+
insertion_code
]
def
__contains__
(
self
,
residue_number
):
return
self
.
residues_by_number
.
__contains__
(
residue_number
)
def
__getitem__
(
self
,
residue_number
):
"""Returns the FIRST residue in this chain with a particular residue number"""
return
self
.
residues_by_number
[
residue_number
]
def
__iter__
(
self
):
for
res
in
self
.
residues
:
yield
res
def
iter_residues
(
self
):
for
res
in
self
:
yield
res
def
iter_atoms
(
self
):
for
res
in
self
:
for
atom
in
res
:
yield
atom
;
def
iter_positions
(
self
,
include_alt_loc
=
False
):
for
res
in
self
:
for
loc
in
res
.
iter_positions
(
include_alt_loc
):
yield
loc
def
__len__
(
self
):
return
len
(
self
.
residues
)
def
_finalize
(
self
):
self
.
residues
[
0
].
is_first_in_chain
=
True
self
.
residues
[
-
1
].
is_final_in_chain
=
True
for
residue
in
self
.
residues
:
residue
.
_finalize
()
class
Residue
(
object
):
def
__init__
(
self
,
name
,
number
,
insertion_code
=
' '
,
primary_alternate_location_indicator
=
' '
):
alt_loc
=
primary_alternate_location_indicator
self
.
primary_location_id
=
alt_loc
self
.
locations
=
{}
self
.
locations
[
alt_loc
]
=
Residue
.
Location
(
alt_loc
,
name
)
self
.
name_with_spaces
=
name
self
.
number
=
number
self
.
insertion_code
=
insertion_code
self
.
atoms
=
[]
self
.
atoms_by_name
=
{}
self
.
is_first_in_chain
=
False
self
.
is_final_in_chain
=
False
self
.
_current_atom
=
None
def
_add_atom
(
self
,
atom
):
"""
"""
alt_loc
=
atom
.
alternate_location_indicator
if
not
self
.
locations
.
has_key
(
alt_loc
):
self
.
locations
[
alt_loc
]
=
Residue
.
Location
(
alt_loc
,
atom
.
residue_name_with_spaces
)
assert
atom
.
residue_number
==
self
.
number
assert
atom
.
insertion_code
==
self
.
insertion_code
# Check whether this is an existing atom with another position
if
(
atom
.
name_with_spaces
in
self
.
atoms_by_name
):
old_atom
=
self
.
atoms_by_name
[
atom
.
name_with_spaces
]
# Unless this is a duplicated atom (warn about file error)
if
atom
.
alternate_location_indicator
in
old_atom
.
locations
:
warnings
.
warn
(
"WARNING: duplicate atom (%s, %s)"
%
(
atom
,
old_atom
.
_pdb_string
(
old_atom
.
serial_number
,
atom
.
alternate_location_indicator
)))
else
:
for
alt_loc
,
position
in
atom
.
locations
.
items
():
old_atom
.
locations
[
alt_loc
]
=
position
return
# no new atom added
# actually use new atom
self
.
atoms_by_name
[
atom
.
name
]
=
atom
self
.
atoms_by_name
[
atom
.
name_with_spaces
]
=
atom
self
.
atoms
.
append
(
atom
)
self
.
_current_atom
=
atom
def
write
(
self
,
next_serial_number
,
output_stream
=
sys
.
stdout
,
alt_loc
=
"*"
):
for
atom
in
self
.
atoms
:
atom
.
write
(
next_serial_number
,
output_stream
,
alt_loc
)
def
_finalize
(
self
):
if
len
(
self
.
atoms
)
>
0
:
self
.
atoms
[
0
].
is_first_atom_in_chain
=
self
.
is_first_in_chain
self
.
atoms
[
-
1
].
is_final_atom_in_chain
=
self
.
is_final_in_chain
for
atom
in
self
.
atoms
:
atom
.
is_first_residue_in_chain
=
self
.
is_first_in_chain
atom
.
is_final_residue_in_chain
=
self
.
is_final_in_chain
def
set_name_with_spaces
(
self
,
name
,
alt_loc
=
None
):
# Gromacs ffamber PDB files can have 4-character residue names
# assert len(name) == 3
if
alt_loc
==
None
:
alt_loc
=
self
.
primary_location_id
loc
=
self
.
locations
[
alt_loc
]
loc
.
name_with_spaces
=
name
loc
.
name
=
name
.
strip
()
def
get_name_with_spaces
(
self
,
alt_loc
=
None
):
if
alt_loc
==
None
:
alt_loc
=
self
.
primary_location_id
loc
=
self
.
locations
[
alt_loc
]
return
loc
.
name_with_spaces
name_with_spaces
=
property
(
get_name_with_spaces
,
set_name_with_spaces
,
doc
=
'four-character residue name including spaces'
)
def
get_name
(
self
,
alt_loc
=
None
):
if
alt_loc
==
None
:
alt_loc
=
self
.
primary_location_id
loc
=
self
.
locations
[
alt_loc
]
return
loc
.
name
name
=
property
(
get_name
,
doc
=
'residue name'
)
def
get_atom
(
self
,
atom_name
):
return
self
.
atoms_by_name
[
atom_name
]
def
__contains__
(
self
,
atom_name
):
return
self
.
atoms_by_name
.
__contains__
(
atom_name
)
def
__getitem__
(
self
,
atom_name
):
"""Returns the FIRST atom in this residue with a particular atom name"""
return
self
.
atoms_by_name
[
atom_name
]
def
__iter__
(
self
):
"""
>>> pdb_lines = [ \
"ATOM 188 N CYS A 42 40.714 -5.292 12.123 1.00 11.29 N",\
"ATOM 189 CA CYS A 42 39.736 -5.883 12.911 1.00 10.01 C",\
"ATOM 190 C CYS A 42 40.339 -6.654 14.087 1.00 22.28 C",\
"ATOM 191 O CYS A 42 41.181 -7.530 13.859 1.00 13.70 O",\
"ATOM 192 CB CYS A 42 38.949 -6.825 12.002 1.00 9.67 C",\
"ATOM 193 SG CYS A 42 37.557 -7.514 12.922 1.00 20.12 S"]
>>> res = Residue("CYS", 42)
>>> for l in pdb_lines:
... res._add_atom(Atom(l))
...
>>> for atom in res:
... print atom
ATOM 188 N CYS A 42 40.714 -5.292 12.123 1.00 11.29 N
ATOM 189 CA CYS A 42 39.736 -5.883 12.911 1.00 10.01 C
ATOM 190 C CYS A 42 40.339 -6.654 14.087 1.00 22.28 C
ATOM 191 O CYS A 42 41.181 -7.530 13.859 1.00 13.70 O
ATOM 192 CB CYS A 42 38.949 -6.825 12.002 1.00 9.67 C
ATOM 193 SG CYS A 42 37.557 -7.514 12.922 1.00 20.12 S
"""
for
atom
in
self
.
iter_atoms
():
yield
atom
# Three possibilities: primary alt_loc, certain alt_loc, or all alt_locs
def
iter_atoms
(
self
,
alt_loc
=
None
):
if
alt_loc
==
None
:
locs
=
[
self
.
primary_location_id
]
elif
alt_loc
==
""
:
locs
=
[
self
.
primary_location_id
]
elif
alt_loc
==
"*"
:
locs
=
None
else
:
locs
=
list
(
alt_loc
)
# If an atom has any location in alt_loc, emit the atom
for
atom
in
self
.
atoms
:
use_atom
=
False
# start pessimistic
for
loc2
in
atom
.
locations
.
keys
():
# print "#%s#%s" % (loc2,locs)
if
locs
==
None
:
# means all locations
use_atom
=
True
elif
loc2
in
locs
:
use_atom
=
True
if
use_atom
:
yield
atom
def
iter_positions
(
self
,
include_alt_loc
=
False
):
"""
Returns one position per atom, even if an individual atom has multiple positions.
>>> pdb_lines = [ \
"ATOM 188 N CYS A 42 40.714 -5.292 12.123 1.00 11.29 N",\
"ATOM 189 CA CYS A 42 39.736 -5.883 12.911 1.00 10.01 C",\
"ATOM 190 C CYS A 42 40.339 -6.654 14.087 1.00 22.28 C",\
"ATOM 191 O CYS A 42 41.181 -7.530 13.859 1.00 13.70 O",\
"ATOM 192 CB CYS A 42 38.949 -6.825 12.002 1.00 9.67 C",\
"ATOM 193 SG CYS A 42 37.557 -7.514 12.922 1.00 20.12 S"]
>>> res = Residue("CYS", 42)
>>> for l in pdb_lines: res._add_atom(Atom(l))
>>> for c in res.iter_positions:
... print c
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'instancemethod' object is not iterable
>>> for c in res.iter_positions():
... print c
[40.714, -5.292, 12.123] A
[39.736, -5.883, 12.911] A
[40.339, -6.654, 14.087] A
[41.181, -7.53, 13.859] A
[38.949, -6.825, 12.002] A
[37.557, -7.514, 12.922] A
"""
for
atom
in
self
:
if
include_alt_loc
:
for
loc
in
atom
.
iter_positions
():
yield
loc
else
:
yield
atom
.
position
def
__len__
(
self
):
return
len
(
self
.
atoms
)
# Residues can have multiple locations, based on alt_loc indicator
class
Location
:
"""
Inner class of residue to allow different residue names for different alternate_locations.
"""
def
__init__
(
self
,
alternate_location_indicator
,
residue_name_with_spaces
):
self
.
alternate_location_indicator
=
alternate_location_indicator
self
.
residue_name_with_spaces
=
residue_name_with_spaces
class
Atom
(
object
):
"""Atom represents one atom in a PDB structure.
"""
def
__init__
(
self
,
pdb_line
):
"""Create a new pdb.Atom from an ATOM or HETATM line.
Example line:
ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C
00000000011111111112222222222333333333344444444445555555555666666666677777777778
12345678901234567890123456789012345678901234567890123456789012345678901234567890
ATOM line format description from
http://deposit.rcsb.org/adit/docs/pdb_atom_format.html:
COLUMNS DATA TYPE CONTENTS
--------------------------------------------------------------------------------
1 - 6 Record name "ATOM "
7 - 11 Integer Atom serial number.
13 - 16 Atom Atom name.
17 Character Alternate location indicator.
18 - 20 Residue name Residue name.
22 Character Chain identifier.
23 - 26 Integer Residue sequence number.
27 AChar Code for insertion of residues.
31 - 38 Real(8.3) Orthogonal coordinates for X in Angstroms.
39 - 46 Real(8.3) Orthogonal coordinates for Y in Angstroms.
47 - 54 Real(8.3) Orthogonal coordinates for Z in Angstroms.
55 - 60 Real(6.2) Occupancy (Default = 1.0).
61 - 66 Real(6.2) Temperature factor (Default = 0.0).
73 - 76 LString(4) Segment identifier, left-justified.
77 - 78 LString(2) Element symbol, right-justified.
79 - 80 LString(2) Charge on the atom.
"""
# We might modify first/final status during _finalize() methods
self
.
is_first_atom_in_chain
=
False
self
.
is_final_atom_in_chain
=
False
self
.
is_first_residue_in_chain
=
False
self
.
is_final_residue_in_chain
=
False
# Start parsing fields from pdb line
self
.
record_name
=
pdb_line
[
0
:
6
].
strip
()
self
.
serial_number
=
int
(
pdb_line
[
6
:
11
])
self
.
name_with_spaces
=
pdb_line
[
12
:
16
]
alternate_location_indicator
=
pdb_line
[
16
]
self
.
residue_name_with_spaces
=
pdb_line
[
17
:
20
]
# In some MD codes, notably ffamber in gromacs, residue name has a fourth character in
# column 21
possible_fourth_character
=
pdb_line
[
20
:
21
]
if
possible_fourth_character
!=
" "
:
# Fourth character should only be there if official 3 are already full
if
len
(
self
.
residue_name_with_spaces
.
strip
())
!=
3
:
raise
ValueError
(
'Misaligned residue name: %s'
%
pdb_line
)
self
.
residue_name_with_spaces
+=
possible_fourth_character
self
.
residue_name
=
self
.
residue_name_with_spaces
.
strip
()
self
.
chain_id
=
pdb_line
[
21
]
self
.
residue_number
=
int
(
pdb_line
[
22
:
26
])
self
.
insertion_code
=
pdb_line
[
26
]
# coordinates, occupancy, and temperature factor belong in Atom.Location object
x
=
float
(
pdb_line
[
30
:
38
])
y
=
float
(
pdb_line
[
38
:
46
])
z
=
float
(
pdb_line
[
46
:
54
])
try
:
occupancy
=
float
(
pdb_line
[
54
:
60
])
except
:
occupancy
=
1.0
try
:
temperature_factor
=
float
(
pdb_line
[
60
:
66
])
*
unit
.
angstroms
*
unit
.
angstroms
except
:
temperature_factor
=
0.0
self
.
locations
=
{}
loc
=
Atom
.
Location
(
alternate_location_indicator
,
Vec3
(
x
,
y
,
z
)
*
unit
.
angstroms
,
occupancy
,
temperature_factor
,
self
.
residue_name_with_spaces
)
self
.
locations
[
alternate_location_indicator
]
=
loc
self
.
default_location_id
=
alternate_location_indicator
# segment id, element_symbol, and formal_charge are not always present
self
.
segment_id
=
pdb_line
[
72
:
76
].
strip
()
self
.
element_symbol
=
pdb_line
[
76
:
78
].
strip
()
try
:
self
.
formal_charge
=
int
(
pdb_line
[
78
:
80
])
except
ValueError
:
self
.
formal_charge
=
None
# figure out atom element
try
:
# First try to find a sensible element symbol from columns 76-77
self
.
element
=
element
.
get_by_symbol
(
self
.
element_symbol
)
except
KeyError
:
# otherwise, deduce element from first two characters of atom name
# remove digits found in some hydrogen atom names
symbol
=
self
.
name_with_spaces
[
0
:
2
].
strip
().
lstrip
(
"0123456789"
)
try
:
# Some molecular dynamics PDB files, such as gromacs with ffamber force
# field, include 4-character hydrogen atom names beginning with "H".
# Hopefully elements like holmium (Ho) and mercury (Hg) will have fewer than four
# characters in the atom name. This problem is the fault of molecular
# dynamics code authors who feel the need to make up their own atom
# nomenclature because it is too tedious to read that provided by the PDB.
# These are the same folks who invent their own meanings for biochemical terms
# like "dipeptide". Clowntards.
if
len
(
self
.
name
)
==
4
and
self
.
name
[
0
:
1
]
==
"H"
:
self
.
element
=
element
.
hydrogen
else
:
self
.
element
=
element
.
get_by_symbol
(
symbol
)
except
KeyError
:
# OK, I give up
self
.
element
=
None
def
iter_locations
(
self
):
"""
Iterate over Atom.Location objects for this atom, including primary location.
>>> atom = Atom("ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C")
>>> for c in atom.iter_locations():
... print c
...
[6.167, 22.607, 20.046] A
"""
for
alt_loc
in
self
.
locations
:
yield
self
.
locations
[
alt_loc
]
def
iter_positions
(
self
):
"""
Iterate over atomic positions. Returns Quantity(Vec3(), unit) objects, unlike
iter_locations, which returns Atom.Location objects.
"""
for
loc
in
self
.
iter_locations
():
yield
loc
.
position
def
iter_coordinates
(
self
):
"""
Iterate over x, y, z values of primary atom position.
>>> atom = Atom("ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C")
>>> for c in atom.iter_coordinates():
... print c
...
6.167 A
22.607 A
20.046 A
"""
for
coord
in
self
.
position
:
yield
coord
# Hide existence of multiple alternate locations to avoid scaring casual users
def
get_location
(
self
,
location_id
=
None
):
id
=
location_id
if
(
id
==
None
):
id
=
self
.
default_location_id
return
self
.
locations
[
id
]
def
set_location
(
self
,
new_location
,
location_id
=
None
):
id
=
location_id
if
(
id
==
None
):
id
=
self
.
default_location_id
self
.
locations
[
id
]
=
new_location
location
=
property
(
get_location
,
set_location
,
doc
=
'default Atom.Location object'
)
def
get_position
(
self
):
return
self
.
location
.
position
def
set_position
(
self
,
coords
):
self
.
location
.
position
=
coords
position
=
property
(
get_position
,
set_position
,
doc
=
'orthogonal coordinates'
)
def
get_alternate_location_indicator
(
self
):
return
self
.
location
.
alternate_location_indicator
alternate_location_indicator
=
property
(
get_alternate_location_indicator
)
def
get_occupancy
(
self
):
return
self
.
location
.
occupancy
occupancy
=
property
(
get_occupancy
)
def
get_temperature_factor
(
self
):
return
self
.
location
.
temperature_factor
temperature_factor
=
property
(
get_temperature_factor
)
def
get_x
(
self
):
return
self
.
position
[
0
]
x
=
property
(
get_x
)
def
get_y
(
self
):
return
self
.
position
[
1
]
y
=
property
(
get_y
)
def
get_z
(
self
):
return
self
.
position
[
2
]
z
=
property
(
get_z
)
def
_pdb_string
(
self
,
serial_number
=
None
,
alternate_location_indicator
=
None
):
"""
Produce a PDB line for this atom using a particular serial number and alternate location
"""
if
serial_number
==
None
:
serial_number
=
self
.
serial_number
if
alternate_location_indicator
==
None
:
alternate_location_indicator
=
self
.
alternate_location_indicator
# produce PDB line in three parts: names, numbers, and end
# Accomodate 4-character residue names that use column 21
long_res_name
=
self
.
residue_name_with_spaces
if
len
(
long_res_name
)
==
3
:
long_res_name
+=
" "
assert
len
(
long_res_name
)
==
4
names
=
"%-6s%5d %4s%1s%4s%1s%4d%1s "
%
(
self
.
record_name
,
serial_number
,
\
self
.
name_with_spaces
,
alternate_location_indicator
,
\
long_res_name
,
self
.
chain_id
,
\
self
.
residue_number
,
self
.
insertion_code
)
numbers
=
"%8.3f%8.3f%8.3f%6.2f%6.2f "
%
(
self
.
x
.
value_in_unit
(
unit
.
angstroms
),
\
self
.
y
.
value_in_unit
(
unit
.
angstroms
),
\
self
.
z
.
value_in_unit
(
unit
.
angstroms
),
\
self
.
occupancy
,
\
self
.
temperature_factor
.
value_in_unit
(
unit
.
angstroms
*
unit
.
angstroms
))
end
=
"%-4s%2s"
%
(
\
self
.
segment_id
,
self
.
element_symbol
)
formal_charge
=
" "
if
(
self
.
formal_charge
!=
None
):
formal_charge
=
"%+2d"
%
self
.
formal_charge
return
names
+
numbers
+
end
+
formal_charge
def
__str__
(
self
):
return
self
.
_pdb_string
(
self
.
serial_number
,
self
.
alternate_location_indicator
)
def
write
(
self
,
next_serial_number
,
output_stream
=
sys
.
stdout
,
alt_loc
=
"*"
):
"""
alt_loc = "*" means write all alternate locations
alt_loc = None means write just the primary location
alt_loc = "AB" means write locations "A" and "B"
"""
if
alt_loc
==
None
:
locs
=
[
self
.
default_location_id
]
elif
alt_loc
==
""
:
locs
=
[
self
.
default_location_id
]
elif
alt_loc
==
"*"
:
locs
=
self
.
locations
.
keys
()
locs
.
sort
()
else
:
locs
=
list
(
alt_loc
)
for
loc_id
in
locs
:
print
>>
output_stream
,
self
.
_pdb_string
(
next_serial_number
.
val
,
loc_id
)
next_serial_number
.
increment
()
def
set_name_with_spaces
(
self
,
name
):
assert
len
(
name
)
==
4
self
.
_name_with_spaces
=
name
self
.
_name
=
name
.
strip
()
def
get_name_with_spaces
(
self
):
return
self
.
_name_with_spaces
name_with_spaces
=
property
(
get_name_with_spaces
,
set_name_with_spaces
,
doc
=
'four-character residue name including spaces'
)
def
get_name
(
self
):
return
self
.
_name
name
=
property
(
get_name
,
doc
=
'residue name'
)
class
Location
(
object
):
"""
Inner class of Atom for holding alternate locations
"""
def
__init__
(
self
,
alt_loc
,
position
,
occupancy
,
temperature_factor
,
residue_name
):
self
.
alternate_location_indicator
=
alt_loc
self
.
position
=
position
self
.
occupancy
=
occupancy
self
.
temperature_factor
=
temperature_factor
self
.
residue_name
=
residue_name
def
__iter__
(
self
):
"""
Examples
>>> from simtk.openmm.vec3 import Vec3
>>> import simtk.unit as unit
>>> l = Atom.Location(' ', Vec3(1,2,3)*unit.angstroms, 1.0, 20.0*unit.angstroms**2, "XXX")
>>> for c in l:
... print c
...
1 A
2 A
3 A
"""
for
coord
in
self
.
position
:
yield
coord
def
__str__
(
self
):
return
str
(
self
.
position
)
# run module directly for testing
if
__name__
==
'__main__'
:
# Test the examples in the docstrings
import
doctest
,
sys
doctest
.
testmod
(
sys
.
modules
[
__name__
])
import
sys
import
os
import
gzip
import
re
import
time
# Test atom line parsing
pdb_line
=
"ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C"
a
=
Atom
(
pdb_line
)
assert
a
.
record_name
==
"ATOM"
assert
a
.
serial_number
==
2209
assert
a
.
name
==
"CB"
assert
a
.
name_with_spaces
==
" CB "
assert
a
.
residue_name
==
"TYR"
assert
a
.
residue_name_with_spaces
==
"TYR"
assert
a
.
chain_id
==
"A"
assert
a
.
residue_number
==
299
assert
a
.
insertion_code
==
" "
assert
a
.
alternate_location_indicator
==
" "
assert
a
.
x
==
6.167
*
unit
.
angstroms
assert
a
.
y
==
22.607
*
unit
.
angstroms
assert
a
.
z
==
20.046
*
unit
.
angstroms
assert
a
.
occupancy
==
1.00
assert
a
.
temperature_factor
==
8.12
*
unit
.
angstroms
*
unit
.
angstroms
assert
a
.
segment_id
==
""
assert
a
.
element_symbol
==
"C"
# print pdb_line
# print str(a)
assert
str
(
a
).
rstrip
()
==
pdb_line
.
rstrip
()
a
=
Atom
(
"ATOM 2209 CB TYR A 299 6.167 22.607 20.046 1.00 8.12 C"
)
# misaligned residue name - bad
try
:
a
=
Atom
(
"ATOM 2209 CB TYRA 299 6.167 22.607 20.046 1.00 8.12 C"
)
assert
(
False
)
except
ValueError
:
pass
# four character residue name -- not so bad
a
=
Atom
(
"ATOM 2209 CB NTYRA 299 6.167 22.607 20.046 1.00 8.12 C"
)
atom_count
=
0
residue_count
=
0
chain_count
=
0
model_count
=
0
structure_count
=
0
def
parse_one_pdb
(
pdb_file_name
):
global
atom_count
,
residue_count
,
chain_count
,
model_count
,
structure_count
print
pdb_file_name
if
pdb_file_name
[
-
3
:]
==
".gz"
:
fh
=
gzip
.
open
(
pdb_file_name
)
else
:
fh
=
open
(
pdb_file_name
)
pdb
=
PdbStructure
(
fh
,
load_all_models
=
True
)
# print " %d atoms found" % len(pdb.atoms)
atom_count
+=
len
(
list
(
pdb
.
iter_atoms
()))
residue_count
+=
len
(
list
(
pdb
.
iter_residues
()))
chain_count
+=
len
(
list
(
pdb
.
iter_chains
()))
model_count
+=
len
(
list
(
pdb
.
iter_models
()))
structure_count
+=
1
fh
.
close
return
pdb
# Parse one file
pdb_file_name
=
"/home/Christopher Bruns/Desktop/1ARJ.pdb"
if
os
.
path
.
exists
(
pdb_file_name
):
parse_one_pdb
(
pdb_file_name
)
# try parsing the entire PDB
pdb_dir
=
"/cygdrive/j/pdb/data/structures/divided/pdb"
if
os
.
path
.
exists
(
pdb_dir
):
parse_entire_pdb
=
False
parse_one_division
=
False
parse_one_file
=
False
start_time
=
time
.
time
()
if
parse_one_file
:
pdb_id
=
"2aed"
middle_two
=
pdb_id
[
1
:
3
]
full_pdb_file
=
os
.
path
.
join
(
pdb_dir
,
middle_two
,
"pdb%s.ent.gz"
%
pdb_id
)
parse_one_pdb
(
full_pdb_file
)
if
parse_one_division
:
subdir
=
"ae"
full_subdir
=
os
.
path
.
join
(
pdb_dir
,
subdir
)
for
pdb_file
in
os
.
listdir
(
full_subdir
):
if
not
re
.
match
(
"pdb.%2s.\.ent\.gz"
%
subdir
,
pdb_file
):
continue
full_pdb_file
=
os
.
path
.
join
(
full_subdir
,
pdb_file
)
parse_one_pdb
(
full_pdb_file
)
if
parse_entire_pdb
:
for
subdir
in
os
.
listdir
(
pdb_dir
):
if
not
len
(
subdir
)
==
2
:
continue
full_subdir
=
os
.
path
.
join
(
pdb_dir
,
subdir
)
if
not
os
.
path
.
isdir
(
full_subdir
):
continue
for
pdb_file
in
os
.
listdir
(
full_subdir
):
if
not
re
.
match
(
"pdb.%2s.\.ent\.gz"
%
subdir
,
pdb_file
):
continue
full_pdb_file
=
os
.
path
.
join
(
full_subdir
,
pdb_file
)
parse_one_pdb
(
full_pdb_file
)
end_time
=
time
.
time
()
elapsed
=
end_time
-
start_time
minutes
=
elapsed
/
60
seconds
=
elapsed
%
60
hours
=
minutes
/
60
minutes
=
minutes
%
60
print
"%dh:%02dm:%02ds elapsed"
%
(
hours
,
minutes
,
seconds
)
print
"%d atoms found"
%
atom_count
print
"%d residues found"
%
residue_count
print
"%d chains found"
%
chain_count
print
"%d models found"
%
model_count
print
"%d structures found"
%
structure_count
wrappers/python/simtk/openmm/app/pdbfile.py
0 → 100644
View file @
ccc0ba85
"""
pdbfile.py: Used for loading PDB files.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
os
import
sys
import
xml.etree.ElementTree
as
etree
from
copy
import
copy
from
simtk.openmm
import
Vec3
from
simtk.openmm.app.internal.pdbstructure
import
PdbStructure
from
simtk.openmm.app
import
Topology
from
simtk.unit
import
nanometers
,
angstroms
,
is_quantity
import
element
as
elem
class
PDBFile
(
object
):
"""PDBFile parses a Protein Data Bank (PDB) file and constructs a Topology and a set of atom positions from it.
This class also provides methods for creating PDB files. To write a file containing a single model, call
writeFile(). You also can create files that contain multiple models. To do this, first call writeHeader(),
then writeModel() once for each model in the file, and finally writeFooter() to complete the file."""
_residueNameReplacements
=
{}
_atomNameReplacements
=
{}
def
__init__
(
self
,
file
):
"""Load a PDB file.
The atom positions and Topology are stored into this object's "positions" and "topology" fields.
Parameters:
- file (string) the name of the file to load
"""
top
=
Topology
()
coords
=
[];
self
.
topology
=
top
# Load the PDB file
pdb
=
PdbStructure
(
open
(
file
))
PDBFile
.
_loadNameReplacementTables
()
# Build the topology
for
chain
in
pdb
.
iter_chains
():
c
=
top
.
addChain
()
for
residue
in
chain
.
iter_residues
():
resName
=
residue
.
get_name
()
if
resName
in
PDBFile
.
_residueNameReplacements
:
resName
=
PDBFile
.
_residueNameReplacements
[
resName
]
r
=
top
.
addResidue
(
resName
,
c
)
if
resName
in
PDBFile
.
_atomNameReplacements
:
atomReplacements
=
PDBFile
.
_atomNameReplacements
[
resName
]
else
:
atomReplacements
=
{}
for
atom
in
residue
.
iter_atoms
():
atomName
=
atom
.
get_name
()
if
atomName
in
atomReplacements
:
atomName
=
atomReplacements
[
atomName
]
atomName
=
atomName
.
strip
()
element
=
None
# Try to guess the element.
upper
=
atomName
.
upper
()
if
upper
.
startswith
(
'CL'
):
element
=
elem
.
chlorine
elif
upper
.
startswith
(
'NA'
):
element
=
elem
.
sodium
elif
upper
.
startswith
(
'MG'
):
element
=
elem
.
magnesium
elif
upper
.
startswith
(
'BE'
):
element
=
elem
.
beryllium
elif
upper
.
startswith
(
'LI'
):
element
=
elem
.
lithium
elif
upper
.
startswith
(
'K'
):
element
=
elem
.
potassium
elif
(
len
(
residue
)
==
1
and
upper
.
startswith
(
'CA'
)
):
element
=
elem
.
calcium
else
:
try
:
element
=
elem
.
get_by_symbol
(
atomName
[
0
])
except
KeyError
:
pass
top
.
addAtom
(
atomName
,
element
,
r
)
pos
=
atom
.
get_position
().
value_in_unit
(
nanometers
)
coords
.
append
(
Vec3
(
pos
[
0
],
pos
[
1
],
pos
[
2
]))
self
.
positions
=
coords
*
nanometers
self
.
topology
.
setUnitCellDimensions
(
pdb
.
get_unit_cell_dimensions
())
self
.
topology
.
createStandardBonds
()
self
.
topology
.
createDisulfideBonds
(
self
.
positions
)
@
staticmethod
def
_loadNameReplacementTables
():
"""Load the list of atom and residue name replacements."""
if
len
(
PDBFile
.
_residueNameReplacements
)
==
0
:
tree
=
etree
.
parse
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'data'
,
'pdbNames.xml'
))
allResidues
=
{}
proteinResidues
=
{}
nucleicAcidResidues
=
{}
for
residue
in
tree
.
getroot
().
findall
(
'Residue'
):
name
=
residue
.
attrib
[
'name'
]
if
name
==
'All'
:
PDBFile
.
_parseResidueAtoms
(
residue
,
allResidues
)
elif
name
==
'Protein'
:
PDBFile
.
_parseResidueAtoms
(
residue
,
proteinResidues
)
elif
name
==
'Nucleic'
:
PDBFile
.
_parseResidueAtoms
(
residue
,
nucleicAcidResidues
)
for
atom
in
allResidues
:
proteinResidues
[
atom
]
=
allResidues
[
atom
]
nucleicAcidResidues
[
atom
]
=
allResidues
[
atom
]
for
residue
in
tree
.
getroot
().
findall
(
'Residue'
):
name
=
residue
.
attrib
[
'name'
]
for
id
in
residue
.
attrib
:
if
id
==
'name'
or
id
.
startswith
(
'alt'
):
PDBFile
.
_residueNameReplacements
[
residue
.
attrib
[
id
]]
=
name
if
'type'
not
in
residue
.
attrib
:
atoms
=
copy
(
allResidues
)
elif
residue
.
attrib
[
'type'
]
==
'Protein'
:
atoms
=
copy
(
proteinResidues
)
elif
residue
.
attrib
[
'type'
]
==
'Nucleic'
:
atoms
=
copy
(
nucleicAcidResidues
)
else
:
atoms
=
copy
(
allResidues
)
PDBFile
.
_parseResidueAtoms
(
residue
,
atoms
)
PDBFile
.
_atomNameReplacements
[
name
]
=
atoms
@
staticmethod
def
_parseResidueAtoms
(
residue
,
map
):
for
atom
in
residue
.
findall
(
'Atom'
):
name
=
atom
.
attrib
[
'name'
]
for
id
in
atom
.
attrib
:
map
[
atom
.
attrib
[
id
]]
=
name
@
staticmethod
def
writeFile
(
topology
,
positions
,
file
=
sys
.
stdout
,
modelIndex
=
None
):
"""Write a PDB file containing a single model.
Parameters:
- topology (Topology) The Topology defining the model to write
- positions (list) The list of atomic positions to write
- file (file=stdout) A file to write to
"""
PDBFile
.
writeHeader
(
topology
,
file
)
PDBFile
.
writeModel
(
topology
,
positions
,
file
)
PDBFile
.
writeFooter
(
topology
,
file
)
@
staticmethod
def
writeHeader
(
topology
,
file
=
sys
.
stdout
):
"""Write out the header for a PDB file.
Parameters:
- topology (Topology) The Topology defining the molecular system being written
- file (file=stdout) A file to write the file to
"""
boxSize
=
topology
.
getUnitCellDimensions
()
if
boxSize
is
not
None
:
size
=
boxSize
.
value_in_unit
(
angstroms
)
print
>>
file
,
"CRYST1%9.3f%9.3f%9.3f 90.00 90.00 90.00 P 1 1 "
%
size
@
staticmethod
def
writeModel
(
topology
,
positions
,
file
=
sys
.
stdout
,
modelIndex
=
None
):
"""Write out a model to a PDB file.
Parameters:
- topology (Topology) The Topology defining the model to write
- positions (list) The list of atomic positions to write
- file (file=stdout) A file to write the model to
- modelIndex (int=None) If not None, the model will be surrounded by MODEL/ENDMDL records with this index
"""
if
len
(
list
(
topology
.
atoms
()))
!=
len
(
positions
):
raise
ValueError
(
'The number of positions must match the number of atoms'
)
if
is_quantity
(
positions
):
positions
=
positions
.
value_in_unit
(
angstroms
)
atomIndex
=
1
posIndex
=
0
if
modelIndex
is
not
None
:
print
>>
file
,
"MODEL %4d"
%
modelIndex
for
(
chainIndex
,
chain
)
in
enumerate
(
topology
.
chains
()):
chainName
=
chr
(
ord
(
'A'
)
+
chainIndex
)
residues
=
list
(
chain
.
residues
())
for
(
resIndex
,
res
)
in
enumerate
(
residues
):
if
len
(
res
.
name
)
>
3
:
resName
=
res
.
name
[:
3
]
else
:
resName
=
res
.
name
for
atom
in
res
.
atoms
():
if
len
(
atom
.
name
)
<
4
:
atomName
=
' '
+
atom
.
name
elif
len
(
atom
.
name
)
>
4
:
atomName
=
atom
.
name
[:
4
]
else
:
atomName
=
atom
.
name
coords
=
positions
[
posIndex
]
print
>>
file
,
"ATOM %5d %-4s %3s %s%4d %8.3f%8.3f%8.3f 1.00 0.00"
%
(
atomIndex
,
atomName
,
resName
,
chainName
,
resIndex
+
1
,
coords
[
0
],
coords
[
1
],
coords
[
2
])
posIndex
+=
1
atomIndex
+=
1
if
resIndex
==
len
(
residues
)
-
1
:
print
>>
file
,
"TER %5d %3s %s%4d"
%
(
atomIndex
,
resName
,
chainName
,
resIndex
+
1
)
atomIndex
+=
1
if
modelIndex
is
not
None
:
print
>>
file
,
"ENDMDL"
@
staticmethod
def
writeFooter
(
topology
,
file
=
sys
.
stdout
):
"""Write out the footer for a PDB file.
Parameters:
- topology (Topology) The Topology defining the molecular system being written
- file (file=stdout) A file to write the file to
"""
print
>>
file
,
"END"
wrappers/python/simtk/openmm/app/pdbreporter.py
0 → 100644
View file @
ccc0ba85
"""
pdbreporter.py: Outputs simulation trajectories in PDB format
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
simtk.openmm
as
mm
from
simtk.openmm.app
import
PDBFile
class
PDBReporter
(
object
):
"""PDBReporter outputs a series of frames from a Simulation to a PDB file.
To use it, create a PDBReporter, than add it to the Simulation's list of reporters.
"""
def
__init__
(
self
,
file
,
reportInterval
):
"""Create a PDBReporter.
Parameters:
- file (string) The file to write to
- reportInterval (int) The interval (in time steps) at which to write frames
"""
self
.
_reportInterval
=
reportInterval
self
.
_out
=
open
(
file
,
'w'
)
self
.
_topology
=
None
self
.
_nextModel
=
0
def
describeNextReport
(
self
,
simulation
):
"""Get information about the next report this object will generate.
Parameters:
- simulation (Simulation) The Simulation to generate a report for
Returns: A five element tuple. The first element is the number of steps until the
next report. The remaining elements specify whether that report will require
positions, velocities, forces, and energies respectively.
"""
steps
=
self
.
_reportInterval
-
simulation
.
currentStep
%
self
.
_reportInterval
return
(
steps
,
True
,
False
,
False
,
False
)
def
report
(
self
,
simulation
,
state
):
"""Generate a report.
Parameters:
- simulation (Simulation) The Simulation to generate a report for
- state (State) The current state of the simulation
"""
if
self
.
_nextModel
==
0
:
PDBFile
.
writeHeader
(
simulation
.
topology
,
self
.
_out
)
self
.
_topology
=
simulation
.
topology
self
.
_nextModel
+=
1
PDBFile
.
writeModel
(
simulation
.
topology
,
state
.
getPositions
(),
self
.
_out
,
self
.
_nextModel
)
self
.
_nextModel
+=
1
def
__del__
(
self
):
PDBFile
.
writeFooter
(
self
.
_topology
,
self
.
_out
)
self
.
_out
.
close
()
wrappers/python/simtk/openmm/app/simulation.py
0 → 100644
View file @
ccc0ba85
"""
simulation.py: Provides a simplified API for running simulations.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
simtk.openmm
as
mm
import
simtk.unit
as
unit
class
Simulation
(
object
):
"""Simulation provides a simplified API for running simulations with OpenMM and reporting results.
A Simulation ties together various objects used for running a simulation: a Topology, System,
Integrator, and Context. To use it, you provide the Topology, System, and Integrator, and it
creates the Context automatically.
Simulation also maintains a list of "reporter" objects that record or analyze data as the simulation
runs, such as writing coordinates to files or displaying structures on the screen. For example,
the following line will cause a file called "output.pdb" to be created, and a structure written to
it every 1000 time steps:
simulation.reporters.append(PDBReporter('output.pdb', 1000))
"""
def
__init__
(
self
,
topology
,
system
,
integrator
,
platform
=
None
,
platformProperties
=
None
):
"""Create a Simulation.
Parameters:
- topology (Topology) A Topology describing the the system to simulation
- system (System) The OpenMM System object to simulate
- integrator (Integrator) The OpenMM Integrator to use for simulating the System
- platform (Platform=None) If not None, the OpenMM Platform to use
- platformProperties (map=None) If not None, a set of platform-specific properties to pass
to the Context's constructor
"""
self
.
topology
=
topology
self
.
system
=
system
self
.
integrator
=
integrator
self
.
currentStep
=
0
self
.
reporters
=
[]
if
platform
is
None
:
self
.
context
=
mm
.
Context
(
system
,
integrator
)
elif
platformProperties
is
None
:
self
.
context
=
mm
.
Context
(
system
,
integrator
,
platform
)
else
:
self
.
context
=
mm
.
Context
(
system
,
integrator
,
platform
,
platformProperties
)
def
minimizeEnergy
(
self
,
tolerance
=
1
*
unit
.
kilojoule
/
unit
.
mole
,
maxIterations
=
0
):
"""Perform a local energy minimization on the system.
Parameters:
- tolerance (energy=1*kilojoule/mole) The energy tolerance to which the system should be minimized
- maxIterations (int=0) The maximum number of iterations to perform. If this is 0, minimization is continued
until the results converge without regard to how many iterations it takes.
"""
mm
.
LocalEnergyMinimizer
.
minimize
(
self
.
context
,
tolerance
,
maxIterations
)
def
step
(
self
,
steps
):
"""Advance the simulation by integrating a specified number of time steps."""
stepTo
=
self
.
currentStep
+
steps
nextReport
=
[
None
]
*
len
(
self
.
reporters
)
while
self
.
currentStep
<
stepTo
:
nextSteps
=
stepTo
-
self
.
currentStep
anyReport
=
False
for
i
,
reporter
in
enumerate
(
self
.
reporters
):
nextReport
[
i
]
=
reporter
.
describeNextReport
(
self
)
if
nextReport
[
i
][
0
]
>
0
and
nextReport
[
i
][
0
]
<=
nextSteps
:
nextSteps
=
nextReport
[
i
][
0
]
anyReport
=
True
self
.
integrator
.
step
(
nextSteps
)
self
.
currentStep
+=
nextSteps
if
anyReport
:
getPositions
=
False
getVelocities
=
False
getForces
=
False
getEnergy
=
False
for
reporter
,
next
in
zip
(
self
.
reporters
,
nextReport
):
if
next
[
0
]
==
nextSteps
:
if
next
[
1
]:
getPositions
=
True
if
next
[
2
]:
getVelocities
=
True
if
next
[
3
]:
getForces
=
True
if
next
[
4
]:
getEnergy
=
True
state
=
self
.
context
.
getState
(
getPositions
=
getPositions
,
getVelocities
=
getVelocities
,
getForces
=
getForces
,
getEnergy
=
getEnergy
,
getParameters
=
True
)
for
reporter
,
next
in
zip
(
self
.
reporters
,
nextReport
):
if
next
[
0
]
==
nextSteps
:
reporter
.
report
(
self
,
state
)
wrappers/python/simtk/openmm/app/topology.py
0 → 100644
View file @
ccc0ba85
"""
topology.py: Used for storing topological information about a system.
"""
__author__
=
"Peter Eastman"
__version__
=
"1.0"
import
os
import
xml.etree.ElementTree
as
etree
from
simtk.unit
import
nanometers
,
sqrt
class
Topology
(
object
):
"""Topology stores the topological information about a system.
The structure of a Topology object is similar to that of a PDB file. It consists of a set of Chains
(often but not always corresponding to polymer chains). Each Chain contains a set of Residues,
and each Residue contains a set of Atoms. In addition, the Topology stores a list of which atom
pairs are bonded to each other, and the dimensions of the crystallographic unit cell.
Atom and residue names should follow the PDB 3.0 nomenclature for all molecules for which one exists.
"""
_standardBonds
=
{}
def
__init__
(
self
):
"""Create a new Topology object"""
self
.
_chains
=
[]
self
.
_numResidues
=
0
self
.
_numAtoms
=
0
self
.
_bonds
=
[]
self
.
_unitCellDimensions
=
None
def
addChain
(
self
):
"""Create a new Chain and add it to the Topology.
Returns: the newly created Chain
"""
chain
=
Chain
(
len
(
self
.
_chains
),
self
)
self
.
_chains
.
append
(
chain
)
return
chain
def
addResidue
(
self
,
name
,
chain
):
"""Create a new Residue and add it to the Topology.
Parameters:
- name (string) The name of the residue to add
- chain (Chain) The Chain to add it to
Returns: the newly created Residue
"""
residue
=
Residue
(
name
,
self
.
_numResidues
,
chain
)
self
.
_numResidues
+=
1
chain
.
_residues
.
append
(
residue
)
return
residue
def
addAtom
(
self
,
name
,
element
,
residue
):
"""Create a new Atom and add it to the Topology.
Parameters:
- name (string) The name of the atom to add
- element (Element) The element of the atom to add
- residue (Residue) The Residue to add it to
Returns: the newly created Atom
"""
atom
=
Atom
(
name
,
element
,
self
.
_numAtoms
,
residue
)
self
.
_numAtoms
+=
1
residue
.
_atoms
.
append
(
atom
)
return
atom
def
addBond
(
self
,
atom1
,
atom2
):
"""Create a new bond and add it to the Topology.
Parameters:
- atom1 (Atom) The first Atom connected by the bond
- atom2 (Atom) The second Atom connected by the bond
"""
self
.
_bonds
.
append
((
atom1
,
atom2
))
def
chains
(
self
):
"""Iterate over all Chains in the Topology."""
return
iter
(
self
.
_chains
)
def
residues
(
self
):
"""Iterate over all Residues in the Topology."""
for
chain
in
self
.
_chains
:
for
residue
in
chain
.
_residues
:
yield
residue
def
atoms
(
self
):
"""Iterate over all Atoms in the Topology."""
for
chain
in
self
.
_chains
:
for
residue
in
chain
.
_residues
:
for
atom
in
residue
.
_atoms
:
yield
atom
def
bonds
(
self
):
"""Iterate over all bonds (each represented as a tuple of two Atoms) in the Topology."""
return
iter
(
self
.
_bonds
)
def
getUnitCellDimensions
(
self
):
"""Get the dimensions of the crystallographic unit cell.
The return value may be None if this Topology does not represent a periodic structure.
"""
return
self
.
_unitCellDimensions
def
setUnitCellDimensions
(
self
,
dimensions
):
"""Set the dimensions of the crystallographic unit cell."""
self
.
_unitCellDimensions
=
dimensions
def
createStandardBonds
(
self
):
"""Create bonds based on the atom and residue names for all standard residue types."""
if
len
(
Topology
.
_standardBonds
)
==
0
:
# Load the standard bond defitions.
tree
=
etree
.
parse
(
os
.
path
.
join
(
os
.
path
.
dirname
(
__file__
),
'data'
,
'residues.xml'
))
for
residue
in
tree
.
getroot
().
findall
(
'Residue'
):
bonds
=
[]
Topology
.
_standardBonds
[
residue
.
attrib
[
'name'
]]
=
bonds
for
bond
in
residue
.
findall
(
'Bond'
):
bonds
.
append
((
bond
.
attrib
[
'from'
],
bond
.
attrib
[
'to'
]))
for
chain
in
self
.
_chains
:
# First build a map of atom names to atoms.
atomMaps
=
[]
for
residue
in
chain
.
_residues
:
atomMap
=
{}
atomMaps
.
append
(
atomMap
)
for
atom
in
residue
.
_atoms
:
atomMap
[
atom
.
name
]
=
atom
# Loop over residues and construct bonds.
for
i
in
range
(
len
(
chain
.
_residues
)):
name
=
chain
.
_residues
[
i
].
name
if
name
in
Topology
.
_standardBonds
:
for
bond
in
Topology
.
_standardBonds
[
name
]:
if
bond
[
0
].
startswith
(
'-'
)
and
i
>
0
:
fromResidue
=
i
-
1
fromAtom
=
bond
[
0
][
1
:]
elif
bond
[
0
].
startswith
(
'+'
)
and
i
<
len
(
chain
.
_residues
):
fromResidue
=
i
+
1
fromAtom
=
bond
[
0
][
1
:]
else
:
fromResidue
=
i
fromAtom
=
bond
[
0
]
if
bond
[
1
].
startswith
(
'-'
)
and
i
>
0
:
toResidue
=
i
-
1
toAtom
=
bond
[
1
][
1
:]
elif
bond
[
1
].
startswith
(
'+'
)
and
i
<
len
(
chain
.
_residues
):
toResidue
=
i
+
1
toAtom
=
bond
[
1
][
1
:]
else
:
toResidue
=
i
toAtom
=
bond
[
1
]
if
fromAtom
in
atomMaps
[
fromResidue
]
and
toAtom
in
atomMaps
[
toResidue
]:
self
.
addBond
(
atomMaps
[
fromResidue
][
fromAtom
],
atomMaps
[
toResidue
][
toAtom
])
def
createDisulfideBonds
(
self
,
positions
):
"""Identify disulfide bonds based on proximity and add them to the Topology.
Parameters:
- positions (list) The list of atomic positions based on which to identify bonded atoms
"""
def
isCyx
(
res
):
names
=
[
atom
.
name
for
atom
in
res
.
_atoms
]
return
'SG'
in
names
and
'HG'
not
in
names
cyx
=
[
res
for
res
in
self
.
residues
()
if
res
.
name
==
'CYS'
and
isCyx
(
res
)]
atomNames
=
[[
atom
.
name
for
atom
in
res
.
_atoms
]
for
res
in
cyx
]
for
i
in
range
(
len
(
cyx
)):
sg1
=
cyx
[
i
].
_atoms
[
atomNames
[
i
].
index
(
'SG'
)]
pos1
=
positions
[
sg1
.
index
]
for
j
in
range
(
i
):
sg2
=
cyx
[
j
].
_atoms
[
atomNames
[
j
].
index
(
'SG'
)]
pos2
=
positions
[
sg2
.
index
]
delta
=
[
x
-
y
for
(
x
,
y
)
in
zip
(
pos1
,
pos2
)]
distance
=
sqrt
(
delta
[
0
]
*
delta
[
0
]
+
delta
[
1
]
*
delta
[
1
]
+
delta
[
2
]
*
delta
[
2
])
if
distance
<
0.3
*
nanometers
:
self
.
addBond
(
sg1
,
sg2
)
class
Chain
(
object
):
def
__init__
(
self
,
index
,
topology
):
"""Construct a new Chain. You should call addChain() on the Topology instead of calling this directly."""
self
.
index
=
index
self
.
topology
=
topology
self
.
_residues
=
[]
def
residues
(
self
):
"""Iterate over all Residues in the Chain."""
return
iter
(
self
.
_residues
)
def
atoms
(
self
):
"""Iterate over all Atoms in the Chain."""
for
residue
in
self
.
_residues
:
for
atom
in
residue
.
_atoms
:
yield
atom
class
Residue
(
object
):
def
__init__
(
self
,
name
,
index
,
chain
):
"""Construct a new Residue. You should call addResidue() on the Topology instead of calling this directly."""
self
.
name
=
name
self
.
index
=
index
self
.
chain
=
chain
self
.
_atoms
=
[]
def
atoms
(
self
):
"""Iterate over all Atoms in the Residue."""
return
iter
(
self
.
_atoms
)
class
Atom
(
object
):
def
__init__
(
self
,
name
,
element
,
index
,
residue
):
"""Construct a new Atom. You should call addAtom() on the Topology instead of calling this directly."""
self
.
name
=
name
self
.
element
=
element
self
.
index
=
index
self
.
residue
=
residue
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment