/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/, 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: Mike Brown (SNL)
------------------------------------------------------------------------- */

#include "pair_resquared.h"

#include "atom.h"
#include "atom_vec_ellipsoid.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "math_extra.h"
#include "memory.h"
#include "neigh_list.h"
#include "neighbor.h"

#include <cmath>

using namespace LAMMPS_NS;

/* ---------------------------------------------------------------------- */

PairRESquared::PairRESquared(LAMMPS *lmp) :
    Pair(lmp), cr60(pow(60.0, 1.0 / 3.0)), b_alpha(45.0 / 56.0)
{
  single_enable = 0;

  cr60 = pow(60.0, 1.0 / 3.0);
  b_alpha = 45.0 / 56.0;
  solv_f_a = 3.0 / (16.0 * atan(1.0) * -36.0);
  solv_f_r = 3.0 / (16.0 * atan(1.0) * 2025.0);
}

/* ----------------------------------------------------------------------
   free all arrays
------------------------------------------------------------------------- */

PairRESquared::~PairRESquared()
{
  if (allocated) {
    memory->destroy(setflag);
    memory->destroy(cutsq);

    memory->destroy(form);
    memory->destroy(epsilon);
    memory->destroy(sigma);
    memory->destroy(shape1);
    memory->destroy(shape2);
    memory->destroy(well);
    memory->destroy(cut);
    memory->destroy(lj1);
    memory->destroy(lj2);
    memory->destroy(lj3);
    memory->destroy(lj4);
    memory->destroy(offset);
    delete[] lshape;
    delete[] setwell;
  }
}

/* ---------------------------------------------------------------------- */

void PairRESquared::compute(int eflag, int vflag)
{
  int i, j, ii, jj, inum, jnum, itype, jtype;
  double evdwl, one_eng, rsq, r2inv, r6inv, forcelj, factor_lj;
  double fforce[3], ttor[3], rtor[3], r12[3];
  int *ilist, *jlist, *numneigh, **firstneigh;
  RE2Vars wi, wj;

  evdwl = 0.0;
  ev_init(eflag, vflag);

  double **x = atom->x;
  double **f = atom->f;
  double **tor = atom->torque;
  int *type = atom->type;
  int nlocal = atom->nlocal;
  double *special_lj = force->special_lj;
  int newton_pair = force->newton_pair;

  inum = list->inum;
  ilist = list->ilist;
  numneigh = list->numneigh;
  firstneigh = list->firstneigh;

  // loop over neighbors of my atoms

  for (ii = 0; ii < inum; ii++) {
    i = ilist[ii];
    itype = type[i];

    // not a LJ sphere

    if (lshape[itype] != 0.0) precompute_i(i, wi);

    jlist = firstneigh[i];
    jnum = numneigh[i];

    for (jj = 0; jj < jnum; jj++) {
      j = jlist[jj];
      factor_lj = special_lj[sbmask(j)];
      j &= NEIGHMASK;

      // r12 = center to center vector

      r12[0] = x[j][0] - x[i][0];
      r12[1] = x[j][1] - x[i][1];
      r12[2] = x[j][2] - x[i][2];
      rsq = MathExtra::dot3(r12, r12);
      jtype = type[j];

      // compute if less than cutoff

      if (rsq < cutsq[itype][jtype]) {
        fforce[0] = fforce[1] = fforce[2] = 0.0;

        switch (form[itype][jtype]) {

          case SPHERE_SPHERE:
            r2inv = 1.0 / rsq;
            r6inv = r2inv * r2inv * r2inv;
            forcelj = r6inv * (lj1[itype][jtype] * r6inv - lj2[itype][jtype]);
            forcelj *= -r2inv;
            if (eflag) {
              one_eng = r6inv * (r6inv * lj3[itype][jtype] - lj4[itype][jtype]);
              one_eng -= offset[itype][jtype];
            }
            fforce[0] = r12[0] * forcelj;
            fforce[1] = r12[1] * forcelj;
            fforce[2] = r12[2] * forcelj;
            break;

          case SPHERE_ELLIPSE:
            precompute_i(j, wj);
            if (newton_pair || j < nlocal) {
              one_eng = resquared_lj(j, i, wj, r12, rsq, fforce, rtor, true);
              tor[j][0] += rtor[0] * factor_lj;
              tor[j][1] += rtor[1] * factor_lj;
              tor[j][2] += rtor[2] * factor_lj;
            } else
              one_eng = resquared_lj(j, i, wj, r12, rsq, fforce, rtor, false);
            break;

          case ELLIPSE_SPHERE:
            one_eng = resquared_lj(i, j, wi, r12, rsq, fforce, ttor, true);
            tor[i][0] += ttor[0] * factor_lj;
            tor[i][1] += ttor[1] * factor_lj;
            tor[i][2] += ttor[2] * factor_lj;
            break;

          default:
            precompute_i(j, wj);
            one_eng = resquared_analytic(i, j, wi, wj, r12, rsq, fforce, ttor, rtor);
            tor[i][0] += ttor[0] * factor_lj;
            tor[i][1] += ttor[1] * factor_lj;
            tor[i][2] += ttor[2] * factor_lj;
            if (newton_pair || j < nlocal) {
              tor[j][0] += rtor[0] * factor_lj;
              tor[j][1] += rtor[1] * factor_lj;
              tor[j][2] += rtor[2] * factor_lj;
            }
            break;
        }

        fforce[0] *= factor_lj;
        fforce[1] *= factor_lj;
        fforce[2] *= factor_lj;
        f[i][0] += fforce[0];
        f[i][1] += fforce[1];
        f[i][2] += fforce[2];

        if (newton_pair || j < nlocal) {
          f[j][0] -= fforce[0];
          f[j][1] -= fforce[1];
          f[j][2] -= fforce[2];
        }

        if (eflag) evdwl = factor_lj * one_eng;

        if (evflag)
          ev_tally_xyz(i, j, nlocal, newton_pair, evdwl, 0.0, fforce[0], fforce[1], fforce[2],
                       -r12[0], -r12[1], -r12[2]);
      }
    }
  }

  if (vflag_fdotr) virial_fdotr_compute();
}

/* ----------------------------------------------------------------------
   allocate all arrays
------------------------------------------------------------------------- */

void PairRESquared::allocate()
{
  allocated = 1;
  const int n = atom->ntypes + 1;

  memory->create(setflag, n, n, "pair:setflag");
  for (int i = 1; i < n; i++)
    for (int j = i; j < n; j++) setflag[i][j] = 0;

  memory->create(cutsq, n, n, "pair:cutsq");

  memory->create(form, n, n, "pair:form");
  memory->create(epsilon, n, n, "pair:epsilon");
  memory->create(sigma, n, n, "pair:sigma");
  memory->create(shape1, n, 3, "pair:shape1");
  memory->create(shape2, n, 3, "pair:shape2");
  memory->create(well, n, 3, "pair:well");
  memory->create(cut, n, n, "pair:cut");
  memory->create(lj1, n, n, "pair:lj1");
  memory->create(lj2, n, n, "pair:lj2");
  memory->create(lj3, n, n, "pair:lj3");
  memory->create(lj4, n, n, "pair:lj4");
  memory->create(offset, n, n, "pair:offset");

  lshape = new double[n];
  setwell = new int[n];
  for (int i = 1; i < n; i++) setwell[i] = 0;
}

/* ----------------------------------------------------------------------
   global settings
------------------------------------------------------------------------- */

void PairRESquared::settings(int narg, char **arg)
{
  if (narg != 1) error->all(FLERR, "Illegal pair_style command");

  cut_global = utils::numeric(FLERR, arg[0], false, lmp);

  // reset cutoffs that have been explicitly set

  if (allocated) {
    int i, j;
    for (i = 1; i <= atom->ntypes; i++)
      for (j = i; j <= atom->ntypes; j++)
        if (setflag[i][j]) cut[i][j] = cut_global;
  }
}

/* ----------------------------------------------------------------------
   set coeffs for one or more type pairs
------------------------------------------------------------------------- */

void PairRESquared::coeff(int narg, char **arg)
{
  if (narg < 10 || narg > 11) error->all(FLERR, "Incorrect args for pair coefficients");
  if (!allocated) allocate();

  int ilo, ihi, jlo, jhi;
  utils::bounds(FLERR, arg[0], 1, atom->ntypes, ilo, ihi, error);
  utils::bounds(FLERR, arg[1], 1, atom->ntypes, jlo, jhi, error);

  double epsilon_one = utils::numeric(FLERR, arg[2], false, lmp);
  double sigma_one = utils::numeric(FLERR, arg[3], false, lmp);
  double eia_one = utils::numeric(FLERR, arg[4], false, lmp);
  double eib_one = utils::numeric(FLERR, arg[5], false, lmp);
  double eic_one = utils::numeric(FLERR, arg[6], false, lmp);
  double eja_one = utils::numeric(FLERR, arg[7], false, lmp);
  double ejb_one = utils::numeric(FLERR, arg[8], false, lmp);
  double ejc_one = utils::numeric(FLERR, arg[9], false, lmp);

  double cut_one = cut_global;
  if (narg == 11) cut_one = utils::numeric(FLERR, arg[10], false, lmp);

  int count = 0;
  for (int i = ilo; i <= ihi; i++) {
    for (int j = MAX(jlo, i); j <= jhi; j++) {
      epsilon[i][j] = epsilon_one;
      sigma[i][j] = sigma_one;
      cut[i][j] = cut_one;
      if (eia_one != 0.0 || eib_one != 0.0 || eic_one != 0.0) {
        well[i][0] = eia_one;
        well[i][1] = eib_one;
        well[i][2] = eic_one;
        if (eia_one == 1.0 && eib_one == 1.0 && eic_one == 1.0)
          setwell[i] = 2;
        else
          setwell[i] = 1;
      }
      if (eja_one != 0.0 || ejb_one != 0.0 || ejc_one != 0.0) {
        well[j][0] = eja_one;
        well[j][1] = ejb_one;
        well[j][2] = ejc_one;
        if (eja_one == 1.0 && ejb_one == 1.0 && ejc_one == 1.0)
          setwell[j] = 2;
        else
          setwell[j] = 1;
      }
      setflag[i][j] = 1;
      count++;
    }
  }

  if (count == 0) error->all(FLERR, "Incorrect args for pair coefficients");
}

/* ----------------------------------------------------------------------
   init specific to this pair style
------------------------------------------------------------------------- */

void PairRESquared::init_style()
{
  avec = (AtomVecEllipsoid *) atom->style_match("ellipsoid");
  if (!avec) error->all(FLERR, "Pair resquared requires atom style ellipsoid");

  neighbor->request(this, instance_me);

  // per-type shape precalculations
  // require that atom shapes are identical within each type

  for (int i = 1; i <= atom->ntypes; i++) {
    if (!atom->shape_consistency(i, shape1[i][0], shape1[i][1], shape1[i][2]))
      error->all(FLERR, "Pair resquared requires atoms with same type have same shape");
    if (setwell[i]) {
      shape2[i][0] = shape1[i][0] * shape1[i][0];
      shape2[i][1] = shape1[i][1] * shape1[i][1];
      shape2[i][2] = shape1[i][2] * shape1[i][2];
      lshape[i] = shape1[i][0] * shape1[i][1] * shape1[i][2];
    }
  }
}

/* ----------------------------------------------------------------------
   init for one type pair i,j and corresponding j,i
------------------------------------------------------------------------- */

double PairRESquared::init_one(int i, int j)
{
  if (setwell[i] == 0 || setwell[j] == 0)
    error->all(FLERR, "Pair resquared epsilon a,b,c coeffs are not all set");

  int ishape = 0;
  if (shape1[i][0] != 0.0 && shape1[i][1] != 0.0 && shape1[i][2] != 0.0) ishape = 1;
  int jshape = 0;
  if (shape1[j][0] != 0.0 && shape1[j][1] != 0.0 && shape1[j][2] != 0.0) jshape = 1;

  if (ishape == 0 && jshape == 0) {
    form[i][j] = SPHERE_SPHERE;
    form[j][i] = SPHERE_SPHERE;
  } else if (ishape == 0) {
    form[i][j] = SPHERE_ELLIPSE;
    form[j][i] = ELLIPSE_SPHERE;
  } else if (jshape == 0) {
    form[i][j] = ELLIPSE_SPHERE;
    form[j][i] = SPHERE_ELLIPSE;
  } else {
    form[i][j] = ELLIPSE_ELLIPSE;
    form[j][i] = ELLIPSE_ELLIPSE;
  }

  // allow mixing only for LJ spheres

  if (setflag[i][j] == 0) {
    if (setflag[j][i] == 0) {
      if (ishape == 0 && jshape == 0) {
        epsilon[i][j] = mix_energy(epsilon[i][i], epsilon[j][j], sigma[i][i], sigma[j][j]);
        sigma[i][j] = mix_distance(sigma[i][i], sigma[j][j]);
        cut[i][j] = mix_distance(cut[i][i], cut[j][j]);
      } else
        error->all(FLERR, "Pair resquared epsilon and sigma coeffs are not all set");
    }
    epsilon[i][j] = epsilon[j][i];
    sigma[i][j] = sigma[j][i];
    cut[i][j] = cut[j][i];
  }

  lj1[i][j] = 48.0 * epsilon[i][j] * pow(sigma[i][j], 12.0);
  lj2[i][j] = 24.0 * epsilon[i][j] * pow(sigma[i][j], 6.0);
  lj3[i][j] = 4.0 * epsilon[i][j] * pow(sigma[i][j], 12.0);
  lj4[i][j] = 4.0 * epsilon[i][j] * pow(sigma[i][j], 6.0);

  if (offset_flag && (cut[i][j] > 0.0)) {
    double ratio = sigma[i][j] / cut[i][j];
    offset[i][j] = 4.0 * epsilon[i][j] * (pow(ratio, 12.0) - pow(ratio, 6.0));
  } else
    offset[i][j] = 0.0;

  epsilon[j][i] = epsilon[i][j];
  sigma[j][i] = sigma[i][j];
  lj1[j][i] = lj1[i][j];
  lj2[j][i] = lj2[i][j];
  lj3[j][i] = lj3[i][j];
  lj4[j][i] = lj4[i][j];
  offset[j][i] = offset[i][j];

  return cut[i][j];
}

/* ----------------------------------------------------------------------
   proc 0 writes to restart file
------------------------------------------------------------------------- */

void PairRESquared::write_restart(FILE *fp)
{
  write_restart_settings(fp);

  int i, j;
  for (i = 1; i <= atom->ntypes; i++) {
    fwrite(&setwell[i], sizeof(int), 1, fp);
    if (setwell[i]) fwrite(&well[i][0], sizeof(double), 3, fp);
    for (j = i; j <= atom->ntypes; j++) {
      fwrite(&setflag[i][j], sizeof(int), 1, fp);
      if (setflag[i][j]) {
        fwrite(&epsilon[i][j], sizeof(double), 1, fp);
        fwrite(&sigma[i][j], sizeof(double), 1, fp);
        fwrite(&cut[i][j], sizeof(double), 1, fp);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   proc 0 reads from restart file, bcasts
------------------------------------------------------------------------- */

void PairRESquared::read_restart(FILE *fp)
{
  read_restart_settings(fp);
  allocate();

  int i, j;
  int me = comm->me;
  for (i = 1; i <= atom->ntypes; i++) {
    if (me == 0) utils::sfread(FLERR, &setwell[i], sizeof(int), 1, fp, nullptr, error);
    MPI_Bcast(&setwell[i], 1, MPI_INT, 0, world);
    if (setwell[i]) {
      if (me == 0) utils::sfread(FLERR, &well[i][0], sizeof(double), 3, fp, nullptr, error);
      MPI_Bcast(&well[i][0], 3, MPI_DOUBLE, 0, world);
    }
    for (j = i; j <= atom->ntypes; j++) {
      if (me == 0) utils::sfread(FLERR, &setflag[i][j], sizeof(int), 1, fp, nullptr, error);
      MPI_Bcast(&setflag[i][j], 1, MPI_INT, 0, world);
      if (setflag[i][j]) {
        if (me == 0) {
          utils::sfread(FLERR, &epsilon[i][j], sizeof(double), 1, fp, nullptr, error);
          utils::sfread(FLERR, &sigma[i][j], sizeof(double), 1, fp, nullptr, error);
          utils::sfread(FLERR, &cut[i][j], sizeof(double), 1, fp, nullptr, error);
        }
        MPI_Bcast(&epsilon[i][j], 1, MPI_DOUBLE, 0, world);
        MPI_Bcast(&sigma[i][j], 1, MPI_DOUBLE, 0, world);
        MPI_Bcast(&cut[i][j], 1, MPI_DOUBLE, 0, world);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   proc 0 writes to restart file
------------------------------------------------------------------------- */

void PairRESquared::write_restart_settings(FILE *fp)
{
  fwrite(&cut_global, sizeof(double), 1, fp);
  fwrite(&mix_flag, sizeof(int), 1, fp);
}

/* ----------------------------------------------------------------------
   proc 0 reads from restart file, bcasts
------------------------------------------------------------------------- */

void PairRESquared::read_restart_settings(FILE *fp)
{
  int me = comm->me;
  if (me == 0) {
    utils::sfread(FLERR, &cut_global, sizeof(double), 1, fp, nullptr, error);
    utils::sfread(FLERR, &mix_flag, sizeof(int), 1, fp, nullptr, error);
  }
  MPI_Bcast(&cut_global, 1, MPI_DOUBLE, 0, world);
  MPI_Bcast(&mix_flag, 1, MPI_INT, 0, world);
}

/* ----------------------------------------------------------------------
   Precompute per-particle temporaries for RE-squared calculation
------------------------------------------------------------------------- */

void PairRESquared::precompute_i(const int i, RE2Vars &ws)
{
  double aTs[3][3];    // A1'*S1^2
  int *ellipsoid = atom->ellipsoid;
  AtomVecEllipsoid::Bonus *bonus = avec->bonus;
  MathExtra::quat_to_mat_trans(bonus[ellipsoid[i]].quat, ws.A);
  MathExtra::transpose_diag3(ws.A, well[atom->type[i]], ws.aTe);
  MathExtra::transpose_diag3(ws.A, shape2[atom->type[i]], aTs);
  MathExtra::diag_times3(shape2[atom->type[i]], ws.A, ws.sa);
  MathExtra::times3(aTs, ws.A, ws.gamma);
  MathExtra::rotation_generator_x(ws.A, ws.lA[0]);
  MathExtra::rotation_generator_y(ws.A, ws.lA[1]);
  MathExtra::rotation_generator_z(ws.A, ws.lA[2]);
  for (int m = 0; m < 3; m++) {
    MathExtra::times3(aTs, ws.lA[m], ws.lAtwo[m]);
    MathExtra::transpose_times3(ws.lA[m], ws.sa, ws.lAsa[m]);
    MathExtra::plus3(ws.lAsa[m], ws.lAtwo[m], ws.lAsa[m]);
  }
}

/* ----------------------------------------------------------------------
   Compute the derivative of the determinant of m, using m and the
   derivative of m (m2)
------------------------------------------------------------------------- */

// clang-format off
double PairRESquared::det_prime(const double m[3][3], const double m2[3][3])
{
  double ans;
  ans = m2[0][0]*m[1][1]*m[2][2] - m2[0][0]*m[1][2]*m[2][1] -
        m[1][0]*m2[0][1]*m[2][2] + m[1][0]*m2[0][2]*m[2][1] +
        m[2][0]*m2[0][1]*m[1][2] - m[2][0]*m2[0][2]*m[1][1] +
        m[0][0]*m2[1][1]*m[2][2] - m[0][0]*m2[1][2]*m[2][1] -
        m2[1][0]*m[0][1]*m[2][2] + m2[1][0]*m[0][2]*m[2][1] +
        m[2][0]*m[0][1]*m2[1][2] - m[2][0]*m[0][2]*m2[1][1] +
        m[0][0]*m[1][1]*m2[2][2] - m[0][0]*m[1][2]*m2[2][1] -
        m[1][0]*m[0][1]*m2[2][2] + m[1][0]*m[0][2]*m2[2][1] +
        m2[2][0]*m[0][1]*m[1][2] - m2[2][0]*m[0][2]*m[1][1];
  return ans;
}
// clang-format on

/* ----------------------------------------------------------------------
   Compute the energy, force, torque for a pair (INTEGRATED-INTEGRATED)
------------------------------------------------------------------------- */

double PairRESquared::resquared_analytic(const int i, const int j, const RE2Vars &wi,
                                         const RE2Vars &wj, const double *r, const double rsq,
                                         double *fforce, double *ttor, double *rtor)
{
  int *type = atom->type;

  // pair computations for energy, force, torque

  double z1[3], z2[3];          // A1*rhat  # don't need to store
  double v1[3], v2[3];          // inv(S1^2)*z1 # don't need to store
  double sigma1, sigma2;        // 1/sqrt(z1'*v1)
  double sigma1p2, sigma2p2;    // sigma1^2
  double rnorm;                 // L2 norm of r
  double rhat[3];               // r/rnorm
  double s[3];                  // inv(gamma1+gamma2)*rhat
  double sigma12;               // 1/sqrt(0.5*s'*rhat)
  double H12[3][3];             // gamma1/sigma1+gamma2/sigma2
  double dH;                    // det(H12)
  double lambda;                // dS1/sigma1p2+dS2/sigma2p2
  double nu;                    // sqrt(dH/(sigma1+sigma2))
  double w[3];                  // inv(A1'*E1*A1+A2'*E2*A2)*rhat
  double h12;                   // rnorm-sigma12;
  double eta;                   // lambda/nu
  double chi;                   // 2*rhat'*w
  double sprod;                 // dS1*dS2
  double sigh;                  // sigma/h12
  double tprod;                 // eta*chi*sigh
  double Ua, Ur;                // attractive/repulsive parts of potential

  // pair computations for force, torque

  double sec;                             // sigma*eta*chi
  double sigma1p3, sigma2p3;              // sigma1^3
  double vsigma1[3], vsigma2[3];          // sigma1^3*v1;
  double sigma12p3;                       // sigma12^3
  double gsigma1[3][3], gsigma2[3][3];    // -gamma1/sigma1^2
  double tsig1sig2;                       // eta/(2*(sigma1+sigma2))
  double tdH;                             // eta/(2*dH)
  double teta1, teta2;                    // 2*eta/lambda*dS1/sigma1p3
  double fourw[3];                        // 4*w;
  double spr[3];                          // 0.5*sigma12^3*s
  double hsec;                            // h12+[3,b_alpha]*sec
  double dspu;                            // 1/h12 - 1/hsec + temp
  double pbsu;                            // 3*sigma/hsec
  double dspr;                            // 7/h12-1/hsec+temp
  double pbsr;                            // b_alpha*sigma/hsec;
  double u[3];                            // (-rhat(i)*rhat+eye(:,i))/rnorm
  double u1[3], u2[3];                    // A1*u
  double dsigma1, dsigma2;                // u1'*vsigma1 (force) p'*vsigma1 (tor)
  double dH12[3][3];                      // dsigma1*gsigma1 + dsigma2*gsigma2
  double ddH;                             // derivative of det(H12)
  double deta, dchi, dh12;                // derivatives of eta,chi,h12
  double dUr, dUa;                        // derivatives of Ua,Ur

  // pair computations for torque

  double fwae[3];    // -fourw'*aTe
  double p[3];       // lA*rhat

  rnorm = sqrt(rsq);
  rhat[0] = r[0] / rnorm;
  rhat[1] = r[1] / rnorm;
  rhat[2] = r[2] / rnorm;

  // energy

  double temp[3][3];
  MathExtra::plus3(wi.gamma, wj.gamma, temp);
  int ierror = MathExtra::mldivide3(temp, rhat, s);
  if (ierror) error->all(FLERR, "Bad matrix inversion in mldivide3");

  sigma12 = 1.0 / sqrt(0.5 * MathExtra::dot3(s, rhat));
  MathExtra::matvec(wi.A, rhat, z1);
  MathExtra::matvec(wj.A, rhat, z2);
  v1[0] = z1[0] / shape2[type[i]][0];
  v1[1] = z1[1] / shape2[type[i]][1];
  v1[2] = z1[2] / shape2[type[i]][2];
  v2[0] = z2[0] / shape2[type[j]][0];
  v2[1] = z2[1] / shape2[type[j]][1];
  v2[2] = z2[2] / shape2[type[j]][2];
  sigma1 = 1.0 / sqrt(MathExtra::dot3(z1, v1));
  sigma2 = 1.0 / sqrt(MathExtra::dot3(z2, v2));
  H12[0][0] = wi.gamma[0][0] / sigma1 + wj.gamma[0][0] / sigma2;
  H12[0][1] = wi.gamma[0][1] / sigma1 + wj.gamma[0][1] / sigma2;
  H12[0][2] = wi.gamma[0][2] / sigma1 + wj.gamma[0][2] / sigma2;
  H12[1][0] = wi.gamma[1][0] / sigma1 + wj.gamma[1][0] / sigma2;
  H12[1][1] = wi.gamma[1][1] / sigma1 + wj.gamma[1][1] / sigma2;
  H12[1][2] = wi.gamma[1][2] / sigma1 + wj.gamma[1][2] / sigma2;
  H12[2][0] = wi.gamma[2][0] / sigma1 + wj.gamma[2][0] / sigma2;
  H12[2][1] = wi.gamma[2][1] / sigma1 + wj.gamma[2][1] / sigma2;
  H12[2][2] = wi.gamma[2][2] / sigma1 + wj.gamma[2][2] / sigma2;
  dH = MathExtra::det3(H12);
  sigma1p2 = sigma1 * sigma1;
  sigma2p2 = sigma2 * sigma2;
  lambda = lshape[type[i]] / sigma1p2 + lshape[type[j]] / sigma2p2;
  nu = sqrt(dH / (sigma1 + sigma2));
  MathExtra::times3(wi.aTe, wi.A, temp);
  double temp2[3][3];
  MathExtra::times3(wj.aTe, wj.A, temp2);
  MathExtra::plus3(temp, temp2, temp);
  ierror = MathExtra::mldivide3(temp, rhat, w);
  if (ierror) error->all(FLERR, "Bad matrix inversion in mldivide3");

  h12 = rnorm - sigma12;
  eta = lambda / nu;
  chi = 2.0 * MathExtra::dot3(rhat, w);
  sprod = lshape[type[i]] * lshape[type[j]];
  sigh = sigma[type[i]][type[j]] / h12;
  tprod = eta * chi * sigh;

  double stemp = h12 / 2.0;
  Ua = (shape1[type[i]][0] + stemp) * (shape1[type[i]][1] + stemp) * (shape1[type[i]][2] + stemp) *
      (shape1[type[j]][0] + stemp) * (shape1[type[j]][1] + stemp) * (shape1[type[j]][2] + stemp);
  Ua = (1.0 + 3.0 * tprod) * sprod / Ua;
  Ua = epsilon[type[i]][type[j]] * Ua / -36.0;

  stemp = h12 / cr60;
  Ur = (shape1[type[i]][0] + stemp) * (shape1[type[i]][1] + stemp) * (shape1[type[i]][2] + stemp) *
      (shape1[type[j]][0] + stemp) * (shape1[type[j]][1] + stemp) * (shape1[type[j]][2] + stemp);
  Ur = (1.0 + b_alpha * tprod) * sprod / Ur;
  Ur = epsilon[type[i]][type[j]] * Ur * pow(sigh, 6.0) / 2025.0;

  // force

  sec = sigma[type[i]][type[j]] * eta * chi;
  sigma12p3 = pow(sigma12, 3.0);
  sigma1p3 = sigma1p2 * sigma1;
  sigma2p3 = sigma2p2 * sigma2;
  vsigma1[0] = -sigma1p3 * v1[0];
  vsigma1[1] = -sigma1p3 * v1[1];
  vsigma1[2] = -sigma1p3 * v1[2];
  vsigma2[0] = -sigma2p3 * v2[0];
  vsigma2[1] = -sigma2p3 * v2[1];
  vsigma2[2] = -sigma2p3 * v2[2];
  gsigma1[0][0] = -wi.gamma[0][0] / sigma1p2;
  gsigma1[0][1] = -wi.gamma[0][1] / sigma1p2;
  gsigma1[0][2] = -wi.gamma[0][2] / sigma1p2;
  gsigma1[1][0] = -wi.gamma[1][0] / sigma1p2;
  gsigma1[1][1] = -wi.gamma[1][1] / sigma1p2;
  gsigma1[1][2] = -wi.gamma[1][2] / sigma1p2;
  gsigma1[2][0] = -wi.gamma[2][0] / sigma1p2;
  gsigma1[2][1] = -wi.gamma[2][1] / sigma1p2;
  gsigma1[2][2] = -wi.gamma[2][2] / sigma1p2;
  gsigma2[0][0] = -wj.gamma[0][0] / sigma2p2;
  gsigma2[0][1] = -wj.gamma[0][1] / sigma2p2;
  gsigma2[0][2] = -wj.gamma[0][2] / sigma2p2;
  gsigma2[1][0] = -wj.gamma[1][0] / sigma2p2;
  gsigma2[1][1] = -wj.gamma[1][1] / sigma2p2;
  gsigma2[1][2] = -wj.gamma[1][2] / sigma2p2;
  gsigma2[2][0] = -wj.gamma[2][0] / sigma2p2;
  gsigma2[2][1] = -wj.gamma[2][1] / sigma2p2;
  gsigma2[2][2] = -wj.gamma[2][2] / sigma2p2;
  tsig1sig2 = eta / (2.0 * (sigma1 + sigma2));
  tdH = eta / (2.0 * dH);
  teta1 = 2.0 * eta / lambda;
  teta2 = teta1 * lshape[type[j]] / sigma2p3;
  teta1 = teta1 * lshape[type[i]] / sigma1p3;
  fourw[0] = 4.0 * w[0];
  fourw[1] = 4.0 * w[1];
  fourw[2] = 4.0 * w[2];
  spr[0] = 0.5 * sigma12p3 * s[0];
  spr[1] = 0.5 * sigma12p3 * s[1];
  spr[2] = 0.5 * sigma12p3 * s[2];

  stemp = 1.0 / (shape1[type[i]][0] * 2.0 + h12) + 1.0 / (shape1[type[i]][1] * 2.0 + h12) +
      1.0 / (shape1[type[i]][2] * 2.0 + h12) + 1.0 / (shape1[type[j]][0] * 2.0 + h12) +
      1.0 / (shape1[type[j]][1] * 2.0 + h12) + 1.0 / (shape1[type[j]][2] * 2.0 + h12);
  hsec = h12 + 3.0 * sec;
  dspu = 1.0 / h12 - 1.0 / hsec + stemp;
  pbsu = 3.0 * sigma[type[i]][type[j]] / hsec;

  stemp = 1.0 / (shape1[type[i]][0] * cr60 + h12) + 1.0 / (shape1[type[i]][1] * cr60 + h12) +
      1.0 / (shape1[type[i]][2] * cr60 + h12) + 1.0 / (shape1[type[j]][0] * cr60 + h12) +
      1.0 / (shape1[type[j]][1] * cr60 + h12) + 1.0 / (shape1[type[j]][2] * cr60 + h12);
  hsec = h12 + b_alpha * sec;
  dspr = 7.0 / h12 - 1.0 / hsec + stemp;
  pbsr = b_alpha * sigma[type[i]][type[j]] / hsec;

  for (int m = 0; m < 3; m++) {
    u[0] = -rhat[m] * rhat[0];
    u[1] = -rhat[m] * rhat[1];
    u[2] = -rhat[m] * rhat[2];
    u[m] += 1.0;
    u[0] /= rnorm;
    u[1] /= rnorm;
    u[2] /= rnorm;
    MathExtra::matvec(wi.A, u, u1);
    MathExtra::matvec(wj.A, u, u2);
    dsigma1 = MathExtra::dot3(u1, vsigma1);
    dsigma2 = MathExtra::dot3(u2, vsigma2);
    dH12[0][0] = dsigma1 * gsigma1[0][0] + dsigma2 * gsigma2[0][0];
    dH12[0][1] = dsigma1 * gsigma1[0][1] + dsigma2 * gsigma2[0][1];
    dH12[0][2] = dsigma1 * gsigma1[0][2] + dsigma2 * gsigma2[0][2];
    dH12[1][0] = dsigma1 * gsigma1[1][0] + dsigma2 * gsigma2[1][0];
    dH12[1][1] = dsigma1 * gsigma1[1][1] + dsigma2 * gsigma2[1][1];
    dH12[1][2] = dsigma1 * gsigma1[1][2] + dsigma2 * gsigma2[1][2];
    dH12[2][0] = dsigma1 * gsigma1[2][0] + dsigma2 * gsigma2[2][0];
    dH12[2][1] = dsigma1 * gsigma1[2][1] + dsigma2 * gsigma2[2][1];
    dH12[2][2] = dsigma1 * gsigma1[2][2] + dsigma2 * gsigma2[2][2];
    ddH = det_prime(H12, dH12);
    deta = (dsigma1 + dsigma2) * tsig1sig2;
    deta -= ddH * tdH;
    deta -= dsigma1 * teta1 + dsigma2 * teta2;
    dchi = MathExtra::dot3(u, fourw);
    dh12 = rhat[m] + MathExtra::dot3(u, spr);
    dUa = pbsu * (eta * dchi + deta * chi) - dh12 * dspu;
    dUr = pbsr * (eta * dchi + deta * chi) - dh12 * dspr;
    fforce[m] = dUr * Ur + dUa * Ua;
  }

  // torque on i

  MathExtra::vecmat(fourw, wi.aTe, fwae);

  for (int i = 0; i < 3; i++) {
    MathExtra::matvec(wi.lA[i], rhat, p);
    dsigma1 = MathExtra::dot3(p, vsigma1);
    dH12[0][0] = wi.lAsa[i][0][0] / sigma1 + dsigma1 * gsigma1[0][0];
    dH12[0][1] = wi.lAsa[i][0][1] / sigma1 + dsigma1 * gsigma1[0][1];
    dH12[0][2] = wi.lAsa[i][0][2] / sigma1 + dsigma1 * gsigma1[0][2];
    dH12[1][0] = wi.lAsa[i][1][0] / sigma1 + dsigma1 * gsigma1[1][0];
    dH12[1][1] = wi.lAsa[i][1][1] / sigma1 + dsigma1 * gsigma1[1][1];
    dH12[1][2] = wi.lAsa[i][1][2] / sigma1 + dsigma1 * gsigma1[1][2];
    dH12[2][0] = wi.lAsa[i][2][0] / sigma1 + dsigma1 * gsigma1[2][0];
    dH12[2][1] = wi.lAsa[i][2][1] / sigma1 + dsigma1 * gsigma1[2][1];
    dH12[2][2] = wi.lAsa[i][2][2] / sigma1 + dsigma1 * gsigma1[2][2];
    ddH = det_prime(H12, dH12);
    deta = tsig1sig2 * dsigma1 - tdH * ddH;
    deta -= teta1 * dsigma1;
    double tempv[3];
    MathExtra::matvec(wi.lA[i], w, tempv);
    dchi = -MathExtra::dot3(fwae, tempv);
    MathExtra::matvec(wi.lAtwo[i], spr, tempv);
    dh12 = -MathExtra::dot3(s, tempv);

    dUa = pbsu * (eta * dchi + deta * chi) - dh12 * dspu;
    dUr = pbsr * (eta * dchi + deta * chi) - dh12 * dspr;
    ttor[i] = -(dUa * Ua + dUr * Ur);
  }

  // torque on j

  if (!(force->newton_pair || j < atom->nlocal)) return Ua + Ur;

  MathExtra::vecmat(fourw, wj.aTe, fwae);

  for (int i = 0; i < 3; i++) {
    MathExtra::matvec(wj.lA[i], rhat, p);
    dsigma2 = MathExtra::dot3(p, vsigma2);
    dH12[0][0] = wj.lAsa[i][0][0] / sigma2 + dsigma2 * gsigma2[0][0];
    dH12[0][1] = wj.lAsa[i][0][1] / sigma2 + dsigma2 * gsigma2[0][1];
    dH12[0][2] = wj.lAsa[i][0][2] / sigma2 + dsigma2 * gsigma2[0][2];
    dH12[1][0] = wj.lAsa[i][1][0] / sigma2 + dsigma2 * gsigma2[1][0];
    dH12[1][1] = wj.lAsa[i][1][1] / sigma2 + dsigma2 * gsigma2[1][1];
    dH12[1][2] = wj.lAsa[i][1][2] / sigma2 + dsigma2 * gsigma2[1][2];
    dH12[2][0] = wj.lAsa[i][2][0] / sigma2 + dsigma2 * gsigma2[2][0];
    dH12[2][1] = wj.lAsa[i][2][1] / sigma2 + dsigma2 * gsigma2[2][1];
    dH12[2][2] = wj.lAsa[i][2][2] / sigma2 + dsigma2 * gsigma2[2][2];
    ddH = det_prime(H12, dH12);
    deta = tsig1sig2 * dsigma2 - tdH * ddH;
    deta -= teta2 * dsigma2;
    double tempv[3];
    MathExtra::matvec(wj.lA[i], w, tempv);
    dchi = -MathExtra::dot3(fwae, tempv);
    MathExtra::matvec(wj.lAtwo[i], spr, tempv);
    dh12 = -MathExtra::dot3(s, tempv);

    dUa = pbsu * (eta * dchi + deta * chi) - dh12 * dspu;
    dUr = pbsr * (eta * dchi + deta * chi) - dh12 * dspr;
    rtor[i] = -(dUa * Ua + dUr * Ur);
  }

  return Ua + Ur;
}

/* ----------------------------------------------------------------------
   Compute the energy, force, torque for a pair (INTEGRATED-LJ)
------------------------------------------------------------------------- */

double PairRESquared::resquared_lj(const int i, const int j, const RE2Vars &wi, const double *r,
                                   const double rsq, double *fforce, double *ttor, bool calc_torque)
{
  int *type = atom->type;

  // pair computations for energy, force, torque

  double rnorm;      // L2 norm of r
  double rhat[3];    // r/rnorm
  double s[3];       // inv(gamma1)*rhat
  double sigma12;    // 1/sqrt(0.5*s'*rhat)
  double w[3];       // inv(A1'*E1*A1+I)*rhat
  double h12;        // rnorm-sigma12;
  double chi;        // 2*rhat'*w
  double sigh;       // sigma/h12
  double tprod;      // chi*sigh
  double Ua, Ur;     // attractive/repulsive parts of potential

  // pair computations for force, torque

  double sec;           // sigma*chi
  double sigma12p3;     // sigma12^3
  double fourw[3];      // 4*w;
  double spr[3];        // 0.5*sigma12^3*s
  double hsec;          // h12+[3,b_alpha]*sec
  double dspu;          // 1/h12 - 1/hsec + temp
  double pbsu;          // 3*sigma/hsec
  double dspr;          // 7/h12-1/hsec+temp
  double pbsr;          // b_alpha*sigma/hsec;
  double u[3];          // (-rhat(i)*rhat+eye(:,i))/rnorm
  double dchi, dh12;    // derivatives of chi,h12
  double dUr, dUa;      // derivatives of Ua,Ur
  double h12p3;         // h12^3

  // pair computations for torque

  double fwae[3];    // -fourw'*aTe
  double p[3];       // lA*rhat

  // distance of closest approach correction

  double aTs[3][3];         // A1'*S1^2
  double gamma[3][3];       // A1'*S1^2*A
  double lAtwo[3][3][3];    // A1'*S1^2*wi.lA
  double scorrect[3];
  double half_sigma = sigma[type[i]][type[j]] / 2.0;
  scorrect[0] = shape1[type[i]][0] + half_sigma;
  scorrect[1] = shape1[type[i]][1] + half_sigma;
  scorrect[2] = shape1[type[i]][2] + half_sigma;
  scorrect[0] = scorrect[0] * scorrect[0] / 2.0;
  scorrect[1] = scorrect[1] * scorrect[1] / 2.0;
  scorrect[2] = scorrect[2] * scorrect[2] / 2.0;
  MathExtra::transpose_diag3(wi.A, scorrect, aTs);
  MathExtra::times3(aTs, wi.A, gamma);
  for (int ii = 0; ii < 3; ii++) MathExtra::times3(aTs, wi.lA[ii], lAtwo[ii]);

  rnorm = sqrt(rsq);
  rhat[0] = r[0] / rnorm;
  rhat[1] = r[1] / rnorm;
  rhat[2] = r[2] / rnorm;

  // energy

  int ierror = MathExtra::mldivide3(gamma, rhat, s);
  if (ierror) error->all(FLERR, "Bad matrix inversion in mldivide3");

  sigma12 = 1.0 / sqrt(0.5 * MathExtra::dot3(s, rhat));
  double temp[3][3];
  MathExtra::times3(wi.aTe, wi.A, temp);
  temp[0][0] += 1.0;
  temp[1][1] += 1.0;
  temp[2][2] += 1.0;
  ierror = MathExtra::mldivide3(temp, rhat, w);
  if (ierror) error->all(FLERR, "Bad matrix inversion in mldivide3");

  h12 = rnorm - sigma12;
  chi = 2.0 * MathExtra::dot3(rhat, w);
  sigh = sigma[type[i]][type[j]] / h12;
  tprod = chi * sigh;

  h12p3 = pow(h12, 3.0);
  double sigmap3 = pow(sigma[type[i]][type[j]], 3.0);
  double stemp = h12 / 2.0;
  Ua = (shape1[type[i]][0] + stemp) * (shape1[type[i]][1] + stemp) * (shape1[type[i]][2] + stemp) *
      h12p3 / 8.0;
  Ua = (1.0 + 3.0 * tprod) * lshape[type[i]] / Ua;
  Ua = epsilon[type[i]][type[j]] * Ua * sigmap3 * solv_f_a;

  stemp = h12 / cr60;
  Ur = (shape1[type[i]][0] + stemp) * (shape1[type[i]][1] + stemp) * (shape1[type[i]][2] + stemp) *
      h12p3 / 60.0;
  Ur = (1.0 + b_alpha * tprod) * lshape[type[i]] / Ur;
  Ur = epsilon[type[i]][type[j]] * Ur * sigmap3 * pow(sigh, 6.0) * solv_f_r;

  // force

  sec = sigma[type[i]][type[j]] * chi;
  sigma12p3 = pow(sigma12, 3.0);
  fourw[0] = 4.0 * w[0];
  fourw[1] = 4.0 * w[1];
  fourw[2] = 4.0 * w[2];
  spr[0] = 0.5 * sigma12p3 * s[0];
  spr[1] = 0.5 * sigma12p3 * s[1];
  spr[2] = 0.5 * sigma12p3 * s[2];

  stemp = 1.0 / (shape1[type[i]][0] * 2.0 + h12) + 1.0 / (shape1[type[i]][1] * 2.0 + h12) +
      1.0 / (shape1[type[i]][2] * 2.0 + h12) + 3.0 / h12;
  hsec = h12 + 3.0 * sec;
  dspu = 1.0 / h12 - 1.0 / hsec + stemp;
  pbsu = 3.0 * sigma[type[i]][type[j]] / hsec;

  stemp = 1.0 / (shape1[type[i]][0] * cr60 + h12) + 1.0 / (shape1[type[i]][1] * cr60 + h12) +
      1.0 / (shape1[type[i]][2] * cr60 + h12) + 3.0 / h12;
  hsec = h12 + b_alpha * sec;
  dspr = 7.0 / h12 - 1.0 / hsec + stemp;
  pbsr = b_alpha * sigma[type[i]][type[j]] / hsec;

  for (int m = 0; m < 3; m++) {
    u[0] = -rhat[m] * rhat[0];
    u[1] = -rhat[m] * rhat[1];
    u[2] = -rhat[m] * rhat[2];
    u[m] += 1.0;
    u[0] /= rnorm;
    u[1] /= rnorm;
    u[2] /= rnorm;
    dchi = MathExtra::dot3(u, fourw);
    dh12 = rhat[m] + MathExtra::dot3(u, spr);
    dUa = pbsu * dchi - dh12 * dspu;
    dUr = pbsr * dchi - dh12 * dspr;
    fforce[m] = dUr * Ur + dUa * Ua;
  }

  // torque on i

  if (calc_torque) {
    MathExtra::vecmat(fourw, wi.aTe, fwae);

    for (int m = 0; m < 3; m++) {
      MathExtra::matvec(wi.lA[m], rhat, p);
      double tempv[3];
      MathExtra::matvec(wi.lA[m], w, tempv);
      dchi = -MathExtra::dot3(fwae, tempv);
      MathExtra::matvec(lAtwo[m], spr, tempv);
      dh12 = -MathExtra::dot3(s, tempv);

      dUa = pbsu * dchi - dh12 * dspu;
      dUr = pbsr * dchi - dh12 * dspr;
      ttor[m] = -(dUa * Ua + dUr * Ur);
    }
  }

  return Ua + Ur;
}
