Hi @peastman,
Following our discussion yesterday, here are some ideas on an API for supporting embedding potentials.
While OpenMM-ML currently assumes that the ML potential replaces only the intramolecular interactions of the ML region, embedding potentials differ in that they replace (either fully or partially) the intermolecular interactions between the ML and MM regions. This means that the Coulomb and/or Lennard-Jones interactions between these regions need to be zeroed out to avoid double-counting. Because this will require specific operations, I propose we create an EmbeddingPotential class that handles this logic, something along the lines of:
class EmbeddingPotential:
def __init__(self, name: str, **args):
self._impl = EmbeddingPotential._implFactories[name].createImpl(name, **args)
def _removeCoulomb(self, …):
pass
def _removeLJ(self, ...):
pass
This class would be used just like MLPotential to create a specific embedding potential:
emle = EmbeddingPotential('emle')
Embedding potentials should be combined with an "intramolecular" MLP, and thus the EmbeddingPotential class does not need any of the logic for creating OpenMM systems, which is already implemented in MLPotential. Furthermore, because embedding potentials only make sense in mixed systems, the main modifications required are in the createMixedSystem method. Therefore, I propose we add an additional parameter to this method, called embeddingPotential, which is by default set to None (mechanical embedding ML/MM) but can also accept an embedding potential. This would preserve the philosophy that these embedding schemes are to be applied on top of ML potentials, e.g.:
potential = MLPotential('ani2x')
emle = EmbeddingPotential('emle')
ml_system = potential.createMixedSystem(topology, mm_system, ml_atoms, embeddingPotential=emle)
Now, the question is: which ML–MM interactions should be zeroed out inside createMixedSystem? Only the electrostatics? Only the LJ? Or both? To minimise the risk of users shooting themselves in the foot, I think the best option is for the embedding potential implementation to determine, during addForces and based on the user choices, which interactions should be removed and to set instance-level flags indicating these, e.g. REMOVE_LJ and REMOVE_COULOMB. Depending on the values of these flags, createMixedSystem would then remove (or not) the corresponding interactions from the system, just as it already does for the bonded terms.
A more tricky situation is when interpolation=True is passed to createMixedSystem. In this case, the approach I propose is to create a CustomNonbondedForce for the intermolecular interactions that need to be removed, restricted only to the MM-ML interaction groups. The question here is whether this force should vary with the same lambda_interpolate used for the underlying MLP, or whether it should be controlled independently. In practice, I have found it useful to couple an indepenent lambda parameter. This makes it possible to individually account for the corrections introduced when going from MM to ML/MM(mechanical embedding) and from ML/MM(mechanical embedding) to ML/MM (electrostatic embedding). Of course, the main disadvantage of using a CustomNonbondedForce is that the PME treatment of the ML-MM nonbonded interactions is lost. I'm not sure whether there is an obvious way around this, or if it's simply a limitation we have to live with. We can also assume that, in general, embedding potentials will have their own cutoff schemes, long-range treatments, switching functions, etc., which are built in and therefore not easily controlled.
Regarding the implementation of the embedding potentials themselves, here there are no majorchanges required to the current functionality of OpenMM-ML, so a standard structure like this would work:
class EMLEPotentialImplFactory(EmbeddingPotentialImplFactory):
def createImpl(self, name: str, **args) -> EmbeddingPotentialImpl:
return EMLEPotentialImpl(name)
class EMLEPotentialImpl(EmbeddingPotentialImpl):
where EMLEPotentialImpl would have and addForces method and the OpenMM-Torch force implementation (similar to what I did here).
I'm looking forward to hearing any thoughts on how to improve these ideas. I'm also cc'ing @jmichel80 and @lohedges as they may have additional suggestions.
Many thanks.
Hi @peastman,
Following our discussion yesterday, here are some ideas on an API for supporting embedding potentials.
While OpenMM-ML currently assumes that the ML potential replaces only the intramolecular interactions of the ML region, embedding potentials differ in that they replace (either fully or partially) the intermolecular interactions between the ML and MM regions. This means that the Coulomb and/or Lennard-Jones interactions between these regions need to be zeroed out to avoid double-counting. Because this will require specific operations, I propose we create an
EmbeddingPotentialclass that handles this logic, something along the lines of:This class would be used just like
MLPotentialto create a specific embedding potential:Embedding potentials should be combined with an "intramolecular" MLP, and thus the
EmbeddingPotentialclass does not need any of the logic for creating OpenMM systems, which is already implemented inMLPotential. Furthermore, because embedding potentials only make sense in mixed systems, the main modifications required are in thecreateMixedSystemmethod. Therefore, I propose we add an additional parameter to this method, calledembeddingPotential, which is by default set toNone(mechanical embedding ML/MM) but can also accept an embedding potential. This would preserve the philosophy that these embedding schemes are to be applied on top of ML potentials, e.g.:Now, the question is: which ML–MM interactions should be zeroed out inside
createMixedSystem? Only the electrostatics? Only the LJ? Or both? To minimise the risk of users shooting themselves in the foot, I think the best option is for the embedding potential implementation to determine, duringaddForcesand based on the user choices, which interactions should be removed and to set instance-level flags indicating these, e.g.REMOVE_LJandREMOVE_COULOMB. Depending on the values of these flags,createMixedSystemwould then remove (or not) the corresponding interactions from the system, just as it already does for the bonded terms.A more tricky situation is when
interpolation=Trueis passed to createMixedSystem. In this case, the approach I propose is to create aCustomNonbondedForcefor the intermolecular interactions that need to be removed, restricted only to the MM-ML interaction groups. The question here is whether this force should vary with the samelambda_interpolateused for the underlying MLP, or whether it should be controlled independently. In practice, I have found it useful to couple an indepenent lambda parameter. This makes it possible to individually account for the corrections introduced when going from MM to ML/MM(mechanical embedding) and from ML/MM(mechanical embedding) to ML/MM (electrostatic embedding). Of course, the main disadvantage of using aCustomNonbondedForceis that the PME treatment of the ML-MM nonbonded interactions is lost. I'm not sure whether there is an obvious way around this, or if it's simply a limitation we have to live with. We can also assume that, in general, embedding potentials will have their own cutoff schemes, long-range treatments, switching functions, etc., which are built in and therefore not easily controlled.Regarding the implementation of the embedding potentials themselves, here there are no majorchanges required to the current functionality of OpenMM-ML, so a standard structure like this would work:
where
EMLEPotentialImplwould have andaddForcesmethod and the OpenMM-Torch force implementation (similar to what I did here).I'm looking forward to hearing any thoughts on how to improve these ideas. I'm also cc'ing @jmichel80 and @lohedges as they may have additional suggestions.
Many thanks.