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/listoflists.h"
68 #include "gromacs/utility/smalloc.h"
69 #include "gromacs/utility/stringutil.h"
71 #include "testutils/testasserts.h"
77 /********************************************************************
78 * NeighborhoodSearchTestData
81 class NeighborhoodSearchTestData
86 RefPair(int refIndex, real distance) :
95 bool operator<(const RefPair& other) const { return refIndex < other.refIndex; }
99 // The variables below are state variables that are only used
100 // during the actual testing after creating a copy of the reference
101 // pair list, not as part of the reference data.
102 // Simpler to have just a single structure for both purposes.
110 explicit TestPosition(const rvec x) : refMinDist(0.0), refNearestPoint(-1)
112 copy_rvec(x, this->x);
118 std::vector<RefPair> refPairs;
121 typedef std::vector<TestPosition> TestPositionList;
123 NeighborhoodSearchTestData(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(), "Cannot add positions after testPositions() call");
149 testPositions_.emplace_back(x);
151 gmx::RVec generateRandomPosition();
152 std::vector<int> generateIndex(int count, uint64_t seed) const;
153 void generateRandomRefPositions(int count);
154 void generateRandomTestPositions(int count);
155 void useRefPositionsAsTestPositions();
156 void computeReferences(t_pbc* pbc) { computeReferencesInternal(pbc, false); }
157 void computeReferencesXY(t_pbc* pbc) { computeReferencesInternal(pbc, true); }
159 bool containsPair(int testIndex, const RefPair& pair) const
161 const std::vector<RefPair>& refPairs = testPositions_[testIndex].refPairs;
162 std::vector<RefPair>::const_iterator foundRefPair =
163 std::lower_bound(refPairs.begin(), refPairs.end(), pair);
164 return !(foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex);
167 // Return a tolerance that accounts for the magnitudes of the coordinates
168 // when doing subtractions, so that we set the ULP tolerance relative to the
169 // coordinate values rather than their difference.
170 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
171 // to 10.0, but a much larger error relative to the difference.
172 gmx::test::FloatingPointTolerance relativeTolerance() const
174 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
175 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
178 gmx::DefaultRandomEngine rng_;
183 std::vector<gmx::RVec> refPos_;
184 TestPositionList testPositions_;
187 void computeReferencesInternal(t_pbc* pbc, bool bXY);
189 mutable std::vector<gmx::RVec> testPos_;
192 //! Shorthand for a collection of reference pairs.
193 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
195 NeighborhoodSearchTestData::NeighborhoodSearchTestData(uint64_t seed, real cutoff) :
201 set_pbc(&pbc_, epbcNONE, box_);
204 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
206 gmx::UniformRealDistribution<real> dist;
212 // Add a small displacement to allow positions outside the box
213 x[XX] += 0.2 * dist(rng_) - 0.1;
214 x[YY] += 0.2 * dist(rng_) - 0.1;
215 x[ZZ] += 0.2 * dist(rng_) - 0.1;
219 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, uint64_t seed) const
221 gmx::DefaultRandomEngine rngIndex(seed);
222 gmx::UniformRealDistribution<real> dist;
223 std::vector<int> result;
225 for (int i = 0; i < count; ++i)
227 if (dist(rngIndex) > 0.5)
235 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
237 refPosCount_ = count;
238 refPos_.reserve(count);
239 for (int i = 0; i < count; ++i)
241 refPos_.push_back(generateRandomPosition());
245 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
247 testPositions_.reserve(count);
248 for (int i = 0; i < count; ++i)
250 addTestPosition(generateRandomPosition());
254 void NeighborhoodSearchTestData::useRefPositionsAsTestPositions()
256 testPositions_.reserve(refPosCount_);
257 for (const auto& refPos : refPos_)
259 addTestPosition(refPos);
263 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc* pbc, bool bXY)
265 real cutoff = cutoff_;
268 cutoff = std::numeric_limits<real>::max();
270 for (TestPosition& testPos : testPositions_)
272 testPos.refMinDist = cutoff;
273 testPos.refNearestPoint = -1;
274 testPos.refPairs.clear();
275 for (int j = 0; j < refPosCount_; ++j)
280 pbc_dx(pbc, testPos.x, refPos_[j], dx);
284 rvec_sub(testPos.x, refPos_[j], dx);
286 // TODO: This may not work intuitively for 2D with the third box
287 // vector not parallel to the Z axis, but neither does the actual
288 // neighborhood search.
289 const real dist = !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
290 if (dist < testPos.refMinDist)
292 testPos.refMinDist = dist;
293 testPos.refNearestPoint = j;
295 if (dist > 0 && dist <= cutoff)
297 RefPair pair(j, dist);
298 GMX_RELEASE_ASSERT(testPos.refPairs.empty() || testPos.refPairs.back() < pair,
299 "Reference pairs should be generated in sorted order");
300 testPos.refPairs.push_back(pair);
306 /********************************************************************
310 class ExclusionsHelper
313 static void markExcludedPairs(RefPairList* refPairs, int testIndex, const gmx::ListOfLists<int>* excls);
315 ExclusionsHelper(int refPosCount, int testPosCount);
317 void generateExclusions();
319 const gmx::ListOfLists<int>* exclusions() const { return &excls_; }
321 gmx::ArrayRef<const int> refPosIds() const
323 return gmx::makeArrayRef(exclusionIds_).subArray(0, refPosCount_);
325 gmx::ArrayRef<const int> testPosIds() const
327 return gmx::makeArrayRef(exclusionIds_).subArray(0, testPosCount_);
333 std::vector<int> exclusionIds_;
334 gmx::ListOfLists<int> excls_;
338 void ExclusionsHelper::markExcludedPairs(RefPairList* refPairs, int testIndex, const gmx::ListOfLists<int>* excls)
341 for (const int excludedIndex : (*excls)[testIndex])
343 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
344 RefPairList::iterator excludedRefPair =
345 std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
346 if (excludedRefPair != refPairs->end() && excludedRefPair->refIndex == excludedIndex)
348 excludedRefPair->bFound = true;
349 excludedRefPair->bExcluded = true;
355 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount) :
356 refPosCount_(refPosCount),
357 testPosCount_(testPosCount)
359 // Generate an array of 0, 1, 2, ...
360 // TODO: Make the tests work also with non-trivial exclusion IDs,
362 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
363 exclusionIds_[0] = 0;
364 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(), exclusionIds_.begin());
367 void ExclusionsHelper::generateExclusions()
369 // TODO: Consider a better set of test data, where the density of the
370 // particles would be higher, or where the exclusions would not be random,
371 // to make a higher percentage of the exclusions to actually be within the
373 for (int i = 0; i < testPosCount_; ++i)
375 excls_.pushBackListOfSize(20);
376 gmx::ArrayRef<int> exclusionsForAtom = excls_.back();
377 for (int j = 0; j < 20; ++j)
379 exclusionsForAtom[j] = i + j * 3;
384 /********************************************************************
385 * NeighborhoodSearchTest
388 class NeighborhoodSearchTest : public ::testing::Test
391 void testIsWithin(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
392 void testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
393 const NeighborhoodSearchTestData& data);
394 void testNearestPoint(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
395 void testPairSearch(gmx::AnalysisNeighborhoodSearch* search, const NeighborhoodSearchTestData& data);
396 void testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
397 const NeighborhoodSearchTestData& data,
399 void testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
400 const NeighborhoodSearchTestData& data,
401 const gmx::AnalysisNeighborhoodPositions& pos,
402 const gmx::ListOfLists<int>* excls,
403 const gmx::ArrayRef<const int>& refIndices,
404 const gmx::ArrayRef<const int>& testIndices,
407 gmx::AnalysisNeighborhood nb_;
410 void NeighborhoodSearchTest::testIsWithin(gmx::AnalysisNeighborhoodSearch* search,
411 const NeighborhoodSearchTestData& data)
413 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
414 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
416 const bool bWithin = (i->refMinDist <= data.cutoff_);
417 EXPECT_EQ(bWithin, search->isWithin(i->x)) << "Distance is " << i->refMinDist;
421 void NeighborhoodSearchTest::testMinimumDistance(gmx::AnalysisNeighborhoodSearch* search,
422 const NeighborhoodSearchTestData& data)
424 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
426 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
428 const real refDist = i->refMinDist;
429 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
433 void NeighborhoodSearchTest::testNearestPoint(gmx::AnalysisNeighborhoodSearch* search,
434 const NeighborhoodSearchTestData& data)
436 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
437 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
439 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
442 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
443 EXPECT_EQ(0, pair.testIndex());
444 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
448 EXPECT_EQ(i->refNearestPoint, -1);
453 //! Helper function for formatting test failure messages.
454 std::string formatVector(const rvec x)
456 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
460 * Helper function to check that all expected pairs were found.
462 void checkAllPairsFound(const RefPairList& refPairs,
463 const std::vector<gmx::RVec>& refPos,
467 // This could be elegantly expressed with Google Mock matchers, but that
468 // has a significant effect on the runtime of the tests...
470 RefPairList::const_iterator first;
471 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
481 ADD_FAILURE() << "Some pairs (" << count << "/" << refPairs.size() << ") "
482 << "within the cutoff were not found. First pair:\n"
483 << " Ref: " << first->refIndex << " at "
484 << formatVector(refPos[first->refIndex]) << "\n"
485 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
486 << "Dist: " << first->distance;
490 void NeighborhoodSearchTest::testPairSearch(gmx::AnalysisNeighborhoodSearch* search,
491 const NeighborhoodSearchTestData& data)
493 testPairSearchFull(search, data, data.testPositions(), nullptr, {}, {}, false);
496 void NeighborhoodSearchTest::testPairSearchIndexed(gmx::AnalysisNeighborhood* nb,
497 const NeighborhoodSearchTestData& data,
500 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
501 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
502 gmx::AnalysisNeighborhoodSearch search =
503 nb->initSearch(&data.pbc_, data.refPositions().indexed(refIndices));
504 testPairSearchFull(&search, data, data.testPositions(), nullptr, refIndices, testIndices, false);
507 void NeighborhoodSearchTest::testPairSearchFull(gmx::AnalysisNeighborhoodSearch* search,
508 const NeighborhoodSearchTestData& data,
509 const gmx::AnalysisNeighborhoodPositions& pos,
510 const gmx::ListOfLists<int>* excls,
511 const gmx::ArrayRef<const int>& refIndices,
512 const gmx::ArrayRef<const int>& testIndices,
515 std::map<int, RefPairList> refPairs;
516 // TODO: Some parts of this code do not work properly if pos does not
517 // initially contain all the test positions.
518 if (testIndices.empty())
520 for (size_t i = 0; i < data.testPositions_.size(); ++i)
522 refPairs[i] = data.testPositions_[i].refPairs;
527 for (int index : testIndices)
529 refPairs[index] = data.testPositions_[index].refPairs;
532 if (excls != nullptr)
534 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with exclusions");
535 for (auto& entry : refPairs)
537 const int testIndex = entry.first;
538 ExclusionsHelper::markExcludedPairs(&entry.second, testIndex, excls);
541 if (!refIndices.empty())
543 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with indexing");
544 for (auto& entry : refPairs)
546 for (auto& refPair : entry.second)
548 refPair.bIndexed = false;
550 for (int index : refIndices)
552 NeighborhoodSearchTestData::RefPair searchPair(index, 0.0);
553 auto refPair = std::lower_bound(entry.second.begin(), entry.second.end(), searchPair);
554 if (refPair != entry.second.end() && refPair->refIndex == index)
556 refPair->bIndexed = true;
559 for (auto& refPair : entry.second)
561 if (!refPair.bIndexed)
563 refPair.bFound = true;
569 gmx::AnalysisNeighborhoodPositions posCopy(pos);
570 if (!testIndices.empty())
572 posCopy.indexed(testIndices);
574 gmx::AnalysisNeighborhoodPairSearch pairSearch =
575 selfPairs ? search->startSelfPairSearch() : search->startPairSearch(posCopy);
576 gmx::AnalysisNeighborhoodPair pair;
577 while (pairSearch.findNextPair(&pair))
579 const int testIndex = (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
580 const int refIndex = (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
582 if (refPairs.count(testIndex) == 0)
584 ADD_FAILURE() << "Expected: No pairs are returned for test position " << testIndex << ".\n"
585 << " Actual: Pair with ref " << refIndex << " is returned.";
588 NeighborhoodSearchTestData::RefPair searchPair(refIndex, std::sqrt(pair.distance2()));
589 const auto foundRefPair =
590 std::lower_bound(refPairs[testIndex].begin(), refPairs[testIndex].end(), searchPair);
591 if (foundRefPair == refPairs[testIndex].end() || foundRefPair->refIndex != refIndex)
593 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
594 << ") is not within the cutoff.\n"
595 << " Actual: It is returned.";
597 else if (foundRefPair->bExcluded)
599 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
600 << ") is excluded from the search.\n"
601 << " Actual: It is returned.";
603 else if (!foundRefPair->bIndexed)
605 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
606 << ") is not part of the indexed set.\n"
607 << " Actual: It is returned.";
609 else if (foundRefPair->bFound)
611 ADD_FAILURE() << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
612 << ") is returned only once.\n"
613 << " Actual: It is returned multiple times.";
618 foundRefPair->bFound = true;
620 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
621 << "Distance computed by the neighborhood search does not match.";
624 searchPair = NeighborhoodSearchTestData::RefPair(testIndex, 0.0);
625 const auto otherRefPair = std::lower_bound(refPairs[refIndex].begin(),
626 refPairs[refIndex].end(), searchPair);
627 GMX_RELEASE_ASSERT(otherRefPair != refPairs[refIndex].end(),
628 "Precomputed reference data is not symmetric");
629 otherRefPair->bFound = true;
634 for (auto& entry : refPairs)
636 const int testIndex = entry.first;
637 checkAllPairsFound(entry.second, data.refPos_, testIndex, data.testPositions_[testIndex].x);
641 /********************************************************************
642 * Test data generation
645 class TrivialTestData
648 static const NeighborhoodSearchTestData& get()
650 static TrivialTestData singleton;
651 return singleton.data_;
654 TrivialTestData() : data_(12345, 1.0)
656 // Make the box so small we are virtually guaranteed to have
657 // several neighbors for the five test positions
658 data_.box_[XX][XX] = 3.0;
659 data_.box_[YY][YY] = 3.0;
660 data_.box_[ZZ][ZZ] = 3.0;
661 data_.generateRandomRefPositions(10);
662 data_.generateRandomTestPositions(5);
663 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
664 data_.computeReferences(&data_.pbc_);
668 NeighborhoodSearchTestData data_;
671 class TrivialSelfPairsTestData
674 static const NeighborhoodSearchTestData& get()
676 static TrivialSelfPairsTestData singleton;
677 return singleton.data_;
680 TrivialSelfPairsTestData() : data_(12345, 1.0)
682 data_.box_[XX][XX] = 3.0;
683 data_.box_[YY][YY] = 3.0;
684 data_.box_[ZZ][ZZ] = 3.0;
685 data_.generateRandomRefPositions(20);
686 data_.useRefPositionsAsTestPositions();
687 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
688 data_.computeReferences(&data_.pbc_);
692 NeighborhoodSearchTestData data_;
695 class TrivialNoPBCTestData
698 static const NeighborhoodSearchTestData& get()
700 static TrivialNoPBCTestData singleton;
701 return singleton.data_;
704 TrivialNoPBCTestData() : data_(12345, 1.0)
706 data_.generateRandomRefPositions(10);
707 data_.generateRandomTestPositions(5);
708 data_.computeReferences(nullptr);
712 NeighborhoodSearchTestData data_;
715 class RandomBoxFullPBCData
718 static const NeighborhoodSearchTestData& get()
720 static RandomBoxFullPBCData singleton;
721 return singleton.data_;
724 RandomBoxFullPBCData() : data_(12345, 1.0)
726 data_.box_[XX][XX] = 10.0;
727 data_.box_[YY][YY] = 5.0;
728 data_.box_[ZZ][ZZ] = 7.0;
729 // TODO: Consider whether manually picking some positions would give better
731 data_.generateRandomRefPositions(1000);
732 data_.generateRandomTestPositions(100);
733 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
734 data_.computeReferences(&data_.pbc_);
738 NeighborhoodSearchTestData data_;
741 class RandomBoxSelfPairsData
744 static const NeighborhoodSearchTestData& get()
746 static RandomBoxSelfPairsData singleton;
747 return singleton.data_;
750 RandomBoxSelfPairsData() : data_(12345, 1.0)
752 data_.box_[XX][XX] = 10.0;
753 data_.box_[YY][YY] = 5.0;
754 data_.box_[ZZ][ZZ] = 7.0;
755 data_.generateRandomRefPositions(1000);
756 data_.useRefPositionsAsTestPositions();
757 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
758 data_.computeReferences(&data_.pbc_);
762 NeighborhoodSearchTestData data_;
765 class RandomBoxXYFullPBCData
768 static const NeighborhoodSearchTestData& get()
770 static RandomBoxXYFullPBCData singleton;
771 return singleton.data_;
774 RandomBoxXYFullPBCData() : data_(54321, 1.0)
776 data_.box_[XX][XX] = 10.0;
777 data_.box_[YY][YY] = 5.0;
778 data_.box_[ZZ][ZZ] = 7.0;
779 // TODO: Consider whether manually picking some positions would give better
781 data_.generateRandomRefPositions(1000);
782 data_.generateRandomTestPositions(100);
783 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
784 data_.computeReferencesXY(&data_.pbc_);
788 NeighborhoodSearchTestData data_;
791 class RandomTriclinicFullPBCData
794 static const NeighborhoodSearchTestData& get()
796 static RandomTriclinicFullPBCData singleton;
797 return singleton.data_;
800 RandomTriclinicFullPBCData() : data_(12345, 1.0)
802 data_.box_[XX][XX] = 5.0;
803 data_.box_[YY][XX] = 2.5;
804 data_.box_[YY][YY] = 2.5 * std::sqrt(3.0);
805 data_.box_[ZZ][XX] = 2.5;
806 data_.box_[ZZ][YY] = 2.5 * std::sqrt(1.0 / 3.0);
807 data_.box_[ZZ][ZZ] = 5.0 * std::sqrt(2.0 / 3.0);
808 // TODO: Consider whether manually picking some positions would give better
810 data_.generateRandomRefPositions(1000);
811 data_.generateRandomTestPositions(100);
812 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
813 data_.computeReferences(&data_.pbc_);
817 NeighborhoodSearchTestData data_;
820 class RandomBox2DPBCData
823 static const NeighborhoodSearchTestData& get()
825 static RandomBox2DPBCData singleton;
826 return singleton.data_;
829 RandomBox2DPBCData() : data_(12345, 1.0)
831 data_.box_[XX][XX] = 10.0;
832 data_.box_[YY][YY] = 7.0;
833 data_.box_[ZZ][ZZ] = 5.0;
834 // TODO: Consider whether manually picking some positions would give better
836 data_.generateRandomRefPositions(1000);
837 data_.generateRandomTestPositions(100);
838 set_pbc(&data_.pbc_, epbcXY, data_.box_);
839 data_.computeReferences(&data_.pbc_);
843 NeighborhoodSearchTestData data_;
846 class RandomBoxNoPBCData
849 static const NeighborhoodSearchTestData& get()
851 static RandomBoxNoPBCData singleton;
852 return singleton.data_;
855 RandomBoxNoPBCData() : data_(12345, 1.0)
857 data_.box_[XX][XX] = 10.0;
858 data_.box_[YY][YY] = 5.0;
859 data_.box_[ZZ][ZZ] = 7.0;
860 // TODO: Consider whether manually picking some positions would give better
862 data_.generateRandomRefPositions(1000);
863 data_.generateRandomTestPositions(100);
864 set_pbc(&data_.pbc_, epbcNONE, data_.box_);
865 data_.computeReferences(nullptr);
869 NeighborhoodSearchTestData data_;
872 /********************************************************************
876 TEST_F(NeighborhoodSearchTest, SimpleSearch)
878 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
880 nb_.setCutoff(data.cutoff_);
881 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
882 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
883 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
885 testIsWithin(&search, data);
886 testMinimumDistance(&search, data);
887 testNearestPoint(&search, data);
888 testPairSearch(&search, data);
891 testPairSearchIndexed(&nb_, data, 123);
894 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
896 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
898 nb_.setCutoff(data.cutoff_);
899 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
901 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
902 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
904 testIsWithin(&search, data);
905 testMinimumDistance(&search, data);
906 testNearestPoint(&search, data);
907 testPairSearch(&search, data);
910 TEST_F(NeighborhoodSearchTest, GridSearchBox)
912 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
914 nb_.setCutoff(data.cutoff_);
915 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
916 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
917 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
919 testIsWithin(&search, data);
920 testMinimumDistance(&search, data);
921 testNearestPoint(&search, data);
922 testPairSearch(&search, data);
925 testPairSearchIndexed(&nb_, data, 456);
928 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
930 const NeighborhoodSearchTestData& data = RandomTriclinicFullPBCData::get();
932 nb_.setCutoff(data.cutoff_);
933 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
934 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
935 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
937 testPairSearch(&search, data);
940 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
942 const NeighborhoodSearchTestData& data = RandomBox2DPBCData::get();
944 nb_.setCutoff(data.cutoff_);
945 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
946 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
947 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
949 testIsWithin(&search, data);
950 testMinimumDistance(&search, data);
951 testNearestPoint(&search, data);
952 testPairSearch(&search, data);
955 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
957 const NeighborhoodSearchTestData& data = RandomBoxNoPBCData::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 testPairSearch(&search, data);
967 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
969 const NeighborhoodSearchTestData& data = RandomBoxXYFullPBCData::get();
971 nb_.setCutoff(data.cutoff_);
972 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
974 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
975 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
977 testIsWithin(&search, data);
978 testMinimumDistance(&search, data);
979 testNearestPoint(&search, data);
980 testPairSearch(&search, data);
983 TEST_F(NeighborhoodSearchTest, SimpleSelfPairsSearch)
985 const NeighborhoodSearchTestData& data = TrivialSelfPairsTestData::get();
987 nb_.setCutoff(data.cutoff_);
988 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
989 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
990 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
992 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
995 TEST_F(NeighborhoodSearchTest, GridSelfPairsSearch)
997 const NeighborhoodSearchTestData& data = RandomBoxSelfPairsData::get();
999 nb_.setCutoff(data.cutoff_);
1000 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1001 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1002 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1004 testPairSearchFull(&search, data, data.testPositions(), nullptr, {}, {}, true);
1007 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1009 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1011 nb_.setCutoff(data.cutoff_);
1012 gmx::AnalysisNeighborhoodSearch search1 = nb_.initSearch(&data.pbc_, data.refPositions());
1013 gmx::AnalysisNeighborhoodSearch search2 = nb_.initSearch(&data.pbc_, data.refPositions());
1015 // These checks are fragile, and unfortunately depend on the random
1016 // engine used to create the test positions. There is no particular reason
1017 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1018 gmx::AnalysisNeighborhoodPairSearch pairSearch1 = search1.startPairSearch(data.testPosition(0));
1019 gmx::AnalysisNeighborhoodPairSearch pairSearch2 = search1.startPairSearch(data.testPosition(2));
1021 testPairSearch(&search2, data);
1023 gmx::AnalysisNeighborhoodPair pair;
1024 ASSERT_TRUE(pairSearch1.findNextPair(&pair))
1025 << "Test data did not contain any pairs for position 0 (problem in the test).";
1026 EXPECT_EQ(0, pair.testIndex());
1028 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1029 EXPECT_TRUE(data.containsPair(0, searchPair));
1032 ASSERT_TRUE(pairSearch2.findNextPair(&pair))
1033 << "Test data did not contain any pairs for position 2 (problem in the test).";
1034 EXPECT_EQ(2, pair.testIndex());
1036 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1037 EXPECT_TRUE(data.containsPair(2, searchPair));
1041 TEST_F(NeighborhoodSearchTest, HandlesNoPBC)
1043 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1045 nb_.setCutoff(data.cutoff_);
1046 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1047 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1049 testIsWithin(&search, data);
1050 testMinimumDistance(&search, data);
1051 testNearestPoint(&search, data);
1052 testPairSearch(&search, data);
1055 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1057 const NeighborhoodSearchTestData& data = TrivialNoPBCTestData::get();
1059 nb_.setCutoff(data.cutoff_);
1060 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(nullptr, data.refPositions());
1061 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1063 testIsWithin(&search, data);
1064 testMinimumDistance(&search, data);
1065 testNearestPoint(&search, data);
1066 testPairSearch(&search, data);
1069 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1071 const NeighborhoodSearchTestData& data = TrivialTestData::get();
1073 nb_.setCutoff(data.cutoff_);
1074 gmx::AnalysisNeighborhoodSearch search = nb_.initSearch(&data.pbc_, data.refPositions());
1075 gmx::AnalysisNeighborhoodPairSearch pairSearch = search.startPairSearch(data.testPositions());
1076 gmx::AnalysisNeighborhoodPair pair;
1077 // TODO: This test needs to be adjusted if the grid search gets optimized
1078 // to loop over the test positions in cell order (first, the ordering
1079 // assumption here breaks, and second, it then needs to be tested
1080 // separately for simple and grid searches).
1081 int currentIndex = 0;
1082 while (pairSearch.findNextPair(&pair))
1084 while (currentIndex < pair.testIndex())
1088 EXPECT_EQ(currentIndex, pair.testIndex());
1089 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1090 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1091 pairSearch.skipRemainingPairsForTestPosition();
1096 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1098 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1100 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1101 helper.generateExclusions();
1103 nb_.setCutoff(data.cutoff_);
1104 nb_.setTopologyExclusions(helper.exclusions());
1105 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1106 gmx::AnalysisNeighborhoodSearch search =
1107 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1108 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1110 testPairSearchFull(&search, data, data.testPositions().exclusionIds(helper.testPosIds()),
1111 helper.exclusions(), {}, {}, false);
1114 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1116 const NeighborhoodSearchTestData& data = RandomBoxFullPBCData::get();
1118 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1119 helper.generateExclusions();
1121 nb_.setCutoff(data.cutoff_);
1122 nb_.setTopologyExclusions(helper.exclusions());
1123 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1124 gmx::AnalysisNeighborhoodSearch search =
1125 nb_.initSearch(&data.pbc_, data.refPositions().exclusionIds(helper.refPosIds()));
1126 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1128 testPairSearchFull(&search, data, data.testPositions().exclusionIds(helper.testPosIds()),
1129 helper.exclusions(), {}, {}, false);