Program Listing for File hs_wca.hpp#

↰ Return to documentation for file (source/pele/hs_wca.hpp)

#ifndef _PELE_HS_WCA_H
#define _PELE_HS_WCA_H

#include <algorithm>
#include <memory>

#include "atomlist_potential.hpp"
#include "base_interaction.hpp"
#include "cell_list_potential.hpp"
#include "distance.hpp"
#include "meta_pow.hpp"
#include "simple_pairwise_ilist.hpp"
#include "simple_pairwise_potential.hpp"

namespace pele {

template <size_t exp = 6>
class sf_HS_WCA_interaction : BaseInteraction {
  const double m_eps;
  const double m_alpha1_2;
  const double m_delta;
  const double m_prfac;
  constexpr static double m_grad_prfac = 4 * 2 * exp;
  constexpr static double m_grad_prfac2 = 4 * 2 * 2 * exp;
  constexpr static double m_hessian_prfac = 2 * (exp + 1) * m_grad_prfac;
  constexpr static double m_hessian_prfac2 = 2 * (2 * exp + 1) * m_grad_prfac2;

 public:
  sf_HS_WCA_interaction(const double eps, const double alpha,
                        const double delta = 1e-10);
  double energy(const double r2, const double dij) const;
  double energy_gradient(const double r2, double *const gij,
                         const double dij) const;
  double energy_gradient_hessian(const double r2, double *const gij,
                                 double *const hij, const double dij) const;
  void evaluate_pair_potential(const double rmin, const double rmax,
                               const size_t nr_points, const double dij,
                               std::vector<double> &x,
                               std::vector<double> &y) const;
};

template <size_t exp>
sf_HS_WCA_interaction<exp>::sf_HS_WCA_interaction(const double eps,
                                                  const double alpha,
                                                  const double delta)
    : m_eps(eps),
      m_alpha1_2(pos_int_pow<2>(alpha + 1)),
      m_delta(delta),
      m_prfac(0.5 * pos_int_pow<exp>((2 + alpha) * alpha)) {
  if (eps < 0) {
    throw std::runtime_error("HS_WCA: illegal input: eps");
  }
  if (alpha < 0) {
    throw std::runtime_error("HS_WCA: illegal input: alpha");
  }
}

template <size_t exp>
double sf_HS_WCA_interaction<exp>::energy(const double r2,
                                          const double dij) const {
  const double r_H2 = pos_int_pow<2>(dij);
  const double r_S2 = m_alpha1_2 * r_H2;
  if (r2 > r_S2) {
    // Energy: separation larger than soft shell.
    return 0;
  }
  // r2 <= r_S2: we have to compute the remaining quantities.
  const double r_X = dij + m_delta;
  const double r_X2 = pos_int_pow<2>(r_X);
  if (__builtin_expect(r2 > r_X2, 1)) {
    // Energy: separation in fHS-WCA regime.
    const double dr = r2 - r_H2;
    const double ir = 1.0 / dr;
    const double r_H2_ir = r_H2 * ir;
    const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
    const double C_ir_2m = C_ir_m * C_ir_m;
    return std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  }
  // r2 <= r_X2
  // Energy: separation in linear regime.
  const double dr = r_X2 - r_H2;
  const double ir = 1.0 / dr;
  const double r_H2_ir = r_H2 * ir;
  const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
  const double C_ir_2m = C_ir_m * C_ir_m;
  const double EX =
      std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  const double GX =
      (m_eps * (m_grad_prfac2 * C_ir_2m - m_grad_prfac * C_ir_m) * ir) * (-r_X);
  return EX + GX * (std::sqrt(r2) - r_X);
}

template <size_t exp>
double sf_HS_WCA_interaction<exp>::energy_gradient(const double r2,
                                                   double *const gij,
                                                   const double dij) const {
  const double r_H2 = pos_int_pow<2>(dij);
  const double r_S2 = m_alpha1_2 * r_H2;
  if (r2 > r_S2) {
    // Energy, gradient: separation larger than soft shell.
    *gij = 0;
    return 0;
  }
  // r2 <= r_S2: we have to compute the remaining quantities.
  const double r_X = dij + m_delta;
  const double r_X2 = pos_int_pow<2>(r_X);
  if (__builtin_expect(r2 > r_X2, 1)) {
    // Energy, gradient: separation in fHS-WCA regime.
    const double dr = r2 - r_H2;
    const double ir = 1.0 / dr;
    const double r_H2_ir = r_H2 * ir;
    const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
    const double C_ir_2m = C_ir_m * C_ir_m;
    *gij = m_eps * (m_grad_prfac2 * C_ir_2m - m_grad_prfac * C_ir_m) * ir;
    return std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  }
  // r2 <= r_X2
  // Energy, gradient: separation in linear regime.
  const double dr = r_X2 - r_H2;
  const double ir = 1.0 / dr;
  const double r_H2_ir = r_H2 * ir;
  const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
  const double C_ir_2m = C_ir_m * C_ir_m;
  const double EX =
      std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  *gij = m_eps * (m_grad_prfac2 * C_ir_2m - m_grad_prfac * C_ir_m) * ir;
  const double GX = *gij * (-r_X);
  return EX + GX * (std::sqrt(r2) - r_X);
}

template <size_t exp>
double sf_HS_WCA_interaction<exp>::energy_gradient_hessian(
    const double r2, double *const gij, double *const hij,
    const double dij) const {
  const double r_H2 = pos_int_pow<2>(dij);
  const double r_S2 = m_alpha1_2 * r_H2;
  if (r2 > r_S2) {
    // Energy, gradient, hessian: separation larger than soft shell.
    *gij = 0;
    *hij = 0;
    return 0;
  }
  // r2 <= r_S2: we have to compute the remaining quantities.
  const double r_X = dij + m_delta;
  const double r_X2 = pos_int_pow<2>(r_X);
  if (__builtin_expect(r2 > r_X2, 1)) {
    // Energy, gradient, hessian: separation in fHS-WCA regime.
    const double dr = r2 - r_H2;
    const double ir = 1.0 / dr;
    const double r_H2_ir = r_H2 * ir;
    const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
    const double C_ir_2m = C_ir_m * C_ir_m;
    *gij = m_eps * (m_grad_prfac2 * C_ir_2m - m_grad_prfac * C_ir_m) * ir;
    *hij = -*gij + m_eps *
                       (m_hessian_prfac2 * C_ir_2m - m_hessian_prfac * C_ir_m) *
                       r2 * ir * ir;
    return std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  }
  // r2 <= r_X2
  // Energy, gradient, hessian: separation in linear regime.
  const double dr = r_X2 - r_H2;
  const double ir = 1.0 / dr;
  const double r_H2_ir = r_H2 * ir;
  const double C_ir_m = m_prfac * pos_int_pow<exp>(r_H2_ir);
  const double C_ir_2m = C_ir_m * C_ir_m;
  const double EX =
      std::max<double>(0, 4. * m_eps * (C_ir_2m - C_ir_m) + m_eps);
  *gij = m_eps * (m_grad_prfac2 * C_ir_2m - m_grad_prfac * C_ir_m) * ir;
  const double GX = *gij * (-r_X);
  *hij = 0;
  return EX + GX * (std::sqrt(r2) - r_X);
}

template <size_t exp>
void sf_HS_WCA_interaction<exp>::evaluate_pair_potential(
    const double rmin, const double rmax, const size_t nr_points,
    const double dij, std::vector<double> &x, std::vector<double> &y) const {
  x = std::vector<double>(nr_points, 0);
  y = std::vector<double>(nr_points, 0);
  const double rdelta = (rmax - rmin) / (nr_points - 1);
  for (size_t i = 0; i < nr_points; ++i) {
    x.at(i) = rmin + i * rdelta;
    y.at(i) = energy(x.at(i) * x.at(i), dij);
  }
}

struct HS_WCA_interaction : BaseInteraction {
  const double _eps;
  const double _sca;
  const double _infty;
  const double _prfac;

  HS_WCA_interaction(const double eps, const double sca)
      : _eps(eps),
        _sca(sca),
        _infty(std::pow(10.0, 50)),
        _prfac(0.5 * pos_int_pow<6>((2 + _sca) * _sca)) {}

  /* calculate energy from distance squared, r0 is the hard core distance, r is
   * the distance between the centres */
  double inline energy(const double r2, const double dij) const {
    const double r02 = dij * dij;
    if (r2 <= r02) {
      return _infty;
    }
    const double coff =
        dij * (1.0 + _sca);  // distance at which the soft cores are at contact
    if (r2 > coff * coff) {
      return 0;
    }
    const double dr =
        r2 - r02;  // note that dr is the difference of the squares
    const double ir = 1.0 / dr;
    const double r02_ir = r02 * ir;
    const double C_ir_6 = _prfac * pos_int_pow<6>(r02_ir);
    const double C_ir_12 = C_ir_6 * C_ir_6;
    return compute_energy(C_ir_6, C_ir_12);
  }

  /* calculate energy and gradient from distance squared, gradient is in
   * g/|rij|, dij is the hard core distance, r is the distance between
   * the centres */
  double inline energy_gradient(const double r2, double *const gij,
                                const double dij) const {
    const double r02 = dij * dij;
    if (r2 <= r02) {
      *gij = _infty;
      return _infty;
    }
    const double coff =
        dij * (1.0 + _sca);  // distance at which the soft cores are at contact
    if (r2 > coff * coff) {
      *gij = 0.;
      return 0.;
    }
    const double dr =
        r2 - r02;  // note that dr is the difference of the squares
    const double ir = 1.0 / dr;
    const double r02_ir = r02 * ir;
    const double C_ir_6 = _prfac * pos_int_pow<6>(r02_ir);
    const double C_ir_12 = C_ir_6 * C_ir_6;
    *gij = _eps * (-48. * C_ir_6 + 96. * C_ir_12) *
           ir;  // this is -g/r, 1/dr because powers must be 7 and 13
    return compute_energy(C_ir_6, C_ir_12);
  }

  double inline energy_gradient_hessian(const double r2, double *const gij,
                                        double *const hij,
                                        const double dij) const {
    const double r02 = dij * dij;
    if (r2 <= r02) {
      *gij = _infty;
      *hij = _infty;
      return _infty;
    }
    const double coff =
        dij * (1.0 + _sca);  // distance at which the soft cores are at contact
    if (r2 > coff * coff) {
      *gij = 0.;
      *hij = 0.;
      return 0.;
    }
    const double dr =
        r2 - r02;  // note that dr is the difference of the squares
    const double ir = 1.0 / dr;
    const double r02_ir = r02 * ir;
    const double C_ir_6 = _prfac * pos_int_pow<6>(r02_ir);
    const double C_ir_12 = C_ir_6 * C_ir_6;
    *gij = _eps * (-48. * C_ir_6 + 96. * C_ir_12) *
           ir;  // this is -g/r, 1/dr because powers must be 7 and 13
    *hij = -*gij + _eps * (-672. * C_ir_6 + 2496. * C_ir_12) * r2 * ir * ir;
    return compute_energy(C_ir_6, C_ir_12);
  }

  double compute_energy(const double C_ir_6, const double C_ir_12) const {
    return std::min<double>(
        _infty, std::max<double>(0, 4. * _eps * (-C_ir_6 + C_ir_12) + _eps));
  }

  void evaluate_pair_potential(const double rmin, const double rmax,
                               const size_t nr_points, const double dij,
                               std::vector<double> &x,
                               std::vector<double> &y) const {
    x = std::vector<double>(nr_points, 0);
    y = std::vector<double>(nr_points, 0);
    const double rdelta = (rmax - rmin) / (nr_points - 1);
    for (size_t i = 0; i < nr_points; ++i) {
      x.at(i) = rmin + i * rdelta;
      y.at(i) = energy(x.at(i) * x.at(i), dij);
    }
  }
};

//
// combine the components (interaction, looping method, distance function) into
// defined classes
//

template <size_t ndim, size_t exp = 6>
class HS_WCA : public SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                              cartesian_distance<ndim>> {
 public:
  HS_WCA(double eps, double sca, Array<double> radii)
      : SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                cartesian_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca), radii,
            std::make_shared<cartesian_distance<ndim>>(), sca) {}
};

template <size_t ndim, size_t exp = 6>
class HS_WCAPeriodic
    : public SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                     periodic_distance<ndim>> {
 public:
  HS_WCAPeriodic(double eps, double sca, Array<double> radii,
                 Array<double> const boxvec)
      : SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                periodic_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca), radii,
            std::make_shared<periodic_distance<ndim>>(boxvec), sca) {}
};

template <size_t ndim, size_t exp = 6>
class HS_WCALeesEdwards
    : public SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                     leesedwards_distance<ndim>> {
 public:
  HS_WCALeesEdwards(double eps, double sca, Array<double> radii,
                    Array<double> const boxvec, const double shear)
      : SimplePairwisePotential<sf_HS_WCA_interaction<exp>,
                                leesedwards_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca), radii,
            std::make_shared<leesedwards_distance<ndim>>(boxvec, shear), sca) {}
};

template <size_t ndim, size_t exp = 6>
class HS_WCACellLists : public CellListPotential<sf_HS_WCA_interaction<exp>,
                                                 cartesian_distance<ndim>> {
 public:
  HS_WCACellLists(double eps, double sca, Array<double> radii,
                  Array<double> const boxvec, const double ncellx_scale = 1.0,
                  const bool balance_omp = true)
      : CellListPotential<sf_HS_WCA_interaction<exp>, cartesian_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca),
            std::make_shared<cartesian_distance<ndim>>(), boxvec,
            2 * (1 + sca) *
                *std::max_element(radii.begin(), radii.end()),  // rcut
            ncellx_scale, radii, sca, balance_omp) {
    if (boxvec.size() != ndim) {
      throw std::runtime_error("HS_WCA: illegal input: boxvec");
    }
  }
  size_t get_nr_unique_pairs() const {
    return CellListPotential<sf_HS_WCA_interaction<exp>,
                             cartesian_distance<ndim>>::m_celliter
        ->get_nr_unique_pairs();
  }
};

template <size_t ndim, size_t exp = 6>
class HS_WCAPeriodicCellLists
    : public CellListPotential<sf_HS_WCA_interaction<exp>,
                               periodic_distance<ndim>> {
 public:
  HS_WCAPeriodicCellLists(double eps, double sca, Array<double> radii,
                          Array<double> const boxvec,
                          const double ncellx_scale = 1.0,
                          const bool balance_omp = true)
      : CellListPotential<sf_HS_WCA_interaction<exp>, periodic_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca),
            std::make_shared<periodic_distance<ndim>>(boxvec), boxvec,
            2 * (1 + sca) *
                *std::max_element(radii.begin(), radii.end()),  // rcut
            ncellx_scale, radii, sca, balance_omp) {}
  size_t get_nr_unique_pairs() const {
    return CellListPotential<sf_HS_WCA_interaction<exp>,
                             periodic_distance<ndim>>::m_celliter
        ->get_nr_unique_pairs();
  }
};

template <size_t ndim, size_t exp = 6>
class HS_WCALeesEdwardsCellLists
    : public CellListPotential<sf_HS_WCA_interaction<exp>,
                               leesedwards_distance<ndim>> {
 public:
  HS_WCALeesEdwardsCellLists(double eps, double sca, Array<double> radii,
                             Array<double> const boxvec, const double shear,
                             const double ncellx_scale = 1.0,
                             const bool balance_omp = true)
      : CellListPotential<sf_HS_WCA_interaction<exp>,
                          leesedwards_distance<ndim>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca),
            std::make_shared<leesedwards_distance<ndim>>(boxvec, shear), boxvec,
            2 * (1 + sca) *
                *std::max_element(radii.begin(), radii.end()),  // rcut
            ncellx_scale, radii, sca, balance_omp) {}
  size_t get_nr_unique_pairs() const {
    return CellListPotential<sf_HS_WCA_interaction<exp>,
                             leesedwards_distance<ndim>>::m_celliter
        ->get_nr_unique_pairs();
  }
};

template <size_t exp = 6>
class HS_WCANeighborList
    : public SimplePairwiseNeighborList<sf_HS_WCA_interaction<exp>> {
 public:
  HS_WCANeighborList(Array<size_t> &ilist, double eps, double sca,
                     Array<double> radii)
      : SimplePairwiseNeighborList<sf_HS_WCA_interaction<exp>>(
            std::make_shared<sf_HS_WCA_interaction<exp>>(eps, sca), ilist,
            radii) {}
};

}  // namespace pele
#endif  //#ifndef _PELE_HS_WCA_H