// Copyright (C) 2008-2011 Jakob Schiotz and Center for Individual // Nanoparticle Functionality, Department of Physics, Technical // University of Denmark. Email: schiotz@fysik.dtu.dk // // This file is part of Asap version 3. // Asap is released under the GNU Lesser Public License (LGPL) version 3. // However, the parts of Asap distributed within the OpenKIM project // (including this file) are also released under the Common Development // and Distribution License (CDDL) version 1.0. // // This program is free software: you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public License // version 3 as published by the Free Software Foundation. Permission // to use other versions of the GNU Lesser General Public License may // granted by Jakob Schiotz or the head of department of the // Department of Physics, Technical University of Denmark, as // described in section 14 of the GNU General Public License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // and the GNU Lesser Public License along with this program. If not, // see . #include "NeighborList.h" #include "Atoms.h" #include "Exception.h" #include "Timing.h" //#define ASAPDEBUG #include "Debug.h" #include #include using std::cerr; using std::endl; using std::flush; extern int verbose; //#define MAXLIST 500 //#define CHKFULLLIST //#define CHECKCONSISTENCY NeighborList::NeighborList(Atoms *a, double rCut, double driftfactor) { CONSTRUCTOR; if (a == NULL) atoms = new Atoms(); else { atoms = a; AsapAtoms_INCREF(atoms); } nAtoms = 0; // Not valid yet invalid = true; firsttime = true; fulllists = false; this->rCut = rCut; rCut2 = rCut * rCut; drift = driftfactor * rCut; drift2 = drift * drift; // A cell list is used to build the neighbor list. Its cutoff must // include the drift of this list, but it does not need its own // drift. It is marked as a slave, so it does not trigger // additional migrations when it updates. if (rCut <= 0.0) throw AsapError("NeighborList: cutoff distance must be positive."); PyAsap_NeighborLocatorObject *nbl; nbl = PyAsap_NewNeighborCellLocator(atoms, rCut+2*drift, 0.0, true); cells_obj = (PyObject *) nbl; cells = dynamic_cast(nbl->cobj); assert(cells); cells->GetTranslationTable(translationTable); } NeighborList::~NeighborList() { DESTRUCTOR; CHECKREF(cells_obj); Py_DECREF(cells_obj); AsapAtoms_DECREF(atoms); } void NeighborList::EnableFullNeighborLists() { invalid = true; fulllists = true; } void NeighborList::MakeList() { RETURNIFASAPERROR; USETIMER("NeighborList::MakeList"); // Do we have any ghosts? //GhostAtoms *ghostAtoms = dynamic_cast(atoms); const Vec *ss = NULL; const bool *periodic = NULL; const double *superCellHeights = NULL; DEBUGPRINT; #ifdef _OPENMP #pragma omp single copyprivate(ss,periodic,superCellHeights) #endif // _OPENMP { ss = atoms->GetCell(); periodic = atoms->GetBoundaryConditions(); superCellHeights = atoms->GetCellHeights(); DEBUGPRINT; if (verbose >= 1) cerr << " NeighborList-Update "; nAtoms = atoms->GetNumberOfAtoms(); nAllAtoms = nAtoms + atoms->GetNumberOfGhostAtoms(); if (nAllAtoms != nbList.size()) nbList.resize(nAllAtoms); if (fulllists && (nAllAtoms != complNbList.size())) complNbList.resize(nAllAtoms); maxLength = 0; memcpy(storedSuperCell, ss, 3*sizeof(Vec)); DEBUGPRINT; for (int i = 0; i < 3; i++) { pbc[i] = periodic[i]; // Store for later tests referenceSuperCell[i] = ss[i]; } // Check the size. for (int i = 0; i < 3; i++) if (periodic[i] && superCellHeights[i] < 2 * rCut) THROW( AsapError("The height of the cell (") << superCellHeights[i] << ") must be larger than " << 2 * rCut ); DEBUGPRINT; cells->UpdateNeighborList(); DEBUGPRINT assert(nAtoms == atoms->GetNumberOfAtoms()); } RETURNIFASAPERROR; // Make the list int myMaxLength = 0; if (firsttime) { vector< pair > buf; buf.reserve(cells->MaxNeighborListLength()); #ifdef _OPENMP #pragma omp for nowait #endif // _OPENMP for (int i = 0; i < nAllAtoms; i++) { int l = cells->GetListAndTranslations(i, buf); #ifdef MAXLIST if (l > MAXLIST) THROW( AsapError("Unreasonably long neighbor list for atom ") << i << ": " << l << " elements." ); #endif nbList[i].reserve(l + l/20 + 2); nbList[i].clear(); nbList[i].insert(nbList[i].begin(), buf.begin(), buf.end()); if (fulllists) { int l2 = cells->GetComplementaryListAndTranslations(i, buf); l += l2; complNbList[i].reserve(l2 + l2/20 + 2); complNbList[i].clear(); complNbList[i].insert(complNbList[i].begin(), buf.begin(), buf.end()); } if (l > myMaxLength) myMaxLength = l; } } else { #ifdef _OPENMP #pragma omp for nowait #endif // _OPENMP for (int i = 0; i < nAllAtoms; i++) { int l = cells->GetListAndTranslations(i, nbList[i]); #ifdef MAXLIST if (l > MAXLIST) THROW( AsapError("Unreasonably long neighbor list for atom ") << i << ": " << l << " elements." ); #endif if (fulllists) l += cells->GetComplementaryListAndTranslations(i, complNbList[i]); if (l > myMaxLength) myMaxLength = l; } } myMaxLength++; // We need space in arrays for a candidate atom after the last neighbor. #ifdef _OPENMP #pragma omp critical #endif // _OPENMP { if (myMaxLength > maxLength) maxLength = myMaxLength; } #ifdef _OPENMP #pragma omp single #endif // _OPENMP { DEBUGPRINT; invalid = false; firsttime = false; } #ifdef CHKFULLLIST CheckFullListConsistency("MakeList"); #endif DEBUGPRINT; } bool NeighborList::CheckNeighborList() { RETURNIFASAPERROR2(false); USETIMER("NeighborList::CheckNeighborList"); if (invalid) return true; int n_at = atoms->GetNumberOfAtoms(); if (nAtoms != n_at || nAllAtoms != n_at + atoms->GetNumberOfGhostAtoms()) return true; bool updateRequired = false; DEBUGPRINT; cells->RenormalizePositions(); // XXX Move out of omp single once parallelized. DEBUGPRINT; const bool *newpbc = atoms->GetBoundaryConditions(); if (invalid && verbose) cerr << "NeighborList::CheckAndUpdateNeighborList: NBList has been marked invalid." << endl; const Vec *ss = atoms->GetCell(); memcpy(storedSuperCell, ss, 3*sizeof(Vec)); if (nAtoms != atoms->GetNumberOfAtoms() || pbc[0] != newpbc[0] || pbc[1] != newpbc[1] || pbc[2] != newpbc[2]) { invalid = true; } if (invalid) cells->Invalidate(); updateRequired = invalid; const Vec *positions = atoms->GetPositions(); const Vec *referencePositions = cells->GetReferencePositions(); if (!updateRequired) { // Check how much the cell has changed. double max_strain_disp = GetMaxStrainDisplacement(); double max2 = drift - max_strain_disp; if (max2 <= 0.0) updateRequired = true; else { max2 = max2 * max2; for (int n = 0; n < nAtoms; n++) if (Length2(positions[n] - referencePositions[n]) > max2) { updateRequired = true; break; } } } #ifdef CHKFULLLIST if (!updateRequired) CheckFullListConsistency("CheckNeighborList"); #endif return updateRequired; } void NeighborList::UpdateNeighborList() { MakeList(); } bool NeighborList::CheckAndUpdateNeighborList() { RETURNIFASAPERROR2(false); DEBUGPRINT; bool update = CheckNeighborList(); MEMORY; if (update) UpdateNeighborList(); return update; } bool NeighborList::CheckAndUpdateNeighborList(PyObject *atoms_obj) { atoms->Begin(atoms_obj); CHECKNOASAPERROR; bool res = CheckAndUpdateNeighborList(); PROPAGATEASAPERROR; atoms->End(); return res; } // Some code could be saved by combining GetNeighbors and // GetFullNeighbors but it would mean a slight performance cost in // GetNeighbors, which is critical for overall performance. int NeighborList::GetNeighbors(int a1, int *neighbors, Vec *diffs, double *diffs2, int& size, double r) const { if (invalid) { DEBUGPRINT; THROW( AsapError("NeighborList has been invalidated, possibly by another NeighborList using the same atoms.") ); } if (size < nbList[a1].size()) { DEBUGPRINT; THROW( AsapError("NeighborList::GetNeighbors: list overflow.") ); } RETURNIFASAPERROR2(0); const vector &positions = cells->GetWrappedPositions(); // Need to use GET_CELL instead of GetCell as the atoms are not open // when called from the Python interface. const Vec *superCell = atoms->GET_CELL(); double rC2 = rCut2; if (r > 0.0) rC2 = r * r; Vec pos1 = positions[a1]; int nNeighbors = 0; typedef vector< pair >::const_iterator iterat; iterat terminate = nbList[a1].end(); for (iterat a2 = nbList[a1].begin(); a2 < terminate; ++a2) { assert(a2->second >= 0 && a2->second < translationTable.size()); assert(a2->first >= 0 && a2->first < nAllAtoms); assert(nNeighbors < size); const IVec &celltranslation = translationTable[a2->second]; diffs[nNeighbors] = positions[a2->first] - pos1 - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; double d2 = Length2(diffs[nNeighbors]); if (d2 < rC2) { diffs2[nNeighbors] = d2; neighbors[nNeighbors] = a2->first; nNeighbors++; } } size -= nNeighbors; assert(size >= 0); return nNeighbors; } void NeighborList::GetNeighbors(int a1, vector &neighbors) const { if (invalid) THROW( AsapError("NeighborList has been invalidated, possibly by another NeighborList using the same atoms.") ); RETURNIFASAPERROR; neighbors.clear(); const vector &positions = cells->GetWrappedPositions(); const Vec *superCell = atoms->GET_CELL(); // atoms may not be open Vec pos1 = positions[a1]; typedef vector< pair >::const_iterator iterat; iterat terminate = nbList[a1].end(); for (iterat a2 = nbList[a1].begin(); a2 < terminate; ++a2) { const IVec &celltranslation = translationTable[a2->second]; Vec diff = positions[a2->first] - pos1 - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; double d2 = Length2(diff); if (d2 < rCut2) neighbors.push_back(a2->first); } } int NeighborList::GetFullNeighbors(int a1, int *neighbors, Vec *diffs, double *diffs2, int& size, double r) const { if (!fulllists) THROW( AsapError("Calling NeighborList::GetFullNeighbors but full lists are not enabled.") ); if (invalid) THROW( AsapError("NeighborList has been invalidated, possibly by another NeighborList using the same atoms.") ); if (size < nbList[a1].size() + complNbList[a1].size()) THROW( AsapError("NeighborList::GetNeighbors: list overflow.") ); RETURNIFASAPERROR2(0); const vector &positions = cells->GetWrappedPositions(); // Need to use GET_CELL instead of GetCell as the atoms are not open // when called from the Python interface. const Vec *superCell = atoms->GET_CELL(); double rC2 = rCut2; if (r > 0.0) rC2 = r * r; Vec pos1 = positions[a1]; int nNeighbors = 0; typedef vector< pair >::const_iterator iterat; iterat terminate = nbList[a1].end(); for (iterat a2 = nbList[a1].begin(); a2 < terminate; ++a2) { const IVec &celltranslation = translationTable[a2->second]; diffs[nNeighbors] = positions[a2->first] - pos1 - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; double d2 = Length2(diffs[nNeighbors]); if (d2 < rC2) { diffs2[nNeighbors] = d2; neighbors[nNeighbors] = a2->first; nNeighbors++; } } terminate = complNbList[a1].end(); for (iterat a2 = complNbList[a1].begin(); a2 < terminate; ++a2) { const IVec &celltranslation = translationTable[a2->second]; diffs[nNeighbors] = positions[a2->first] - pos1 - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; double d2 = Length2(diffs[nNeighbors]); if (d2 < rC2) { diffs2[nNeighbors] = d2; neighbors[nNeighbors] = a2->first; nNeighbors++; } } size -= nNeighbors; assert(size >= 0); return nNeighbors; } void NeighborList::GetFullNeighbors(int a1, vector &neighbors) const { if (!fulllists) THROW( AsapError("Calling NeighborList::GetFullNeighbors but full lists are not enabled.") ); RETURNIFASAPERROR; const vector &positions = cells->GetWrappedPositions(); const Vec *superCell; if (atoms->IsActive()) superCell = atoms->GetCell(); else superCell = storedSuperCell; Vec pos1 = positions[a1]; // Get the first half of the neighbor list GetNeighbors(a1, neighbors); // Get the second half typedef vector< pair >::const_iterator iterat; iterat terminate = complNbList[a1].end(); for (iterat a2 = complNbList[a1].begin(); a2 < terminate; ++a2) { const IVec &celltranslation = translationTable[a2->second]; Vec diff = positions[a2->first] - pos1 - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; double d2 = Length2(diff); if (d2 < rCut2) neighbors.push_back(a2->first); } } void NeighborList::RemakeLists(const set &modified, set &affected) { // Never called in OpenMP context - throwing exceptions is OK. #ifdef CHKFULLLIST CheckFullListConsistency("Beginning of RemakeLists", false); #endif // CHKFULLLIST #ifdef CHECKCONSISTENCY const vector &pos = cells->GetWrappedPositions(); const Vec *superCell = atoms->GetCell(); #endif // CHECKCONSISTENCY typedef vector< pair >::iterator nbiterat; if (invalid) throw AsapError("NeighborList has been invalidated, possibly by another neighbor list sharing the same atoms."); if (!fulllists) throw AsapError("NeighborList::RemakeLists() only supported if 'full neigbor lists' are enabled."); assert(modified.size() > 0); affected.clear(); affected.insert(modified.begin(), modified.end()); // Update the NeighborCellLocator. The positions of the // modified atoms are normalized here. cells->RemakeLists_Simple(modified); // Collect the current neighbors to the modified atoms, and // remove the atoms from the neighbors' lists. Atoms that are // already in the modified set are not affected. for (set::const_iterator i = modified.begin(); i != modified.end(); ++i) { nbiterat terminate = nbList[*i].end(); for (nbiterat j = nbList[*i].begin(); j != terminate; ++j) { // j->first is a neighbor to modified atom *i affected.insert(j->first); if (!modified.count(j->first)) { // Not in modified set. Fix its complementary list: // Remove atom *i from compl. neighbor list of j->first //int x = complNbList[j->first].erase(*i); nbiterat k = complNbList[j->first].begin(); while ((k->first != *i) && (k != complNbList[j->first].end())) ++k; if (k != complNbList[j->first].end()) complNbList[j->first].erase(k); #ifdef CHECKCONSISTENCY else { // Looks like an inconsistency, but we have to live // with them if the distance is above rCut. IVec celltranslation = translationTable[j->second]; Vec pos1 = pos[*i] + celltranslation[0] * superCell[0] + celltranslation[1] * superCell[1] + celltranslation[2] * superCell[2]; if (Length2(pos1 - pos[j->first]) < rCut2) throw AsapError("Inconsistent neighbor list data: Did not find ") << *i << " on complementary nb list of atom " << j->first; } #endif // CHECKCONSISTENCY } } terminate = complNbList[*i].end(); for (nbiterat j = complNbList[*i].begin(); j != terminate; ++j) { affected.insert(j->first); if (!modified.count(j->first)) { // Not in modified set. Fix its complementary list //int x = nbList[j->first].erase(*i); nbiterat k = nbList[j->first].begin(); while ((k->first != *i) && (k != nbList[j->first].end())) ++k; if (k != nbList[j->first].end()) nbList[j->first].erase(k); #ifdef CHECKCONSISTENCY else { // Looks like an inconsistency, but we have to live // with them if the distance is above rCut. IVec celltranslation = translationTable[j->second]; Vec pos1 = pos[*i] + celltranslation[0] * superCell[0] + celltranslation[1] * superCell[1] + celltranslation[2] * superCell[2]; if (Length2(pos1 - pos[j->first]) < rCut2) throw AsapError("Inconsistent neighbor list data: Did not find ") << *i << " on normal nb list of atom " << j->first; } #endif // CHECKCONSISTENCY } } } // Get new neighbor lists for the modified atoms and for their new neighbors set newneighbors; for (set::const_iterator i = modified.begin(); i != modified.end(); ++i) { int l = cells->GetListAndTranslations(*i, nbList[*i]); l += cells->GetComplementaryListAndTranslations(*i, complNbList[*i]); if (l > maxLength) maxLength = l; for (nbiterat j = nbList[*i].begin(); j != nbList[*i].end(); ++j) if (!modified.count(j->first)) newneighbors.insert(j->first); for (nbiterat j = complNbList[*i].begin(); j != complNbList[*i].end(); ++j) if (!modified.count(j->first)) newneighbors.insert(j->first); } for (set::const_iterator i = newneighbors.begin(); i != newneighbors.end(); ++i) { int l = cells->GetListAndTranslations(*i, nbList[*i]); l += cells->GetComplementaryListAndTranslations(*i, complNbList[*i]); if (l > maxLength) maxLength = l; } affected.insert(newneighbors.begin(), newneighbors.end()); // Now, we need to update the reference positions cells->UpdateReferencePositions(modified); #if 0 cerr << "Modified:"; for (set::const_iterator i = modified.begin(); i != modified.end(); ++i) cerr << " " << *i; cerr << endl << "Affected:"; for (set::const_iterator i = affected.begin(); i != affected.end(); ++i) cerr << " " << *i; cerr << endl; #endif // 0 #ifdef CHKFULLLIST CheckFullListConsistency("End of RemakeLists"); #endif } int NeighborList::TestPartialUpdate(set modified, PyObject *pyatoms) { set dummy; Atoms *atoms = GetAtoms(); atoms->Begin(pyatoms); RemakeLists(modified, dummy); atoms->End(); return dummy.size(); } // Check consistency between nbList and complNbList. Note that with // Monte Carlo optimizations, consistency is not guaranteed for atoms // separated by more than rCut. If the distance is greater than // rCut+2*drift the inconsistency is always safe, if lower than that // but still greater than rCut is is most likely safe, but cannot be // determined easily. void NeighborList::CheckFullListConsistency(const string where, bool chkdst) { RETURNIFASAPERROR; if (invalid || !fulllists) return; const vector &pos = cells->GetWrappedPositions(); const Vec *superCell = atoms->GetCell(); double rc2 = rCut + 4*drift; double rCut2 = rCut * rCut; rc2 *= rc2; typedef vector< pair >::const_iterator iterat; // Check that all info on nbList is found on complNbList for (int i = 0; i < nAtoms; i++) for (iterat j = nbList[i].begin(); j != nbList[i].end(); ++j) { // j->first is on the nblist of i. Is i on complnblist of j->first? iterat k = complNbList[j->first].begin(); iterat k_end = complNbList[j->first].end(); while (k->first != i && k != k_end) ++k; if (k->first != i) { // Is the inconsistency safe? IVec celltranslation = translationTable[j->second]; Vec pos1 = pos[i] + celltranslation[0] * superCell[0] + celltranslation[1] * superCell[1] + celltranslation[2] * superCell[2]; if (Length2(pos1 - pos[j->first]) < rCut2) throw AsapError("nbList[") << i << "] contains " << j->first << ", but complNbList[" << j->first << "] is missing " << i << ". (" << where << ")"; } if (chkdst) { // Are the atoms actually neighbors? IVec celltranslation = translationTable[j->second]; Vec diff = pos[j->first] - pos[i] - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; if (Length2(diff) > rc2 + 1e-5) throw AsapError("nbList[") << i << "] contains " << j->first << ", but they are too far apart. (" << where << ") " << Length2(diff) << " > " << rc2 << "\npos1 = " << pos[i] << "\npos2 = " << pos[j->first] << "\ntranslation = " << celltranslation; } } // Check that all info on complNbList is found on nbList for (int i = 0; i < nAtoms; i++) for (iterat j = complNbList[i].begin(); j != complNbList[i].end(); ++j) { // j->first is on the complnblist of i. Is i on nblist of j->first? iterat k = nbList[j->first].begin(); iterat k_end = nbList[j->first].end(); while (k->first != i && k != k_end) ++k; if (k->first != i) { // Is the inconsistency safe? IVec celltranslation = translationTable[j->second]; Vec diff = pos[j->first] - pos[i] - celltranslation[0] * superCell[0] - celltranslation[1] * superCell[1] - celltranslation[2] * superCell[2]; if (Length2(diff) < rCut2) throw AsapError("complNbList[") << i << "] contains " << j->first << ", but nbList[" << j->first << "] is missing " << i << ". (" << where << ")"; } } } void NeighborList::printlist(int n) const { typedef vector< pair >::const_iterator iterat; cerr << "nbList[" << n << "]"; for (iterat i = nbList[n].begin(); i != nbList[n].end(); ++i) cerr << " " << i->first; cerr << endl; if (fulllists) { cerr << "complNbList[" << n << "]"; for (iterat i = complNbList[n].begin(); i != complNbList[n].end(); ++i) cerr << " " << i->first; cerr << endl; } } void NeighborList::print_info(int n) { cerr << "NeighborList info on atom " << n << ":" << endl; cerr << "nbList:"; for (int i = 0; i < nbList[n].size(); i++) cerr << " " << nbList[n][i].first << " " << nbList[n][i].second; cerr << endl; if (fulllists) { cerr << "complNbList:"; for (int i = 0; i < complNbList[n].size(); i++) cerr << " " << complNbList[n][i].first << " " << complNbList[n][i].second; cerr << endl; } cells->print_info(n); } long NeighborList::PrintMemory() const { long n = 0; long ntot = 0; typedef vector< vector< pair > >::const_iterator iter; for (iter i = nbList.begin(); i != nbList.end(); i++) { n += i->size(); ntot += i->capacity(); } if (fulllists) for (iter i = complNbList.begin(); i != complNbList.end(); i++) { n += i->size(); ntot += i->capacity(); } long mem = ntot * (sizeof(int) + sizeof(translationsidx_t)); mem = (mem + 512*1024)/(1024*1024); long overhead = (ntot - n) * (sizeof(int) + sizeof(translationsidx_t)); overhead = (overhead + 512*1024)/(1024*1024); char buffer[500]; snprintf(buffer, 500, "*MEM* NeighborList %ld MB. [ overhead %ld MB, %.2e items, full=%d, sizeof(xlat_idx)=%ld ]", mem, overhead, (double) n, (int) fulllists, (long) sizeof(translationsidx_t)); cerr << buffer << endl; mem += cells->PrintMemory(); return mem; } double NeighborList::GetMaxStrainDisplacement() { double disp2 = 0.0; const Vec *ss = atoms->GetCell(); vector strain(3); Vec factor; for (int i =0; i < 3; i++) { double length = sqrt(Length2(ss[i])); factor[i] = rCut / length; strain[i] = ss[i] - referenceSuperCell[i]; } for (int i = -1; i <= 1; i++) for (int j = -1; j <= 1; j++) for (int k = -1; k <= 1; k++) { Vec v = i * factor[0] * strain[0] + j * factor[1] * strain[1] + k * factor[2] * strain[2]; double l2 = Length2(v); if (l2 > disp2) disp2 = l2; } return sqrt(disp2); }