LCOV - code coverage report
Current view: top level - cltools - SimpleMD.cpp (source / functions) Hit Total Coverage
Test: plumed test coverage Lines: 304 320 95.0 %
Date: 2026-11-05 10:19:04 Functions: 20 20 100.0 %

          Line data    Source code
       1             : /* +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
       2             :    Copyright (c) 2012-2023 The plumed team
       3             :    (see the PEOPLE file at the root of the distribution for a list of names)
       4             : 
       5             :    See http://www.plumed.org for more information.
       6             : 
       7             :    This file is part of plumed, version 2.
       8             : 
       9             :    plumed is free software: you can redistribute it and/or modify
      10             :    it under the terms of the GNU Lesser General Public License as published by
      11             :    the Free Software Foundation, either version 3 of the License, or
      12             :    (at your option) any later version.
      13             : 
      14             :    plumed is distributed in the hope that it will be useful,
      15             :    but WITHOUT ANY WARRANTY; without even the implied warranty of
      16             :    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      17             :    GNU Lesser General Public License for more details.
      18             : 
      19             :    You should have received a copy of the GNU Lesser General Public License
      20             :    along with plumed.  If not, see <http://www.gnu.org/licenses/>.
      21             : +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */
      22             : #include "CLTool.h"
      23             : #include "core/CLToolRegister.h"
      24             : #include "core/PlumedMain.h"
      25             : #include "tools/Vector.h"
      26             : #include "tools/Random.h"
      27             : #include "tools/OpenMP.h"
      28             : #include <string>
      29             : #include <cstdio>
      30             : #include <cmath>
      31             : #include <vector>
      32             : #include <memory>
      33             : #include <algorithm>
      34             : 
      35             : namespace PLMD {
      36             : namespace cltools {
      37             : 
      38             : //+PLUMEDOC TOOLS simplemd
      39             : /*
      40             : simplemd allows one to do molecular dynamics on systems of Lennard-Jones atoms.
      41             : 
      42             : You can use simplemd to do a molecular dynamics simulation on a system of Lennard-Jones particles by using the following command
      43             : 
      44             : ```plumed
      45             : plumed simplemd < in
      46             : ```
      47             : 
      48             : The input to simplemd is specified in an input file that contains one directive per line like the one shown below.
      49             : 
      50             : ```plumed
      51             : #TOOL=simplemd
      52             : inputfile input.xyz
      53             : outputfile output.xyz
      54             : temperature 0.722
      55             : tstep 0.005
      56             : friction 1
      57             : forcecutoff 2.5
      58             : listcutoff  3.0
      59             : nstep 50
      60             : nconfig 10 trajectory.xyz
      61             : nstat   10 energies.dat
      62             : ```
      63             : 
      64             : This input instructs  simplemd to do 50 steps of MD at a temperature of 0.722 [Lennard Jones Units](https://en.wikipedia.org/wiki/Lennard-Jones_potential).
      65             : Notice that the atomic configurations are input and output in xyz format.
      66             : 
      67             : If you run the following command a description of all the directives that can be used in the input file will be output.
      68             : 
      69             : ```plumed
      70             : plumed simplemd --help
      71             : ```
      72             : 
      73             : */
      74             : //+ENDPLUMEDOC
      75             : 
      76             : // simple static function to close a file
      77             : // defined once here since it's used in many places in this file
      78             : // in addition, this seems the only way to use it in the write_statistics_fp_deleter member
      79             : static void (*deleter)(FILE* f) = [](auto f) {
      80             :   if(f) {
      81             :     std::fclose(f);
      82             :   }
      83             : };
      84             : 
      85             : class SimpleMD:
      86             :   public PLMD::CLTool {
      87           7 :   std::string description()const override {
      88           7 :     return "run lj code";
      89             :   }
      90             : 
      91             :   bool write_positions_first;
      92             :   bool write_statistics_first;
      93             :   int write_statistics_last_time_reopened;
      94             :   FILE* write_statistics_fp;
      95             :   std::unique_ptr<FILE,decltype(deleter)> write_statistics_fp_deleter{nullptr,deleter};
      96             : 
      97             : 
      98             : public:
      99        7321 :   static void registerKeywords( Keywords& keys ) {
     100        7321 :     CLTool::registerKeywords( keys );
     101        7321 :     keys.add("compulsory","nstep","The number of steps of dynamics you want to run");
     102        7321 :     keys.add("compulsory","temperature","NVE","the temperature at which you wish to run the simulation in LJ units. With NVE there is no thermostat and the atoms start at rest");
     103        7321 :     keys.add("compulsory","friction","off","The friction (in LJ units) for the Langevin thermostat that is used to keep the temperature constant");
     104        7321 :     keys.add("compulsory","tstep","0.005","the integration timestep in LJ units");
     105        7321 :     keys.add("compulsory","epsilon","1.0","LJ parameter");
     106        7321 :     keys.add("compulsory","sigma","1.0","LJ parameter");
     107        7321 :     keys.add("compulsory","inputfile","An xyz file containing the initial configuration of the system");
     108        7321 :     keys.add("compulsory","forcecutoff","2.5","the cutoff for the Lennard-Jones interactions in LJ units (the potential is shifted to zero at the cutoff)");
     109        7321 :     keys.add("compulsory","listcutoff","3.0","the cutoff for the neighbor list in LJ units, should be larger than forcecutoff");
     110        7321 :     keys.add("compulsory","outputfile","An output xyz file containing the final configuration of the system");
     111        7321 :     keys.add("compulsory","nconfig","10 trajectory.xyz","The frequency with which to write configurations to the trajectory file followed by the name of the trajectory file");
     112        7321 :     keys.add("compulsory","nstat","1 energies.dat","The frequency with which to write the statistics to the statistics file followed by the name of the statistics file");
     113        7321 :     keys.add("compulsory","idum","0","The random number seed");
     114        7321 :     keys.add("compulsory","ndim","3","The dimensionality of the system (some interesting LJ clusters are two dimensional)");
     115        7321 :     keys.add("compulsory","wrapatoms","false","If true, atomic coordinates are written wrapped in minimal cell");
     116        7321 :   }
     117             : 
     118          21 :   explicit SimpleMD( const CLToolOptions& co ) :
     119             :     CLTool(co),
     120          21 :     write_positions_first(true),
     121          21 :     write_statistics_first(true),
     122          21 :     write_statistics_last_time_reopened(0),
     123          21 :     write_statistics_fp(NULL) {
     124          21 :     inputdata=inputType::ifile;
     125          21 :   }
     126             : 
     127             : private:
     128             : 
     129             :   void
     130          14 :   read_input(double& temperature,
     131             :              double& tstep,
     132             :              double& friction,
     133             :              double& forcecutoff,
     134             :              double& listcutoff,
     135             :              int&    nstep,
     136             :              int&    nconfig,
     137             :              int&    nstat,
     138             :              bool&   wrapatoms,
     139             :              std::string& inputfile,
     140             :              std::string& outputfile,
     141             :              std::string& trajfile,
     142             :              std::string& statfile,
     143             :              int&    ndim,
     144             :              int&    idum,
     145             :              double& epsilon,
     146             :              double& sigma) {
     147             : 
     148             :     // Read everything from input file
     149             :     char buffer1[256];
     150             :     std::string tempstr;
     151          31 :     parse("temperature",tempstr);
     152             :     std::string frictionstr;
     153          14 :     parse("friction",frictionstr);
     154          14 :     if( tempstr=="NVE" ) {
     155             :       // no thermostat, atoms start at rest
     156           3 :       temperature=0.0;
     157           3 :       friction=0.0;
     158             :     } else {
     159          11 :       if(!Tools::convertNoexcept(tempstr,temperature) || temperature<0.0) {
     160           0 :         error("temperature should be NVE or a non negative number");
     161             :       }
     162          11 :       if(frictionstr=="off") {
     163           1 :         error("Specify friction for thermostat");
     164             :       }
     165          10 :       if(!Tools::convertNoexcept(frictionstr,friction) || friction<0.0) {
     166           0 :         error("friction should be a non negative number");
     167             :       }
     168             :     }
     169          13 :     parse("tstep",tstep);
     170          13 :     parse("forcecutoff",forcecutoff);
     171          13 :     parse("listcutoff",listcutoff);
     172          13 :     parse("nstep",nstep);
     173          13 :     parse("idum",idum);
     174          13 :     parse("epsilon",epsilon);
     175          13 :     parse("sigma",sigma);
     176             : 
     177             :     // Read in stuff with sanity checks
     178          26 :     parse("inputfile",inputfile);
     179          13 :     if(inputfile.length()==0) {
     180           0 :       error("Specify input file");
     181             :     }
     182          13 :     if(sigma<=0.0) {
     183           0 :       error("sigma should be positive");
     184             :     }
     185          13 :     if(listcutoff<forcecutoff) {
     186           1 :       error("listcutoff should not be smaller than forcecutoff");
     187             :     }
     188          24 :     parse("outputfile",outputfile);
     189          12 :     if(outputfile.length()==0) {
     190           3 :       error("Specify output file");
     191             :     }
     192             :     std::string nconfstr;
     193          12 :     parse("nconfig",nconfstr);
     194          12 :     buffer1[0]='\0';
     195          12 :     if(std::sscanf(nconfstr.c_str(),"%100d %255s",&nconfig,buffer1)!=2 || nconfig<=0) {
     196           2 :       error("nconfig should be followed by a positive stride and by the name of the trajectory file");
     197             :     }
     198             :     trajfile=buffer1;
     199             :     std::string nstatstr;
     200          11 :     parse("nstat",nstatstr);
     201          11 :     buffer1[0]='\0';
     202          11 :     if(std::sscanf(nstatstr.c_str(),"%100d %255s",&nstat,buffer1)!=2 || nstat<=0) {
     203           0 :       error("nstat should be followed by a positive stride and by the name of the statistics file");
     204             :     }
     205             :     statfile=buffer1;
     206          11 :     parse("ndim",ndim);
     207          11 :     if(ndim<1 || ndim>3) {
     208           0 :       error("ndim should be 1,2 or 3");
     209             :     }
     210             :     std::string w;
     211          11 :     parse("wrapatoms",w);
     212          11 :     wrapatoms=false;
     213          11 :     if(w.length()>0 && (w[0]=='T' || w[0]=='t')) {
     214           2 :       wrapatoms=true;
     215             :     }
     216          11 :   }
     217             : 
     218          11 :   void read_natoms(const std::string & inputfile,int & natoms) {
     219             : // read the number of atoms in file "input.xyz"
     220          11 :     FILE* fp=std::fopen(inputfile.c_str(),"r");
     221          11 :     if(!fp) {
     222           0 :       error(std::string("file ") + inputfile + std::string(" not found"));
     223             :     }
     224             : 
     225             : // call fclose when fp goes out of scope
     226             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     227             : 
     228          11 :     int ret=std::fscanf(fp,"%1000d",&natoms);
     229          11 :     if(ret!=1 || natoms<=0) {
     230           0 :       plumed_error() <<"Error reading number of atoms from file "<<inputfile;
     231             :     }
     232          11 :   }
     233             : 
     234          11 :   void read_positions(const std::string& inputfile,int natoms,std::vector<Vector>& positions,double cell[3]) {
     235             : // read positions and cell from a file called inputfile
     236             : // natoms (input variable) and number of atoms in the file should be consistent
     237          11 :     FILE* fp=std::fopen(inputfile.c_str(),"r");
     238          11 :     if(!fp) {
     239           0 :       error(std::string("file ") + inputfile + std::string(" not found"));
     240             :     }
     241             : // call fclose when fp goes out of scope
     242             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     243             : 
     244             :     char buffer[256];
     245             :     char atomname[256];
     246             :     char* cret=fgets(buffer,256,fp);
     247          11 :     if(cret==nullptr) {
     248           0 :       plumed_error() <<"Error reading buffer from file "<<inputfile;
     249             :     }
     250          11 :     int ret=std::fscanf(fp,"%1000lf %1000lf %1000lf",&cell[0],&cell[1],&cell[2]);
     251          11 :     if(ret!=3) {
     252           0 :       plumed_error() <<"Error reading cell line from file "<<inputfile;
     253             :     }
     254        1206 :     for(int i=0; i<natoms; i++) {
     255        1195 :       ret=std::fscanf(fp,"%255s %1000lf %1000lf %1000lf",atomname,&positions[i][0],&positions[i][1],&positions[i][2]);
     256             : // note: atomname is read but not used
     257        1195 :       if(ret!=4) {
     258           0 :         plumed_error() <<"Error reading atom line from file "<<inputfile;
     259             :       }
     260             :     }
     261          11 :   }
     262             : 
     263          11 :   void randomize_velocities(const int natoms,const int ndim,const double temperature,const std::vector<double>&masses,std::vector<Vector>& velocities,Random&random) {
     264             : // randomize the velocities according to the temperature
     265        1206 :     for(int iatom=0; iatom<natoms; iatom++)
     266        4773 :       for(int i=0; i<ndim; i++) {
     267        3578 :         velocities[iatom][i]=std::sqrt(temperature/masses[iatom])*random.Gaussian();
     268             :       }
     269          11 :   }
     270             : 
     271    10690043 :   void pbc(const double cell[3],const Vector & vin,Vector & vout) {
     272             : // apply periodic boundary condition to a vector
     273    42760172 :     for(int i=0; i<3; i++) {
     274    32070129 :       vout[i]=vin[i]-std::floor(vin[i]/cell[i]+0.5)*cell[i];
     275             :     }
     276    10690043 :   }
     277             : 
     278        3900 :   void check_list(const int natoms,const std::vector<Vector>& positions,const std::vector<Vector>&positions0,const double listcutoff,
     279             :                   const double forcecutoff,bool & recompute) {
     280             : // check if the neighbour list have to be recomputed
     281        3900 :     recompute=false;
     282        3900 :     auto delta2=(0.5*(listcutoff-forcecutoff))*(0.5*(listcutoff-forcecutoff));
     283             : // if ANY atom moved more than half of the skin thickness, recompute is set to .true.
     284      192715 :     for(int iatom=0; iatom<natoms; iatom++) {
     285      189245 :       if(modulo2(positions[iatom]-positions0[iatom])>delta2) {
     286         430 :         recompute=true;
     287         430 :         break;
     288             :       }
     289             :     }
     290        3900 :   }
     291             : 
     292             : 
     293         441 :   void compute_list(const int natoms,const std::vector<Vector>& positions,const double cell[3],const double listcutoff,
     294             :                     std::vector<std::vector<int> >& list) {
     295         441 :     double listcutoff2=listcutoff*listcutoff; // squared list cutoff
     296         441 :     list.assign(natoms,std::vector<int>());
     297             : // linked cells: they are used when there are at least 3 cells in every direction
     298             : // (so that the 27 neighboring cells are distinct). Cells are slightly larger
     299             : // than listcutoff so that rounding cannot miss a pair. The list of each atom is then
     300             : // sorted, so that the list (and thus the forces) are identical to the O(N^2) search
     301             :     int ncells[3];
     302             :     bool usecells=true;
     303        1764 :     for(int k=0; k<3; k++) {
     304        1323 :       const double nc=std::floor(cell[k]/(listcutoff*(1.0+1e-6)));
     305        1323 :       ncells[k]=(nc<64?int(nc):64); // at most 64^3 cells, larger cells are still correct
     306        1323 :       if(ncells[k]<3) {
     307             :         usecells=false;
     308             :       }
     309             :     }
     310         441 :     if(usecells) {
     311          25 :       const int ntot=ncells[0]*ncells[1]*ncells[2];
     312          25 :       std::vector<int> atomcell(natoms);
     313          25 :       std::vector<int> cellstart(ntot+1,0);
     314          25 :       std::vector<int> cellatoms(natoms);
     315         409 :       for(int iatom=0; iatom<natoms; iatom++) {
     316             :         int c[3];
     317        1536 :         for(int k=0; k<3; k++) {
     318        1152 :           double sc=positions[iatom][k]/cell[k];
     319        1152 :           sc-=std::floor(sc);
     320        1152 :           c[k]=int(sc*ncells[k]);
     321        1152 :           if(c[k]>=ncells[k]) {
     322           0 :             c[k]=ncells[k]-1;
     323             :           }
     324        1152 :           if(c[k]<0) {
     325           0 :             c[k]=0;
     326             :           }
     327             :         }
     328         384 :         atomcell[iatom]=(c[0]*ncells[1]+c[1])*ncells[2]+c[2];
     329         384 :         cellstart[atomcell[iatom]+1]++;
     330             :       }
     331      862540 :       for(int ic=0; ic<ntot; ic++) {
     332      862515 :         cellstart[ic+1]+=cellstart[ic];
     333             :       }
     334             :       {
     335          25 :         std::vector<int> fill(cellstart.begin(),cellstart.end()-1);
     336         409 :         for(int iatom=0; iatom<natoms; iatom++) {
     337         384 :           cellatoms[fill[atomcell[iatom]]++]=iatom;
     338             :         }
     339          25 :       }
     340          25 :       #   pragma omp parallel for num_threads(OpenMP::getNumThreads()) schedule(static,1)
     341             :       for(int iatom=0; iatom<natoms-1; iatom++) {
     342             :         const int ic=atomcell[iatom];
     343             :         const int c0=ic/(ncells[1]*ncells[2]);
     344             :         const int c1=(ic/ncells[2])%ncells[1];
     345             :         const int c2=ic%ncells[2];
     346             :         for(int d0=-1; d0<=1; d0++)
     347             :           for(int d1=-1; d1<=1; d1++)
     348             :             for(int d2=-1; d2<=1; d2++) {
     349             :               const int jc=(((c0+d0+ncells[0])%ncells[0])*ncells[1]+(c1+d1+ncells[1])%ncells[1])*ncells[2]+(c2+d2+ncells[2])%ncells[2];
     350             :               for(int jj=cellstart[jc]; jj<cellstart[jc+1]; jj++) {
     351             :                 const int jatom=cellatoms[jj];
     352             :                 if(jatom<=iatom) {
     353             :                   continue;
     354             :                 }
     355             :                 auto distance=positions[iatom]-positions[jatom];
     356             :                 Vector distance_pbc; // minimum-image distance of the two atoms
     357             :                 pbc(cell,distance,distance_pbc);
     358             :                 if(modulo2(distance_pbc)>listcutoff2) {
     359             :                   continue;
     360             :                 }
     361             :                 list[iatom].push_back(jatom);
     362             :               }
     363             :             }
     364             :         std::sort(list[iatom].begin(),list[iatom].end());
     365             :       }
     366             :       return;
     367          25 :     }
     368         416 :     #   pragma omp parallel for num_threads(OpenMP::getNumThreads()) schedule(static,1)
     369             :     for(int iatom=0; iatom<natoms-1; iatom++) {
     370             :       for(int jatom=iatom+1; jatom<natoms; jatom++) {
     371             :         auto distance=positions[iatom]-positions[jatom];
     372             :         Vector distance_pbc; // minimum-image distance of the two atoms
     373             :         pbc(cell,distance,distance_pbc);
     374             : // if the interparticle distance is larger than the cutoff, skip
     375             :         if(modulo2(distance_pbc)>listcutoff2) {
     376             :           continue;
     377             :         }
     378             :         list[iatom].push_back(jatom);
     379             :       }
     380             :     }
     381             :   }
     382             : 
     383        3911 :   void compute_forces(const int natoms,double epsilon, double sigma,const std::vector<Vector>& positions,const double cell[3],
     384             :                       double forcecutoff,const std::vector<std::vector<int> >& list,std::vector<Vector>& forces,double & engconf) {
     385        3911 :     double forcecutoff2=forcecutoff*forcecutoff; // squared force cutoff
     386        3911 :     double engcorrection=4.0*epsilon*(1.0/std::pow(forcecutoff2/(sigma*sigma),6.0)-1.0/std::pow(forcecutoff2/(sigma*sigma),3)); // energy necessary shift the potential avoiding discontinuities
     387             : // one buffer (forces and energy) per thread: they are summed in thread order after the loop,
     388             : // so that the result does not depend on the order in which the threads finish
     389        3911 :     const unsigned nt=OpenMP::getNumThreads();
     390        3911 :     std::vector<std::vector<Vector>> omp_forces_all(nt);
     391        3911 :     std::vector<double> omp_engconf_all(nt,0.0);
     392        3911 :     #   pragma omp parallel num_threads(nt)
     393             :     {
     394             :       const unsigned tid=OpenMP::getThreadNum();
     395             :       std::vector<Vector>& omp_forces(omp_forces_all[tid]);
     396             :       omp_forces.assign(forces.size(),Vector(0.0,0.0,0.0));
     397             :       double omp_engconf=0.0;
     398             :       #pragma omp for schedule(static,1)
     399             :       for(int iatom=0; iatom<natoms-1; iatom++) {
     400             :         for(unsigned jlist=0; jlist<list[iatom].size(); jlist++) {
     401             :           const int jatom=list[iatom][jlist];
     402             :           auto distance=positions[iatom]-positions[jatom];
     403             :           Vector distance_pbc;    // minimum-image distance of the two atoms
     404             :           pbc(cell,distance,distance_pbc);
     405             :           auto distance_pbc2=modulo2(distance_pbc);   // squared minimum-image distance
     406             : // if the interparticle distance is larger than the cutoff, skip
     407             :           if(distance_pbc2>forcecutoff2) {
     408             :             continue;
     409             :           }
     410             :           auto distance_pbcm2=sigma*sigma/distance_pbc2;
     411             :           auto distance_pbcm6=distance_pbcm2*distance_pbcm2*distance_pbcm2;
     412             :           auto distance_pbcm8=distance_pbcm6*distance_pbcm2;
     413             :           auto distance_pbcm12=distance_pbcm6*distance_pbcm6;
     414             :           auto distance_pbcm14=distance_pbcm12*distance_pbcm2;
     415             :           omp_engconf+=4.0*epsilon*(distance_pbcm12 - distance_pbcm6) - engcorrection;
     416             : // F = 24 epsilon (2 sigma^12/r^14 - sigma^6/r^8) r = 24 epsilon (2 m14 - m8) r / sigma^2, with m=sigma^2/r^2
     417             :           auto f=24.0*distance_pbc*(2.0*distance_pbcm14-distance_pbcm8)*epsilon/(sigma*sigma);
     418             :           omp_forces[iatom]+=f;
     419             :           omp_forces[jatom]-=f;
     420             :         }
     421             :       }
     422             :       omp_engconf_all[tid]=omp_engconf;
     423             :     }
     424        3911 :     engconf=0.0;
     425      235106 :     for(int i=0; i<natoms; i++) {
     426      231195 :       forces[i].zero();
     427             :     }
     428       10230 :     for(unsigned t=0; t<nt; t++) {
     429        6319 :       if(omp_forces_all[t].empty()) {
     430           0 :         continue;  // this thread did not run
     431             :       }
     432        6319 :       engconf+=omp_engconf_all[t];
     433      306385 :       for(int i=0; i<natoms; i++) {
     434      300066 :         forces[i]+=omp_forces_all[t][i];
     435             :       }
     436             :     }
     437        3911 :   }
     438             : 
     439        3900 :   void compute_engkin(const int natoms,const std::vector<double>& masses,const std::vector<Vector>& velocities,double & engkin) {
     440             : // calculate the kinetic energy from the velocities
     441        3900 :     engkin=0.0;
     442      233900 :     for(int iatom=0; iatom<natoms; iatom++) {
     443      230000 :       engkin+=0.5*masses[iatom]*modulo2(velocities[iatom]);
     444             :     }
     445        3900 :   }
     446             : 
     447             : 
     448        7800 :   void thermostat(const int natoms,const int ndim,const std::vector<double>& masses,const double dt,const double friction,
     449             :                   const double temperature,std::vector<Vector>& velocities,double & engint,Random & random) {
     450             : // Langevin thermostat, implemented as described in Bussi and Parrinello, Phys. Rev. E (2007)
     451             : // it is a linear combination of old velocities and new, randomly chosen, velocity,
     452             : // with proper coefficients
     453        7800 :     double c1=std::exp(-friction*dt);
     454      467800 :     for(int iatom=0; iatom<natoms; iatom++) {
     455      460000 :       double c2=std::sqrt((1.0-c1*c1)*temperature/masses[iatom]);
     456     1812000 :       for(int i=0; i<ndim; i++) {
     457     1352000 :         engint+=0.5*masses[iatom]*velocities[iatom][i]*velocities[iatom][i];
     458     1352000 :         velocities[iatom][i]=c1*velocities[iatom][i]+c2*random.Gaussian();
     459     1352000 :         engint-=0.5*masses[iatom]*velocities[iatom][i]*velocities[iatom][i];
     460             :       }
     461             :     }
     462        7800 :   }
     463             : 
     464          49 :   void write_positions(const std::string& trajfile,int natoms,const std::vector<Vector>& positions,const double cell[3],const bool wrapatoms) {
     465             : // write positions on file trajfile
     466             : // positions are appended at the end of the file
     467          49 :     Vector pos;
     468             :     FILE*fp;
     469          49 :     if(write_positions_first) {
     470          11 :       fp=std::fopen(trajfile.c_str(),"w");
     471          11 :       write_positions_first=false;
     472             :     } else {
     473          38 :       fp=std::fopen(trajfile.c_str(),"a");
     474             :     }
     475          49 :     plumed_assert(fp)<<"cannot open file "<<trajfile;
     476             : // call fclose when fp goes out of scope
     477             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     478             :     std::fprintf(fp,"%d\n",natoms);
     479          49 :     std::fprintf(fp,"%f %f %f\n",cell[0],cell[1],cell[2]);
     480        5355 :     for(int iatom=0; iatom<natoms; iatom++) {
     481             : // usually, it is better not to apply pbc here, so that diffusion
     482             : // is more easily calculated from a trajectory file:
     483        5306 :       if(wrapatoms) {
     484        1080 :         pbc(cell,positions[iatom],pos);
     485             :       } else {
     486        4226 :         pos=positions[iatom];
     487             :       }
     488        5306 :       std::fprintf(fp,"Ar %10.7f %10.7f %10.7f\n",pos[0],pos[1],pos[2]);
     489             :     }
     490          49 :   }
     491             : 
     492          11 :   void write_final_positions(const std::string& outputfile,int natoms,const std::vector<Vector>& positions,const double cell[3],const bool wrapatoms) {
     493             : // write positions on file outputfile
     494          11 :     Vector pos;
     495             :     FILE*fp;
     496          11 :     fp=std::fopen(outputfile.c_str(),"w");
     497          11 :     plumed_assert(fp)<<"cannot open file "<<outputfile;
     498             : // call fclose when fp goes out of scope
     499             :     std::unique_ptr<FILE,decltype(deleter)> fp_deleter(fp,deleter);
     500             :     std::fprintf(fp,"%d\n",natoms);
     501          11 :     std::fprintf(fp,"%f %f %f\n",cell[0],cell[1],cell[2]);
     502        1206 :     for(int iatom=0; iatom<natoms; iatom++) {
     503             : // usually, it is better not to apply pbc here, so that diffusion
     504             : // is more easily calculated from a trajectory file:
     505        1195 :       if(wrapatoms) {
     506         216 :         pbc(cell,positions[iatom],pos);
     507             :       } else {
     508         979 :         pos=positions[iatom];
     509             :       }
     510        1195 :       std::fprintf(fp,"Ar %10.7f %10.7f %10.7f\n",pos[0],pos[1],pos[2]);
     511             :     }
     512          11 :   }
     513             : 
     514             : 
     515         192 :   void write_statistics(const std::string & statfile,const int istep,const double tstep,
     516             :                         const int natoms,const int ndim,const double engkin,const double engconf,const double engint) {
     517             : // write statistics on file statfile
     518         192 :     if(write_statistics_first) {
     519             : // first time this routine is called, open the file
     520          11 :       write_statistics_fp=std::fopen(statfile.c_str(),"w");
     521          11 :       plumed_assert(write_statistics_fp)<<"cannot open file "<<statfile;
     522             :       write_statistics_fp_deleter.reset(write_statistics_fp);
     523          11 :       write_statistics_first=false;
     524             :     }
     525         192 :     if(istep-write_statistics_last_time_reopened>100) {
     526             : // every 100 steps, reopen the file to flush the buffer
     527             :       write_statistics_fp_deleter.reset(nullptr); // close file
     528          14 :       write_statistics_fp=std::fopen(statfile.c_str(),"a");
     529          14 :       plumed_assert(write_statistics_fp)<<"cannot open file "<<statfile;
     530             :       write_statistics_fp_deleter.reset(write_statistics_fp);
     531          14 :       write_statistics_last_time_reopened=istep;
     532             :     }
     533         192 :     std::fprintf(write_statistics_fp,"%d %f %f %f %f %f\n",istep,istep*tstep,2.0*engkin/double(ndim*natoms),engconf,engkin+engconf,engkin+engconf+engint);
     534         192 :   }
     535             : 
     536             : 
     537             : 
     538          14 :   int main(FILE* in,FILE*out,PLMD::Communicator& pc) override {
     539             :     int            natoms;       // number of atoms
     540          14 :     std::vector<Vector> positions;    // atomic positions
     541          14 :     std::vector<Vector> velocities;   // velocities
     542          14 :     std::vector<double> masses;       // masses
     543          14 :     std::vector<Vector> forces;       // forces
     544             :     double         cell[3];      // cell size
     545             :     double         cell9[3][3];  // cell size
     546             : 
     547             : // neighbour list variables
     548             : // see Allen and Tildesey book for details
     549          14 :     std::vector< std::vector<int> >  list; // neighbour list
     550          14 :     std::vector<Vector> positions0;   // reference atomic positions, i.e. positions when the neighbour list
     551             : 
     552             : // input parameters
     553             : // all of them have a reasonable default value, set in read_input()
     554             :     double      tstep;             // simulation timestep
     555             :     double      temperature;       // temperature
     556             :     double      friction;          // friction for Langevin dynamics (for NVE, use 0)
     557             :     double      listcutoff;        // cutoff for neighbour list
     558             :     double      forcecutoff;       // cutoff for forces
     559             :     int         nstep;             // number of steps
     560             :     int         nconfig;           // stride for output of configurations
     561             :     int         nstat;             // stride for output of statistics
     562             :     int         ndim;              // dimensionality of the system (1, 2, or 3)
     563             :     int         idum;              // seed
     564             :     int         plumedWantsToStop; // stop flag
     565             :     bool        wrapatoms;         // if true, atomic coordinates are written wrapped in minimal cell
     566             :     std::string inputfile;         // name of file with starting configuration (xyz)
     567             :     std::string outputfile;        // name of file with final configuration (xyz)
     568             :     std::string trajfile;          // name of the trajectory file (xyz)
     569             :     std::string statfile;          // name of the file with statistics
     570             : 
     571             :     double      epsilon, sigma;    // LJ parameters
     572             : 
     573             :     double engkin;                 // kinetic energy
     574             :     double engconf;                // configurational energy
     575             :     double engint;                 // integral for conserved energy in Langevin dynamics
     576             : 
     577             :     bool recompute_list;           // control if the neighbour list have to be recomputed
     578             : 
     579          14 :     Random random;                 // random numbers stream
     580             : 
     581          14 :     std::unique_ptr<PlumedMain> plumed;
     582             : 
     583             : // Commenting the next line it is possible to switch-off plumed
     584          17 :     plumed=Tools::make_unique<PLMD::PlumedMain>();
     585             : 
     586          14 :     if(plumed) {
     587          14 :       plumed->cmd("setRealPrecision",int(sizeof(double)));
     588             :     }
     589             : 
     590          14 :     read_input(temperature,tstep,friction,forcecutoff,
     591             :                listcutoff,nstep,nconfig,nstat,
     592             :                wrapatoms,inputfile,outputfile,trajfile,statfile,
     593             :                ndim,idum,epsilon,sigma);
     594             : 
     595             : // number of atoms is read from file inputfile
     596          11 :     read_natoms(inputfile,natoms);
     597             : 
     598             : // write the parameters in output so they can be checked
     599             :     std::fprintf(out,"%s %s\n","Starting configuration           :",inputfile.c_str());
     600             :     std::fprintf(out,"%s %s\n","Final configuration              :",outputfile.c_str());
     601          11 :     std::fprintf(out,"%s %d\n","Number of atoms                  :",natoms);
     602          11 :     std::fprintf(out,"%s %f\n","Temperature                      :",temperature);
     603          11 :     std::fprintf(out,"%s %f\n","Time step                        :",tstep);
     604          11 :     std::fprintf(out,"%s %f\n","Friction                         :",friction);
     605          11 :     std::fprintf(out,"%s %f\n","Cutoff for forces                :",forcecutoff);
     606          11 :     std::fprintf(out,"%s %f\n","Cutoff for neighbour list        :",listcutoff);
     607          11 :     std::fprintf(out,"%s %d\n","Number of steps                  :",nstep);
     608          11 :     std::fprintf(out,"%s %d\n","Stride for trajectory            :",nconfig);
     609             :     std::fprintf(out,"%s %s\n","Trajectory file                  :",trajfile.c_str());
     610          11 :     std::fprintf(out,"%s %d\n","Stride for statistics            :",nstat);
     611             :     std::fprintf(out,"%s %s\n","Statistics file                  :",statfile.c_str());
     612          11 :     std::fprintf(out,"%s %d\n","Dimensionality                   :",ndim);
     613          11 :     std::fprintf(out,"%s %d\n","Seed                             :",idum);
     614          11 :     std::fprintf(out,"%s %s\n","Are atoms wrapped on output?     :",(wrapatoms?"T":"F"));
     615          11 :     std::fprintf(out,"%s %f\n","Epsilon                          :",epsilon);
     616          11 :     std::fprintf(out,"%s %f\n","Sigma                            :",sigma);
     617             : 
     618             : // Setting the seed
     619          11 :     random.setSeed(idum);
     620             : 
     621             : // allocation of dynamical arrays
     622          11 :     positions.resize(natoms);
     623          11 :     positions0.resize(natoms);
     624          11 :     velocities.resize(natoms);
     625          11 :     forces.resize(natoms);
     626          11 :     masses.resize(natoms);
     627          11 :     list.resize(natoms);
     628             : 
     629             : // masses are hard-coded to 1
     630        1206 :     for(int i=0; i<natoms; ++i) {
     631        1195 :       masses[i]=1.0;
     632             :     }
     633             : 
     634             : // energy integral initialized to 0
     635          11 :     engint=0.0;
     636             : 
     637             : // positions are read from file inputfile
     638          11 :     read_positions(inputfile,natoms,positions,cell);
     639             : 
     640             : // velocities are randomized according to temperature
     641          11 :     randomize_velocities(natoms,ndim,temperature,masses,velocities,random);
     642             : 
     643          11 :     if(plumed) {
     644          11 :       plumed->cmd("setNatoms",natoms);
     645          11 :       plumed->cmd("setNoVirial");
     646          11 :       plumed->cmd("setMDEngine","simpleMD");
     647          11 :       plumed->cmd("setTimestep",tstep);
     648          11 :       plumed->cmd("setPlumedDat","plumed.dat");
     649          11 :       int pversion=0;
     650          11 :       plumed->cmd("getApiVersion",&pversion);
     651             : // setting kbT is only implemented with api>1
     652             : // even if not necessary in principle in SimpleMD (which is part of plumed)
     653             : // we leave the check here as a reference
     654          11 :       if(pversion>1 && temperature>0.0) {
     655          10 :         plumed->cmd("setKbT",temperature);
     656             :       }
     657          11 :       plumed->cmd("init");
     658             :     }
     659             : 
     660             : // neighbour list are computed, and reference positions are saved
     661          11 :     compute_list(natoms,positions,cell,listcutoff,list);
     662             :     {
     663             :       int list_size=0;
     664        1206 :       for(const auto &element: list) {
     665        1195 :         list_size+=element.size();
     666             :       }
     667             :       std::fprintf(out,"List size: %d\n",list_size);
     668             :     }
     669        1206 :     for(int iatom=0; iatom<natoms; ++iatom) {
     670        1195 :       positions0[iatom]=positions[iatom];
     671             :     }
     672             : 
     673             : // forces are computed before starting md
     674          11 :     compute_forces(natoms,epsilon,sigma,positions,cell,forcecutoff,list,forces,engconf);
     675             : 
     676             : // remove forces if ndim<3
     677          11 :     if(ndim<3)
     678           8 :       for(int iatom=0; iatom<natoms; ++iatom)
     679          14 :         for(int k=ndim; k<3; ++k) {
     680           7 :           forces[iatom][k]=0.0;
     681             :         }
     682             : 
     683             : // here is the main md loop
     684             : // Langevin thermostat is applied before and after a velocity-Verlet integrator
     685             : // the overall structure is:
     686             : //   thermostat
     687             : //   update velocities
     688             : //   update positions
     689             : //   (eventually recompute neighbour list)
     690             : //   compute forces
     691             : //   update velocities
     692             : //   thermostat
     693             : //   (eventually dump output information)
     694        3911 :     for(int istep=0; istep<nstep; istep++) {
     695        3900 :       thermostat(natoms,ndim,masses,0.5*tstep,friction,temperature,velocities,engint,random);
     696             : 
     697      233900 :       for(int iatom=0; iatom<natoms; iatom++) {
     698      230000 :         velocities[iatom]+=forces[iatom]*0.5*tstep/masses[iatom];
     699             :       }
     700             : 
     701      233900 :       for(int iatom=0; iatom<natoms; iatom++) {
     702      230000 :         positions[iatom]+=velocities[iatom]*tstep;
     703             :       }
     704             : 
     705             : // a check is performed to decide whether to recalculate the neighbour list
     706        3900 :       check_list(natoms,positions,positions0,listcutoff,forcecutoff,recompute_list);
     707        3900 :       if(recompute_list) {
     708         430 :         compute_list(natoms,positions,cell,listcutoff,list);
     709       44547 :         for(int iatom=0; iatom<natoms; ++iatom) {
     710       44117 :           positions0[iatom]=positions[iatom];
     711             :         }
     712             :         int list_size=0;
     713       44547 :         for(const auto &element: list) {
     714       44117 :           list_size+=element.size();
     715             :         }
     716             :         std::fprintf(out,"List size: %d\n",list_size);
     717             :       }
     718             : 
     719        3900 :       compute_forces(natoms,epsilon,sigma,positions,cell,forcecutoff,list,forces,engconf);
     720             : 
     721        3900 :       if(plumed) {
     722        3900 :         plumedWantsToStop=0;
     723       15600 :         for(int i=0; i<3; i++)
     724       46800 :           for(int k=0; k<3; k++) {
     725       35100 :             cell9[i][k]=0.0;
     726             :           }
     727       15600 :         for(int i=0; i<3; i++) {
     728       11700 :           cell9[i][i]=cell[i];
     729             :         }
     730        3900 :         plumed->cmd("setStep",istep+1);
     731        3900 :         plumed->cmd("setMasses",&masses[0]);
     732        3900 :         plumed->cmd("setForces",&forces[0][0]);
     733        3900 :         plumed->cmd("setEnergy",engconf);
     734        3900 :         plumed->cmd("setPositions",&positions[0][0]);
     735        3900 :         plumed->cmd("setBox",&cell9[0][0]);
     736        3900 :         plumed->cmd("setStopFlag",&plumedWantsToStop);
     737        3900 :         plumed->cmd("calc");
     738        3900 :         if(plumedWantsToStop) {
     739           0 :           nstep=istep;
     740             :         }
     741             :       }
     742             : // remove forces if ndim<3
     743        3900 :       if(ndim<3)
     744       16000 :         for(int iatom=0; iatom<natoms; ++iatom)
     745       28000 :           for(int k=ndim; k<3; ++k) {
     746       14000 :             forces[iatom][k]=0.0;
     747             :           }
     748             : 
     749      233900 :       for(int iatom=0; iatom<natoms; iatom++) {
     750      230000 :         velocities[iatom]+=forces[iatom]*0.5*tstep/masses[iatom];
     751             :       }
     752             : 
     753        3900 :       thermostat(natoms,ndim,masses,0.5*tstep,friction,temperature,velocities,engint,random);
     754             : 
     755             : // kinetic energy is calculated
     756        3900 :       compute_engkin(natoms,masses,velocities,engkin);
     757             : 
     758             : // eventually, write positions and statistics
     759        3900 :       if((istep+1)%nconfig==0) {
     760          49 :         write_positions(trajfile,natoms,positions,cell,wrapatoms);
     761             :       }
     762        3900 :       if((istep+1)%nstat==0) {
     763         192 :         write_statistics(statfile,istep+1,tstep,natoms,ndim,engkin,engconf,engint);
     764             :       }
     765             : 
     766             :     }
     767             : 
     768             : // call final plumed jobs
     769          11 :     if(plumed) {
     770          11 :       plumed->cmd("runFinalJobs");
     771             :     }
     772             : 
     773             : // write final positions
     774          11 :     write_final_positions(outputfile,natoms,positions,cell,wrapatoms);
     775             : 
     776          11 :     return 0;
     777          51 :   }
     778             : 
     779             : 
     780             : };
     781             : 
     782       21984 : PLUMED_REGISTER_CLTOOL(SimpleMD,"simplemd")
     783             : 
     784             : }
     785             : }
     786             : 
     787             : 
     788             : 
     789             : 

Generated by: LCOV version 1.16