Molmodel

VanDerWaalsForce.h

Go to the documentation of this file.
00001 #ifndef SIMTK_MOLMODEL_VANDERWAALS_FORCE_H_
00002 #define SIMTK_MOLMODEL_VANDERWAALS_FORCE_H_
00003 
00004 #include "SimTKsimbody.h"
00005 #include <vector>
00006 #include <iterator>
00007 
00008 namespace SimTK {
00009 
00010 namespace md {
00011     const Real angstroms = 0.10; // angstroms to nanometers
00012     const Real nanometers = 1.0;
00013     const Real square_nanometers = 1.0;
00014 
00015     const Real kilocalories_per_mole = 4.184; // calories to joules
00016     const Real kilojoules_per_mole = 1.0;
00017 
00018     const Real daltons = 1.0;
00019 }
00020 
00021 class VanDerWaalsForce : public Force::Custom::Implementation
00022 {
00023 public:
00024     typedef AtomSubsystem::AtomIndex AtomIndex;
00025 
00026     // Definitions to elide previous use of Boost::Units
00027     typedef Real length_t;
00028     typedef Real area_t;
00029     typedef Real energy_t;
00030     typedef Real inverse_area_t;
00031     typedef Vec3 location_t;
00032     typedef Vec3 force_t;
00033 
00034     class VdwAtom
00035     {
00036     public:
00037         VdwAtom() 
00038             : rMin(1.90*md::angstroms), wellDepth(0.1*md::kilocalories_per_mole)
00039         {}
00040         
00041         VdwAtom(AtomIndex atomIndex, length_t rMin, energy_t wellDepth) 
00042             : atomIndex(atomIndex), rMin(rMin), wellDepth(wellDepth)
00043         {}
00044 
00045         AtomIndex atomIndex;
00046         length_t rMin; // nanometers, minimum energy half-distance
00047         energy_t wellDepth; // kilojoules per mole
00048     };
00049 
00050     // Sphere for containing atoms with forces sort of like other atoms
00051     struct WallSphere
00052     {
00053         length_t radius;
00054         location_t center;
00055 
00056         length_t vdwRadius;
00057         energy_t wellDepth;
00058     };
00059 
00060     VanDerWaalsForce(const AtomSubsystem& atomSubsystem, length_t cutoff = 0.0 * nanometers) 
00061         : atomSubsystem(atomSubsystem), cutoff(cutoff), cutoffSquared(cutoff * cutoff)
00062     {}
00063 
00064     bool dependsOnlyOnPositions() const {return true;}
00065 
00066     void addAtom(AtomSubsystem::AtomIndex atomIndex, length_t rMin, energy_t wellDepth) 
00067     {
00068         if (AtomSubsystem::AtomIndex(vdwAtoms.size()) <= atomIndex)
00069             vdwAtoms.resize(atomIndex + 1);
00070         
00071         vdwAtoms[atomIndex] = VdwAtom(atomIndex, rMin, wellDepth);
00072     }
00073 
00074     void addBoundarySphere(
00075             const location_t& center, 
00076             length_t radius,
00077             length_t vdwRadius,
00078             energy_t wellDepth) 
00079     {
00080         WallSphere sphere;
00081         sphere.center = center;
00082         sphere.radius = radius;
00083         sphere.vdwRadius = vdwRadius;
00084         sphere.wellDepth = wellDepth;
00085         wallSpheres.push_back(sphere);
00086     }
00087 
00088     void decorateAtoms(DecorationSubsystem& decorations, Real scale = 1.0) const {
00089         for (AtomIndex a(0); a < vdwAtoms.size(); ++a) 
00090         {      
00091             const VdwAtom& vdwAtom = vdwAtoms[a];
00092             
00093             if (!vdwAtom.atomIndex.isValid()) continue;
00094                         
00095             const AtomSubsystem::Atom& atom = atomSubsystem.getAtom(a);
00096 
00097             Vec3 color(0.5, 0.5, 0.5); // default color gray
00098 
00099             // Color argon cyan
00100             // Argon mass (39.9) is very close to Calcium (40.1)
00101             if (atom.getMass() < 39.5) 
00102                 ;
00103             else if (atom.getMass() < 40.0) 
00104                 color = Vec3(0.5, 1, 1); // argon
00105 
00106             decorations.addBodyFixedDecoration(
00107                 atom.getMobilizedBodyIndex(), 
00108                 atom.getStationInBodyFrame(),
00109                 DecorativeSphere(vdwAtom.rMin * scale).setColor(color)
00110             );
00111         }
00112     }
00113 
00114     void decorateBoundingSpheres(DecorationSubsystem& decorations) const {
00115         for (size_t s(0); s < wallSpheres.size(); ++s) 
00116         {      
00117             Vec3 color(1.0, 1.0, 0.6); // pale yellow
00118 
00119             const WallSphere& sphere = wallSpheres[s];
00120 
00121             decorations.addBodyFixedDecoration(
00122                 atomSubsystem.getMultibodySystem().getMatterSubsystem().getGround().getMobilizedBodyIndex(),
00123                 sphere.center,
00124                 DecorativeSphere(sphere.radius).setColor(color).setOpacity(0.2)
00125             );
00126         }
00127     }
00128     void calcForce(const State& state, Vector_<SpatialVec>& bodyForces,  
00129             Vector_<Vec3>& particleForces, Vector& mobilityForces) const 
00130     {
00131         // Forces on pairs of atoms
00132         for ( AtomSubsystem::PairIterator pair = 
00133                     atomSubsystem.pairBegin(state, cutoff);
00134               pair != atomSubsystem.pairEnd();
00135               ++pair) 
00136         {
00137             const VdwAtom& vdwAtom1 = vdwAtoms[pair->first];
00138             const VdwAtom& vdwAtom2 = vdwAtoms[pair->second];
00139 
00140             if (!vdwAtom1.atomIndex.isValid()) continue;
00141             if (!vdwAtom2.atomIndex.isValid()) continue;
00142             
00143             calcForce(vdwAtom1, vdwAtom2, state, bodyForces);
00144         }
00145 
00146         // Apply any bounding spheres
00147         for ( std::vector<WallSphere>::const_iterator sphereI = wallSpheres.begin();
00148               sphereI != wallSpheres.end(); ++sphereI) {
00149                 for ( std::vector<VdwAtom>::const_iterator atomI = vdwAtoms.begin();
00150                     atomI != vdwAtoms.end();  ++atomI) {
00151                         if ( ! atomI->atomIndex.isValid() ) continue;
00152                         calcForce(*sphereI, *atomI, state, bodyForces);
00153                 }
00154         }
00155     }
00156 
00157     Real calcPotentialEnergy(const State& state) const {
00158         energy_t energy = 0.0 * md::kilojoules_per_mole;
00159 
00160         for (   AtomSubsystem::PairIterator pair = 
00161                         atomSubsystem.pairBegin(state, cutoff);
00162                 pair != atomSubsystem.pairEnd();
00163                 ++pair) 
00164         {
00165             const VdwAtom& vdwAtom1 = vdwAtoms[pair->first];
00166             const VdwAtom& vdwAtom2 = vdwAtoms[pair->second];
00167 
00168             if (!vdwAtom1.atomIndex.isValid()) continue;
00169             if (!vdwAtom2.atomIndex.isValid()) continue;
00170             
00171             energy += calcPotentialEnergy(vdwAtom1, vdwAtom2, state);
00172         }
00173 
00174         // Apply any bounding spheres
00175         for ( std::vector<WallSphere>::const_iterator sphereI = wallSpheres.begin();
00176               sphereI != wallSpheres.end(); ++sphereI) {
00177                 for ( std::vector<VdwAtom>::const_iterator atomI = vdwAtoms.begin();
00178                     atomI != vdwAtoms.end();  ++atomI) {
00179                         if ( ! atomI->atomIndex.isValid() ) continue;
00180                         energy += calcPotentialEnergy(*sphereI, *atomI, state);
00181                 }
00182         }
00183 
00184         return energy;
00185     }
00186 
00187 protected:
00188 
00189     struct ForceAndEnergy {
00190         force_t force;
00191         energy_t energy;
00192     };
00193 
00194     // calcForceAndEnergy.  
00195     // Agnostic to DuMMForceFieldSubsystem vs AtomSubsystem.  Location in ground are arguments.
00196     // Agnostic to combining rules: precombined values are arguments to calcForceAndEnergy
00197     // Bonded atom scaling should be performed outside of this routine
00198     // Return force and energy on second location
00199     // TODO softer potential with non-infinity center
00200     ForceAndEnergy calcForceAndEnergy( 
00201         const location_t& r, // vector from atom1 to atom2
00202         Real dij, Real eij) const // pre-combined Dij and eij
00203     {
00204         ForceAndEnergy forceAndEnergy;
00205 
00206         const area_t d2 = r.normSqr(); // 5 flops
00207 
00208         // If cutoff is present, force drops to zero abruptly at and beyond the cutoff radius.
00209         // But force is not otherwise affected, since within the cutoff radius
00210         // the truncated potential is a constant offset to the potential energy.
00211         if ( (cutoff > 0.0) && (d2 > cutoffSquared) )
00212             return forceAndEnergy;
00213 
00214         // Don't need to calculate 1/d unless we compute Coulomb at the same time.
00215         inverse_area_t ood2(1.0 / d2);        // 1 flop
00216 
00217         // should be dimensionless
00218         const Real ddij2  = dij*dij*ood2;        // (dmin_ij/d)^2 (2 flops)
00219         const Real ddij6  = ddij2*ddij2*ddij2;   // 2 flops
00220         const Real ddij12 = ddij6*ddij6;         // 1 flop
00221 
00222         // Force
00223         const energy_t fVdw = 12.0 * eij * (ddij12 - ddij6);   // factor of 1/d^2 missing (3 flops)
00224         forceAndEnergy.force = fVdw * ood2 * r;      // to apply to atom j on b2 (5 flops)
00225 
00226         // Energy
00227         forceAndEnergy.energy = eij * (ddij12 - 2*ddij6); // 3 flops
00228 
00229         // offset potential to reach zero at cutoff
00230         if (cutoff > 0.0) 
00231         {
00232             energy_t truncationOffset = 0.0;
00233             assert(d2 < cutoffSquared); // should have short circuited above
00234             const Real off2 = dij * dij / cutoffSquared; // off2 = (rmin/cutoff)^2
00235             const Real off6 = off2 * off2 * off2;
00236             truncationOffset = eij * ( (off6 - 2.0) * off6);
00237             assert(truncationOffset < 0.0); // unreasonable to put cutoff within repulsive region
00238 
00239             forceAndEnergy.energy -= truncationOffset;
00240         }
00241 
00242         return forceAndEnergy;
00243     }
00244 
00245     // Calculate force between two atoms
00246     void calcForce(const VdwAtom& vdwAtom1, const VdwAtom& vdwAtom2, const State& state, Vector_<SpatialVec>& bodyForces) const
00247     {
00248         Real vdwScale = 1.0;  // TODO scale bonded atoms
00249 
00250         const AtomSubsystem::Atom& atom1 = atomSubsystem.getAtom(vdwAtom1.atomIndex);
00251         const AtomSubsystem::Atom& atom2 = atomSubsystem.getAtom(vdwAtom2.atomIndex);
00252 
00253         const Vec3& a1Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom1.atomIndex);
00254         const Vec3& a2Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom2.atomIndex);
00255 
00256         // TODO better combining rules
00257         const length_t dij = vdwAtom1.rMin + vdwAtom2.rMin;
00258         const energy_t eij = std::sqrt(vdwAtom1.wellDepth * vdwAtom2.wellDepth);
00259 
00260         const force_t fj = calcForceAndEnergy(a2Pos_G - a1Pos_G, dij, eij).force * vdwScale;
00261 
00262         const Vec3& a1Station_G = atomSubsystem.getAtomBodyStationInGround(state, vdwAtom1.atomIndex);
00263         const Vec3& a2Station_G = atomSubsystem.getAtomBodyStationInGround(state, vdwAtom2.atomIndex);
00264 
00265         bodyForces[atom2.getMobilizedBodyIndex()] += SpatialVec( a2Station_G % fj, fj);   // 15 flops
00266         bodyForces[atom1.getMobilizedBodyIndex()] -= SpatialVec( a1Station_G % fj, fj);   // 15 flops
00267     }
00268 
00269     energy_t calcPotentialEnergy(const VdwAtom& vdwAtom1, const VdwAtom& vdwAtom2, const State& state) const 
00270     {
00271         Real vdwScale = 1.0;  // TODO scale bonded atoms
00272 
00273         const AtomSubsystem::Atom& atom1 = atomSubsystem.getAtom(vdwAtom1.atomIndex);
00274         const AtomSubsystem::Atom& atom2 = atomSubsystem.getAtom(vdwAtom2.atomIndex);
00275 
00276         const Vec3& a1Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom1.atomIndex);
00277         const Vec3& a2Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom2.atomIndex);
00278 
00279         // TODO better combining rules
00280         const length_t dij = vdwAtom1.rMin + vdwAtom2.rMin;
00281         const energy_t eij = std::sqrt(vdwAtom1.wellDepth * vdwAtom2.wellDepth);
00282 
00283         return calcForceAndEnergy(a2Pos_G - a1Pos_G, dij, eij).energy * vdwScale;
00284     }
00285 
00286     // Calculate force between one atom and a bounding sphere
00287     void calcForce(const WallSphere& sphere, const VdwAtom& vdwAtom2, const State& state, Vector_<SpatialVec>& bodyForces) const
00288     {
00289         const AtomSubsystem::Atom& atom2 = atomSubsystem.getAtom(vdwAtom2.atomIndex);
00290 
00291         const Vec3& a2Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom2.atomIndex);
00292 
00293         // TODO better combining rules
00294         const length_t dij = sphere.vdwRadius + vdwAtom2.rMin;
00295         const energy_t eij = std::sqrt(sphere.wellDepth * vdwAtom2.wellDepth);
00296 
00297         Vec3 dv = a2Pos_G - sphere.center; // Atom relative to center of bounding sphere
00298 
00299         // Distance from atom to closest edge of bounding sphere
00300         Real centerDist = dv.norm();
00301         UnitVec3 centerDir(dv);
00302         
00303         // Vector to closest edge of sphere
00304         Vec3 rA = (centerDist - sphere.radius) * centerDir;
00305         // And to furthest edge of sphere, to avoid singularity at center
00306         Vec3 rB = (sphere.radius + centerDist) * centerDir;
00307 
00308         force_t fj = calcForceAndEnergy(rA, dij, eij).force;
00309         fj += calcForceAndEnergy(rB, dij, eij).force;
00310 
00311         const Vec3& a2Station_G = atomSubsystem.getAtomBodyStationInGround(state, vdwAtom2.atomIndex);
00312 
00313         // No force on the sphere - it's just a forcefield
00314         bodyForces[atom2.getMobilizedBodyIndex()] += SpatialVec( a2Station_G % fj, fj);   // 15 flops
00315     }
00316 
00317     energy_t calcPotentialEnergy(const WallSphere& sphere, const VdwAtom& vdwAtom2, const State& state) const
00318     {
00319         const AtomSubsystem::Atom& atom2 = atomSubsystem.getAtom(vdwAtom2.atomIndex);
00320 
00321         const Vec3& a2Pos_G = atomSubsystem.getAtomLocationInGround(state, vdwAtom2.atomIndex);
00322 
00323         // TODO better combining rules
00324         const length_t dij = sphere.vdwRadius + vdwAtom2.rMin;
00325         const energy_t eij = std::sqrt(sphere.wellDepth * vdwAtom2.wellDepth);
00326 
00327         Vec3 dv = a2Pos_G - sphere.center; // Atom relative to center of bounding sphere
00328 
00329         // Distance from atom to closest edge of bounding sphere
00330         Real centerDist = dv.norm();
00331         UnitVec3 centerDir(dv);
00332         
00333         // Vector to closest edge of sphere
00334         Vec3 rA = (sphere.radius - centerDist) * centerDir;
00335         // And to furthest edge of sphere, to avoid singularity at center
00336         Vec3 rB = -(sphere.radius + centerDist) * centerDir;
00337 
00338         energy_t energy = calcForceAndEnergy(rA, dij, eij).energy;
00339         energy += calcForceAndEnergy(rB, dij, eij).energy;
00340 
00341         return energy;
00342     }
00343 
00344     const AtomSubsystem& atomSubsystem;
00345 
00346     // TODO - place these data into State
00347     std::vector<VdwAtom> vdwAtoms;
00348     length_t cutoff;
00349     area_t cutoffSquared;
00350     std::vector<WallSphere> wallSpheres;
00351 };
00352 
00353 } // namespace SimTK
00354 
00355 #endif /* SIMTK_MOLMODEL_VANDERWAALS_FORCE_H_ */
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines