/*---------------------------force.c---------------------------
  LJball and LJplane output the exact solutions for the force 
  generated by a Lenard Jones spherical shell, and infinite plane,
  respectively.  The parameters are calibrated as if the shell
  and plane are made of graphene and the force is being exerted
  on carbon. This software was written by Stephen Jordan.
  -------------------------------------------------------------*/


#include "force.h"
#include "cell.h"
#include "defs.h"
#include "diags.h"
#include <stdio.h>
#include <math.h>
#include <string.h>

#define  sqrt3          1.7320508075688772
#define  FOURPI         12.256637061435917
#define  TWOPI          6.2831853071795865     

void warn(const cell *C, double *f, int ftype, int i, const defs *d) {
  double mag = 0.0;
  char dir;
  char function[32];
  char element[32];
  char msg[512];
  if(fabs(f[0]) >= mag) {
    mag = fabs(f[0]);
    dir = 'x';
  }
  if(fabs(f[1]) >= mag) {
    mag = fabs(f[1]);
    dir = 'y';
  }
  if(fabs(f[2]) >= mag) {
    mag = fabs(f[2]);
    dir = 'z';
  }
  if(C->atom_array[i].type == 1) {
   if(mag < 120) return; //everything's ok
   strcpy(element, "Carbon");
  }
  if(C->atom_array[i].type == 2) {
    if(mag < 10) return; //also ok, force/12 since mass/12 (roughly) 
    strcpy(element, "Hydrogen");
  }
  if(C->atom_array[i].type != 1 && C->atom_array[i].type != 2) {
    msglog("Invalid atom type in warn!\n", d);
    return;
  }
  //otherwise force is too big:
  if(ftype == 0) strcpy(function, "LJBall");
  if(ftype == 1) strcpy(function, "LJPlane");
  if(ftype == 2) strcpy(function, "LJHole");
  if(ftype == 4) strcpy(function, "Squeeze");
  if(ftype < 0 || ftype > 4 || ftype == 3) {
    sprintf(msg, "Invalid force type (%d) in warn!\n", ftype);
    msglog(msg, d);
  }
  else {
    sprintf(msg, "Warning, large forces (%c = %e) in %s on %s %d!\n", dir, mag, function, element, i);
    msglog(msg, d);
  }
}


//force due to a Lenard-Jones spherical shell
void LJball(const cell *C,int i,double R,double x,double y,double z, double *f, const defs *d){
  double D, RN, RF, DRN, DRF, A, B, DX, DY, DZ, FB;
  char msg[256];
  DX = C->atom_array[i].x[0] - x;
  DY = C->atom_array[i].x[1] - y;
  DZ = C->atom_array[i].x[2] - z;
  D = sqrt(DX*DX+DY*DY+DZ*DZ);
  if(D < R) {  //it is inside!
    sprintf(msg, "Atom %d is inside LJBall!\n", i);
    msglog(msg, d);
    f[0] = 0;
    f[1] = 0;
    f[2] = 0;
    return;
  }
  RN = D*D - 2*R*D + R*R;
  RF = D*D + 2*R*D + R*R;
  DRN = 2*D - 2*R;
  DRF = 2*D + 2*R;
  A = (1708*R)/D;
  B = (3.443*R)/D;
  FB = -(B/D)*(1/(RN*RN)-1/(RF*RF));
  FB += B*(-2*DRN*pow(RN, -3.0) + 2*DRF*pow(RF, -3.0));
  FB += (A/D)*(pow(RN,-5.0) - pow(RF, -5.0));
  FB -= A*(-5*DRN*pow(RN,-6.0)+5*DRF*pow(RF,-6.0));
  f[0] = FB*(DX/D);
  f[1] = FB*(DY/D);
  f[2] = FB*(DZ/D);
  warn(C, f, 0, i, d);
}


//the potential energy due to a Lenard-Jones spherical shell
double e_LJball(const cell *C,int i,double R,double x,double y, double z) {
  double D, RN, RF, A, B, DX, DY, DZ, E;
  DX = C->atom_array[i].x[0] - x;
  DY = C->atom_array[i].x[1] - y;
  DZ = C->atom_array[i].x[2] - z;
  D = sqrt(DX*DX+DY*DY+DZ*DZ);
  RN = D*D - 2*R*D + R*R;
  RF = D*D + 2*R*D + R*R;  
  A = (1708*R)/D;
  B = (3.443*R)/D;
  E = -B*(1/(RN*RN)-1/(RF*RF)) + A*(pow(RN, -5.0)-pow(RF, -5.0));
  return E;
}

//the force due to a Lenard-Jones infinite plane
void LJplane(const cell *C, int i, double z, double *f, const defs *d) {
  double F;
  double dz;
  double sign;
  dz = fabs(C->atom_array[i].x[2]-z);
  sign = (C->atom_array[i].x[2]-z)/dz;
  F = 17078*pow(dz, -11.0)-13.773*pow(dz, -5.0);
  f[0] = 0.0;
  f[1] = 0.0;
  f[2] = sign*F;
  warn(C, f, 1, i, d);
}


//the potential energy due to a Lenard-Jones infinite plane
double e_LJplane(const cell *C, int i, double z) {
  double dz;
  double E;
  dz = fabs(C->atom_array[i].x[2]-z);
  E=1707.8*pow(dz, -10)-3.4433*pow(dz,-5);
  return E;
}


//(fictitious) potential due to a plane with a hole
double e_LJhole(const cell *C, int i, double z, double r) {
  /*we want the force to be zero in the middle of the hole. We do this by
    making the potential go to zero in the middle, but we must do this
    smoothly so it is differentiable.*/
  double r_atom, energy, dz;
  r_atom = sqrt(C->atom_array[i].x[0]*C->atom_array[i].x[0]
              + C->atom_array[i].x[1]*C->atom_array[i].x[1]);
  if(r_atom < 0.75*r) return 0;                          //the center 3/4 of the hole
  dz = fabs(C->atom_array[i].x[2]-z);
  energy = 1707.8*pow(dz, -10.0)-3.4433*pow(dz, -4.0);
  if(r_atom > r) return energy;                          //outside the hole
  return energy*0.5*(1-cos((FOURPI/r)*(r_atom-0.75*r))); //the outer 1/4 of the hole 
}


//(fictitious) force due to a plane with a hole
void LJhole(const cell *C, int i, double z, double r, double *f, const defs *d) {
  double r_atom, dz, e0, f0, urx, ury, mag, fac, dfac, sign;
  r_atom = sqrt(C->atom_array[i].x[0]*C->atom_array[i].x[0]
              + C->atom_array[i].x[1]*C->atom_array[i].x[1]);
  if(r_atom < 0.75*r) { //the center 3/4 of the hole:
    f[0]=0;
    f[1]=0;
    f[2]=0;
  }
  else {
    dz = fabs(C->atom_array[i].x[2]-z);
    sign = (C->atom_array[i].x[2]-z)/dz;
    f0 = sign*17078*pow(dz, -11.0)-13.773*pow(dz, -5.0);
    if(r_atom > r) { //outside the hole:
      f[0]=0;
      f[1]=0;
      f[2]=f0;
    }
    else {
      //the outer 1/4 of the hole:
      //urx and ury are the components of a unit vector pointing toward the
      //projection of the current atom's position onto the xy-plane.
      e0 = 1707.8*pow(dz, -10.0)-3.4433*pow(dz, -4.0);
      urx=C->atom_array[i].x[0];
      ury=C->atom_array[i].x[1];
      mag=sqrt(urx*urx+ury*ury);
      urx /= mag;
      ury /= mag;
      //dfac is minus one times the derivative of fac
      fac = 0.5*(1-cos((FOURPI/r)*(r_atom-0.75*r)));
      dfac = -(TWOPI/r)*sin((FOURPI/r)*(r_atom-0.75*r));
      f[0]=dfac*urx*e0;
      f[1]=dfac*ury*e0;
      f[2]=f0*fac;
    }
  }
  warn(C,f,2,i, d);
}

  
//the potential energy due to squeeze
double e_squeeze(const cell *C, int i, double strength) {
  double r_atom;
  r_atom = sqrt(C->atom_array[i].x[0]*C->atom_array[i].x[0]
              + C->atom_array[i].x[1]*C->atom_array[i].x[1]);
  return 0.5*strength*r_atom*r_atom;
}


//something to hold the cone at the origin:
void squeeze (const cell *C, int i, double strength, double *f, const defs *d) {
  double r_atom;
  double fmag;
  r_atom = sqrt(C->atom_array[i].x[0]*C->atom_array[i].x[0]
              + C->atom_array[i].x[1]*C->atom_array[i].x[1]);
  fmag=-strength*r_atom;
  f[0] = (C->atom_array[i].x[0]/r_atom)*fmag;
  f[1] = (C->atom_array[i].x[1]/r_atom)*fmag;
  f[2] = 0.0;
  warn(C, f, 4, i, d);
}


double maxabs(double *f) {
  double max = 0.0;
  if(max < fabs(f[0])) max = fabs(f[0]);
  if(max < fabs(f[1])) max = fabs(f[1]);
  if(max < fabs(f[2])) max = fabs(f[2]);
  return max;
}


//f becomes the force on atom i due to forcer f_count
void force(const cell *C, const defs *d, diags *dia, int i, int f_count, double *f, int stage) {
  double f_obj[3];
  double max;
  int q;
  f[0] = 0.0;
  f[1] = 0.0;
  f[2] = 0.0;
  if((C->atom_array[i].type & d->f_list[f_count].mask) != 0) { //bitwise
    //apply the various forces:
    if(d->f_list[f_count].type == 0) { //ball
      LJball(C, i, d->f_list[f_count].radius + d->f_list[f_count].radius_step*stage,
      0, 0, d->f_list[f_count].height + d->f_list[f_count].height_step*stage, f_obj, d);
      for(q = 0; q < 3; q++) f[q] += f_obj[q];
    }
    if(d->f_list[f_count].type == 1) { //plane
      LJplane(C, i, 
      d->f_list[f_count].height + d->f_list[f_count].height_step*stage, f_obj, d);
      for(q = 0; q < 3; q++) f[q] += f_obj[q];
    }
    if(d->f_list[f_count].type == 2) { //hole
      LJhole(C, i, d->f_list[f_count].height + d->f_list[f_count].height_step*stage,
      d->f_list[f_count].radius + d->f_list[f_count].radius_step*stage, f_obj, d);
      for(q = 0; q < 3; q++) f[q] += f_obj[q];
    }
    if(d->f_list[f_count].type == 3 && d->f_list[f_count].atom == i) //push
      for(q = 0; q < 3; q++) f[q] += d->f_list[f_count].dir[q]
                                 + d->f_list[f_count].dir_step[q]*stage;
    if(d->f_list[f_count].type == 4) { //squeeze
      squeeze(C, i, d->f_list[f_count].strength, f_obj, d);      
      for(q = 0; q < 3; q++) { 
        f[q] += f_obj[q];
      }
    }
    //do record keeping:
    if(d->f_list[f_count].type != 3) { //no need to keep track of s_max_f for push
      max = maxabs(f_obj);
      if(dia->s_max_f[f_count] < max) dia->s_max_f[f_count] = max;
    }
  }  
}


double f_energy(const cell *C, const defs *d, diags *dia, int i, int f_count, int stage) {
  //tally the various energies:
  //ball:
  if((C->atom_array[i].type & d->f_list[f_count].mask) != 0) { //bitwise
    if(d->f_list[f_count].type == 0)
      return 
      e_LJball(C, i, d->f_list[f_count].radius + d->f_list[f_count].radius_step*stage,
      0, 0, d->f_list[f_count].height + d->f_list[f_count].height_step*stage);
    //plane:
    if(d->f_list[f_count].type == 1)
      return
      e_LJplane(C, i, d->f_list[f_count].height + d->f_list[f_count].height_step*stage);
    //hole:
    if(d->f_list[f_count].type == 2)
      return
      e_LJhole(C, i, d->f_list[f_count].height + d->f_list[f_count].height_step*stage,
      d->f_list[f_count].radius + d->f_list[f_count].radius_step*stage);
    //push:
    if(d->f_list[f_count].type == 3 && d->f_list[f_count].atom == i)
      return d->f_list[f_count].dir[0]*C->atom_array[i].x[0] +
             d->f_list[f_count].dir[1]*C->atom_array[i].x[1] +
             d->f_list[f_count].dir[2]*C->atom_array[i].x[2];
    //squeeze
    if(d->f_list[f_count].type == 4)
      return e_squeeze(C, i, d->f_list[f_count].strength);  
  }
  return 0;    
}
   

//external forces, not due to other cone atoms
void f_ext(const cell *C, const defs *d, diags *dia, int i, double *f, int stage) {
  int f_count;
  double f_obj[3];
  f[0] = 0.0;
  f[1] = 0.0;
  f[2] = 0.0;
  for(f_count = 0; f_count < d->f_num; f_count++) {
    force(C, d, dia, i, f_count, f_obj, stage);
    f[0] += f_obj[0];
    f[1] += f_obj[1];
    f[2] += f_obj[2];
  }
}


void tipforce(const cell *C, const defs *d, diags *dia, int stage) {
  int i;
  double forcei[3];
  dia->tip_force[0] = 0.0;
  dia->tip_force[1] = 0.0;
  dia->tip_force[2] = 0.0;
  for(i = 0; i < C->atoms; i++) {
    force(C, d, dia, i, d->tip, forcei, stage);
    dia->tip_force[0] += forcei[0];
    dia->tip_force[1] += forcei[1];
    dia->tip_force[2] += forcei[2];
  } 
}


void energy_tally(const cell *C, const defs *d, diags *dia, int stage) {
  int f_count;
  int max;
  int i;
  max = 63;
  if(d->f_num > 63) msglog("Error: more than 64 forcers!\n", d);
  else max = d->f_num;
  for(f_count = 0; f_count < max; f_count++) {
    dia->obj_energy[f_count] = 0.0;
    for(i = 0; i < C->atoms; i++) {
      dia->obj_energy[f_count] += f_energy(C, d, dia, i, f_count, stage);
    }
  }
}
