Program Listing for File pairwise_potential_interface.hpp#

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

#ifndef PYGMIN_PAIRWISE_POTENTIAL_INTERFACE_H
#define PYGMIN_PAIRWISE_POTENTIAL_INTERFACE_H

#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <iterator>
#include <set>
#include <stdexcept>
#include <vector>

#include "array.hpp"
#include "base_potential.hpp"
#include "utils.hpp"
using namespace std;

namespace pele {

class NonAdditiveCutoffCalculator {
 public:
  NonAdditiveCutoffCalculator(double non_additivity, double cutoff_factor = 1.0)
      : _non_additivity(non_additivity), _cutoff_factor_(cutoff_factor) {}
  NonAdditiveCutoffCalculator() : _non_additivity(0) {}
  inline double get_cutoff(const double radius_i, const double radius_j) const {
    return (radius_i + radius_j) * _cutoff_factor_ *
           (1 - 2 * _non_additivity * abs(radius_i - radius_j));
  }
  inline double get_max_cutoff_distance(double max_radius) const {
    return 2 * max_radius * _cutoff_factor_;
  }

 private:
  double _non_additivity;
  double _cutoff_factor_;  // For the soft sphere potential defined in the paper
};

class PairwisePotentialInterface : public BasePotential {
 private:
  const bool m_radii_size_is_zero;

 protected:
  const Array<double> m_radii;
  const NonAdditiveCutoffCalculator m_cutoff_calculator;
  inline void initialize() {
    if (m_radii.size() == 0) {
      throw std::runtime_error(
          "PairwisePotentialInterface: illegal input: radii");
    }
    const auto [min_radius_address, max_radius_address] =
        std::minmax_element(m_radii.begin(), m_radii.end());
    size_t min_radius_index =
        std::distance(m_radii.begin(), min_radius_address);
    size_t max_radius_index =
        std::distance(m_radii.begin(), max_radius_address);

    double dij = get_non_additive_cutoff(min_radius_index, max_radius_index);

    if (dij < 0) {
      std::cout << "dij = " << dij << std::endl;
      std::cout << "min_radius = " << m_radii[min_radius_index] << std::endl;
      std::cout << "max_radius = " << m_radii[max_radius_index] << std::endl;
      throw std::runtime_error("cutoff(dij) is negative");
    }
  }

 public:
  PairwisePotentialInterface()
      : m_radii_size_is_zero(true), m_radii(0), m_cutoff_calculator() {}
  PairwisePotentialInterface(pele::Array<double> const &radii,
                             double non_additivity = 0)
      : m_radii_size_is_zero(radii.size() == 0),
        m_radii(radii.copy()),
        m_cutoff_calculator(non_additivity) {
    initialize();
  }
  PairwisePotentialInterface(pele::Array<double> const &radii,
                             NonAdditiveCutoffCalculator cutoff_calculator)
      : m_radii_size_is_zero(radii.size() == 0),
        m_radii(radii.copy()),
        m_cutoff_calculator(cutoff_calculator) {
    initialize();
  }

  virtual ~PairwisePotentialInterface() {}

  virtual pele::Array<double> get_radii() { return m_radii.copy(); }

  /*
   * Finds the interaction distance between two particles, i and j. defaults to
   * radii[i] + radii[j] when non_additivity is zero
   */
  inline double get_non_additive_cutoff(const std::size_t atom_i,
                                        const std::size_t atom_j) const {
    // TODO: this if statement actually takes a sizeable amount of time, for
    // something that is known at class instantiation.
    if (m_radii_size_is_zero) {
      return 0;
    } else {
      // uses the diameters being twice the radii
      return m_cutoff_calculator.get_cutoff(m_radii[atom_i], m_radii[atom_j]);
    }
  }

  inline double get_max_cutoff() const {
    if (m_radii_size_is_zero) {
      return 0;
    } else {
      // uses the diameters being twice the radii
      return m_cutoff_calculator.get_max_cutoff_distance(
          *std::max_element(m_radii.begin(), m_radii.end()));
    }
  }

  virtual inline size_t get_ndim() const {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_ndim must be overloaded");
  }

  virtual inline void get_rij(double *const, double const *const,
                              double const *const) const {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_rij must be overloaded");
  }

  virtual inline double get_interaction_energy_gradient(double, double *,
                                                        size_t, size_t) const {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_interaction_"
        "energy_gradient must be overloaded");
  }

  virtual inline double get_interaction_energy_gradient_hessian(
      double, double *, double *, size_t, size_t) const {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_interaction_"
        "energy_gradient_hessian must be overloaded");
  }

  virtual void get_neighbors_picky(
      pele::Array<double> const &, pele::Array<std::vector<size_t>> &,
      pele::Array<std::vector<std::vector<double>>> &,
      pele::Array<short> const &, const double cutoff_factor = 1.0) {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_neighbors_picky must be overloaded");
  }

  inline void print_neighbor_index_and_displacements(
      pele::Array<std::vector<size_t>> &neighbor_indss,
      pele::Array<std::vector<std::vector<double>>> &neighbor_distss) {
    for (size_t i = 0; i < neighbor_indss.size(); i++) {
      cout << "neighbor list for atom " << i << ": ";
      for (size_t j = 0; j < neighbor_indss[i].size(); j++) {
        cout << "neighbor " << neighbor_indss[i][j] << ": ";
        cout << endl;
        cout << "displacement: ";
        for (size_t k = 0; k < neighbor_distss[i][j].size(); k++) {
          cout << neighbor_distss[i][j][k] << " ";
        }
        cout << endl;
      }
    }
  }

  virtual void get_neighbors(pele::Array<double> const &,
                             pele::Array<std::vector<size_t>> &,
                             pele::Array<std::vector<std::vector<double>>> &,
                             const double cutoff_factor = 1) {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_neighbors must be overloaded");
  }

  virtual std::vector<size_t> get_overlaps(Array<double> const &coords) {
    throw std::runtime_error(
        "PairwisePotentialInterface::get_overlaps must be overloaded");
  }

  virtual pele::Array<size_t> get_atom_order(Array<double> &coords) {
    return pele::Array<size_t>(0);
  }

  bool find_rattlers(pele::Array<double> const &minimum_coords,
                     Array<uint8_t> not_rattlers, bool &jammed) {
    cout << "find rattlers started " << endl;
    not_rattlers.assign(1);
    size_t dim = get_ndim();

    // Not implemented for higher dimensions
    assert(dim == 2);
    size_t n_particles = minimum_coords.size() / dim;

    size_t zmin = dim + 1;

    // check if the radii /coordinate array sizes are consistent
    // with the dimensionality
    assert(n_particles == m_radii.size());

    // get neighbors of atoms
    pele::Array<std::vector<size_t>> neighbor_indss;
    pele::Array<std::vector<std::vector<double>>> neighbor_distss;
    get_neighbors(minimum_coords, neighbor_indss, neighbor_distss);

    size_t n_rattlers = 0;
    std::set<size_t> rattler_check_list = std::set<size_t>();
    for (size_t i = 0; i < n_particles; ++i) {
      rattler_check_list.insert(i);
    }
    std::set<size_t> current_check_list;

    bool found_rattler = false;
    size_t no_of_neighbors = 0;
    // allocation for the index of atom i in the neighbors of atom j
    size_t i_in_j;
    size_t i = 0;
    while (rattler_check_list.size() > 0) {
      i++;
      print_neighbor_index_and_displacements(neighbor_indss, neighbor_distss);
      current_check_list = rattler_check_list;
      rattler_check_list.clear();

      for (size_t atom_i : current_check_list) {
        found_rattler = false;
        no_of_neighbors = neighbor_indss[atom_i].size();
        if (no_of_neighbors < zmin) {
          found_rattler = true;
        } else {
          cout << "atom " << atom_i << " has " << no_of_neighbors
               << " neighbors" << endl;
          cout << "atom_i" << atom_i << " neighbor distsss size"
               << neighbor_distss[atom_i].size() << endl;
          found_rattler = origin_in_hull_2d(neighbor_distss[atom_i]);
        }
        if (found_rattler) {
          // atom i is a rattler
          not_rattlers[atom_i] = 0;
          n_rattlers++;

          // remove atom from the list of atoms to check if present in the check
          // list
          if (rattler_check_list.find(atom_i) != rattler_check_list.end()) {
            rattler_check_list.erase(atom_i);
          }

          for (size_t atom_j : neighbor_indss[atom_i]) {
            rattler_check_list.insert(atom_j);

            // find index of atom i in the neighbors of atom j
            i_in_j = 0;
            for (size_t k = 0; k < neighbor_indss[atom_j].size(); ++k) {
              if (neighbor_indss[atom_j][k] == atom_i) {
                i_in_j = k;
                break;
              }
            }

            // remove atom i from the list of neighbors of atom j
            neighbor_indss[atom_j].erase(neighbor_indss[atom_j].begin() +
                                         i_in_j);
          }
        }
      }
    }  // end while
    size_t n_stable_particles = n_particles - n_rattlers;

    size_t total_contacts = 0;
    for (size_t i = 0; i < n_particles; ++i) {
      total_contacts += neighbor_indss[i].size();
    }
    // print out not_rattlers
    cout << "not_rattlers: ";
    for (size_t i = 0; i < n_particles; ++i) {
      cout << int(not_rattlers[i]) << " ";
    }
    cout << endl;

    size_t minimum_jammed_contacts = 2 * ((n_stable_particles - 1) * dim + 1);

    if ((total_contacts < minimum_jammed_contacts) ||
        (n_stable_particles < dim)) {
      jammed = false;
    }
    jammed = true;
    cout << "find rattlers finished " << endl;
    return jammed;
  }
};

}  // namespace pele

#endif  // #ifndef PYGMIN_PAIRWISE_POTENTIAL_INTERFACE_H