C++-ify analysis nbsearch more.
authorTeemu Murtola <teemu.murtola@gmail.com>
Sun, 12 May 2013 11:38:35 +0000 (14:38 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Thu, 27 Jun 2013 20:18:57 +0000 (22:18 +0200)
- Use static_cast instead of C casts throughout.
- Use more C++-style comments.
- Use the same pattern for startPairSearch() as for initSearch() to
  allow multiple concurrent searches.
- Use std::vector instead of explicit memory allocation for the grid
  cell management.  Memory is still managed explicitly for rvec and ivec
  arrays, since they can't be put into std::vector.
- Make most members of the implementation classes private to clarify the
  code (the implementation class is now explicitly responsible for
  managing its internal state).

Part of #866.

Change-Id: I2af5552b696644f954957f96705baabd891284a9

src/gromacs/selection/nbsearch.cpp
src/gromacs/selection/nbsearch.h
src/gromacs/selection/tests/nbsearch.cpp

index 31f5b386cd093a23cfe541d37f639c1912a4b10e..d050c89e1c1d453158b325e504e73cf29b53c500 100644 (file)
@@ -86,13 +86,20 @@ class AnalysisNeighborhoodSearchImpl
     public:
         typedef AnalysisNeighborhoodPairSearch::ImplPointer
             PairSearchImplPointer;
+        typedef std::vector<PairSearchImplPointer> PairSearchList;
+        typedef std::vector<std::vector<int> > CellList;
 
         explicit AnalysisNeighborhoodSearchImpl(real cutoff);
         ~AnalysisNeighborhoodSearchImpl();
 
         void init(AnalysisNeighborhood::SearchMode mode,
-                  const t_pbc *pbc, int n, const rvec x[]);
+                  const t_pbc *pbc, int n, const rvec x[], const int *refid);
+        PairSearchImplPointer getPairSearch();
 
+        real cutoffSquared() const { return cutoff2_; }
+        bool usesGridSearch() const { return bGrid_; }
+
+    private:
         //! Calculates offsets to neighboring grid cells that should be considered.
         void initGridCellNeighborList();
         /*! \brief
@@ -109,8 +116,6 @@ class AnalysisNeighborhoodSearchImpl
          * \returns  false if grid search is not suitable.
          */
         bool initGrid(const t_pbc *pbc);
-        //! Clears all grid cells.
-        void clearGridCells();
         /*! \brief
          * Maps a point into a grid cell.
          *
@@ -135,59 +140,54 @@ class AnalysisNeighborhoodSearchImpl
          */
         void addToGridCell(const ivec cell, int i);
 
-        /** Whether to try grid searching. */
+        //! Whether to try grid searching.
         bool                    bTryGrid_;
-        /** The cutoff. */
+        //! The cutoff.
         real                    cutoff_;
-        /** The cutoff squared. */
+        //! The cutoff squared.
         real                    cutoff2_;
 
-        /** Number of reference points for the current frame. */
+        //! Number of reference points for the current frame.
         int                     nref_;
-        /** Reference point positions. */
+        //! Reference point positions.
         const rvec             *xref_;
-        /** Reference position ids (NULL if not available). */
-        int                    *refid_;
-        /** PBC data. */
+        //! Reference position ids (NULL if not available).
+        const int              *refid_;
+        //! PBC data.
         t_pbc                  *pbc_;
 
-        /** Number of excluded reference positions for current test particle. */
+        //! Number of excluded reference positions for current test particle.
         int                     nexcl_;
-        /** Exclusions for current test particle. */
+        //! Exclusions for current test particle.
         int                    *excl_;
 
-        /** Whether grid searching is actually used for the current positions. */
+        //! Whether grid searching is actually used for the current positions.
         bool                    bGrid_;
-        /** Array allocated for storing in-unit-cell reference positions. */
+        //! Array allocated for storing in-unit-cell reference positions.
         rvec                   *xref_alloc_;
-        /** Allocation count for xref_alloc. */
+        //! Allocation count for xref_alloc.
         int                     xref_nalloc_;
-        /** false if the box is rectangular. */
+        //! false if the box is rectangular.
         bool                    bTric_;
-        /** Box vectors of a single grid cell. */
+        //! Box vectors of a single grid cell.
         matrix                  cellbox_;
-        /** The reciprocal cell vectors as columns; the inverse of \p cellbox. */
+        //! The reciprocal cell vectors as columns; the inverse of \p cellbox.
         matrix                  recipcell_;
-        /** Number of cells along each dimension. */
+        //! Number of cells along each dimension.
         ivec                    ncelldim_;
-        /** Total number of cells. */
-        int                     ncells_;
-        /** Number of reference positions in each cell. */
-        int                    *ncatoms_;
-        /** List of reference positions in each cell. */
-        atom_id               **catom_;
-        /** Allocation counts for each \p catom[i]. */
-        int                    *catom_nalloc_;
-        /** Allocation count for the per-cell arrays. */
-        int                     cells_nalloc_;
-        /** Number of neighboring cells to consider. */
+        //! Data structure to hold the grid cell contents.
+        CellList                cells_;
+        //! Number of neighboring cells to consider.
         int                     ngridnb_;
-        /** Offsets of the neighboring cells to consider. */
+        //! Offsets of the neighboring cells to consider.
         ivec                   *gnboffs_;
-        /** Allocation count for \p gnboffs. */
+        //! Allocation count for \p gnboffs.
         int                     gnboffs_nalloc_;
 
-        PairSearchImplPointer   pairSearch_;
+        tMPI::mutex             createPairSearchMutex_;
+        PairSearchList          pairSearchList_;
+
+        friend class AnalysisNeighborhoodPairSearchImpl;
 
         GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodSearchImpl);
 };
@@ -207,25 +207,29 @@ class AnalysisNeighborhoodPairSearchImpl
         void reset();
         //! Initializes a search to find reference positions neighboring \p x.
         void startSearch(const rvec x, int testIndex);
-        //! Checks whether a reference positiong should be excluded.
-        bool isExcluded(int j);
         //! Searches for the next neighbor.
         template <class Action>
         bool searchNext(Action action);
+        //! Initializes a pair representing the pair found by searchNext().
+        void initFoundPair(AnalysisNeighborhoodPair *pair) const;
+
+    private:
+        //! Checks whether a reference positiong should be excluded.
+        bool isExcluded(int j);
 
         const AnalysisNeighborhoodSearchImpl   &search_;
         int                                     testIndex_;
-        /** Stores test position during a pair loop. */
+        //! Stores test position during a pair loop.
         rvec                                    xtest_;
-        /** Stores the previous returned position during a pair loop. */
+        //! Stores the previous returned position during a pair loop.
         int                                     previ_;
-        /** Stores the current exclusion index during loops. */
+        //! Stores the current exclusion index during loops.
         int                                     exclind_;
-        /** Stores the test particle cell index during loops. */
+        //! Stores the test particle cell index during loops.
         ivec                                    testcell_;
-        /** Stores the current cell neighbor index during pair loops. */
+        //! Stores the current cell neighbor index during pair loops.
         int                                     prevnbi_;
-        /** Stores the index within the current cell during pair loops. */
+        //! Stores the index within the current cell during pair loops.
         int                                     prevcai_;
 
         GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisNeighborhoodPairSearchImpl);
@@ -236,7 +240,6 @@ class AnalysisNeighborhoodPairSearchImpl
  */
 
 AnalysisNeighborhoodSearchImpl::AnalysisNeighborhoodSearchImpl(real cutoff)
-    : pairSearch_(new AnalysisNeighborhoodPairSearchImpl(*this))
 {
     bTryGrid_       = true;
     cutoff_         = cutoff;
@@ -263,11 +266,6 @@ AnalysisNeighborhoodSearchImpl::AnalysisNeighborhoodSearchImpl(real cutoff)
     clear_mat(cellbox_);
     clear_mat(recipcell_);
     clear_ivec(ncelldim_);
-    ncells_         = 0;
-    ncatoms_        = NULL;
-    catom_          = NULL;
-    catom_nalloc_   = 0;
-    cells_nalloc_   = 0;
 
     ngridnb_        = 0;
     gnboffs_        = NULL;
@@ -276,20 +274,33 @@ AnalysisNeighborhoodSearchImpl::AnalysisNeighborhoodSearchImpl(real cutoff)
 
 AnalysisNeighborhoodSearchImpl::~AnalysisNeighborhoodSearchImpl()
 {
-    GMX_RELEASE_ASSERT(pairSearch_.unique(),
-                       "Dangling AnalysisNeighborhoodPairSearch reference");
+    PairSearchList::const_iterator i;
+    for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
+    {
+        GMX_RELEASE_ASSERT(i->unique(),
+                           "Dangling AnalysisNeighborhoodPairSearch reference");
+    }
     sfree(xref_alloc_);
-    sfree(ncatoms_);
-    if (catom_ != NULL)
+    sfree(gnboffs_);
+}
+
+AnalysisNeighborhoodSearchImpl::PairSearchImplPointer
+AnalysisNeighborhoodSearchImpl::getPairSearch()
+{
+    tMPI::lock_guard<tMPI::mutex> lock(createPairSearchMutex_);
+    // TODO: Consider whether this needs to/can be faster, e.g., by keeping a
+    // separate pool of unused search objects.
+    PairSearchList::const_iterator i;
+    for (i = pairSearchList_.begin(); i != pairSearchList_.end(); ++i)
     {
-        for (int ci = 0; ci < ncells_; ++ci)
+        if (i->unique())
         {
-            sfree(catom_[ci]);
+            return *i;
         }
-        sfree(catom_);
     }
-    sfree(catom_nalloc_);
-    sfree(gnboffs_);
+    PairSearchImplPointer pairSearch(new AnalysisNeighborhoodPairSearchImpl(*this));
+    pairSearchList_.push_back(pairSearch);
+    return pairSearch;
 }
 
 void AnalysisNeighborhoodSearchImpl::initGridCellNeighborList()
@@ -298,12 +309,12 @@ void AnalysisNeighborhoodSearchImpl::initGridCellNeighborList()
     real  rvnorm;
 
     /* Find the extent of the sphere in triclinic coordinates */
-    maxz   = (int)(cutoff_ * recipcell_[ZZ][ZZ]) + 1;
+    maxz   = static_cast<int>(cutoff_ * recipcell_[ZZ][ZZ]) + 1;
     rvnorm = sqrt(sqr(recipcell_[YY][YY]) + sqr(recipcell_[ZZ][YY]));
-    maxy   = (int)(cutoff_ * rvnorm) + 1;
+    maxy   = static_cast<int>(cutoff_ * rvnorm) + 1;
     rvnorm = sqrt(sqr(recipcell_[XX][XX]) + sqr(recipcell_[YY][XX])
                   + sqr(recipcell_[ZZ][XX]));
-    maxx   = (int)(cutoff_ * rvnorm) + 1;
+    maxx   = static_cast<int>(cutoff_ * rvnorm) + 1;
 
     /* Calculate the number of cells and reallocate if necessary */
     ngridnb_ = (2 * maxx + 1) * (2 * maxy + 1) * (2 * maxz + 1);
@@ -337,28 +348,26 @@ bool AnalysisNeighborhoodSearchImpl::initGridCells(const t_pbc *pbc)
         pow(pbc->box[XX][XX] * pbc->box[YY][YY] * pbc->box[ZZ][ZZ]
             * 10 / nref_, static_cast<real>(1./3.));
 
-    ncells_ = 1;
+    int cellCount = 1;
     for (int dd = 0; dd < DIM; ++dd)
     {
         ncelldim_[dd] = static_cast<int>(pbc->box[dd][dd] / targetsize);
-        ncells_      *= ncelldim_[dd];
+        cellCount    *= ncelldim_[dd];
         if (ncelldim_[dd] < 3)
         {
             return false;
         }
     }
-    /* Reallocate if necessary */
-    if (cells_nalloc_ < ncells_)
+    // Never decrease the size of the cell vector to avoid reallocating
+    // memory for the nested vectors.  The actual size of the vector is not
+    // used outside this function.
+    if (cells_.size() < static_cast<size_t>(cellCount))
     {
-        srenew(ncatoms_, ncells_);
-        srenew(catom_, ncells_);
-        srenew(catom_nalloc_, ncells_);
-        for (int i = cells_nalloc_; i < ncells_; ++i)
-        {
-            catom_[i]        = NULL;
-            catom_nalloc_[i] = 0;
-        }
-        cells_nalloc_ = ncells_;
+        cells_.resize(cellCount);
+    }
+    for (int ci = 0; ci < cellCount; ++ci)
+    {
+        cells_[ci].clear();
     }
     return true;
 }
@@ -421,33 +430,25 @@ void AnalysisNeighborhoodSearchImpl::mapPointToGridCell(const rvec x,
 
 int AnalysisNeighborhoodSearchImpl::getGridCellIndex(const ivec cell) const
 {
+    GMX_ASSERT(cell[XX] >= 0 && cell[XX] < ncelldim_[XX],
+               "Grid cell X index out of range");
+    GMX_ASSERT(cell[YY] >= 0 && cell[YY] < ncelldim_[YY],
+               "Grid cell Y index out of range");
+    GMX_ASSERT(cell[ZZ] >= 0 && cell[ZZ] < ncelldim_[ZZ],
+               "Grid cell Z index out of range");
     return cell[XX] + cell[YY] * ncelldim_[XX]
            + cell[ZZ] * ncelldim_[XX] * ncelldim_[YY];
 }
 
-void AnalysisNeighborhoodSearchImpl::clearGridCells()
-{
-    for (int ci = 0; ci < ncells_; ++ci)
-    {
-        ncatoms_[ci] = 0;
-    }
-}
-
 void AnalysisNeighborhoodSearchImpl::addToGridCell(const ivec cell, int i)
 {
     const int ci = getGridCellIndex(cell);
-
-    if (ncatoms_[ci] == catom_nalloc_[ci])
-    {
-        catom_nalloc_[ci] += 10;
-        srenew(catom_[ci], catom_nalloc_[ci]);
-    }
-    catom_[ci][ncatoms_[ci]++] = i;
+    cells_[ci].push_back(i);
 }
 
 void AnalysisNeighborhoodSearchImpl::init(
         AnalysisNeighborhood::SearchMode mode,
-        const t_pbc *pbc, int n, const rvec x[])
+        const t_pbc *pbc, int n, const rvec x[], const int *refid)
 {
     pbc_  = const_cast<t_pbc *>(pbc);
     nref_ = n;
@@ -471,7 +472,6 @@ void AnalysisNeighborhoodSearchImpl::init(
             xref_nalloc_ = n;
         }
         xref_ = xref_alloc_;
-        clearGridCells();
 
         for (int i = 0; i < n; ++i)
         {
@@ -490,7 +490,7 @@ void AnalysisNeighborhoodSearchImpl::init(
     {
         xref_ = x;
     }
-    refid_ = NULL;
+    refid_ = refid;
 }
 
 #if 0
@@ -590,11 +590,12 @@ bool AnalysisNeighborhoodPairSearchImpl::searchNext(Action action)
             cell[YY] = (cell[YY] + search_.ncelldim_[YY]) % search_.ncelldim_[YY];
             cell[ZZ] = (cell[ZZ] + search_.ncelldim_[ZZ]) % search_.ncelldim_[ZZ];
 
-            const int ci = search_.getGridCellIndex(cell);
+            const int ci       = search_.getGridCellIndex(cell);
+            const int cellSize = static_cast<int>(search_.cells_[ci].size());
             /* TODO: Calculate the required PBC shift outside the inner loop */
-            for (; cai < search_.ncatoms_[ci]; ++cai)
+            for (; cai < cellSize; ++cai)
             {
-                const int i = search_.catom_[ci][cai];
+                const int i = search_.cells_[ci][cai];
                 if (isExcluded(i))
                 {
                     continue;
@@ -645,9 +646,23 @@ bool AnalysisNeighborhoodPairSearchImpl::searchNext(Action action)
             }
         }
     }
+    reset();
     return false;
 }
 
+void AnalysisNeighborhoodPairSearchImpl::initFoundPair(
+        AnalysisNeighborhoodPair *pair) const
+{
+    if (previ_ < 0)
+    {
+        *pair = AnalysisNeighborhoodPair();
+    }
+    else
+    {
+        *pair = AnalysisNeighborhoodPair(previ_, testIndex_);
+    }
+}
+
 }   // namespace internal
 
 namespace
@@ -799,7 +814,7 @@ AnalysisNeighborhoodSearch
 AnalysisNeighborhood::initSearch(const t_pbc *pbc, int n, const rvec x[])
 {
     Impl::SearchImplPointer search(impl_->getSearch());
-    search->init(mode(), pbc, n, x);
+    search->init(mode(), pbc, n, x, NULL);
     return AnalysisNeighborhoodSearch(search);
 }
 
@@ -807,8 +822,7 @@ AnalysisNeighborhoodSearch
 AnalysisNeighborhood::initSearch(const t_pbc *pbc, const gmx_ana_pos_t *p)
 {
     Impl::SearchImplPointer search(impl_->getSearch());
-    search->init(mode(), pbc, p->nr, p->x);
-    search->refid_ = p->m.refid;
+    search->init(mode(), pbc, p->nr, p->x, p->m.refid);
     return AnalysisNeighborhoodSearch(search);
 }
 
@@ -833,7 +847,7 @@ void AnalysisNeighborhoodSearch::reset()
 AnalysisNeighborhood::SearchMode AnalysisNeighborhoodSearch::mode() const
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
-    return (impl_->bGrid_
+    return (impl_->usesGridSearch()
             ? AnalysisNeighborhood::eSearchMode_Grid
             : AnalysisNeighborhood::eSearchMode_Simple);
 }
@@ -856,7 +870,7 @@ real AnalysisNeighborhoodSearch::minimumDistance(const rvec x) const
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
     pairSearch.startSearch(x, 0);
-    real          minDist2     = impl_->cutoff2_;
+    real          minDist2     = impl_->cutoffSquared();
     int           closestPoint = -1;
     MindistAction action(&closestPoint, &minDist2);
     (void)pairSearch.searchNext(action);
@@ -874,7 +888,7 @@ AnalysisNeighborhoodSearch::nearestPoint(const rvec x) const
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
     pairSearch.startSearch(x, 0);
-    real          minDist2     = impl_->cutoff2_;
+    real          minDist2     = impl_->cutoffSquared();
     int           closestPoint = -1;
     MindistAction action(&closestPoint, &minDist2);
     (void)pairSearch.searchNext(action);
@@ -887,7 +901,7 @@ AnalysisNeighborhoodSearch::nearestPoint(const gmx_ana_pos_t *p, int i) const
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
     pairSearch.startSearch(p->x[i], 0);
-    real          minDist2     = impl_->cutoff2_;
+    real          minDist2     = impl_->cutoffSquared();
     int           closestPoint = -1;
     MindistAction action(&closestPoint, &minDist2);
     (void)pairSearch.searchNext(action);
@@ -898,20 +912,18 @@ AnalysisNeighborhoodPairSearch
 AnalysisNeighborhoodSearch::startPairSearch(const rvec x)
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
-    GMX_RELEASE_ASSERT(impl_->pairSearch_.unique(),
-                       "Multiple concurrent searches not currently supported");
-    impl_->pairSearch_->startSearch(x, 0);
-    return AnalysisNeighborhoodPairSearch(impl_->pairSearch_);
+    Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
+    pairSearch->startSearch(x, 0);
+    return AnalysisNeighborhoodPairSearch(pairSearch);
 }
 
 AnalysisNeighborhoodPairSearch
 AnalysisNeighborhoodSearch::startPairSearch(const gmx_ana_pos_t *p, int i)
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
-    GMX_RELEASE_ASSERT(impl_->pairSearch_.unique(),
-                       "Multiple concurrent searches not currently supported");
-    impl_->pairSearch_->startSearch(p->x[i], i);
-    return AnalysisNeighborhoodPairSearch(impl_->pairSearch_);
+    Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
+    pairSearch->startSearch(p->x[i], i);
+    return AnalysisNeighborhoodPairSearch(pairSearch);
 }
 
 /********************************************************************
@@ -926,13 +938,9 @@ AnalysisNeighborhoodPairSearch::AnalysisNeighborhoodPairSearch(
 
 bool AnalysisNeighborhoodPairSearch::findNextPair(AnalysisNeighborhoodPair *pair)
 {
-    if (impl_->searchNext(&withinAction))
-    {
-        *pair = AnalysisNeighborhoodPair(impl_->previ_, impl_->testIndex_);
-        return true;
-    }
-    *pair = AnalysisNeighborhoodPair();
-    return false;
+    bool bFound = impl_->searchNext(&withinAction);
+    impl_->initFoundPair(pair);
+    return bFound;
 }
 
 } // namespace gmx
index a6127f76ba9ace9b64ebb412fdbaf743fc56f52b..e923b77fbda96fe858f7048c4f7a08c5b0a5b01e 100644 (file)
@@ -88,9 +88,10 @@ class AnalysisNeighborhoodPairSearch;
  *
  * initSearch() is thread-safe and can be called from multiple threads.  Each
  * call returns a different instance of the search object that can be used
- * independently of the others.  However, the returned search objects can only
- * be used within a single thread.  It is also possible to create multiple
- * concurrent searches within a single thread.
+ * independently of the others.  The returned AnalysisNeighborhoodSearch
+ * objects are also thread-safe, and can be used concurrently from multiple
+ * threads.  It is also possible to create multiple concurrent searches within
+ * a single thread.
  *
  * \todo
  * Support for exclusions.
@@ -240,16 +241,16 @@ class AnalysisNeighborhoodPair
  * An instance of this class is obtained through
  * AnalysisNeighborhood::initSearch(), and can be used to do multiple searches
  * against the provided set of reference positions.
- * Currently, it is not possible to call startPairSearch() while a previous
- * AnalysisNeighborhoodPairSearch object obtained from startPairSearch() of the
- * same instance still exists.
+ * It is possible to create concurrent pair searches (including from different
+ * threads), as well as call other methods in this class while a pair search is
+ * in progress.
  *
  * This class works like a pointer: copies of it point to the same search.
  * In general, avoid creating copies, and only use the copy/assignment support
  * for moving the variable around.  With C++11, this class would best be
  * movable.
  *
- * Methods in this class do not throw.
+ * Methods in this class do not throw unless otherwise indicated.
  *
  * \todo
  * Make it such that reset() is not necessary to call in code that repeatedly
@@ -375,6 +376,7 @@ class AnalysisNeighborhoodSearch
          * \param[in] x  Test position to search the neighbors for.
          * \returns   Initialized search object to loop through all reference
          *     positions within the configured cutoff.
+         * \throws    std::bad_alloc if out of memory.
          *
          * In the AnalysisNeighborhoodPair objects returned by the search, the
          * test index is always zero.
@@ -387,6 +389,7 @@ class AnalysisNeighborhoodSearch
          * \param[in] i  Use the i'th position in \p p for testing.
          * \returns   Initialized search object to loop through all reference
          *     positions within the configured cutoff.
+         * \throws    std::bad_alloc if out of memory.
          *
          * In the AnalysisNeighborhoodPair objects returned by the search, the
          * test index is always \p i.
@@ -394,6 +397,8 @@ class AnalysisNeighborhoodSearch
         AnalysisNeighborhoodPairSearch startPairSearch(const gmx_ana_pos_t *p, int i);
 
     private:
+        typedef internal::AnalysisNeighborhoodSearchImpl Impl;
+
         ImplPointer             impl_;
 };
 
index dfecebd05c9e8b24e6eec8f61796fa80f2f9e12e..f50202df464da2e0fa555e0d3522e8a80764f35f 100644 (file)
@@ -50,6 +50,7 @@
 
 #include <limits>
 #include <set>
+#include <vector>
 
 #include "gromacs/legacyheaders/gmx_random.h"
 #include "gromacs/legacyheaders/pbc.h"
@@ -477,8 +478,21 @@ TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
     gmx::AnalysisNeighborhoodSearch search2 =
         nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
 
-    testPairSearch(&search1, data);
+    gmx::AnalysisNeighborhoodPairSearch pairSearch1 =
+        search1.startPairSearch(data.testPositions_[0].x);
+    gmx::AnalysisNeighborhoodPairSearch pairSearch2 =
+        search1.startPairSearch(data.testPositions_[1].x);
+
     testPairSearch(&search2, data);
+
+    gmx::AnalysisNeighborhoodPair pair;
+    pairSearch1.findNextPair(&pair);
+    EXPECT_EQ(0, pair.testIndex());
+    EXPECT_TRUE(data.testPositions_[0].refPairs.count(pair.refIndex()) == 1);
+
+    pairSearch2.findNextPair(&pair);
+    EXPECT_EQ(0, pair.testIndex());
+    EXPECT_TRUE(data.testPositions_[1].refPairs.count(pair.refIndex()) == 1);
 }
 
 } // namespace