mcpele  1.0.0
The Monte Carlo Python Energy Landscape Explorer
/home/sm958/Work/pele/source/pele/neighbor_iterator.h
00001 #ifndef PELE_NEIGHBOR_ITERATOR_H
00002 #define PELE_NEIGHBOR_ITERATOR_H
00003 
00004 #include <iostream>
00005 #include <memory>
00006 #include <exception>
00007 #include <cassert>
00008 #include <vector>
00009 
00010 #include "base_potential.h"
00011 #include "array.h"
00012 #include "distance.h"
00013 #include "vecn.h"
00014 
00015 namespace pele {
00016 
00017 template<class T, size_t box_dimension>
00018 struct periodic_policy_check_helper {
00019     const static bool is_periodic = false;
00020 };
00021 
00022 template<size_t box_dimension>
00023 struct periodic_policy_check_helper<pele::periodic_distance<box_dimension>, box_dimension > {
00024     const static bool is_periodic = true;
00025 };
00026 
00027 template<class T>
00028 struct periodic_policy_check {
00029     const static bool is_periodic = periodic_policy_check_helper<T, T::_ndim>::is_periodic;
00030 };
00031 
00037 class AtomInCellIterator {
00038 private:
00039     long const * const m_ll;
00040     long m_current_atom;
00041 public:
00042     AtomInCellIterator(long const * const ll, size_t first_atom)
00043         : m_ll(ll),
00044           m_current_atom(first_atom)
00045     {}
00046 
00047     inline long operator*() const
00048     {
00049         return m_current_atom;
00050     }
00051     inline void operator++()
00052     {
00053         m_current_atom = m_ll[m_current_atom];
00054     }
00055 };
00056 
00057 /*
00058  * cell list currently only work with box of equal side lengths
00059  * cell lists are currently not implemented for non cubic boxes:
00060  * in that case m_ncellx should be an array rather than a scalar and the definition
00061  * of ncells and rcell would change to array. This implies that in all the function these
00062  * would need to be replace with the correct array element. This adds room for errors
00063  * so in this first implementation we do not account for that scenario
00064  * */
00065 
00066 template<typename distance_policy = periodic_distance<3> >
00067 class CellIter{
00068 public:
00069     typedef std::vector<std::pair<size_t, size_t> > container_type;
00070     typedef typename container_type::const_iterator const_iterator;
00071 protected:
00072     static const size_t m_ndim = distance_policy::_ndim;
00073 
00074     std::shared_ptr<distance_policy> m_dist; // the distance function
00075     pele::Array<double> m_coords; // the coordinates array
00076     size_t m_natoms; // the number of atoms
00077     const double m_rcut; // the potential cutoff
00078     bool m_initialised; // flag for whether the class has been initialized
00079     const pele::Array<double> m_boxv; // the array of box lengths
00080     const size_t m_ncellx; // the number of cells in the x direction
00081     const size_t m_ncells; // the total number of cells
00082     const double m_rcell; // the size of a cell
00083 
00090     pele::Array<long int> m_hoc;
00091 
00098     pele::Array<long int> m_ll;
00099 
00100 //    std::vector<std::vector<size_t> > m_cell_neighbors; // keeps track of the neighbors of each cell
00101 
00103     std::vector<std::pair<size_t, size_t> > m_cell_neighbor_pairs;
00104 
00110     std::vector<std::pair<size_t, size_t> > m_atom_neighbor_list;
00111     const double m_xmin;
00112     const double m_xmax;
00113 public:
00114     ~CellIter() {}
00115 
00122     CellIter(
00123         std::shared_ptr<distance_policy> dist,
00124         pele::Array<double> const boxv, const double rcut,
00125         const double ncellx_scale=1.0);
00126 
00130     const_iterator begin() const { return m_atom_neighbor_list.begin(); }
00131     const_iterator end() const { return m_atom_neighbor_list.end(); }
00132 
00136     size_t get_nr_cells() const { return m_ncells; }
00137 
00141     size_t get_nr_cellsx() const { return m_ncellx; }
00142 
00148     size_t get_nr_unique_pairs() const { return m_atom_neighbor_list.size(); }
00149     size_t get_direct_nr_unique_pairs(const double max_distance, pele::Array<double> x) const;
00150     size_t get_maximum_nr_unique_pairs(pele::Array<double> x) const;
00151 
00155     void reset(pele::Array<double> coords);
00156 
00157 protected:
00158     void setup(Array<double> coords);
00159     void sanity_check();
00160     size_t atom2xbegin(const size_t atom_index) const { return m_ndim * atom_index; }
00161     template <class T> T loop_pow(const T x, int ex) const;
00162     size_t atom2cell(const size_t i);
00163     pele::Array<double> cell2coords(const size_t icell) const;
00164     bool cells_are_neighbors(const size_t icell, const size_t jcell) const;
00165     double get_minimum_corner_distance2(pele::Array<double> ic, pele::Array<double> jc) const;
00166     void build_cell_neighbors_list();
00167     void build_atom_neighbors_list();
00168     void build_linked_lists();
00169 };
00170 
00171 template<typename distance_policy>
00172 CellIter<distance_policy>::CellIter(
00173         std::shared_ptr<distance_policy> dist,
00174         pele::Array<double> const boxv, const double rcut,
00175         const double ncellx_scale)
00176     : m_dist(dist),
00177       m_natoms(0),
00178       m_rcut(rcut),
00179       m_initialised(false),
00180       m_boxv(boxv.copy()),
00181       m_ncellx(std::max<size_t>(1, (size_t)(ncellx_scale * m_boxv[0] / rcut))),     //no of cells in one dimension
00182       m_ncells(std::pow(m_ncellx, m_ndim)),                                                     //total no of cells
00183       m_rcell(m_boxv[0] / static_cast<double>(m_ncellx)),                                      //size of cell
00184       m_hoc(m_ncells),                                                                         //head of chain
00185       m_xmin(-0.5 * m_boxv[0]),
00186       m_xmax(0.5 * m_boxv[0])                                     
00187 {
00188     if (m_boxv.size() != m_ndim) {
00189         throw std::runtime_error("CellIter::CellIter: distance policy boxv and cell list boxv differ in size");
00190     }
00191     if (*std::min_element(m_boxv.data(), m_boxv.data() + m_ndim) < rcut) {
00192         throw std::runtime_error("CellIter::CellIter: illegal rcut");
00193     }
00194     const double boxv_epsilon = 1e-10;
00195     for (size_t i = 1; i < boxv.size(); ++i) {
00196         if (fabs(boxv[0] - boxv[i]) > boxv_epsilon) {
00197             throw std::runtime_error("CellIter::CellIter: illegal input boxv is not for square box");
00198         }
00199         if (boxv[i] < 0) {
00200             throw std::runtime_error("CellIter::CellIter: illegal input: boxvector");
00201         }
00202     }
00203     if (m_ncellx == 0) {
00204         throw std::runtime_error("CellIter::CellIter: illegal lattice spacing");
00205     }
00206     if (ncellx_scale < 0) {
00207         throw std::runtime_error("CellIter::CellIter: illegal input");
00208     }
00209 
00210 //    std::cout << "total number of cells " << m_ncells << std::endl;
00211 }
00212 
00213 template<typename distance_policy>
00214 size_t CellIter<distance_policy>::get_direct_nr_unique_pairs(const double max_distance, pele::Array<double> x) const
00215 {
00216     size_t nr_unique_pairs = 0;
00217     const size_t natoms = x.size() / m_ndim;
00218     for (size_t i = 0; i < natoms; ++i) {
00219         for (size_t j = i + 1; j < natoms; ++j) {
00220             double rij[m_ndim];
00221             const double* xi = x.data() + atom2xbegin(i);
00222             const double* xj = x.data() + atom2xbegin(j);
00223             m_dist->get_rij(rij, xi, xj);
00224             double r2 = 0;
00225             for (size_t k = 0; k < m_ndim; ++k) {
00226                 r2 += rij[k] * rij[k];
00227             }
00228             nr_unique_pairs += (r2 <= (max_distance * max_distance));
00229         }
00230     }
00231     return nr_unique_pairs;
00232 }
00233 
00234 template <typename distance_policy>
00235 size_t CellIter<distance_policy>::get_maximum_nr_unique_pairs(pele::Array<double> x) const
00236 {
00237     const size_t natoms = x.size() / m_ndim;
00238     return (natoms * (natoms - 1)) / 2;
00239 }
00240 
00241 template <typename distance_policy>
00242 void CellIter<distance_policy>::setup(Array<double> coords)
00243 {
00244     m_coords = coords.copy();
00245     m_natoms = coords.size() / m_ndim;
00246     m_ll = Array<long int>(m_natoms);
00247     if (coords.size() != m_ndim * m_natoms) {
00248         throw std::runtime_error("CellIter::setup: illegal coords.size() not divisible by m_ndim");
00249     }
00250 
00251     m_atom_neighbor_list.reserve(m_natoms * (m_natoms - 1) / 2);
00252     build_cell_neighbors_list();
00253     m_initialised = true;
00254 //    sanity_check();
00255 
00256     // print messages if any of the parameters seem bad
00257     if (m_ncellx < 5) {
00258         // If there are only a few cells in any direction then it doesn't make sense to use cell lists
00259         // because so many cells will be neighbors with each other.
00260         // It would be better to use simple loops over atom pairs.
00261         std::cout << "CellIter: efficiency warning: there are not many cells ("<<m_ncellx<<") in each direction.\n";
00262     }
00263     if (m_ncells > m_natoms) {
00264         // It would be more efficient (I think) to reduce the number of cells.
00265         std::cout << "CellIter: efficiency warning: the number of cells ("<<m_ncells<<")"<<
00266                 " is greater than the number of atoms ("<<m_natoms<<").\n";
00267     }
00268     if (m_rcut > 0.5 * m_boxv[0]) {
00269         // an atom can interact with more than just the nearest image of it's neighbor
00270         std::cerr << "CellIter: warning: rcut > half the box length.  This might cause errors with periodic boundaries.\n";
00271     }
00272 }
00273 
00274 template <typename distance_policy>
00275 void CellIter<distance_policy>::sanity_check()
00276 {
00277     const size_t nr_unique_pairs_lists = get_nr_unique_pairs();
00278     const size_t nr_unique_pairs_direct = get_direct_nr_unique_pairs(m_rcut, m_coords);
00279     const size_t maximum_nr_unique_pairs = get_maximum_nr_unique_pairs(m_coords);
00280     //std::cout << "nr_unique_pairs_lists: " << nr_unique_pairs_lists << "\n";
00281     //std::cout << "nr_unique_pairs_direct: " << nr_unique_pairs_direct << "\n";
00282     //std::cout << "maximum_nr_unique_pairs: " << maximum_nr_unique_pairs << "\n";
00283     if (nr_unique_pairs_lists < nr_unique_pairs_direct) {
00284         std::cout << "nr_unique_pairs_lists: " << nr_unique_pairs_lists << "\n";
00285         std::cout << "nr_unique_pairs_direct: " << nr_unique_pairs_direct << "\n";
00286         std::cout << "maximum_nr_unique_pairs: " << maximum_nr_unique_pairs << "\n";
00287         throw std::runtime_error("CellIter::setup: sanity check failed: too few pairs");
00288     }
00289     if (nr_unique_pairs_lists > maximum_nr_unique_pairs) {
00290         throw std::runtime_error("CellIter::setup: sanity check failed: too many pairs");
00291     }
00292 }
00293 
00304 template <typename distance_policy>
00305 void CellIter<distance_policy>::reset(pele::Array<double> coords)
00306 {
00307     if (! m_initialised) {
00308         setup(coords);
00309     }
00310 
00311     m_coords.assign(coords);
00312     if (periodic_policy_check<distance_policy>::is_periodic) {
00313         // distance policy is periodic: put particles "back in box" first
00314         periodic_distance<m_ndim>(m_boxv).put_in_box(m_coords);
00315     }
00316     else {
00317         // distance policy is not periodic: check that particles are inside box
00318         for (size_t i = 0; i < m_coords.size(); ++i) {
00319             if (m_coords[i] < -0.5 * m_boxv[0] || m_coords[i] > 0.5 * m_boxv[0]) {
00320                 std::cout << "m_coords[i]: " << m_coords[i] << "\n";
00321                 std::cout << "0.5 * m_boxv[0]: " << 0.5 * m_boxv[0] << std::endl;
00322                 throw std::runtime_error("CellIter::reset: coords are incompatible with boxvector");
00323             }
00324         }
00325     }
00326     build_linked_lists();
00327     build_atom_neighbors_list();
00328 }
00329 
00335 template <typename distance_policy>
00336 template <class T>
00337 T CellIter<distance_policy>::loop_pow(const T x, int ex) const
00338 {
00339     T result(1);
00340     while (--ex > -1) {
00341         result *= x;
00342     }
00343     return result;
00344 }
00345 
00351 template <typename distance_policy>
00352 size_t CellIter<distance_policy>::atom2cell(const size_t i)
00353 {
00354     assert(i < m_natoms);
00355     size_t icell = 0;
00356     for(size_t j = 0; j < m_ndim; ++j) {
00357         // j1 is the index for the coords array
00358         const size_t j1 = atom2xbegin(i) + j;
00359         assert(j1 < m_coords.size());
00360         double x = m_coords[j1];
00361         // min is needed in case x == m_rcell * m_ncellx
00362         const size_t icell_jpart = std::min<size_t>(m_ncellx - 1, static_cast<size_t>(((x - m_xmin) / (m_xmax - m_xmin)) * m_ncellx));
00363         assert(icell_jpart == icell_jpart);
00364         if (icell_jpart >= m_ncellx) {
00365             std::cout << "x: " << x << std::endl;
00366             std::cout << "m_rcell: " << m_rcell << std::endl;
00367             std::cout << "m_ndim: " << m_ndim << std::endl;
00368             std::cout << "m_ncellx: " << m_ncellx << std::endl;
00369             std::cout << "icell_jpart: " << icell_jpart << std::endl;
00370         }
00371         assert(icell_jpart < m_ncellx);
00372         //icell += icell_jpart * std::pow(m_ncellx, j);
00373         icell += icell_jpart * loop_pow(m_ncellx, j);
00374     }
00375     assert(icell < m_ncells);
00376     return icell;
00377 }
00378 
00384 template <typename distance_policy>
00385 pele::Array<double> CellIter<distance_policy>::cell2coords(const size_t icell) const
00386 {
00387     pele::Array<double> cellcorner(m_ndim); //coordinate of cell bottom left corner
00388     std::vector<double> indexes(m_ndim, 0); //this array will store indexes, set to 0
00389     double index = 0;
00390 
00391     //don't change these loops to size_t or the conditions will not hold
00392     for(int i = m_ndim - 1; i >= 0; --i) {
00393         index = icell;
00394         for (int j = m_ndim - 1; j >= i; --j) {
00395             //index -= indexes[j] * std::pow(m_ncellx, j);
00396             index -= indexes[j] * loop_pow(m_ncellx, j);
00397         }
00398         //indexes[i] = floor(index / std::pow(m_ncellx, i));
00399         indexes[i] = floor(index / loop_pow(m_ncellx, i));
00400         cellcorner[i] = m_rcell * indexes[i];
00401     }
00402 
00403     return cellcorner.copy();
00404 }
00405 
00412 template <typename distance_policy>
00413 bool CellIter<distance_policy>::cells_are_neighbors(const size_t icell, const size_t jcell) const
00414 {
00415     if (icell == jcell) {
00416         return true;
00417     }
00418     // Get "lower-left" corners.
00419     pele::Array<double> icell_coords = cell2coords(icell);
00420     pele::Array<double> jcell_coords = cell2coords(jcell);
00421     return get_minimum_corner_distance2(icell_coords, jcell_coords) <= m_rcut * m_rcut;
00422 }
00423 
00424 template <typename distance_policy>
00425 double CellIter<distance_policy>::get_minimum_corner_distance2(pele::Array<double> lower_left1, pele::Array<double> lower_left2) const
00426 {
00427     // copy them so we don't accidentally change them
00428     lower_left1 = lower_left1.copy();
00429     lower_left2 = lower_left2.copy();
00430     pele::VecN<m_ndim> ll1, ll2, dr;
00431     pele::VecN<m_ndim> minimum_distance; // the minimum possible distance in each direction
00432     for (size_t i = 0; i < m_ndim; ++i) {
00433         double min_dist = std::numeric_limits<double>::max();
00434         double dri;
00435         // find the minimum distance in the i'th direction.
00436         ll1 = lower_left1;
00437         ll2 = lower_left2;
00438         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00439         dri = std::abs(dr[i]);
00440         if (dri < min_dist) {
00441             min_dist = dri;
00442         }
00443 
00444         ll1 = lower_left1;
00445         ll2 = lower_left2;
00446         ll1[i] += m_rcell;
00447         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00448         dri = std::abs(dr[i]);
00449         if (dri < min_dist) {
00450             min_dist = dri;
00451         }
00452 
00453         ll1 = lower_left1;
00454         ll2 = lower_left2;
00455         ll2[i] += m_rcell;
00456         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00457         dri = std::abs(dr[i]);
00458         if (dri < min_dist) {
00459             min_dist = dri;
00460         }
00461 
00462         ll1 = lower_left1;
00463         ll2 = lower_left2;
00464         ll1[i] += m_rcell;
00465         ll2[i] += m_rcell;
00466         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00467         dri = std::abs(dr[i]);
00468         if (dri < min_dist) {
00469             min_dist = dri;
00470         }
00471 
00472         minimum_distance[i] = min_dist;
00473     }
00474     double r2_min = dot<m_ndim> (minimum_distance, minimum_distance);
00475     return r2_min;
00476 }
00477 
00481 template <typename distance_policy>
00482 void CellIter<distance_policy>::build_cell_neighbors_list()
00483 {
00484     size_t max_n_neibs = 0;
00485     m_cell_neighbor_pairs.reserve(2 * m_ncells); // A lower end guess for the size
00486     for(size_t i = 0; i < m_ncells; ++i) {
00487         size_t nneibs = -0;
00488         for(size_t j = 0; j <= i; ++j) {
00489             if (cells_are_neighbors(i, j)) { //includes itself as a neighbor
00490                 m_cell_neighbor_pairs.push_back(std::pair<size_t, size_t>(i, j));
00491                 ++nneibs;
00492             }
00493         }
00494         if (nneibs > max_n_neibs) max_n_neibs = nneibs;
00495     }
00496     if (max_n_neibs > 0.5 * m_ncells) {
00497         // If each cell has many neighbors it would be better to just use a simple loop over atom pairs.
00498         // Alternatively you might think abour reducing rcut.
00499         std::cout << "CellIter: efficiency warning: the cells have very many neighbors ("
00500                 <<max_n_neibs << ", with "<<m_ncells<<" cells total).\n";
00501     }
00502 
00503 }
00504 
00508 template <typename distance_policy>
00509 void CellIter<distance_policy>::build_atom_neighbors_list()
00510 {
00511     m_atom_neighbor_list.clear();
00512     for (auto const & ijpair : m_cell_neighbor_pairs) {
00513         const size_t icell = ijpair.first;
00514         const size_t jcell = ijpair.second;
00515         if (icell == jcell) {
00516             // do double loop through atoms, avoiding duplicate pairs
00517             for (auto iiter = AtomInCellIterator(m_ll.data(), m_hoc[icell]); *iiter >= 0; ++iiter) {
00518                 size_t const atomi = *iiter;
00519                 for (auto jiter = AtomInCellIterator(m_ll.data(), m_hoc[icell]); *jiter != *iiter; ++jiter) {
00520                     size_t const atomj = *jiter;
00521                     m_atom_neighbor_list.push_back(std::pair<size_t, size_t>(atomi, atomj));
00522                 }
00523             }
00524         } else {
00525             // do double loop through atoms in each cell
00526             for (auto iiter = AtomInCellIterator(m_ll.data(), m_hoc[icell]); *iiter >= 0; ++iiter) {
00527                 size_t const atomi = *iiter;
00528                 for (auto jiter = AtomInCellIterator(m_ll.data(), m_hoc[jcell]); *jiter >= 0; ++jiter) {
00529                     size_t const atomj = *jiter;
00530                     m_atom_neighbor_list.push_back(std::pair<size_t, size_t>(atomi, atomj));
00531                 }
00532             }
00533         }
00534     }
00535 }
00536 
00540 template <typename distance_policy>
00541 void CellIter<distance_policy>::build_linked_lists()
00542 {
00543     m_hoc.assign(-1); //set head of chains to -1 (empty state)
00544     for(size_t i = 0; i < m_natoms; ++i) {
00545         size_t icell = atom2cell(i);
00546         m_ll[i] = m_hoc[icell];
00547         m_hoc[icell] = i;
00548     }
00549 }
00550 
00551 } // namespace pele
00552 
00553 #endif // #ifndef PELE_NEIGHBOR_ITERATOR_H
 All Classes Namespaces Functions Variables Typedefs