pele
Python energy landscape explorer
|
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_