openmmml 1.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openmmml-1.6/LICENSE +23 -0
- openmmml-1.6/PKG-INFO +34 -0
- openmmml-1.6/README.md +83 -0
- openmmml-1.6/openmmml/__init__.py +2 -0
- openmmml-1.6/openmmml/mlpotential.py +479 -0
- openmmml-1.6/openmmml/models/__init__.py +1 -0
- openmmml-1.6/openmmml/models/aimnet2potential.py +118 -0
- openmmml-1.6/openmmml/models/anipotential.py +144 -0
- openmmml-1.6/openmmml/models/asepotential.py +134 -0
- openmmml-1.6/openmmml/models/deepmdpotential.py +165 -0
- openmmml-1.6/openmmml/models/fennixpotential.py +199 -0
- openmmml-1.6/openmmml/models/macepotential.py +267 -0
- openmmml-1.6/openmmml/models/nequippotential.py +250 -0
- openmmml-1.6/openmmml/models/orbpotential.py +135 -0
- openmmml-1.6/openmmml/models/torchmdnetpotential.py +252 -0
- openmmml-1.6/openmmml.egg-info/PKG-INFO +34 -0
- openmmml-1.6/openmmml.egg-info/SOURCES.txt +22 -0
- openmmml-1.6/openmmml.egg-info/dependency_links.txt +1 -0
- openmmml-1.6/openmmml.egg-info/entry_points.txt +27 -0
- openmmml-1.6/openmmml.egg-info/not-zip-safe +1 -0
- openmmml-1.6/openmmml.egg-info/requires.txt +2 -0
- openmmml-1.6/openmmml.egg-info/top_level.txt +1 -0
- openmmml-1.6/setup.cfg +4 -0
- openmmml-1.6/setup.py +72 -0
openmmml-1.6/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2021 Stanford University and the Authors.
|
|
4
|
+
Authors: Peter Eastman
|
|
5
|
+
Contributors:
|
|
6
|
+
|
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
in the Software without restriction, including without limitation the rights
|
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
furnished to do so, subject to the following conditions:
|
|
13
|
+
|
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
SOFTWARE.
|
openmmml-1.6/PKG-INFO
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openmmml
|
|
3
|
+
Version: 1.6
|
|
4
|
+
Summary: OpenMM-ML: A high level API for using machine learning models in OpenMM simulations
|
|
5
|
+
Home-page: https://github.com/openmm/openmm-ml
|
|
6
|
+
Author: Peter Eastman
|
|
7
|
+
License: MIT
|
|
8
|
+
Platform: Linux
|
|
9
|
+
Platform: Mac OS-X
|
|
10
|
+
Platform: Unix
|
|
11
|
+
Platform: Windows
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Chemistry
|
|
20
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
21
|
+
Classifier: Operating System :: POSIX
|
|
22
|
+
Classifier: Operating System :: Unix
|
|
23
|
+
Classifier: Operating System :: MacOS
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: numpy
|
|
26
|
+
Requires-Dist: openmm>=8.5
|
|
27
|
+
Dynamic: author
|
|
28
|
+
Dynamic: classifier
|
|
29
|
+
Dynamic: home-page
|
|
30
|
+
Dynamic: license
|
|
31
|
+
Dynamic: license-file
|
|
32
|
+
Dynamic: platform
|
|
33
|
+
Dynamic: requires-dist
|
|
34
|
+
Dynamic: summary
|
openmmml-1.6/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# OpenMM-ML
|
|
2
|
+
|
|
3
|
+
This is a high level API for using machine learning models in OpenMM simulations. With just a few lines of code, you
|
|
4
|
+
can set up a simulation that uses a standard, pretrained model to represent some or all of the interactions in a system.
|
|
5
|
+
|
|
6
|
+
### Supported models and frameworks
|
|
7
|
+
|
|
8
|
+
The current release of OpenMM-ML supports the following potential functions.
|
|
9
|
+
|
|
10
|
+
- [MACE](https://arxiv.org/abs/2206.07697) models, including the pre-trained [MACE-OFF23](https://arxiv.org/abs/2312.15211),
|
|
11
|
+
[MACE-MPA-0](https://github.com/ACEsuit/mace-foundations), [MACE-OMAT-0](https://github.com/ACEsuit/mace-foundations),
|
|
12
|
+
and MACE-OMOL-0 models, utilizing the [MACE implementation](https://github.com/ACEsuit/mace).
|
|
13
|
+
|
|
14
|
+
- The pretrained [AIMNet2](https://doi.org/10.1039/D4SC08572H) model.
|
|
15
|
+
|
|
16
|
+
- Models generated with [TorchMD-Net](https://github.com/torchmd/torchmd-net), including the pretrained [AceFF](https://arxiv.org/abs/2601.00581) models.
|
|
17
|
+
|
|
18
|
+
- Models generated with [FeNNol](https://github.com/FeNNol-tools/FeNNol), including the pretrained [FeNNix-Bio1](https://chemrxiv.org/doi/full/10.26434/chemrxiv-2025-f1hgn-v4) models.
|
|
19
|
+
|
|
20
|
+
- Pretrained [Orb](https://github.com/orbital-materials/orb-models) models.
|
|
21
|
+
|
|
22
|
+
- Models generated by [NequIP](https://github.com/mir-group/nequip). Models generated by [Allegro](https://github.com/mir-group/allegro) are also supported by this interface.
|
|
23
|
+
|
|
24
|
+
- The pretrained [ANI-1ccx](https://www.nature.com/articles/s41467-019-10827-4) and [ANI-2x](https://pubs.acs.org/doi/full/10.1021/acs.jctc.0c00121)
|
|
25
|
+
models, using the implementations in [TorchANI](https://github.com/aiqm/torchani).
|
|
26
|
+
|
|
27
|
+
- Models generated by [DeePMD-kit](https://github.com/deepmodeling/deepmd-kit).
|
|
28
|
+
|
|
29
|
+
In addition to its built in support for the models listed above, OpenMM-ML can use any model implemented with an
|
|
30
|
+
[ASE Calculator](https://ase-lib.org/ase/calculators/calculators.html). A very wide range of machine learning and quantum
|
|
31
|
+
chemistry packages provide interfaces in this format, allowing them to be used with OpenMM-ML.
|
|
32
|
+
|
|
33
|
+
See the documentation for details on using each of these model types.
|
|
34
|
+
|
|
35
|
+
### Documentation
|
|
36
|
+
|
|
37
|
+
You can find the current documentation at https://openmm.github.io/openmm-ml.
|
|
38
|
+
|
|
39
|
+
### Installation
|
|
40
|
+
|
|
41
|
+
The stable version of OpenMM-ML can be installed with pip.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
pip install openmmml
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
This installs only OpenMM-ML itself, not the packages that provide specific models. Those must be installed separately
|
|
48
|
+
by following the instructions from the package developers.
|
|
49
|
+
|
|
50
|
+
OpenMM-ML can also be installed with conda or mamba.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
mamba install -c conda-forge openmm-ml
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
However, we generally recommend using pip because most of the packages that provide models are only available
|
|
57
|
+
with pip, not conda.
|
|
58
|
+
|
|
59
|
+
### Usage
|
|
60
|
+
|
|
61
|
+
To use this package, create a MLPotential object, specifying the name of the potential function to use. You can then
|
|
62
|
+
call createSystem() to create a System object for a simulation. For example,
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from openmmml import MLPotential
|
|
66
|
+
potential = MLPotential('ani2x')
|
|
67
|
+
system = potential.createSystem(topology)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Alternatively, you can use createMixedSystem() to create a System where part is modeled with this potential and the rest
|
|
71
|
+
is modeled with a conventional force field. As an example, suppose the Topology contains three chains. Chain 0 is a
|
|
72
|
+
protein, chain 1 is a ligand, and chain 2 is solvent. The following code creates a System in which the internal energy
|
|
73
|
+
of the ligand is computed with ANI2x, while everything else (including interactions between the ligand and the rest of
|
|
74
|
+
the System) is computed with Amber14.
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml')
|
|
78
|
+
mm_system = forcefield.createSystem(topology)
|
|
79
|
+
chains = list(topology.chains())
|
|
80
|
+
ml_atoms = [atom.index for atom in chains[1].atoms()]
|
|
81
|
+
potential = MLPotential('ani2x')
|
|
82
|
+
ml_system = potential.createMixedSystem(topology, mm_system, ml_atoms)
|
|
83
|
+
```
|
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mlpotential.py: Provides a common API for creating OpenMM Systems with ML potentials.
|
|
3
|
+
|
|
4
|
+
This is part of the OpenMM molecular simulation toolkit originating from
|
|
5
|
+
Simbios, the NIH National Center for Physics-Based Simulation of
|
|
6
|
+
Biological Structures at Stanford, funded under the NIH Roadmap for
|
|
7
|
+
Medical Research, grant U54 GM072970. See https://simtk.org.
|
|
8
|
+
|
|
9
|
+
Portions copyright (c) 2021-2026 Stanford University and the Authors.
|
|
10
|
+
Authors: Peter Eastman
|
|
11
|
+
Contributors:
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
|
14
|
+
copy of this software and associated documentation files (the "Software"),
|
|
15
|
+
to deal in the Software without restriction, including without limitation
|
|
16
|
+
the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
|
17
|
+
and/or sell copies of the Software, and to permit persons to whom the
|
|
18
|
+
Software is furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in
|
|
21
|
+
all copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
26
|
+
THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
|
27
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
28
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
|
29
|
+
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
import openmm
|
|
33
|
+
import openmm.app
|
|
34
|
+
import openmm.unit as unit
|
|
35
|
+
from copy import deepcopy
|
|
36
|
+
from typing import Dict, Iterable, Optional
|
|
37
|
+
import os
|
|
38
|
+
import shutil
|
|
39
|
+
import tempfile
|
|
40
|
+
import urllib.request
|
|
41
|
+
import sys
|
|
42
|
+
if sys.version_info < (3, 10):
|
|
43
|
+
from importlib_metadata import entry_points
|
|
44
|
+
else:
|
|
45
|
+
from importlib.metadata import entry_points
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MLPotentialImplFactory(object):
|
|
49
|
+
"""Abstract interface for classes that create MLPotentialImpl objects.
|
|
50
|
+
|
|
51
|
+
If you are defining a new potential function, you need to create subclasses
|
|
52
|
+
of MLPotentialImpl and MLPotentialImplFactory, and register an instance of
|
|
53
|
+
the factory by calling MLPotential.registerImplFactory(). Alternatively,
|
|
54
|
+
if a Python package creates an entry point in the group "openmmml.potentials",
|
|
55
|
+
the potential will be registered automatically. The entry point name is the
|
|
56
|
+
name of the potential function, and the value should be the name of the
|
|
57
|
+
MLPotentialImplFactory subclass.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def createImpl(self, name: str, **args) -> "MLPotentialImpl":
|
|
61
|
+
"""Create a MLPotentialImpl that will be used to implement a MLPotential.
|
|
62
|
+
|
|
63
|
+
When a MLPotential is created, it invokes this method to create an object
|
|
64
|
+
implementing the requested potential. Subclasses must implement this method
|
|
65
|
+
to return an instance of the correct MLPotentialImpl subclass.
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
name: str
|
|
70
|
+
the name of the potential that was specified to the MLPotential constructor
|
|
71
|
+
args:
|
|
72
|
+
any additional keyword arguments that were provided to the MLPotential
|
|
73
|
+
constructor are passed to this method. This allows subclasses to customize
|
|
74
|
+
their behavior based on extra arguments.
|
|
75
|
+
|
|
76
|
+
Returns
|
|
77
|
+
-------
|
|
78
|
+
a MLPotentialImpl that implements the potential
|
|
79
|
+
"""
|
|
80
|
+
raise NotImplementedError('Subclasses must implement createImpl()')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class MLPotentialImpl(object):
|
|
84
|
+
"""Abstract interface for classes that implement potential functions.
|
|
85
|
+
|
|
86
|
+
If you are defining a new potential function, you need to create subclasses
|
|
87
|
+
of MLPotentialImpl and MLPotentialImplFactory. When a user creates a
|
|
88
|
+
MLPotential and specifies a name for the potential to use, it looks up the
|
|
89
|
+
factory that has been registered for that name and uses it to create a
|
|
90
|
+
MLPotentialImpl of the appropriate subclass.
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def addForces(self,
|
|
94
|
+
topology: openmm.app.Topology,
|
|
95
|
+
system: openmm.System,
|
|
96
|
+
atoms: Optional[Iterable[int]],
|
|
97
|
+
forceGroup: int,
|
|
98
|
+
**args):
|
|
99
|
+
"""Add Force objects to a System to implement the potential function.
|
|
100
|
+
|
|
101
|
+
This is invoked by MLPotential.createSystem(). Subclasses must implement
|
|
102
|
+
it to create the requested potential function.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
----------
|
|
106
|
+
topology: Topology
|
|
107
|
+
the Topology from which the System is being created
|
|
108
|
+
system: System
|
|
109
|
+
the System that is being created
|
|
110
|
+
atoms: Optional[Iterable[int]]
|
|
111
|
+
the indices of atoms the potential should be applied to, or None if
|
|
112
|
+
it should be applied to the entire System
|
|
113
|
+
forceGroup: int
|
|
114
|
+
the force group that any newly added Forces should be in
|
|
115
|
+
args:
|
|
116
|
+
any additional keyword arguments that were provided to createSystem()
|
|
117
|
+
are passed to this method. This allows subclasses to customize their
|
|
118
|
+
behavior based on extra arguments.
|
|
119
|
+
"""
|
|
120
|
+
raise NotImplementedError('Subclasses must implement addForces()')
|
|
121
|
+
|
|
122
|
+
def _getTorchDevice(self, args):
|
|
123
|
+
"""This is a utility routine for use by subclasses that are implemented with PyTorch. It selects what device
|
|
124
|
+
to use, which can either by specified by the user with the 'device' argument, or chosen automatically based on
|
|
125
|
+
the available hardware."""
|
|
126
|
+
import torch
|
|
127
|
+
if 'device' in args:
|
|
128
|
+
device = args['device']
|
|
129
|
+
if isinstance(device, str):
|
|
130
|
+
device = torch.device(device)
|
|
131
|
+
return device
|
|
132
|
+
if torch.cuda.is_available():
|
|
133
|
+
return torch.device('cuda')
|
|
134
|
+
return torch.device('cpu')
|
|
135
|
+
|
|
136
|
+
def _getCacheDir(self):
|
|
137
|
+
"""This is a utility routine returning a cache directory for use by subclasses that need to download their own
|
|
138
|
+
models. Before returning, it will create the directory and any parent directories if they do not already exist."""
|
|
139
|
+
path = os.path.expanduser("~/.cache/openmm-ml")
|
|
140
|
+
os.makedirs(path, exist_ok=True)
|
|
141
|
+
return path
|
|
142
|
+
|
|
143
|
+
def _downloadOrFindFile(self, name: str, url: str):
|
|
144
|
+
"""Downloads a file at the requested URL and saves it in a cache directory under the specified name. If a file
|
|
145
|
+
with the specified name already exists in the cache, the path to it will be returned without downloading it again."""
|
|
146
|
+
cacheDir = self._getCacheDir()
|
|
147
|
+
targetPath = os.path.join(cacheDir, name)
|
|
148
|
+
if not os.path.isfile(targetPath):
|
|
149
|
+
with urllib.request.urlopen(url) as remoteFile:
|
|
150
|
+
# Download into a temporary directory first so that we do not
|
|
151
|
+
# end up with a partially downloaded file at targetPath and we
|
|
152
|
+
# do not create temporary files directly in the cache directory.
|
|
153
|
+
with tempfile.TemporaryDirectory(dir=cacheDir) as tempDir:
|
|
154
|
+
tempPath = os.path.join(tempDir, name)
|
|
155
|
+
with open(tempPath, "wb") as localFile:
|
|
156
|
+
shutil.copyfileobj(remoteFile, localFile)
|
|
157
|
+
os.replace(tempPath, targetPath)
|
|
158
|
+
return targetPath
|
|
159
|
+
|
|
160
|
+
class MLPotential(object):
|
|
161
|
+
"""A potential function that can be used in simulations.
|
|
162
|
+
|
|
163
|
+
To use this class, create a MLPotential, specifying the name of the potential
|
|
164
|
+
function to use. You can then call createSystem() to create a System object
|
|
165
|
+
for a simulation. For example,
|
|
166
|
+
|
|
167
|
+
>>> potential = MLPotential('ani2x')
|
|
168
|
+
>>> system = potential.createSystem(topology)
|
|
169
|
+
|
|
170
|
+
Alternatively, you can use createMixedSystem() to create a System where part is
|
|
171
|
+
modeled with this potential and the rest is modeled with a conventional force
|
|
172
|
+
field. As an example, suppose the Topology contains three chains. Chain 0 is
|
|
173
|
+
a protein, chain 1 is a ligand, and chain 2 is solvent. The following code
|
|
174
|
+
creates a System in which the internal energy of the ligand is computed with
|
|
175
|
+
ANI2x, while everything else (including interactions between the ligand and the
|
|
176
|
+
rest of the System) is computed with Amber14.
|
|
177
|
+
|
|
178
|
+
>>> forcefield = ForceField('amber14-all.xml', 'amber14/tip3pfb.xml')
|
|
179
|
+
>>> mm_system = forcefield.createSystem(topology)
|
|
180
|
+
>>> chains = list(topology.chains())
|
|
181
|
+
>>> ml_atoms = [atom.index for atom in chains[1].atoms()]
|
|
182
|
+
>>> potential = MLPotential('ani2x')
|
|
183
|
+
>>> ml_system = potential.createMixedSystem(topology, mm_system, ml_atoms)
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
_implFactories: Dict[str, MLPotentialImplFactory] = {}
|
|
187
|
+
|
|
188
|
+
def __init__(self, name: str, **args):
|
|
189
|
+
"""Create a MLPotential.
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
name: str
|
|
194
|
+
the name of the potential function to use. Built in support is currently
|
|
195
|
+
provided for the following: 'ani1ccx', 'ani2x'. Others may be added by
|
|
196
|
+
calling MLPotential.registerImplFactory().
|
|
197
|
+
args:
|
|
198
|
+
particular potential functions may define additional arguments that can
|
|
199
|
+
be used to customize them. See the documentation on the specific
|
|
200
|
+
potential functions for more information.
|
|
201
|
+
"""
|
|
202
|
+
self._impl = MLPotential._implFactories[name].createImpl(name, **args)
|
|
203
|
+
|
|
204
|
+
def createSystem(self, topology: openmm.app.Topology, removeCMMotion: bool = True, **args) -> openmm.System:
|
|
205
|
+
"""Create a System for running a simulation with this potential function.
|
|
206
|
+
|
|
207
|
+
Parameters
|
|
208
|
+
----------
|
|
209
|
+
topology: Topology
|
|
210
|
+
the Topology for which to create a System
|
|
211
|
+
removeCMMotion: bool
|
|
212
|
+
if true, a CMMotionRemover will be added to the System.
|
|
213
|
+
args:
|
|
214
|
+
particular potential functions may define additional arguments that can
|
|
215
|
+
be used to customize them. See the documentation on the specific
|
|
216
|
+
potential functions for more information.
|
|
217
|
+
|
|
218
|
+
Returns
|
|
219
|
+
-------
|
|
220
|
+
a newly created System object that uses this potential function to model the Topology
|
|
221
|
+
"""
|
|
222
|
+
system = openmm.System()
|
|
223
|
+
if topology.getPeriodicBoxVectors() is not None:
|
|
224
|
+
system.setDefaultPeriodicBoxVectors(*topology.getPeriodicBoxVectors())
|
|
225
|
+
for atom in topology.atoms():
|
|
226
|
+
if atom.element is None:
|
|
227
|
+
system.addParticle(0)
|
|
228
|
+
else:
|
|
229
|
+
system.addParticle(atom.element.mass)
|
|
230
|
+
self._impl.addForces(topology, system, None, 0, **args)
|
|
231
|
+
if removeCMMotion:
|
|
232
|
+
system.addForce(openmm.CMMotionRemover())
|
|
233
|
+
return system
|
|
234
|
+
|
|
235
|
+
def createMixedSystem(self,
|
|
236
|
+
topology: openmm.app.Topology,
|
|
237
|
+
system: openmm.System,
|
|
238
|
+
atoms: Iterable[int],
|
|
239
|
+
removeConstraints: bool = True,
|
|
240
|
+
forceGroup: int = 0,
|
|
241
|
+
interpolate: bool = False,
|
|
242
|
+
**args) -> openmm.System:
|
|
243
|
+
"""Create a System that is partly modeled with this potential and partly
|
|
244
|
+
with a conventional force field.
|
|
245
|
+
|
|
246
|
+
To use this method, first create a System that is entirely modeled with the
|
|
247
|
+
conventional force field. Pass it to this method, along with the indices of the
|
|
248
|
+
atoms to model with this potential (the "ML subset"). It returns a new System
|
|
249
|
+
that is identical to the original one except for the following changes.
|
|
250
|
+
|
|
251
|
+
1. Removing all bonds, angles, and torsions for which *all* atoms are in the
|
|
252
|
+
ML subset.
|
|
253
|
+
2. For every NonbondedForce and CustomNonbondedForce, adding exceptions/exclusions
|
|
254
|
+
to prevent atoms in the ML subset from interacting with each other.
|
|
255
|
+
3. (Optional) Removing constraints between atoms that are both in the ML subset.
|
|
256
|
+
4. Adding Forces as necessary to compute the internal energy of the ML subset
|
|
257
|
+
with this potential.
|
|
258
|
+
|
|
259
|
+
Alternatively, the System can include Forces to compute the energy both with the
|
|
260
|
+
conventional force field and with this potential, and to smoothly interpolate
|
|
261
|
+
between them. In that case, it creates a CustomCVForce containing the following.
|
|
262
|
+
|
|
263
|
+
1. The Forces to compute this potential.
|
|
264
|
+
2. Forces to compute the bonds, angles, and torsions that were removed above.
|
|
265
|
+
3. For every NonbondedForce, a corresponding CustomBondForce to compute the
|
|
266
|
+
nonbonded interactions within the ML subset.
|
|
267
|
+
|
|
268
|
+
The CustomCVForce defines a global parameter called "lambda_interpolate" that interpolates
|
|
269
|
+
between the two potentials. When lambda_interpolate=0, the energy is computed entirely with
|
|
270
|
+
the conventional force field. When lambda_interpolate=1, the energy is computed entirely with
|
|
271
|
+
the ML potential. You can set its value by calling setParameter() on the Context.
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
topology: Topology
|
|
276
|
+
the Topology for which to create a System
|
|
277
|
+
system: System
|
|
278
|
+
a System that models the Topology with a conventional force field
|
|
279
|
+
atoms: Iterable[int]
|
|
280
|
+
the indices of all atoms whose interactions should be computed with
|
|
281
|
+
this potential
|
|
282
|
+
removeConstraints: bool
|
|
283
|
+
if True, remove constraints between pairs of atoms whose interaction
|
|
284
|
+
will be computed with this potential
|
|
285
|
+
forceGroup: int
|
|
286
|
+
the force group the ML potential's Forces should be placed in
|
|
287
|
+
interpolate: bool
|
|
288
|
+
if True, create a System that can smoothly interpolate between the conventional
|
|
289
|
+
and ML potentials
|
|
290
|
+
args:
|
|
291
|
+
particular potential functions may define additional arguments that can
|
|
292
|
+
be used to customize them. See the documentation on the specific
|
|
293
|
+
potential functions for more information.
|
|
294
|
+
|
|
295
|
+
Returns
|
|
296
|
+
-------
|
|
297
|
+
a newly created System object that uses this potential function to model the Topology
|
|
298
|
+
"""
|
|
299
|
+
# Create the new System, removing bonded interactions within the ML subset.
|
|
300
|
+
|
|
301
|
+
newSystem = self._removeBonds(system, atoms, True, removeConstraints)
|
|
302
|
+
|
|
303
|
+
# Add nonbonded exceptions and exclusions.
|
|
304
|
+
|
|
305
|
+
atomList = list(atoms)
|
|
306
|
+
for force in newSystem.getForces():
|
|
307
|
+
if isinstance(force, openmm.NonbondedForce):
|
|
308
|
+
for i in range(len(atomList)):
|
|
309
|
+
for j in range(i):
|
|
310
|
+
force.addException(atomList[i], atomList[j], 0, 1, 0, True)
|
|
311
|
+
elif isinstance(force, openmm.CustomNonbondedForce):
|
|
312
|
+
existing = set(tuple(force.getExclusionParticles(i)) for i in range(force.getNumExclusions()))
|
|
313
|
+
for i in range(len(atomList)):
|
|
314
|
+
a1 = atomList[i]
|
|
315
|
+
for j in range(i):
|
|
316
|
+
a2 = atomList[j]
|
|
317
|
+
if (a1, a2) not in existing and (a2, a1) not in existing:
|
|
318
|
+
force.addExclusion(a1, a2)
|
|
319
|
+
|
|
320
|
+
# Add the ML potential.
|
|
321
|
+
|
|
322
|
+
if not interpolate:
|
|
323
|
+
self._impl.addForces(topology, newSystem, atomList, forceGroup, **args)
|
|
324
|
+
else:
|
|
325
|
+
# Create a CustomCVForce and put the ML forces inside it.
|
|
326
|
+
|
|
327
|
+
cv = openmm.CustomCVForce('')
|
|
328
|
+
cv.addGlobalParameter('lambda_interpolate', 1)
|
|
329
|
+
tempSystem = openmm.System()
|
|
330
|
+
self._impl.addForces(topology, tempSystem, atomList, forceGroup, **args)
|
|
331
|
+
mlVarNames = []
|
|
332
|
+
for i, force in enumerate(tempSystem.getForces()):
|
|
333
|
+
name = f'mlForce{i+1}'
|
|
334
|
+
cv.addCollectiveVariable(name, deepcopy(force))
|
|
335
|
+
mlVarNames.append(name)
|
|
336
|
+
|
|
337
|
+
# Create Forces for all the bonded interactions within the ML subset and add them to the CustomCVForce.
|
|
338
|
+
|
|
339
|
+
bondedSystem = self._removeBonds(system, atoms, False, removeConstraints)
|
|
340
|
+
bondedForces = []
|
|
341
|
+
for force in bondedSystem.getForces():
|
|
342
|
+
if hasattr(force, 'addBond') or hasattr(force, 'addAngle') or hasattr(force, 'addTorsion'):
|
|
343
|
+
bondedForces.append(force)
|
|
344
|
+
mmVarNames = []
|
|
345
|
+
for i, force in enumerate(bondedForces):
|
|
346
|
+
name = f'mmForce{i+1}'
|
|
347
|
+
cv.addCollectiveVariable(name, deepcopy(force))
|
|
348
|
+
mmVarNames.append(name)
|
|
349
|
+
|
|
350
|
+
# Create a CustomBondForce that computes all nonbonded interactions within the ML subset.
|
|
351
|
+
|
|
352
|
+
for force in system.getForces():
|
|
353
|
+
if isinstance(force, openmm.NonbondedForce):
|
|
354
|
+
internalNonbonded = openmm.CustomBondForce('138.935456*chargeProd/r + 4*epsilon*((sigma/r)^12-(sigma/r)^6)')
|
|
355
|
+
internalNonbonded.addPerBondParameter('chargeProd')
|
|
356
|
+
internalNonbonded.addPerBondParameter('sigma')
|
|
357
|
+
internalNonbonded.addPerBondParameter('epsilon')
|
|
358
|
+
numParticles = system.getNumParticles()
|
|
359
|
+
atomCharge = [0]*numParticles
|
|
360
|
+
atomSigma = [0]*numParticles
|
|
361
|
+
atomEpsilon = [0]*numParticles
|
|
362
|
+
for i in range(numParticles):
|
|
363
|
+
charge, sigma, epsilon = force.getParticleParameters(i)
|
|
364
|
+
atomCharge[i] = charge
|
|
365
|
+
atomSigma[i] = sigma
|
|
366
|
+
atomEpsilon[i] = epsilon
|
|
367
|
+
exceptions = {}
|
|
368
|
+
for i in range(force.getNumExceptions()):
|
|
369
|
+
p1, p2, chargeProd, sigma, epsilon = force.getExceptionParameters(i)
|
|
370
|
+
exceptions[(p1, p2)] = (chargeProd, sigma, epsilon)
|
|
371
|
+
for p1 in atomList:
|
|
372
|
+
for p2 in atomList:
|
|
373
|
+
if p1 == p2:
|
|
374
|
+
break
|
|
375
|
+
if (p1, p2) in exceptions:
|
|
376
|
+
chargeProd, sigma, epsilon = exceptions[(p1, p2)]
|
|
377
|
+
elif (p2, p1) in exceptions:
|
|
378
|
+
chargeProd, sigma, epsilon = exceptions[(p2, p1)]
|
|
379
|
+
else:
|
|
380
|
+
chargeProd = atomCharge[p1]*atomCharge[p2]
|
|
381
|
+
sigma = 0.5*(atomSigma[p1]+atomSigma[p2])
|
|
382
|
+
epsilon = unit.sqrt(atomEpsilon[p1]*atomEpsilon[p2])
|
|
383
|
+
if chargeProd._value != 0 or epsilon._value != 0:
|
|
384
|
+
internalNonbonded.addBond(p1, p2, [chargeProd, sigma, epsilon])
|
|
385
|
+
if internalNonbonded.getNumBonds() > 0:
|
|
386
|
+
name = f'mmForce{len(mmVarNames)+1}'
|
|
387
|
+
cv.addCollectiveVariable(name, internalNonbonded)
|
|
388
|
+
mmVarNames.append(name)
|
|
389
|
+
|
|
390
|
+
# Configure the CustomCVForce so lambda_interpolate interpolates between the conventional and ML potentials.
|
|
391
|
+
|
|
392
|
+
mlSum = '+'.join(mlVarNames) if len(mlVarNames) > 0 else '0'
|
|
393
|
+
mmSum = '+'.join(mmVarNames) if len(mmVarNames) > 0 else '0'
|
|
394
|
+
cv.setEnergyFunction(f'lambda_interpolate*({mlSum}) + (1-lambda_interpolate)*({mmSum})')
|
|
395
|
+
newSystem.addForce(cv)
|
|
396
|
+
return newSystem
|
|
397
|
+
|
|
398
|
+
def _removeBonds(self, system: openmm.System, atoms: Iterable[int], removeInSet: bool, removeConstraints: bool) -> openmm.System:
|
|
399
|
+
"""Copy a System, removing all bonded interactions between atoms in (or not in) a particular set.
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
system: System
|
|
404
|
+
the System to copy
|
|
405
|
+
atoms: Iterable[int]
|
|
406
|
+
a set of atom indices
|
|
407
|
+
removeInSet: bool
|
|
408
|
+
if True, any bonded term connecting atoms in the specified set is removed. If False,
|
|
409
|
+
any term that does *not* connect atoms in the specified set is removed
|
|
410
|
+
removeConstraints: bool
|
|
411
|
+
if True, remove constraints between pairs of atoms in the set
|
|
412
|
+
|
|
413
|
+
Returns
|
|
414
|
+
-------
|
|
415
|
+
a newly created System object in which the specified bonded interactions have been removed
|
|
416
|
+
"""
|
|
417
|
+
atomSet = set(atoms)
|
|
418
|
+
|
|
419
|
+
# Create an XML representation of the System.
|
|
420
|
+
|
|
421
|
+
import xml.etree.ElementTree as ET
|
|
422
|
+
xml = openmm.XmlSerializer.serialize(system)
|
|
423
|
+
root = ET.fromstring(xml)
|
|
424
|
+
|
|
425
|
+
# This function decides whether a bonded interaction should be removed.
|
|
426
|
+
|
|
427
|
+
def shouldRemove(termAtoms):
|
|
428
|
+
return all(a in atomSet for a in termAtoms) == removeInSet
|
|
429
|
+
|
|
430
|
+
# Remove bonds, angles, and torsions.
|
|
431
|
+
|
|
432
|
+
for bonds in root.findall('./Forces/Force/Bonds'):
|
|
433
|
+
for bond in bonds.findall('Bond'):
|
|
434
|
+
bondAtoms = [int(bond.attrib[p]) for p in ('p1', 'p2')]
|
|
435
|
+
if shouldRemove(bondAtoms):
|
|
436
|
+
bonds.remove(bond)
|
|
437
|
+
for angles in root.findall('./Forces/Force/Angles'):
|
|
438
|
+
for angle in angles.findall('Angle'):
|
|
439
|
+
angleAtoms = [int(angle.attrib[p]) for p in ('p1', 'p2', 'p3')]
|
|
440
|
+
if shouldRemove(angleAtoms):
|
|
441
|
+
angles.remove(angle)
|
|
442
|
+
for torsions in root.findall('./Forces/Force/Torsions'):
|
|
443
|
+
for torsion in torsions.findall('Torsion'):
|
|
444
|
+
torsionLabels = ('p1', 'p2', 'p3', 'p4') if 'p1' in torsion.attrib else ('a1', 'a2', 'a3', 'a4', 'b1', 'b2', 'b3', 'b4')
|
|
445
|
+
torsionAtoms = [int(torsion.attrib[p]) for p in torsionLabels]
|
|
446
|
+
if shouldRemove(torsionAtoms):
|
|
447
|
+
torsions.remove(torsion)
|
|
448
|
+
|
|
449
|
+
# Optionally remove constraints.
|
|
450
|
+
|
|
451
|
+
if removeConstraints:
|
|
452
|
+
for constraints in root.findall('./Constraints'):
|
|
453
|
+
for constraint in constraints.findall('Constraint'):
|
|
454
|
+
constraintAtoms = [int(constraint.attrib[p]) for p in ('p1', 'p2')]
|
|
455
|
+
if shouldRemove(constraintAtoms):
|
|
456
|
+
constraints.remove(constraint)
|
|
457
|
+
|
|
458
|
+
# Create a new System from it.
|
|
459
|
+
|
|
460
|
+
return openmm.XmlSerializer.deserialize(ET.tostring(root, encoding='unicode'))
|
|
461
|
+
|
|
462
|
+
@staticmethod
|
|
463
|
+
def registerImplFactory(name: str, factory: MLPotentialImplFactory):
|
|
464
|
+
"""Register a new potential function that can be used with MLPotential.
|
|
465
|
+
|
|
466
|
+
Parameters
|
|
467
|
+
----------
|
|
468
|
+
name: str
|
|
469
|
+
the name of the potential function that will be passed to the MLPotential constructor
|
|
470
|
+
factory: MLPotentialImplFactory
|
|
471
|
+
a factory object that will be used to create MLPotentialImpl objects
|
|
472
|
+
"""
|
|
473
|
+
MLPotential._implFactories[name] = factory
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
# Register any potential functions defined by entry points.
|
|
477
|
+
|
|
478
|
+
for potential in entry_points(group='openmmml.potentials'):
|
|
479
|
+
MLPotential.registerImplFactory(potential.name, potential.load()())
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from . import anipotential, macepotential, nequippotential, deepmdpotential, aimnet2potential
|