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:
......
"""
lcpo.py: LCPO coefficient tables and setup.
This is part of the OpenMM molecular simulation toolkit.
See https://openmm.org/development.
Portions copyright (c) 2025 Stanford University and the Authors.
Authors: 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.
"""
import collections
import math
import openmm as mm
import openmm.unit as u
LCPO_PARAMETERS = {
# For H atoms, virtual sites, etc.
'none': (0.0, 0.0, 0.0, 0.0, 0.0),
# Weiser, Shenkin and Still, J. Comput. Chem. 20, 217-230 (1999).
'C_sp3_1': (1.7, 0.77887, -0.28063, -0.0012968, 0.00039328),
'C_sp3_2': (1.7, 0.56482, -0.19608, -0.0010219, 0.0002658),
'C_sp3_3': (1.7, 0.23348, -0.072627, -0.00020079, 0.00007967),
'C_sp3_4': (1.7, 0.0, 0.0, 0.0, 0.0),
'C_sp2_2': (1.7, 0.51245, -0.15966, -0.00019781, 0.00016392),
'C_sp2_3': (1.7, 0.070344, -0.019015, -0.000022009, 0.000016875),
'O_sp3_1': (1.6, 0.77914, -0.25262, -0.0016056, 0.00035071),
'O_sp3_2': (1.6, 0.49392, -0.16038, -0.00015512, 0.00016453),
'O_sp2_1': (1.6, 0.68563, -0.1868, -0.00135573, 0.00023743),
'O_carboxylate': (1.6, 0.88857, -0.33421, -0.0018683, 0.00049372),
'N_sp3_1': (1.65, 0.078602, -0.29198, -0.0006537, 0.00036247),
'N_sp3_2': (1.65, 0.22599, -0.036648, -0.0012297, 0.000080038),
'N_sp3_3': (1.65, 0.051481, -0.012603, -0.00032006, 0.000024774),
'N_sp2_1': (1.65, 0.73511, -0.22116, -0.00089148, 0.0002523),
'N_sp2_2': (1.65, 0.41102, -0.12254, -0.000075448, 0.00011804),
'N_sp2_3': (1.65, 0.062577, -0.017874, -0.00008312, 0.000019849),
'S_1': (1.9, 0.7722, -0.26393, 0.0010629, 0.0002179),
'S_2': (1.9, 0.54581, -0.19477, -0.0012873, 0.00029247),
'P_3': (1.9, 0.3865, -0.18249, -0.0036598, 0.0004264),
'P_4': (1.9, 0.03873, -0.0089339, 0.0000083582, 0.0000030381),
'Cl': (1.8, 0.98318, -0.40437, 0.00011249, 0.00049901),
# AmberTools
'F': (1.47, 0.68563, -0.1868, -0.00135573, 0.00023743),
'Mg': (1.18, 0.49392, -0.16038, -0.00015512, 0.00016453),
}
def addLCPOForce(system, paramsList, usePeriodic, surfaceTension=0.005*u.kilocalorie_per_mole/u.angstrom**2, probeRadius=1.4*u.angstrom):
"""
Adds a force to an OpenMM System implementing the LCPO method for estimating
solvent-accessible surface area of a molecule.
Parameters
----------
system : System
The OpenMM System to add the force to.
paramsList : list
A list containing LCPO parameters for each atom, specifically, a sphere
radiuis that does not include a solvent probe radius, and coefficients
P1 through P4 in the LCPO equations.
surfaceTension : energy/area
The energy per area to scale the surface area from the LCPO method by.
probeRadius : distance
The radius of the solvent probe to use.
"""
force = mm.LCPOForce()
force.setSurfaceTension(surfaceTension)
for atomRadius, p1, p2, p3, p4 in paramsList:
force.addParticle(atomRadius + probeRadius if atomRadius else 0, p1, p2, p3, p4)
force.setUsesPeriodicBoundaryConditions(usePeriodic)
system.addForce(force)
def _raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds):
raise ValueError(f'No LCPO parameters found for element with atomic number {atomicNumber}, {numTotalBonds} bonds, and {numHeavyBonds} bonds excluding H')
def getLCPOParamsAmber(prmtop, elements):
"""
Generates LCPO parameters for each atom in an Amber prmtop file.
Parameters
----------
prmtop : PrmtopLoader
The PrmtopLoader object containing data about the prmtop file.
elements : list(Element)
The elements of the atoms in the prmtop file.
Returns
-------
list
A list containing LCPO parameters for each atom, specifically, a sphere
radiuis that does not include a solvent probe radius, and coefficients
P1 through P4 in the LCPO equations.
"""
numHeavyBondsList = [0] * prmtop.getNumAtoms()
for atom1, atom2, _, _ in prmtop.getBondsNoH():
numHeavyBondsList[atom1] += 1
numHeavyBondsList[atom2] += 1
numTotalBondsList = numHeavyBondsList.copy()
for atom1, atom2, _, _ in prmtop.getBondsWithH():
numTotalBondsList[atom1] += 1
numTotalBondsList[atom2] += 1
paramsList = []
for atom, (element, numHeavyBonds, numTotalBonds) in enumerate(zip(elements, numHeavyBondsList, numTotalBondsList)):
# Give atoms with no element 'none' parameters.
atomicNumber = 0 if element is None else element.atomic_number
atomType = prmtop.getAtomType(atom)
params = LCPO_PARAMETERS['none']
# Use Amber logic for selecting parameters, except that in cases where
# Amber would raise an error for assigning incorrect parameters, OpenMM
# will raise an exception.
if atomicNumber == 6:
if numTotalBonds == 4:
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['C_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['C_sp3_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['C_sp3_3']
elif numHeavyBonds == 4:
params = LCPO_PARAMETERS['C_sp3_4']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
else:
if numHeavyBonds == 2:
params = LCPO_PARAMETERS['C_sp2_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['C_sp2_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 8:
if atomType == 'O':
params = LCPO_PARAMETERS['O_sp2_1']
elif atomType == 'O2':
params = LCPO_PARAMETERS['O_carboxylate']
else:
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['O_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['O_sp3_2']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 7:
if atomType == 'N3':
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['N_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['N_sp3_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['N_sp3_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
else:
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['N_sp2_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['N_sp2_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['N_sp2_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 16:
if atomType == 'SH':
params = LCPO_PARAMETERS['S_1']
else:
params = LCPO_PARAMETERS['S_2']
elif atomicNumber == 15:
if numHeavyBonds == 3:
params = LCPO_PARAMETERS['P_3']
elif numHeavyBonds == 4:
params = LCPO_PARAMETERS['P_4']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomType.startswith('Z') or atomicNumber <= 1:
# Use default 'none' parameters.
pass
elif atomType == 'MG':
params = LCPO_PARAMETERS['Mg']
elif atomType == 'F':
params = LCPO_PARAMETERS['F']
elif atomicNumber == 17:
# Cl is the only element in the LCPO paper not implemented in Amber.
params = LCPO_PARAMETERS['Cl']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
paramsList.append((params[0] * u.angstrom, params[1], params[2], params[3], params[4] / u.angstrom ** 2))
return paramsList
def getLCPOParamsTopology(topology):
"""
Generates LCPO parameters for each atom in a Topology.
Parameters
----------
topology : Topology
The Topology object containing element and bond information.
Returns
-------
list
A list containing LCPO parameters for each atom, specifically, a sphere
radiuis that does not include a solvent probe radius, and coefficients
P1 through P4 in the LCPO equations.
"""
numAtoms = topology.getNumAtoms()
atomicNumbers = [0 if atom.element is None else atom.element.atomic_number for atom in topology.atoms()]
bondedToList = [set() for index in range(numAtoms)]
for atom1, atom2 in topology.bonds():
bondedToList[atom1.index].add(atom2.index)
bondedToList[atom2.index].add(atom1.index)
numHeavyBondsList = [0] * numAtoms
numTotalBondsList = [0] * numAtoms
for index1, bondedTo in enumerate(bondedToList):
for index2 in bondedTo:
numTotalBondsList[index1] += 1
if atomicNumbers[index2] > 1:
numHeavyBondsList[index1] += 1
# Identify terminal O atoms (-O(-), =O) and their bonding partners, then
# identify partners that look like carboxylate C or phosphate P atoms. Note
# that we follow Amber's implementation and assign phosphate O atoms the
# LCPO carboxylate O- type. The original LCPO publication does not comment
# on the appropriate parameters for this case, and NAMD's implementation has
# inconsistent behavior between Amber and CHARMM, so we follow Amber here.
terminalO = {}
for index1, (atomicNumber, bondedTo, numTotalBonds) in enumerate(zip(atomicNumbers, bondedToList, numTotalBondsList)):
if atomicNumber == 8 and numTotalBonds == 1:
index2, = bondedTo
if (atomicNumbers[index2], numTotalBondsList[index2]) in (6, 3) or (15, 4):
terminalO[index1] = index2
terminalOPartners = {indexPartner for indexPartner, countO in collections.Counter(terminalO.values()).items() if countO > 1}
# Identify sp2-hybridized Ns with 3 bonding partners based on the
# hybridization of surrounding C atoms. This may fail in a few unusual
# cases but should handle standard amino and nucleic acids correctly.
planarC = set(index for index, (atomicNumber, numTotalBonds) in enumerate(zip(atomicNumbers, numTotalBondsList))
if atomicNumber == 6 and numTotalBonds == 3)
planarN = set(index1 for index1, (atomicNumber, bondedTo, numTotalBonds) in enumerate(zip(atomicNumbers, bondedToList, numTotalBondsList))
if atomicNumber == 7 and numTotalBonds == 3 and any(index2 in planarC for index2 in bondedTo))
paramsList = []
for index, (atomicNumber, numHeavyBonds, numTotalBonds) in enumerate(zip(atomicNumbers, numHeavyBondsList, numTotalBondsList)):
params = LCPO_PARAMETERS['none']
if atomicNumber <= 1:
# Use default 'none' parameters for H and virtual sites.
pass
elif atomicNumber == 6:
if numTotalBonds == 4:
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['C_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['C_sp3_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['C_sp3_3']
elif numHeavyBonds == 4:
params = LCPO_PARAMETERS['C_sp3_4']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
else:
if numTotalBonds != 3:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
if numHeavyBonds == 2:
params = LCPO_PARAMETERS['C_sp2_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['C_sp2_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 7:
if (numTotalBonds == 3 and index not in planarN) or numTotalBonds == 4:
# sp3 N.
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['N_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['N_sp3_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['N_sp3_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
else:
# Fail if this is not an sp2 N.
if not (numTotalBonds == 2 or index in planarN):
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['N_sp2_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['N_sp2_2']
elif numHeavyBonds == 3:
params = LCPO_PARAMETERS['N_sp2_3']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 8:
if numTotalBonds == 1:
# sp2 O (check to see if it is a carboxylate or phosphate O: see
# above regarding assigning the carboxylate type on phosphates).
if index in terminalO and terminalO[index] in terminalOPartners:
params = LCPO_PARAMETERS['O_carboxylate']
else:
params = LCPO_PARAMETERS['O_sp2_1']
else:
# Assume sp3 O (fail if it doesn't have 2 bonds).
if numTotalBonds != 2:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['O_sp3_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['O_sp3_2']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 9:
# Parameters for F are from Amber (not in original LCPO paper).
params = LCPO_PARAMETERS['F']
elif atomicNumber == 12:
# Parameters for Mg are from Amber (not in original LCPO paper).
params = LCPO_PARAMETERS['Mg']
elif atomicNumber == 15:
if numHeavyBonds == 3:
params = LCPO_PARAMETERS['P_3']
elif numHeavyBonds == 4:
params = LCPO_PARAMETERS['P_4']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 16:
if numHeavyBonds == 1:
params = LCPO_PARAMETERS['S_1']
elif numHeavyBonds == 2:
params = LCPO_PARAMETERS['S_2']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
elif atomicNumber == 17:
if numHeavyBonds != 1:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
params = LCPO_PARAMETERS['Cl']
else:
_raiseLCPOException(atomicNumber, numTotalBonds, numHeavyBonds)
paramsList.append((params[0] * u.angstrom, params[1], params[2], params[3], params[4] / u.angstrom ** 2))
return paramsList
...@@ -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."""
......
%VERSION VERSION_STAMP = V0001.000 DATE = 12/04/25 11:41:58
%FLAG TITLE
%FORMAT(20a4)
OP3
%FLAG POINTERS
%FORMAT(10I8)
3 2 3 0 0 0 0 0 0 0
4 1 0 0 0 2 0 0 2 0
0 0 0 0 0 0 0 0 3 0
0
%FLAG ATOM_NAME
%FORMAT(20a4)
O H1 H2
%FLAG CHARGE
%FORMAT(5E16.8)
-1.63120563E+01 8.15602815E+00 8.15602815E+00
%FLAG ATOMIC_NUMBER
%FORMAT(10I8)
8 1 1
%FLAG MASS
%FORMAT(5E16.8)
1.60000000E+01 1.00800000E+00 1.00800000E+00
%FLAG ATOM_TYPE_INDEX
%FORMAT(10I8)
1 2 2
%FLAG NUMBER_EXCLUDED_ATOMS
%FORMAT(10I8)
2 1 1
%FLAG NONBONDED_PARM_INDEX
%FORMAT(10I8)
1 2 2 3
%FLAG RESIDUE_LABEL
%FORMAT(20a4)
WAT
%FLAG RESIDUE_POINTER
%FORMAT(10I8)
1
%FLAG BOND_FORCE_CONSTANT
%FORMAT(5E16.8)
5.53000000E+02 5.53000000E+02
%FLAG BOND_EQUIL_VALUE
%FORMAT(5E16.8)
1.59850700E+00 9.78882000E-01
%FLAG ANGLE_FORCE_CONSTANT
%FORMAT(5E16.8)
%FLAG ANGLE_EQUIL_VALUE
%FORMAT(5E16.8)
%FLAG DIHEDRAL_FORCE_CONSTANT
%FORMAT(5E16.8)
%FLAG DIHEDRAL_PERIODICITY
%FORMAT(5E16.8)
%FLAG DIHEDRAL_PHASE
%FORMAT(5E16.8)
%FLAG SCEE_SCALE_FACTOR
%FORMAT(5E16.8)
%FLAG SCNB_SCALE_FACTOR
%FORMAT(5E16.8)
%FLAG SOLTY
%FORMAT(5E16.8)
0.00000000E+00 0.00000000E+00
%FLAG LENNARD_JONES_ACOEF
%FORMAT(5E16.8)
6.83998173E+05 0.00000000E+00 0.00000000E+00
%FLAG LENNARD_JONES_BCOEF
%FORMAT(5E16.8)
6.68638633E+02 0.00000000E+00 0.00000000E+00
%FLAG BONDS_INC_HYDROGEN
%FORMAT(10I8)
3 6 1 0 3 2 0 6 2
%FLAG BONDS_WITHOUT_HYDROGEN
%FORMAT(10I8)
%FLAG ANGLES_INC_HYDROGEN
%FORMAT(10I8)
%FLAG ANGLES_WITHOUT_HYDROGEN
%FORMAT(10I8)
%FLAG DIHEDRALS_INC_HYDROGEN
%FORMAT(10I8)
%FLAG DIHEDRALS_WITHOUT_HYDROGEN
%FORMAT(10I8)
%FLAG EXCLUDED_ATOMS_LIST
%FORMAT(10I8)
2 3 3 0
%FLAG HBOND_ACOEF
%FORMAT(5E16.8)
%FLAG HBOND_BCOEF
%FORMAT(5E16.8)
%FLAG HBCUT
%FORMAT(5E16.8)
%FLAG AMBER_ATOM_TYPE
%FORMAT(20a4)
OW HW HW
%FLAG TREE_CHAIN_CLASSIFICATION
%FORMAT(20a4)
BLA BLA BLA
%FLAG JOIN_ARRAY
%FORMAT(10I8)
0 0 0
%FLAG IROTAT
%FORMAT(10I8)
0 0 0
%FLAG RADIUS_SET
%FORMAT(1a80)
ArgH and AspGluO modified Bondi2 radii (mbondi3)
%FLAG RADII
%FORMAT(5E16.8)
1.50000000E+00 1.20000000E+00 1.20000000E+00
%FLAG SCREEN
%FORMAT(5E16.8)
8.50000000E-01 8.50000000E-01 8.50000000E-01
%FLAG IPOL
%FORMAT(1I8)
0
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