Molecular Dynamic with Lennard-Jones potential with verlet list
This example show a simple Lennard-Jones molecular dynamic simulation in a stable regime. We will use Verlet-list in order to get a speed-up from force calculation
Constants
Here we define some useful constants
constexpr int velocity = 0;
constexpr int force = 1;
Calculate forces
In this function we calculate the forces between particles. It require the vector of particles, the Verlet-list and sigma for the Lennard-Jhones potential. The function is exactly the same as the original with the following changes
- See also
- Calculate forces
- The function accept a VerletList instead of a CellList
void calc_forces(
vector_dist<3,
double,
aggregate<
double[3],
double[3]> > & vd,
VerletList<3,
double,
Mem_fast<>,
shift<3, double> > & NN,
double sigma12,
double sigma6,
double r_cut)
{
It is a class that work like a vector of vector.
Class for Verlet list implementation.
aggregate of properties, from a list of object if create a struct that follow the OPENFPM native stru...
- There is no call to updateCellList
- How to get an iterator over neighborhood of a particle
auto Np = NN.template getNNIterator<NO_CHECK>(p.getKey());
Teh rest remain the same
void calc_forces(
vector_dist<3,
double,
aggregate<
double[3],
double[3]> > & vd,
VerletList<3,
double,
Mem_fast<>,
shift<3, double> > & NN,
double sigma12,
double sigma6,
double r_cut)
{
auto it2 = vd.getDomainIterator();
while (it2.isNext())
{
auto p = it2.get();
vd.template getProp<force>(p)[0] = 0.0;
vd.template getProp<force>(p)[1] = 0.0;
vd.template getProp<force>(p)[2] = 0.0;
auto Np = NN.template getNNIterator<NO_CHECK>(p.getKey());
while (Np.isNext())
{
auto q = Np.get();
if (q == p.getKey()) {++Np; continue;};
double rn = norm2(r);
if (rn > r_cut * r_cut) {++Np;continue;}
Point<3,double> f = 24.0*(2.0 *sigma12 / (rn*rn*rn*rn*rn*rn*rn) - sigma6 / (rn*rn*rn*rn)) * r;
vd.template getProp<force>(p)[0] += f.get(0);
vd.template getProp<force>(p)[1] += f.get(1);
vd.template getProp<force>(p)[2] += f.get(2);
++Np;
}
++it2;
}
}
This class implement the point shape in an N-dimensional space.
Calculate energy
We also need a function to calculate energy, this function require the same parameter as calculate forces
- See also
- Calculate energy
The following changes has been made
- The function accept a VerletList instead of a cell-List
- There is no call to updateCellList
- How to get an iterator over neigborhood particles
double calc_energy(
vector_dist<3,
double,
aggregate<
double[3],
double[3]> > & vd,
VerletList<3,
double,
Mem_fast<>,
shift<3, double> > & NN,
double sigma12,
double sigma6,
double r_cut)
{
double E = 0.0;
double rc = r_cut*r_cut;
double shift = 2.0 * ( sigma12 / (rc*rc*rc*rc*rc*rc) - sigma6 / ( rc*rc*rc) );
auto it2 = vd.getDomainIterator();
while (it2.isNext())
{
auto p = it2.get();
auto Np = NN.template getNNIterator<NO_CHECK>(p.getKey());
while (Np.isNext())
{
auto q = Np.get();
if (q == p.getKey()) {++Np; continue;};
double rn = norm2(xp - xq);
if (rn >= r_cut*r_cut)
{++Np;continue;}
E += 2.0 * ( sigma12 / (rn*rn*rn*rn*rn*rn) - sigma6 / ( rn*rn*rn) ) -
shift;
++Np;
}
E += (vd.template getProp<velocity>(p)[0]*vd.template getProp<velocity>(p)[0] +
vd.template getProp<velocity>(p)[1]*vd.template getProp<velocity>(p)[1] +
vd.template getProp<velocity>(p)[2]*vd.template getProp<velocity>(p)[2]) / 2;
++it2;
}
return E;
}
Simulation
The simulation is equal to the simulation explained in the example molecular dynamic
- See also
- Molecular Dynamic with Lennard-Jones potential
The differences are that:
- The Ghost must be r_cut+skin We create a Verlet list with skin instead of a Cell list
auto NN = vd.getVerlet(r_gskin);
- every 10 steps we do a map and update the verlet-list, in all the other case we just do skip labelling
if (cnt % 10 == 0)
{
vd.map();
vd.template ghost_get<>();
vd.updateVerlet(NN,r_gskin);
}
else
{
vd.template ghost_get<>(SKIP_LABELLING);
}
Explanation
Updating the verlet list is extremely expensive. For this reason we create a Verlet list that contain r_cut + skin particles. Using the fact that during the full simulation each particle does not move more than 0.0015 in one iteration, if the skin is 0.03 we can update the Verlet list every \( \frac{0.03}{2 \cdot 0.0015} = 10 \). The 2 factor if given by the fact that in the worst case where one particle is going left and one on the right from the prospective of one particle the particle moove \( 2 \cdot 0.0015 \).
Because the Verlet lists are constructed based on the local-id of the particles a map or a ghost_get would invalidate the verlet. For this reason the map is called every 10 time-step (when we update the verlet), and a particular ghost_get with SKIP_LABELLING is used during every iteration.
The function ghost_get with skip labeling does not recompute the particle to send but use the the ids of the old particles updating the positions (and properties if needed) and keeping the old indexes without invalidating the Verlet-list. Doing this we can avoid to send particles that are entering the ghost area r_cut+skin. Because we know that no particle in 10 iteration can travel for a distance bigger than the skin, we are sure that in 10 iteration no-new particle that were not in the r_cut+skin ghost area can enter the ghost area r_cut.
double dt = 0.00025;
double sigma = 0.1;
double r_cut = 3.0*sigma;
double r_gskin = 1.3*r_cut;
double sigma12 = pow(sigma,12);
double sigma6 = pow(sigma,6);
openfpm_init(&argc,&argv);
size_t sz[3] = {10,10,10};
size_t bc[3]={PERIODIC,PERIODIC,PERIODIC};
auto it = vd.getGridIterator(sz);
while (it.isNext())
{
vd.add();
auto key = it.get();
vd.getLastPos()[0] = key.get(0) * it.getSpacing(0);
vd.getLastPos()[1] = key.get(1) * it.getSpacing(1);
vd.getLastPos()[2] = key.get(2) * it.getSpacing(2);
vd.template getLastProp<velocity>()[0] = 0.0;
vd.template getLastProp<velocity>()[1] = 0.0;
vd.template getLastProp<velocity>()[2] = 0.0;
vd.template getLastProp<force>()[0] = 0.0;
vd.template getLastProp<force>()[1] = 0.0;
vd.template getLastProp<force>()[2] = 0.0;
++it;
}
auto NN = vd.getVerlet(r_gskin);
calc_forces(vd,NN,sigma12,sigma6,r_cut);
unsigned long int f = 0;
int cnt = 0;
double max_disp = 0.0;
for (size_t i = 0; i < 10000 ; i++)
{
auto it3 = vd.getDomainIterator();
double max_displ = 0.0;
while (it3.isNext())
{
auto p = it3.get();
vd.template getProp<velocity>(p)[0] += 0.5*dt*vd.template getProp<force>(p)[0];
vd.template getProp<velocity>(p)[1] += 0.5*dt*vd.template getProp<force>(p)[1];
vd.template getProp<velocity>(p)[2] += 0.5*dt*vd.template getProp<force>(p)[2];
Point<3,double> disp({vd.template getProp<velocity>(p)[0]*dt,vd.template getProp<velocity>(p)[1]*dt,vd.template getProp<velocity>(p)[2]*dt});
vd.getPos(p)[0] += disp.
get(0);
vd.getPos(p)[1] += disp.get(1);
vd.getPos(p)[2] += disp.get(2);
if (disp.norm() > max_displ)
max_displ = disp.norm();
++it3;
}
if (max_disp < max_displ)
max_disp = max_displ;
if (cnt % 10 == 0)
{
vd.map();
vd.template ghost_get<>();
vd.updateVerlet(NN,r_gskin);
}
else
{
vd.template ghost_get<>(SKIP_LABELLING);
}
cnt++;
calc_forces(vd,NN,sigma12,sigma6,r_cut);
auto it4 = vd.getDomainIterator();
while (it4.isNext())
{
auto p = it4.get();
vd.template getProp<velocity>(p)[0] += 0.5*dt*vd.template getProp<force>(p)[0];
vd.template getProp<velocity>(p)[1] += 0.5*dt*vd.template getProp<force>(p)[1];
vd.template getProp<velocity>(p)[2] += 0.5*dt*vd.template getProp<force>(p)[2];
++it4;
}
if (i % 100 == 0)
{
vd.deleteGhost();
vd.write_frame("particles_",f);
vd.ghost_get<>(SKIP_LABELLING);
double energy = calc_energy(vd,NN,sigma12,sigma6,r_cut);
auto & vcl = create_vcluster();
vcl.sum(energy);
vcl.max(max_disp);
vcl.execute();
x.add(i);
y.add({energy});
if (vcl.getProcessUnitID() == 0)
std::cout << "Energy: " << energy << " " << max_disp << " " << std::endl;
max_disp = 0.0;
f++;
}
}
std::cout <<
"Time: " << tsim.
getwct() << std::endl;
This class represent an N-dimensional box.
__device__ __host__ const T & get(unsigned int i) const
Get coordinate.
Implementation of VCluster class.
Implementation of 1-D std::vector like structure.
Class for cpu time benchmarking.
void stop()
Stop the timer.
void start()
Start the timer.
double getwct()
Return the elapsed real time.
Finalize
At the very end of the program we have always to de-initialize the library