2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2009,2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements neighborhood searching for analysis (from nbsearch.h).
40 * The grid implementation could still be optimized in several different ways:
41 * - Pruning grid cells from the search list if they are completely outside
42 * the sphere that is being considered.
43 * - A better heuristic could be added for falling back to simple loops for a
44 * small number of reference particles.
45 * - A better heuristic for selecting the grid size.
46 * - A multi-level grid implementation could be used to be able to use small
47 * grids for short cutoffs with very inhomogeneous particle distributions
48 * without a memory cost.
50 * \author Teemu Murtola <teemu.murtola@gmail.com>
51 * \ingroup module_selection
63 #include "thread_mpi/mutex.h"
65 #include "gromacs/legacyheaders/names.h"
66 #include "gromacs/math/vec.h"
67 #include "gromacs/pbcutil/pbc.h"
68 #include "gromacs/selection/position.h"
69 #include "gromacs/topology/block.h"
70 #include "gromacs/utility/arrayref.h"
71 #include "gromacs/utility/exceptions.h"
72 #include "gromacs/utility/gmxassert.h"
73 #include "gromacs/utility/stringutil.h"
82 * Computes the bounding box for a set of positions.
84 * \param[in] posCount Number of positions in \p x.
85 * \param[in] x Positions to compute the bounding box for.
86 * \param[out] origin Origin of the bounding box.
87 * \param[out] size Size of the bounding box.
89 void computeBoundingBox(int posCount, const rvec x[], rvec origin, rvec size)
92 copy_rvec(x[0], origin);
93 copy_rvec(x[0], maxBound);
94 for (int i = 1; i < posCount; ++i)
96 for (int d = 0; d < DIM; ++d)
98 if (origin[d] > x[i][d])
102 if (maxBound[d] < x[i][d])
104 maxBound[d] = x[i][d];
108 rvec_sub(maxBound, origin, size);
116 /********************************************************************
117 * Implementation class declarations
120 class AnalysisNeighborhoodSearchImpl
123 typedef AnalysisNeighborhoodPairSearch::ImplPointer
124 PairSearchImplPointer;
125 typedef std::vector<PairSearchImplPointer> PairSearchList;
126 typedef std::vector<std::vector<int> > CellList;
128 explicit AnalysisNeighborhoodSearchImpl(real cutoff);
129 ~AnalysisNeighborhoodSearchImpl();
132 * Initializes the search with a given box and reference positions.
134 * \param[in] mode Search mode to use.
135 * \param[in] bUseBoundingBox Whether to use bounding box for
136 * non-periodic grids.
137 * \param[in] bXY Whether to use 2D searching.
138 * \param[in] excls Exclusions.
139 * \param[in] pbc PBC information.
140 * \param[in] positions Set of reference positions.
142 void init(AnalysisNeighborhood::SearchMode mode,
143 bool bUseBoundingBox,
145 const t_blocka *excls,
147 const AnalysisNeighborhoodPositions &positions);
148 PairSearchImplPointer getPairSearch();
150 real cutoffSquared() const { return cutoff2_; }
151 bool usesGridSearch() const { return bGrid_; }
155 * Checks the efficiency and possibility of doing grid-based searching.
157 * \param[in] bForce If `true`, grid search will be forced if possible.
158 * \returns `false` if grid search is not suitable.
160 bool checkGridSearchEfficiency(bool bForce);
162 * Determines a suitable grid size and sets up the cells.
164 * \param[in] box Box vectors (should not have zero vectors).
165 * \param[in] bSingleCell If `true`, the corresponding dimension will
166 * be forced to use a single cell.
167 * \param[in] posCount Number of positions that will be put on the
169 * \returns `false` if grid search is not suitable.
171 bool initGridCells(const matrix box, bool bSingleCell[DIM],
174 * Sets ua a search grid for a given box.
176 * \param[in] pbc Information about the box.
177 * \param[in] posCount Number of positions in \p x.
178 * \param[in] x Reference positions that will be put on the grid.
179 * \param[in] bUseBoundingBox If `true`, non-periodic grid dimensions
180 * will use the bounding box of \p x instead of the box.
181 * \param[in] bForce If `true`, grid searching will be used if at all
182 * possible, even if a simple search might give better performance.
183 * \returns `false` if grid search is not suitable.
185 bool initGrid(const t_pbc &pbc, int posCount, const rvec x[],
186 bool bUseBoundingBox, bool bForce);
188 * Maps a point into a grid cell.
190 * \param[in] x Point to map.
191 * \param[out] cell Fractional cell coordinates of \p x on the grid.
192 * \param[out] xout Coordinates to use.
194 * \p xout will be within the rectangular unit cell in dimensions where
195 * the grid is periodic. For other dimensions, both \p xout and
196 * \p cell can be outside the grid/unit cell.
198 void mapPointToGridCell(const rvec x, rvec cell, rvec xout) const;
200 * Calculates linear index of a grid cell.
202 * \param[in] cell Cell indices (must be within the grid).
203 * \returns Linear index of \p cell.
205 int getGridCellIndex(const ivec cell) const;
207 * Adds an index into a grid cell.
209 * \param[in] cell Fractional cell coordinates into which \p i should
211 * \param[in] i Index to add.
213 * \p cell should satisfy the conditions that \p mapPointToGridCell()
216 void addToGridCell(const rvec cell, int i);
218 * Initializes a cell pair loop for a dimension.
220 * \param[in] centerCell Fractional cell coordiates of the particle
221 * for which pairs are being searched.
222 * \param[in,out] cell Current/initial cell to loop over.
223 * \param[in,out] upperBound Last cell to loop over.
224 * \param[in] dim Dimension to initialize in this call.
226 * Initializes `cell[dim]` and `upperBound[dim]` for looping over
227 * neighbors of a particle at position given by \p centerCell.
228 * If 'dim != ZZ`, `cell[d]` (`d > dim`) set the plane/row of cells
229 * for which the loop is initialized. The loop should then go from
230 * `cell[dim]` until `upperBound[dim]`, inclusive.
231 * `cell[d]` with `d < dim` or `upperBound[d]` with `d != dim` are not
232 * modified by this function.
234 * `cell` and `upperBound` may be outside the grid for periodic
235 * dimensions and need to be shifted separately: to simplify the
236 * looping, the range is always (roughly) symmetric around the value in
239 void initCellRange(const rvec centerCell, ivec cell,
240 ivec upperBound, int dim) const;
242 * Advances cell pair loop to the next cell.
244 * \param[in] centerCell Fractional cell coordiates of the particle
245 * for which pairs are being searched.
246 * \param[in,out] cell Current (in)/next (out) cell in the loop.
247 * \param[in,out] upperBound Last cell in the loop for each dimension.
249 bool nextCell(const rvec centerCell, ivec cell, ivec upperBound) const;
251 * Calculates the index and shift of a grid cell during looping.
253 * \param[in] cell Unshifted cell index.
254 * \param[out] shift Shift to apply to get the periodic distance
255 * for distances between the cells.
256 * \returns Grid cell index corresponding to `cell`.
258 int shiftCell(const ivec cell, rvec shift) const;
260 //! Whether to try grid searching.
264 //! The cutoff squared.
266 //! Whether to do searching in XY plane only.
269 //! Number of reference points for the current frame.
271 //! Reference point positions.
273 //! Reference position exclusion IDs.
274 const int *refExclusionIds_;
275 //! Reference position indices (NULL if no indices).
276 const int *refIndices_;
278 const t_blocka *excls_;
282 //! Whether grid searching is actually used for the current positions.
284 //! false if the box is rectangular.
286 //! Whether the grid is periodic in a dimension.
288 //! Array for storing in-unit-cell reference positions.
289 std::vector<RVec> xrefAlloc_;
290 //! Origin of the grid (zero for periodic dimensions).
292 //! Size of a single grid cell.
294 //! Inverse of \p cellSize_. Zero for dimensions where grid is not used.
297 * Shift in cell coordinates (for triclinic boxes) in X when crossing
298 * the Z periodic boundary.
302 * Shift in cell coordinates (for triclinic boxes) in Y when crossing
303 * the Z periodic boundary.
307 * Shift in cell coordinates (for triclinic boxes) in X when crossing
308 * the Y periodic boundary.
311 //! Number of cells along each dimension.
313 //! Data structure to hold the grid cell contents.
316 tMPI::mutex createPairSearchMutex_;
317 PairSearchList pairSearchList_;
319 friend class AnalysisNeighborhoodPairSearchImpl;
321 GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodSearchImpl);
324 class AnalysisNeighborhoodPairSearchImpl
327 explicit AnalysisNeighborhoodPairSearchImpl(const AnalysisNeighborhoodSearchImpl &search)
331 testPositions_ = NULL;
332 testExclusionIds_ = NULL;
337 clear_rvec(testcell_);
338 clear_ivec(currCell_);
339 clear_ivec(cellBound_);
343 //! Initializes a search to find reference positions neighboring \p x.
344 void startSearch(const AnalysisNeighborhoodPositions &positions);
345 //! Searches for the next neighbor.
346 template <class Action>
347 bool searchNext(Action action);
348 //! Initializes a pair representing the pair found by searchNext().
349 void initFoundPair(AnalysisNeighborhoodPair *pair) const;
350 //! Advances to the next test position, skipping any remaining pairs.
351 void nextTestPosition();
354 //! Clears the loop indices.
355 void reset(int testIndex);
356 //! Checks whether a reference positiong should be excluded.
357 bool isExcluded(int j);
359 //! Parent search object.
360 const AnalysisNeighborhoodSearchImpl &search_;
361 //! Number of test positions.
363 //! Reference to the test positions.
364 const rvec *testPositions_;
365 //! Reference to the test exclusion indices.
366 const int *testExclusionIds_;
367 //! Reference to the test position indices.
368 const int *testIndices_;
369 //! Number of excluded reference positions for current test particle.
371 //! Exclusions for current test particle.
373 //! Index of the currently active test position in \p testPositions_.
375 //! Stores test position during a pair loop.
377 //! Stores the previous returned position during a pair loop.
379 //! Stores the pair distance corresponding to previ_;
381 //! Stores the shortest distance vector corresponding to previ_;
383 //! Stores the current exclusion index during loops.
385 //! Stores the fractional test particle cell location during loops.
387 //! Stores the current cell during pair loops.
389 //! Stores the current loop upper bounds for each dimension during pair loops.
391 //! Stores the index within the current cell during pair loops.
394 GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodPairSearchImpl);
397 /********************************************************************
398 * AnalysisNeighborhoodSearchImpl
401 AnalysisNeighborhoodSearchImpl::AnalysisNeighborhoodSearchImpl(real cutoff)
407 cutoff_ = cutoff2_ = GMX_REAL_MAX;
412 cutoff2_ = sqr(cutoff_);
417 refExclusionIds_ = NULL;
419 std::memset(&pbc_, 0, sizeof(pbc_));
423 bGridPBC_[XX] = true;
424 bGridPBC_[YY] = true;
425 bGridPBC_[ZZ] = true;
427 clear_rvec(gridOrigin_);
428 clear_rvec(cellSize_);
429 clear_rvec(invCellSize_);
430 clear_ivec(ncelldim_);
433 AnalysisNeighborhoodSearchImpl::~AnalysisNeighborhoodSearchImpl()
435 PairSearchList::const_iterator i;
436 for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
438 GMX_RELEASE_ASSERT(i->unique(),
439 "Dangling AnalysisNeighborhoodPairSearch reference");
443 AnalysisNeighborhoodSearchImpl::PairSearchImplPointer
444 AnalysisNeighborhoodSearchImpl::getPairSearch()
446 tMPI::lock_guard<tMPI::mutex> lock(createPairSearchMutex_);
447 // TODO: Consider whether this needs to/can be faster, e.g., by keeping a
448 // separate pool of unused search objects.
449 PairSearchList::const_iterator i;
450 for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
457 PairSearchImplPointer pairSearch(new AnalysisNeighborhoodPairSearchImpl(*this));
458 pairSearchList_.push_back(pairSearch);
462 bool AnalysisNeighborhoodSearchImpl::checkGridSearchEfficiency(bool bForce)
464 // Find the extent of the sphere in cells.
466 for (int dd = 0; dd < DIM; ++dd)
468 range[dd] = static_cast<int>(ceil(cutoff_ * invCellSize_[dd]));
471 // Calculate the fraction of cell pairs that need to be searched,
472 // and check that the cutoff is not too large for periodic dimensions.
473 real coveredCells = 1.0;
474 for (int dd = 0; dd < DIM; ++dd)
476 const int cellCount = ncelldim_[dd];
477 const int coveredCount = 2 * range[dd] + 1;
480 if (coveredCount > cellCount)
482 // Cutoff is too close to half the box size for grid searching
483 // (it is not possible to find a single shift for every pair of
487 coveredCells *= coveredCount;
491 if (range[dd] >= cellCount - 1)
493 range[dd] = cellCount - 1;
494 coveredCells *= cellCount;
496 else if (coveredCount > cellCount)
498 // The sum of range+1, range+2, ..., range+N/2, ... range+1.
499 coveredCells *= range[dd] +
500 static_cast<real>((cellCount + 1)/2 * (cellCount/2 + 1)) / cellCount;
504 // The sum of range+1, ..., 2*range+1, ..., 2*range+1, ... range+1.
505 coveredCells *= coveredCount -
506 static_cast<real>(range[dd] * (range[dd] + 1)) / cellCount;
510 // Magic constant that would need tuning for optimal performance:
511 // Don't do grid searching if nearly all cell pairs would anyways need to
512 // be looped through.
513 const int totalCellCount = ncelldim_[XX] * ncelldim_[YY] * ncelldim_[ZZ];
514 if (!bForce && coveredCells >= 0.5 * totalCellCount)
521 bool AnalysisNeighborhoodSearchImpl::initGridCells(
522 const matrix box, bool bSingleCell[DIM], int posCount)
524 // Determine the size of cubes where there are on average 10 positions.
525 // The loop takes care of cases where some of the box edges are shorter
526 // than the the desired cube size; in such cases, a single grid cell is
527 // used in these dimensions, and the cube size is determined only from the
528 // larger box vectors. Such boxes should be rare, but the bounding box
529 // approach can result in very flat boxes with certain types of selections
530 // (e.g., for interfacial systems or for small number of atoms).
531 real targetsize = 0.0;
532 int prevDimCount = 4;
537 for (int dd = 0; dd < DIM; ++dd)
539 const real boxSize = box[dd][dd];
540 if (boxSize < targetsize)
542 bSingleCell[dd] = true;
557 if (dimCount == 0 || dimCount == prevDimCount)
561 targetsize = pow(volume * 10 / posCount, static_cast<real>(1./dimCount));
562 prevDimCount = dimCount;
565 int totalCellCount = 1;
566 for (int dd = 0; dd < DIM; ++dd)
575 cellCount = std::max(1, static_cast<int>(box[dd][dd] / targetsize));
576 if (bGridPBC_[dd] && cellCount < 3)
581 totalCellCount *= cellCount;
582 ncelldim_[dd] = cellCount;
584 if (totalCellCount <= 3)
588 // Never decrease the size of the cell vector to avoid reallocating
589 // memory for the nested vectors. The actual size of the vector is not
590 // used outside this function.
591 if (cells_.size() < static_cast<size_t>(totalCellCount))
593 cells_.resize(totalCellCount);
595 for (int ci = 0; ci < totalCellCount; ++ci)
602 bool AnalysisNeighborhoodSearchImpl::initGrid(
603 const t_pbc &pbc, int posCount, const rvec x[], bool bUseBoundingBox,
614 bGridPBC_[XX] = false;
615 bGridPBC_[YY] = false;
616 bGridPBC_[ZZ] = false;
619 bGridPBC_[XX] = true;
620 bGridPBC_[YY] = true;
621 bGridPBC_[ZZ] = false;
624 bGridPBC_[XX] = true;
625 bGridPBC_[YY] = true;
626 bGridPBC_[ZZ] = true;
629 // Grid searching not supported for now with screw.
633 bool bSingleCell[DIM] = {false, false, bXY_};
635 copy_mat(pbc.box, box);
636 // TODO: In principle, we could use the bounding box for periodic
637 // dimensions as well if the bounding box is sufficiently far from the box
639 rvec origin, boundingBoxSize;
640 computeBoundingBox(posCount, x, origin, boundingBoxSize);
641 clear_rvec(gridOrigin_);
642 for (int dd = 0; dd < DIM; ++dd)
644 if (bUseBoundingBox && !bGridPBC_[dd] && !bSingleCell[dd])
646 gridOrigin_[dd] = origin[dd];
648 box[dd][dd] = boundingBoxSize[dd];
650 // TODO: In case the zero vector comes from the bounding box, this does
651 // not lead to a very efficient grid search, but that should be rare.
652 if (box[dd][dd] <= 0.0)
654 GMX_ASSERT(!bGridPBC_[dd], "Periodic box vector is zero");
655 bSingleCell[dd] = true;
661 if (!initGridCells(box, bSingleCell, posCount))
666 bTric_ = TRICLINIC(pbc.box);
667 for (int dd = 0; dd < DIM; ++dd)
669 cellSize_[dd] = box[dd][dd] / ncelldim_[dd];
672 invCellSize_[dd] = 0.0;
676 invCellSize_[dd] = 1.0 / cellSize_[dd];
681 cellShiftZY_ = box[ZZ][YY] * invCellSize_[YY];
682 cellShiftZX_ = box[ZZ][XX] * invCellSize_[XX];
683 cellShiftYX_ = box[YY][XX] * invCellSize_[XX];
685 return checkGridSearchEfficiency(bForce);
688 void AnalysisNeighborhoodSearchImpl::mapPointToGridCell(const rvec x,
693 rvec_sub(x, gridOrigin_, xtmp);
694 // The reverse order is necessary for triclinic cells: shifting in Z may
695 // modify also X and Y, and shifting in Y may modify X, so the mapping to
696 // a rectangular grid needs to be done in this order.
697 for (int dd = DIM - 1; dd >= 0; --dd)
699 real cellIndex = xtmp[dd] * invCellSize_[dd];
702 const real cellCount = ncelldim_[dd];
703 while (cellIndex < 0)
705 cellIndex += cellCount;
706 rvec_inc(xtmp, pbc_.box[dd]);
708 while (cellIndex >= cellCount)
710 cellIndex -= cellCount;
711 rvec_dec(xtmp, pbc_.box[dd]);
714 cell[dd] = cellIndex;
716 copy_rvec(xtmp, xout);
719 int AnalysisNeighborhoodSearchImpl::getGridCellIndex(const ivec cell) const
721 GMX_ASSERT(cell[XX] >= 0 && cell[XX] < ncelldim_[XX],
722 "Grid cell X index out of range");
723 GMX_ASSERT(cell[YY] >= 0 && cell[YY] < ncelldim_[YY],
724 "Grid cell Y index out of range");
725 GMX_ASSERT(cell[ZZ] >= 0 && cell[ZZ] < ncelldim_[ZZ],
726 "Grid cell Z index out of range");
727 return cell[XX] + cell[YY] * ncelldim_[XX]
728 + cell[ZZ] * ncelldim_[XX] * ncelldim_[YY];
731 void AnalysisNeighborhoodSearchImpl::addToGridCell(const rvec cell, int i)
734 for (int dd = 0; dd < DIM; ++dd)
736 int cellIndex = static_cast<int>(floor(cell[dd]));
739 const int cellCount = ncelldim_[dd];
744 else if (cellIndex >= cellCount)
746 cellIndex = cellCount - 1;
749 icell[dd] = cellIndex;
751 const int ci = getGridCellIndex(icell);
752 cells_[ci].push_back(i);
755 void AnalysisNeighborhoodSearchImpl::initCellRange(
756 const rvec centerCell, ivec currCell, ivec upperBound, int dim) const
758 // TODO: Prune off cells that are completely outside the cutoff.
759 const real range = cutoff_ * invCellSize_[dim];
760 real startOffset = centerCell[dim] - range;
761 real endOffset = centerCell[dim] + range;
769 if (currCell[ZZ] < 0)
771 startOffset += cellShiftZY_;
772 endOffset += cellShiftZY_;
774 else if (currCell[ZZ] >= ncelldim_[ZZ])
776 startOffset -= cellShiftZY_;
777 endOffset -= cellShiftZY_;
781 if (currCell[ZZ] < 0)
783 startOffset += cellShiftZX_;
784 endOffset += cellShiftZX_;
786 else if (currCell[ZZ] >= ncelldim_[ZZ])
788 startOffset -= cellShiftZX_;
789 endOffset -= cellShiftZX_;
791 if (currCell[YY] < 0)
793 startOffset += cellShiftYX_;
794 endOffset += cellShiftYX_;
796 else if (currCell[YY] >= ncelldim_[YY])
798 startOffset -= cellShiftYX_;
799 endOffset -= cellShiftYX_;
804 // For non-periodic dimensions, clamp to the actual grid edges.
807 // If endOffset < 0 or startOffset > N, these may cause the whole
808 // test position/grid plane/grid row to be skipped.
813 const int cellCount = ncelldim_[dim];
814 if (endOffset > cellCount - 1)
816 endOffset = cellCount - 1;
819 currCell[dim] = static_cast<int>(floor(startOffset));
820 upperBound[dim] = static_cast<int>(floor(endOffset));
823 bool AnalysisNeighborhoodSearchImpl::nextCell(
824 const rvec centerCell, ivec cell, ivec upperBound) const
831 if (cell[dim] > upperBound[dim])
836 for (int d = dim - 1; d >= 0; --d)
838 initCellRange(centerCell, cell, upperBound, d);
839 if (cell[d] > upperBound[d])
850 int AnalysisNeighborhoodSearchImpl::shiftCell(const ivec cell, rvec shift) const
853 copy_ivec(cell, shiftedCell);
856 for (int d = 0; d < DIM; ++d)
858 const int cellCount = ncelldim_[d];
861 // A single shift may not be sufficient if the cell must be shifted
862 // in more than one dimension, although for each individual
863 // dimension it would be.
864 while (shiftedCell[d] < 0)
866 shiftedCell[d] += cellCount;
867 rvec_inc(shift, pbc_.box[d]);
869 while (shiftedCell[d] >= cellCount)
871 shiftedCell[d] -= cellCount;
872 rvec_dec(shift, pbc_.box[d]);
877 return getGridCellIndex(shiftedCell);
880 void AnalysisNeighborhoodSearchImpl::init(
881 AnalysisNeighborhood::SearchMode mode,
882 bool bUseBoundingBox,
884 const t_blocka *excls,
886 const AnalysisNeighborhoodPositions &positions)
888 GMX_RELEASE_ASSERT(positions.index_ == -1,
889 "Individual indexed positions not supported as reference");
891 if (bXY_ && pbc != NULL && pbc->ePBC != epbcNONE)
893 if (pbc->ePBC != epbcXY && pbc->ePBC != epbcXYZ)
895 std::string message =
896 formatString("Computations in the XY plane are not supported with PBC type '%s'",
898 GMX_THROW(NotImplementedError(message));
900 if (pbc->ePBC == epbcXYZ &&
901 (std::fabs(pbc->box[ZZ][XX]) > GMX_REAL_EPS*pbc->box[ZZ][ZZ] ||
902 std::fabs(pbc->box[ZZ][YY]) > GMX_REAL_EPS*pbc->box[ZZ][ZZ]))
904 GMX_THROW(NotImplementedError("Computations in the XY plane are not supported when the last box vector is not parallel to the Z axis"));
906 // Use a single grid cell in Z direction.
908 copy_mat(pbc->box, box);
910 set_pbc(&pbc_, epbcXY, box);
912 else if (pbc != NULL)
918 pbc_.ePBC = epbcNONE;
921 nref_ = positions.count_;
922 if (mode == AnalysisNeighborhood::eSearchMode_Simple)
928 bGrid_ = initGrid(pbc_, positions.count_, positions.x_, bUseBoundingBox,
929 mode == AnalysisNeighborhood::eSearchMode_Grid);
931 refIndices_ = positions.indices_;
934 xrefAlloc_.resize(nref_);
935 xref_ = as_rvec_array(&xrefAlloc_[0]);
937 for (int i = 0; i < nref_; ++i)
939 const int ii = (refIndices_ != NULL) ? refIndices_[i] : i;
941 mapPointToGridCell(positions.x_[ii], refcell, xrefAlloc_[i]);
942 addToGridCell(refcell, i);
945 else if (refIndices_ != NULL)
947 xrefAlloc_.resize(nref_);
948 xref_ = as_rvec_array(&xrefAlloc_[0]);
949 for (int i = 0; i < nref_; ++i)
951 copy_rvec(positions.x_[refIndices_[i]], xrefAlloc_[i]);
956 xref_ = positions.x_;
959 refExclusionIds_ = NULL;
962 // TODO: Check that the IDs are ascending, or remove the limitation.
963 refExclusionIds_ = positions.exclusionIds_;
964 GMX_RELEASE_ASSERT(refExclusionIds_ != NULL,
965 "Exclusion IDs must be set for reference positions "
966 "when exclusions are enabled");
970 /********************************************************************
971 * AnalysisNeighborhoodPairSearchImpl
974 void AnalysisNeighborhoodPairSearchImpl::reset(int testIndex)
976 testIndex_ = testIndex;
977 if (testIndex_ >= 0 && testIndex_ < testPosCount_)
980 (testIndices_ != NULL ? testIndices_[testIndex] : testIndex);
983 search_.mapPointToGridCell(testPositions_[index], testcell_, xtest_);
984 search_.initCellRange(testcell_, currCell_, cellBound_, ZZ);
985 search_.initCellRange(testcell_, currCell_, cellBound_, YY);
986 search_.initCellRange(testcell_, currCell_, cellBound_, XX);
990 copy_rvec(testPositions_[index], xtest_);
992 if (search_.excls_ != NULL)
994 const int exclIndex = testExclusionIds_[index];
995 if (exclIndex < search_.excls_->nr)
997 const int startIndex = search_.excls_->index[exclIndex];
998 nexcl_ = search_.excls_->index[exclIndex + 1] - startIndex;
999 excl_ = &search_.excls_->a[startIndex];
1010 clear_rvec(prevdx_);
1015 void AnalysisNeighborhoodPairSearchImpl::nextTestPosition()
1017 if (testIndex_ < testPosCount_)
1024 bool AnalysisNeighborhoodPairSearchImpl::isExcluded(int j)
1026 if (exclind_ < nexcl_)
1029 (search_.refIndices_ != NULL ? search_.refIndices_[j] : j);
1030 const int refId = search_.refExclusionIds_[index];
1031 while (exclind_ < nexcl_ && excl_[exclind_] < refId)
1035 if (exclind_ < nexcl_ && refId == excl_[exclind_])
1044 void AnalysisNeighborhoodPairSearchImpl::startSearch(
1045 const AnalysisNeighborhoodPositions &positions)
1047 testPosCount_ = positions.count_;
1048 testPositions_ = positions.x_;
1049 testExclusionIds_ = positions.exclusionIds_;
1050 testIndices_ = positions.indices_;
1051 GMX_RELEASE_ASSERT(search_.excls_ == NULL || testExclusionIds_ != NULL,
1052 "Exclusion IDs must be set when exclusions are enabled");
1053 if (positions.index_ < 0)
1059 // Somewhat of a hack: setup the array such that only the last position
1061 testPosCount_ = positions.index_ + 1;
1062 reset(positions.index_);
1066 template <class Action>
1067 bool AnalysisNeighborhoodPairSearchImpl::searchNext(Action action)
1069 while (testIndex_ < testPosCount_)
1073 int cai = prevcai_ + 1;
1078 const int ci = search_.shiftCell(currCell_, shift);
1079 const int cellSize = static_cast<int>(search_.cells_[ci].size());
1080 for (; cai < cellSize; ++cai)
1082 const int i = search_.cells_[ci][cai];
1088 rvec_sub(search_.xref_[i], xtest_, dx);
1089 rvec_sub(dx, shift, dx);
1092 ? dx[XX]*dx[XX] + dx[YY]*dx[YY]
1094 if (r2 <= search_.cutoff2_)
1096 if (action(i, r2, dx))
1101 copy_rvec(dx, prevdx_);
1109 while (search_.nextCell(testcell_, currCell_, cellBound_));
1113 for (int i = previ_ + 1; i < search_.nref_; ++i)
1120 if (search_.pbc_.ePBC != epbcNONE)
1122 pbc_dx(&search_.pbc_, search_.xref_[i], xtest_, dx);
1126 rvec_sub(search_.xref_[i], xtest_, dx);
1130 ? dx[XX]*dx[XX] + dx[YY]*dx[YY]
1132 if (r2 <= search_.cutoff2_)
1134 if (action(i, r2, dx))
1138 copy_rvec(dx, prevdx_);
1149 void AnalysisNeighborhoodPairSearchImpl::initFoundPair(
1150 AnalysisNeighborhoodPair *pair) const
1154 *pair = AnalysisNeighborhoodPair();
1158 *pair = AnalysisNeighborhoodPair(previ_, testIndex_, prevr2_, prevdx_);
1162 } // namespace internal
1168 * Search action to find the next neighbor.
1170 * Used as the action for AnalysisNeighborhoodPairSearchImpl::searchNext() to
1171 * find the next neighbor.
1173 * Simply breaks the loop on the first found neighbor.
1175 bool withinAction(int /*i*/, real /*r2*/, const rvec /* dx */)
1181 * Search action find the minimum distance.
1183 * Used as the action for AnalysisNeighborhoodPairSearchImpl::searchNext() to
1184 * find the nearest neighbor.
1186 * With this action, AnalysisNeighborhoodPairSearchImpl::searchNext() always
1187 * returns false, and the output is put into the variables passed by pointer
1188 * into the constructor. If no neighbors are found, the output variables are
1189 * not modified, i.e., the caller must initialize them.
1195 * Initializes the action with given output locations.
1197 * \param[out] closestPoint Index of the closest reference location.
1198 * \param[out] minDist2 Minimum distance squared.
1199 * \param[out] dx Shortest distance vector.
1201 * The constructor call does not modify the pointed values, but only
1202 * stores the pointers for later use.
1203 * See the class description for additional semantics.
1205 MindistAction(int *closestPoint, real *minDist2, rvec *dx)
1206 : closestPoint_(*closestPoint), minDist2_(*minDist2), dx_(*dx)
1210 //! Processes a neighbor to find the nearest point.
1211 bool operator()(int i, real r2, const rvec dx)
1227 GMX_DISALLOW_ASSIGN(MindistAction);
1232 /********************************************************************
1233 * AnalysisNeighborhood::Impl
1236 class AnalysisNeighborhood::Impl
1239 typedef AnalysisNeighborhoodSearch::ImplPointer SearchImplPointer;
1240 typedef std::vector<SearchImplPointer> SearchList;
1243 : cutoff_(0), excls_(NULL), mode_(eSearchMode_Automatic),
1244 bUseBoundingBox_(true), bXY_(false)
1249 SearchList::const_iterator i;
1250 for (i = searchList_.begin(); i != searchList_.end(); ++i)
1252 GMX_RELEASE_ASSERT(i->unique(),
1253 "Dangling AnalysisNeighborhoodSearch reference");
1257 SearchImplPointer getSearch();
1259 tMPI::mutex createSearchMutex_;
1260 SearchList searchList_;
1262 const t_blocka *excls_;
1264 bool bUseBoundingBox_;
1268 AnalysisNeighborhood::Impl::SearchImplPointer
1269 AnalysisNeighborhood::Impl::getSearch()
1271 tMPI::lock_guard<tMPI::mutex> lock(createSearchMutex_);
1272 // TODO: Consider whether this needs to/can be faster, e.g., by keeping a
1273 // separate pool of unused search objects.
1274 SearchList::const_iterator i;
1275 for (i = searchList_.begin(); i != searchList_.end(); ++i)
1282 SearchImplPointer search(new internal::AnalysisNeighborhoodSearchImpl(cutoff_));
1283 searchList_.push_back(search);
1287 /********************************************************************
1288 * AnalysisNeighborhood
1291 AnalysisNeighborhood::AnalysisNeighborhood()
1296 AnalysisNeighborhood::~AnalysisNeighborhood()
1300 void AnalysisNeighborhood::setCutoff(real cutoff)
1302 GMX_RELEASE_ASSERT(impl_->searchList_.empty(),
1303 "Changing the cutoff after initSearch() not currently supported");
1304 impl_->cutoff_ = cutoff;
1307 void AnalysisNeighborhood::setUseBoundingBox(bool bUseBoundingBox)
1309 impl_->bUseBoundingBox_ = bUseBoundingBox;
1312 void AnalysisNeighborhood::setXYMode(bool bXY)
1317 void AnalysisNeighborhood::setTopologyExclusions(const t_blocka *excls)
1319 GMX_RELEASE_ASSERT(impl_->searchList_.empty(),
1320 "Changing the exclusions after initSearch() not currently supported");
1321 impl_->excls_ = excls;
1324 void AnalysisNeighborhood::setMode(SearchMode mode)
1326 impl_->mode_ = mode;
1329 AnalysisNeighborhood::SearchMode AnalysisNeighborhood::mode() const
1331 return impl_->mode_;
1334 AnalysisNeighborhoodSearch
1335 AnalysisNeighborhood::initSearch(const t_pbc *pbc,
1336 const AnalysisNeighborhoodPositions &positions)
1338 Impl::SearchImplPointer search(impl_->getSearch());
1339 search->init(mode(), impl_->bUseBoundingBox_, impl_->bXY_, impl_->excls_,
1341 return AnalysisNeighborhoodSearch(search);
1344 /********************************************************************
1345 * AnalysisNeighborhoodSearch
1348 AnalysisNeighborhoodSearch::AnalysisNeighborhoodSearch()
1352 AnalysisNeighborhoodSearch::AnalysisNeighborhoodSearch(const ImplPointer &impl)
1357 void AnalysisNeighborhoodSearch::reset()
1362 AnalysisNeighborhood::SearchMode AnalysisNeighborhoodSearch::mode() const
1364 GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1365 return (impl_->usesGridSearch()
1366 ? AnalysisNeighborhood::eSearchMode_Grid
1367 : AnalysisNeighborhood::eSearchMode_Simple);
1370 bool AnalysisNeighborhoodSearch::isWithin(
1371 const AnalysisNeighborhoodPositions &positions) const
1373 GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1374 internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1375 pairSearch.startSearch(positions);
1376 return pairSearch.searchNext(&withinAction);
1379 real AnalysisNeighborhoodSearch::minimumDistance(
1380 const AnalysisNeighborhoodPositions &positions) const
1382 GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1383 internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1384 pairSearch.startSearch(positions);
1385 real minDist2 = impl_->cutoffSquared();
1386 int closestPoint = -1;
1387 rvec dx = {0.0, 0.0, 0.0};
1388 MindistAction action(&closestPoint, &minDist2, &dx);
1389 (void)pairSearch.searchNext(action);
1390 return sqrt(minDist2);
1393 AnalysisNeighborhoodPair
1394 AnalysisNeighborhoodSearch::nearestPoint(
1395 const AnalysisNeighborhoodPositions &positions) const
1397 GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1398 internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1399 pairSearch.startSearch(positions);
1400 real minDist2 = impl_->cutoffSquared();
1401 int closestPoint = -1;
1402 rvec dx = {0.0, 0.0, 0.0};
1403 MindistAction action(&closestPoint, &minDist2, &dx);
1404 (void)pairSearch.searchNext(action);
1405 return AnalysisNeighborhoodPair(closestPoint, 0, minDist2, dx);
1408 AnalysisNeighborhoodPairSearch
1409 AnalysisNeighborhoodSearch::startPairSearch(
1410 const AnalysisNeighborhoodPositions &positions) const
1412 GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1413 Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
1414 pairSearch->startSearch(positions);
1415 return AnalysisNeighborhoodPairSearch(pairSearch);
1418 /********************************************************************
1419 * AnalysisNeighborhoodPairSearch
1422 AnalysisNeighborhoodPairSearch::AnalysisNeighborhoodPairSearch(
1423 const ImplPointer &impl)
1428 bool AnalysisNeighborhoodPairSearch::findNextPair(AnalysisNeighborhoodPair *pair)
1430 bool bFound = impl_->searchNext(&withinAction);
1431 impl_->initFoundPair(pair);
1435 void AnalysisNeighborhoodPairSearch::skipRemainingPairsForTestPosition()
1437 impl_->nextTestPosition();