pele
Python energy landscape explorer
/home/js850/projects/pele/source/pele/cell_lists.h
00001 #ifndef _PELE_CELL_LISTS_H_
00002 #define _PELE_CELL_LISTS_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 {
00016 static const long CELL_END = -1;
00017 
00018 template<class T, size_t box_dimension>
00019 struct periodic_policy_check_helper {
00020     const static bool is_periodic = false;
00021 };
00022 
00023 template<size_t box_dimension>
00024 struct periodic_policy_check_helper<pele::periodic_distance<box_dimension>, box_dimension > {
00025     const static bool is_periodic = true;
00026 };
00027 
00028 template<class T>
00029 struct periodic_policy_check {
00030     const static bool is_periodic = periodic_policy_check_helper<T, T::_ndim>::is_periodic;
00031 };
00032 
00033 
00039 class AtomInCellIterator {
00040 private:
00041     long const * const m_ll;
00042     long m_current_atom;
00043     long const m_end;
00044 public:
00045     AtomInCellIterator(long const * const ll, size_t first_atom, long end=CELL_END)
00046         : m_ll(ll),
00047           m_current_atom(first_atom),
00048           m_end(end)
00049     {}
00050     AtomInCellIterator()
00051         : m_ll(NULL),
00052           m_current_atom(CELL_END),
00053           m_end(CELL_END)
00054     {}
00055 
00056     inline long operator*() const
00057     {
00058         return m_current_atom;
00059     }
00060 
00067     inline void operator++()
00068     {
00069         m_current_atom = m_ll[m_current_atom];
00070     }
00071 
00072     inline bool done() const
00073     {
00074         // If this were a real iterator, you would test if it's
00075         // done by comparing to some end() iterator.  I havn't quite
00076         // figured out how to do that in fast way.
00077         return m_current_atom == m_end;
00078     }
00079 };
00080 
00081 
00085 class CellListsContainer {
00086 public:
00093     pele::Array<long> m_ll;
00094 
00101     pele::Array<long> m_hoc;
00102 
00104     std::vector<std::pair<size_t, size_t> > m_cell_neighbor_pairs;
00105 
00106     CellListsContainer(size_t ncells)
00107         : m_hoc(ncells, CELL_END)
00108     {}
00109 
00113     void clear()
00114     {
00115         m_hoc.assign(CELL_END);
00116     }
00117 
00121     inline void add_atom_to_cell(size_t iatom, size_t icell)
00122     {
00123         m_ll[iatom] = m_hoc[icell];
00124         m_hoc[icell] = iatom;
00125     }
00126 
00130     inline void set_natoms(size_t natoms)
00131     {
00132         m_ll = pele::Array<long>(natoms);
00133     }
00134 };
00135 
00136 } // end anonymous namespace
00137 
00138 namespace pele {
00139 
00153 template <class visitor_t>
00154 class CellListsLoop {
00155 protected:
00156     visitor_t & m_visitor;
00157     pele::Array<long> const m_ll;
00158     pele::Array<long> const m_hoc;
00159     std::vector<std::pair<size_t, size_t> > const & m_cell_neighbor_pairs;
00160 
00161 public:
00162     CellListsLoop(visitor_t & visitor, CellListsContainer const & container)
00163         : m_visitor(visitor),
00164           m_ll(container.m_ll),
00165           m_hoc(container.m_hoc),
00166           m_cell_neighbor_pairs(container.m_cell_neighbor_pairs)
00167     {}
00168 
00169 
00170     void loop_through_atom_pairs()
00171     {
00172         for (auto const & ijpair : m_cell_neighbor_pairs) {
00173             const size_t icell = ijpair.first;
00174             const size_t jcell = ijpair.second;
00175             // do double loop through atoms, avoiding duplicate pairs
00176             for (auto iiter = AtomInCellIterator(m_ll.data(), m_hoc[icell]); !iiter.done(); ++iiter) {
00177                 size_t const atomi = *iiter;
00178                 // if icell==jcell we need to avoid duplicate atom pairs
00179                 long const loop_end = (icell == jcell) ? atomi : CELL_END;
00180                 for (auto jiter = AtomInCellIterator(m_ll.data(), m_hoc[jcell], loop_end); !jiter.done(); ++jiter) {
00181                     size_t const atomj = *jiter;
00182                     m_visitor.insert_atom_pair(atomi, atomj);
00183                 }
00184             }
00185         }
00186 
00187     }
00188 
00189 };
00190 
00191 
00200 template<typename distance_policy = periodic_distance<3> >
00201 class CellLists{
00202 public:
00203     typedef CellListsContainer container_type;
00204 protected:
00205     static const size_t m_ndim = distance_policy::_ndim;
00206 
00207     std::shared_ptr<distance_policy> m_dist; // the distance function
00208     pele::Array<double> m_coords; // the coordinates array
00209     size_t m_natoms; // the number of atoms
00210     const double m_rcut; // the potential cutoff
00211     bool m_initialised; // flag for whether the class has been initialized
00212     const pele::Array<double> m_boxv; // the array of box lengths
00213     const size_t m_ncellx; // the number of cells in the x direction
00214     const size_t m_ncells; // the total number of cells
00215     const double m_rcell; // the size of a cell
00216 
00222     container_type m_container;
00223     const double m_xmin;
00224     const double m_xmax;
00225 public:
00226     ~CellLists() {}
00227 
00234     CellLists(
00235         std::shared_ptr<distance_policy> dist,
00236         pele::Array<double> const boxv, const double rcut,
00237         const double ncellx_scale=1.0);
00238 
00242     template <class callback_class>
00243     inline CellListsLoop<callback_class> get_atom_pair_looper(callback_class & callback) const
00244     {
00245         return CellListsLoop<callback_class>(callback, m_container);
00246     }
00247 
00251     size_t get_nr_cells() const { return m_ncells; }
00252 
00256     size_t get_nr_cellsx() const { return m_ncellx; }
00257 
00263     size_t get_nr_unique_pairs() const;
00264     size_t get_direct_nr_unique_pairs(const double max_distance, pele::Array<double> x) const;
00265     size_t get_maximum_nr_unique_pairs(pele::Array<double> x) const;
00266 
00270     void reset(pele::Array<double> coords);
00271 
00272 protected:
00273     void setup(Array<double> coords);
00274     void sanity_check();
00275     size_t atom2xbegin(const size_t atom_index) const { return m_ndim * atom_index; }
00276     template <class T> T loop_pow(const T x, int ex) const;
00277     size_t atom2cell(const size_t i);
00278     pele::Array<double> cell2coords(const size_t icell) const;
00279     bool cells_are_neighbors(const size_t icell, const size_t jcell) const;
00280     double get_minimum_corner_distance2(pele::Array<double> ic, pele::Array<double> jc) const;
00281     void build_cell_neighbors_list();
00282     void build_atom_neighbors_list_old();
00283     void build_atom_neighbors_list();
00284     void build_linked_lists();
00285 };
00286 
00287 
00288 
00289 template<typename distance_policy>
00290 CellLists<distance_policy>::CellLists(
00291         std::shared_ptr<distance_policy> dist,
00292         pele::Array<double> const boxv, const double rcut,
00293         const double ncellx_scale)
00294     : m_dist(dist),
00295       m_natoms(0),
00296       m_rcut(rcut),
00297       m_initialised(false),
00298       m_boxv(boxv.copy()),
00299       m_ncellx(std::max<size_t>(1, (size_t)(ncellx_scale * m_boxv[0] / rcut))),     //no of cells in one dimension
00300       m_ncells(std::pow(m_ncellx, m_ndim)),                                                     //total no of cells
00301       m_rcell(m_boxv[0] / static_cast<double>(m_ncellx)),                                      //size of cell
00302       m_container(m_ncells),                                                                         //head of chain
00303       m_xmin(-0.5 * m_boxv[0]),
00304       m_xmax(0.5 * m_boxv[0])                                     
00305 {
00306     if (m_boxv.size() != m_ndim) {
00307         throw std::runtime_error("CellLists::CellLists: distance policy boxv and cell list boxv differ in size");
00308     }
00309     if (*std::min_element(m_boxv.data(), m_boxv.data() + m_ndim) < rcut) {
00310         throw std::runtime_error("CellLists::CellLists: illegal rcut");
00311     }
00312     const double boxv_epsilon = 1e-10;
00313     for (size_t i = 1; i < boxv.size(); ++i) {
00314         if (fabs(boxv[0] - boxv[i]) > boxv_epsilon) {
00315             throw std::runtime_error("CellLists::CellLists: illegal input boxv is not for square box");
00316         }
00317         if (boxv[i] < 0) {
00318             throw std::runtime_error("CellLists::CellLists: illegal input: boxvector");
00319         }
00320     }
00321     if (m_ncellx == 0) {
00322         throw std::runtime_error("CellLists::CellLists: illegal lattice spacing");
00323     }
00324     if (ncellx_scale < 0) {
00325         throw std::runtime_error("CellLists::CellLists: illegal input");
00326     }
00327 
00328 //    std::cout << "total number of cells " << m_ncells << std::endl;
00329 }
00330 
00331 // this is used only for tests.  It should be moved to the tests folder
00332 class stupid_counter {
00333 public:
00334     size_t count;
00335     stupid_counter() : count(0) {}
00336     void insert_atom_pair(size_t const atom_i, size_t const atom_j)
00337     { ++count; }
00338 };
00339 
00340 template<typename distance_policy>
00341 size_t CellLists<distance_policy>::get_nr_unique_pairs() const
00342 {
00343     // this function should be removed
00344     stupid_counter counter;
00345     auto looper = this->get_atom_pair_looper(counter);
00346     looper.loop_through_atom_pairs();
00347     return counter.count;
00348 }
00349 
00350 
00351 template<typename distance_policy>
00352 size_t CellLists<distance_policy>::get_direct_nr_unique_pairs(const double max_distance, pele::Array<double> x) const
00353 {
00354     size_t nr_unique_pairs = 0;
00355     const size_t natoms = x.size() / m_ndim;
00356     for (size_t i = 0; i < natoms; ++i) {
00357         for (size_t j = i + 1; j < natoms; ++j) {
00358             double rij[m_ndim];
00359             const double* xi = x.data() + atom2xbegin(i);
00360             const double* xj = x.data() + atom2xbegin(j);
00361             m_dist->get_rij(rij, xi, xj);
00362             double r2 = 0;
00363             for (size_t k = 0; k < m_ndim; ++k) {
00364                 r2 += rij[k] * rij[k];
00365             }
00366             nr_unique_pairs += (r2 <= (max_distance * max_distance));
00367         }
00368     }
00369     return nr_unique_pairs;
00370 }
00371 
00372 template <typename distance_policy>
00373 size_t CellLists<distance_policy>::get_maximum_nr_unique_pairs(pele::Array<double> x) const
00374 {
00375     const size_t natoms = x.size() / m_ndim;
00376     return (natoms * (natoms - 1)) / 2;
00377 }
00378 
00379 template <typename distance_policy>
00380 void CellLists<distance_policy>::setup(Array<double> coords)
00381 {
00382     m_coords = coords.copy();
00383     m_natoms = coords.size() / m_ndim;
00384     m_container.set_natoms(m_natoms);
00385     if (coords.size() != m_ndim * m_natoms) {
00386         throw std::runtime_error("CellLists::setup: illegal coords.size() not divisible by m_ndim");
00387     }
00388 
00389 //    m_atom_neighbor_list.reserve(m_natoms * (m_natoms - 1) / 2);
00390     build_cell_neighbors_list();
00391     m_initialised = true;
00392 //    sanity_check();
00393 
00394     // print messages if any of the parameters seem bad
00395     if (m_ncellx < 5) {
00396         // If there are only a few cells in any direction then it doesn't make sense to use cell lists
00397         // because so many cells will be neighbors with each other.
00398         // It would be better to use simple loops over atom pairs.
00399         std::cout << "CellLists: efficiency warning: there are not many cells ("<<m_ncellx<<") in each direction.\n";
00400     }
00401     if (m_ncells > m_natoms) {
00402         // It would be more efficient (I think) to reduce the number of cells.
00403         std::cout << "CellLists: efficiency warning: the number of cells ("<<m_ncells<<")"<<
00404                 " is greater than the number of atoms ("<<m_natoms<<").\n";
00405     }
00406     if (m_rcut > 0.5 * m_boxv[0]) {
00407         // an atom can interact with more than just the nearest image of it's neighbor
00408         std::cerr << "CellLists: warning: rcut > half the box length.  This might cause errors with periodic boundaries.\n";
00409     }
00410 }
00411 
00412 template <typename distance_policy>
00413 void CellLists<distance_policy>::sanity_check()
00414 {
00415     const size_t nr_unique_pairs_lists = get_nr_unique_pairs();
00416     const size_t nr_unique_pairs_direct = get_direct_nr_unique_pairs(m_rcut, m_coords);
00417     const size_t maximum_nr_unique_pairs = get_maximum_nr_unique_pairs(m_coords);
00418     //std::cout << "nr_unique_pairs_lists: " << nr_unique_pairs_lists << "\n";
00419     //std::cout << "nr_unique_pairs_direct: " << nr_unique_pairs_direct << "\n";
00420     //std::cout << "maximum_nr_unique_pairs: " << maximum_nr_unique_pairs << "\n";
00421     if (nr_unique_pairs_lists < nr_unique_pairs_direct) {
00422         std::cout << "nr_unique_pairs_lists: " << nr_unique_pairs_lists << "\n";
00423         std::cout << "nr_unique_pairs_direct: " << nr_unique_pairs_direct << "\n";
00424         std::cout << "maximum_nr_unique_pairs: " << maximum_nr_unique_pairs << "\n";
00425         throw std::runtime_error("CellLists::setup: sanity check failed: too few pairs");
00426     }
00427     if (nr_unique_pairs_lists > maximum_nr_unique_pairs) {
00428         throw std::runtime_error("CellLists::setup: sanity check failed: too many pairs");
00429     }
00430 }
00431 
00442 template <typename distance_policy>
00443 void CellLists<distance_policy>::reset(pele::Array<double> coords)
00444 {
00445     if (! m_initialised) {
00446         setup(coords);
00447     }
00448 
00449     m_coords.assign(coords);
00450     if (periodic_policy_check<distance_policy>::is_periodic) {
00451         // distance policy is periodic: put particles "back in box" first
00452         periodic_distance<m_ndim>(m_boxv).put_in_box(m_coords);
00453     }
00454     else {
00455         // distance policy is not periodic: check that particles are inside box
00456         for (size_t i = 0; i < m_coords.size(); ++i) {
00457             if (m_coords[i] < -0.5 * m_boxv[0] || m_coords[i] > 0.5 * m_boxv[0]) {
00458                 std::cout << "m_coords[i]: " << m_coords[i] << "\n";
00459                 std::cout << "0.5 * m_boxv[0]: " << 0.5 * m_boxv[0] << std::endl;
00460                 throw std::runtime_error("CellLists::reset: coords are incompatible with boxvector");
00461             }
00462         }
00463     }
00464     build_linked_lists();
00465 }
00466 
00472 template <typename distance_policy>
00473 template <class T>
00474 T CellLists<distance_policy>::loop_pow(const T x, int ex) const
00475 {
00476     T result(1);
00477     while (--ex > -1) {
00478         result *= x;
00479     }
00480     return result;
00481 }
00482 
00488 template <typename distance_policy>
00489 size_t CellLists<distance_policy>::atom2cell(const size_t i)
00490 {
00491     assert(i < m_natoms);
00492     size_t icell = 0;
00493     for(size_t j = 0; j < m_ndim; ++j) {
00494         // j1 is the index for the coords array
00495         const size_t j1 = atom2xbegin(i) + j;
00496         assert(j1 < m_coords.size());
00497         double x = m_coords[j1];
00498         // min is needed in case x == m_rcell * m_ncellx
00499         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));
00500         assert(icell_jpart == icell_jpart);
00501         if (icell_jpart >= m_ncellx) {
00502             std::cout << "x: " << x << std::endl;
00503             std::cout << "m_rcell: " << m_rcell << std::endl;
00504             std::cout << "m_ndim: " << m_ndim << std::endl;
00505             std::cout << "m_ncellx: " << m_ncellx << std::endl;
00506             std::cout << "icell_jpart: " << icell_jpart << std::endl;
00507         }
00508         assert(icell_jpart < m_ncellx);
00509         //icell += icell_jpart * std::pow(m_ncellx, j);
00510         icell += icell_jpart * loop_pow(m_ncellx, j);
00511     }
00512     assert(icell < m_ncells);
00513     return icell;
00514 }
00515 
00521 template <typename distance_policy>
00522 pele::Array<double> CellLists<distance_policy>::cell2coords(const size_t icell) const
00523 {
00524     pele::Array<double> cellcorner(m_ndim); //coordinate of cell bottom left corner
00525     std::vector<double> indexes(m_ndim, 0); //this array will store indexes, set to 0
00526     double index = 0;
00527 
00528     //don't change these loops to size_t or the conditions will not hold
00529     for(int i = m_ndim - 1; i >= 0; --i) {
00530         index = icell;
00531         for (int j = m_ndim - 1; j >= i; --j) {
00532             //index -= indexes[j] * std::pow(m_ncellx, j);
00533             index -= indexes[j] * loop_pow(m_ncellx, j);
00534         }
00535         //indexes[i] = floor(index / std::pow(m_ncellx, i));
00536         indexes[i] = floor(index / loop_pow(m_ncellx, i));
00537         cellcorner[i] = m_rcell * indexes[i];
00538     }
00539 
00540     return cellcorner.copy();
00541 }
00542 
00549 template <typename distance_policy>
00550 bool CellLists<distance_policy>::cells_are_neighbors(const size_t icell, const size_t jcell) const
00551 {
00552     if (icell == jcell) {
00553         return true;
00554     }
00555     // Get "lower-left" corners.
00556     pele::Array<double> icell_coords = cell2coords(icell);
00557     pele::Array<double> jcell_coords = cell2coords(jcell);
00558     return get_minimum_corner_distance2(icell_coords, jcell_coords) <= m_rcut * m_rcut;
00559 }
00560 
00561 template <typename distance_policy>
00562 double CellLists<distance_policy>::get_minimum_corner_distance2(pele::Array<double> lower_left1, pele::Array<double> lower_left2) const
00563 {
00564     // copy them so we don't accidentally change them
00565     lower_left1 = lower_left1.copy();
00566     lower_left2 = lower_left2.copy();
00567     pele::VecN<m_ndim> ll1, ll2, dr;
00568     pele::VecN<m_ndim> minimum_distance; // the minimum possible distance in each direction
00569     for (size_t i = 0; i < m_ndim; ++i) {
00570         double min_dist = std::numeric_limits<double>::max();
00571         double dri;
00572         // find the minimum distance in the i'th direction.
00573         ll1 = lower_left1;
00574         ll2 = lower_left2;
00575         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00576         dri = std::abs(dr[i]);
00577         if (dri < min_dist) {
00578             min_dist = dri;
00579         }
00580 
00581         ll1 = lower_left1;
00582         ll2 = lower_left2;
00583         ll1[i] += m_rcell;
00584         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00585         dri = std::abs(dr[i]);
00586         if (dri < min_dist) {
00587             min_dist = dri;
00588         }
00589 
00590         ll1 = lower_left1;
00591         ll2 = lower_left2;
00592         ll2[i] += m_rcell;
00593         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00594         dri = std::abs(dr[i]);
00595         if (dri < min_dist) {
00596             min_dist = dri;
00597         }
00598 
00599         ll1 = lower_left1;
00600         ll2 = lower_left2;
00601         ll1[i] += m_rcell;
00602         ll2[i] += m_rcell;
00603         m_dist->get_rij(dr.data(), ll1.data(), ll2.data());
00604         dri = std::abs(dr[i]);
00605         if (dri < min_dist) {
00606             min_dist = dri;
00607         }
00608 
00609         minimum_distance[i] = min_dist;
00610     }
00611     double r2_min = dot<m_ndim> (minimum_distance, minimum_distance);
00612     return r2_min;
00613 }
00614 
00618 template <typename distance_policy>
00619 void CellLists<distance_policy>::build_cell_neighbors_list()
00620 {
00621     size_t max_n_neibs = 0;
00622     m_container.m_cell_neighbor_pairs.reserve(2 * m_ncells); // A lower end guess for the size
00623     for(size_t i = 0; i < m_ncells; ++i) {
00624         size_t nneibs = -0;
00625         for(size_t j = 0; j <= i; ++j) {
00626             if (cells_are_neighbors(i, j)) { //includes itself as a neighbor
00627                 m_container.m_cell_neighbor_pairs.push_back(std::pair<size_t, size_t>(i, j));
00628                 ++nneibs;
00629             }
00630         }
00631         if (nneibs > max_n_neibs) max_n_neibs = nneibs;
00632     }
00633     if (max_n_neibs > 0.5 * m_ncells) {
00634         // If each cell has many neighbors it would be better to just use a simple loop over atom pairs.
00635         // Alternatively you might think abour reducing rcut.
00636         std::cout << "CellLists: efficiency warning: the cells have very many neighbors ("
00637                 <<max_n_neibs << ", with "<<m_ncells<<" cells total).\n";
00638     }
00639 
00640 }
00641 
00645 template <typename distance_policy>
00646 void CellLists<distance_policy>::build_linked_lists()
00647 {
00648     m_container.clear();
00649     for(size_t i = 0; i < m_natoms; ++i) {
00650         size_t icell = atom2cell(i);
00651         m_container.add_atom_to_cell(i, icell);
00652     }
00653 }
00654 
00655 } // namespace pele
00656 
00657 
00658 
00659 
00660 
00661 #endif // #ifndef _PELE_CELL_LISTS_H_
 All Classes Namespaces Functions Variables Typedefs