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)
86 : refIndex(refIndex), distance(distance), bFound(false),
87 bExcluded(false), bIndexed(true)
91 bool operator<(const RefPair &other) const
93 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)
110 : 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(),
149 "Cannot add positions after testPositions() call");
150 testPositions_.emplace_back(x);
152 gmx::RVec generateRandomPosition();
153 std::vector<int> generateIndex(int count, uint64_t seed) const;
154 void generateRandomRefPositions(int count);
155 void generateRandomTestPositions(int count);
156 void useRefPositionsAsTestPositions();
157 void computeReferences(t_pbc *pbc)
159 computeReferencesInternal(pbc, false);
161 void computeReferencesXY(t_pbc *pbc)
163 computeReferencesInternal(pbc, true);
166 bool containsPair(int testIndex, const RefPair &pair) const
168 const std::vector<RefPair> &refPairs = testPositions_[testIndex].refPairs;
169 std::vector<RefPair>::const_iterator foundRefPair
170 = std::lower_bound(refPairs.begin(), refPairs.end(), pair);
171 return !(foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex);
174 // Return a tolerance that accounts for the magnitudes of the coordinates
175 // when doing subtractions, so that we set the ULP tolerance relative to the
176 // coordinate values rather than their difference.
177 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
178 // to 10.0, but a much larger error relative to the difference.
179 gmx::test::FloatingPointTolerance relativeTolerance() const
181 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
182 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
185 gmx::DefaultRandomEngine rng_;
190 std::vector<gmx::RVec> refPos_;
191 TestPositionList testPositions_;
194 void computeReferencesInternal(t_pbc *pbc, bool bXY);
196 mutable std::vector<gmx::RVec> testPos_;
199 //! Shorthand for a collection of reference pairs.
200 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
202 NeighborhoodSearchTestData::NeighborhoodSearchTestData(uint64_t seed, real cutoff)
203 : rng_(seed), cutoff_(cutoff), refPosCount_(0)
206 set_pbc(&pbc_, epbcNONE, box_);
209 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
211 gmx::UniformRealDistribution<real> dist;
217 // Add a small displacement to allow positions outside the box
218 x[XX] += 0.2 * dist(rng_) - 0.1;
219 x[YY] += 0.2 * dist(rng_) - 0.1;
220 x[ZZ] += 0.2 * dist(rng_) - 0.1;
224 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, uint64_t seed) const
226 gmx::DefaultRandomEngine rngIndex(seed);
227 gmx::UniformRealDistribution<real> dist;
228 std::vector<int> result;
230 for (int i = 0; i < count; ++i)
232 if (dist(rngIndex) > 0.5)
240 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
242 refPosCount_ = count;
243 refPos_.reserve(count);
244 for (int i = 0; i < count; ++i)
246 refPos_.push_back(generateRandomPosition());
250 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
252 testPositions_.reserve(count);
253 for (int i = 0; i < count; ++i)
255 addTestPosition(generateRandomPosition());
259 void NeighborhoodSearchTestData::useRefPositionsAsTestPositions()
261 testPositions_.reserve(refPosCount_);
262 for (const auto &refPos : refPos_)
264 addTestPosition(refPos);
268 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc *pbc, bool bXY)
270 real cutoff = cutoff_;
273 cutoff = std::numeric_limits<real>::max();
275 for (TestPosition &testPos : testPositions_)
277 testPos.refMinDist = cutoff;
278 testPos.refNearestPoint = -1;
279 testPos.refPairs.clear();
280 for (int j = 0; j < refPosCount_; ++j)
285 pbc_dx(pbc, testPos.x, refPos_[j], dx);
289 rvec_sub(testPos.x, refPos_[j], dx);
291 // TODO: This may not work intuitively for 2D with the third box
292 // vector not parallel to the Z axis, but neither does the actual
293 // neighborhood search.
295 !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
296 if (dist < testPos.refMinDist)
298 testPos.refMinDist = dist;
299 testPos.refNearestPoint = j;
301 if (dist > 0 && dist <= cutoff)
303 RefPair pair(j, dist);
304 GMX_RELEASE_ASSERT(testPos.refPairs.empty() || testPos.refPairs.back() < pair,
305 "Reference pairs should be generated in sorted order");
306 testPos.refPairs.push_back(pair);
312 /********************************************************************
316 class ExclusionsHelper
319 static void markExcludedPairs(RefPairList *refPairs, int testIndex,
320 const t_blocka *excls);
322 ExclusionsHelper(int refPosCount, int testPosCount);
324 void generateExclusions();
326 const t_blocka *exclusions() const { return &excls_; }
328 gmx::ArrayRef<const int> refPosIds() const
330 return gmx::makeArrayRef(exclusionIds_).subArray(0, refPosCount_);
332 gmx::ArrayRef<const int> testPosIds() const
334 return gmx::makeArrayRef(exclusionIds_).subArray(0, testPosCount_);
340 std::vector<int> exclusionIds_;
341 std::vector<int> exclsIndex_;
342 std::vector<int> exclsAtoms_;
347 void ExclusionsHelper::markExcludedPairs(RefPairList *refPairs, int testIndex,
348 const t_blocka *excls)
351 for (int i = excls->index[testIndex]; i < excls->index[testIndex + 1]; ++i)
353 const int excludedIndex = excls->a[i];
354 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
355 RefPairList::iterator excludedRefPair
356 = std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
357 if (excludedRefPair != refPairs->end()
358 && excludedRefPair->refIndex == excludedIndex)
360 excludedRefPair->bFound = true;
361 excludedRefPair->bExcluded = true;
367 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount)
368 : refPosCount_(refPosCount), testPosCount_(testPosCount)
370 // Generate an array of 0, 1, 2, ...
371 // TODO: Make the tests work also with non-trivial exclusion IDs,
373 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
374 exclusionIds_[0] = 0;
375 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(),
376 exclusionIds_.begin());
379 excls_.index = nullptr;
382 excls_.nalloc_index = 0;
386 void ExclusionsHelper::generateExclusions()
388 // TODO: Consider a better set of test data, where the density of the
389 // particles would be higher, or where the exclusions would not be random,
390 // to make a higher percentage of the exclusions to actually be within the
392 exclsIndex_.reserve(testPosCount_ + 1);
393 exclsAtoms_.reserve(testPosCount_ * 20);
394 exclsIndex_.push_back(0);
395 for (int i = 0; i < testPosCount_; ++i)
397 for (int j = 0; j < 20; ++j)
399 exclsAtoms_.push_back(i + j*3);
401 exclsIndex_.push_back(exclsAtoms_.size());
403 excls_.nr = exclsIndex_.size();
404 excls_.index = exclsIndex_.data();
405 excls_.nra = exclsAtoms_.size();
406 excls_.a = exclsAtoms_.data();
409 /********************************************************************
410 * NeighborhoodSearchTest
413 class NeighborhoodSearchTest : public ::testing::Test
416 void testIsWithin(gmx::AnalysisNeighborhoodSearch *search,
417 const NeighborhoodSearchTestData &data);
418 void testMinimumDistance(gmx::AnalysisNeighborhoodSearch *search,
419 const NeighborhoodSearchTestData &data);
420 void testNearestPoint(gmx::AnalysisNeighborhoodSearch *search,
421 const NeighborhoodSearchTestData &data);
422 void testPairSearch(gmx::AnalysisNeighborhoodSearch *search,
423 const NeighborhoodSearchTestData &data);
424 void testPairSearchIndexed(gmx::AnalysisNeighborhood *nb,
425 const NeighborhoodSearchTestData &data,
427 void testPairSearchFull(gmx::AnalysisNeighborhoodSearch *search,
428 const NeighborhoodSearchTestData &data,
429 const gmx::AnalysisNeighborhoodPositions &pos,
430 const t_blocka *excls,
431 const gmx::ArrayRef<const int> &refIndices,
432 const gmx::ArrayRef<const int> &testIndices,
435 gmx::AnalysisNeighborhood nb_;
438 void NeighborhoodSearchTest::testIsWithin(
439 gmx::AnalysisNeighborhoodSearch *search,
440 const NeighborhoodSearchTestData &data)
442 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
443 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
445 const bool bWithin = (i->refMinDist <= data.cutoff_);
446 EXPECT_EQ(bWithin, search->isWithin(i->x))
447 << "Distance is " << i->refMinDist;
451 void NeighborhoodSearchTest::testMinimumDistance(
452 gmx::AnalysisNeighborhoodSearch *search,
453 const NeighborhoodSearchTestData &data)
455 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
457 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
459 const real refDist = i->refMinDist;
460 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
464 void NeighborhoodSearchTest::testNearestPoint(
465 gmx::AnalysisNeighborhoodSearch *search,
466 const NeighborhoodSearchTestData &data)
468 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
469 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
471 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
474 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
475 EXPECT_EQ(0, pair.testIndex());
476 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
480 EXPECT_EQ(i->refNearestPoint, -1);
485 //! Helper function for formatting test failure messages.
486 std::string formatVector(const rvec x)
488 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
492 * Helper function to check that all expected pairs were found.
494 void checkAllPairsFound(const RefPairList &refPairs,
495 const std::vector<gmx::RVec> &refPos,
496 int testPosIndex, const rvec testPos)
498 // This could be elegantly expressed with Google Mock matchers, but that
499 // has a significant effect on the runtime of the tests...
501 RefPairList::const_iterator first;
502 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
513 << "Some pairs (" << count << "/" << refPairs.size() << ") "
514 << "within the cutoff were not found. First pair:\n"
515 << " Ref: " << first->refIndex << " at "
516 << formatVector(refPos[first->refIndex]) << "\n"
517 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
518 << "Dist: " << first->distance;
522 void NeighborhoodSearchTest::testPairSearch(
523 gmx::AnalysisNeighborhoodSearch *search,
524 const NeighborhoodSearchTestData &data)
526 testPairSearchFull(search, data, data.testPositions(), nullptr,
530 void NeighborhoodSearchTest::testPairSearchIndexed(
531 gmx::AnalysisNeighborhood *nb,
532 const NeighborhoodSearchTestData &data,
535 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
536 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
537 gmx::AnalysisNeighborhoodSearch search =
538 nb->initSearch(&data.pbc_,
539 data.refPositions().indexed(refIndices));
540 testPairSearchFull(&search, data, data.testPositions(), nullptr,
541 refIndices, testIndices, false);
544 void NeighborhoodSearchTest::testPairSearchFull(
545 gmx::AnalysisNeighborhoodSearch *search,
546 const NeighborhoodSearchTestData &data,
547 const gmx::AnalysisNeighborhoodPositions &pos,
548 const t_blocka *excls,
549 const gmx::ArrayRef<const int> &refIndices,
550 const gmx::ArrayRef<const int> &testIndices,
553 std::map<int, RefPairList> refPairs;
554 // TODO: Some parts of this code do not work properly if pos does not
555 // initially contain all the test positions.
556 if (testIndices.empty())
558 for (size_t i = 0; i < data.testPositions_.size(); ++i)
560 refPairs[i] = data.testPositions_[i].refPairs;
565 for (int index : testIndices)
567 refPairs[index] = data.testPositions_[index].refPairs;
570 if (excls != nullptr)
572 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with exclusions");
573 for (auto &entry : refPairs)
575 const int testIndex = entry.first;
576 ExclusionsHelper::markExcludedPairs(&entry.second, testIndex, excls);
579 if (!refIndices.empty())
581 GMX_RELEASE_ASSERT(!selfPairs, "Self-pairs testing not implemented with indexing");
582 for (auto &entry : refPairs)
584 for (auto &refPair : entry.second)
586 refPair.bIndexed = false;
588 for (int index : refIndices)
590 NeighborhoodSearchTestData::RefPair searchPair(index, 0.0);
591 auto refPair = std::lower_bound(entry.second.begin(), entry.second.end(), searchPair);
592 if (refPair != entry.second.end() && refPair->refIndex == index)
594 refPair->bIndexed = true;
597 for (auto &refPair : entry.second)
599 if (!refPair.bIndexed)
601 refPair.bFound = true;
607 gmx::AnalysisNeighborhoodPositions posCopy(pos);
608 if (!testIndices.empty())
610 posCopy.indexed(testIndices);
612 gmx::AnalysisNeighborhoodPairSearch pairSearch
614 ? search->startSelfPairSearch()
615 : search->startPairSearch(posCopy);
616 gmx::AnalysisNeighborhoodPair pair;
617 while (pairSearch.findNextPair(&pair))
619 const int testIndex =
620 (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
622 (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
624 if (refPairs.count(testIndex) == 0)
627 << "Expected: No pairs are returned for test position " << testIndex << ".\n"
628 << " Actual: Pair with ref " << refIndex << " is returned.";
631 NeighborhoodSearchTestData::RefPair searchPair(refIndex,
632 std::sqrt(pair.distance2()));
633 const auto foundRefPair
634 = std::lower_bound(refPairs[testIndex].begin(), refPairs[testIndex].end(),
636 if (foundRefPair == refPairs[testIndex].end() || foundRefPair->refIndex != refIndex)
639 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
640 << ") is not within the cutoff.\n"
641 << " Actual: It is returned.";
643 else if (foundRefPair->bExcluded)
646 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
647 << ") is excluded from the search.\n"
648 << " Actual: It is returned.";
650 else if (!foundRefPair->bIndexed)
653 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
654 << ") is not part of the indexed set.\n"
655 << " Actual: It is returned.";
657 else if (foundRefPair->bFound)
660 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
661 << ") is returned only once.\n"
662 << " Actual: It is returned multiple times.";
667 foundRefPair->bFound = true;
669 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
670 << "Distance computed by the neighborhood search does not match.";
673 searchPair = NeighborhoodSearchTestData::RefPair(testIndex, 0.0);
674 const auto otherRefPair
675 = std::lower_bound(refPairs[refIndex].begin(), refPairs[refIndex].end(),
677 GMX_RELEASE_ASSERT(otherRefPair != refPairs[refIndex].end(),
678 "Precomputed reference data is not symmetric");
679 otherRefPair->bFound = true;
684 for (auto &entry : refPairs)
686 const int testIndex = entry.first;
687 checkAllPairsFound(entry.second, data.refPos_, testIndex,
688 data.testPositions_[testIndex].x);
692 /********************************************************************
693 * Test data generation
696 class TrivialTestData
699 static const NeighborhoodSearchTestData &get()
701 static TrivialTestData singleton;
702 return singleton.data_;
705 TrivialTestData() : data_(12345, 1.0)
707 // Make the box so small we are virtually guaranteed to have
708 // several neighbors for the five test positions
709 data_.box_[XX][XX] = 3.0;
710 data_.box_[YY][YY] = 3.0;
711 data_.box_[ZZ][ZZ] = 3.0;
712 data_.generateRandomRefPositions(10);
713 data_.generateRandomTestPositions(5);
714 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
715 data_.computeReferences(&data_.pbc_);
719 NeighborhoodSearchTestData data_;
722 class TrivialSelfPairsTestData
725 static const NeighborhoodSearchTestData &get()
727 static TrivialSelfPairsTestData singleton;
728 return singleton.data_;
731 TrivialSelfPairsTestData() : data_(12345, 1.0)
733 data_.box_[XX][XX] = 3.0;
734 data_.box_[YY][YY] = 3.0;
735 data_.box_[ZZ][ZZ] = 3.0;
736 data_.generateRandomRefPositions(20);
737 data_.useRefPositionsAsTestPositions();
738 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
739 data_.computeReferences(&data_.pbc_);
743 NeighborhoodSearchTestData data_;
746 class TrivialNoPBCTestData
749 static const NeighborhoodSearchTestData &get()
751 static TrivialNoPBCTestData singleton;
752 return singleton.data_;
755 TrivialNoPBCTestData() : data_(12345, 1.0)
757 data_.generateRandomRefPositions(10);
758 data_.generateRandomTestPositions(5);
759 data_.computeReferences(nullptr);
763 NeighborhoodSearchTestData data_;
766 class RandomBoxFullPBCData
769 static const NeighborhoodSearchTestData &get()
771 static RandomBoxFullPBCData singleton;
772 return singleton.data_;
775 RandomBoxFullPBCData() : data_(12345, 1.0)
777 data_.box_[XX][XX] = 10.0;
778 data_.box_[YY][YY] = 5.0;
779 data_.box_[ZZ][ZZ] = 7.0;
780 // TODO: Consider whether manually picking some positions would give better
782 data_.generateRandomRefPositions(1000);
783 data_.generateRandomTestPositions(100);
784 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
785 data_.computeReferences(&data_.pbc_);
789 NeighborhoodSearchTestData data_;
792 class RandomBoxSelfPairsData
795 static const NeighborhoodSearchTestData &get()
797 static RandomBoxSelfPairsData singleton;
798 return singleton.data_;
801 RandomBoxSelfPairsData() : data_(12345, 1.0)
803 data_.box_[XX][XX] = 10.0;
804 data_.box_[YY][YY] = 5.0;
805 data_.box_[ZZ][ZZ] = 7.0;
806 data_.generateRandomRefPositions(1000);
807 data_.useRefPositionsAsTestPositions();
808 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
809 data_.computeReferences(&data_.pbc_);
813 NeighborhoodSearchTestData data_;
816 class RandomBoxXYFullPBCData
819 static const NeighborhoodSearchTestData &get()
821 static RandomBoxXYFullPBCData singleton;
822 return singleton.data_;
825 RandomBoxXYFullPBCData() : data_(54321, 1.0)
827 data_.box_[XX][XX] = 10.0;
828 data_.box_[YY][YY] = 5.0;
829 data_.box_[ZZ][ZZ] = 7.0;
830 // TODO: Consider whether manually picking some positions would give better
832 data_.generateRandomRefPositions(1000);
833 data_.generateRandomTestPositions(100);
834 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
835 data_.computeReferencesXY(&data_.pbc_);
839 NeighborhoodSearchTestData data_;
842 class RandomTriclinicFullPBCData
845 static const NeighborhoodSearchTestData &get()
847 static RandomTriclinicFullPBCData singleton;
848 return singleton.data_;
851 RandomTriclinicFullPBCData() : data_(12345, 1.0)
853 data_.box_[XX][XX] = 5.0;
854 data_.box_[YY][XX] = 2.5;
855 data_.box_[YY][YY] = 2.5*std::sqrt(3.0);
856 data_.box_[ZZ][XX] = 2.5;
857 data_.box_[ZZ][YY] = 2.5*std::sqrt(1.0/3.0);
858 data_.box_[ZZ][ZZ] = 5.0*std::sqrt(2.0/3.0);
859 // TODO: Consider whether manually picking some positions would give better
861 data_.generateRandomRefPositions(1000);
862 data_.generateRandomTestPositions(100);
863 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
864 data_.computeReferences(&data_.pbc_);
868 NeighborhoodSearchTestData data_;
871 class RandomBox2DPBCData
874 static const NeighborhoodSearchTestData &get()
876 static RandomBox2DPBCData singleton;
877 return singleton.data_;
880 RandomBox2DPBCData() : data_(12345, 1.0)
882 data_.box_[XX][XX] = 10.0;
883 data_.box_[YY][YY] = 7.0;
884 data_.box_[ZZ][ZZ] = 5.0;
885 // TODO: Consider whether manually picking some positions would give better
887 data_.generateRandomRefPositions(1000);
888 data_.generateRandomTestPositions(100);
889 set_pbc(&data_.pbc_, epbcXY, data_.box_);
890 data_.computeReferences(&data_.pbc_);
894 NeighborhoodSearchTestData data_;
897 class RandomBoxNoPBCData
900 static const NeighborhoodSearchTestData &get()
902 static RandomBoxNoPBCData singleton;
903 return singleton.data_;
906 RandomBoxNoPBCData() : data_(12345, 1.0)
908 data_.box_[XX][XX] = 10.0;
909 data_.box_[YY][YY] = 5.0;
910 data_.box_[ZZ][ZZ] = 7.0;
911 // TODO: Consider whether manually picking some positions would give better
913 data_.generateRandomRefPositions(1000);
914 data_.generateRandomTestPositions(100);
915 set_pbc(&data_.pbc_, epbcNONE, data_.box_);
916 data_.computeReferences(nullptr);
920 NeighborhoodSearchTestData data_;
923 /********************************************************************
927 TEST_F(NeighborhoodSearchTest, SimpleSearch)
929 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
931 nb_.setCutoff(data.cutoff_);
932 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
933 gmx::AnalysisNeighborhoodSearch search =
934 nb_.initSearch(&data.pbc_, data.refPositions());
935 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
937 testIsWithin(&search, data);
938 testMinimumDistance(&search, data);
939 testNearestPoint(&search, data);
940 testPairSearch(&search, data);
943 testPairSearchIndexed(&nb_, data, 123);
946 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
948 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
950 nb_.setCutoff(data.cutoff_);
951 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
953 gmx::AnalysisNeighborhoodSearch search =
954 nb_.initSearch(&data.pbc_, data.refPositions());
955 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
957 testIsWithin(&search, data);
958 testMinimumDistance(&search, data);
959 testNearestPoint(&search, data);
960 testPairSearch(&search, data);
963 TEST_F(NeighborhoodSearchTest, GridSearchBox)
965 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
967 nb_.setCutoff(data.cutoff_);
968 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
969 gmx::AnalysisNeighborhoodSearch search =
970 nb_.initSearch(&data.pbc_, data.refPositions());
971 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
973 testIsWithin(&search, data);
974 testMinimumDistance(&search, data);
975 testNearestPoint(&search, data);
976 testPairSearch(&search, data);
979 testPairSearchIndexed(&nb_, data, 456);
982 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
984 const NeighborhoodSearchTestData &data = RandomTriclinicFullPBCData::get();
986 nb_.setCutoff(data.cutoff_);
987 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
988 gmx::AnalysisNeighborhoodSearch search =
989 nb_.initSearch(&data.pbc_, data.refPositions());
990 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
992 testPairSearch(&search, data);
995 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
997 const NeighborhoodSearchTestData &data = RandomBox2DPBCData::get();
999 nb_.setCutoff(data.cutoff_);
1000 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1001 gmx::AnalysisNeighborhoodSearch search =
1002 nb_.initSearch(&data.pbc_, data.refPositions());
1003 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1005 testIsWithin(&search, data);
1006 testMinimumDistance(&search, data);
1007 testNearestPoint(&search, data);
1008 testPairSearch(&search, data);
1011 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
1013 const NeighborhoodSearchTestData &data = RandomBoxNoPBCData::get();
1015 nb_.setCutoff(data.cutoff_);
1016 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1017 gmx::AnalysisNeighborhoodSearch search =
1018 nb_.initSearch(&data.pbc_, data.refPositions());
1019 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1021 testPairSearch(&search, data);
1024 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
1026 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
1028 nb_.setCutoff(data.cutoff_);
1029 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1030 nb_.setXYMode(true);
1031 gmx::AnalysisNeighborhoodSearch search =
1032 nb_.initSearch(&data.pbc_, data.refPositions());
1033 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1035 testIsWithin(&search, data);
1036 testMinimumDistance(&search, data);
1037 testNearestPoint(&search, data);
1038 testPairSearch(&search, data);
1041 TEST_F(NeighborhoodSearchTest, SimpleSelfPairsSearch)
1043 const NeighborhoodSearchTestData &data = TrivialSelfPairsTestData::get();
1045 nb_.setCutoff(data.cutoff_);
1046 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1047 gmx::AnalysisNeighborhoodSearch search =
1048 nb_.initSearch(&data.pbc_, data.refPositions());
1049 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1051 testPairSearchFull(&search, data, data.testPositions(), nullptr,
1055 TEST_F(NeighborhoodSearchTest, GridSelfPairsSearch)
1057 const NeighborhoodSearchTestData &data = RandomBoxSelfPairsData::get();
1059 nb_.setCutoff(data.cutoff_);
1060 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1061 gmx::AnalysisNeighborhoodSearch search =
1062 nb_.initSearch(&data.pbc_, data.refPositions());
1063 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1065 testPairSearchFull(&search, data, data.testPositions(), nullptr,
1069 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1071 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1073 nb_.setCutoff(data.cutoff_);
1074 gmx::AnalysisNeighborhoodSearch search1 =
1075 nb_.initSearch(&data.pbc_, data.refPositions());
1076 gmx::AnalysisNeighborhoodSearch search2 =
1077 nb_.initSearch(&data.pbc_, data.refPositions());
1079 // These checks are fragile, and unfortunately depend on the random
1080 // engine used to create the test positions. There is no particular reason
1081 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1082 gmx::AnalysisNeighborhoodPairSearch pairSearch1 =
1083 search1.startPairSearch(data.testPosition(0));
1084 gmx::AnalysisNeighborhoodPairSearch pairSearch2 =
1085 search1.startPairSearch(data.testPosition(2));
1087 testPairSearch(&search2, data);
1089 gmx::AnalysisNeighborhoodPair pair;
1090 ASSERT_TRUE(pairSearch1.findNextPair(&pair))
1091 << "Test data did not contain any pairs for position 0 (problem in the test).";
1092 EXPECT_EQ(0, pair.testIndex());
1094 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1095 EXPECT_TRUE(data.containsPair(0, searchPair));
1098 ASSERT_TRUE(pairSearch2.findNextPair(&pair))
1099 << "Test data did not contain any pairs for position 2 (problem in the test).";
1100 EXPECT_EQ(2, pair.testIndex());
1102 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1103 EXPECT_TRUE(data.containsPair(2, searchPair));
1107 TEST_F(NeighborhoodSearchTest, HandlesNoPBC)
1109 const NeighborhoodSearchTestData &data = TrivialNoPBCTestData::get();
1111 nb_.setCutoff(data.cutoff_);
1112 gmx::AnalysisNeighborhoodSearch search =
1113 nb_.initSearch(&data.pbc_, data.refPositions());
1114 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1116 testIsWithin(&search, data);
1117 testMinimumDistance(&search, data);
1118 testNearestPoint(&search, data);
1119 testPairSearch(&search, data);
1122 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1124 const NeighborhoodSearchTestData &data = TrivialNoPBCTestData::get();
1126 nb_.setCutoff(data.cutoff_);
1127 gmx::AnalysisNeighborhoodSearch search =
1128 nb_.initSearch(nullptr, data.refPositions());
1129 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1131 testIsWithin(&search, data);
1132 testMinimumDistance(&search, data);
1133 testNearestPoint(&search, data);
1134 testPairSearch(&search, data);
1137 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1139 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1141 nb_.setCutoff(data.cutoff_);
1142 gmx::AnalysisNeighborhoodSearch search =
1143 nb_.initSearch(&data.pbc_, data.refPositions());
1144 gmx::AnalysisNeighborhoodPairSearch pairSearch =
1145 search.startPairSearch(data.testPositions());
1146 gmx::AnalysisNeighborhoodPair pair;
1147 // TODO: This test needs to be adjusted if the grid search gets optimized
1148 // to loop over the test positions in cell order (first, the ordering
1149 // assumption here breaks, and second, it then needs to be tested
1150 // separately for simple and grid searches).
1151 int currentIndex = 0;
1152 while (pairSearch.findNextPair(&pair))
1154 while (currentIndex < pair.testIndex())
1158 EXPECT_EQ(currentIndex, pair.testIndex());
1159 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1160 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1161 pairSearch.skipRemainingPairsForTestPosition();
1166 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1168 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1170 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1171 helper.generateExclusions();
1173 nb_.setCutoff(data.cutoff_);
1174 nb_.setTopologyExclusions(helper.exclusions());
1175 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1176 gmx::AnalysisNeighborhoodSearch search =
1177 nb_.initSearch(&data.pbc_,
1178 data.refPositions().exclusionIds(helper.refPosIds()));
1179 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1181 testPairSearchFull(&search, data,
1182 data.testPositions().exclusionIds(helper.testPosIds()),
1183 helper.exclusions(), {},
1187 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1189 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1191 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1192 helper.generateExclusions();
1194 nb_.setCutoff(data.cutoff_);
1195 nb_.setTopologyExclusions(helper.exclusions());
1196 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1197 gmx::AnalysisNeighborhoodSearch search =
1198 nb_.initSearch(&data.pbc_,
1199 data.refPositions().exclusionIds(helper.refPosIds()));
1200 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1202 testPairSearchFull(&search, data,
1203 data.testPositions().exclusionIds(helper.testPosIds()),
1204 helper.exclusions(), {},