// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/ Sandia National Laboratories
   LAMMPS development team: developers@lammps.org

   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 authors: Ngoc Cuong Nguyen (MIT)
------------------------------------------------------------------------- */

// LAMMPS header files

#include "comm.h"
#include "error.h"
#include "math_const.h"
#include "math_special.h"
#include "memory.h"
#include "tokenizer.h"

#include <algorithm>
#include <cmath>

// header file. Moved down here to avoid polluting other headers with its defines
#include "eapod.h"

using namespace LAMMPS_NS;
using MathConst::MY_PI;
using MathSpecial::powint;

static constexpr int MAXLINE=1024;

// constructor
EAPOD::EAPOD(LAMMPS *_lmp, const std::string &pod_file, const std::string &coeff_file) :
    Pointers(_lmp), elemindex(nullptr), Phi(nullptr), Lambda(nullptr), coeff(nullptr),
    tmpmem(nullptr), Proj(nullptr), Centroids(nullptr), bd(nullptr), bdd(nullptr), pd(nullptr),
    pdd(nullptr), pn3(nullptr), pq3(nullptr), pc3(nullptr), pq4(nullptr), pa4(nullptr),
    pb4(nullptr), pc4(nullptr), tmpint(nullptr), ind23(nullptr), ind32(nullptr), ind33(nullptr),
    ind34(nullptr), ind43(nullptr), ind44(nullptr), ind33l(nullptr), ind33r(nullptr),
    ind34l(nullptr), ind34r(nullptr), ind44l(nullptr), ind44r(nullptr)
{
  rin = 0.5;
  rcut = 5.0;
  nClusters = 1;
  nComponents = 1;
  nelements = 1;
  onebody = 1;
  besseldegree = 4;
  inversedegree = 8;
  nbesselpars = 3;
  true4BodyDesc = 1;
  ns = nbesselpars*besseldegree + inversedegree;
  Njmax = 100;
  nrbf2 = 8;
  nrbf3 = 6;
  nrbf4 = 4;
  nabf3 = 5;
  nabf4 = 3;
  nrbf23 = 0;
  nabf23 = 0;
  nrbf33 = 0;
  nabf33 = 0;
  nrbf34 = 0;
  nabf34 = 0;
  nabf43 = 0;
  nrbf44 = 0;
  nabf44 = 0;
  P3 = 4;
  P4 = 3;
  P23 = 0;
  P33 = 0;
  P34 = 0;
  P44 = 0;
  pdegree[0] = besseldegree;
  pdegree[1] = inversedegree;
  pbc[0] = 1;
  pbc[1] = 1;
  pbc[2] = 1;
  besselparams[0] = 1e-3;
  besselparams[1] = 2.0;
  besselparams[2] = 4.0;

  // read pod input file to podstruct
  read_pod_file(pod_file);

  if (coeff_file != "") {
    read_model_coeff_file(coeff_file);
  }
}

// destructor
EAPOD::~EAPOD()
{
  memory->destroy(elemindex);
  memory->destroy(Phi);
  memory->destroy(Lambda);
  memory->destroy(Proj);
  memory->destroy(Centroids);
  memory->destroy(bd);
  memory->destroy(bdd);
  memory->destroy(pd);
  memory->destroy(pdd);
  memory->destroy(coeff);
  memory->destroy(tmpmem);
  memory->destroy(tmpint);
  memory->destroy(pn3);
  memory->destroy(pq3);
  memory->destroy(pc3);
  memory->destroy(pa4);
  memory->destroy(pb4);
  memory->destroy(pc4);
  memory->destroy(pq4);
  memory->destroy(ind23);
  memory->destroy(ind32);
  memory->destroy(ind33);
  memory->destroy(ind34);
  memory->destroy(ind43);
  memory->destroy(ind44);
  memory->destroy(ind33l);
  memory->destroy(ind34l);
  memory->destroy(ind44l);
  memory->destroy(ind33r);
  memory->destroy(ind34r);
  memory->destroy(ind44r);
}

void EAPOD::read_pod_file(std::string pod_file)
{
  std::string podfilename = pod_file;
  FILE *fppod;
  if (comm->me == 0) {

    fppod = utils::open_potential(podfilename,lmp,nullptr);
    if (fppod == nullptr)
      error->one(FLERR,"Cannot open POD coefficient file {}: ",
                                   podfilename, utils::getsyserror());
  }

  // loop through lines of POD file and parse keywords

  char line[MAXLINE],*ptr;
  int eof = 0;

  while (true) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fppod);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fppod);
      }
    }
    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) break;
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    // words = ptrs to all words in line
    // strip single and double quotes from words

    std::vector<std::string> words;
    try {
      words = Tokenizer(utils::trim_comment(line),"\"' \t\n\r\f").as_vector();
    } catch (TokenizerException &) {
      // ignore
    }

    if (words.size() == 0) continue;

    auto keywd = words[0];

    if (keywd == "species") {
      nelements = words.size()-1;
      for (int ielem = 1; ielem <= nelements; ielem++) {
        species.push_back(words[ielem]);
      }
    }

    if (keywd == "pbc") {
      if (words.size() != 4)
        error->one(FLERR,"Improper POD file.", utils::getsyserror());
      pbc[0] = utils::inumeric(FLERR,words[1],false,lmp);
      pbc[1] = utils::inumeric(FLERR,words[2],false,lmp);
      pbc[2] = utils::inumeric(FLERR,words[3],false,lmp);
    }

    if ((keywd != "#") && (keywd != "species") && (keywd != "pbc")) {

      if (words.size() != 2)
        error->one(FLERR,"Improper POD file.", utils::getsyserror());

      if (keywd == "rin") rin = utils::numeric(FLERR,words[1],false,lmp);
      if (keywd == "rcut") rcut = utils::numeric(FLERR,words[1],false,lmp);
      if (keywd == "number_of_environment_clusters")
        nClusters = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "number_of_principal_components")
        nComponents = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "bessel_polynomial_degree")
        besseldegree = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "inverse_polynomial_degree")
        inversedegree = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "onebody") onebody = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "twobody_number_radial_basis_functions")
        nrbf2 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "threebody_number_radial_basis_functions")
        nrbf3 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "threebody_angular_degree")
        P3 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "fourbody_number_radial_basis_functions")
        nrbf4 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "fourbody_angular_degree")
        P4 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "fivebody_number_radial_basis_functions")
        nrbf33 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "fivebody_angular_degree")
        P33 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "sixbody_number_radial_basis_functions")
        nrbf34 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "sixbody_angular_degree")
        P34 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "sevenbody_number_radial_basis_functions")
        nrbf44 = utils::inumeric(FLERR,words[1],false,lmp);
      if (keywd == "sevenbody_angular_degree")
        P44 = utils::inumeric(FLERR,words[1],false,lmp);
    }
  }
  if (nrbf3 < nrbf4) error->all(FLERR,"number of four-body radial basis functions must be equal or less than number of three-body radial basis functions");
  if (nrbf4 < nrbf33) error->all(FLERR,"number of five-body radial basis functions must be equal or less than number of four-body radial basis functions");
  if (nrbf4 < nrbf34) error->all(FLERR,"number of six-body radial basis functions must be equal or less than number of four-body radial basis functions");
  if (nrbf4 < nrbf44) error->all(FLERR,"number of seven-body radial basis functions must be equal or less than number of four-body radial basis functions");
  nrbfmax = (nrbf2 < nrbf3) ? nrbf3 : nrbf2;
  nrbfmax = (nrbfmax < nrbf4) ? nrbf4 : nrbfmax;
  nrbfmax = (nrbfmax < nrbf33) ? nrbf33 : nrbfmax;
  nrbfmax = (nrbfmax < nrbf34) ? nrbf34 : nrbfmax;
  nrbfmax = (nrbfmax < nrbf44) ? nrbf44 : nrbfmax;

  if (P3 < P4) error->all(FLERR,"four-body angular degree must be equal or less than three-body angular degree");
  if (P4 < P33) error->all(FLERR,"five-body angular degree must be equal or less than four-body angular degree");
  if (P4 < P34) error->all(FLERR,"six-body angular degree must be equal or less than four-body angular degree");
  if (P4 < P44) error->all(FLERR,"seven-body angular degree must be equal or less than four-body angular degree");

  if (P3 > 12) error->all(FLERR,"three-body angular degree must be equal or less than 12");
  if (P4 > 6) error->all(FLERR,"four-body angular degree must be equal or less than 6");

  int Ne = nelements;
  memory->create(elemindex, Ne*Ne, "elemindex");
  int k = 0;
  for (int i1 = 0; i1<Ne; i1++)
    for (int i2 = i1; i2<Ne; i2++) {
      elemindex[i2 + Ne*i1] = k;
      elemindex[i1 + Ne*i2] = k;
      k += 1;
    }

  init2body();
  init3body(P3);
  init4body(P4);

  int nb[] = {1,     2,     4,     7,    11,    16,    23};
  nabf33 = P33+1;
  nabf34 = P34+1;
  nabf43 = nb[P34];
  nabf44 = nb[P44];

  if (onebody==0)
    nd1 = 0;
  else
    nd1 = Ne;

  nl1 = nd1/Ne;
  nl2 = nrbf2*Ne;
  nl3 = nabf3*nrbf3*Ne*(Ne+1)/2;
  nl4 = nabf4*nrbf4*Ne*(Ne+1)*(Ne+2)/6;

  nd2 = nl2*Ne;
  nd3 = nl3*Ne;
  nd4 = nl4*Ne;

  n23 = nrbf23*Ne;
  n32 = nabf23*nrbf23*Ne*(Ne+1)/2;
  n33 = nabf33*nrbf33*Ne*(Ne+1)/2;
  n34 = nabf34*nrbf34*Ne*(Ne+1)/2;
  n43 = nabf43*nrbf34*Ne*(Ne+1)*(Ne+2)/6;
  n44 = nabf44*nrbf44*Ne*(Ne+1)*(Ne+2)/6;

  nl23 = n23*n32;
  nl34 = n34*n43;
  nl33 = n33*(n33+1)/2;
  nl44 = n44*(n44+1)/2;

  nd23 = nl23*Ne;
  nd33 = nl33*Ne;
  nd34 = nl34*Ne;
  nd44 = nl44*Ne;

  memory->create(ind23, n23, "ind23");
  memory->create(ind32, n32, "ind32");
  memory->create(ind33, n33, "ind33");
  memory->create(ind34, n34, "ind34");
  memory->create(ind43, n43, "ind43");
  memory->create(ind44, n44, "ind44");

  indexmap3(ind23, 1, nrbf23, Ne, 1, nrbf2);
  indexmap3(ind32, nabf23, nrbf23, Ne*(Ne+1)/2, nabf3, nrbf3);
  indexmap3(ind33, nabf33, nrbf33, Ne*(Ne+1)/2, nabf3, nrbf3);
  indexmap3(ind34, nabf34, nrbf34, Ne*(Ne+1)/2, nabf3, nrbf3);
  indexmap3(ind43, nabf43, nrbf34, Ne*(Ne+1)*(Ne+2)/6, nabf4, nrbf4);
  indexmap3(ind44, nabf44, nrbf44, Ne*(Ne+1)*(Ne+2)/6, nabf4, nrbf4);

  nld33 = 0;
  nld34 = 0;
  nld44 = 0;
  int nebf3 = Ne*(Ne+1)/2;
  int nebf4 = Ne*(Ne+1)*(Ne+2)/6;
  int dabf3[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
  int dabf4[] = {0, 1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6};
  if (nrbf33>0) {
    nld33 = crossindices(dabf3, nabf3, nrbf3, nebf3, dabf3, nabf3, nrbf3, nebf3, P33, nrbf33);
    memory->create(ind33l, nld33, "ind33l");
    memory->create(ind33r, nld33, "ind33r");
    crossindices(ind33l, ind33r, dabf3, nabf3, nrbf3, nebf3, dabf3, nabf3, nrbf3, nebf3, P33, nrbf33);
  }
  if (nrbf34>0) {
    nld34 = crossindices(dabf3, nabf3, nrbf3, nebf3, dabf4, nabf4, nrbf4, nebf4, P34, nrbf34);
    memory->create(ind34l, nld34, "ind34l");
    memory->create(ind34r, nld34, "ind34r");
    crossindices(ind34l, ind34r, dabf3, nabf3, nrbf3, nebf3, dabf4, nabf4, nrbf4, nebf4, P34, nrbf34);
  }
  if (nrbf44>0) {
    nld44 = crossindices(dabf4, nabf4, nrbf4, nebf4, dabf4, nabf4, nrbf4, nebf4, P44, nrbf44);
    memory->create(ind44l, nld44, "ind44l");
    memory->create(ind44r, nld44, "ind44r");
    crossindices(ind44l, ind44r, dabf4, nabf4, nrbf4, nebf4, dabf4, nabf4, nrbf4, nebf4, P44, nrbf44);
  }
  ngd33 = nld33*Ne;
  ngd34 = nld34*Ne;
  ngd44 = nld44*Ne;
  nl33 = nld33;
  nl34 = nld34;
  nl44 = nld44;
  nd33 = ngd33;
  nd34 = ngd34;
  nd44 = ngd44;

  Mdesc = nl2 + nl3 + nl4 + nl23 + nl33 + nl34 + nl44;
  nl = nl1 + nl2 + nl3 + nl4 + nl23 + nl33 + nl34 + nl44;
  nd = nd1 + nd2 + nd3 + nd4 + nd23 + nd33 + nd34 + nd44;
  nCoeffPerElement = nl1 + Mdesc*nClusters;
  nCoeffAll = nCoeffPerElement*nelements;

  allocate_temp_memory(Njmax);

  if (comm->me == 0) {
    utils::logmesg(lmp, "**************** Begin of POD Potentials ****************\n");
    utils::logmesg(lmp, "species: ");
    for (int i=0; i<nelements; i++)
      utils::logmesg(lmp, "{} ", species[i]);
    utils::logmesg(lmp, "\n");
    utils::logmesg(lmp, "periodic boundary conditions: {} {} {}\n", pbc[0], pbc[1], pbc[2]);
    utils::logmesg(lmp, "number of environment clusters: {}\n", nClusters);
    utils::logmesg(lmp, "number of principal compoments: {}\n", nComponents);
    utils::logmesg(lmp, "inner cut-off radius: {}\n", rin);
    utils::logmesg(lmp, "outer cut-off radius: {}\n", rcut);
    utils::logmesg(lmp, "bessel polynomial degree: {}\n", besseldegree);
    utils::logmesg(lmp, "inverse polynomial degree: {}\n",inversedegree);
    utils::logmesg(lmp, "one-body potential: {}\n", onebody);
    utils::logmesg(lmp, "two-body radial basis functions: {}\n", nrbf2);
    utils::logmesg(lmp, "three-body radial basis functions: {}\n", nrbf3);
    utils::logmesg(lmp, "three-body angular degree: {}\n", P3);
    if (P23 < P4) {
      utils::logmesg(lmp, "four-body radial basis functions: {}\n", nrbf4);
      utils::logmesg(lmp, "four-body angular degree: {}\n", P4);
    }
    else {
      utils::logmesg(lmp, "four-body radial basis functions: {}\n", nrbf23);
      utils::logmesg(lmp, "four-body angular degree: {}\n", P23);
    }
    utils::logmesg(lmp, "five-body radial basis functions: {}\n", nrbf33);
    utils::logmesg(lmp, "five-body angular degree: {}\n", P33);
    utils::logmesg(lmp, "six-body radial basis functions: {}\n", nrbf34);
    utils::logmesg(lmp, "six-body angular degree: {}\n", P34);
    utils::logmesg(lmp, "seven-body radial basis functions: {}\n", nrbf44);
    utils::logmesg(lmp, "seven-body angular degree: {}\n", P44);
    utils::logmesg(lmp, "number of local descriptors per element for one-body potential: {}\n", nl1);
    utils::logmesg(lmp, "number of local descriptors per element for two-body potential: {}\n", nl2);
    utils::logmesg(lmp, "number of local descriptors per element for three-body potential: {}\n", nl3);
    utils::logmesg(lmp, "number of local descriptors per element for four-body potential: {}\n", nl4+nl23);
    utils::logmesg(lmp, "number of local descriptors per element for five-body potential: {}\n", nl33);
    utils::logmesg(lmp, "number of local descriptors per element for six-body potential: {}\n", nl34);
    utils::logmesg(lmp, "number of local descriptors per element for seven-body potential: {}\n", nl44);
    utils::logmesg(lmp, "number of local descriptors per element for all potentials: {}\n", nl);
    utils::logmesg(lmp, "number of global descriptors: {}\n", nCoeffAll);
    utils::logmesg(lmp, "**************** End of POD Potentials ****************\n\n");
  }
}

void EAPOD::read_model_coeff_file(std::string coeff_file)
{
  std::string coefffilename = coeff_file;
  FILE *fpcoeff;
  if (comm->me == 0) {

    fpcoeff = utils::open_potential(coefffilename,lmp,nullptr);
    if (fpcoeff == nullptr)
      error->one(FLERR,"Cannot open model coefficient file {}: ", coefffilename, utils::getsyserror());
  }

  // check format for first line of file

  char line[MAXLINE],*ptr;
  int eof = 0;
  int nwords = 0;
  while (nwords == 0) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }
    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) break;
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    // strip comment, skip line if blank
    nwords = utils::count_words(utils::trim_comment(line));
  }

  if (nwords != 4)
    error->all(FLERR,"Incorrect format in POD coefficient file");

  // strip single and double quotes from words

  int ncoeffall, nprojall, ncentall;
  std::string tmp_str;
  try {
    ValueTokenizer words(utils::trim_comment(line),"\"' \t\n\r\f");
    tmp_str = words.next_string();
    ncoeffall = words.next_int();
    nprojall = words.next_int();
    ncentall = words.next_int();
  } catch (TokenizerException &e) {
    error->all(FLERR,"Incorrect format in POD coefficient file: {}", e.what());
  }

  // loop over single block of coefficients and insert values in coeff

  memory->create(coeff, ncoeffall, "pod:pod_coeff");

  for (int icoeff = 0; icoeff < ncoeffall; icoeff++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) error->all(FLERR,"Incorrect format in model coefficient file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1) error->all(FLERR,"Incorrect format in model coefficient file");

      coeff[icoeff] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in model coefficient file: {}", e.what());
    }
  }

  memory->create(Proj, nprojall, "pod:pca_proj");

  for (int iproj = 0; iproj < nprojall; iproj++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) error->all(FLERR,"Incorrect format in model coefficient file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1) error->all(FLERR,"Incorrect format in model coefficient file");

      Proj[iproj] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in model coefficient file: {}", e.what());
    }
  }

  memory->create(Centroids, ncentall, "pod:pca_cent");

  for (int icent = 0; icent < ncentall; icent++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) error->all(FLERR,"Incorrect format in model coefficient file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1) error->all(FLERR,"Incorrect format in model coefficient file");

      Centroids[icent] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in model coefficient file: {}", e.what());
    }
  }

  if (comm->me == 0) {
    if (!eof) fclose(fpcoeff);
  }


  if (ncoeffall != nCoeffAll)
    error->all(FLERR,"number of coefficients in the coefficient file is not correct");

  if (nClusters > 1) {
    if (nprojall != nComponents*Mdesc*nelements)
      error->all(FLERR,"number of coefficients in the projection file is not correct");

    if (ncentall != nComponents*nClusters*nelements)
        error->all(FLERR,"number of coefficients in the projection file is not correct");
  }

  if (comm->me == 0) {
    utils::logmesg(lmp, "**************** Begin of Model Coefficients ****************\n");
    utils::logmesg(lmp, "total number of coefficients for POD potential: {}\n", ncoeffall);
    utils::logmesg(lmp, "total number of elements for PCA projection matrix: {}\n", nprojall);
    utils::logmesg(lmp, "total number of elements for PCA centroids: {}\n", ncentall);
    utils::logmesg(lmp, "**************** End of Model Coefficients ****************\n\n");
  }
}

int EAPOD::read_coeff_file(std::string coeff_file)
{
  std::string coefffilename = coeff_file;
  FILE *fpcoeff;
  if (comm->me == 0) {

    fpcoeff = utils::open_potential(coefffilename,lmp,nullptr);
    if (fpcoeff == nullptr)
      error->one(FLERR,"Cannot open POD coefficient file {}: ",
                                   coefffilename, utils::getsyserror());
  }

  // check format for first line of file

  char line[MAXLINE],*ptr;
  int eof = 0;
  int nwords = 0;
  while (nwords == 0) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }
    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) break;
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    // strip comment, skip line if blank

    nwords = utils::count_words(utils::trim_comment(line));
  }

  if (nwords != 2)
    error->all(FLERR,"Incorrect format in POD coefficient file");

  // strip single and double quotes from words

  int ncoeffall;
  std::string tmp_str;
  try {
    ValueTokenizer words(utils::trim_comment(line),"\"' \t\n\r\f");
    tmp_str = words.next_string();
    ncoeffall = words.next_int();
  } catch (TokenizerException &e) {
    error->all(FLERR,"Incorrect format in POD coefficient file: {}", e.what());
  }

  // loop over single block of coefficients and insert values in coeff

  memory->create(coeff, ncoeffall, "pod:pod_coeff");

  for (int icoeff = 0; icoeff < ncoeffall; icoeff++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcoeff);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcoeff);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof)
      error->all(FLERR,"Incorrect format in POD coefficient file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1)
        error->all(FLERR,"Incorrect format in POD coefficient file");

      coeff[icoeff] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in POD coefficient file: {}", e.what());
    }
  }

  if (comm->me == 0) {
    if (!eof) fclose(fpcoeff);
  }

  if (comm->me == 0) {
    utils::logmesg(lmp, "**************** Begin of POD Coefficients ****************\n");
    utils::logmesg(lmp, "total number of coefficients for POD potential: {}\n", ncoeffall);
    utils::logmesg(lmp, "**************** End of POD Coefficients ****************\n\n");
  }

  return ncoeffall;
}

// funcion to read the projection matrix from file.
int EAPOD::read_projection_matrix(std::string proj_file)
{
  std::string projfilename = proj_file;
  FILE *fpproj;
  if (comm->me == 0) {

    fpproj = utils::open_potential(projfilename,lmp,nullptr);
    if (fpproj == nullptr)
      error->one(FLERR,"Cannot open PCA projection matrix file {}: ",
                                   projfilename, utils::getsyserror());
  }

  // check format for first line of file

  char line[MAXLINE],*ptr;
  int eof = 0;
  int nwords = 0;
  while (nwords == 0) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpproj);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpproj);
      }
    }
    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) break;
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    // strip comment, skip line if blank

    nwords = utils::count_words(utils::trim_comment(line));
  }

  if (nwords != 2)
    error->all(FLERR,"Incorrect format in PCA projection matrix file");

  // strip single and double quotes from words

  int nprojall;
  std::string tmp_str;
  try {
    ValueTokenizer words(utils::trim_comment(line),"\"' \t\n\r\f");
    tmp_str = words.next_string();
    nprojall = words.next_int();
  } catch (TokenizerException &e) {
    error->all(FLERR,"Incorrect format in PCA projection matrix file: {}", e.what());
  }

  // loop over single block of coefficients and insert values in coeff

  memory->create(Proj, nprojall, "pod:pca_proj");

  for (int iproj = 0; iproj < nprojall; iproj++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpproj);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpproj);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof)
      error->all(FLERR,"Incorrect format in PCA projection matrix file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1)
        error->all(FLERR,"Incorrect format in PCA projection matrix file");

      Proj[iproj] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in PCA projection matrix file: {}", e.what());
    }
  }
  if (comm->me == 0) {
    if (!eof) fclose(fpproj);
  }

  if (comm->me == 0) {
    utils::logmesg(lmp, "**************** Begin of PCA projection matrix ****************\n");
    utils::logmesg(lmp, "total number of elements for PCA projection matrix: {}\n", nprojall);
    utils::logmesg(lmp, "**************** End of PCA projection matrix ****************\n\n");
  }

  return nprojall;
}

// read Centroids from file
int EAPOD::read_centroids(std::string centroids_file)
{
  std::string centfilename = centroids_file;
  FILE *fpcent;
  if (comm->me == 0) {

    fpcent = utils::open_potential(centfilename,lmp,nullptr);
    if (fpcent == nullptr)
      error->one(FLERR,"Cannot open PCA centroids file {}: ",
                                   centfilename, utils::getsyserror());
  }

  // check format for first line of file

  char line[MAXLINE],*ptr;
  int eof = 0;
  int nwords = 0;
  while (nwords == 0) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcent);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcent);
      }
    }
    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof) break;
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    // strip comment, skip line if blank

    nwords = utils::count_words(utils::trim_comment(line));
  }

  if (nwords != 2)
    error->all(FLERR,"Incorrect format in PCA centroids file");

  // strip single and double quotes from words

  int ncentall;
  std::string tmp_str;
  try {
    ValueTokenizer words(utils::trim_comment(line),"\"' \t\n\r\f");
    tmp_str = words.next_string();
    ncentall = words.next_int();
  } catch (TokenizerException &e) {
    error->all(FLERR,"Incorrect format in PCA centroids file: {}", e.what());
  }

  // loop over single block of coefficients and insert values in coeff

  memory->create(Centroids, ncentall, "pod:pca_cent");

  for (int icent = 0; icent < ncentall; icent++) {
    if (comm->me == 0) {
      ptr = fgets(line,MAXLINE,fpcent);
      if (ptr == nullptr) {
        eof = 1;
        fclose(fpcent);
      }
    }

    MPI_Bcast(&eof,1,MPI_INT,0,world);
    if (eof)
      error->all(FLERR,"Incorrect format in PCA centroids file");
    MPI_Bcast(line,MAXLINE,MPI_CHAR,0,world);

    try {
      ValueTokenizer cff(utils::trim_comment(line));
      if (cff.count() != 1)
        error->all(FLERR,"Incorrect format in PCA centroids file");

      Centroids[icent] = cff.next_double();
    } catch (TokenizerException &e) {
      error->all(FLERR,"Incorrect format in PCA centroids file: {}", e.what());
    }
  }
  if (comm->me == 0) {
    if (!eof) fclose(fpcent);
  }

  if (comm->me == 0) {
    utils::logmesg(lmp, "**************** Begin of PCA centroids ****************\n");
    utils::logmesg(lmp, "total number of elements for PCA centroids: {}\n", ncentall);
    utils::logmesg(lmp, "**************** End of PCA centroids ****************\n\n");
  }

  return ncentall;
}


void EAPOD::peratombase_descriptors(double *bd1, double *bdd1, double *rij, double *temp,
        int *tj, int Nj)
{
  for (int i=0; i<Mdesc; i++) bd1[i] = 0.0;
  for (int i=0; i<3*Nj*Mdesc; i++) bdd1[i] = 0.0;

  if (Nj == 0) return;

  double *d2 =  &bd1[0]; // nl2
  double *d3 =  &bd1[nl2]; // nl3
  double *d4 =  &bd1[nl2 + nl3]; // nl4
  double *d23 =  &bd1[nl2 + nl3 + nl4]; // nl23
  double *d33 =  &bd1[nl2 + nl3 + nl4 + nl23]; // nl33
  double *d34 =  &bd1[nl2 + nl3 + nl4 + nl23 + nl33]; // nl34
  double *d44 =  &bd1[nl2 + nl3 + nl4 + nl23 + nl33 + nl34]; // nl44

  double *dd2 = &bdd1[0]; // 3*Nj*nl2
  double *dd3 = &bdd1[3*Nj*nl2]; // 3*Nj*nl3
  double *dd4 = &bdd1[3*Nj*(nl2+nl3)]; // 3*Nj*nl4
  double *dd23 = &bdd1[3*Nj*(nl2+nl3+nl4)]; // 3*Nj*nl23
  double *dd33 = &bdd1[3*Nj*(nl2+nl3+nl4+nl23)]; // 3*Nj*nl33
  double *dd34 = &bdd1[3*Nj*(nl2+nl3+nl4+nl23+nl33)]; // 3*Nj*nl34
  double *dd44 = &bdd1[3*Nj*(nl2+nl3+nl4+nl23+nl33+nl34)]; // 3*Nj*nl44

  int n1 = Nj*K3*nrbf3;
  int n2 = Nj*nrbfmax;
  int n3 = Nj*ns;
  int n4 = Nj*K3;
  int n5 = K3*nrbf3*nelements;

  double *U = &temp[0]; // Nj*K3*nrbf3
  double *Ux = &temp[n1]; // Nj*K3*nrbf3
  double *Uy = &temp[2*n1]; // Nj*K3*nrbf3
  double *Uz = &temp[3*n1]; // Nj*K3*nrbf3
  double *sumU = &temp[4*n1]; // K3*nrbf3*nelements

  double *rbf = &temp[4*n1 + n5]; // Nj*nrbf2
  double *rbfx = &temp[4*n1 + n5 + n2]; // Nj*nrbf2
  double *rbfy = &temp[4*n1 + n5 + 2*n2]; // Nj*nrbf2
  double *rbfz = &temp[4*n1 + n5 + 3*n2]; // Nj*nrbf2

  double *rbft = &temp[4*n1 + n5 + 4*n2]; // Nj*ns
  double *rbfxt = &temp[4*n1 + n5 + 4*n2 + n3]; // Nj*ns
  double *rbfyt = &temp[4*n1 + n5 + 4*n2 + 2*n3]; // Nj*ns
  double *rbfzt = &temp[4*n1 + n5 + 4*n2 + 3*n3]; // Nj*ns

  radialbasis(rbft, rbfxt, rbfyt, rbfzt, rij, besselparams, rin, rcut-rin, pdegree[0], pdegree[1], nbesselpars, Nj);

  char chn = 'N';
  double alpha = 1.0, beta = 0.0;
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbft, &Nj, Phi, &ns, &beta, rbf, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfxt, &Nj, Phi, &ns, &beta, rbfx, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfyt, &Nj, Phi, &ns, &beta, rbfy, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfzt, &Nj, Phi, &ns, &beta, rbfz, &Nj);

  if ((nl2>0) && (Nj>0)) {
    twobodydescderiv(d2, dd2, rbf, rbfx, rbfy, rbfz, tj, Nj);
  }

  if ((nl3 > 0) && (Nj>1)) {
    double *abf = &temp[4*n1 + n5 + 4*n2]; // Nj*K3
    double *abfx = &temp[4*n1 + n5 + 4*n2 + n4]; // Nj*K3
    double *abfy = &temp[4*n1 + n5 + 4*n2 + 2*n4]; // Nj*K3
    double *abfz = &temp[4*n1 + n5 + 4*n2 + 3*n4]; // Nj*K3
    double *tm = &temp[4*n1 + n5 + 4*n2 + 4*n4]; // 4*K3

    angularbasis(abf, abfx, abfy, abfz, rij, tm, pq3, Nj, K3);

    radialangularbasis(sumU, U, Ux, Uy, Uz, rbf, rbfx, rbfy, rbfz,
            abf, abfx, abfy, abfz, tj, Nj, K3, nrbf3, nelements);

    threebodydesc(d3, sumU);
    threebodydescderiv(dd3, sumU, Ux, Uy, Uz, tj, Nj);

    if ((nl23>0) && (Nj>2)) {
      fourbodydesc23(d23, d2, d3);
      fourbodydescderiv23(dd23, d2, d3, dd2, dd3, 3*Nj);
    }

    if ((nl33>0) && (Nj>3)) {
      crossdesc(d33, d3, d3, ind33l, ind33r, nl33);
      crossdescderiv(dd33, d3, d3, dd3, dd3, ind33l, ind33r, nl33, 3*Nj);
    }

    if ((nl4 > 0) && (Nj>2)) {
      if (K4 < K3) {
        for (int m=0; m<nrbf4; m++)
          for (int k=0; k<K4; k++)
            for (int i=0; i<nelements; i++)
              sumU[i + nelements*k + nelements*K4*m] = sumU[i + nelements*k + nelements*K3*m];

        for (int m=0; m<nrbf4; m++)
          for (int k=0; k<K4; k++)
            for (int i=0; i<Nj; i++) {
              int ii = i + Nj*k + Nj*K4*m;
              int jj = i + Nj*k + Nj*K3*m;
              Ux[ii] = Ux[jj];
              Uy[ii] = Uy[jj];
              Uz[ii] = Uz[jj];
            }
      }
      fourbodydescderiv(d4, dd4, sumU, Ux, Uy, Uz, tj, Nj);

      if ((nl34>0) && (Nj>4)) {
        crossdesc(d34, d3, d4, ind34l, ind34r, nl34);
        crossdescderiv(dd34, d3, d4, dd3, dd4, ind34l, ind34r, nl34, 3*Nj);
      }

      if ((nl44>0) && (Nj>5)) {
        crossdesc(d44, d4, d4, ind44l, ind44r, nl44);
        crossdescderiv(dd44, d4, d4, dd4, dd4, ind44l, ind44r, nl44, 3*Nj);
      }
    }
  }
}

double EAPOD::peratombase_coefficients(double *cb, double *bd, int *ti)
{
  int nc = nCoeffPerElement*(ti[0]-1);

  double ei = coeff[0 + nc];
  for (int m=0; m<Mdesc; m++) {
    ei += coeff[1 + m + nc]*bd[m];
    cb[m] = coeff[1 + m + nc];
  }

  return ei;
}

double EAPOD::peratom_environment_descriptors(double *cb, double *bd, double *tm, int *ti)
{
  double *P    = &tm[0];    // nClusters
  double *cp   = &tm[(nClusters)];  // nClusters
  double *D    = &tm[(2*nClusters)];   // nClusters
  double *pca  = &tm[(3*nClusters)]; // nComponents

  double *proj = &Proj[0];
  double *cent = &Centroids[0];
  int typei = ti[0]-1;

  for (int k=0; k<nComponents; k++) {
    double sum = 0.0;
    for (int m = 0; m < Mdesc; m++) {
      sum += proj[k + nComponents*m + nComponents*Mdesc*typei] * bd[m];
    }
    pca[k] = sum;
  }

  for (int j=0; j<nClusters; j++) {
    double sum = 1e-20;
    for (int k = 0; k < nComponents; k++) {
      double c = cent[k + j * nComponents + nClusters*nComponents*typei];
      double p = pca[k];
      sum += (p - c) * (p - c);
    }
    D[j] = 1.0 / sum;
  }

  double sum = 0.0;
  for (int j = 0; j < nClusters; j++) sum += D[j];
  double sumD = sum;
  if (sum != 0.0) {
    for (int j = 0; j < nClusters; j++) P[j] = D[j]/sum;
  } else {
    for (int j = 0; j < nClusters; j++) P[j] = 0.0;
  }

  int nc = nCoeffPerElement*(ti[0]-1);
  double ei = coeff[0 + nc];
  for (int k = 0; k<nClusters; k++)
    for (int m=0; m<Mdesc; m++)
      ei += coeff[1 + m + Mdesc*k + nc]*bd[m]*P[k];

  for (int k=0; k<nClusters; k++) {
    double sum = 0;
    for (int m = 0; m<Mdesc; m++)
      sum += coeff[1 + m + k*Mdesc + nc]*bd[m];
    cp[k] = sum;
  }

  for (int m = 0; m<Mdesc; m++) {
    double sum = 0.0;
    for (int k = 0; k<nClusters; k++)
      sum += coeff[1 + m + k*Mdesc + nc]*P[k];
    cb[m] = sum;
  }

  for (int m = 0; m<Mdesc; m++) {
    double S1 = 1.0/sumD;
    double S2 = S1*S1;
    double sum = 0.0;
    for (int j=0; j<nClusters; j++) {
      double dP_dB = 0.0;
      for (int k = 0; k < nClusters; k++) {
        double dP_dD = -D[j] * S2;
        if (k==j) dP_dD += S1;
        double dD_dB = 0.0;
        double D2 = 2 * D[k] * D[k];
        for (int n = 0; n < nComponents; n++) {
          double dD_dpca = D2 * (cent[n + k * nComponents + nClusters*nComponents*typei] - pca[n]);
          dD_dB += dD_dpca * proj[n + m * nComponents + nComponents*Mdesc*typei];
        }
        dP_dB += dP_dD * dD_dB;
      }
      sum += cp[j]*dP_dB;
    }
    cb[m] += sum;
  }

  return ei;
}

void EAPOD::twobody_forces(double *fij, double *cb2, double *rbfx, double *rbfy, double *rbfz, int *tj, int Nj)
{
  // Calculate the two-body descriptors and their derivatives
  int totalIterations = nrbf2 * Nj;
  for (int idx = 0; idx < totalIterations; idx++) {
    int n = idx / nrbf2; // Recalculate m
    int m = idx % nrbf2; // Recalculate n

    int i2 = n + Nj * m; // Index of the radial basis function for atom n and RBF m
    int i1 = 3*n;
    double c = cb2[m + nrbf2*(tj[n] - 1)];
    fij[0 + i1] += c*rbfx[i2]; // Add the derivative with respect to x to the corresponding descriptor derivative
    fij[1 + i1] += c*rbfy[i2]; // Add the derivative with respect to y to the corresponding descriptor derivative
    fij[2 + i1] += c*rbfz[i2]; // Add the derivative with respect to z to the corresponding descriptor derivative
  }
}

void EAPOD::threebody_forcecoeff(double *fb3, double *cb3, double *sumU)
{
  if (nelements==1) {
    for (int m = 0; m < nrbf3; ++m) {
      for (int p = 0; p < nabf3; p++) {
        double c3 = 2.0 * cb3[p + nabf3*m];
        int n1 = pn3[p];
        int n2 = pn3[p + 1];
        int nn = n2 - n1;
        int idxU = K3 * m;
        for (int q = 0; q < nn; q++) {
          int k = n1 + q;
          fb3[k + idxU] += c3 * pc3[k] * sumU[k + idxU];
        }
      }
    }
  }
  else {
    int N3 = nabf3 * nrbf3;
    for (int m = 0; m < nrbf3; ++m) {
      for (int p = 0; p < nabf3; p++) {
        int n1 = pn3[p];
        int n2 = pn3[p + 1];
        int nn = n2 - n1;
        int jmp = p + nabf3*m;
        for (int q = 0; q < nn; q++) {
          int k = n1 + q;  // Combine n1 and q into a single index
          int idxU = nelements * k + nelements * K3 * m;
          for (int i1 = 0; i1 < nelements; i1++) {
            double tm = pc3[k] * sumU[i1 + idxU];
            for (int i2 = i1; i2 < nelements; i2++) {
              int em = elemindex[i2 + nelements * i1];
              double t1 = tm * cb3[jmp + N3*em]; // Ni *  nabf3 * nrbf3 * nelements*(nelements+1)/2
              fb3[i2 + idxU] += t1;   // K3*nrbf3*Ni
              fb3[i1 + idxU] += pc3[k] * cb3[jmp + N3*em] * sumU[i2 + idxU];
            }
          }
        }
      }
    }
  }
}

void EAPOD::fourbody_forcecoeff(double *fb4, double *cb4, double *sumU)
{
  if (nelements==1) {
    for (int m = 0; m < nrbf4; ++m) {
      int idxU = K3 * m;
      for (int p = 0; p < nabf4; p++) {
        int n1 = pa4[p];
        int n2 = pa4[p + 1];
        int nn = n2 - n1;
        double c4 = cb4[p + nabf4*m];
        for (int q = 0; q < nn; q++) {
          int idxNQ = n1 + q;  // Combine n1 and q into a single index
          double c = c4 * pc4[idxNQ];
          int j1 = idxU + pb4[idxNQ];
          int j2 = idxU + pb4[idxNQ + Q4];
          int j3 = idxU + pb4[idxNQ + 2 * Q4];
          double c1 = sumU[j1];
          double c2 = sumU[j2];
          double c3 = sumU[j3];
          fb4[j3] += c * c1 * c2;
          fb4[j2] += c * c1 * c3;
          fb4[j1] += c * c2 * c3;
        }
      }
    }
  }
  else {
    int N3 = nabf4 * nrbf4;
    for (int m = 0; m < nrbf4; ++m) {
      for (int p = 0; p < nabf4; p++)  {
        int n1 = pa4[p];
        int n2 = pa4[p + 1];
        int nn = n2 - n1;
        int jpm = p + nabf4*m;
        for (int q = 0; q < nn; q++) {
          int c = pc4[n1 + q];
          int j1 = pb4[n1 + q];
          int j2 = pb4[n1 + q + Q4];
          int j3 = pb4[n1 + q + 2 * Q4];
          int idx1 = nelements * j1 + nelements * K3 * m;
          int idx2 = nelements * j2 + nelements * K3 * m;
          int idx3 = nelements * j3 + nelements * K3 * m;
          int k = 0;
          for (int i1 = 0; i1 < nelements; i1++) {
            double c1 = sumU[idx1 + i1];
            for (int i2 = i1; i2 < nelements; i2++) {
              double c2 = sumU[idx2 + i2];
              for (int i3 = i2; i3 < nelements; i3++) {
                double c3 = sumU[idx3 + i3];
                double c4 = c * cb4[jpm + N3*k];
                fb4[idx3 + i3] += c4*(c1 * c2);
                fb4[idx2 + i2] += c4*(c1 * c3);
                fb4[idx1 + i1] += c4*(c2 * c3);
                k += 1;
              }
            }
          }
        }
      }
    }
  }
}

void EAPOD::allbody_forces(double *fij, double *forcecoeff, double *rbf, double *rbfx, double *rbfy, double *rbfz,
        double *abf, double *abfx, double *abfy, double *abfz, int *tj, int Nj)
{
  int totalIterations = nrbf3 * Nj;
  for (int idx = 0; idx < totalIterations; ++idx) {
    int j = idx / nrbf3;  // Derive the original j value
    int m = idx % nrbf3;  // Derive the original m value
    int idxR = m + nrbfmax * j;  // Pre-compute the index for rbf
    int i2 = tj[j] - 1;
    double rbfBase = rbf[idxR];
    double rbfxBase = rbfx[idxR];
    double rbfyBase = rbfy[idxR];
    double rbfzBase = rbfz[idxR];
    double fx = 0;
    double fy = 0;
    double fz = 0;
    for (int k = 0; k < K3; k++) {
      int idxU = nelements * k + nelements * K3 * m;
      double fc = forcecoeff[i2 + idxU];
      int idxA = k + K3 * j;  // Pre-compute the index for abf
      double abfA = abf[idxA];
      double abfxA = abfx[idxA];
      double abfyA = abfy[idxA];
      double abfzA = abfz[idxA];
      fx += fc * (abfxA * rbfBase + rbfxBase * abfA); // K3*nrbf3*Nij
      fy += fc * (abfyA * rbfBase + rbfyBase * abfA);
      fz += fc * (abfzA * rbfBase + rbfzBase * abfA);
    }
    int baseIdx = 3 * j;
    fij[baseIdx]     += fx;
    fij[baseIdx + 1] += fy;
    fij[baseIdx + 2] += fz;
  }
}

void EAPOD::allbody_forces(double *fij, double *forcecoeff, double *Ux, double *Uy, double *Uz, int *tj, int Nj)
{
  for (int j = 0; j < Nj; ++j) {
    int i2 = tj[j] - 1;
    double fx = 0;
    double fy = 0;
    double fz = 0;
    for (int m = 0; m < nrbf3; m++)
      for (int k = 0; k < K3; k++) {
        double fc = forcecoeff[i2 + nelements * k + nelements * K3 * m];
        int idxU = j + Nj*k + Nj * K3 * m;  // Pre-compute the index for abf
        fx += fc * Ux[idxU]; // K3*nrbf3*Nij
        fy += fc * Uy[idxU];
        fz += fc * Uz[idxU];
      }
    int baseIdx = 3 * j;
    fij[baseIdx]     += fx;
    fij[baseIdx + 1] += fy;
    fij[baseIdx + 2] += fz;
  }
}

double EAPOD::peratomenergyforce2(double *fij, double *rij, double *temp,
        int *ti, int *tj, int Nj)
{
  if (Nj==0) {
    return coeff[nCoeffPerElement*(ti[0]-1)];
  }

  int N = 3*Nj;
  for (int n=0; n<N; n++) fij[n] = 0.0;

  //double *coeff1 = &coeff[nCoeffPerElement*(ti[0]-1)];
  double e = 0.0;
  for (int i=0; i<Mdesc; i++) bd[i] = 0.0;

  double *d2 =  &bd[0]; // nl2
  double *d3 =  &bd[nl2]; // nl3
  double *d4 =  &bd[nl2 + nl3]; // nl4
  double *d23 =  &bd[nl2 + nl3 + nl4]; // nl23
  double *d33 =  &bd[nl2 + nl3 + nl4 + nl23]; // nl33
  double *d34 =  &bd[nl2 + nl3 + nl4 + nl23 + nl33]; // nl34
  double *d44 =  &bd[nl2 + nl3 + nl4 + nl23 + nl33 + nl34]; // nl44

  int n1 = Nj*K3*nrbf3;
  int n2 = Nj*nrbfmax;
  int n3 = Nj*ns;
  int n4 = Nj*K3;
  int n5 = K3*nrbf3*nelements;

  double *U = &temp[0]; // Nj*K3*nrbf3
  double *Ux = &temp[n1]; // Nj*K3*nrbf3
  double *Uy = &temp[2*n1]; // Nj*K3*nrbf3
  double *Uz = &temp[3*n1]; // Nj*K3*nrbf3
  double *sumU = &temp[4*n1]; // K3*nrbf3*nelements

  double *rbf = &temp[4*n1 + n5]; // Nj*nrbf2
  double *rbfx = &temp[4*n1 + n5 + n2]; // Nj*nrbf2
  double *rbfy = &temp[4*n1 + n5 + 2*n2]; // Nj*nrbf2
  double *rbfz = &temp[4*n1 + n5 + 3*n2]; // Nj*nrbf2

  double *rbft = &temp[4*n1 + n5 + 4*n2]; // Nj*ns
  double *rbfxt = &temp[4*n1 + n5 + 4*n2 + n3]; // Nj*ns
  double *rbfyt = &temp[4*n1 + n5 + 4*n2 + 2*n3]; // Nj*ns
  double *rbfzt = &temp[4*n1 + n5 + 4*n2 + 3*n3]; // Nj*ns

  radialbasis(rbft, rbfxt, rbfyt, rbfzt, rij, besselparams, rin, rcut-rin, pdegree[0], pdegree[1], nbesselpars, Nj);

  char chn = 'N';
  double alpha = 1.0, beta = 0.0;
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbft, &Nj, Phi, &ns, &beta, rbf, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfxt, &Nj, Phi, &ns, &beta, rbfx, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfyt, &Nj, Phi, &ns, &beta, rbfy, &Nj);
  DGEMM(&chn, &chn, &Nj, &nrbfmax, &ns, &alpha, rbfzt, &Nj, Phi, &ns, &beta, rbfz, &Nj);

  if ((nl2>0) && (Nj>0)) {
    twobodydesc(d2, rbf, tj, Nj);
  }

  if ((nl3 > 0) && (Nj>1)) {
    double *abf = &temp[4*n1 + n5 + 4*n2]; // Nj*K3
    double *abfx = &temp[4*n1 + n5 + 4*n2 + n4]; // Nj*K3
    double *abfy = &temp[4*n1 + n5 + 4*n2 + 2*n4]; // Nj*K3
    double *abfz = &temp[4*n1 + n5 + 4*n2 + 3*n4]; // Nj*K3
    double *tm = &temp[4*n1 + n5 + 4*n2 + 4*n4]; // 4*K3

    angularbasis(abf, abfx, abfy, abfz, rij, tm, pq3, Nj, K3);

    radialangularbasis(sumU, U, Ux, Uy, Uz, rbf, rbfx, rbfy, rbfz,
            abf, abfx, abfy, abfz, tj, Nj, K3, nrbf3, nelements);

    threebodydesc(d3, sumU);

    if ((nl23>0) && (Nj>2)) {
      fourbodydesc23(d23, d2, d3);
    }

    if ((nl33>0) && (Nj>3)) {
      crossdesc(d33, d3, d3, ind33l, ind33r, nl33);
    }

    if ((nl4 > 0) && (Nj>2)) {
      fourbodydesc(d4, sumU);

      if ((nl34>0) && (Nj>4)) {
        crossdesc(d34, d3, d4, ind34l, ind34r, nl34);
      }

      if ((nl44>0) && (Nj>5)) {
        crossdesc(d44, d4, d4, ind44l, ind44r, nl44);
      }
    }
  }

  double *cb = &bdd[0];
  if (nClusters > 1) {
    e += peratom_environment_descriptors(cb, bd, &temp[4*n1 + n5 + 4*n2], ti);
  }
  else {
    e += peratombase_coefficients(cb, bd, ti);
  }

  double *cb2 =  &cb[0]; // nl3
  double *cb3 =  &cb[nl2]; // nl3
  double *cb4 =  &cb[(nl2 + nl3)]; // nl4
  double *cb33 = &cb[(nl2 + nl3 + nl4)]; // nl33
  double *cb34 = &cb[(nl2 + nl3 + nl4 + nl33)]; // nl34
  double *cb44 = &cb[(nl2 + nl3 + nl4 + nl33 + nl34)]; // nl44

  if ((nl33>0) && (Nj>3)) {
    crossdesc_reduction(cb3, cb3, cb33, d3, d3, ind33l, ind33r, nl33);
  }
  if ((nl34>0) && (Nj>4)) {
    crossdesc_reduction(cb3, cb4, cb34, d3, d4, ind34l, ind34r, nl34);
  }
  if ((nl44>0) && (Nj>5)) {
    crossdesc_reduction(cb4, cb4, cb44, d4, d4, ind44l, ind44r, nl44);
  }

  if ((nl2 > 0) && (Nj>0)) twobody_forces(fij, cb2, rbfx, rbfy, rbfz, tj, Nj);

  // Initialize forcecoeff to zero
  double *forcecoeff = &cb[(nl2 + nl3 + nl4)]; // nl33
  std::fill(forcecoeff, forcecoeff + nelements * K3 * nrbf3, 0.0);
  if ((nl3 > 0) && (Nj>1)) threebody_forcecoeff(forcecoeff, cb3, sumU);
  if ((nl4 > 0) && (Nj>2)) fourbody_forcecoeff(forcecoeff, cb4, sumU);
  if ((nl3 > 0) && (Nj>1)) allbody_forces(fij, forcecoeff, Ux, Uy, Uz, tj, Nj);

  return e;
}

double EAPOD::peratomenergyforce(double *fij, double *rij, double *temp,
        int *ti, int *tj, int Nj)
{
  if (Nj==0) {
    return coeff[nCoeffPerElement*(ti[0]-1)];
  }

  int N = 3*Nj;
  for (int n=0; n<N; n++) fij[n] = 0.0;

  double *coeff1 = &coeff[nCoeffPerElement*(ti[0]-1)];
  double e = coeff1[0];

  // calculate base descriptors and their derivatives with respect to atom coordinates
  peratombase_descriptors(bd, bdd, rij, temp, tj, Nj);

  if (nClusters > 1) { // multi-environment descriptors
    // calculate multi-environment descriptors and their derivatives with respect to atom coordinates
    peratomenvironment_descriptors(pd, pdd, bd, bdd, temp, ti[0] - 1,  Nj);

    for (int j = 0; j<nClusters; j++)
      for (int m=0; m<Mdesc; m++)
        e += coeff1[1 + m + j*Mdesc]*bd[m]*pd[j];

    double *cb = &temp[0];
    double *cp = &temp[Mdesc];
    for (int m = 0; m<Mdesc; m++) cb[m] = 0.0;
    for (int j = 0; j<nClusters; j++) cp[j] = 0.0;
    for (int j = 0; j<nClusters; j++)
      for (int m = 0; m<Mdesc; m++)  {
        cb[m] += coeff1[1 + m + j*Mdesc]*pd[j];
        cp[j] += coeff1[1 + m + j*Mdesc]*bd[m];
      }
    char chn = 'N';
    double alpha = 1.0, beta = 0.0;
    int inc1 = 1;
    DGEMV(&chn, &N, &Mdesc, &alpha, bdd, &N, cb, &inc1, &beta, fij, &inc1);
    DGEMV(&chn, &N, &nClusters, &alpha, pdd, &N, cp, &inc1, &alpha, fij, &inc1);
  }
  else { // single-environment descriptors
    for (int m=0; m<Mdesc; m++)
      e += coeff1[1+m]*bd[m];

    char chn = 'N';
    double alpha = 1.0, beta = 0.0;
    int inc1 = 1;
    DGEMV(&chn, &N, &Mdesc, &alpha, bdd, &N, &coeff1[1], &inc1, &beta, fij, &inc1);
  }

  return e;
}

double EAPOD::energyforce(double *force, double *x, int *atomtype, int *alist,
          int *jlist, int *pairnumsum, int natom)
{
  double etot = 0.0;
  for (int i=0; i<3*natom; i++) force[i] = 0.0;

  for (int i=0; i<natom; i++) {
    int Nj = pairnumsum[i+1] - pairnumsum[i]; // # neighbors around atom i

    if (Nj==0) {
      etot += coeff[nCoeffPerElement*(atomtype[i]-1)];
    }
    else
    {
      // reallocate temporary memory
      if (Nj>Njmax) {
        Njmax = Nj;
        free_temp_memory();
        allocate_temp_memory(Njmax);
      }

      double *rij = &tmpmem[0];    // 3*Nj
      double *fij = &tmpmem[3*Nj]; // 3*Nj
      int *ai = &tmpint[0];        // Nj
      int *aj = &tmpint[Nj];       // Nj
      int *ti = &tmpint[2*Nj];     // Nj
      int *tj = &tmpint[3*Nj];     // Nj

      myneighbors(rij, x, ai, aj, ti, tj, jlist, pairnumsum, atomtype, alist, i);

      etot += peratomenergyforce(fij, rij, &tmpmem[6*Nj], ti, tj, Nj);

      tallyforce(force, fij, ai, aj, Nj);
    }
  }

  return etot;
}

void EAPOD::base_descriptors(double *basedesc, double *x,
        int *atomtype, int *alist, int *jlist, int *pairnumsum, int natom)
{
  for (int i=0; i<natom*Mdesc; i++) basedesc[i] = 0.0;

  for (int i=0; i<natom; i++) {
    int Nj = pairnumsum[i+1] - pairnumsum[i]; // # neighbors around atom i

    if (Nj>0) {
      // reallocate temporary memory
      if (Nj>Njmax) {
        Njmax = Nj;
        free_temp_memory();
        allocate_temp_memory(Njmax);
        if (comm->me == 0) utils::logmesg(lmp, "reallocate temporary memory with Njmax = %d ...\n", Njmax);
      }

      double *rij = &tmpmem[0]; // 3*Nj
      int *ai = &tmpint[0];     // Nj
      int *aj = &tmpint[Nj];   // Nj
      int *ti = &tmpint[2*Nj]; // Nj
      int *tj = &tmpint[3*Nj]; // Nj

      myneighbors(rij, x, ai, aj, ti, tj, jlist, pairnumsum, atomtype, alist, i);

      // many-body descriptors
      peratombase_descriptors(bd, bdd, rij, &tmpmem[3*Nj], tj, Nj);

      for (int m=0; m<Mdesc; m++) {
        basedesc[i + natom*(m)] = bd[m];
      }

    }
  }
}

void EAPOD::descriptors(double *gd, double *gdd, double *basedesc, double *x,
        int *atomtype, int *alist, int *jlist, int *pairnumsum, int natom)
{
  for (int i=0; i<nd; i++) gd[i] = 0.0;
  for (int i=0; i<3*natom*nd; i++) gdd[i] = 0.0;
  for (int i=0; i<natom*Mdesc; i++) basedesc[i] = 0.0;

  for (int i=0; i<natom; i++) {
    int Nj = pairnumsum[i+1] - pairnumsum[i]; // # neighbors around atom i

    // one-body descriptor
    if (nd1>0) {
      gd[nCoeffPerElement*(atomtype[i]-1)] += 1.0;
    }

    if (Nj>0) {
      // reallocate temporary memory
      if (Nj>Njmax) {
        Njmax = Nj;
        free_temp_memory();
        allocate_temp_memory(Njmax);
        if (comm->me == 0) utils::logmesg(lmp, "reallocate temporary memory with Njmax = %d ...\n", Njmax);
      }

      double *rij = &tmpmem[0]; // 3*Nj
      int *ai = &tmpint[0];     // Nj
      int *aj = &tmpint[Nj];   // Nj
      int *ti = &tmpint[2*Nj]; // Nj
      int *tj = &tmpint[3*Nj]; // Nj

      myneighbors(rij, x, ai, aj, ti, tj, jlist, pairnumsum, atomtype, alist, i);

      // many-body descriptors
      peratombase_descriptors(bd, bdd, rij, &tmpmem[3*Nj], tj, Nj);

      for (int m=0; m<Mdesc; m++) {
        basedesc[i + natom*(m)] = bd[m];
        int k = nCoeffPerElement*(ti[0]-1) + nl1 + m; // increment by nl1 because of the one-body descriptor
        gd[k] += bd[m];
        for (int n=0; n<Nj; n++) {
          int im = 3*ai[n] + 3*natom*k;
          int jm = 3*aj[n] + 3*natom*k;
          int nm = 3*n + 3*Nj*m;
          gdd[0 + im] += bdd[0 + nm];
          gdd[1 + im] += bdd[1 + nm];
          gdd[2 + im] += bdd[2 + nm];
          gdd[0 + jm] -= bdd[0 + nm];
          gdd[1 + jm] -= bdd[1 + nm];
          gdd[2 + jm] -= bdd[2 + nm];
        }
      }

    }
  }
}

void EAPOD::descriptors(double *gd, double *gdd, double *basedesc, double *probdesc, double *x,
        int *atomtype, int *alist, int *jlist, int *pairnumsum, int natom)
{
  for (int i=0; i<nCoeffAll; i++) gd[i] = 0.0;
  for (int i=0; i<3*natom*nCoeffAll; i++) gdd[i] = 0.0;
  for (int i=0; i<natom*Mdesc; i++) basedesc[i] = 0.0;
  for (int i=0; i<natom*nClusters; i++) probdesc[i] = 0.0;

  for (int i=0; i<natom; i++) {
    int Nj = pairnumsum[i+1] - pairnumsum[i]; // # neighbors around atom i

    // one-body descriptor
    if (nd1>0) {
      gd[nCoeffPerElement*(atomtype[i]-1)] += 1.0;
    }

    if (Nj>0) {
      // reallocate temporary memory
      if (Nj>Njmax) {
        Njmax = Nj;
        free_temp_memory();
        allocate_temp_memory(Njmax);
        if (comm->me == 0) utils::logmesg(lmp, "reallocate temporary memory with Njmax = %d ...\n", Njmax);
      }

      double *rij = &tmpmem[0]; // 3*Nj
      int *ai = &tmpint[0];     // Nj
      int *aj = &tmpint[Nj];   // Nj
      int *ti = &tmpint[2*Nj]; // Nj
      int *tj = &tmpint[3*Nj]; // Nj

      myneighbors(rij, x, ai, aj, ti, tj, jlist, pairnumsum, atomtype, alist, i);

      // many-body descriptors
      peratombase_descriptors(bd, bdd, rij, &tmpmem[3*Nj], tj, Nj);

      // calculate multi-environment descriptors and their derivatives with respect to atom coordinates
      peratomenvironment_descriptors(pd, pdd, bd, bdd, tmpmem, ti[0] - 1,  Nj);

      for (int j = 0; j < nClusters; j++) {
        probdesc[i + natom*(j)] = pd[j];
        for (int m=0; m<Mdesc; m++) {
          basedesc[i + natom*(m)] = bd[m];
          int k = nCoeffPerElement*(ti[0]-1) + nl1 + m + j*Mdesc; // increment by nl1 because of the one-body descriptor
          gd[k] += pd[j]*bd[m];
          for (int n=0; n<Nj; n++) {
            int im = 3*ai[n] + 3*natom*k;
            int jm = 3*aj[n] + 3*natom*k;
            int nm = 3*n + 3*Nj*m;
            int nj = 3*n + 3*Nj*j;
            gdd[0 + im] += bdd[0 + nm]*pd[j] + bd[m]*pdd[0 + nj];
            gdd[1 + im] += bdd[1 + nm]*pd[j] + bd[m]*pdd[1 + nj];
            gdd[2 + im] += bdd[2 + nm]*pd[j] + bd[m]*pdd[2 + nj];
            gdd[0 + jm] -= bdd[0 + nm]*pd[j] + bd[m]*pdd[0 + nj];
            gdd[1 + jm] -= bdd[1 + nm]*pd[j] + bd[m]*pdd[1 + nj];
            gdd[2 + jm] -= bdd[2 + nm]*pd[j] + bd[m]*pdd[2 + nj];
          }
        }
      }

    }
  }
}

void EAPOD::fourbodydesc23(double *d23, double *d2, double *d3)
{
  for (int j = 0; j<n32; j++)
    for (int i = 0; i<n23; i++)
      d23[i + n23*j] = d2[ind23[i]]*d3[ind32[j]];
}

void EAPOD::fourbodydescderiv23(double* dd23, double *d2, double *d3, double *dd2, double *dd3, int N)
{
  for (int j = 0; j<n32; j++)
    for (int i = 0; i<n23; i++)
      for (int n=0; n<N; n++)
        dd23[n + N*i + N*n23*j] = d2[ind23[i]]*dd3[n + N*ind32[j]] + dd2[n + N*ind23[i]]*d3[ind32[j]];
}

void EAPOD::crossdesc(double *d12, double *d1, double *d2, int *ind1, int *ind2, int n12)
{
  for (int i = 0; i<n12; i++)
    d12[i] = d1[ind1[i]]*d2[ind2[i]];
}

void EAPOD::crossdescderiv(double *dd12, double *d1, double *d2, double *dd1, double *dd2,
        int *ind1, int *ind2, int n12, int N)
{
  for (int i = 0; i<n12; i++)
    for (int n=0; n<N; n++)
      dd12[n + N*i] = d1[ind1[i]]*dd2[n + N*ind2[i]] + dd1[n + N*ind1[i]]*d2[ind2[i]];
}

void EAPOD::crossdesc_reduction(double *cb1, double *cb2, double *c12, double *d1,
        double *d2, int *ind1, int *ind2, int n12)
{
  for (int m = 0; m < n12; m++) {
    int k1 = ind1[m]; // dd1
    int k2 = ind2[m]; // dd2
    double c = c12[m];
    cb1[k1] += c * d2[k2];
    cb2[k2] += c * d1[k1];
  }
}

void EAPOD::myneighbors(double *rij, double *x, int *ai, int *aj, int *ti, int *tj,
        int *jlist, int *pairnumsum, int *atomtype, int *alist, int i)
{
  int itype = atomtype[i];
  int start = pairnumsum[i];
  int m = pairnumsum[i+1] - start; // number of neighbors around i
  for (int l=0; l<m ; l++) {   // loop over each atom around atom i
    int j = jlist[l + start];  // atom j
    ai[l]        = i;
    aj[l]        = alist[j];
    ti[l]        = itype;
    tj[l]        = atomtype[alist[j]];
    rij[0 + 3*l]   = x[0 + 3*j] -  x[0 + 3*i];
    rij[1 + 3*l]   = x[1 + 3*j] -  x[1 + 3*i];
    rij[2 + 3*l]   = x[2 + 3*j] -  x[2 + 3*i];
  }
}

void EAPOD::fourbodydesc(double *d4, double *sumU)
{
  int Me = nelements*(nelements+1)*(nelements+2)/6; //count4(nelements);
  for (int m=0; m<nabf4*nrbf4*Me; m++)
    d4[m] = 0.0;

  for (int m = 0; m < nrbf4; m++) {
    int idxU = nelements * K3 * m;
    for (int p = 0; p < nabf4; p++) {
      int n1 = pa4[p];
      int n2 = pa4[p + 1];
      int nn = n2 - n1;
      for (int q = 0; q < nn; q++) {
        int c = pc4[n1 + q];
        int j1 = pb4[n1 + q];
        int j2 = pb4[n1 + q + Q4];
        int j3 = pb4[n1 + q + 2 * Q4];
        int k = 0;
        for (int i1 = 0; i1 < nelements; i1++) {
          double c1 =  sumU[idxU + i1 + nelements * j1];
          for (int i2 = i1; i2 < nelements; i2++) {
            double c2 = sumU[idxU + i2 + nelements * j2];
            double t12 = c * c1 * c2;
            for (int i3 = i2; i3 < nelements; i3++) {
              double c3 = sumU[idxU + i3 + nelements * j3];
              int kk = p + nabf4 * m + nabf4 * nrbf4 * k;
              d4[kk] += t12 * c3;
              k += 1;
            }
          }
        }
      }
    }
  }
}

void EAPOD::fourbodydescderiv(double *d4, double *dd4, double *sumU, double *Ux, double *Uy,
        double *Uz, int *atomtype, int N)
{
  int Me = nelements*(nelements+1)*(nelements+2)/6; //count4(nelements);
  for (int m=0; m<nabf4*nrbf4*Me; m++)
    d4[m] = 0.0;

  for (int m=0; m<3*N*nabf4*nrbf4*Me; m++)
    dd4[m] = 0.0;

  int Q = pa4[nabf4];

  if (nelements==1) {
    for (int m=0; m<nrbf4; m++)
      for (int p=0; p<nabf4; p++) {
        int n1 = pa4[p];
        int n2 = pa4[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          int c = pc4[n1+q];
          int j1 = pb4[n1+q];
          int j2 = pb4[n1+q + Q];
          int j3 = pb4[n1+q + 2*Q];
          double c1 = c*sumU[j1 + K4*m];
          double c2 = c*sumU[j2 + K4*m];
          double t12 = c1*sumU[j2 + K4*m];
          double c3 = sumU[j3 + K4*m];
          double t13 = c1*c3;
          double t23 = c2*c3;
          int kk = p + nabf4*m;
          int ii = 3*N*(p + nabf4*m);
          d4[kk] += t12*c3;
          for (int j=0; j<N; j++) {
            int jj = j + N*j3 + N*K4*m;
            dd4[0 + 3*j + ii] += t12*Ux[jj];
            dd4[1 + 3*j + ii] += t12*Uy[jj];
            dd4[2 + 3*j + ii] += t12*Uz[jj];
            jj = j + N*j2 + N*K4*m;
            dd4[0 + 3*j + ii] += t13*Ux[jj];
            dd4[1 + 3*j + ii] += t13*Uy[jj];
            dd4[2 + 3*j + ii] += t13*Uz[jj];
            jj = j + N*j1 + N*K4*m;
            dd4[0 + 3*j + ii] += t23*Ux[jj];
            dd4[1 + 3*j + ii] += t23*Uy[jj];
            dd4[2 + 3*j + ii] += t23*Uz[jj];
          }
        }
      }
  }
  else {
    for (int m=0; m<nrbf4; m++)
      for (int p=0; p<nabf4; p++) {
        int n1 = pa4[p];
        int n2 = pa4[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          int c = pc4[n1+q];
          int j1 = pb4[n1+q];
          int j2 = pb4[n1+q + Q];
          int j3 = pb4[n1+q + 2*Q];
          int k = 0;
          for (int i1=0; i1<nelements; i1++) {
            double c1 = c*sumU[i1 + nelements*j1 + nelements*K4*m];
            for (int i2=i1; i2<nelements; i2++) {
              double c2 = c*sumU[i2 + nelements*j2 + nelements*K4*m];
              double t12 = c1*sumU[i2 + nelements*j2 + nelements*K4*m];
              for (int i3=i2; i3<nelements; i3++) {
                double c3 = sumU[i3 + nelements*j3 + nelements*K4*m];
                double t13 = c1*c3;
                double t23 = c2*c3;
                int kk = p + nabf4*m + nabf4*nrbf4*k;
                int ii = 3*N*(p + nabf4*m + nabf4*nrbf4*k);
                d4[kk] += t12*c3;
                for (int j=0; j<N; j++) {
                  int tj = atomtype[j]-1;
                  if (tj==i3) {
                    int jj = j + N*j3 + N*K4*m;
                    dd4[0 + 3*j + ii] += t12*Ux[jj];
                    dd4[1 + 3*j + ii] += t12*Uy[jj];
                    dd4[2 + 3*j + ii] += t12*Uz[jj];
                  }
                  if (tj==i2) {
                    int jj = j + N*j2 + N*K4*m;
                    dd4[0 + 3*j + ii] += t13*Ux[jj];
                    dd4[1 + 3*j + ii] += t13*Uy[jj];
                    dd4[2 + 3*j + ii] += t13*Uz[jj];
                  }
                  if (tj==i1) {
                    int jj = j + N*j1 + N*K4*m;
                    dd4[0 + 3*j + ii] += t23*Ux[jj];
                    dd4[1 + 3*j + ii] += t23*Uy[jj];
                    dd4[2 + 3*j + ii] += t23*Uz[jj];
                  }
                }
                k += 1;
              }
            }
          }
        }
      }
  }
}

void EAPOD::threebodydesc(double *d3, double *sumU)
{
  int Me = nelements*(nelements+1)/2;
  for (int m=0; m<nabf3*nrbf3*Me; m++)
    d3[m] = 0.0;

  if (nelements==1) {
    for (int m=0; m<nrbf3; m++)
      for (int p=0; p<nabf3; p++) {
        int n1 = pn3[p];
        int n2 = pn3[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          double t1 = pc3[n1+q]*sumU[(n1+q) + K3*m];
          d3[p + nabf3*m] += t1*sumU[(n1+q) + K3*m];
        }
      }
  }
  else {
    for (int m=0; m<nrbf3; m++)
      for (int p=0; p<nabf3; p++) {
        int n1 = pn3[p];
        int n2 = pn3[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          int k = 0;
          for (int i1=0; i1<nelements; i1++) {
            double t1 = pc3[n1+q]*sumU[i1 + nelements*(n1+q) + nelements*K3*m];
            for (int i2=i1; i2<nelements; i2++) {
              d3[p + nabf3*m + nabf3*nrbf3*k] += t1*sumU[i2 + nelements*(n1+q) + nelements*K3*m];
              k += 1;
            }
          }
        }
      }
  }
}

void EAPOD::threebodydescderiv(double *dd3, double *sumU, double *Ux, double *Uy, double *Uz,
        int *atomtype, int N)
{
  int Me = nelements*(nelements+1)/2;
  for (int m=0; m<3*N*nabf3*nrbf3*Me; m++)
    dd3[m] = 0.0;

  if (nelements==1) {
    for (int m=0; m<nrbf3; m++)
      for (int p=0; p<nabf3; p++) {
        int n1 = pn3[p];
        int n2 = pn3[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          double t1 = pc3[n1+q]*sumU[(n1+q) + K3*m];
          for (int j=0; j<N; j++) {
            double f = 2.0*t1;
            int ii = 3*j + 3*N*(p + nabf3*m);
            int jj = j + N*(n1+q) + N*K3*m;
            dd3[0 + ii] += f*Ux[jj];
            dd3[1 + ii] += f*Uy[jj];
            dd3[2 + ii] += f*Uz[jj];
          }
        }
      }
  }
  else {
    for (int m=0; m<nrbf3; m++)
      for (int p=0; p<nabf3; p++) {
        int n1 = pn3[p];
        int n2 = pn3[p+1];
        int nn = n2 - n1;
        for (int q=0; q<nn; q++) {
          for (int i1=0; i1<nelements; i1++) {
            double t1 = pc3[n1+q]*sumU[i1 + nelements*(n1+q) + nelements*K3*m];
            for (int j=0; j<N; j++) {
              int i2 = atomtype[j]-1;
              int k = elemindex[i2 + nelements*i1];
              double f = (i1==i2) ? 2.0*t1 : t1;
              int ii = 3*j + 3*N*(p + nabf3*m + nabf3*nrbf3*k);
              int jj = j + N*(n1+q) + N*K3*m;
              dd3[0 + ii] += f*Ux[jj];
              dd3[1 + ii] += f*Uy[jj];
              dd3[2 + ii] += f*Uz[jj];
            }
          }
        }
      }
  }
}

void EAPOD::twobodydesc(double *d2, double *rbf, int *tj, int N)
{
  // Initialize the two-body descriptors and their derivatives to zero
  for (int m=0; m<nl2; m++)
    d2[m] = 0.0;

  // Calculate the two-body descriptors and their derivatives
  for (int m=0; m<nrbf2; m++) {
    for (int n=0; n<N; n++) {
      int i2 = n + N*m; // Index of the radial basis function for atom n and RBF m
      d2[m + nrbf2*(tj[n]-1)] += rbf[i2]; // Add the radial basis function to the corresponding descriptor
    }
  }
}

/**
 * @brief Calculates the two-body descriptor derivatives for a given set of atoms.
 *
 * @param d2   Pointer to the array of two-body descriptors.
 * @param dd2  Pointer to the array of two-body descriptor derivatives.
 * @param rbf  Pointer to the array of radial basis functions.
 * @param rbfx Pointer to the array of radial basis function derivatives with respect to x.
 * @param rbfy Pointer to the array of radial basis function derivatives with respect to y.
 * @param rbfz Pointer to the array of radial basis function derivatives with respect to z.
 * @param tj   Pointer to the array of atom types of neighboring atoms.
 * @param N    Number of neighboring atoms.
 */
void EAPOD::twobodydescderiv(double *d2, double *dd2, double *rbf, double *rbfx,
        double *rbfy, double *rbfz, int *tj, int N)
{
  // Initialize the two-body descriptors and their derivatives to zero
  for (int m=0; m<nl2; m++)
    d2[m] = 0.0;
  for (int m=0; m<3*N*nl2; m++)
    dd2[m] = 0.0;

  // Calculate the two-body descriptors and their derivatives
  for (int m=0; m<nrbf2; m++) {
    for (int n=0; n<N; n++) {
      int i2 = n + N*m; // Index of the radial basis function for atom n and RBF m
      int i1 = n + N*m + N*nrbf2*(tj[n]-1); // Index of the descriptor for atom n, RBF m, and atom type tj[n]
      d2[m + nrbf2*(tj[n]-1)] += rbf[i2]; // Add the radial basis function to the corresponding descriptor
      dd2[0 + 3*i1] += rbfx[i2]; // Add the derivative with respect to x to the corresponding descriptor derivative
      dd2[1 + 3*i1] += rbfy[i2]; // Add the derivative with respect to y to the corresponding descriptor derivative
      dd2[2 + 3*i1] += rbfz[i2]; // Add the derivative with respect to z to the corresponding descriptor derivative
    }
  }
}

/**
 * @brief Calculates the radial basis functions and their derivatives.
 *
 * @param rbf           Pointer to the array of radial basis functions.
 * @param rbfx          Pointer to the array of derivatives of radial basis functions with respect to x.
 * @param rbfy          Pointer to the array of derivatives of radial basis functions with respect to y.
 * @param rbfz          Pointer to the array of derivatives of radial basis functions with respect to z.
 * @param rij           Pointer to the relative positions of neighboring atoms and atom i.
 * @param besselparams  Pointer to the array of Bessel function parameters.
 * @param rin           Minimum distance for radial basis functions.
 * @param rmax          Maximum distance for radial basis functions.
 * @param besseldegree  Degree of Bessel functions.
 * @param inversedegree Degree of inverse distance functions.
 * @param nbesselpars   Number of Bessel function parameters.
 * @param N             Number of neighboring atoms.
 */
void EAPOD::radialbasis(double *rbf, double *rbfx, double *rbfy, double *rbfz, double *rij, double *besselparams, double rin,
        double rmax, int besseldegree, int inversedegree, int nbesselpars, int N)
{
  // Loop over all neighboring atoms
  for (int n=0; n<N; n++) {
    double xij1 = rij[0+3*n];
    double xij2 = rij[1+3*n];
    double xij3 = rij[2+3*n];

    double dij = sqrt(xij1*xij1 + xij2*xij2 + xij3*xij3);
    double dr1 = xij1/dij;
    double dr2 = xij2/dij;
    double dr3 = xij3/dij;

    double r = dij - rin;
    double y = r/rmax;
    double y2 = y*y;

    double y3 = 1.0 - y2*y;
    double y4 = y3*y3 + 1e-6;
    double y5 = sqrt(y4);
    double y6 = exp(-1.0/y5);
    double y7 = y4*sqrt(y4);

    // Calculate the final cutoff function as y6/exp(-1)
    double fcut = y6/exp(-1.0);

    // Calculate the derivative of the final cutoff function
    double dfcut = ((3.0/(rmax*exp(-1.0)))*(y2)*y6*(y*y2 - 1.0))/y7;

    // Calculate fcut/r, fcut/r^2, and dfcut/r
    double f1 = fcut/r;
    double f2 = f1/r;
    double df1 = dfcut/r;

    double alpha = besselparams[0];
    double t1 = (1.0-exp(-alpha));
    double t2 = exp(-alpha*r/rmax);
    double x0 =  (1.0 - t2)/t1;
    double dx0 = (alpha/rmax)*t2/t1;

    if (nbesselpars==1) {
      for (int i=0; i<besseldegree; i++) {
        double a = (i+1)*MY_PI;
        double b = (sqrt(2.0/(rmax))/(i+1));
        double af1 = a*f1;

        double sinax = sin(a*x0);
        int nij = n + N*i;

        rbf[nij] = b*f1*sinax;

        double drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x0)*dx0);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;
      }
    }
    else if (nbesselpars==2) {
      alpha = besselparams[1];
      t1 = (1.0-exp(-alpha));
      t2 = exp(-alpha*r/rmax);
      double x1 =  (1.0 - t2)/t1;
      double dx1 = (alpha/rmax)*t2/t1;
      for (int i=0; i<besseldegree; i++) {
        double a = (i+1)*MY_PI;
        double b = (sqrt(2.0/(rmax))/(i+1));
        double af1 = a*f1;

        double sinax = sin(a*x0);
        int nij = n + N*i;

        rbf[nij] = b*f1*sinax;

        double drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x0)*dx0);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;

        sinax = sin(a*x1);
        nij = n + N*i + N*besseldegree*1;
        rbf[nij] = b*f1*sinax;

        drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x1)*dx1);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;
      }
    }
    else if (nbesselpars==3) {
      alpha = besselparams[1];
      t1 = (1.0-exp(-alpha));
      t2 = exp(-alpha*r/rmax);
      double x1 =  (1.0 - t2)/t1;
      double dx1 = (alpha/rmax)*t2/t1;

      alpha = besselparams[2];
      t1 = (1.0-exp(-alpha));
      t2 = exp(-alpha*r/rmax);
      double x2 =  (1.0 - t2)/t1;
      double dx2 = (alpha/rmax)*t2/t1;
      for (int i=0; i<besseldegree; i++) {
        double a = (i+1)*MY_PI;
        double b = (sqrt(2.0/(rmax))/(i+1));
        double af1 = a*f1;

        double sinax = sin(a*x0);
        int nij = n + N*i;

        rbf[nij] = b*f1*sinax;
        double drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x0)*dx0);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;

        sinax = sin(a*x1);
        nij = n + N*i + N*besseldegree*1;

        rbf[nij] = b*f1*sinax;
        drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x1)*dx1);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;

        sinax = sin(a*x2);
        nij = n + N*i + N*besseldegree*2;
        rbf[nij] = b*f1*sinax;
        drbfdr = b*(df1*sinax - f2*sinax + af1*cos(a*x2)*dx2);
        rbfx[nij] = drbfdr*dr1;
        rbfy[nij] = drbfdr*dr2;
        rbfz[nij] = drbfdr*dr3;
      }
    }

    // Calculate fcut/dij and dfcut/dij
    f1 = fcut/dij;
    for (int i=0; i<inversedegree; i++) {
      int p = besseldegree*nbesselpars + i;
      int nij = n + N*p;
      double a = powint(dij, i+1);

      rbf[nij] = fcut/a;

      double drbfdr = (dfcut - (i+1.0)*f1)/a;
      rbfx[nij] = drbfdr*dr1;
      rbfy[nij] = drbfdr*dr2;
      rbfz[nij] = drbfdr*dr3;
    }
  }
}

/**
 * @brief Calculates the angular basis functions and their derivatives.
 *
 * @param abf   Pointer to the angular basis functions.
 * @param abfx  Pointer to the derivative of the angular basis functions w.r.t. x.
 * @param abfy  Pointer to the derivative of the angular basis functions w.r.t. y.
 * @param abfz  Pointer to the derivative of the angular basis functions w.r.t. z.
 * @param rij   Pointer to the relative positions of neighboring atoms and atom i.
 * @param tm    Pointer to temporary array.
 * @param pq    Pointer to array of indices for angular basis functions.
 * @param N     Number of neighboring atoms.
 * @param K     Number of angular basis functions.
 */
void EAPOD::angularbasis(double *abf, double *abfx, double *abfy, double *abfz, double *rij, double *tm, int *pq, int N, int K)
{
  // Initialize temporary arrays
  double *tmu = &tm[K];
  double *tmv = &tm[2*K];
  double *tmw = &tm[3*K];

  // Initialize first angular basis function and its derivatives
  tm[0] = 1.0;
  tmu[0] = 0.0;
  tmv[0] = 0.0;
  tmw[0] = 0.0;

  // Loop over all neighboring atoms
  for (int j=0; j<N; j++) {
    // Calculate relative positions of neighboring atoms and atom i
    double x = rij[0+3*j];
    double y = rij[1+3*j];
    double z = rij[2+3*j];

    // Calculate various terms for derivatives
    double xx = x*x;
    double yy = y*y;
    double zz = z*z;
    double xy = x*y;
    double xz = x*z;
    double yz = y*z;

    // Calculate distance between neighboring atoms and unit vectors
    double dij = sqrt(xx + yy + zz);
    double u = x/dij;
    double v = y/dij;
    double w = z/dij;

    // Calculate derivatives of unit vectors
    double dij3 = dij*dij*dij;
    double dudx = (yy+zz)/dij3;
    double dudy = -xy/dij3;
    double dudz = -xz/dij3;

    double dvdx = -xy/dij3;
    double dvdy = (xx+zz)/dij3;
    double dvdz = -yz/dij3;

    double dwdx = -xz/dij3;
    double dwdy = -yz/dij3;
    double dwdz = (xx+yy)/dij3;

    // Initialize first angular basis function and its derivatives
    abf[j] = tm[0];
    abfx[j] = 0.0;
    abfy[j] = 0.0;
    abfz[j] = 0.0;

    // Loop over all angular basis functions
    for (int n=1; n<K; n++) {
      // Get indices for angular basis function
      int m = pq[n]-1;
      int d = pq[n + K];

      // Calculate angular basis function and its derivatives using recursion relation
      if (d==1) {
        tm[n] = tm[m]*u;
        tmu[n] = tmu[m]*u + tm[m];
        tmv[n] = tmv[m]*u;
        tmw[n] = tmw[m]*u;
      }
      else if (d==2) {
        tm[n] = tm[m]*v;
        tmu[n] = tmu[m]*v;
        tmv[n] = tmv[m]*v + tm[m];
        tmw[n] = tmw[m]*v;
      }
      else if (d==3) {
        tm[n] = tm[m]*w;
        tmu[n] = tmu[m]*w;
        tmv[n] = tmv[m]*w;
        tmw[n] = tmw[m]*w + tm[m];
      }
      abf[j + N*n] = tm[n];
      abfx[j + N*n] = tmu[n]*dudx + tmv[n]*dvdx + tmw[n]*dwdx;
      abfy[j + N*n] = tmu[n]*dudy + tmv[n]*dvdy + tmw[n]*dwdy;
      abfz[j + N*n] = tmu[n]*dudz + tmv[n]*dvdz + tmw[n]*dwdz;
    }
  }
}

/**
 * @brief Calculates the radial-angular basis functions and their derivatives.
 *
 * @param sumU  Pointer to the array to store the sum of the basis functions.
 * @param U     Pointer to the array to store the radial-angular basis functions.
 * @param Ux    Pointer to the array to store the derivative of U with respect to x.
 * @param Uy    Pointer to the array to store the derivative of U with respect to y.
 * @param Uz    Pointer to the array to store the derivative of U with respect to z.
 * @param rbf   Pointer to the radial basis function array.
 * @param rbfx  Pointer to the derivative of rbf with respect to x.
 * @param rbfy  Pointer to the derivative of rbf with respect to y.
 * @param rbfz  Pointer to the derivative of rbf with respect to z.
 * @param abf   Pointer to the angular basis function array.
 * @param abfx  Pointer to the derivative of abf with respect to x.
 * @param abfy  Pointer to the derivative of abf with respect to y.
 * @param abfz  Pointer to the derivative of abf with respect to z.
 * @param tm    Pointer to the temporary memory array .
 * @param atomtype  Pointer to the array of atom types.
 * @param N     Number of neighboring atoms.
 * @param K     Number of angular basis functions.
 * @param M     Number of radial basis functions.
 * @param Ne    Number of elements.
 */
void EAPOD::radialangularbasis(double *sumU, double *U, double *Ux, double *Uy, double *Uz,
                 double *rbf, double *rbfx, double *rbfy, double *rbfz,
                 double *abf, double *abfx, double *abfy, double *abfz,
                 int *atomtype, int N, int K, int M, int Ne)
{
  // Initialize sumU to zero
  std::fill(sumU, sumU + Ne * K * M, 0.0);

  // Calculate radial-angular basis functions
  if (Ne == 1) { // Case when Ne is 1
    // Case when Ne is not 1
    for (int m = 0; m < M; m++) {
      for (int k = 0; k < K; k++) {
        double sum = 0.0;
        for (int n = 0; n < N; n++) {
          int ia = n + N * k;
          int ib = n + N * m;
          int ii = ia + N * K * m;

          // Calculate c1 and c2
          double c1 = rbf[ib];
          double c2 = abf[ia];

          // Calculate U, Ux, Uy, Uz
          U[ii] = c1 * c2;
          Ux[ii] = abfx[ia] * c1 + c2 * rbfx[ib];
          Uy[ii] = abfy[ia] * c1 + c2 * rbfy[ib];
          Uz[ii] = abfz[ia] * c1 + c2 * rbfz[ib];

          // Update sum
          sum += c1 * c2;
        }
        // Update sumU
        sumU[k + K * m] += sum;
      }
    }
  } else { // Case when Ne is not 1
    // Loop over all radial, angular basis functions, and neighboring atoms
    for (int m = 0; m < M; m++) {
      for (int k = 0; k < K; k++) {
        for (int n = 0; n < N; n++) {
          int ia = n + N * k;
          int ib = n + N * m;
          int ii = ia + N * K * m;

          // Calculate c1 and c2
          double c1 = rbf[ib];
          double c2 = abf[ia];

          // Calculate U, Ux, Uy, Uz
          U[ii] = c1 * c2;
          Ux[ii] = abfx[ia] * c1 + c2 * rbfx[ib];
          Uy[ii] = abfy[ia] * c1 + c2 * rbfy[ib];
          Uz[ii] = abfz[ia] * c1 + c2 * rbfz[ib];

          // Update sumU with atomtype adjustment
          int tn = atomtype[n] - 1; // offset the atom type by 1, since atomtype is 1-based
          sumU[tn + Ne * k + Ne * K * m] += c1 * c2;
        }
      }
    }
  }
}

/**
 * @brief Tally the force on each atom i and its neighboring atoms.
 *
 * @param force Pointer to the output array for the global force
 * @param fij Pointer to the array of forces between each pair of neighboring atoms.
 * @param ai Pointer to the array of atom indices for each pair of neighboring atoms.
 * @param aj Pointer to the array of neighboring atom indices for each pair of neighboring atoms.
 * @param N Number of neighboring atom pairs.
 */
void EAPOD::tallyforce(double *force, double *fij,  int *ai, int *aj, int N)
{
  // Loop over all neighboring atoms
  for (int n=0; n<N; n++) {
    int im =  3*ai[n]; // Output index for the force on atom i
    int jm =  3*aj[n]; // Output index for the force on atom j
    int nm = 3*n; // Input index for the force between atom i and j
    force[0 + im] += fij[0 + nm]; // Tally the x-component of the force on atom i
    force[1 + im] += fij[1 + nm]; // Tally the y-component of the force on atom i
    force[2 + im] += fij[2 + nm]; // Tally the z-component of the force on atom i
    force[0 + jm] -= fij[0 + nm]; // Tally the x-component of the force on atom j
    force[1 + jm] -= fij[1 + nm]; // Tally the y-component of the force on atom j
    force[2 + jm] -= fij[2 + nm]; // Tally the z-component of the force on atom j
  }
}

/**
 * @brief Create new coefficients for the local descriptors.
 *
 * @param c Pointer to the input array of original coefficients for the global descriptors.
 */
void EAPOD::mknewcoeff(double *c, int nc)
{
  // Allocate memory for the new coefficients
  memory->create(coeff, nc, "coeff");

  // Copy the  coefficients
  for (int n=0; n<nc; n++)
    coeff[n] = c[n];
}

/**
 * @brief Compute the radial basis function (RBF) for each atom.
 *
 * @param rbf Pointer to the output array for the RBF.
 * @param xij Pointer to the array of distances between each pair of atoms.
 * @param N Number of points in the interval [rin, rcut].
 */
void EAPOD::snapshots(double *rbf, double *xij, int N)
{
  // Compute the maximum distance between two atoms
  double rmax = rcut-rin;

  // Loop over all atoms
  for (int n=0; n<N; n++) {
    double dij = xij[n];

    // Compute the distance between two atoms
    double r = dij - rin;

    // Compute the normalized distance
    double y = r/rmax;
    double y2 = y*y;
    double y3 = 1.0 - y2*y;
    double y4 = y3*y3 + 1e-6;
    double y5 = sqrt(y4);
    double y6 = exp(-1.0/y5);

    // Compute the cutoff function
    double fcut = y6/exp(-1.0);

    // Loop over all Bessel parameters
    for (int j=0; j<nbesselpars; j++) {
      double alpha = besselparams[j];
      if (fabs(alpha) <= 1.0e-6) alpha = 1e-3;
      double x =  (1.0 - exp(-alpha*r/rmax))/(1.0-exp(-alpha));

      // Loop over all Bessel degrees
      for (int i=0; i<besseldegree; i++) {
        double a = (i+1)*MY_PI;
        double b = (sqrt(2.0/(rmax))/(i+1));
        int nij = n + N*i + N*besseldegree*j;

        // Compute the RBF
        rbf[nij] = b*fcut*sin(a*x)/r;
      }
    }

    // Loop over all polynomial degrees of the radial inverse functions
    for (int i=0; i<inversedegree; i++) {
      int p = besseldegree*nbesselpars + i;
      int nij = n + N*p;
      double a = powint(dij, i+1);

      // Compute the RBF
      rbf[nij] = fcut/a;
    }
  }
}


/**
 * @brief Perform eigenvalue decomposition of the snapshots matrix S and return the eigenvectors and eigenvalues.
 *
 * @param Phi Pointer to the output array for the eigenvectors.
 * @param Lambda Pointer to the output array for the eigenvalues.
 * @param N Number of points in the interval [rin, rcut].
 */
void EAPOD::eigenvaluedecomposition(double *Phi, double *Lambda, int N)
{
  int ns = besseldegree*nbesselpars + inversedegree;

  double *xij;
  double *S;
  double *Q;
  double *A;
  double *work;
  double *b;

  memory->create(xij, N, "eapod:xij");
  memory->create(S, N*ns, "eapod:S");
  memory->create(Q, N*ns, "eapod:Q");
  memory->create(A, ns*ns, "eapod:A");
  memory->create(work, ns*ns, "eapod:work");
  memory->create(b, ns, "eapod:ns");

  // Generate the xij array
  for (int i=0; i<N; i++)
    xij[i] = (rin+1e-6) + (rcut-rin-1e-6)*(i*1.0/(N-1));

  // Compute the snapshots matrix S
  snapshots(S, xij, N);

  // Compute the matrix A = S^T * S
  char chn = 'N';
  char cht = 'T';
  double alpha = 1.0, beta = 0.0;
  DGEMM(&cht, &chn, &ns, &ns, &N, &alpha, S, &N, S, &N, &beta, A, &ns);

  // Normalize the matrix A by dividing by N
  for (int i=0; i<ns*ns; i++)
    A[i] = A[i]*(1.0/N);

  // Compute the eigenvectors and eigenvalues of A
  int lwork = ns * ns;  // the length of the array work, lwork >= max(1,3*N-1)
  int info = 1;     // = 0:  successful exit
  //double work[ns*ns];
  char chv = 'V';
  char chu = 'U';
  DSYEV(&chv, &chu, &ns, A, &ns, b, work, &lwork, &info);

  // Order eigenvalues and eigenvectors from largest to smallest
  for (int j=0; j<ns; j++)
    for (int i=0; i<ns; i++)
      Phi[i + ns*(ns-j-1)] = A[i + ns*j];

  for (int i=0; i<ns; i++)
    Lambda[(ns-i-1)] = b[i];

  // Compute the matrix Q = S * Phi
  DGEMM(&chn, &chn, &N, &ns, &ns, &alpha, S, &N, Phi, &ns, &beta, Q, &N);

  // Compute the area of each snapshot and normalize the eigenvectors
  for (int i=0; i<(N-1); i++)
    xij[i] = xij[i+1] - xij[i];
  double area;
  for (int m=0; m<ns; m++) {
    area = 0.0;
    for (int i=0; i<(N-1); i++)
      area += 0.5*xij[i]*(Q[i + N*m]*Q[i + N*m] + Q[i+1 + N*m]*Q[i+1 + N*m]);
    for (int i=0; i<ns; i++)
      Phi[i + ns*m] = Phi[i + ns*m]/sqrt(area);
  }

  // Enforce consistent signs for the eigenvectors
  for (int m=0; m<ns; m++) {
    if (Phi[m + ns*m] < 0.0) {
      for (int i=0; i<ns; i++)
        Phi[i + ns*m] = -Phi[i + ns*m];
    }
  }

  // Free temporary arrays
  memory->destroy(xij);
  memory->destroy(S);
  memory->destroy(A);
  memory->destroy(work);
  memory->destroy(b);
  memory->destroy(Q);
}

/**
 * @brief Initialize the two-body coefficients.
 *
 * @param None
 */
void EAPOD::init2body()
{
  // Set the degree of the Bessel function and the inverse distance function
  pdegree[0] = besseldegree;
  pdegree[1] = inversedegree;

  // Compute the total number of snapshots
  ns = nbesselpars * pdegree[0] + pdegree[1];

  // Allocate memory for the eigenvectors and eigenvalues
  memory->create(Phi, ns * ns, "Phi");
  memory->create(Lambda, ns, "Lambda");

  // Perform eigenvalue decomposition of the snapshots matrix S and store the eigenvectors and eigenvalues
  eigenvaluedecomposition(Phi, Lambda, 2000);
}

/**
 * @brief Initialize arrays for the three-body descriptors.
 *
 * @param Pa3 The degree of the angular basis functions of the three-body descriptors.
 */
void EAPOD::init3body(int Pa3)
{
  // Define the number of monomials for each degree
  int npa[] = {0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455};

  // Set the number of coefficients, the number of basis functions, and the degree of the Bessel function
  nabf3 = Pa3+1;    // Number of angular basis functions
  K3 = npa[nabf3];  // number of monimials
  P3 = nabf3-1;     // the degree of angular basis functions of the three-body descriptors

  // Allocate memory for the coefficients, the basis functions, and the cutoff function
  memory->create(pn3, nabf3+1, "pn3"); // array stores the number of monomials for each degree
  memory->create(pq3, K3*2, "pq3"); // array needed for the recursive computation of the angular basis functions
  memory->create(pc3, K3, "pc3");   // array needed for the computation of the three-body descriptors

  // Initialize the arrays
  init3bodyarray(pn3, pq3, pc3, nabf3-1);
}

/**
 * @brief Initialize arrays for the four-body descriptors.
 *
 * @param Pa4 The degree of the angular basis functions of the four-body descriptors.
 */
void EAPOD::init4body(int Pa4)
{
  // Define the number of monomials for each degree
  int npa[] = {0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455};

  // Define the number of angular basis functions for each degree
  int nb[] = {1,     2,     4,     7,    11,    16,    23};

  // Define the number of terms needed to compute angular basis functions
  int ns[] = {0, 1, 4, 10, 19, 29, 47, 74, 89, 119, 155, 209, 230, 275, 335, 425, 533, 561, 624, 714, 849, 949, 1129, 1345};

  // Set the degree of the angular basis functions of the four-body descriptors
  P4 = Pa4;

  // Set the number of monomials for the angular basis functions of the four-body descriptors
  K4 = npa[Pa4+1];

  // Allocate memory for the output arrays
  int *pn4, *tm4;
  memory->create(pn4, Pa4+2, "pn4"); // array stores the number of monomials for each degree
  memory->create(pq4, K4*2, "pq4");  // array needed for the recursive computation of the angular basis functions
  memory->create(tm4, K4, "tm4");

  // Initialize the arrays
  init3bodyarray(pn4, pq4, tm4, Pa4);

  // Set the number of angular basis functions for the four-body descriptors
  nabf4 = nb[Pa4];

  // the size of the array pc4
  Q4 = ns[nabf4];

  // Allocate memory for the coefficients, the basis functions, and the cutoff function
  memory->create(pa4, nabf4+1, "pa4"); // this array is a subset of the array ns
  memory->create(pb4, Q4*3, "pb4"); // array stores the indices of the monomials needed for the computation of the angular basis functions
  memory->create(pc4, Q4, "pc4");   // array of monomial coefficients needed for the computation of the four-body descriptors

  // Initialize the arrays
  init4bodyarray(pa4, pb4, pc4, Pa4);

  // Deallocate memory
  memory->destroy(pn4);
  memory->destroy(tm4);
}


/**
 * @brief Estimate the amount of memory needed for the computation.
 *
 * @param Nj Number of neighboring atoms.
 * @return int The estimated amount of memory needed.
 */
int EAPOD::estimate_temp_memory(int Nj)
{
  // Determine the maximum number of radial basis functions and angular basis functions
  int Kmax = (K3 > K4) ? K3 : K4;
  int nrbf34 = (nrbf3 > nrbf4) ? nrbf3 : nrbf4;
  int nrbfmax = (nrbf2 > nrbf34) ? nrbf2 : nrbf34;
  int Knrbf34 = (K3*nrbf3 > K4*nrbf4) ? K3*nrbf3 : K4*nrbf4;

  // Determine the maximum number of local descriptors
  int nld = (nl23 > nl33) ? nl23 : nl33;
  nld = (nld > nl34) ? nld : nl34;
  nld = (nld > nl44) ? nld : nl44;

  // rij, fij, and d2, dd2, d3, dd3, d4, dd4
  int nmax1 = 6*Nj + nl2 + 3*Nj*nl2 + nl3 + 3*Nj*nl3 + nl4 + 3*Nj*nl4 + nld + 3*Nj*nld;

  // U, Ux, Uy, Uz
  int nmax2 = 4*Nj*Knrbf34;

  // sumU and cU
  int nmax3 = 2*nelements*Knrbf34;

  // rbf, rbfx, rbfy, rbfz
  int nmax4 = 4*Nj*nrbfmax;

  // rbft, rbfxt, rbfyt, rbfzt
  int nmax5 = 4*Nj*ns;

  // abf, abfx, abfy, abfz
  int nmax6 = 4*(Nj+1)*Kmax;

  // Determine the maximum amount of memory needed for U, Ux, Uy, Uz, sumU, cU, rbf, rbfx, rbfy, rbfz, abf, abfx, abfy, abfz
  int nmax7 = (nmax5 > nmax6) ? nmax5 : nmax6;
  int nmax8 = nmax2 + nmax3 + nmax4 + nmax7;

  // Determine the total amount of memory needed for all double memory
  ndblmem = (nmax1 + nmax8);

  int nmax9 = 6*Nj + nComponents + nClusters + nClusters*nComponents + 2*nClusters*Mdesc + nClusters*nClusters;
  if (ndblmem < nmax9) ndblmem = nmax9;

  // Determine the total amount of memory needed for all integer memory
  nintmem = 4*Nj;

  // Return the estimated amount of memory needed
  return ndblmem;
}

void EAPOD::allocate_temp_memory(int Nj)
{
  estimate_temp_memory(Nj);
  memory->create(tmpmem, ndblmem, "tmpmem");
  memory->create(tmpint, nintmem, "tmpint");
  memory->create(bd, Mdesc, "bdd");
  memory->create(bdd, 3*Nj*Mdesc, "bdd");
  memory->create(pd, nClusters, "bdd");
  memory->create(pdd, 3*Nj*nClusters, "bdd");
}

void EAPOD::free_temp_memory()
{
  memory->destroy(tmpmem);
  memory->destroy(tmpint);
  memory->destroy(bd);
  memory->destroy(bdd);
  memory->destroy(pd);
  memory->destroy(pdd);
}

/**
 * @brief Map a 3D index to a 1D index.
 *
 * @param indx The 1D index array.
 * @param n1 The size of the first dimension.
 * @param n2 The size of the second dimension.
 * @param n3 The size of the third dimension.
 * @param N1 The stride of the first dimension.
 * @param N2 The stride of the second dimension.
 * @return int The total number of elements in the 1D index array.
 */
int EAPOD::indexmap3(int *indx, int n1, int n2, int n3, int N1, int N2)
{
  int k = 0;
  for (int i3=0; i3<n3; i3++)
    for (int i2=0; i2<n2; i2++)
      for (int i1=0; i1<n1; i1++)
      {
        // Map the 3D index to a 1D index
        indx[k] = i1 + N1*i2 + N1*N2*i3;
        k += 1;
      }

  // Return the total number of elements in the 1D index array
  return k;
}

/**
 * @brief Calculate the number of cross descriptors between two sets of descriptors.
 *
 * @param dabf1 Pointer to the array of degrees of angular basis functions of the first set of descriptors.
 * @param nabf1 Number of angular basis functions in the first set of descriptors.
 * @param nrbf1 Number of radial basis functions in the first set of descriptors.
 * @param nebf1 Number of element interactions in the first set of descriptors.
 * @param dabf2 Pointer to the array of degrees of angular basis functions of the second set of descriptors.
 * @param nabf2 Number of angular basis functions in the second set of descriptors.
 * @param nrbf2 Number of radial basis functions in the second set of descriptors.
 * @param nebf2 Number of element interactions in the second set descriptors.
 * @param dabf12 degree of angular basis functions for the cross descriptors
 * @param nrbf12 number of radial basis functions for the cross descriptors
 * @return int The number of cross descriptors between two sets of descriptors.
 */
int EAPOD::crossindices(int *dabf1, int nabf1, int nrbf1, int nebf1,
         int *dabf2, int nabf2, int nrbf2, int nebf2, int dabf12, int nrbf12)
{
  int n = 0;

  // Loop over the first set of descriptors
  for (int i1=0; i1<nebf1; i1++)
    for (int j1=0; j1<nrbf1; j1++)
      for (int k1=0; k1<nabf1; k1++) {
        int m1 = k1 + j1*nabf1;
        int a1 = dabf1[k1];
        // Loop over the second set of descriptors
        for (int i2=0; i2<nebf2; i2++)
          for (int j2=0; j2<nrbf2; j2++)
            for (int k2=0; k2<nabf2; k2++) {
              int m2 = k2 + j2*nabf2;
              int a2 = dabf2[k2];
              // Check if the sum of the angular degrees is less than or equal to dabf12,
              // the number of radial basis functions is less than nrbf12, and the indices are in the correct order
              if ((m2 >= m1) && (i2 >= i1) && (a1 + a2 <= dabf12) && (j1+j2 < nrbf12)) {
                n += 1;
              }
            }
      }

  return n;
}

/**
 * @brief Calculate the number of cross descriptors between two sets of descriptors and store the indices in two arrays.
 *
 * @param ind1 Pointer to the array of indices of the first set of descriptors.
 * @param ind2 Pointer to the array of indices of the second set of descriptors.
 * @param dabf1 Pointer to the array of degrees of angular basis functions of the first set of descriptors.
 * @param nabf1 Number of angular basis functions in the first set of descriptors.
 * @param nrbf1 Number of radial basis functions in the first set of descriptors.
 * @param nebf1 Number of element interactions in the first set of descriptors.
 * @param dabf2 Pointer to the array of degrees of angular basis functions of the second set of descriptors.
 * @param nabf2 Number of angular basis functions in the second set of descriptors.
 * @param nrbf2 Number of radial basis functions in the second set of descriptors.
 * @param nebf2 Number of element interactions in the second set descriptors.
 * @param dabf12 degree of angular basis functions for the cross descriptors
 * @param nrbf12 number of radial basis functions for the cross descriptors
 * @return int The number of cross descriptors between two sets of descriptors.
 */
int EAPOD::crossindices(int *ind1, int *ind2, int *dabf1, int nabf1, int nrbf1, int nebf1,
         int *dabf2, int nabf2, int nrbf2, int nebf2, int dabf12, int nrbf12)
{
  int n = 0;

  // Loop over the first set of descriptors
  for (int i1=0; i1<nebf1; i1++)
    for (int j1=0; j1<nrbf1; j1++)
      for (int k1=0; k1<nabf1; k1++) {
        int m1 = k1 + j1*nabf1;
        int n1 = m1 + i1*nabf1*nrbf1;
        int a1 = dabf1[k1];
        // Loop over the second set of descriptors
        for (int i2=0; i2<nebf2; i2++)
          for (int j2=0; j2<nrbf2; j2++)
            for (int k2=0; k2<nabf2; k2++) {
              int m2 = k2 + j2*nabf2;
              int n2 = m2 + i2*nabf2*nrbf2;
              int a2 = dabf2[k2];
              // Check if the sum of the angular degrees is less than or equal to dabf12,
              // the number of radial basis functions is less than nrbf12, and the indices are in the correct order
              if ((m2 >= m1) && (i2 >= i1) && (a1 + a2 <= dabf12) && (j1+j2 < nrbf12)) {
                ind1[n] = n1;
                ind2[n] = n2;
                n += 1;
              }
            }
      }

  return n;
}

void EAPOD::scalarproduct(double *d, double c, int N)
{
  for (int n=0; n<N; n++)
    d[n] = d[n]*c;
}

double EAPOD::dotproduct(double *c, double *d, int ndesc)
{
  double e = 0.0;
  for (int n = 0; n<ndesc; n++)
    e += d[n]*c[n];
  return e;
}

void EAPOD::mvproduct(double *fij, double *c, double *dd, int N, int ndesc)
{
  for (int m=0; m<ndesc; m++)
    for (int n=0; n<N; n++)
      fij[n] += dd[n + N*m]*c[m];
}

void EAPOD::MatMul(double *c, double *a, double *b, int r1, int c1, int c2)
{
  int i, j, k;

  for(j = 0; j < c2; j++)
    for(i = 0; i < r1; i++)
      c[i + r1*j] = 0.0;

  for(j = 0; j < c2; j++)
    for(i = 0; i < r1; i++)
      for(k = 0; k < c1; k++)
        c[i + r1*j] += a[i + r1*k] * b[k + c1*j];
}

void EAPOD::peratomenvironment_descriptors(double *P, double *dP_dR, double *B, double *dB_dR, double *tmp, int elem, int nNeighbors)
{
  double *ProjMat = &Proj[nComponents*Mdesc*elem];
  double *centroids = &Centroids[nComponents*nClusters*elem];
  double *pca = &tmp[0];
  double *D = &tmp[nComponents];
  double *dD_dpca = &tmp[nComponents + nClusters];
  double *dD_dB = &tmp[nComponents + nClusters + nClusters*nComponents];
  double *dP_dD = &tmp[nComponents + nClusters + nClusters*nComponents + nClusters*Mdesc];
  double *dP_dB = &tmp[nComponents + nClusters + nClusters*nComponents + nClusters*Mdesc + nClusters*nClusters];

  // calculate principal components
  for (int k = 0; k < nComponents; k++) {
    pca[k] = 0.0;
    for (int m = 0; m < Mdesc; m++) {
      pca[k] += ProjMat[k + nComponents*m] * B[m];
    }
  }

  // calculate inverse square distances
  double sumD = 0.0;
  for (int j = 0; j < nClusters; j++) {
    D[j] = 1e-20; // fix for zero distances
    for (int k = 0; k < nComponents; k++) {
      D[j] += (pca[k] - centroids[k + j * nComponents]) * (pca[k] - centroids[k + j * nComponents]);
    }
    D[j] = 1.0 / D[j];
    sumD += D[j];
  }

  // calculate probabilities
  for (int j = 0; j < nClusters; j++) {
    P[j] = D[j] / sumD;
  }

  // calculate dD_dpca
  for (int n = 0; n < nComponents; n++) {
    for (int k = 0; k < nClusters; k++) {
      dD_dpca[k + n * nClusters] = 2 * D[k] * D[k] * (centroids[n + k * nComponents] - pca[n]);
    }
  }

  // calculate dD_dB
  char chn = 'N';
  char cht = 'T';
  double alpha = 1.0, beta = 0.0;
  DGEMM(&chn, &chn, &nClusters, &Mdesc, &nComponents, &alpha, dD_dpca, &nClusters, ProjMat, &nComponents, &beta, dD_dB, &nClusters);

  // calculate dP_dD
  double S1 = 1.0 / sumD;
  double S2 = S1 * S1;
  for (int k = 0; k < nClusters; k++) {
    for (int j = 0; j < nClusters; j++) {
      dP_dD[j + k * nClusters] = -D[j] * S2;
    }
  }
  for (int j = 0; j < nClusters; j++) {
    dP_dD[j + j * nClusters] += S1;
  }

  // calculate dP_dB = dP_dD * dD_dB, which are derivatives of probabilities with respect to local descriptors
  DGEMM(&chn, &chn, &nClusters, &Mdesc, &nClusters, &alpha, dP_dD, &nClusters, dD_dB, &nClusters, &beta, dP_dB, &nClusters);

  // calculate dP_dR = dB_dR * dP_dB , which are derivatives of probabilities with respect to atomic coordinates
  int N = 3*nNeighbors;
  DGEMM(&chn, &cht, &N, &nClusters, &Mdesc, &alpha, dB_dR, &N, dP_dB, &nClusters, &beta, dP_dR, &N);
}

void EAPOD::init3bodyarray(int *np, int *pq, int *pc, int Pa)
{
  int npa[] = {0, 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, 286, 364, 455};

  int poly[455][6] =
  {
    {0, 0, 0, 0, 0, 1},
    {1, 0, 0, 1, 1, 1},
    {0, 1, 0, 1, 2, 1},
    {0, 0, 1, 1, 3, 1},
    {2, 0, 0, 2, 1, 1},
    {1, 1, 0, 2, 2, 2},
    {0, 2, 0, 3, 2, 1},
    {1, 0, 1, 2, 3, 2},
    {0, 1, 1, 3, 3, 2},
    {0, 0, 2, 4, 3, 1},
    {3, 0, 0, 5, 1, 1},
    {2, 1, 0, 5, 2, 3},
    {1, 2, 0, 6, 2, 3},
    {0, 3, 0, 7, 2, 1},
    {2, 0, 1, 5, 3, 3},
    {1, 1, 1, 6, 3, 6},
    {0, 2, 1, 7, 3, 3},
    {1, 0, 2, 8, 3, 3},
    {0, 1, 2, 9, 3, 3},
    {0, 0, 3, 10, 3, 1},
    {4, 0, 0, 11, 1, 1},
    {3, 1, 0, 11, 2, 4},
    {2, 2, 0, 12, 2, 6},
    {1, 3, 0, 13, 2, 4},
    {0, 4, 0, 14, 2, 1},
    {3, 0, 1, 11, 3, 4},
    {2, 1, 1, 12, 3, 12},
    {1, 2, 1, 13, 3, 12},
    {0, 3, 1, 14, 3, 4},
    {2, 0, 2, 15, 3, 6},
    {1, 1, 2, 16, 3, 12},
    {0, 2, 2, 17, 3, 6},
    {1, 0, 3, 18, 3, 4},
    {0, 1, 3, 19, 3, 4},
    {0, 0, 4, 20, 3, 1},
    {5, 0, 0, 21, 1, 1},
    {4, 1, 0, 21, 2, 5},
    {3, 2, 0, 22, 2, 10},
    {2, 3, 0, 23, 2, 10},
    {1, 4, 0, 24, 2, 5},
    {0, 5, 0, 25, 2, 1},
    {4, 0, 1, 21, 3, 5},
    {3, 1, 1, 22, 3, 20},
    {2, 2, 1, 23, 3, 30},
    {1, 3, 1, 24, 3, 20},
    {0, 4, 1, 25, 3, 5},
    {3, 0, 2, 26, 3, 10},
    {2, 1, 2, 27, 3, 30},
    {1, 2, 2, 28, 3, 30},
    {0, 3, 2, 29, 3, 10},
    {2, 0, 3, 30, 3, 10},
    {1, 1, 3, 31, 3, 20},
    {0, 2, 3, 32, 3, 10},
    {1, 0, 4, 33, 3, 5},
    {0, 1, 4, 34, 3, 5},
    {0, 0, 5, 35, 3, 1},
    {6, 0, 0, 36, 1, 1},
    {5, 1, 0, 36, 2, 6},
    {4, 2, 0, 37, 2, 15},
    {3, 3, 0, 38, 2, 20},
    {2, 4, 0, 39, 2, 15},
    {1, 5, 0, 40, 2, 6},
    {0, 6, 0, 41, 2, 1},
    {5, 0, 1, 36, 3, 6},
    {4, 1, 1, 37, 3, 30},
    {3, 2, 1, 38, 3, 60},
    {2, 3, 1, 39, 3, 60},
    {1, 4, 1, 40, 3, 30},
    {0, 5, 1, 41, 3, 6},
    {4, 0, 2, 42, 3, 15},
    {3, 1, 2, 43, 3, 60},
    {2, 2, 2, 44, 3, 90},
    {1, 3, 2, 45, 3, 60},
    {0, 4, 2, 46, 3, 15},
    {3, 0, 3, 47, 3, 20},
    {2, 1, 3, 48, 3, 60},
    {1, 2, 3, 49, 3, 60},
    {0, 3, 3, 50, 3, 20},
    {2, 0, 4, 51, 3, 15},
    {1, 1, 4, 52, 3, 30},
    {0, 2, 4, 53, 3, 15},
    {1, 0, 5, 54, 3, 6},
    {0, 1, 5, 55, 3, 6},
    {0, 0, 6, 56, 3, 1},
    {7, 0, 0, 57, 1, 1},
    {6, 1, 0, 57, 2, 7},
    {5, 2, 0, 58, 2, 21},
    {4, 3, 0, 59, 2, 35},
    {3, 4, 0, 60, 2, 35},
    {2, 5, 0, 61, 2, 21},
    {1, 6, 0, 62, 2, 7},
    {0, 7, 0, 63, 2, 1},
    {6, 0, 1, 57, 3, 7},
    {5, 1, 1, 58, 3, 42},
    {4, 2, 1, 59, 3, 105},
    {3, 3, 1, 60, 3, 140},
    {2, 4, 1, 61, 3, 105},
    {1, 5, 1, 62, 3, 42},
    {0, 6, 1, 63, 3, 7},
    {5, 0, 2, 64, 3, 21},
    {4, 1, 2, 65, 3, 105},
    {3, 2, 2, 66, 3, 210},
    {2, 3, 2, 67, 3, 210},
    {1, 4, 2, 68, 3, 105},
    {0, 5, 2, 69, 3, 21},
    {4, 0, 3, 70, 3, 35},
    {3, 1, 3, 71, 3, 140},
    {2, 2, 3, 72, 3, 210},
    {1, 3, 3, 73, 3, 140},
    {0, 4, 3, 74, 3, 35},
    {3, 0, 4, 75, 3, 35},
    {2, 1, 4, 76, 3, 105},
    {1, 2, 4, 77, 3, 105},
    {0, 3, 4, 78, 3, 35},
    {2, 0, 5, 79, 3, 21},
    {1, 1, 5, 80, 3, 42},
    {0, 2, 5, 81, 3, 21},
    {1, 0, 6, 82, 3, 7},
    {0, 1, 6, 83, 3, 7},
    {0, 0, 7, 84, 3, 1},
    {8, 0, 0, 85, 1, 1},
    {7, 1, 0, 85, 2, 8},
    {6, 2, 0, 86, 2, 28},
    {5, 3, 0, 87, 2, 56},
    {4, 4, 0, 88, 2, 70},
    {3, 5, 0, 89, 2, 56},
    {2, 6, 0, 90, 2, 28},
    {1, 7, 0, 91, 2, 8},
    {0, 8, 0, 92, 2, 1},
    {7, 0, 1, 85, 3, 8},
    {6, 1, 1, 86, 3, 56},
    {5, 2, 1, 87, 3, 168},
    {4, 3, 1, 88, 3, 280},
    {3, 4, 1, 89, 3, 280},
    {2, 5, 1, 90, 3, 168},
    {1, 6, 1, 91, 3, 56},
    {0, 7, 1, 92, 3, 8},
    {6, 0, 2, 93, 3, 28},
    {5, 1, 2, 94, 3, 168},
    {4, 2, 2, 95, 3, 420},
    {3, 3, 2, 96, 3, 560},
    {2, 4, 2, 97, 3, 420},
    {1, 5, 2, 98, 3, 168},
    {0, 6, 2, 99, 3, 28},
    {5, 0, 3, 100, 3, 56},
    {4, 1, 3, 101, 3, 280},
    {3, 2, 3, 102, 3, 560},
    {2, 3, 3, 103, 3, 560},
    {1, 4, 3, 104, 3, 280},
    {0, 5, 3, 105, 3, 56},
    {4, 0, 4, 106, 3, 70},
    {3, 1, 4, 107, 3, 280},
    {2, 2, 4, 108, 3, 420},
    {1, 3, 4, 109, 3, 280},
    {0, 4, 4, 110, 3, 70},
    {3, 0, 5, 111, 3, 56},
    {2, 1, 5, 112, 3, 168},
    {1, 2, 5, 113, 3, 168},
    {0, 3, 5, 114, 3, 56},
    {2, 0, 6, 115, 3, 28},
    {1, 1, 6, 116, 3, 56},
    {0, 2, 6, 117, 3, 28},
    {1, 0, 7, 118, 3, 8},
    {0, 1, 7, 119, 3, 8},
    {0, 0, 8, 120, 3, 1},
    {9, 0, 0, 121, 1, 1},
    {8, 1, 0, 121, 2, 9},
    {7, 2, 0, 122, 2, 36},
    {6, 3, 0, 123, 2, 84},
    {5, 4, 0, 124, 2, 126},
    {4, 5, 0, 125, 2, 126},
    {3, 6, 0, 126, 2, 84},
    {2, 7, 0, 127, 2, 36},
    {1, 8, 0, 128, 2, 9},
    {0, 9, 0, 129, 2, 1},
    {8, 0, 1, 121, 3, 9},
    {7, 1, 1, 122, 3, 72},
    {6, 2, 1, 123, 3, 252},
    {5, 3, 1, 124, 3, 504},
    {4, 4, 1, 125, 3, 630},
    {3, 5, 1, 126, 3, 504},
    {2, 6, 1, 127, 3, 252},
    {1, 7, 1, 128, 3, 72},
    {0, 8, 1, 129, 3, 9},
    {7, 0, 2, 130, 3, 36},
    {6, 1, 2, 131, 3, 252},
    {5, 2, 2, 132, 3, 756},
    {4, 3, 2, 133, 3, 1260},
    {3, 4, 2, 134, 3, 1260},
    {2, 5, 2, 135, 3, 756},
    {1, 6, 2, 136, 3, 252},
    {0, 7, 2, 137, 3, 36},
    {6, 0, 3, 138, 3, 84},
    {5, 1, 3, 139, 3, 504},
    {4, 2, 3, 140, 3, 1260},
    {3, 3, 3, 141, 3, 1680},
    {2, 4, 3, 142, 3, 1260},
    {1, 5, 3, 143, 3, 504},
    {0, 6, 3, 144, 3, 84},
    {5, 0, 4, 145, 3, 126},
    {4, 1, 4, 146, 3, 630},
    {3, 2, 4, 147, 3, 1260},
    {2, 3, 4, 148, 3, 1260},
    {1, 4, 4, 149, 3, 630},
    {0, 5, 4, 150, 3, 126},
    {4, 0, 5, 151, 3, 126},
    {3, 1, 5, 152, 3, 504},
    {2, 2, 5, 153, 3, 756},
    {1, 3, 5, 154, 3, 504},
    {0, 4, 5, 155, 3, 126},
    {3, 0, 6, 156, 3, 84},
    {2, 1, 6, 157, 3, 252},
    {1, 2, 6, 158, 3, 252},
    {0, 3, 6, 159, 3, 84},
    {2, 0, 7, 160, 3, 36},
    {1, 1, 7, 161, 3, 72},
    {0, 2, 7, 162, 3, 36},
    {1, 0, 8, 163, 3, 9},
    {0, 1, 8, 164, 3, 9},
    {0, 0, 9, 165, 3, 1},
    {10, 0, 0, 166, 1, 1},
    {9, 1, 0, 166, 2, 10},
    {8, 2, 0, 167, 2, 45},
    {7, 3, 0, 168, 2, 120},
    {6, 4, 0, 169, 2, 210},
    {5, 5, 0, 170, 2, 252},
    {4, 6, 0, 171, 2, 210},
    {3, 7, 0, 172, 2, 120},
    {2, 8, 0, 173, 2, 45},
    {1, 9, 0, 174, 2, 10},
    {0, 10, 0, 175, 2, 1},
    {9, 0, 1, 166, 3, 10},
    {8, 1, 1, 167, 3, 90},
    {7, 2, 1, 168, 3, 360},
    {6, 3, 1, 169, 3, 840},
    {5, 4, 1, 170, 3, 1260},
    {4, 5, 1, 171, 3, 1260},
    {3, 6, 1, 172, 3, 840},
    {2, 7, 1, 173, 3, 360},
    {1, 8, 1, 174, 3, 90},
    {0, 9, 1, 175, 3, 10},
    {8, 0, 2, 176, 3, 45},
    {7, 1, 2, 177, 3, 360},
    {6, 2, 2, 178, 3, 1260},
    {5, 3, 2, 179, 3, 2520},
    {4, 4, 2, 180, 3, 3150},
    {3, 5, 2, 181, 3, 2520},
    {2, 6, 2, 182, 3, 1260},
    {1, 7, 2, 183, 3, 360},
    {0, 8, 2, 184, 3, 45},
    {7, 0, 3, 185, 3, 120},
    {6, 1, 3, 186, 3, 840},
    {5, 2, 3, 187, 3, 2520},
    {4, 3, 3, 188, 3, 4200},
    {3, 4, 3, 189, 3, 4200},
    {2, 5, 3, 190, 3, 2520},
    {1, 6, 3, 191, 3, 840},
    {0, 7, 3, 192, 3, 120},
    {6, 0, 4, 193, 3, 210},
    {5, 1, 4, 194, 3, 1260},
    {4, 2, 4, 195, 3, 3150},
    {3, 3, 4, 196, 3, 4200},
    {2, 4, 4, 197, 3, 3150},
    {1, 5, 4, 198, 3, 1260},
    {0, 6, 4, 199, 3, 210},
    {5, 0, 5, 200, 3, 252},
    {4, 1, 5, 201, 3, 1260},
    {3, 2, 5, 202, 3, 2520},
    {2, 3, 5, 203, 3, 2520},
    {1, 4, 5, 204, 3, 1260},
    {0, 5, 5, 205, 3, 252},
    {4, 0, 6, 206, 3, 210},
    {3, 1, 6, 207, 3, 840},
    {2, 2, 6, 208, 3, 1260},
    {1, 3, 6, 209, 3, 840},
    {0, 4, 6, 210, 3, 210},
    {3, 0, 7, 211, 3, 120},
    {2, 1, 7, 212, 3, 360},
    {1, 2, 7, 213, 3, 360},
    {0, 3, 7, 214, 3, 120},
    {2, 0, 8, 215, 3, 45},
    {1, 1, 8, 216, 3, 90},
    {0, 2, 8, 217, 3, 45},
    {1, 0, 9, 218, 3, 10},
    {0, 1, 9, 219, 3, 10},
    {0, 0, 10, 220, 3, 1},
    {11, 0, 0, 221, 1, 1},
    {10, 1, 0, 221, 2, 11},
    {9, 2, 0, 222, 2, 55},
    {8, 3, 0, 223, 2, 165},
    {7, 4, 0, 224, 2, 330},
    {6, 5, 0, 225, 2, 462},
    {5, 6, 0, 226, 2, 462},
    {4, 7, 0, 227, 2, 330},
    {3, 8, 0, 228, 2, 165},
    {2, 9, 0, 229, 2, 55},
    {1, 10, 0, 230, 2, 11},
    {0, 11, 0, 231, 2, 1},
    {10, 0, 1, 221, 3, 11},
    {9, 1, 1, 222, 3, 110},
    {8, 2, 1, 223, 3, 495},
    {7, 3, 1, 224, 3, 1320},
    {6, 4, 1, 225, 3, 2310},
    {5, 5, 1, 226, 3, 2772},
    {4, 6, 1, 227, 3, 2310},
    {3, 7, 1, 228, 3, 1320},
    {2, 8, 1, 229, 3, 495},
    {1, 9, 1, 230, 3, 110},
    {0, 10, 1, 231, 3, 11},
    {9, 0, 2, 232, 3, 55},
    {8, 1, 2, 233, 3, 495},
    {7, 2, 2, 234, 3, 1980},
    {6, 3, 2, 235, 3, 4620},
    {5, 4, 2, 236, 3, 6930},
    {4, 5, 2, 237, 3, 6930},
    {3, 6, 2, 238, 3, 4620},
    {2, 7, 2, 239, 3, 1980},
    {1, 8, 2, 240, 3, 495},
    {0, 9, 2, 241, 3, 55},
    {8, 0, 3, 242, 3, 165},
    {7, 1, 3, 243, 3, 1320},
    {6, 2, 3, 244, 3, 4620},
    {5, 3, 3, 245, 3, 9240},
    {4, 4, 3, 246, 3, 11550},
    {3, 5, 3, 247, 3, 9240},
    {2, 6, 3, 248, 3, 4620},
    {1, 7, 3, 249, 3, 1320},
    {0, 8, 3, 250, 3, 165},
    {7, 0, 4, 251, 3, 330},
    {6, 1, 4, 252, 3, 2310},
    {5, 2, 4, 253, 3, 6930},
    {4, 3, 4, 254, 3, 11550},
    {3, 4, 4, 255, 3, 11550},
    {2, 5, 4, 256, 3, 6930},
    {1, 6, 4, 257, 3, 2310},
    {0, 7, 4, 258, 3, 330},
    {6, 0, 5, 259, 3, 462},
    {5, 1, 5, 260, 3, 2772},
    {4, 2, 5, 261, 3, 6930},
    {3, 3, 5, 262, 3, 9240},
    {2, 4, 5, 263, 3, 6930},
    {1, 5, 5, 264, 3, 2772},
    {0, 6, 5, 265, 3, 462},
    {5, 0, 6, 266, 3, 462},
    {4, 1, 6, 267, 3, 2310},
    {3, 2, 6, 268, 3, 4620},
    {2, 3, 6, 269, 3, 4620},
    {1, 4, 6, 270, 3, 2310},
    {0, 5, 6, 271, 3, 462},
    {4, 0, 7, 272, 3, 330},
    {3, 1, 7, 273, 3, 1320},
    {2, 2, 7, 274, 3, 1980},
    {1, 3, 7, 275, 3, 1320},
    {0, 4, 7, 276, 3, 330},
    {3, 0, 8, 277, 3, 165},
    {2, 1, 8, 278, 3, 495},
    {1, 2, 8, 279, 3, 495},
    {0, 3, 8, 280, 3, 165},
    {2, 0, 9, 281, 3, 55},
    {1, 1, 9, 282, 3, 110},
    {0, 2, 9, 283, 3, 55},
    {1, 0, 10, 284, 3, 11},
    {0, 1, 10, 285, 3, 11},
    {0, 0, 11, 286, 3, 1},
    {12, 0, 0, 287, 1, 1},
    {11, 1, 0, 287, 2, 12},
    {10, 2, 0, 288, 2, 66},
    {9, 3, 0, 289, 2, 220},
    {8, 4, 0, 290, 2, 495},
    {7, 5, 0, 291, 2, 792},
    {6, 6, 0, 292, 2, 924},
    {5, 7, 0, 293, 2, 792},
    {4, 8, 0, 294, 2, 495},
    {3, 9, 0, 295, 2, 220},
    {2, 10, 0, 296, 2, 66},
    {1, 11, 0, 297, 2, 12},
    {0, 12, 0, 298, 2, 1},
    {11, 0, 1, 287, 3, 12},
    {10, 1, 1, 288, 3, 132},
    {9, 2, 1, 289, 3, 660},
    {8, 3, 1, 290, 3, 1980},
    {7, 4, 1, 291, 3, 3960},
    {6, 5, 1, 292, 3, 5544},
    {5, 6, 1, 293, 3, 5544},
    {4, 7, 1, 294, 3, 3960},
    {3, 8, 1, 295, 3, 1980},
    {2, 9, 1, 296, 3, 660},
    {1, 10, 1, 297, 3, 132},
    {0, 11, 1, 298, 3, 12},
    {10, 0, 2, 299, 3, 66},
    {9, 1, 2, 300, 3, 660},
    {8, 2, 2, 301, 3, 2970},
    {7, 3, 2, 302, 3, 7920},
    {6, 4, 2, 303, 3, 13860},
    {5, 5, 2, 304, 3, 16632},
    {4, 6, 2, 305, 3, 13860},
    {3, 7, 2, 306, 3, 7920},
    {2, 8, 2, 307, 3, 2970},
    {1, 9, 2, 308, 3, 660},
    {0, 10, 2, 309, 3, 66},
    {9, 0, 3, 310, 3, 220},
    {8, 1, 3, 311, 3, 1980},
    {7, 2, 3, 312, 3, 7920},
    {6, 3, 3, 313, 3, 18480},
    {5, 4, 3, 314, 3, 27720},
    {4, 5, 3, 315, 3, 27720},
    {3, 6, 3, 316, 3, 18480},
    {2, 7, 3, 317, 3, 7920},
    {1, 8, 3, 318, 3, 1980},
    {0, 9, 3, 319, 3, 220},
    {8, 0, 4, 320, 3, 495},
    {7, 1, 4, 321, 3, 3960},
    {6, 2, 4, 322, 3, 13860},
    {5, 3, 4, 323, 3, 27720},
    {4, 4, 4, 324, 3, 34650},
    {3, 5, 4, 325, 3, 27720},
    {2, 6, 4, 326, 3, 13860},
    {1, 7, 4, 327, 3, 3960},
    {0, 8, 4, 328, 3, 495},
    {7, 0, 5, 329, 3, 792},
    {6, 1, 5, 330, 3, 5544},
    {5, 2, 5, 331, 3, 16632},
    {4, 3, 5, 332, 3, 27720},
    {3, 4, 5, 333, 3, 27720},
    {2, 5, 5, 334, 3, 16632},
    {1, 6, 5, 335, 3, 5544},
    {0, 7, 5, 336, 3, 792},
    {6, 0, 6, 337, 3, 924},
    {5, 1, 6, 338, 3, 5544},
    {4, 2, 6, 339, 3, 13860},
    {3, 3, 6, 340, 3, 18480},
    {2, 4, 6, 341, 3, 13860},
    {1, 5, 6, 342, 3, 5544},
    {0, 6, 6, 343, 3, 924},
    {5, 0, 7, 344, 3, 792},
    {4, 1, 7, 345, 3, 3960},
    {3, 2, 7, 346, 3, 7920},
    {2, 3, 7, 347, 3, 7920},
    {1, 4, 7, 348, 3, 3960},
    {0, 5, 7, 349, 3, 792},
    {4, 0, 8, 350, 3, 495},
    {3, 1, 8, 351, 3, 1980},
    {2, 2, 8, 352, 3, 2970},
    {1, 3, 8, 353, 3, 1980},
    {0, 4, 8, 354, 3, 495},
    {3, 0, 9, 355, 3, 220},
    {2, 1, 9, 356, 3, 660},
    {1, 2, 9, 357, 3, 660},
    {0, 3, 9, 358, 3, 220},
    {2, 0, 10, 359, 3, 66},
    {1, 1, 10, 360, 3, 132},
    {0, 2, 10, 361, 3, 66},
    {1, 0, 11, 362, 3, 12},
    {0, 1, 11, 363, 3, 12},
    {0, 0, 12, 364, 3, 1}
  };

  for (int i = 0; i<= Pa+1; i++)
    np[i] = npa[i];

  int nmax = np[Pa+1];

  for (int i=0; i<nmax; i++) {
    pq[i]        = poly[i][3];
    pq[i+nmax]   = poly[i][4];
    pc[i]        = poly[i][5];
  }
}

void EAPOD::init4bodyarray(int *ns4, int *pb4, int *pc4, int Pa)
{
  int nb[] = {1,     2,     4,     7,    11,    16,    23};

  int ns[] =
  {
    0,
    1,
    4,
    10,
    19,
    29,
    47,
    74,
    89,
    119,
    155,
    209,
    230,
    275,
    335,
    425,
    533,
    561,
    624,
    714,
    849,
    949,
    1129,
    1345
  };

  int poly[1345][4] =
  {
    {1, 1, 1, 1},
    {2, 2, 1, 1},
    {3, 3, 1, 1},
    {4, 4, 1, 1},
    {5, 5, 1, 1},
    {6, 6, 1, 2},
    {8, 8, 1, 2},
    {7, 7, 1, 1},
    {9, 9, 1, 2},
    {10, 10, 1, 1},
    {5, 2, 2, 1},
    {6, 2, 3, 1},
    {6, 3, 2, 1},
    {8, 2, 4, 1},
    {8, 4, 2, 1},
    {7, 3, 3, 1},
    {9, 3, 4, 1},
    {9, 4, 3, 1},
    {10, 4, 4, 1},
    {11, 11, 1, 1},
    {12, 12, 1, 3},
    {15, 15, 1, 3},
    {13, 13, 1, 3},
    {16, 16, 1, 6},
    {18, 18, 1, 3},
    {14, 14, 1, 1},
    {17, 17, 1, 3},
    {19, 19, 1, 3},
    {20, 20, 1, 1},
    {11, 5, 2, 1},
    {12, 5, 3, 1},
    {12, 6, 2, 2},
    {15, 5, 4, 1},
    {15, 8, 2, 2},
    {13, 6, 3, 2},
    {13, 7, 2, 1},
    {16, 6, 4, 2},
    {16, 8, 3, 2},
    {16, 9, 2, 2},
    {18, 8, 4, 2},
    {18, 10, 2, 1},
    {14, 7, 3, 1},
    {17, 7, 4, 1},
    {17, 9, 3, 2},
    {19, 9, 4, 2},
    {19, 10, 3, 1},
    {20, 10, 4, 1},
    {5, 5, 5, 1},
    {5, 6, 6, 1},
    {5, 8, 8, 1},
    {6, 5, 6, 1},
    {6, 6, 5, 1},
    {6, 6, 7, 1},
    {6, 8, 9, 1},
    {6, 7, 6, 1},
    {6, 9, 8, 1},
    {8, 5, 8, 1},
    {8, 6, 9, 1},
    {8, 8, 5, 1},
    {8, 8, 10, 1},
    {8, 9, 6, 1},
    {8, 10, 8, 1},
    {7, 6, 6, 1},
    {7, 7, 7, 1},
    {7, 9, 9, 1},
    {9, 6, 8, 1},
    {9, 8, 6, 1},
    {9, 7, 9, 1},
    {9, 9, 7, 1},
    {9, 9, 10, 1},
    {9, 10, 9, 1},
    {10, 8, 8, 1},
    {10, 9, 9, 1},
    {10, 10, 10, 1},
    {21, 21, 1, 1},
    {22, 22, 1, 4},
    {26, 26, 1, 4},
    {23, 23, 1, 6},
    {27, 27, 1, 12},
    {30, 30, 1, 6},
    {24, 24, 1, 4},
    {28, 28, 1, 12},
    {31, 31, 1, 12},
    {33, 33, 1, 4},
    {25, 25, 1, 1},
    {29, 29, 1, 4},
    {32, 32, 1, 6},
    {34, 34, 1, 4},
    {35, 35, 1, 1},
    {21, 11, 2, 1},
    {22, 11, 3, 1},
    {22, 12, 2, 3},
    {26, 11, 4, 1},
    {26, 15, 2, 3},
    {23, 12, 3, 3},
    {23, 13, 2, 3},
    {27, 12, 4, 3},
    {27, 15, 3, 3},
    {27, 16, 2, 6},
    {30, 15, 4, 3},
    {30, 18, 2, 3},
    {24, 13, 3, 3},
    {24, 14, 2, 1},
    {28, 13, 4, 3},
    {28, 16, 3, 6},
    {28, 17, 2, 3},
    {31, 16, 4, 6},
    {31, 18, 3, 3},
    {31, 19, 2, 3},
    {33, 18, 4, 3},
    {33, 20, 2, 1},
    {25, 14, 3, 1},
    {29, 14, 4, 1},
    {29, 17, 3, 3},
    {32, 17, 4, 3},
    {32, 19, 3, 3},
    {34, 19, 4, 3},
    {34, 20, 3, 1},
    {35, 20, 4, 1},
    {21, 5, 5, 1},
    {22, 5, 6, 2},
    {22, 6, 5, 2},
    {26, 5, 8, 2},
    {26, 8, 5, 2},
    {23, 5, 7, 1},
    {23, 6, 6, 4},
    {23, 7, 5, 1},
    {27, 5, 9, 2},
    {27, 6, 8, 4},
    {27, 8, 6, 4},
    {27, 9, 5, 2},
    {30, 5, 10, 1},
    {30, 8, 8, 4},
    {30, 10, 5, 1},
    {24, 6, 7, 2},
    {24, 7, 6, 2},
    {28, 6, 9, 4},
    {28, 8, 7, 2},
    {28, 7, 8, 2},
    {28, 9, 6, 4},
    {31, 6, 10, 2},
    {31, 8, 9, 4},
    {31, 9, 8, 4},
    {31, 10, 6, 2},
    {33, 8, 10, 2},
    {33, 10, 8, 2},
    {25, 7, 7, 1},
    {29, 7, 9, 2},
    {29, 9, 7, 2},
    {32, 7, 10, 1},
    {32, 9, 9, 4},
    {32, 10, 7, 1},
    {34, 9, 10, 2},
    {34, 10, 9, 2},
    {35, 10, 10, 1},
    {11, 11, 5, 1},
    {11, 12, 6, 1},
    {11, 15, 8, 1},
    {12, 11, 6, 1},
    {12, 12, 5, 2},
    {12, 12, 7, 1},
    {12, 15, 9, 1},
    {12, 13, 6, 2},
    {12, 16, 8, 2},
    {15, 11, 8, 1},
    {15, 12, 9, 1},
    {15, 15, 5, 2},
    {15, 15, 10, 1},
    {15, 16, 6, 2},
    {15, 18, 8, 2},
    {13, 12, 6, 2},
    {13, 13, 5, 1},
    {13, 13, 7, 2},
    {13, 16, 9, 2},
    {13, 14, 6, 1},
    {13, 17, 8, 1},
    {16, 12, 8, 2},
    {16, 15, 6, 2},
    {16, 13, 9, 2},
    {16, 16, 5, 2},
    {16, 16, 7, 2},
    {16, 16, 10, 2},
    {16, 18, 9, 2},
    {16, 17, 6, 2},
    {16, 19, 8, 2},
    {18, 15, 8, 2},
    {18, 16, 9, 2},
    {18, 18, 5, 1},
    {18, 18, 10, 2},
    {18, 19, 6, 1},
    {18, 20, 8, 1},
    {14, 13, 6, 1},
    {14, 14, 7, 1},
    {14, 17, 9, 1},
    {17, 13, 8, 1},
    {17, 16, 6, 2},
    {17, 14, 9, 1},
    {17, 17, 7, 2},
    {17, 17, 10, 1},
    {17, 19, 9, 2},
    {19, 16, 8, 2},
    {19, 18, 6, 1},
    {19, 17, 9, 2},
    {19, 19, 7, 1},
    {19, 19, 10, 2},
    {19, 20, 9, 1},
    {20, 18, 8, 1},
    {20, 19, 9, 1},
    {20, 20, 10, 1},
    {36, 36, 1, 1},
    {37, 37, 1, 5},
    {42, 42, 1, 5},
    {38, 38, 1, 10},
    {43, 43, 1, 20},
    {47, 47, 1, 10},
    {39, 39, 1, 10},
    {44, 44, 1, 30},
    {48, 48, 1, 30},
    {51, 51, 1, 10},
    {40, 40, 1, 5},
    {45, 45, 1, 20},
    {49, 49, 1, 30},
    {52, 52, 1, 20},
    {54, 54, 1, 5},
    {41, 41, 1, 1},
    {46, 46, 1, 5},
    {50, 50, 1, 10},
    {53, 53, 1, 10},
    {55, 55, 1, 5},
    {56, 56, 1, 1},
    {36, 21, 2, 1},
    {37, 21, 3, 1},
    {37, 22, 2, 4},
    {42, 21, 4, 1},
    {42, 26, 2, 4},
    {38, 22, 3, 4},
    {38, 23, 2, 6},
    {43, 22, 4, 4},
    {43, 26, 3, 4},
    {43, 27, 2, 12},
    {47, 26, 4, 4},
    {47, 30, 2, 6},
    {39, 23, 3, 6},
    {39, 24, 2, 4},
    {44, 23, 4, 6},
    {44, 27, 3, 12},
    {44, 28, 2, 12},
    {48, 27, 4, 12},
    {48, 30, 3, 6},
    {48, 31, 2, 12},
    {51, 30, 4, 6},
    {51, 33, 2, 4},
    {40, 24, 3, 4},
    {40, 25, 2, 1},
    {45, 24, 4, 4},
    {45, 28, 3, 12},
    {45, 29, 2, 4},
    {49, 28, 4, 12},
    {49, 31, 3, 12},
    {49, 32, 2, 6},
    {52, 31, 4, 12},
    {52, 33, 3, 4},
    {52, 34, 2, 4},
    {54, 33, 4, 4},
    {54, 35, 2, 1},
    {41, 25, 3, 1},
    {46, 25, 4, 1},
    {46, 29, 3, 4},
    {50, 29, 4, 4},
    {50, 32, 3, 6},
    {53, 32, 4, 6},
    {53, 34, 3, 4},
    {55, 34, 4, 4},
    {55, 35, 3, 1},
    {56, 35, 4, 1},
    {36, 11, 5, 1},
    {37, 11, 6, 2},
    {37, 12, 5, 3},
    {42, 11, 8, 2},
    {42, 15, 5, 3},
    {38, 11, 7, 1},
    {38, 12, 6, 6},
    {38, 13, 5, 3},
    {43, 11, 9, 2},
    {43, 12, 8, 6},
    {43, 15, 6, 6},
    {43, 16, 5, 6},
    {47, 11, 10, 1},
    {47, 15, 8, 6},
    {47, 18, 5, 3},
    {39, 12, 7, 3},
    {39, 13, 6, 6},
    {39, 14, 5, 1},
    {44, 12, 9, 6},
    {44, 15, 7, 3},
    {44, 13, 8, 6},
    {44, 16, 6, 12},
    {44, 17, 5, 3},
    {48, 12, 10, 3},
    {48, 15, 9, 6},
    {48, 16, 8, 12},
    {48, 18, 6, 6},
    {48, 19, 5, 3},
    {51, 15, 10, 3},
    {51, 18, 8, 6},
    {51, 20, 5, 1},
    {40, 13, 7, 3},
    {40, 14, 6, 2},
    {45, 13, 9, 6},
    {45, 16, 7, 6},
    {45, 14, 8, 2},
    {45, 17, 6, 6},
    {49, 13, 10, 3},
    {49, 16, 9, 12},
    {49, 18, 7, 3},
    {49, 17, 8, 6},
    {49, 19, 6, 6},
    {52, 16, 10, 6},
    {52, 18, 9, 6},
    {52, 19, 8, 6},
    {52, 20, 6, 2},
    {54, 18, 10, 3},
    {54, 20, 8, 2},
    {41, 14, 7, 1},
    {46, 14, 9, 2},
    {46, 17, 7, 3},
    {50, 14, 10, 1},
    {50, 17, 9, 6},
    {50, 19, 7, 3},
    {53, 17, 10, 3},
    {53, 19, 9, 6},
    {53, 20, 7, 1},
    {55, 19, 10, 3},
    {55, 20, 9, 2},
    {56, 20, 10, 1},
    {21, 21, 5, 1},
    {21, 22, 6, 1},
    {21, 26, 8, 1},
    {22, 21, 6, 1},
    {22, 22, 5, 3},
    {22, 22, 7, 1},
    {22, 26, 9, 1},
    {22, 23, 6, 3},
    {22, 27, 8, 3},
    {26, 21, 8, 1},
    {26, 22, 9, 1},
    {26, 26, 5, 3},
    {26, 26, 10, 1},
    {26, 27, 6, 3},
    {26, 30, 8, 3},
    {23, 22, 6, 3},
    {23, 23, 5, 3},
    {23, 23, 7, 3},
    {23, 27, 9, 3},
    {23, 24, 6, 3},
    {23, 28, 8, 3},
    {27, 22, 8, 3},
    {27, 26, 6, 3},
    {27, 23, 9, 3},
    {27, 27, 5, 6},
    {27, 27, 7, 3},
    {27, 27, 10, 3},
    {27, 30, 9, 3},
    {27, 28, 6, 6},
    {27, 31, 8, 6},
    {30, 26, 8, 3},
    {30, 27, 9, 3},
    {30, 30, 5, 3},
    {30, 30, 10, 3},
    {30, 31, 6, 3},
    {30, 33, 8, 3},
    {24, 23, 6, 3},
    {24, 24, 5, 1},
    {24, 24, 7, 3},
    {24, 28, 9, 3},
    {24, 25, 6, 1},
    {24, 29, 8, 1},
    {28, 23, 8, 3},
    {28, 27, 6, 6},
    {28, 24, 9, 3},
    {28, 28, 5, 3},
    {28, 28, 7, 6},
    {28, 28, 10, 3},
    {28, 31, 9, 6},
    {28, 29, 6, 3},
    {28, 32, 8, 3},
    {31, 27, 8, 6},
    {31, 30, 6, 3},
    {31, 28, 9, 6},
    {31, 31, 5, 3},
    {31, 31, 7, 3},
    {31, 31, 10, 6},
    {31, 33, 9, 3},
    {31, 32, 6, 3},
    {31, 34, 8, 3},
    {33, 30, 8, 3},
    {33, 31, 9, 3},
    {33, 33, 5, 1},
    {33, 33, 10, 3},
    {33, 34, 6, 1},
    {33, 35, 8, 1},
    {25, 24, 6, 1},
    {25, 25, 7, 1},
    {25, 29, 9, 1},
    {29, 24, 8, 1},
    {29, 28, 6, 3},
    {29, 25, 9, 1},
    {29, 29, 7, 3},
    {29, 29, 10, 1},
    {29, 32, 9, 3},
    {32, 28, 8, 3},
    {32, 31, 6, 3},
    {32, 29, 9, 3},
    {32, 32, 7, 3},
    {32, 32, 10, 3},
    {32, 34, 9, 3},
    {34, 31, 8, 3},
    {34, 33, 6, 1},
    {34, 32, 9, 3},
    {34, 34, 7, 1},
    {34, 34, 10, 3},
    {34, 35, 9, 1},
    {35, 33, 8, 1},
    {35, 34, 9, 1},
    {35, 35, 10, 1},
    {21, 11, 11, 1},
    {21, 12, 12, 1},
    {21, 15, 15, 1},
    {22, 11, 12, 2},
    {22, 12, 11, 2},
    {22, 12, 13, 2},
    {22, 15, 16, 2},
    {22, 13, 12, 2},
    {22, 16, 15, 2},
    {26, 11, 15, 2},
    {26, 12, 16, 2},
    {26, 15, 11, 2},
    {26, 15, 18, 2},
    {26, 16, 12, 2},
    {26, 18, 15, 2},
    {23, 11, 13, 1},
    {23, 12, 12, 4},
    {23, 12, 14, 1},
    {23, 15, 17, 1},
    {23, 13, 11, 1},
    {23, 13, 13, 4},
    {23, 16, 16, 4},
    {23, 14, 12, 1},
    {23, 17, 15, 1},
    {27, 11, 16, 2},
    {27, 12, 15, 4},
    {27, 12, 17, 2},
    {27, 15, 12, 4},
    {27, 15, 19, 2},
    {27, 13, 16, 4},
    {27, 16, 11, 2},
    {27, 16, 13, 4},
    {27, 16, 18, 4},
    {27, 18, 16, 4},
    {27, 17, 12, 2},
    {27, 19, 15, 2},
    {30, 11, 18, 1},
    {30, 12, 19, 1},
    {30, 15, 15, 4},
    {30, 15, 20, 1},
    {30, 16, 16, 4},
    {30, 18, 11, 1},
    {30, 18, 18, 4},
    {30, 19, 12, 1},
    {30, 20, 15, 1},
    {24, 12, 13, 2},
    {24, 13, 12, 2},
    {24, 13, 14, 2},
    {24, 16, 17, 2},
    {24, 14, 13, 2},
    {24, 17, 16, 2},
    {28, 12, 16, 4},
    {28, 15, 13, 2},
    {28, 13, 15, 2},
    {28, 13, 17, 4},
    {28, 16, 12, 4},
    {28, 16, 14, 2},
    {28, 16, 19, 4},
    {28, 18, 17, 2},
    {28, 14, 16, 2},
    {28, 17, 13, 4},
    {28, 17, 18, 2},
    {28, 19, 16, 4},
    {31, 12, 18, 2},
    {31, 15, 16, 4},
    {31, 13, 19, 2},
    {31, 16, 15, 4},
    {31, 16, 17, 4},
    {31, 16, 20, 2},
    {31, 18, 12, 2},
    {31, 18, 19, 4},
    {31, 17, 16, 4},
    {31, 19, 13, 2},
    {31, 19, 18, 4},
    {31, 20, 16, 2},
    {33, 15, 18, 2},
    {33, 16, 19, 2},
    {33, 18, 15, 2},
    {33, 18, 20, 2},
    {33, 19, 16, 2},
    {33, 20, 18, 2},
    {25, 13, 13, 1},
    {25, 14, 14, 1},
    {25, 17, 17, 1},
    {29, 13, 16, 2},
    {29, 16, 13, 2},
    {29, 14, 17, 2},
    {29, 17, 14, 2},
    {29, 17, 19, 2},
    {29, 19, 17, 2},
    {32, 13, 18, 1},
    {32, 16, 16, 4},
    {32, 18, 13, 1},
    {32, 14, 19, 1},
    {32, 17, 17, 4},
    {32, 17, 20, 1},
    {32, 19, 14, 1},
    {32, 19, 19, 4},
    {32, 20, 17, 1},
    {34, 16, 18, 2},
    {34, 18, 16, 2},
    {34, 17, 19, 2},
    {34, 19, 17, 2},
    {34, 19, 20, 2},
    {34, 20, 19, 2},
    {35, 18, 18, 1},
    {35, 19, 19, 1},
    {35, 20, 20, 1},
    {57, 57, 1, 1},
    {58, 58, 1, 6},
    {64, 64, 1, 6},
    {59, 59, 1, 15},
    {65, 65, 1, 30},
    {70, 70, 1, 15},
    {60, 60, 1, 20},
    {66, 66, 1, 60},
    {71, 71, 1, 60},
    {75, 75, 1, 20},
    {61, 61, 1, 15},
    {67, 67, 1, 60},
    {72, 72, 1, 90},
    {76, 76, 1, 60},
    {79, 79, 1, 15},
    {62, 62, 1, 6},
    {68, 68, 1, 30},
    {73, 73, 1, 60},
    {77, 77, 1, 60},
    {80, 80, 1, 30},
    {82, 82, 1, 6},
    {63, 63, 1, 1},
    {69, 69, 1, 6},
    {74, 74, 1, 15},
    {78, 78, 1, 20},
    {81, 81, 1, 15},
    {83, 83, 1, 6},
    {84, 84, 1, 1},
    {57, 36, 2, 1},
    {58, 36, 3, 1},
    {58, 37, 2, 5},
    {64, 36, 4, 1},
    {64, 42, 2, 5},
    {59, 37, 3, 5},
    {59, 38, 2, 10},
    {65, 37, 4, 5},
    {65, 42, 3, 5},
    {65, 43, 2, 20},
    {70, 42, 4, 5},
    {70, 47, 2, 10},
    {60, 38, 3, 10},
    {60, 39, 2, 10},
    {66, 38, 4, 10},
    {66, 43, 3, 20},
    {66, 44, 2, 30},
    {71, 43, 4, 20},
    {71, 47, 3, 10},
    {71, 48, 2, 30},
    {75, 47, 4, 10},
    {75, 51, 2, 10},
    {61, 39, 3, 10},
    {61, 40, 2, 5},
    {67, 39, 4, 10},
    {67, 44, 3, 30},
    {67, 45, 2, 20},
    {72, 44, 4, 30},
    {72, 48, 3, 30},
    {72, 49, 2, 30},
    {76, 48, 4, 30},
    {76, 51, 3, 10},
    {76, 52, 2, 20},
    {79, 51, 4, 10},
    {79, 54, 2, 5},
    {62, 40, 3, 5},
    {62, 41, 2, 1},
    {68, 40, 4, 5},
    {68, 45, 3, 20},
    {68, 46, 2, 5},
    {73, 45, 4, 20},
    {73, 49, 3, 30},
    {73, 50, 2, 10},
    {77, 49, 4, 30},
    {77, 52, 3, 20},
    {77, 53, 2, 10},
    {80, 52, 4, 20},
    {80, 54, 3, 5},
    {80, 55, 2, 5},
    {82, 54, 4, 5},
    {82, 56, 2, 1},
    {63, 41, 3, 1},
    {69, 41, 4, 1},
    {69, 46, 3, 5},
    {74, 46, 4, 5},
    {74, 50, 3, 10},
    {78, 50, 4, 10},
    {78, 53, 3, 10},
    {81, 53, 4, 10},
    {81, 55, 3, 5},
    {83, 55, 4, 5},
    {83, 56, 3, 1},
    {84, 56, 4, 1},
    {57, 21, 5, 1},
    {58, 21, 6, 2},
    {58, 22, 5, 4},
    {64, 21, 8, 2},
    {64, 26, 5, 4},
    {59, 21, 7, 1},
    {59, 22, 6, 8},
    {59, 23, 5, 6},
    {65, 21, 9, 2},
    {65, 22, 8, 8},
    {65, 26, 6, 8},
    {65, 27, 5, 12},
    {70, 21, 10, 1},
    {70, 26, 8, 8},
    {70, 30, 5, 6},
    {60, 22, 7, 4},
    {60, 23, 6, 12},
    {60, 24, 5, 4},
    {66, 22, 9, 8},
    {66, 26, 7, 4},
    {66, 23, 8, 12},
    {66, 27, 6, 24},
    {66, 28, 5, 12},
    {71, 22, 10, 4},
    {71, 26, 9, 8},
    {71, 27, 8, 24},
    {71, 30, 6, 12},
    {71, 31, 5, 12},
    {75, 26, 10, 4},
    {75, 30, 8, 12},
    {75, 33, 5, 4},
    {61, 23, 7, 6},
    {61, 24, 6, 8},
    {61, 25, 5, 1},
    {67, 23, 9, 12},
    {67, 27, 7, 12},
    {67, 24, 8, 8},
    {67, 28, 6, 24},
    {67, 29, 5, 4},
    {72, 23, 10, 6},
    {72, 27, 9, 24},
    {72, 30, 7, 6},
    {72, 28, 8, 24},
    {72, 31, 6, 24},
    {72, 32, 5, 6},
    {76, 27, 10, 12},
    {76, 30, 9, 12},
    {76, 31, 8, 24},
    {76, 33, 6, 8},
    {76, 34, 5, 4},
    {79, 30, 10, 6},
    {79, 33, 8, 8},
    {79, 35, 5, 1},
    {62, 24, 7, 4},
    {62, 25, 6, 2},
    {68, 24, 9, 8},
    {68, 28, 7, 12},
    {68, 25, 8, 2},
    {68, 29, 6, 8},
    {73, 24, 10, 4},
    {73, 28, 9, 24},
    {73, 31, 7, 12},
    {73, 29, 8, 8},
    {73, 32, 6, 12},
    {77, 28, 10, 12},
    {77, 31, 9, 24},
    {77, 33, 7, 4},
    {77, 32, 8, 12},
    {77, 34, 6, 8},
    {80, 31, 10, 12},
    {80, 33, 9, 8},
    {80, 34, 8, 8},
    {80, 35, 6, 2},
    {82, 33, 10, 4},
    {82, 35, 8, 2},
    {63, 25, 7, 1},
    {69, 25, 9, 2},
    {69, 29, 7, 4},
    {74, 25, 10, 1},
    {74, 29, 9, 8},
    {74, 32, 7, 6},
    {78, 29, 10, 4},
    {78, 32, 9, 12},
    {78, 34, 7, 4},
    {81, 32, 10, 6},
    {81, 34, 9, 8},
    {81, 35, 7, 1},
    {83, 34, 10, 4},
    {83, 35, 9, 2},
    {84, 35, 10, 1},
    {36, 36, 5, 1},
    {36, 37, 6, 1},
    {36, 42, 8, 1},
    {37, 36, 6, 1},
    {37, 37, 5, 4},
    {37, 37, 7, 1},
    {37, 42, 9, 1},
    {37, 38, 6, 4},
    {37, 43, 8, 4},
    {42, 36, 8, 1},
    {42, 37, 9, 1},
    {42, 42, 5, 4},
    {42, 42, 10, 1},
    {42, 43, 6, 4},
    {42, 47, 8, 4},
    {38, 37, 6, 4},
    {38, 38, 5, 6},
    {38, 38, 7, 4},
    {38, 43, 9, 4},
    {38, 39, 6, 6},
    {38, 44, 8, 6},
    {43, 37, 8, 4},
    {43, 42, 6, 4},
    {43, 38, 9, 4},
    {43, 43, 5, 12},
    {43, 43, 7, 4},
    {43, 43, 10, 4},
    {43, 47, 9, 4},
    {43, 44, 6, 12},
    {43, 48, 8, 12},
    {47, 42, 8, 4},
    {47, 43, 9, 4},
    {47, 47, 5, 6},
    {47, 47, 10, 4},
    {47, 48, 6, 6},
    {47, 51, 8, 6},
    {39, 38, 6, 6},
    {39, 39, 5, 4},
    {39, 39, 7, 6},
    {39, 44, 9, 6},
    {39, 40, 6, 4},
    {39, 45, 8, 4},
    {44, 38, 8, 6},
    {44, 43, 6, 12},
    {44, 39, 9, 6},
    {44, 44, 5, 12},
    {44, 44, 7, 12},
    {44, 44, 10, 6},
    {44, 48, 9, 12},
    {44, 45, 6, 12},
    {44, 49, 8, 12},
    {48, 43, 8, 12},
    {48, 47, 6, 6},
    {48, 44, 9, 12},
    {48, 48, 5, 12},
    {48, 48, 7, 6},
    {48, 48, 10, 12},
    {48, 51, 9, 6},
    {48, 49, 6, 12},
    {48, 52, 8, 12},
    {51, 47, 8, 6},
    {51, 48, 9, 6},
    {51, 51, 5, 4},
    {51, 51, 10, 6},
    {51, 52, 6, 4},
    {51, 54, 8, 4},
    {40, 39, 6, 4},
    {40, 40, 5, 1},
    {40, 40, 7, 4},
    {40, 45, 9, 4},
    {40, 41, 6, 1},
    {40, 46, 8, 1},
    {45, 39, 8, 4},
    {45, 44, 6, 12},
    {45, 40, 9, 4},
    {45, 45, 5, 4},
    {45, 45, 7, 12},
    {45, 45, 10, 4},
    {45, 49, 9, 12},
    {45, 46, 6, 4},
    {45, 50, 8, 4},
    {49, 44, 8, 12},
    {49, 48, 6, 12},
    {49, 45, 9, 12},
    {49, 49, 5, 6},
    {49, 49, 7, 12},
    {49, 49, 10, 12},
    {49, 52, 9, 12},
    {49, 50, 6, 6},
    {49, 53, 8, 6},
    {52, 48, 8, 12},
    {52, 51, 6, 4},
    {52, 49, 9, 12},
    {52, 52, 5, 4},
    {52, 52, 7, 4},
    {52, 52, 10, 12},
    {52, 54, 9, 4},
    {52, 53, 6, 4},
    {52, 55, 8, 4},
    {54, 51, 8, 4},
    {54, 52, 9, 4},
    {54, 54, 5, 1},
    {54, 54, 10, 4},
    {54, 55, 6, 1},
    {54, 56, 8, 1},
    {41, 40, 6, 1},
    {41, 41, 7, 1},
    {41, 46, 9, 1},
    {46, 40, 8, 1},
    {46, 45, 6, 4},
    {46, 41, 9, 1},
    {46, 46, 7, 4},
    {46, 46, 10, 1},
    {46, 50, 9, 4},
    {50, 45, 8, 4},
    {50, 49, 6, 6},
    {50, 46, 9, 4},
    {50, 50, 7, 6},
    {50, 50, 10, 4},
    {50, 53, 9, 6},
    {53, 49, 8, 6},
    {53, 52, 6, 4},
    {53, 50, 9, 6},
    {53, 53, 7, 4},
    {53, 53, 10, 6},
    {53, 55, 9, 4},
    {55, 52, 8, 4},
    {55, 54, 6, 1},
    {55, 53, 9, 4},
    {55, 55, 7, 1},
    {55, 55, 10, 4},
    {55, 56, 9, 1},
    {56, 54, 8, 1},
    {56, 55, 9, 1},
    {56, 56, 10, 1},
    {57, 11, 11, 1},
    {58, 11, 12, 3},
    {58, 12, 11, 3},
    {64, 11, 15, 3},
    {64, 15, 11, 3},
    {59, 11, 13, 3},
    {59, 12, 12, 9},
    {59, 13, 11, 3},
    {65, 11, 16, 6},
    {65, 12, 15, 9},
    {65, 15, 12, 9},
    {65, 16, 11, 6},
    {70, 11, 18, 3},
    {70, 15, 15, 9},
    {70, 18, 11, 3},
    {60, 11, 14, 1},
    {60, 12, 13, 9},
    {60, 13, 12, 9},
    {60, 14, 11, 1},
    {66, 11, 17, 3},
    {66, 12, 16, 18},
    {66, 15, 13, 9},
    {66, 13, 15, 9},
    {66, 16, 12, 18},
    {66, 17, 11, 3},
    {71, 11, 19, 3},
    {71, 12, 18, 9},
    {71, 15, 16, 18},
    {71, 16, 15, 18},
    {71, 18, 12, 9},
    {71, 19, 11, 3},
    {75, 11, 20, 1},
    {75, 15, 18, 9},
    {75, 18, 15, 9},
    {75, 20, 11, 1},
    {61, 12, 14, 3},
    {61, 13, 13, 9},
    {61, 14, 12, 3},
    {67, 12, 17, 9},
    {67, 15, 14, 3},
    {67, 13, 16, 18},
    {67, 16, 13, 18},
    {67, 14, 15, 3},
    {67, 17, 12, 9},
    {72, 12, 19, 9},
    {72, 15, 17, 9},
    {72, 13, 18, 9},
    {72, 16, 16, 36},
    {72, 18, 13, 9},
    {72, 17, 15, 9},
    {72, 19, 12, 9},
    {76, 12, 20, 3},
    {76, 15, 19, 9},
    {76, 16, 18, 18},
    {76, 18, 16, 18},
    {76, 19, 15, 9},
    {76, 20, 12, 3},
    {79, 15, 20, 3},
    {79, 18, 18, 9},
    {79, 20, 15, 3},
    {62, 13, 14, 3},
    {62, 14, 13, 3},
    {68, 13, 17, 9},
    {68, 16, 14, 6},
    {68, 14, 16, 6},
    {68, 17, 13, 9},
    {73, 13, 19, 9},
    {73, 16, 17, 18},
    {73, 18, 14, 3},
    {73, 14, 18, 3},
    {73, 17, 16, 18},
    {73, 19, 13, 9},
    {77, 13, 20, 3},
    {77, 16, 19, 18},
    {77, 18, 17, 9},
    {77, 17, 18, 9},
    {77, 19, 16, 18},
    {77, 20, 13, 3},
    {80, 16, 20, 6},
    {80, 18, 19, 9},
    {80, 19, 18, 9},
    {80, 20, 16, 6},
    {82, 18, 20, 3},
    {82, 20, 18, 3},
    {63, 14, 14, 1},
    {69, 14, 17, 3},
    {69, 17, 14, 3},
    {74, 14, 19, 3},
    {74, 17, 17, 9},
    {74, 19, 14, 3},
    {78, 14, 20, 1},
    {78, 17, 19, 9},
    {78, 19, 17, 9},
    {78, 20, 14, 1},
    {81, 17, 20, 3},
    {81, 19, 19, 9},
    {81, 20, 17, 3},
    {83, 19, 20, 3},
    {83, 20, 19, 3},
    {84, 20, 20, 1},
    {36, 21, 11, 1},
    {36, 22, 12, 1},
    {36, 26, 15, 1},
    {37, 21, 12, 2},
    {37, 22, 11, 3},
    {37, 22, 13, 2},
    {37, 26, 16, 2},
    {37, 23, 12, 3},
    {37, 27, 15, 3},
    {42, 21, 15, 2},
    {42, 22, 16, 2},
    {42, 26, 11, 3},
    {42, 26, 18, 2},
    {42, 27, 12, 3},
    {42, 30, 15, 3},
    {38, 21, 13, 1},
    {38, 22, 12, 6},
    {38, 22, 14, 1},
    {38, 26, 17, 1},
    {38, 23, 11, 3},
    {38, 23, 13, 6},
    {38, 27, 16, 6},
    {38, 24, 12, 3},
    {38, 28, 15, 3},
    {43, 21, 16, 2},
    {43, 22, 15, 6},
    {43, 22, 17, 2},
    {43, 26, 12, 6},
    {43, 26, 19, 2},
    {43, 23, 16, 6},
    {43, 27, 11, 6},
    {43, 27, 13, 6},
    {43, 27, 18, 6},
    {43, 30, 16, 6},
    {43, 28, 12, 6},
    {43, 31, 15, 6},
    {47, 21, 18, 1},
    {47, 22, 19, 1},
    {47, 26, 15, 6},
    {47, 26, 20, 1},
    {47, 27, 16, 6},
    {47, 30, 11, 3},
    {47, 30, 18, 6},
    {47, 31, 12, 3},
    {47, 33, 15, 3},
    {39, 22, 13, 3},
    {39, 23, 12, 6},
    {39, 23, 14, 3},
    {39, 27, 17, 3},
    {39, 24, 11, 1},
    {39, 24, 13, 6},
    {39, 28, 16, 6},
    {39, 25, 12, 1},
    {39, 29, 15, 1},
    {44, 22, 16, 6},
    {44, 26, 13, 3},
    {44, 23, 15, 6},
    {44, 23, 17, 6},
    {44, 27, 12, 12},
    {44, 27, 14, 3},
    {44, 27, 19, 6},
    {44, 30, 17, 3},
    {44, 24, 16, 6},
    {44, 28, 11, 3},
    {44, 28, 13, 12},
    {44, 28, 18, 6},
    {44, 31, 16, 12},
    {44, 29, 12, 3},
    {44, 32, 15, 3},
    {48, 22, 18, 3},
    {48, 26, 16, 6},
    {48, 23, 19, 3},
    {48, 27, 15, 12},
    {48, 27, 17, 6},
    {48, 27, 20, 3},
    {48, 30, 12, 6},
    {48, 30, 19, 6},
    {48, 28, 16, 12},
    {48, 31, 11, 3},
    {48, 31, 13, 6},
    {48, 31, 18, 12},
    {48, 33, 16, 6},
    {48, 32, 12, 3},
    {48, 34, 15, 3},
    {51, 26, 18, 3},
    {51, 27, 19, 3},
    {51, 30, 15, 6},
    {51, 30, 20, 3},
    {51, 31, 16, 6},
    {51, 33, 11, 1},
    {51, 33, 18, 6},
    {51, 34, 12, 1},
    {51, 35, 15, 1},
    {40, 23, 13, 3},
    {40, 24, 12, 2},
    {40, 24, 14, 3},
    {40, 28, 17, 3},
    {40, 25, 13, 2},
    {40, 29, 16, 2},
    {45, 23, 16, 6},
    {45, 27, 13, 6},
    {45, 24, 15, 2},
    {45, 24, 17, 6},
    {45, 28, 12, 6},
    {45, 28, 14, 6},
    {45, 28, 19, 6},
    {45, 31, 17, 6},
    {45, 25, 16, 2},
    {45, 29, 13, 6},
    {45, 29, 18, 2},
    {45, 32, 16, 6},
    {49, 23, 18, 3},
    {49, 27, 16, 12},
    {49, 30, 13, 3},
    {49, 24, 19, 3},
    {49, 28, 15, 6},
    {49, 28, 17, 12},
    {49, 28, 20, 3},
    {49, 31, 12, 6},
    {49, 31, 14, 3},
    {49, 31, 19, 12},
    {49, 33, 17, 3},
    {49, 29, 16, 6},
    {49, 32, 13, 6},
    {49, 32, 18, 6},
    {49, 34, 16, 6},
    {52, 27, 18, 6},
    {52, 30, 16, 6},
    {52, 28, 19, 6},
    {52, 31, 15, 6},
    {52, 31, 17, 6},
    {52, 31, 20, 6},
    {52, 33, 12, 2},
    {52, 33, 19, 6},
    {52, 32, 16, 6},
    {52, 34, 13, 2},
    {52, 34, 18, 6},
    {52, 35, 16, 2},
    {54, 30, 18, 3},
    {54, 31, 19, 3},
    {54, 33, 15, 2},
    {54, 33, 20, 3},
    {54, 34, 16, 2},
    {54, 35, 18, 2},
    {41, 24, 13, 1},
    {41, 25, 14, 1},
    {41, 29, 17, 1},
    {46, 24, 16, 2},
    {46, 28, 13, 3},
    {46, 25, 17, 2},
    {46, 29, 14, 3},
    {46, 29, 19, 2},
    {46, 32, 17, 3},
    {50, 24, 18, 1},
    {50, 28, 16, 6},
    {50, 31, 13, 3},
    {50, 25, 19, 1},
    {50, 29, 17, 6},
    {50, 29, 20, 1},
    {50, 32, 14, 3},
    {50, 32, 19, 6},
    {50, 34, 17, 3},
    {53, 28, 18, 3},
    {53, 31, 16, 6},
    {53, 33, 13, 1},
    {53, 29, 19, 3},
    {53, 32, 17, 6},
    {53, 32, 20, 3},
    {53, 34, 14, 1},
    {53, 34, 19, 6},
    {53, 35, 17, 1},
    {55, 31, 18, 3},
    {55, 33, 16, 2},
    {55, 32, 19, 3},
    {55, 34, 17, 2},
    {55, 34, 20, 3},
    {55, 35, 19, 2},
    {56, 33, 18, 1},
    {56, 34, 19, 1},
    {56, 35, 20, 1},
    {21, 21, 21, 1},
    {21, 22, 22, 2},
    {21, 26, 26, 2},
    {21, 23, 23, 1},
    {21, 27, 27, 2},
    {21, 30, 30, 1},
    {22, 21, 22, 2},
    {22, 22, 21, 2},
    {22, 22, 23, 4},
    {22, 26, 27, 4},
    {22, 23, 22, 4},
    {22, 23, 24, 2},
    {22, 27, 26, 4},
    {22, 27, 28, 4},
    {22, 30, 31, 2},
    {22, 24, 23, 2},
    {22, 28, 27, 4},
    {22, 31, 30, 2},
    {26, 21, 26, 2},
    {26, 22, 27, 4},
    {26, 26, 21, 2},
    {26, 26, 30, 4},
    {26, 23, 28, 2},
    {26, 27, 22, 4},
    {26, 27, 31, 4},
    {26, 30, 26, 4},
    {26, 30, 33, 2},
    {26, 28, 23, 2},
    {26, 31, 27, 4},
    {26, 33, 30, 2},
    {23, 21, 23, 1},
    {23, 22, 22, 4},
    {23, 22, 24, 2},
    {23, 26, 28, 2},
    {23, 23, 21, 1},
    {23, 23, 23, 8},
    {23, 23, 25, 1},
    {23, 27, 27, 8},
    {23, 27, 29, 2},
    {23, 30, 32, 1},
    {23, 24, 22, 2},
    {23, 24, 24, 4},
    {23, 28, 26, 2},
    {23, 28, 28, 8},
    {23, 31, 31, 4},
    {23, 25, 23, 1},
    {23, 29, 27, 2},
    {23, 32, 30, 1},
    {27, 21, 27, 2},
    {27, 22, 26, 4},
    {27, 22, 28, 4},
    {27, 26, 22, 4},
    {27, 26, 31, 4},
    {27, 23, 27, 8},
    {27, 23, 29, 2},
    {27, 27, 21, 2},
    {27, 27, 23, 8},
    {27, 27, 30, 8},
    {27, 27, 32, 4},
    {27, 30, 27, 8},
    {27, 30, 34, 2},
    {27, 24, 28, 4},
    {27, 28, 22, 4},
    {27, 28, 24, 4},
    {27, 28, 31, 8},
    {27, 31, 26, 4},
    {27, 31, 28, 8},
    {27, 31, 33, 4},
    {27, 33, 31, 4},
    {27, 29, 23, 2},
    {27, 32, 27, 4},
    {27, 34, 30, 2},
    {30, 21, 30, 1},
    {30, 22, 31, 2},
    {30, 26, 26, 4},
    {30, 26, 33, 2},
    {30, 23, 32, 1},
    {30, 27, 27, 8},
    {30, 27, 34, 2},
    {30, 30, 21, 1},
    {30, 30, 30, 8},
    {30, 30, 35, 1},
    {30, 28, 28, 4},
    {30, 31, 22, 2},
    {30, 31, 31, 8},
    {30, 33, 26, 2},
    {30, 33, 33, 4},
    {30, 32, 23, 1},
    {30, 34, 27, 2},
    {30, 35, 30, 1},
    {24, 22, 23, 2},
    {24, 23, 22, 2},
    {24, 23, 24, 4},
    {24, 27, 28, 4},
    {24, 24, 23, 4},
    {24, 24, 25, 2},
    {24, 28, 27, 4},
    {24, 28, 29, 4},
    {24, 31, 32, 2},
    {24, 25, 24, 2},
    {24, 29, 28, 4},
    {24, 32, 31, 2},
    {28, 22, 27, 4},
    {28, 26, 23, 2},
    {28, 23, 26, 2},
    {28, 23, 28, 8},
    {28, 27, 22, 4},
    {28, 27, 24, 4},
    {28, 27, 31, 8},
    {28, 30, 28, 4},
    {28, 24, 27, 4},
    {28, 24, 29, 4},
    {28, 28, 23, 8},
    {28, 28, 30, 4},
    {28, 28, 25, 2},
    {28, 28, 32, 8},
    {28, 31, 27, 8},
    {28, 31, 29, 4},
    {28, 31, 34, 4},
    {28, 33, 32, 2},
    {28, 25, 28, 2},
    {28, 29, 24, 4},
    {28, 29, 31, 4},
    {28, 32, 28, 8},
    {28, 32, 33, 2},
    {28, 34, 31, 4},
    {31, 22, 30, 2},
    {31, 26, 27, 4},
    {31, 23, 31, 4},
    {31, 27, 26, 4},
    {31, 27, 28, 8},
    {31, 27, 33, 4},
    {31, 30, 22, 2},
    {31, 30, 31, 8},
    {31, 24, 32, 2},
    {31, 28, 27, 8},
    {31, 28, 29, 4},
    {31, 28, 34, 4},
    {31, 31, 23, 4},
    {31, 31, 30, 8},
    {31, 31, 32, 8},
    {31, 31, 35, 2},
    {31, 33, 27, 4},
    {31, 33, 34, 4},
    {31, 29, 28, 4},
    {31, 32, 24, 2},
    {31, 32, 31, 8},
    {31, 34, 28, 4},
    {31, 34, 33, 4},
    {31, 35, 31, 2},
    {33, 26, 30, 2},
    {33, 27, 31, 4},
    {33, 30, 26, 2},
    {33, 30, 33, 4},
    {33, 28, 32, 2},
    {33, 31, 27, 4},
    {33, 31, 34, 4},
    {33, 33, 30, 4},
    {33, 33, 35, 2},
    {33, 32, 28, 2},
    {33, 34, 31, 4},
    {33, 35, 33, 2},
    {25, 23, 23, 1},
    {25, 24, 24, 2},
    {25, 28, 28, 2},
    {25, 25, 25, 1},
    {25, 29, 29, 2},
    {25, 32, 32, 1},
    {29, 23, 27, 2},
    {29, 27, 23, 2},
    {29, 24, 28, 4},
    {29, 28, 24, 4},
    {29, 28, 31, 4},
    {29, 31, 28, 4},
    {29, 25, 29, 2},
    {29, 29, 25, 2},
    {29, 29, 32, 4},
    {29, 32, 29, 4},
    {29, 32, 34, 2},
    {29, 34, 32, 2},
    {32, 23, 30, 1},
    {32, 27, 27, 4},
    {32, 30, 23, 1},
    {32, 24, 31, 2},
    {32, 28, 28, 8},
    {32, 28, 33, 2},
    {32, 31, 24, 2},
    {32, 31, 31, 8},
    {32, 33, 28, 2},
    {32, 25, 32, 1},
    {32, 29, 29, 4},
    {32, 29, 34, 2},
    {32, 32, 25, 1},
    {32, 32, 32, 8},
    {32, 32, 35, 1},
    {32, 34, 29, 2},
    {32, 34, 34, 4},
    {32, 35, 32, 1},
    {34, 27, 30, 2},
    {34, 30, 27, 2},
    {34, 28, 31, 4},
    {34, 31, 28, 4},
    {34, 31, 33, 4},
    {34, 33, 31, 4},
    {34, 29, 32, 2},
    {34, 32, 29, 2},
    {34, 32, 34, 4},
    {34, 34, 32, 4},
    {34, 34, 35, 2},
    {34, 35, 34, 2},
    {35, 30, 30, 1},
    {35, 31, 31, 2},
    {35, 33, 33, 2},
    {35, 32, 32, 1},
    {35, 34, 34, 2},
    {35, 35, 35, 1}
  };

  int nb4 = nb[Pa];
  for (int i = 0; i<= nb4; i++)
    ns4[i] = ns[i];

  int nmax = ns4[nb4];
  for (int i=0; i<nmax; i++) {
    pb4[i]        = poly[i][0]-1;
    pb4[i+nmax]   = poly[i][1]-1;
    pb4[i+2*nmax] = poly[i][2]-1;
    pc4[i]        = poly[i][3];
  }
}
