/*---------------------------------potential.c----------------------------
  This file contains functions for calculating the potential energy of a
  molecule, based on D.W Brenner et al J Phys Cond Mat 14 (2002) 783-802.
  Bond breaking is straightforwardly handled, but the formation of new
  bonds requires rerunning a separate routine for that purpose. This
  software was written by Stephen Jordan.
  ----------------------------------------------------------------------*/

#include "cell.h"        //for cell
#include "radic.h"       //for Tij and Fij
#include "pij.h"         //for Pij
#include "g.h"           //for G
#include "constants.h"   //for the parameters of the potential
#include <math.h>        //for sqrt, cos, and exp
#include <stdio.h>       //for printf

#define PI 3.14159265359

//for testing and debugging
void disp_atom(FILE *fp, atom *a) {
  int i;
  fprintf(fp, "type = %i\n", a->type);
  fprintf(fp, "neighbs = %i\n", a->neighbs);
  fprintf(fp, "neighbs2 = %i\n", a->neighbs2);
  fprintf(fp, "x = %f\t%f\t%f\n", a->x[0], a->x[1], a->x[2]);
  fprintf(fp, "f_tb = %f\t%f\t%f\n", a->f_tb[0], a->f_tb[1], a->f_tb[2]);
  fprintf(fp, "f_x = %f\t%f\t%f\n", a->f_x[0], a->f_x[1], a->f_x[2]);
  for(i = 0; i < a->neighbs; i++)
    printf("neighb_i[%i] = %i\n", i, a->neighb_i[i]);  
}

//for testing and debugging
void disp_bond(FILE *fp, bond *b) {
  fprintf(fp, "N[0] = %f\t%f\t%f\n", b->N[0][0], b->N[0][1], b->N[0][2]);
  fprintf(fp, "N[1] = %f\t%f\t%f\n", b->N[1][0], b->N[1][1], b->N[1][2]);
  fprintf(fp, "Nconj = %f\n", b->Nconj);
  fprintf(fp, "F = %f\t%f\n", b->F[0], b->F[1]);
  fprintf(fp, "Fij = %f\n", b->Fij);
  fprintf(fp, "Tij = %f\n", b->Tij);
  fprintf(fp, "dx = %f\t%f\t%f\n", b->dx[0], b->dx[1], b->dx[2]);
  fprintf(fp, "r = %f\n", b->r);
  fprintf(fp, "fc = %f\n", b->fc);
  fprintf(fp, "theta[0] = %f\t%f\t%f\t%f\n", b->theta[0][0], b->theta[0][1],
                                             b->theta[0][2], b->theta[0][3]);
  fprintf(fp, "theta[1] = %f\t%f\t%f\t%f\n", b->theta[1][0], b->theta[1][1],
                                             b->theta[1][2], b->theta[1][3]);
  fprintf(fp, "theta[2] = %f\t%f\t%f\t%f\n", b->theta[2][0], b->theta[2][1],
                                             b->theta[2][2], b->theta[2][3]);
  fprintf(fp, "theta[3] = %f\t%f\t%f\t%f\n", b->theta[3][0], b->theta[3][1],
                                             b->theta[3][2], b->theta[3][3]);
  fprintf(fp, "bDH = %f\n", b->bDH);
  fprintf(fp, "bpi = %f\n", b->bpi);
  fprintf(fp, "W[0] = %f\t%f\t%f\t%f\n", b->W[0][0], b->W[0][1], b->W[0][2], b->W[0][3]);
  fprintf(fp, "W[1] = %f\t%f\t%f\t%f\n", b->W[1][0], b->W[1][1], b->W[1][2], b->W[1][3]);
  fprintf(fp, "costh[0] = %f\t%f\t%f\t%f\n", b->costh[0][0], b->costh[0][1],
	                                     b->costh[0][2], b->costh[0][3]);
  fprintf(fp, "costh[1] = %f\t%f\t%f\t%f\n", b->costh[1][0], b->costh[1][1],
	                                     b->costh[1][2], b->costh[1][3]);
  fprintf(fp, "S = %f\t%f\n", b->S[0], b->S[1]);
  fprintf(fp, "G[0] = %f\t%f\t%f\t%f\n", b->G[0][0], b->G[0][1], b->G[0][2], b->G[0][3]);
  fprintf(fp, "G[1] = %f\t%f\t%f\t%f\n", b->G[1][0], b->G[1][1], b->G[1][2], b->G[1][3]);
  fprintf(fp, "bsigmapi = %f\t%f\n", b->bsigmapi[0], b->bsigmapi[1]);
  fprintf(fp, "bbar = %f\n", b->bbar);
  fprintf(fp, "VR = %f\n", b->VR);
  fprintf(fp, "VA = %f\n", b->VA);
}

//for testing and debugging
void disp_cell(FILE *fp, const cell *C) {
  int i;
  fprintf(fp, "atoms = %i\n", C->atoms);
  fprintf(fp, "bonds = %i\n", C->bonds);
  for(i = 0; i < C->atoms; i++) {
    fprintf(fp, "atom %i\n", i);
    disp_atom(fp, &C->atom_array[i]);
  }
  for(i = 0; i < C->bonds; i++) {
    fprintf(fp, "bond %i\n", i);
    disp_bond(fp, &C->bond_array[i]);
  }   
}


//calculates dx and r
void geometry(bond *b) {
  int q;
  double dx, rsquared;
  rsquared = 0;
 for(q = 0; q < 3; q++) {
    b->dx[q] = b->bond_atom[1]->x[q] - b->bond_atom[0]->x[q];
    rsquared += b->dx[q]*b->dx[q]; 
  }
  b->r = sqrt(rsquared);
}


//given by equation 20 of Brenner's paper
//returns 1 if a bond has broken, 0 otherwise
int fc(bond *b) {
  double new_value;
  if(b->r < b->Dmin) new_value = 1.0;
  else {
    if(b->r > b->Dmax) new_value = 0.0;
    //this is equation 20 except in Brenner's paper he
    //forgot to multiply by PI
    else new_value = 0.5*(1.0 + cos(PI*(b->r - b->Dmin)/(b->Dmax-b->Dmin)));
  }
  if(b->fc != new_value) {
     b->fc  = new_value;
    return 1;
  }
  return 0;
}


//calculates N[][], the number of neighbors of each atom in the bond
void N(bond *b) {
  int num, type, ij;
  for(ij = 0; ij < 2; ij++) {
    b->N[ij][0] = 0;
    b->N[ij][1] = 0;
    b->N[ij][2] = 0;
    for(num = 0; num < b->neighbs[ij]; num++) {
      b->N[ij][b->neighb_a[ij][num]->type] += b->neighb_b[ij][num]->fc;
    }
    b->N[ij][0] = b->N[ij][1] + b->N[ij][2];
  }
}
         

//given by equation 16 of Brenner's paper
void F(bond *b) {
  int ij;
  //Nt is also called xik in some references
  for(ij = 0; ij < 2; ij++) {
    if(b->N[ij][0] > 3.0) b->F[ij] = 0.0;
    else {
      if(b->N[ij][0] < 2.0) b->F[ij] = 1.0;
      //this is equation 16 except in Brenner's paper he
      //mistakenly use 2*PI instead of PI
      else b->F[ij] = 0.5*(1.0 + cos(PI*(b->N[ij][0]-2.0)));
    }
  }
}


//given by equation 15 of Brenner's paper
void Nconj(bond *b) {
  double sum[2];
  int k, ij;
  for(ij = 0; ij < 2; ij++) {
    sum[ij] = 0.0;
    for(k = 0; k < b->neighbs[ij]; k++) {
      if(b->neighb_a[ij][k]->type==1) {
        if(b->neighb_b[ij][k]->bond_atom[0] == b->bond_atom[ij])
        sum[ij] += b->neighb_b[ij][k]->fc*b->neighb_b[ij][k]->F[1];
        else sum[ij] += b->neighb_b[ij][k]->fc*b->neighb_b[ij][k]->F[0];
      }
    }
  }
  b->Nconj = 1.0 + sum[0]*sum[0] + sum[1]*sum[1];
}


//the dot product for 3D vectors
double dot(const double *v1, const double *v2) {
  return v1[0]*v2[0] + v1[1]*v2[1] + v1[2]*v2[2];
}


//the cross product
void cross(const double *v1, const double *v2, double *vout) {
  vout[0] = v1[1]*v2[2] - v1[2]*v2[1];
  vout[1] = v1[2]*v2[0] - v1[0]*v2[2];
  vout[2] = v1[0]*v2[1] - v1[1]*v2[0];
}


//the magnitude of a 3D vector
double mag(const double *v) {
  return sqrt(v[0]*v[0] + v[1]*v[1] + v[2]*v[2]);
}


//capital theta sub ijkl  as used in equation 18 and defined in equation 19
void theta(bond *b) {
  int b1, b2, e;
  double ejik[3]; //see equation 19
  double eijl[3];
  double m1, m2;
  for(b1 = 0; b1 < b->neighbs[0]; b1++) {
    for(b2 = 0; b2 < b->neighbs[1]; b2++) {
      //sign doesn't matter since we only use theta squared anyway
      cross(b->neighb_b[0][b1]->dx, b->dx, ejik);
      cross(b->neighb_b[1][b2]->dx, b->dx, eijl);
      m1 = mag(ejik);
      m2 = mag(eijl);
      if(m1 < 1e-16 || m2 < 1e-16) b->theta[b1][b2] = 0.0;    
      else b->theta[b1][b2] = dot(ejik, eijl)/(m1*m2);
    }
  }
}


//b sub ij super DH as defined in equation 18
void bDH(bond *b) {
  int k, l;
  double tot;
  //if t is zero there is no need to do all these computations
  if(fabs(b->Tij) < 1.0e-8) {
    b->bDH = 0.0;
    return;
  }
  //bij super dh is nonzero only if both atoms are carbon
  if(b->bond_atom[0]->type != 1 || b->bond_atom[1]->type != 1) {
    b->bDH = 0.0;
    return;
  }
  tot = 0;
  //otherwise:
  for(k = 0; k < b->neighbs[0]; k++) {
    for(l = 0; l < b->neighbs[1]; l++) {
      if(b->neighb_b[0][k] != b->neighb_b[1][l]) {
        tot+=(1-b->theta[k][l]*b->theta[k][l])*b->neighb_b[0][k]->fc*b->neighb_b[1][l]->fc;
      }
    }
  }
  b->bDH = b->Tij*tot;
}


//b sub ij super pi as defined in equation 4 of Brenner's paper
void bpi(bond *b) {
  double piRC;
  int ki;
  int kj;
  piRC = Fij(b->bond_atom[0]->type, b->bond_atom[1]->type,
             b->N[0][0],b->N[1][0], b->Nconj);
  b->bpi = piRC + b->bDH;
}

/*W, REG, and XDB are not from Brenner's papers, but are deduced from
  his source code. They are explained in "Simulating the Inversion of
  Graphene Nanocones" by Stephen Jordan (2003). Honors Thesis, Pennsylvania
  State University.*/

void W(bond *b) {
  int k, j;
  double dist;
  for(j = 0; j < 2; j++) {
    for(k = 0; k < b->neighbs[j]; k++) {
      dist = b->r - b->neighb_b[j][k]->r; 
      b->W[j][k] = b->REG[j][k]*exp(b->XDB[j][k]*dist);
    }
  }
}

//returns the cosine of the angle between bond ij and bond ik
void costh(bond *b) {
  int e,k;
  double val;
  for(e = 0; e < 2; e++) {
    for(k = 0; k < b->neighbs[e]; k++) {
      val =  dot(b->neighb_b[e][k]->dx, b->dx)/(b->neighb_b[e][k]->r*b->r);
     //are the dx vectors tip to tail? If not reverse sign.
     if(b->neighb_b[e][k]->bond_atom[0] == b->bond_atom[0] ||
        b->neighb_b[e][k]->bond_atom[1] == b->bond_atom[1])
       b->costh[e][k] = val;
     else b->costh[e][k] = -val;
    }
  }
}


void S(bond *b) {
  int k, c;
  double sum[2];
  for(c = 0; c < 2; c++) {
    sum[c] = 0;
    for(k = 0; k < b->neighbs[c]; k++) {
      sum[c] += b->neighb_b[c][k]->fc*b->G[c][k]*b->W[c][k];
    }
    b->S[c] = sum[c];
  }
}


//this is the result of equation 8 in Brenner's new paper
void bsigmapi(bond *b) {
  b->bsigmapi[0] = 1.0/sqrt(1 + b->S[0] + b->Pij[0]);
  b->bsigmapi[1] = 1.0/sqrt(1 + b->S[1] + b->Pij[1]);
}


//This is called b bar sub ij in equation 3 of Brenner's J Cond. Mat. paper
//(It is called simply b sub ij in equation 1)
void bbar(bond *b) {
  //in the paper it says that bbar should be:
  //0.5*(bsigmapi(C, i, j) + bsigmapi(C, j, i)) + bpi(C, i, j)
  //however Brenner's own software doesn't do this
  b->bbar = b->bsigmapi[0] + b->bsigmapi[1] + b->bpi;
}


//this is V super R as given by equation 5 of Brenner's new paper
void VR(bond *b) {
  b->VR = b->fc*(1+b->Q/b->r)*b->A*exp(-b->alpha*b->r);
}  


//this is V super A as given by equation 6 of Brenner's new paper
void VA(bond *b) {
  double sum = 0.0;
  //in Brenner's software it is 1,2,3 not 0,1,2
  sum += b->B[0]*exp(-b->beta[0]*b->r);
  sum += b->B[1]*exp(-b->beta[1]*b->r);
  sum += b->B[2]*exp(-b->beta[2]*b->r);
  //the 0.5 is used in Brenner's software, but is not listed in his paper
  b->VA = 0.5*b->fc*sum;
}


//the calculations must be done in the correct order
void reconnect(bond *b_array, int length) {
  int b;
  for(b = 0; b < length; b++) N(&b_array[b]);
  for(b = 0; b < length; b++) {
    F(&b_array[b]);
    if(b_array[b].bond_atom[0]->type == 2) b_array[b].Pij[0] = 0;
    else  b_array[b].Pij[0]=Pij(b_array[b].bond_atom[1]->type,
                                b_array[b].N[0][2],
                                b_array[b].N[0][1]);
    if(b_array[b].bond_atom[1]->type == 2) b_array[b].Pij[1] = 0;
    else b_array[b].Pij[1]=Pij(b_array[b].bond_atom[0]->type,
                               b_array[b].N[1][2],
                               b_array[b].N[1][1]);
  }
  for(b = 0; b < length; b++) Nconj(&b_array[b]);
  for(b = 0; b < length; b++) { 
    b_array[b].Fij = Fij(b_array[b].bond_atom[0]->type,
                         b_array[b].bond_atom[1]->type,
                         b_array[b].N[0][0],
                         b_array[b].N[1][0],
                         b_array[b].Nconj);
    b_array[b].Tij = Tij(b_array[b].N[0][0],
                         b_array[b].N[1][0],
                         b_array[b].Nconj);
  }
}


void reconnect_local(bond **b_ptr_array, int length) {
  int b;
  for(b = 0; b < length; b++) N(b_ptr_array[b]);
  for(b = 0; b < length; b++) {
    F(b_ptr_array[b]);    
    b_ptr_array[b]->Pij[0]=Pij(b_ptr_array[b]->bond_atom[1]->type,
                               b_ptr_array[b]->N[0][2],
                               b_ptr_array[b]->N[0][1]);
    b_ptr_array[b]->Pij[1]=Pij(b_ptr_array[b]->bond_atom[0]->type,
                               b_ptr_array[b]->N[1][2],
                               b_ptr_array[b]->N[1][1]);
  }
  for(b = 0; b < length; b++) Nconj(b_ptr_array[b]);
  for(b = 0; b < length; b++) { 
    b_ptr_array[b]->Fij = Fij(b_ptr_array[b]->bond_atom[0]->type,
                              b_ptr_array[b]->bond_atom[1]->type,
                              b_ptr_array[b]->N[0][0],
                              b_ptr_array[b]->N[1][0],
                              b_ptr_array[b]->Nconj);
    b_ptr_array[b]->Tij = Tij(b_ptr_array[b]->N[0][0],
                              b_ptr_array[b]->N[1][0],
                              b_ptr_array[b]->Nconj);
  }
}


void prep_G(bond *b) {
  int e, n;
  for(e = 0; e < 2; e++) {
    for(n = 0; n < b->neighbs[e]; n++) {
      b->G[e][n] = G(b->bond_atom[e]->type, b->costh[e][n], b->N[e][0]);
    }
  }
}


void prepare(bond *b_array, int length) {
  int b;
  int changed = 0;
  for(b = 0; b < length; b++) geometry(&b_array[b]);  
  for(b = 0; b < length; b++) changed += fc(&b_array[b]);  
  if(changed > 0) reconnect(b_array, length);
  for(b = 0; b < length; b++) {
    VA(&b_array[b]);
    VR(&b_array[b]);
    theta(&b_array[b]);
    W(&b_array[b]);
    costh(&b_array[b]);
  }
  for(b = 0; b < length; b++) {
    bDH(&b_array[b]);
    prep_G(&b_array[b]);
  }
  for(b = 0; b < length; b++) {
    bpi(&b_array[b]);
    S(&b_array[b]);
  }
  for(b = 0; b < length; b++) bsigmapi(&b_array[b]);
  for(b = 0; b < length; b++) bbar(&b_array[b]);  
  for(b = 0; b < length; b++)
    b_array[b].E = b_array[b].VR - b_array[b].bbar*b_array[b].VA;
}


void prepare_local(atom *a) {
  int b;
  int changed = 0;
  bond *bond_b;
  for(b = 0; b < a->neighbs; b++) geometry(a->neighb_b[b]);
  for(b = 0; b < a->neighbs; b++) changed += fc(a->neighb_b[b]);
  if(changed > 0) reconnect_local(a->neighb_b2, a->neighbs2);
  for(b = 0; b < a->neighbs; b++) {
    VA(a->neighb_b[b]);
    VR(a->neighb_b[b]);
    W(a->neighb_b[b]);
  }
  for(b = 0; b < a->neighbs2; b++) {
    costh(a->neighb_b2[b]);
    theta(a->neighb_b2[b]);
  }
  for(b = 0; b < a->neighbs2; b++) {
    bDH(a->neighb_b2[b]);
    prep_G(a->neighb_b2[b]);
  }
  for(b = 0; b < a->neighbs2; b++) {
    bpi(a->neighb_b2[b]);
    S(a->neighb_b2[b]);
  }
  for(b = 0; b < a->neighbs2; b++) bsigmapi(a->neighb_b2[b]);
  for(b = 0; b < a->neighbs2; b++) bbar(a->neighb_b2[b]);
  for(b = 0; b < a->neighbs2; b++) {
    bond_b = a->neighb_b2[b]; 
    bond_b->E = bond_b->VR - bond_b->bbar*bond_b->VA;
  }
}


//this gives the total chemical energy as calculated by this
//"REBO" Tersoff-Brenner potential (as defined in equation 1
//of Brenners new (J. Phys. Cond. Mat.) paper
//to calculate the energy of the entire cell, just feed it
//bond_array. To calculate the energy of a subset of the bonds
//(such as when calculating forces) feed it the appropriate subset
double energy(bond *b_array, int length) {
  double sum;
  int bond;
  sum = 0.0;
  prepare(b_array, length);
  for(bond = 0; bond < length; bond++) sum += b_array[bond].E;
  return sum;
}  


double local_energy(atom *a) {
  int b;
  double sum = 0.0;
  prepare_local(a);
  for(b = 0; b < a->neighbs2; b++) sum += a->neighb_b2[b]->E;
  return sum;
}

void neighb_cpy(const atom *a, atom *b) {
  int i;
  b->neighbs = a->neighbs;
  b->neighbs2 = a->neighbs2;
  for(i = 0; i < a->neighbs; i++) *b->neighb_b[i] = *a->neighb_b[i];
  for(i = 0; i < a->neighbs2; i++) *b->neighb_b2[i] = *a->neighb_b2[i];
  b->x[0] = a->x[0];
  b->x[1] = a->x[1];
  b->x[2] = a->x[2];
}

//this function calculates f_tb by numerically differentiating the energy
//with respect to the positions of the atoms
void nftb(cell *C) {
  int q, i;
  double e1, e2;
  double step = 1e-6; //this value was optimized through trial and error
  atom a;
  double ax[3];
  bond n[5];
  bond n2[25];
  a.x = ax;
  for(i = 0; i < 5; i++) a.neighb_b[i] = &n[i];
  for(i = 0; i < 25; i++) a.neighb_b2[i] = &n2[i];
  prepare(C->bond_array, C->bonds);
  for(i = 0; i < C->atoms; i++) {
    neighb_cpy(&C->atom_array[i], &a);
    for(q = 0; q < 3; q++) {
      C->atom_array[i].x[q] += step;
      e1 = local_energy(&C->atom_array[i]);
      C->atom_array[i].x[q] -= 2.0*step;
      e2 = local_energy(&C->atom_array[i]);
      C->atom_array[i].f_tb[q] = 0.5*(e2 - e1)/step;
      //C->atom_array[i].x[q] += step;
      //local_energy(&C->atom_array[i]);
      neighb_cpy(&a, &C->atom_array[i]);
    }
  }
}
