/* -------------------------------------------------------------------------
   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: Axel Kohlmeyer (Temple U)
   OpenMP based threading support for LAMMPS
------------------------------------------------------------------------- */

#include <cstring>

#include "atom.h"
#include "comm.h"
#include "error.h"
#include "force.h"
#include "modify.h"
#include "neighbor.h"
#include "timer.h"

#include "thr_omp.h"

#include "pair.h"
#include "bond.h"
#include "angle.h"
#include "dihedral.h"
#include "improper.h"
#include "compute.h"

#include "math_const.h"

using namespace LAMMPS_NS;
using namespace MathConst;

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

ThrOMP::ThrOMP(LAMMPS *ptr, int style)
  : lmp(ptr), fix(NULL), thr_style(style), thr_error(0)
{
  // register fix omp with this class
  int ifix = lmp->modify->find_fix("package_omp");
  if (ifix < 0)
    lmp->error->all(FLERR,"The 'package omp' command is required for /omp styles");
  fix = static_cast<FixOMP *>(lmp->modify->fix[ifix]);
}

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

ThrOMP::~ThrOMP()
{
  // nothing to do?
}

/* ----------------------------------------------------------------------
   Hook up per thread per atom arrays into the tally infrastructure
   ---------------------------------------------------------------------- */

void ThrOMP::ev_setup_thr(int eflag, int vflag, int nall, double *eatom,
                          double **vatom, ThrData *thr)
{
  const int tid = thr->get_tid();
  if (tid == 0) thr_error = 0;

  if (thr_style & THR_PAIR) {
    if (eflag & 2) {
      thr->eatom_pair = eatom + tid*nall;
      if (nall > 0)
        memset(&(thr->eatom_pair[0]),0,nall*sizeof(double));
    }
    if (vflag & 4) {
      thr->vatom_pair = vatom + tid*nall;
      if (nall > 0)
        memset(&(thr->vatom_pair[0][0]),0,nall*6*sizeof(double));
    }
  }

  if (thr_style & THR_BOND) {
    if (eflag & 2) {
      thr->eatom_bond = eatom + tid*nall;
      if (nall > 0)
        memset(&(thr->eatom_bond[0]),0,nall*sizeof(double));
    }
    if (vflag & 4) {
      thr->vatom_bond = vatom + tid*nall;
      if (nall > 0)
        memset(&(thr->vatom_bond[0][0]),0,nall*6*sizeof(double));
    }
  }

  if (thr_style & THR_ANGLE) {
    if (eflag & 2) {
      thr->eatom_angle = eatom + tid*nall;
      if (nall > 0)
        memset(&(thr->eatom_angle[0]),0,nall*sizeof(double));
    }
    if (vflag & 4) {
      thr->vatom_angle = vatom + tid*nall;
      if (nall > 0)
        memset(&(thr->vatom_angle[0][0]),0,nall*6*sizeof(double));
    }
  }

  if (thr_style & THR_DIHEDRAL) {
    if (eflag & 2) {
      thr->eatom_dihed = eatom + tid*nall;
      if (nall > 0)
        memset(&(thr->eatom_dihed[0]),0,nall*sizeof(double));
    }
    if (vflag & 4) {
      thr->vatom_dihed = vatom + tid*nall;
      if (nall > 0)
        memset(&(thr->vatom_dihed[0][0]),0,nall*6*sizeof(double));
    }
  }

  if (thr_style & THR_IMPROPER) {
    if (eflag & 2) {
      thr->eatom_imprp = eatom + tid*nall;
      if (nall > 0)
        memset(&(thr->eatom_imprp[0]),0,nall*sizeof(double));
    }
    if (vflag & 4) {
      thr->vatom_imprp = vatom + tid*nall;
      if (nall > 0)
        memset(&(thr->vatom_imprp[0][0]),0,nall*6*sizeof(double));
    }
  }

  // nothing to do for THR_KSPACE
}

/* ----------------------------------------------------------------------
   Reduce per thread data into the regular structures
   Reduction of global properties is serialized with a "critical"
   directive, so that only one thread at a time will access the
   global variables. Since we are not synchronized, this should
   come with little overhead. The reduction of per-atom properties
   in contrast is parallelized over threads in the same way as forces.
   ---------------------------------------------------------------------- */

void ThrOMP::reduce_thr(void *style, const int eflag, const int vflag,
                        ThrData *const thr)
{
  const int nlocal = lmp->atom->nlocal;
  const int nghost = lmp->atom->nghost;
  const int nall = nlocal + nghost;
  const int nfirst = lmp->atom->nfirst;
  const int nthreads = lmp->comm->nthreads;
  const int evflag = eflag | vflag;

  const int tid = thr->get_tid();
  double **f = lmp->atom->f;
  double **x = lmp->atom->x;

  int need_force_reduce = 1;

  if (evflag)
    sync_threads();

  switch (thr_style) {

  case THR_PAIR: {

    if (lmp->force->pair->vflag_fdotr) {

      // this is a non-hybrid pair style. compute per thread fdotr
      if (fix->last_pair_hybrid == NULL) {
        if (lmp->neighbor->includegroup == 0)
          thr->virial_fdotr_compute(x, nlocal, nghost, -1);
        else
          thr->virial_fdotr_compute(x, nlocal, nghost, nfirst);
      } else {
        if (style == fix->last_pair_hybrid) {
          // pair_style hybrid will compute fdotr for us
          // but we first need to reduce the forces
          data_reduce_thr(&(f[0][0]), nall, nthreads, 3, tid);
          fix->did_reduce();
          need_force_reduce = 0;
        }
      }
    }

    if (evflag) {
      Pair * const pair = (Pair *)style;

#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          pair->eng_vdwl += thr->eng_vdwl;
          pair->eng_coul += thr->eng_coul;
          thr->eng_vdwl = 0.0;
          thr->eng_coul = 0.0;
        }
        if (vflag & 3)
          for (int i=0; i < 6; ++i) {
            pair->virial[i] += thr->virial_pair[i];
            thr->virial_pair[i] = 0.0;
          }
      }

      if (eflag & 2) {
        data_reduce_thr(&(pair->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(pair->vatom[0][0]), nall, nthreads, 6, tid);
      }
    }
  }
    break;

  case THR_BOND:

    if (evflag) {
      Bond * const bond = lmp->force->bond;
#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          bond->energy += thr->eng_bond;
          thr->eng_bond = 0.0;
        }

        if (vflag & 3) {
          for (int i=0; i < 6; ++i) {
            bond->virial[i] += thr->virial_bond[i];
            thr->virial_bond[i] = 0.0;
          }
        }
      }

      if (eflag & 2) {
        data_reduce_thr(&(bond->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(bond->vatom[0][0]), nall, nthreads, 6, tid);
      }

    }
    break;

  case THR_ANGLE:

    if (evflag) {
      Angle * const angle = lmp->force->angle;
#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          angle->energy += thr->eng_angle;
          thr->eng_angle = 0.0;
        }

        if (vflag & 3) {
          for (int i=0; i < 6; ++i) {
            angle->virial[i] += thr->virial_angle[i];
            thr->virial_angle[i] = 0.0;
          }
        }
      }

      if (eflag & 2) {
        data_reduce_thr(&(angle->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(angle->vatom[0][0]), nall, nthreads, 6, tid);
      }

    }
    break;

  case THR_DIHEDRAL:

    if (evflag) {
      Dihedral * const dihedral = lmp->force->dihedral;
#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          dihedral->energy += thr->eng_dihed;
          thr->eng_dihed = 0.0;
        }

        if (vflag & 3) {
          for (int i=0; i < 6; ++i) {
            dihedral->virial[i] += thr->virial_dihed[i];
            thr->virial_dihed[i] = 0.0;
          }
        }
      }

      if (eflag & 2) {
        data_reduce_thr(&(dihedral->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(dihedral->vatom[0][0]), nall, nthreads, 6, tid);
      }

    }
    break;

  case THR_DIHEDRAL|THR_CHARMM: // special case for CHARMM dihedrals

    if (evflag) {
      Dihedral * const dihedral = lmp->force->dihedral;
      Pair * const pair = lmp->force->pair;
#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          dihedral->energy += thr->eng_dihed;
          pair->eng_vdwl += thr->eng_vdwl;
          pair->eng_coul += thr->eng_coul;
          thr->eng_dihed = 0.0;
          thr->eng_vdwl = 0.0;
          thr->eng_coul = 0.0;
        }

        if (vflag & 3) {
          for (int i=0; i < 6; ++i) {
            dihedral->virial[i] += thr->virial_dihed[i];
            pair->virial[i] += thr->virial_pair[i];
            thr->virial_dihed[i] = 0.0;
            thr->virial_pair[i] = 0.0;
          }
        }
      }

      if (eflag & 2) {
        data_reduce_thr(&(dihedral->eatom[0]), nall, nthreads, 1, tid);
        data_reduce_thr(&(pair->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(dihedral->vatom[0][0]), nall, nthreads, 6, tid);
        data_reduce_thr(&(pair->vatom[0][0]), nall, nthreads, 6, tid);
      }
    }
    break;

  case THR_IMPROPER:

    if (evflag) {
      Improper *improper = lmp->force->improper;
#if defined(_OPENMP)
#pragma omp critical
#endif
      {
        if (eflag & 1) {
          improper->energy += thr->eng_imprp;
          thr->eng_imprp = 0.0;
        }

        if (vflag & 3) {
          for (int i=0; i < 6; ++i) {
            improper->virial[i] += thr->virial_imprp[i];
            thr->virial_imprp[i] = 0.0;
          }
        }
      }

      if (eflag & 2) {
        data_reduce_thr(&(improper->eatom[0]), nall, nthreads, 1, tid);
      }
      if (vflag & 4) {
        data_reduce_thr(&(improper->vatom[0][0]), nall, nthreads, 6, tid);
      }

    }
    break;

  case THR_KSPACE:
    // nothing to do. XXX may need to add support for per-atom info
    break;

  case THR_INTGR:
    // nothing to do
    break;

  default:
    printf("tid:%d unhandled thr_style case %d\n", tid, thr_style);
    break;
  }

  if (style == fix->last_omp_style) {
    if (need_force_reduce) {
      data_reduce_thr(&(f[0][0]), nall, nthreads, 3, tid);
      fix->did_reduce();
    }

    if (lmp->atom->torque)
      data_reduce_thr(&(lmp->atom->torque[0][0]), nall, nthreads, 3, tid);
  }
  thr->timer(Timer::COMM);
}

/* ----------------------------------------------------------------------
   tally eng_vdwl and eng_coul into per thread global and per-atom accumulators
------------------------------------------------------------------------- */

void ThrOMP::e_tally_thr(Pair * const pair, const int i, const int j,
                         const int nlocal, const int newton_pair,
                         const double evdwl, const double ecoul, ThrData * const thr)
{
  if (pair->eflag_global) {
    if (newton_pair) {
      thr->eng_vdwl += evdwl;
      thr->eng_coul += ecoul;
    } else {
      const double evdwlhalf = 0.5*evdwl;
      const double ecoulhalf = 0.5*ecoul;
      if (i < nlocal) {
        thr->eng_vdwl += evdwlhalf;
        thr->eng_coul += ecoulhalf;
      }
      if (j < nlocal) {
        thr->eng_vdwl += evdwlhalf;
        thr->eng_coul += ecoulhalf;
      }
    }
  }
  if (pair->eflag_atom) {
    const double epairhalf = 0.5 * (evdwl + ecoul);
    if (newton_pair || i < nlocal) thr->eatom_pair[i] += epairhalf;
    if (newton_pair || j < nlocal) thr->eatom_pair[j] += epairhalf;
  }
}

/* helper functions */
static void v_tally(double * const vout, const double * const vin)
{
  vout[0] += vin[0];
  vout[1] += vin[1];
  vout[2] += vin[2];
  vout[3] += vin[3];
  vout[4] += vin[4];
  vout[5] += vin[5];
}

static void v_tally(double * const vout, const double scale, const double * const vin)
{
  vout[0] += scale*vin[0];
  vout[1] += scale*vin[1];
  vout[2] += scale*vin[2];
  vout[3] += scale*vin[3];
  vout[4] += scale*vin[4];
  vout[5] += scale*vin[5];
}

/* ----------------------------------------------------------------------
   tally virial into per thread global and per-atom accumulators
------------------------------------------------------------------------- */
void ThrOMP::v_tally_thr(Pair * const pair, const int i, const int j,
                         const int nlocal, const int newton_pair,
                         const double * const v, ThrData * const thr)
{
  if (pair->vflag_global) {
    double * const va = thr->virial_pair;
    if (newton_pair) {
      v_tally(va,v);
    } else {
      if (i < nlocal) v_tally(va,0.5,v);
      if (j < nlocal) v_tally(va,0.5,v);
    }
  }

  if (pair->vflag_atom) {
    if (newton_pair || i < nlocal) {
      double * const va = thr->vatom_pair[i];
      v_tally(va,0.5,v);
    }
    if (newton_pair || j < nlocal) {
      double * const va = thr->vatom_pair[j];
      v_tally(va,0.5,v);
    }
  }
}

/* ----------------------------------------------------------------------
   tally eng_vdwl and virial into per thread global and per-atom accumulators
   need i < nlocal test since called by bond_quartic and dihedral_charmm
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_thr(Pair * const pair, const int i, const int j, const int nlocal,
                          const int newton_pair, const double evdwl, const double ecoul,
                          const double fpair, const double delx, const double dely,
                          const double delz, ThrData * const thr)
{

  if (pair->eflag_either)
    e_tally_thr(pair, i, j, nlocal, newton_pair, evdwl, ecoul, thr);

  if (pair->vflag_either) {
    double v[6];
    v[0] = delx*delx*fpair;
    v[1] = dely*dely*fpair;
    v[2] = delz*delz*fpair;
    v[3] = delx*dely*fpair;
    v[4] = delx*delz*fpair;
    v[5] = dely*delz*fpair;

    v_tally_thr(pair, i, j, nlocal, newton_pair, v, thr);
  }

  if (pair->num_tally_compute > 0) {
    // ev_tally callbacks are not thread safe and thus have to be protected
#if defined(_OPENMP)
#pragma omp critical
#endif
    for (int k=0; k < pair->num_tally_compute; ++k) {
      Compute *c = pair->list_tally_compute[k];
      c->pair_tally_callback(i, j, nlocal, newton_pair,
                             evdwl, ecoul, fpair, delx, dely, delz);
    }
  }
}

/* ----------------------------------------------------------------------
   tally eng_vdwl and virial into global and per-atom accumulators
   for virial, have delx,dely,delz and fx,fy,fz
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_xyz_thr(Pair * const pair, const int i, const int j,
                              const int nlocal, const int newton_pair,
                              const double evdwl, const double ecoul,
                              const double fx, const double fy, const double fz,
                              const double delx, const double dely, const double delz,
                              ThrData * const thr)
{

  if (pair->eflag_either)
    e_tally_thr(pair, i, j, nlocal, newton_pair, evdwl, ecoul, thr);

  if (pair->vflag_either) {
    double v[6];
    v[0] = delx*fx;
    v[1] = dely*fy;
    v[2] = delz*fz;
    v[3] = delx*fy;
    v[4] = delx*fz;
    v[5] = dely*fz;

    v_tally_thr(pair, i, j, nlocal, newton_pair, v, thr);
  }
}


/* ----------------------------------------------------------------------
   tally eng_vdwl and virial into global and per-atom accumulators
   for virial, have delx,dely,delz and fx,fy,fz
   called when using full neighbor lists
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_xyz_full_thr(Pair * const pair, const int i,
                                   const double evdwl, const double ecoul,
                                   const double fx, const double fy,
                                   const double fz, const double delx,
                                   const double dely, const double delz,
                                   ThrData * const thr)
{

  if (pair->eflag_either)
    e_tally_thr(pair,i,i,i+1,0,0.5*evdwl,ecoul,thr);

  if (pair->vflag_either) {
    double v[6];
    v[0] = 0.5*delx*fx;
    v[1] = 0.5*dely*fy;
    v[2] = 0.5*delz*fz;
    v[3] = 0.5*delx*fy;
    v[4] = 0.5*delx*fz;
    v[5] = 0.5*dely*fz;

    v_tally_thr(pair,i,i,i+1,0,v,thr);
  }
}

/* ----------------------------------------------------------------------
   tally eng_vdwl and virial into global and per-atom accumulators
   called by SW and hbond potentials, newton_pair is always on
   virial = riFi + rjFj + rkFk = (rj-ri) Fj + (rk-ri) Fk = drji*fj + drki*fk
 ------------------------------------------------------------------------- */

void ThrOMP::ev_tally3_thr(Pair * const pair, const int i, const int j, const int k,
                           const double evdwl, const double ecoul,
                           const double * const fj, const double * const fk,
                           const double * const drji, const double * const drki,
                           ThrData * const thr)
{
  if (pair->eflag_either) {
    if (pair->eflag_global) {
      thr->eng_vdwl += evdwl;
      thr->eng_coul += ecoul;
    }
    if (pair->eflag_atom) {
      const double epairthird = THIRD * (evdwl + ecoul);
      thr->eatom_pair[i] += epairthird;
      thr->eatom_pair[j] += epairthird;
      thr->eatom_pair[k] += epairthird;
    }
  }

  if (pair->vflag_either) {
    double v[6];

    v[0] = drji[0]*fj[0] + drki[0]*fk[0];
    v[1] = drji[1]*fj[1] + drki[1]*fk[1];
    v[2] = drji[2]*fj[2] + drki[2]*fk[2];
    v[3] = drji[0]*fj[1] + drki[0]*fk[1];
    v[4] = drji[0]*fj[2] + drki[0]*fk[2];
    v[5] = drji[1]*fj[2] + drki[1]*fk[2];

    if (pair->vflag_global) v_tally(thr->virial_pair,v);

    if (pair->vflag_atom) {
      v_tally(thr->vatom_pair[i],THIRD,v);
      v_tally(thr->vatom_pair[j],THIRD,v);
      v_tally(thr->vatom_pair[k],THIRD,v);
    }
  }
}

/* ----------------------------------------------------------------------
   tally eng_vdwl and virial into global and per-atom accumulators
   called by AIREBO potential, newton_pair is always on
 ------------------------------------------------------------------------- */

void ThrOMP::ev_tally4_thr(Pair * const pair, const int i, const int j,
                           const int k, const int m, const double evdwl,
                           const double * const fi, const double * const fj,
                           const double * const fk, const double * const drim,
                           const double * const drjm, const double * const drkm,
                           ThrData * const thr)
{
  double v[6];

  if (pair->eflag_either) {
    if (pair->eflag_global) thr->eng_vdwl += evdwl;
    if (pair->eflag_atom) {
      const double epairfourth = 0.25 * evdwl;
      thr->eatom_pair[i] += epairfourth;
      thr->eatom_pair[j] += epairfourth;
      thr->eatom_pair[k] += epairfourth;
      thr->eatom_pair[m] += epairfourth;
    }
  }

  if (pair->vflag_atom) {
    v[0] = 0.25 * (drim[0]*fi[0] + drjm[0]*fj[0] + drkm[0]*fk[0]);
    v[1] = 0.25 * (drim[1]*fi[1] + drjm[1]*fj[1] + drkm[1]*fk[1]);
    v[2] = 0.25 * (drim[2]*fi[2] + drjm[2]*fj[2] + drkm[2]*fk[2]);
    v[3] = 0.25 * (drim[0]*fi[1] + drjm[0]*fj[1] + drkm[0]*fk[1]);
    v[4] = 0.25 * (drim[0]*fi[2] + drjm[0]*fj[2] + drkm[0]*fk[2]);
    v[5] = 0.25 * (drim[1]*fi[2] + drjm[1]*fj[2] + drkm[1]*fk[2]);

    v_tally(thr->vatom_pair[i],v);
    v_tally(thr->vatom_pair[j],v);
    v_tally(thr->vatom_pair[k],v);
    v_tally(thr->vatom_pair[m],v);
  }
}

/* ----------------------------------------------------------------------
   tally ecoul and virial into each of n atoms in list
   called by TIP4P potential, newton_pair is always on
   changes v values by dividing by n
 ------------------------------------------------------------------------- */

void ThrOMP::ev_tally_list_thr(Pair * const pair, const int key,
                               const int * const list, const double * const v,
                               const double ecoul, const double alpha,
                               ThrData * const thr)
{
  int i;
  if (pair->eflag_either) {
    if (pair->eflag_global) thr->eng_coul += ecoul;
    if (pair->eflag_atom) {
      if (key == 0) {
        thr->eatom_pair[list[0]] += 0.5*ecoul;
        thr->eatom_pair[list[1]] += 0.5*ecoul;
      } else if (key == 1) {
        thr->eatom_pair[list[0]] += 0.5*ecoul*(1-alpha);
        thr->eatom_pair[list[1]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[2]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[3]] += 0.5*ecoul;
      } else if (key == 2) {
        thr->eatom_pair[list[0]] += 0.5*ecoul;
        thr->eatom_pair[list[1]] += 0.5*ecoul*(1-alpha);
        thr->eatom_pair[list[2]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[3]] += 0.25*ecoul*alpha;
      } else {
        thr->eatom_pair[list[0]] += 0.5*ecoul*(1-alpha);
        thr->eatom_pair[list[1]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[2]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[3]] += 0.5*ecoul*(1-alpha);
        thr->eatom_pair[list[4]] += 0.25*ecoul*alpha;
        thr->eatom_pair[list[5]] += 0.25*ecoul*alpha;
      }
    }
  }

  if (pair->vflag_either) {
    if (pair->vflag_global)
      v_tally(thr->virial_pair,v);

    if (pair->vflag_atom) {
      if (key == 0) {
        for (i = 0; i <= 5; i++) {
          thr->vatom_pair[list[0]][i] += 0.5*v[i];
          thr->vatom_pair[list[1]][i] += 0.5*v[i];
        }
      } else if (key == 1) {
        for (i = 0; i <= 5; i++) {
          thr->vatom_pair[list[0]][i] += 0.5*v[i]*(1-alpha);
          thr->vatom_pair[list[1]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[2]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[3]][i] += 0.5*v[i];
        }
      } else if (key == 2) {
        for (i = 0; i <= 5; i++) {
          thr->vatom_pair[list[0]][i] += 0.5*v[i];
          thr->vatom_pair[list[1]][i] += 0.5*v[i]*(1-alpha);
          thr->vatom_pair[list[2]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[3]][i] += 0.25*v[i]*alpha;
        }
      } else {
        for (i = 0; i <= 5; i++) {
          thr->vatom_pair[list[0]][i] += 0.5*v[i]*(1-alpha);
          thr->vatom_pair[list[1]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[2]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[3]][i] += 0.5*v[i]*(1-alpha);
          thr->vatom_pair[list[4]][i] += 0.25*v[i]*alpha;
          thr->vatom_pair[list[5]][i] += 0.25*v[i]*alpha;
        }
      }
    }
  }
}

/* ----------------------------------------------------------------------
   tally energy and virial into global and per-atom accumulators
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_thr(Bond * const bond, const int i, const int j, const int nlocal,
                          const int newton_bond, const double ebond, const double fbond,
                          const double delx, const double dely, const double delz,
                          ThrData * const thr)
{
  if (bond->eflag_either) {
    const double ebondhalf = 0.5*ebond;
    if (newton_bond) {
      if (bond->eflag_global)
        thr->eng_bond += ebond;
      if (bond->eflag_atom) {
        thr->eatom_bond[i] += ebondhalf;
        thr->eatom_bond[j] += ebondhalf;
      }
    } else {
      if (bond->eflag_global) {
        if (i < nlocal) thr->eng_bond += ebondhalf;
        if (j < nlocal) thr->eng_bond += ebondhalf;
      }
      if (bond->eflag_atom) {
        if (i < nlocal) thr->eatom_bond[i] += ebondhalf;
        if (j < nlocal) thr->eatom_bond[j] += ebondhalf;
      }
    }
  }

  if (bond->vflag_either) {
    double v[6];

    v[0] = delx*delx*fbond;
    v[1] = dely*dely*fbond;
    v[2] = delz*delz*fbond;
    v[3] = delx*dely*fbond;
    v[4] = delx*delz*fbond;
    v[5] = dely*delz*fbond;

    if (bond->vflag_global) {
      if (newton_bond)
        v_tally(thr->virial_bond,v);
      else {
        if (i < nlocal)
          v_tally(thr->virial_bond,0.5,v);
        if (j < nlocal)
          v_tally(thr->virial_bond,0.5,v);
      }
    }

    if (bond->vflag_atom) {
      v[0] *= 0.5;
      v[1] *= 0.5;
      v[2] *= 0.5;
      v[3] *= 0.5;
      v[4] *= 0.5;
      v[5] *= 0.5;

      if (newton_bond) {
        v_tally(thr->vatom_bond[i],v);
        v_tally(thr->vatom_bond[j],v);
      } else {
        if (i < nlocal)
          v_tally(thr->vatom_bond[i],v);
        if (j < nlocal)
          v_tally(thr->vatom_bond[j],v);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   tally energy and virial into global and per-atom accumulators
   virial = r1F1 + r2F2 + r3F3 = (r1-r2) F1 + (r3-r2) F3 = del1*f1 + del2*f3
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_thr(Angle * const angle, const int i, const int j, const int k,
                          const int nlocal, const int newton_bond, const double eangle,
                          const double * const f1, const double * const f3,
                          const double delx1, const double dely1, const double delz1,
                          const double delx2, const double dely2, const double delz2,
                          ThrData * const thr)
{
  if (angle->eflag_either) {
    const double eanglethird = THIRD*eangle;
    if (newton_bond) {
      if (angle->eflag_global)
        thr->eng_angle += eangle;
      if (angle->eflag_atom) {
        thr->eatom_angle[i] += eanglethird;
        thr->eatom_angle[j] += eanglethird;
        thr->eatom_angle[k] += eanglethird;
      }
    } else {
      if (angle->eflag_global) {
        if (i < nlocal) thr->eng_angle += eanglethird;
        if (j < nlocal) thr->eng_angle += eanglethird;
        if (k < nlocal) thr->eng_angle += eanglethird;
      }
      if (angle->eflag_atom) {
        if (i < nlocal) thr->eatom_angle[i] += eanglethird;
        if (j < nlocal) thr->eatom_angle[j] += eanglethird;
        if (k < nlocal) thr->eatom_angle[k] += eanglethird;
      }
    }
  }

  if (angle->vflag_either) {
    double v[6];

    v[0] = delx1*f1[0] + delx2*f3[0];
    v[1] = dely1*f1[1] + dely2*f3[1];
    v[2] = delz1*f1[2] + delz2*f3[2];
    v[3] = delx1*f1[1] + delx2*f3[1];
    v[4] = delx1*f1[2] + delx2*f3[2];
    v[5] = dely1*f1[2] + dely2*f3[2];

    if (angle->vflag_global) {
      if (newton_bond) {
        v_tally(thr->virial_angle,v);
      } else {
        int cnt = 0;
        if (i < nlocal) ++cnt;
        if (j < nlocal) ++cnt;
        if (k < nlocal) ++cnt;
        v_tally(thr->virial_angle,cnt*THIRD,v);
      }
    }

    if (angle->vflag_atom) {
      v[0] *= THIRD;
      v[1] *= THIRD;
      v[2] *= THIRD;
      v[3] *= THIRD;
      v[4] *= THIRD;
      v[5] *= THIRD;

      if (newton_bond) {
        v_tally(thr->vatom_angle[i],v);
        v_tally(thr->vatom_angle[j],v);
        v_tally(thr->vatom_angle[k],v);
      } else {
        if (i < nlocal) v_tally(thr->vatom_angle[i],v);
        if (j < nlocal) v_tally(thr->vatom_angle[j],v);
        if (k < nlocal) v_tally(thr->vatom_angle[k],v);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   tally energy and virial from 1-3 repulsion of SDK angle into accumulators
------------------------------------------------------------------------- */

void ThrOMP::ev_tally13_thr(Angle * const angle, const int i1, const int i3,
                            const int nlocal, const int newton_bond,
                            const double epair, const double fpair,
                            const double delx, const double dely,
                            const double delz, ThrData * const thr)
{

  if (angle->eflag_either) {
    const double epairhalf = 0.5 * epair;

    if (angle->eflag_global) {
      if (newton_bond || i1 < nlocal)
        thr->eng_angle += epairhalf;
      if (newton_bond || i3 < nlocal)
        thr->eng_angle += epairhalf;
    }

    if (angle->eflag_atom) {
      if (newton_bond || i1 < nlocal) thr->eatom_angle[i1] += epairhalf;
      if (newton_bond || i3 < nlocal) thr->eatom_angle[i3] += epairhalf;
    }
  }

  if (angle->vflag_either) {
    double v[6];
    v[0] = delx*delx*fpair;
    v[1] = dely*dely*fpair;
    v[2] = delz*delz*fpair;
    v[3] = delx*dely*fpair;
    v[4] = delx*delz*fpair;
    v[5] = dely*delz*fpair;

    if (angle->vflag_global) {
      double * const va = thr->virial_angle;
      if (newton_bond || i1 < nlocal) v_tally(va,0.5,v);
      if (newton_bond || i3 < nlocal) v_tally(va,0.5,v);
    }

    if (angle->vflag_atom) {
      if (newton_bond || i1 < nlocal) {
        double * const va = thr->vatom_angle[i1];
        v_tally(va,0.5,v);
      }
      if (newton_bond || i3 < nlocal) {
        double * const va = thr->vatom_angle[i3];
        v_tally(va,0.5,v);
      }
    }
  }
}


/* ----------------------------------------------------------------------
   tally energy and virial into global and per-atom accumulators
   virial = r1F1 + r2F2 + r3F3 + r4F4 = (r1-r2) F1 + (r3-r2) F3 + (r4-r2) F4
          = (r1-r2) F1 + (r3-r2) F3 + (r4-r3 + r3-r2) F4
          = vb1*f1 + vb2*f3 + (vb3+vb2)*f4
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_thr(Dihedral * const dihed, const int i1, const int i2,
                          const int i3, const int i4, const int nlocal,
                          const int newton_bond, const double edihedral,
                          const double * const f1, const double * const f3,
                          const double * const f4, const double vb1x,
                          const double vb1y, const double vb1z, const double vb2x,
                          const double vb2y, const double vb2z, const double vb3x,
                          const double vb3y, const double vb3z, ThrData * const thr)
{

  if (dihed->eflag_either) {
    if (dihed->eflag_global) {
      if (newton_bond) {
        thr->eng_dihed += edihedral;
      } else {
        const double edihedralquarter = 0.25*edihedral;
        int cnt = 0;
        if (i1 < nlocal) ++cnt;
        if (i2 < nlocal) ++cnt;
        if (i3 < nlocal) ++cnt;
        if (i4 < nlocal) ++cnt;
        thr->eng_dihed += static_cast<double>(cnt)*edihedralquarter;
      }
    }
    if (dihed->eflag_atom) {
      const double edihedralquarter = 0.25*edihedral;
      if (newton_bond) {
        thr->eatom_dihed[i1] += edihedralquarter;
        thr->eatom_dihed[i2] += edihedralquarter;
        thr->eatom_dihed[i3] += edihedralquarter;
        thr->eatom_dihed[i4] += edihedralquarter;
      } else {
        if (i1 < nlocal) thr->eatom_dihed[i1] +=  edihedralquarter;
        if (i2 < nlocal) thr->eatom_dihed[i2] +=  edihedralquarter;
        if (i3 < nlocal) thr->eatom_dihed[i3] +=  edihedralquarter;
        if (i4 < nlocal) thr->eatom_dihed[i4] +=  edihedralquarter;
      }
    }
  }

  if (dihed->vflag_either) {
    double v[6];
    v[0] = vb1x*f1[0] + vb2x*f3[0] + (vb3x+vb2x)*f4[0];
    v[1] = vb1y*f1[1] + vb2y*f3[1] + (vb3y+vb2y)*f4[1];
    v[2] = vb1z*f1[2] + vb2z*f3[2] + (vb3z+vb2z)*f4[2];
    v[3] = vb1x*f1[1] + vb2x*f3[1] + (vb3x+vb2x)*f4[1];
    v[4] = vb1x*f1[2] + vb2x*f3[2] + (vb3x+vb2x)*f4[2];
    v[5] = vb1y*f1[2] + vb2y*f3[2] + (vb3y+vb2y)*f4[2];

    if (dihed->vflag_global) {
      if (newton_bond) {
        v_tally(thr->virial_dihed,v);
      } else {
        int cnt = 0;
        if (i1 < nlocal) ++cnt;
        if (i2 < nlocal) ++cnt;
        if (i3 < nlocal) ++cnt;
        if (i4 < nlocal) ++cnt;
        v_tally(thr->virial_dihed,0.25*static_cast<double>(cnt),v);
      }
    }

    v[0] *= 0.25;
    v[1] *= 0.25;
    v[2] *= 0.25;
    v[3] *= 0.25;
    v[4] *= 0.25;
    v[5] *= 0.25;

    if (dihed->vflag_atom) {
      if (newton_bond) {
        v_tally(thr->vatom_dihed[i1],v);
        v_tally(thr->vatom_dihed[i2],v);
        v_tally(thr->vatom_dihed[i3],v);
        v_tally(thr->vatom_dihed[i4],v);
      } else {
        if (i1 < nlocal) v_tally(thr->vatom_dihed[i1],v);
        if (i2 < nlocal) v_tally(thr->vatom_dihed[i2],v);
        if (i3 < nlocal) v_tally(thr->vatom_dihed[i3],v);
        if (i4 < nlocal) v_tally(thr->vatom_dihed[i4],v);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   tally energy and virial into global and per-atom accumulators
   virial = r1F1 + r2F2 + r3F3 + r4F4 = (r1-r2) F1 + (r3-r2) F3 + (r4-r2) F4
          = (r1-r2) F1 + (r3-r2) F3 + (r4-r3 + r3-r2) F4
          = vb1*f1 + vb2*f3 + (vb3+vb2)*f4
------------------------------------------------------------------------- */

void ThrOMP::ev_tally_thr(Improper * const imprp, const int i1, const int i2,
                          const int i3, const int i4, const int nlocal,
                          const int newton_bond, const double eimproper,
                          const double * const f1, const double * const f3,
                          const double * const f4, const double vb1x,
                          const double vb1y, const double vb1z, const double vb2x,
                          const double vb2y, const double vb2z, const double vb3x,
                          const double vb3y, const double vb3z, ThrData * const thr)
{

  if (imprp->eflag_either) {
    if (imprp->eflag_global) {
      if (newton_bond) {
        thr->eng_imprp += eimproper;
      } else {
        const double eimproperquarter = 0.25*eimproper;
        int cnt = 0;
        if (i1 < nlocal) ++cnt;
        if (i2 < nlocal) ++cnt;
        if (i3 < nlocal) ++cnt;
        if (i4 < nlocal) ++cnt;
        thr->eng_imprp += static_cast<double>(cnt)*eimproperquarter;
      }
    }
    if (imprp->eflag_atom) {
      const double eimproperquarter = 0.25*eimproper;
      if (newton_bond) {
        thr->eatom_imprp[i1] += eimproperquarter;
        thr->eatom_imprp[i2] += eimproperquarter;
        thr->eatom_imprp[i3] += eimproperquarter;
        thr->eatom_imprp[i4] += eimproperquarter;
      } else {
        if (i1 < nlocal) thr->eatom_imprp[i1] +=  eimproperquarter;
        if (i2 < nlocal) thr->eatom_imprp[i2] +=  eimproperquarter;
        if (i3 < nlocal) thr->eatom_imprp[i3] +=  eimproperquarter;
        if (i4 < nlocal) thr->eatom_imprp[i4] +=  eimproperquarter;
      }
    }
  }

  if (imprp->vflag_either) {
    double v[6];
    v[0] = vb1x*f1[0] + vb2x*f3[0] + (vb3x+vb2x)*f4[0];
    v[1] = vb1y*f1[1] + vb2y*f3[1] + (vb3y+vb2y)*f4[1];
    v[2] = vb1z*f1[2] + vb2z*f3[2] + (vb3z+vb2z)*f4[2];
    v[3] = vb1x*f1[1] + vb2x*f3[1] + (vb3x+vb2x)*f4[1];
    v[4] = vb1x*f1[2] + vb2x*f3[2] + (vb3x+vb2x)*f4[2];
    v[5] = vb1y*f1[2] + vb2y*f3[2] + (vb3y+vb2y)*f4[2];

    if (imprp->vflag_global) {
      if (newton_bond) {
        v_tally(thr->virial_imprp,v);
      } else {
        int cnt = 0;
        if (i1 < nlocal) ++cnt;
        if (i2 < nlocal) ++cnt;
        if (i3 < nlocal) ++cnt;
        if (i4 < nlocal) ++cnt;
        v_tally(thr->virial_imprp,0.25*static_cast<double>(cnt),v);
      }
    }

    v[0] *= 0.25;
    v[1] *= 0.25;
    v[2] *= 0.25;
    v[3] *= 0.25;
    v[4] *= 0.25;
    v[5] *= 0.25;

    if (imprp->vflag_atom) {
      if (newton_bond) {
        v_tally(thr->vatom_imprp[i1],v);
        v_tally(thr->vatom_imprp[i2],v);
        v_tally(thr->vatom_imprp[i3],v);
        v_tally(thr->vatom_imprp[i4],v);
      } else {
        if (i1 < nlocal) v_tally(thr->vatom_imprp[i1],v);
        if (i2 < nlocal) v_tally(thr->vatom_imprp[i2],v);
        if (i3 < nlocal) v_tally(thr->vatom_imprp[i3],v);
        if (i4 < nlocal) v_tally(thr->vatom_imprp[i4],v);
      }
    }
  }
}

/* ----------------------------------------------------------------------
   tally virial into per-atom accumulators
   called by AIREBO potential, newton_pair is always on
   fpair is magnitude of force on atom I
------------------------------------------------------------------------- */

void ThrOMP::v_tally2_thr(const int i, const int j, const double fpair,
                          const double * const drij, ThrData * const thr)
{
  double v[6];

  v[0] = 0.5 * drij[0]*drij[0]*fpair;
  v[1] = 0.5 * drij[1]*drij[1]*fpair;
  v[2] = 0.5 * drij[2]*drij[2]*fpair;
  v[3] = 0.5 * drij[0]*drij[1]*fpair;
  v[4] = 0.5 * drij[0]*drij[2]*fpair;
  v[5] = 0.5 * drij[1]*drij[2]*fpair;

  v_tally(thr->vatom_pair[i],v);
  v_tally(thr->vatom_pair[j],v);
}

/* ----------------------------------------------------------------------
   tally virial into per-atom accumulators
   called by AIREBO and Tersoff potential, newton_pair is always on
------------------------------------------------------------------------- */

void ThrOMP::v_tally3_thr(const int i, const int j, const int k,
                          const double * const fi, const double * const fj,
                          const double * const drik, const double * const drjk,
                          ThrData * const thr)
{
  double v[6];

  v[0] = THIRD * (drik[0]*fi[0] + drjk[0]*fj[0]);
  v[1] = THIRD * (drik[1]*fi[1] + drjk[1]*fj[1]);
  v[2] = THIRD * (drik[2]*fi[2] + drjk[2]*fj[2]);
  v[3] = THIRD * (drik[0]*fi[1] + drjk[0]*fj[1]);
  v[4] = THIRD * (drik[0]*fi[2] + drjk[0]*fj[2]);
  v[5] = THIRD * (drik[1]*fi[2] + drjk[1]*fj[2]);

  v_tally(thr->vatom_pair[i],v);
  v_tally(thr->vatom_pair[j],v);
  v_tally(thr->vatom_pair[k],v);
}

/* ----------------------------------------------------------------------
   tally virial into per-atom accumulators
   called by AIREBO potential, newton_pair is always on
------------------------------------------------------------------------- */

void ThrOMP::v_tally4_thr(const int i, const int j, const int k, const int m,
                          const double * const fi, const double * const fj,
                          const double * const fk, const double * const drim,
                          const double * const drjm, const double * const drkm,
                          ThrData * const thr)
{
  double v[6];

  v[0] = 0.25 * (drim[0]*fi[0] + drjm[0]*fj[0] + drkm[0]*fk[0]);
  v[1] = 0.25 * (drim[1]*fi[1] + drjm[1]*fj[1] + drkm[1]*fk[1]);
  v[2] = 0.25 * (drim[2]*fi[2] + drjm[2]*fj[2] + drkm[2]*fk[2]);
  v[3] = 0.25 * (drim[0]*fi[1] + drjm[0]*fj[1] + drkm[0]*fk[1]);
  v[4] = 0.25 * (drim[0]*fi[2] + drjm[0]*fj[2] + drkm[0]*fk[2]);
  v[5] = 0.25 * (drim[1]*fi[2] + drjm[1]*fj[2] + drkm[1]*fk[2]);

  v_tally(thr->vatom_pair[i],v);
  v_tally(thr->vatom_pair[j],v);
  v_tally(thr->vatom_pair[k],v);
  v_tally(thr->vatom_pair[m],v);
}

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

double ThrOMP::memory_usage_thr()
{
  double bytes=0.0;

  return bytes;
}
