#include "fix_tune_kspace.h"
#include <cmath>
#include <cstring>
#include <limits>
#include "update.h"
#include "force.h"
#include "kspace.h"
#include "pair.h"
#include "error.h"
#include "memory.h"
#include "timer.h"
#include "neighbor.h"
#include "modify.h"
#include "compute.h"
#define SWAP(a,b) {temp=(a);(a)=(b);(b)=temp;}
#define SIGN(a,b) ((b) >= 0.0 ? fabs(a) : -fabs(a))
#define GOLD 1.618034
using namespace std;
using namespace LAMMPS_NS;
using namespace FixConst;
FixTuneKspace::FixTuneKspace(LAMMPS *lmp, int narg, char **arg) :
Fix(lmp, narg, arg)
{
if (narg < 3) error->all(FLERR,"Illegal fix tune/kspace command");
global_freq = 1;
firststep = 0;
niter = 0;
niter_adjust_rcut = 0;
keep_bracketing = true;
first_brent_pass = true;
converged = false;
need_fd2_brent = false;
ewald_time = pppm_time = msm_time = 0.0;
nevery = force->inumeric(FLERR,arg[3]);
if (nevery <= 0) error->all(FLERR,"Illegal fix tune/kspace command");
force_reneighbor = 1;
next_reneighbor = update->ntimestep + 1;
}
int FixTuneKspace::setmask()
{
int mask = 0;
mask |= PRE_EXCHANGE;
mask |= PRE_NEIGHBOR;
return mask;
}
void FixTuneKspace::init()
{
if (!force->kspace)
error->all(FLERR,"Cannot use fix tune/kspace without a kspace style");
if (!force->pair)
error->all(FLERR,"Cannot use fix tune/kspace without a pair style");
if (strncmp(force->pair_style,"hybrid",6) == 0)
error->all(FLERR,"Cannot use fix tune/kspace with a hybrid pair style");
if (force->kspace->dispersionflag)
error->all(FLERR,"Cannot use fix tune/kspace with long-range dispersion");
if (force->kspace->tip4pflag)
error->all(FLERR,"Cannot use fix tune/kspace with TIP4P water");
if (force->kspace->dipoleflag)
error->all(FLERR,"Cannot use fix tune/kspace with dipole long-range solver");
double old_acc = force->kspace->accuracy/force->kspace->two_charge_force;
char old_acc_str[16];
snprintf(old_acc_str,16,"%g",old_acc);
strncpy(new_acc_str,old_acc_str,16);
int itmp;
double *p_cutoff = (double *) force->pair->extract("cut_coul",itmp);
pair_cut_coul = *p_cutoff;
}
void FixTuneKspace::pre_exchange()
{
if (!nevery) return;
if (!force->kspace) return;
if (!force->pair) return;
if (next_reneighbor != update->ntimestep) return;
next_reneighbor = update->ntimestep + nevery;
double time = get_timing_info();
if (strcmp(force->kspace_style,"ewald") == 0) ewald_time = time;
if (strcmp(force->kspace_style,"pppm") == 0) pppm_time = time;
if (strcmp(force->kspace_style,"msm") == 0) msm_time = time;
niter++;
if (niter == 1) {
store_old_kspace_settings();
strcpy(new_kspace_style,"ewald");
snprintf(new_pair_style,64,"%s/long",base_pair_style);
update_pair_style(new_pair_style,pair_cut_coul);
update_kspace_style(new_kspace_style,new_acc_str);
} else if (niter == 2) {
store_old_kspace_settings();
strcpy(new_kspace_style,"pppm");
snprintf(new_pair_style,64,"%s/long",base_pair_style);
update_pair_style(new_pair_style,pair_cut_coul);
update_kspace_style(new_kspace_style,new_acc_str);
} else if (niter == 3) {
store_old_kspace_settings();
strcpy(new_kspace_style,"msm");
snprintf(new_pair_style,64,"%s/msm",base_pair_style);
update_pair_style(new_pair_style,pair_cut_coul);
update_kspace_style(new_kspace_style,new_acc_str);
} else if (niter == 4) {
store_old_kspace_settings();
if (screen) fprintf(screen,"ewald_time = %g\npppm_time = %g\nmsm_time = %g",
ewald_time, pppm_time, msm_time);
if (logfile) fprintf(logfile,"ewald_time = %g\npppm_time = %g\nmsm_time = %g",
ewald_time, pppm_time, msm_time);
strcpy(new_kspace_style,"ewald");
snprintf(new_pair_style,64,"%s/long",base_pair_style);
if (pppm_time < ewald_time && pppm_time < msm_time)
strcpy(new_kspace_style,"pppm");
else if (msm_time < pppm_time && msm_time < ewald_time) {
strcpy(new_kspace_style,"msm");
snprintf(new_pair_style,64,"%s/msm",base_pair_style);
}
update_pair_style(new_pair_style,pair_cut_coul);
update_kspace_style(new_kspace_style,new_acc_str);
} else {
adjust_rcut(time);
}
last_spcpu = timer->elapsed(Timer::TOTAL);
}
double FixTuneKspace::get_timing_info()
{
double dvalue;
double new_cpu;
int new_step = update->ntimestep;
if (firststep == 0) {
new_cpu = 0.0;
dvalue = 0.0;
firststep = 1;
} else {
new_cpu = timer->elapsed(Timer::TOTAL);
double cpu_diff = new_cpu - last_spcpu;
int step_diff = new_step - last_step;
if (step_diff > 0.0) dvalue = cpu_diff/step_diff;
else dvalue = 0.0;
}
last_step = new_step;
last_spcpu = new_cpu;
return dvalue;
}
void FixTuneKspace::store_old_kspace_settings()
{
int n = strlen(force->kspace_style) + 1;
char *old_kspace_style = new char[n];
strcpy(old_kspace_style,force->kspace_style);
strcpy(new_kspace_style,old_kspace_style);
double old_acc = force->kspace->accuracy_relative;
char old_acc_str[16];
snprintf(old_acc_str,16,"%g",old_acc);
strcpy(new_pair_style,force->pair_style);
strcpy(base_pair_style,force->pair_style);
char *trunc;
if ((trunc = strstr(base_pair_style, "/long")) != NULL) *trunc = '\0';
if ((trunc = strstr(base_pair_style, "/msm" )) != NULL) *trunc = '\0';
old_differentiation_flag = force->kspace->differentiation_flag;
old_slabflag = force->kspace->slabflag;
old_slab_volfactor = force->kspace->slab_volfactor;
delete[] old_kspace_style;
}
void FixTuneKspace::update_pair_style(char *new_pair_style,
double pair_cut_coul)
{
int itmp;
double *p_cutoff = (double *) force->pair->extract("cut_coul",itmp);
*p_cutoff = pair_cut_coul;
if (strcmp(new_pair_style,force->pair_style) == 0) return;
FILE *p_pair_settings_file;
p_pair_settings_file = tmpfile();
force->pair->write_restart(p_pair_settings_file);
rewind(p_pair_settings_file);
if (screen) fprintf(screen,"Creating new pair style: %s\n",new_pair_style);
if (logfile) fprintf(logfile,"Creating new pair style: %s\n",new_pair_style);
force->create_pair(new_pair_style,1);
force->pair->read_restart(p_pair_settings_file);
double *pcutoff = (double *) force->pair->extract("cut_coul",itmp);
double current_cutoff = *pcutoff;
if (screen) fprintf(screen,"Coulomb cutoff for real space: %g\n", current_cutoff);
if (logfile) fprintf(logfile,"Coulomb cutoff for real space: %g\n", current_cutoff);
fclose(p_pair_settings_file);
}
void FixTuneKspace::update_kspace_style(char *new_kspace_style,
char *new_acc_str)
{
int narg = 2;
char **arg;
arg = NULL;
int maxarg = 100;
arg = (char **) memory->srealloc(arg,maxarg*sizeof(char *),"tune/kspace:arg");
int n = 12;
arg[0] = new char[n];
strcpy(arg[0],new_kspace_style);
arg[1] = new char[n];
strcpy(arg[1],new_acc_str);
force->create_kspace(arg[0],1);
force->kspace->settings(narg-1,&arg[1]);
force->kspace->differentiation_flag = old_differentiation_flag;
force->kspace->slabflag = old_slabflag;
force->kspace->slab_volfactor = old_slab_volfactor;
force->init();
force->kspace->setup_grid();
neighbor->init();
for (int i = 0; i < modify->ncompute; i++) modify->compute[i]->init();
memory->sfree(arg);
}
void FixTuneKspace::adjust_rcut(double time)
{
if (strcmp(force->kspace_style,"msm") == 0) return;
if (converged) return;
double temp;
const double TINY = 1.0e-20;
int itmp;
double *p_cutoff = (double *) force->pair->extract("cut_coul",itmp);
double current_cutoff = *p_cutoff;
if (screen) fprintf(screen,"Old Coulomb cutoff for real space: %g\n",current_cutoff);
if (logfile) fprintf(logfile,"Old Coulomb cutoff for real space: %g\n",current_cutoff);
if (keep_bracketing) {
if (niter_adjust_rcut == 0) {
pair_cut_coul /= 2;
} else if (niter_adjust_rcut == 1) {
ax_brent = current_cutoff;
fa_brent = time;
pair_cut_coul *= 2;
} else if (niter_adjust_rcut == 2) {
bx_brent = current_cutoff;
fb_brent = time;
if (fb_brent > fa_brent) {
SWAP(ax_brent,bx_brent);
SWAP(fb_brent,fa_brent);
pair_cut_coul /= 4;
} else {
pair_cut_coul *= 2;
}
} else if (niter_adjust_rcut == 3) {
cx_brent = current_cutoff;
fc_brent = time;
if (fc_brent > fb_brent) keep_bracketing = false;
else {
double r = (bx_brent - ax_brent)*(fb_brent - fc_brent);
double q = (bx_brent - cx_brent)*(fb_brent - fa_brent);
dx_brent = bx_brent - ((bx_brent - cx_brent)*q - (bx_brent - ax_brent)*r)/
(2.0*SIGN(MAX(fabs(q - r),TINY),q - r));
pair_cut_coul = dx_brent;
}
} else if (niter_adjust_rcut > 3) {
dx_brent = current_cutoff;
if (need_fd2_brent) fd2_brent = time;
else fd_brent = time;
mnbrak();
pair_cut_coul = dx_brent;
}
}
if (!keep_bracketing) {
dx_brent = current_cutoff;
fd_brent = time;
if (first_brent_pass) brent0();
else brent2();
brent1();
pair_cut_coul = dx_brent;
}
niter_adjust_rcut++;
if (pair_cut_coul <= 0.0) pair_cut_coul = fabs(MIN(ax_brent,MIN(bx_brent,(MIN(cx_brent,dx_brent))))/2.0) + TINY;
if (pair_cut_coul != pair_cut_coul)
error->all(FLERR,"Bad real space Coulomb cutoff in fix tune/kspace");
*p_cutoff = pair_cut_coul;
double *new_cutoff = (double *) force->pair->extract("cut_coul",itmp);
current_cutoff = *new_cutoff;
if (screen) fprintf(screen,"Adjusted Coulomb cutoff for real space: %g\n", current_cutoff);
if (logfile) fprintf(logfile,"Adjusted Coulomb cutoff for real space: %g\n", current_cutoff);
store_old_kspace_settings();
update_pair_style(new_pair_style,pair_cut_coul);
update_kspace_style(new_kspace_style,new_acc_str);
}
void FixTuneKspace::mnbrak()
{
const double GLIMIT = 100.0, TINY = 1.0e-20;
double r,q;
r = (bx_brent - ax_brent)*(fb_brent - fc_brent);
q = (bx_brent - cx_brent)*(fb_brent - fa_brent);
dx_brent = bx_brent - ((bx_brent - cx_brent)*q - (bx_brent - ax_brent)*r)/
(2.0*SIGN(MAX(fabs(q - r),TINY),q - r));
dxlim = bx_brent + GLIMIT*(cx_brent - bx_brent);
if ((bx_brent - dx_brent)*(dx_brent - cx_brent) > 0.0) {
if (fd_brent < fc_brent) {
ax_brent = bx_brent;
bx_brent = dx_brent;
fa_brent = fb_brent;
fb_brent = fd_brent;
keep_bracketing = false;
return;
} else if (fd_brent > fb_brent) {
cx_brent = dx_brent;
fc_brent = fd_brent;
keep_bracketing = false;
return;
}
dx_brent = cx_brent + GOLD*(cx_brent - bx_brent);
if (need_fd2_brent) {
fd_brent = fd2_brent;
need_fd2_brent = false;
} else {
need_fd2_brent = true;
return;
}
} else if ((cx_brent - dx_brent)*(dx_brent - dxlim) > 0.0) {
if (fd_brent < fc_brent) {
if (need_fd2_brent) {
need_fd2_brent = false;
} else {
need_fd2_brent = true;
dx_brent += GOLD*(dx_brent - cx_brent);
return;
}
shft3(bx_brent,cx_brent,dx_brent,dx_brent + GOLD*(dx_brent - cx_brent));
shft3(fb_brent,fc_brent,fd_brent,fd2_brent);
}
} else if ((dx_brent - dxlim)*(dxlim - cx_brent) >= 0.0) {
dx_brent = dxlim;
if (need_fd2_brent) {
fd_brent = fd2_brent;
need_fd2_brent = false;
} else {
need_fd2_brent = true;
return;
}
} else {
dx_brent = cx_brent + GOLD*(cx_brent - bx_brent);
if (need_fd2_brent) {
fd_brent = fd2_brent;
need_fd2_brent = false;
} else {
need_fd2_brent = true;
return;
}
}
shft3(ax_brent,bx_brent,cx_brent,dx_brent);
shft3(fa_brent,fb_brent,fc_brent,fd_brent);
}
void FixTuneKspace::brent0()
{
a_brent=(ax_brent < cx_brent ? ax_brent : cx_brent);
b_brent=(ax_brent > cx_brent ? ax_brent : cx_brent);
x_brent=w_brent=v_brent=bx_brent;
fw_brent=fv_brent=fx_brent=fb_brent;
}
void FixTuneKspace::brent1()
{
const double CGOLD=0.3819660;
const double ZEPS=numeric_limits<double>::epsilon()*1.0e-3;
double d=0.0,etemp;
double p,q,r,tol1,tol2,xm;
double e=0.0;
double tol=0.001;
xm=0.5*(a_brent+b_brent);
tol2=2.0*(tol1=tol*fabs(x_brent)+ZEPS);
if (fabs(x_brent-xm) <= (tol2-0.5*(b_brent-a_brent))) {
converged = true;
dx_brent = x_brent;
return;
}
if (fabs(e) > tol1) {
r=(x_brent-w_brent)*(fx_brent-fv_brent);
q=(x_brent-v_brent)*(fx_brent-fw_brent);
p=(x_brent-v_brent)*q-(x_brent-w_brent)*r;
q=2.0*(q-r);
if (q > 0.0) p = -p;
q=fabs(q);
etemp=e;
e=d;
if (fabs(p) >= fabs(0.5*q*etemp) || p <= q*(a_brent-x_brent) || p >= q*(b_brent-x_brent))
d=CGOLD*(e=(x_brent >= xm ? a_brent-x_brent : b_brent-x_brent));
else {
d=p/q;
dx_brent=x_brent+d;
if (dx_brent-a_brent < tol2 || b_brent-dx_brent < tol2)
d=SIGN(tol1,xm-x_brent);
}
} else {
d=CGOLD*(e=(x_brent >= xm ? a_brent-x_brent : b_brent-x_brent));
}
dx_brent=(fabs(d) >= tol1 ? x_brent+d : x_brent+SIGN(tol1,d));
first_brent_pass = false;
return;
}
void FixTuneKspace::brent2()
{
if (fd_brent <= fx_brent) {
if (dx_brent >= x_brent) a_brent=x_brent; else b_brent=x_brent;
shft3(v_brent,w_brent,x_brent,dx_brent);
shft3(fv_brent,fw_brent,fx_brent,fd_brent);
} else {
if (dx_brent < x_brent) a_brent=dx_brent; else b_brent=dx_brent;
if (fd_brent <= fw_brent || w_brent == x_brent) {
v_brent=w_brent;
w_brent=dx_brent;
fv_brent=fw_brent;
fw_brent=fd_brent;
} else if (fd_brent <= fv_brent || v_brent == x_brent || v_brent == w_brent) {
v_brent=dx_brent;
fv_brent=fd_brent;
}
}
}