2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
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.
37 * Tests selection neighborhood searching.
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
49 #include "gromacs/selection/nbsearch.h"
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"
76 /********************************************************************
77 * NeighborhoodSearchTestData
80 class NeighborhoodSearchTestData
85 RefPair(int refIndex, real distance) :
94 bool operator<(const RefPair& other) const { return refIndex < other.refIndex; }
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.
109 explicit TestPosition(const rvec x) : refMinDist(0.0), refNearestPoint(-1)
111 copy_rvec(x, this->x);
117 std::vector<RefPair> refPairs;
120 typedef std::vector<TestPosition> TestPositionList;
122 NeighborhoodSearchTestData(uint64_t seed, real cutoff);
124 gmx::AnalysisNeighborhoodPositions refPositions() const
126 return gmx::AnalysisNeighborhoodPositions(refPos_);
128 gmx::AnalysisNeighborhoodPositions testPositions() const
130 if (testPos_.empty())
132 testPos_.reserve(testPositions_.size());
133 for (size_t i = 0; i < testPositions_.size(); ++i)
135 testPos_.emplace_back(testPositions_[i].x);
138 return gmx::AnalysisNeighborhoodPositions(testPos_);
140 gmx::AnalysisNeighborhoodPositions testPosition(int index) const
142 return testPositions().selectSingleFromArray(index);
145 void addTestPosition(const rvec x)
147 GMX_RELEASE_ASSERT(testPos_.empty(), "Cannot add positions after testPositions() call");
148 testPositions_.emplace_back(x);
150 gmx::RVec generateRandomPosition();
151 std::vector<int> generateIndex(int count, uint64_t seed) const;
152 void generateRandomRefPositions(int count);
153 void generateRandomTestPositions(int count);
154 void useRefPositionsAsTestPositions();
155 void computeReferences(t_pbc* pbc) { computeReferencesInternal(pbc, false); }
156 void computeReferencesXY(t_pbc* pbc) { computeReferencesInternal(pbc, true); }
158 bool containsPair(int testIndex, const RefPair& pair) const
160 const std::vector<RefPair>& refPairs = testPositions_[testIndex].refPairs;
161 std::vector<RefPair>::const_iterator foundRefPair =
162 std::lower_bound(refPairs.begin(), refPairs.end(), pair);
163 return !(foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex);
166 // Return a tolerance that accounts for the magnitudes of the coordinates
167 // when doing subtractions, so that we set the ULP tolerance relative to the
168 // coordinate values rather than their difference.
169 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
170 // to 10.0, but a much larger error relative to the difference.
171 gmx::test::FloatingPointTolerance relativeTolerance() const
173 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
174 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
177 gmx::DefaultRandomEngine rng_;
182 std::vector<gmx::RVec> refPos_;
183 TestPositionList testPositions_;
186 void computeReferencesInternal(t_pbc* pbc, bool bXY);
188 mutable std::vector<gmx::RVec> testPos_;
191 //! Shorthand for a collection of reference pairs.
192 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
194 NeighborhoodSearchTestData::NeighborhoodSearchTestData(uint64_t seed, real cutoff) :
200 set_pbc(&pbc_, epbcNONE, box_);
203 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
205 gmx::UniformRealDistribution<real> dist;
211 // Add a small displacement to allow positions outside the box
212 x[XX] += 0.2 * dist(rng_) - 0.1;
213 x[YY] += 0.2 * dist(rng_) - 0.1;
214 x[ZZ] += 0.2 * dist(rng_) - 0.1;
218 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, uint64_t seed) const
220 gmx::DefaultRandomEngine rngIndex(seed);
221 gmx::UniformRealDistribution<real> dist;
222 std::vector<int> result;
224 for (int i = 0; i < count; ++i)
226 if (dist(rngIndex) > 0.5)
234 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
236 refPosCount_ = count;
237 refPos_.reserve(count);
238 for (int i = 0; i < count; ++i)
240 refPos_.push_back(generateRandomPosition());
244 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
246 testPositions_.reserve(count);
247 for (int i = 0; i < count; ++i)
249 addTestPosition(generateRandomPosition());
253 void NeighborhoodSearchTestData::useRefPositionsAsTestPositions()
255 testPositions_.reserve(refPosCount_);
256 for (const auto& refPos : refPos_)
258 addTestPosition(refPos);
262 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc* pbc, bool bXY)
264 real cutoff = cutoff_;
267 cutoff = std::numeric_limits<real>::max();
269 for (TestPosition& testPos : testPositions_)
271 testPos.refMinDist = cutoff;
272 testPos.refNearestPoint = -1;
273 testPos.refPairs.clear();
274 for (int j = 0; j < refPosCount_; ++j)
279 pbc_dx(pbc, testPos.x, refPos_[j], dx);
283 rvec_sub(testPos.x, refPos_[j], dx);
285 // TODO: This may not work intuitively for 2D with the third box
286 // vector not parallel to the Z axis, but neither does the actual
287 // neighborhood search.
288 const real dist = !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
289 if (dist < testPos.refMinDist)
291 testPos.refMinDist = dist;
292 testPos.refNearestPoint = j;
294 if (dist > 0 && dist <= cutoff)
296 RefPair pair(j, dist);
297 GMX_RELEASE_ASSERT(testPos.refPairs.empty() || testPos.refPairs.back() < pair,
298 "Reference pairs should be generated in sorted order");
299 testPos.refPairs.push_back(pair);
305 /********************************************************************
309 class ExclusionsHelper
312 static void markExcludedPairs(RefPairList* refPairs, int testIndex, const t_blocka* excls);
314 ExclusionsHelper(int refPosCount, int testPosCount);
316 void generateExclusions();
318 const t_blocka* exclusions() const { return &excls_; }
320 gmx::ArrayRef<const int> refPosIds() const
322 return gmx::makeArrayRef(exclusionIds_).subArray(0, refPosCount_);
324 gmx::ArrayRef<const int> testPosIds() const
326 return gmx::makeArrayRef(exclusionIds_).subArray(0, testPosCount_);
332 std::vector<int> exclusionIds_;
333 std::vector<int> exclsIndex_;
334 std::vector<int> exclsAtoms_;
339 void ExclusionsHelper::markExcludedPairs(RefPairList* refPairs, int testIndex, const t_blocka* excls)
342 for (int i = excls->index[testIndex]; i < excls->index[testIndex + 1]; ++i)
344 const int excludedIndex = excls->a[i];
345 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
346 RefPairList::iterator excludedRefPair =
347 std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
348 if (excludedRefPair != refPairs->end() && excludedRefPair->refIndex == excludedIndex)
350 excludedRefPair->bFound = true;
351 excludedRefPair->bExcluded = true;
357 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount) :
358 refPosCount_(refPosCount),
359 testPosCount_(testPosCount)
361 // Generate an array of 0, 1, 2, ...
362 // TODO: Make the tests work also with non-trivial exclusion IDs,
364 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
365 exclusionIds_[0] = 0;
366 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(), exclusionIds_.begin());
369 excls_.index = nullptr;
372 excls_.nalloc_index = 0;
376 void ExclusionsHelper::generateExclusions()
378 // TODO: Consider a better set of test data, where the density of the
379 // particles would be higher, or where the exclusions would not be random,
380 // to make a higher percentage of the exclusions to actually be within the
382 exclsIndex_.reserve(testPosCount_ + 1);
383 exclsAtoms_.reserve(testPosCount_ * 20);
384 exclsIndex_.push_back(0);
385 for (int i = 0; i < testPosCount_; ++i)
387 for (int j = 0; j < 20; ++j)
389 exclsAtoms_.push_back(i + j * 3);
391 exclsIndex_.push_back(exclsAtoms_.size());
393 excls_.nr = exclsIndex_.size();
394 excls_.index = exclsIndex_.data();
395 excls_.nra = exclsAtoms_.size();
396 excls_.a = exclsAtoms_.data();
399 /********************************************************************
400 * NeighborhoodSearchTest
403 class NeighborhoodSearchTest : public ::testing::Test
406 void testIsWithin(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
407 void testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
408 const NeighborhoodSearchTestData& data);
409 void testNearestPoint(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
410 void testPairSearch(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
411 void testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
412 const NeighborhoodSearchTestData& data,
414 void testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
415 const NeighborhoodSearchTestData& data,
416 const gmx::AnalysisNeighborhoodPositions& pos,
417 const t_blocka* excls,
418 const gmx::ArrayRef<const int>& refIndices,
419 const gmx::ArrayRef<const int>& testIndices,
422 gmx::AnalysisNeighborhood nb_;
425 void NeighborhoodSearchTest::testIsWithin(gmx::AnalysisNeighborhoodSearch* search,
426 const NeighborhoodSearchTestData& data)
428 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
429 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
431 const bool bWithin = (i->refMinDist <= data.cutoff_);
432 EXPECT_EQ(bWithin, search->isWithin(i->x)) << "Distance is " << i->refMinDist;
436 void NeighborhoodSearchTest::testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
437 const NeighborhoodSearchTestData& data)
439 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
441 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
443 const real refDist = i->refMinDist;
444 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
448 void NeighborhoodSearchTest::testNearestPoint(gmx::AnalysisNeighborhoodSearch* search,
449 const NeighborhoodSearchTestData& data)
451 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
452 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
454 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
457 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
458 EXPECT_EQ(0, pair.testIndex());
459 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
463 EXPECT_EQ(i->refNearestPoint, -1);
468 //! Helper function for formatting test failure messages.
469 std::string formatVector(const rvec x)
471 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
475 * Helper function to check that all expected pairs were found.
477 void checkAllPairsFound(const RefPairList& refPairs,
478 const std::vector<gmx::RVec>& refPos,
482 // This could be elegantly expressed with Google Mock matchers, but that
483 // has a significant effect on the runtime of the tests...
485 RefPairList::const_iterator first;
486 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
496 ADD_FAILURE() << "Some pairs (" << count << "/" << refPairs.size() << ") "
497 << "within the cutoff were not found. First pair:\n"
498 << " Ref: " << first->refIndex << " at "
499 << formatVector(refPos[first->refIndex]) << "\n"
500 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
501 << "Dist: " << first->distance;
505 void NeighborhoodSearchTest::testPairSearch(gmx::AnalysisNeighborhoodSearch* search,
506 const NeighborhoodSearchTestData& data)
508 testPairSearchFull(search, data, data.testPositions(), nullptr, {}, {}, false);
511 void NeighborhoodSearchTest::testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
512 const NeighborhoodSearchTestData& data,
515 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
516 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
517 gmx::AnalysisNeighborhoodSearch search =
518 nb->initSearch(&data.pbc_, data.refPositions().indexed(refIndices));
519 testPairSearchFull(&search, data, data.testPositions(), nullptr, refIndices, testIndices, false);
522 void NeighborhoodSearchTest::testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
523 const NeighborhoodSearchTestData& data,
524 const gmx::AnalysisNeighborhoodPositions& pos,
525 const t_blocka* excls,
526 const gmx::ArrayRef<const int>& refIndices,
527 const gmx::ArrayRef<const int>& testIndices,
530 std::map<int, RefPairList> refPairs;
531 // TODO: Some parts of this code do not work properly if pos does not
532 // initially contain all the test positions.
533 if (testIndices.empty())
535 for (size_t i = 0; i < data.testPositions_.size(); ++i)
537 refPairs[i] = data.testPositions_[i].refPairs;
542 for (int index : testIndices)
544 refPairs[index] = data.testPositions_[index].refPairs;
547 if (excls != nullptr)
549 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with exclusions");
550 for (auto& entry : refPairs)
552 const int testIndex = entry.first;
553 ExclusionsHelper::markExcludedPairs(&entry.second, testIndex, excls);
556 if (!refIndices.empty())
558 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with indexing");
559 for (auto& entry : refPairs)
561 for (auto& refPair : entry.second)
563 refPair.bIndexed = false;
565 for (int index : refIndices)
567 NeighborhoodSearchTestData::RefPair searchPair(index, 0.0);
568 auto refPair = std::lower_bound(entry.second.begin(), entry.second.end(), searchPair);
569 if (refPair != entry.second.end() && refPair->refIndex == index)
571 refPair->bIndexed = true;
574 for (auto& refPair : entry.second)
576 if (!refPair.bIndexed)
578 refPair.bFound = true;
584 gmx::AnalysisNeighborhoodPositions posCopy(pos);
585 if (!testIndices.empty())
587 posCopy.indexed(testIndices);
589 gmx::AnalysisNeighborhoodPairSearch pairSearch =
590 selfPairs ? search->startSelfPairSearch() : search->startPairSearch(posCopy);
591 gmx::AnalysisNeighborhoodPair pair;
592 while (pairSearch.findNextPair(&pair))
594 const int testIndex = (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
595 const int refIndex = (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
597 if (refPairs.count(testIndex) == 0)
599 ADD_FAILURE() << "Expected: No pairs are returned for test position " << testIndex << ".\n"
600 << " Actual: Pair with ref " << refIndex << " is returned.";
603 NeighborhoodSearchTestData::RefPair searchPair(refIndex, std::sqrt(pair.distance2()));
604 const auto foundRefPair =
605 std::lower_bound(refPairs[testIndex].begin(), refPairs[testIndex].end(), searchPair);
606 if (foundRefPair == refPairs[testIndex].end() || foundRefPair->refIndex != refIndex)
608 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
609 << ") is not within the cutoff.\n"
610 << " Actual: It is returned.";
612 else if (foundRefPair->bExcluded)
614 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
615 << ") is excluded from the search.\n"
616 << " Actual: It is returned.";
618 else if (!foundRefPair->bIndexed)
620 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
621 << ") is not part of the indexed set.\n"
622 << " Actual: It is returned.";
624 else if (foundRefPair->bFound)
626 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
627 << ") is returned only once.\n"
628 << " Actual: It is returned multiple times.";
633 foundRefPair->bFound = true;
635 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
636 << "Distance computed by the neighborhood search does not match.";
639 searchPair = NeighborhoodSearchTestData::RefPair(testIndex, 0.0);
640 const auto otherRefPair = std::lower_bound(refPairs[refIndex].begin(),
641 refPairs[refIndex].end(), searchPair);
642 GMX_RELEASE_ASSERT(otherRefPair != refPairs[refIndex].end(),
643 "Precomputed reference data is not symmetric");
644 otherRefPair->bFound = true;
649 for (auto& entry : refPairs)
651 const int testIndex = entry.first;
652 checkAllPairsFound(entry.second, data.refPos_, testIndex, data.testPositions_[testIndex].x);
656 /********************************************************************
657 * Test data generation
660 class TrivialTestData
663 static const NeighborhoodSearchTestData& get()
665 static TrivialTestData singleton;
666 return singleton.data_;
669 TrivialTestData() : data_(12345, 1.0)
671 // Make the box so small we are virtually guaranteed to have
672 // several neighbors for the five test positions
673 data_.box_[XX][XX] = 3.0;
674 data_.box_[YY][YY] = 3.0;
675 data_.box_[ZZ][ZZ] = 3.0;
676 data_.generateRandomRefPositions(10);
677 data_.generateRandomTestPositions(5);
678 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
679 data_.computeReferences(&data_.pbc_);
683 NeighborhoodSearchTestData data_;
686 class TrivialSelfPairsTestData
689 static const NeighborhoodSearchTestData& get()
691 static TrivialSelfPairsTestData singleton;
692 return singleton.data_;
695 TrivialSelfPairsTestData() : data_(12345, 1.0)
697 data_.box_[XX][XX] = 3.0;
698 data_.box_[YY][YY] = 3.0;
699 data_.box_[ZZ][ZZ] = 3.0;
700 data_.generateRandomRefPositions(20);
701 data_.useRefPositionsAsTestPositions();
702 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
703 data_.computeReferences(&data_.pbc_);
707 NeighborhoodSearchTestData data_;
710 class TrivialNoPBCTestData
713 static const NeighborhoodSearchTestData& get()
715 static TrivialNoPBCTestData singleton;
716 return singleton.data_;
719 TrivialNoPBCTestData() : data_(12345, 1.0)
721 data_.generateRandomRefPositions(10);
722 data_.generateRandomTestPositions(5);
723 data_.computeReferences(nullptr);
727 NeighborhoodSearchTestData data_;
730 class RandomBoxFullPBCData
733 static const NeighborhoodSearchTestData& get()
735 static RandomBoxFullPBCData singleton;
736 return singleton.data_;
739 RandomBoxFullPBCData() : data_(12345, 1.0)
741 data_.box_[XX][XX] = 10.0;
742 data_.box_[YY][YY] = 5.0;
743 data_.box_[ZZ][ZZ] = 7.0;
744 // TODO: Consider whether manually picking some positions would give better
746 data_.generateRandomRefPositions(1000);
747 data_.generateRandomTestPositions(100);
748 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
749 data_.computeReferences(&data_.pbc_);
753 NeighborhoodSearchTestData data_;
756 class RandomBoxSelfPairsData
759 static const NeighborhoodSearchTestData& get()
761 static RandomBoxSelfPairsData singleton;
762 return singleton.data_;
765 RandomBoxSelfPairsData() : data_(12345, 1.0)
767 data_.box_[XX][XX] = 10.0;
768 data_.box_[YY][YY] = 5.0;
769 data_.box_[ZZ][ZZ] = 7.0;
770 data_.generateRandomRefPositions(1000);
771 data_.useRefPositionsAsTestPositions();
772 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
773 data_.computeReferences(&data_.pbc_);
777 NeighborhoodSearchTestData data_;
780 class RandomBoxXYFullPBCData
783 static const NeighborhoodSearchTestData& get()
785 static RandomBoxXYFullPBCData singleton;
786 return singleton.data_;
789 RandomBoxXYFullPBCData() : data_(54321, 1.0)
791 data_.box_[XX][XX] = 10.0;
792 data_.box_[YY][YY] = 5.0;
793 data_.box_[ZZ][ZZ] = 7.0;
794 // TODO: Consider whether manually picking some positions would give better
796 data_.generateRandomRefPositions(1000);
797 data_.generateRandomTestPositions(100);
798 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
799 data_.computeReferencesXY(&data_.pbc_);
803 NeighborhoodSearchTestData data_;
806 class RandomTriclinicFullPBCData
809 static const NeighborhoodSearchTestData& get()
811 static RandomTriclinicFullPBCData singleton;
812 return singleton.data_;
815 RandomTriclinicFullPBCData() : data_(12345, 1.0)
817 data_.box_[XX][XX] = 5.0;
818 data_.box_[YY][XX] = 2.5;
819 data_.box_[YY][YY] = 2.5 * std::sqrt(3.0);
820 data_.box_[ZZ][XX] = 2.5;
821 data_.box_[ZZ][YY] = 2.5 * std::sqrt(1.0 / 3.0);
822 data_.box_[ZZ][ZZ] = 5.0 * std::sqrt(2.0 / 3.0);
823 // TODO: Consider whether manually picking some positions would give better
825 data_.generateRandomRefPositions(1000);
826 data_.generateRandomTestPositions(100);
827 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
828 data_.computeReferences(&data_.pbc_);
832 NeighborhoodSearchTestData data_;
835 class RandomBox2DPBCData
838 static const NeighborhoodSearchTestData& get()
840 static RandomBox2DPBCData singleton;
841 return singleton.data_;
844 RandomBox2DPBCData() : data_(12345, 1.0)
846 data_.box_[XX][XX] = 10.0;
847 data_.box_[YY][YY] = 7.0;
848 data_.box_[ZZ][ZZ] = 5.0;
849 // TODO: Consider whether manually picking some positions would give better
851 data_.generateRandomRefPositions(1000);
852 data_.generateRandomTestPositions(100);
853 set_pbc(&data_.pbc_, epbcXY, data_.box_);
854 data_.computeReferences(&data_.pbc_);
858 NeighborhoodSearchTestData data_;
861 class RandomBoxNoPBCData
864 static const NeighborhoodSearchTestData& get()
866 static RandomBoxNoPBCData singleton;
867 return singleton.data_;
870 RandomBoxNoPBCData() : data_(12345, 1.0)
872 data_.box_[XX][XX] = 10.0;
873 data_.box_[YY][YY] = 5.0;
874 data_.box_[ZZ][ZZ] = 7.0;
875 // TODO: Consider whether manually picking some positions would give better
877 data_.generateRandomRefPositions(1000);
878 data_.generateRandomTestPositions(100);
879 set_pbc(&data_.pbc_, epbcNONE, data_.box_);
880 data_.computeReferences(nullptr);
884 NeighborhoodSearchTestData data_;
887 /********************************************************************
891 TEST_F(NeighborhoodSearchTest, SimpleSearch)
893 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
895 nb_.setCutoff(data.cutoff_);
896 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
897 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
898 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
900 testIsWithin(&search, data);
901 testMinimumDistance(&search, data);
902 testNearestPoint(&search, data);
903 testPairSearch(&search, data);
906 testPairSearchIndexed(&nb_, data, 123);
909 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
911 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
913 nb_.setCutoff(data.cutoff_);
914 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
916 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
917 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
919 testIsWithin(&search, data);
920 testMinimumDistance(&search, data);
921 testNearestPoint(&search, data);
922 testPairSearch(&search, data);
925 TEST_F(NeighborhoodSearchTest, GridSearchBox)
927 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::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 testIsWithin(&search, data);
935 testMinimumDistance(&search, data);
936 testNearestPoint(&search, data);
937 testPairSearch(&search, data);
940 testPairSearchIndexed(&nb_, data, 456);
943 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
945 const NeighborhoodSearchTestData& data = RandomTriclinicFullPBCData::get();
947 nb_.setCutoff(data.cutoff_);
948 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
949 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
950 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
952 testPairSearch(&search, data);
955 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
957 const NeighborhoodSearchTestData& data = RandomBox2DPBCData::get();
959 nb_.setCutoff(data.cutoff_);
960 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
961 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
962 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
964 testIsWithin(&search, data);
965 testMinimumDistance(&search, data);
966 testNearestPoint(&search, data);
967 testPairSearch(&search, data);
970 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
972 const NeighborhoodSearchTestData& data = RandomBoxNoPBCData::get();
974 nb_.setCutoff(data.cutoff_);
975 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
976 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
977 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
979 testPairSearch(&search, data);
982 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
984 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
986 nb_.setCutoff(data.cutoff_);
987 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
989 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
990 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
992 testIsWithin(&search, data);
993 testMinimumDistance(&search, data);
994 testNearestPoint(&search, data);
995 testPairSearch(&search, data);
998 TEST_F(NeighborhoodSearchTest, SimpleSelfPairsSearch)
1000 const NeighborhoodSearchTestData& data = TrivialSelfPairsTestData::get();
1002 nb_.setCutoff(data.cutoff_);
1003 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1004 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1005 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1007 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
1010 TEST_F(NeighborhoodSearchTest, GridSelfPairsSearch)
1012 const NeighborhoodSearchTestData& data = RandomBoxSelfPairsData::get();
1014 nb_.setCutoff(data.cutoff_);
1015 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1016 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1017 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1019 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
1022 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1024 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1026 nb_.setCutoff(data.cutoff_);
1027 gmx::AnalysisNeighborhoodSearch search1 = nb_.initSearch(&data.pbc_, data.refPositions());
1028 gmx::AnalysisNeighborhoodSearch search2 = nb_.initSearch(&data.pbc_, data.refPositions());
1030 // These checks are fragile, and unfortunately depend on the random
1031 // engine used to create the test positions. There is no particular reason
1032 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1033 gmx::AnalysisNeighborhoodPairSearch pairSearch1 = search1.startPairSearch(data.testPosition(0));
1034 gmx::AnalysisNeighborhoodPairSearch pairSearch2 = search1.startPairSearch(data.testPosition(2));
1036 testPairSearch(&search2, data);
1038 gmx::AnalysisNeighborhoodPair pair;
1039 ASSERT_TRUE(pairSearch1.findNextPair(&pair))
1040 << "Test data did not contain any pairs for position 0 (problem in the test).";
1041 EXPECT_EQ(0, pair.testIndex());
1043 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1044 EXPECT_TRUE(data.containsPair(0, searchPair));
1047 ASSERT_TRUE(pairSearch2.findNextPair(&pair))
1048 << "Test data did not contain any pairs for position 2 (problem in the test).";
1049 EXPECT_EQ(2, pair.testIndex());
1051 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1052 EXPECT_TRUE(data.containsPair(2, searchPair));
1056 TEST_F(NeighborhoodSearchTest, HandlesNoPBC)
1058 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1060 nb_.setCutoff(data.cutoff_);
1061 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1062 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1064 testIsWithin(&search, data);
1065 testMinimumDistance(&search, data);
1066 testNearestPoint(&search, data);
1067 testPairSearch(&search, data);
1070 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1072 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1074 nb_.setCutoff(data.cutoff_);
1075 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(nullptr, data.refPositions());
1076 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1078 testIsWithin(&search, data);
1079 testMinimumDistance(&search, data);
1080 testNearestPoint(&search, data);
1081 testPairSearch(&search, data);
1084 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1086 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1088 nb_.setCutoff(data.cutoff_);
1089 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1090 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(data.testPositions());
1091 gmx::AnalysisNeighborhoodPair pair;
1092 // TODO: This test needs to be adjusted if the grid search gets optimized
1093 // to loop over the test positions in cell order (first, the ordering
1094 // assumption here breaks, and second, it then needs to be tested
1095 // separately for simple and grid searches).
1096 int currentIndex = 0;
1097 while (pairSearch.findNextPair(&pair))
1099 while (currentIndex < pair.testIndex())
1103 EXPECT_EQ(currentIndex, pair.testIndex());
1104 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1105 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1106 pairSearch.skipRemainingPairsForTestPosition();
1111 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1113 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1115 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1116 helper.generateExclusions();
1118 nb_.setCutoff(data.cutoff_);
1119 nb_.setTopologyExclusions(helper.exclusions());
1120 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1121 gmx::AnalysisNeighborhoodSearch search =
1122 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1123 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1125 testPairSearchFull(&search, data, data.testPositions().exclusionIds(helper.testPosIds()),
1126 helper.exclusions(), {}, {}, false);
1129 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1131 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1133 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1134 helper.generateExclusions();
1136 nb_.setCutoff(data.cutoff_);
1137 nb_.setTopologyExclusions(helper.exclusions());
1138 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1139 gmx::AnalysisNeighborhoodSearch search =
1140 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1141 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1143 testPairSearchFull(&search, data, data.testPositions().exclusionIds(helper.testPosIds()),
1144 helper.exclusions(), {}, {}, false);