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
41d061dd
Unverified
Commit
41d061dd
authored
Oct 28, 2019
by
peastman
Committed by
GitHub
Oct 28, 2019
Browse files
Merge pull request #2440 from JoaoRodrigues/bugfix_ion_concentration
Fixes _addIons behavior for membrane systems.
parents
ad7a55b8
1695878a
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
74 additions
and
45 deletions
+74
-45
wrappers/python/simtk/openmm/app/modeller.py
wrappers/python/simtk/openmm/app/modeller.py
+51
-40
wrappers/python/tests/TestModeller.py
wrappers/python/tests/TestModeller.py
+23
-5
No files found.
wrappers/python/simtk/openmm/app/modeller.py
View file @
41d061dd
...
@@ -258,13 +258,16 @@ class Modeller(object):
...
@@ -258,13 +258,16 @@ class Modeller(object):
self
.
topology
=
newTopology
self
.
topology
=
newTopology
self
.
positions
=
newPositions
self
.
positions
=
newPositions
def
_addIons
(
self
,
forcefield
,
replaceableMols
,
ionCutoff
=
0.05
*
nanometer
,
positiveIon
=
'Na+'
,
negativeIon
=
'Cl-'
,
ionicStrength
=
0
*
molar
,
neutralize
=
True
):
def
_addIons
(
self
,
forcefield
,
numWaters
,
replaceableMols
,
ionCutoff
=
0.05
*
nanometer
,
positiveIon
=
'Na+'
,
negativeIon
=
'Cl-'
,
ionicStrength
=
0
*
molar
,
neutralize
=
True
):
"""Adds ions to the system by replacing certain molecules.
"""Adds ions to the system by replacing certain molecules.
Parameters
Parameters
----------
----------
forcefield : ForceField
forcefield : ForceField
the ForceField to use to determine the total charge of the system.
the ForceField to use to determine the total charge of the system.
numWaters : int
the total number of water molecules in the simulation box, used to
calculate the number of ions / concentration to add.
replaceableMols : dict
replaceableMols : dict
the molecules to replace by ions, as a dictionary of residue:positions
the molecules to replace by ions, as a dictionary of residue:positions
ionCutoff: distance=0.5*nanometer
ionCutoff: distance=0.5*nanometer
...
@@ -326,51 +329,52 @@ class Modeller(object):
...
@@ -326,51 +329,52 @@ class Modeller(object):
numPositive
-=
totalCharge
numPositive
-=
totalCharge
if
ionicStrength
>
0
*
molar
:
if
ionicStrength
>
0
*
molar
:
numIons
=
(
num
ReplaceableMol
s
-
numPositive
-
numNegative
)
*
ionicStrength
/
(
55.4
*
molar
)
# Pure water is about 55.4 molar (depending on temperature)
numIons
=
(
num
Water
s
-
numPositive
-
numNegative
)
*
ionicStrength
/
(
55.4
*
molar
)
# Pure water is about 55.4 molar (depending on temperature)
numPairs
=
int
(
floor
(
numIons
+
0.5
))
numPairs
=
int
(
floor
(
numIons
+
0.5
))
numPositive
+=
numPairs
numPositive
+=
numPairs
numNegative
+=
numPairs
numNegative
+=
numPairs
totalIons
=
numPositive
+
numNegative
totalIons
=
numPositive
+
numNegative
# Randomly select a set of waters
if
totalIons
>
0
:
# while ensuring ions are not placed too close to each other.
# Randomly select a set of waters
modeller
=
Modeller
(
self
.
topology
,
self
.
positions
)
# while ensuring ions are not placed too close to each other.
modeller
=
Modeller
(
self
.
topology
,
self
.
positions
)
replaceableList
=
list
(
replaceableMols
.
keys
())
numAddedIons
=
0
replaceableList
=
list
(
replaceableMols
.
keys
())
numTrials
=
10
# Attempts to add ions N times before quitting
numAddedIons
=
0
toReplace
=
[]
# list of molecules to be replaced
numTrials
=
10
# Attempts to add ions N times before quitting
while
numAddedIons
<
totalIons
:
toReplace
=
[]
# list of molecules to be replaced
pickedMol
=
random
.
choice
(
replaceableList
)
while
numAddedIons
<
totalIons
:
replaceableList
.
remove
(
pickedMol
)
pickedMol
=
random
.
choice
(
replaceableList
)
# Check distance to other ions
replaceableList
.
remove
(
pickedMol
)
for
pos
in
ionPositions
:
# Check distance to other ions
distance
=
norm
(
pos
-
replaceableMols
[
pickedMol
])
for
pos
in
ionPositions
:
if
distance
<=
ionCutoff
:
distance
=
norm
(
pos
-
replaceableMols
[
pickedMol
])
numTrials
-=
1
if
distance
<=
ionCutoff
:
break
numTrials
-=
1
else
:
break
toReplace
.
append
(
pickedMol
)
else
:
ionPositions
.
append
(
replaceableMols
[
pickedMol
])
toReplace
.
append
(
pickedMol
)
numAddedIons
+=
1
ionPositions
.
append
(
replaceableMols
[
pickedMol
])
numAddedIons
+=
1
n_trials
=
10
n_trials
=
10
if
n_trials
==
0
:
if
n_trials
==
0
:
raise
ValueError
(
'Could not add more than {} ions to the system'
.
format
(
numAddedIons
))
raise
ValueError
(
'Could not add more than {} ions to the system'
.
format
(
numAddedIons
))
# Replace waters/ions in the topology
# Replace waters/ions in the topology
modeller
.
delete
(
toReplace
)
modeller
.
delete
(
toReplace
)
ionChain
=
modeller
.
topology
.
addChain
()
ionChain
=
modeller
.
topology
.
addChain
()
for
i
,
water
in
enumerate
(
toReplace
):
for
i
,
water
in
enumerate
(
toReplace
):
element
=
(
positiveElement
if
i
<
numPositive
else
negativeElement
)
element
=
(
positiveElement
if
i
<
numPositive
else
negativeElement
)
newResidue
=
modeller
.
topology
.
addResidue
(
element
.
symbol
.
upper
(),
ionChain
)
newResidue
=
modeller
.
topology
.
addResidue
(
element
.
symbol
.
upper
(),
ionChain
)
modeller
.
topology
.
addAtom
(
element
.
symbol
,
element
,
newResidue
)
modeller
.
topology
.
addAtom
(
element
.
symbol
,
element
,
newResidue
)
modeller
.
positions
.
append
(
replaceableMols
[
water
])
modeller
.
positions
.
append
(
replaceableMols
[
water
])
# Update topology/positions
# Update topology/positions
self
.
topology
=
modeller
.
topology
self
.
topology
=
modeller
.
topology
self
.
positions
=
modeller
.
positions
self
.
positions
=
modeller
.
positions
def
addSolvent
(
self
,
forcefield
,
model
=
'tip3p'
,
boxSize
=
None
,
boxVectors
=
None
,
padding
=
None
,
numAdded
=
None
,
positiveIon
=
'Na+'
,
negativeIon
=
'Cl-'
,
ionicStrength
=
0
*
molar
,
neutralize
=
True
):
def
addSolvent
(
self
,
forcefield
,
model
=
'tip3p'
,
boxSize
=
None
,
boxVectors
=
None
,
padding
=
None
,
numAdded
=
None
,
positiveIon
=
'Na+'
,
negativeIon
=
'Cl-'
,
ionicStrength
=
0
*
molar
,
neutralize
=
True
):
"""Add solvent (both water and ions) to the model to fill a rectangular box.
"""Add solvent (both water and ions) to the model to fill a rectangular box.
...
@@ -621,8 +625,11 @@ class Modeller(object):
...
@@ -621,8 +625,11 @@ class Modeller(object):
if
atom
.
element
==
_oxygen
:
if
atom
.
element
==
_oxygen
:
waterPos
[
residue
]
=
newPositions
[
atom
.
index
]
waterPos
[
residue
]
=
newPositions
[
atom
.
index
]
# Total number of waters in the box
numTotalWaters
=
len
(
waterPos
)
# Add ions to neutralize the system.
# Add ions to neutralize the system.
self
.
_addIons
(
forcefield
,
waterPos
,
positiveIon
=
positiveIon
,
negativeIon
=
negativeIon
,
ionicStrength
=
ionicStrength
,
neutralize
=
neutralize
)
self
.
_addIons
(
forcefield
,
numTotalWaters
,
waterPos
,
positiveIon
=
positiveIon
,
negativeIon
=
negativeIon
,
ionicStrength
=
ionicStrength
,
neutralize
=
neutralize
)
class
_ResidueData
:
class
_ResidueData
:
"""Inner class used to encapsulate data about the hydrogens for a residue."""
"""Inner class used to encapsulate data about the hydrogens for a residue."""
...
@@ -1352,7 +1359,6 @@ class Modeller(object):
...
@@ -1352,7 +1359,6 @@ class Modeller(object):
# number get removed from each leaf.
# number get removed from each leaf.
overlapCutoff
=
0.22
overlapCutoff
=
0.22
chain
=
membraneTopology
.
addChain
()
addedWater
=
[]
addedWater
=
[]
addedLipids
=
[]
addedLipids
=
[]
removedFromLeaf
=
[
0
,
0
]
removedFromLeaf
=
[
0
,
0
]
...
@@ -1542,6 +1548,11 @@ class Modeller(object):
...
@@ -1542,6 +1548,11 @@ class Modeller(object):
if
atom
.
element
==
elem
.
oxygen
:
if
atom
.
element
==
elem
.
oxygen
:
waterPos
[
residue
]
=
modeller
.
positions
[
atom
.
index
]
waterPos
[
residue
]
=
modeller
.
positions
[
atom
.
index
]
# Total number of water molecules
# Use this number to avoid underestimating the concentration of ions
# in _addIons after we exclude waters close to lipids.
numTotalWaters
=
len
(
waterPos
)
# Calculate lipid Z boundaries
# Calculate lipid Z boundaries
lipidNames
=
{
res
.
name
for
res
in
patch
.
topology
.
residues
()
if
res
.
name
!=
'HOH'
}
lipidNames
=
{
res
.
name
for
res
in
patch
.
topology
.
residues
()
if
res
.
name
!=
'HOH'
}
lipidZMax
=
sys
.
float_info
.
min
lipidZMax
=
sys
.
float_info
.
min
...
@@ -1563,7 +1574,7 @@ class Modeller(object):
...
@@ -1563,7 +1574,7 @@ class Modeller(object):
if
lowerZBoundary
<
waterZ
.
value_in_unit
(
nanometer
)
<
upperZBoundary
:
if
lowerZBoundary
<
waterZ
.
value_in_unit
(
nanometer
)
<
upperZBoundary
:
del
waterPos
[
wRes
]
del
waterPos
[
wRes
]
self
.
_addIons
(
forcefield
,
waterPos
,
positiveIon
=
positiveIon
,
negativeIon
=
negativeIon
,
ionicStrength
=
ionicStrength
,
neutralize
=
neutralize
)
self
.
_addIons
(
forcefield
,
numTotalWaters
,
waterPos
,
positiveIon
=
positiveIon
,
negativeIon
=
negativeIon
,
ionicStrength
=
ionicStrength
,
neutralize
=
neutralize
)
class
_CellList
(
object
):
class
_CellList
(
object
):
...
...
wrappers/python/tests/TestModeller.py
View file @
41d061dd
from
collections
import
defaultdict
import
unittest
import
unittest
import
math
import
sys
from
validateModeller
import
*
from
validateModeller
import
*
from
simtk.openmm.app
import
*
from
simtk.openmm.app
import
*
from
simtk.openmm
import
*
from
simtk.openmm
import
*
from
simtk.unit
import
*
from
simtk.unit
import
*
from
collections
import
defaultdict
if
sys
.
version_info
>=
(
3
,
0
):
if
sys
.
version_info
>=
(
3
,
0
):
from
io
import
StringIO
from
io
import
StringIO
else
:
else
:
...
@@ -1136,12 +1140,26 @@ class TestModeller(unittest.TestCase):
...
@@ -1136,12 +1140,26 @@ class TestModeller(unittest.TestCase):
resCount
=
defaultdict
(
int
)
resCount
=
defaultdict
(
int
)
for
res
in
modeller
.
topology
.
residues
():
for
res
in
modeller
.
topology
.
residues
():
resCount
[
res
.
name
]
+=
1
resCount
[
res
.
name
]
+=
1
self
.
assertTrue
(
resCount
[
'HOH'
]
>
1
)
self
.
assertTrue
(
resCount
[
'CL'
]
>
1
)
self
.
assertTrue
(
resCount
[
'NA'
]
>
1
)
self
.
assertTrue
(
resCount
[
'CL'
]
>
resCount
[
'NA'
])
# overall negative
self
.
assertEqual
(
16
,
resCount
[
'ALA'
])
self
.
assertEqual
(
16
,
resCount
[
'ALA'
])
self
.
assertEqual
(
226
,
resCount
[
'POP'
])
# 2x128 - overlapping
self
.
assertEqual
(
226
,
resCount
[
'POP'
])
# 2x128 - overlapping
self
.
assertTrue
(
resCount
[
'HOH'
]
>
1
)
deltaQ
=
resCount
[
'CL'
]
-
resCount
[
'NA'
]
self
.
assertEqual
(
deltaQ
,
10
)
# protein net q: +10
# Check _addIons did the right thing.
expected_ion_fraction
=
1.0
*
molar
/
(
55.4
*
molar
)
total_water
=
resCount
[
'HOH'
]
total_water_ions
=
resCount
[
'HOH'
]
+
resCount
[
'CL'
]
+
resCount
[
'NA'
]
# total_water_ions - protein charge
expected_sodium
=
math
.
floor
((
total_water_ions
-
10
)
*
expected_ion_fraction
+
0.5
)
expected_chlorine
=
expected_sodium
+
10
self
.
assertEqual
(
resCount
[
'CL'
],
expected_chlorine
)
self
.
assertEqual
(
resCount
[
'NA'
],
expected_sodium
)
# Check lipid numbering for repetitions
# Check lipid numbering for repetitions
lipidIdList
=
[(
r
.
chain
.
id
,
r
.
id
)
for
r
in
modeller
.
topology
.
residues
()
lipidIdList
=
[(
r
.
chain
.
id
,
r
.
id
)
for
r
in
modeller
.
topology
.
residues
()
...
...
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