Molmodel
|
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_ */