1 """ @package forcebalance.openmmio OpenMM input/output. 6 from __future__
import division
8 from builtins
import zip
9 from builtins
import range
11 from forcebalance
import BaseReader
26 from copy
import deepcopy
27 from forcebalance.engine
import Engine
32 from collections
import OrderedDict
33 from forcebalance.output
import getLogger
34 logger = getLogger(__name__)
39 import simtk.openmm._openmm
as _openmm
44 """ Given a list of booleans [1, 0, 1] return the bitmask that sets 45 these force groups appropriately in Context.getState(). Any values 46 not provided are defaulted to 1. """ 48 for i, j
in enumerate(grps):
51 for k
in range(i+1, 31):
58 EnergyTerms = OrderedDict()
59 if type(Sim.integrator)
in [LangevinIntegrator, VerletIntegrator]:
60 for i
in range(Sim.system.getNumForces()):
61 EnergyTerms[Sim.system.getForce(i).__class__.__name__] = Sim.context.getState(getEnergy=
True,groups=2**i).getPotentialEnergy() / kilojoules_per_mole
64 def get_multipoles(simulation,q=None,mass=None,positions=None,rmcom=True):
65 """Return the current multipole moments in Debye and Buckingham units. """ 75 enm_debye = 48.03204255928332
76 for i
in simulation.system.getForces():
77 if isinstance(i, AmoebaMultipoleForce):
78 mm = i.getSystemMultipoleMoments(simulation.context)
88 if isinstance(i, NonbondedForce):
91 q = np.array([i.getParticleParameters(j)[0]._value
for j
in range(i.getNumParticles())])
94 positions = simulation.context.getState(getPositions=
True).getPositions()
96 mass = np.array([simulation.context.getSystem().getParticleMass(k).value_in_unit(dalton) \
97 for k
in range(simulation.context.getSystem().getNumParticles())])
98 x = np.array(positions.value_in_unit(nanometer))
100 com = np.sum(x*mass.reshape(-1,1),axis=0) / np.sum(mass)
102 xx, xy, xz, yy, yz, zz = (x[:,k]*x[:,l]
for k, l
in [(0,0),(0,1),(0,2),(1,1),(1,2),(2,2)])
104 dip = enm_debye * np.sum(x*q.reshape(-1,1),axis=0)
108 qxx += enm_debye * 15 * np.sum(q*xx)
109 qxy += enm_debye * 15 * np.sum(q*xy)
110 qxz += enm_debye * 15 * np.sum(q*xz)
111 qyy += enm_debye * 15 * np.sum(q*yy)
112 qyz += enm_debye * 15 * np.sum(q*yz)
113 qzz += enm_debye * 15 * np.sum(q*zz)
119 return [dx,dy,dz,qxx,qxy,qyy,qxz,qyz,qzz]
121 def get_dipole(simulation,q=None,mass=None,positions=None):
122 """Return the current dipole moment in Debye. 123 Note that this quantity is meaningless if the system carries a net charge.""" 124 return get_multipoles(simulation, q=q, mass=mass, positions=positions, rmcom=
False)[:3]
127 """ Prepare a list of function wrappers and vsite parameters from the system. """ 132 for i
in range(system.getNumParticles()):
133 if system.isVirtualSite(i):
135 vs = system.getVirtualSite(i)
136 if isinstance(vs, TwoParticleAverageSite):
137 vsidx = [_openmm.VirtualSite_getParticle(vs, 0), _openmm.VirtualSite_getParticle(vs, 1)]
138 vswt = [_openmm.TwoParticleAverageSite_getWeight(vs, 0), _openmm.TwoParticleAverageSite_getWeight(vs, 1)]
139 def vsfunc(pos, idx_, wt_):
140 return wt_[0]*pos[idx_[0]] + wt_[1]*pos[idx_[1]]
141 elif isinstance(vs, ThreeParticleAverageSite):
142 vsidx = [_openmm.VirtualSite_getParticle(vs, 0), _openmm.VirtualSite_getParticle(vs, 1), _openmm.VirtualSite_getParticle(vs, 2)]
143 vswt = [_openmm.ThreeParticleAverageSite_getWeight(vs, 0), _openmm.ThreeParticleAverageSite_getWeight(vs, 1), _openmm.ThreeParticleAverageSite_getWeight(vs, 2)]
144 def vsfunc(pos, idx_, wt_):
145 return wt_[0]*pos[idx_[0]] + wt_[1]*pos[idx_[1]] + wt_[2]*pos[idx_[2]]
146 elif isinstance(vs, OutOfPlaneSite):
147 vsidx = [_openmm.VirtualSite_getParticle(vs, 0), _openmm.VirtualSite_getParticle(vs, 1), _openmm.VirtualSite_getParticle(vs, 2)]
148 vswt = [_openmm.OutOfPlaneSite_getWeight12(vs), _openmm.OutOfPlaneSite_getWeight13(vs), _openmm.OutOfPlaneSite_getWeightCross(vs)]
149 def vsfunc(pos, idx_, wt_):
150 v1 = pos[idx_[1]] - pos[idx_[0]]
151 v2 = pos[idx_[2]] - pos[idx_[0]]
152 cross = np.array([v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0]])
153 return pos[idx_[0]] + wt_[0]*v1 + wt_[1]*v2 + wt_[2]*cross
159 vsfuncs.append(deepcopy(vsfunc))
160 vsidxs.append(deepcopy(vsidx))
161 vswts.append(deepcopy(vswt))
162 return (isvsites, vsfuncs, vsidxs, vswts)
165 """Given a set of OpenMM-compatible positions and a System object, 166 compute the correct virtual site positions according to the System.""" 167 isvsites, vsfuncs, vsidxs, vswts = vsinfo
169 pos = np.array(positions.value_in_unit(nanometer))
170 for i
in range(len(positions)):
172 pos[i] = vsfuncs[i](pos, vsidxs[i], vswts[i])
173 newpos = [Vec3(*i)
for i
in pos]*nanometer
179 """Given a set of OpenMM-compatible positions and a System object, 180 compute the correct virtual site positions according to the System.""" 181 if any([system.isVirtualSite(j)
for j
in range(system.getNumParticles())]):
182 pos = np.array(positions.value_in_unit(nanometer))
183 for i
in range(system.getNumParticles()):
184 if system.isVirtualSite(i):
185 vs = system.getVirtualSite(i)
187 if isinstance(vs, TwoParticleAverageSite):
188 vspos = _openmm.TwoParticleAverageSite_getWeight(vs, 0)*pos[_openmm.VirtualSite_getParticle(vs, 0)] \
189 + _openmm.TwoParticleAverageSite_getWeight(vs, 1)*pos[_openmm.VirtualSite_getParticle(vs, 1)]
190 elif isinstance(vs, ThreeParticleAverageSite):
191 vspos = _openmm.ThreeParticleAverageSite_getWeight(vs, 0)*pos[_openmm.VirtualSite_getParticle(vs, 0)] \
192 + _openmm.ThreeParticleAverageSite_getWeight(vs, 1)*pos[_openmm.VirtualSite_getParticle(vs, 1)] \
193 + _openmm.ThreeParticleAverageSite_getWeight(vs, 2)*pos[_openmm.VirtualSite_getParticle(vs, 2)]
194 elif isinstance(vs, OutOfPlaneSite):
195 v1 = pos[_openmm.VirtualSite_getParticle(vs, 1)] - pos[_openmm.VirtualSite_getParticle(vs, 0)]
196 v2 = pos[_openmm.VirtualSite_getParticle(vs, 2)] - pos[_openmm.VirtualSite_getParticle(vs, 0)]
197 cross = Vec3(v1[1]*v2[2]-v1[2]*v2[1], v1[2]*v2[0]-v1[0]*v2[2], v1[0]*v2[1]-v1[1]*v2[0])
198 vspos = pos[_openmm.VirtualSite_getParticle(vs, 0)] + _openmm.OutOfPlaneSite_getWeight12(vs)*v1 \
199 + _openmm.OutOfPlaneSite_getWeight13(vs)*v2 + _openmm.OutOfPlaneSite_getWeightCross(vs)*cross
201 newpos = [Vec3(*i)
for i
in pos]*nanometer
203 else:
return positions
206 """Return an array of all virtual site parameters in the system. Used for comparison purposes.""" 208 for i
in range(system.getNumParticles()):
209 if system.isVirtualSite(i):
210 vs = system.getVirtualSite(i)
211 vstype = vs.__class__.__name__
212 if vstype ==
'TwoParticleAverageSite':
213 vsprm.append(_openmm.TwoParticleAverageSite_getWeight(vs, 0))
214 vsprm.append(_openmm.TwoParticleAverageSite_getWeight(vs, 1))
215 elif vstype ==
'ThreeParticleAverageSite':
216 vsprm.append(_openmm.ThreeParticleAverageSite_getWeight(vs, 0))
217 vsprm.append(_openmm.ThreeParticleAverageSite_getWeight(vs, 1))
218 vsprm.append(_openmm.ThreeParticleAverageSite_getWeight(vs, 2))
219 elif vstype ==
'OutOfPlaneSite':
220 vsprm.append(_openmm.OutOfPlaneSite_getWeight12(vs))
221 vsprm.append(_openmm.OutOfPlaneSite_getWeight13(vs))
222 vsprm.append(_openmm.OutOfPlaneSite_getWeightCross(vs))
223 return np.array(vsprm)
226 dest.setAmoebaGlobalBondCubic(src.getAmoebaGlobalBondCubic())
227 dest.setAmoebaGlobalBondQuartic(src.getAmoebaGlobalBondQuartic())
228 for i
in range(src.getNumBonds()):
229 dest.setBondParameters(i,*src.getBondParameters(i))
232 dest.setAmoebaGlobalOutOfPlaneBendCubic(src.getAmoebaGlobalOutOfPlaneBendCubic())
233 dest.setAmoebaGlobalOutOfPlaneBendQuartic(src.getAmoebaGlobalOutOfPlaneBendQuartic())
234 dest.setAmoebaGlobalOutOfPlaneBendPentic(src.getAmoebaGlobalOutOfPlaneBendPentic())
235 dest.setAmoebaGlobalOutOfPlaneBendSextic(src.getAmoebaGlobalOutOfPlaneBendSextic())
236 for i
in range(src.getNumOutOfPlaneBends()):
237 dest.setOutOfPlaneBendParameters(i,*src.getOutOfPlaneBendParameters(i))
240 dest.setAmoebaGlobalAngleCubic(src.getAmoebaGlobalAngleCubic())
241 dest.setAmoebaGlobalAngleQuartic(src.getAmoebaGlobalAngleQuartic())
242 dest.setAmoebaGlobalAnglePentic(src.getAmoebaGlobalAnglePentic())
243 dest.setAmoebaGlobalAngleSextic(src.getAmoebaGlobalAngleSextic())
244 for i
in range(src.getNumAngles()):
245 dest.setAngleParameters(i,*src.getAngleParameters(i))
249 dest.setAmoebaGlobalInPlaneAngleCubic(src.getAmoebaGlobalInPlaneAngleCubic())
250 dest.setAmoebaGlobalInPlaneAngleQuartic(src.getAmoebaGlobalInPlaneAngleQuartic())
251 dest.setAmoebaGlobalInPlaneAnglePentic(src.getAmoebaGlobalInPlaneAnglePentic())
252 dest.setAmoebaGlobalInPlaneAngleSextic(src.getAmoebaGlobalInPlaneAngleSextic())
253 for i
in range(src.getNumAngles()):
254 dest.setAngleParameters(i,*src.getAngleParameters(i))
258 for i
in range(src.getNumParticles()):
259 dest.setParticleParameters(i,*src.getParticleParameters(i))
262 for i
in range(src.getNumMultipoles()):
263 dest.setMultipoleParameters(i,*src.getMultipoleParameters(i))
266 for i
in range(src.getNumBonds()):
267 dest.setBondParameters(i,*src.getBondParameters(i))
270 for i
in range(src.getNumAngles()):
271 dest.setAngleParameters(i,*src.getAngleParameters(i))
274 for i
in range(src.getNumTorsions()):
275 dest.setTorsionParameters(i,*src.getTorsionParameters(i))
278 dest.setReactionFieldDielectric(src.getReactionFieldDielectric())
279 for i
in range(src.getNumParticles()):
280 dest.setParticleParameters(i,*src.getParticleParameters(i))
281 for i
in range(src.getNumExceptions()):
282 dest.setExceptionParameters(i,*src.getExceptionParameters(i))
285 dest.setSolventDielectric(src.getSolventDielectric())
286 dest.setSoluteDielectric(src.getSoluteDielectric())
287 for i
in range(src.getNumParticles()):
288 dest.setParticleParameters(i,*src.getParticleParameters(i))
292 copy whatever updateParametersInContext can update: 293 per-particle parameters 295 for i
in range(src.getNumParticles()):
296 dest.setParticleParameters(i, list(src.getParticleParameters(i)))
302 """Copy parameters from one system (i.e. that which is created by a new force field) 303 sto another system (i.e. the one stored inside the Target object). 304 DANGER: These need to be implemented manually!!!""" 305 Copiers = {
'AmoebaBondForce':CopyAmoebaBondParameters,
306 'AmoebaOutOfPlaneBendForce':CopyAmoebaOutOfPlaneBendParameters,
307 'AmoebaAngleForce':CopyAmoebaAngleParameters,
308 'AmoebaInPlaneAngleForce':CopyAmoebaInPlaneAngleParameters,
309 'AmoebaVdwForce':CopyAmoebaVdwParameters,
310 'AmoebaMultipoleForce':CopyAmoebaMultipoleParameters,
311 'HarmonicBondForce':CopyHarmonicBondParameters,
312 'HarmonicAngleForce':CopyHarmonicAngleParameters,
313 'PeriodicTorsionForce':CopyPeriodicTorsionParameters,
314 'NonbondedForce':CopyNonbondedParameters,
315 'CustomNonbondedForce':CopyCustomNonbondedParameters,
316 'GBSAOBCForce':CopyGBSAOBCParameters,
317 'CMMotionRemover':do_nothing}
318 for i
in range(src.getNumForces()):
319 nm = src.getForce(i).__class__.__name__
321 Copiers[nm](src.getForce(i),dest.getForce(i))
323 warn_press_key(
'There is no Copier function implemented for the OpenMM force type %s!' % nm)
327 for i
in range(src_system.getNumForces()):
328 if hasattr(dest_simulation.system.getForce(i),
'updateParametersInContext'):
329 dest_simulation.system.getForce(i).updateParametersInContext(dest_simulation.context)
330 if isinstance(dest_simulation.system.getForce(i), CustomNonbondedForce):
331 force = src_system.getForce(i)
332 for j
in range(force.getNumGlobalParameters()):
333 pName = force.getGlobalParameterName(j)
334 pValue = force.getGlobalParameterDefaultValue(j)
335 dest_simulation.context.setParameter(pName, pValue)
339 if any([f.__class__.__name__ ==
"AmoebaMultipoleForce" for f
in system.getForces()]):
341 vss = [(i, [system.getVirtualSite(i).getParticle(j)
for j
in range(system.getVirtualSite(i).getNumParticles())]) \
342 for i
in range(system.getNumParticles())
if system.isVirtualSite(i)]
343 for f
in system.getForces():
344 if f.__class__.__name__ ==
"AmoebaMultipoleForce":
349 f.setCovalentMap(i, 0, j)
350 f.setCovalentMap(i, 4, j+[i])
352 f.setCovalentMap(k, 0, list(f.getCovalentMap(k, 0))+[i])
353 f.setCovalentMap(k, 4, list(f.getCovalentMap(k, 4))+[i])
360 for ir, R
in enumerate(list(mod.topology.residues())):
363 for vs
in ff._templates[R.name].virtualSites:
366 bi = sorted([A[ai], A[vi]])
368 mod.topology.addBond(*bi)
371 """ Manually set the AmoebaVdwForce, AmoebaMultipoleForce to exclude all atoms belonging to the same residue """ 373 atom_residue_index = [a.residue.index
for a
in topology.atoms()]
374 residue_atoms = [[a.index
for a
in r.atoms()]
for r
in topology.residues()]
375 for f
in system.getForces():
376 if f.__class__.__name__ ==
"AmoebaVdwForce":
377 for i
in range(f.getNumParticles()):
378 f.setParticleExclusions(i, residue_atoms[atom_residue_index[i]])
379 elif f.__class__.__name__ ==
"AmoebaMultipoleForce":
380 for i
in range(f.getNumMultipoles()):
381 f.setCovalentMap(i, 0, residue_atoms[atom_residue_index[i]])
382 for m
in range(1, 4):
383 f.setCovalentMap(i, m, [])
385 def MTSVVVRIntegrator(temperature, collision_rate, timestep, system, ninnersteps=4):
387 Create a multiple timestep velocity verlet with velocity randomization (VVVR) integrator. 391 temperature (Quantity compatible with kelvin) - the temperature 392 collision_rate (Quantity compatible with 1/picoseconds) - the collision rate 393 timestep (Quantity compatible with femtoseconds) - the integration timestep 394 system (simtk.openmm.System) - system whose forces will be partitioned 395 ninnersteps (int) - number of inner timesteps (default: 4) 399 integrator (openmm.CustomIntegrator) - a VVVR integrator 403 This integrator is equivalent to a Langevin integrator in the velocity Verlet discretization with a 404 timestep correction to ensure that the field-free diffusion constant is timestep invariant. The inner 405 velocity Verlet discretization is transformed into a multiple timestep algorithm. 409 VVVR Langevin integrator: 410 * http://arxiv.org/abs/1301.3800 411 * http://arxiv.org/abs/1107.2967 (to appear in PRX 2013) 415 Move initialization of 'sigma' to setting the per-particle variables. 419 for i
in system.getForces():
420 if i.__class__.__name__
in [
"NonbondedForce",
"CustomNonbondedForce",
"AmoebaVdwForce",
"AmoebaMultipoleForce"]:
422 logger.info(i.__class__.__name__ +
" is a Slow Force\n")
425 logger.info(i.__class__.__name__ +
" is a Fast Force\n")
429 kB = BOLTZMANN_CONSTANT_kB * AVOGADRO_CONSTANT_NA
430 kT = kB * temperature
432 integrator = CustomIntegrator(timestep)
434 integrator.addGlobalVariable(
"dt_fast", timestep/float(ninnersteps))
435 integrator.addGlobalVariable(
"kT", kT)
436 integrator.addGlobalVariable(
"a", np.exp(-collision_rate*timestep))
437 integrator.addGlobalVariable(
"b", np.sqrt((2/(collision_rate*timestep)) * np.tanh(collision_rate*timestep/2)))
438 integrator.addPerDofVariable(
"sigma", 0)
439 integrator.addPerDofVariable(
"x1", 0)
446 integrator.addComputePerDof(
"sigma",
"sqrt(kT/m)")
451 integrator.addComputePerDof(
"v",
"sqrt(a)*v + sqrt(1-a)*sigma*gaussian")
452 integrator.addConstrainVelocities();
457 integrator.addUpdateContextState();
458 integrator.addComputePerDof(
"v",
"v + 0.5*b*dt*f1/m")
459 for innerstep
in range(ninnersteps):
461 integrator.addComputePerDof(
"v",
"v + 0.5*b*dt_fast*f0/m")
462 integrator.addComputePerDof(
"x",
"x + v*b*dt_fast")
463 integrator.addComputePerDof(
"x1",
"x")
464 integrator.addConstrainPositions();
465 integrator.addComputePerDof(
"v",
"v + 0.5*b*dt_fast*f0/m + (x-x1)/dt_fast")
466 integrator.addComputePerDof(
"v",
"v + 0.5*b*dt*f1/m")
467 integrator.addConstrainVelocities();
472 integrator.addComputePerDof(
"v",
"sqrt(a)*v + sqrt(1-a)*sigma*gaussian")
473 integrator.addConstrainVelocities();
478 """Dictionary for building parameter identifiers. As usual they go like this: 480 The dictionary is two-layered because the same interaction type (Bond) 481 could be under two different parent types (HarmonicBondForce, AmoebaHarmonicBondForce)""" 482 suffix_dict = {
"HarmonicBondForce" : {
"Bond" : [
"class1",
"class2"]},
483 "HarmonicAngleForce" : {
"Angle" : [
"class1",
"class2",
"class3"],},
484 "PeriodicTorsionForce" : {
"Proper" : [
"class1",
"class2",
"class3",
"class4"],},
485 "NonbondedForce" : {
"Atom": [
"type"]},
486 "CustomNonbondedForce" : {
"Atom": [
"class"]},
487 "GBSAOBCForce" : {
"Atom": [
"type"]},
488 "AmoebaBondForce" : {
"Bond" : [
"class1",
"class2"]},
489 "AmoebaAngleForce" : {
"Angle" : [
"class1",
"class2",
"class3"]},
490 "AmoebaStretchBendForce" : {
"StretchBend" : [
"class1",
"class2",
"class3"]},
491 "AmoebaVdwForce" : {
"Vdw" : [
"class"]},
492 "AmoebaMultipoleForce" : {
"Multipole" : [
"type",
"kz",
"kx"],
"Polarize" : [
"type"]},
493 "AmoebaUreyBradleyForce" : {
"UreyBradley" : [
"class1",
"class2",
"class3"]},
494 "Residues.Residue" : {
"VirtualSite" : [
"index"]},
496 "ForceBalance" : {
"GB": [
"type"]},
500 pdict =
"XML_Override" 502 class OpenMM_Reader(BaseReader):
503 """ Class for parsing OpenMM force field files. """ 504 def __init__(self,fnm):
506 super(OpenMM_Reader,self).__init__(fnm)
510 def build_pid(self, element, parameter):
511 """ Build the parameter identifier (see _link_ for an example) 512 @todo Add a link here """ 514 ParentType =
".".join([i.tag
for i
in list(element.iterancestors())][::-1][1:])
515 InteractionType = element.tag
517 if ParentType ==
"Residues.Residue":
518 pfx = list(element.iterancestors())[0].attrib[
"name"]
519 Involved =
'.'.join([pfx+
"-"+element.attrib[i]
for i
in suffix_dict[ParentType][InteractionType]])
521 Involved1 =
'.'.join([element.attrib[i]
for i
in suffix_dict[ParentType][InteractionType]
if i
in element.attrib])
522 suffix2 = [i.replace(
'class',
'type')
for i
in suffix_dict[ParentType][InteractionType]]
523 suffix3 = [i.replace(
'type',
'class')
for i
in suffix_dict[ParentType][InteractionType]]
524 Involved2 =
'.'.join([element.attrib[i]
for i
in suffix2
if i
in element.attrib])
525 Involved3 =
'.'.join([element.attrib[i]
for i
in suffix3
if i
in element.attrib])
527 Involved = [Involved1, Involved2, Involved3][np.argmax(np.array([len(Involved1),len(Involved2),len(Involved3)]))]
528 return "/".join([InteractionType, parameter, Involved])
530 logger.info(
"Minor warning: Parameter ID %s doesn't contain any atom types, redundancies are possible\n" % (
"/".join([InteractionType, parameter])))
531 return "/".join([InteractionType, parameter])
533 class OpenMM(Engine):
535 """ Derived from Engine object for carrying out general purpose OpenMM calculations. """ 537 def __init__(self, name="openmm", **kwargs):
538 if not hasattr(self,
'valkwd'):
539 self.valkwd = [
'ffxml',
'pdb',
'platname',
'precision',
'mmopts',
'vsite_bonds',
'implicit_solvent',
'restrain_k',
'freeze_atoms']
540 super(OpenMM,self).__init__(name=name, **kwargs)
542 def setopts(self, platname="CUDA", precision="single", **kwargs):
544 """ Called by __init__ ; Set OpenMM-specific options. """ 547 if hasattr(self,
'target'):
548 self.platname = self.target.platname
549 self.precision = self.target.precision
551 self.platname = platname
552 self.precision = precision
554 valnames = [Platform.getPlatform(i).getName()
for i
in range(Platform.getNumPlatforms())]
555 if self.platname
not in valnames:
556 warn_press_key(
"Platform %s does not exist (valid options are %s (case-sensitive))" % (self.platname, valnames))
557 self.platname =
'Reference' 558 self.precision = self.precision.lower()
559 valprecs = [
'single',
'mixed',
'double']
560 if self.precision
not in valprecs:
561 logger.error(
"Please specify one of %s for precision\n" % valprecs)
564 if self.verbose: logger.info(
"Setting Platform to %s\n" % self.platname)
565 self.platform = Platform.getPlatformByName(self.platname)
566 if self.platname ==
'CUDA':
568 device = os.environ.get(
'CUDA_DEVICE',
"0")
569 if self.verbose: logger.info(
"Setting CUDA Device to %s\n" % device)
570 self.platform.setPropertyDefaultValue(
"CudaDeviceIndex", device)
571 if self.verbose: logger.info(
"Setting CUDA Precision to %s\n" % self.precision)
572 self.platform.setPropertyDefaultValue(
"CudaPrecision", self.precision)
573 elif self.platname ==
'OpenCL':
574 device = os.environ.get(
'OPENCL_DEVICE',
"0")
575 if self.verbose: logger.info(
"Setting OpenCL Device to %s\n" % device)
576 self.platform.setPropertyDefaultValue(
"OpenCLDeviceIndex", device)
577 if self.verbose: logger.info(
"Setting OpenCL Precision to %s\n" % self.precision)
578 self.platform.setPropertyDefaultValue(
"OpenCLPrecision", self.precision)
580 if 'implicit_solvent' in kwargs:
582 self.ism = kwargs[
'implicit_solvent']
586 def readsrc(self, **kwargs):
588 """ Called by __init__ ; read files from the source directory. 589 Provide a molecule object or a coordinate file. 590 Add an optional PDB file for residues, atom names etc. """ 594 if 'pdb' in kwargs
and os.path.exists(kwargs[
'pdb']):
596 pdbfnm = kwargs[
'pdb']
597 if not os.path.exists(pdbfnm):
598 logger.error(
"%s specified but doesn't exist\n" % pdbfnm)
602 self.mol = kwargs[
'mol']
603 elif 'coords' in kwargs:
604 self.mol = Molecule(kwargs[
'coords'])
605 if pdbfnm
is None and kwargs[
'coords'].endswith(
'.pdb'):
606 pdbfnm = kwargs[
'coords']
608 logger.error(
'Must provide either a molecule object or coordinate file.\n')
614 if pdbfnm
is not None:
615 self.abspdb = os.path.abspath(pdbfnm)
616 mpdb = Molecule(pdbfnm)
617 for i
in [
"chain",
"atomname",
"resid",
"resname",
"elem"]:
618 self.mol.Data[i] = mpdb.Data[i]
621 self.ref_mol = deepcopy(self.mol)
623 def prepare(self, pbc=False, mmopts={}, **kwargs):
626 Prepare the calculation. Note that we don't create the 627 Simulation object yet, because that may depend on MD 628 integrator parameters, thermostat, barostat etc. 638 if hasattr(self,
'abspdb'):
639 self.pdb = PDBFile(self.abspdb)
641 pdb1 =
"%s-1.pdb" % os.path.splitext(os.path.basename(self.mol.fnm))[0]
642 self.mol[0].write(pdb1)
643 self.pdb = PDBFile(pdb1)
647 if hasattr(self,
'FF'):
648 self.ffxml = [self.FF.openmmxml]
649 self.forcefield = ForceField(os.path.join(self.root, self.FF.ffdir, self.FF.openmmxml))
651 self.ffxml =
listfiles(kwargs.get(
'ffxml'),
'xml', err=
True)
652 self.forcefield = ForceField(*self.ffxml)
656 self.vbonds = kwargs.get(
'vsite_bonds', 0)
659 self.mmopts = dict(mmopts)
662 self.AMOEBA = any([
'Amoeba' in f.__class__.__name__
for f
in self.forcefield._forces])
665 if 'restrain_k' in kwargs:
666 self.restrain_k = kwargs[
'restrain_k']
667 if 'freeze_atoms' in kwargs:
668 self.freeze_atoms = kwargs[
'freeze_atoms'][:]
671 if hasattr(self,
'FF'):
673 if self.FF.amoeba_pol
is None:
674 logger.error(
'You must specify amoeba_pol if there are any AMOEBA forces.\n')
676 if self.FF.amoeba_pol ==
'mutual':
677 self.mmopts[
'polarization'] =
'mutual' 678 self.mmopts.setdefault(
'mutualInducedTargetEpsilon', self.FF.amoeba_eps
if self.FF.amoeba_eps
is not None else 1e-6)
679 self.mmopts[
'mutualInducedMaxIterations'] = 500
680 elif self.FF.amoeba_pol ==
'direct':
681 self.mmopts[
'polarization'] =
'direct' 682 self.mmopts[
'rigidWater'] = self.FF.rigid_water
683 if self.FF.constrain_h
is True:
684 self.mmopts[
'constraints'] = HBonds
685 logger.info(
'Constraining hydrogen bond lengths (SHAKE)')
690 minbox = min([self.mol.boxes[0].a, self.mol.boxes[0].b, self.mol.boxes[0].c])
696 self.mmopts.setdefault(
'nonbondedMethod', PME)
698 nonbonded_cutoff = kwargs.get(
'nonbonded_cutoff', 7.0)
699 vdw_cutoff = kwargs.get(
'nonbonded_cutoff', 8.5)
700 vdw_cutoff = kwargs.get(
'vdw_cutoff', vdw_cutoff)
702 nonbonded_cutoff /= 10
704 if 'nonbonded_cutoff' in kwargs
and 'vdw_cutoff' not in kwargs:
705 warn_press_key(
'AMOEBA detected and nonbonded_cutoff is set, but not vdw_cutoff; it will be set equal to nonbonded_cutoff')
706 if nonbonded_cutoff > 0.05*(float(int(minbox - 1))):
707 warn_press_key(
"nonbonded_cutoff = %.1f should be smaller than half the box size = %.1f Angstrom" % (nonbonded_cutoff*10, minbox))
708 if vdw_cutoff > 0.05*(float(int(minbox - 1))):
709 warn_press_key(
"vdw_cutoff = %.1f should be smaller than half the box size = %.1f Angstrom" % (vdw_cutoff*10, minbox))
710 self.mmopts.setdefault(
'nonbondedCutoff', nonbonded_cutoff*nanometer)
711 self.mmopts.setdefault(
'vdwCutoff', vdw_cutoff*nanometer)
712 self.mmopts.setdefault(
'aEwald', 5.4459052)
715 if 'vdw_cutoff' in kwargs:
716 warn_press_key(
'AMOEBA not detected, your provided vdw_cutoff will not be used')
717 nonbonded_cutoff = kwargs.get(
'nonbonded_cutoff', 8.5)
719 nonbonded_cutoff /= 10
720 if nonbonded_cutoff > 0.05*(float(int(minbox - 1))):
721 warn_press_key(
"nonbonded_cutoff = %.1f should be smaller than half the box size = %.1f Angstrom" % (nonbonded_cutoff*10, minbox))
723 self.mmopts.setdefault(
'nonbondedCutoff', nonbonded_cutoff*nanometer)
724 self.mmopts.setdefault(
'useSwitchingFunction',
True)
725 self.mmopts.setdefault(
'switchingDistance', (nonbonded_cutoff-0.1)*nanometer)
726 self.mmopts.setdefault(
'useDispersionCorrection',
True)
728 if 'nonbonded_cutoff' in kwargs
or 'vdw_cutoff' in kwargs:
729 warn_press_key(
'No periodic boundary conditions, your provided nonbonded_cutoff and vdw_cutoff will not be used')
731 self.mmopts.setdefault(
'nonbondedMethod', NoCutoff)
732 self.mmopts[
'removeCMMotion'] =
False 735 mod = self.generate_xyz_omm(self.mol)
737 Top = mod.getTopology()
738 Atoms = list(Top.atoms())
739 Bonds = [(a.index, b.index)
for a, b
in list(Top.bonds())]
743 self.AtomLists = defaultdict(list)
744 self.AtomLists[
'Mass'] = [a.element.mass.value_in_unit(dalton)
if a.element
is not None else 0
for a
in Atoms]
745 self.AtomLists[
'ParticleType'] = [
'A' if m >= 1.0
else 'D' for m
in self.AtomLists[
'Mass']]
746 self.AtomLists[
'ResidueNumber'] = [a.residue.index
for a
in Atoms]
747 self.AtomMask = [a ==
'A' for a
in self.AtomLists[
'ParticleType']]
748 self.realAtomIdxs = [i
for i, a
in enumerate(self.AtomMask)
if a
is True]
750 def generate_xyz_omm(self, mol):
753 for I
in range(len(mol)):
755 xyz_omm = [Vec3(i[0],i[1],i[2])
for i
in xyz]*angstrom
757 mod = Modeller(self.pdb.topology, xyz_omm)
758 mod.addExtraParticles(self.forcefield)
761 if mol.boxes[I].alpha != 90.0
or mol.boxes[I].beta != 90.0
or mol.boxes[I].gamma != 90.0:
762 logger.error(
'OpenMM cannot handle nonorthogonal boxes.\n')
764 box_omm = [Vec3(mol.boxes[I].a, 0, 0)*angstrom,
765 Vec3(0, mol.boxes[I].b, 0)*angstrom,
766 Vec3(0, 0, mol.boxes[I].c)*angstrom]
770 self.xyz_omms.append((mod.getPositions(), box_omm))
773 def create_simulation(self, timestep=1.0, faststep=0.25, temperature=None, pressure=None, anisotropic=False, mts=False, collision=1.0, nbarostat=25, rpmd_beads=0, **kwargs):
776 Create simulation object. Note that this also takes in some 777 options pertinent to system setup, including the type of MD 778 integrator and type of pressure control. 789 logger.error(
"No multiple timestep integrator without temperature control.\n")
792 timestep*femtosecond, self.system, ninnersteps=int(timestep/faststep))
795 logger.info(
"Creating RPMD integrator with %i beads.\n" % rpmd_beads)
796 self.tdiv = rpmd_beads
797 integrator = RPMDIntegrator(rpmd_beads, temperature*kelvin, collision/picosecond, timestep*femtosecond)
799 integrator = LangevinIntegrator(temperature*kelvin, collision/picosecond, timestep*femtosecond)
803 logger.error(
"No RPMD integrator without temperature control.\n")
805 if mts:
warn_once(
"No multiple timestep integrator without temperature control.")
806 integrator = VerletIntegrator(timestep*femtoseconds)
809 if pressure
is not None:
811 barostat = MonteCarloAnisotropicBarostat([pressure, pressure, pressure]*atmospheres,
812 temperature*kelvin, nbarostat)
814 barostat = MonteCarloBarostat(pressure*atmospheres, temperature*kelvin, nbarostat)
815 if self.pbc
and pressure
is not None: self.system.addForce(barostat)
816 elif pressure
is not None:
warn_once(
"Pressure is ignored because pbc is set to False.")
819 self.restraint_frc_index =
None 820 if hasattr(self,
'restrain_k')
and self.restrain_k != 0.0:
821 restraint_frc = CustomExternalForce(
"0.5*k*((x-x0)^2+(y-y0)^2+(z-z0)^2)")
822 restraint_frc.addGlobalParameter(
"k", self.restrain_k * kilocalorie_per_mole / angstrom**2)
823 restraint_frc.addPerParticleParameter(
"x0")
824 restraint_frc.addPerParticleParameter(
"y0")
825 restraint_frc.addPerParticleParameter(
"z0")
826 for i, j
in enumerate(self.realAtomIdxs):
827 restraint_frc.addParticle(j)
828 restraint_frc.setParticleParameters(i, j, [0.0, 0.0, 0.0])
829 self.restraint_frc_index = self.system.addForce(restraint_frc)
832 if hasattr(self,
'freeze_atoms'):
833 for i
in self.freeze_atoms:
834 j = self.realAtomIdxs[i]
835 self.system.setParticleMass(j, 0.0)
838 GrpTogether = [
'AmoebaGeneralizedKirkwoodForce',
'AmoebaMultipoleForce',
'AmoebaWcaDispersionForce',
839 'CustomNonbondedForce',
'NonbondedForce']
842 for j
in self.system.getForces():
844 if j.__class__.__name__
in GrpTogether:
849 if i == -1: i = len(set(GrpNums.values()))
850 GrpNums[j.__class__.__name__] = i
860 self.simulation = Simulation(self.mod.topology, self.system, integrator, self.platform)
868 def update_simulation(self, **kwargs):
871 Create the simulation object, or update the force field 872 parameters in the existing simulation object. This should be 873 run when we write a new force field XML file. 876 self.simkwargs = kwargs
877 self.forcefield = ForceField(*self.ffxml)
879 ismgens = [forcefield.AmoebaGeneralizedKirkwoodGenerator, forcefield.AmoebaWcaDispersionGenerator,
880 forcefield.CustomGBGenerator, forcefield.GBSAOBCGenerator]
881 if self.ism
is not None:
882 if self.ism ==
False:
883 self.forcefield._forces = [f
for f
in self.forcefield._forces
if not any([isinstance(f, f_)
for f_
in ismgens])]
884 elif self.ism ==
True:
885 if len([f
for f
in self.forcefield._forces
if any([isinstance(f, f_)
for f_
in ismgens])]) == 0:
886 logger.error(
"There is no implicit solvent model!\n")
888 self.mod = Modeller(self.pdb.topology, self.pdb.positions)
889 self.mod.addExtraParticles(self.forcefield)
895 self.system = self.forcefield.createSystem(self.mod.topology, **self.mmopts)
897 self.nbcharges = np.zeros(self.system.getNumParticles())
899 for i
in self.system.getForces():
900 if isinstance(i, NonbondedForce):
901 self.nbcharges = np.array([i.getParticleParameters(j)[0]._value
for j
in range(i.getNumParticles())])
903 i.setNonbondedMethod(i.PME)
904 if isinstance(i, AmoebaMultipoleForce):
906 i.setNonbondedMethod(i.PME)
913 if hasattr(self,
'vsprm')
and len(self.vsprm) > 0
and np.max(np.abs(vsprm - self.vsprm)) != 0.0:
914 if hasattr(self,
'simulation'):
915 delattr(self,
'simulation')
916 self.vsprm = vsprm.copy()
918 if hasattr(self,
'simulation'):
921 self.create_simulation(**self.simkwargs)
923 def set_restraint_positions(self, shot):
925 Set reference positions for energy restraints. This may be a different set of positions 926 from the "current" positions that are stored in self.mol and self.xyz_omm. 928 if self.restraint_frc_index
is not None:
930 xyz = self.ref_mol.xyzs[shot] / 10.0
931 frc = self.simulation.system.getForce(self.restraint_frc_index)
932 for i, j
in enumerate(self.realAtomIdxs):
933 frc.setParticleParameters(i, j, xyz[i])
934 frc.updateParametersInContext(self.simulation.context)
936 raise RuntimeError(
'Asked to set restraint positions, but no restraint force has been added to the system')
938 def set_positions(self, shot):
941 Set the positions and periodic box vectors to one of the 944 *** NOTE: If you run a MD simulation, then the coordinates are 945 overwritten by the MD trajectory. *** 958 self.simulation.context.setPeriodicBoxVectors(*self.xyz_omms[shot][1])
961 self.simulation.context.setPositions(self.xyz_omms[shot][0])
962 self.simulation.context.computeVirtualSites()
964 def get_charges(self):
965 logger.error(
'OpenMM engine does not have get_charges (should be trivial to implement however.)')
966 raise NotImplementedError
968 def compute_volume(self, box_vectors):
969 """ Compute the total volume of an OpenMM system. """ 970 [a,b,c] = box_vectors
971 A = np.array([a/a.unit, b/a.unit, c/a.unit])
973 volume = np.linalg.det(A) * a.unit**3
976 def compute_mass(self, system):
977 """ Compute the total mass of an OpenMM system. """ 979 for i
in range(system.getNumParticles()):
980 mass += system.getParticleMass(i)
983 def evaluate_one_(self, force=False, dipole=False):
984 """ Perform a single point calculation on the current geometry. """ 986 state = self.simulation.context.getState(getPositions=dipole, getEnergy=
True, getForces=force)
988 Result[
"Energy"] = state.getPotentialEnergy() / kilojoules_per_mole
990 Force = state.getForces(asNumpy=
True).value_in_unit(kilojoule/(nanometer*mole))
992 Result[
"Force"] = Force[self.realAtomIdxs].flatten()
994 Result[
"Dipole"] =
get_dipole(self.simulation, q=self.nbcharges, mass=self.AtomLists[
'Mass'], positions=state.getPositions())
997 def evaluate_(self, force=False, dipole=False, traj=False):
1000 Utility function for computing energy, and (optionally) forces and dipoles using OpenMM. 1003 force: Switch for calculating the force. 1004 dipole: Switch for calculating the dipole. 1005 traj: Trajectory (listing of coordinate and box 2-tuples). If provide, will loop over these snapshots. 1006 Otherwise will do a single point evaluation at the current geometry. 1009 Result: Dictionary containing energies, forces and/or dipoles. 1012 self.update_simulation()
1015 if not traj:
return evaluate_one_(force, dipole)
1019 for I
in range(len(self.xyz_omms)):
1020 self.set_positions(I)
1021 R1 = self.evaluate_one_(force, dipole)
1022 Energies.append(R1[
"Energy"])
1023 if force: Forces.append(R1[
"Force"])
1024 if dipole: Dipoles.append(R1[
"Dipole"])
1026 Result = OrderedDict()
1028 Result[
"Energy"] = np.array(Energies)
1029 if force: Result[
"Force"] = np.array(Forces)
1030 if dipole: Result[
"Dipole"] = np.array(Dipoles)
1033 def energy_one(self, shot):
1034 self.set_positions(shot)
1035 return self.evaluate_()[
"Energy"]
1037 def energy_force_one(self, shot):
1038 self.set_positions(shot)
1039 Result = self.evaluate_(force=
True)
1040 return np.hstack((Result[
"Energy"].reshape(-1,1), Result[
"Force"]))
1043 return self.evaluate_(traj=
True)[
"Energy"]
1045 def energy_force(self):
1046 """ Loop through the snapshots and compute the energies and forces using OpenMM. """ 1047 Result = self.evaluate_(force=
True, traj=
True)
1048 E = Result[
"Energy"].reshape(-1,1)
1050 return np.hstack((Result[
"Energy"].reshape(-1,1), Result[
"Force"]))
1052 def energy_dipole(self):
1053 """ Loop through the snapshots and compute the energies and forces using OpenMM. """ 1054 Result = self.evaluate_(dipole=
True, traj=
True)
1055 return np.hstack((Result[
"Energy"].reshape(-1,1), Result[
"Dipole"]))
1057 def build_mass_weighted_hessian(self, shot=0, optimize=True):
1058 """OpenMM single frame hessian evaluation 1059 Since OpenMM doesnot provide a Hessian evaluation method, we used finite difference on forces 1064 The frame number in the trajectory of this target 1068 hessian: np.array with shape 3N x 3N, N = number of "real" atoms 1069 The result hessian matrix. 1070 The row indices are fx0, fy0, fz0, fx1, fy1, ... 1071 The column indices are x0, y0, z0, x1, y1, .. 1072 The unit is kilojoule / (nanometer^2 * mole * dalton) => 10^24 s^-2 1074 self.update_simulation()
1075 if optimize
is True:
1076 self.optimize(shot, crit=1e-8)
1078 warn_once(
"Computing mass-weighted hessian without geometry optimization")
1079 self.set_positions(shot)
1080 context = self.simulation.context
1081 pos = context.getState(getPositions=
True).getPositions(asNumpy=
True)
1083 massList = np.array(self.AtomLists[
'Mass'])[self.realAtomIdxs]
1085 noa = len(self.realAtomIdxs)
1086 hessian = np.empty((noa*3, noa*3), dtype=float)
1088 diff = Quantity(0.0001, unit=nanometer)
1089 coef = 1.0 / (0.0001 * 2)
1090 for i, i_atom
in enumerate(self.realAtomIdxs):
1091 massWeight = 1.0 / np.sqrt(massList * massList[i])
1095 pos[i_atom][j] += diff
1096 context.setPositions(pos)
1097 grad_plus = context.getState(getForces=
True).getForces(asNumpy=
True).value_in_unit(kilojoule/(nanometer*mole))
1098 grad_plus = -grad_plus[self.realAtomIdxs]
1100 pos[i_atom][j] -= 2*diff
1101 context.setPositions(pos)
1102 grad_minus = context.getState(getForces=
True).getForces(asNumpy=
True).value_in_unit(kilojoule/(nanometer*mole))
1103 grad_minus = -grad_minus[self.realAtomIdxs]
1105 pos[i_atom][j] += diff
1107 hessian[i*3+j] = np.ravel((grad_plus - grad_minus) * coef * massWeight[:, np.newaxis])
1109 hessian += hessian.T
1112 context.setPositions(pos)
1115 def normal_modes(self, shot=0, optimize=True):
1116 """OpenMM Normal Mode Analysis 1117 Since OpenMM doesnot provide a Hessian evaluation method, we used finite difference on forces 1122 The frame number in the trajectory of this target 1123 optimize: bool, default True 1124 Optimize the geometry before evaluating the normal modes 1128 freqs: np.array with shape (3N - 6) x 1, N = number of "real" atoms 1129 Harmonic frequencies, sorted from smallest to largest, with the 6 smallest removed, in unit cm^-1 1130 normal_modes: np.array with shape (3N - 6) x (3N), N = number of "real" atoms 1131 The normal modes corresponding to each of the frequencies, scaled by mass^-1/2. 1133 if self.precision ==
'single':
1134 warn_once(
"Single-precision OpenMM engine used for normal mode analysis - recommend that you use mix or double precision.")
1136 warn_once(
"Asking for normal modes without geometry optimization?")
1138 noa = len(self.realAtomIdxs)
1140 error(
'normal mode analysis not suitable for system with one or less atoms')
1142 hessian_matrix = self.build_mass_weighted_hessian(shot=shot, optimize=optimize)
1144 eigvals, eigvecs = np.linalg.eigh(hessian_matrix)
1146 coef = 0.5 / np.pi * 33.3564095
1147 negatives = (eigvals >= 0).astype(int) * 2 - 1
1148 freqs = np.sqrt(np.abs(eigvals)) * coef * negatives
1151 normal_modes = eigvecs.T.reshape(noa*3, noa, 3)
1153 massList = np.array(self.AtomLists[
'Mass'])[self.realAtomIdxs]
1154 for i
in range(normal_modes.shape[0]):
1155 mode = normal_modes[i]
1156 mode /= np.sqrt(massList[:,np.newaxis])
1157 mode /= np.linalg.norm(mode)
1159 n_remove = 5
if len(self.realAtomIdxs) == 2
else 6
1160 larger_freq_idxs = np.sort(np.argpartition(np.abs(freqs), n_remove)[n_remove:])
1161 freqs = freqs[larger_freq_idxs]
1162 normal_modes = normal_modes[larger_freq_idxs]
1163 return freqs, normal_modes
1165 def optimize(self, shot, crit=1e-4, disable_vsite=False, align=True, include_restraint_energy=False):
1168 Optimize the geometry and align the optimized 1169 geometry to the starting geometry. 1174 The snapshot number to be minimized 1176 Convergence criterion in kJ/mol 1177 disable_vsite : bool 1178 Disable virtual sites (needed for SMIRNOFF) 1179 include_restraint_energy : bool 1180 Include energy component from CustomExternalForce 1185 Potential energy of the system 1187 RMSD of the system (w/r.t. starting geometry) in Angstrom 1190 steps = int(max(1, -1*np.log10(crit)))
1191 self.update_simulation()
1192 self.set_positions(shot)
1193 if self.restraint_frc_index
is not None:
1194 self.set_restraint_positions(shot)
1196 X0 = self.simulation.context.getState(getPositions=
True).getPositions(asNumpy=
True).value_in_unit(angstrom)[self.realAtomIdxs]
1199 for logc
in np.linspace(0, np.log10(crit), steps):
1200 self.simulation.minimizeEnergy(tolerance=10**logc*kilojoule/mole, maxIterations=100000)
1203 for _
in range(1000):
1204 e_minimized = self.simulation.context.getState(getEnergy=
True).getPotentialEnergy().value_in_unit(kilojoule_per_mole)
1205 self.simulation.minimizeEnergy(tolerance=crit*kilojoule_per_mole, maxIterations=10)
1206 e_new = self.simulation.context.getState(getEnergy=
True).getPotentialEnergy().value_in_unit(kilojoule_per_mole)
1207 if abs(e_new - e_minimized) < crit * 10:
1210 logger.error(
"Energy minimization did not converge")
1211 raise RuntimeError(
"Energy minimization did not converge")
1213 groups = set(range(32))
1214 if self.restraint_frc_index
is not None and not include_restraint_energy:
1215 frc = self.simulation.system.getForce(self.restraint_frc_index)
1216 groups.remove(frc.getForceGroup())
1218 S = self.simulation.context.getState(getPositions=
True, getEnergy=
True, groups=groups)
1220 X1 = S.getPositions(asNumpy=
True).value_in_unit(angstrom)[self.realAtomIdxs]
1221 E = S.getPotentialEnergy().value_in_unit(kilocalorie_per_mole)
1223 M = deepcopy(self.mol[0])
1226 if not self.pbc
and align:
1227 M.align(center=
False)
1230 self.simulation.context.setPositions(X1 * angstrom)
1233 mod = Modeller(self.pdb.topology, X1*angstrom)
1234 mod.addExtraParticles(self.forcefield)
1236 return E, M.ref_rmsd(0)[1], M[1]
1238 def getContextPosition(self, removeVirtual=False):
1240 Get current position from simulation context. 1245 Remove positions of virtual atoms, result will only have positions of real atoms. 1249 pos: np.ndarray of shape (N x 3) 1250 Position array in unit of Angstrom. If removeVirtual=True, N = No. real atoms, else N = No. all atoms. 1253 pos = self.simulation.context.getState(getPositions=
True).getPositions(asNumpy=
True).value_in_unit(angstrom)
1255 pos = pos[self.realAtomIdxs]
1258 def multipole_moments(self, shot=0, optimize=True, polarizability=False):
1260 """ Return the multipole moments of the i-th snapshot in Debye and Buckingham units. """ 1262 self.update_simulation()
1265 logger.error(
"Polarizability calculation is available in TINKER only.\n")
1266 raise NotImplementedError
1268 if (self.platname
in [
'CUDA',
'OpenCL']
and self.precision
in [
'single',
'mixed']):
1273 if optimize: self.optimize(shot, crit=crit)
1274 else: self.set_positions(shot)
1278 dipole_dict = OrderedDict(zip([
'x',
'y',
'z'], moments[:3]))
1279 quadrupole_dict = OrderedDict(zip([
'xx',
'xy',
'yy',
'xz',
'yz',
'zz'], moments[3:10]))
1281 calc_moments = OrderedDict([(
'dipole', dipole_dict), (
'quadrupole', quadrupole_dict)])
1285 def energy_rmsd(self, shot=0, optimize=True):
1287 """ Calculate energy of the 1st structure (optionally minimize and return the minimized energy and RMSD). In kcal/mol. """ 1289 self.update_simulation()
1291 if (self.platname
in [
'CUDA',
'OpenCL']
and self.precision
in [
'single',
'mixed']):
1298 E, rmsd, _ = self.optimize(shot, crit=crit)
1300 self.set_positions(shot)
1301 E = self.simulation.context.getState(getEnergy=
True).getPotentialEnergy().value_in_unit(kilocalories_per_mole)
1305 def interaction_energy(self, fraga, fragb):
1307 """ Calculate the interaction energy for two fragments. """ 1309 self.update_simulation()
1311 if self.name ==
'A' or self.name ==
'B':
1312 logger.error(
"Don't name the engine A or B!\n")
1316 if hasattr(self,
'target'):
1317 if not hasattr(self,
'A'):
1318 self.A = OpenMM(name=
"A", mol=self.mol.atom_select(fraga), target=self.target)
1319 if not hasattr(self,
'B'):
1320 self.B = OpenMM(name=
"B", mol=self.mol.atom_select(fragb), target=self.target)
1322 if not hasattr(self,
'A'):
1323 self.A = OpenMM(name=
"A", mol=self.mol.atom_select(fraga), platname=self.platname, \
1324 precision=self.precision, ffxml=self.ffxml, mmopts=self.mmopts)
1325 if not hasattr(self,
'B'):
1326 self.B = OpenMM(name=
"B", mol=self.mol.atom_select(fragb), platname=self.platname, \
1327 precision=self.precision, ffxml=self.ffxml, mmopts=self.mmopts)
1334 return (D - A - B) / 4.184
1336 def molecular_dynamics(self, nsteps, timestep, temperature=None, pressure=None, nequil=0, nsave=1000, minimize=True, anisotropic=False, save_traj=False, verbose=False, **kwargs):
1339 Method for running a molecular dynamics simulation. 1342 nsteps = (int) Number of total time steps 1343 timestep = (float) Time step in FEMTOSECONDS 1344 temperature = (float) Temperature control (Kelvin) 1345 pressure = (float) Pressure control (atmospheres) 1346 nequil = (int) Number of additional time steps at the beginning for equilibration 1347 nsave = (int) Step interval for saving and printing data 1348 minimize = (bool) Perform an energy minimization prior to dynamics 1350 Returns simulation data: 1351 Rhos = (array) Density in kilogram m^-3 1352 Potentials = (array) Potential energies 1353 Kinetics = (array) Kinetic energies 1354 Volumes = (array) Box volumes 1355 Dips = (3xN array) Dipole moments 1356 EComps = (dict) Energy components 1359 if float(int(float(nequil)/float(nsave))) != float(nequil)/float(nsave):
1360 logger.error(
"Please set nequil to an integer multiple of nsave\n")
1362 iequil = int(nequil/nsave)
1364 if float(int(float(nsteps)/float(nsave))) != float(nsteps)/float(nsave):
1365 logger.error(
"Please set nsteps to an integer multiple of nsave\n")
1367 isteps = int(nsteps/nsave)
1370 if hasattr(self,
'simulation'):
1371 logger.warning(
'Deleting the simulation object and re-creating for MD\n')
1372 delattr(self,
'simulation')
1374 self.update_simulation(timestep=timestep, temperature=temperature, pressure=pressure, anisotropic=anisotropic, **kwargs)
1375 self.set_positions(0)
1379 if verbose: logger.info(
"Minimizing the energy... (starting energy % .3f kJ/mol)" %
1380 self.simulation.context.getState(getEnergy=
True).getPotentialEnergy().value_in_unit(kilojoule_per_mole))
1381 self.simulation.minimizeEnergy()
1382 if verbose: logger.info(
"Done (final energy % .3f kJ/mol)\n" %
1383 self.simulation.context.getState(getEnergy=
True).getPotentialEnergy().value_in_unit(kilojoule_per_mole))
1388 serial = XmlSerializer.serializeSystem(system)
1389 with
wopen(
'%s_system.xml' % phase)
as f: f.write(serial)
1392 kB = BOLTZMANN_CONSTANT_kB * AVOGADRO_CONSTANT_NA
1395 self.mass = self.compute_mass(self.system).in_units_of(gram / mole) / AVOGADRO_CONSTANT_NA
1398 self.ndof = 3*(self.system.getNumParticles() - sum([self.system.isVirtualSite(i)
for i
in range(self.system.getNumParticles())])) \
1399 - self.system.getNumConstraints() - 3*self.pbc
1402 edecomp = OrderedDict()
1416 self.simulation.context.setVelocitiesToTemperature(temperature*kelvin)
1419 if verbose: logger.info(
"Equilibrating...\n")
1421 if verbose: logger.info(
"%6s %9s %9s %13s %10s %13s\n" % (
"Iter.",
"Time(ps)",
"Temp(K)",
"Epot(kJ/mol)",
"Vol(nm^3)",
"Rho(kg/m^3)"))
1423 if verbose: logger.info(
"%6s %9s %9s %13s\n" % (
"Iter.",
"Time(ps)",
"Temp(K)",
"Epot(kJ/mol)"))
1424 for iteration
in range(-1, iequil):
1426 self.simulation.step(nsave)
1427 state = self.simulation.context.getState(getEnergy=
True,getPositions=
True,getVelocities=
False,getForces=
False)
1428 kinetic = state.getKineticEnergy()/self.tdiv
1429 potential = state.getPotentialEnergy()
1431 box_vectors = state.getPeriodicBoxVectors()
1432 volume = self.compute_volume(box_vectors)
1433 density = (self.mass / volume).in_units_of(kilogram / meter**3)
1435 volume = 0.0 * nanometers ** 3
1436 density = 0.0 * kilogram / meter ** 3
1437 kinetic_temperature = 2.0 * kinetic / kB / self.ndof
1439 if verbose: logger.info(
"%6d %9.3f %9.3f % 13.3f %10.4f %13.4f\n" % (iteration+1, state.getTime() / picoseconds,
1440 kinetic_temperature / kelvin, potential / kilojoules_per_mole,
1441 volume / nanometers**3, density / (kilogram / meter**3)))
1443 if verbose: logger.info(
"%6d %9.3f %9.3f % 13.3f\n" % (iteration+1, state.getTime() / picoseconds,
1444 kinetic_temperature / kelvin, potential / kilojoules_per_mole))
1446 if verbose: logger.info(
"Production...\n")
1448 if verbose: logger.info(
"%6s %9s %9s %13s %10s %13s\n" % (
"Iter.",
"Time(ps)",
"Temp(K)",
"Epot(kJ/mol)",
"Vol(nm^3)",
"Rho(kg/m^3)"))
1450 if verbose: logger.info(
"%6s %9s %9s %13s\n" % (
"Iter.",
"Time(ps)",
"Temp(K)",
"Epot(kJ/mol)"))
1452 self.simulation.reporters.append(PDBReporter(
'%s-md.pdb' % self.name, nsteps))
1453 self.simulation.reporters.append(DCDReporter(
'%s-md.dcd' % self.name, nsave))
1455 for iteration
in range(-1, isteps):
1457 if iteration >= 0: self.simulation.step(nsave)
1459 state = self.simulation.context.getState(getEnergy=
True,getPositions=
True,getVelocities=
False,getForces=
False)
1460 kinetic = state.getKineticEnergy()/self.tdiv
1461 potential = state.getPotentialEnergy()
1462 kinetic_temperature = 2.0 * kinetic / kB / self.ndof
1464 box_vectors = state.getPeriodicBoxVectors()
1465 volume = self.compute_volume(box_vectors)
1466 density = (self.mass / volume).in_units_of(kilogram / meter**3)
1469 volume = 0.0 * nanometers ** 3
1470 density = 0.0 * kilogram / meter ** 3
1471 positions = state.getPositions(asNumpy=
True).astype(np.float32) * nanometer
1472 self.xyz_omms.append([positions, box_vectors])
1476 edecomp[comp].append(val)
1478 edecomp[comp] = [val]
1480 if verbose: logger.info(
"%6d %9.3f %9.3f % 13.3f %10.4f %13.4f\n" % (iteration+1, state.getTime() / picoseconds,
1481 kinetic_temperature / kelvin, potential / kilojoules_per_mole,
1482 volume / nanometers**3, density / (kilogram / meter**3)))
1484 if verbose: logger.info(
"%6d %9.3f %9.3f % 13.3f\n" % (iteration+1, state.getTime() / picoseconds,
1485 kinetic_temperature / kelvin, potential / kilojoules_per_mole))
1486 Temps.append(kinetic_temperature / kelvin)
1487 Rhos.append(density.value_in_unit(kilogram / meter**3))
1488 Potentials.append(potential / kilojoules_per_mole)
1489 Kinetics.append(kinetic / kilojoules_per_mole)
1490 Volumes.append(volume / nanometer**3)
1491 Dips.append(
get_dipole(self.simulation,positions=self.xyz_omms[-1][0]))
1492 Rhos = np.array(Rhos)
1493 Potentials = np.array(Potentials)
1494 Kinetics = np.array(Kinetics)
1495 Volumes = np.array(Volumes)
1496 Dips = np.array(Dips)
1497 Ecomps = OrderedDict([(key, np.array(val))
for key, val
in edecomp.items()])
1498 Ecomps[
"Potential Energy"] = Potentials
1499 Ecomps[
"Kinetic Energy"] = Kinetics
1500 Ecomps[
"Temperature"] = Temps
1501 Ecomps[
"Total Energy"] = Potentials + Kinetics
1503 prop_return = OrderedDict()
1504 prop_return.update({
'Rhos': Rhos,
'Potentials': Potentials,
'Kinetics': Kinetics,
'Volumes': Volumes,
'Dips': Dips,
'Ecomps': Ecomps})
1507 def scale_box(self, x=1.0, y=1.0, z=1.0):
1508 """ Scale the positions of molecules and box vectors. Molecular structures will be kept the same. 1509 Input: x, y, z :scaling factors (float) 1511 After this function call, self.xyz_omms will be overwritten with the new positions and box_vectors. 1513 if not hasattr(self,
'xyz_omms'):
1514 logger.error(
"molecular_dynamics has not finished correctly!")
1517 if not hasattr(self,
'residues_idxs'):
1518 self.residues_idxs = np.array([[a.index
for a
in r.atoms()]
for r
in self.simulation.topology.residues()])
1519 scale_xyz = np.array([x,y,z])
1521 for i
in range(len(self.xyz_omms)):
1522 pos, box = self.xyz_omms[i]
1524 new_box = np.array(box/nanometer) * scale_xyz
1526 positions = np.array(pos/nanometer)
1528 residue_positions = np.take(positions, self.residues_idxs, axis=0)
1530 res_center_positions = np.mean(residue_positions, axis=1)
1532 center_pos_shift = res_center_positions * (scale_xyz-1)
1534 new_pos = (residue_positions + center_pos_shift[:,np.newaxis,:]).reshape(-1,3)
1536 self.xyz_omms[i] = [new_pos.astype(np.float32)*nanometer, new_box*nanometer]
1538 class Liquid_OpenMM(Liquid):
1539 """ Condensed phase property matching using OpenMM. """ 1540 def __init__(self,options,tgt_opts,forcefield):
1542 self.set_option(tgt_opts,
'force_cuda',forceprint=
True)
1544 self.set_option(tgt_opts,
'mts_integrator',forceprint=
True)
1546 self.set_option(options,
'rpmd_beads',forceprint=
True)
1548 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"mixed")
1550 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"CUDA")
1552 self.set_option(tgt_opts,
'liquid_coords',default=
'liquid.pdb',forceprint=
True)
1554 self.set_option(tgt_opts,
'gas_coords',default=
'gas.pdb',forceprint=
True)
1556 self.set_option(tgt_opts,
'nvt_coords',default=
'surf.pdb',forceprint=
True)
1558 self.set_option(tgt_opts,
'mc_nbarostat')
1560 self.engine_ = OpenMM
1562 self.engname =
"openmm" 1564 self.nptpfx =
"bash runcuda.sh" 1565 if tgt_opts[
'remote_backup']:
1566 self.nptpfx +=
" -b" 1571 self.gas_engine_args = {}
1573 self.scripts = [
'runcuda.sh']
1575 super(Liquid_OpenMM,self).__init__(options,tgt_opts,forcefield)
1577 if self.save_traj > 0:
1578 self.extra_output = [
'liquid-md.pdb',
'liquid-md.dcd']
1580 self.post_init(options)
1582 class AbInitio_OpenMM(AbInitio):
1583 """ Force and energy matching using OpenMM. """ 1584 def __init__(self,options,tgt_opts,forcefield):
1586 self.set_option(tgt_opts,
'pdb',default=
"conf.pdb")
1587 self.set_option(tgt_opts,
'coords',default=
"all.gro")
1588 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1589 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"CUDA", forceprint=
True)
1590 self.engine_ = OpenMM
1592 super(AbInitio_OpenMM,self).__init__(options,tgt_opts,forcefield)
1594 class BindingEnergy_OpenMM(BindingEnergy):
1595 """ Binding energy matching using OpenMM. """ 1597 def __init__(self,options,tgt_opts,forcefield):
1598 self.engine_ = OpenMM
1599 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1600 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1602 super(BindingEnergy_OpenMM,self).__init__(options,tgt_opts,forcefield)
1604 class Interaction_OpenMM(Interaction):
1605 """ Interaction matching using OpenMM. """ 1606 def __init__(self,options,tgt_opts,forcefield):
1608 self.set_option(tgt_opts,
'coords',default=
"all.pdb")
1609 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1610 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1611 self.engine_ = OpenMM
1613 super(Interaction_OpenMM,self).__init__(options,tgt_opts,forcefield)
1615 class Moments_OpenMM(Moments):
1616 """ Multipole moment matching using OpenMM. """ 1617 def __init__(self,options,tgt_opts,forcefield):
1619 self.set_option(tgt_opts,
'coords',default=
"input.pdb")
1620 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1621 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1622 self.engine_ = OpenMM
1624 super(Moments_OpenMM,self).__init__(options,tgt_opts,forcefield)
1626 class Hydration_OpenMM(Hydration):
1627 """ Single point hydration free energies using OpenMM. """ 1629 def __init__(self,options,tgt_opts,forcefield):
1632 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1633 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"CUDA", forceprint=
True)
1634 self.engine_ = OpenMM
1635 self.engname =
"openmm" 1637 self.scripts = [
'runcuda.sh']
1639 self.crdsfx =
'.pdb' 1641 self.prefix =
"bash runcuda.sh" 1642 if tgt_opts[
'remote_backup']:
1643 self.prefix +=
" -b" 1645 super(Hydration_OpenMM,self).__init__(options,tgt_opts,forcefield)
1647 if self.save_traj > 0:
1648 self.extra_output = [
'openmm-md.dcd']
1650 class Vibration_OpenMM(Vibration):
1651 """ Vibrational frequency matching using TINKER. """ 1652 def __init__(self,options,tgt_opts,forcefield):
1654 self.set_option(tgt_opts,
'coords',default=
"input.pdb")
1655 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1656 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1657 self.engine_ = OpenMM
1659 super(Vibration_OpenMM,self).__init__(options,tgt_opts,forcefield)
1661 class OptGeoTarget_OpenMM(OptGeoTarget):
1662 """ Optimized geometry matching using OpenMM. """ 1663 def __init__(self,options,tgt_opts,forcefield):
1664 self.engine_ = OpenMM
1665 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1666 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1668 super(OptGeoTarget_OpenMM,self).__init__(options,tgt_opts,forcefield)
1670 def create_engines(self, engine_args):
1671 """ create a dictionary of self.engines = {sysname: Engine} """ 1672 self.engines = OrderedDict()
1673 for sysname, sysopt
in self.sys_opts.items():
1675 pdbpath = os.path.join(self.root, self.tgtdir, sysopt[
'topology'])
1677 M = Molecule(pdbpath)
1679 M0 = Molecule(os.path.join(self.root, self.tgtdir, sysopt[
'geometry']))
1683 self.engines[sysname] = self.engine_(target=self, mol=M, name=sysname, pdb=pdbpath, **engine_args)
1685 class TorsionProfileTarget_OpenMM(TorsionProfileTarget):
1686 """ Optimized geometry matching using OpenMM. """ 1687 def __init__(self,options,tgt_opts,forcefield):
1689 self.set_option(tgt_opts,
'pdb',default=
"conf.pdb")
1690 self.set_option(tgt_opts,
'coords',default=
"scan.xyz")
1691 self.set_option(tgt_opts,
'openmm_precision',
'precision',default=
"double", forceprint=
True)
1692 self.set_option(tgt_opts,
'openmm_platform',
'platname',default=
"Reference", forceprint=
True)
1693 self.engine_ = OpenMM
1695 super(TorsionProfileTarget_OpenMM,self).__init__(options,tgt_opts,forcefield)
def CopyAmoebaAngleParameters(src, dest)
def CopyGBSAOBCParameters(src, dest)
def AddVirtualSiteBonds(mod, ff)
Optimized Geometry fitting module.
Vibrational mode fitting module.
def CopyAmoebaOutOfPlaneBendParameters(src, dest)
Nifty functions, intended to be imported by any module within ForceBalance.
Ab-initio fitting module (energies, forces, resp).
def PrepareVirtualSites(system)
Prepare a list of function wrappers and vsite parameters from the system.
def CopyNonbondedParameters(src, dest)
def ResetVirtualSites_fast(positions, vsinfo)
Given a set of OpenMM-compatible positions and a System object, compute the correct virtual site posi...
def CopySystemParameters(src, dest)
Copy parameters from one system (i.e.
def CopyAmoebaVdwParameters(src, dest)
def energy_components(Sim, verbose=False)
def GetVirtualSiteParameters(system)
Return an array of all virtual site parameters in the system.
Torsion profile fitting module.
Binding energy fitting module.
def SetAmoebaVirtualExclusions(system)
def UpdateSimulationParameters(src_system, dest_simulation)
def CopyAmoebaBondParameters(src, dest)
def do_nothing(src, dest)
def CopyAmoebaInPlaneAngleParameters(src, dest)
def warn_press_key(warning, timeout=10)
Hydration free energy fitting module.
def CopyHarmonicAngleParameters(src, dest)
def CopyCustomNonbondedParameters(src, dest)
copy whatever updateParametersInContext can update: per-particle parameters
def SetAmoebaNonbondedExcludeAll(system, topology)
Manually set the AmoebaVdwForce, AmoebaMultipoleForce to exclude all atoms belonging to the same resi...
Interaction energy fitting module.
def CopyAmoebaMultipoleParameters(src, dest)
def get_mask(grps)
Given a list of booleans [1, 0, 1] return the bitmask that sets these force groups appropriately in C...
def listfiles(fnms=None, ext=None, err=False, dnm=None)
def ResetVirtualSites(positions, system)
Given a set of OpenMM-compatible positions and a System object, compute the correct virtual site posi...
def warn_once(warning, warnhash=None)
Prints a warning but will only do so once in a given run.
Multipole moment fitting module.
def wopen(dest, binary=False)
If trying to write to a symbolic link, remove it first.
def CopyHarmonicBondParameters(src, dest)
Matching of liquid bulk properties.
def MTSVVVRIntegrator(temperature, collision_rate, timestep, system, ninnersteps=4)
Create a multiple timestep velocity verlet with velocity randomization (VVVR) integrator.
def get_dipole(simulation, q=None, mass=None, positions=None)
Return the current dipole moment in Debye.
def CopyPeriodicTorsionParameters(src, dest)
def get_multipoles(simulation, q=None, mass=None, positions=None, rmcom=True)
Return the current multipole moments in Debye and Buckingham units.