Introduce self-pairs search in nbsearch
[gromacs.git] / src / gromacs / selection / tests / nbsearch.cpp
blobdae59c0ba09616af01d61b8b65d3c638ec0c3c65
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
35 /*! \internal \file
36 * \brief
37 * Tests selection neighborhood searching.
39 * \todo
40 * Increase coverage of these tests for different corner cases: other PBC cases
41 * than full 3D, large cutoffs (larger than half the box size), etc.
42 * At least some of these probably don't work correctly.
44 * \author Teemu Murtola <teemu.murtola@gmail.com>
45 * \ingroup module_selection
47 #include "gmxpre.h"
49 #include "gromacs/selection/nbsearch.h"
51 #include <cmath>
53 #include <algorithm>
54 #include <limits>
55 #include <map>
56 #include <numeric>
57 #include <vector>
59 #include <gtest/gtest.h>
61 #include "gromacs/math/functions.h"
62 #include "gromacs/math/vec.h"
63 #include "gromacs/pbcutil/pbc.h"
64 #include "gromacs/random/threefry.h"
65 #include "gromacs/random/uniformrealdistribution.h"
66 #include "gromacs/topology/block.h"
67 #include "gromacs/utility/smalloc.h"
68 #include "gromacs/utility/stringutil.h"
70 #include "testutils/testasserts.h"
73 namespace
76 /********************************************************************
77 * NeighborhoodSearchTestData
80 class NeighborhoodSearchTestData
82 public:
83 struct RefPair
85 RefPair(int refIndex, real distance)
86 : refIndex(refIndex), distance(distance), bFound(false),
87 bExcluded(false), bIndexed(true)
91 bool operator<(const RefPair &other) const
93 return refIndex < other.refIndex;
96 int refIndex;
97 real distance;
98 // The variables below are state variables that are only used
99 // during the actual testing after creating a copy of the reference
100 // pair list, not as part of the reference data.
101 // Simpler to have just a single structure for both purposes.
102 bool bFound;
103 bool bExcluded;
104 bool bIndexed;
107 struct TestPosition
109 explicit TestPosition(const rvec x)
110 : refMinDist(0.0), refNearestPoint(-1)
112 copy_rvec(x, this->x);
115 rvec x;
116 real refMinDist;
117 int refNearestPoint;
118 std::vector<RefPair> refPairs;
121 typedef std::vector<TestPosition> TestPositionList;
123 NeighborhoodSearchTestData(gmx_uint64_t seed, real cutoff);
125 gmx::AnalysisNeighborhoodPositions refPositions() const
127 return gmx::AnalysisNeighborhoodPositions(refPos_);
129 gmx::AnalysisNeighborhoodPositions testPositions() const
131 if (testPos_.empty())
133 testPos_.reserve(testPositions_.size());
134 for (size_t i = 0; i < testPositions_.size(); ++i)
136 testPos_.emplace_back(testPositions_[i].x);
139 return gmx::AnalysisNeighborhoodPositions(testPos_);
141 gmx::AnalysisNeighborhoodPositions testPosition(int index) const
143 return testPositions().selectSingleFromArray(index);
146 void addTestPosition(const rvec x)
148 GMX_RELEASE_ASSERT(testPos_.empty(),
149 "Cannot add positions after testPositions() call");
150 testPositions_.emplace_back(x);
152 gmx::RVec generateRandomPosition();
153 std::vector<int> generateIndex(int count, gmx_uint64_t seed) const;
154 void generateRandomRefPositions(int count);
155 void generateRandomTestPositions(int count);
156 void useRefPositionsAsTestPositions();
157 void computeReferences(t_pbc *pbc)
159 computeReferencesInternal(pbc, false);
161 void computeReferencesXY(t_pbc *pbc)
163 computeReferencesInternal(pbc, true);
166 bool containsPair(int testIndex, const RefPair &pair) const
168 const std::vector<RefPair> &refPairs = testPositions_[testIndex].refPairs;
169 std::vector<RefPair>::const_iterator foundRefPair
170 = std::lower_bound(refPairs.begin(), refPairs.end(), pair);
171 if (foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex)
173 return false;
175 return true;
178 // Return a tolerance that accounts for the magnitudes of the coordinates
179 // when doing subtractions, so that we set the ULP tolerance relative to the
180 // coordinate values rather than their difference.
181 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
182 // to 10.0, but a much larger error relative to the difference.
183 gmx::test::FloatingPointTolerance relativeTolerance() const
185 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
186 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
189 gmx::DefaultRandomEngine rng_;
190 real cutoff_;
191 matrix box_;
192 t_pbc pbc_;
193 int refPosCount_;
194 std::vector<gmx::RVec> refPos_;
195 TestPositionList testPositions_;
197 private:
198 void computeReferencesInternal(t_pbc *pbc, bool bXY);
200 mutable std::vector<gmx::RVec> testPos_;
203 //! Shorthand for a collection of reference pairs.
204 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
206 NeighborhoodSearchTestData::NeighborhoodSearchTestData(gmx_uint64_t seed, real cutoff)
207 : rng_(seed), cutoff_(cutoff), refPosCount_(0)
209 clear_mat(box_);
210 set_pbc(&pbc_, epbcNONE, box_);
213 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
215 gmx::UniformRealDistribution<real> dist;
216 rvec fx, x;
217 fx[XX] = dist(rng_);
218 fx[YY] = dist(rng_);
219 fx[ZZ] = dist(rng_);
220 mvmul(box_, fx, x);
221 // Add a small displacement to allow positions outside the box
222 x[XX] += 0.2 * dist(rng_) - 0.1;
223 x[YY] += 0.2 * dist(rng_) - 0.1;
224 x[ZZ] += 0.2 * dist(rng_) - 0.1;
225 return x;
228 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, gmx_uint64_t seed) const
230 gmx::DefaultRandomEngine rngIndex(seed);
231 gmx::UniformRealDistribution<real> dist;
232 std::vector<int> result;
234 for (int i = 0; i < count; ++i)
236 if (dist(rngIndex) > 0.5)
238 result.push_back(i);
241 return result;
244 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
246 refPosCount_ = count;
247 refPos_.reserve(count);
248 for (int i = 0; i < count; ++i)
250 refPos_.push_back(generateRandomPosition());
254 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
256 testPositions_.reserve(count);
257 for (int i = 0; i < count; ++i)
259 addTestPosition(generateRandomPosition());
263 void NeighborhoodSearchTestData::useRefPositionsAsTestPositions()
265 testPositions_.reserve(refPosCount_);
266 for (const auto &refPos : refPos_)
268 addTestPosition(refPos);
272 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc *pbc, bool bXY)
274 real cutoff = cutoff_;
275 if (cutoff <= 0)
277 cutoff = std::numeric_limits<real>::max();
279 for (TestPosition &testPos : testPositions_)
281 testPos.refMinDist = cutoff;
282 testPos.refNearestPoint = -1;
283 testPos.refPairs.clear();
284 for (int j = 0; j < refPosCount_; ++j)
286 rvec dx;
287 if (pbc != nullptr)
289 pbc_dx(pbc, testPos.x, refPos_[j], dx);
291 else
293 rvec_sub(testPos.x, refPos_[j], dx);
295 // TODO: This may not work intuitively for 2D with the third box
296 // vector not parallel to the Z axis, but neither does the actual
297 // neighborhood search.
298 const real dist =
299 !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
300 if (dist < testPos.refMinDist)
302 testPos.refMinDist = dist;
303 testPos.refNearestPoint = j;
305 if (dist > 0 && dist <= cutoff)
307 RefPair pair(j, dist);
308 GMX_RELEASE_ASSERT(testPos.refPairs.empty() || testPos.refPairs.back() < pair,
309 "Reference pairs should be generated in sorted order");
310 testPos.refPairs.push_back(pair);
316 /********************************************************************
317 * ExclusionsHelper
320 class ExclusionsHelper
322 public:
323 static void markExcludedPairs(RefPairList *refPairs, int testIndex,
324 const t_blocka *excls);
326 ExclusionsHelper(int refPosCount, int testPosCount);
328 void generateExclusions();
330 const t_blocka *exclusions() const { return &excls_; }
332 gmx::ConstArrayRef<int> refPosIds() const
334 return gmx::constArrayRefFromVector<int>(exclusionIds_.begin(),
335 exclusionIds_.begin() + refPosCount_);
337 gmx::ConstArrayRef<int> testPosIds() const
339 return gmx::constArrayRefFromVector<int>(exclusionIds_.begin(),
340 exclusionIds_.begin() + testPosCount_);
343 private:
344 int refPosCount_;
345 int testPosCount_;
346 std::vector<int> exclusionIds_;
347 std::vector<int> exclsIndex_;
348 std::vector<int> exclsAtoms_;
349 t_blocka excls_;
352 // static
353 void ExclusionsHelper::markExcludedPairs(RefPairList *refPairs, int testIndex,
354 const t_blocka *excls)
356 int count = 0;
357 for (int i = excls->index[testIndex]; i < excls->index[testIndex + 1]; ++i)
359 const int excludedIndex = excls->a[i];
360 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
361 RefPairList::iterator excludedRefPair
362 = std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
363 if (excludedRefPair != refPairs->end()
364 && excludedRefPair->refIndex == excludedIndex)
366 excludedRefPair->bFound = true;
367 excludedRefPair->bExcluded = true;
368 ++count;
373 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount)
374 : refPosCount_(refPosCount), testPosCount_(testPosCount)
376 // Generate an array of 0, 1, 2, ...
377 // TODO: Make the tests work also with non-trivial exclusion IDs,
378 // and test that.
379 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
380 exclusionIds_[0] = 0;
381 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(),
382 exclusionIds_.begin());
384 excls_.nr = 0;
385 excls_.index = nullptr;
386 excls_.nra = 0;
387 excls_.a = nullptr;
388 excls_.nalloc_index = 0;
389 excls_.nalloc_a = 0;
392 void ExclusionsHelper::generateExclusions()
394 // TODO: Consider a better set of test data, where the density of the
395 // particles would be higher, or where the exclusions would not be random,
396 // to make a higher percentage of the exclusions to actually be within the
397 // cutoff.
398 exclsIndex_.reserve(testPosCount_ + 1);
399 exclsAtoms_.reserve(testPosCount_ * 20);
400 exclsIndex_.push_back(0);
401 for (int i = 0; i < testPosCount_; ++i)
403 for (int j = 0; j < 20; ++j)
405 exclsAtoms_.push_back(i + j*3);
407 exclsIndex_.push_back(exclsAtoms_.size());
409 excls_.nr = exclsIndex_.size();
410 excls_.index = exclsIndex_.data();
411 excls_.nra = exclsAtoms_.size();
412 excls_.a = exclsAtoms_.data();
415 /********************************************************************
416 * NeighborhoodSearchTest
419 class NeighborhoodSearchTest : public ::testing::Test
421 public:
422 void testIsWithin(gmx::AnalysisNeighborhoodSearch *search,
423 const NeighborhoodSearchTestData &data);
424 void testMinimumDistance(gmx::AnalysisNeighborhoodSearch *search,
425 const NeighborhoodSearchTestData &data);
426 void testNearestPoint(gmx::AnalysisNeighborhoodSearch *search,
427 const NeighborhoodSearchTestData &data);
428 void testPairSearch(gmx::AnalysisNeighborhoodSearch *search,
429 const NeighborhoodSearchTestData &data);
430 void testPairSearchIndexed(gmx::AnalysisNeighborhood *nb,
431 const NeighborhoodSearchTestData &data,
432 gmx_uint64_t seed);
433 void testPairSearchFull(gmx::AnalysisNeighborhoodSearch *search,
434 const NeighborhoodSearchTestData &data,
435 const gmx::AnalysisNeighborhoodPositions &pos,
436 const t_blocka *excls,
437 const gmx::ConstArrayRef<int> &refIndices,
438 const gmx::ConstArrayRef<int> &testIndices,
439 bool selfPairs);
441 gmx::AnalysisNeighborhood nb_;
444 void NeighborhoodSearchTest::testIsWithin(
445 gmx::AnalysisNeighborhoodSearch *search,
446 const NeighborhoodSearchTestData &data)
448 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
449 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
451 const bool bWithin = (i->refMinDist <= data.cutoff_);
452 EXPECT_EQ(bWithin, search->isWithin(i->x))
453 << "Distance is " << i->refMinDist;
457 void NeighborhoodSearchTest::testMinimumDistance(
458 gmx::AnalysisNeighborhoodSearch *search,
459 const NeighborhoodSearchTestData &data)
461 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
463 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
465 const real refDist = i->refMinDist;
466 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
470 void NeighborhoodSearchTest::testNearestPoint(
471 gmx::AnalysisNeighborhoodSearch *search,
472 const NeighborhoodSearchTestData &data)
474 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
475 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
477 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
478 if (pair.isValid())
480 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
481 EXPECT_EQ(0, pair.testIndex());
482 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
484 else
486 EXPECT_EQ(i->refNearestPoint, -1);
491 //! Helper function for formatting test failure messages.
492 std::string formatVector(const rvec x)
494 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
497 /*! \brief
498 * Helper function to check that all expected pairs were found.
500 void checkAllPairsFound(const RefPairList &refPairs,
501 const std::vector<gmx::RVec> &refPos,
502 int testPosIndex, const rvec testPos)
504 // This could be elegantly expressed with Google Mock matchers, but that
505 // has a significant effect on the runtime of the tests...
506 int count = 0;
507 RefPairList::const_iterator first;
508 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
510 if (!i->bFound)
512 ++count;
513 first = i;
516 if (count > 0)
518 ADD_FAILURE()
519 << "Some pairs (" << count << "/" << refPairs.size() << ") "
520 << "within the cutoff were not found. First pair:\n"
521 << " Ref: " << first->refIndex << " at "
522 << formatVector(refPos[first->refIndex]) << "\n"
523 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
524 << "Dist: " << first->distance;
528 void NeighborhoodSearchTest::testPairSearch(
529 gmx::AnalysisNeighborhoodSearch *search,
530 const NeighborhoodSearchTestData &data)
532 testPairSearchFull(search, data, data.testPositions(), nullptr,
533 gmx::EmptyArrayRef(), gmx::EmptyArrayRef(), false);
536 void NeighborhoodSearchTest::testPairSearchIndexed(
537 gmx::AnalysisNeighborhood *nb,
538 const NeighborhoodSearchTestData &data,
539 gmx_uint64_t seed)
541 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
542 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
543 gmx::AnalysisNeighborhoodSearch search =
544 nb->initSearch(&data.pbc_,
545 data.refPositions().indexed(refIndices));
546 testPairSearchFull(&search, data, data.testPositions(), nullptr,
547 refIndices, testIndices, false);
550 void NeighborhoodSearchTest::testPairSearchFull(
551 gmx::AnalysisNeighborhoodSearch *search,
552 const NeighborhoodSearchTestData &data,
553 const gmx::AnalysisNeighborhoodPositions &pos,
554 const t_blocka *excls,
555 const gmx::ConstArrayRef<int> &refIndices,
556 const gmx::ConstArrayRef<int> &testIndices,
557 bool selfPairs)
559 std::map<int, RefPairList> refPairs;
560 // TODO: Some parts of this code do not work properly if pos does not
561 // initially contain all the test positions.
562 if (testIndices.empty())
564 for (size_t i = 0; i < data.testPositions_.size(); ++i)
566 refPairs[i] = data.testPositions_[i].refPairs;
569 else
571 for (int index : testIndices)
573 refPairs[index] = data.testPositions_[index].refPairs;
576 if (excls != nullptr)
578 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with exclusions");
579 for (auto &entry : refPairs)
581 const int testIndex = entry.first;
582 ExclusionsHelper::markExcludedPairs(&entry.second, testIndex, excls);
585 if (!refIndices.empty())
587 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with indexing");
588 for (auto &entry : refPairs)
590 for (auto &refPair : entry.second)
592 refPair.bIndexed = false;
594 for (int index : refIndices)
596 NeighborhoodSearchTestData::RefPair searchPair(index, 0.0);
597 auto refPair = std::lower_bound(entry.second.begin(), entry.second.end(), searchPair);
598 if (refPair != entry.second.end() && refPair->refIndex == index)
600 refPair->bIndexed = true;
603 for (auto &refPair : entry.second)
605 if (!refPair.bIndexed)
607 refPair.bFound = true;
613 gmx::AnalysisNeighborhoodPositions posCopy(pos);
614 if (!testIndices.empty())
616 posCopy.indexed(testIndices);
618 gmx::AnalysisNeighborhoodPairSearch pairSearch
619 = selfPairs
620 ? search->startSelfPairSearch()
621 : search->startPairSearch(posCopy);
622 gmx::AnalysisNeighborhoodPair pair;
623 while (pairSearch.findNextPair(&pair))
625 const int testIndex =
626 (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
627 const int refIndex =
628 (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
630 if (refPairs.count(testIndex) == 0)
632 ADD_FAILURE()
633 << "Expected: No pairs are returned for test position " << testIndex << ".\n"
634 << " Actual: Pair with ref " << refIndex << " is returned.";
635 continue;
637 NeighborhoodSearchTestData::RefPair searchPair(refIndex,
638 std::sqrt(pair.distance2()));
639 const auto foundRefPair
640 = std::lower_bound(refPairs[testIndex].begin(), refPairs[testIndex].end(),
641 searchPair);
642 if (foundRefPair == refPairs[testIndex].end() || foundRefPair->refIndex != refIndex)
644 ADD_FAILURE()
645 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
646 << ") is not within the cutoff.\n"
647 << " Actual: It is returned.";
649 else if (foundRefPair->bExcluded)
651 ADD_FAILURE()
652 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
653 << ") is excluded from the search.\n"
654 << " Actual: It is returned.";
656 else if (!foundRefPair->bIndexed)
658 ADD_FAILURE()
659 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
660 << ") is not part of the indexed set.\n"
661 << " Actual: It is returned.";
663 else if (foundRefPair->bFound)
665 ADD_FAILURE()
666 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
667 << ") is returned only once.\n"
668 << " Actual: It is returned multiple times.";
669 return;
671 else
673 foundRefPair->bFound = true;
675 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
676 << "Distance computed by the neighborhood search does not match.";
677 if (selfPairs)
679 searchPair = NeighborhoodSearchTestData::RefPair(testIndex, 0.0);
680 const auto otherRefPair
681 = std::lower_bound(refPairs[refIndex].begin(), refPairs[refIndex].end(),
682 searchPair);
683 GMX_RELEASE_ASSERT(otherRefPair != refPairs[refIndex].end(),
684 "Precomputed reference data is not symmetric");
685 otherRefPair->bFound = true;
690 for (auto &entry : refPairs)
692 const int testIndex = entry.first;
693 checkAllPairsFound(entry.second, data.refPos_, testIndex,
694 data.testPositions_[testIndex].x);
698 /********************************************************************
699 * Test data generation
702 class TrivialTestData
704 public:
705 static const NeighborhoodSearchTestData &get()
707 static TrivialTestData singleton;
708 return singleton.data_;
711 TrivialTestData() : data_(12345, 1.0)
713 // Make the box so small we are virtually guaranteed to have
714 // several neighbors for the five test positions
715 data_.box_[XX][XX] = 3.0;
716 data_.box_[YY][YY] = 3.0;
717 data_.box_[ZZ][ZZ] = 3.0;
718 data_.generateRandomRefPositions(10);
719 data_.generateRandomTestPositions(5);
720 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
721 data_.computeReferences(&data_.pbc_);
724 private:
725 NeighborhoodSearchTestData data_;
728 class TrivialSelfPairsTestData
730 public:
731 static const NeighborhoodSearchTestData &get()
733 static TrivialSelfPairsTestData singleton;
734 return singleton.data_;
737 TrivialSelfPairsTestData() : data_(12345, 1.0)
739 data_.box_[XX][XX] = 3.0;
740 data_.box_[YY][YY] = 3.0;
741 data_.box_[ZZ][ZZ] = 3.0;
742 data_.generateRandomRefPositions(20);
743 data_.useRefPositionsAsTestPositions();
744 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
745 data_.computeReferences(&data_.pbc_);
748 private:
749 NeighborhoodSearchTestData data_;
752 class TrivialNoPBCTestData
754 public:
755 static const NeighborhoodSearchTestData &get()
757 static TrivialNoPBCTestData singleton;
758 return singleton.data_;
761 TrivialNoPBCTestData() : data_(12345, 1.0)
763 data_.generateRandomRefPositions(10);
764 data_.generateRandomTestPositions(5);
765 data_.computeReferences(nullptr);
768 private:
769 NeighborhoodSearchTestData data_;
772 class RandomBoxFullPBCData
774 public:
775 static const NeighborhoodSearchTestData &get()
777 static RandomBoxFullPBCData singleton;
778 return singleton.data_;
781 RandomBoxFullPBCData() : data_(12345, 1.0)
783 data_.box_[XX][XX] = 10.0;
784 data_.box_[YY][YY] = 5.0;
785 data_.box_[ZZ][ZZ] = 7.0;
786 // TODO: Consider whether manually picking some positions would give better
787 // test coverage.
788 data_.generateRandomRefPositions(1000);
789 data_.generateRandomTestPositions(100);
790 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
791 data_.computeReferences(&data_.pbc_);
794 private:
795 NeighborhoodSearchTestData data_;
798 class RandomBoxSelfPairsData
800 public:
801 static const NeighborhoodSearchTestData &get()
803 static RandomBoxSelfPairsData singleton;
804 return singleton.data_;
807 RandomBoxSelfPairsData() : data_(12345, 1.0)
809 data_.box_[XX][XX] = 10.0;
810 data_.box_[YY][YY] = 5.0;
811 data_.box_[ZZ][ZZ] = 7.0;
812 data_.generateRandomRefPositions(1000);
813 data_.useRefPositionsAsTestPositions();
814 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
815 data_.computeReferences(&data_.pbc_);
818 private:
819 NeighborhoodSearchTestData data_;
822 class RandomBoxXYFullPBCData
824 public:
825 static const NeighborhoodSearchTestData &get()
827 static RandomBoxXYFullPBCData singleton;
828 return singleton.data_;
831 RandomBoxXYFullPBCData() : data_(54321, 1.0)
833 data_.box_[XX][XX] = 10.0;
834 data_.box_[YY][YY] = 5.0;
835 data_.box_[ZZ][ZZ] = 7.0;
836 // TODO: Consider whether manually picking some positions would give better
837 // test coverage.
838 data_.generateRandomRefPositions(1000);
839 data_.generateRandomTestPositions(100);
840 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
841 data_.computeReferencesXY(&data_.pbc_);
844 private:
845 NeighborhoodSearchTestData data_;
848 class RandomTriclinicFullPBCData
850 public:
851 static const NeighborhoodSearchTestData &get()
853 static RandomTriclinicFullPBCData singleton;
854 return singleton.data_;
857 RandomTriclinicFullPBCData() : data_(12345, 1.0)
859 data_.box_[XX][XX] = 5.0;
860 data_.box_[YY][XX] = 2.5;
861 data_.box_[YY][YY] = 2.5*std::sqrt(3.0);
862 data_.box_[ZZ][XX] = 2.5;
863 data_.box_[ZZ][YY] = 2.5*std::sqrt(1.0/3.0);
864 data_.box_[ZZ][ZZ] = 5.0*std::sqrt(2.0/3.0);
865 // TODO: Consider whether manually picking some positions would give better
866 // test coverage.
867 data_.generateRandomRefPositions(1000);
868 data_.generateRandomTestPositions(100);
869 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
870 data_.computeReferences(&data_.pbc_);
873 private:
874 NeighborhoodSearchTestData data_;
877 class RandomBox2DPBCData
879 public:
880 static const NeighborhoodSearchTestData &get()
882 static RandomBox2DPBCData singleton;
883 return singleton.data_;
886 RandomBox2DPBCData() : data_(12345, 1.0)
888 data_.box_[XX][XX] = 10.0;
889 data_.box_[YY][YY] = 7.0;
890 data_.box_[ZZ][ZZ] = 5.0;
891 // TODO: Consider whether manually picking some positions would give better
892 // test coverage.
893 data_.generateRandomRefPositions(1000);
894 data_.generateRandomTestPositions(100);
895 set_pbc(&data_.pbc_, epbcXY, data_.box_);
896 data_.computeReferences(&data_.pbc_);
899 private:
900 NeighborhoodSearchTestData data_;
903 class RandomBoxNoPBCData
905 public:
906 static const NeighborhoodSearchTestData &get()
908 static RandomBoxNoPBCData singleton;
909 return singleton.data_;
912 RandomBoxNoPBCData() : data_(12345, 1.0)
914 data_.box_[XX][XX] = 10.0;
915 data_.box_[YY][YY] = 5.0;
916 data_.box_[ZZ][ZZ] = 7.0;
917 // TODO: Consider whether manually picking some positions would give better
918 // test coverage.
919 data_.generateRandomRefPositions(1000);
920 data_.generateRandomTestPositions(100);
921 set_pbc(&data_.pbc_, epbcNONE, data_.box_);
922 data_.computeReferences(nullptr);
925 private:
926 NeighborhoodSearchTestData data_;
929 /********************************************************************
930 * Actual tests
933 TEST_F(NeighborhoodSearchTest, SimpleSearch)
935 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
937 nb_.setCutoff(data.cutoff_);
938 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
939 gmx::AnalysisNeighborhoodSearch search =
940 nb_.initSearch(&data.pbc_, data.refPositions());
941 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
943 testIsWithin(&search, data);
944 testMinimumDistance(&search, data);
945 testNearestPoint(&search, data);
946 testPairSearch(&search, data);
948 search.reset();
949 testPairSearchIndexed(&nb_, data, 123);
952 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
954 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
956 nb_.setCutoff(data.cutoff_);
957 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
958 nb_.setXYMode(true);
959 gmx::AnalysisNeighborhoodSearch search =
960 nb_.initSearch(&data.pbc_, data.refPositions());
961 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
963 testIsWithin(&search, data);
964 testMinimumDistance(&search, data);
965 testNearestPoint(&search, data);
966 testPairSearch(&search, data);
969 TEST_F(NeighborhoodSearchTest, GridSearchBox)
971 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
973 nb_.setCutoff(data.cutoff_);
974 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
975 gmx::AnalysisNeighborhoodSearch search =
976 nb_.initSearch(&data.pbc_, data.refPositions());
977 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
979 testIsWithin(&search, data);
980 testMinimumDistance(&search, data);
981 testNearestPoint(&search, data);
982 testPairSearch(&search, data);
984 search.reset();
985 testPairSearchIndexed(&nb_, data, 456);
988 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
990 const NeighborhoodSearchTestData &data = RandomTriclinicFullPBCData::get();
992 nb_.setCutoff(data.cutoff_);
993 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
994 gmx::AnalysisNeighborhoodSearch search =
995 nb_.initSearch(&data.pbc_, data.refPositions());
996 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
998 testPairSearch(&search, data);
1001 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
1003 const NeighborhoodSearchTestData &data = RandomBox2DPBCData::get();
1005 nb_.setCutoff(data.cutoff_);
1006 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1007 gmx::AnalysisNeighborhoodSearch search =
1008 nb_.initSearch(&data.pbc_, data.refPositions());
1009 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1011 testIsWithin(&search, data);
1012 testMinimumDistance(&search, data);
1013 testNearestPoint(&search, data);
1014 testPairSearch(&search, data);
1017 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
1019 const NeighborhoodSearchTestData &data = RandomBoxNoPBCData::get();
1021 nb_.setCutoff(data.cutoff_);
1022 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1023 gmx::AnalysisNeighborhoodSearch search =
1024 nb_.initSearch(&data.pbc_, data.refPositions());
1025 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1027 testPairSearch(&search, data);
1030 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
1032 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
1034 nb_.setCutoff(data.cutoff_);
1035 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1036 nb_.setXYMode(true);
1037 gmx::AnalysisNeighborhoodSearch search =
1038 nb_.initSearch(&data.pbc_, data.refPositions());
1039 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1041 testIsWithin(&search, data);
1042 testMinimumDistance(&search, data);
1043 testNearestPoint(&search, data);
1044 testPairSearch(&search, data);
1047 TEST_F(NeighborhoodSearchTest, SimpleSelfPairsSearch)
1049 const NeighborhoodSearchTestData &data = TrivialSelfPairsTestData::get();
1051 nb_.setCutoff(data.cutoff_);
1052 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1053 gmx::AnalysisNeighborhoodSearch search =
1054 nb_.initSearch(&data.pbc_, data.refPositions());
1055 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1057 testPairSearchFull(&search, data, data.testPositions(), nullptr,
1058 gmx::EmptyArrayRef(), gmx::EmptyArrayRef(), true);
1061 TEST_F(NeighborhoodSearchTest, GridSelfPairsSearch)
1063 const NeighborhoodSearchTestData &data = RandomBoxSelfPairsData::get();
1065 nb_.setCutoff(data.cutoff_);
1066 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1067 gmx::AnalysisNeighborhoodSearch search =
1068 nb_.initSearch(&data.pbc_, data.refPositions());
1069 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1071 testPairSearchFull(&search, data, data.testPositions(), nullptr,
1072 gmx::EmptyArrayRef(), gmx::EmptyArrayRef(), true);
1075 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1077 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1079 nb_.setCutoff(data.cutoff_);
1080 gmx::AnalysisNeighborhoodSearch search1 =
1081 nb_.initSearch(&data.pbc_, data.refPositions());
1082 gmx::AnalysisNeighborhoodSearch search2 =
1083 nb_.initSearch(&data.pbc_, data.refPositions());
1085 // These checks are fragile, and unfortunately depend on the random
1086 // engine used to create the test positions. There is no particular reason
1087 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1088 gmx::AnalysisNeighborhoodPairSearch pairSearch1 =
1089 search1.startPairSearch(data.testPosition(0));
1090 gmx::AnalysisNeighborhoodPairSearch pairSearch2 =
1091 search1.startPairSearch(data.testPosition(2));
1093 testPairSearch(&search2, data);
1095 gmx::AnalysisNeighborhoodPair pair;
1096 ASSERT_TRUE(pairSearch1.findNextPair(&pair))
1097 << "Test data did not contain any pairs for position 0 (problem in the test).";
1098 EXPECT_EQ(0, pair.testIndex());
1100 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1101 EXPECT_TRUE(data.containsPair(0, searchPair));
1104 ASSERT_TRUE(pairSearch2.findNextPair(&pair))
1105 << "Test data did not contain any pairs for position 2 (problem in the test).";
1106 EXPECT_EQ(2, pair.testIndex());
1108 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1109 EXPECT_TRUE(data.containsPair(2, searchPair));
1113 TEST_F(NeighborhoodSearchTest, HandlesNoPBC)
1115 const NeighborhoodSearchTestData &data = TrivialNoPBCTestData::get();
1117 nb_.setCutoff(data.cutoff_);
1118 gmx::AnalysisNeighborhoodSearch search =
1119 nb_.initSearch(&data.pbc_, data.refPositions());
1120 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1122 testIsWithin(&search, data);
1123 testMinimumDistance(&search, data);
1124 testNearestPoint(&search, data);
1125 testPairSearch(&search, data);
1128 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1130 const NeighborhoodSearchTestData &data = TrivialNoPBCTestData::get();
1132 nb_.setCutoff(data.cutoff_);
1133 gmx::AnalysisNeighborhoodSearch search =
1134 nb_.initSearch(nullptr, data.refPositions());
1135 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1137 testIsWithin(&search, data);
1138 testMinimumDistance(&search, data);
1139 testNearestPoint(&search, data);
1140 testPairSearch(&search, data);
1143 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1145 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1147 nb_.setCutoff(data.cutoff_);
1148 gmx::AnalysisNeighborhoodSearch search =
1149 nb_.initSearch(&data.pbc_, data.refPositions());
1150 gmx::AnalysisNeighborhoodPairSearch pairSearch =
1151 search.startPairSearch(data.testPositions());
1152 gmx::AnalysisNeighborhoodPair pair;
1153 // TODO: This test needs to be adjusted if the grid search gets optimized
1154 // to loop over the test positions in cell order (first, the ordering
1155 // assumption here breaks, and second, it then needs to be tested
1156 // separately for simple and grid searches).
1157 int currentIndex = 0;
1158 while (pairSearch.findNextPair(&pair))
1160 while (currentIndex < pair.testIndex())
1162 ++currentIndex;
1164 EXPECT_EQ(currentIndex, pair.testIndex());
1165 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1166 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1167 pairSearch.skipRemainingPairsForTestPosition();
1168 ++currentIndex;
1172 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1174 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1176 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1177 helper.generateExclusions();
1179 nb_.setCutoff(data.cutoff_);
1180 nb_.setTopologyExclusions(helper.exclusions());
1181 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1182 gmx::AnalysisNeighborhoodSearch search =
1183 nb_.initSearch(&data.pbc_,
1184 data.refPositions().exclusionIds(helper.refPosIds()));
1185 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1187 testPairSearchFull(&search, data,
1188 data.testPositions().exclusionIds(helper.testPosIds()),
1189 helper.exclusions(), gmx::EmptyArrayRef(),
1190 gmx::EmptyArrayRef(), false);
1193 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1195 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1197 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1198 helper.generateExclusions();
1200 nb_.setCutoff(data.cutoff_);
1201 nb_.setTopologyExclusions(helper.exclusions());
1202 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1203 gmx::AnalysisNeighborhoodSearch search =
1204 nb_.initSearch(&data.pbc_,
1205 data.refPositions().exclusionIds(helper.refPosIds()));
1206 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1208 testPairSearchFull(&search, data,
1209 data.testPositions().exclusionIds(helper.testPosIds()),
1210 helper.exclusions(), gmx::EmptyArrayRef(),
1211 gmx::EmptyArrayRef(), false);
1214 } // namespace