Files
lammps/src/APIP/fix_lambda_thermostat_apip.cpp

629 lines
19 KiB
C++

/* ----------------------------------------------------------------------
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
https://www.lammps.org/, Sandia National Laboratories
LAMMPS development team: developers@lammps.org
Copyright (2003) Sandia Corporation. Under the terms of Contract
DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
certain rights in this software. This software is distributed under
the GNU General Public License.
See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */
/* ----------------------------------------------------------------------
Contributing author: David Immel (d.immel@fz-juelich.de, FZJ, Germany)
------------------------------------------------------------------------- */
#include "fix_lambda_thermostat_apip.h"
#include "atom.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "memory.h"
#include "modify.h"
#include "my_page.h"
#include "neigh_list.h"
#include "neighbor.h"
#include "random_park.h"
#include "update.h"
#include <algorithm>
using namespace LAMMPS_NS;
using namespace FixConst;
#define PGDELTA 1
/* ---------------------------------------------------------------------- */
FixLambdaThermostatAPIP::FixLambdaThermostatAPIP(LAMMPS *lmp, int narg, char **arg) :
Fix(lmp, narg, arg), list(nullptr), energy_change_atom(nullptr), peratom_stats(nullptr),
local_numneigh(nullptr), local_firstneigh(nullptr), ipage(nullptr), jlist_copy(nullptr)
{
// set global options for fix class
vector_flag = 1;
size_vector = 6;
extvector = 0;
// set default vaues
dtf = 0;
update_stats = false;
int seed = 42;
rescaling_N_neighbours = 200;
// output
peratom_flag = 0;
peratom_freq = -1; // default from fix.cpp
size_peratom_cols = 4;
// parse arguments
for (int iarg = 3; iarg < narg; iarg++) {
if (strcmp(arg[iarg], "seed") == 0) {
if (iarg + 1 >= narg)
error->all(FLERR, "fix lambda_thermostat/apip: seed requires one argument");
seed = utils::inumeric(FLERR, arg[iarg + 1], false, lmp);
if (seed <= 0) { error->all(FLERR, "fix lambda_thermostat/apip seed <= 0"); }
iarg++;
} else if (strcmp(arg[iarg], "store_atomic_forces") == 0) {
if (iarg + 1 >= narg)
error->all(FLERR, "fix lambda_thermostat/apip: store_atomic_forces requires one argument");
peratom_flag = 1;
peratom_freq = utils::inumeric(FLERR, arg[iarg + 1], false, lmp);
if (peratom_freq < 1)
error->all(FLERR, "fix lambda_thermostat/apip: frequency of store_atomic_forces < 1");
iarg++;
} else if (strcmp(arg[iarg], "N_rescaling") == 0) {
if (iarg + 1 >= narg)
error->all(FLERR, "fix lambda_thermostat/apip: mode number requires one argument");
rescaling_N_neighbours = utils::inumeric(FLERR, arg[iarg + 1], false, lmp);
iarg += 1;
} else
error->all(FLERR, "fix lambda_thermostat/apip: unknown argument {}", arg[iarg]);
}
// error checks
if (!atom->apip_e_fast_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with e_simple.");
}
if (!atom->apip_e_precise_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with e_complex.");
}
if (!atom->apip_lambda_const_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with lambda_const.");
}
if (!atom->apip_lambda_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with lambda.");
}
if (!atom->apip_f_const_lambda_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with f_const_lambda.");
}
if (!atom->apip_f_dyn_lambda_flag) {
error->all(FLERR, "fix lambda_thermostat/apip requires atomic style with f_dyn_lambda.");
}
if (rescaling_N_neighbours <= 1)
error->all(FLERR, "fix lambda_thermostat/apip: rescaling_N_neighbours <= 1");
// rng for shuffle
random_mt = std::mt19937(seed);
// init output values
energy_change_kin = energy_change_pot = 0;
nmax_energy = 0;
reduceflag = 0;
for (int i = 0; i < size_vector; i++) { outvec[i] = 0; }
sum_energy_change = sum_energy_violation = 0;
n_energy_violation = n_energy_differences = 0;
if (peratom_flag) {
// zero the array since dump may access it on timestep 0
// zero the array since a variable may access it before first run
nmax_stats = atom->nmax;
memory->create(peratom_stats, nmax_stats, size_peratom_cols,
"lambda_thermostat/apip:peratom_stats");
array_atom = peratom_stats;
int nlocal = atom->nlocal;
for (int i = 0; i < nlocal; i++)
for (int j = 0; j < size_peratom_cols; j++) peratom_stats[i][j] = 0;
} else {
nmax_stats = 0;
}
nmax_list = 0;
pgsize = oneatom = 0;
}
/* ---------------------------------------------------------------------- */
FixLambdaThermostatAPIP::~FixLambdaThermostatAPIP()
{
memory->destroy(energy_change_atom);
memory->destroy(peratom_stats);
memory->destroy(local_numneigh);
memory->sfree(local_firstneigh);
memory->destroy(jlist_copy);
delete[] ipage;
}
/* ---------------------------------------------------------------------- */
int FixLambdaThermostatAPIP::setmask()
{
int mask = 0;
mask |= POST_FORCE;
mask |= END_OF_STEP;
return mask;
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::init()
{
dtf = 0.5 * update->dt * force->ftm2v;
// full neighbour list for thermostating
neighbor->add_request(this, NeighConst::REQ_FULL);
int counter = 0;
for (int i = 0; i < modify->nfix; i++)
if (strcmp(modify->fix[i]->style, "lambda_thermostat/apip") == 0) counter++;
if (counter > 1)
error->all(FLERR, "fix lambda_thermostat/apip: more than one fix lambda_thermostat/apip");
// local neighbor list
// create pages if first time or if neighbor pgsize/oneatom has changed
int create = 0;
if (ipage == nullptr) create = 1;
if (pgsize != neighbor->pgsize) create = 1;
if (oneatom != neighbor->oneatom) create = 1;
if (oneatom != neighbor->oneatom || ipage == nullptr) {
// allocate memory for copy of one ngh list
memory->destroy(jlist_copy);
memory->create(jlist_copy, neighbor->oneatom, "lambda_thermostat/apip:jlist_copy");
}
if (create) {
delete[] ipage;
pgsize = neighbor->pgsize;
oneatom = neighbor->oneatom;
int nmypage = comm->nthreads;
ipage = new MyPage<int>[nmypage];
for (int i = 0; i < nmypage; i++) ipage[i].init(oneatom, pgsize, PGDELTA);
}
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::init_list(int /*id*/, NeighList *ptr)
{
list = ptr;
}
/* ----------------------------------------------------------------------
Create neighbor list from main neighbor list with local atoms only.
The velocity updates are dependent on the velocity.
Thus, one would need to communicate a velocity change of a ghost atom directly to all neighbouring processors.
This communication would kill the performance.
Thus, update only local particles.
------------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::local_neighbour_list()
{
int i, j, ii, jj, n, inum, jnum, nlocal;
int *ilist, *jlist, *numneigh, **firstneigh;
int *neighptr;
int *mask = atom->mask;
if (atom->nmax > nmax_list) {
nmax_list = atom->nmax;
memory->destroy(local_numneigh);
memory->sfree(local_firstneigh);
memory->create(local_numneigh, nmax_list, "lambda_thermostat/apip:numneigh");
local_firstneigh =
(int **) memory->smalloc(nmax_list * sizeof(int *), "lambda_thermostat/apip:firstneigh");
}
nlocal = atom->nlocal;
inum = list->inum;
ilist = list->ilist;
numneigh = list->numneigh;
firstneigh = list->firstneigh;
// store all local neighbours of local atoms
// scan full neighbor list of I
ipage->reset();
for (ii = 0; ii < inum; ii++) {
i = ilist[ii];
n = 0;
neighptr = ipage->vget();
jlist = firstneigh[i];
jnum = numneigh[i];
for (jj = 0; jj < jnum; jj++) {
j = jlist[jj];
j &= NEIGHMASK;
if (j < nlocal && mask[j] & groupbit) neighptr[n++] = j;
}
local_firstneigh[i] = neighptr;
local_numneigh[i] = n;
ipage->vgot(n);
if (ipage->status()) error->one(FLERR, "Neighbor list overflow, boost neigh_modify one");
}
}
/* ---------------------------------------------------------------------- */
double FixLambdaThermostatAPIP::calculate_kinetic_energy(int i)
{
double *v = atom->v[i];
double m = (atom->rmass ? atom->rmass[i] : atom->mass[atom->type[i]]);
return 0.5 * force->mvv2e * m * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::post_force(int /*vflag*/)
{
init_peratom_stats();
calculate_energy_change();
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::end_of_step()
{
apply_thermostat();
}
/**
* Init per-atom array with zeros.
*/
void FixLambdaThermostatAPIP::init_peratom_stats()
{
if ((!peratom_flag) || (update->ntimestep % peratom_freq != 0)) {
update_stats = false;
return;
}
update_stats = true;
int nlocal;
nlocal = atom->nlocal;
// grow stats array if required
if (atom->nmax > nmax_stats) {
memory->destroy(peratom_stats);
nmax_stats = atom->nmax;
memory->create(peratom_stats, nmax_stats, size_peratom_cols,
"lambda_thermostat/apip:peratom_stats");
array_atom = peratom_stats;
}
for (int i = 0; i < nlocal; i++)
peratom_stats[i][0] = peratom_stats[i][1] = peratom_stats[i][2] = peratom_stats[i][3] = 0;
}
/**
* Calculate the energy difference of atoms that needs to be corrected
* by the local thermostat.
*/
void FixLambdaThermostatAPIP::calculate_energy_change()
{
double **v = atom->v;
double *rmass = atom->rmass;
double *mass = atom->mass;
int *type = atom->type;
int *mask = atom->mask;
int nlocal = atom->nlocal;
if (igroup == atom->firstgroup) nlocal = atom->nfirst;
double *e_simple = atom->apip_e_fast;
double *e_complex = atom->apip_e_precise;
double *lambda_const = atom->apip_lambda_const;
double *lambda = atom->apip_lambda;
double **f_const_lambda = atom->apip_f_const_lambda;
double **f_dyn_lambda = atom->apip_f_dyn_lambda;
double masstmp, dtfm, changetmp;
// allocate memory for energy change
if (atom->nmax > nmax_energy) {
memory->destroy(energy_change_atom);
nmax_energy = atom->nmax;
memory->create(energy_change_atom, nmax_energy, "lambda_thermostat/apip:energy_change_atom");
}
// reset calculated changes
energy_change_pot = 0;
energy_change_kin = 0;
for (int i = 0; i < nlocal; i++) { energy_change_atom[i] = 0; }
// calculate potential energy differences
for (int i = 0; i < nlocal; i++) {
if ((!(mask[i] & groupbit)) || lambda_const[i] == lambda[i]) { continue; }
if (e_simple[i] == 0 || e_complex[i] == 0)
error->one(FLERR, "lambda = {} != {} = lambda_const and e_simple = {} and e_complex = {}",
lambda[i], lambda_const[i], e_simple[i], e_complex[i]);
changetmp =
(lambda_const[i] - lambda[i]) * e_simple[i] + (lambda[i] - lambda_const[i]) * e_complex[i];
energy_change_atom[i] += changetmp;
energy_change_pot += changetmp;
}
// calculate kinetic energy difference
// consider all local atoms
for (int i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) { continue; }
masstmp = (rmass ? rmass[i] : mass[type[i]]);
dtfm = dtf / masstmp;
// dtfm = Delta t / 2 (in corresponding units)
changetmp = (v[i][0] * 2 * dtfm * (f_const_lambda[i][0] - f_dyn_lambda[i][0]) +
dtfm * dtfm *
(f_const_lambda[i][0] * f_const_lambda[i][0] -
f_dyn_lambda[i][0] * f_dyn_lambda[i][0]) +
v[i][1] * 2 * dtfm * (f_const_lambda[i][1] - f_dyn_lambda[i][1]) +
dtfm * dtfm *
(f_const_lambda[i][1] * f_const_lambda[i][1] -
f_dyn_lambda[i][1] * f_dyn_lambda[i][1]) +
v[i][2] * 2 * dtfm * (f_const_lambda[i][2] - f_dyn_lambda[i][2]) +
dtfm * dtfm *
(f_const_lambda[i][2] * f_const_lambda[i][2] -
f_dyn_lambda[i][2] * f_dyn_lambda[i][2])) *
masstmp * force->mvv2e * 0.5;
energy_change_atom[i] += changetmp;
energy_change_kin += changetmp;
}
if (update_stats) {
for (int i = 0; i < nlocal; i++) peratom_stats[i][4] = energy_change_atom[i];
}
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::apply_thermostat()
{
double xtmp, ytmp, ztmp, masstmp, massj;
double m_cm, v_cm[3], beta_rescaling, k_rel, v_rel[3], radicand;
int i, ii, inum, *ilist;
int j, jj, jnum, *jlist;
double **x = atom->x;
double **v = atom->v;
double *rmass = atom->rmass;
double *mass = atom->mass;
int *type = atom->type;
int *mask = atom->mask;
double *lambda_const = atom->apip_lambda_const;
double *lambda = atom->apip_lambda;
// calculate local neighbour list without ghost atoms
local_neighbour_list();
inum = list->inum;
ilist = list->ilist;
// reset stats
// outvec has to be calculated again
reduceflag = 1;
sum_energy_change = 0;
n_energy_differences = 0;
// perform bath collisions
// consider all local atoms
for (ii = 0; ii < inum; ii++) {
i = ilist[ii];
if (!(mask[i] & groupbit)) { continue; }
if (energy_change_atom[i] == 0) { continue; }
// update stats
n_energy_differences++;
// store constant information of target atom
masstmp = (rmass ? rmass[i] : mass[type[i]]);
xtmp = x[i][0];
ytmp = x[i][1];
ztmp = x[i][2];
// get neighbour list
jlist = local_firstneigh[i];
jnum = local_numneigh[i];
if (jnum == 0)
error->one(FLERR,
"fix lambda_thermostat/apip: thermostating required for particle with no local "
"particles in neighbour list particle: {} {} {} groupbit {}\n",
xtmp, ytmp, ztmp, mask[i] & groupbit);
double e_remain = energy_change_atom[i];
// copy ngh list
for (jj = 0; jj < jnum; jj++) jlist_copy[jj] = jlist[jj];
// shuffle neighbour list for random rescaling set
std::shuffle(jlist_copy, jlist_copy + jnum, random_mt);
// rescale velocities relative to centre of mass velocity
const int n_ngh = MIN(rescaling_N_neighbours, jnum);
if (n_ngh < 2)
error->one(FLERR,
"fix lambda_thermostat/apip: rescaling not possible for local ngh list size {}",
jnum);
// 1. calculate centre of mass velocity
// start with own particle ...
m_cm = masstmp;
v_cm[0] = masstmp * v[i][0];
v_cm[1] = masstmp * v[i][1];
v_cm[2] = masstmp * v[i][2];
// ... and include neighbours
for (jj = 0; jj < n_ngh; jj++) {
j = jlist_copy[jj];
j &= NEIGHMASK;
massj = (rmass ? rmass[j] : mass[type[j]]);
m_cm += massj;
v_cm[0] += massj * v[j][0];
v_cm[1] += massj * v[j][1];
v_cm[2] += massj * v[j][2];
}
// normalisation
v_cm[0] /= m_cm;
v_cm[1] /= m_cm;
v_cm[2] /= m_cm;
// 2. calculate beta_rescaling
// calculate kinetic energy of relative velocity for own particle ...
v_rel[0] = v[i][0] - v_cm[0];
v_rel[1] = v[i][1] - v_cm[1];
v_rel[2] = v[i][2] - v_cm[2];
k_rel = masstmp * (v_rel[0] * v_rel[0] + v_rel[1] * v_rel[1] + v_rel[2] * v_rel[2]);
// ... and include neighbours
for (jj = 0; jj < n_ngh; jj++) {
j = jlist_copy[jj];
j &= NEIGHMASK;
massj = (rmass ? rmass[j] : mass[type[j]]);
v_rel[0] = v[j][0] - v_cm[0];
v_rel[1] = v[j][1] - v_cm[1];
v_rel[2] = v[j][2] - v_cm[2];
k_rel += massj * (v_rel[0] * v_rel[0] + v_rel[1] * v_rel[1] + v_rel[2] * v_rel[2]);
}
// normalisation
k_rel *= force->mvv2e / 2.0;
radicand = e_remain / k_rel + 1;
if (radicand < 0) {
// cooling is not possible
// e_remain is the requested energy change
// radicand = 0 <=> e_remain = -k_rel
// -> save the energy error
sum_energy_violation += (-e_remain - k_rel); // > 0
n_energy_violation++;
e_remain += k_rel; // save corrected part
// use smallest possible radicand
radicand = 0;
}
beta_rescaling = sqrt(radicand) - 1;
// 3. apply velocity changes
// start with own particle ...
v_rel[0] = beta_rescaling * (v[i][0] - v_cm[0]);
v_rel[1] = beta_rescaling * (v[i][1] - v_cm[1]);
v_rel[2] = beta_rescaling * (v[i][2] - v_cm[2]);
v[i][0] += v_rel[0];
v[i][1] += v_rel[1];
v[i][2] += v_rel[2];
if (update_stats) {
// forces
peratom_stats[i][0] += v_rel[0] * masstmp / dtf;
peratom_stats[i][1] += v_rel[1] * masstmp / dtf;
peratom_stats[i][2] += v_rel[2] * masstmp / dtf;
}
// ... continue with neighbours
for (jj = 0; jj < n_ngh; jj++) {
j = jlist_copy[jj];
j &= NEIGHMASK;
v_rel[0] = beta_rescaling * (v[j][0] - v_cm[0]);
v_rel[1] = beta_rescaling * (v[j][1] - v_cm[1]);
v_rel[2] = beta_rescaling * (v[j][2] - v_cm[2]);
v[j][0] += v_rel[0];
v[j][1] += v_rel[1];
v[j][2] += v_rel[2];
if (update_stats) {
// forces
massj = (rmass ? rmass[j] : mass[type[j]]);
peratom_stats[j][0] += v_rel[0] * massj / dtf;
peratom_stats[j][1] += v_rel[1] * massj / dtf;
peratom_stats[j][2] += v_rel[2] * massj / dtf;
}
}
sum_energy_change += fabs(e_remain);
}
// lambda_const is used -> reset to lambda
for (ii = 0; ii < inum; ii++) {
i = ilist[ii];
if (mask[i] & groupbit) lambda_const[i] = lambda[i];
}
}
/* ---------------------------------------------------------------------- */
double FixLambdaThermostatAPIP::compute_vector(int i)
{
// 0 # atoms with energy differences compared to lambda_const
// 1 # bath collisions
// 2 total change of potential energy compared to lambda_const (sum over all atoms)
// 3 total change of kinetic energy compared to lambda_const (sum over all atoms)
// 4 total energy change due to thermostat
if (reduceflag) {
// perform reduction only once per step (if at all)
outvec[0] = n_energy_differences;
outvec[1] = energy_change_pot;
outvec[2] = energy_change_kin;
outvec[3] = sum_energy_change;
outvec[4] = sum_energy_violation;
outvec[5] = n_energy_violation;
MPI_Allreduce(MPI_IN_PLACE, &outvec, size_vector, MPI_DOUBLE, MPI_SUM, world);
reduceflag = 0;
}
if (i < size_vector) return outvec[i];
return 0;
}
/* ---------------------------------------------------------------------- */
void FixLambdaThermostatAPIP::reset_dt()
{
dtf = 0.5 * update->dt * force->ftm2v;
}
/* ----------------------------------------------------------------------
memory usage of local atom-based arrays
------------------------------------------------------------------------- */
double FixLambdaThermostatAPIP::memory_usage()
{
double bytes = 0;
bytes += (double) nmax_energy * sizeof(double);
bytes += (double) nmax_stats * size_peratom_cols * sizeof(double);
bytes += (double) nmax_list * sizeof(int);
bytes += (double) nmax_list * sizeof(int *);
for (int i = 0; i < comm->nthreads; i++) bytes += ipage[i].size();
return bytes;
}