Files
lammps/src/DIELECTRIC/fix_polarize_bem_icc.cpp

511 lines
18 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 authors:
Trung Nguyen and Monica Olvera de la Cruz (Northwestern)
References:
1) Tyagi, Suzen, Sega, Barbosa, Kantorovich, Holm, J. Chem. Phys. 2010,
132, 154112
2) Barros, Sinkovits, Luijten, J. Chem. Phys 2014, 140, 064903
Implement a boundary-element solver for image charge computation (ICC)
using successive overrelaxation
------------------------------------------------------------------------- */
#include "fix_polarize_bem_icc.h"
#include "atom.h"
#include "atom_vec_dielectric.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "group.h"
#include "kspace.h"
#include "math_const.h"
#include "msm_dielectric.h"
#include "pair_coul_cut_dielectric.h"
#include "pair_coul_long_dielectric.h"
#include "pair_lj_cut_coul_cut_dielectric.h"
#include "pair_lj_cut_coul_debye_dielectric.h"
#include "pair_lj_cut_coul_long_dielectric.h"
#include "pair_lj_cut_coul_msm_dielectric.h"
#include "pppm_dielectric.h"
#include "random_park.h"
#include "update.h"
#include <cmath>
#include <cstring>
using namespace LAMMPS_NS;
using namespace FixConst;
using MathConst::MY_PI;
/* ---------------------------------------------------------------------- */
FixPolarizeBEMICC::FixPolarizeBEMICC(LAMMPS *_lmp, int narg, char **arg) : Fix(_lmp, narg, arg)
{
if (narg < 5) error->all(FLERR, "Illegal fix polarize/bem/icc command");
avec = dynamic_cast<AtomVecDielectric *>(atom->style_match("dielectric"));
if (!avec) error->all(FLERR, "Fix polarize requires atom style dielectric");
// parse required arguments
nevery = utils::inumeric(FLERR, arg[3], false, lmp);
if (nevery < 0) error->all(FLERR, "Illegal fix polarize/bem/icc command");
double tol = utils::numeric(FLERR, arg[4], false, lmp);
tol_abs = tol_rel = tol;
itr_max = 50;
omega = 0.7;
randomized = 0;
ave_charge = 0;
efield_pair = nullptr;
efield_kspace = nullptr;
comm_forward = 1;
kspaceflag = 0;
global_freq = 1;
vector_flag = 1;
size_vector = 2;
extvector = 0;
// set flags for arrays to clear in force_clear()
torqueflag = extraflag = 0;
if (atom->torque_flag) torqueflag = 1;
if (atom->avec->forceclearflag) extraflag = 1;
}
/* ---------------------------------------------------------------------- */
int FixPolarizeBEMICC::setmask()
{
int mask = 0;
mask |= PRE_FORCE;
return mask;
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::init()
{
int ncount = group->count(igroup);
if (comm->me == 0) {
utils::logmesg(lmp, "BEM/ICC solver for {} induced charges\n", ncount);
utils::logmesg(lmp, " using pair style {}\n", force->pair_style);
if (force->kspace) utils::logmesg(lmp, " using kspace style {}\n", force->kspace_style);
}
// initialize random induced charges with zero sum
if (randomized) {
int i;
double *q = atom->q;
int *mask = atom->mask;
int nlocal = atom->nlocal;
auto random = new RanPark(lmp, seed_charge + comm->me);
for (i = 0; i < 100; i++) random->uniform();
double sum, tmp = 0;
for (i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
q[i] = ave_charge * (random->uniform() - 0.5);
tmp += q[i];
}
MPI_Allreduce(&tmp, &sum, 1, MPI_DOUBLE, MPI_SUM, world);
sum /= (double) ncount;
tmp = 0;
for (i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
q[i] -= sum;
tmp += q[i];
}
MPI_Allreduce(&tmp, &sum, 1, MPI_DOUBLE, MPI_SUM, world);
delete random;
}
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::setup(int /*vflag*/)
{
// check if the pair styles in use are compatible
if (strcmp(force->pair_style, "lj/cut/coul/long/dielectric") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulLongDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/long/dielectric/omp") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulLongDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/msm/dielectric") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulMSMDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/cut/dielectric") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulCutDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/cut/dielectric/omp") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulCutDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/debye/dielectric") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulDebyeDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "lj/cut/coul/debye/dielectric/omp") == 0)
efield_pair = (dynamic_cast<PairLJCutCoulDebyeDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "coul/long/dielectric") == 0)
efield_pair = (dynamic_cast<PairCoulLongDielectric *>(force->pair))->efield;
else if (strcmp(force->pair_style, "coul/cut/dielectric") == 0)
efield_pair = (dynamic_cast<PairCoulCutDielectric *>(force->pair))->efield;
else
error->all(FLERR, "Pair style not compatible with fix polarize/bem/icc");
// check if kspace is used for force computation
if (force->kspace) {
kspaceflag = 1;
if (strcmp(force->kspace_style, "pppm/dielectric") == 0)
efield_kspace = (dynamic_cast<PPPMDielectric *>(force->kspace))->efield;
else if (strcmp(force->kspace_style, "msm/dielectric") == 0)
efield_kspace = (dynamic_cast<MSMDielectric *>(force->kspace))->efield;
else
error->all(FLERR, "Kspace style not compatible with fix polarize/bem/icc");
} else {
if (kspaceflag == 1) { // users specified kspace yes but there is no kspace pair style
error->warning(FLERR, "No Kspace pair style available for fix polarize/bem/icc");
kspaceflag = 0;
}
}
// NOTE: epsilon0e2q converts (epsilon0 * efield) to the unit of (charge unit / squared distance unit)
// efield is computed by pair and kspace styles in the unit of energy unit / charge unit / distance unit
// for units real efield is in the unit of kcal/mol/e/A
// converting from (F/m) (kcal/mol/e/A) to e/A^2 (1 e = 1.6e-19 C, 1 m = 1e+10 A)
// epsilon0e2q = 8.854187812813e-12 (C^2/N/m^2) * (4184 Nm/6.023e+23) /e/A
// = 8.854187812813e-12 * (4184/6.023e+23) * (1/1.6e-19)^2 e^2 / (1e+10 A) /e/A
// = 0.000240263377163643 e/A^2
// for units metal efield is in the unit of eV/e/A
// converting from (F/m) (eV/e/A) to e/A^2 (1 V = 1 Nm/C)
// epsilon0e2q = 8.854187812813e-12 (C^2/N/m^2) * (1 e Nm/C) /e/A
// = 8.854187812813e-12 * 1/1.6e-19 e^2 / (1e+10 A) /e/A
// = 0.00553386738300813 e/A^2
// for units si efield is in the unit of J/C/m
// converting from (F/m) (J/C/m) to C/m^2
// epsilon0e2q = 8.854187812813e-12 (C^2/N/m^2) * (1 Nm/C/m)
// = 8.854187812813e-12 C/m^2
// for units nano efield is in the unit of attogram nm^2/ns^2/e/nm
// converting from (F/m) (attogram nm^2/ns^2/e/nm) to e/nm^2
// epsilon0e2q = 8.854187812813e-12 (C^2/N/m^2) * (1e-21 kg nm^2 / (1e-18s^2) / e / nm)
// = 8.854187812813e-12 (C^2/N/m^2) * (1e-21 kg 1e-9 m / (1e-18s^2) / e)
// = 8.854187812813e-12 (1/1.6e-19)^2 (1e-21 * 1e-9 / (1e-18)) e / (1e+18 nm^2)
// = 0.000345866711328125 e/nm^2
epsilon0e2q = 1.0;
if (strcmp(update->unit_style, "real") == 0)
epsilon0e2q = 0.000240263377163643 * (4 * MY_PI);
else if (strcmp(update->unit_style, "metal") == 0)
epsilon0e2q = 0.00553386738300813 * (4 * MY_PI);
else if (strcmp(update->unit_style, "si") == 0)
epsilon0e2q = 8.854187812813e-12 * (4 * MY_PI);
else if (strcmp(update->unit_style, "nano") == 0)
epsilon0e2q = 0.000345866711328125 * (4 * MY_PI);
else if (strcmp(update->unit_style, "lj") != 0)
error->all(FLERR, "Only unit styles 'lj', 'real', 'metal', 'si' and 'nano' are supported");
compute_induced_charges();
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::pre_force(int)
{
if (nevery == 0) return;
if (update->ntimestep % nevery) return;
compute_induced_charges();
// make sure forces are reset to zero before actual forces are computed
force_clear();
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::compute_induced_charges()
{
double *q_scaled = atom->q_scaled;
double *q = atom->q;
double **norm = atom->mu;
double *area = atom->area;
double *ed = atom->ed;
double *em = atom->em;
double *epsilon = atom->epsilon;
int *mask = atom->mask;
int nlocal = atom->nlocal;
int eflag = 1;
int vflag = 0;
int itr;
// use Eq. (64) in Barros et al. to initialize the induced charges
// Note: area[i] is included here to ensure correct charge unit
// for direct use in force/efield compute
// for induced charges q_real stores the free surface charge
// q_real are read from the data file
// Note that the electrical fields here are due to the rescaled real charges,
// and also multiplied by epsilon[i]
// for the interface particles assume that epsilon[i] = em[i]
// NOTE: 13Dec2022 pair and kspace with dielectric suffix operate on q_scaled
force_clear();
force->pair->compute(eflag, vflag);
if (kspaceflag) force->kspace->compute(eflag, vflag);
if (force->newton) comm->reverse_comm();
for (int i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
double Ex = efield_pair[i][0];
double Ey = efield_pair[i][1];
double Ez = efield_pair[i][2];
if (kspaceflag) {
Ex += efield_kspace[i][0];
Ey += efield_kspace[i][1];
Ez += efield_kspace[i][2];
}
// divide (Ex,Ey,Ez) by epsilon[i] here
double ndotE = epsilon0e2q * (Ex * norm[i][0] + Ey * norm[i][1] + Ez * norm[i][2]) /
epsilon[i] / (2 * MY_PI);
double q_free = q[i];
double q_bound = 0;
q_bound = (1.0 / em[i] - 1) * q_free - (ed[i] / (2 * em[i])) * ndotE * area[i];
q_scaled[i] = q_free + q_bound;
}
// communicate q_scaled between neighboring procs
comm->forward_comm(this);
// iterate until convergence
for (itr = 0; itr < itr_max; itr++) {
force_clear();
force->pair->compute(eflag, vflag);
if (kspaceflag) force->kspace->compute(eflag, vflag);
if (force->newton) comm->reverse_comm();
double tol = 0;
for (int i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
double q_free = q[i];
double qtmp = q_scaled[i] - q_free;
double Ex = efield_pair[i][0];
double Ey = efield_pair[i][1];
double Ez = efield_pair[i][2];
if (kspaceflag) {
Ex += efield_kspace[i][0];
Ey += efield_kspace[i][1];
Ez += efield_kspace[i][2];
}
// Eq. (69) in Barros et al., sigma_f[i] = q_real[i] / area[i]
// note the area[i] is included here to ensure correct charge unit
// for direct use in force/efield compute
double ndotE = epsilon0e2q * (Ex * norm[i][0] + Ey * norm[i][1] + Ez * norm[i][2]) /
(4 * MY_PI) / epsilon[i];
double q_bound = q_scaled[i] - q_free;
q_bound = (1 - omega) * q_bound +
omega * ((1.0 / em[i] - 1) * q_free - (ed[i] / em[i]) * ndotE * area[i]);
q_scaled[i] = q_free + q_bound;
// Eq. (11) in Tyagi et al., with f from Eq. (6)
// NOTE: Tyagi et al. defined the normal vector n_i pointing
// from the medium containg the ions toward the other medium,
// which makes the normal vector direction depend on the ion position
// Also, since Tyagi et al. chose epsilon_1 for the uniform dielectric constant
// of the equivalent system, there is epsilon_1 in f in Eq. (6).
// Here we are using (q/epsilon_i) for the real charges and 1.0 for the equivalent system
// hence there's no epsilon_1 in the factor f
//double dot = (Ex*norm[i][0] + Ey*norm[i][1] + Ez*norm[i][2]);
//double f = (ed[i] / (2 * em[i])) / (2*MY_PI);
//q[i] = (1 - omega) * q[i] - omega * epsilon0 * f * dot * area[i];
double delta = fabs(qtmp - q_bound);
double r = (fabs(qtmp) > 0) ? delta / fabs(qtmp) : 0;
if (tol < r) tol = r;
}
// communicate q_scaled for efield compute in the next iteration
comm->forward_comm(this);
MPI_Allreduce(&tol, &rho, 1, MPI_DOUBLE, MPI_MAX, world);
if (itr > 0 && rho < tol_rel) break;
}
iterations = itr;
// compute the total induced charges of the interface particles
// for interface particles: set the charge to be the sum of unscaled (free) charges and induced charges
double tmp = 0;
for (int i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
double q_bound = q_scaled[i] - q[i];
tmp += q_bound;
q[i] = q_scaled[i];
}
// ensure sum of all induced charges being zero
int ncount = group->count(igroup);
double sum = 0;
MPI_Allreduce(&tmp, &sum, 1, MPI_DOUBLE, MPI_SUM, world);
double qboundave = sum/(double)ncount;
for (int i = 0; i < nlocal; i++) {
if (!(mask[i] & groupbit)) continue;
q[i] -= qboundave;
}
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::force_clear()
{
int nbytes = sizeof(double) * atom->nlocal;
if (force->newton) nbytes += sizeof(double) * atom->nghost;
if (nbytes) {
memset(&atom->f[0][0], 0, 3 * nbytes);
if (torqueflag) memset(&atom->torque[0][0], 0, 3 * nbytes);
if (extraflag) atom->avec->force_clear(0, nbytes);
}
}
/* ---------------------------------------------------------------------- */
int FixPolarizeBEMICC::modify_param(int narg, char **arg)
{
int iarg = 0;
while (iarg < narg) {
if (strcmp(arg[iarg], "itr_max") == 0) {
if (iarg + 2 > narg) error->all(FLERR, "Illegal fix_modify command");
itr_max = utils::numeric(FLERR, arg[iarg + 1], false, lmp);
iarg += 2;
} else if (strcmp(arg[iarg], "omega") == 0) {
if (iarg + 2 > narg) error->all(FLERR, "Illegal fix_modify command");
omega = utils::numeric(FLERR, arg[iarg + 1], false, lmp);
iarg += 2;
} else if (strcmp(arg[iarg], "kspace") == 0) {
if (iarg + 2 > narg) error->all(FLERR, "Illegal fix_modify command");
kspaceflag = utils::logical(FLERR, arg[iarg + 1], false, lmp);
iarg += 2;
} else if (strcmp(arg[iarg], "dielectrics") == 0) {
if (iarg + 6 > narg) error->all(FLERR, "Illegal fix_modify command");
double epsiloni = -1, areai = -1;
double qunscaledi = 0;
int set_charge = 0;
double ediff = utils::numeric(FLERR, arg[iarg + 1], false, lmp);
double emean = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
if (strcmp(arg[iarg + 3], "NULL") != 0)
epsiloni = utils::numeric(FLERR, arg[iarg + 3], false, lmp);
if (strcmp(arg[iarg + 4], "NULL") != 0)
areai = utils::numeric(FLERR, arg[iarg + 4], false, lmp);
if (strcmp(arg[iarg + 5], "NULL") != 0) {
qunscaledi = utils::numeric(FLERR, arg[iarg + 5], false, lmp);
set_charge = 1;
}
set_dielectric_params(ediff, emean, epsiloni, areai, set_charge, qunscaledi);
iarg += 6;
} else if (strcmp(arg[iarg], "rand") == 0) {
if (iarg + 3 > narg) error->all(FLERR, "Illegal fix_modify command");
ave_charge = utils::numeric(FLERR, arg[iarg + 1], false, lmp);
seed_charge = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
randomized = 1;
iarg += 3;
} else
error->all(FLERR, "Illegal fix_modify command");
}
return iarg;
}
/* ---------------------------------------------------------------------- */
int FixPolarizeBEMICC::pack_forward_comm(int n, int *list, double *buf, int /*pbc_flag*/,
int * /*pbc*/)
{
int m;
for (m = 0; m < n; m++) buf[m] = atom->q_scaled[list[m]];
return n;
}
/* ---------------------------------------------------------------------- */
void FixPolarizeBEMICC::unpack_forward_comm(int n, int first, double *buf)
{
int i, m;
for (m = 0, i = first; m < n; m++, i++) atom->q_scaled[i] = buf[m];
}
/* ----------------------------------------------------------------------
set dielectric params for the atoms in the group
------------------------------------------------------------------------- */
void FixPolarizeBEMICC::set_dielectric_params(double ediff, double emean, double epsiloni,
double areai, int set_charge, double qvalue)
{
double *area = atom->area;
double *ed = atom->ed;
double *em = atom->em;
double *q = atom->q;
double *epsilon = atom->epsilon;
int *mask = atom->mask;
int nlocal = atom->nlocal;
for (int i = 0; i < nlocal; i++) {
if (mask[i] & groupbit) {
ed[i] = ediff;
em[i] = emean;
if (areai > 0) area[i] = areai;
if (epsiloni > 0) epsilon[i] = epsiloni;
if (set_charge) q[i] = qvalue;
}
}
}
/* ----------------------------------------------------------------------
return the actual number of iterations
and current relative error
------------------------------------------------------------------------- */
double FixPolarizeBEMICC::compute_vector(int n)
{
if (n == 0)
return iterations;
else if (n == 1)
return rho;
else
return 0;
}