mcpele
1.0.0
The Monte Carlo Python Energy Landscape Explorer
|
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