#include "pppm_dipole_spin.h"
#include <mpi.h>
#include <cstring>
#include "atom.h"
#include "comm.h"
#include "gridcomm.h"
#include "force.h"
#include "pair.h"
#include "domain.h"
#include "memory.h"
#include "error.h"
#include "update.h"
#include "math_const.h"
using namespace LAMMPS_NS;
using namespace MathConst;
#define MAXORDER 7
#define OFFSET 16384
#define LARGE 10000.0
#define SMALL 0.00001
#define EPS_HOC 1.0e-7
enum{REVERSE_MU};
enum{FORWARD_MU,FORWARD_MU_PERATOM};
#ifdef FFT_SINGLE
#define ZEROF 0.0f
#define ONEF 1.0f
#else
#define ZEROF 0.0
#define ONEF 1.0
#endif
PPPMDipoleSpin::PPPMDipoleSpin(LAMMPS *lmp) :
PPPMDipole(lmp)
{
dipoleflag = 0;
spinflag = 1;
hbar = force->hplanck/MY_2PI; mub = 9.274e-4; mu_0 = 785.15; mub2mu0 = mub * mub * mu_0 / (4.0*MY_PI); mub2mu0hbinv = mub2mu0 / hbar; }
PPPMDipoleSpin::~PPPMDipoleSpin()
{
if (copymode) return;
deallocate();
if (peratom_allocate_flag) deallocate_peratom();
fft1 = NULL;
fft2 = NULL;
remap = NULL;
cg_dipole = NULL;
}
void PPPMDipoleSpin::init()
{
if (me == 0) {
if (screen) fprintf(screen,"PPPMDipoleSpin initialization ...\n");
if (logfile) fprintf(logfile,"PPPMDipoleSpin initialization ...\n");
}
spinflag = atom->sp?1:0;
triclinic_check();
if (triclinic != domain->triclinic)
error->all(FLERR,"Must redefine kspace_style after changing to triclinic box");
if (domain->dimension == 2) error->all(FLERR,
"Cannot use PPPMDipoleSpin with 2d simulation");
if (comm->style != 0)
error->universe_all(FLERR,"PPPMDipoleSpin can only currently be used with "
"comm_style brick");
if (!atom->sp) error->all(FLERR,"Kspace style requires atom attribute sp");
if (atom->sp && differentiation_flag == 1) error->all(FLERR,"Cannot (yet) use"
" kspace_modify diff ad with spins");
if (spinflag && strcmp(update->unit_style,"metal") != 0)
error->all(FLERR,"'metal' units have to be used with spins");
if (slabflag == 0 && domain->nonperiodic > 0)
error->all(FLERR,"Cannot use nonperiodic boundaries with PPPMDipoleSpin");
if (slabflag) {
if (domain->xperiodic != 1 || domain->yperiodic != 1 ||
domain->boundary[2][0] != 1 || domain->boundary[2][1] != 1)
error->all(FLERR,"Incorrect boundaries with slab PPPMDipoleSpin");
}
if (order < 2 || order > MAXORDER) {
char str[128];
sprintf(str,"PPPMDipoleSpin order cannot be < 2 or > than %d",MAXORDER);
error->all(FLERR,str);
}
triclinic = domain->triclinic;
if (triclinic)
error->all(FLERR,"Cannot yet use triclinic cells with PPPMDipoleSpin");
pair_check();
int itmp = 0;
double *p_cutoff = (double *) force->pair->extract("cut_coul",itmp);
if (p_cutoff == NULL)
error->all(FLERR,"KSpace style is incompatible with Pair style");
cutoff = *p_cutoff;
qdist = 0.0;
if (tip4pflag)
error->all(FLERR,"Cannot yet use TIP4P with PPPMDipoleSpin");
scale = 1.0;
spsum_spsq();
natoms_original = atom->natoms;
if (accuracy_absolute >= 0.0) accuracy = accuracy_absolute;
else accuracy = accuracy_relative * two_charge_force;
deallocate();
if (peratom_allocate_flag) deallocate_peratom();
int (*procneigh)[2] = comm->procneigh;
GridComm *cgtmp = NULL;
int iteration = 0;
while (order >= minorder) {
if (iteration && me == 0)
error->warning(FLERR,"Reducing PPPMDipoleSpin order b/c stencil extends "
"beyond nearest neighbor processor");
compute_gf_denom();
set_grid_global();
set_grid_local();
if (overlap_allowed) break;
cgtmp = new GridComm(lmp,world,1,1,
nxlo_in,nxhi_in,nylo_in,nyhi_in,nzlo_in,nzhi_in,
nxlo_out,nxhi_out,nylo_out,nyhi_out,nzlo_out,nzhi_out,
procneigh[0][0],procneigh[0][1],procneigh[1][0],
procneigh[1][1],procneigh[2][0],procneigh[2][1]);
cgtmp->ghost_notify();
if (!cgtmp->ghost_overlap()) break;
delete cgtmp;
order--;
iteration++;
}
if (order < minorder) error->all(FLERR,"PPPMDipoleSpin order < minimum allowed order");
if (!overlap_allowed && cgtmp->ghost_overlap())
error->all(FLERR,"PPPMDipoleSpin grid stencil extends "
"beyond nearest neighbor processor");
if (cgtmp) delete cgtmp;
if (!gewaldflag) adjust_gewald();
double estimated_accuracy = final_accuracy_dipole();
int ngrid_max,nfft_both_max;
MPI_Allreduce(&ngrid,&ngrid_max,1,MPI_INT,MPI_MAX,world);
MPI_Allreduce(&nfft_both,&nfft_both_max,1,MPI_INT,MPI_MAX,world);
if (me == 0) {
#ifdef FFT_SINGLE
const char fft_prec[] = "single";
#else
const char fft_prec[] = "double";
#endif
if (screen) {
fprintf(screen," G vector (1/distance) = %g\n",g_ewald);
fprintf(screen," grid = %d %d %d\n",nx_pppm,ny_pppm,nz_pppm);
fprintf(screen," stencil order = %d\n",order);
fprintf(screen," estimated absolute RMS force accuracy = %g\n",
estimated_accuracy);
fprintf(screen," estimated relative force accuracy = %g\n",
estimated_accuracy/two_charge_force);
fprintf(screen," using %s precision FFTs\n",fft_prec);
fprintf(screen," 3d grid and FFT values/proc = %d %d\n",
ngrid_max,nfft_both_max);
}
if (logfile) {
fprintf(logfile," G vector (1/distance) = %g\n",g_ewald);
fprintf(logfile," grid = %d %d %d\n",nx_pppm,ny_pppm,nz_pppm);
fprintf(logfile," stencil order = %d\n",order);
fprintf(logfile," estimated absolute RMS force accuracy = %g\n",
estimated_accuracy);
fprintf(logfile," estimated relative force accuracy = %g\n",
estimated_accuracy/two_charge_force);
fprintf(logfile," using %s precision FFTs\n",fft_prec);
fprintf(logfile," 3d grid and FFT values/proc = %d %d\n",
ngrid_max,nfft_both_max);
}
}
allocate();
cg_dipole->ghost_notify();
cg_dipole->setup();
compute_gf_denom();
compute_rho_coeff();
}
void PPPMDipoleSpin::compute(int eflag, int vflag)
{
int i,j;
if (eflag || vflag) ev_setup(eflag,vflag);
else evflag = evflag_atom = eflag_global = vflag_global =
eflag_atom = vflag_atom = 0;
if (vflag_atom)
error->all(FLERR,"Cannot (yet) compute per-atom virial "
"with kspace style pppm/dipole/spin");
if (evflag_atom && !peratom_allocate_flag) {
allocate_peratom();
cg_peratom_dipole->ghost_notify();
cg_peratom_dipole->setup();
}
if (atom->natoms != natoms_original) {
spsum_spsq();
natoms_original = atom->natoms;
}
if (musqsum == 0.0) return;
boxlo = domain->boxlo;
if (atom->nmax > nmax) {
memory->destroy(part2grid);
nmax = atom->nmax;
memory->create(part2grid,nmax,3,"pppm_spin:part2grid");
}
particle_map();
make_rho_spin();
cg_dipole->reverse_comm(this,REVERSE_MU);
brick2fft_dipole();
poisson_ik_dipole();
cg_dipole->forward_comm(this,FORWARD_MU);
if (evflag_atom) {
cg_peratom_dipole->forward_comm(this,FORWARD_MU_PERATOM);
}
fieldforce_ik_spin();
if (evflag_atom) fieldforce_peratom_spin();
const double spscale = mub2mu0 * scale;
const double g3 = g_ewald*g_ewald*g_ewald;
if (eflag_global) {
double energy_all;
MPI_Allreduce(&energy,&energy_all,1,MPI_DOUBLE,MPI_SUM,world);
energy = energy_all;
energy *= 0.5*volume;
energy -= musqsum*2.0*g3/3.0/MY_PIS;
energy *= spscale;
}
if (vflag_global) {
double virial_all[6];
MPI_Allreduce(virial,virial_all,6,MPI_DOUBLE,MPI_SUM,world);
for (i = 0; i < 6; i++) virial[i] = 0.5*spscale*volume*virial_all[i];
}
if (evflag_atom) {
double **sp = atom->sp;
double spx,spy,spz;
int nlocal = atom->nlocal;
int ntotal = nlocal;
if (eflag_atom) {
for (i = 0; i < nlocal; i++) {
spx = sp[i][0]*sp[i][3];
spy = sp[i][1]*sp[i][3];
spz = sp[i][2]*sp[i][3];
eatom[i] *= 0.5;
eatom[i] -= (spx*spx + spy*spy + spz*spz)*2.0*g3/3.0/MY_PIS;
eatom[i] *= spscale;
}
}
if (vflag_atom) {
for (i = 0; i < ntotal; i++)
for (j = 0; j < 6; j++) vatom[i][j] *= 0.5*spscale;
}
}
if (slabflag == 1) slabcorr();
}
void PPPMDipoleSpin::make_rho_spin()
{
int l,m,n,nx,ny,nz,mx,my,mz;
FFT_SCALAR dx,dy,dz;
FFT_SCALAR x0,y0,z0;
FFT_SCALAR x1,y1,z1;
FFT_SCALAR x2,y2,z2;
memset(&(densityx_brick_dipole[nzlo_out][nylo_out][nxlo_out]),0,
ngrid*sizeof(FFT_SCALAR));
memset(&(densityy_brick_dipole[nzlo_out][nylo_out][nxlo_out]),0,
ngrid*sizeof(FFT_SCALAR));
memset(&(densityz_brick_dipole[nzlo_out][nylo_out][nxlo_out]),0,
ngrid*sizeof(FFT_SCALAR));
double **sp = atom->sp;
double spx,spy,spz;
double **x = atom->x;
int nlocal = atom->nlocal;
for (int i = 0; i < nlocal; i++) {
nx = part2grid[i][0];
ny = part2grid[i][1];
nz = part2grid[i][2];
dx = nx+shiftone - (x[i][0]-boxlo[0])*delxinv;
dy = ny+shiftone - (x[i][1]-boxlo[1])*delyinv;
dz = nz+shiftone - (x[i][2]-boxlo[2])*delzinv;
compute_rho1d(dx,dy,dz);
spx = sp[i][0]*sp[i][3];
spy = sp[i][1]*sp[i][3];
spz = sp[i][2]*sp[i][3];
z0 = delvolinv * spx;
z1 = delvolinv * spy;
z2 = delvolinv * spz;
for (n = nlower; n <= nupper; n++) {
mz = n+nz;
y0 = z0*rho1d[2][n];
y1 = z1*rho1d[2][n];
y2 = z2*rho1d[2][n];
for (m = nlower; m <= nupper; m++) {
my = m+ny;
x0 = y0*rho1d[1][m];
x1 = y1*rho1d[1][m];
x2 = y2*rho1d[1][m];
for (l = nlower; l <= nupper; l++) {
mx = l+nx;
densityx_brick_dipole[mz][my][mx] += x0*rho1d[0][l];
densityy_brick_dipole[mz][my][mx] += x1*rho1d[0][l];
densityz_brick_dipole[mz][my][mx] += x2*rho1d[0][l];
}
}
}
}
}
void PPPMDipoleSpin::fieldforce_ik_spin()
{
int i,l,m,n,nx,ny,nz,mx,my,mz;
FFT_SCALAR dx,dy,dz;
FFT_SCALAR x0,y0,z0;
FFT_SCALAR ex,ey,ez;
FFT_SCALAR vxx,vyy,vzz,vxy,vxz,vyz;
double **sp = atom->sp;
double spx,spy,spz;
double **x = atom->x;
double **f = atom->f;
double **fm_long = atom->fm_long;
int nlocal = atom->nlocal;
for (i = 0; i < nlocal; i++) {
nx = part2grid[i][0];
ny = part2grid[i][1];
nz = part2grid[i][2];
dx = nx+shiftone - (x[i][0]-boxlo[0])*delxinv;
dy = ny+shiftone - (x[i][1]-boxlo[1])*delyinv;
dz = nz+shiftone - (x[i][2]-boxlo[2])*delzinv;
compute_rho1d(dx,dy,dz);
ex = ey = ez = ZEROF;
vxx = vyy = vzz = vxy = vxz = vyz = ZEROF;
for (n = nlower; n <= nupper; n++) {
mz = n+nz;
z0 = rho1d[2][n];
for (m = nlower; m <= nupper; m++) {
my = m+ny;
y0 = z0*rho1d[1][m];
for (l = nlower; l <= nupper; l++) {
mx = l+nx;
x0 = y0*rho1d[0][l];
ex -= x0*ux_brick_dipole[mz][my][mx];
ey -= x0*uy_brick_dipole[mz][my][mx];
ez -= x0*uz_brick_dipole[mz][my][mx];
vxx -= x0*vdxx_brick_dipole[mz][my][mx];
vyy -= x0*vdyy_brick_dipole[mz][my][mx];
vzz -= x0*vdzz_brick_dipole[mz][my][mx];
vxy -= x0*vdxy_brick_dipole[mz][my][mx];
vxz -= x0*vdxz_brick_dipole[mz][my][mx];
vyz -= x0*vdyz_brick_dipole[mz][my][mx];
}
}
}
const double spfactor = mub2mu0 * scale;
spx = sp[i][0]*sp[i][3];
spy = sp[i][1]*sp[i][3];
spz = sp[i][2]*sp[i][3];
f[i][0] += spfactor*(vxx*spx + vxy*spy + vxz*spz);
f[i][1] += spfactor*(vxy*spx + vyy*spy + vyz*spz);
f[i][2] += spfactor*(vxz*spx + vyz*spy + vzz*spz);
const double spfactorh = mub2mu0hbinv * scale;
fm_long[i][0] += spfactorh*ex;
fm_long[i][1] += spfactorh*ey;
fm_long[i][2] += spfactorh*ez;
}
}
void PPPMDipoleSpin::fieldforce_peratom_spin()
{
int i,l,m,n,nx,ny,nz,mx,my,mz;
FFT_SCALAR dx,dy,dz,x0,y0,z0;
FFT_SCALAR ux,uy,uz;
FFT_SCALAR v0x,v1x,v2x,v3x,v4x,v5x;
FFT_SCALAR v0y,v1y,v2y,v3y,v4y,v5y;
FFT_SCALAR v0z,v1z,v2z,v3z,v4z,v5z;
double **sp = atom->sp;
double spx,spy,spz;
double **x = atom->x;
int nlocal = atom->nlocal;
for (i = 0; i < nlocal; i++) {
nx = part2grid[i][0];
ny = part2grid[i][1];
nz = part2grid[i][2];
dx = nx+shiftone - (x[i][0]-boxlo[0])*delxinv;
dy = ny+shiftone - (x[i][1]-boxlo[1])*delyinv;
dz = nz+shiftone - (x[i][2]-boxlo[2])*delzinv;
compute_rho1d(dx,dy,dz);
ux = uy = uz = ZEROF;
v0x = v1x = v2x = v3x = v4x = v5x = ZEROF;
v0y = v1y = v2y = v3y = v4y = v5y = ZEROF;
v0z = v1z = v2z = v3z = v4z = v5z = ZEROF;
for (n = nlower; n <= nupper; n++) {
mz = n+nz;
z0 = rho1d[2][n];
for (m = nlower; m <= nupper; m++) {
my = m+ny;
y0 = z0*rho1d[1][m];
for (l = nlower; l <= nupper; l++) {
mx = l+nx;
x0 = y0*rho1d[0][l];
if (eflag_atom) {
ux += x0*ux_brick_dipole[mz][my][mx];
uy += x0*uy_brick_dipole[mz][my][mx];
uz += x0*uz_brick_dipole[mz][my][mx];
}
if (vflag_atom) {
v0x += x0*v0x_brick_dipole[mz][my][mx];
v1x += x0*v1x_brick_dipole[mz][my][mx];
v2x += x0*v2x_brick_dipole[mz][my][mx];
v3x += x0*v3x_brick_dipole[mz][my][mx];
v4x += x0*v4x_brick_dipole[mz][my][mx];
v5x += x0*v5x_brick_dipole[mz][my][mx];
v0y += x0*v0y_brick_dipole[mz][my][mx];
v1y += x0*v1y_brick_dipole[mz][my][mx];
v2y += x0*v2y_brick_dipole[mz][my][mx];
v3y += x0*v3y_brick_dipole[mz][my][mx];
v4y += x0*v4y_brick_dipole[mz][my][mx];
v5y += x0*v5y_brick_dipole[mz][my][mx];
v0z += x0*v0z_brick_dipole[mz][my][mx];
v1z += x0*v1z_brick_dipole[mz][my][mx];
v2z += x0*v2z_brick_dipole[mz][my][mx];
v3z += x0*v3z_brick_dipole[mz][my][mx];
v4z += x0*v4z_brick_dipole[mz][my][mx];
v5z += x0*v5z_brick_dipole[mz][my][mx];
}
}
}
}
spx = sp[i][0]*sp[i][3];
spy = sp[i][1]*sp[i][3];
spz = sp[i][2]*sp[i][3];
if (eflag_atom) eatom[i] += spx*ux + spy*uy + spz*uz;
if (vflag_atom) {
vatom[i][0] += spx*v0x + spy*v0y + spz*v0z;
vatom[i][1] += spx*v1x + spy*v1y + spz*v1z;
vatom[i][2] += spx*v2x + spy*v2y + spz*v2z;
vatom[i][3] += spx*v3x + spy*v3y + spz*v3z;
vatom[i][4] += spx*v4x + spy*v4y + spz*v4z;
vatom[i][5] += spx*v5x + spy*v5y + spz*v5z;
}
}
}
void PPPMDipoleSpin::slabcorr()
{
double **x = atom->x;
double zprd = domain->zprd;
int nlocal = atom->nlocal;
double spin = 0.0;
double **sp = atom->sp;
double spx,spy,spz;
for (int i = 0; i < nlocal; i++) {
spz = sp[i][2]*sp[i][3];
spin += spz;
}
double spin_all;
MPI_Allreduce(&spin,&spin_all,1,MPI_DOUBLE,MPI_SUM,world);
const double e_slabcorr = MY_2PI*(spin_all*spin_all/12.0)/volume;
const double spscale = mub2mu0 * scale;
if (eflag_global) energy += spscale * e_slabcorr;
if (eflag_atom) {
double efact = spscale * MY_2PI/volume/12.0;
for (int i = 0; i < nlocal; i++) {
spz = sp[i][2]*sp[i][3];
eatom[i] += efact * spz * spin_all;
}
}
double ffact = spscale * (-4.0*MY_PI/volume);
double **fm_long = atom->fm_long;
for (int i = 0; i < nlocal; i++) {
fm_long[i][2] += ffact * spin_all;
}
}
void PPPMDipoleSpin::spsum_spsq()
{
const int nlocal = atom->nlocal;
musum = musqsum = mu2 = 0.0;
if (atom->sp_flag) {
double **sp = atom->sp;
double spx, spy, spz;
double spsum_local(0.0), spsqsum_local(0.0);
for (int i = 0; i < nlocal; i++) {
spx = sp[i][0]*sp[i][3];
spy = sp[i][1]*sp[i][3];
spz = sp[i][2]*sp[i][3];
spsum_local += spx + spy + spz;
spsqsum_local += spx*spx + spy*spy + spz*spz;
}
MPI_Allreduce(&spsum_local,&musum,1,MPI_DOUBLE,MPI_SUM,world);
MPI_Allreduce(&spsqsum_local,&musqsum,1,MPI_DOUBLE,MPI_SUM,world);
mu2 = musqsum;
}
if (mu2 == 0 && comm->me == 0)
error->all(FLERR,"Using kspace solver PPPMDipoleSpin on system with no spins");
}