"""
A collection of wrappers around commonly employed force fields.
"""
import abc
from enum import Enum
from propertyestimator import unit
from propertyestimator.utils.serialization import TypedBaseModel
[docs]class ForceFieldSource(TypedBaseModel):
"""A helper object to define the source of a force field
and any associated meta data, such as version, file paths,
or generation options.
Notes
-----
It is likely that this class and classes based off of it will
not be permanent fixtures of the framework, but rather will
exist until the force fields can be stored in a uniform format /
object model.
"""
@abc.abstractmethod
def __getstate__(self):
pass
@abc.abstractmethod
def __setstate__(self, state):
pass
[docs]class SmirnoffForceFieldSource(ForceFieldSource):
"""A wrapper around force fields based on the
`SMIRks Native Open Force Field (SMIRNOFF) specification
<https://open-forcefield-toolkit.readthedocs.io/en/latest/smirnoff.html>`_.
"""
[docs] def __init__(self, inner_xml=None):
"""Constructs a new SmirnoffForceFieldSource object
Parameters
----------
inner_xml: str, optional
A string containing the xml representation of the force field.
"""
self._inner_xml = inner_xml
[docs] def to_force_field(self):
"""Returns the SMIRNOFF force field created from this source.
Returns
-------
openforcefield.typing.engines.smirnoff.ForceField
The created force field.
"""
from openforcefield.typing.engines import smirnoff
return smirnoff.ForceField(self._inner_xml)
[docs] @classmethod
def from_object(cls, force_field):
"""Creates a new `SmirnoffForceFieldSource` from an existing
`ForceField` object
Notes
-----
All cosmetic attributes will be discarded.
Parameters
----------
force_field: openforcefield.typing.engines.smirnoff.ForceField
The existing force field.
Returns
-------
SmirnoffForceFieldSource
The created object.
"""
return cls(force_field.to_string('XML', True))
[docs] @classmethod
def from_path(cls, file_path):
"""Creates a new `SmirnoffForceFieldSource` from the file path to a
`ForceField` object.
Notes
-----
All cosmetic attributes will be discarded.
Parameters
----------
file_path: str
The file path to the force field object. This may also be the
name of a file which can be loaded via an entry point.
Returns
-------
SmirnoffForceFieldSource
The created object.
"""
from openforcefield.typing.engines.smirnoff import ForceField
force_field = ForceField(file_path, allow_cosmetic_attributes=True)
return cls.from_object(force_field)
def __getstate__(self):
return {'inner_xml': self._inner_xml}
def __setstate__(self, state):
self._inner_xml = state['inner_xml']
[docs]class TLeapForceFieldSource(ForceFieldSource):
"""A wrapper around Amber force fields which may be
applied via the `tleap` software package.
Notes
-----
Currently this only supports force fields which are installed
alongside `tleap`.
"""
@property
def leap_source(self):
"""list of str: The parameter file which should be sourced by `leap`
when applying the force field.
"""
return self._leap_source
@property
def cutoff(self):
"""unit.Quantity: The non-bonded interaction cutoff.
"""
return self._cutoff
[docs] def __init__(self, leap_source='leaprc.gaff2', cutoff=9.0*unit.angstrom):
"""Constructs a new TLeapForceFieldSource object
Parameters
----------
leap_source: str
The parameter file which should be sourced by `leap`
when applying the force field. Currently only
`'leaprc.gaff'` and `'leaprc.gaff2'` are supported.
cutoff: unit.Quantity
The non-bonded interaction cutoff.
Examples
--------
To create a source for the GAFF force field with tip3p water:
>>> amber_gaff_source = TLeapForceFieldSource('leaprc.gaff')
To create a source for the GAFF 2 force field with tip3p water:
>>> amber_gaff_2_source = TLeapForceFieldSource('leaprc.gaff2')
"""
if leap_source is not None:
assert leap_source == 'leaprc.gaff2' or leap_source == 'leaprc.gaff'
self._leap_source = leap_source
self._cutoff = cutoff
def __getstate__(self):
return {
'leap_source': self._leap_source,
'cutoff': self._cutoff
}
def __setstate__(self, state):
self._leap_source = state['leap_source']
self._cutoff = state['cutoff']
[docs]class LigParGenForceFieldSource(ForceFieldSource):
"""A wrapper and the OPLSAAM force field which can be applied
via the `LigParGen server <http://zarbi.chem.yale.edu/ligpargen/>`_.
References
----------
[1] Potential energy functions for atomic-level simulations of water and organic and
biomolecular systems. Jorgensen, W. L.; Tirado-Rives, J. Proc. Nat. Acad. Sci.
USA 2005, 102, 6665-6670
[2] 1.14*CM1A-LBCC: Localized Bond-Charge Corrected CM1A Charges for Condensed-Phase
Simulations. Dodda, L. S.; Vilseck, J. Z.; Tirado-Rives, J.; Jorgensen, W. L.
J. Phys. Chem. B, 2017, 121 (15), pp 3864-3870
[3] LigParGen web server: An automatic OPLS-AA parameter generator for organic ligands.
Dodda, L. S.;Cabeza de Vaca, I.; Tirado-Rives, J.; Jorgensen, W. L.
Nucleic Acids Research, Volume 45, Issue W1, 3 July 2017, Pages W331-W336
"""
[docs] class ChargeModel(Enum):
CM1A_1_14_LBCC = '1.14*CM1A-LBCC'
CM1A_1_14 = '1.14*CM1A'
@property
def preferred_charge_model(self):
"""ChargeModel: The preferred charge model to apply. In some cases
the preferred charge model may not be applicable (e.g. 1.14*CM1A-LBCC
may only be applied to neutral molecules) and so another model may be
applied in its place.
"""
return self._preferred_charge_model
@property
def cutoff(self):
"""unit.Quantity: The non-bonded interaction cutoff.
"""
return self._cutoff
[docs] def __init__(self, preferred_charge_model=ChargeModel.CM1A_1_14_LBCC, cutoff=9.0*unit.angstrom):
"""Constructs a new LigParGenForceFieldSource object
Parameters
----------
preferred_charge_model: ChargeModel
The preferred charge model to apply. In some cases
the preferred charge model may not be applicable
(e.g. 1.14*CM1A-LBCC may only be applied to neutral
molecules) and so another model may be applied in its
place.
cutoff: unit.Quantity
The non-bonded interaction cutoff.
"""
self._preferred_charge_model = preferred_charge_model
self._cutoff = cutoff
def __getstate__(self):
return {
'preferred_charge_model': self._preferred_charge_model,
'cutoff': self._cutoff
}
def __setstate__(self, state):
self._preferred_charge_model = state['preferred_charge_model']
self._cutoff = state['cutoff']