2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2010-2018, The GROMACS development team.
5 * Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Tests selection parsing and compilation.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_selection
45 #include "gromacs/selection/selectioncollection.h"
47 #include <gtest/gtest.h>
49 #include "gromacs/options/basicoptions.h"
50 #include "gromacs/options/ioptionscontainer.h"
51 #include "gromacs/selection/indexutil.h"
52 #include "gromacs/selection/selection.h"
53 #include "gromacs/topology/topology.h"
54 #include "gromacs/trajectory/trajectoryframe.h"
55 #include "gromacs/utility/arrayref.h"
56 #include "gromacs/utility/exceptions.h"
57 #include "gromacs/utility/flags.h"
58 #include "gromacs/utility/stringutil.h"
60 #include "testutils/interactivetest.h"
61 #include "testutils/refdata.h"
62 #include "testutils/testasserts.h"
63 #include "testutils/testfilemanager.h"
64 #include "testutils/testoptions.h"
71 /********************************************************************
72 * Test fixture for selection testing
75 class SelectionCollectionTest : public ::testing::Test
78 static int s_debugLevel;
80 SelectionCollectionTest();
81 ~SelectionCollectionTest() override;
83 void setAtomCount(int natoms) { ASSERT_NO_THROW_GMX(sc_.setTopology(nullptr, natoms)); }
84 void loadTopology(const char* filename);
86 void loadIndexGroups(const char* filename);
88 gmx::test::TopologyManager topManager_;
89 gmx::SelectionCollection sc_;
90 gmx::SelectionList sel_;
91 gmx_ana_indexgrps_t* grps_;
94 int SelectionCollectionTest::s_debugLevel = 0;
96 // cond/endcond do not seem to work here with Doxygen 1.8.5 parser.
98 GMX_TEST_OPTIONS(SelectionCollectionTestOptions, options)
100 options->addOption(gmx::IntegerOption("seldebug")
101 .store(&SelectionCollectionTest::s_debugLevel)
102 .description("Set selection debug level"));
106 SelectionCollectionTest::SelectionCollectionTest() : grps_(nullptr)
108 topManager_.requestFrame();
109 sc_.setDebugLevel(s_debugLevel);
110 sc_.setReferencePosType("atom");
111 sc_.setOutputPosType("atom");
114 SelectionCollectionTest::~SelectionCollectionTest()
116 if (grps_ != nullptr)
118 gmx_ana_indexgrps_free(grps_);
122 void SelectionCollectionTest::loadTopology(const char* filename)
124 topManager_.loadTopology(filename);
128 void SelectionCollectionTest::setTopology()
130 ASSERT_NO_THROW_GMX(sc_.setTopology(topManager_.topology(), -1));
133 void SelectionCollectionTest::loadIndexGroups(const char* filename)
135 GMX_RELEASE_ASSERT(grps_ == nullptr, "External groups can only be loaded once");
136 std::string fullpath = gmx::test::TestFileManager::getInputFilePath(filename);
137 gmx_ana_indexgrps_init(&grps_, nullptr, fullpath.c_str());
138 sc_.setIndexGroups(grps_);
141 /********************************************************************
142 * Test fixture for interactive SelectionCollection tests
145 class SelectionCollectionInteractiveTest : public SelectionCollectionTest
148 SelectionCollectionInteractiveTest() : helper_(data_.rootChecker()) {}
150 void runTest(int count, bool bInteractive, const gmx::ArrayRef<const char* const>& input);
152 gmx::test::TestReferenceData data_;
153 gmx::test::InteractiveTestHelper helper_;
156 void SelectionCollectionInteractiveTest::runTest(int count,
158 const gmx::ArrayRef<const char* const>& inputLines)
160 helper_.setInputLines(inputLines);
161 // TODO: Check something about the returned selections as well.
162 ASSERT_NO_THROW_GMX(sc_.parseInteractive(count,
163 &helper_.inputStream(),
164 bInteractive ? &helper_.outputStream() : nullptr,
165 "for test context"));
166 helper_.checkSession();
170 /********************************************************************
171 * Test fixture for selection testing with reference data
174 class SelectionCollectionDataTest : public SelectionCollectionTest
177 enum TestFlag : uint64_t
179 efTestEvaluation = 1 << 0,
180 efTestPositionAtoms = 1 << 1,
181 efTestPositionCoordinates = 1 << 2,
182 efTestPositionMapping = 1 << 3,
183 efTestPositionMasses = 1 << 4,
184 efTestPositionCharges = 1 << 5,
185 efTestSelectionNames = 1 << 6,
186 efDontTestCompiledAtoms = 1 << 8
188 typedef gmx::FlagsTemplate<TestFlag> TestFlags;
190 SelectionCollectionDataTest() : checker_(data_.rootChecker()), count_(0), framenr_(0) {}
192 void setFlags(TestFlags flags) { flags_ = flags; }
194 void runParser(const gmx::ArrayRef<const char* const>& selections);
197 void runEvaluateFinal();
199 void runTest(int natoms, const gmx::ArrayRef<const char* const>& selections);
200 void runTest(const char* filename, const gmx::ArrayRef<const char* const>& selections);
202 void checkCompiled();
205 static void checkSelection(gmx::test::TestReferenceChecker* checker,
206 const gmx::Selection& sel,
210 gmx::test::TestReferenceData data_;
211 gmx::test::TestReferenceChecker checker_;
218 void SelectionCollectionDataTest::checkSelection(gmx::test::TestReferenceChecker* checker,
219 const gmx::Selection& sel,
222 using gmx::test::TestReferenceChecker;
225 gmx::ArrayRef<const int> atoms = sel.atomIndices();
226 checker->checkSequence(atoms.begin(), atoms.end(), "Atoms");
228 if (flags.test(efTestPositionAtoms) || flags.test(efTestPositionCoordinates)
229 || flags.test(efTestPositionMapping) || flags.test(efTestPositionMasses)
230 || flags.test(efTestPositionCharges))
232 TestReferenceChecker compound(checker->checkSequenceCompound("Positions", sel.posCount()));
233 for (int i = 0; i < sel.posCount(); ++i)
235 TestReferenceChecker poscompound(compound.checkCompound("Position", nullptr));
236 const gmx::SelectionPosition& p = sel.position(i);
237 if (flags.test(efTestPositionAtoms))
239 gmx::ArrayRef<const int> atoms = p.atomIndices();
240 poscompound.checkSequence(atoms.begin(), atoms.end(), "Atoms");
242 if (flags.test(efTestPositionCoordinates))
244 poscompound.checkVector(p.x(), "Coordinates");
246 if (flags.test(efTestPositionMapping))
248 poscompound.checkInteger(p.refId(), "RefId");
249 poscompound.checkInteger(p.mappedId(), "MappedId");
251 if (flags.test(efTestPositionMasses))
253 poscompound.checkReal(p.mass(), "Mass");
255 if (flags.test(efTestPositionCharges))
257 poscompound.checkReal(p.charge(), "Charge");
264 void SelectionCollectionDataTest::runParser(const gmx::ArrayRef<const char* const>& selections)
266 using gmx::test::TestReferenceChecker;
268 TestReferenceChecker compound(checker_.checkCompound("ParsedSelections", "Parsed"));
271 for (gmx::index i = 0; i < selections.ssize(); ++i)
273 SCOPED_TRACE(std::string("Parsing selection \"") + selections[i] + "\"");
274 gmx::SelectionList result;
275 ASSERT_NO_THROW_GMX(result = sc_.parseFromString(selections[i]));
276 sel_.insert(sel_.end(), result.begin(), result.end());
277 if (sel_.size() == count_)
279 std::string id = gmx::formatString("Variable%d", static_cast<int>(varcount + 1));
280 TestReferenceChecker varcompound(compound.checkCompound("ParsedVariable", id.c_str()));
281 varcompound.checkString(selections[i], "Input");
286 std::string id = gmx::formatString("Selection%d", static_cast<int>(count_ + 1));
287 TestReferenceChecker selcompound(compound.checkCompound("ParsedSelection", id.c_str()));
288 selcompound.checkString(selections[i], "Input");
289 if (flags_.test(efTestSelectionNames))
291 selcompound.checkString(sel_[count_].name(), "Name");
293 selcompound.checkString(sel_[count_].selectionText(), "Text");
294 selcompound.checkBoolean(sel_[count_].isDynamic(), "Dynamic");
301 void SelectionCollectionDataTest::runCompiler()
303 ASSERT_NO_THROW_GMX(sc_.compile());
304 ASSERT_EQ(count_, sel_.size());
309 void SelectionCollectionDataTest::checkCompiled()
311 using gmx::test::TestReferenceChecker;
312 const TestFlags mask = ~TestFlags(efTestPositionCoordinates);
314 TestReferenceChecker compound(checker_.checkCompound("CompiledSelections", "Compiled"));
315 for (size_t i = 0; i < count_; ++i)
317 SCOPED_TRACE(std::string("Checking selection \"") + sel_[i].selectionText() + "\"");
318 std::string id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
319 TestReferenceChecker selcompound(compound.checkCompound("Selection", id.c_str()));
320 if (flags_.test(efTestSelectionNames))
322 selcompound.checkString(sel_[i].name(), "Name");
324 if (!flags_.test(efDontTestCompiledAtoms))
326 checkSelection(&selcompound, sel_[i], flags_ & mask);
332 void SelectionCollectionDataTest::runEvaluate()
334 using gmx::test::TestReferenceChecker;
337 ASSERT_NO_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr));
338 std::string frame = gmx::formatString("Frame%d", framenr_);
339 TestReferenceChecker compound(checker_.checkCompound("EvaluatedSelections", frame.c_str()));
340 for (size_t i = 0; i < count_; ++i)
342 SCOPED_TRACE(std::string("Checking selection \"") + sel_[i].selectionText() + "\"");
343 std::string id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
344 TestReferenceChecker selcompound(compound.checkCompound("Selection", id.c_str()));
345 checkSelection(&selcompound, sel_[i], flags_);
350 void SelectionCollectionDataTest::runEvaluateFinal()
352 ASSERT_NO_THROW_GMX(sc_.evaluateFinal(framenr_));
357 void SelectionCollectionDataTest::runTest(int natoms, const gmx::ArrayRef<const char* const>& selections)
359 ASSERT_NO_FATAL_FAILURE(runParser(selections));
360 ASSERT_NO_FATAL_FAILURE(setAtomCount(natoms));
361 ASSERT_NO_FATAL_FAILURE(runCompiler());
365 void SelectionCollectionDataTest::runTest(const char* filename,
366 const gmx::ArrayRef<const char* const>& selections)
368 ASSERT_NO_FATAL_FAILURE(runParser(selections));
369 ASSERT_NO_FATAL_FAILURE(loadTopology(filename));
370 ASSERT_NO_FATAL_FAILURE(runCompiler());
371 if (flags_.test(efTestEvaluation))
373 ASSERT_NO_FATAL_FAILURE(runEvaluate());
374 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
379 /********************************************************************
380 * Tests for SelectionCollection functionality without reference data
383 TEST_F(SelectionCollectionTest, HandlesNoSelections)
385 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
386 EXPECT_NO_THROW_GMX(sc_.compile());
387 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
390 TEST_F(SelectionCollectionTest, HandlesNoSelectionsWithDefaultPositionType)
392 EXPECT_NO_THROW_GMX(sc_.setOutputPosType("res_com"));
393 EXPECT_TRUE(sc_.requiredTopologyProperties().needsTopology);
394 EXPECT_TRUE(sc_.requiredTopologyProperties().needsMasses);
395 EXPECT_NO_THROW_GMX(sc_.setOutputPosType("res_cog"));
396 EXPECT_TRUE(sc_.requiredTopologyProperties().needsTopology);
397 EXPECT_FALSE(sc_.requiredTopologyProperties().needsMasses);
398 ASSERT_NO_THROW_GMX(sc_.parseFromString("atom of atomnr 1 to 10"));
399 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
400 ASSERT_NO_THROW_GMX(sc_.compile());
401 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
404 TEST_F(SelectionCollectionTest, HandlesVelocityAndForceRequests)
406 ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr 1 to 10; none"));
407 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
408 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
409 ASSERT_EQ(2U, sel_.size());
410 ASSERT_NO_THROW_GMX(sel_[0].setEvaluateVelocities(true));
411 ASSERT_NO_THROW_GMX(sel_[1].setEvaluateVelocities(true));
412 ASSERT_NO_THROW_GMX(sel_[0].setEvaluateForces(true));
413 ASSERT_NO_THROW_GMX(sel_[1].setEvaluateForces(true));
414 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
415 ASSERT_NO_THROW_GMX(sc_.compile());
416 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
417 EXPECT_TRUE(sel_[0].hasVelocities());
418 EXPECT_TRUE(sel_[1].hasVelocities());
419 EXPECT_TRUE(sel_[0].hasForces());
420 EXPECT_TRUE(sel_[1].hasForces());
423 TEST_F(SelectionCollectionTest, HandlesForceRequestForCenterOfGeometry)
425 ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("res_cog of atomnr 1 to 10"));
426 EXPECT_TRUE(sc_.requiredTopologyProperties().needsTopology);
427 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
428 ASSERT_EQ(1U, sel_.size());
429 ASSERT_NO_THROW_GMX(sel_[0].setEvaluateForces(true));
430 // In principle, the code could know here that the masses are required, but
431 // currently it only knows this after compilation.
432 ASSERT_NO_THROW_GMX(sc_.compile());
433 EXPECT_TRUE(sc_.requiredTopologyProperties().needsMasses);
434 EXPECT_TRUE(sel_[0].hasForces());
437 TEST_F(SelectionCollectionTest, ParsesSelectionsFromFile)
440 sel_ = sc_.parseFromFile(gmx::test::TestFileManager::getInputFilePath("selfile.dat")));
441 // These should match the contents of selfile.dat
442 ASSERT_EQ(2U, sel_.size());
443 EXPECT_STREQ("resname RA RB", sel_[0].selectionText());
444 EXPECT_STREQ("resname RB RC", sel_[1].selectionText());
447 TEST_F(SelectionCollectionTest, HandlesAtypicalWhitespace)
449 ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr\n1\r\nto\t10;\vatomnr 3\f to 14\r"));
450 ASSERT_EQ(2U, sel_.size());
451 EXPECT_STREQ("atomnr 1 to 10", sel_[0].selectionText());
452 // TODO: Get rid of the trailing whitespace.
453 EXPECT_STREQ("atomnr 3 to 14 ", sel_[1].selectionText());
456 TEST_F(SelectionCollectionTest, HandlesInvalidRegularExpressions)
458 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
461 sc_.parseFromString("resname ~ \"R[A\"");
464 gmx::InvalidInputError);
467 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue)
469 EXPECT_THROW_GMX(sc_.parseFromString("mindist from atomnr 1 cutoff"), gmx::InvalidInputError);
472 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue2)
474 EXPECT_THROW_GMX(sc_.parseFromString("within 1 of"), gmx::InvalidInputError);
477 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue3)
479 EXPECT_THROW_GMX(sc_.parseFromString("within of atomnr 1"), gmx::InvalidInputError);
482 // TODO: Tests for more parser errors
484 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser1)
486 ASSERT_NO_THROW_GMX(sc_.setIndexGroups(nullptr));
487 EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
488 EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
491 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser2)
493 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
494 EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
495 EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
498 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed1)
500 ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"foo\""));
501 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
502 EXPECT_THROW_GMX(sc_.setIndexGroups(nullptr), gmx::InconsistentInputError);
503 EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
506 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed2)
508 ASSERT_NO_THROW_GMX(sc_.parseFromString("group 4; group \"foo\""));
509 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
510 EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
511 EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
514 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReference)
516 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
517 EXPECT_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""),
518 gmx::InconsistentInputError);
519 EXPECT_THROW_GMX(sc_.parseFromString("group 2 or atomnr 2 to 5"), gmx::InconsistentInputError);
520 EXPECT_THROW_GMX(sc_.parseFromString("within 1 of group 2"), gmx::InconsistentInputError);
523 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReferenceDelayed)
525 ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""));
526 ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group 2"));
527 EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
528 // TODO: Add a separate check in the selection compiler for a safer API
529 // (makes sense in the future if the compiler needs the information for
530 // other purposes as well).
531 // EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
534 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroup)
536 ASSERT_NO_THROW_GMX(sc_.setTopology(nullptr, 5));
537 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
538 EXPECT_THROW_GMX(sc_.parseFromString("group \"GrpB\""), gmx::InconsistentInputError);
541 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed)
543 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
544 ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
545 EXPECT_THROW_GMX(sc_.setTopology(nullptr, 5), gmx::InconsistentInputError);
548 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed2)
550 ASSERT_NO_THROW_GMX(sc_.setTopology(nullptr, 5));
551 ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
552 EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
555 TEST_F(SelectionCollectionTest, RecoversFromMissingMoleculeInfo)
557 ASSERT_NO_THROW_GMX(sc_.parseFromString("molindex 1 to 5"));
558 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
559 EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
562 TEST_F(SelectionCollectionTest, RecoversFromMissingAtomTypes)
564 ASSERT_NO_THROW_GMX(sc_.parseFromString("type CA"));
565 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
566 EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
569 TEST_F(SelectionCollectionTest, RecoversFromMissingPDBInfo)
571 ASSERT_NO_THROW_GMX(sc_.parseFromString("altloc A"));
572 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
573 EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
576 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation)
578 ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 1 1"));
579 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
580 EXPECT_THROW_GMX(sc_.compile(), gmx::InvalidInputError);
583 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation2)
585 ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 3 2 1"));
586 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
587 EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
590 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation3)
592 ASSERT_NO_THROW_GMX(sc_.parseFromString("x < 1.5 permute 3 2 1"));
593 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
594 ASSERT_NO_THROW_GMX(sc_.compile());
595 EXPECT_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr), gmx::InconsistentInputError);
598 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets)
600 ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 3 to 10"));
601 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
602 ASSERT_NO_THROW_GMX(sc_.compile());
603 topManager_.frame()->natoms = 8;
604 EXPECT_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr), gmx::InconsistentInputError);
607 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets2)
609 const int index[] = { 1, 2, 3, 9 };
610 ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 3 4 7 10"));
611 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
612 ASSERT_NO_THROW_GMX(sc_.compile());
613 topManager_.initFrameIndices(index);
614 EXPECT_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr), gmx::InconsistentInputError);
617 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets3)
619 const int index[] = { 0, 1, 2, 3, 4, 5, 6, 9, 10, 11 };
620 // Evaluating the positions will require atoms 1-3, 7-12.
621 ASSERT_NO_THROW_GMX(sc_.parseFromString("whole_res_cog of atomnr 2 7 11"));
622 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
623 ASSERT_NO_THROW_GMX(sc_.compile());
624 topManager_.initFrameIndices(index);
625 EXPECT_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr), gmx::InconsistentInputError);
628 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets4)
630 ASSERT_NO_THROW_GMX(sc_.parseFromString("mindistance from atomnr 1 to 5 < 2"));
631 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
632 ASSERT_NO_THROW_GMX(sc_.compile());
633 topManager_.frame()->natoms = 10;
634 EXPECT_THROW_GMX(sc_.evaluate(topManager_.frame(), nullptr), gmx::InconsistentInputError);
637 // TODO: Tests for more evaluation errors
640 /********************************************************************
641 * Tests for retrieving selections.
643 TEST_F(SelectionCollectionTest, RetrieveValidSelection)
645 ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr 1 to 10; none"));
646 const std::optional<gmx::Selection> retrievedSel = sc_.selection(sel_[0].name());
647 ASSERT_TRUE(retrievedSel.has_value());
648 EXPECT_STREQ(sel_[0].name(), retrievedSel->name());
651 TEST_F(SelectionCollectionTest, RetrieveInvalidSelection)
653 ASSERT_FALSE(sc_.selection("some invalid key").has_value());
656 /********************************************************************
657 * Tests for assignment/copying.
659 TEST_F(SelectionCollectionTest, CanCopyEmptyCollection)
661 EXPECT_NO_THROW_GMX(gmx::SelectionCollection sc2(sc_));
664 TEST_F(SelectionCollectionTest, CopiedSelectionListsAreHandledSeparately)
666 ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr 1 to 10; none"));
667 EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
668 ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
669 ASSERT_EQ(2U, sel_.size());
670 gmx::SelectionCollection sc2(sc_);
671 ASSERT_NO_THROW_GMX(sel_[1].setEvaluateVelocities(true));
672 ASSERT_NO_THROW_GMX(sel_[1].setEvaluateForces(true));
673 ASSERT_NO_THROW_GMX(sc2.compile());
674 // These would only be populated if sc_.compile() was called
675 EXPECT_FALSE(sel_[1].hasVelocities());
676 EXPECT_FALSE(sel_[1].hasForces());
680 /********************************************************************
681 * Tests for interactive selection input
684 TEST_F(SelectionCollectionInteractiveTest, HandlesBasicInput)
686 const char* const input[] = { "foo = resname RA", "resname RB", "\"Name\" resname RC" };
687 runTest(-1, true, input);
690 TEST_F(SelectionCollectionInteractiveTest, HandlesContinuation)
692 const char* const input[] = { "resname RB and \\", "resname RC" };
693 runTest(-1, true, input);
696 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInput)
698 const char* const input[] = { "foo = resname RA", "resname RA" };
699 runTest(1, true, input);
702 TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInput)
704 const char* const input[] = { "resname RA", "resname RB" };
705 runTest(2, true, input);
708 TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithGroups)
710 const char* const input[] = { "resname RA", "" };
711 loadIndexGroups("simple.ndx");
712 runTest(-1, true, input);
715 TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithExistingSelections)
717 const char* const input[] = { "", "bar = resname RC", "resname RA", "" };
718 ASSERT_NO_THROW_GMX(sc_.parseFromString("foo = resname RA"));
719 ASSERT_NO_THROW_GMX(sc_.parseFromString("resname RB"));
720 runTest(-1, true, input);
723 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputStatus)
725 const char* const input[] = { "foo = resname RA", "", "resname RB" };
726 runTest(1, true, input);
729 TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInputStatus)
731 const char* const input[] = { "\"Sel\" resname RA", "", "resname RB" };
732 runTest(2, true, input);
735 TEST_F(SelectionCollectionInteractiveTest, HandlesMultiSelectionInputStatus)
737 const char* const input[] = { "\"Sel\" resname RA", "\"Sel2\" resname RB", "" };
738 runTest(-1, true, input);
741 TEST_F(SelectionCollectionInteractiveTest, HandlesNoFinalNewline)
743 // TODO: There is an extra prompt printed after the input is finished; it
744 // would be cleaner not to have it, but it's only a cosmetic issue.
745 const char* const input[] = { "resname RA" };
746 helper_.setLastNewline(false);
747 runTest(-1, true, input);
750 TEST_F(SelectionCollectionInteractiveTest, HandlesEmptySelections)
752 const char* const input[] = { "resname RA;", "; resname RB;;", " ", ";" };
753 runTest(-1, true, input);
756 TEST_F(SelectionCollectionInteractiveTest, HandlesMultipleSelectionsOnLine)
758 const char* const input[] = { "resname RA; resname RB and \\", "resname RC" };
759 runTest(2, true, input);
762 TEST_F(SelectionCollectionInteractiveTest, HandlesNoninteractiveInput)
764 const char* const input[] = { "foo = resname RA", "resname RB", "\"Name\" resname RC" };
765 runTest(-1, false, input);
768 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputNoninteractively)
770 const char* const input[] = { "foo = resname RA", "resname RA" };
771 runTest(1, false, input);
775 /********************************************************************
776 * Tests for selection keywords
779 TEST_F(SelectionCollectionDataTest, HandlesAllNone)
781 static const char* const selections[] = { "all", "none" };
782 runTest(10, selections);
785 TEST_F(SelectionCollectionDataTest, HandlesAtomnr)
787 static const char* const selections[] = { "atomnr 1 to 3 6 to 8",
790 runTest(10, selections);
793 TEST_F(SelectionCollectionDataTest, HandlesResnr)
795 static const char* const selections[] = { "resnr 1 2 5", "resid 4 to 3" };
796 runTest("simple.gro", selections);
799 TEST_F(SelectionCollectionDataTest, HandlesResIndex)
801 static const char* const selections[] = { "resindex 1 4", "residue 1 3" };
802 runTest("simple.pdb", selections);
805 TEST_F(SelectionCollectionDataTest, HandlesMolIndex)
807 static const char* const selections[] = { "molindex 1 4", "molecule 2 3 5" };
808 ASSERT_NO_FATAL_FAILURE(runParser(selections));
809 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
810 topManager_.initUniformMolecules(3);
811 ASSERT_NO_FATAL_FAILURE(setTopology());
812 ASSERT_NO_FATAL_FAILURE(runCompiler());
815 TEST_F(SelectionCollectionDataTest, HandlesAtomname)
817 static const char* const selections[] = { "name CB", "atomname S1 S2" };
818 runTest("simple.gro", selections);
821 TEST_F(SelectionCollectionDataTest, HandlesPdbAtomname)
823 static const char* const selections[] = {
824 "name HG21", "name 1HG2", "pdbname HG21 CB", "pdbatomname 1HG2"
826 runTest("simple.pdb", selections);
830 TEST_F(SelectionCollectionDataTest, HandlesAtomtype)
832 static const char* const selections[] = { "atomtype CA" };
833 ASSERT_NO_FATAL_FAILURE(runParser(selections));
834 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
835 const char* const types[] = { "CA", "SA", "SB" };
836 topManager_.initAtomTypes(types);
837 ASSERT_NO_FATAL_FAILURE(setTopology());
838 ASSERT_NO_FATAL_FAILURE(runCompiler());
841 TEST_F(SelectionCollectionDataTest, HandlesChain)
843 static const char* const selections[] = { "chain A", "chain B" };
844 runTest("simple.pdb", selections);
847 TEST_F(SelectionCollectionDataTest, HandlesMass)
849 static const char* const selections[] = { "mass > 5" };
850 ASSERT_NO_FATAL_FAILURE(runParser(selections));
851 EXPECT_TRUE(sc_.requiredTopologyProperties().needsMasses);
852 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
853 t_atoms& atoms = topManager_.atoms();
854 for (int i = 0; i < atoms.nr; ++i)
856 atoms.atom[i].m = 1.0 + i;
858 atoms.haveMass = TRUE;
859 ASSERT_NO_FATAL_FAILURE(setTopology());
860 ASSERT_NO_FATAL_FAILURE(runCompiler());
863 TEST_F(SelectionCollectionDataTest, HandlesCharge)
865 static const char* const selections[] = { "charge < 0.5" };
866 ASSERT_NO_FATAL_FAILURE(runParser(selections));
867 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
868 t_atoms& atoms = topManager_.atoms();
869 for (int i = 0; i < atoms.nr; ++i)
871 atoms.atom[i].q = i / 10.0;
873 // Ensure exact representation of 0.5 is used, so that the test is
875 atoms.atom[5].q = 0.5;
876 atoms.haveCharge = TRUE;
877 ASSERT_NO_FATAL_FAILURE(setTopology());
878 ASSERT_NO_FATAL_FAILURE(runCompiler());
881 TEST_F(SelectionCollectionDataTest, HandlesAltLoc)
883 static const char* const selections[] = { "altloc \" \"", "altloc A" };
884 runTest("simple.pdb", selections);
887 TEST_F(SelectionCollectionDataTest, HandlesInsertCode)
889 static const char* const selections[] = { "insertcode \" \"", "insertcode A" };
890 runTest("simple.pdb", selections);
893 TEST_F(SelectionCollectionDataTest, HandlesOccupancy)
895 static const char* const selections[] = { "occupancy 1", "occupancy < .5" };
896 runTest("simple.pdb", selections);
899 TEST_F(SelectionCollectionDataTest, HandlesBeta)
901 static const char* const selections[] = { "beta 0", "beta >= 0.3" };
902 runTest("simple.pdb", selections);
905 TEST_F(SelectionCollectionDataTest, HandlesResname)
907 static const char* const selections[] = { "resname RA", "resname RB RC" };
908 runTest("simple.gro", selections);
911 TEST_F(SelectionCollectionDataTest, HandlesCoordinateKeywords)
913 static const char* const selections[] = { "x < 3", "y >= 3", "x {-1 to 2}" };
914 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
915 runTest("simple.gro", selections);
919 TEST_F(SelectionCollectionDataTest, HandlesSameResidue)
921 static const char* const selections[] = { "same residue as atomnr 1 4 12" };
922 runTest("simple.gro", selections);
926 TEST_F(SelectionCollectionDataTest, HandlesSameResidueName)
928 static const char* const selections[] = { "same resname as atomnr 1 14" };
929 runTest("simple.gro", selections);
933 TEST_F(SelectionCollectionDataTest, HandlesPositionKeywords)
935 static const char* const selections[] = { "cog of resnr 1 3",
936 "res_cog of name CB and resnr 1 3",
937 "whole_res_cog of name CB and resnr 1 3",
938 "part_res_cog of x < 3",
939 "dyn_res_cog of x < 3" };
940 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms);
941 runTest("simple.gro", selections);
945 TEST_F(SelectionCollectionDataTest, HandlesDistanceKeyword)
947 static const char* const selections[] = { "distance from cog of resnr 1 < 2" };
948 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
949 runTest("simple.gro", selections);
953 TEST_F(SelectionCollectionDataTest, HandlesMinDistanceKeyword)
955 static const char* const selections[] = { "mindistance from resnr 1 < 2" };
956 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
957 runTest("simple.gro", selections);
961 TEST_F(SelectionCollectionDataTest, HandlesWithinKeyword)
963 static const char* const selections[] = { "within 1 of resnr 2" };
964 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
965 runTest("simple.gro", selections);
969 TEST_F(SelectionCollectionDataTest, HandlesInSolidAngleKeyword)
971 // Both of these should evaluate to empty on a correct implementation.
972 static const char* const selections[] = {
973 "resname TP and not insolidangle center cog of resname C span resname R cutoff 20",
974 "resname TN and insolidangle center cog of resname C span resname R cutoff 20"
976 setFlags(TestFlags() | efDontTestCompiledAtoms | efTestEvaluation);
977 runTest("sphere.gro", selections);
981 TEST_F(SelectionCollectionDataTest, HandlesPermuteModifier)
983 static const char* const selections[] = { "all permute 3 1 2",
984 "res_cog of resnr 1 to 4 permute 2 1",
985 "name CB S1 and res_cog x < 3 permute 2 1" };
986 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms
987 | efTestPositionMapping);
988 runTest("simple.gro", selections);
992 TEST_F(SelectionCollectionDataTest, HandlesPlusModifier)
994 static const char* const selections[] = {
995 "name S2 plus name S1",
996 "res_cog of resnr 2 plus res_cog of resnr 1 plus res_cog of resnr 3",
997 "name S1 and y < 3 plus res_cog of x < 2.5"
999 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms
1000 | efTestPositionMapping);
1001 runTest("simple.gro", selections);
1005 TEST_F(SelectionCollectionDataTest, HandlesMergeModifier)
1007 static const char* const selections[] = {
1008 "name S2 merge name S1",
1009 "resnr 1 2 and name S2 merge resnr 1 2 and name S1 merge res_cog of resnr 1 2",
1010 "name S1 and x < 2.5 merge res_cog of x < 2.5"
1012 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms
1013 | efTestPositionMapping);
1014 runTest("simple.gro", selections);
1018 /********************************************************************
1019 * Tests for generic selection evaluation
1022 TEST_F(SelectionCollectionDataTest, ComputesMassesAndCharges)
1024 static const char* const selections[] = { "name CB", "y > 2", "res_cog of y > 2" };
1025 setFlags(TestFlags() | efTestEvaluation | efTestPositionAtoms | efTestPositionMasses
1026 | efTestPositionCharges);
1027 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1028 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
1029 t_atoms& atoms = topManager_.atoms();
1030 for (int i = 0; i < atoms.nr; ++i)
1032 atoms.atom[i].m = 1.0 + i / 100.0;
1033 atoms.atom[i].q = -(1.0 + i / 100.0);
1035 atoms.haveMass = TRUE;
1036 atoms.haveCharge = TRUE;
1037 ASSERT_NO_FATAL_FAILURE(setTopology());
1038 ASSERT_NO_FATAL_FAILURE(runCompiler());
1039 ASSERT_NO_FATAL_FAILURE(runEvaluate());
1040 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1043 TEST_F(SelectionCollectionDataTest, ComputesMassesAndChargesWithoutTopology)
1045 static const char* const selections[] = { "atomnr 1 to 3 8 to 9", "y > 2", "cog of (y > 2)" };
1046 setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMasses | efTestPositionCharges);
1047 runTest(10, selections);
1050 TEST_F(SelectionCollectionDataTest, HandlesFramesWithAtomSubsets)
1052 const int index[] = { 0, 1, 2, 3, 4, 5, 9, 10, 11 };
1053 const char* const selections[] = { "resnr 1 4",
1054 "atomnr 1 2 5 11 and y > 2",
1055 "res_cog of atomnr 2 5 11" };
1056 setFlags(TestFlags() | efTestEvaluation | efTestPositionAtoms);
1057 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1058 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1059 ASSERT_NO_FATAL_FAILURE(runCompiler());
1060 topManager_.initFrameIndices(index);
1061 ASSERT_NO_FATAL_FAILURE(runEvaluate());
1062 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1066 /********************************************************************
1067 * Tests for selection syntactic constructs
1070 TEST_F(SelectionCollectionDataTest, HandlesSelectionNames)
1072 static const char* const selections[] = { "\"GroupSelection\" group \"GrpA\"",
1073 "\"DynamicSelection\" x < 5",
1075 setFlags(TestFlags() | efTestSelectionNames);
1076 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1077 runTest(10, selections);
1080 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelections)
1082 static const char* const selections[] = { "group \"GrpA\"",
1085 // These test that the name of the group is not too
1086 // eagerly promoted to the name of the selection.
1087 "group \"GrpB\" and resname RB",
1088 "group \"GrpA\" permute 5 3 2 1 4",
1089 "group \"GrpA\" plus group \"GrpB\"",
1090 "res_cog of group \"GrpA\"" };
1091 setFlags(TestFlags() | efTestSelectionNames);
1092 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1093 runTest("simple.gro", selections);
1096 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelectionsDelayed)
1098 static const char* const selections[] = {
1099 "group \"GrpA\"", "GrpB", "1", "group \"GrpB\" and resname RB"
1101 setFlags(TestFlags() | efTestSelectionNames);
1102 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1103 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1104 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1105 ASSERT_NO_FATAL_FAILURE(runCompiler());
1108 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelections)
1110 static const char* const selections[] = { "foo = group \"GrpUnsorted\"",
1111 "group \"GrpUnsorted\"",
1114 "res_cog of group \"GrpUnsorted\"",
1115 "group \"GrpUnsorted\" permute 2 1",
1117 setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMapping | efTestSelectionNames);
1118 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1119 runTest("simple.gro", selections);
1122 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelectionsDelayed)
1124 static const char* const selections[] = { "foo = group \"GrpUnsorted\"",
1125 "group \"GrpUnsorted\"",
1128 "res_cog of group \"GrpUnsorted\"",
1129 "group \"GrpUnsorted\" permute 2 1",
1131 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1132 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1133 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1134 ASSERT_NO_FATAL_FAILURE(runCompiler());
1138 TEST_F(SelectionCollectionDataTest, HandlesConstantPositions)
1140 static const char* const selections[] = { "[1, -2, 3.5]" };
1141 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionMapping);
1142 runTest("simple.gro", selections);
1146 TEST_F(SelectionCollectionDataTest, HandlesConstantPositionsWithModifiers)
1148 static const char* const selections[] = { "[0, 0, 0] plus [0, 1, 0]" };
1149 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionMapping);
1150 runTest("simple.gro", selections);
1154 TEST_F(SelectionCollectionDataTest, HandlesWithinConstantPositions)
1156 static const char* const selections[] = { "within 1 of [2, 1, 0]" };
1157 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1158 runTest("simple.gro", selections);
1162 TEST_F(SelectionCollectionDataTest, HandlesOverlappingIntegerRanges)
1164 static const char* const selections[] = { "atomnr 2 to 4 5 to 8", "atomnr 2 to 5 4 to 7" };
1165 ASSERT_NO_FATAL_FAILURE(runTest(10, selections));
1169 TEST_F(SelectionCollectionDataTest, HandlesOverlappingRealRanges)
1171 static const char* const selections[] = { "charge {-0.35 to -0.05 0.25 to 0.75}",
1172 "charge {0.05 to -0.3 -0.05 to 0.55}" };
1173 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1174 ASSERT_NO_FATAL_FAILURE(topManager_.loadTopology("simple.gro"));
1175 t_atoms& atoms = topManager_.atoms();
1176 for (int i = 0; i < atoms.nr; ++i)
1178 atoms.atom[i].q = i / 10.0 - 0.5;
1180 atoms.haveCharge = TRUE;
1181 ASSERT_NO_FATAL_FAILURE(setTopology());
1182 ASSERT_NO_FATAL_FAILURE(runCompiler());
1186 TEST_F(SelectionCollectionDataTest, HandlesForcedStringMatchingMode)
1188 static const char* const selections[] = { "name = S1 \"C?\"", "name ? S1 \"C?\"" };
1189 runTest("simple.gro", selections);
1193 TEST_F(SelectionCollectionDataTest, HandlesWildcardMatching)
1195 static const char* const selections[] = { "name \"S?\"", "name ? \"S?\"" };
1196 runTest("simple.gro", selections);
1200 TEST_F(SelectionCollectionDataTest, HandlesRegexMatching)
1202 static const char* const selections[] = { "resname \"R[BD]\"", "resname ~ \"R[BD]\"" };
1203 runTest("simple.gro", selections);
1207 TEST_F(SelectionCollectionDataTest, HandlesBasicBoolean)
1209 static const char* const selections[] = {
1210 "atomnr 1 to 5 and atomnr 2 to 7",
1211 "atomnr 1 to 5 or not atomnr 3 to 8",
1212 "not not atomnr 1 to 5 and atomnr 2 to 6 and not not atomnr 3 to 7",
1213 "atomnr 1 to 5 and (atomnr 2 to 7 and atomnr 3 to 6)",
1214 "x < 5 and atomnr 1 to 5 and y < 3 and atomnr 2 to 4"
1216 runTest(10, selections);
1220 TEST_F(SelectionCollectionDataTest, HandlesDynamicAtomValuedParameters)
1222 static const char* const selections[] = {
1223 "same residue as (atomnr 3 5 13 or y > 5)",
1224 "(resnr 1 3 5 or x > 10) and same residue as (atomnr 3 5 13 or z > 5)"
1226 setFlags(TestFlags() | efTestEvaluation);
1227 runTest("simple.gro", selections);
1231 TEST_F(SelectionCollectionDataTest, HandlesEmptySelectionWithUnevaluatedExpressions)
1233 static const char* const selections[] = { "none and x > 2", "none and same resname as resnr 2" };
1234 runTest("simple.gro", selections);
1238 TEST_F(SelectionCollectionDataTest, HandlesEmptyReferenceForSame)
1240 static const char* const selections[] = { "same residue as none", "same resname as none" };
1241 runTest("simple.gro", selections);
1245 TEST_F(SelectionCollectionDataTest, HandlesPositionModifiersForKeywords)
1247 static const char* const selections[] = { "res_cog x > 2", "name CB and res_cog y > 2.5" };
1248 setFlags(TestFlags() | efTestEvaluation);
1249 runTest("simple.gro", selections);
1253 TEST_F(SelectionCollectionDataTest, HandlesPositionModifiersForMethods)
1255 static const char* const selections[] = { "res_cog distance from cog of resnr 1 < 2",
1256 "res_cog within 2 of cog of resnr 1" };
1257 setFlags(TestFlags() | efTestEvaluation);
1258 runTest("simple.gro", selections);
1262 TEST_F(SelectionCollectionDataTest, HandlesKeywordOfPositions)
1264 static const char* const selections[] = { "x < y of cog of resnr 2" };
1265 setFlags(TestFlags() | efTestEvaluation);
1266 runTest("simple.gro", selections);
1269 TEST_F(SelectionCollectionDataTest, HandlesKeywordOfPositionsInArithmetic)
1271 static const char* const selections[] = { "x - y of cog of resnr 2 < 0" };
1272 setFlags(TestFlags() | efTestEvaluation);
1273 runTest("simple.gro", selections);
1277 TEST_F(SelectionCollectionDataTest, HandlesNumericComparisons)
1279 static const char* const selections[] = {
1280 "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1282 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1283 runTest("simple.gro", selections);
1287 TEST_F(SelectionCollectionDataTest, HandlesArithmeticExpressions)
1289 static const char* const selections[] = { "x+1 > 3", "(y-1)^2 <= 1", "x+--1 > 3", "-x+-1 < -3" };
1290 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1291 runTest("simple.gro", selections);
1295 TEST_F(SelectionCollectionDataTest, HandlesNumericVariables)
1297 static const char* const selections[] = {
1298 "value = x + y", "value <= 4", "index = resnr", "index < 3"
1300 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1301 runTest("simple.gro", selections);
1305 TEST_F(SelectionCollectionDataTest, HandlesComplexNumericVariables)
1307 static const char* const selections[] = {
1308 "value = x + y", "resname RA and value <= 4", "resname RA RB and x < 3 and value <= 4",
1309 "index = atomnr", "resname RA and index < 3", "resname RB and y < 3 and index < 6"
1311 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1312 runTest("simple.gro", selections);
1316 TEST_F(SelectionCollectionDataTest, HandlesPositionVariables)
1318 static const char* const selections[] = {
1319 "foo = res_cog of resname RA", "foo", "within 1 of foo",
1320 "bar = cog of resname RA", "bar", "within 1 of bar"
1322 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1323 runTest("simple.gro", selections);
1327 TEST_F(SelectionCollectionDataTest, HandlesPositionVariableInModifier)
1329 static const char* const selections[] = { "foo = cog of resnr 1",
1330 "cog of resnr 2 plus foo",
1331 "cog of resnr 3 plus foo" };
1332 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1333 runTest("simple.gro", selections);
1337 TEST_F(SelectionCollectionDataTest, HandlesConstantPositionInVariable)
1339 static const char* const selections[] = { "constpos = [1.0, 2.5, 0.5]",
1341 "within 2 of constpos" };
1342 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms);
1343 runTest("simple.gro", selections);
1347 TEST_F(SelectionCollectionDataTest, HandlesNumericConstantsInVariables)
1349 static const char* const selections[] = { "constint = 4",
1353 "x + constreal1 < constreal2" };
1354 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1355 runTest("simple.gro", selections);
1359 /********************************************************************
1360 * Tests for complex boolean syntax
1363 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysis)
1365 static const char* const selections[] = {
1366 "atomnr 1 to 5 and atomnr 2 to 7 and x < 2",
1367 "atomnr 1 to 5 and (atomnr 4 to 7 or x < 2)",
1368 "atomnr 1 to 5 and y < 3 and (atomnr 4 to 7 or x < 2)",
1369 "atomnr 1 to 5 and not (atomnr 4 to 7 or x < 2)",
1370 "atomnr 1 to 5 or (atomnr 4 to 6 and (atomnr 5 to 7 or x < 2))"
1372 runTest(10, selections);
1376 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithVariables)
1378 static const char* const selections[] = { "foo = atomnr 4 to 7 or x < 2",
1379 "atomnr 1 to 4 and foo",
1380 "atomnr 2 to 6 and y < 3 and foo",
1381 "atomnr 6 to 10 and not foo" };
1382 runTest(10, selections);
1386 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithMoreVariables)
1388 static const char* const selections[] = { "foo = atomnr 4 to 7",
1389 "bar = foo and x < 2",
1390 "bar2 = foo and y < 2",
1391 "atomnr 1 to 4 and bar",
1392 "atomnr 2 to 6 and y < 3 and bar2",
1393 "atomnr 6 to 10 and not foo" };
1394 runTest(10, selections);
1398 /********************************************************************
1399 * Tests for complex subexpression cases
1401 * These tests use some knowledge of the implementation to trigger different
1402 * paths in the code.
1405 TEST_F(SelectionCollectionDataTest, HandlesUnusedVariables)
1407 static const char* const selections[] = { "unused1 = atomnr 1 to 3",
1408 "foo = atomnr 4 to 7",
1409 "atomnr 1 to 6 and foo",
1410 "unused2 = atomnr 3 to 5" };
1411 runTest(10, selections);
1415 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithStaticEvaluationGroups)
1417 static const char* const selections[] = { "foo = atomnr 4 to 7 and x < 2",
1418 "atomnr 1 to 5 and foo",
1419 "atomnr 3 to 7 and foo" };
1420 runTest(10, selections);
1424 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups)
1426 static const char* const selections[] = {
1427 "foo = atomnr 4 to 7 and x < 2", "atomnr 1 to 6 and foo", "within 1 of foo", "foo"
1429 runTest(10, selections);
1433 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups2)
1435 static const char* const selections[] = { "foo = atomnr 1 to 8 and x < 10",
1436 "atomnr 1 to 5 and y < 10 and foo",
1438 setFlags(TestFlags() | efTestEvaluation);
1439 runTest("simple.gro", selections);
1442 /*******************************************************************
1443 * Tests for copy validation.
1445 * These tests ensure that copies of a SelectionCollection behave as the original while being
1446 * independently evaluated.
1448 TEST_F(SelectionCollectionDataTest, CopiedSelectionWorksPreCompilation)
1450 static const char* const selections[] = {
1451 "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1453 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1454 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1455 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1456 std::vector<std::string> selNames;
1457 for (const auto& sel : sel_)
1459 selNames.emplace_back(sel.name());
1461 // Swap copied selection with original and update selections, to reuse the testing machinery.
1462 gmx::SelectionCollection sc2(sc_);
1464 std::vector<gmx::Selection> sel2;
1465 for (const std::string_view selName : selNames)
1467 std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1468 ASSERT_TRUE(maybeSel.has_value());
1469 sel2.push_back(*maybeSel);
1471 sel_ = std::move(sel2);
1473 ASSERT_NO_FATAL_FAILURE(runCompiler());
1474 ASSERT_NO_FATAL_FAILURE(runEvaluate());
1475 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1478 TEST_F(SelectionCollectionDataTest, CopiedSelectionWorksPostCompilation)
1480 static const char* const selections[] = {
1481 "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1483 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1484 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1485 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1487 ASSERT_NO_FATAL_FAILURE(runCompiler());
1488 // Note the copy is made post-compilation.
1489 std::vector<std::string> selNames;
1490 for (const auto& sel : sel_)
1492 selNames.emplace_back(sel.name());
1494 gmx::SelectionCollection sc2(sc_);
1496 std::vector<gmx::Selection> sel2;
1497 for (const std::string_view selName : selNames)
1499 std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1500 ASSERT_TRUE(maybeSel.has_value());
1501 sel2.push_back(*maybeSel);
1503 sel_ = std::move(sel2);
1505 ASSERT_NO_FATAL_FAILURE(runEvaluate());
1506 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1509 TEST_F(SelectionCollectionDataTest, CopiedSelectionsAreIndependent)
1511 static const char* const selections[] = {
1512 "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1514 setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1515 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1516 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1518 ASSERT_NO_FATAL_FAILURE(runCompiler());
1519 // Check that copy evaluation does not conflict with original.
1520 gmx::SelectionCollection sc2(sc_);
1521 ASSERT_NO_THROW_GMX(sc2.evaluate(topManager_.frame(), nullptr));
1523 ASSERT_NO_FATAL_FAILURE(runEvaluate());
1524 ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1527 TEST_F(SelectionCollectionDataTest, CopiedSelectionWithIndexPostCompilation)
1529 static const char* const selections[] = { "group \"GrpA\"",
1532 "group \"GrpB\" and resname RB",
1533 "group \"GrpA\" permute 5 3 2 1 4",
1534 "group \"GrpA\" plus group \"GrpB\"",
1535 "res_cog of group \"GrpA\"" };
1536 setFlags(TestFlags() | efTestSelectionNames);
1537 ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1538 ASSERT_NO_FATAL_FAILURE(runParser(selections));
1539 ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1540 ASSERT_NO_FATAL_FAILURE(runCompiler());
1541 std::vector<std::string> selNames;
1542 for (const auto& sel : sel_)
1544 selNames.emplace_back(sel.name());
1546 gmx::SelectionCollection sc2(sc_);
1548 std::vector<gmx::Selection> sel2;
1549 for (const std::string_view selName : selNames)
1551 std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1552 ASSERT_TRUE(maybeSel.has_value());
1553 sel2.push_back(*maybeSel);
1555 sel_ = std::move(sel2);