More flexible position input for nbsearch routines.
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 17 May 2013 09:25:57 +0000 (12:25 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Thu, 27 Jun 2013 20:19:07 +0000 (22:19 +0200)
In addition to single positions, it is now possible to do a full
pairwise search between two set of positions.  Introduced a helper class
that encapsulates all information about the positions, such that all
methods can take this class as input, and all different forms of
initializing the positions only needs to be implemented once in this
class.  Also made it possible to pass selections directly to the
neighborhood search routines and removed deprecated
Selection::positions(), which was only useful in combination with the
nbsearch code.

Part of #866.

Change-Id: I1829d981ddff16a98c5a2bae873cc3589ef44d4a

share/template/template.cpp
src/gromacs/selection/nbsearch.cpp
src/gromacs/selection/nbsearch.h
src/gromacs/selection/selection.cpp
src/gromacs/selection/selection.h
src/gromacs/selection/sm_distance.cpp
src/gromacs/selection/tests/nbsearch.cpp
src/gromacs/trajectoryanalysis/modules/freevolume.cpp

index 3b268350a7c1cd73a9bc03d2768c91db555e65e6..2fee9cff1edcdc2bdffd67e943430065463b5de8 100644 (file)
@@ -153,8 +153,7 @@ AnalysisTemplate::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
     AnalysisDataHandle         dh     = pdata->dataHandle(data_);
     const Selection           &refsel = pdata->parallelSelection(refsel_);
 
-    AnalysisNeighborhoodSearch nbsearch =
-        nb_.initSearch(pbc, refsel.positions());
+    AnalysisNeighborhoodSearch nbsearch = nb_.initSearch(pbc, refsel);
     dh.startFrame(frnr, fr.time);
     for (size_t g = 0; g < sel_.size(); ++g)
     {
index d050c89e1c1d453158b325e504e73cf29b53c500..1c4e77dc56c1fe0a464faba5448c5480a08f3d55 100644 (file)
@@ -69,6 +69,7 @@
 #include "gromacs/legacyheaders/thread_mpi/mutex.h"
 
 #include "gromacs/selection/position.h"
+#include "gromacs/utility/arrayref.h"
 #include "gromacs/utility/gmxassert.h"
 
 namespace gmx
@@ -92,8 +93,16 @@ class AnalysisNeighborhoodSearchImpl
         explicit AnalysisNeighborhoodSearchImpl(real cutoff);
         ~AnalysisNeighborhoodSearchImpl();
 
-        void init(AnalysisNeighborhood::SearchMode mode,
-                  const t_pbc *pbc, int n, const rvec x[], const int *refid);
+        /*! \brief
+         * Initializes the search with a given box and reference positions.
+         *
+         * \param[in]     mode      Search mode to use.
+         * \param[in]     pbc       PBC information.
+         * \param[in]     positions Set of reference positions.
+         */
+        void init(AnalysisNeighborhood::SearchMode     mode,
+                  const t_pbc                         *pbc,
+                  const AnalysisNeighborhoodPositions &positions);
         PairSearchImplPointer getPairSearch();
 
         real cutoffSquared() const { return cutoff2_; }
@@ -196,28 +205,34 @@ class AnalysisNeighborhoodPairSearchImpl
 {
     public:
         explicit AnalysisNeighborhoodPairSearchImpl(const AnalysisNeighborhoodSearchImpl &search)
-            : search_(search), testIndex_(0)
+            : search_(search)
         {
             clear_rvec(xtest_);
             clear_ivec(testcell_);
-            reset();
+            reset(-1);
         }
 
-        //! Clears the loop indices.
-        void reset();
         //! Initializes a search to find reference positions neighboring \p x.
-        void startSearch(const rvec x, int testIndex);
+        void startSearch(const AnalysisNeighborhoodPositions &positions);
         //! 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;
+        //! Advances to the next test position, skipping any remaining pairs.
+        void nextTestPosition();
 
     private:
+        //! Clears the loop indices.
+        void reset(int testIndex);
         //! Checks whether a reference positiong should be excluded.
         bool isExcluded(int j);
 
+        //! Parent search object.
         const AnalysisNeighborhoodSearchImpl   &search_;
+        //! Reference to the test positions.
+        ConstArrayRef<rvec>                     testPositions_;
+        //! Index of the currently active test position in \p testPositions_.
         int                                     testIndex_;
         //! Stores test position during a pair loop.
         rvec                                    xtest_;
@@ -447,11 +462,14 @@ void AnalysisNeighborhoodSearchImpl::addToGridCell(const ivec cell, int i)
 }
 
 void AnalysisNeighborhoodSearchImpl::init(
-        AnalysisNeighborhood::SearchMode mode,
-        const t_pbc *pbc, int n, const rvec x[], const int *refid)
+        AnalysisNeighborhood::SearchMode     mode,
+        const t_pbc                         *pbc,
+        const AnalysisNeighborhoodPositions &positions)
 {
+    GMX_RELEASE_ASSERT(positions.index_ == -1,
+                       "Individual indexed positions not supported as reference");
     pbc_  = const_cast<t_pbc *>(pbc);
-    nref_ = n;
+    nref_ = positions.count_;
     // TODO: Consider whether it would be possible to support grid searching in
     // more cases.
     if (mode == AnalysisNeighborhood::eSearchMode_Simple
@@ -466,19 +484,20 @@ void AnalysisNeighborhoodSearchImpl::init(
     }
     if (bGrid_)
     {
-        if (xref_nalloc_ < n)
+        if (xref_nalloc_ < nref_)
         {
-            srenew(xref_alloc_, n);
-            xref_nalloc_ = n;
+            srenew(xref_alloc_, nref_);
+            xref_nalloc_ = nref_;
         }
         xref_ = xref_alloc_;
 
-        for (int i = 0; i < n; ++i)
+        for (int i = 0; i < nref_; ++i)
         {
-            copy_rvec(x[i], xref_alloc_[i]);
+            copy_rvec(positions.x_[i], xref_alloc_[i]);
         }
-        put_atoms_in_triclinic_unitcell(ecenterTRIC, pbc_->box, n, xref_alloc_);
-        for (int i = 0; i < n; ++i)
+        put_atoms_in_triclinic_unitcell(ecenterTRIC, pbc_->box,
+                                        nref_, xref_alloc_);
+        for (int i = 0; i < nref_; ++i)
         {
             ivec refcell;
 
@@ -488,9 +507,10 @@ void AnalysisNeighborhoodSearchImpl::init(
     }
     else
     {
-        xref_ = x;
+        xref_ = positions.x_;
     }
-    refid_ = refid;
+    // TODO: Once exclusions are supported, this may need to be initialized.
+    refid_ = NULL;
 }
 
 #if 0
@@ -517,12 +537,32 @@ gmx_ana_nbsearch_set_excl(gmx_ana_nbsearch_t *d, int nexcl, int excl[])
  * AnalysisNeighborhoodPairSearchImpl
  */
 
-void AnalysisNeighborhoodPairSearchImpl::reset()
+void AnalysisNeighborhoodPairSearchImpl::reset(int testIndex)
 {
-    previ_   = -1;
-    exclind_ = 0;
-    prevnbi_ = 0;
-    prevcai_ = -1;
+    testIndex_ = testIndex;
+    if (testIndex_ >= 0 && testIndex_ < static_cast<int>(testPositions_.size()))
+    {
+        copy_rvec(testPositions_[testIndex_], xtest_);
+        if (search_.bGrid_)
+        {
+            put_atoms_in_triclinic_unitcell(ecenterTRIC, search_.pbc_->box,
+                                            1, &xtest_);
+            search_.mapPointToGridCell(xtest_, testcell_);
+        }
+    }
+    previ_     = -1;
+    exclind_   = 0;
+    prevnbi_   = 0;
+    prevcai_   = -1;
+}
+
+void AnalysisNeighborhoodPairSearchImpl::nextTestPosition()
+{
+    if (testIndex_ < static_cast<int>(testPositions_.size()))
+    {
+        ++testIndex_;
+        reset(testIndex_);
+    }
 }
 
 bool AnalysisNeighborhoodPairSearchImpl::isExcluded(int j)
@@ -560,93 +600,100 @@ bool AnalysisNeighborhoodPairSearchImpl::isExcluded(int j)
     return false;
 }
 
-void AnalysisNeighborhoodPairSearchImpl::startSearch(const rvec x, int testIndex)
+void AnalysisNeighborhoodPairSearchImpl::startSearch(
+        const AnalysisNeighborhoodPositions &positions)
 {
-    testIndex_ = testIndex;
-    copy_rvec(x, xtest_);
-    if (search_.bGrid_)
+    if (positions.index_ < 0)
     {
-        put_atoms_in_triclinic_unitcell(ecenterTRIC, search_.pbc_->box,
-                                        1, &xtest_);
-        search_.mapPointToGridCell(xtest_, testcell_);
+        testPositions_ = ConstArrayRef<rvec>(positions.x_, positions.count_);
+        reset(0);
+    }
+    else
+    {
+        // Somewhat of a hack: setup the array such that only the last position
+        // will be used.
+        testPositions_ = ConstArrayRef<rvec>(positions.x_, positions.index_ + 1);
+        reset(positions.index_);
     }
-    reset();
 }
 
 template <class Action>
 bool AnalysisNeighborhoodPairSearchImpl::searchNext(Action action)
 {
-    if (search_.bGrid_)
+    while (testIndex_ < static_cast<int>(testPositions_.size()))
     {
-        int nbi = prevnbi_;
-        int cai = prevcai_ + 1;
-
-        for (; nbi < search_.ngridnb_; ++nbi)
+        if (search_.bGrid_)
         {
-            ivec cell;
+            int nbi = prevnbi_;
+            int cai = prevcai_ + 1;
+
+            for (; nbi < search_.ngridnb_; ++nbi)
+            {
+                ivec cell;
 
-            ivec_add(testcell_, search_.gnboffs_[nbi], cell);
-            cell[XX] = (cell[XX] + search_.ncelldim_[XX]) % search_.ncelldim_[XX];
-            cell[YY] = (cell[YY] + search_.ncelldim_[YY]) % search_.ncelldim_[YY];
-            cell[ZZ] = (cell[ZZ] + search_.ncelldim_[ZZ]) % search_.ncelldim_[ZZ];
+                ivec_add(testcell_, search_.gnboffs_[nbi], cell);
+                cell[XX] = (cell[XX] + search_.ncelldim_[XX]) % search_.ncelldim_[XX];
+                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 cellSize = static_cast<int>(search_.cells_[ci].size());
-            /* TODO: Calculate the required PBC shift outside the inner loop */
-            for (; cai < cellSize; ++cai)
+                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 < cellSize; ++cai)
+                {
+                    const int i = search_.cells_[ci][cai];
+                    if (isExcluded(i))
+                    {
+                        continue;
+                    }
+                    rvec       dx;
+                    pbc_dx_aiuc(search_.pbc_, xtest_, search_.xref_[i], dx);
+                    const real r2 = norm2(dx);
+                    if (r2 <= search_.cutoff2_)
+                    {
+                        if (action(i, r2))
+                        {
+                            prevnbi_ = nbi;
+                            prevcai_ = cai;
+                            previ_   = i;
+                            return true;
+                        }
+                    }
+                }
+                exclind_ = 0;
+                cai      = 0;
+            }
+        }
+        else
+        {
+            for (int i = previ_ + 1; i < search_.nref_; ++i)
             {
-                const int i = search_.cells_[ci][cai];
                 if (isExcluded(i))
                 {
                     continue;
                 }
-                rvec       dx;
-                pbc_dx_aiuc(search_.pbc_, xtest_, search_.xref_[i], dx);
+                rvec dx;
+                if (search_.pbc_)
+                {
+                    pbc_dx(search_.pbc_, xtest_, search_.xref_[i], dx);
+                }
+                else
+                {
+                    rvec_sub(xtest_, search_.xref_[i], dx);
+                }
                 const real r2 = norm2(dx);
                 if (r2 <= search_.cutoff2_)
                 {
                     if (action(i, r2))
                     {
-                        prevnbi_ = nbi;
-                        prevcai_ = cai;
-                        previ_   = i;
+                        previ_ = i;
                         return true;
                     }
                 }
             }
-            exclind_ = 0;
-            cai      = 0;
         }
+        nextTestPosition();
     }
-    else
-    {
-        for (int i = previ_ + 1; i < search_.nref_; ++i)
-        {
-            if (isExcluded(i))
-            {
-                continue;
-            }
-            rvec dx;
-            if (search_.pbc_)
-            {
-                pbc_dx(search_.pbc_, xtest_, search_.xref_[i], dx);
-            }
-            else
-            {
-                rvec_sub(xtest_, search_.xref_[i], dx);
-            }
-            const real r2 = norm2(dx);
-            if (r2 <= search_.cutoff2_)
-            {
-                if (action(i, r2))
-                {
-                    previ_ = i;
-                    return true;
-                }
-            }
-        }
-    }
-    reset();
     return false;
 }
 
@@ -811,18 +858,11 @@ AnalysisNeighborhood::SearchMode AnalysisNeighborhood::mode() const
 }
 
 AnalysisNeighborhoodSearch
-AnalysisNeighborhood::initSearch(const t_pbc *pbc, int n, const rvec x[])
+AnalysisNeighborhood::initSearch(const t_pbc                         *pbc,
+                                 const AnalysisNeighborhoodPositions &positions)
 {
     Impl::SearchImplPointer search(impl_->getSearch());
-    search->init(mode(), pbc, n, x, NULL);
-    return AnalysisNeighborhoodSearch(search);
-}
-
-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, p->m.refid);
+    search->init(mode(), pbc, positions);
     return AnalysisNeighborhoodSearch(search);
 }
 
@@ -852,24 +892,21 @@ AnalysisNeighborhood::SearchMode AnalysisNeighborhoodSearch::mode() const
             : AnalysisNeighborhood::eSearchMode_Simple);
 }
 
-bool AnalysisNeighborhoodSearch::isWithin(const rvec x) const
+bool AnalysisNeighborhoodSearch::isWithin(
+        const AnalysisNeighborhoodPositions &positions) const
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
-    pairSearch.startSearch(x, 0);
+    pairSearch.startSearch(positions);
     return pairSearch.searchNext(&withinAction);
 }
 
-bool AnalysisNeighborhoodSearch::isWithin(const gmx_ana_pos_t *p, int i) const
-{
-    return isWithin(p->x[i]);
-}
-
-real AnalysisNeighborhoodSearch::minimumDistance(const rvec x) const
+real AnalysisNeighborhoodSearch::minimumDistance(
+        const AnalysisNeighborhoodPositions &positions) const
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
-    pairSearch.startSearch(x, 0);
+    pairSearch.startSearch(positions);
     real          minDist2     = impl_->cutoffSquared();
     int           closestPoint = -1;
     MindistAction action(&closestPoint, &minDist2);
@@ -877,17 +914,13 @@ real AnalysisNeighborhoodSearch::minimumDistance(const rvec x) const
     return sqrt(minDist2);
 }
 
-real AnalysisNeighborhoodSearch::minimumDistance(const gmx_ana_pos_t *p, int i) const
-{
-    return minimumDistance(p->x[i]);
-}
-
 AnalysisNeighborhoodPair
-AnalysisNeighborhoodSearch::nearestPoint(const rvec x) const
+AnalysisNeighborhoodSearch::nearestPoint(
+        const AnalysisNeighborhoodPositions &positions) const
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     internal::AnalysisNeighborhoodPairSearchImpl pairSearch(*impl_);
-    pairSearch.startSearch(x, 0);
+    pairSearch.startSearch(positions);
     real          minDist2     = impl_->cutoffSquared();
     int           closestPoint = -1;
     MindistAction action(&closestPoint, &minDist2);
@@ -895,34 +928,13 @@ AnalysisNeighborhoodSearch::nearestPoint(const rvec x) const
     return AnalysisNeighborhoodPair(closestPoint, 0);
 }
 
-AnalysisNeighborhoodPair
-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_->cutoffSquared();
-    int           closestPoint = -1;
-    MindistAction action(&closestPoint, &minDist2);
-    (void)pairSearch.searchNext(action);
-    return AnalysisNeighborhoodPair(closestPoint, i);
-}
-
 AnalysisNeighborhoodPairSearch
-AnalysisNeighborhoodSearch::startPairSearch(const rvec x)
+AnalysisNeighborhoodSearch::startPairSearch(
+        const AnalysisNeighborhoodPositions &positions) const
 {
     GMX_RELEASE_ASSERT(impl_, "Accessing an invalid search object");
     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");
-    Impl::PairSearchImplPointer pairSearch(impl_->getPairSearch());
-    pairSearch->startSearch(p->x[i], i);
+    pairSearch->startSearch(positions);
     return AnalysisNeighborhoodPairSearch(pairSearch);
 }
 
@@ -943,4 +955,9 @@ bool AnalysisNeighborhoodPairSearch::findNextPair(AnalysisNeighborhoodPair *pair
     return bFound;
 }
 
+void AnalysisNeighborhoodPairSearch::skipRemainingPairsForTestPosition()
+{
+    impl_->nextTestPosition();
+}
+
 } // namespace gmx
index e923b77fbda96fe858f7048c4f7a08c5b0a5b01e..646252168ec3b85e126eaa5a2c95f288a489b034 100644 (file)
@@ -71,6 +71,80 @@ class AnalysisNeighborhoodPairSearchImpl;
 class AnalysisNeighborhoodSearch;
 class AnalysisNeighborhoodPairSearch;
 
+/*! \brief
+ * Input positions for neighborhood searching.
+ *
+ * This class supports uniformly specifying sets of positions for various
+ * methods in the analysis neighborhood searching classes
+ * (AnalysisNeighborhood and AnalysisNeighborhoodSearch).
+ *
+ * Note that copies are not made: only a reference to the positions passed to
+ * the constructors are kept.  The caller is responsible to ensure that those
+ * positions remain in scope as long as the neighborhood search object requires
+ * access to them.
+ *
+ * Also note that in addition to constructors here, Selection and
+ * SelectionPosition provide conversions operators to this type.  It is done
+ * this way to not introduce a cyclic dependency between the selection code and
+ * the neighborhood search code, which in turn allows splitting this search
+ * code into a separate lower-level module if desired at some point.
+ *
+ * Methods in this class do not throw.
+ *
+ * \inpublicapi
+ * \ingroup module_selection
+ */
+class AnalysisNeighborhoodPositions
+{
+    public:
+        /*! \brief
+         * Initializes positions from a single position vector.
+         *
+         * For positions initialized this way, AnalysisNeighborhoodPair always
+         * returns zero in the corresponding index.
+         *
+         * This constructor is not explicit to allow directly passing an rvec
+         * to methods that accept positions.
+         */
+        AnalysisNeighborhoodPositions(const rvec &x)
+            : count_(1), index_(-1), x_(&x)
+        {
+        }
+        /*! \brief
+         * Initializes positions from an array of position vectors.
+         */
+        AnalysisNeighborhoodPositions(const rvec x[], int count)
+            : count_(count), index_(-1), x_(x)
+        {
+        }
+
+        /*! \brief
+         * Selects a single position to use from an array.
+         *
+         * If called, a single position from the array of positions passed to
+         * the constructor is used instead of the whole array.
+         * In contrast to the AnalysisNeighborhoodPositions(const rvec &)
+         * constructor, AnalysisNeighborhoodPair objects return \p index
+         * instead of zero.
+         */
+        AnalysisNeighborhoodPositions &selectSingleFromArray(int index)
+        {
+            GMX_ASSERT(index >= 0 && index < count_, "Invalid position index");
+            index_ = index;
+            return *this;
+        }
+
+    private:
+        int                     count_;
+        int                     index_;
+        const rvec             *x_;
+
+        //! To access the positions for initialization.
+        friend class internal::AnalysisNeighborhoodSearchImpl;
+        //! To access the positions for initialization.
+        friend class internal::AnalysisNeighborhoodPairSearchImpl;
+};
+
 /*! \brief
  * Neighborhood searching for analysis tools.
  *
@@ -153,26 +227,18 @@ class AnalysisNeighborhood
         /*! \brief
          * Initializes neighborhood search for a set of positions.
          *
-         * \param[in] pbc PBC information for the frame.
-         * \param[in] n   Number of reference positions for the frame.
-         * \param[in] x   \p n reference positions for the frame.
+         * \param[in] pbc        PBC information for the frame.
+         * \param[in] positions  Set of reference positions to use.
          * \returns   Search object that can be used to find positions from
          *      \p x within the given cutoff.
          * \throws    std::bad_alloc if out of memory.
-         */
-        AnalysisNeighborhoodSearch
-        initSearch(const t_pbc *pbc, int n, const rvec x[]);
-        /*! \brief
-         * Initializes neighborhood search for a set of positions.
          *
-         * \param[in] pbc PBC information for the frame.
-         * \param[in] p   Reference positions for the frame.
-         * \returns   Search object that can be used to find positions from
-         *      \p p within the given cutoff.
-         * \throws    std::bad_alloc if out of memory.
+         * Currently, the input positions cannot use
+         * AnalysisNeighborhoodPositions::selectSingleFromArray().
          */
         AnalysisNeighborhoodSearch
-        initSearch(const t_pbc *pbc, const gmx_ana_pos_t *p);
+        initSearch(const t_pbc                         *pbc,
+                   const AnalysisNeighborhoodPositions &positions);
 
     private:
         class Impl;
@@ -315,86 +381,43 @@ class AnalysisNeighborhoodSearch
         /*! \brief
          * Check whether a point is within a neighborhood.
          *
-         * \param[in] x  Test position.
-         * \returns   true if the test position is within the cutoff of any
-         *     reference position.
-         */
-        bool isWithin(const rvec x) const;
-        /*! \brief
-         * Check whether a point is within a neighborhood.
-         *
-         * \param[in] p  Test positions.
-         * \param[in] i  Use the i'th position in \p p for testing.
-         * \returns   true if the test position is within the cutoff of any
-         *     reference position.
+         * \param[in] positions  Set of test positions to use.
+         * \returns   true if any of the test positions is within the cutoff of
+         *     any reference position.
          */
-        bool isWithin(const gmx_ana_pos_t *p, int i) const;
+        bool isWithin(const AnalysisNeighborhoodPositions &positions) const;
         /*! \brief
          * Calculates the minimum distance from the reference points.
          *
-         * \param[in] x  Test position.
+         * \param[in] positions  Set of test positions to use.
          * \returns   The distance to the nearest reference position, or the
          *     cutoff value if there are no reference positions within the
          *     cutoff.
          */
-        real minimumDistance(const rvec x) const;
-        /*! \brief
-         * Calculates the minimum distance from the reference points.
-         *
-         * \param[in] p  Test positions.
-         * \param[in] i  Use the i'th position in \p p for testing.
-         * \returns   The distance to the nearest reference position, or the
-         *     cutoff value if there are no reference positions within the
-         *     cutoff.
-         */
-        real minimumDistance(const gmx_ana_pos_t *p, int i) const;
-        /*! \brief
-         * Finds the closest reference point.
-         *
-         * \param[in] x  Test position.
-         * \returns   The reference index identifies the reference position
-         *     that is closest to the test position.
-         *     The test index is always zero.  The returned pair is invalid if
-         *     no reference position is within the cutoff.
-         */
-        AnalysisNeighborhoodPair nearestPoint(const rvec x) const;
+        real minimumDistance(const AnalysisNeighborhoodPositions &positions) const;
         /*! \brief
          * Finds the closest reference point.
          *
-         * \param[in] p  Test positions.
-         * \param[in] i  Use the i'th position in \p p for testing.
+         * \param[in] positions  Set of test positions to use.
          * \returns   The reference index identifies the reference position
-         *     that is closest to the test position.
-         *     The test index is always \p i.  The returned pair is invalid if
+         *     that is closest to the test positions.
+         *     The test index identifies the test position that is closest to
+         *     the provided test position.  The returned pair is invalid if
          *     no reference position is within the cutoff.
          */
-        AnalysisNeighborhoodPair nearestPoint(const gmx_ana_pos_t *p, int i) const;
+        AnalysisNeighborhoodPair
+        nearestPoint(const AnalysisNeighborhoodPositions &positions) const;
 
         /*! \brief
          * Start a search to find reference positions within a cutoff.
          *
-         * \param[in] x  Test position to search the neighbors for.
+         * \param[in] positions  Set of test positions to use.
          * \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.
          */
-        AnalysisNeighborhoodPairSearch startPairSearch(const rvec x);
-        /*! \brief
-         * Start a search to find reference positions within a cutoff.
-         *
-         * \param[in] p  Test positions.
-         * \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.
-         */
-        AnalysisNeighborhoodPairSearch startPairSearch(const gmx_ana_pos_t *p, int i);
+        AnalysisNeighborhoodPairSearch
+        startPairSearch(const AnalysisNeighborhoodPositions &positions) const;
 
     private:
         typedef internal::AnalysisNeighborhoodSearchImpl Impl;
@@ -410,8 +433,9 @@ class AnalysisNeighborhoodSearch
  * \code
    gmx::AnalysisNeighborhood       nb;
    nb.setCutoff(cutoff);
-   gmx::AnalysisNeighborhoodSearch search = nb.initSearch(pbc, nref, xref);
-   gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(x);
+   gmx::AnalysisNeighborhoodPositions refPos(xref, nref);
+   gmx::AnalysisNeighborhoodSearch search = nb.initSearch(pbc, refPos);
+   gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(selection);
    gmx::AnalysisNeighborhoodPair pair;
    while (pairSearch.findNextPair(&pair))
    {
@@ -464,6 +488,19 @@ class AnalysisNeighborhoodPairSearch
          * \see AnalysisNeighborhoodSearch::startPairSearch()
          */
         bool findNextPair(AnalysisNeighborhoodPair *pair);
+        /*! \brief
+         * Skip remaining pairs for a test position in the search.
+         *
+         * When called after findNextPair(), makes subsequent calls to
+         * findNextPair() skip any pairs that have the same test position as
+         * that previously returned.
+         * This is useful if the caller wants to search whether any reference
+         * position within the cutoff satisfies some condition.  This method
+         * can be used to skip remaining pairs after the first such position
+         * has been found if the remaining pairs would not have an effect on
+         * the outcome.
+         */
+        void skipRemainingPairsForTestPosition();
 
     private:
         ImplPointer             impl_;
index bfc0a14b74eeb79bea127758c9a8a6838c72d68f..573b086ba1bae83f3d17f1ccc179cc76807ffa9f 100644 (file)
@@ -41,6 +41,7 @@
  */
 #include "selection.h"
 
+#include "nbsearch.h"
 #include "position.h"
 #include "selelem.h"
 #include "selvalue.h"
@@ -51,6 +52,10 @@ namespace gmx
 namespace internal
 {
 
+/********************************************************************
+ * SelectionData
+ */
+
 SelectionData::SelectionData(SelectionTreeElement *elem,
                              const char           *selstr)
     : name_(elem->name()), selectionText_(selstr),
@@ -231,6 +236,16 @@ SelectionData::restoreOriginalPositions(const t_topology *top)
 
 }   // namespace internal
 
+/********************************************************************
+ * Selection
+ */
+
+Selection::operator AnalysisNeighborhoodPositions() const
+{
+    return AnalysisNeighborhoodPositions(data().rawPositions_.x,
+                                         data().rawPositions_.nr);
+}
+
 
 void
 Selection::printInfo(FILE *fp) const
@@ -318,4 +333,16 @@ Selection::printDebugInfo(FILE *fp, int nmaxind) const
     fprintf(fp, "\n");
 }
 
+
+/********************************************************************
+ * SelectionPosition
+ */
+
+SelectionPosition::operator AnalysisNeighborhoodPositions() const
+{
+    return AnalysisNeighborhoodPositions(sel_->rawPositions_.x,
+                                         sel_->rawPositions_.nr)
+               .selectSingleFromArray(i_);
+}
+
 } // namespace gmx
index b91ed8b213e0751432956b0898c64d8fe863b3a4..f910ab817bcb36e1d72453389240f1d25f1b1595 100644 (file)
@@ -62,6 +62,7 @@ namespace gmx
 class SelectionOptionStorage;
 class SelectionTreeElement;
 
+class AnalysisNeighborhoodPositions;
 class Selection;
 class SelectionPosition;
 
@@ -383,14 +384,25 @@ class Selection
             return ConstArrayRef<int>(data().rawPositions_.m.mapid, posCount());
         }
 
-        //! Deprecated method for direct access to position data.
-        const gmx_ana_pos_t *positions() const { return &data().rawPositions_; }
-
         //! Returns whether the covered fraction can change between frames.
         bool isCoveredFractionDynamic() const { return data().isCoveredFractionDynamic(); }
         //! Returns the covered fraction for the current frame.
         real coveredFraction() const { return data().coveredFraction_; }
 
+        /*! \brief
+         * Allows passing a selection directly to neighborhood searching.
+         *
+         * When initialized this way, AnalysisNeighborhoodPair objects return
+         * indices that can be used to index the selection positions with
+         * position().
+         *
+         * Works exactly like if AnalysisNeighborhoodPositions had a
+         * constructor taking a Selection object as a parameter.
+         * See AnalysisNeighborhoodPositions for rationale and additional
+         * discussion.
+         */
+        operator AnalysisNeighborhoodPositions() const;
+
         /*! \brief
          * Initializes information about covered fractions.
          *
@@ -677,6 +689,20 @@ class SelectionPosition
             return sel_->rawPositions_.m.mapid[i_];
         }
 
+        /*! \brief
+         * Allows passing a selection position directly to neighborhood searching.
+         *
+         * When initialized this way, AnalysisNeighborhoodPair objects return
+         * the index that can be used to access this position using
+         * Selection::position().
+         *
+         * Works exactly like if AnalysisNeighborhoodPositions had a
+         * constructor taking a SelectionPosition object as a parameter.
+         * See AnalysisNeighborhoodPositions for rationale and additional
+         * discussion.
+         */
+        operator AnalysisNeighborhoodPositions() const;
+
     private:
         const internal::SelectionData  *sel_;
         int                             i_;
index 9e315791bc46e2f33716e84de560bdaef8ff2f90..6e9d8d276a78fa704c4c9cea9342d740473c62aa 100644 (file)
@@ -251,7 +251,8 @@ init_frame_common(t_topology *top, t_trxframe *fr, t_pbc *pbc, void *data)
     t_methoddata_distance *d = (t_methoddata_distance *)data;
 
     d->nbsearch.reset();
-    d->nbsearch = d->nb.initSearch(pbc, &d->p);
+    gmx::AnalysisNeighborhoodPositions pos(d->p.x, d->p.nr);
+    d->nbsearch = d->nb.initSearch(pbc, pos);
 }
 
 /*!
@@ -270,7 +271,7 @@ evaluate_distance(t_topology *top, t_trxframe *fr, t_pbc *pbc,
     out->nr = pos->g->isize;
     for (int b = 0; b < pos->nr; ++b)
     {
-        real dist = d->nbsearch.minimumDistance(pos, b);
+        real dist = d->nbsearch.minimumDistance(pos->x[b]);
         for (int i = pos->m.mapb.index[b]; i < pos->m.mapb.index[b+1]; ++i)
         {
             out->u.r[i] = dist;
@@ -294,7 +295,7 @@ evaluate_within(t_topology *top, t_trxframe *fr, t_pbc *pbc,
     out->u.g->isize = 0;
     for (int b = 0; b < pos->nr; ++b)
     {
-        if (d->nbsearch.isWithin(pos, b))
+        if (d->nbsearch.isWithin(pos->x[b]))
         {
             gmx_ana_pos_append(NULL, out->u.g, pos, b, 0);
         }
index f50202df464da2e0fa555e0d3522e8a80764f35f..0fadff19a949bf8f96421b0fb6fead2a829f9d98 100644 (file)
@@ -93,8 +93,32 @@ class NeighborhoodSearchTestData
         NeighborhoodSearchTestData(int seed, real cutoff);
         ~NeighborhoodSearchTestData();
 
+        gmx::AnalysisNeighborhoodPositions refPositions() const
+        {
+            return gmx::AnalysisNeighborhoodPositions(refPos_, refPosCount_);
+        }
+        gmx::AnalysisNeighborhoodPositions testPositions() const
+        {
+            if (testPos_ == NULL)
+            {
+                snew(testPos_, testPositions_.size());
+                for (size_t i = 0; i < testPositions_.size(); ++i)
+                {
+                    copy_rvec(testPositions_[i].x, testPos_[i]);
+                }
+            }
+            return gmx::AnalysisNeighborhoodPositions(testPos_,
+                                                      testPositions_.size());
+        }
+        gmx::AnalysisNeighborhoodPositions testPosition(int index) const
+        {
+            return testPositions().selectSingleFromArray(index);
+        }
+
         void addTestPosition(const rvec x)
         {
+            GMX_RELEASE_ASSERT(testPos_ == NULL,
+                               "Cannot add positions after testPositions() call");
             testPositions_.push_back(TestPosition(x));
         }
         void generateRandomPosition(rvec x);
@@ -109,10 +133,13 @@ class NeighborhoodSearchTestData
         int                              refPosCount_;
         rvec                            *refPos_;
         TestPositionList                 testPositions_;
+
+    private:
+        mutable rvec                    *testPos_;
 };
 
 NeighborhoodSearchTestData::NeighborhoodSearchTestData(int seed, real cutoff)
-    : rng_(NULL), cutoff_(cutoff), refPosCount_(0), refPos_(NULL)
+    : rng_(NULL), cutoff_(cutoff), refPosCount_(0), refPos_(NULL), testPos_(NULL)
 {
     // TODO: Handle errors.
     rng_ = gmx_rng_init(seed);
@@ -127,6 +154,7 @@ NeighborhoodSearchTestData::~NeighborhoodSearchTestData()
         gmx_rng_destroy(rng_);
     }
     sfree(refPos_);
+    sfree(testPos_);
 }
 
 void NeighborhoodSearchTestData::generateRandomPosition(rvec x)
@@ -413,7 +441,7 @@ TEST_F(NeighborhoodSearchTest, SimpleSearch)
     nb_.setCutoff(data.cutoff_);
     nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
     gmx::AnalysisNeighborhoodSearch search =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
     ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
 
     testIsWithin(&search, data);
@@ -429,7 +457,7 @@ TEST_F(NeighborhoodSearchTest, GridSearchBox)
     nb_.setCutoff(data.cutoff_);
     nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
     gmx::AnalysisNeighborhoodSearch search =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
     ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
 
     testIsWithin(&search, data);
@@ -445,7 +473,7 @@ TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
     nb_.setCutoff(data.cutoff_);
     nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
     gmx::AnalysisNeighborhoodSearch search =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
     ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
 
     testPairSearch(&search, data);
@@ -458,7 +486,7 @@ TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
     nb_.setCutoff(data.cutoff_);
     nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
     gmx::AnalysisNeighborhoodSearch search =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
     // Currently, grid searching not supported with 2D PBC.
     //ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
 
@@ -474,14 +502,14 @@ TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
 
     nb_.setCutoff(data.cutoff_);
     gmx::AnalysisNeighborhoodSearch search1 =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
     gmx::AnalysisNeighborhoodSearch search2 =
-        nb_.initSearch(&data.pbc_, data.refPosCount_, data.refPos_);
+        nb_.initSearch(&data.pbc_, data.refPositions());
 
     gmx::AnalysisNeighborhoodPairSearch pairSearch1 =
-        search1.startPairSearch(data.testPositions_[0].x);
+        search1.startPairSearch(data.testPosition(0));
     gmx::AnalysisNeighborhoodPairSearch pairSearch2 =
-        search1.startPairSearch(data.testPositions_[1].x);
+        search1.startPairSearch(data.testPosition(1));
 
     testPairSearch(&search2, data);
 
@@ -491,8 +519,36 @@ TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
     EXPECT_TRUE(data.testPositions_[0].refPairs.count(pair.refIndex()) == 1);
 
     pairSearch2.findNextPair(&pair);
-    EXPECT_EQ(0, pair.testIndex());
+    EXPECT_EQ(1, pair.testIndex());
     EXPECT_TRUE(data.testPositions_[1].refPairs.count(pair.refIndex()) == 1);
 }
 
+TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
+{
+    const NeighborhoodSearchTestData &data = TrivialTestData::get();
+
+    nb_.setCutoff(data.cutoff_);
+    gmx::AnalysisNeighborhoodSearch     search =
+        nb_.initSearch(&data.pbc_, data.refPositions());
+    gmx::AnalysisNeighborhoodPairSearch pairSearch =
+        search.startPairSearch(data.testPositions());
+    gmx::AnalysisNeighborhoodPair       pair;
+    // TODO: This test needs to be adjusted if the grid search gets optimized
+    // to loop over the test positions in cell order (first, the ordering
+    // assumption here breaks, and second, it then needs to be tested
+    // separately for simple and grid searches).
+    int currentIndex = 0;
+    while (pairSearch.findNextPair(&pair))
+    {
+        while (currentIndex < pair.testIndex())
+        {
+            ++currentIndex;
+        }
+        EXPECT_EQ(currentIndex, pair.testIndex());
+        EXPECT_TRUE(data.testPositions_[currentIndex].refPairs.count(pair.refIndex()) == 1);
+        pairSearch.skipRemainingPairsForTestPosition();
+        ++currentIndex;
+    }
+}
+
 } // namespace
index fd0b295f8e18c759c40373921f153c442a4e2ece..103057fe7802da4a12c0db02b0868098f2ac798a 100644 (file)
@@ -282,7 +282,7 @@ FreeVolume::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
     int  Ninsert = static_cast<int>(ninsert_*V);
 
     // Use neighborsearching tools!
-    AnalysisNeighborhoodSearch nbsearch = nb_.initSearch(pbc, sel.positions());
+    AnalysisNeighborhoodSearch nbsearch = nb_.initSearch(pbc, sel);
 
     // Then loop over insertions
     int NinsTot = 0;