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 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,2 @@
1
+ from .mlpotential import MLPotential
2
+ from . import models
@@ -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