2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2013,2014,2015,2016,2017, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
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"
58 #include <gtest/gtest.h>
60 #include "gromacs/math/functions.h"
61 #include "gromacs/math/vec.h"
62 #include "gromacs/pbcutil/pbc.h"
63 #include "gromacs/random/threefry.h"
64 #include "gromacs/random/uniformrealdistribution.h"
65 #include "gromacs/topology/block.h"
66 #include "gromacs/utility/smalloc.h"
67 #include "gromacs/utility/stringutil.h"
69 #include "testutils/testasserts.h"
75 /********************************************************************
76 * NeighborhoodSearchTestData
79 class NeighborhoodSearchTestData
84 RefPair(int refIndex, real distance)
85 : refIndex(refIndex), distance(distance), bFound(false),
86 bExcluded(false), bIndexed(true)
90 bool operator<(const RefPair &other) const
92 return refIndex < other.refIndex;
97 // The variables below are state variables that are only used
98 // during the actual testing after creating a copy of the reference
99 // pair list, not as part of the reference data.
100 // Simpler to have just a single structure for both purposes.
108 explicit TestPosition(const rvec x)
109 : refMinDist(0.0), refNearestPoint(-1)
111 copy_rvec(x, this->x);
117 std::vector<RefPair> refPairs;
120 typedef std::vector<TestPosition> TestPositionList;
122 NeighborhoodSearchTestData(gmx_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(),
148 "Cannot add positions after testPositions() call");
149 testPositions_.emplace_back(x);
151 gmx::RVec generateRandomPosition();
152 std::vector<int> generateIndex(int count, gmx_uint64_t seed) const;
153 void generateRandomRefPositions(int count);
154 void generateRandomTestPositions(int count);
155 void computeReferences(t_pbc *pbc)
157 computeReferencesInternal(pbc, false);
159 void computeReferencesXY(t_pbc *pbc)
161 computeReferencesInternal(pbc, true);
164 bool containsPair(int testIndex, const RefPair &pair) const
166 const std::vector<RefPair> &refPairs = testPositions_[testIndex].refPairs;
167 std::vector<RefPair>::const_iterator foundRefPair
168 = std::lower_bound(refPairs.begin(), refPairs.end(), pair);
169 if (foundRefPair == refPairs.end() || foundRefPair->refIndex != pair.refIndex)
176 // Return a tolerance that accounts for the magnitudes of the coordinates
177 // when doing subtractions, so that we set the ULP tolerance relative to the
178 // coordinate values rather than their difference.
179 // i.e., 10.0-9.9999999 will achieve a few ULP accuracy relative
180 // to 10.0, but a much larger error relative to the difference.
181 gmx::test::FloatingPointTolerance relativeTolerance() const
183 real magnitude = std::max(box_[XX][XX], std::max(box_[YY][YY], box_[ZZ][ZZ]));
184 return gmx::test::relativeToleranceAsUlp(magnitude, 4);
187 gmx::DefaultRandomEngine rng_;
192 std::vector<gmx::RVec> refPos_;
193 TestPositionList testPositions_;
196 void computeReferencesInternal(t_pbc *pbc, bool bXY);
198 mutable std::vector<gmx::RVec> testPos_;
201 //! Shorthand for a collection of reference pairs.
202 typedef std::vector<NeighborhoodSearchTestData::RefPair> RefPairList;
204 NeighborhoodSearchTestData::NeighborhoodSearchTestData(gmx_uint64_t seed, real cutoff)
205 : rng_(seed), cutoff_(cutoff), refPosCount_(0)
208 set_pbc(&pbc_, epbcNONE, box_);
211 gmx::RVec NeighborhoodSearchTestData::generateRandomPosition()
213 gmx::UniformRealDistribution<real> dist;
219 // Add a small displacement to allow positions outside the box
220 x[XX] += 0.2 * dist(rng_) - 0.1;
221 x[YY] += 0.2 * dist(rng_) - 0.1;
222 x[ZZ] += 0.2 * dist(rng_) - 0.1;
226 std::vector<int> NeighborhoodSearchTestData::generateIndex(int count, gmx_uint64_t seed) const
228 gmx::DefaultRandomEngine rngIndex(seed);
229 gmx::UniformRealDistribution<real> dist;
230 std::vector<int> result;
232 for (int i = 0; i < count; ++i)
234 if (dist(rngIndex) > 0.5)
242 void NeighborhoodSearchTestData::generateRandomRefPositions(int count)
244 refPosCount_ = count;
245 refPos_.reserve(count);
246 for (int i = 0; i < count; ++i)
248 refPos_.push_back(generateRandomPosition());
252 void NeighborhoodSearchTestData::generateRandomTestPositions(int count)
254 testPositions_.reserve(count);
255 for (int i = 0; i < count; ++i)
257 addTestPosition(generateRandomPosition());
261 void NeighborhoodSearchTestData::computeReferencesInternal(t_pbc *pbc, bool bXY)
263 real cutoff = cutoff_;
266 cutoff = std::numeric_limits<real>::max();
268 TestPositionList::iterator i;
269 for (i = testPositions_.begin(); i != testPositions_.end(); ++i)
271 i->refMinDist = cutoff;
272 i->refNearestPoint = -1;
274 for (int j = 0; j < refPosCount_; ++j)
279 pbc_dx(pbc, i->x, refPos_[j], dx);
283 rvec_sub(i->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.
289 !bXY ? norm(dx) : std::hypot(dx[XX], dx[YY]);
290 if (dist < i->refMinDist)
292 i->refMinDist = dist;
293 i->refNearestPoint = j;
297 RefPair pair(j, dist);
298 GMX_RELEASE_ASSERT(i->refPairs.empty() || i->refPairs.back() < pair,
299 "Reference pairs should be generated in sorted order");
300 i->refPairs.push_back(pair);
306 /********************************************************************
310 class ExclusionsHelper
313 static void markExcludedPairs(RefPairList *refPairs, int testIndex,
314 const t_blocka *excls);
316 ExclusionsHelper(int refPosCount, int testPosCount);
318 void generateExclusions();
320 const t_blocka *exclusions() const { return &excls_; }
322 gmx::ConstArrayRef<int> refPosIds() const
324 return gmx::constArrayRefFromVector<int>(exclusionIds_.begin(),
325 exclusionIds_.begin() + refPosCount_);
327 gmx::ConstArrayRef<int> testPosIds() const
329 return gmx::constArrayRefFromVector<int>(exclusionIds_.begin(),
330 exclusionIds_.begin() + testPosCount_);
336 std::vector<int> exclusionIds_;
337 std::vector<int> exclsIndex_;
338 std::vector<int> exclsAtoms_;
343 void ExclusionsHelper::markExcludedPairs(RefPairList *refPairs, int testIndex,
344 const t_blocka *excls)
347 for (int i = excls->index[testIndex]; i < excls->index[testIndex + 1]; ++i)
349 const int excludedIndex = excls->a[i];
350 NeighborhoodSearchTestData::RefPair searchPair(excludedIndex, 0.0);
351 RefPairList::iterator excludedRefPair
352 = std::lower_bound(refPairs->begin(), refPairs->end(), searchPair);
353 if (excludedRefPair != refPairs->end()
354 && excludedRefPair->refIndex == excludedIndex)
356 excludedRefPair->bFound = true;
357 excludedRefPair->bExcluded = true;
363 ExclusionsHelper::ExclusionsHelper(int refPosCount, int testPosCount)
364 : refPosCount_(refPosCount), testPosCount_(testPosCount)
366 // Generate an array of 0, 1, 2, ...
367 // TODO: Make the tests work also with non-trivial exclusion IDs,
369 exclusionIds_.resize(std::max(refPosCount, testPosCount), 1);
370 exclusionIds_[0] = 0;
371 std::partial_sum(exclusionIds_.begin(), exclusionIds_.end(),
372 exclusionIds_.begin());
375 excls_.index = nullptr;
378 excls_.nalloc_index = 0;
382 void ExclusionsHelper::generateExclusions()
384 // TODO: Consider a better set of test data, where the density of the
385 // particles would be higher, or where the exclusions would not be random,
386 // to make a higher percentage of the exclusions to actually be within the
388 exclsIndex_.reserve(testPosCount_ + 1);
389 exclsAtoms_.reserve(testPosCount_ * 20);
390 exclsIndex_.push_back(0);
391 for (int i = 0; i < testPosCount_; ++i)
393 for (int j = 0; j < 20; ++j)
395 exclsAtoms_.push_back(i + j*3);
397 exclsIndex_.push_back(exclsAtoms_.size());
399 excls_.nr = exclsIndex_.size();
400 excls_.index = exclsIndex_.data();
401 excls_.nra = exclsAtoms_.size();
402 excls_.a = exclsAtoms_.data();
405 /********************************************************************
406 * NeighborhoodSearchTest
409 class NeighborhoodSearchTest : public ::testing::Test
412 void testIsWithin(gmx::AnalysisNeighborhoodSearch *search,
413 const NeighborhoodSearchTestData &data);
414 void testMinimumDistance(gmx::AnalysisNeighborhoodSearch *search,
415 const NeighborhoodSearchTestData &data);
416 void testNearestPoint(gmx::AnalysisNeighborhoodSearch *search,
417 const NeighborhoodSearchTestData &data);
418 void testPairSearch(gmx::AnalysisNeighborhoodSearch *search,
419 const NeighborhoodSearchTestData &data);
420 void testPairSearchIndexed(gmx::AnalysisNeighborhood *nb,
421 const NeighborhoodSearchTestData &data,
423 void testPairSearchFull(gmx::AnalysisNeighborhoodSearch *search,
424 const NeighborhoodSearchTestData &data,
425 const gmx::AnalysisNeighborhoodPositions &pos,
426 const t_blocka *excls,
427 const gmx::ConstArrayRef<int> &refIndices,
428 const gmx::ConstArrayRef<int> &testIndices);
430 gmx::AnalysisNeighborhood nb_;
433 void NeighborhoodSearchTest::testIsWithin(
434 gmx::AnalysisNeighborhoodSearch *search,
435 const NeighborhoodSearchTestData &data)
437 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
438 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
440 const bool bWithin = (i->refMinDist <= data.cutoff_);
441 EXPECT_EQ(bWithin, search->isWithin(i->x))
442 << "Distance is " << i->refMinDist;
446 void NeighborhoodSearchTest::testMinimumDistance(
447 gmx::AnalysisNeighborhoodSearch *search,
448 const NeighborhoodSearchTestData &data)
450 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
452 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
454 const real refDist = i->refMinDist;
455 EXPECT_REAL_EQ_TOL(refDist, search->minimumDistance(i->x), data.relativeTolerance());
459 void NeighborhoodSearchTest::testNearestPoint(
460 gmx::AnalysisNeighborhoodSearch *search,
461 const NeighborhoodSearchTestData &data)
463 NeighborhoodSearchTestData::TestPositionList::const_iterator i;
464 for (i = data.testPositions_.begin(); i != data.testPositions_.end(); ++i)
466 const gmx::AnalysisNeighborhoodPair pair = search->nearestPoint(i->x);
469 EXPECT_EQ(i->refNearestPoint, pair.refIndex());
470 EXPECT_EQ(0, pair.testIndex());
471 EXPECT_REAL_EQ_TOL(i->refMinDist, std::sqrt(pair.distance2()), data.relativeTolerance());
475 EXPECT_EQ(i->refNearestPoint, -1);
480 //! Helper function for formatting test failure messages.
481 std::string formatVector(const rvec x)
483 return gmx::formatString("[%.3f, %.3f, %.3f]", x[XX], x[YY], x[ZZ]);
487 * Helper function to check that all expected pairs were found.
489 void checkAllPairsFound(const RefPairList &refPairs,
490 const std::vector<gmx::RVec> &refPos,
491 int testPosIndex, const rvec testPos)
493 // This could be elegantly expressed with Google Mock matchers, but that
494 // has a significant effect on the runtime of the tests...
496 RefPairList::const_iterator first;
497 for (RefPairList::const_iterator i = refPairs.begin(); i != refPairs.end(); ++i)
508 << "Some pairs (" << count << "/" << refPairs.size() << ") "
509 << "within the cutoff were not found. First pair:\n"
510 << " Ref: " << first->refIndex << " at "
511 << formatVector(refPos[first->refIndex]) << "\n"
512 << "Test: " << testPosIndex << " at " << formatVector(testPos) << "\n"
513 << "Dist: " << first->distance;
517 void NeighborhoodSearchTest::testPairSearch(
518 gmx::AnalysisNeighborhoodSearch *search,
519 const NeighborhoodSearchTestData &data)
521 testPairSearchFull(search, data, data.testPositions(), nullptr,
522 gmx::EmptyArrayRef(), gmx::EmptyArrayRef());
525 void NeighborhoodSearchTest::testPairSearchIndexed(
526 gmx::AnalysisNeighborhood *nb,
527 const NeighborhoodSearchTestData &data,
530 std::vector<int> refIndices(data.generateIndex(data.refPos_.size(), seed++));
531 std::vector<int> testIndices(data.generateIndex(data.testPositions_.size(), seed++));
532 gmx::AnalysisNeighborhoodSearch search =
533 nb->initSearch(&data.pbc_,
534 data.refPositions().indexed(refIndices));
535 testPairSearchFull(&search, data, data.testPositions(), nullptr,
536 refIndices, testIndices);
539 void NeighborhoodSearchTest::testPairSearchFull(
540 gmx::AnalysisNeighborhoodSearch *search,
541 const NeighborhoodSearchTestData &data,
542 const gmx::AnalysisNeighborhoodPositions &pos,
543 const t_blocka *excls,
544 const gmx::ConstArrayRef<int> &refIndices,
545 const gmx::ConstArrayRef<int> &testIndices)
547 // TODO: Some parts of this code do not work properly if pos does not
548 // initially contain all the test positions.
549 std::set<int> remainingTestPositions;
550 gmx::AnalysisNeighborhoodPositions posCopy(pos);
551 if (testIndices.empty())
553 for (size_t i = 0; i < data.testPositions_.size(); ++i)
555 remainingTestPositions.insert(i);
560 remainingTestPositions.insert(testIndices.begin(), testIndices.end());
561 posCopy.indexed(testIndices);
564 gmx::AnalysisNeighborhoodPairSearch pairSearch
565 = search->startPairSearch(posCopy);
566 gmx::AnalysisNeighborhoodPair pair;
567 // There is an ordering assumption here that all pairs for a test position
568 // are returned consencutively; with the current optimizations in the
569 // search code, this is reasoable, as the set of grid cell pairs searched
570 // depends on the test position.
571 RefPairList refPairs;
572 int prevTestPos = -1;
573 while (pairSearch.findNextPair(&pair))
575 const int testIndex =
576 (testIndices.empty() ? pair.testIndex() : testIndices[pair.testIndex()]);
578 (refIndices.empty() ? pair.refIndex() : refIndices[pair.refIndex()]);
579 if (testIndex != prevTestPos)
581 if (prevTestPos != -1)
583 checkAllPairsFound(refPairs, data.refPos_, prevTestPos,
584 data.testPositions_[prevTestPos].x);
586 if (remainingTestPositions.count(testIndex) == 0)
589 << "Pairs for test position " << testIndex
590 << " are returned more than once.";
592 remainingTestPositions.erase(testIndex);
593 refPairs = data.testPositions_[testIndex].refPairs;
594 if (excls != nullptr)
596 ExclusionsHelper::markExcludedPairs(&refPairs, testIndex, excls);
598 if (!refIndices.empty())
600 RefPairList::iterator refPair;
601 for (refPair = refPairs.begin(); refPair != refPairs.end(); ++refPair)
603 refPair->bIndexed = false;
605 for (size_t i = 0; i < refIndices.size(); ++i)
607 NeighborhoodSearchTestData::RefPair searchPair(refIndices[i], 0.0);
608 refPair = std::lower_bound(refPairs.begin(), refPairs.end(), searchPair);
609 if (refPair != refPairs.end() && refPair->refIndex == refIndices[i])
611 refPair->bIndexed = true;
614 for (refPair = refPairs.begin(); refPair != refPairs.end(); ++refPair)
616 if (!refPair->bIndexed)
618 refPair->bFound = true;
622 prevTestPos = testIndex;
625 NeighborhoodSearchTestData::RefPair searchPair(refIndex,
626 std::sqrt(pair.distance2()));
627 RefPairList::iterator foundRefPair
628 = std::lower_bound(refPairs.begin(), refPairs.end(), searchPair);
629 if (foundRefPair == refPairs.end() || foundRefPair->refIndex != refIndex)
632 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
633 << ") is not within the cutoff.\n"
634 << " Actual: It is returned.";
636 else if (foundRefPair->bExcluded)
639 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
640 << ") is excluded from the search.\n"
641 << " Actual: It is returned.";
643 else if (!foundRefPair->bIndexed)
646 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
647 << ") is not part of the indexed set.\n"
648 << " Actual: It is returned.";
650 else if (foundRefPair->bFound)
653 << "Expected: Pair (ref: " << refIndex << ", test: " << testIndex
654 << ") is returned only once.\n"
655 << " Actual: It is returned multiple times.";
660 foundRefPair->bFound = true;
662 EXPECT_REAL_EQ_TOL(foundRefPair->distance, searchPair.distance, data.relativeTolerance())
663 << "Distance computed by the neighborhood search does not match.";
667 checkAllPairsFound(refPairs, data.refPos_, prevTestPos,
668 data.testPositions_[prevTestPos].x);
670 std::set<int> refPositions(refIndices.begin(), refIndices.end());
672 for (std::set<int>::const_iterator i = remainingTestPositions.begin();
673 i != remainingTestPositions.end(); ++i)
675 // Account for the case where the i particle is listed in the testIndex,
676 // but none of its ref neighbours were listed in the refIndex.
677 if (!refIndices.empty())
679 RefPairList::const_iterator refPair;
680 bool foundAnyRefInIndex = false;
682 for (refPair = data.testPositions_[*i].refPairs.begin();
683 refPair != data.testPositions_[*i].refPairs.end() && !foundAnyRefInIndex; ++refPair)
685 foundAnyRefInIndex = (refPositions.count(refPair->refIndex) > 0);
687 if (!foundAnyRefInIndex)
692 if (!data.testPositions_[*i].refPairs.empty())
695 << "Expected: Pairs would be returned for test position " << *i << ".\n"
696 << " Actual: None were returned.";
702 /********************************************************************
703 * Test data generation
706 class TrivialTestData
709 static const NeighborhoodSearchTestData &get()
711 static TrivialTestData singleton;
712 return singleton.data_;
715 TrivialTestData() : data_(12345, 1.0)
717 // Make the box so small we are virtually guaranteed to have
718 // several neighbors for the five test positions
719 data_.box_[XX][XX] = 3.0;
720 data_.box_[YY][YY] = 3.0;
721 data_.box_[ZZ][ZZ] = 3.0;
722 data_.generateRandomRefPositions(10);
723 data_.generateRandomTestPositions(5);
724 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
725 data_.computeReferences(&data_.pbc_);
729 NeighborhoodSearchTestData data_;
732 class TrivialNoPBCTestData
735 static const NeighborhoodSearchTestData &get()
737 static TrivialNoPBCTestData singleton;
738 return singleton.data_;
741 TrivialNoPBCTestData() : data_(12345, 1.0)
743 data_.generateRandomRefPositions(10);
744 data_.generateRandomTestPositions(5);
745 data_.computeReferences(nullptr);
749 NeighborhoodSearchTestData data_;
752 class RandomBoxFullPBCData
755 static const NeighborhoodSearchTestData &get()
757 static RandomBoxFullPBCData singleton;
758 return singleton.data_;
761 RandomBoxFullPBCData() : data_(12345, 1.0)
763 data_.box_[XX][XX] = 10.0;
764 data_.box_[YY][YY] = 5.0;
765 data_.box_[ZZ][ZZ] = 7.0;
766 // TODO: Consider whether manually picking some positions would give better
768 data_.generateRandomRefPositions(1000);
769 data_.generateRandomTestPositions(100);
770 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
771 data_.computeReferences(&data_.pbc_);
775 NeighborhoodSearchTestData data_;
778 class RandomBoxXYFullPBCData
781 static const NeighborhoodSearchTestData &get()
783 static RandomBoxXYFullPBCData singleton;
784 return singleton.data_;
787 RandomBoxXYFullPBCData() : data_(54321, 1.0)
789 data_.box_[XX][XX] = 10.0;
790 data_.box_[YY][YY] = 5.0;
791 data_.box_[ZZ][ZZ] = 7.0;
792 // TODO: Consider whether manually picking some positions would give better
794 data_.generateRandomRefPositions(1000);
795 data_.generateRandomTestPositions(100);
796 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
797 data_.computeReferencesXY(&data_.pbc_);
801 NeighborhoodSearchTestData data_;
804 class RandomTriclinicFullPBCData
807 static const NeighborhoodSearchTestData &get()
809 static RandomTriclinicFullPBCData singleton;
810 return singleton.data_;
813 RandomTriclinicFullPBCData() : data_(12345, 1.0)
815 data_.box_[XX][XX] = 5.0;
816 data_.box_[YY][XX] = 2.5;
817 data_.box_[YY][YY] = 2.5*std::sqrt(3.0);
818 data_.box_[ZZ][XX] = 2.5;
819 data_.box_[ZZ][YY] = 2.5*std::sqrt(1.0/3.0);
820 data_.box_[ZZ][ZZ] = 5.0*std::sqrt(2.0/3.0);
821 // TODO: Consider whether manually picking some positions would give better
823 data_.generateRandomRefPositions(1000);
824 data_.generateRandomTestPositions(100);
825 set_pbc(&data_.pbc_, epbcXYZ, data_.box_);
826 data_.computeReferences(&data_.pbc_);
830 NeighborhoodSearchTestData data_;
833 class RandomBox2DPBCData
836 static const NeighborhoodSearchTestData &get()
838 static RandomBox2DPBCData singleton;
839 return singleton.data_;
842 RandomBox2DPBCData() : data_(12345, 1.0)
844 data_.box_[XX][XX] = 10.0;
845 data_.box_[YY][YY] = 7.0;
846 data_.box_[ZZ][ZZ] = 5.0;
847 // TODO: Consider whether manually picking some positions would give better
849 data_.generateRandomRefPositions(1000);
850 data_.generateRandomTestPositions(100);
851 set_pbc(&data_.pbc_, epbcXY, data_.box_);
852 data_.computeReferences(&data_.pbc_);
856 NeighborhoodSearchTestData data_;
859 class RandomBoxNoPBCData
862 static const NeighborhoodSearchTestData &get()
864 static RandomBoxNoPBCData singleton;
865 return singleton.data_;
868 RandomBoxNoPBCData() : data_(12345, 1.0)
870 data_.box_[XX][XX] = 10.0;
871 data_.box_[YY][YY] = 5.0;
872 data_.box_[ZZ][ZZ] = 7.0;
873 // TODO: Consider whether manually picking some positions would give better
875 data_.generateRandomRefPositions(1000);
876 data_.generateRandomTestPositions(100);
877 set_pbc(&data_.pbc_, epbcNONE, data_.box_);
878 data_.computeReferences(nullptr);
882 NeighborhoodSearchTestData data_;
885 /********************************************************************
889 TEST_F(NeighborhoodSearchTest, SimpleSearch)
891 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
893 nb_.setCutoff(data.cutoff_);
894 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
895 gmx::AnalysisNeighborhoodSearch search =
896 nb_.initSearch(&data.pbc_, data.refPositions());
897 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
899 testIsWithin(&search, data);
900 testMinimumDistance(&search, data);
901 testNearestPoint(&search, data);
902 testPairSearch(&search, data);
905 testPairSearchIndexed(&nb_, data, 123);
908 TEST_F(NeighborhoodSearchTest, SimpleSearchXY)
910 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
912 nb_.setCutoff(data.cutoff_);
913 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
915 gmx::AnalysisNeighborhoodSearch search =
916 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 =
932 nb_.initSearch(&data.pbc_, data.refPositions());
933 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
935 testIsWithin(&search, data);
936 testMinimumDistance(&search, data);
937 testNearestPoint(&search, data);
938 testPairSearch(&search, data);
941 testPairSearchIndexed(&nb_, data, 456);
944 TEST_F(NeighborhoodSearchTest, GridSearchTriclinic)
946 const NeighborhoodSearchTestData &data = RandomTriclinicFullPBCData::get();
948 nb_.setCutoff(data.cutoff_);
949 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
950 gmx::AnalysisNeighborhoodSearch search =
951 nb_.initSearch(&data.pbc_, data.refPositions());
952 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
954 testPairSearch(&search, data);
957 TEST_F(NeighborhoodSearchTest, GridSearch2DPBC)
959 const NeighborhoodSearchTestData &data = RandomBox2DPBCData::get();
961 nb_.setCutoff(data.cutoff_);
962 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
963 gmx::AnalysisNeighborhoodSearch search =
964 nb_.initSearch(&data.pbc_, data.refPositions());
965 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
967 testIsWithin(&search, data);
968 testMinimumDistance(&search, data);
969 testNearestPoint(&search, data);
970 testPairSearch(&search, data);
973 TEST_F(NeighborhoodSearchTest, GridSearchNoPBC)
975 const NeighborhoodSearchTestData &data = RandomBoxNoPBCData::get();
977 nb_.setCutoff(data.cutoff_);
978 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
979 gmx::AnalysisNeighborhoodSearch search =
980 nb_.initSearch(&data.pbc_, data.refPositions());
981 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
983 testPairSearch(&search, data);
986 TEST_F(NeighborhoodSearchTest, GridSearchXYBox)
988 const NeighborhoodSearchTestData &data = RandomBoxXYFullPBCData::get();
990 nb_.setCutoff(data.cutoff_);
991 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
993 gmx::AnalysisNeighborhoodSearch search =
994 nb_.initSearch(&data.pbc_, data.refPositions());
995 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
997 testIsWithin(&search, data);
998 testMinimumDistance(&search, data);
999 testNearestPoint(&search, data);
1000 testPairSearch(&search, data);
1003 TEST_F(NeighborhoodSearchTest, HandlesConcurrentSearches)
1005 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1007 nb_.setCutoff(data.cutoff_);
1008 gmx::AnalysisNeighborhoodSearch search1 =
1009 nb_.initSearch(&data.pbc_, data.refPositions());
1010 gmx::AnalysisNeighborhoodSearch search2 =
1011 nb_.initSearch(&data.pbc_, data.refPositions());
1013 // These checks are fragile, and unfortunately depend on the random
1014 // engine used to create the test positions. There is no particular reason
1015 // why exactly particles 0 & 2 should have neighbors, but in this case they do.
1016 gmx::AnalysisNeighborhoodPairSearch pairSearch1 =
1017 search1.startPairSearch(data.testPosition(0));
1018 gmx::AnalysisNeighborhoodPairSearch pairSearch2 =
1019 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 =
1047 nb_.initSearch(&data.pbc_, data.refPositions());
1048 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1050 testIsWithin(&search, data);
1051 testMinimumDistance(&search, data);
1052 testNearestPoint(&search, data);
1053 testPairSearch(&search, data);
1056 TEST_F(NeighborhoodSearchTest, HandlesNullPBC)
1058 const NeighborhoodSearchTestData &data = TrivialNoPBCTestData::get();
1060 nb_.setCutoff(data.cutoff_);
1061 gmx::AnalysisNeighborhoodSearch search =
1062 nb_.initSearch(nullptr, data.refPositions());
1063 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1065 testIsWithin(&search, data);
1066 testMinimumDistance(&search, data);
1067 testNearestPoint(&search, data);
1068 testPairSearch(&search, data);
1071 TEST_F(NeighborhoodSearchTest, HandlesSkippingPairs)
1073 const NeighborhoodSearchTestData &data = TrivialTestData::get();
1075 nb_.setCutoff(data.cutoff_);
1076 gmx::AnalysisNeighborhoodSearch search =
1077 nb_.initSearch(&data.pbc_, data.refPositions());
1078 gmx::AnalysisNeighborhoodPairSearch pairSearch =
1079 search.startPairSearch(data.testPositions());
1080 gmx::AnalysisNeighborhoodPair pair;
1081 // TODO: This test needs to be adjusted if the grid search gets optimized
1082 // to loop over the test positions in cell order (first, the ordering
1083 // assumption here breaks, and second, it then needs to be tested
1084 // separately for simple and grid searches).
1085 int currentIndex = 0;
1086 while (pairSearch.findNextPair(&pair))
1088 while (currentIndex < pair.testIndex())
1092 EXPECT_EQ(currentIndex, pair.testIndex());
1093 NeighborhoodSearchTestData::RefPair searchPair(pair.refIndex(), std::sqrt(pair.distance2()));
1094 EXPECT_TRUE(data.containsPair(currentIndex, searchPair));
1095 pairSearch.skipRemainingPairsForTestPosition();
1100 TEST_F(NeighborhoodSearchTest, SimpleSearchExclusions)
1102 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1104 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1105 helper.generateExclusions();
1107 nb_.setCutoff(data.cutoff_);
1108 nb_.setTopologyExclusions(helper.exclusions());
1109 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Simple);
1110 gmx::AnalysisNeighborhoodSearch search =
1111 nb_.initSearch(&data.pbc_,
1112 data.refPositions().exclusionIds(helper.refPosIds()));
1113 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Simple, search.mode());
1115 testPairSearchFull(&search, data,
1116 data.testPositions().exclusionIds(helper.testPosIds()),
1117 helper.exclusions(), gmx::EmptyArrayRef(), gmx::EmptyArrayRef());
1120 TEST_F(NeighborhoodSearchTest, GridSearchExclusions)
1122 const NeighborhoodSearchTestData &data = RandomBoxFullPBCData::get();
1124 ExclusionsHelper helper(data.refPosCount_, data.testPositions_.size());
1125 helper.generateExclusions();
1127 nb_.setCutoff(data.cutoff_);
1128 nb_.setTopologyExclusions(helper.exclusions());
1129 nb_.setMode(gmx::AnalysisNeighborhood::eSearchMode_Grid);
1130 gmx::AnalysisNeighborhoodSearch search =
1131 nb_.initSearch(&data.pbc_,
1132 data.refPositions().exclusionIds(helper.refPosIds()));
1133 ASSERT_EQ(gmx::AnalysisNeighborhood::eSearchMode_Grid, search.mode());
1135 testPairSearchFull(&search, data,
1136 data.testPositions().exclusionIds(helper.testPosIds()),
1137 helper.exclusions(), gmx::EmptyArrayRef(), gmx::EmptyArrayRef());