// // CDDL HEADER START // // The contents of this file are subject to the terms of the Common Development // and Distribution License Version 1.0 (the "License"). // // You can obtain a copy of the license at // http://www.opensource.org/licenses/CDDL-1.0. See the License for the // specific language governing permissions and limitations under the License. // // When distributing Covered Code, include this CDDL HEADER in each file and // include the License file in a prominent location with the name LICENSE.CDDL. // If applicable, add the following below this CDDL HEADER, with the fields // enclosed by brackets "[]" replaced with your own identifying information: // // Portions Copyright (c) [yyyy] [name of copyright owner]. All rights reserved. // // CDDL HEADER END // // // Copyright (c) 2019, Regents of the University of Minnesota. // All rights reserved. // // Contributors: // Mingjian Wen // #include "descriptor.h" #define LOG_ERROR(msg) \ (std::cerr << "ERROR (descriptor): " << (msg) << std::endl) Descriptor::Descriptor() : rcut_2D_(nullptr), has_three_body_(false), normalize_(true) { return; } Descriptor::~Descriptor() { for (size_t i = 0; i < params_.size(); i++) { Deallocate2DArray(params_.at(i)); } Deallocate2DArray(rcut_2D_); } int Descriptor::read_parameter_file(FILE * const filePointer) { int ier; int endOfFileFlag = 0; char nextLine[MAXLINE]; char errorMsg[1024]; char name[128]; // cutoff and species int numSpecies; int numUniqueSpeciesPairs; char spec1[32], spec2[32]; int iIndex, jIndex; double cutoff; double ** cutoff_2D; std::vector species; // descriptor int numDescTypes; int numDescs; int numParams; int numParamSets; double ** descParams = nullptr; // species and cutoff getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%s", name); if (ier != 1) { sprintf(errorMsg, "unable to read cutoff type from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } lowerCase(name); if (strcmp(name, "cos") != 0) { sprintf(errorMsg, "Currently, only cutoff type `cos` is supported, but given %s.\n", name); LOG_ERROR(errorMsg); return true; } getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%d", &numSpecies); if (ier != 1) { sprintf(errorMsg, "unable to read number of species from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } numUniqueSpeciesPairs = (numSpecies + 1) * numSpecies / 2; AllocateAndInitialize2DArray(cutoff_2D, numSpecies, numSpecies); // keep track of known species std::map speciesMap; int index = 0; // species code integer code starting from 0 for (int i = 0; i < numUniqueSpeciesPairs; i++) { getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%s %s %lg", spec1, spec2, &cutoff); if (ier != 3) { sprintf(errorMsg, "unable to read cutoff from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } // check for new species std::string s1(spec1); std::map::const_iterator iIter = speciesMap.find(s1); if (iIter == speciesMap.end()) { speciesMap[s1] = index; species.push_back(s1); iIndex = index; index++; } else { iIndex = speciesMap[s1]; } std::string s2(spec2); std::map::const_iterator jIter = speciesMap.find(s2); if (jIter == speciesMap.end()) { speciesMap[s2] = index; species.push_back(s2); jIndex = index; index++; } else { jIndex = speciesMap[s2]; } // store cutoff values cutoff_2D[iIndex][jIndex] = cutoff_2D[jIndex][iIndex] = cutoff; } // register species cutoff set_species(species); set_cutoff(name, numSpecies, cutoff_2D[0]); Deallocate2DArray(cutoff_2D); // number of descriptor types getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%d", &numDescTypes); if (ier != 1) { sprintf(errorMsg, "unable to read number of descriptor types from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } // descriptor for (int i = 0; i < numDescTypes; i++) { // descriptor name and parameter dimensions getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); // name of descriptor ier = sscanf(nextLine, "%s", name); if (ier != 1) { sprintf(errorMsg, "unable to read descriptor from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } lowerCase(name); // change to lower case name if (strcmp(name, "g1") == 0) { add_descriptor(name, nullptr, 1, 0); } else { // re-read name, and read number of param sets and number of params ier = sscanf(nextLine, "%s %d %d", name, &numParamSets, &numParams); if (ier != 3) { sprintf(errorMsg, "unable to read descriptor from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } // change name to lower case lowerCase(name); // check size of params is correct w.r.t its name if (strcmp(name, "g2") == 0) { if (numParams != 2) { sprintf(errorMsg, "number of params for descriptor G2 is incorrect, " "expecting 2, but given %d.\n", numParams); LOG_ERROR(errorMsg); return true; } } else if (strcmp(name, "g3") == 0) { if (numParams != 1) { sprintf(errorMsg, "number of params for descriptor G3 is incorrect, " "expecting 1, but given %d.\n", numParams); LOG_ERROR(errorMsg); return true; } } else if (strcmp(name, "g4") == 0) { if (numParams != 3) { sprintf(errorMsg, "number of params for descriptor G4 is incorrect, " "expecting 3, but given %d.\n", numParams); LOG_ERROR(errorMsg); return true; } } else if (strcmp(name, "g5") == 0) { if (numParams != 3) { sprintf(errorMsg, "number of params for descriptor G5 is incorrect, " "expecting 3, but given %d.\n", numParams); LOG_ERROR(errorMsg); return true; } } else { sprintf(errorMsg, "unsupported descriptor `%s' from line:\n", name); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } // read descriptor params AllocateAndInitialize2DArray(descParams, numParamSets, numParams); for (int j = 0; j < numParamSets; j++) { getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = getXdouble(nextLine, numParams, descParams[j]); if (ier) { sprintf(errorMsg, "unable to read descriptor parameters from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } } // copy data to Descriptor add_descriptor(name, descParams[0], numParamSets, numParams); Deallocate2DArray(descParams); } } // number of descriptors numDescs = get_num_descriptors(); // centering and normalizing params // flag, whether we use this feature getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%*s %s", name); if (ier != 1) { sprintf(errorMsg, "unable to read centering and normalization info from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } lowerCase(name); bool normalize; if (strcmp(name, "true") == 0) { normalize = true; } else { normalize = false; } int size = 0; double * means = nullptr; double * stds = nullptr; if (normalize) { // size of the data, this should be equal to numDescs getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%d", &size); if (ier != 1) { sprintf(errorMsg, "unable to read the size of centering and normalization " "data info from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } if (size != numDescs) { sprintf(errorMsg, "Size of centering and normalizing data inconsistent with " "the number of descriptors. Size = %d, num_descriptors=%d\n", size, numDescs); LOG_ERROR(errorMsg); return true; } // read means AllocateAndInitialize1DArray(means, size); for (int i = 0; i < size; i++) { getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%lg", &means[i]); if (ier != 1) { sprintf(errorMsg, "unable to read `means' from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } } // read standard deviations AllocateAndInitialize1DArray(stds, size); for (int i = 0; i < size; i++) { getNextDataLine(filePointer, nextLine, MAXLINE, &endOfFileFlag); ier = sscanf(nextLine, "%lg", &stds[i]); if (ier != 1) { sprintf(errorMsg, "unable to read `means' from line:\n"); strcat(errorMsg, nextLine); LOG_ERROR(errorMsg); return true; } } } // store info into descriptor class set_feature_mean_and_std(normalize, size, means, stds); Deallocate1DArray(means); Deallocate1DArray(stds); // everything is OK return false; } void Descriptor::set_cutoff(const char * name, const int Nspecies, const double * rcut_2D) { (void) name; // to avoid unused warning // Support cos cutoff function currently, but can be easily extended cutoff_func_ = &cut_cos; d_cutoff_func_ = &d_cut_cos; AllocateAndInitialize2DArray(rcut_2D_, Nspecies, Nspecies); int idx = 0; for (int i = 0; i < Nspecies; i++) { for (int j = 0; j < Nspecies; j++) { rcut_2D_[i][j] = rcut_2D[idx]; idx++; } } } void Descriptor::add_descriptor(char const * name, double const * values, int const row, int const col) { int idx = 0; double ** params = 0; AllocateAndInitialize2DArray(params, row, col); for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { params[i][j] = values[idx]; idx++; } } idx = 0; for (size_t i = 0; i < num_param_sets_.size(); i++) { idx += num_param_sets_[i]; } name_.push_back(name); params_.push_back(params); num_param_sets_.push_back(row); num_params_.push_back(col); starting_index_.push_back(idx); if (strcmp(name, "g4") == 0 || strcmp(name, "g5") == 0) { has_three_body_ = true; } } void Descriptor::set_feature_mean_and_std(bool normalize, int const size, double const * means, double const * stds) { normalize_ = normalize; for (int i = 0; i < size; i++) { feature_mean_.push_back(means[i]); feature_std_.push_back(stds[i]); } } int Descriptor::get_num_descriptors() { int N = 0; for (size_t i = 0; i < num_param_sets_.size(); i++) { N += num_param_sets_.at(i); } return N; } void Descriptor::convert_units(double convertEnergy, double convertLength) { (void) convertEnergy; // avoid unused warning // parameters for (size_t p = 0; p < name_.size(); p++) { for (int q = 0; q < num_param_sets_[p]; q++) { if (name_[p] == "g2") { params_[p][q][0] /= convertLength * convertLength; // eta params_[p][q][1] *= convertLength; // Rs } if (name_[p] == "g3") { params_[p][q][0] /= convertLength; // kappa } if (name_[p] == "g4") { params_[p][q][2] /= convertLength * convertLength; // eta } if (name_[p] == "g5") { params_[p][q][2] /= convertLength * convertLength; // eta } } } // cutoff int N = get_num_species(); for (int p = 0; p < N; p++) { for (int q = 0; q < N; q++) { rcut_2D_[p][q] *= convertLength; } } } //***************************************************************************** // Compute the descriptor values and their derivatives w.r.t. the coordinates of // atom i and its neighbors. // Note `grad_desc` should be of length numDesc*(numnei+1)*DIM. The last DIM // associated with each descriptor whose last store the derivate of descriptors // w.r.t. the coordinates of atom i. //***************************************************************************** void Descriptor::generate_one_atom(int const i, double const * coords, int const * particleSpeciesCodes, int const * neighlist, int const numnei, double * const desc, double * const grad_desc, bool grad) { // prepare data VectorOfSizeDIM * coordinates = (VectorOfSizeDIM *) coords; int const iSpecies = particleSpeciesCodes[i]; // Setup loop over neighbors of current particle for (int jj = 0; jj < numnei; ++jj) { // adjust index of particle neighbor int const j = neighlist[jj]; int const jSpecies = particleSpeciesCodes[j]; // cutoff between ij double rcutij = rcut_2D_[iSpecies][jSpecies]; // Compute rij double rij[DIM]; for (int dim = 0; dim < DIM; ++dim) { rij[dim] = coordinates[j][dim] - coordinates[i][dim]; } double const rijsq = rij[0] * rij[0] + rij[1] * rij[1] + rij[2] * rij[2]; double const rijmag = sqrt(rijsq); // if particles i and j not interact if (rijmag > rcutij) { continue; } // two-body descriptors for (size_t p = 0; p < name_.size(); p++) { if (name_[p] != "g1" && name_[p] != "g2" && name_[p] != "g3") { continue; } int idx = starting_index_[p]; for (int q = 0; q < num_param_sets_[p]; q++) { double gc = 0; double dgcdr_two = 0; if (name_[p] == "g1") { if (grad) { sym_d_g1(rijmag, rcutij, gc, dgcdr_two); } else { sym_g1(rijmag, rcutij, gc); } } else if (name_[p] == "g2") { double eta = params_[p][q][0]; double Rs = params_[p][q][1]; if (grad) { sym_d_g2(eta, Rs, rijmag, rcutij, gc, dgcdr_two); } else { sym_g2(eta, Rs, rijmag, rcutij, gc); } } else if (name_[p] == "g3") { double kappa = params_[p][q][0]; if (grad) { sym_d_g3(kappa, rijmag, rcutij, gc, dgcdr_two); } else { sym_g3(kappa, rijmag, rcutij, gc); } } desc[idx] += gc; if (grad) { int page = idx * DIM * (numnei + 1); for (int dim = 0; dim < DIM; ++dim) { double pair = dgcdr_two * rij[dim] / rijmag; grad_desc[page + numnei * DIM + dim] -= pair; grad_desc[page + jj * DIM + dim] += pair; } } idx += 1; } // loop over same descriptor but different parameter set } // loop over descriptors // three-body descriptors if (has_three_body_ == false) continue; for (int kk = jj + 1; kk < numnei; ++kk) { // adjust index of particle neighbor int const k = neighlist[kk]; int const kSpecies = particleSpeciesCodes[k]; // cutoff between ik and jk double const rcutik = rcut_2D_[iSpecies][kSpecies]; double const rcutjk = rcut_2D_[jSpecies][kSpecies]; // Compute rik, rjk and their squares double rik[DIM]; double rjk[DIM]; for (int dim = 0; dim < DIM; ++dim) { rik[dim] = coordinates[k][dim] - coordinates[i][dim]; rjk[dim] = coordinates[k][dim] - coordinates[j][dim]; } double const riksq = rik[0] * rik[0] + rik[1] * rik[1] + rik[2] * rik[2]; double const rjksq = rjk[0] * rjk[0] + rjk[1] * rjk[1] + rjk[2] * rjk[2]; double const rikmag = sqrt(riksq); double const rjkmag = sqrt(rjksq); if (rikmag > rcutik) continue; // three-dody not interacting double const rvec[3] = {rijmag, rikmag, rjkmag}; double const rcutvec[3] = {rcutij, rcutik, rcutjk}; for (size_t p = 0; p < name_.size(); p++) { if (name_[p] != "g4" && name_[p] != "g5") { continue; } int idx = starting_index_[p]; for (int q = 0; q < num_param_sets_[p]; q++) { double gc = 0; double dgcdr_three[3] = {0, 0, 0}; if (name_[p] == "g4") { double zeta = params_[p][q][0]; double lambda = params_[p][q][1]; double eta = params_[p][q][2]; if (grad) { sym_d_g4(zeta, lambda, eta, rvec, rcutvec, gc, dgcdr_three); } else { sym_g4(zeta, lambda, eta, rvec, rcutvec, gc); } } else if (name_[p] == "g5") { double zeta = params_[p][q][0]; double lambda = params_[p][q][1]; double eta = params_[p][q][2]; if (grad) { sym_d_g5(zeta, lambda, eta, rvec, rcutvec, gc, dgcdr_three); } else { sym_g5(zeta, lambda, eta, rvec, rcutvec, gc); } } desc[idx] += gc; if (grad) { int page = idx * DIM * (numnei + 1); for (int dim = 0; dim < DIM; ++dim) { double pair_ij = dgcdr_three[0] * rij[dim] / rijmag; double pair_ik = dgcdr_three[1] * rik[dim] / rikmag; double pair_jk = dgcdr_three[2] * rjk[dim] / rjkmag; grad_desc[page + numnei * DIM + dim] += -pair_ij - pair_ik; grad_desc[page + jj * DIM + dim] += pair_ij - pair_jk; grad_desc[page + kk * DIM + dim] += pair_ik + pair_jk; } } idx += 1; } // loop over same descriptor but different parameter set } // loop over descriptors } // loop over kk } // loop over jj } //***************************************************************************** // Symmetry functions: Jorg Behler, J. Chem. Phys. 134, 074106, 2011. //***************************************************************************** void Descriptor::sym_g1(double r, double rcut, double & phi) { phi = cutoff_func_(r, rcut); } void Descriptor::sym_d_g1(double r, double rcut, double & phi, double & dphi) { phi = cutoff_func_(r, rcut); dphi = d_cutoff_func_(r, rcut); } void Descriptor::sym_g2( double eta, double Rs, double r, double rcut, double & phi) { phi = exp(-eta * (r - Rs) * (r - Rs)) * cutoff_func_(r, rcut); } void Descriptor::sym_d_g2( double eta, double Rs, double r, double rcut, double & phi, double & dphi) { if (r > rcut) { phi = 0.; dphi = 0.; } else { double eterm = exp(-eta * (r - Rs) * (r - Rs)); double determ = -2 * eta * (r - Rs) * eterm; double fc = cutoff_func_(r, rcut); double dfc = d_cutoff_func_(r, rcut); phi = eterm * fc; dphi = determ * fc + eterm * dfc; } } void Descriptor::sym_g3(double kappa, double r, double rcut, double & phi) { phi = cos(kappa * r) * cutoff_func_(r, rcut); } void Descriptor::sym_d_g3( double kappa, double r, double rcut, double & phi, double & dphi) { double costerm = cos(kappa * r); double dcosterm = -kappa * sin(kappa * r); double fc = cutoff_func_(r, rcut); double dfc = d_cutoff_func_(r, rcut); phi = costerm * fc; dphi = dcosterm * fc + costerm * dfc; } void Descriptor::sym_g4(double zeta, double lambda, double eta, const double * r, const double * rcut, double & phi) { double rij = r[0]; double rik = r[1]; double rjk = r[2]; double rcutij = rcut[0]; double rcutik = rcut[1]; double rcutjk = rcut[2]; double rijsq = rij * rij; double riksq = rik * rik; double rjksq = rjk * rjk; if (rij > rcutij || rik > rcutik || rjk > rcutjk) { phi = 0.0; } else { // i is the apex atom double cos_ijk = (rijsq + riksq - rjksq) / (2 * rij * rik); double costerm; double base = 1 + lambda * cos_ijk; // prevent numerical instability (when lambda=-1 and cos_ijk=1) if (base <= 0) { costerm = 0; } else { costerm = pow(base, zeta); } double eterm = exp(-eta * (rijsq + riksq + rjksq)); phi = pow(2, 1 - zeta) * costerm * eterm * cutoff_func_(rij, rcutij) * cutoff_func_(rik, rcutik) * cutoff_func_(rjk, rcutjk); } } void Descriptor::sym_d_g4(double zeta, double lambda, double eta, const double * r, const double * rcut, double & phi, double * const dphi) { double rij = r[0]; double rik = r[1]; double rjk = r[2]; double rcutij = rcut[0]; double rcutik = rcut[1]; double rcutjk = rcut[2]; double rijsq = rij * rij; double riksq = rik * rik; double rjksq = rjk * rjk; if (rij > rcutij || rik > rcutik || rjk > rcutjk) { phi = 0.0; dphi[0] = 0.0; dphi[1] = 0.0; dphi[2] = 0.0; } else { // cosine term, i is the apex atom double cos_ijk = (rijsq + riksq - rjksq) / (2 * rij * rik); double dcos_dij = (rijsq - riksq + rjksq) / (2 * rijsq * rik); double dcos_dik = (riksq - rijsq + rjksq) / (2 * rij * riksq); double dcos_djk = -rjk / (rij * rik); double costerm; double dcosterm_dcos; double base = 1 + lambda * cos_ijk; // prevent numerical instability (when lambda=-1 and cos_ijk=1) if (base <= 0) { costerm = 0.0; dcosterm_dcos = 0.0; } else { double power = pow(base, zeta); double power_minus1 = power / base; costerm = power; dcosterm_dcos = zeta * power_minus1 * lambda; } double dcosterm_dij = dcosterm_dcos * dcos_dij; double dcosterm_dik = dcosterm_dcos * dcos_dik; double dcosterm_djk = dcosterm_dcos * dcos_djk; // exponential term double eterm = exp(-eta * (rijsq + riksq + rjksq)); double determ_dij = -2 * eterm * eta * rij; double determ_dik = -2 * eterm * eta * rik; double determ_djk = -2 * eterm * eta * rjk; // power 2 term double p2 = pow(2, 1 - zeta); // cutoff_func double fcij = cutoff_func_(rij, rcutij); double fcik = cutoff_func_(rik, rcutik); double fcjk = cutoff_func_(rjk, rcutjk); double fcprod = fcij * fcik * fcjk; double dfcprod_dij = d_cutoff_func_(rij, rcutij) * fcik * fcjk; double dfcprod_dik = d_cutoff_func_(rik, rcutik) * fcij * fcjk; double dfcprod_djk = d_cutoff_func_(rjk, rcutjk) * fcij * fcik; // phi phi = p2 * costerm * eterm * fcprod; // dphi_dij dphi[0] = p2 * (dcosterm_dij * eterm * fcprod + costerm * determ_dij * fcprod + costerm * eterm * dfcprod_dij); // dphi_dik dphi[1] = p2 * (dcosterm_dik * eterm * fcprod + costerm * determ_dik * fcprod + costerm * eterm * dfcprod_dik); // dphi_djk dphi[2] = p2 * (dcosterm_djk * eterm * fcprod + costerm * determ_djk * fcprod + costerm * eterm * dfcprod_djk); } } void Descriptor::sym_g5(double zeta, double lambda, double eta, const double * r, const double * rcut, double & phi) { double rij = r[0]; double rik = r[1]; double rjk = r[2]; double rcutij = rcut[0]; double rcutik = rcut[1]; double rijsq = rij * rij; double riksq = rik * rik; double rjksq = rjk * rjk; if (rij > rcutij || rik > rcutik) { phi = 0.0; } else { // i is the apex atom double cos_ijk = (rijsq + riksq - rjksq) / (2 * rij * rik); double costerm; double base = 1 + lambda * cos_ijk; // prevent numerical instability (when lambda=-1 and cos_ijk=1) if (base <= 0) { costerm = 0; } else { costerm = pow(base, zeta); } double eterm = exp(-eta * (rijsq + riksq)); phi = pow(2, 1 - zeta) * costerm * eterm * cutoff_func_(rij, rcutij) * cutoff_func_(rik, rcutik); } } void Descriptor::sym_d_g5(double zeta, double lambda, double eta, const double * r, const double * rcut, double & phi, double * const dphi) { double rij = r[0]; double rik = r[1]; double rjk = r[2]; double rcutij = rcut[0]; double rcutik = rcut[1]; double rijsq = rij * rij; double riksq = rik * rik; double rjksq = rjk * rjk; if (rij > rcutij || rik > rcutik) { phi = 0.0; dphi[0] = 0.0; dphi[1] = 0.0; dphi[2] = 0.0; } else { // cosine term, i is the apex atom double cos_ijk = (rijsq + riksq - rjksq) / (2 * rij * rik); double dcos_dij = (rijsq - riksq + rjksq) / (2 * rijsq * rik); double dcos_dik = (riksq - rijsq + rjksq) / (2 * rij * riksq); double dcos_djk = -rjk / (rij * rik); double costerm; double dcosterm_dcos; double base = 1 + lambda * cos_ijk; // prevent numerical instability (when lambda=-1 and cos_ijk=1) if (base <= 0) { costerm = 0.0; dcosterm_dcos = 0.0; } else { costerm = pow(base, zeta); dcosterm_dcos = zeta * pow(base, zeta - 1) * lambda; } double dcosterm_dij = dcosterm_dcos * dcos_dij; double dcosterm_dik = dcosterm_dcos * dcos_dik; double dcosterm_djk = dcosterm_dcos * dcos_djk; // exponential term double eterm = exp(-eta * (rijsq + riksq)); double determ_dij = -2 * eterm * eta * rij; double determ_dik = -2 * eterm * eta * rik; // power 2 term double p2 = pow(2, 1 - zeta); // cutoff_func double fcij = cutoff_func_(rij, rcutij); double fcik = cutoff_func_(rik, rcutik); double fcprod = fcij * fcik; double dfcprod_dij = d_cutoff_func_(rij, rcutij) * fcik; double dfcprod_dik = d_cutoff_func_(rik, rcutik) * fcij; // phi phi = p2 * costerm * eterm * fcprod; // dphi_dij dphi[0] = p2 * (dcosterm_dij * eterm * fcprod + costerm * determ_dij * fcprod + costerm * eterm * dfcprod_dij); // dphi_dik dphi[1] = p2 * (dcosterm_dik * eterm * fcprod + costerm * determ_dik * fcprod + costerm * eterm * dfcprod_dik); // dphi_djk dphi[2] = p2 * dcosterm_djk * eterm * fcprod; } }