1563 lines
52 KiB
C++
1563 lines
52 KiB
C++
// clang-format off
|
|
/* ----------------------------------------------------------------------
|
|
LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
|
|
https://www.lammps.org/, Sandia National Laboratories
|
|
Steve Plimpton, sjplimp@sandia.gov
|
|
|
|
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: Pieter in 't Veld (SNL), Stan Moore (SNL)
|
|
------------------------------------------------------------------------- */
|
|
|
|
#include "ewald_disp.h"
|
|
|
|
#include "atom.h"
|
|
#include "comm.h"
|
|
#include "domain.h"
|
|
#include "error.h"
|
|
#include "force.h"
|
|
#include "math_const.h"
|
|
#include "math_extra.h"
|
|
#include "math_special.h"
|
|
#include "memory.h"
|
|
#include "pair.h"
|
|
#include "update.h"
|
|
|
|
#include <cmath>
|
|
#include <cstring>
|
|
|
|
using namespace LAMMPS_NS;
|
|
using namespace MathConst;
|
|
using namespace MathSpecial;
|
|
using namespace MathExtra;
|
|
|
|
#define SMALL 0.00001
|
|
|
|
//#define DEBUG
|
|
|
|
struct LAMMPS_NS::complex { double re, im; };
|
|
struct LAMMPS_NS::cvector { complex x, y, z; };
|
|
struct LAMMPS_NS::hvector { double x, y, z; };
|
|
struct LAMMPS_NS::kvector { long x, y, z; };
|
|
|
|
#define COMPLEX_NULL {0, 0}
|
|
|
|
#define C_RMULT(d, x, y) { \
|
|
complex t = x; \
|
|
d.re = t.re*y.re-t.im*y.im; \
|
|
d.im = t.re*y.im+t.im*y.re; }
|
|
|
|
#define C_CRMULT(d, x, y) { \
|
|
complex t = x; \
|
|
d.re = t.re*y.re-t.im*y.im; \
|
|
d.im = -t.re*y.im-t.im*y.re; }
|
|
|
|
#define C_SET(d, x, y) { \
|
|
d.re = x; \
|
|
d.im = y; }
|
|
|
|
#define C_CONJ(d, x) { \
|
|
d.re = x.re; \
|
|
d.im = -x.im; }
|
|
|
|
#define C_ANGLE(d, angle) { \
|
|
double a = angle; \
|
|
d.re = cos(a); \
|
|
d.im = sin(a); }
|
|
|
|
static inline void shape_add(double *dest, const double *src) { // h_a+h_b
|
|
dest[0] += src[0]; dest[1] += src[1]; dest[2] += src[2];
|
|
dest[3] += src[3]; dest[4] += src[4]; dest[5] += src[5]; }
|
|
|
|
static inline void shape_subtr(double *dest, const double *src) { // h_a-h_b
|
|
dest[0] -= src[0]; dest[1] -= src[1]; dest[2] -= src[2];
|
|
dest[3] -= src[3]; dest[4] -= src[4]; dest[5] -= src[5]; }
|
|
|
|
static inline double shape_det(double *s) {
|
|
return s[0]*s[1]*s[2]; }
|
|
|
|
static inline void shape_scalar_mult(double *dest, double f) { // f*h
|
|
dest[0] *= f; dest[1] *= f; dest[2] *= f;
|
|
dest[3] *= f; dest[4] *= f; dest[5] *= f; }
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
EwaldDisp::EwaldDisp(LAMMPS *lmp) : KSpace(lmp),
|
|
kenergy(nullptr), kvirial(nullptr), energy_self_peratom(nullptr), virial_self_peratom(nullptr),
|
|
ekr_local(nullptr), hvec(nullptr), kvec(nullptr), B(nullptr), cek_local(nullptr), cek_global(nullptr)
|
|
{
|
|
ewaldflag = dispersionflag = dipoleflag = 1;
|
|
|
|
memset(function, 0, EWALD_NFUNCS*sizeof(int));
|
|
kenergy = kvirial = nullptr;
|
|
cek_local = cek_global = nullptr;
|
|
ekr_local = nullptr;
|
|
hvec = nullptr;
|
|
kvec = nullptr;
|
|
B = nullptr;
|
|
first_output = 0;
|
|
energy_self_peratom = nullptr;
|
|
virial_self_peratom = nullptr;
|
|
nmax = 0;
|
|
q2 = 0;
|
|
b2 = 0;
|
|
M2 = 0;
|
|
}
|
|
|
|
void EwaldDisp::settings(int narg, char **arg)
|
|
{
|
|
if (narg!=1) error->all(FLERR,"Illegal kspace_style ewald/n command");
|
|
accuracy_relative = fabs(utils::numeric(FLERR,arg[0],false,lmp));
|
|
}
|
|
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
EwaldDisp::~EwaldDisp()
|
|
{
|
|
deallocate();
|
|
deallocate_peratom();
|
|
delete [] ekr_local;
|
|
delete [] B;
|
|
}
|
|
|
|
/* --------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::init()
|
|
{
|
|
nkvec = nkvec_max = nevec = nevec_max = 0;
|
|
nfunctions = nsums = sums = 0;
|
|
nbox = -1;
|
|
bytes = 0.0;
|
|
|
|
if (!comm->me) utils::logmesg(lmp,"EwaldDisp initialization ...\n");
|
|
|
|
triclinic_check();
|
|
if (domain->dimension == 2)
|
|
error->all(FLERR,"Cannot use EwaldDisp with 2d simulation");
|
|
if (slabflag == 0 && domain->nonperiodic > 0)
|
|
error->all(FLERR,"Cannot use non-periodic boundaries with EwaldDisp");
|
|
if (slabflag == 1) {
|
|
if (domain->xperiodic != 1 || domain->yperiodic != 1 ||
|
|
domain->boundary[2][0] != 1 || domain->boundary[2][1] != 1)
|
|
error->all(FLERR,"Incorrect boundaries with slab EwaldDisp");
|
|
}
|
|
|
|
scale = 1.0;
|
|
mumurd2e = force->qqrd2e;
|
|
dielectric = force->dielectric;
|
|
|
|
int tmp;
|
|
Pair *pair = force->pair;
|
|
int *ptr = pair ? (int *) pair->extract("ewald_order",tmp) : nullptr;
|
|
double *cutoff = pair ? (double *) pair->extract("cut_coul",tmp) : nullptr;
|
|
if (!(ptr||cutoff))
|
|
error->all(FLERR,"KSpace style is incompatible with Pair style");
|
|
int ewald_order = ptr ? *((int *) ptr) : 1<<1;
|
|
int ewald_mix = ptr ? *((int *) pair->extract("ewald_mix",tmp)) : Pair::GEOMETRIC;
|
|
memset(function, 0, EWALD_NFUNCS*sizeof(int));
|
|
for (int i=0; i<=EWALD_NORDER; ++i) // transcribe order
|
|
if (ewald_order&(1<<i)) { // from pair_style
|
|
int n[] = EWALD_NSUMS, k = 0;
|
|
switch (i) {
|
|
case 1:
|
|
k = 0; break;
|
|
case 3:
|
|
k = 3; break;
|
|
case 6:
|
|
if (ewald_mix==Pair::GEOMETRIC) { k = 1; break; }
|
|
else if (ewald_mix==Pair::ARITHMETIC) { k = 2; break; }
|
|
error->all(FLERR,"Unsupported mixing rule in kspace_style ewald/disp");
|
|
break;
|
|
default:
|
|
error->all(FLERR,"Unsupported order in kspace_style ewald/disp");
|
|
}
|
|
nfunctions += function[k] = 1;
|
|
nsums += n[k];
|
|
}
|
|
|
|
if (!gewaldflag) g_ewald = 1.0;
|
|
if (!gewaldflag_6) g_ewald_6 = 1.0;
|
|
pair->init(); // so B is defined
|
|
init_coeffs();
|
|
init_coeff_sums();
|
|
if (function[0]) qsum_qsq();
|
|
else qsqsum = qsum = 0.0;
|
|
natoms_original = atom->natoms;
|
|
if (!gewaldflag) g_ewald = 0.0;
|
|
if (!gewaldflag_6) g_ewald_6 = 0.0;
|
|
|
|
// turn off coulombic if no charge
|
|
|
|
if (function[0] && qsqsum == 0.0) {
|
|
function[0] = 0;
|
|
nfunctions -= 1;
|
|
nsums -= 1;
|
|
}
|
|
|
|
double bsbsum = 0.0;
|
|
M2 = 0.0;
|
|
if (function[1]) bsbsum = sum[1].x2;
|
|
if (function[2]) bsbsum = sum[2].x2;
|
|
|
|
if (function[3]) M2 = sum[9].x2;
|
|
|
|
if (function[3] && strcmp(update->unit_style,"electron") == 0)
|
|
error->all(FLERR,"Cannot (yet) use 'electron' units with dipoles");
|
|
|
|
if (qsqsum == 0.0 && bsbsum == 0.0 && M2 == 0.0)
|
|
error->all(FLERR,"Cannot use Ewald/disp solver on system without "
|
|
"charged, dipole, or LJ particles");
|
|
if (fabs(qsum) > SMALL && comm->me == 0)
|
|
error->warning(FLERR,"System is not charge neutral, net charge = {:.8g}",qsum);
|
|
|
|
if (!function[1] && !function[2]) dispersionflag = 0;
|
|
if (!function[3]) dipoleflag = 0;
|
|
|
|
// compute two charge force
|
|
|
|
two_charge();
|
|
|
|
// extract short-range Coulombic cutoff from pair style
|
|
|
|
pair_check();
|
|
|
|
// set accuracy (force units) from accuracy_relative or accuracy_absolute
|
|
|
|
if (accuracy_absolute >= 0.0) accuracy = accuracy_absolute;
|
|
else accuracy = accuracy_relative * two_charge_force;
|
|
|
|
// setup K-space resolution
|
|
|
|
q2 = qsqsum * force->qqrd2e;
|
|
M2 *= mumurd2e;
|
|
b2 = bsbsum; //Are these units right?
|
|
bigint natoms = atom->natoms;
|
|
|
|
if (!gewaldflag) {
|
|
if (function[0]) {
|
|
if (accuracy <= 0.0)
|
|
error->all(FLERR,"KSpace accuracy must be > 0");
|
|
if (q2 == 0.0)
|
|
error->all(FLERR,"Must use 'kspace_modify gewald' for uncharged system");
|
|
g_ewald = accuracy*sqrt(natoms*(*cutoff)*shape_det(domain->h)) / (2.0*q2);
|
|
if (g_ewald >= 1.0) g_ewald = (1.35 - 0.15*log(accuracy))/(*cutoff);
|
|
else g_ewald = sqrt(-log(g_ewald)) / (*cutoff);
|
|
} else if (function[3]) {
|
|
//Try Newton Solver
|
|
//Use old method to get guess
|
|
g_ewald = (1.35 - 0.15*log(accuracy))/ *cutoff;
|
|
double g_ewald_new =
|
|
NewtonSolve(g_ewald,(*cutoff),natoms,shape_det(domain->h),M2);
|
|
if (g_ewald_new > 0.0) g_ewald = g_ewald_new;
|
|
else error->warning(FLERR,"Ewald/disp Newton solver failed, "
|
|
"using old method to estimate g_ewald");
|
|
} else if (function[1] || function[2]) {
|
|
//Try Newton Solver
|
|
//Use old method to get guess
|
|
g_ewald = (1.35 - 0.15*log(accuracy))/ *cutoff;
|
|
|
|
double g_ewald_new =
|
|
NewtonSolve(g_ewald,(*cutoff),natoms,shape_det(domain->h),b2);
|
|
if (g_ewald_new > 0.0) g_ewald = g_ewald_new;
|
|
else error->warning(FLERR,"Ewald/disp Newton solver failed, "
|
|
"using old method to estimate g_ewald");
|
|
}
|
|
}
|
|
|
|
if (comm->me == 0)
|
|
utils::logmesg(lmp," G vector = {:.8g}, accuracy = {:.8g}\n",
|
|
g_ewald,accuracy);
|
|
|
|
// apply coulomb g_ewald to dispersion unless it is explicitly set
|
|
|
|
if (!gewaldflag_6) g_ewald_6 = g_ewald;
|
|
deallocate_peratom();
|
|
peratom_allocate_flag = 0;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
adjust EwaldDisp coeffs, called initially and whenever volume has changed
|
|
------------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::setup()
|
|
{
|
|
volume = shape_det(domain->h)*slab_volfactor;
|
|
memcpy(unit, domain->h_inv, 6*sizeof(double));
|
|
shape_scalar_mult(unit, 2.0*MY_PI);
|
|
unit[2] /= slab_volfactor;
|
|
|
|
// int nbox_old = nbox, nkvec_old = nkvec;
|
|
|
|
if (accuracy >= 1) {
|
|
nbox = 0;
|
|
error->all(FLERR,"KSpace accuracy too low");
|
|
}
|
|
|
|
bigint natoms = atom->natoms;
|
|
double err;
|
|
int kxmax = 1;
|
|
int kymax = 1;
|
|
int kzmax = 1;
|
|
err = rms(kxmax,domain->h[0],natoms,q2,b2,M2);
|
|
while (err > accuracy) {
|
|
kxmax++;
|
|
err = rms(kxmax,domain->h[0],natoms,q2,b2,M2);
|
|
}
|
|
err = rms(kymax,domain->h[1],natoms,q2,b2,M2);
|
|
while (err > accuracy) {
|
|
kymax++;
|
|
err = rms(kymax,domain->h[1],natoms,q2,b2,M2);
|
|
}
|
|
err = rms(kzmax,domain->h[2]*slab_volfactor,natoms,q2,b2,M2);
|
|
while (err > accuracy) {
|
|
kzmax++;
|
|
err = rms(kzmax,domain->h[2]*slab_volfactor,natoms,q2,b2,M2);
|
|
}
|
|
nbox = MAX(kxmax,kymax);
|
|
nbox = MAX(nbox,kzmax);
|
|
double gsqxmx = unit[0]*unit[0]*kxmax*kxmax;
|
|
double gsqymx = unit[1]*unit[1]*kymax*kymax;
|
|
double gsqzmx = unit[2]*unit[2]*kzmax*kzmax;
|
|
gsqmx = MAX(gsqxmx,gsqymx);
|
|
gsqmx = MAX(gsqmx,gsqzmx);
|
|
gsqmx *= 1.00001;
|
|
|
|
reallocate();
|
|
coefficients();
|
|
init_coeffs();
|
|
init_coeff_sums();
|
|
init_self();
|
|
|
|
if (!(first_output||comm->me)) {
|
|
first_output = 1;
|
|
utils::logmesg(lmp," vectors: nbox = {}, nkvec = {}\n", nbox, nkvec);
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
compute RMS accuracy for a dimension
|
|
------------------------------------------------------------------------- */
|
|
|
|
double EwaldDisp::rms(int km, double prd, bigint natoms,
|
|
double q2, double b2, double M2)
|
|
{
|
|
double value = 0.0;
|
|
if (natoms == 0) natoms = 1; // avoid division by zero
|
|
|
|
// Coulombic
|
|
|
|
double g2 = g_ewald*g_ewald;
|
|
|
|
value += 2.0*q2*g_ewald/prd *
|
|
sqrt(1.0/(MY_PI*km*natoms)) *
|
|
exp(-MY_PI*MY_PI*km*km/(g2*prd*prd));
|
|
|
|
// Lennard-Jones
|
|
|
|
double g7 = g2*g2*g2*g_ewald;
|
|
|
|
value += 4.0*b2*g7/3.0 *
|
|
sqrt(1.0/(MY_PI*natoms)) *
|
|
(exp(-MY_PI*MY_PI*km*km/(g2*prd*prd)) *
|
|
(MY_PI*km/(g_ewald*prd) + 1));
|
|
|
|
// dipole
|
|
|
|
value += 8.0*MY_PI*M2/volume*g_ewald *
|
|
sqrt(2.0*MY_PI*km*km*km/(15.0*natoms)) *
|
|
exp(-pow(MY_PI*km/(g_ewald*prd),2.0));
|
|
|
|
return value;
|
|
}
|
|
|
|
void EwaldDisp::reallocate()
|
|
{
|
|
int ix, iy, iz;
|
|
int nkvec_max = nkvec;
|
|
double h[3];
|
|
|
|
nkvec = 0;
|
|
int *kflag = new int[(nbox+1)*(2*nbox+1)*(2*nbox+1)];
|
|
int *flag = kflag;
|
|
|
|
for (ix=0; ix<=nbox; ++ix)
|
|
for (iy=-nbox; iy<=nbox; ++iy)
|
|
for (iz=-nbox; iz<=nbox; ++iz)
|
|
if (!(ix||iy||iz)) *(flag++) = 0;
|
|
else if ((!ix)&&(iy<0)) *(flag++) = 0;
|
|
else if ((!(ix||iy))&&(iz<0)) *(flag++) = 0; // use symmetry
|
|
else {
|
|
h[0] = unit[0]*ix;
|
|
h[1] = unit[5]*ix+unit[1]*iy;
|
|
h[2] = unit[4]*ix+unit[3]*iy+unit[2]*iz;
|
|
if ((*(flag++) = (h[0]*h[0]+h[1]*h[1]+h[2]*h[2]<=gsqmx))) ++nkvec;
|
|
}
|
|
|
|
if (nkvec>nkvec_max) {
|
|
deallocate(); // free memory
|
|
hvec = new hvector[nkvec]; // hvec
|
|
bytes += (double)(nkvec-nkvec_max)*sizeof(hvector);
|
|
kvec = new kvector[nkvec]; // kvec
|
|
bytes += (double)(nkvec-nkvec_max)*sizeof(kvector);
|
|
kenergy = new double[nkvec*nfunctions]; // kenergy
|
|
bytes += (double)(nkvec-nkvec_max)*nfunctions*sizeof(double);
|
|
kvirial = new double[6*nkvec*nfunctions]; // kvirial
|
|
bytes += (double)6*(nkvec-nkvec_max)*nfunctions*sizeof(double);
|
|
cek_local = new complex[nkvec*nsums]; // cek_local
|
|
bytes += (double)(nkvec-nkvec_max)*nsums*sizeof(complex);
|
|
cek_global = new complex[nkvec*nsums]; // cek_global
|
|
bytes += (double)(nkvec-nkvec_max)*nsums*sizeof(complex);
|
|
nkvec_max = nkvec;
|
|
}
|
|
|
|
flag = kflag; // create index and
|
|
kvector *k = kvec; // wave vectors
|
|
hvector *hi = hvec;
|
|
for (ix=0; ix<=nbox; ++ix)
|
|
for (iy=-nbox; iy<=nbox; ++iy)
|
|
for (iz=-nbox; iz<=nbox; ++iz)
|
|
if (*(flag++)) {
|
|
hi->x = unit[0]*ix;
|
|
hi->y = unit[5]*ix+unit[1]*iy;
|
|
(hi++)->z = unit[4]*ix+unit[3]*iy+unit[2]*iz;
|
|
k->x = ix+nbox; k->y = iy+nbox; (k++)->z = iz+nbox; }
|
|
|
|
delete [] kflag;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::reallocate_atoms()
|
|
{
|
|
if (eflag_atom || vflag_atom)
|
|
if (atom->nmax > nmax) {
|
|
deallocate_peratom();
|
|
allocate_peratom();
|
|
nmax = atom->nmax;
|
|
}
|
|
|
|
if ((nevec = atom->nmax*(2*nbox+1))<=nevec_max) return;
|
|
delete [] ekr_local;
|
|
ekr_local = new cvector[nevec];
|
|
bytes += (double)(nevec-nevec_max)*sizeof(cvector);
|
|
nevec_max = nevec;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::allocate_peratom()
|
|
{
|
|
memory->create(energy_self_peratom,
|
|
atom->nmax,EWALD_NFUNCS,"ewald/n:energy_self_peratom");
|
|
memory->create(virial_self_peratom,
|
|
atom->nmax,EWALD_NFUNCS,"ewald/n:virial_self_peratom");
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::deallocate_peratom() // free memory
|
|
{
|
|
if (energy_self_peratom) {
|
|
memory->destroy(energy_self_peratom);
|
|
energy_self_peratom = nullptr;
|
|
}
|
|
|
|
if (virial_self_peratom) {
|
|
memory->destroy(virial_self_peratom);
|
|
virial_self_peratom = nullptr;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::deallocate() // free memory
|
|
{
|
|
delete [] hvec; hvec = nullptr;
|
|
delete [] kvec; kvec = nullptr;
|
|
delete [] kenergy; kenergy = nullptr;
|
|
delete [] kvirial; kvirial = nullptr;
|
|
delete [] cek_local; cek_local = nullptr;
|
|
delete [] cek_global; cek_global = nullptr;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::coefficients()
|
|
{
|
|
double h[3];
|
|
hvector *hi = hvec, *nh;
|
|
double eta2 = 0.25/(g_ewald*g_ewald);
|
|
double b1, b2, expb2, h1, h2, c1, c2;
|
|
double *ke = kenergy, *kv = kvirial;
|
|
int func0 = function[0], func12 = function[1]||function[2],
|
|
func3 = function[3];
|
|
|
|
for (nh = (hi = hvec)+nkvec; hi<nh; ++hi) { // wave vectors
|
|
memcpy(h, hi, 3*sizeof(double));
|
|
expb2 = exp(-(b2 = (h2 = dot3(h, h))*eta2));
|
|
if (func0) { // qi*qj/r coeffs
|
|
*(ke++) = c1 = expb2/h2;
|
|
*(kv++) = c1-(c2 = 2.0*c1*(1.0+b2)/h2)*h[0]*h[0];
|
|
*(kv++) = c1-c2*h[1]*h[1]; // lammps convention
|
|
*(kv++) = c1-c2*h[2]*h[2]; // instead of voigt
|
|
*(kv++) = -c2*h[1]*h[0];
|
|
*(kv++) = -c2*h[2]*h[0];
|
|
*(kv++) = -c2*h[2]*h[1];
|
|
}
|
|
if (func12) { // -Bij/r^6 coeffs
|
|
b1 = sqrt(b2); // minus sign folded
|
|
h1 = sqrt(h2); // into constants
|
|
*(ke++) = c1 = -h1*h2*((c2=MY_PIS*erfc(b1))+(0.5/b2-1.0)*expb2/b1);
|
|
*(kv++) = c1-(c2 = 3.0*h1*(c2-expb2/b1))*h[0]*h[0];
|
|
*(kv++) = c1-c2*h[1]*h[1]; // lammps convention
|
|
*(kv++) = c1-c2*h[2]*h[2]; // instead of voigt
|
|
*(kv++) = -c2*h[1]*h[0];
|
|
*(kv++) = -c2*h[2]*h[0];
|
|
*(kv++) = -c2*h[2]*h[1];
|
|
}
|
|
if (func3) { // dipole coeffs
|
|
*(ke++) = c1 = expb2/h2;
|
|
*(kv++) = c1-(c2 = 2.0*c1*(1.0+b2)/h2)*h[0]*h[0];
|
|
*(kv++) = c1-c2*h[1]*h[1]; // lammps convention
|
|
*(kv++) = c1-c2*h[2]*h[2]; // instead of voigt
|
|
*(kv++) = -c2*h[1]*h[0];
|
|
*(kv++) = -c2*h[2]*h[0];
|
|
*(kv++) = -c2*h[2]*h[1];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::init_coeffs()
|
|
{
|
|
int tmp;
|
|
int n = atom->ntypes;
|
|
|
|
if (function[1]) { // geometric 1/r^6
|
|
auto b = (double **) force->pair->extract("B",tmp);
|
|
delete [] B;
|
|
B = new double[n+1];
|
|
B[0] = 0.0;
|
|
bytes += (double)(n+1)*sizeof(double);
|
|
for (int i=1; i<=n; ++i) B[i] = sqrt(fabs(b[i][i]));
|
|
}
|
|
if (function[2]) { // arithmetic 1/r^6
|
|
auto epsilon = (double **) force->pair->extract("epsilon",tmp);
|
|
auto sigma = (double **) force->pair->extract("sigma",tmp);
|
|
delete [] B;
|
|
double eps_i, sigma_i, sigma_n, *bi = B = new double[7*n+7];
|
|
double c[7] = {
|
|
1.0, sqrt(6.0), sqrt(15.0), sqrt(20.0), sqrt(15.0), sqrt(6.0), 1.0};
|
|
|
|
if (!(epsilon&&sigma))
|
|
error->all(
|
|
FLERR,"Epsilon or sigma reference not set by pair style in ewald/n");
|
|
for (int j=0; j<7; ++j)
|
|
*(bi++) = 0.0;
|
|
for (int i=1; i<=n; ++i) {
|
|
eps_i = sqrt(epsilon[i][i]);
|
|
sigma_i = sigma[i][i];
|
|
sigma_n = 1.0;
|
|
for (int j=0; j<7; ++j) {
|
|
*(bi++) = sigma_n*eps_i*c[j]; sigma_n *= sigma_i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::init_coeff_sums()
|
|
{
|
|
if (sums) return; // calculated only once
|
|
sums = 1;
|
|
|
|
Sum sum_local[EWALD_MAX_NSUMS];
|
|
|
|
memset(sum_local, 0, EWALD_MAX_NSUMS*sizeof(Sum));
|
|
memset(sum, 0, EWALD_MAX_NSUMS*sizeof(Sum));
|
|
|
|
// now perform qsum and qsq via parent qsum_qsq()
|
|
|
|
sum_local[0].x = 0.0;
|
|
sum_local[0].x2 = 0.0;
|
|
|
|
//if (function[0]) { // 1/r
|
|
// double *q = atom->q, *qn = q+atom->nlocal;
|
|
// for (double *i=q; i<qn; ++i) {
|
|
// sum_local[0].x += i[0]; sum_local[0].x2 += i[0]*i[0]; }
|
|
//}
|
|
|
|
if (function[1]) { // geometric 1/r^6
|
|
int *type = atom->type, *ntype = type+atom->nlocal;
|
|
for (int *i=type; i<ntype; ++i) {
|
|
sum_local[1].x += B[i[0]]; sum_local[1].x2 += B[i[0]]*B[i[0]]; }
|
|
}
|
|
if (function[2]) { // arithmetic 1/r^6
|
|
double *bi;
|
|
int *type = atom->type, *ntype = type+atom->nlocal;
|
|
for (int *i=type; i<ntype; ++i) {
|
|
bi = B+7*i[0];
|
|
sum_local[2].x2 += bi[0]*bi[6];
|
|
for (int k=2; k<9; ++k) sum_local[k].x += *(bi++);
|
|
}
|
|
}
|
|
if (function[3]&&atom->mu) { // dipole
|
|
double *mu = atom->mu[0], *nmu = mu+4*atom->nlocal;
|
|
for (double *i = mu; i < nmu; i += 4)
|
|
sum_local[9].x2 += i[3]*i[3];
|
|
}
|
|
MPI_Allreduce(sum_local, sum, 2*EWALD_MAX_NSUMS, MPI_DOUBLE, MPI_SUM, world);
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::init_self()
|
|
{
|
|
double g1 = g_ewald, g2 = g1*g1, g3 = g1*g2;
|
|
const double qscale = force->qqrd2e * scale;
|
|
|
|
memset(energy_self, 0, EWALD_NFUNCS*sizeof(double)); // self energy
|
|
memset(virial_self, 0, EWALD_NFUNCS*sizeof(double));
|
|
|
|
if (function[0]) { // 1/r
|
|
virial_self[0] = -0.5*MY_PI*qscale/(g2*volume)*qsum*qsum;
|
|
energy_self[0] = qsqsum*qscale*g1/MY_PIS-virial_self[0];
|
|
}
|
|
if (function[1]) { // geometric 1/r^6
|
|
virial_self[1] = MY_PI*MY_PIS*g3/(6.0*volume)*sum[1].x*sum[1].x;
|
|
energy_self[1] = -sum[1].x2*g3*g3/12.0+virial_self[1];
|
|
}
|
|
if (function[2]) { // arithmetic 1/r^6
|
|
virial_self[2] = MY_PI*MY_PIS*g3/(48.0*volume)*(sum[2].x*sum[8].x+
|
|
sum[3].x*sum[7].x+sum[4].x*sum[6].x+0.5*sum[5].x*sum[5].x);
|
|
energy_self[2] = -sum[2].x2*g3*g3/3.0+virial_self[2];
|
|
}
|
|
if (function[3]) { // dipole
|
|
virial_self[3] = 0; // in surface
|
|
energy_self[3] = sum[9].x2*mumurd2e*2.0*g3/3.0/MY_PIS-virial_self[3];
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::init_self_peratom()
|
|
{
|
|
if (!(vflag_atom || eflag_atom)) return;
|
|
|
|
double g1 = g_ewald, g2 = g1*g1, g3 = g1*g2;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double *energy = energy_self_peratom[0];
|
|
double *virial = virial_self_peratom[0];
|
|
int nlocal = atom->nlocal;
|
|
|
|
memset(energy, 0, EWALD_NFUNCS*nlocal*sizeof(double));
|
|
memset(virial, 0, EWALD_NFUNCS*nlocal*sizeof(double));
|
|
|
|
if (function[0]) { // 1/r
|
|
double *ei = energy;
|
|
double *vi = virial;
|
|
double ce = qscale*g1/MY_PIS;
|
|
double cv = -0.5*MY_PI*qscale/(g2*volume);
|
|
double *qi = atom->q, *qn = qi + nlocal;
|
|
for (; qi < qn; qi++, vi += EWALD_NFUNCS, ei += EWALD_NFUNCS) {
|
|
double q = *qi;
|
|
*vi = cv*q*qsum;
|
|
*ei = ce*q*q-vi[0];
|
|
}
|
|
}
|
|
if (function[1]) { // geometric 1/r^6
|
|
double *ei = energy+1;
|
|
double *vi = virial+1;
|
|
double ce = -g3*g3/12.0;
|
|
double cv = MY_PI*MY_PIS*g3/(6.0*volume);
|
|
int *typei = atom->type, *typen = typei + atom->nlocal;
|
|
for (; typei < typen; typei++, vi += EWALD_NFUNCS, ei += EWALD_NFUNCS) {
|
|
double b = B[*typei];
|
|
*vi = cv*b*sum[1].x;
|
|
*ei = ce*b*b+vi[0];
|
|
}
|
|
}
|
|
if (function[2]) { // arithmetic 1/r^6
|
|
double *bi;
|
|
double *ei = energy+2;
|
|
double *vi = virial+2;
|
|
double ce = -g3*g3/3.0;
|
|
double cv = 0.5*MY_PI*MY_PIS*g3/(48.0*volume);
|
|
int *typei = atom->type, *typen = typei + atom->nlocal;
|
|
for (; typei < typen; typei++, vi += EWALD_NFUNCS, ei += EWALD_NFUNCS) {
|
|
bi = B+7*typei[0]+7;
|
|
for (int k=2; k<9; ++k) *vi += cv*sum[k].x*(--bi)[0];
|
|
|
|
/* PJV 20120225:
|
|
should this be this instead? above implies an inverse dependence
|
|
seems to be the above way in original; i recall having tested
|
|
arithmetic mixing in the conception phase, but an extra test would
|
|
be prudent (pattern repeats in multiple functions below)
|
|
|
|
bi = B+7*typei[0];
|
|
for (int k=2; k<9; ++k) *vi += cv*sum[k].x*(bi++)[0];
|
|
|
|
*/
|
|
|
|
*ei = ce*bi[0]*bi[6]+vi[0];
|
|
}
|
|
}
|
|
if (function[3]&&atom->mu) { // dipole
|
|
double *ei = energy+3;
|
|
double *vi = virial+3;
|
|
double *imu = atom->mu[0], *nmu = imu+4*atom->nlocal;
|
|
double ce = mumurd2e*2.0*g3/3.0/MY_PIS;
|
|
for (; imu < nmu; imu += 4, vi += EWALD_NFUNCS, ei += EWALD_NFUNCS) {
|
|
*vi = 0; // in surface
|
|
*ei = ce*imu[3]*imu[3]-vi[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
compute the EwaldDisp long-range force, energy, virial
|
|
------------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute(int eflag, int vflag)
|
|
{
|
|
if (!nbox) return;
|
|
|
|
// set energy/virial flags
|
|
// invoke allocate_peratom() if needed for first time
|
|
|
|
ev_init(eflag,vflag);
|
|
|
|
if (!peratom_allocate_flag && (eflag_atom || vflag_atom)) {
|
|
allocate_peratom();
|
|
peratom_allocate_flag = 1;
|
|
nmax = atom->nmax;
|
|
}
|
|
|
|
reallocate_atoms();
|
|
init_self_peratom();
|
|
compute_ek();
|
|
compute_force();
|
|
//compute_surface(); // assume conducting metal (tinfoil) boundary conditions
|
|
|
|
// update qsum and qsqsum, if atom count has changed and energy needed
|
|
|
|
if ((eflag_global || eflag_atom) && atom->natoms != natoms_original) {
|
|
if (function[0]) qsum_qsq();
|
|
natoms_original = atom->natoms;
|
|
}
|
|
|
|
compute_energy();
|
|
compute_energy_peratom();
|
|
compute_virial();
|
|
compute_virial_dipole();
|
|
compute_virial_peratom();
|
|
|
|
if (slabflag) compute_slabcorr();
|
|
}
|
|
|
|
|
|
void EwaldDisp::compute_ek()
|
|
{
|
|
cvector *ekr = ekr_local;
|
|
int lbytes = (2*nbox+1)*sizeof(cvector);
|
|
hvector *h = nullptr;
|
|
kvector *k, *nk = kvec+nkvec;
|
|
auto z = new cvector[2*nbox+1];
|
|
cvector z1, *zx, *zy, *zz, *zn = z+2*nbox;
|
|
complex *cek, zxyz, zxy = COMPLEX_NULL, cx = COMPLEX_NULL;
|
|
double mui[3];
|
|
double *x = atom->x[0], *xn = x+3*atom->nlocal, *q = atom->q, qi = 0.0;
|
|
double bi = 0.0, ci[7];
|
|
double *mu = atom->mu ? atom->mu[0] : nullptr;
|
|
int i, kx, ky, n = nkvec*nsums, *type = atom->type, tri = domain->triclinic;
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
memset(cek_local, 0, n*sizeof(complex)); // reset sums
|
|
while (x<xn) {
|
|
zx = (zy = (zz = z+nbox)+1)-2;
|
|
C_SET(zz->x, 1, 0); C_SET(zz->y, 1, 0); C_SET(zz->z, 1, 0); // z[0]
|
|
if (tri) { // triclinic z[1]
|
|
C_ANGLE(z1.x, unit[0]*x[0]+unit[5]*x[1]+unit[4]*x[2]);
|
|
C_ANGLE(z1.y, unit[1]*x[1]+unit[3]*x[2]);
|
|
C_ANGLE(z1.z, x[2]*unit[2]); x += 3;
|
|
}
|
|
else { // orthogonal z[1]
|
|
C_ANGLE(z1.x, *(x++)*unit[0]);
|
|
C_ANGLE(z1.y, *(x++)*unit[1]);
|
|
C_ANGLE(z1.z, *(x++)*unit[2]);
|
|
}
|
|
for (; zz<zn; --zx, ++zy, ++zz) { // set up z[k]=e^(ik.r)
|
|
C_RMULT(zy->x, zz->x, z1.x); // 3D k-vector
|
|
C_RMULT(zy->y, zz->y, z1.y); C_CONJ(zx->y, zy->y);
|
|
C_RMULT(zy->z, zz->z, z1.z); C_CONJ(zx->z, zy->z);
|
|
}
|
|
kx = ky = -1;
|
|
cek = cek_local;
|
|
if (func[0]) qi = *(q++);
|
|
if (func[1]) bi = B[*type];
|
|
if (func[2]) memcpy(ci, B+7*type[0], 7*sizeof(double));
|
|
if (func[3]) {
|
|
memcpy(mui, mu, 3*sizeof(double));
|
|
mu += 4;
|
|
h = hvec;
|
|
}
|
|
for (k=kvec; k<nk; ++k) { // compute rho(k)
|
|
if (ky!=k->y) { // based on order in
|
|
if (kx!=k->x) cx = z[kx = k->x].x; // reallocate
|
|
C_RMULT(zxy, z[ky = k->y].y, cx);
|
|
}
|
|
C_RMULT(zxyz, z[k->z].z, zxy);
|
|
if (func[0]) {
|
|
cek->re += zxyz.re*qi; (cek++)->im += zxyz.im*qi;
|
|
}
|
|
if (func[1]) {
|
|
cek->re += zxyz.re*bi; (cek++)->im += zxyz.im*bi;
|
|
}
|
|
if (func[2]) for (i=0; i<7; ++i) {
|
|
cek->re += zxyz.re*ci[i]; (cek++)->im += zxyz.im*ci[i];
|
|
}
|
|
if (func[3]) {
|
|
double muk = mui[0]*h->x+mui[1]*h->y+mui[2]*h->z; ++h;
|
|
cek->re += zxyz.re*muk; (cek++)->im += zxyz.im*muk;
|
|
}
|
|
}
|
|
ekr = (cvector *) ((char *) memcpy(ekr, z, lbytes)+lbytes);
|
|
++type;
|
|
}
|
|
MPI_Allreduce(cek_local, cek_global, 2*n, MPI_DOUBLE, MPI_SUM, world);
|
|
|
|
delete [] z;
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_force()
|
|
{
|
|
kvector *k;
|
|
hvector *h, *nh;
|
|
cvector *z = ekr_local;
|
|
double mysum[EWALD_MAX_NSUMS][3], mui[3] = {0.0,0.0,0.0};
|
|
complex *cek, zc, zx = COMPLEX_NULL, zxy = COMPLEX_NULL;
|
|
complex *cek_coul;
|
|
double *f = atom->f[0], *fn = f+3*atom->nlocal, *q = atom->q, *t = nullptr;
|
|
double *mu = atom->mu ? atom->mu[0] : nullptr;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double *ke, c[EWALD_NFUNCS] = {
|
|
8.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(12.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 8.0*MY_PI*mumurd2e/volume};
|
|
int i, kx, ky, lbytes = (2*nbox+1)*sizeof(cvector), *type = atom->type;
|
|
int func[EWALD_NFUNCS];
|
|
|
|
if (atom->torque) t = atom->torque[0];
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
memset(mysum, 0, EWALD_MAX_NSUMS*3*sizeof(double)); // fj = -dE/dr =
|
|
for (; f<fn; f+=3) { // -i*qj*fac*
|
|
k = kvec; // Sum[conj(d)-d]
|
|
kx = ky = -1; // d = k*conj(ekj)*ek
|
|
ke = kenergy;
|
|
cek = cek_global;
|
|
memset(mysum, 0, EWALD_MAX_NSUMS*3*sizeof(double));
|
|
if (func[3]) {
|
|
double di = c[3];
|
|
mui[0] = di*(mu++)[0]; mui[1] = di*(mu++)[0]; mui[2] = di*(mu++)[0];
|
|
mu++;
|
|
}
|
|
for (nh = (h = hvec)+nkvec; h<nh; ++h, ++k) {
|
|
if (ky!=k->y) { // based on order in
|
|
if (kx!=k->x) zx = z[kx = k->x].x; // reallocate
|
|
C_RMULT(zxy, z[ky = k->y].y, zx);
|
|
}
|
|
C_CRMULT(zc, z[k->z].z, zxy);
|
|
if (func[0]) { // 1/r
|
|
double im = *(ke++)*(zc.im*cek->re+cek->im*zc.re);
|
|
if (func[3]) cek_coul = cek;
|
|
++cek;
|
|
mysum[0][0] += h->x*im; mysum[0][1] += h->y*im; mysum[0][2] += h->z*im;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double im = *(ke++)*(zc.im*cek->re+cek->im*zc.re); ++cek;
|
|
mysum[1][0] += h->x*im; mysum[1][1] += h->y*im; mysum[1][2] += h->z*im;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double im, c = *(ke++);
|
|
for (i=2; i<9; ++i) {
|
|
im = c*(zc.im*cek->re+cek->im*zc.re); ++cek;
|
|
mysum[i][0] += h->x*im; mysum[i][1] += h->y*im; mysum[i][2] += h->z*im;
|
|
}
|
|
}
|
|
if (func[3]) { // dipole
|
|
double im = *(ke)*(zc.im*cek->re+
|
|
cek->im*zc.re)*(mui[0]*h->x+mui[1]*h->y+mui[2]*h->z);
|
|
double im2 = *(ke)*(zc.re*cek->re-
|
|
cek->im*zc.im);
|
|
mysum[9][0] += h->x*im; mysum[9][1] += h->y*im; mysum[9][2] += h->z*im;
|
|
t[0] += -mui[1]*h->z*im2 + mui[2]*h->y*im2; // torque
|
|
t[1] += -mui[2]*h->x*im2 + mui[0]*h->z*im2;
|
|
t[2] += -mui[0]*h->y*im2 + mui[1]*h->x*im2;
|
|
if (func[0]) { // charge-dipole
|
|
double qi = *(q)*c[0];
|
|
im = - *(ke)*(zc.re*cek_coul->re -
|
|
cek_coul->im*zc.im)*(mui[0]*h->x+mui[1]*h->y+mui[2]*h->z);
|
|
im += *(ke)*(zc.re*cek->re - cek->im*zc.im)*qi;
|
|
mysum[9][0] += h->x*im; mysum[9][1] += h->y*im; mysum[9][2] += h->z*im;
|
|
|
|
im2 = *(ke)*(zc.re*cek_coul->im + cek_coul->re*zc.im);
|
|
im2 += -*(ke)*(zc.re*cek->im - cek->im*zc.re);
|
|
t[0] += -mui[1]*h->z*im2 + mui[2]*h->y*im2; // torque
|
|
t[1] += -mui[2]*h->x*im2 + mui[0]*h->z*im2;
|
|
t[2] += -mui[0]*h->y*im2 + mui[1]*h->x*im2;
|
|
}
|
|
++cek;
|
|
ke++;
|
|
}
|
|
}
|
|
if (func[0]) { // 1/r
|
|
double qi = *(q++)*c[0];
|
|
f[0] -= mysum[0][0]*qi; f[1] -= mysum[0][1]*qi; f[2] -= mysum[0][2]*qi;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double bi = B[*type]*c[1];
|
|
f[0] -= mysum[1][0]*bi; f[1] -= mysum[1][1]*bi; f[2] -= mysum[1][2]*bi;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double *bi = B+7*type[0]+7;
|
|
for (i=2; i<9; ++i) {
|
|
double c2 = (--bi)[0]*c[2];
|
|
f[0] -= mysum[i][0]*c2; f[1] -= mysum[i][1]*c2; f[2] -= mysum[i][2]*c2;
|
|
}
|
|
}
|
|
if (func[3]) { // dipole
|
|
f[0] -= mysum[9][0]; f[1] -= mysum[9][1]; f[2] -= mysum[9][2];
|
|
}
|
|
z = (cvector *) ((char *) z+lbytes);
|
|
++type;
|
|
t += 3;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_surface()
|
|
{
|
|
// assume conducting metal (tinfoil) boundary conditions, so this function is
|
|
// not called because dielectric at the boundary --> infinity, which makes all
|
|
// the terms here zero.
|
|
|
|
if (!function[3]) return;
|
|
if (!atom->mu) return;
|
|
|
|
double sum_local[3] = {0.0,0.0,0.0}, sum_total[3] = {0.0,0.0,0.0};
|
|
double *i, *n, *mu = atom->mu[0];
|
|
|
|
for (n = (i = mu) + 4*atom->nlocal; i < n; ++i) {
|
|
sum_local[0] += (i++)[0];
|
|
sum_local[1] += (i++)[0];
|
|
sum_local[2] += (i++)[0];
|
|
}
|
|
MPI_Allreduce(sum_local, sum_total, 3, MPI_DOUBLE, MPI_SUM, world);
|
|
|
|
virial_self[3] =
|
|
mumurd2e*(2.0*MY_PI*dot3(sum_total,sum_total)/(2.0*dielectric+1)/volume);
|
|
energy_self[3] -= virial_self[3];
|
|
|
|
if (!(vflag_atom || eflag_atom)) return;
|
|
|
|
double *ei = energy_self_peratom[0]+3;
|
|
double *vi = virial_self_peratom[0]+3;
|
|
double cv = 2.0*mumurd2e*MY_PI/(2.0*dielectric+1)/volume;
|
|
|
|
for (i = mu; i < n; i += 4, ei += EWALD_NFUNCS, vi += EWALD_NFUNCS) {
|
|
*vi = cv*(i[0]*sum_total[0]+i[1]*sum_total[1]+i[2]*sum_total[2]);
|
|
*ei -= *vi;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_energy()
|
|
{
|
|
energy = 0.0;
|
|
if (!eflag_global) return;
|
|
|
|
complex *cek = cek_global;
|
|
complex *cek_coul;
|
|
double *ke = kenergy;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double c[EWALD_NFUNCS] = {
|
|
4.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(24.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 4.0*MY_PI*mumurd2e/volume};
|
|
double mysum[EWALD_NFUNCS];
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
memset(mysum, 0, EWALD_NFUNCS*sizeof(double)); // reset sums
|
|
for (int k=0; k<nkvec; ++k) { // sum over k vectors
|
|
if (func[0]) { // 1/r
|
|
mysum[0] += *(ke++)*(cek->re*cek->re+cek->im*cek->im);
|
|
if (func[3]) cek_coul = cek;
|
|
++cek;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
mysum[1] += *(ke++)*(cek->re*cek->re+cek->im*cek->im); ++cek; }
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double r =
|
|
(cek[0].re*cek[6].re+cek[0].im*cek[6].im)+
|
|
(cek[1].re*cek[5].re+cek[1].im*cek[5].im)+
|
|
(cek[2].re*cek[4].re+cek[2].im*cek[4].im)+
|
|
0.5*(cek[3].re*cek[3].re+cek[3].im*cek[3].im); cek += 7;
|
|
mysum[2] += *(ke++)*r;
|
|
}
|
|
if (func[3]) { // dipole
|
|
mysum[3] += *(ke)*(cek->re*cek->re+cek->im*cek->im);
|
|
if (func[0]) { // charge-dipole
|
|
mysum[3] += *(ke)*2.0*(cek->re*cek_coul->im - cek->im*cek_coul->re);
|
|
}
|
|
ke++;
|
|
++cek;
|
|
}
|
|
}
|
|
for (int k=0; k<EWALD_NFUNCS; ++k) energy += c[k]*mysum[k]-energy_self[k];
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_energy_peratom()
|
|
{
|
|
if (!eflag_atom) return;
|
|
|
|
kvector *k;
|
|
hvector *h, *nh;
|
|
cvector *z = ekr_local;
|
|
double mui[3] = {0.0,0.0,0.0};
|
|
double mysum[EWALD_MAX_NSUMS];
|
|
complex *cek, zc = COMPLEX_NULL, zx = COMPLEX_NULL, zxy = COMPLEX_NULL;
|
|
complex *cek_coul;
|
|
double *q = atom->q;
|
|
double *eatomj = eatom;
|
|
double *mu = atom->mu ? atom->mu[0] : nullptr;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double *ke = kenergy;
|
|
double c[EWALD_NFUNCS] = {
|
|
4.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(24.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 4.0*MY_PI*mumurd2e/volume};
|
|
int i, kx, ky, lbytes = (2*nbox+1)*sizeof(cvector), *type = atom->type;
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
for (int j = 0; j < atom->nlocal; j++, ++eatomj) {
|
|
k = kvec;
|
|
kx = ky = -1;
|
|
ke = kenergy;
|
|
cek = cek_global;
|
|
memset(mysum, 0, EWALD_MAX_NSUMS*sizeof(double));
|
|
if (func[3]) {
|
|
double di = c[3];
|
|
mui[0] = di*(mu++)[0]; mui[1] = di*(mu++)[0]; mui[2] = di*(mu++)[0];
|
|
mu++;
|
|
}
|
|
for (nh = (h = hvec)+nkvec; h<nh; ++h, ++k) {
|
|
if (ky!=k->y) { // based on order in
|
|
if (kx!=k->x) zx = z[kx = k->x].x; // reallocate
|
|
C_RMULT(zxy, z[ky = k->y].y, zx);
|
|
}
|
|
C_CRMULT(zc, z[k->z].z, zxy);
|
|
if (func[0]) { // 1/r
|
|
mysum[0] += *(ke++)*(cek->re*zc.re - cek->im*zc.im);
|
|
if (func[3]) cek_coul = cek;
|
|
++cek;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
mysum[1] += *(ke++)*(cek->re*zc.re - cek->im*zc.im); ++cek; }
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double im, c = *(ke++);
|
|
for (i=2; i<9; ++i) {
|
|
im = c*(cek->re*zc.re - cek->im*zc.im); ++cek;
|
|
mysum[i] += im;
|
|
}
|
|
}
|
|
if (func[3]) { // dipole
|
|
double muk = (mui[0]*h->x+mui[1]*h->y+mui[2]*h->z);
|
|
mysum[9] += *(ke)*(cek->re*zc.re - cek->im*zc.im)*muk;
|
|
if (func[0]) { // charge-dipole
|
|
double qj = *(q)*c[0];
|
|
mysum[9] += *(ke)*(cek_coul->im*zc.re + cek_coul->re*zc.im)*muk;
|
|
mysum[9] -= *(ke)*(cek->re*zc.im + cek->im*zc.re)*qj;
|
|
}
|
|
++cek;
|
|
ke++;
|
|
}
|
|
}
|
|
|
|
if (func[0]) { // 1/r
|
|
double qj = *(q++)*c[0];
|
|
*eatomj += mysum[0]*qj - energy_self_peratom[j][0];
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double bj = B[*type]*c[1];
|
|
*eatomj += mysum[1]*bj - energy_self_peratom[j][1];
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double *bj = B+7*type[0]+7;
|
|
for (i=2; i<9; ++i) {
|
|
double c2 = (--bj)[0]*c[2];
|
|
*eatomj += 0.5*mysum[i]*c2;
|
|
}
|
|
*eatomj -= energy_self_peratom[j][2];
|
|
}
|
|
if (func[3]) { // dipole
|
|
*eatomj += mysum[9] - energy_self_peratom[j][3];
|
|
}
|
|
z = (cvector *) ((char *) z+lbytes);
|
|
++type;
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
#define swap(a, b) { register double t = a; a= b; b = t; }
|
|
|
|
void EwaldDisp::compute_virial()
|
|
{
|
|
memset(virial, 0, 6*sizeof(double));
|
|
if (!vflag_global) return;
|
|
|
|
complex *cek = cek_global;
|
|
complex *cek_coul;
|
|
double *kv = kvirial;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double c[EWALD_NFUNCS] = {
|
|
4.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(24.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 4.0*MY_PI*mumurd2e/volume};
|
|
double mysum[EWALD_NFUNCS][6];
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
memset(mysum, 0, EWALD_NFUNCS*6*sizeof(double));
|
|
for (int k=0; k<nkvec; ++k) { // sum over k vectors
|
|
if (func[0]) { // 1/r
|
|
double r = cek->re*cek->re+cek->im*cek->im;
|
|
if (func[3]) cek_coul = cek;
|
|
++cek;
|
|
mysum[0][0] += *(kv++)*r; mysum[0][1] += *(kv++)*r; mysum[0][2] += *(kv++)*r;
|
|
mysum[0][3] += *(kv++)*r; mysum[0][4] += *(kv++)*r; mysum[0][5] += *(kv++)*r;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double r = cek->re*cek->re+cek->im*cek->im; ++cek;
|
|
mysum[1][0] += *(kv++)*r; mysum[1][1] += *(kv++)*r; mysum[1][2] += *(kv++)*r;
|
|
mysum[1][3] += *(kv++)*r; mysum[1][4] += *(kv++)*r; mysum[1][5] += *(kv++)*r;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double r =
|
|
(cek[0].re*cek[6].re+cek[0].im*cek[6].im)+
|
|
(cek[1].re*cek[5].re+cek[1].im*cek[5].im)+
|
|
(cek[2].re*cek[4].re+cek[2].im*cek[4].im)+
|
|
0.5*(cek[3].re*cek[3].re+cek[3].im*cek[3].im); cek += 7;
|
|
mysum[2][0] += *(kv++)*r; mysum[2][1] += *(kv++)*r; mysum[2][2] += *(kv++)*r;
|
|
mysum[2][3] += *(kv++)*r; mysum[2][4] += *(kv++)*r; mysum[2][5] += *(kv++)*r;
|
|
}
|
|
if (func[3]) {
|
|
double r = cek->re*cek->re+cek->im*cek->im;
|
|
mysum[3][0] += *(kv++)*r; mysum[3][1] += *(kv++)*r; mysum[3][2] += *(kv++)*r;
|
|
mysum[3][3] += *(kv++)*r; mysum[3][4] += *(kv++)*r; mysum[3][5] += *(kv++)*r;
|
|
if (func[0]) { // charge-dipole
|
|
kv -= 6;
|
|
double r = 2.0*(cek->re*cek_coul->im - cek->im*cek_coul->re);
|
|
mysum[3][0] += *(kv++)*r; mysum[3][1] += *(kv++)*r; mysum[3][2] += *(kv++)*r;
|
|
mysum[3][3] += *(kv++)*r; mysum[3][4] += *(kv++)*r; mysum[3][5] += *(kv++)*r;
|
|
}
|
|
++cek;
|
|
}
|
|
}
|
|
for (int k=0; k<EWALD_NFUNCS; ++k)
|
|
if (func[k]) {
|
|
double self[6] = {virial_self[k], virial_self[k], virial_self[k], 0, 0, 0};
|
|
shape_scalar_mult(mysum[k], c[k]);
|
|
shape_add(virial, mysum[k]);
|
|
shape_subtr(virial, self);
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_virial_dipole()
|
|
{
|
|
if (!function[3]) return;
|
|
if (!vflag_atom && !vflag_global) return;
|
|
kvector *k;
|
|
hvector *h, *nh;
|
|
cvector *z = ekr_local;
|
|
double mui[3] = {0.0,0.0,0.0};
|
|
double mysum[6];
|
|
double sum_total[6];
|
|
complex *cek, zc, zx = COMPLEX_NULL, zxy = COMPLEX_NULL;
|
|
complex *cek_coul;
|
|
double *mu = atom->mu ? atom->mu[0] : nullptr;
|
|
double *vatomj = nullptr;
|
|
if (vflag_atom && vatom) vatomj = vatom[0];
|
|
const double qscale = force->qqrd2e * scale;
|
|
double *ke, c[EWALD_NFUNCS] = {
|
|
8.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(12.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 8.0*MY_PI*mumurd2e/volume};
|
|
int i, kx, ky, lbytes = (2*nbox+1)*sizeof(cvector), *type = atom->type;
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
memset(&mysum[0], 0, 6*sizeof(double));
|
|
memset(&sum_total[0], 0, 6*sizeof(double));
|
|
for (int j = 0; j < atom->nlocal; j++) {
|
|
k = kvec;
|
|
kx = ky = -1;
|
|
ke = kenergy;
|
|
cek = cek_global;
|
|
memset(&mysum[0], 0, 6*sizeof(double));
|
|
if (func[3]) {
|
|
double di = c[3];
|
|
mui[0] = di*(mu++)[0]; mui[1] = di*(mu++)[0]; mui[2] = di*(mu++)[0];
|
|
mu++;
|
|
}
|
|
for (nh = (h = hvec)+nkvec; h<nh; ++h, ++k) {
|
|
if (ky!=k->y) { // based on order in
|
|
if (kx!=k->x) zx = z[kx = k->x].x; // reallocate
|
|
C_RMULT(zxy, z[ky = k->y].y, zx);
|
|
}
|
|
C_CRMULT(zc, z[k->z].z, zxy);
|
|
double im = 0.0;
|
|
if (func[0]) { // 1/r
|
|
ke++;
|
|
if (func[3]) cek_coul = cek;
|
|
++cek;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
ke++;
|
|
++cek;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
ke++;
|
|
for (i=2; i<9; ++i) {
|
|
++cek;
|
|
}
|
|
}
|
|
if (func[3]) { // dipole
|
|
im = *(ke)*(zc.re*cek->re - cek->im*zc.im);
|
|
if (func[0]) { // charge-dipole
|
|
im += *(ke)*(zc.im*cek_coul->re + cek_coul->im*zc.re);
|
|
}
|
|
mysum[0] -= mui[0]*h->x*im;
|
|
mysum[1] -= mui[1]*h->y*im;
|
|
mysum[2] -= mui[2]*h->z*im;
|
|
mysum[3] -= mui[0]*h->y*im;
|
|
mysum[4] -= mui[0]*h->z*im;
|
|
mysum[5] -= mui[1]*h->z*im;
|
|
++cek;
|
|
ke++;
|
|
}
|
|
}
|
|
|
|
if (vflag_global)
|
|
for (int n = 0; n < 6; n++)
|
|
sum_total[n] -= mysum[n];
|
|
|
|
if (vflag_atom)
|
|
for (int n = 0; n < 6; n++)
|
|
vatomj[n] -= mysum[n];
|
|
|
|
z = (cvector *) ((char *) z+lbytes);
|
|
++type;
|
|
if (vflag_atom) vatomj += 6;
|
|
}
|
|
|
|
if (vflag_global) {
|
|
MPI_Allreduce(&sum_total[0],&mysum[0],6,MPI_DOUBLE,MPI_SUM,world);
|
|
for (int n = 0; n < 6; n++)
|
|
virial[n] += mysum[n];
|
|
}
|
|
}
|
|
|
|
/* ---------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_virial_peratom()
|
|
{
|
|
if (!vflag_atom) return;
|
|
|
|
kvector *k;
|
|
hvector *h, *nh;
|
|
cvector *z = ekr_local;
|
|
double mui[3] = {0.0,0.0,0.0};
|
|
complex *cek, zc = COMPLEX_NULL, zx = COMPLEX_NULL, zxy = COMPLEX_NULL;
|
|
complex *cek_coul;
|
|
double *kv;
|
|
double *q = atom->q;
|
|
double *vatomj = vatom ? vatom[0] : nullptr;
|
|
double *mu = atom->mu ? atom->mu[0] : nullptr;
|
|
const double qscale = force->qqrd2e * scale;
|
|
double c[EWALD_NFUNCS] = {
|
|
4.0*MY_PI*qscale/volume, 2.0*MY_PI*MY_PIS/(24.0*volume),
|
|
2.0*MY_PI*MY_PIS/(192.0*volume), 4.0*MY_PI*mumurd2e/volume};
|
|
double mysum[EWALD_MAX_NSUMS][6];
|
|
int func[EWALD_NFUNCS];
|
|
|
|
memcpy(func, function, EWALD_NFUNCS*sizeof(int));
|
|
int i, kx, ky, lbytes = (2*nbox+1)*sizeof(cvector), *type = atom->type;
|
|
for (int j = 0; j < atom->nlocal; j++) {
|
|
k = kvec;
|
|
kx = ky = -1;
|
|
kv = kvirial;
|
|
cek = cek_global;
|
|
memset(mysum, 0, EWALD_MAX_NSUMS*6*sizeof(double));
|
|
if (func[3]) {
|
|
double di = c[3];
|
|
mui[0] = di*(mu++)[0]; mui[1] = di*(mu++)[0]; mui[2] = di*(mu++)[0];
|
|
mu++;
|
|
}
|
|
for (nh = (h = hvec)+nkvec; h<nh; ++h, ++k) {
|
|
if (ky!=k->y) { // based on order in
|
|
if (kx!=k->x) zx = z[kx = k->x].x; // reallocate
|
|
C_RMULT(zxy, z[ky = k->y].y, zx);
|
|
}
|
|
C_CRMULT(zc, z[k->z].z, zxy);
|
|
if (func[0]) { // 1/r
|
|
if (func[3]) cek_coul = cek;
|
|
double r = cek->re*zc.re - cek->im*zc.im; ++cek;
|
|
mysum[0][0] += *(kv++)*r;
|
|
mysum[0][1] += *(kv++)*r;
|
|
mysum[0][2] += *(kv++)*r;
|
|
mysum[0][3] += *(kv++)*r;
|
|
mysum[0][4] += *(kv++)*r;
|
|
mysum[0][5] += *(kv++)*r;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double r = cek->re*zc.re - cek->im*zc.im; ++cek;
|
|
mysum[1][0] += *(kv++)*r;
|
|
mysum[1][1] += *(kv++)*r;
|
|
mysum[1][2] += *(kv++)*r;
|
|
mysum[1][3] += *(kv++)*r;
|
|
mysum[1][4] += *(kv++)*r;
|
|
mysum[1][5] += *(kv++)*r;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double r;
|
|
for (i=2; i<9; ++i) {
|
|
r = cek->re*zc.re - cek->im*zc.im; ++cek;
|
|
mysum[i][0] += *(kv++)*r;
|
|
mysum[i][1] += *(kv++)*r;
|
|
mysum[i][2] += *(kv++)*r;
|
|
mysum[i][3] += *(kv++)*r;
|
|
mysum[i][4] += *(kv++)*r;
|
|
mysum[i][5] += *(kv++)*r;
|
|
kv -= 6;
|
|
}
|
|
kv += 6;
|
|
}
|
|
if (func[3]) { // dipole
|
|
double muk = (mui[0]*h->x+mui[1]*h->y+mui[2]*h->z);
|
|
double
|
|
r = (cek->re*zc.re - cek->im*zc.im)*muk;
|
|
mysum[9][0] += *(kv++)*r;
|
|
mysum[9][1] += *(kv++)*r;
|
|
mysum[9][2] += *(kv++)*r;
|
|
mysum[9][3] += *(kv++)*r;
|
|
mysum[9][4] += *(kv++)*r;
|
|
mysum[9][5] += *(kv++)*r;
|
|
if (func[0]) { // charge-dipole
|
|
kv -= 6;
|
|
double qj = *(q)*c[0];
|
|
r = (cek_coul->im*zc.re + cek_coul->re*zc.im)*muk;
|
|
r += -(cek->re*zc.im + cek->im*zc.re)*qj;
|
|
mysum[9][0] += *(kv++)*r; mysum[9][1] += *(kv++)*r; mysum[9][2] += *(kv++)*r;
|
|
mysum[9][3] += *(kv++)*r; mysum[9][4] += *(kv++)*r; mysum[9][5] += *(kv++)*r;
|
|
}
|
|
++cek;
|
|
}
|
|
}
|
|
|
|
if (func[0]) { // 1/r
|
|
double qi = *(q++)*c[0];
|
|
for (int n = 0; n < 6; n++) vatomj[n] += mysum[0][n]*qi;
|
|
}
|
|
if (func[1]) { // geometric 1/r^6
|
|
double bi = B[*type]*c[1];
|
|
for (int n = 0; n < 6; n++) vatomj[n] += mysum[1][n]*bi;
|
|
}
|
|
if (func[2]) { // arithmetic 1/r^6
|
|
double *bj = B+7*type[0]+7;
|
|
for (i=2; i<9; ++i) {
|
|
double c2 = (--bj)[0]*c[2];
|
|
for (int n = 0; n < 6; n++) vatomj[n] += 0.5*mysum[i][n]*c2;
|
|
}
|
|
}
|
|
if (func[3]) { // dipole
|
|
for (int n = 0; n < 6; n++) vatomj[n] += mysum[9][n];
|
|
}
|
|
|
|
for (int k=0; k<EWALD_NFUNCS; ++k) {
|
|
if (func[k]) {
|
|
for (int n = 0; n < 3; n++) vatomj[n] -= virial_self_peratom[j][k];
|
|
}
|
|
}
|
|
|
|
z = (cvector *) ((char *) z+lbytes);
|
|
++type;
|
|
vatomj += 6;
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
Slab-geometry correction term to dampen inter-slab interactions between
|
|
periodically repeating slabs. Yields good approximation to 2D Ewald if
|
|
adequate empty space is left between repeating slabs (J. Chem. Phys.
|
|
111, 3155). Slabs defined here to be parallel to the xy plane. Also
|
|
extended to non-neutral systems (J. Chem. Phys. 131, 094107).
|
|
------------------------------------------------------------------------- */
|
|
|
|
void EwaldDisp::compute_slabcorr()
|
|
{
|
|
// compute local contribution to global dipole moment
|
|
|
|
double *q = atom->q;
|
|
double **x = atom->x;
|
|
double zprd = domain->zprd;
|
|
int nlocal = atom->nlocal;
|
|
|
|
double dipole = 0.0;
|
|
for (int i = 0; i < nlocal; i++) dipole += q[i]*x[i][2];
|
|
|
|
if (function[3] && atom->mu) {
|
|
double **mu = atom->mu;
|
|
for (int i = 0; i < nlocal; i++) dipole += mu[i][2];
|
|
}
|
|
|
|
// sum local contributions to get global dipole moment
|
|
|
|
double dipole_all;
|
|
MPI_Allreduce(&dipole,&dipole_all,1,MPI_DOUBLE,MPI_SUM,world);
|
|
|
|
// need to make non-neutral systems and/or
|
|
// per-atom energy translationally invariant
|
|
|
|
double dipole_r2 = 0.0;
|
|
if (eflag_atom || fabs(qsum) > SMALL) {
|
|
|
|
if (function[3] && atom->mu)
|
|
error->all(FLERR,"Cannot (yet) use kspace slab correction with "
|
|
"long-range dipoles and non-neutral systems or per-atom energy");
|
|
|
|
for (int i = 0; i < nlocal; i++)
|
|
dipole_r2 += q[i]*x[i][2]*x[i][2];
|
|
|
|
// sum local contributions
|
|
|
|
double tmp;
|
|
MPI_Allreduce(&dipole_r2,&tmp,1,MPI_DOUBLE,MPI_SUM,world);
|
|
dipole_r2 = tmp;
|
|
}
|
|
|
|
// compute corrections
|
|
|
|
const double e_slabcorr = MY_2PI*(dipole_all*dipole_all -
|
|
qsum*dipole_r2 - qsum*qsum*zprd_slab*zprd_slab/12.0)/volume;
|
|
const double qscale = force->qqrd2e * scale;
|
|
|
|
if (eflag_global) energy += qscale * e_slabcorr;
|
|
|
|
// per-atom energy
|
|
|
|
if (eflag_atom) {
|
|
double efact = qscale * MY_2PI/volume;
|
|
for (int i = 0; i < nlocal; i++)
|
|
eatom[i] += efact * q[i]*(x[i][2]*dipole_all - 0.5*(dipole_r2 +
|
|
qsum*x[i][2]*x[i][2]) - qsum*zprd_slab*zprd_slab/12.0);
|
|
}
|
|
|
|
// add on force corrections
|
|
|
|
double ffact = qscale * (-4.0*MY_PI/volume);
|
|
double **f = atom->f;
|
|
|
|
for (int i = 0; i < nlocal; i++)
|
|
f[i][2] += ffact * q[i]*(dipole_all - qsum*x[i][2]);
|
|
|
|
// add on torque corrections
|
|
|
|
if (function[3] && atom->mu && atom->torque) {
|
|
double **mu = atom->mu;
|
|
double **torque = atom->torque;
|
|
for (int i = 0; i < nlocal; i++) {
|
|
torque[i][0] += ffact * dipole_all * mu[i][1];
|
|
torque[i][1] += -ffact * dipole_all * mu[i][0];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
Newton solver used to find g_ewald for LJ systems
|
|
------------------------------------------------------------------------- */
|
|
|
|
double EwaldDisp::NewtonSolve(double x, double Rc,
|
|
bigint natoms, double vol, double b2)
|
|
{
|
|
double dx,tol;
|
|
int maxit;
|
|
|
|
maxit = 10000; //Maximum number of iterations
|
|
tol = 0.00001; //Convergence tolerance
|
|
|
|
//Begin algorithm
|
|
|
|
for (int i = 0; i < maxit; i++) {
|
|
dx = f(x,Rc,natoms,vol,b2) / derivf(x,Rc,natoms,vol,b2);
|
|
x = x - dx; //Update x
|
|
if (fabs(dx) < tol) return x;
|
|
if (x < 0 || x != x) // solver failed
|
|
return -1;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
Calculate f(x)
|
|
------------------------------------------------------------------------- */
|
|
|
|
double EwaldDisp::f(double x, double Rc, bigint natoms, double vol, double b2)
|
|
{
|
|
double a = Rc*x;
|
|
double f = 0.0;
|
|
|
|
if (function[3]) { // dipole
|
|
double rg2 = a*a;
|
|
double rg4 = rg2*rg2;
|
|
double rg6 = rg4*rg2;
|
|
double Cc = 4.0*rg4 + 6.0*rg2 + 3.0;
|
|
double Dc = 8.0*rg6 + 20.0*rg4 + 30.0*rg2 + 15.0;
|
|
f = (b2/(sqrt(vol*powint(x,4)*powint(Rc,9)*natoms)) *
|
|
sqrt(13.0/6.0*Cc*Cc + 2.0/15.0*Dc*Dc - 13.0/15.0*Cc*Dc) *
|
|
exp(-rg2)) - accuracy;
|
|
} else if (function[1] || function[2]) { // LJ
|
|
f = (4.0*MY_PI*b2*powint(x,4)/vol/sqrt((double)natoms)*erfc(a) *
|
|
(6.0*powint(a,-5) + 6.0*powint(a,-3) + 3.0/a + a) - accuracy);
|
|
}
|
|
|
|
return f;
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------
|
|
Calculate numerical derivative f'(x)
|
|
------------------------------------------------------------------------- */
|
|
|
|
double EwaldDisp::derivf(double x, double Rc,
|
|
bigint natoms, double vol, double b2)
|
|
{
|
|
double h = 0.000001; //Derivative step-size
|
|
return (f(x + h,Rc,natoms,vol,b2) - f(x,Rc,natoms,vol,b2)) / h;
|
|
}
|