/* ---------------------------------------------------------------------- LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator http://lammps.sandia.gov, Sandia National Laboratories Steve Plimpton, sjplimp@sandia.gov Copyright (2003) Sandia Corporation. Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains certain rights in this software. This software is distributed under the GNU General Public License. See the README file in the top-level LAMMPS directory. ------------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- Contributing author: Aidan Thompson (SNL) Modified for use with KIM by Tobias Brink (2012,2013,2014,2017,2018,2019, 2020). process_dEdr support added by Mingjian Wen (2018) ------------------------------------------------------------------------- */ #include "pair_tersoff.hpp" #include #include #include #include using namespace model_driver_Tersoff; using namespace std; /* ---------------------------------------------------------------------- */ PairTersoff::PairTersoff(const string& parameter_file, int n_spec, map type_map, // Conversion factors. double energy_conv, double, // unused inv_energy_conv double length_conv, double inv_length_conv, double // unused charge_conv ) : kim_params(n_spec), n_spec(n_spec), params2(n_spec, n_spec), params3(n_spec, n_spec, n_spec) { // Prepare index -> element name mapping. for (map::const_iterator i = type_map.begin(); i != type_map.end(); ++i) { to_spec[i->second] = i->first; } // Read parameter file. std::fstream f(parameter_file.c_str(), std::ios_base::in); read_params(f, type_map, energy_conv, length_conv, inv_length_conv); } PairTersoff::PairTersoff(int n_spec, map type_map) : kim_params(n_spec), n_spec(n_spec), params2(n_spec, n_spec), params3(n_spec, n_spec, n_spec) { // Prepare index -> element name mapping. for (map::const_iterator i = type_map.begin(); i != type_map.end(); ++i) { to_spec[i->second] = i->first; } } PairTersoff::~PairTersoff() { } /* ---------------------------------------------------------------------- */ void PairTersoff::compute(const KIM::ModelComputeArguments& model_compute_arguments, int n_atoms, // Actual number of atoms const int * const atom_types, const int * const contributing, const Array2D& atom_coords, double* energy, double* atom_energy, Array2D* forces, double* virial, Array2D* particle_virial, bool compute_process_dEdr) const { int error; // KIM error code. int n_neigh; // Number of neighbors of i. const int * neighbors; // The indices of the neighbors. const bool eflag = energy || atom_energy; // Calculate energy? // If requested, reset energy. if (energy) *energy = 0.0; if (atom_energy) for (int i = 0; i != n_atoms; ++i) { atom_energy[i] = 0.0; } // Reset forces. if (forces) for (int i = 0; i != n_atoms; ++i) { (*forces)(i, 0) = 0.0; (*forces)(i, 1) = 0.0; (*forces)(i, 2) = 0.0; } // Reset virial. if (virial) for (int i = 0; i != 6; ++i) virial[i] = 0.0; if (particle_virial) for (int i = 0; i != n_atoms; ++i) for (int j = 0; j != 6; ++j) (*particle_virial)(i, j) = 0.0; // loop over full neighbor list of my atoms for (int i = 0; i != n_atoms; ++i) { // Skip central ghost atoms. if (!contributing[i]) continue; // Get neighbors. error = model_compute_arguments.GetNeighborList(0, i, &n_neigh, &neighbors); if (error) { throw runtime_error("Error in " "KIM::ModelComputeArguments.GetNeighborList"); } const int itype = atom_types[i]; const double xtmp = atom_coords(i,0); const double ytmp = atom_coords(i,1); const double ztmp = atom_coords(i,2); for (int jj = 0; jj != n_neigh; ++jj) { int j = neighbors[jj]; const int jtype = atom_types[j]; const double delr_ij[3] = { atom_coords(j,0) - xtmp, atom_coords(j,1) - ytmp, atom_coords(j,2) - ztmp }; const double rsq_ij = delr_ij[0]*delr_ij[0] + delr_ij[1]*delr_ij[1] + delr_ij[2]*delr_ij[2]; const double cutsq = params2(itype,jtype).cutsq; if (rsq_ij > cutsq) continue; const double r_ij = sqrt(rsq_ij); const double R = params2(itype,jtype).R; const double D = params2(itype,jtype).D; const double fc_ij = ters_fc(r_ij, R, D); // Value of the cutoff function. const double dfc_ij = ters_fc_d(r_ij, R, D); // Derivative of fc_ij. // two-body interactions, skip half of them unless j is a ghost atom if (!contributing[j] || (i < j)) { double evdwl; // Particle energy. const double fpair = repulsive(r_ij, fc_ij, dfc_ij, itype, jtype, eflag, evdwl); const double half_prefactor = contributing[j] ? 1.0 : 0.5; if (energy) *energy += half_prefactor * evdwl; if (atom_energy) { atom_energy[i] += 0.5 * evdwl; if (contributing[j]) atom_energy[j] += 0.5 * evdwl; } if (forces || virial || particle_virial || compute_process_dEdr) { const double fx = delr_ij[0]*fpair; const double fy = delr_ij[1]*fpair; const double fz = delr_ij[2]*fpair; if (forces) { (*forces)(i,0) -= half_prefactor*fx; (*forces)(i,1) -= half_prefactor*fy; (*forces)(i,2) -= half_prefactor*fz; (*forces)(j,0) += half_prefactor*fx; (*forces)(j,1) += half_prefactor*fy; (*forces)(j,2) += half_prefactor*fz; } if (virial || particle_virial) { const double vxx = delr_ij[0] * fx; const double vyy = delr_ij[1] * fy; const double vzz = delr_ij[2] * fz; const double vyz = delr_ij[1] * fz; const double vxz = delr_ij[0] * fz; const double vxy = delr_ij[0] * fy; if (virial) { virial[0] -= half_prefactor * vxx; virial[1] -= half_prefactor * vyy; virial[2] -= half_prefactor * vzz; virial[3] -= half_prefactor * vyz; virial[4] -= half_prefactor * vxz; virial[5] -= half_prefactor * vxy; } if (particle_virial) { (*particle_virial)(i,0) -= 0.5 * vxx; (*particle_virial)(i,1) -= 0.5 * vyy; (*particle_virial)(i,2) -= 0.5 * vzz; (*particle_virial)(i,3) -= 0.5 * vyz; (*particle_virial)(i,4) -= 0.5 * vxz; (*particle_virial)(i,5) -= 0.5 * vxy; if (contributing[j]) { (*particle_virial)(j,0) -= 0.5 * vxx; (*particle_virial)(j,1) -= 0.5 * vyy; (*particle_virial)(j,2) -= 0.5 * vzz; (*particle_virial)(j,3) -= 0.5 * vyz; (*particle_virial)(j,4) -= 0.5 * vxz; (*particle_virial)(j,5) -= 0.5 * vxy; } } } if (compute_process_dEdr) { run_process_dEdr(model_compute_arguments, -half_prefactor*fpair*r_ij, // dEdr r_ij, delr_ij, i, j, __LINE__, __FILE__); } } } // three-body interactions // accumulate bondorder zeta for each i-j interaction via loop over k double zeta_ij = 0.0; for (int kk = 0; kk != n_neigh; ++kk) { if (jj == kk) continue; int k = neighbors[kk]; const int ktype = atom_types[k]; const double delr_ik[3] = { atom_coords(k,0) - xtmp, atom_coords(k,1) - ytmp, atom_coords(k,2) - ztmp }; const double rsq_ik = delr_ik[0]*delr_ik[0] + delr_ik[1]*delr_ik[1] + delr_ik[2]*delr_ik[2]; const double cutsq = params3(itype,jtype,ktype).cutsq; if (rsq_ik > cutsq) continue; const double r_ik = sqrt(rsq_ik); const double R = params3(itype,jtype,ktype).R; const double D = params3(itype,jtype,ktype).D; const int m = params3(itype,jtype,ktype).m; const double lam3 = params3(itype,jtype,ktype).lam3; const double gamma = params3(itype,jtype,ktype).gamma; const double h = params3(itype,jtype,ktype).h; const double c2 = params3(itype,jtype,ktype).c2; const double d2 = params3(itype,jtype,ktype).d2; const double c2_d2 = params3(itype,jtype,ktype).c2_d2; zeta_ij += zeta(r_ij,r_ik,m,lam3,R,D,gamma,c2,d2,c2_d2,h, delr_ij,delr_ik); } // pairwise force due to zeta double prefactor; // -0.5 * fa * ∇bij double evdwl; // Particle energy. const double fzeta = force_zeta(r_ij, fc_ij, dfc_ij, zeta_ij, itype, jtype, prefactor, eflag, evdwl); if (energy) *energy += evdwl; if (atom_energy) { const double en = 0.5 * evdwl; atom_energy[i] += en; atom_energy[j] += en; } if (forces || virial || particle_virial || compute_process_dEdr) { const double fx = delr_ij[0]*fzeta; const double fy = delr_ij[1]*fzeta; const double fz = delr_ij[2]*fzeta; if (forces) { (*forces)(i,0) += fx; (*forces)(i,1) += fy; (*forces)(i,2) += fz; (*forces)(j,0) -= fx; (*forces)(j,1) -= fy; (*forces)(j,2) -= fz; } if (virial || particle_virial) { const double vxx = delr_ij[0] * fx; const double vyy = delr_ij[1] * fy; const double vzz = delr_ij[2] * fz; const double vyz = delr_ij[1] * fz; // yz const double vxz = delr_ij[0] * fz; // xz const double vxy = delr_ij[0] * fy; // xy if (virial) { virial[0] += vxx; virial[1] += vyy; virial[2] += vzz; virial[3] += vyz; virial[4] += vxz; virial[5] += vxy; } if (particle_virial) { const double vxx2 = 0.5 * vxx; const double vyy2 = 0.5 * vyy; const double vzz2 = 0.5 * vzz; const double vyz2 = 0.5 * vyz; const double vxz2 = 0.5 * vxz; const double vxy2 = 0.5 * vxy; (*particle_virial)(i,0) += vxx2; (*particle_virial)(i,1) += vyy2; (*particle_virial)(i,2) += vzz2; (*particle_virial)(i,3) += vyz2; (*particle_virial)(i,4) += vxz2; (*particle_virial)(i,5) += vxy2; (*particle_virial)(j,0) += vxx2; (*particle_virial)(j,1) += vyy2; (*particle_virial)(j,2) += vzz2; (*particle_virial)(j,3) += vyz2; (*particle_virial)(j,4) += vxz2; (*particle_virial)(j,5) += vxy2; } } if (compute_process_dEdr) { run_process_dEdr(model_compute_arguments, fzeta*r_ij, // dEdr r_ij, delr_ij, i, j, __LINE__, __FILE__); } } // attractive term via loop over k if (forces || virial || particle_virial || compute_process_dEdr) { for (int kk = 0; kk != n_neigh; ++kk) { if (jj == kk) continue; int k = neighbors[kk]; const int ktype = atom_types[k]; const double delr_ik[3] = { atom_coords(k,0) - xtmp, atom_coords(k,1) - ytmp, atom_coords(k,2) - ztmp }; const double rsq_ik = delr_ik[0]*delr_ik[0] + delr_ik[1]*delr_ik[1] + delr_ik[2]*delr_ik[2]; const double cutsq = params3(itype,jtype,ktype).cutsq; if (rsq_ik > cutsq) continue; const double r_ik = sqrt(rsq_ik); const double R = params3(itype,jtype,ktype).R; const double D = params3(itype,jtype,ktype).D; const int m = params3(itype,jtype,ktype).m; const double lam3 = params3(itype,jtype,ktype).lam3; const double gamma = params3(itype,jtype,ktype).gamma; const double h = params3(itype,jtype,ktype).h; const double c2 = params3(itype,jtype,ktype).c2; const double d2 = params3(itype,jtype,ktype).d2; const double c2_d2 = params3(itype,jtype,ktype).c2_d2; double delr_jk[3]; double r_jk; double fi[3], fj[3], fk[3]; // dEdr_ij not normalized by r_ij, same for dEdr_ik and dEdr_jk // note prefactor = -0.5 * fa * ∇bij, as defined above double dEdr_ij, dEdr_ik, dEdr_jk; attractive(prefactor, R, D, m, lam3, gamma, c2, d2, c2_d2, h, r_ij, r_ik, rsq_ij, rsq_ik, delr_ij, delr_ik, fi, fj, fk, r_jk, delr_jk, dEdr_ij, dEdr_ik, dEdr_jk, forces || virial || particle_virial, compute_process_dEdr); if (forces) { for (int dim = 0; dim < 3; ++dim) { (*forces)(i,dim) += fi[dim]; (*forces)(j,dim) += fj[dim]; (*forces)(k,dim) += fk[dim]; } } if (virial || particle_virial) { const double vxx = delr_ij[0]*fj[0] + delr_ik[0]*fk[0]; const double vyy = delr_ij[1]*fj[1] + delr_ik[1]*fk[1]; const double vzz = delr_ij[2]*fj[2] + delr_ik[2]*fk[2]; const double vyz = delr_ij[1]*fj[2] + delr_ik[1]*fk[2]; const double vxz = delr_ij[0]*fj[2] + delr_ik[0]*fk[2]; const double vxy = delr_ij[0]*fj[1] + delr_ik[0]*fk[1]; if (virial) { virial[0] -= vxx; virial[1] -= vyy; virial[2] -= vzz; virial[3] -= vyz; virial[4] -= vxz; virial[5] -= vxy; } if (particle_virial) { const double vxx3 = 1.0/3.0 * vxx; const double vyy3 = 1.0/3.0 * vyy; const double vzz3 = 1.0/3.0 * vzz; const double vyz3 = 1.0/3.0 * vyz; const double vxz3 = 1.0/3.0 * vxz; const double vxy3 = 1.0/3.0 * vxy; (*particle_virial)(i,0) -= vxx3; (*particle_virial)(i,1) -= vyy3; (*particle_virial)(i,2) -= vzz3; (*particle_virial)(i,3) -= vyz3; (*particle_virial)(i,4) -= vxz3; (*particle_virial)(i,5) -= vxy3; (*particle_virial)(j,0) -= vxx3; (*particle_virial)(j,1) -= vyy3; (*particle_virial)(j,2) -= vzz3; (*particle_virial)(j,3) -= vyz3; (*particle_virial)(j,4) -= vxz3; (*particle_virial)(j,5) -= vxy3; (*particle_virial)(k,0) -= vxx3; (*particle_virial)(k,1) -= vyy3; (*particle_virial)(k,2) -= vzz3; (*particle_virial)(k,3) -= vyz3; (*particle_virial)(k,4) -= vxz3; (*particle_virial)(k,5) -= vxy3; } } if (compute_process_dEdr) { run_process_dEdr(model_compute_arguments, dEdr_ij, r_ij, delr_ij, i, j, __LINE__, __FILE__); run_process_dEdr(model_compute_arguments, dEdr_ik, r_ik, delr_ik, i, k, __LINE__, __FILE__); run_process_dEdr(model_compute_arguments, dEdr_jk, r_jk, delr_jk, j, k, __LINE__, __FILE__); } } } } } } /* ---------------------------------------------------------------------- */ /* Read parameters. Reads the parameters from an input file and stores them. Automatically calls prepare_params() to check validity of parameters and pre-compute some values. */ void PairTersoff::read_params(istream& infile, std::map type_map, double energy_conv, double length_conv, double inv_length_conv) { // Strip comment lines. stringstream buffer; string line; while(getline(infile, line)) buffer << line.substr(0, line.find('#')) << endl; // Read in parameters. Array3D got_interaction(n_spec,n_spec,n_spec); got_interaction = false; Params2 temp_params2; Params3 temp_params3; double m; // m is an integer but some input files use floating point // notation, so we need to read into a double variable, // otherwise the C++ standard library chokes on the input. string type_i, type_j, type_k; double c, d; // Those are only stored in the published KIM parameters // since they are not needed for computation, we use // precomputed c², d² and c²/d² instead. while (buffer >> type_i >> type_j >> type_k >> m >> temp_params3.gamma >> temp_params3.lam3 >> c >> d >> temp_params3.h // costheta0 >> temp_params2.n >> temp_params2.beta >> temp_params2.lam2 >> temp_params2.B >> temp_params3.R >> temp_params3.D >> temp_params2.lam1 >> temp_params2.A) { // Convert m to integer. temp_params3.m = m; if (abs(m - temp_params3.m) > 1e-8) throw runtime_error("m must be an integer"); // Unit conversion. temp_params2.A *= energy_conv; temp_params2.B *= energy_conv; temp_params2.lam1 *= inv_length_conv; temp_params2.lam2 *= inv_length_conv; temp_params3.lam3 *= inv_length_conv; temp_params3.R *= length_conv; temp_params3.D *= length_conv; // Get the atom type indices. std::map::const_iterator it; int i,j,k; it = type_map.find(type_i); if (it != type_map.end()) i = it->second; else throw runtime_error("Unknown species: " + type_i); it = type_map.find(type_j); if (it != type_map.end()) j = it->second; else throw runtime_error("Unknown species: " + type_j); it = type_map.find(type_k); if (it != type_map.end()) k = it->second; else throw runtime_error("Unknown species: " + type_k); // Check if parameters were given twice. if (got_interaction(i,j,k)) throw runtime_error("Interaction " + type_i + "-" + type_j + "-" + type_k + " is defined twice!"); // All OK, store. got_interaction(i,j,k) = true; if (j == k) { temp_params2.R = temp_params3.R; temp_params2.D = temp_params3.D; params2(i,j) = temp_params2; } params3(i,j,k) = temp_params3; kim_params.c(i,j,k) = c; kim_params.d(i,j,k) = d; } if (!got_interaction.all()) throw runtime_error("Not all interactions were set!"); // Check parameters and pre-compute values. prepare_params(); // Copy to KIM-published data. kim_params.from_params(params2, params3); } void PairTersoff::update_params() { kim_params.to_params(params2, params3); prepare_params(); } /* Check parameters and pre-compute values. */ void PairTersoff::prepare_params() { max_cutoff = 0.0; for (int i = 0; i != n_spec; ++i) { const string type_i = to_spec.at(i); for (int j = 0; j != n_spec; ++j) { const string type_j = to_spec.at(j); Params2& temp_params2 = params2(i,j); if (temp_params2.n < 0) throw runtime_error("Parameter n (" + type_i + "-" + type_j + ") may not be smaller than zero."); if (temp_params2.beta < 0) throw runtime_error("Parameter beta (" + type_i + "-" + type_j + ") may not be smaller than zero."); if (temp_params2.lam1 < 0) throw runtime_error("Parameter lambda1 (" + type_i + "-" + type_j + ") may not be smaller than zero."); if (temp_params2.lam2 < 0) throw runtime_error("Parameter lambda2 (" + type_i + "-" + type_j + ") may not be smaller than zero."); if (temp_params2.A < 0) throw runtime_error("Parameter A (" + type_i + "-" + type_j + ") may not be smaller than zero."); if (temp_params2.B < 0) throw runtime_error("Parameter B (" + type_i + "-" + type_j + ") may not be smaller than zero."); // Pre-compute values. const double n2 = 2.0 * temp_params2.n; const double n_r = -1.0/temp_params2.n; temp_params2.n_precomp[0] = pow(n2 * 1e-16, n_r); temp_params2.n_precomp[1] = pow(n2 * 1e-8, n_r); temp_params2.n_precomp[2] = 1.0 / temp_params2.n_precomp[1]; temp_params2.n_precomp[3] = 1.0 / temp_params2.n_precomp[0]; for (int k = 0; k != n_spec; ++k) { const string type_k = to_spec.at(k); Params3& temp_params3 = params3(i,j,k); // Check values of parameters. if (kim_params.c(i,j,k) < 0) throw runtime_error("Parameter c (" + type_i + "-" + type_j + "-" + type_k + ") may not be smaller than zero."); if (kim_params.d(i,j,k) < 0) throw runtime_error("Parameter d (" + type_i + "-" + type_j + "-" + type_k + ") may not be smaller than zero."); if (temp_params3.m != 1 && temp_params3.m != 3) throw runtime_error("Parameter m (" + type_i + "-" + type_j + "-" + type_k + ") must be one or three."); if (temp_params3.R < 0) throw runtime_error("Parameter R (" + type_i + "-" + type_j + "-" + type_k + ") may not be smaller than zero."); if (temp_params3.D < 0) throw runtime_error("Parameter D (" + type_i + "-" + type_j + "-" + type_k + ") may not be smaller than zero."); if (temp_params3.D > temp_params3.R) throw runtime_error("Parameter D (" + type_i + "-" + type_j + "-" + type_k + ") must be smaller than R."); if (temp_params3.gamma < 0) throw runtime_error("Parameter gamma (" + type_i + "-" + type_j + "-" + type_k + ") may not be smaller than zero."); // Cutoff. double cut = temp_params3.R + temp_params3.D; // max cutoff. temp_params3.cutsq = cut * cut; // for fast check if inside cutoff if (j == k) temp_params2.cutsq = temp_params3.cutsq; // Get the cutoff to pass to KIM, which is the biggest cutoff. if (cut > max_cutoff) max_cutoff = cut; // Pre-compute values. temp_params3.c2 = kim_params.c(i,j,k) * kim_params.c(i,j,k); temp_params3.d2 = kim_params.d(i,j,k) * kim_params.d(i,j,k); temp_params3.c2_d2 = temp_params3.c2 / temp_params3.d2; } } } } void PairTersoff::write_params(ofstream& outfile) { // Set maximum precision. outfile << setprecision(16); // Write. for (int i = 0; i < n_spec; ++i) { const string type_i = to_spec.at(i); for (int j = 0; j < n_spec; ++j) { const string type_j = to_spec.at(j); for (int k = 0; k < n_spec; ++k) { const string type_k = to_spec.at(k); outfile << type_i << " " << type_j << " " << type_k << " "; outfile << kim_params.m(i,j,k) << " "; outfile << kim_params.gamma(i,j,k) << " "; outfile << kim_params.lam3(i,j,k) << " "; outfile << kim_params.c(i,j,k) << " "; outfile << kim_params.d(i,j,k) << " "; outfile << kim_params.h(i,j,k) << " "; if (j == k) { // Two-body parameters are only taken from j == k, so in // order to make that clear, we write zeros otherwise. outfile << kim_params.n(i,j) << " "; outfile << kim_params.beta(i,j) << " "; outfile << kim_params.lam2(i,j) << " "; outfile << kim_params.B(i,j) << " "; } else { outfile << "0 0 0 0 "; } outfile << kim_params.R(i,j,k) << " "; outfile << kim_params.D(i,j,k) << " "; if (j == k) { // Two-body parameters are only taken from j == k, so in // order to make that clear, we write zeros otherwise. outfile << kim_params.lam1(i,j) << " "; outfile << kim_params.A(i,j) << endl; } else { outfile << "0 0" << endl; } } } } } /* ---------------------------------------------------------------------- */ double PairTersoff::repulsive(double r, double fc, double fc_d, int itype, int jtype, bool eflag, double &eng) const { // In this case, we look up the parameters inside the function, // since derived classes (e.g., for ZBL support) need more // parameters. const double lam1 = params2(itype,jtype).lam1; const double A = params2(itype,jtype).A; const double tmp_exp = exp(-lam1 * r); if (eflag) eng = fc * A * tmp_exp; return -A * tmp_exp * (fc_d - fc*lam1) / r; } /* ---------------------------------------------------------------------- */ double PairTersoff::zeta(double rij, double rik, int m, double lam3, double R, double D, double gamma, double c2, double d2, double c2_d2, double h, const double* delrij, const double* delrik) const { const double costheta = (delrij[0]*delrik[0] + delrij[1]*delrik[1] + delrij[2]*delrik[2] ) / (rij * rik); double arg; if (m == 3) arg = pow3(lam3 * (rij-rik)); else arg = lam3 * (rij-rik); // m == 1 double ex_delr; if (arg > 69.0776) ex_delr = 1.e30; else if (arg < -69.0776) ex_delr = 0.0; else ex_delr = exp(arg); return ters_fc(rik,R,D) * ters_gijk(costheta,gamma,c2,d2,c2_d2,h) * ex_delr; } /* ---------------------------------------------------------------------- */ double PairTersoff::force_zeta(double r, double fc, double fc_d, double zeta_ij, int itype, int jtype, double &prefactor, bool eflag, double &eng) const { // In this case, we look up the parameters inside the function, // since derived classes (e.g., for ZBL support) need more // parameters. const double beta = params2(itype,jtype).beta; const double n = params2(itype,jtype).n; const double* n_precomp = params2(itype,jtype).n_precomp; const double fa = ters_fa(r, fc, itype, jtype); const double fa_d = ters_fa_d(r, fc, fc_d, itype, jtype); const double bij = ters_bij(zeta_ij, beta, n, n_precomp); prefactor = -0.5*fa * ters_bij_d(zeta_ij, beta, n, n_precomp); if (eflag) eng = 0.5*bij*fa; return 0.5*bij*fa_d / r; } /* ---------------------------------------------------------------------- attractive term use param_ij cutoff for rij test use param_ijk cutoff for rik test ------------------------------------------------------------------------- */ void PairTersoff::attractive(double prefactor, double R, double D, int m, double lam3, double gamma, double c2, double d2, double c2_d2, double h, double rij, double rik, double rijsq, double riksq, const double *delrij, const double *delrik, // output for force/virial double *fi, double *fj, double *fk, // output for process_dEdr double &rjk, double *delrjk, double &drij, double &drik, double &drjk, // bool want_force_or_virial, bool want_dEdr) const { /* This routine is a little bit more complicated now, since we want to both support virial calculation as implemented/defined in LAMMPS and the process_dEdr() callback of KIM. For the former, we need derivatives with regard to the positions and for the latter with regard to the distances. So we have some booleans to toggle what we want to compute. Currently, there might be a slight performance penalty if process_dEdr is requested but not virial. This is to avoid two code paths for certain computations depending on what values were requested. Will calculate r_jk (all related quantities) only if want_dEdr is true. TODO: can we save some computations and combine both at least partially? */ // Values needed by either approach. const double fc = ters_fc(rik,R,D); const double dfc = ters_fc_d(rik,R,D); const double tmp = (m == 3) ? pow3(lam3 * (rij-rik)) // m == 3 : lam3 * (rij-rik); // m == 1 const double ex_delr = (tmp > 69.0776) ? 1.0e30 : ( (tmp < -69.0776) ? 0.0 : exp(tmp) ); const double ex_delr_d = (m == 3) ? 3.0*pow3(lam3) * pow2(rij-rik)*ex_delr // m == 3 : lam3 * ex_delr; // m == 1 double rij_hat[3]; vec3_scale(1.0/rij, delrij, rij_hat); // rij_hat = delrij / rij double rik_hat[3]; vec3_scale(1.0/rik, delrik, rik_hat); // rik_hat = delrik / rik const double cos_theta = vec3_dot(rij_hat, rik_hat); const double gijk = ters_gijk(cos_theta, gamma, c2, d2, c2_d2, h); const double gijk_d = ters_gijk_d(cos_theta, gamma, c2, d2, h); if (want_force_or_virial) { ters_zetaterm_d_pos(prefactor, fc, dfc, ex_delr, ex_delr_d, cos_theta, gijk, gijk_d, rij_hat, rij, rik_hat, rik, fi, fj, fk); } if (want_dEdr) { // compute jk vector delrjk[0] = delrik[0] - delrij[0]; delrjk[1] = delrik[1] - delrij[1]; delrjk[2] = delrik[2] - delrij[2]; const double rjksq = delrjk[0]*delrjk[0] + delrjk[1]*delrjk[1] + delrjk[2]*delrjk[2]; rjk = sqrt(rjksq); ters_zetaterm_d_dist(-prefactor, fc, dfc, ex_delr, ex_delr_d, gijk, gijk_d, rij, rik, rjk, rijsq, riksq, rjksq, drij, drik, drjk); } } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_fc(double r, double R, double D) const { if (r < R-D) return 1.0; if (r > R+D) return 0.0; return 0.5*(1.0 - sin(pi_2*(r - R)/D)); } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_fc_d(double r, double R, double D) const { if (r < R-D) return 0.0; if (r > R+D) return 0.0; return -(pi_4/D) * cos(pi_2*(r - R)/D); } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_fa(double r, double fc, int itype, int jtype) const { if (fc == 0.0) return 0.0; // In this case, we look up the parameters inside the function, // since derived classes (e.g., for ZBL support) need more // parameters. const double B = params2(itype,jtype).B; const double lam2 = params2(itype,jtype).lam2; return -B * exp(-lam2 * r) * fc; } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_fa_d(double r, double fc, double fc_d, int itype, int jtype) const { if (fc == 0.0) return 0.0; // In this case, we look up the parameters inside the function, // since derived classes (e.g., for ZBL support) need more // parameters. const double B = params2(itype,jtype).B; const double lam2 = params2(itype,jtype).lam2; return B * exp(-lam2 * r) * (lam2 * fc - fc_d); } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_bij(double zeta, double beta, double n, const double n_precomp[4]) const { const double tmp = beta * zeta; if (tmp > n_precomp[0]) return 1.0/sqrt(tmp); if (tmp > n_precomp[1]) return (1.0 - pow(tmp,-n) / (2.0*n))/sqrt(tmp); if (tmp < n_precomp[3]) return 1.0; if (tmp < n_precomp[2]) return 1.0 - pow(tmp,n)/(2.0*n); return pow(1.0 + pow(tmp,n), -1.0/(2.0*n)); } /* ---------------------------------------------------------------------- */ double PairTersoff::ters_bij_d(double zeta, double beta, double n, const double n_precomp[4]) const { const double tmp = beta * zeta; if (tmp > n_precomp[0]) return beta * -0.5*pow(tmp,-1.5); if (tmp > n_precomp[1]) return beta * (-0.5*pow(tmp,-1.5) * (1.0 - (1.0 + 1.0/(2.0*n)) * pow(tmp,-n))); if (tmp < n_precomp[3]) return 0.0; if (tmp < n_precomp[2]) return -0.5*beta * pow(tmp, n-1.0); const double tmp_n = pow(tmp, n); return -0.5 * pow(1.0+tmp_n, -1.0-(1.0/(2.0*n)))*tmp_n / zeta; } /* ---------------------------------------------------------------------- */ void PairTersoff::ters_zetaterm_d_pos(double prefactor, double fc, double dfc, double ex_delr, double ex_delr_d, double cos_theta, double gijk, double gijk_d, double *rij_hat, double rij, double *rik_hat, double rik, double *dri, double *drj, double *drk) const { double dcosdri[3], dcosdrj[3], dcosdrk[3]; costheta_d(rij_hat,rij, rik_hat,rik, cos_theta, dcosdri,dcosdrj,dcosdrk); // compute the derivative wrt Ri // dri = -dfc*gijk*ex_delr*rik_hat; // dri += fc*gijk_d*ex_delr*dcosdri; // dri += fc*gijk*ex_delr_d*(rik_hat - rij_hat); vec3_scale(-dfc*gijk*ex_delr,rik_hat,dri); vec3_scaleadd(fc*gijk_d*ex_delr,dcosdri,dri,dri); vec3_scaleadd(fc*gijk*ex_delr_d,rik_hat,dri,dri); vec3_scaleadd(-fc*gijk*ex_delr_d,rij_hat,dri,dri); vec3_scale(prefactor,dri,dri); // compute the derivative wrt Rj // drj = fc*gijk_d*ex_delr*dcosdrj; // drj += fc*gijk*ex_delr_d*rij_hat; vec3_scale(fc*gijk_d*ex_delr,dcosdrj,drj); vec3_scaleadd(fc*gijk*ex_delr_d,rij_hat,drj,drj); vec3_scale(prefactor,drj,drj); // compute the derivative wrt Rk // drk = dfc*gijk*ex_delr*rik_hat; // drk += fc*gijk_d*ex_delr*dcosdrk; // drk += -fc*gijk*ex_delr_d*rik_hat; vec3_scale(dfc*gijk*ex_delr,rik_hat,drk); vec3_scaleadd(fc*gijk_d*ex_delr,dcosdrk,drk,drk); vec3_scaleadd(-fc*gijk*ex_delr_d,rik_hat,drk,drk); vec3_scale(prefactor,drk,drk); } /* ---------------------------------------------------------------------- */ void PairTersoff::ters_zetaterm_d_dist(double prefactor, double fc, double dfc, double ex_delr, double ex_delr_d, double gijk, double gijk_d, double rij, double rik, double rjk, double rijsq, double riksq, double rjksq, double &drij, double &drik, double &drjk) const { // dcos_ijk/drij, dcos_ijk/drik, dcos_ijk/drjk const double dcosdrij = (rijsq - riksq + rjksq)/(2*rijsq*rik); const double dcosdrik = (riksq - rijsq + rjksq)/(2*rij*riksq); const double dcosdrjk = -rjk/(rij*rik); // compute the derivative wrt rij, rik, rjk drij = (fc*gijk_d*ex_delr*dcosdrij + fc*gijk*ex_delr_d) * prefactor; drik = (dfc*gijk*ex_delr + fc*gijk_d*ex_delr*dcosdrik - fc*gijk*ex_delr_d) * prefactor; drjk = (fc*gijk_d*ex_delr*dcosdrjk) * prefactor; } /* ---------------------------------------------------------------------- */ void PairTersoff::costheta_d(double *rij_hat, double rij, double *rik_hat, double rik, double cos_theta, double *dri, double *drj, double *drk) const { // first element is devative wrt Ri, second wrt Rj, third wrt Rk vec3_scaleadd(-cos_theta,rij_hat,rik_hat,drj); vec3_scale(1.0/rij,drj,drj); vec3_scaleadd(-cos_theta,rik_hat,rij_hat,drk); vec3_scale(1.0/rik,drk,drk); vec3_add(drj,drk,dri); vec3_scale(-1.0,dri,dri); }