Last commit for serdy.c: 0d9f0800be49834ca3f9c51a13da668fcc0e397a

Serial md code

Ramses van Zon [2016-09-27 13:53:05]
Serial md code
/*
 *  serdy.c
 *
 *  Example of a SERial molecular DYnamics simulation in C
 *
 *  Input (from standard input or filename given on command line):
 *   N = <number of particles>
 *   rho = <density of the system>
 *   T = <initial temperature (standard deviation of the velocities)>
 *   runtime = <runtime>
 *   dt = <time step>
 *   seed = <random number generator seed>
 *   equil = <equilibration time>
 *   usecells
 *   (when the latter flag is not present, cells will not be used
 *   within processes)
 *
 *  Output (from standard output): lines containing:
 *   time, energy E, pot. en. U, kin. en. K, temperature T, fluctuations, walltime-per-step, walltime-overall
 *
 *  Notes:
 *  - To compile: make
 *  - To run: ./pardy input.ini
 *    where input parameters are listed in the file "input.ini", and
 *    output is sent to stdout.
 *  - Appropriate values for the equilibrium time are best found by
 *    doing a short run and seeing when the potential energy has reach
 *    a stationary value.
 *  - All reported energies values are divided by the number of particles N.
 *  - Fluctuations are the root mean square of E-<E> with <E> the mean energy E.
 *
 *  Ramses van Zon, 13 November 2008
 *  - In Nov 25 2009:
 *    + condensed code
 *    + OpenMP version
 *  - In Aug 2016:
 *    + Renamed to ljhpc, included mpi header (not used yet)
 *  - In Sep 2016:
 *    + Added cells to determine interacting pairs.
 *    + Added MPI
 *    + Renamed to pardy
 *    + Change input format and modularized
 */

#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "ndmalloc.h"
#include "lcg.h"
#include "lattice.h"
#include "debug.h"
#include "system.h"
#include "atom.h"
#include "inifile.h"
#include "global.h"
#include "estimates.h"
#include "forces.h"
#include "cells.h"
#include "elapsed.h"

void sortParticlesAndComputeForces(int *N, atom_t atoms[], interaction_pairs_t* p, system_t* sys)
{
    /* sort particles into cells */
    cellDivide(atoms, sys);
    /* find neighbors */
    findPairsFromCells(p, sys);
    /* compute forces */
    sys->U = computeForces(*N, atoms, p, sys->L, false);
}

double periodic(double u, double L)
{
    while (u < -0.5*L)
        u += L;
    while (u >= 0.5*L)
        u -= L;
    return u;
}

void exchangeParticles(atom_t atoms[], system_t* sys)
{
    for (int i  =0; i < sys->N; ++i) {
        atoms[i].rx = periodic(atoms[i].rx, sys->L);
        atoms[i].ry = periodic(atoms[i].ry, sys->L);
        atoms[i].rz = periodic(atoms[i].rz, sys->L);
    }
}

void initialize(atom_t atoms[], interaction_pairs_t* p, system_t* sys)
{
    /* generate positions */
    /* initialize lattice position generator, then pick at most Ntot (will be less if parallel) */
    lattice_t lat = init_partial_lattice(sys->L, sys->Ntot,
                                         (double[3]){-0.5*sys->L,-0.5*sys->L,-0.5*sys->L},
                                         (double[3]){sys->L,sys->L,sys->L});
    int i;
    for (i = 0; i < sys->Ntot; ++i) {
        long long index = make_lattice_position(&lat, &(atoms[i].rx), &(atoms[i].ry), &(atoms[i].rz));
        if (index != NO_LATTICE_POSITION) {
            atoms[i].index = index;
        } else {
            break; /* no more points */
        }
    }
    sys->N = i;
    /* generate momenta */
    lcg_t rng = lcg_init_rand48(sys->seed);  /* initialize random number
                                                generator used in gaussian
                                                instead of "srand48(seed)" */
    double scale = sqrt(sys->T0);
    sys->K = 0;
    /* must do some skipping (4 random numbers per particle)*/
    lcg_skip(&rng, 4*atoms[0].index);
    for (int i = 0; i < sys->N; ++i){
        atoms[i].px = scale*lcg_normal(&rng);
        atoms[i].py = scale*lcg_normal(&rng);
        atoms[i].pz = scale*lcg_normal(&rng);
        lcg_normal(&rng); /* must have even number of normal random numbers */
        sys->K += atoms[i].px*atoms[i].px + atoms[i].py*atoms[i].py + atoms[i].pz*atoms[i].pz;
        if (i<(sys->N)-1) {
            int index_diff = atoms[i+1].index - atoms[i].index;
            if (index_diff > 1)
                lcg_skip(&rng, 4*(index_diff-1));
        }
    }
    /* compute instantaneous kinetic temperature and energy */
    sys->K *= 0.5;
    sortParticlesAndComputeForces(&(sys->N), atoms, p, sys);
    double Utot = sys->U, Ktot = sys->K;
    /* report results */
    printf("#  step    time    E       U       K       T    <[E-<E>]^2>  walltime(s) cum.walltime(s)\n");
    printf("%7d %7.3f %7.3f %7.3f %7.3f %7.3f   .....         0.000      0.000\n",
           0, 0., (Ktot+Utot) / (sys->Ntot), Utot / (sys->Ntot), Ktot / (sys->Ntot), (2*Ktot)/(DIM*(sys->Ntot)));
}

/* Verlet integration step */
void integrateStep(system_t* sys, atom_t atoms[], interaction_pairs_t* p)
{
    /* first momentum half step (forces assume to be computed in
       initialization or in the last step) */
    for (int i = 0; i < sys->N; ++i) {
        atoms[i].px += 0.5*sys->dt*atoms[i].fx;
        atoms[i].py += 0.5*sys->dt*atoms[i].fy;
        atoms[i].pz += 0.5*sys->dt*atoms[i].fz;
    }
    /* full free motion step */
    for (int i = 0; i<sys->N; ++i) {
        atoms[i].rx = atoms[i].rx + sys->dt*atoms[i].px;
        atoms[i].ry = atoms[i].ry + sys->dt*atoms[i].py;
        atoms[i].rz = atoms[i].rz + sys->dt*atoms[i].pz;
    }
    /* send atoms that have digressed too far to other side (pbc)*/
    exchangeParticles(atoms, sys);
    /* positions were changed, so recompute the forces */
    sortParticlesAndComputeForces(&(sys->N), atoms, p, sys);
    /* final momentum half-step */
    double K2 = 0;
    for (int i = 0; i < sys->N; ++i) {
        atoms[i].px+=0.5*sys->dt*atoms[i].fx;
        atoms[i].py+=0.5*sys->dt*atoms[i].fy;
        atoms[i].pz+=0.5*sys->dt*atoms[i].fz;
        K2 += atoms[i].px*atoms[i].px + atoms[i].py*atoms[i].py + atoms[i].pz*atoms[i].pz;
    }
    /* finish computing K */
    sys->K = 0.5*K2;
}

/* integration and measurement */
void run(system_t* sys)
{
    /* setup cell stuff */
    for (int d = 0; d < DIM; ++d) {
        if (sys->usecells)
            sys->nc[d] = (int)(sys->L/rc);
        else
            sys->nc[d] = 1;
        sys->totnc[d]    = sys->nc[d];
        sys->minc[d]     = 0;
        sys->maxc[d]     = sys->nc[d] - 1;
        sys->cellsize[d] = sys->L/sys->nc[d];
    }
    sys->ncprod = sys->nc[0] * sys->nc[1] * sys->nc[2];
    sys->start_of_cell = ndmalloc(sizeof(sys->start_of_cell[0]), 1, sys->ncprod);
    sys->n_in_cell = ndmalloc(sizeof(sys->n_in_cell[0]), 1, sys->ncprod);
    /* allocate buffers */
    const int Nmax = estimateNmax(sys->N, sys->rho, (double[3]){sys->L, sys->L, sys->L}, sys->cellsize);
    const long long Npairsmax = estimateNpairsmax(sys->N, sys->rho, (double[3]){sys->L, sys->L, sys->L}, sys->cellsize);
    interaction_pairs_t p;
    atom_t*   atoms  = ndmalloc(sizeof(atom_t), 1, Nmax);
    int       numSteps = (int)(sys->runtime/sys->dt+0.5);
    int       equilSteps = (int)(sys->equil/sys->dt+0.5);
    int       count;                 /* counts time steps */
    int       numPoints = 0;         /* counts measurements */
    double    sumE = 0;              /* total energy accumulated over steps */
    double    sumE2 = 0;             /* total energy squared accumulated */
    double    avgE, avgE2, fluctE;   /* average energy, its square, and its fluctuations */
    interaction_pairs_alloc(&p, Npairsmax);
    /* draw initial conditions */
    initialize(atoms, &p, sys);
    double  walltimestart = elapsed_etime();
    double  walltimelast = walltimestart;
    /* perform equilibration steps */
    for (count = 0; count < equilSteps; ++count) {
        integrateStep(sys, atoms, &p);
        double t = elapsed_etime();
        printf("%7d %7.3f %7.3f %7.3f %7.3f %7.3f   .....    %10.3lf %10.3lf\n",
               count+1, (count+1)*sys->dt, (sys->K+sys->U)/sys->Ntot, sys->U/sys->Ntot, sys->K/sys->Ntot, (2*sys->K)/(DIM*sys->Ntot), t-walltimelast, t-walltimestart );
        walltimelast = t;
    }
    /* perform rest of time steps */
    for (count = equilSteps; count<numSteps; ++count) {
        integrateStep(sys, atoms, &p);
        double Etot = sys->K + sys->U;
        sumE  += Etot;
        sumE2 += Etot * Etot;
        ++numPoints;
        avgE   = sumE/numPoints;
        avgE2  = sumE2/numPoints;
        if (avgE2 > avgE*avgE) {
            fluctE = sqrt(avgE2-avgE*avgE);
        } else {  /* correct for round-off */
            fluctE = 0.0;
        }
        double t = elapsed_etime();
        printf("%7d %7.3f %7.3f %7.3f %7.3f %7.3f %10.2e %10.3lf %10.3lf\n",
               count+1, (count+1)*sys->dt, Etot/sys->Ntot, sys->U/sys->Ntot, sys->K/sys->Ntot, (2*sys->K)/(DIM*sys->Ntot), fluctE/sys->Ntot, t-walltimelast, t-walltimestart);
        walltimelast = t;
    }
    interaction_pairs_free(&p);
    ndfree(atoms);
    ndfree(sys->n_in_cell);
    ndfree(sys->start_of_cell);
}

/* program start */
int main(int argc, char* argv[])
{
    system_t sys;
    FILE* file = (argc>1)?fopen(argv[1],"r"):stdin;
    if (file == NULL)
        exit(1);
    key_value_table_t* ini = calloc(1,sizeof(*ini));
    kvt_read(ini, file);
    sys.Ntot     = kvt_lookup_long_long(ini, "N");
    sys.rho      = kvt_lookup_double(ini, "rho");
    sys.T0       = kvt_lookup_double(ini, "T");
    sys.runtime  = kvt_lookup_double(ini, "runtime");
    sys.dt       = kvt_lookup_double(ini, "dt");
    sys.seed     = kvt_lookup_long(ini, "seed");
    sys.equil    = kvt_lookup_double(ini, "equil");
    sys.usecells = kvt_lookup_entry(ini, "usecells") != NULL;
    sys.L        = cbrt(sys.Ntot/sys.rho);
    if (argc > 1 && file != NULL)
        fclose(file);
    free(ini);
    printf("#Ntot=%lld rho=%f L=%f T=%f runtime=%f dt=%f seed=%ld equil=%f usecells=%d\n",
           sys.Ntot,sys.rho,sys.L,sys.T0,sys.runtime,sys.dt,sys.seed,sys.equil,sys.usecells);
    if (sanity_check(&sys, rc, true))
        run(&sys);
    else
        exit(1);
    return 0;
}
ViewGit