#include "Stress.h"
#include "CauchyBorn.h"
#include "CBLattice.h"
#include "CbLjCut.h" 
#include "CbLjSmoothLinear.h" 
#include "CbEam.h" 
#include "ATC_Error.h"
#include "LammpsInterface.h"
#include "VoigtOperations.h"
#include <iostream>

using ATC_Utility::command_line;
using ATC_Utility::str2dbl;
using voigt3::voigt_idx1;
using voigt3::voigt_idx2;
using voigt3::to_voigt_unsymmetric;
using voigt3::from_voigt_unsymmetric;
using voigt3::to_voigt;
using voigt3::from_voigt;
using std::stringstream;
using std::vector;
using std::string;
using std::fstream;

namespace ATC {
//=============================================================================
// extracts a stress at an integration point 
// Note: Utility function: not in header
//=============================================================================
DENS_MAT extract_stress(const DENS_MAT_VEC &sigma, INDEX ip=0)
{
  DENS_MAT s(3,3,false);
  for (int j=0; j<3; j++) for (int i=0; i<3; i++) s(i,j) = sigma[i](ip,j);
  return s;
}
//=============================================================================
// computes the pressure from the stress at the first quadrature point (in atm)
// Note: Utility function: not in header
//=============================================================================
double compute_pressure(const DENS_MAT_VEC &sigma, const DENS_MAT &F)
{
  // pressure in units (mass-velocity^2)/Volume (LAMMPS real)
  double p = (sigma[0](0,0) + sigma[1](0,1) + sigma[2](0,2)) * (1.0/3.0);
  p *= 1.0e14/6.0221415;            // convert from units real to Pa 
  p *= 1.0/101235.0;                // convert from Pa to ATM
  return p * pow(det(F), -1.0/3.0); // convert from PK2 to Cauchy stress
}
//=============================================================================
// extracts the deformation gradient at a quadrature point, q
// Note: Utility function: not in header
//=============================================================================
void deformation_gradient(const DENS_MAT_VEC &du, INDEX q, MATRIX &F)
{
  F.reset(du.size(), du.size(), false);
  for (INDEX j=0; j<F.nCols(); j++) {
    for (INDEX i=0; i<F.nRows(); i++) F(i,j) = du[j](q,i);
    F(j,j) += 1.0;
  } 
}

//=============================================================================

// E = 1/2 stress*strain for linear elastic models
//=============================================================================
void Stress::elastic_energy(const FIELD_MATS &fields,
                            const GRAD_FIELD_MATS &gradFields,
                            DENS_MAT &energy) const
{
 int nRows = ( ((gradFields.find(DISPLACEMENT))->second)[0]).nRows();
 energy.reset(nRows,1);
 ATC::LammpsInterface::instance()->print_msg("WARNING: returning dummy elastic energy");
 
}

//=============================================================================
// isotropic linear elastic
//=============================================================================
StressLinearElastic::StressLinearElastic(fstream &fileId) 
  : StressCubicElastic(), E_(0), nu_(0), mu_(0), lambda_(0)
{
  if (!fileId.is_open()) throw ATC_Error("cannot open material file");
  vector<string> line;
  while(fileId.good()) {
    command_line(fileId, line);
    if (line[0] == "end") {
      mu_ = E_/(2.0+2.0*nu_);
      lambda_ = mu_*nu_ / (0.5 - nu_);
      StressCubicElastic::c11_ = E_*(1-nu_)/(1+nu_)/(1-2*nu_);
      StressCubicElastic::c12_ = E_*nu_    /(1+nu_)/(1-2*nu_);
      StressCubicElastic::c44_ = E_/(1+nu_)/2;
      if (nu_ < 0.0 || nu_ > 1.0) 
        throw ATC_Error("bad linear elastic constants");   
      if (lambda_ < 0.0 || mu_ < 0.0)
        throw ATC_Error("bad continuum material parameter");
      return;
    }
    else if (line[0]=="modulus")        E_ = str2dbl(line[1]);
    else if (line[0]=="possions_ratio") nu_ = str2dbl(line[1]);
    else throw ATC_Error( "unrecognized material function");
  }
}
//=============================================================================
// compute the stress at N integration points from the displacement gradient
// T_{ij} = 1/2*C_{ijkl}* (u_{k,l} + u_{l,k})
//=============================================================================
void StressLinearElastic::stress(const FIELD_MATS      &fields,
                                 const GRAD_FIELD_MATS &gradFields,
                                 DENS_MAT_VEC &sigma)
{
  GRAD_FIELD_MATS::const_iterator du_itr = gradFields.find(DISPLACEMENT);
  const DENS_MAT_VEC &du = du_itr->second;

  CLON_VEC uxx(du[0],CLONE_COL,0);
  CLON_VEC uxy(du[1],CLONE_COL,0);
  CLON_VEC uxz(du[2],CLONE_COL,0);
  CLON_VEC uyx(du[0],CLONE_COL,1);
  CLON_VEC uyy(du[1],CLONE_COL,1);
  CLON_VEC uyz(du[2],CLONE_COL,1);
  CLON_VEC uzx(du[0],CLONE_COL,2);
  CLON_VEC uzy(du[1],CLONE_COL,2);
  CLON_VEC uzz(du[2],CLONE_COL,2);

  const INDEX N = uxx.size();          // # of integration pts
  sigma.assign(3, DENS_MAT(N,3));

  // precompute the pressure and copy to the diagonal
  column(sigma[0],0) = (uxx + uyy + uzz)*(-lambda_);
  column(sigma[1],1) = column(sigma[0],0);
  column(sigma[2],2) = column(sigma[0],0);

  column(sigma[0],0) -= 2.0*mu_*uxx;
  column(sigma[0],1) = (uxy + uyx)*(-mu_);
  column(sigma[0],2) = (uxz + uzx)*(-mu_);
  column(sigma[1],0) = column(sigma[0],1);
  column(sigma[1],1) -= 2.0*mu_*uyy;
  column(sigma[1],2) = (uyz + uzy)*(-mu_);
  column(sigma[2],0) = column(sigma[0],2);
  column(sigma[2],1) = column(sigma[1],2);
  column(sigma[2],2) -= 2.0*mu_*uzz;
}
//=============================================================================
// cubic elastic
//=============================================================================
StressCubicElastic::StressCubicElastic(fstream &fileId) 
  : c11_(0), c12_(0), c44_(0)
{
  if (!fileId.is_open()) throw ATC_Error("cannot open material file");
  vector<string> line;
  while(fileId.good()) {
    command_line(fileId, line);
    if      (line[0]=="end") return;
    else if (line[0]=="c11") c11_ = str2dbl(line[1]);
    else if (line[0]=="c12") c12_ = str2dbl(line[1]);
    else if (line[0]=="c44") c44_ = str2dbl(line[1]);
    else throw ATC_Error( "unrecognized material function"); 
  }
}
//---------------------------------------------------------------------------
// compute the stress at N integration points from the displacement gradient
// T_{ij} = 1/2*C_{ijkl}*(u_{k,l} + u_{l,k}) 
//---------------------------------------------------------------------------
void StressCubicElastic::stress(const FIELD_MATS      &fields,
                                const GRAD_FIELD_MATS &gradFields,
                                DENS_MAT_VEC  &sigma)  
{
  GRAD_FIELD_MATS::const_iterator du_itr = gradFields.find(DISPLACEMENT);
  const DENS_MAT_VEC &du = du_itr->second;
  CLON_VEC uxx(du[0],CLONE_COL,0);
  CLON_VEC uxy(du[1],CLONE_COL,0);
  CLON_VEC uxz(du[2],CLONE_COL,0);
  CLON_VEC uyx(du[0],CLONE_COL,1);
  CLON_VEC uyy(du[1],CLONE_COL,1);
  CLON_VEC uyz(du[2],CLONE_COL,1);
  CLON_VEC uzx(du[0],CLONE_COL,2);
  CLON_VEC uzy(du[1],CLONE_COL,2);
  CLON_VEC uzz(du[2],CLONE_COL,2);

  const INDEX N = uxx.size();          // # of integration pts
  sigma.assign(3, DENS_MAT(N,3));

  const double c12 = c12_;
  const double c11 = c11_;
  const double c44 = c44_;

  // scaling: stress must return (-) stress
  column(sigma[0],0) = -c11*uxx - c12*(uyy+uzz);
  column(sigma[1],1) = -c11*uyy - c12*(uxx+uzz);
  column(sigma[2],2) = -c11*uzz - c12*(uxx+uyy);
  column(sigma[0],1) = -c44*(uxy+uyx);
  column(sigma[1],0) = column(sigma[0],1);
  column(sigma[0],2) = -c44*(uxz+uzx);
  column(sigma[2],0) = column(sigma[0],2);
  column(sigma[1],2) = -c44*(uyz+uzy);
  column(sigma[2],1) = column(sigma[1],2);

}
//---------------------------------------------------------------------------
// compute the elastic energy at N integration points from displacement gradient
// E = 1/8*C_{ijkl}* (u_{k,l} + u_{l,k})* (u_{i,j} + u_{j,i})*rho ?
//   = 1/2 (4 c44 (u12^2 + u13^2 + u23^2) + 2 c12 (u11 u22 + u11 u33 + u22 u33) 
//        + c11 (u11^2 + u22^2 + u33^2))
//---------------------------------------------------------------------------
void StressCubicElastic::elastic_energy(const FIELD_MATS      &fields,
                                        const GRAD_FIELD_MATS &gradFields,
                                        DENS_MAT  &energy) const
{
  GRAD_FIELD_MATS::const_iterator du_itr = gradFields.find(DISPLACEMENT);
  const DENS_MAT_VEC &du = du_itr->second;
  CLON_VEC uxx(du[0],CLONE_COL,0);
  CLON_VEC uxy(du[1],CLONE_COL,0);
  CLON_VEC uxz(du[2],CLONE_COL,0);
  CLON_VEC uyx(du[0],CLONE_COL,1);
  CLON_VEC uyy(du[1],CLONE_COL,1);
  CLON_VEC uyz(du[2],CLONE_COL,1);
  CLON_VEC uzx(du[0],CLONE_COL,2);
  CLON_VEC uzy(du[1],CLONE_COL,2);
  CLON_VEC uzz(du[2],CLONE_COL,2);

  CLON_VEC E(energy,CLONE_COL,0);

  const double c12 = c12_;
  const double c11 = c11_;
  const double c44 = c44_;
  //double scale = (ATC::LammpsInterface::instance()->mvv2e());
  for (INDEX gp=0; gp<du.front().nRows(); gp++) {
    double u11 = uxx(gp); 
    double u22 = uyy(gp); 
    double u33 = uzz(gp); 
    double u12 = 0.5*(uxy(gp)+uyx(gp)); 
    double u13 = 0.5*(uxz(gp)+uzx(gp));
    double u23 = 0.5*(uyz(gp)+uzy(gp));
    double EE  = 0.5* (4.0*c44*(u12*u12 + u13*u13 + u23*u23) 
                     + 2.0*c12*(u11*u22 + u11*u33 + u22*u33) 
                         + c11*(u11*u11 + u22*u22 + u33*u33));
    
    E(gp) = EE;
  }
}

void StressCubicElastic::set_tangent(void) 
{
  C_.reset(6,6);
  C_(0,0)=C_(1,1)=C_(2,2)                        =c11_;
  C_(0,1)=C_(1,0)=C_(1,2)=C_(2,1)=C_(0,2)=C_(2,0)=c12_;
  C_(3,3)=C_(4,4)=C_(5,5)                        =c44_;
}

//=============================================================================
// damped cubic elastic
//=============================================================================
StressCubicElasticDamped::StressCubicElasticDamped(fstream &fileId) 
  : StressCubicElastic(), gamma_(0)
{
  if (!fileId.is_open()) throw ATC_Error("cannot open material file");
  vector<string> line;
  while(fileId.good()) {
    command_line(fileId, line);
    if      (line[0]=="end") return;
    else if (line[0]=="c11") StressCubicElastic::c11_ = str2dbl(line[1]);
    else if (line[0]=="c12") StressCubicElastic::c12_ = str2dbl(line[1]);
    else if (line[0]=="c44") StressCubicElastic::c44_ = str2dbl(line[1]);
    else if (line[0]=="gamma") gamma_ = str2dbl(line[1]);
    else throw ATC_Error( "unrecognized material function");
  }
}
//---------------------------------------------------------------------------
// compute the stress at N integration points 
//---------------------------------------------------------------------------

void StressCubicElasticDamped::stress(const FIELD_MATS      &fields,
                                const GRAD_FIELD_MATS &gradFields,
                                DENS_MAT_VEC  &sigma)  
{
  StressCubicElastic::stress(fields,gradFields,sigma);
  GRAD_FIELD_MATS::const_iterator dv_itr = gradFields.find(VELOCITY);
  const DENS_MAT_VEC &dv = dv_itr->second;
  CLON_VEC vxx(dv[0],CLONE_COL,0);
  CLON_VEC vxy(dv[1],CLONE_COL,0);
  CLON_VEC vxz(dv[2],CLONE_COL,0);
  CLON_VEC vyx(dv[0],CLONE_COL,1);
  CLON_VEC vyy(dv[1],CLONE_COL,1);
  CLON_VEC vyz(dv[2],CLONE_COL,1);
  CLON_VEC vzx(dv[0],CLONE_COL,2);
  CLON_VEC vzy(dv[1],CLONE_COL,2);
  CLON_VEC vzz(dv[2],CLONE_COL,2);

  // scaling: stress must return (-) stress
  column(sigma[0],0) += -gamma_*vxx;
  column(sigma[1],1) += -gamma_*vyy;
  column(sigma[2],2) += -gamma_*vzz;
  column(sigma[0],1) += -0.5*gamma_*(vxy+vyx);
  column(sigma[1],0) += column(sigma[0],1);
  column(sigma[0],2) += -0.5*gamma_*(vxz+vzx);
  column(sigma[2],0) += column(sigma[0],2);
  column(sigma[1],2) += -0.5*gamma_*(vyz+vzy);
  column(sigma[2],1) += column(sigma[1],2);
}
//==============================================================================
// cauchy born model
//==============================================================================
StressCauchyBorn::StressCauchyBorn(fstream &fileId, CbData &cb)
  :  cblattice_(NULL),
     potential_(NULL),
     makeLinear_(false),
     cubicMat_(NULL),
     initialized_(false),
     fixed_temperature_(0.),
     cbdata_(cb)
{
  if (!fileId.is_open()) throw ATC_Error("cannot open material file"); 
  while(fileId.good()) {
    // reads a line from the material file
    vector<string> line;
    command_line(fileId, line);
    if (line.empty()) continue;   // skip blank lines
    else if (line[0]=="end") {
      delete cblattice_;
      if (!potential_) throw ATC_Error( "no potential defined"); 
      cblattice_ = new CBLattice(cbdata_.cell_vectors, cbdata_.basis_vectors); 
      return;
    }
    else if (line[0] == "pair_style") {
      if (line[1] == "lj/cut") {      // Lennard-Jones w/ cutoff radius
        if (line.size()<3) throw(ATC_Error("no lj/cut cutoff radius"));
        const double rc = str2dbl(line[2]);
        while (!fileId.eof()) { // find next pair_coeff command
          command_line(fileId, line); 
          if (line.size() && line[0]=="pair_coeff") break;
        }
        if (line[0] != "pair_coeff" || line.size() != 3) {
          throw(ATC_Error("lj/cut needs 2 coefficents"));
        }
        delete potential_;
        potential_ = new CbLjCut(str2dbl(line[1]), str2dbl(line[2]), rc);
      }
      else if (line[1] == "lj/smooth/linear") { // Lennard-Jones w/ cutoff radius and smoothed
        if (line.size()<3) throw(ATC_Error("no lj/smooth/linear cutoff radius"));
        const double rc = str2dbl(line[2]);
        while (!fileId.eof()) { // find next pair_coeff command
          command_line(fileId, line);
          if (line.size() && line[0]=="pair_coeff") break;
        }
        if (line[0] != "pair_coeff" || line.size() != 3) {
          throw(ATC_Error("lj/smooth/linear needs 2 coefficents"));
        }
        delete potential_;
        potential_ = new CbLjSmoothLinear(str2dbl(line[1]), str2dbl(line[2]), rc);
      }
      else if (line[1] == "eam") {      // Embedded atom method potential 
        delete potential_;
        potential_ = new CbEam();
      }
      else throw (ATC_Error("Invalid pair style"));
    }
    else if (line[0] == "linear") makeLinear_ = true;
    else if (line[0] == "temperature" && line.size() == 2) {
      fixed_temperature_ = str2dbl(line[1]);
    }
    else if (line[0]=="material" || line[0]=="stress") /* ignore this */; 
    else throw ATC_Error( "Unrecognized Cauchy-Born parameter: "+line[0]+".");
  }

}
//==============================================================================
//* default destructor - delete potential and lattice
//==============================================================================
StressCauchyBorn::~StressCauchyBorn()
{
  if (potential_) delete potential_;
  if (cblattice_) delete cblattice_;
  if (cubicMat_) delete cubicMat_;
}

//==============================================================================
// initialize
//==============================================================================
void StressCauchyBorn::initialize(void) 
{
  if (!initialized_) {
    if (makeLinear_) linearize();

    stringstream ss;
    double k = stiffness()*cbdata_.e2mvv; 
    double m = cbdata_.atom_mass;
    double w0 = sqrt(k*m);
    ss << "CB stiffness: " << stiffness() << " Einstein freq: " << w0;
    ATC::LammpsInterface::instance()->print_msg_once(ss.str());

    initialized_ = true;
  }
}

//==============================================================================
// compute the bond stiffness consistent with the einstein freq
//==============================================================================
double StressCauchyBorn::stiffness(void) const
{
  AtomCluster vac;
  cblattice_->atom_cluster(eye<double>(3,3), potential_->cutoff_radius(), vac);
  DENS_MAT k = vac.force_constants(0,potential_);
  return k(0,0);
}
//==============================================================================
// compute the stress at N integration points from the displacement gradient
//==============================================================================
void StressCauchyBorn::stress(const FIELD_MATS &fields, 
                              const GRAD_FIELD_MATS &gradFields, 
                              DENS_MAT_VEC &sigma)
{
  if (cubicMat_) {
    cubicMat_->stress(fields, gradFields, sigma);
    return;
  }
  FIELD_MATS::const_iterator               temp = fields.find(TEMPERATURE);
  GRAD_FIELD_MATS::const_iterator disp_gradient = gradFields.find(DISPLACEMENT);

  // Scaling factor - scale by atomic volume and energy conversion.
  // negative because stress must return (-) stress
  const double fact = -cbdata_.inv_atom_volume * cbdata_.e2mvv;
  const DENS_MAT_VEC &du(disp_gradient->second);
  const INDEX num_integration_pts = du.front().nRows(); 
  const INDEX nsd = du.size();
  DENS_MAT F(nsd,nsd);                // displacement gradient 
  bool temp_varies = (temp != fields.end());
  sigma.assign(nsd, DENS_MAT(num_integration_pts, nsd));
  StressAtIP S(sigma); // wrapper for quadrature points.
  AtomCluster vac;

  for (INDEX gp=0; gp<num_integration_pts; gp++) {
    // Sets the quadrature point to be computed.
    S.set_quadrature_point(gp);
    // Get displacement gradient and construct a virtual atom cluster.
    deformation_gradient(du, gp, F);
    // Generates the atom cluster, given the deformation gradient.
    cblattice_->atom_cluster(F, potential_->cutoff_radius(), vac);
    // Get temperature (assume 0K if no temperature field is present).
    const double T = (temp_varies ? temp->second[gp] : fixed_temperature_);
    // Computes the cauchy-born stresses.
    const StressArgs args(vac, potential_, cbdata_.boltzmann, cbdata_.hbar, T);
    cb_stress(args, S);

    // copy symmetric part of stress and scale by V0
    for (INDEX i=0; i<nsd; i++) { 
      S(i,i) *= fact;  
      for (INDEX j=i+1; j<nsd; j++)  S(j,i)=(S(i,j)*=fact);
    }
  }
}

//==============================================================================
// Computes free (T>0)/potential(T=0) energy density.  [mvv/L^3]
//==============================================================================
void StressCauchyBorn::elastic_energy(const FIELD_MATS &fields, 
                                      const GRAD_FIELD_MATS &gradFields,
                                      DENS_MAT &energy) const
{
  if (cubicMat_) {
    cubicMat_->elastic_energy(fields, gradFields, energy);
    return;
  }
  FIELD_MATS::const_iterator               temp = fields.find(TEMPERATURE);
  GRAD_FIELD_MATS::const_iterator disp_gradient = gradFields.find(DISPLACEMENT);
  const DENS_MAT_VEC &du(disp_gradient->second);
  DENS_MAT F(du.size(),du.size());
  AtomCluster vac;

  for (INDEX gp=0; gp<du.front().nRows(); gp++) {
    deformation_gradient(du, gp, F);
    cblattice_->atom_cluster(F, potential_->cutoff_radius(), vac);
    double T = (temp!=fields.end() ? temp->second[gp] : fixed_temperature_);
    energy[gp] = cb_energy(StressArgs(vac, potential_, cbdata_.boltzmann, cbdata_.hbar, T));
  }
  // Scaling factor - scale by atomic volume and energy conversion.
  
  energy *= cbdata_.inv_atom_volume * cbdata_.e2mvv;
}

//==============================================================================
// Computes entropic energy density.  [mvv/L^3]
//==============================================================================
void StressCauchyBorn::entropic_energy(const FIELD_MATS &fields, 
                                       const GRAD_FIELD_MATS &gradFields,
                                       DENS_MAT &energy) const
{
  FIELD_MATS::const_iterator               temp = fields.find(TEMPERATURE);
  GRAD_FIELD_MATS::const_iterator disp_gradient = gradFields.find(DISPLACEMENT);
  const DENS_MAT_VEC &du(disp_gradient->second);
  DENS_MAT F(du.size(),du.size());
  AtomCluster vac;

  for (INDEX gp=0; gp<du.front().nRows(); gp++) {
    deformation_gradient(du, gp, F);
    cblattice_->atom_cluster(F, potential_->cutoff_radius(), vac);
    double T = (temp!=fields.end() ? temp->second[gp] : fixed_temperature_);
    energy[gp] = cb_entropic_energy(StressArgs(vac, potential_, cbdata_.boltzmann, cbdata_.hbar, T));
  }
  // Scaling factor - scale by atomic volume and energy conversion.
  energy *= cbdata_.inv_atom_volume * cbdata_.e2mvv;
}

//==============================================================================
// creates a linearization for a deformation gradient
//==============================================================================
void StressCauchyBorn::linearize(MATRIX *F)
{
  if (cubicMat_) delete cubicMat_;
  DENS_MAT C;   
  if (F) tangent(*F, C);
  else   tangent(eye<double>(3,3), C);
  cubicMat_ = new StressCubicElastic(C(0,0), C(0,1), C(3,3));

  stringstream ss;
  double c11 = C(0,0)/cbdata_.e2mvv;
  double c12 = C(0,1)/cbdata_.e2mvv;
  double c44 = C(3,3)/cbdata_.e2mvv;
  ss << "created cubic stress function:"
     << "\n   lammps         ATC units" 
     << "\n   c11=" << c11 << " " << C(0,0)
     << "\n   c12=" << c12 << " " << C(0,1)
     << "\n   c44=" << c44 << " " << C(3,3);
  ATC::LammpsInterface::instance()->print_msg_once(ss.str());
}
//==============================================================================
// sets C as the material tangent modulus, given deformation gradient F
//==============================================================================
// Note: C is dS/dC which is 1/2 dS/dF_sym

void StressCauchyBorn::tangent(const MATRIX &F, MATRIX &C)  const
{
  if (cubicMat_) {
    cubicMat_->tangent(F,C);
    return;
  }
  elasticity_tensor(F,C);
}
//==============================================================================
// 1st elasticity tensor : B = dP/dF = C F F + S I ( 9 x 9 in Voigt notation)
// 2nd elasticity tensor : C = dS/dE ( 6 x 6 in Voigt notation)
//==============================================================================
DENS_VEC StressCauchyBorn::elasticity_tensor(const VECTOR &Fv, MATRIX &C, const ElasticityTensorType type)  const
{
   DENS_MAT F;
   if (Fv.nRows()==9) { F = from_voigt_unsymmetric(Fv); }
   else               { F = from_voigt(Fv); }
   return elasticity_tensor(F, C,type);
}
DENS_VEC StressCauchyBorn::elasticity_tensor(const MATRIX &F, MATRIX &C, const ElasticityTensorType type)  const
{
  double T = 0; 
  AtomCluster vac;
  cblattice_->atom_cluster(F, potential_->cutoff_radius(), vac);
  if (vac.size() < 4) throw ATC_Error("StressCauchyBorn::second_elasticity_tensor cluster does not have sufficient atoms");
  const StressArgs args(vac, potential_, cbdata_.boltzmann, cbdata_.hbar, T);
  // if using EAM potential, calculate embedding function and derivatives
  bool hasEAM = potential_->terms.embedding;
  double embed_p  = 0;
  double embed_pp = 0;
  if (hasEAM) {
    double e_density = cb_electron_density(args);
    embed_p  = potential_->F_p(e_density); // "F" in usual EAM symbology
    embed_pp = potential_->F_pp(e_density);
  }


  int size = 6;
  if (type == FIRST_ELASTICITY_TENSOR) { size = 9; }
  DENS_VEC Z(size), S(size), Zfp(size);
  Zfp = 0;
  C.reset(size,size);
  for (INDEX a=0; a<vac.size(); a++) {
    const DENS_VEC &Ra = vac.R(a);
    if (type == FIRST_ELASTICITY_TENSOR) { 
      DENS_VEC ra = F*Ra;
      for (INDEX i=0; i<size; i++) { Z(i)=ra(voigt_idx1[i])*Ra(voigt_idx2[i]); }
    }
    else {
      for (INDEX i=0; i<size; i++) { Z(i)=Ra(voigt_idx1[i])*Ra(voigt_idx2[i]); }
    }
    double d = vac.bond_length(a);
    double rinv   = 1.0/d;
    double phi_r  = potential_->phi_r(d);  // computes phi'
    double phi_rr = potential_->phi_rr(d); // computes phi''
    double fact1 = 0.5*phi_r*rinv;  // 1/2 see Philips
    double fact2 = 0.5*(phi_rr - phi_r*rinv) * rinv*rinv; 
    if (hasEAM) {
      double rho_r  = potential_->rho_r(d);  // computes rho'
      double rho_rr = potential_->rho_rr(d); // computes rho''
      fact1 += embed_p*rho_r*rinv;
      fact2 += embed_p*(rho_rr - rho_r*rinv) * rinv*rinv; 
      Zfp += Z*(rho_r*rinv);
    }
    for (INDEX i=0; i<size; i++) {
      S(i) += fact1*Z(i);
      for (INDEX j=0; j<size; j++) { 
        C(i,j) += fact2*Z(i)*Z(j); 
      }
    }
    if (type == FIRST_ELASTICITY_TENSOR) { 
      for (INDEX i=0; i<9; i++) {
        for (INDEX j=0; j<9; j++) { 
          if ( voigt_idx1[i] == voigt_idx1[j] )  { // \delta_ik S_JL
            C(i,j) += fact1*Ra(voigt_idx2[i])*Ra(voigt_idx2[j]);
          }
        }
      }
    }
  }
  if (hasEAM) {
    for (INDEX i=0; i<6; i++) {
      for (INDEX j=0; j<6; j++) { 
        C(i,j) += embed_pp*Zfp(i)*Zfp(j);  
      }
    }
  }
  double s = cbdata_.inv_atom_volume * cbdata_.e2mvv;  
  S *= s;
  C *= s;
  return S;
}
}// end atc namespace
