Use ListOfLists in gmx_mtop_t and gmx_localtop_t
[alexxy/gromacs.git] / src / gromacs / selection / nbsearch.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements neighborhood searching for analysis (from nbsearch.h).
38  *
39  * High-level overview of the algorithm is at \ref page_analysisnbsearch.
40  *
41  * \todo
42  * The grid implementation could still be optimized in several different ways:
43  *   - A better heuristic for selecting the grid size or falling back to a
44  *     simple all-pairs search.
45  *   - A multi-level grid implementation could be used to be able to use small
46  *     grids for short cutoffs with very inhomogeneous particle distributions
47  *     without a memory cost.
48  *
49  * \author Teemu Murtola <teemu.murtola@gmail.com>
50  * \ingroup module_selection
51  */
52 #include "gmxpre.h"
53
54 #include "nbsearch.h"
55
56 #include <cmath>
57 #include <cstring>
58
59 #include <algorithm>
60 #include <vector>
61
62 #include "gromacs/math/functions.h"
63 #include "gromacs/math/vec.h"
64 #include "gromacs/pbcutil/pbc.h"
65 #include "gromacs/utility/arrayref.h"
66 #include "gromacs/utility/exceptions.h"
67 #include "gromacs/utility/gmxassert.h"
68 #include "gromacs/utility/listoflists.h"
69 #include "gromacs/utility/mutex.h"
70 #include "gromacs/utility/stringutil.h"
71
72 #include "position.h"
73
74 namespace gmx
75 {
76
77 namespace
78 {
79
80 /*! \brief
81  * Computes the bounding box for a set of positions.
82  *
83  * \param[in]  posCount Number of positions in \p x.
84  * \param[in]  x        Positions to compute the bounding box for.
85  * \param[out] origin   Origin of the bounding box.
86  * \param[out] size     Size of the bounding box.
87  */
88 void computeBoundingBox(int posCount, const rvec x[], rvec origin, rvec size)
89 {
90     rvec maxBound;
91     copy_rvec(x[0], origin);
92     copy_rvec(x[0], maxBound);
93     for (int i = 1; i < posCount; ++i)
94     {
95         for (int d = 0; d < DIM; ++d)
96         {
97             if (origin[d] > x[i][d])
98             {
99                 origin[d] = x[i][d];
100             }
101             if (maxBound[d] < x[i][d])
102             {
103                 maxBound[d] = x[i][d];
104             }
105         }
106     }
107     rvec_sub(maxBound, origin, size);
108 }
109
110 } // namespace
111
112 namespace internal
113 {
114
115 /********************************************************************
116  * Implementation class declarations
117  */
118
119 class AnalysisNeighborhoodSearchImpl
120 {
121 public:
122     typedef AnalysisNeighborhoodPairSearch::ImplPointer PairSearchImplPointer;
123     typedef std::vector<PairSearchImplPointer>          PairSearchList;
124     typedef std::vector<std::vector<int>>               CellList;
125
126     explicit AnalysisNeighborhoodSearchImpl(real cutoff);
127     ~AnalysisNeighborhoodSearchImpl();
128
129     /*! \brief
130      * Initializes the search with a given box and reference positions.
131      *
132      * \param[in] mode            Search mode to use.
133      * \param[in] bXY             Whether to use 2D searching.
134      * \param[in] excls           Exclusions.
135      * \param[in] pbc             PBC information.
136      * \param[in] positions       Set of reference positions.
137      */
138     void                  init(AnalysisNeighborhood::SearchMode     mode,
139                                bool                                 bXY,
140                                const ListOfLists<int>*              excls,
141                                const t_pbc*                         pbc,
142                                const AnalysisNeighborhoodPositions& positions);
143     PairSearchImplPointer getPairSearch();
144
145     real cutoffSquared() const { return cutoff2_; }
146     bool usesGridSearch() const { return bGrid_; }
147
148 private:
149     /*! \brief
150      * Determines a suitable grid size and sets up the cells.
151      *
152      * \param[in] box          Box vectors (should not have zero vectors).
153      * \param[in] bSingleCell  If `true`, the corresponding dimension will
154      *     be forced to use a single cell.
155      * \param[in] posCount     Number of positions that will be put on the
156      *     grid.
157      * \returns   `false` if grid search is not suitable.
158      */
159     bool initGridCells(const matrix box, bool bSingleCell[DIM], int posCount);
160     /*! \brief
161      * Sets ua a search grid for a given box.
162      *
163      * \param[in] pbc      Information about the box.
164      * \param[in] posCount Number of positions in \p x.
165      * \param[in] x        Reference positions that will be put on the grid.
166      * \param[in] bForce   If `true`, grid searching will be used if at all
167      *     possible, even if a simple search might give better performance.
168      * \returns   `false` if grid search is not suitable.
169      */
170     bool initGrid(const t_pbc& pbc, int posCount, const rvec x[], bool bForce);
171     /*! \brief
172      * Maps a point into a grid cell.
173      *
174      * \param[in]  x    Point to map.
175      * \param[out] cell Fractional cell coordinates of \p x on the grid.
176      * \param[out] xout Coordinates to use.
177      *
178      * \p xout will be within the rectangular unit cell in dimensions where
179      * the grid is periodic.  For other dimensions, both \p xout and
180      * \p cell can be outside the grid/unit cell.
181      */
182     void mapPointToGridCell(const rvec x, rvec cell, rvec xout) const;
183     /*! \brief
184      * Calculates linear index of a grid cell.
185      *
186      * \param[in]  cell Cell indices (must be within the grid).
187      * \returns    Linear index of \p cell.
188      */
189     int getGridCellIndex(const ivec cell) const;
190     /*! \brief
191      * Calculates linear index of a grid cell from fractional coordinates.
192      *
193      * \param[in]  cell Cell indices (must be within the grid).
194      * \returns    Linear index of \p cell.
195      */
196     int getGridCellIndex(const rvec cell) const;
197     /*! \brief
198      * Adds an index into a grid cell.
199      *
200      * \param[in]  cell Fractional cell coordinates into which \p i should
201      *     be added.
202      * \param[in]  i    Index to add.
203      *
204      * \p cell should satisfy the conditions that \p mapPointToGridCell()
205      * produces.
206      */
207     void addToGridCell(const rvec cell, int i);
208     /*! \brief
209      * Initializes a cell pair loop for a dimension.
210      *
211      * \param[in]     centerCell Fractional cell coordiates of the particle
212      *     for which pairs are being searched.
213      * \param[in,out] cell       Current/initial cell to loop over.
214      * \param[in,out] upperBound Last cell to loop over.
215      * \param[in]     dim        Dimension to initialize in this call.
216      *
217      * Initializes `cell[dim]` and `upperBound[dim]` for looping over
218      * neighbors of a particle at position given by \p centerCell.
219      * If 'dim != ZZ`, `cell[d]` (`d > dim`) set the plane/row of cells
220      * for which the loop is initialized.  The loop should then go from
221      * `cell[dim]` until `upperBound[dim]`, inclusive.
222      * `cell[d]` with `d < dim` or `upperBound[d]` with `d != dim` are not
223      * modified by this function.
224      *
225      * `cell` and `upperBound` may be outside the grid for periodic
226      * dimensions and need to be shifted separately: to simplify the
227      * looping, the range is always (roughly) symmetric around the value in
228      * `centerCell`.
229      */
230     void initCellRange(const rvec centerCell, ivec cell, ivec upperBound, int dim) const;
231     /*! \brief
232      * Computes the extent of the cutoff sphere on a particular cell edge.
233      *
234      * \param[in]     centerCell Fractional cell coordiates of the particle
235      *     for which pairs are being searched.
236      * \param[in]     cell       Current cell (for dimensions `>dim`).
237      * \param[in]     dim        Dimension to compute in this call.
238      * \returns       Fractional extent of the cutoff sphere when looping
239      *    over cells in dimension `dim`, for `cell[d]` (`d > dim`).
240      *
241      * Input parameters are as for initCellRange(), except that if `cell`
242      * is over a periodic boundary from `centerCell`, triclinic shifts
243      * should have been applied to `centerCell` X/Y components.
244      */
245     real computeCutoffExtent(RVec centerCell, const ivec cell, int dim) const;
246     /*! \brief
247      * Advances cell pair loop to the next cell.
248      *
249      * \param[in]     centerCell Fractional cell coordiates of the particle
250      *     for which pairs are being searched.
251      * \param[in,out] cell       Current (in)/next (out) cell in the loop.
252      * \param[in,out] upperBound Last cell in the loop for each dimension.
253      */
254     bool nextCell(const rvec centerCell, ivec cell, ivec upperBound) const;
255     /*! \brief
256      * Calculates the index and shift of a grid cell during looping.
257      *
258      * \param[in]  cell       Unshifted cell index.
259      * \param[out] shift      Shift to apply to get the periodic distance
260      *     for distances between the cells.
261      * \returns    Grid cell index corresponding to `cell`.
262      */
263     int shiftCell(const ivec cell, rvec shift) const;
264
265     //! Whether to try grid searching.
266     bool bTryGrid_;
267     //! The cutoff.
268     real cutoff_;
269     //! The cutoff squared.
270     real cutoff2_;
271     //! Whether to do searching in XY plane only.
272     bool bXY_;
273
274     //! Number of reference points for the current frame.
275     int nref_;
276     //! Reference point positions.
277     const rvec* xref_;
278     //! Reference position exclusion IDs.
279     const int* refExclusionIds_;
280     //! Reference position indices (NULL if no indices).
281     const int* refIndices_;
282     //! Exclusions.
283     const ListOfLists<int>* excls_;
284     //! PBC data.
285     t_pbc pbc_;
286
287     //! Whether grid searching is actually used for the current positions.
288     bool bGrid_;
289     //! false if the box is rectangular.
290     bool bTric_;
291     //! Whether the grid is periodic in a dimension.
292     bool bGridPBC_[DIM];
293     //! Array for storing in-unit-cell reference positions.
294     std::vector<RVec> xrefAlloc_;
295     //! Origin of the grid (zero for periodic dimensions).
296     rvec gridOrigin_;
297     //! Size of a single grid cell.
298     rvec cellSize_;
299     //! Inverse of \p cellSize_. Zero for dimensions where grid is not used.
300     rvec invCellSize_;
301     /*! \brief
302      * Shift in cell coordinates (for triclinic boxes) in X when crossing
303      * the Z periodic boundary.
304      */
305     real cellShiftZX_;
306     /*! \brief
307      * Shift in cell coordinates (for triclinic boxes) in Y when crossing
308      * the Z periodic boundary.
309      */
310     real cellShiftZY_;
311     /*! \brief
312      * Shift in cell coordinates (for triclinic boxes) in X when crossing
313      * the Y periodic boundary.
314      */
315     real cellShiftYX_;
316     //! Number of cells along each dimension.
317     ivec ncelldim_;
318     //! Data structure to hold the grid cell contents.
319     CellList cells_;
320
321     Mutex          createPairSearchMutex_;
322     PairSearchList pairSearchList_;
323
324     friend class AnalysisNeighborhoodPairSearchImpl;
325
326     GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodSearchImpl);
327 };
328
329 class AnalysisNeighborhoodPairSearchImpl
330 {
331 public:
332     explicit AnalysisNeighborhoodPairSearchImpl(const AnalysisNeighborhoodSearchImpl& search) :
333         search_(search)
334     {
335         selfSearchMode_   = false;
336         testPosCount_     = 0;
337         testPositions_    = nullptr;
338         testExclusionIds_ = nullptr;
339         testIndices_      = nullptr;
340         clear_rvec(xtest_);
341         clear_rvec(testcell_);
342         clear_ivec(currCell_);
343         clear_ivec(cellBound_);
344         reset(-1);
345     }
346
347     //! Initializes a search to find reference positions neighboring \p x.
348     void startSearch(const AnalysisNeighborhoodPositions& positions);
349     //! Initializes a search to find reference position pairs.
350     void startSelfSearch();
351     //! Searches for the next neighbor.
352     template<class Action>
353     bool searchNext(Action action);
354     //! Initializes a pair representing the pair found by searchNext().
355     void initFoundPair(AnalysisNeighborhoodPair* pair) const;
356     //! Advances to the next test position, skipping any remaining pairs.
357     void nextTestPosition();
358
359 private:
360     //! Clears the loop indices.
361     void reset(int testIndex);
362     //! Checks whether a reference positiong should be excluded.
363     bool isExcluded(int j);
364
365     //! Parent search object.
366     const AnalysisNeighborhoodSearchImpl& search_;
367     //! Whether we are searching for ref-ref pairs.
368     bool selfSearchMode_;
369     //! Number of test positions.
370     int testPosCount_;
371     //! Reference to the test positions.
372     const rvec* testPositions_;
373     //! Reference to the test exclusion indices.
374     const int* testExclusionIds_;
375     //! Reference to the test position indices.
376     const int* testIndices_;
377     //! Exclusions for current test particle.
378     ArrayRef<const int> excl_;
379     //! Index of the currently active test position in \p testPositions_.
380     int testIndex_;
381     //! Stores test position during a pair loop.
382     rvec xtest_;
383     //! Stores the previous returned position during a pair loop.
384     int previ_;
385     //! Stores the pair distance corresponding to previ_.
386     real prevr2_;
387     //! Stores the shortest distance vector corresponding to previ_.
388     rvec prevdx_;
389     //! Stores the current exclusion index during loops.
390     int exclind_;
391     //! Stores the fractional test particle cell location during loops.
392     rvec testcell_;
393     //! Stores the cell index corresponding to testcell_.
394     int testCellIndex_;
395     //! Stores the current cell during pair loops.
396     ivec currCell_;
397     //! Stores the current loop upper bounds for each dimension during pair loops.
398     ivec cellBound_;
399     //! Stores the index within the current cell during pair loops.
400     int prevcai_;
401
402     GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodPairSearchImpl);
403 };
404
405 /********************************************************************
406  * AnalysisNeighborhoodSearchImpl
407  */
408
409 AnalysisNeighborhoodSearchImpl::AnalysisNeighborhoodSearchImpl(real cutoff)
410 {
411     bTryGrid_ = true;
412     cutoff_   = cutoff;
413     if (cutoff_ <= 0)
414     {
415         cutoff_ = cutoff2_ = GMX_REAL_MAX;
416         bTryGrid_          = false;
417     }
418     else
419     {
420         cutoff2_ = gmx::square(cutoff_);
421     }
422     bXY_             = false;
423     nref_            = 0;
424     xref_            = nullptr;
425     refExclusionIds_ = nullptr;
426     refIndices_      = nullptr;
427     std::memset(&pbc_, 0, sizeof(pbc_));
428
429     bGrid_        = false;
430     bTric_        = false;
431     bGridPBC_[XX] = true;
432     bGridPBC_[YY] = true;
433     bGridPBC_[ZZ] = true;
434
435     clear_rvec(gridOrigin_);
436     clear_rvec(cellSize_);
437     clear_rvec(invCellSize_);
438     clear_ivec(ncelldim_);
439 }
440
441 AnalysisNeighborhoodSearchImpl::~AnalysisNeighborhoodSearchImpl()
442 {
443     PairSearchList::const_iterator i;
444     for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
445     {
446         GMX_RELEASE_ASSERT(i->unique(), "Dangling AnalysisNeighborhoodPairSearch reference");
447     }
448 }
449
450 AnalysisNeighborhoodSearchImpl::PairSearchImplPointer AnalysisNeighborhoodSearchImpl::getPairSearch()
451 {
452     lock_guard<Mutex> lock(createPairSearchMutex_);
453     // TODO: Consider whether this needs to/can be faster, e.g., by keeping a
454     // separate pool of unused search objects.
455     PairSearchList::const_iterator i;
456     for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
457     {
458         if (i->unique())
459         {
460             return *i;
461         }
462     }
463     PairSearchImplPointer pairSearch(new AnalysisNeighborhoodPairSearchImpl(*this));
464     pairSearchList_.push_back(pairSearch);
465     return pairSearch;
466 }
467
468 bool AnalysisNeighborhoodSearchImpl::initGridCells(const matrix box, bool bSingleCell[DIM], int posCount)
469 {
470     // Determine the size of cubes where there are on average 10 positions.
471     // The loop takes care of cases where some of the box edges are shorter
472     // than the desired cube size; in such cases, a single grid cell is
473     // used in these dimensions, and the cube size is determined only from the
474     // larger box vectors.  Such boxes should be rare, but the bounding box
475     // approach can result in very flat boxes with certain types of selections
476     // (e.g., for interfacial systems or for small number of atoms).
477     real targetsize   = 0.0;
478     int  prevDimCount = 4;
479     while (true)
480     {
481         real volume   = 1.0;
482         int  dimCount = 3;
483         for (int dd = 0; dd < DIM; ++dd)
484         {
485             const real boxSize = box[dd][dd];
486             if (boxSize < targetsize)
487             {
488                 bSingleCell[dd] = true;
489                 if (bGridPBC_[dd])
490                 {
491                     // TODO: Consider if a fallback would be possible/better.
492                     return false;
493                 }
494             }
495             if (bSingleCell[dd])
496             {
497                 --dimCount;
498             }
499             else
500             {
501                 volume *= boxSize;
502             }
503         }
504         if (dimCount == 0 || dimCount == prevDimCount)
505         {
506             break;
507         }
508         targetsize   = pow(volume * 10 / posCount, static_cast<real>(1. / dimCount));
509         prevDimCount = dimCount;
510     }
511
512     int totalCellCount = 1;
513     for (int dd = 0; dd < DIM; ++dd)
514     {
515         int cellCount;
516         if (bSingleCell[dd])
517         {
518             cellCount = 1;
519         }
520         else
521         {
522             cellCount = std::max(1, static_cast<int>(box[dd][dd] / targetsize));
523             // TODO: If the cell count is one or two, it could be better to
524             // just fall back to bSingleCell[dd] = true.
525             if (bGridPBC_[dd] && cellCount < 3)
526             {
527                 return false;
528             }
529         }
530         totalCellCount *= cellCount;
531         ncelldim_[dd] = cellCount;
532     }
533     if (totalCellCount <= 3)
534     {
535         return false;
536     }
537     // Never decrease the size of the cell vector to avoid reallocating
538     // memory for the nested vectors.  The actual size of the vector is not
539     // used outside this function.
540     if (cells_.size() < static_cast<size_t>(totalCellCount))
541     {
542         cells_.resize(totalCellCount);
543     }
544     for (int ci = 0; ci < totalCellCount; ++ci)
545     {
546         cells_[ci].clear();
547     }
548     return true;
549 }
550
551 bool AnalysisNeighborhoodSearchImpl::initGrid(const t_pbc& pbc, int posCount, const rvec x[], bool bForce)
552 {
553     if (posCount == 0)
554     {
555         return false;
556     }
557
558     // TODO: Use this again (can be useful when tuning initGridCells()),
559     // or remove throughout.
560     GMX_UNUSED_VALUE(bForce);
561
562     switch (pbc.ePBC)
563     {
564         case epbcNONE:
565             bGridPBC_[XX] = false;
566             bGridPBC_[YY] = false;
567             bGridPBC_[ZZ] = false;
568             break;
569         case epbcXY:
570             bGridPBC_[XX] = true;
571             bGridPBC_[YY] = true;
572             bGridPBC_[ZZ] = false;
573             break;
574         case epbcXYZ:
575             bGridPBC_[XX] = true;
576             bGridPBC_[YY] = true;
577             bGridPBC_[ZZ] = true;
578             break;
579         default:
580             // Grid searching not supported for now with screw.
581             return false;
582     }
583
584     bool   bSingleCell[DIM] = { false, false, bXY_ };
585     matrix box;
586     copy_mat(pbc.box, box);
587     // TODO: In principle, we could use the bounding box for periodic
588     // dimensions as well if the bounding box is sufficiently far from the box
589     // edges.
590     rvec origin, boundingBoxSize;
591     computeBoundingBox(posCount, x, origin, boundingBoxSize);
592     clear_rvec(gridOrigin_);
593     for (int dd = 0; dd < DIM; ++dd)
594     {
595         if (!bGridPBC_[dd] && !bSingleCell[dd])
596         {
597             gridOrigin_[dd] = origin[dd];
598             clear_rvec(box[dd]);
599             box[dd][dd] = boundingBoxSize[dd];
600         }
601         // TODO: In case the zero vector comes from the bounding box, this does
602         // not lead to a very efficient grid search, but that should be rare.
603         if (box[dd][dd] <= 0.0)
604         {
605             GMX_ASSERT(!bGridPBC_[dd], "Periodic box vector is zero");
606             bSingleCell[dd] = true;
607             clear_rvec(box[dd]);
608             box[dd][dd] = 1.0;
609         }
610     }
611
612     if (!initGridCells(box, bSingleCell, posCount))
613     {
614         return false;
615     }
616
617     bTric_ = TRICLINIC(pbc.box);
618     for (int dd = 0; dd < DIM; ++dd)
619     {
620         cellSize_[dd] = box[dd][dd] / ncelldim_[dd];
621         if (bSingleCell[dd])
622         {
623             invCellSize_[dd] = 0.0;
624         }
625         else
626         {
627             invCellSize_[dd] = 1.0 / cellSize_[dd];
628             // TODO: It could be better to avoid this when determining the cell
629             // size, but this can still remain here as a fallback to avoid
630             // incorrect results.
631             if (std::ceil(2 * cutoff_ * invCellSize_[dd]) >= ncelldim_[dd])
632             {
633                 // Cutoff is too close to half the box size for grid searching
634                 // (it is not possible to find a single shift for every pair of
635                 // grid cells).
636                 return false;
637             }
638         }
639     }
640     if (bTric_)
641     {
642         cellShiftZY_ = box[ZZ][YY] * invCellSize_[YY];
643         cellShiftZX_ = box[ZZ][XX] * invCellSize_[XX];
644         cellShiftYX_ = box[YY][XX] * invCellSize_[XX];
645     }
646     return true;
647 }
648
649 void AnalysisNeighborhoodSearchImpl::mapPointToGridCell(const rvec x, rvec cell, rvec xout) const
650 {
651     rvec xtmp;
652     rvec_sub(x, gridOrigin_, xtmp);
653     // The reverse order is necessary for triclinic cells: shifting in Z may
654     // modify also X and Y, and shifting in Y may modify X, so the mapping to
655     // a rectangular grid needs to be done in this order.
656     for (int dd = DIM - 1; dd >= 0; --dd)
657     {
658         real cellIndex = xtmp[dd] * invCellSize_[dd];
659         if (bGridPBC_[dd])
660         {
661             const real cellCount = ncelldim_[dd];
662             while (cellIndex < 0)
663             {
664                 cellIndex += cellCount;
665                 rvec_inc(xtmp, pbc_.box[dd]);
666             }
667             while (cellIndex >= cellCount)
668             {
669                 cellIndex -= cellCount;
670                 rvec_dec(xtmp, pbc_.box[dd]);
671             }
672         }
673         cell[dd] = cellIndex;
674     }
675     copy_rvec(xtmp, xout);
676 }
677
678 int AnalysisNeighborhoodSearchImpl::getGridCellIndex(const ivec cell) const
679 {
680     GMX_ASSERT(cell[XX] >= 0 && cell[XX] < ncelldim_[XX], "Grid cell X index out of range");
681     GMX_ASSERT(cell[YY] >= 0 && cell[YY] < ncelldim_[YY], "Grid cell Y index out of range");
682     GMX_ASSERT(cell[ZZ] >= 0 && cell[ZZ] < ncelldim_[ZZ], "Grid cell Z index out of range");
683     return cell[XX] + cell[YY] * ncelldim_[XX] + cell[ZZ] * ncelldim_[XX] * ncelldim_[YY];
684 }
685
686 int AnalysisNeighborhoodSearchImpl::getGridCellIndex(const rvec cell) const
687 {
688     ivec icell;
689     for (int dd = 0; dd < DIM; ++dd)
690     {
691         int cellIndex = static_cast<int>(floor(cell[dd]));
692         if (!bGridPBC_[dd])
693         {
694             const int cellCount = ncelldim_[dd];
695             if (cellIndex < 0)
696             {
697                 cellIndex = 0;
698             }
699             else if (cellIndex >= cellCount)
700             {
701                 cellIndex = cellCount - 1;
702             }
703         }
704         icell[dd] = cellIndex;
705     }
706     return getGridCellIndex(icell);
707 }
708
709 void AnalysisNeighborhoodSearchImpl::addToGridCell(const rvec cell, int i)
710 {
711     const int ci = getGridCellIndex(cell);
712     cells_[ci].push_back(i);
713 }
714
715 void AnalysisNeighborhoodSearchImpl::initCellRange(const rvec centerCell, ivec currCell, ivec upperBound, int dim) const
716 {
717     RVec shiftedCenter(centerCell);
718     // Shift the center to the cell coordinates of currCell, so that
719     // computeCutoffExtent() can assume simple rectangular grid.
720     if (bTric_)
721     {
722         if (dim == XX)
723         {
724             if (currCell[ZZ] < 0)
725             {
726                 shiftedCenter[XX] += cellShiftZX_;
727             }
728             else if (currCell[ZZ] >= ncelldim_[ZZ])
729             {
730                 shiftedCenter[XX] -= cellShiftZX_;
731             }
732             if (currCell[YY] < 0)
733             {
734                 shiftedCenter[XX] += cellShiftYX_;
735             }
736             else if (currCell[YY] >= ncelldim_[YY])
737             {
738                 shiftedCenter[XX] -= cellShiftYX_;
739             }
740         }
741         if (dim == XX || dim == YY)
742         {
743             if (currCell[ZZ] < 0)
744             {
745                 shiftedCenter[YY] += cellShiftZY_;
746             }
747             else if (currCell[ZZ] >= ncelldim_[ZZ])
748             {
749                 shiftedCenter[YY] -= cellShiftZY_;
750             }
751         }
752     }
753     const real range       = computeCutoffExtent(shiftedCenter, currCell, dim) * invCellSize_[dim];
754     real       startOffset = shiftedCenter[dim] - range;
755     real       endOffset   = shiftedCenter[dim] + range;
756     // For non-periodic dimensions, clamp to the actual grid edges.
757     if (!bGridPBC_[dim])
758     {
759         // If endOffset < 0 or startOffset > N, these may cause the whole
760         // test position/grid plane/grid row to be skipped.
761         if (startOffset < 0)
762         {
763             startOffset = 0;
764         }
765         const int cellCount = ncelldim_[dim];
766         if (endOffset > cellCount - 1)
767         {
768             endOffset = cellCount - 1;
769         }
770     }
771     currCell[dim]   = static_cast<int>(floor(startOffset));
772     upperBound[dim] = static_cast<int>(floor(endOffset));
773 }
774
775 real AnalysisNeighborhoodSearchImpl::computeCutoffExtent(const RVec centerCell, const ivec cell, int dim) const
776 {
777     if (dim == ZZ)
778     {
779         return cutoff_;
780     }
781
782     real dist2 = 0;
783     for (int d = dim + 1; d < DIM; ++d)
784     {
785         real dimDist = cell[d] - centerCell[d];
786         if (dimDist < -1)
787         {
788             dimDist += 1;
789         }
790         else if (dimDist <= 0)
791         {
792             continue;
793         }
794         dist2 += dimDist * dimDist * cellSize_[d] * cellSize_[d];
795     }
796     if (dist2 >= cutoff2_)
797     {
798         return 0;
799     }
800     return std::sqrt(cutoff2_ - dist2);
801 }
802
803 bool AnalysisNeighborhoodSearchImpl::nextCell(const rvec centerCell, ivec cell, ivec upperBound) const
804 {
805     int dim = 0;
806     while (dim < DIM)
807     {
808     next:
809         ++cell[dim];
810         if (cell[dim] > upperBound[dim])
811         {
812             ++dim;
813             continue;
814         }
815         for (int d = dim - 1; d >= 0; --d)
816         {
817             initCellRange(centerCell, cell, upperBound, d);
818             if (cell[d] > upperBound[d])
819             {
820                 dim = d + 1;
821                 goto next;
822             }
823         }
824         return true;
825     }
826     return false;
827 }
828
829 int AnalysisNeighborhoodSearchImpl::shiftCell(const ivec cell, rvec shift) const
830 {
831     ivec shiftedCell;
832     copy_ivec(cell, shiftedCell);
833
834     clear_rvec(shift);
835     for (int d = 0; d < DIM; ++d)
836     {
837         const int cellCount = ncelldim_[d];
838         if (bGridPBC_[d])
839         {
840             // A single shift may not be sufficient if the cell must be shifted
841             // in more than one dimension, although for each individual
842             // dimension it would be.
843             while (shiftedCell[d] < 0)
844             {
845                 shiftedCell[d] += cellCount;
846                 rvec_inc(shift, pbc_.box[d]);
847             }
848             while (shiftedCell[d] >= cellCount)
849             {
850                 shiftedCell[d] -= cellCount;
851                 rvec_dec(shift, pbc_.box[d]);
852             }
853         }
854     }
855
856     return getGridCellIndex(shiftedCell);
857 }
858
859 void AnalysisNeighborhoodSearchImpl::init(AnalysisNeighborhood::SearchMode     mode,
860                                           bool                                 bXY,
861                                           const ListOfLists<int>*              excls,
862                                           const t_pbc*                         pbc,
863                                           const AnalysisNeighborhoodPositions& positions)
864 {
865     GMX_RELEASE_ASSERT(positions.index_ == -1,
866                        "Individual indexed positions not supported as reference");
867     bXY_ = bXY;
868     if (bXY_ && pbc != nullptr && pbc->ePBC != epbcNONE)
869     {
870         if (pbc->ePBC != epbcXY && pbc->ePBC != epbcXYZ)
871         {
872             std::string message = formatString(
873                     "Computations in the XY plane are not supported with PBC type '%s'",
874                     epbc_names[pbc->ePBC]);
875             GMX_THROW(NotImplementedError(message));
876         }
877         if (pbc->ePBC == epbcXYZ
878             && (std::fabs(pbc->box[ZZ][XX]) > GMX_REAL_EPS * pbc->box[ZZ][ZZ]
879                 || std::fabs(pbc->box[ZZ][YY]) > GMX_REAL_EPS * pbc->box[ZZ][ZZ]))
880         {
881             GMX_THROW(
882                     NotImplementedError("Computations in the XY plane are not supported when the "
883                                         "last box vector is not parallel to the Z axis"));
884         }
885         // Use a single grid cell in Z direction.
886         matrix box;
887         copy_mat(pbc->box, box);
888         clear_rvec(box[ZZ]);
889         set_pbc(&pbc_, epbcXY, box);
890     }
891     else if (pbc != nullptr)
892     {
893         pbc_ = *pbc;
894     }
895     else
896     {
897         pbc_.ePBC = epbcNONE;
898         clear_mat(pbc_.box);
899     }
900     nref_ = positions.count_;
901     if (mode == AnalysisNeighborhood::eSearchMode_Simple)
902     {
903         bGrid_ = false;
904     }
905     else if (bTryGrid_)
906     {
907         bGrid_ = initGrid(pbc_, positions.count_, positions.x_,
908                           mode == AnalysisNeighborhood::eSearchMode_Grid);
909     }
910     refIndices_ = positions.indices_;
911     if (bGrid_)
912     {
913         xrefAlloc_.resize(nref_);
914         xref_ = as_rvec_array(xrefAlloc_.data());
915
916         for (int i = 0; i < nref_; ++i)
917         {
918             const int ii = (refIndices_ != nullptr) ? refIndices_[i] : i;
919             rvec      refcell;
920             mapPointToGridCell(positions.x_[ii], refcell, xrefAlloc_[i]);
921             addToGridCell(refcell, i);
922         }
923     }
924     else if (refIndices_ != nullptr)
925     {
926         xrefAlloc_.resize(nref_);
927         xref_ = as_rvec_array(xrefAlloc_.data());
928         for (int i = 0; i < nref_; ++i)
929         {
930             copy_rvec(positions.x_[refIndices_[i]], xrefAlloc_[i]);
931         }
932     }
933     else
934     {
935         xref_ = positions.x_;
936     }
937     excls_           = excls;
938     refExclusionIds_ = nullptr;
939     if (excls != nullptr)
940     {
941         // TODO: Check that the IDs are ascending, or remove the limitation.
942         refExclusionIds_ = positions.exclusionIds_;
943         GMX_RELEASE_ASSERT(refExclusionIds_ != nullptr,
944                            "Exclusion IDs must be set for reference positions "
945                            "when exclusions are enabled");
946     }
947 }
948
949 /********************************************************************
950  * AnalysisNeighborhoodPairSearchImpl
951  */
952
953 void AnalysisNeighborhoodPairSearchImpl::reset(int testIndex)
954 {
955     testIndex_     = testIndex;
956     testCellIndex_ = -1;
957     previ_         = -1;
958     prevr2_        = 0.0;
959     clear_rvec(prevdx_);
960     exclind_ = 0;
961     prevcai_ = -1;
962     if (testIndex_ >= 0 && testIndex_ < testPosCount_)
963     {
964         const int index = (testIndices_ != nullptr ? testIndices_[testIndex] : testIndex);
965         if (search_.bGrid_)
966         {
967             search_.mapPointToGridCell(testPositions_[index], testcell_, xtest_);
968             search_.initCellRange(testcell_, currCell_, cellBound_, ZZ);
969             search_.initCellRange(testcell_, currCell_, cellBound_, YY);
970             search_.initCellRange(testcell_, currCell_, cellBound_, XX);
971             if (selfSearchMode_)
972             {
973                 testCellIndex_ = search_.getGridCellIndex(testcell_);
974             }
975         }
976         else
977         {
978             copy_rvec(testPositions_[index], xtest_);
979             if (selfSearchMode_)
980             {
981                 previ_ = testIndex_;
982             }
983         }
984         if (search_.excls_ != nullptr)
985         {
986             const int exclIndex = testExclusionIds_[index];
987             if (exclIndex < search_.excls_->ssize())
988             {
989                 excl_ = (*search_.excls_)[exclIndex];
990             }
991             else
992             {
993                 excl_ = ArrayRef<const int>();
994             }
995         }
996     }
997 }
998
999 void AnalysisNeighborhoodPairSearchImpl::nextTestPosition()
1000 {
1001     if (testIndex_ < testPosCount_)
1002     {
1003         ++testIndex_;
1004         reset(testIndex_);
1005     }
1006 }
1007
1008 bool AnalysisNeighborhoodPairSearchImpl::isExcluded(int j)
1009 {
1010     const int nexcl = excl_.ssize();
1011     if (exclind_ < nexcl)
1012     {
1013         const int index = (search_.refIndices_ != nullptr ? search_.refIndices_[j] : j);
1014         const int refId = search_.refExclusionIds_[index];
1015         while (exclind_ < nexcl && excl_[exclind_] < refId)
1016         {
1017             ++exclind_;
1018         }
1019         if (exclind_ < nexcl && refId == excl_[exclind_])
1020         {
1021             ++exclind_;
1022             return true;
1023         }
1024     }
1025     return false;
1026 }
1027
1028 void AnalysisNeighborhoodPairSearchImpl::startSearch(const AnalysisNeighborhoodPositions& positions)
1029 {
1030     selfSearchMode_   = false;
1031     testPosCount_     = positions.count_;
1032     testPositions_    = positions.x_;
1033     testExclusionIds_ = positions.exclusionIds_;
1034     testIndices_      = positions.indices_;
1035     GMX_RELEASE_ASSERT(search_.excls_ == nullptr || testExclusionIds_ != nullptr,
1036                        "Exclusion IDs must be set when exclusions are enabled");
1037     if (positions.index_ < 0)
1038     {
1039         reset(0);
1040     }
1041     else
1042     {
1043         // Somewhat of a hack: setup the array such that only the last position
1044         // will be used.
1045         testPosCount_ = positions.index_ + 1;
1046         reset(positions.index_);
1047     }
1048 }
1049
1050 void AnalysisNeighborhoodPairSearchImpl::startSelfSearch()
1051 {
1052     selfSearchMode_   = true;
1053     testPosCount_     = search_.nref_;
1054     testPositions_    = search_.xref_;
1055     testExclusionIds_ = search_.refExclusionIds_;
1056     testIndices_      = search_.refIndices_;
1057     GMX_RELEASE_ASSERT(search_.excls_ == nullptr || testIndices_ == nullptr,
1058                        "Exclusion IDs not implemented with indexed ref positions");
1059     reset(0);
1060 }
1061
1062 template<class Action>
1063 bool AnalysisNeighborhoodPairSearchImpl::searchNext(Action action)
1064 {
1065     while (testIndex_ < testPosCount_)
1066     {
1067         if (search_.bGrid_)
1068         {
1069             int cai = prevcai_ + 1;
1070
1071             do
1072             {
1073                 rvec      shift;
1074                 const int ci = search_.shiftCell(currCell_, shift);
1075                 if (selfSearchMode_ && ci > testCellIndex_)
1076                 {
1077                     continue;
1078                 }
1079                 const int cellSize = ssize(search_.cells_[ci]);
1080                 for (; cai < cellSize; ++cai)
1081                 {
1082                     const int i = search_.cells_[ci][cai];
1083                     if (selfSearchMode_ && ci == testCellIndex_ && i >= testIndex_)
1084                     {
1085                         continue;
1086                     }
1087                     if (isExcluded(i))
1088                     {
1089                         continue;
1090                     }
1091                     rvec dx;
1092                     rvec_sub(search_.xref_[i], xtest_, dx);
1093                     rvec_sub(dx, shift, dx);
1094                     const real r2 = search_.bXY_ ? dx[XX] * dx[XX] + dx[YY] * dx[YY] : norm2(dx);
1095                     if (r2 <= search_.cutoff2_)
1096                     {
1097                         if (action(i, r2, dx))
1098                         {
1099                             prevcai_ = cai;
1100                             previ_   = i;
1101                             prevr2_  = r2;
1102                             copy_rvec(dx, prevdx_);
1103                             return true;
1104                         }
1105                     }
1106                 }
1107                 exclind_ = 0;
1108                 cai      = 0;
1109             } while (search_.nextCell(testcell_, currCell_, cellBound_));
1110         }
1111         else
1112         {
1113             for (int i = previ_ + 1; i < search_.nref_; ++i)
1114             {
1115                 if (isExcluded(i))
1116                 {
1117                     continue;
1118                 }
1119                 rvec dx;
1120                 if (search_.pbc_.ePBC != epbcNONE)
1121                 {
1122                     pbc_dx(&search_.pbc_, search_.xref_[i], xtest_, dx);
1123                 }
1124                 else
1125                 {
1126                     rvec_sub(search_.xref_[i], xtest_, dx);
1127                 }
1128                 const real r2 = search_.bXY_ ? dx[XX] * dx[XX] + dx[YY] * dx[YY] : norm2(dx);
1129                 if (r2 <= search_.cutoff2_)
1130                 {
1131                     if (action(i, r2, dx))
1132                     {
1133                         previ_  = i;
1134                         prevr2_ = r2;
1135                         copy_rvec(dx, prevdx_);
1136                         return true;
1137                     }
1138                 }
1139             }
1140         }
1141         nextTestPosition();
1142     }
1143     return false;
1144 }
1145
1146 void AnalysisNeighborhoodPairSearchImpl::initFoundPair(AnalysisNeighborhoodPair* pair) const
1147 {
1148     if (previ_ < 0)
1149     {
1150         *pair = AnalysisNeighborhoodPair();
1151     }
1152     else
1153     {
1154         *pair = AnalysisNeighborhoodPair(previ_, testIndex_, prevr2_, prevdx_);
1155     }
1156 }
1157
1158 } // namespace internal
1159
1160 namespace
1161 {
1162
1163 /*! \brief
1164  * Search action to find the next neighbor.
1165  *
1166  * Used as the action for AnalysisNeighborhoodPairSearchImpl::searchNext() to
1167  * find the next neighbor.
1168  *
1169  * Simply breaks the loop on the first found neighbor.
1170  */
1171 bool withinAction(int /*i*/, real /*r2*/, const rvec /* dx */)
1172 {
1173     return true;
1174 }
1175
1176 /*! \brief
1177  * Search action find the minimum distance.
1178  *
1179  * Used as the action for AnalysisNeighborhoodPairSearchImpl::searchNext() to
1180  * find the nearest neighbor.
1181  *
1182  * With this action, AnalysisNeighborhoodPairSearchImpl::searchNext() always
1183  * returns false, and the output is put into the variables passed by pointer
1184  * into the constructor.  If no neighbors are found, the output variables are
1185  * not modified, i.e., the caller must initialize them.
1186  */
1187 class MindistAction
1188 {
1189 public:
1190     /*! \brief
1191      * Initializes the action with given output locations.
1192      *
1193      * \param[out] closestPoint Index of the closest reference location.
1194      * \param[out] minDist2     Minimum distance squared.
1195      * \param[out] dx           Shortest distance vector.
1196      *
1197      * The constructor call does not modify the pointed values, but only
1198      * stores the pointers for later use.
1199      * See the class description for additional semantics.
1200      */
1201     MindistAction(int* closestPoint, real* minDist2, rvec* dx) // NOLINT(readability-non-const-parameter)
1202         :
1203         closestPoint_(*closestPoint),
1204         minDist2_(*minDist2),
1205         dx_(*dx)
1206     {
1207     }
1208     //! Copies the action.
1209     MindistAction(const MindistAction&) = default;
1210
1211     //! Processes a neighbor to find the nearest point.
1212     bool operator()(int i, real r2, const rvec dx)
1213     {
1214         if (r2 < minDist2_)
1215         {
1216             closestPoint_ = i;
1217             minDist2_     = r2;
1218             copy_rvec(dx, dx_);
1219         }
1220         return false;
1221     }
1222
1223 private:
1224     int&  closestPoint_;
1225     real& minDist2_;
1226     rvec& dx_;
1227
1228     GMX_DISALLOW_ASSIGN(MindistAction);
1229 };
1230
1231 } // namespace
1232
1233 /********************************************************************
1234  * AnalysisNeighborhood::Impl
1235  */
1236
1237 class AnalysisNeighborhood::Impl
1238 {
1239 public:
1240     typedef AnalysisNeighborhoodSearch::ImplPointer SearchImplPointer;
1241     typedef std::vector<SearchImplPointer>          SearchList;
1242
1243     Impl() : cutoff_(0), excls_(nullptr), mode_(eSearchMode_Automatic), bXY_(false) {}
1244     ~Impl()
1245     {
1246         SearchList::const_iterator i;
1247         for (i = searchList_.begin(); i != searchList_.end(); ++i)
1248         {
1249             GMX_RELEASE_ASSERT(i->unique(), "Dangling AnalysisNeighborhoodSearch reference");
1250         }
1251     }
1252
1253     SearchImplPointer getSearch();
1254
1255     Mutex                   createSearchMutex_;
1256     SearchList              searchList_;
1257     real                    cutoff_;
1258     const ListOfLists<int>* excls_;
1259     SearchMode              mode_;
1260     bool                    bXY_;
1261 };
1262
1263 AnalysisNeighborhood::Impl::SearchImplPointer AnalysisNeighborhood::Impl::getSearch()
1264 {
1265     lock_guard<Mutex> lock(createSearchMutex_);
1266     // TODO: Consider whether this needs to/can be faster, e.g., by keeping a
1267     // separate pool of unused search objects.
1268     SearchList::const_iterator i;
1269     for (i = searchList_.begin(); i != searchList_.end(); ++i)
1270     {
1271         if (i->unique())
1272         {
1273             return *i;
1274         }
1275     }
1276     SearchImplPointer search(new internal::AnalysisNeighborhoodSearchImpl(cutoff_));
1277     searchList_.push_back(search);
1278     return search;
1279 }
1280
1281 /********************************************************************
1282  * AnalysisNeighborhood
1283  */
1284
1285 AnalysisNeighborhood::AnalysisNeighborhood() : impl_(new Impl) {}
1286
1287 AnalysisNeighborhood::~AnalysisNeighborhood() {}
1288
1289 void AnalysisNeighborhood::setCutoff(real cutoff)
1290 {
1291     GMX_RELEASE_ASSERT(impl_->searchList_.empty(),
1292                        "Changing the cutoff after initSearch() not currently supported");
1293     impl_->cutoff_ = cutoff;
1294 }
1295
1296 void AnalysisNeighborhood::setXYMode(bool bXY)
1297 {
1298     impl_->bXY_ = bXY;
1299 }
1300
1301 void AnalysisNeighborhood::setTopologyExclusions(const ListOfLists<int>* excls)
1302 {
1303     GMX_RELEASE_ASSERT(impl_->searchList_.empty(),
1304                        "Changing the exclusions after initSearch() not currently supported");
1305     impl_->excls_ = excls;
1306 }
1307
1308 void AnalysisNeighborhood::setMode(SearchMode mode)
1309 {
1310     impl_->mode_ = mode;
1311 }
1312
1313 AnalysisNeighborhood::SearchMode AnalysisNeighborhood::mode() const
1314 {
1315     return impl_->mode_;
1316 }
1317
1318 AnalysisNeighborhoodSearch AnalysisNeighborhood::initSearch(const t_pbc* pbc,
1319                                                             const AnalysisNeighborhoodPositions& positions)
1320 {
1321     Impl::SearchImplPointer search(impl_->getSearch());
1322     search->init(mode(), impl_->bXY_, impl_->excls_, pbc, positions);
1323     return AnalysisNeighborhoodSearch(search);
1324 }
1325
1326 /********************************************************************
1327  * AnalysisNeighborhoodSearch
1328  */
1329
1330 AnalysisNeighborhoodSearch::AnalysisNeighborhoodSearch() {}
1331
1332 AnalysisNeighborhoodSearch::AnalysisNeighborhoodSearch(const ImplPointer& impl) : impl_(impl) {}
1333
1334 void AnalysisNeighborhoodSearch::reset()
1335 {
1336     impl_.reset();
1337 }
1338
1339 AnalysisNeighborhood::SearchMode AnalysisNeighborhoodSearch::mode() const
1340 {
1341     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1342     return (impl_->usesGridSearch() ? AnalysisNeighborhood::eSearchMode_Grid
1343                                     : AnalysisNeighborhood::eSearchMode_Simple);
1344 }
1345
1346 bool AnalysisNeighborhoodSearch::isWithin(const AnalysisNeighborhoodPositions& positions) const
1347 {
1348     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1349     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1350     pairSearch.startSearch(positions);
1351     return pairSearch.searchNext(&withinAction);
1352 }
1353
1354 real AnalysisNeighborhoodSearch::minimumDistance(const AnalysisNeighborhoodPositions& positions) const
1355 {
1356     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1357     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1358     pairSearch.startSearch(positions);
1359     real          minDist2     = impl_->cutoffSquared();
1360     int           closestPoint = -1;
1361     rvec          dx           = { 0.0, 0.0, 0.0 };
1362     MindistAction action(&closestPoint, &minDist2, &dx);
1363     (void)pairSearch.searchNext(action);
1364     return std::sqrt(minDist2);
1365 }
1366
1367 AnalysisNeighborhoodPair AnalysisNeighborhoodSearch::nearestPoint(const AnalysisNeighborhoodPositions& positions) const
1368 {
1369     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1370     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
1371     pairSearch.startSearch(positions);
1372     real          minDist2     = impl_->cutoffSquared();
1373     int           closestPoint = -1;
1374     rvec          dx           = { 0.0, 0.0, 0.0 };
1375     MindistAction action(&closestPoint, &minDist2, &dx);
1376     (void)pairSearch.searchNext(action);
1377     return AnalysisNeighborhoodPair(closestPoint, 0, minDist2, dx);
1378 }
1379
1380 AnalysisNeighborhoodPairSearch AnalysisNeighborhoodSearch::startSelfPairSearch() const
1381 {
1382     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1383     Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
1384     pairSearch->startSelfSearch();
1385     return AnalysisNeighborhoodPairSearch(pairSearch);
1386 }
1387
1388 AnalysisNeighborhoodPairSearch
1389 AnalysisNeighborhoodSearch::startPairSearch(const AnalysisNeighborhoodPositions& positions) const
1390 {
1391     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
1392     Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
1393     pairSearch->startSearch(positions);
1394     return AnalysisNeighborhoodPairSearch(pairSearch);
1395 }
1396
1397 /********************************************************************
1398  * AnalysisNeighborhoodPairSearch
1399  */
1400
1401 AnalysisNeighborhoodPairSearch::AnalysisNeighborhoodPairSearch(const ImplPointer& impl) :
1402     impl_(impl)
1403 {
1404 }
1405
1406 bool AnalysisNeighborhoodPairSearch::findNextPair(AnalysisNeighborhoodPair* pair)
1407 {
1408     bool bFound = impl_->searchNext(&withinAction);
1409     impl_->initFoundPair(pair);
1410     return bFound;
1411 }
1412
1413 void AnalysisNeighborhoodPairSearch::skipRemainingPairsForTestPosition()
1414 {
1415     impl_->nextTestPosition();
1416 }
1417
1418 } // namespace gmx