/* -------------------------------------------------------------------------- * * OpenMM * * -------------------------------------------------------------------------- * * This is part of the OpenMM molecular simulation toolkit originating from * * Simbios, the NIH National Center for Physics-Based Simulation of * * Biological Structures at Stanford, funded under the NIH Roadmap for * * Medical Research, grant U54 GM072970. See https://simtk.org. * * * * Portions copyright (c) 2011-2013 Stanford University and the Authors. * * Authors: Peter Eastman * * 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 "ReferenceDrudeKernels.h" #include "openmm/HarmonicAngleForce.h" #include "openmm/OpenMMException.h" #include "openmm/internal/ContextImpl.h" #include "SimTKUtilities/SimTKOpenMMUtilities.h" #include "SimTKReference/ReferenceCCMAAlgorithm.h" #include using namespace OpenMM; using namespace std; static vector& extractPositions(ContextImpl& context) { ReferencePlatform::PlatformData* data = reinterpret_cast(context.getPlatformData()); return *((vector*) data->positions); } static vector& extractVelocities(ContextImpl& context) { ReferencePlatform::PlatformData* data = reinterpret_cast(context.getPlatformData()); return *((vector*) data->velocities); } static vector& extractForces(ContextImpl& context) { ReferencePlatform::PlatformData* data = reinterpret_cast(context.getPlatformData()); return *((vector*) data->forces); } static void findAnglesForCCMA(const System& system, vector& angles) { for (int i = 0; i < system.getNumForces(); i++) { const HarmonicAngleForce* force = dynamic_cast(&system.getForce(i)); if (force != NULL) { for (int j = 0; j < force->getNumAngles(); j++) { int atom1, atom2, atom3; double angle, k; force->getAngleParameters(j, atom1, atom2, atom3, angle, k); angles.push_back(ReferenceCCMAAlgorithm::AngleInfo(atom1, atom2, atom3, (RealOpenMM)angle)); } } } } void ReferenceCalcDrudeForceKernel::initialize(const System& system, const DrudeForce& force) { // Initialize particle parameters. int numParticles = force.getNumParticles(); particle.resize(numParticles); particle1.resize(numParticles); particle2.resize(numParticles); particle3.resize(numParticles); particle4.resize(numParticles); charge.resize(numParticles); polarizability.resize(numParticles); aniso12.resize(numParticles); aniso34.resize(numParticles); for (int i = 0; i < numParticles; i++) force.getParticleParameters(i, particle[i], particle1[i], particle2[i], particle3[i], particle4[i], charge[i], polarizability[i], aniso12[i], aniso34[i]); // Initialize screened pair parameters. int numPairs = force.getNumScreenedPairs(); pair1.resize(numPairs); pair2.resize(numPairs); pairThole.resize(numPairs); for (int i = 0; i < numPairs; i++) force.getScreenedPairParameters(i, pair1[i], pair2[i], pairThole[i]); } double ReferenceCalcDrudeForceKernel::execute(ContextImpl& context, bool includeForces, bool includeEnergy) { vector& pos = extractPositions(context); vector& force = extractForces(context); int numParticles = particle.size(); double energy = 0; // Compute the interactions from the harmonic springs. for (int i = 0; i < numParticles; i++) { int p = particle[i]; int p1 = particle1[i]; int p2 = particle2[i]; int p3 = particle3[i]; int p4 = particle4[i]; RealOpenMM a1 = (p2 == -1 ? 1 : aniso12[i]); RealOpenMM a2 = (p3 == -1 || p4 == -1 ? 1 : aniso34[i]); RealOpenMM a3 = 3-a1-a2; RealOpenMM k3 = charge[i]*charge[i]/(polarizability[i]*a3); RealOpenMM k1 = charge[i]*charge[i]/(polarizability[i]*a1) - k3; RealOpenMM k2 = charge[i]*charge[i]/(polarizability[i]*a2) - k3; // Compute the isotropic force. RealVec delta = pos[p]-pos[p1]; RealOpenMM r2 = delta.dot(delta); energy += 0.5*k3*r2; force[p] -= delta*k3; force[p1] += delta*k3; // Compute the first anisotropic force. if (p2 != -1) { RealVec dir = pos[p1]-pos[p2]; RealOpenMM invDist = 1.0/sqrt(dir.dot(dir)); dir *= invDist; RealOpenMM rprime = dir.dot(delta); energy += 0.5*k1*rprime*rprime; RealVec f1 = dir*(k1*rprime); RealVec f2 = (delta-dir*rprime)*(k1*rprime*invDist); force[p] -= f1; force[p1] += f1-f2; force[p2] += f2; } // Compute the second anisotropic force. if (p3 != -1 && p4 != -1) { RealVec dir = pos[p3]-pos[p4]; RealOpenMM invDist = 1.0/sqrt(dir.dot(dir)); dir *= invDist; RealOpenMM rprime = dir.dot(delta); energy += 0.5*k2*rprime*rprime; RealVec f1 = dir*(k2*rprime); RealVec f2 = (delta-dir*rprime)*(k2*rprime*invDist); force[p] -= f1; force[p1] += f1; force[p3] -= f2; force[p4] += f2; } } // Compute the screened interaction between bonded dipoles. int numPairs = pair1.size(); for (int i = 0; i < numPairs; i++) { int dipole1 = pair1[i]; int dipole2 = pair2[i]; int dipole1Particles[] = {particle[dipole1], particle1[dipole1]}; int dipole2Particles[] = {particle[dipole2], particle1[dipole2]}; for (int j = 0; j < 2; j++) for (int k = 0; k < 2; k++) { int p1 = dipole1Particles[j]; int p2 = dipole2Particles[k]; RealOpenMM chargeProduct = charge[dipole1]*charge[dipole2]*(j == k ? 1 : -1); RealVec delta = pos[p1]-pos[p2]; RealOpenMM r = sqrt(delta.dot(delta)); RealOpenMM u = r*pairThole[i]/pow(polarizability[dipole1]*polarizability[dipole2], 1.0/6.0); RealOpenMM screening = 1.0 - (1.0+0.5*u)*exp(-u); energy += ONE_4PI_EPS0*chargeProduct*screening/r; RealVec f = delta*(ONE_4PI_EPS0*chargeProduct*screening/(r*r*r)); force[p1] += f; force[p2] -= f; } } return energy; } void ReferenceCalcDrudeForceKernel::copyParametersToContext(ContextImpl& context, const DrudeForce& force) { if (force.getNumParticles() != particle.size()) throw OpenMMException("updateParametersInContext: The number of Drude particles has changed"); if (force.getNumScreenedPairs() != pair1.size()) throw OpenMMException("updateParametersInContext: The number of screened pairs has changed"); for (int i = 0; i < force.getNumParticles(); i++) { int p, p1, p2, p3, p4; force.getParticleParameters(i, p, p1, p2, p3, p4, charge[i], polarizability[i], aniso12[i], aniso34[i]); if (p != particle[i] || p1 != particle1[i] || p2 != particle2[i] || p3 != particle3[i] || p4 != particle4[i]) throw OpenMMException("updateParametersInContext: A particle index has changed"); } for (int i = 0; i < force.getNumScreenedPairs(); i++) { int p1, p2; force.getScreenedPairParameters(i, p1, p2, pairThole[i]); if (p1 != pair1[i] || p2 != pair2[i]) throw OpenMMException("updateParametersInContext: A particle index for a screened pair has changed"); } } ReferenceIntegrateDrudeLangevinStepKernel::~ReferenceIntegrateDrudeLangevinStepKernel() { if (constraints != NULL) delete constraints; } void ReferenceIntegrateDrudeLangevinStepKernel::initialize(const System& system, const DrudeLangevinIntegrator& integrator, const DrudeForce& force) { drudeForce = &force; SimTKOpenMMUtilities::setRandomNumberSeed((unsigned int) integrator.getRandomNumberSeed()); // Identify particle pairs and ordinary particles. set particles; vector particleMass; for (int i = 0; i < system.getNumParticles(); i++) { particles.insert(i); double mass = system.getParticleMass(i); particleMass.push_back(mass); particleInvMass.push_back(mass == 0.0 ? 0.0 : 1.0/mass); } for (int i = 0; i < force.getNumParticles(); i++) { int p, p1, p2, p3, p4; double charge, polarizability, aniso12, aniso34; force.getParticleParameters(i, p, p1, p2, p3, p4, charge, polarizability, aniso12, aniso34); particles.erase(p); particles.erase(p1); pairParticles.push_back(make_pair(p, p1)); double m1 = system.getParticleMass(p); double m2 = system.getParticleMass(p1); pairInvTotalMass.push_back(1.0/(m1+m2)); pairInvReducedMass.push_back((m1+m2)/(m1*m2)); } normalParticles.insert(normalParticles.begin(), particles.begin(), particles.end()); // Prepare constraints. int numConstraints = system.getNumConstraints(); if (numConstraints > 0) { vector > constraintIndices(numConstraints); vector constraintDistances(numConstraints); for (int i = 0; i < numConstraints; ++i) { int particle1, particle2; double distance; system.getConstraintParameters(i, particle1, particle2, distance); constraintIndices[i].first = particle1; constraintIndices[i].second = particle2; constraintDistances[i] = static_cast(distance); } vector angles; findAnglesForCCMA(system, angles); constraints = new ReferenceCCMAAlgorithm(system.getNumParticles(), numConstraints, constraintIndices, constraintDistances, particleMass, angles, (RealOpenMM)integrator.getConstraintTolerance()); } } void ReferenceIntegrateDrudeLangevinStepKernel::execute(ContextImpl& context, const DrudeLangevinIntegrator& integrator) { // Update velocities of ordinary particles. const RealOpenMM vscale = exp(-integrator.getStepSize()*integrator.getFriction()); const RealOpenMM fscale = (1-vscale)/integrator.getFriction(); const RealOpenMM kT = BOLTZ*integrator.getTemperature(); const RealOpenMM noisescale = sqrt(2*kT*integrator.getFriction())*sqrt(0.5*(1-vscale*vscale)/integrator.getFriction()); vector& pos = extractPositions(context); vector& vel = extractVelocities(context); vector& force = extractForces(context); for (int i = 0; i < (int) normalParticles.size(); i++) { int index = normalParticles[i]; RealOpenMM invMass = particleInvMass[index]; if (invMass != 0.0) { RealOpenMM sqrtInvMass = sqrt(invMass); for (int j = 0; j < 3; j++) vel[i][j] = vscale*vel[i][j] + fscale*invMass*force[i][j] + noisescale*sqrtInvMass*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber(); } } // Update velocities of Drude particle pairs. const RealOpenMM vscaleDrude = exp(-integrator.getStepSize()*integrator.getDrudeFriction()); const RealOpenMM fscaleDrude = (1-vscaleDrude)/integrator.getDrudeFriction(); const RealOpenMM kTDrude = BOLTZ*integrator.getDrudeTemperature(); const RealOpenMM noisescaleDrude = sqrt(2*kTDrude*integrator.getDrudeFriction())*sqrt(0.5*(1-vscaleDrude*vscaleDrude)/integrator.getDrudeFriction()); for (int i = 0; i < (int) pairParticles.size(); i++) { int p1 = pairParticles[i].first; int p2 = pairParticles[i].second; RealOpenMM sqrtInvTotalMass = sqrt(pairInvTotalMass[i]); RealOpenMM sqrtInvReducedMass = sqrt(pairInvReducedMass[i]); RealVec cmVel = (vel[p2]+vel[p1])*0.5; RealVec relVel = vel[p2]-vel[p1]; RealVec cmForce = force[p1]+force[p2]; RealVec relForce = force[p2]*(pairInvTotalMass[i]/particleInvMass[p1]) - force[p1]*(pairInvTotalMass[i]/particleInvMass[p2]); for (int j = 0; j < 3; j++) { cmVel[j] = vscale*cmVel[j] + fscale*pairInvTotalMass[i]*cmForce[j] + noisescale*sqrtInvTotalMass*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber(); relVel[j] = vscaleDrude*relVel[j] + fscaleDrude*pairInvReducedMass[i]*relForce[j] + noisescaleDrude*sqrtInvReducedMass*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber(); } vel[p1] = cmVel-relVel*0.5; vel[p2] = cmVel+relVel*0.5; } // Update the particle positions. int numParticles = particleInvMass.size(); vector xPrime(numParticles); RealOpenMM dt = integrator.getStepSize(); for (int i = 0; i < numParticles; i++) if (particleInvMass[i] != 0.0) xPrime[i] = pos[i]+vel[i]*dt; // Apply constraints. constraints->apply(numParticles, pos, xPrime, particleInvMass); // Record the constrained positions and velocities. RealOpenMM dtInv = 1.0/dt; for (int i = 0; i < numParticles; i++) { if (particleInvMass[i] != 0.0) { vel[i] = (xPrime[i]-pos[i])*dtInv; pos[i] = xPrime[i]; } } } double ReferenceIntegrateDrudeLangevinStepKernel::computeKineticEnergy(ContextImpl& context, const DrudeLangevinIntegrator& integrator) { const System& system = context.getSystem(); int numParticles = system.getNumParticles(); vector& posData = extractPositions(context); vector& velData = extractVelocities(context); vector& forceData = extractForces(context); // Compute the shifted velocities. vector shiftedVel(numParticles); double timeShift = 0.5*integrator.getStepSize(); for (int i = 0; i < numParticles; ++i) { if (particleInvMass[i] > 0) shiftedVel[i] = velData[i]+forceData[i]*(timeShift/particleInvMass[i]); else shiftedVel[i] = velData[i]; } // Apply constraints to them. if (constraints != NULL) { constraints->setTolerance(1e-4); constraints->applyToVelocities(numParticles, posData, shiftedVel, particleInvMass); } // Compute the kinetic energy. double energy = 0.0; for (int i = 0; i < numParticles; ++i) if (particleInvMass[i] > 0) energy += (shiftedVel[i].dot(shiftedVel[i]))/particleInvMass[i]; return 0.5*energy; }