2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017 by the GROMACS development team.
5 * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Tests selection neighborhood searching.
41 * Increase coverage of these tests for different corner cases: other PBC cases
42 * than full 3D, large cutoffs (larger than half the box size), etc.
43 * At least some of these probably don't work correctly.
45 * \author Teemu Murtola <teemu.murtola@gmail.com>
46 * \ingroup module_selection
50 #include "gromacs/selection/nbsearch.h"
60 #include <gtest/gtest.h>
62 #include "gromacs/math/functions.h"
63 #include "gromacs/math/vec.h"
64 #include "gromacs/pbcutil/pbc.h"
65 #include "gromacs/random/threefry.h"
66 #include "gromacs/random/uniformrealdistribution.h"
67 #include "gromacs/topology/block.h"
68 #include "gromacs/utility/listoflists.h"
69 #include "gromacs/utility/smalloc.h"
70 #include "gromacs/utility/stringutil.h"
72 #include "testutils/testasserts.h"
78 /********************************************************************
79 * NeighborhoodSearchTestData
82 class NeighborhoodSearchTestData
87 RefPair(int refIndex, real distance) :
88 refIndex(refIndex), distance(distance), bFound(false), bExcluded(false), bIndexed(true)
92 bool operator<(const RefPair& other) const { return refIndex < other.refIndex; }
96 // The variables below are state variables that are only used
97 // during the actual testing after creating a copy of the reference
98 // pair list, not as part of the reference data.
99 // Simpler to have just a single structure for both purposes.
107 explicit TestPosition(const rvec x) : refMinDist(0.0), refNearestPoint(-1)
109 copy_rvec(x, this->x);
115 std::vector<RefPair> refPairs;
118 typedef std::vector<TestPosition> TestPositionList;
120 NeighborhoodSearchTestData(uint64_t seed, real cutoff);
122 gmx::AnalysisNeighborhoodPositions refPositions() const
124 return gmx::AnalysisNeighborhoodPositions(refPos_);
126 gmx::AnalysisNeighborhoodPositions testPositions() const
128 if (testPos_.empty())
130 testPos_.reserve(testPositions_.size());
131 for (size_t i = 0; i < testPositions_.size(); ++i)
133 testPos_.emplace_back(testPositions_[i].x);
136 return gmx::AnalysisNeighborhoodPositions(testPos_);
138 gmx::AnalysisNeighborhoodPositions testPosition(int index) const
140 return testPositions().selectSingleFromArray(index);
143 void addTestPosition(const rvec x)
145 GMX_RELEASE_ASSERT(testPos_.empty(), "Cannot add positions after testPositions() call");
146 testPositions_.emplace_back(x);
148 gmx::RVec generateRandomPosition();
149 static std::vector<int> generateIndex(int count, uint64_t seed);
150 void generateRandomRefPositions(int count);
151 void generateRandomTestPositions(int count);
152 void useRefPositionsAsTestPositions();
153 void computeReferences(t_pbc* pbc) { computeReferencesInternal(pbc, false); }
154 void computeReferencesXY(t_pbc* pbc) { computeReferencesInternal(pbc, true); }
156 bool containsPair(int testIndex, const RefPair& pair) const
158 const std::vector<RefPair>& refPairs = testPositions_[testIndex].refPairs;
159 std::vector<RefPair>::const_iterator foundRefPair =
160 std::lower_bound(refPairs.begin(), refPairs.end(), pair);
161 return !(foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex);
164 // Return a tolerance that accounts for the magnitudes of the coordinates
165 // when doing subtractions, so that we set the ULP tolerance relative to the
166 // coordinate values rather than their difference.
167 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
168 // to 10.0, but a much larger error relative to the difference.
169 gmx::test::FloatingPointTolerance relativeTolerance() const
171 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
172 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
175 gmx::DefaultRandomEngine rng_;
180 std::vector<gmx::RVec> refPos_;
181 TestPositionList testPositions_;
184 void computeReferencesInternal(t_pbc* pbc, bool bXY);
186 mutable std::vector<gmx::RVec> testPos_;
189 //! Shorthand for a collection of reference pairs.
190 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
192 NeighborhoodSearchTestData::NeighborhoodSearchTestData(uint64_t seed, real cutoff) :
193 rng_(seed), cutoff_(cutoff), refPosCount_(0)
196 set_pbc(&pbc_, PbcType::No, box_);
199 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
201 gmx::UniformRealDistribution<real> dist;
207 // Add a small displacement to allow positions outside the box
208 x[XX] += 0.2 * dist(rng_) - 0.1;
209 x[YY] += 0.2 * dist(rng_) - 0.1;
210 x[ZZ] += 0.2 * dist(rng_) - 0.1;
214 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, uint64_t seed)
216 gmx::DefaultRandomEngine rngIndex(seed);
217 gmx::UniformRealDistribution<real> dist;
218 std::vector<int> result;
220 for (int i = 0; i < count; ++i)
222 if (dist(rngIndex) > 0.5)
230 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
232 refPosCount_ = count;
233 refPos_.reserve(count);
234 for (int i = 0; i < count; ++i)
236 refPos_.push_back(generateRandomPosition());
240 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
242 testPositions_.reserve(count);
243 for (int i = 0; i < count; ++i)
245 addTestPosition(generateRandomPosition());
249 void NeighborhoodSearchTestData::useRefPositionsAsTestPositions()
251 testPositions_.reserve(refPosCount_);
252 for (const auto& refPos : refPos_)
254 addTestPosition(refPos);
258 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc* pbc, bool bXY)
260 real cutoff = cutoff_;
263 cutoff = std::numeric_limits<real>::max();
265 for (TestPosition& testPos : testPositions_)
267 testPos.refMinDist = cutoff;
268 testPos.refNearestPoint = -1;
269 testPos.refPairs.clear();
270 for (int j = 0; j < refPosCount_; ++j)
275 pbc_dx(pbc, testPos.x, refPos_[j], dx);
279 rvec_sub(testPos.x, refPos_[j], dx);
281 // TODO: This may not work intuitively for 2D with the third box
282 // vector not parallel to the Z axis, but neither does the actual
283 // neighborhood search.
284 const real dist = !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
285 if (dist < testPos.refMinDist)
287 testPos.refMinDist = dist;
288 testPos.refNearestPoint = j;
290 if (dist > 0 && dist <= cutoff)
292 RefPair pair(j, dist);
293 GMX_RELEASE_ASSERT(testPos.refPairs.empty() || testPos.refPairs.back() < pair,
294 "Reference pairs should be generated in sorted order");
295 testPos.refPairs.push_back(pair);
301 /********************************************************************
305 class ExclusionsHelper
308 static void markExcludedPairs(RefPairList* refPairs, int testIndex, const gmx::ListOfLists<int>* excls);
310 ExclusionsHelper(int refPosCount, int testPosCount);
312 void generateExclusions();
314 const gmx::ListOfLists<int>* exclusions() const { return &excls_; }
316 gmx::ArrayRef<const int> refPosIds() const
318 return gmx::makeArrayRef(exclusionIds_).subArray(0, refPosCount_);
320 gmx::ArrayRef<const int> testPosIds() const
322 return gmx::makeArrayRef(exclusionIds_).subArray(0, testPosCount_);
328 std::vector<int> exclusionIds_;
329 gmx::ListOfLists<int> excls_;
333 void ExclusionsHelper::markExcludedPairs(RefPairList* refPairs, int testIndex, const gmx::ListOfLists<int>* excls)
336 for (const int excludedIndex : (*excls)[testIndex])
338 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
339 RefPairList::iterator excludedRefPair =
340 std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
341 if (excludedRefPair != refPairs->end() && excludedRefPair->refIndex == excludedIndex)
343 excludedRefPair->bFound = true;
344 excludedRefPair->bExcluded = true;
350 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount) :
351 refPosCount_(refPosCount), testPosCount_(testPosCount)
353 // Generate an array of 0, 1, 2, ...
354 // TODO: Make the tests work also with non-trivial exclusion IDs,
356 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
357 exclusionIds_[0] = 0;
358 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(), exclusionIds_.begin());
361 void ExclusionsHelper::generateExclusions()
363 // TODO: Consider a better set of test data, where the density of the
364 // particles would be higher, or where the exclusions would not be random,
365 // to make a higher percentage of the exclusions to actually be within the
367 for (int i = 0; i < testPosCount_; ++i)
369 excls_.pushBackListOfSize(20);
370 gmx::ArrayRef<int> exclusionsForAtom = excls_.back();
371 for (int j = 0; j < 20; ++j)
373 exclusionsForAtom[j] = i + j * 3;
378 /********************************************************************
379 * NeighborhoodSearchTest
382 class NeighborhoodSearchTest : public ::testing::Test
385 static void testIsWithin(gmx::AnalysisNeighborhoodSearch* search,
386 const NeighborhoodSearchTestData& data);
387 static void testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
388 const NeighborhoodSearchTestData& data);
389 static void testNearestPoint(gmx::AnalysisNeighborhoodSearch* search,
390 const NeighborhoodSearchTestData& data);
391 static void testPairSearch(gmx::AnalysisNeighborhoodSearch* search,
392 const NeighborhoodSearchTestData& data);
393 static void testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
394 const NeighborhoodSearchTestData& data,
396 static void testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
397 const NeighborhoodSearchTestData& data,
398 const gmx::AnalysisNeighborhoodPositions& pos,
399 const gmx::ListOfLists<int>* excls,
400 const gmx::ArrayRef<const int>& refIndices,
401 const gmx::ArrayRef<const int>& testIndices,
404 gmx::AnalysisNeighborhood nb_;
407 void NeighborhoodSearchTest::testIsWithin(gmx::AnalysisNeighborhoodSearch* search,
408 const NeighborhoodSearchTestData& data)
410 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
411 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
413 const bool bWithin = (i->refMinDist <= data.cutoff_);
414 EXPECT_EQ(bWithin, search->isWithin(i->x)) << "Distance is " << i->refMinDist;
418 void NeighborhoodSearchTest::testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
419 const NeighborhoodSearchTestData& data)
421 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
423 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
425 const real refDist = i->refMinDist;
426 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
430 void NeighborhoodSearchTest::testNearestPoint(gmx::AnalysisNeighborhoodSearch* search,
431 const NeighborhoodSearchTestData& data)
433 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
434 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
436 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
439 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
440 EXPECT_EQ(0, pair.testIndex());
441 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
445 EXPECT_EQ(i->refNearestPoint, -1);
450 //! Helper function for formatting test failure messages.
451 std::string formatVector(const rvec x)
453 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
457 * Helper function to check that all expected pairs were found.
459 void checkAllPairsFound(const RefPairList& refPairs,
460 const std::vector<gmx::RVec>& refPos,
464 // This could be elegantly expressed with Google Mock matchers, but that
465 // has a significant effect on the runtime of the tests...
467 RefPairList::const_iterator first;
468 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
478 ADD_FAILURE() << "Some pairs (" << count << "/" << refPairs.size() << ") "
479 << "within the cutoff were not found. First pair:\n"
480 << " Ref: " << first->refIndex << " at "
481 << formatVector(refPos[first->refIndex]) << "\n"
482 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
483 << "Dist: " << first->distance;
487 void NeighborhoodSearchTest::testPairSearch(gmx::AnalysisNeighborhoodSearch* search,
488 const NeighborhoodSearchTestData& data)
490 testPairSearchFull(search, data, data.testPositions(), nullptr, {}, {}, false);
493 void NeighborhoodSearchTest::testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
494 const NeighborhoodSearchTestData& data,
497 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
498 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
499 gmx::AnalysisNeighborhoodSearch search =
500 nb->initSearch(&data.pbc_, data.refPositions().indexed(refIndices));
501 testPairSearchFull(&search, data, data.testPositions(), nullptr, refIndices, testIndices, false);
504 void NeighborhoodSearchTest::testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
505 const NeighborhoodSearchTestData& data,
506 const gmx::AnalysisNeighborhoodPositions& pos,
507 const gmx::ListOfLists<int>* excls,
508 const gmx::ArrayRef<const int>& refIndices,
509 const gmx::ArrayRef<const int>& testIndices,
512 std::map<int, RefPairList> refPairs;
513 // TODO: Some parts of this code do not work properly if pos does not
514 // initially contain all the test positions.
515 if (testIndices.empty())
517 for (size_t i = 0; i < data.testPositions_.size(); ++i)
519 refPairs[i] = data.testPositions_[i].refPairs;
524 for (int index : testIndices)
526 refPairs[index] = data.testPositions_[index].refPairs;
529 if (excls != nullptr)
531 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with exclusions");
532 for (auto& entry : refPairs)
534 const int testIndex = entry.first;
535 ExclusionsHelper::markExcludedPairs(&entry.second, testIndex, excls);
538 if (!refIndices.empty())
540 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with indexing");
541 for (auto& entry : refPairs)
543 for (auto& refPair : entry.second)
545 refPair.bIndexed = false;
547 for (int index : refIndices)
549 NeighborhoodSearchTestData::RefPair searchPair(index, 0.0);
550 auto refPair = std::lower_bound(entry.second.begin(), entry.second.end(), searchPair);
551 if (refPair != entry.second.end() && refPair->refIndex == index)
553 refPair->bIndexed = true;
556 for (auto& refPair : entry.second)
558 if (!refPair.bIndexed)
560 refPair.bFound = true;
566 gmx::AnalysisNeighborhoodPositions posCopy(pos);
567 if (!testIndices.empty())
569 posCopy.indexed(testIndices);
571 gmx::AnalysisNeighborhoodPairSearch pairSearch =
572 selfPairs ? search->startSelfPairSearch() : search->startPairSearch(posCopy);
573 gmx::AnalysisNeighborhoodPair pair;
574 while (pairSearch.findNextPair(&pair))
576 const int testIndex = (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
577 const int refIndex = (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
579 if (refPairs.count(testIndex) == 0)
581 ADD_FAILURE() << "Expected: No pairs are returned for test position " << testIndex << ".\n"
582 << " Actual: Pair with ref " << refIndex << " is returned.";
585 NeighborhoodSearchTestData::RefPair searchPair(refIndex, std::sqrt(pair.distance2()));
586 const auto foundRefPair =
587 std::lower_bound(refPairs[testIndex].begin(), refPairs[testIndex].end(), searchPair);
588 if (foundRefPair == refPairs[testIndex].end() || foundRefPair->refIndex != refIndex)
590 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
591 << ") is not within the cutoff.\n"
592 << " Actual: It is returned.";
594 else if (foundRefPair->bExcluded)
596 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
597 << ") is excluded from the search.\n"
598 << " Actual: It is returned.";
600 else if (!foundRefPair->bIndexed)
602 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
603 << ") is not part of the indexed set.\n"
604 << " Actual: It is returned.";
606 else if (foundRefPair->bFound)
608 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
609 << ") is returned only once.\n"
610 << " Actual: It is returned multiple times.";
615 foundRefPair->bFound = true;
617 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
618 << "Distance computed by the neighborhood search does not match.";
621 searchPair = NeighborhoodSearchTestData::RefPair(testIndex, 0.0);
622 const auto otherRefPair = std::lower_bound(
623 refPairs[refIndex].begin(), refPairs[refIndex].end(), searchPair);
624 GMX_RELEASE_ASSERT(otherRefPair != refPairs[refIndex].end(),
625 "Precomputed reference data is not symmetric");
626 otherRefPair->bFound = true;
631 for (auto& entry : refPairs)
633 const int testIndex = entry.first;
634 checkAllPairsFound(entry.second, data.refPos_, testIndex, data.testPositions_[testIndex].x);
638 /********************************************************************
639 * Test data generation
642 class TrivialTestData
645 static const NeighborhoodSearchTestData& get()
647 static TrivialTestData singleton;
648 return singleton.data_;
651 TrivialTestData() : data_(12345, 1.0)
653 // Make the box so small we are virtually guaranteed to have
654 // several neighbors for the five test positions
655 data_.box_[XX][XX] = 3.0;
656 data_.box_[YY][YY] = 3.0;
657 data_.box_[ZZ][ZZ] = 3.0;
658 data_.generateRandomRefPositions(10);
659 data_.generateRandomTestPositions(5);
660 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
661 data_.computeReferences(&data_.pbc_);
665 NeighborhoodSearchTestData data_;
668 class TrivialSelfPairsTestData
671 static const NeighborhoodSearchTestData& get()
673 static TrivialSelfPairsTestData singleton;
674 return singleton.data_;
677 TrivialSelfPairsTestData() : data_(12345, 1.0)
679 data_.box_[XX][XX] = 3.0;
680 data_.box_[YY][YY] = 3.0;
681 data_.box_[ZZ][ZZ] = 3.0;
682 data_.generateRandomRefPositions(20);
683 data_.useRefPositionsAsTestPositions();
684 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
685 data_.computeReferences(&data_.pbc_);
689 NeighborhoodSearchTestData data_;
692 class TrivialNoPBCTestData
695 static const NeighborhoodSearchTestData& get()
697 static TrivialNoPBCTestData singleton;
698 return singleton.data_;
701 TrivialNoPBCTestData() : data_(12345, 1.0)
703 data_.generateRandomRefPositions(10);
704 data_.generateRandomTestPositions(5);
705 data_.computeReferences(nullptr);
709 NeighborhoodSearchTestData data_;
712 class RandomBoxFullPBCData
715 static const NeighborhoodSearchTestData& get()
717 static RandomBoxFullPBCData singleton;
718 return singleton.data_;
721 RandomBoxFullPBCData() : data_(12345, 1.0)
723 data_.box_[XX][XX] = 10.0;
724 data_.box_[YY][YY] = 5.0;
725 data_.box_[ZZ][ZZ] = 7.0;
726 // TODO: Consider whether manually picking some positions would give better
728 data_.generateRandomRefPositions(1000);
729 data_.generateRandomTestPositions(100);
730 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
731 data_.computeReferences(&data_.pbc_);
735 NeighborhoodSearchTestData data_;
738 class RandomBoxSelfPairsData
741 static const NeighborhoodSearchTestData& get()
743 static RandomBoxSelfPairsData singleton;
744 return singleton.data_;
747 RandomBoxSelfPairsData() : data_(12345, 1.0)
749 data_.box_[XX][XX] = 10.0;
750 data_.box_[YY][YY] = 5.0;
751 data_.box_[ZZ][ZZ] = 7.0;
752 data_.generateRandomRefPositions(1000);
753 data_.useRefPositionsAsTestPositions();
754 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
755 data_.computeReferences(&data_.pbc_);
759 NeighborhoodSearchTestData data_;
762 class RandomBoxXYFullPBCData
765 static const NeighborhoodSearchTestData& get()
767 static RandomBoxXYFullPBCData singleton;
768 return singleton.data_;
771 RandomBoxXYFullPBCData() : data_(54321, 1.0)
773 data_.box_[XX][XX] = 10.0;
774 data_.box_[YY][YY] = 5.0;
775 data_.box_[ZZ][ZZ] = 7.0;
776 // TODO: Consider whether manually picking some positions would give better
778 data_.generateRandomRefPositions(1000);
779 data_.generateRandomTestPositions(100);
780 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
781 data_.computeReferencesXY(&data_.pbc_);
785 NeighborhoodSearchTestData data_;
788 class RandomTriclinicFullPBCData
791 static const NeighborhoodSearchTestData& get()
793 static RandomTriclinicFullPBCData singleton;
794 return singleton.data_;
797 RandomTriclinicFullPBCData() : data_(12345, 1.0)
799 data_.box_[XX][XX] = 5.0;
800 data_.box_[YY][XX] = 2.5;
801 data_.box_[YY][YY] = 2.5 * std::sqrt(3.0);
802 data_.box_[ZZ][XX] = 2.5;
803 data_.box_[ZZ][YY] = 2.5 * std::sqrt(1.0 / 3.0);
804 data_.box_[ZZ][ZZ] = 5.0 * std::sqrt(2.0 / 3.0);
805 // TODO: Consider whether manually picking some positions would give better
807 data_.generateRandomRefPositions(1000);
808 data_.generateRandomTestPositions(100);
809 set_pbc(&data_.pbc_, PbcType::Xyz, data_.box_);
810 data_.computeReferences(&data_.pbc_);
814 NeighborhoodSearchTestData data_;
817 class RandomBox2DPBCData
820 static const NeighborhoodSearchTestData& get()
822 static RandomBox2DPBCData singleton;
823 return singleton.data_;
826 RandomBox2DPBCData() : data_(12345, 1.0)
828 data_.box_[XX][XX] = 10.0;
829 data_.box_[YY][YY] = 7.0;
830 data_.box_[ZZ][ZZ] = 5.0;
831 // TODO: Consider whether manually picking some positions would give better
833 data_.generateRandomRefPositions(1000);
834 data_.generateRandomTestPositions(100);
835 set_pbc(&data_.pbc_, PbcType::XY, data_.box_);
836 data_.computeReferences(&data_.pbc_);
840 NeighborhoodSearchTestData data_;
843 class RandomBoxNoPBCData
846 static const NeighborhoodSearchTestData& get()
848 static RandomBoxNoPBCData singleton;
849 return singleton.data_;
852 RandomBoxNoPBCData() : data_(12345, 1.0)
854 data_.box_[XX][XX] = 10.0;
855 data_.box_[YY][YY] = 5.0;
856 data_.box_[ZZ][ZZ] = 7.0;
857 // TODO: Consider whether manually picking some positions would give better
859 data_.generateRandomRefPositions(1000);
860 data_.generateRandomTestPositions(100);
861 set_pbc(&data_.pbc_, PbcType::No, data_.box_);
862 data_.computeReferences(nullptr);
866 NeighborhoodSearchTestData data_;
869 /********************************************************************
873 TEST_F(NeighborhoodSearchTest, SimpleSearch)
875 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
877 nb_.setCutoff(data.cutoff_);
878 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
879 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
880 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
882 testIsWithin(&search, data);
883 testMinimumDistance(&search, data);
884 testNearestPoint(&search, data);
885 testPairSearch(&search, data);
888 testPairSearchIndexed(&nb_, data, 123);
891 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
893 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
895 nb_.setCutoff(data.cutoff_);
896 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
898 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
899 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
901 testIsWithin(&search, data);
902 testMinimumDistance(&search, data);
903 testNearestPoint(&search, data);
904 testPairSearch(&search, data);
907 TEST_F(NeighborhoodSearchTest, GridSearchBox)
909 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
911 nb_.setCutoff(data.cutoff_);
912 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
913 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
914 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
916 testIsWithin(&search, data);
917 testMinimumDistance(&search, data);
918 testNearestPoint(&search, data);
919 testPairSearch(&search, data);
922 testPairSearchIndexed(&nb_, data, 456);
925 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
927 const NeighborhoodSearchTestData& data = RandomTriclinicFullPBCData::get();
929 nb_.setCutoff(data.cutoff_);
930 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
931 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
932 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
934 testPairSearch(&search, data);
937 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
939 const NeighborhoodSearchTestData& data = RandomBox2DPBCData::get();
941 nb_.setCutoff(data.cutoff_);
942 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
943 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
944 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
946 testIsWithin(&search, data);
947 testMinimumDistance(&search, data);
948 testNearestPoint(&search, data);
949 testPairSearch(&search, data);
952 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
954 const NeighborhoodSearchTestData& data = RandomBoxNoPBCData::get();
956 nb_.setCutoff(data.cutoff_);
957 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
958 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
959 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
961 testPairSearch(&search, data);
964 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
966 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
968 nb_.setCutoff(data.cutoff_);
969 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
971 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
972 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
974 testIsWithin(&search, data);
975 testMinimumDistance(&search, data);
976 testNearestPoint(&search, data);
977 testPairSearch(&search, data);
980 TEST_F(NeighborhoodSearchTest, SimpleSelfPairsSearch)
982 const NeighborhoodSearchTestData& data = TrivialSelfPairsTestData::get();
984 nb_.setCutoff(data.cutoff_);
985 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
986 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
987 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
989 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
992 TEST_F(NeighborhoodSearchTest, GridSelfPairsSearch)
994 const NeighborhoodSearchTestData& data = RandomBoxSelfPairsData::get();
996 nb_.setCutoff(data.cutoff_);
997 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
998 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
999 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1001 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
1004 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1006 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1008 nb_.setCutoff(data.cutoff_);
1009 gmx::AnalysisNeighborhoodSearch search1 = nb_.initSearch(&data.pbc_, data.refPositions());
1010 gmx::AnalysisNeighborhoodSearch search2 = nb_.initSearch(&data.pbc_, data.refPositions());
1012 // These checks are fragile, and unfortunately depend on the random
1013 // engine used to create the test positions. There is no particular reason
1014 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1015 gmx::AnalysisNeighborhoodPairSearch pairSearch1 = search1.startPairSearch(data.testPosition(0));
1016 gmx::AnalysisNeighborhoodPairSearch pairSearch2 = search1.startPairSearch(data.testPosition(2));
1018 testPairSearch(&search2, data);
1020 gmx::AnalysisNeighborhoodPair pair;
1021 ASSERT_TRUE(pairSearch1.findNextPair(&pair))
1022 << "Test data did not contain any pairs for position 0 (problem in the test).";
1023 EXPECT_EQ(0, pair.testIndex());
1025 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1026 EXPECT_TRUE(data.containsPair(0, searchPair));
1029 ASSERT_TRUE(pairSearch2.findNextPair(&pair))
1030 << "Test data did not contain any pairs for position 2 (problem in the test).";
1031 EXPECT_EQ(2, pair.testIndex());
1033 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1034 EXPECT_TRUE(data.containsPair(2, searchPair));
1038 TEST_F(NeighborhoodSearchTest, HandlesNoPBC)
1040 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1042 nb_.setCutoff(data.cutoff_);
1043 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1044 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1046 testIsWithin(&search, data);
1047 testMinimumDistance(&search, data);
1048 testNearestPoint(&search, data);
1049 testPairSearch(&search, data);
1052 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1054 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1056 nb_.setCutoff(data.cutoff_);
1057 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(nullptr, data.refPositions());
1058 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1060 testIsWithin(&search, data);
1061 testMinimumDistance(&search, data);
1062 testNearestPoint(&search, data);
1063 testPairSearch(&search, data);
1066 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1068 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1070 nb_.setCutoff(data.cutoff_);
1071 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1072 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(data.testPositions());
1073 gmx::AnalysisNeighborhoodPair pair;
1074 // TODO: This test needs to be adjusted if the grid search gets optimized
1075 // to loop over the test positions in cell order (first, the ordering
1076 // assumption here breaks, and second, it then needs to be tested
1077 // separately for simple and grid searches).
1078 int currentIndex = 0;
1079 while (pairSearch.findNextPair(&pair))
1081 while (currentIndex < pair.testIndex())
1085 EXPECT_EQ(currentIndex, pair.testIndex());
1086 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1087 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1088 pairSearch.skipRemainingPairsForTestPosition();
1093 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1095 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1097 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1098 helper.generateExclusions();
1100 nb_.setCutoff(data.cutoff_);
1101 nb_.setTopologyExclusions(helper.exclusions());
1102 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1103 gmx::AnalysisNeighborhoodSearch search =
1104 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1105 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1107 testPairSearchFull(&search,
1109 data.testPositions().exclusionIds(helper.testPosIds()),
1110 helper.exclusions(),
1116 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1118 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1120 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1121 helper.generateExclusions();
1123 nb_.setCutoff(data.cutoff_);
1124 nb_.setTopologyExclusions(helper.exclusions());
1125 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1126 gmx::AnalysisNeighborhoodSearch search =
1127 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1128 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1130 testPairSearchFull(&search,
1132 data.testPositions().exclusionIds(helper.testPosIds()),
1133 helper.exclusions(),