//
// 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) 2018--2021, Regents of the University of Minnesota.
// All rights reserved.
//
// Contributors:
//    Mingjian Wen
//    Yaser Afshar
//


#ifndef STILLINGER_WEBER_IMPLEMENTATION_HPP_
#define STILLINGER_WEBER_IMPLEMENTATION_HPP_

#include "StillingerWeber.hpp"

#include "KIM_LogMacros.hpp"

#include "helper.hpp"

#include <cmath>

#include <vector>

#define ONE 1.0
#define HALF 0.5

#define MAX_PARAMETER_FILES 1


//==============================================================================
//
// Declaration of StillingerWeberImplementation class
//
//==============================================================================

//******************************************************************************
class StillingerWeberImplementation
{
 public:
  StillingerWeberImplementation(
    KIM::ModelDriverCreate * const modelDriverCreate,
    KIM::LengthUnit const requestedLengthUnit,
    KIM::EnergyUnit const requestedEnergyUnit,
    KIM::ChargeUnit const requestedChargeUnit,
    KIM::TemperatureUnit const requestedTemperatureUnit,
    KIM::TimeUnit const requestedTimeUnit,
    int * const ier);

  ~StillingerWeberImplementation();  // no explicit Destroy() needed here

  int Refresh(KIM::ModelRefresh * const modelRefresh);

  int Compute(KIM::ModelCompute const * const modelCompute,
              KIM::ModelComputeArguments const * const modelComputeArguments);

  int ComputeArgumentsCreate(KIM::ModelComputeArgumentsCreate * const
                             modelComputeArgumentsCreate) const;

  int ComputeArgumentsDestroy(KIM::ModelComputeArgumentsDestroy * const
                              modelComputeArgumentsDestroy) const;

  int WriteParameterizedModel(KIM::ModelWriteParameterizedModel const * const
                              modelWriteParameterizedModel) const;

 private:
  // Constant values that never change
  //   Set in constructor (via SetConstantValues)
  //
  //
  // StillingerWeberImplementation: constants
  int numberModelSpecies_;
  std::vector<int> modelSpeciesCodeList_;
  std::vector<std::string> modelSpeciesStringList_;
  int numberUniqueSpeciesPairs_;

  // Constant values that are read from the input files and never change
  //   Set in constructor (via functions listed below)
  //
  //
  // Private Model Parameters
  //   Memory allocated in AllocatePrivateParameterMemory() (from constructor)
  //   Memory deallocated in destructor
  //   Data set in ReadParameterFile routines
  // none
  //
  // KIM API: Model Parameters whose (pointer) values never change
  //   Memory allocated in AllocateParameterMemory() (from constructor)
  //   Memory deallocated in destructor
  //   Data set in ReadParameterFile routines OR by KIM Simulator
  double * cutoff_;
  double * A_;
  double * B_;
  double * p_;
  double * q_;
  double * sigma_;
  double * lambda_;
  double * gamma_;
  double * costheta0_;

  // Mutable values that only change when Refresh() executes
  //   Set in Refresh (via SetRefreshMutableValues)
  //
  //
  // KIM API: Model Parameters (can be changed directly by KIM Simulator)
  // none
  //
  // StillingerWeberImplementation: values (changed only by Refresh())
  double influenceDistance_;
  int modelWillNotRequestNeighborsOfNoncontributingParticles_;

  double ** cutoffSq_2D_;
  double ** A_2D_;
  double ** B_2D_;
  double ** p_2D_;
  double ** q_2D_;
  double ** sigma_2D_;
  double ** lambda_2D_;
  double ** gamma_2D_;
  double ** costheta0_2D_;

  // Mutable values that can change with each call to Refresh() and Compute()
  //   Memory may be reallocated on each call
  //
  //
  // StillingerWeberImplementation: values that change
  int cachedNumberOfParticles_;

  // Helper methods
  //
  //
  // Related to constructor
  void AllocatePrivateParameterMemory();
  void AllocateParameterMemory();

  static int
  OpenParameterFiles(KIM::ModelDriverCreate * const modelDriverCreate,
                     int const numberParameterFiles,
                     FILE * parameterFilePointers[MAX_PARAMETER_FILES]);

  int ProcessParameterFiles(
      KIM::ModelDriverCreate * const modelDriverCreate,
      FILE * const parameterFilePointers[MAX_PARAMETER_FILES]);

  void getNextDataLine(FILE * const filePtr,
                       char * const nextLine,
                       int const maxSize,
                       int * endOfFileFlag);

  static void
  CloseParameterFiles(int const numberParameterFiles,
                      FILE * const parameterFilePointers[MAX_PARAMETER_FILES]);

  int ConvertUnits(KIM::ModelDriverCreate * const modelDriverCreate,
                   KIM::LengthUnit const requestedLengthUnit,
                   KIM::EnergyUnit const requestedEnergyUnit,
                   KIM::ChargeUnit const requestedChargeUnit,
                   KIM::TemperatureUnit const requestedTemperatureUnit,
                   KIM::TimeUnit const requestedTimeUnit);

  int RegisterKIMModelSettings(
      KIM::ModelDriverCreate * const modelDriverCreate) const;

  int RegisterKIMComputeArgumentsSettings(
      KIM::ModelComputeArgumentsCreate * const modelComputeArgumentsCreate)
      const;

  int RegisterKIMParameters(KIM::ModelDriverCreate * const modelDriverCreate);

  int RegisterKIMFunctions(
      KIM::ModelDriverCreate * const modelDriverCreate) const;

  //
  // Related to Refresh()
  template<class ModelObj>
  int SetRefreshMutableValues(ModelObj * const modelObj);

  //
  // Related to Compute()
  int SetComputeMutableValues(
      KIM::ModelComputeArguments const * const modelComputeArguments,
      bool & isComputeProcess_dEdr,
      bool & isComputeProcess_d2Edr2,
      bool & isComputeEnergy,
      bool & isComputeForces,
      bool & isComputeParticleEnergy,
      bool & isComputeVirial,
      bool & isComputeParticleVirial,
      int const *& particleSpeciesCodes,
      int const *& particleContributing,
      VectorOfSizeDIM const *& coordinates,
      double *& energy,
      VectorOfSizeDIM *& forces,
      double *& particleEnergy,
      VectorOfSizeSix *& virial,
      VectorOfSizeSix *& particleViral);

  int CheckParticleSpeciesCodes(KIM::ModelCompute const * const modelCompute,
                                int const * const particleSpeciesCodes) const;

  int GetComputeIndex(const bool & isComputeProcess_dEdr,
                      const bool & isComputeProcess_d2Edr2,
                      const bool & isComputeEnergy,
                      const bool & isComputeForces,
                      const bool & isComputeParticleEnergy,
                      const bool & isComputeVirial,
                      const bool & isComputeParticleVirial) const;

  // compute functions
  template<bool isComputeProcess_dEdr,
           bool isComputeProcess_d2Edr2,
           bool isComputeEnergy,
           bool isComputeForces,
           bool isComputeParticleEnergy,
           bool isComputeVirial,
           bool isComputeParticleVirial>
  int Compute(KIM::ModelCompute const * const modelCompute,
              KIM::ModelComputeArguments const * const modelComputeArguments,
              const int * const particleSpeciesCodes,
              const int * const particleContributing,
              const VectorOfSizeDIM * const coordinates,
              double * const energy,
              VectorOfSizeDIM * const forces,
              double * const particleEnergy,
              VectorOfSizeSix virial,
              VectorOfSizeSix * const particleVirial) const;


  // Stillinger-Weber functions
  void CalcPhiTwo(int const ispec,
                  int const jspec,
                  double const r_sq,
                  double const r,
                  double & phi) const;

  void CalcPhiDphiTwo(int const ispec,
                      int const jspec,
                      double const r_sq,
                      double const r,
                      double & phi,
                      double & dphi) const;

  void CalcPhiD2phiTwo(int const ispec,
                       int const jspec,
                       double const r_sq,
                       double const r,
                       double & phi,
                       double & dphi,
                       double & d2phi) const;

  void CalcPhiThree(int const ispec,
                    int const jspec,
                    int const kspec,
                    double const rij_sq,
                    double const rij,
                    double const rik_sq,
                    double const rik,
                    double const rjk_sq,
                    double const rjk,
                    double & phi) const;

  void CalcPhiDphiThree(int const ispec,
                        int const jspec,
                        int const kspec,
                        double const rij_sq,
                        double const rij,
                        double const rik_sq,
                        double const rik,
                        double const rjk_sq,
                        double const rjk,
                        double & phi,
                        double * const dphi) const;

  void CalcPhiD2phiThree(int const ispec,
                         int const jspec,
                         int const kspec,
                         double const rij_sq,
                         double const rij,
                         double const rik_sq,
                         double const rik,
                         double const rjk_sq,
                         double const rjk,
                         double & phi,
                         double * const dphi,
                         double * const d2phi) const;
};

//==============================================================================
//
// Definition of StillingerWeberImplementation::Compute functions
//
// NOTE: Here we rely on the compiler optimizations to prune dead code
//       after the template expansions.  This provides high efficiency
//       and easy maintenance.
//
//==============================================================================

#undef KIM_LOGGER_OBJECT_NAME
#define KIM_LOGGER_OBJECT_NAME modelCompute
template<bool isComputeProcess_dEdr,
         bool isComputeProcess_d2Edr2,
         bool isComputeEnergy,
         bool isComputeForces,
         bool isComputeParticleEnergy,
         bool isComputeVirial,
         bool isComputeParticleVirial>
int StillingerWeberImplementation::Compute(
  KIM::ModelCompute const * const modelCompute,
  KIM::ModelComputeArguments const * const modelComputeArguments,
  const int * const particleSpeciesCodes,
  const int * const particleContributing,
  const VectorOfSizeDIM * const coordinates,
  double * const energy,
  VectorOfSizeDIM * const forces,
  double * const particleEnergy,
  VectorOfSizeSix virial,
  VectorOfSizeSix * const particleVirial) const
{
  // everything is good
  int ier = false;

  if (!isComputeEnergy &&
      !isComputeParticleEnergy &&
      !isComputeForces &&
      !isComputeProcess_dEdr &&
      !isComputeProcess_d2Edr2 &&
      !isComputeVirial &&
      !isComputeParticleVirial)
  { return ier; }

  // initialize energy and forces
  if (isComputeEnergy) { *energy = 0.0; }

  if (isComputeForces)
  {
    for (int i = 0; i < cachedNumberOfParticles_; ++i)
    {
      forces[i][0] = 0.0;
      forces[i][1] = 0.0;
      forces[i][2] = 0.0;
    }
  }

  if (isComputeParticleEnergy)
  {
    for (int i = 0; i < cachedNumberOfParticles_; ++i)
    { particleEnergy[i] = 0.0; }
  }

  if (isComputeVirial)
  {
    virial[0] = 0.0;
    virial[1] = 0.0;
    virial[2] = 0.0;
    virial[3] = 0.0;
    virial[4] = 0.0;
    virial[5] = 0.0;
  }

  if (isComputeParticleVirial)
  {
    for (int i = 0; i < cachedNumberOfParticles_; ++i)
    {
      particleVirial[i][0] = 0.0;
      particleVirial[i][1] = 0.0;
      particleVirial[i][2] = 0.0;
      particleVirial[i][3] = 0.0;
      particleVirial[i][4] = 0.0;
      particleVirial[i][5] = 0.0;
    }
  }

  int numnei;
  int const * n1atom;

  // size of short neighbor list array
  int neighShortSize = 32;
  // short neighbor list array
  std::vector<int> neighShort(neighShortSize);
  // short neighbor list distance array
  std::vector<double> neighShortDist(neighShortSize);

  // calculate contribution from pair function
  for (int i = 0; i < cachedNumberOfParticles_; ++i)
  {
    // Setup loop over contributing particles
    if (!particleContributing[i]) { continue; }

    modelComputeArguments->GetNeighborList(0, i, &numnei, &n1atom);
    int const iSpecies = particleSpeciesCodes[i];
    int numshort = 0;

    double const xi = coordinates[i][0];
    double const yi = coordinates[i][1];
    double const zi = coordinates[i][2];

    // Setup loop over neighbors of current particle
    for (int jj = 0; jj < numnei; ++jj)
    {
      int const j = n1atom[jj];
      int const jSpecies = particleSpeciesCodes[j];

      double const xj = coordinates[j][0];
      double const yj = coordinates[j][1];
      double const zj = coordinates[j][2];

      // Compute rij
      VectorOfSizeDIM rij;
      rij[0] = xj - xi;
      rij[1] = yj - yi;
      rij[2] = zj - zi;

      // compute distance squared
      double const rij_sq =
        rij[0] * rij[0] + rij[1] * rij[1] + rij[2] * rij[2];

      if (rij_sq > cutoffSq_2D_[iSpecies][jSpecies]) { continue; }

      neighShort[numshort] = j;
      neighShortDist[numshort++] = rij_sq;

      if (numshort >= neighShortSize) {
        neighShortSize += neighShortSize / 2;
        neighShort.resize(neighShortSize);
        neighShortDist.resize(neighShortSize);
      }

      // two-body contributions
      int const contributing_j = particleContributing[j];

      // effective half list
      if (contributing_j && j < i) { continue; }

      double const rij_mag = std::sqrt(rij_sq);

      double phi_two = 0.0;
      double dEidr_two = 0.0;
      double d2Eidr2_two = 0.0;

      // Compute two body potenitals and its derivatives
      if (isComputeProcess_d2Edr2)
      {
        CalcPhiD2phiTwo(iSpecies, jSpecies, rij_sq, rij_mag,
                        phi_two, dEidr_two, d2Eidr2_two);

        if (!contributing_j)
        {
          dEidr_two *= HALF;
          d2Eidr2_two *= HALF;
        }
      }
      else if (isComputeProcess_dEdr ||
               isComputeForces ||
               isComputeVirial ||
               isComputeParticleVirial)
      {
        CalcPhiDphiTwo(iSpecies, jSpecies, rij_sq, rij_mag,
                       phi_two, dEidr_two);
        if (!contributing_j) { dEidr_two *= HALF; }
      }
      else if (isComputeEnergy || isComputeParticleEnergy)
      {
        CalcPhiTwo(iSpecies, jSpecies, rij_sq, rij_mag, phi_two);
      }

      // Contribution to energy
      if (isComputeEnergy)
      {
        if (contributing_j) { *energy += phi_two; }
        else { *energy += HALF * phi_two; }
      }

      // Contribution to particleEnergy
      if (isComputeParticleEnergy)
      {
        double const halfphi = HALF * phi_two;
        particleEnergy[i] += halfphi;
        if (contributing_j) { particleEnergy[j] += halfphi; }
      }

      // Contribution to forces
      if (isComputeForces || isComputeVirial || isComputeParticleVirial)
      {
        double const contrib0x = dEidr_two * rij[0] / rij_mag;
        double const contrib0y = dEidr_two * rij[1] / rij_mag;
        double const contrib0z = dEidr_two * rij[2] / rij_mag;

        if (isComputeForces)
        {
          forces[i][0] += contrib0x;
          forces[i][1] += contrib0y;
          forces[i][2] += contrib0z;

          forces[j][0] -= contrib0x;
          forces[j][1] -= contrib0y;
          forces[j][2] -= contrib0z;
        }

        // Contribution to virial || particleVirial
        if (isComputeVirial || isComputeParticleVirial)
        {
          // Virial has 6 components and is stored as a 6-element
          // vector in the following order: xx, yy, zz, yz, xz, xy.
          VectorOfSizeSix v;
          v[0] = contrib0x * rij[0];
          v[1] = contrib0y * rij[1];
          v[2] = contrib0z * rij[2];
          v[3] = contrib0y * rij[2];
          v[4] = contrib0x * rij[2];
          v[5] = contrib0x * rij[1];

          // Contribution to virial
          if (isComputeVirial) {
            virial[0] += v[0];
            virial[1] += v[1];
            virial[2] += v[2];
            virial[3] += v[3];
            virial[4] += v[4];
            virial[5] += v[5];
          } // (isComputeVirial)

          // Contribution to particleVirial
          if (isComputeParticleVirial)
          {
            v[0] *= HALF;
            v[1] *= HALF;
            v[2] *= HALF;
            v[3] *= HALF;
            v[4] *= HALF;
            v[5] *= HALF;

            particleVirial[i][0] += v[0];
            particleVirial[i][1] += v[1];
            particleVirial[i][2] += v[2];
            particleVirial[i][3] += v[3];
            particleVirial[i][4] += v[4];
            particleVirial[i][5] += v[5];

            particleVirial[j][0] += v[0];
            particleVirial[j][1] += v[1];
            particleVirial[j][2] += v[2];
            particleVirial[j][3] += v[3];
            particleVirial[j][4] += v[4];
            particleVirial[j][5] += v[5];
          } // (isComputeParticleVirial)
        }   // (isComputeVirial || isComputeParticleVirial)
      }     // (isComputeForces || isComputeVirial || isComputeParticleVirial)

      // Call process_dEdr
      if (isComputeProcess_dEdr)
      {
        ier = modelComputeArguments->ProcessDEDrTerm(
          dEidr_two, rij_mag, rij, i, j);
        if (ier)
        {
           LOG_ERROR("ProcessDEdr");
           return ier;
        }
      } // (isComputeProcess_dEdr)

      // Call process_d2Edr2
      if (isComputeProcess_d2Edr2)
      {
        double const R_pairs[2] = {rij_mag, rij_mag};
        double const * const pRs = &R_pairs[0];
        double const Rij_pairs[6] =
          {rij[0], rij[1], rij[2], rij[0], rij[1], rij[2]};
        double const * const pRijConsts = &Rij_pairs[0];
        int const i_pairs[2] = {i, i};
        int const j_pairs[2] = {j, j};
        int const * const pis = &i_pairs[0];
        int const * const pjs = &j_pairs[0];

        ier = modelComputeArguments->ProcessD2EDr2Term(
          d2Eidr2_two, pRs, pRijConsts, pis, pjs);
        if (ier)
        {
          LOG_ERROR("ProcessD2Edr2");
          return ier;
        }
      } // (isComputeProcess_d2Edr2)
    }   // (int jj = 0; jj < numnei; ++jj)

    // Setup loop over neighbors of current particle
    for (int jj = 0; jj < numshort - 1; ++jj)
    {
      double const rij_sq = neighShortDist[jj];
      double const rij_mag = std::sqrt(rij_sq);

      int const j = neighShort[jj];
      int const jSpecies = particleSpeciesCodes[j];

      double const xj = coordinates[j][0];
      double const yj = coordinates[j][1];
      double const zj = coordinates[j][2];

      // Compute rij
      VectorOfSizeDIM rij;
      rij[0] = xj - xi;
      rij[1] = yj - yi;
      rij[2] = zj - zi;

      // three-body contribution
      for (int kk = jj + 1; kk < numshort; ++kk)
      {
        double const rik_sq = neighShortDist[kk];
        double const rik_mag = std::sqrt(rik_sq);

        int const k = neighShort[kk];
        int const kSpecies = particleSpeciesCodes[k];

        // Compute rik and rjk vector
        VectorOfSizeDIM rik;
        VectorOfSizeDIM rjk;
        {
          double const xk = coordinates[k][0];
          double const yk = coordinates[k][1];
          double const zk = coordinates[k][2];

          rik[0] = xk - xi;
          rik[1] = yk - yi;
          rik[2] = zk - zi;

          // Compute rjk
          rjk[0] = xk - xj;
          rjk[1] = yk - yj;
          rjk[2] = zk - zj;
        }

        double const rjk_sq =
          rjk[0] * rjk[0] + rjk[1] * rjk[1] + rjk[2] * rjk[2];
        double const rjk_mag = std::sqrt(rjk_sq);

        // compute energy and force

        // three-body contributions
        double phi_three = 0.0;
        VectorOfSizeDIM dEidr_three = {0.0, 0.0, 0.0};
        VectorOfSizeSix d2Eidr2_three = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

        // compute three-body potential and its derivatives
        if (isComputeProcess_d2Edr2)
        {
          CalcPhiD2phiThree(iSpecies,
                            jSpecies,
                            kSpecies,
                            rij_sq,
                            rij_mag,
                            rik_sq,
                            rik_mag,
                            rjk_sq,
                            rjk_mag,
                            phi_three,
                            dEidr_three,
                            d2Eidr2_three);
        }
        else if (isComputeProcess_dEdr ||
                 isComputeForces ||
                 isComputeVirial ||
                 isComputeParticleVirial)
        {
          CalcPhiDphiThree(iSpecies,
                           jSpecies,
                           kSpecies,
                           rij_sq,
                           rij_mag,
                           rik_sq,
                           rik_mag,
                           rjk_sq,
                           rjk_mag,
                           phi_three,
                           dEidr_three);
        }
        else if (isComputeEnergy || isComputeParticleEnergy)
        {
          CalcPhiThree(iSpecies,
                       jSpecies,
                       kSpecies,
                       rij_sq,
                       rij_mag,
                       rik_sq,
                       rik_mag,
                       rjk_sq,
                       rjk_mag,
                       phi_three);
        }

        // Contribution to energy
        if (isComputeEnergy) { *energy += phi_three; }

        // Contribution to particleEnergy
        if (isComputeParticleEnergy) { particleEnergy[i] += phi_three; }

        // Contribution to forces
        if (isComputeForces || isComputeVirial || isComputeParticleVirial)
        {
          double const contrib0x = dEidr_three[0] * rij[0] / rij_mag;
          double const contrib0y = dEidr_three[0] * rij[1] / rij_mag;
          double const contrib0z = dEidr_three[0] * rij[2] / rij_mag;

          double const contrib1x = dEidr_three[1] * rik[0] / rik_mag;
          double const contrib1y = dEidr_three[1] * rik[1] / rik_mag;
          double const contrib1z = dEidr_three[1] * rik[2] / rik_mag;

          double const contrib2x = dEidr_three[2] * rjk[0] / rjk_mag;
          double const contrib2y = dEidr_three[2] * rjk[1] / rjk_mag;
          double const contrib2z = dEidr_three[2] * rjk[2] / rjk_mag;

          if (isComputeForces)
          {
            forces[i][0] += contrib0x + contrib1x;
            forces[i][1] += contrib0y + contrib1y;
            forces[i][2] += contrib0z + contrib1z;

            forces[j][0] += -contrib0x + contrib2x;
            forces[j][1] += -contrib0y + contrib2y;
            forces[j][2] += -contrib0z + contrib2z;

            forces[k][0] -= contrib2x + contrib1x;
            forces[k][1] -= contrib2y + contrib1y;
            forces[k][2] -= contrib2z + contrib1z;
          } // (isComputeForces)

          // Contribution to virial || particleVirial
          if (isComputeVirial || isComputeParticleVirial)
          {
            // Virial has 6 components and is stored as a 6-element
            // vector in the following order: xx, yy, zz, yz, xz, xy.
            VectorOfSizeSix v;
            v[0] = contrib0x * rij[0] + contrib1x * rik[0] + contrib2x * rjk[0];
            v[1] = contrib0y * rij[1] + contrib1y * rik[1] + contrib2y * rjk[1];
            v[2] = contrib0z * rij[2] + contrib1z * rik[2] + contrib2z * rjk[2];
            v[3] = contrib0y * rij[2] + contrib1y * rik[2] + contrib2y * rjk[2];
            v[4] = contrib0x * rij[2] + contrib1x * rik[2] + contrib2x * rjk[2];
            v[5] = contrib0x * rij[1] + contrib1x * rik[1] + contrib2x * rjk[1];

            // Contribution to virial
            if (isComputeVirial)
            {
              virial[0] += v[0];
              virial[1] += v[1];
              virial[2] += v[2];
              virial[3] += v[3];
              virial[4] += v[4];
              virial[5] += v[5];
            } // (isComputeVirial)

            // Contribution to particleVirial
            if (isComputeParticleVirial)
            {
              v[0] *= HALF;
              v[1] *= HALF;
              v[2] *= HALF;
              v[3] *= HALF;
              v[4] *= HALF;
              v[5] *= HALF;

              particleVirial[i][0] += v[0];
              particleVirial[i][1] += v[1];
              particleVirial[i][2] += v[2];
              particleVirial[i][3] += v[3];
              particleVirial[i][4] += v[4];
              particleVirial[i][5] += v[5];

              particleVirial[j][0] += v[0];
              particleVirial[j][1] += v[1];
              particleVirial[j][2] += v[2];
              particleVirial[j][3] += v[3];
              particleVirial[j][4] += v[4];
              particleVirial[j][5] += v[5];
            } // (isComputeParticleVirial)
          }   // (isComputeVirial || isComputeParticleVirial)
        }     // (isComputeForces || isComputeVirial || isComputeParticleVirial)

        // Call process_dEdr
        if (isComputeProcess_dEdr)
        {
          ier =
            modelComputeArguments->ProcessDEDrTerm(
              dEidr_three[0], rij_mag, rij, i, j) ||
            modelComputeArguments->ProcessDEDrTerm(
              dEidr_three[1], rik_mag, rik, i, k) ||
            modelComputeArguments->ProcessDEDrTerm(
              dEidr_three[2], rjk_mag, rjk, j, k);
          if (ier)
          {
            LOG_ERROR("ProcessDEdr");
            return ier;
          }
        } // (isComputeProcess_dEdr)

        // Call process_d2Edr2
        if (isComputeProcess_d2Edr2)
        {
          double R_pairs[2];
          double Rij_pairs[6];
          int i_pairs[2];
          int j_pairs[2];
          double * const pRs = &R_pairs[0];
          double * const pRijConsts = &Rij_pairs[0];
          int * const pis = &i_pairs[0];
          int * const pjs = &j_pairs[0];

          R_pairs[0] = R_pairs[1] = rij_mag;
          Rij_pairs[0] = Rij_pairs[3] = rij[0];
          Rij_pairs[1] = Rij_pairs[4] = rij[1];
          Rij_pairs[2] = Rij_pairs[5] = rij[2];
          i_pairs[0] = i_pairs[1] = i;
          j_pairs[0] = j_pairs[1] = j;

          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[0], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = R_pairs[1] = rik_mag;
          Rij_pairs[0] = Rij_pairs[3] = rik[0];
          Rij_pairs[1] = Rij_pairs[4] = rik[1];
          Rij_pairs[2] = Rij_pairs[5] = rik[2];
          i_pairs[0] = i_pairs[1] = i;
          j_pairs[0] = j_pairs[1] = k;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[1], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = R_pairs[1] = rjk_mag;
          Rij_pairs[0] = Rij_pairs[3] = rjk[0];
          Rij_pairs[1] = Rij_pairs[4] = rjk[1];
          Rij_pairs[2] = Rij_pairs[5] = rjk[2];
          i_pairs[0] = i_pairs[1] = j;
          j_pairs[0] = j_pairs[1] = k;

          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[2], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rij_mag;
          R_pairs[1] = rik_mag;
          Rij_pairs[0] = rij[0];
          Rij_pairs[1] = rij[1];
          Rij_pairs[2] = rij[2];
          Rij_pairs[3] = rik[0];
          Rij_pairs[4] = rik[1];
          Rij_pairs[5] = rik[2];
          i_pairs[0] = i;
          j_pairs[0] = j;
          i_pairs[1] = i;
          j_pairs[1] = k;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[3], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rik_mag;
          R_pairs[1] = rij_mag;
          Rij_pairs[0] = rik[0];
          Rij_pairs[1] = rik[1];
          Rij_pairs[2] = rik[2];
          Rij_pairs[3] = rij[0];
          Rij_pairs[4] = rij[1];
          Rij_pairs[5] = rij[2];
          i_pairs[0] = i;
          j_pairs[0] = k;
          i_pairs[1] = i;
          j_pairs[1] = j;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[3], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rij_mag;
          R_pairs[1] = rjk_mag;
          Rij_pairs[0] = rij[0];
          Rij_pairs[1] = rij[1];
          Rij_pairs[2] = rij[2];
          Rij_pairs[3] = rjk[0];
          Rij_pairs[4] = rjk[1];
          Rij_pairs[5] = rjk[2];
          i_pairs[0] = i;
          j_pairs[0] = j;
          i_pairs[1] = j;
          j_pairs[1] = k;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[4], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rjk_mag;
          R_pairs[1] = rij_mag;
          Rij_pairs[0] = rjk[0];
          Rij_pairs[1] = rjk[1];
          Rij_pairs[2] = rjk[2];
          Rij_pairs[3] = rij[0];
          Rij_pairs[4] = rij[1];
          Rij_pairs[5] = rij[2];
          i_pairs[0] = j;
          j_pairs[0] = k;
          i_pairs[1] = i;
          j_pairs[1] = j;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[4], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rik_mag;
          R_pairs[1] = rjk_mag;
          Rij_pairs[0] = rik[0];
          Rij_pairs[1] = rik[1];
          Rij_pairs[2] = rik[2];
          Rij_pairs[3] = rjk[0];
          Rij_pairs[4] = rjk[1];
          Rij_pairs[5] = rjk[2];
          i_pairs[0] = i;
          j_pairs[0] = k;
          i_pairs[1] = j;
          j_pairs[1] = k;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[5], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }

          R_pairs[0] = rjk_mag;
          R_pairs[1] = rik_mag;
          Rij_pairs[0] = rjk[0];
          Rij_pairs[1] = rjk[1];
          Rij_pairs[2] = rjk[2];
          Rij_pairs[3] = rik[0];
          Rij_pairs[4] = rik[1];
          Rij_pairs[5] = rik[2];
          i_pairs[0] = j;
          j_pairs[0] = k;
          i_pairs[1] = i;
          j_pairs[1] = k;
          ier = modelComputeArguments->ProcessD2EDr2Term(
            d2Eidr2_three[5], pRs, pRijConsts, pis, pjs);
          if (ier)
          {
            LOG_ERROR("ProcessD2Edr2");
            return ier;
          }
        }  // (isComputeProcess_d2Edr2)
      }    // (int kk = jj + 1; kk < numshort; ++kk)
    }      // (int jj = 0; jj < numshort - 1; ++jj)
  }        // (i = 0; i < cachedNumberOfParticles_; ++i)

  return ier;
}

#undef HALF

#endif  // STILLINGER_WEBER_IMPLEMENTATION_HPP_
