Unverified Commit adfd84c2 authored by Evan Pretti's avatar Evan Pretti Committed by GitHub
Browse files

Add LCPO method (#5130)

* Basic LCPO support

* Add basic test for LCPO from a prmtop file

* API for LCPOForce

* Started LCPO reference implementation

* Finished reference forces & test cases

* Use other test for finite difference since grid might have discontinuous forces

* Reference platform formatting

* Initial implementation of CPU platform

* Bugfixes

* More vectorization and improve neighbor list query speed

* Parallelize part of neighbor search

* Check box size for LCPO with periodic boundary conditions

* Fixes for updating parameters in context

* GBSAOBCForce doesn't use first & last indices for updates, so no need for this optimization here

* Changes to neighbor checking and optimization

* Fixes and minor changes

* Add global surface tension parameter

* Only process half of the pairs in the neighbor list

* Remove unnecessary checks

* Initial version of common platform implementation

* Asynchronously download neighbor list size

* Debugging

* Do pair precomputation in copyPairsToNeighborList

* Recompute interactions instead of scanning neighbor list in inner loop

* Condense position array before computations

* Also make neighbor count download asynchronous on device

* Fixes for kernel launching

* Topology-based LCPO parameter assignment

* Fixes, and use test system for LCPO with nucleic acids

* Always raise instead of warn when LCPO parameters can't be assigned

* Use Amber convention for phosphates
parent ccb83f1d
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit. *
* See https://openmm.org/development. *
* *
* Portions copyright (c) 2025 Stanford University and the Authors. *
* Authors: Peter Eastman, Evan Pretti *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "openmm/serialization/LCPOForceProxy.h"
#include "openmm/serialization/SerializationNode.h"
#include "openmm/Force.h"
#include "openmm/LCPOForce.h"
#include <sstream>
using namespace OpenMM;
using namespace std;
LCPOForceProxy::LCPOForceProxy() : SerializationProxy("LCPOForce") {
}
void LCPOForceProxy::serialize(const void* object, SerializationNode& node) const {
node.setIntProperty("version", 1);
const LCPOForce& force = *reinterpret_cast<const LCPOForce*>(object);
node.setIntProperty("forceGroup", force.getForceGroup());
node.setStringProperty("name", force.getName());
node.setBoolProperty("usesPeriodic", force.usesPeriodicBoundaryConditions());
node.setDoubleProperty("surfaceTension", force.getSurfaceTension());
SerializationNode& particles = node.createChildNode("Particles");
for (int i = 0; i < force.getNumParticles(); i++) {
double radius, p1, p2, p3, p4;
force.getParticleParameters(i, radius, p1, p2, p3, p4);
particles.createChildNode("Particle").setDoubleProperty("r", radius).setDoubleProperty("p1", p1).setDoubleProperty("p2", p2).setDoubleProperty("p3", p3).setDoubleProperty("p4", p4);
}
}
void* LCPOForceProxy::deserialize(const SerializationNode& node) const {
int version = node.getIntProperty("version");
if (version != 1) {
throw OpenMMException("Unsupported version number");
}
LCPOForce* force = new LCPOForce();
try {
force->setForceGroup(node.getIntProperty("forceGroup", 0));
force->setName(node.getStringProperty("name", force->getName()));
force->setUsesPeriodicBoundaryConditions(node.getBoolProperty("usesPeriodic"));
force->setSurfaceTension(node.getDoubleProperty("surfaceTension"));
const SerializationNode& particles = node.getChildNode("Particles");
for (auto& particle : particles.getChildren()) {
force->addParticle(particle.getDoubleProperty("r"), particle.getDoubleProperty("p1"), particle.getDoubleProperty("p2"), particle.getDoubleProperty("p3"), particle.getDoubleProperty("p4"));
}
}
catch (...) {
delete force;
throw;
}
return force;
}
...@@ -54,6 +54,7 @@ ...@@ -54,6 +54,7 @@
#include "openmm/HarmonicBondForce.h" #include "openmm/HarmonicBondForce.h"
#include "openmm/LangevinIntegrator.h" #include "openmm/LangevinIntegrator.h"
#include "openmm/LangevinMiddleIntegrator.h" #include "openmm/LangevinMiddleIntegrator.h"
#include "openmm/LCPOForce.h"
#include "openmm/MonteCarloAnisotropicBarostat.h" #include "openmm/MonteCarloAnisotropicBarostat.h"
#include "openmm/MonteCarloBarostat.h" #include "openmm/MonteCarloBarostat.h"
#include "openmm/MonteCarloFlexibleBarostat.h" #include "openmm/MonteCarloFlexibleBarostat.h"
...@@ -100,6 +101,7 @@ ...@@ -100,6 +101,7 @@
#include "openmm/serialization/HarmonicBondForceProxy.h" #include "openmm/serialization/HarmonicBondForceProxy.h"
#include "openmm/serialization/LangevinIntegratorProxy.h" #include "openmm/serialization/LangevinIntegratorProxy.h"
#include "openmm/serialization/LangevinMiddleIntegratorProxy.h" #include "openmm/serialization/LangevinMiddleIntegratorProxy.h"
#include "openmm/serialization/LCPOForceProxy.h"
#include "openmm/serialization/MonteCarloAnisotropicBarostatProxy.h" #include "openmm/serialization/MonteCarloAnisotropicBarostatProxy.h"
#include "openmm/serialization/MonteCarloBarostatProxy.h" #include "openmm/serialization/MonteCarloBarostatProxy.h"
#include "openmm/serialization/MonteCarloFlexibleBarostatProxy.h" #include "openmm/serialization/MonteCarloFlexibleBarostatProxy.h"
...@@ -167,6 +169,7 @@ extern "C" void registerSerializationProxies() { ...@@ -167,6 +169,7 @@ extern "C" void registerSerializationProxies() {
SerializationProxy::registerProxy(typeid(HarmonicBondForce), new HarmonicBondForceProxy()); SerializationProxy::registerProxy(typeid(HarmonicBondForce), new HarmonicBondForceProxy());
SerializationProxy::registerProxy(typeid(LangevinIntegrator), new LangevinIntegratorProxy()); SerializationProxy::registerProxy(typeid(LangevinIntegrator), new LangevinIntegratorProxy());
SerializationProxy::registerProxy(typeid(LangevinMiddleIntegrator), new LangevinMiddleIntegratorProxy()); SerializationProxy::registerProxy(typeid(LangevinMiddleIntegrator), new LangevinMiddleIntegratorProxy());
SerializationProxy::registerProxy(typeid(LCPOForce), new LCPOForceProxy());
SerializationProxy::registerProxy(typeid(MonteCarloAnisotropicBarostat), new MonteCarloAnisotropicBarostatProxy()); SerializationProxy::registerProxy(typeid(MonteCarloAnisotropicBarostat), new MonteCarloAnisotropicBarostatProxy());
SerializationProxy::registerProxy(typeid(MonteCarloBarostat), new MonteCarloBarostatProxy()); SerializationProxy::registerProxy(typeid(MonteCarloBarostat), new MonteCarloBarostatProxy());
SerializationProxy::registerProxy(typeid(MonteCarloFlexibleBarostat), new MonteCarloFlexibleBarostatProxy()); SerializationProxy::registerProxy(typeid(MonteCarloFlexibleBarostat), new MonteCarloFlexibleBarostatProxy());
......
/* -------------------------------------------------------------------------- *
* OpenMM *
* -------------------------------------------------------------------------- *
* This is part of the OpenMM molecular simulation toolkit. *
* See https://openmm.org/development. *
* *
* Portions copyright (c) 2025 Stanford University and the Authors. *
* Authors: Peter Eastman, Evan Pretti *
* Contributors: *
* *
* Permission is hereby granted, free of charge, to any person obtaining a *
* copy of this software and associated documentation files (the "Software"), *
* to deal in the Software without restriction, including without limitation *
* the rights to use, copy, modify, merge, publish, distribute, sublicense, *
* and/or sell copies of the Software, and to permit persons to whom the *
* Software is furnished to do so, subject to the following conditions: *
* *
* The above copyright notice and this permission notice shall be included in *
* all copies or substantial portions of the Software. *
* *
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR *
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *
* THE AUTHORS, CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, *
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR *
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE *
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/LCPOForce.h"
#include "openmm/serialization/XmlSerializer.h"
#include <iostream>
#include <sstream>
using namespace OpenMM;
using namespace std;
void testSerialization() {
// Create a Force.
LCPOForce force;
force.setForceGroup(3);
force.setName("custom name");
force.setUsesPeriodicBoundaryConditions(true);
force.setSurfaceTension(100.0);
force.addParticle(1.0, 2.0, 3.0, 4.0, 5.0);
force.addParticle(0.0, 0.0, 0.0, 0.0, 0.0);
force.addParticle(6.0, 7.0, 8.0, 9.0, 10.0);
force.addParticle(1.0, 2.0, 3.0, 4.0, 5.0);
force.addParticle(0.0, 0.0, 0.0, 0.0, 0.0);
force.addParticle(6.0, 7.0, 8.0, 9.0, 10.0);
// Serialize and then deserialize it.
stringstream buffer;
XmlSerializer::serialize<LCPOForce>(&force, "Force", buffer);
LCPOForce* copy = XmlSerializer::deserialize<LCPOForce>(buffer);
// Compare the two forces to see if they are identical.
LCPOForce& force2 = *copy;
ASSERT_EQUAL(force.getForceGroup(), force2.getForceGroup());
ASSERT_EQUAL(force.getName(), force2.getName());
ASSERT_EQUAL(force.usesPeriodicBoundaryConditions(), force2.usesPeriodicBoundaryConditions());
ASSERT_EQUAL(force.getSurfaceTension(), force2.getSurfaceTension());
ASSERT_EQUAL(force.getNumParticles(), force2.getNumParticles());
for (int i = 0; i < force.getNumParticles(); i++) {
double radius1, radius2, p11, p12, p21, p22, p31, p32, p41, p42;
force.getParticleParameters(i, radius1, p11, p21, p31, p41);
force2.getParticleParameters(i, radius2, p12, p22, p32, p42);
ASSERT_EQUAL(radius1, radius2);
ASSERT_EQUAL(p11, p12);
ASSERT_EQUAL(p21, p22);
ASSERT_EQUAL(p31, p32);
ASSERT_EQUAL(p41, p42);
}
}
int main() {
try {
testSerialization();
}
catch(const exception& e) {
cout << "exception: " << e.what() << endl;
return 1;
}
cout << "Done" << endl;
return 0;
}
This diff is collapsed.
...@@ -68,6 +68,13 @@ class GBn2(Singleton): ...@@ -68,6 +68,13 @@ class GBn2(Singleton):
return 'GBn2' return 'GBn2'
GBn2 = GBn2() GBn2 = GBn2()
# Placeholder value for implicit solvent surface area model
class Unspecified(Singleton):
def __repr__(self):
return 'Unspecified'
Unspecified = Unspecified()
def _strip_optunit(thing, unit): def _strip_optunit(thing, unit):
""" """
Strips optional units, converting to specified unit type. If no unit Strips optional units, converting to specified unit type. If no unit
...@@ -180,7 +187,7 @@ class AmberPrmtopFile(object): ...@@ -180,7 +187,7 @@ class AmberPrmtopFile(object):
implicitSolventKappa=None, temperature=298.15*u.kelvin, implicitSolventKappa=None, temperature=298.15*u.kelvin,
soluteDielectric=1.0, solventDielectric=78.5, soluteDielectric=1.0, solventDielectric=78.5,
removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005, removeCMMotion=True, hydrogenMass=None, ewaldErrorTolerance=0.0005,
switchDistance=0.0*u.nanometer, flexibleConstraints=False, gbsaModel='ACE'): switchDistance=0.0*u.nanometer, flexibleConstraints=False, sasaMethod=Unspecified, gbsaModel=Unspecified):
"""Construct an OpenMM System representing the topology described by this """Construct an OpenMM System representing the topology described by this
prmtop file. prmtop file.
...@@ -230,11 +237,13 @@ class AmberPrmtopFile(object): ...@@ -230,11 +237,13 @@ class AmberPrmtopFile(object):
Values greater than nonbondedCutoff or less than 0 raise ValueError Values greater than nonbondedCutoff or less than 0 raise ValueError
flexibleConstraints : boolean=False flexibleConstraints : boolean=False
If True, parameters for constrained degrees of freedom will be added to the System If True, parameters for constrained degrees of freedom will be added to the System
gbsaModel : str='ACE' sasaMethod : str, optional
The SA model used to model the nonpolar solvation component of GB The SA model used to model the nonpolar solvation component of GB
implicit solvent models. If GB is active, this must be 'ACE' or None implicit solvent models. If GB is active, this must be 'ACE',
(the latter indicates no SA model will be used). Other values will 'LCPO', or None (the latter indicates no SA model will be used).
result in a ValueError Other values will result in a ValueError. If unspecified, uses ACE.
gbsaModel : str, optional
Deprecated. Use `sasaMethod` instead.
Returns Returns
------- -------
...@@ -292,11 +301,16 @@ class AmberPrmtopFile(object): ...@@ -292,11 +301,16 @@ class AmberPrmtopFile(object):
elif implicitSolvent is None: elif implicitSolvent is None:
implicitSolventKappa = 0.0 implicitSolventKappa = 0.0
if sasaMethod is Unspecified and gbsaModel is not Unspecified:
sasaMethod = gbsaModel
if sasaMethod is Unspecified:
sasaMethod = 'ACE'
sys = amber_file_parser.readAmberSystem(self.topology, prmtop_loader=self._prmtop, shake=constraintString, sys = amber_file_parser.readAmberSystem(self.topology, prmtop_loader=self._prmtop, shake=constraintString,
nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod], nonbondedCutoff=nonbondedCutoff, nonbondedMethod=methodMap[nonbondedMethod],
flexibleConstraints=flexibleConstraints, gbmodel=implicitString, soluteDielectric=soluteDielectric, flexibleConstraints=flexibleConstraints, gbmodel=implicitString, soluteDielectric=soluteDielectric,
solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa, solventDielectric=solventDielectric, implicitSolventKappa=implicitSolventKappa,
rigidWater=rigidWater, elements=self.elements, gbsaModel=gbsaModel) rigidWater=rigidWater, elements=self.elements, sasaMethod=sasaMethod)
if hydrogenMass is not None: if hydrogenMass is not None:
for atom1, atom2 in self.topology.bonds(): for atom1, atom2 in self.topology.bonds():
......
...@@ -41,10 +41,11 @@ import openmm as mm ...@@ -41,10 +41,11 @@ import openmm as mm
from openmm.vec3 import Vec3 from openmm.vec3 import Vec3
import openmm.unit as u import openmm.unit as u
from openmm.app import (forcefield as ff, Topology, element, PDBFile) from openmm.app import (forcefield as ff, Topology, element, PDBFile)
from openmm.app.amberprmtopfile import HCT, OBC1, OBC2, GBn, GBn2 from openmm.app.amberprmtopfile import HCT, OBC1, OBC2, GBn, GBn2, Unspecified
from openmm.app.internal.customgbforces import (GBSAHCTForce, from openmm.app.internal.customgbforces import (GBSAHCTForce,
GBSAOBC1Force, GBSAOBC2Force, GBSAGBnForce, GBSAGBn2Force) GBSAOBC1Force, GBSAOBC2Force, GBSAGBnForce, GBSAGBn2Force)
from openmm.app.internal.unitcell import computePeriodicBoxVectors from openmm.app.internal.unitcell import computePeriodicBoxVectors
from openmm.app.internal import lcpo
# CHARMM imports # CHARMM imports
from openmm.app.internal.charmm.topologyobjects import ( from openmm.app.internal.charmm.topologyobjects import (
ResidueList, AtomList, TrackedList, Bond, Angle, Dihedral, ResidueList, AtomList, TrackedList, Bond, Angle, Dihedral,
...@@ -795,7 +796,8 @@ class CharmmPsfFile(object): ...@@ -795,7 +796,8 @@ class CharmmPsfFile(object):
ewaldErrorTolerance=0.0005, ewaldErrorTolerance=0.0005,
flexibleConstraints=True, flexibleConstraints=True,
verbose=False, verbose=False,
gbsaModel=None, sasaMethod=Unspecified,
gbsaModel=Unspecified,
drudeMass=0.4*u.amu): drudeMass=0.4*u.amu):
"""Construct an OpenMM System representing the topology described by the """Construct an OpenMM System representing the topology described by the
prmtop file. You MUST have loaded a parameter set into this PSF before prmtop file. You MUST have loaded a parameter set into this PSF before
...@@ -852,9 +854,14 @@ class CharmmPsfFile(object): ...@@ -852,9 +854,14 @@ class CharmmPsfFile(object):
If True, parameters for constrained degrees of freedom will be added to the System If True, parameters for constrained degrees of freedom will be added to the System
verbose : bool=False verbose : bool=False
Optionally prints out a running progress report Optionally prints out a running progress report
gbsaModel : str=None sasaMethod : str, optional
Can be ACE (to use the ACE solvation model) or None. Other values The SA model used to model the nonpolar solvation component of GB
raise a ValueError implicit solvent models. If GB is active, this must be 'ACE',
'LCPO', or None (the latter indicates no SA model will be used,
which is the default behavior if this parameter is not specified).
Other values will result in a ValueError.
gbsaModel : str, optional
Deprecated. Use `sasaMethod` instead.
drudeMass : mass=0.4*amu drudeMass : mass=0.4*amu
The mass to use for Drude particles. Any mass added to a Drude particle is The mass to use for Drude particles. Any mass added to a Drude particle is
subtracted from its parent atom to keep their total mass the same. subtracted from its parent atom to keep their total mass the same.
...@@ -863,8 +870,12 @@ class CharmmPsfFile(object): ...@@ -863,8 +870,12 @@ class CharmmPsfFile(object):
self.loadParameters(params) self.loadParameters(params)
hasbox = self.topology.getUnitCellDimensions() is not None hasbox = self.topology.getUnitCellDimensions() is not None
# Check GB input parameters # Check GB input parameters
if implicitSolvent is not None and gbsaModel not in ('ACE', None): if sasaMethod is Unspecified and gbsaModel is not Unspecified:
raise ValueError('gbsaModel must be ACE or None') sasaMethod = gbsaModel
if sasaMethod is Unspecified:
sasaMethod = None
if implicitSolvent is not None and sasaMethod not in ('ACE', 'LCPO', None):
raise ValueError('sasaMethod must be ACE, LCPO, or None')
# Set the cutoff distance in nanometers # Set the cutoff distance in nanometers
cutoff = None cutoff = None
if nonbondedMethod is not ff.NoCutoff: if nonbondedMethod is not ff.NoCutoff:
...@@ -1536,19 +1547,19 @@ class CharmmPsfFile(object): ...@@ -1536,19 +1547,19 @@ class CharmmPsfFile(object):
implicitSolventKappa = implicitSolventKappa.value_in_unit( implicitSolventKappa = implicitSolventKappa.value_in_unit(
(1.0/u.nanometer).unit) (1.0/u.nanometer).unit)
if implicitSolvent is HCT: if implicitSolvent is HCT:
gb = GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel, gb = GBSAHCTForce(solventDielectric, soluteDielectric, sasaMethod,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC1: elif implicitSolvent is OBC1:
gb = GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel, gb = GBSAOBC1Force(solventDielectric, soluteDielectric, sasaMethod,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is OBC2: elif implicitSolvent is OBC2:
gb = GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel, gb = GBSAOBC2Force(solventDielectric, soluteDielectric, sasaMethod,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn: elif implicitSolvent is GBn:
gb = GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel, gb = GBSAGBnForce(solventDielectric, soluteDielectric, sasaMethod,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
elif implicitSolvent is GBn2: elif implicitSolvent is GBn2:
gb = GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel, gb = GBSAGBn2Force(solventDielectric, soluteDielectric, sasaMethod,
cutoff, kappa=implicitSolventKappa) cutoff, kappa=implicitSolventKappa)
gb_parms = gb.getStandardParameters(self.topology) gb_parms = gb.getStandardParameters(self.topology)
for atom, gb_parm in zip(self.atom_list, gb_parms): for atom, gb_parm in zip(self.atom_list, gb_parms):
...@@ -1569,6 +1580,9 @@ class CharmmPsfFile(object): ...@@ -1569,6 +1580,9 @@ class CharmmPsfFile(object):
system.addForce(gb) system.addForce(gb)
force.setReactionFieldDielectric(1.0) # applies to NonbondedForce force.setReactionFieldDielectric(1.0) # applies to NonbondedForce
if sasaMethod == 'LCPO':
lcpo.addLCPOForce(system, lcpo.getLCPOParamsTopology(self.topology), nonbondedMethod is ff.CutoffPeriodic)
# See if we repartition the hydrogen masses # See if we repartition the hydrogen masses
if hydrogenMass is not None: if hydrogenMass is not None:
for bond in self.bond_list: for bond in self.bond_list:
......
...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1) ...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1)
# Construct the CustomGBForce. # Construct the CustomGBForce.
from openmm.app.internal.customgbforces import GBSAGBnForce from openmm.app.internal.customgbforces import GBSAGBnForce
argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa'} argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa', 'sasaMethod':'SA'}
solventArgs = {'SA':'ACE'} solventArgs = {'SA':'ACE'}
for key in argMap: for key in argMap:
if key in args: if key in args:
...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic: ...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic:
else: else:
raise ValueError("Illegal nonbonded method for use with implicit solvent") raise ValueError("Illegal nonbonded method for use with implicit solvent")
sys.addForce(force) sys.addForce(force)
if solventArgs['SA'] == 'LCPO':
from openmm.app.internal import lcpo
lcpo.addLCPOForce(sys, lcpo.getLCPOParamsTopology(topology), nonbondedMethod == app.CutoffPeriodic)
</Script> </Script>
</ForceField> </ForceField>
\ No newline at end of file
...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1) ...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1)
# Construct the CustomGBForce. # Construct the CustomGBForce.
from openmm.app.internal.customgbforces import GBSAGBn2Force from openmm.app.internal.customgbforces import GBSAGBn2Force
argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa'} argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa', 'sasaMethod':'SA'}
solventArgs = {'SA':'ACE'} solventArgs = {'SA':'ACE'}
for key in argMap: for key in argMap:
if key in args: if key in args:
...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic: ...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic:
else: else:
raise ValueError("Illegal nonbonded method for use with implicit solvent") raise ValueError("Illegal nonbonded method for use with implicit solvent")
sys.addForce(force) sys.addForce(force)
if solventArgs['SA'] == 'LCPO':
from openmm.app.internal import lcpo
lcpo.addLCPOForce(sys, lcpo.getLCPOParamsTopology(topology), nonbondedMethod == app.CutoffPeriodic)
</Script> </Script>
</ForceField> </ForceField>
\ No newline at end of file
...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1) ...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1)
# Construct the CustomGBForce. # Construct the CustomGBForce.
from openmm.app.internal.customgbforces import GBSAHCTForce from openmm.app.internal.customgbforces import GBSAHCTForce
argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa'} argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa', 'sasaMethod':'SA'}
solventArgs = {'SA':'ACE'} solventArgs = {'SA':'ACE'}
for key in argMap: for key in argMap:
if key in args: if key in args:
...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic: ...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic:
else: else:
raise ValueError("Illegal nonbonded method for use with implicit solvent") raise ValueError("Illegal nonbonded method for use with implicit solvent")
sys.addForce(force) sys.addForce(force)
if solventArgs['SA'] == 'LCPO':
from openmm.app.internal import lcpo
lcpo.addLCPOForce(sys, lcpo.getLCPOParamsTopology(topology), nonbondedMethod == app.CutoffPeriodic)
</Script> </Script>
</ForceField> </ForceField>
\ No newline at end of file
...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1) ...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1)
# Construct the CustomGBForce. # Construct the CustomGBForce.
from openmm.app.internal.customgbforces import GBSAOBC1Force from openmm.app.internal.customgbforces import GBSAOBC1Force
argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa'} argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa', 'sasaMethod':'SA'}
solventArgs = {'SA':'ACE'} solventArgs = {'SA':'ACE'}
for key in argMap: for key in argMap:
if key in args: if key in args:
...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic: ...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic:
else: else:
raise ValueError("Illegal nonbonded method for use with implicit solvent") raise ValueError("Illegal nonbonded method for use with implicit solvent")
sys.addForce(force) sys.addForce(force)
if solventArgs['SA'] == 'LCPO':
from openmm.app.internal import lcpo
lcpo.addLCPOForce(sys, lcpo.getLCPOParamsTopology(topology), nonbondedMethod == app.CutoffPeriodic)
</Script> </Script>
</ForceField> </ForceField>
\ No newline at end of file
...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1) ...@@ -15,7 +15,7 @@ nonbonded.setReactionFieldDielectric(1)
# Construct the CustomGBForce. # Construct the CustomGBForce.
from openmm.app.internal.customgbforces import GBSAOBC2Force from openmm.app.internal.customgbforces import GBSAOBC2Force
argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa'} argMap = {'soluteDielectric':'soluteDielectric', 'solventDielectric':'solventDielectric', 'implicitSolventKappa':'kappa', 'sasaMethod':'SA'}
solventArgs = {'SA':'ACE'} solventArgs = {'SA':'ACE'}
for key in argMap: for key in argMap:
if key in args: if key in args:
...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic: ...@@ -42,5 +42,9 @@ elif nonbondedMethod == app.CutoffPeriodic:
else: else:
raise ValueError("Illegal nonbonded method for use with implicit solvent") raise ValueError("Illegal nonbonded method for use with implicit solvent")
sys.addForce(force) sys.addForce(force)
if solventArgs['SA'] == 'LCPO':
from openmm.app.internal import lcpo
lcpo.addLCPOForce(sys, lcpo.getLCPOParamsTopology(topology), nonbondedMethod == app.CutoffPeriodic)
</Script> </Script>
</ForceField> </ForceField>
\ No newline at end of file
...@@ -54,6 +54,7 @@ from openmm.app import element as elem ...@@ -54,6 +54,7 @@ from openmm.app import element as elem
from openmm.app.internal.unitcell import computePeriodicBoxVectors from openmm.app.internal.unitcell import computePeriodicBoxVectors
from openmm.vec3 import Vec3 from openmm.vec3 import Vec3
from . import customgbforces as customgb from . import customgbforces as customgb
from . import lcpo
#============================================================================================= #=============================================================================================
# AMBER parmtop loader (from 'zander', by Randall J. Radmer) # AMBER parmtop loader (from 'zander', by Randall J. Radmer)
...@@ -674,7 +675,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -674,7 +675,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
implicitSolventKappa=0.0*(1/units.nanometer), nonbondedCutoff=None, implicitSolventKappa=0.0*(1/units.nanometer), nonbondedCutoff=None,
nonbondedMethod='NoCutoff', scee=None, scnb=None, mm=None, verbose=False, nonbondedMethod='NoCutoff', scee=None, scnb=None, mm=None, verbose=False,
EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True, elements=None, EwaldErrorTolerance=None, flexibleConstraints=True, rigidWater=True, elements=None,
gbsaModel='ACE'): sasaMethod='ACE'):
""" """
Create an OpenMM System from an Amber prmtop file. Create an OpenMM System from an Amber prmtop file.
...@@ -698,7 +699,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -698,7 +699,7 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
verbose (boolean) - if True, print out information on progress (default: False) verbose (boolean) - if True, print out information on progress (default: False)
flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds flexibleConstraints (boolean) - if True, flexible bonds will be added in addition ot constrained bonds
rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the shake argument rigidWater (boolean=True) If true, water molecules will be fully rigid regardless of the value passed for the shake argument
gbsaModel (str='ACE') The string representing the SA model to use for GB calculations. Must be 'ACE' or None sasaMethod (str='ACE') The string representing the SA model to use for GB calculations. Must be 'ACE', 'LCPO', or None
NOTES NOTES
...@@ -744,8 +745,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -744,8 +745,8 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
warnings.warn("1-4 scaling parameters in topology file are being ignored. " warnings.warn("1-4 scaling parameters in topology file are being ignored. "
"This is not recommended unless you know what you are doing.") "This is not recommended unless you know what you are doing.")
if gbmodel is not None and gbsaModel not in ('ACE', None): if gbmodel is not None and sasaMethod not in ('ACE', 'LCPO', None):
raise ValueError('gbsaModel must be ACE or None') raise ValueError('sasaMethod must be ACE, LCPO, or None')
has_1264 = 'LENNARD_JONES_CCOEF' in prmtop._raw_data.keys() has_1264 = 'LENNARD_JONES_CCOEF' in prmtop._raw_data.keys()
if has_1264: if has_1264:
...@@ -1129,22 +1130,22 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -1129,22 +1130,22 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
if units.is_quantity(cutoff): if units.is_quantity(cutoff):
cutoff = cutoff.value_in_unit(units.nanometers) cutoff = cutoff.value_in_unit(units.nanometers)
if gbmodel == 'HCT': if gbmodel == 'HCT':
gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa) gb = customgb.GBSAHCTForce(solventDielectric, soluteDielectric, sasaMethod, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC1': elif gbmodel == 'OBC1':
gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa) gb = customgb.GBSAOBC1Force(solventDielectric, soluteDielectric, sasaMethod, cutoff, implicitSolventKappa)
elif gbmodel == 'OBC2': elif gbmodel == 'OBC2':
if implicitSolventKappa > 0: if implicitSolventKappa > 0:
gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa) gb = customgb.GBSAOBC2Force(solventDielectric, soluteDielectric, sasaMethod, cutoff, implicitSolventKappa)
else: else:
gb = mm.GBSAOBCForce() gb = mm.GBSAOBCForce()
gb.setSoluteDielectric(soluteDielectric) gb.setSoluteDielectric(soluteDielectric)
gb.setSolventDielectric(solventDielectric) gb.setSolventDielectric(solventDielectric)
if gbsaModel is None: if sasaMethod != 'ACE':
gb.setSurfaceAreaEnergy(0) gb.setSurfaceAreaEnergy(0)
elif gbmodel == 'GBn': elif gbmodel == 'GBn':
gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa) gb = customgb.GBSAGBnForce(solventDielectric, soluteDielectric, sasaMethod, cutoff, implicitSolventKappa)
elif gbmodel == 'GBn2': elif gbmodel == 'GBn2':
gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, gbsaModel, cutoff, implicitSolventKappa) gb = customgb.GBSAGBn2Force(solventDielectric, soluteDielectric, sasaMethod, cutoff, implicitSolventKappa)
else: else:
raise ValueError("Illegal value specified for implicit solvent model") raise ValueError("Illegal value specified for implicit solvent model")
if isinstance(gb, mm.GBSAOBCForce): if isinstance(gb, mm.GBSAOBCForce):
...@@ -1199,6 +1200,9 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No ...@@ -1199,6 +1200,9 @@ def readAmberSystem(topology, prmtop_filename=None, prmtop_loader=None, shake=No
# created above. Do not bind force to another name before this! # created above. Do not bind force to another name before this!
force.setReactionFieldDielectric(1.0) force.setReactionFieldDielectric(1.0)
if sasaMethod == 'LCPO':
lcpo.addLCPOForce(system, lcpo.getLCPOParamsAmber(prmtop, elements), nonbondedMethod == 'CutoffPeriodic')
return system return system
#============================================================================================= #=============================================================================================
......
...@@ -371,6 +371,9 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k ...@@ -371,6 +371,9 @@ def _createEnergyTerms(force, solventDielectric, soluteDielectric, SA, cutoff, k
CustomGBForce.SingleParticle) CustomGBForce.SingleParticle)
if SA=='ACE': if SA=='ACE':
force.addEnergyTerm("28.3919551*(radius+0.14)^2*(radius/B)^6; radius=or+offset"+params, CustomGBForce.SingleParticle) force.addEnergyTerm("28.3919551*(radius+0.14)^2*(radius/B)^6; radius=or+offset"+params, CustomGBForce.SingleParticle)
elif SA=='LCPO':
# Handled by caller
pass
elif SA is not None: elif SA is not None:
raise ValueError('Unknown surface area method: '+SA) raise ValueError('Unknown surface area method: '+SA)
if cutoff is None: if cutoff is None:
......
This diff is collapsed.
...@@ -451,6 +451,11 @@ UNITS = { ...@@ -451,6 +451,11 @@ UNITS = {
("HarmonicBondForce", "addBond") : (None, (None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")), ("HarmonicBondForce", "addBond") : (None, (None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")),
("HarmonicBondForce", "getBondParameters") : (None, (None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")), ("HarmonicBondForce", "getBondParameters") : (None, (None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")),
("HarmonicBondForce", "setBondParameters") : (None, (None, None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")), ("HarmonicBondForce", "setBondParameters") : (None, (None, None, None, "unit.nanometer", "unit.kilojoule_per_mole/(unit.nanometer*unit.nanometer)")),
("LCPOForce", "getSurfaceTension") : ("unit.kilojoule_per_mole/unit.nanometer**2", ()),
("LCPOForce", "setSurfaceTension") : (None, ("unit.kilojoule_per_mole/unit.nanometer**2",)),
("LCPOForce", "addParticle") : (None, ("unit.nanometer", None, None, None, "unit.nanometer**-2")),
("LCPOForce", "getParticleParameters") : (None, ("unit.nanometer", None, None, None, "unit.nanometer**-2")),
("LCPOForce", "setParticleParameters") : (None, (None, "unit.nanometer", None, None, None, "unit.nanometer**-2")),
("MonteCarloBarostat", "getFrequency") : (None, ()), ("MonteCarloBarostat", "getFrequency") : (None, ()),
("MonteCarloAnisotropicBarostat", "getFrequency") : (None, ()), ("MonteCarloAnisotropicBarostat", "getFrequency") : (None, ()),
("NonbondedForce", "getPMEParameters") : (None, ("unit.nanometer**-1", None, None, None)), ("NonbondedForce", "getPMEParameters") : (None, ("unit.nanometer**-1", None, None, None)),
......
...@@ -369,6 +369,64 @@ class TestAPIUnits(unittest.TestCase): ...@@ -369,6 +369,64 @@ class TestAPIUnits(unittest.TestCase):
self.assertIs(charge1.unit, elementary_charge) self.assertIs(charge1.unit, elementary_charge)
self.assertIs(charge2.unit, elementary_charge) self.assertIs(charge2.unit, elementary_charge)
def testLCPOForce(self):
""" Tests the LCPOForce API features """
force = LCPOForce()
force.setSurfaceTension(10.0*kilocalorie_per_mole/bohr**2)
surfaceTension = force.getSurfaceTension()
self.assertAlmostEqualUnit(surfaceTension, 10.0*kilocalorie_per_mole/bohr**2)
self.assertIs(surfaceTension.unit, kilojoule_per_mole/nanometer**2)
force.setSurfaceTension(10.0)
surfaceTension = force.getSurfaceTension()
self.assertAlmostEqualUnit(surfaceTension, 10.0*kilojoule_per_mole/nanometer**2)
force.addParticle(1.0, 2.0, 3.0, 4.0, 5.0)
force.addParticle(6.0*bohr, 7.0, 8.0, 9.0, 10.0/bohr**2)
self.assertEqual(force.getNumParticles(), 2)
radius, p1, p2, p3, p4 = force.getParticleParameters(0)
self.assertAlmostEqualUnit(radius, 1.0*nanometer)
self.assertEqual(p1, 2.0)
self.assertEqual(p2, 3.0)
self.assertEqual(p3, 4.0)
self.assertAlmostEqualUnit(p4, 5.0/nanometer**2)
self.assertIs(radius.unit, nanometer)
self.assertIs(p4.unit, nanometer**-2)
radius, p1, p2, p3, p4 = force.getParticleParameters(1)
self.assertAlmostEqualUnit(radius, 6.0*bohr)
self.assertEqual(p1, 7.0)
self.assertEqual(p2, 8.0)
self.assertEqual(p3, 9.0)
self.assertAlmostEqualUnit(p4, 10.0/bohr**2)
self.assertIs(radius.unit, nanometer)
self.assertIs(p4.unit, nanometer**-2)
force.setParticleParameters(0, 11.0, 12.0, 13.0, 14.0, 15.0)
force.setParticleParameters(1, 16.0*bohr, 17.0, 18.0, 19.0, 20.0/bohr**2)
radius, p1, p2, p3, p4 = force.getParticleParameters(0)
self.assertAlmostEqualUnit(radius, 11.0*nanometer)
self.assertEqual(p1, 12.0)
self.assertEqual(p2, 13.0)
self.assertEqual(p3, 14.0)
self.assertAlmostEqualUnit(p4, 15.0/nanometer**2)
radius, p1, p2, p3, p4 = force.getParticleParameters(1)
self.assertAlmostEqualUnit(radius, 16.0*bohr)
self.assertEqual(p1, 17.0)
self.assertEqual(p2, 18.0)
self.assertEqual(p3, 19.0)
self.assertAlmostEqualUnit(p4, 20.0/bohr**2)
self.assertFalse(force.usesPeriodicBoundaryConditions())
force.setUsesPeriodicBoundaryConditions(True)
self.assertTrue(force.usesPeriodicBoundaryConditions())
force.setUsesPeriodicBoundaryConditions(False)
self.assertFalse(force.usesPeriodicBoundaryConditions())
def testCmapForce(self): def testCmapForce(self):
""" Tests the CMAPTorsionForce API features """ """ Tests the CMAPTorsionForce API features """
map1 = [random.random() for i in range(24*24)] map1 = [random.random() for i in range(24*24)]
......
...@@ -100,7 +100,7 @@ class TestAmberPrmtopFile(unittest.TestCase): ...@@ -100,7 +100,7 @@ class TestAmberPrmtopFile(unittest.TestCase):
""" """
for implicitSolvent_value, gbsa in zip([HCT, OBC1, OBC2, GBn], ['ACE', None, 'ACE', None]): for implicitSolvent_value, gbsa in zip([HCT, OBC1, OBC2, GBn], ['ACE', None, 'ACE', None]):
system = prmtop2.createSystem(implicitSolvent=implicitSolvent_value, gbsaModel=gbsa) system = prmtop2.createSystem(implicitSolvent=implicitSolvent_value, sasaMethod=gbsa)
forces = system.getForces() forces = system.getForces()
if implicitSolvent_value in set([HCT, OBC1, GBn]): if implicitSolvent_value in set([HCT, OBC1, GBn]):
force_type = CustomGBForce force_type = CustomGBForce
...@@ -139,13 +139,26 @@ class TestAmberPrmtopFile(unittest.TestCase): ...@@ -139,13 +139,26 @@ class TestAmberPrmtopFile(unittest.TestCase):
found_matching_solute_dielectric) found_matching_solute_dielectric)
def test_ImplicitSolventZeroSA(self): def test_ImplicitSolventZeroSA(self):
"""Test that requesting gbsaModel=None yields a surface area energy of 0 when """Test that requesting sasaMethod=None yields a surface area energy of 0 when
prmtop.createSystem produces a GBSAOBCForce""" prmtop.createSystem produces a GBSAOBCForce"""
system = prmtop2.createSystem(implicitSolvent=OBC2, gbsaModel=None) system = prmtop2.createSystem(implicitSolvent=OBC2, sasaMethod=None)
for force in system.getForces(): for force in system.getForces():
if isinstance(force, GBSAOBCForce): if isinstance(force, GBSAOBCForce):
self.assertEqual(force.getSurfaceAreaEnergy(), 0*kilojoule/(nanometer**2*mole)) self.assertEqual(force.getSurfaceAreaEnergy(), 0*kilojoule/(nanometer**2*mole))
def test_SASAMethodAlias(self):
"""Tests that gbsaModel is an alias for sasaMethod"""
for method in (None, 'ACE', 'LCPO'):
system1 = prmtop2.createSystem(implicitSolvent=OBC2, sasaMethod=method)
system2 = prmtop2.createSystem(implicitSolvent=OBC2, gbsaModel=method)
self.assertEqual(XmlSerializer.serialize(system1), XmlSerializer.serialize(system2))
def test_SASAMethodDefault(self):
"""Tests that ACE is the default for sasaMethod"""
system1 = prmtop2.createSystem(implicitSolvent=OBC2)
system2 = prmtop2.createSystem(implicitSolvent=OBC2, sasaMethod='ACE')
self.assertEqual(XmlSerializer.serialize(system1), XmlSerializer.serialize(system2))
def test_HydrogenMass(self): def test_HydrogenMass(self):
"""Test that altering the mass of hydrogens works correctly.""" """Test that altering the mass of hydrogens works correctly."""
...@@ -326,6 +339,28 @@ class TestAmberPrmtopFile(unittest.TestCase): ...@@ -326,6 +339,28 @@ class TestAmberPrmtopFile(unittest.TestCase):
diff = norm(f1-f2) diff = norm(f1-f2)
self.assertTrue(diff < 0.1 or diff/norm(f1) < 1e-4) self.assertTrue(diff < 0.1 or diff/norm(f1) < 1e-4)
def test_LCPO(self):
"""Compute LCPO energy and compare it to a reference value from Amber."""
prmtopLCPO = AmberPrmtopFile('systems/lcpo_test.prmtop')
pdb = PDBFile('systems/lcpo_test.pdb')
systemNone = prmtopLCPO.createSystem(implicitSolvent=GBn2, sasaMethod=None)
systemLCPO = prmtopLCPO.createSystem(implicitSolvent=GBn2, sasaMethod='LCPO')
contextNone = Context(systemNone, VerletIntegrator(0.001), Platform.getPlatformByName("Reference"))
contextLCPO = Context(systemLCPO, VerletIntegrator(0.001), Platform.getPlatformByName("Reference"))
contextNone.setPositions(pdb.positions)
contextLCPO.setPositions(pdb.positions)
energyRef = 14.1908 * kilocalorie_per_mole
energyLCPO = contextLCPO.getState(energy=True).getPotentialEnergy() - contextNone.getState(energy=True).getPotentialEnergy()
self.assertAlmostEqual(energyLCPO.value_in_unit(kilocalorie_per_mole), energyRef.value_in_unit(kilocalorie_per_mole), 4)
def test_LCPOInvalid(self):
"""Check that LCPO parameter assignment fails instead of assigning incorrect parameters for unsupported atom types."""
prmtop = AmberPrmtopFile('systems/lcpo_invalid.prmtop')
with self.assertRaisesRegex(ValueError, 'atomic number 8.+2 bonds.+0 bonds excluding H'):
prmtop.createSystem(implicitSolvent=GBn2, sasaMethod='LCPO')
def testSwitchFunction(self): def testSwitchFunction(self):
""" Tests the switching function option in AmberPrmtopFile """ """ Tests the switching function option in AmberPrmtopFile """
system = prmtop1.createSystem(nonbondedMethod=PME, system = prmtop1.createSystem(nonbondedMethod=PME,
...@@ -424,7 +459,7 @@ class TestAmberPrmtopFile(unittest.TestCase): ...@@ -424,7 +459,7 @@ class TestAmberPrmtopFile(unittest.TestCase):
inpcrd = AmberInpcrdFile('systems/DNA_mbondi3.inpcrd') inpcrd = AmberInpcrdFile('systems/DNA_mbondi3.inpcrd')
sanderEnergy = [-19223.87993545, -19527.40433175, -19788.1070698] sanderEnergy = [-19223.87993545, -19527.40433175, -19788.1070698]
for solvent, expectedEnergy in zip([OBC2, GBn, GBn2], sanderEnergy): for solvent, expectedEnergy in zip([OBC2, GBn, GBn2], sanderEnergy):
system = prmtop.createSystem(implicitSolvent=solvent, gbsaModel=None) system = prmtop.createSystem(implicitSolvent=solvent, sasaMethod=None)
for f in system.getForces(): for f in system.getForces():
if isinstance(f, CustomGBForce) or isinstance(f, GBSAOBCForce): if isinstance(f, CustomGBForce) or isinstance(f, GBSAOBCForce):
f.setForceGroup(1) f.setForceGroup(1)
......
...@@ -68,7 +68,7 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -68,7 +68,7 @@ class TestCharmmFiles(unittest.TestCase):
"""Test implicit solvent using the implicitSolvent parameter. """Test implicit solvent using the implicitSolvent parameter.
""" """
system = self.psf_v.createSystem(self.params, implicitSolvent=OBC2, gbsaModel='ACE') system = self.psf_v.createSystem(self.params, implicitSolvent=OBC2, sasaMethod='ACE')
self.assertTrue(any(isinstance(f, CustomGBForce) for f in system.getForces())) self.assertTrue(any(isinstance(f, CustomGBForce) for f in system.getForces()))
def test_ImplicitSolventParameters(self): def test_ImplicitSolventParameters(self):
...@@ -82,6 +82,27 @@ class TestCharmmFiles(unittest.TestCase): ...@@ -82,6 +82,27 @@ class TestCharmmFiles(unittest.TestCase):
if isinstance(force, NonbondedForce): if isinstance(force, NonbondedForce):
self.assertEqual(force.getReactionFieldDielectric(), 1.0) self.assertEqual(force.getReactionFieldDielectric(), 1.0)
def test_SASAMethodAlias(self):
"""Tests that gbsaModel is an alias for sasaMethod"""
for method in (None, 'ACE', 'LCPO'):
system1 = self.psf_c.createSystem(self.params, implicitSolvent=OBC2, sasaMethod=method)
system2 = self.psf_c.createSystem(self.params, implicitSolvent=OBC2, gbsaModel=method)
self.assertEqual(XmlSerializer.serialize(system1), XmlSerializer.serialize(system2))
def test_SASAMethodDefault(self):
"""Tests that None is the default for sasaMethod"""
system1 = self.psf_c.createSystem(self.params, implicitSolvent=OBC2)
system2 = self.psf_c.createSystem(self.params, implicitSolvent=OBC2, sasaMethod=None)
self.assertEqual(XmlSerializer.serialize(system1), XmlSerializer.serialize(system2))
def test_LCPO(self):
"""Check LCPO parameter assignment vs. using a Topology and ForceField."""
system1 = self.psf_v.createSystem(self.params, implicitSolvent=GBn2, sasaMethod='LCPO')
system2 = ForceField('charmm36.xml', 'implicit/gbn2.xml').createSystem(self.pdb.topology, sasaMethod='LCPO')
lcpo1, = (force for force in system1.getForces() if isinstance(force, LCPOForce))
lcpo2, = (force for force in system2.getForces() if isinstance(force, LCPOForce))
self.assertEqual(XmlSerializer.serialize(lcpo1), XmlSerializer.serialize(lcpo2))
def test_HydrogenMass(self): def test_HydrogenMass(self):
"""Test that altering the mass of hydrogens works correctly.""" """Test that altering the mass of hydrogens works correctly."""
......
...@@ -238,6 +238,36 @@ class TestForceField(unittest.TestCase): ...@@ -238,6 +238,36 @@ class TestForceField(unittest.TestCase):
self.assertTrue(found_matching_solvent_dielectric and self.assertTrue(found_matching_solvent_dielectric and
found_matching_solute_dielectric) found_matching_solute_dielectric)
def test_LCPO(self):
"""Check LCPO parameter assignment vs. the Amber implementation."""
prmtop = AmberPrmtopFile('systems/lcpo_test.prmtop')
pdb = PDBFile('systems/lcpo_test.pdb')
system1 = prmtop.createSystem(implicitSolvent=GBn2, sasaMethod='LCPO')
system2 = ForceField('amber14-all.xml', 'implicit/gbn2.xml').createSystem(pdb.topology, sasaMethod='LCPO')
lcpo1, = (force for force in system1.getForces() if isinstance(force, LCPOForce))
lcpo2, = (force for force in system2.getForces() if isinstance(force, LCPOForce))
self.assertEqual(XmlSerializer.serialize(lcpo1), XmlSerializer.serialize(lcpo2))
def test_LCPOInvalid(self):
"""Check that LCPO parameter assignment fails instead of assigning incorrect parameters for unsupported atom types."""
# Build a water molecule.
topology = Topology()
chain = topology.addChain()
residue = topology.addResidue("HOH", chain)
o = topology.addAtom("O", elem.oxygen, residue)
h1 = topology.addAtom("H1", elem.hydrogen, residue)
h2 = topology.addAtom("H2", elem.hydrogen, residue)
topology.addBond(o, h1)
topology.addBond(o, h2)
# Water should be matched correctly but there are no LCPO parameters for
# O bonded to two H atoms, so an exception should be raised.
ff = ForceField('amber14-all.xml', 'amber14/opc3.xml', 'implicit/gbn2.xml')
with self.assertRaisesRegex(ValueError, 'atomic number 8.+2 bonds.+0 bonds excluding H'):
ff.createSystem(topology, sasaMethod='LCPO')
def test_HydrogenMass(self): def test_HydrogenMass(self):
"""Test that altering the mass of hydrogens works correctly.""" """Test that altering the mass of hydrogens works correctly."""
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment