SYCL: Avoid using no_init read accessor in rocFFT
[alexxy/gromacs.git] / src / gromacs / selection / tests / selectioncollection.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
9  *
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.
14  *
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.
19  *
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.
24  *
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.
32  *
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.
35  */
36 /*! \internal \file
37  * \brief
38  * Tests selection parsing and compilation.
39  *
40  * \author Teemu Murtola <teemu.murtola@gmail.com>
41  * \ingroup module_selection
42  */
43 #include "gmxpre.h"
44
45 #include "gromacs/selection/selectioncollection.h"
46
47 #include <gtest/gtest.h>
48
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"
59
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"
65
66 #include "toputils.h"
67
68 namespace
69 {
70
71 /********************************************************************
72  * Test fixture for selection testing
73  */
74
75 class SelectionCollectionTest : public ::testing::Test
76 {
77 public:
78     static int s_debugLevel;
79
80     SelectionCollectionTest();
81     ~SelectionCollectionTest() override;
82
83     void setAtomCount(int natoms) { ASSERT_NO_THROW_GMX(sc_.setTopology(nullptr, natoms)); }
84     void loadTopology(const char* filename);
85     void setTopology();
86     void loadIndexGroups(const char* filename);
87
88     gmx::test::TopologyManager topManager_;
89     gmx::SelectionCollection   sc_;
90     gmx::SelectionList         sel_;
91     gmx_ana_indexgrps_t*       grps_;
92 };
93
94 int SelectionCollectionTest::s_debugLevel = 0;
95
96 // cond/endcond do not seem to work here with Doxygen 1.8.5 parser.
97 #ifndef DOXYGEN
98 GMX_TEST_OPTIONS(SelectionCollectionTestOptions, options)
99 {
100     options->addOption(gmx::IntegerOption("seldebug")
101                                .store(&SelectionCollectionTest::s_debugLevel)
102                                .description("Set selection debug level"));
103 }
104 #endif
105
106 SelectionCollectionTest::SelectionCollectionTest() : grps_(nullptr)
107 {
108     topManager_.requestFrame();
109     sc_.setDebugLevel(s_debugLevel);
110     sc_.setReferencePosType("atom");
111     sc_.setOutputPosType("atom");
112 }
113
114 SelectionCollectionTest::~SelectionCollectionTest()
115 {
116     if (grps_ != nullptr)
117     {
118         gmx_ana_indexgrps_free(grps_);
119     }
120 }
121
122 void SelectionCollectionTest::loadTopology(const char* filename)
123 {
124     topManager_.loadTopology(filename);
125     setTopology();
126 }
127
128 void SelectionCollectionTest::setTopology()
129 {
130     ASSERT_NO_THROW_GMX(sc_.setTopology(topManager_.topology(), -1));
131 }
132
133 void SelectionCollectionTest::loadIndexGroups(const char* filename)
134 {
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_);
139 }
140
141 /********************************************************************
142  * Test fixture for interactive SelectionCollection tests
143  */
144
145 class SelectionCollectionInteractiveTest : public SelectionCollectionTest
146 {
147 public:
148     SelectionCollectionInteractiveTest() : helper_(data_.rootChecker()) {}
149
150     void runTest(int count, bool bInteractive, const gmx::ArrayRef<const char* const>& input);
151
152     gmx::test::TestReferenceData     data_;
153     gmx::test::InteractiveTestHelper helper_;
154 };
155
156 void SelectionCollectionInteractiveTest::runTest(int  count,
157                                                  bool bInteractive,
158                                                  const gmx::ArrayRef<const char* const>& inputLines)
159 {
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();
167 }
168
169
170 /********************************************************************
171  * Test fixture for selection testing with reference data
172  */
173
174 class SelectionCollectionDataTest : public SelectionCollectionTest
175 {
176 public:
177     enum TestFlag : uint64_t
178     {
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
187     };
188     typedef gmx::FlagsTemplate<TestFlag> TestFlags;
189
190     SelectionCollectionDataTest() : checker_(data_.rootChecker()), count_(0), framenr_(0) {}
191
192     void setFlags(TestFlags flags) { flags_ = flags; }
193
194     void runParser(const gmx::ArrayRef<const char* const>& selections);
195     void runCompiler();
196     void runEvaluate();
197     void runEvaluateFinal();
198
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);
201
202     void checkCompiled();
203
204 private:
205     static void checkSelection(gmx::test::TestReferenceChecker* checker,
206                                const gmx::Selection&            sel,
207                                TestFlags                        flags);
208
209
210     gmx::test::TestReferenceData    data_;
211     gmx::test::TestReferenceChecker checker_;
212     size_t                          count_;
213     int                             framenr_;
214     TestFlags                       flags_;
215 };
216
217
218 void SelectionCollectionDataTest::checkSelection(gmx::test::TestReferenceChecker* checker,
219                                                  const gmx::Selection&            sel,
220                                                  TestFlags                        flags)
221 {
222     using gmx::test::TestReferenceChecker;
223
224     {
225         gmx::ArrayRef<const int> atoms = sel.atomIndices();
226         checker->checkSequence(atoms.begin(), atoms.end(), "Atoms");
227     }
228     if (flags.test(efTestPositionAtoms) || flags.test(efTestPositionCoordinates)
229         || flags.test(efTestPositionMapping) || flags.test(efTestPositionMasses)
230         || flags.test(efTestPositionCharges))
231     {
232         TestReferenceChecker compound(checker->checkSequenceCompound("Positions", sel.posCount()));
233         for (int i = 0; i < sel.posCount(); ++i)
234         {
235             TestReferenceChecker          poscompound(compound.checkCompound("Position", nullptr));
236             const gmx::SelectionPosition& p = sel.position(i);
237             if (flags.test(efTestPositionAtoms))
238             {
239                 gmx::ArrayRef<const int> atoms = p.atomIndices();
240                 poscompound.checkSequence(atoms.begin(), atoms.end(), "Atoms");
241             }
242             if (flags.test(efTestPositionCoordinates))
243             {
244                 poscompound.checkVector(p.x(), "Coordinates");
245             }
246             if (flags.test(efTestPositionMapping))
247             {
248                 poscompound.checkInteger(p.refId(), "RefId");
249                 poscompound.checkInteger(p.mappedId(), "MappedId");
250             }
251             if (flags.test(efTestPositionMasses))
252             {
253                 poscompound.checkReal(p.mass(), "Mass");
254             }
255             if (flags.test(efTestPositionCharges))
256             {
257                 poscompound.checkReal(p.charge(), "Charge");
258             }
259         }
260     }
261 }
262
263
264 void SelectionCollectionDataTest::runParser(const gmx::ArrayRef<const char* const>& selections)
265 {
266     using gmx::test::TestReferenceChecker;
267
268     TestReferenceChecker compound(checker_.checkCompound("ParsedSelections", "Parsed"));
269     size_t               varcount = 0;
270     count_                        = 0;
271     for (gmx::index i = 0; i < selections.ssize(); ++i)
272     {
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_)
278         {
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");
282             ++varcount;
283         }
284         else
285         {
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))
290             {
291                 selcompound.checkString(sel_[count_].name(), "Name");
292             }
293             selcompound.checkString(sel_[count_].selectionText(), "Text");
294             selcompound.checkBoolean(sel_[count_].isDynamic(), "Dynamic");
295             ++count_;
296         }
297     }
298 }
299
300
301 void SelectionCollectionDataTest::runCompiler()
302 {
303     ASSERT_NO_THROW_GMX(sc_.compile());
304     ASSERT_EQ(count_, sel_.size());
305     checkCompiled();
306 }
307
308
309 void SelectionCollectionDataTest::checkCompiled()
310 {
311     using gmx::test::TestReferenceChecker;
312     const TestFlags mask = ~TestFlags(efTestPositionCoordinates);
313
314     TestReferenceChecker compound(checker_.checkCompound("CompiledSelections", "Compiled"));
315     for (size_t i = 0; i < count_; ++i)
316     {
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))
321         {
322             selcompound.checkString(sel_[i].name(), "Name");
323         }
324         if (!flags_.test(efDontTestCompiledAtoms))
325         {
326             checkSelection(&selcompound, sel_[i], flags_ & mask);
327         }
328     }
329 }
330
331
332 void SelectionCollectionDataTest::runEvaluate()
333 {
334     using gmx::test::TestReferenceChecker;
335
336     ++framenr_;
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)
341     {
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_);
346     }
347 }
348
349
350 void SelectionCollectionDataTest::runEvaluateFinal()
351 {
352     ASSERT_NO_THROW_GMX(sc_.evaluateFinal(framenr_));
353     checkCompiled();
354 }
355
356
357 void SelectionCollectionDataTest::runTest(int natoms, const gmx::ArrayRef<const char* const>& selections)
358 {
359     ASSERT_NO_FATAL_FAILURE(runParser(selections));
360     ASSERT_NO_FATAL_FAILURE(setAtomCount(natoms));
361     ASSERT_NO_FATAL_FAILURE(runCompiler());
362 }
363
364
365 void SelectionCollectionDataTest::runTest(const char*                             filename,
366                                           const gmx::ArrayRef<const char* const>& selections)
367 {
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))
372     {
373         ASSERT_NO_FATAL_FAILURE(runEvaluate());
374         ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
375     }
376 }
377
378
379 /********************************************************************
380  * Tests for SelectionCollection functionality without reference data
381  */
382
383 TEST_F(SelectionCollectionTest, HandlesNoSelections)
384 {
385     EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
386     EXPECT_NO_THROW_GMX(sc_.compile());
387     EXPECT_FALSE(sc_.requiredTopologyProperties().hasAny());
388 }
389
390 TEST_F(SelectionCollectionTest, HandlesNoSelectionsWithDefaultPositionType)
391 {
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());
402 }
403
404 TEST_F(SelectionCollectionTest, HandlesVelocityAndForceRequests)
405 {
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());
421 }
422
423 TEST_F(SelectionCollectionTest, HandlesForceRequestForCenterOfGeometry)
424 {
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());
435 }
436
437 TEST_F(SelectionCollectionTest, ParsesSelectionsFromFile)
438 {
439     ASSERT_NO_THROW_GMX(
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());
445 }
446
447 TEST_F(SelectionCollectionTest, HandlesAtypicalWhitespace)
448 {
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());
454 }
455
456 TEST_F(SelectionCollectionTest, HandlesInvalidRegularExpressions)
457 {
458     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
459     EXPECT_THROW_GMX(
460             {
461                 sc_.parseFromString("resname ~ \"R[A\"");
462                 sc_.compile();
463             },
464             gmx::InvalidInputError);
465 }
466
467 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue)
468 {
469     EXPECT_THROW_GMX(sc_.parseFromString("mindist from atomnr 1 cutoff"), gmx::InvalidInputError);
470 }
471
472 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue2)
473 {
474     EXPECT_THROW_GMX(sc_.parseFromString("within 1 of"), gmx::InvalidInputError);
475 }
476
477 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue3)
478 {
479     EXPECT_THROW_GMX(sc_.parseFromString("within of atomnr 1"), gmx::InvalidInputError);
480 }
481
482 // TODO: Tests for more parser errors
483
484 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser1)
485 {
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);
489 }
490
491 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser2)
492 {
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);
496 }
497
498 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed1)
499 {
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);
504 }
505
506 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed2)
507 {
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);
512 }
513
514 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReference)
515 {
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);
521 }
522
523 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReferenceDelayed)
524 {
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);
532 }
533
534 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroup)
535 {
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);
539 }
540
541 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed)
542 {
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);
546 }
547
548 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed2)
549 {
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);
553 }
554
555 TEST_F(SelectionCollectionTest, RecoversFromMissingMoleculeInfo)
556 {
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);
560 }
561
562 TEST_F(SelectionCollectionTest, RecoversFromMissingAtomTypes)
563 {
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);
567 }
568
569 TEST_F(SelectionCollectionTest, RecoversFromMissingPDBInfo)
570 {
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);
574 }
575
576 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation)
577 {
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);
581 }
582
583 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation2)
584 {
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);
588 }
589
590 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation3)
591 {
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);
596 }
597
598 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets)
599 {
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);
605 }
606
607 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets2)
608 {
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);
615 }
616
617 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets3)
618 {
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);
626 }
627
628 TEST_F(SelectionCollectionTest, HandlesFramesWithTooSmallAtomSubsets4)
629 {
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);
635 }
636
637 // TODO: Tests for more evaluation errors
638
639
640 /********************************************************************
641  * Tests for retrieving selections.
642  */
643 TEST_F(SelectionCollectionTest, RetrieveValidSelection)
644 {
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());
649 }
650
651 TEST_F(SelectionCollectionTest, RetrieveInvalidSelection)
652 {
653     ASSERT_FALSE(sc_.selection("some invalid key").has_value());
654 }
655
656 /********************************************************************
657  * Tests for assignment/copying.
658  */
659 TEST_F(SelectionCollectionTest, CanCopyEmptyCollection)
660 {
661     EXPECT_NO_THROW_GMX(gmx::SelectionCollection sc2(sc_));
662 }
663
664 TEST_F(SelectionCollectionTest, CopiedSelectionListsAreHandledSeparately)
665 {
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());
677 }
678
679
680 /********************************************************************
681  * Tests for interactive selection input
682  */
683
684 TEST_F(SelectionCollectionInteractiveTest, HandlesBasicInput)
685 {
686     const char* const input[] = { "foo = resname RA", "resname RB", "\"Name\" resname RC" };
687     runTest(-1, true, input);
688 }
689
690 TEST_F(SelectionCollectionInteractiveTest, HandlesContinuation)
691 {
692     const char* const input[] = { "resname RB and \\", "resname RC" };
693     runTest(-1, true, input);
694 }
695
696 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInput)
697 {
698     const char* const input[] = { "foo = resname RA", "resname RA" };
699     runTest(1, true, input);
700 }
701
702 TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInput)
703 {
704     const char* const input[] = { "resname RA", "resname RB" };
705     runTest(2, true, input);
706 }
707
708 TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithGroups)
709 {
710     const char* const input[] = { "resname RA", "" };
711     loadIndexGroups("simple.ndx");
712     runTest(-1, true, input);
713 }
714
715 TEST_F(SelectionCollectionInteractiveTest, HandlesStatusWithExistingSelections)
716 {
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);
721 }
722
723 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputStatus)
724 {
725     const char* const input[] = { "foo = resname RA", "", "resname RB" };
726     runTest(1, true, input);
727 }
728
729 TEST_F(SelectionCollectionInteractiveTest, HandlesTwoSelectionInputStatus)
730 {
731     const char* const input[] = { "\"Sel\" resname RA", "", "resname RB" };
732     runTest(2, true, input);
733 }
734
735 TEST_F(SelectionCollectionInteractiveTest, HandlesMultiSelectionInputStatus)
736 {
737     const char* const input[] = { "\"Sel\" resname RA", "\"Sel2\" resname RB", "" };
738     runTest(-1, true, input);
739 }
740
741 TEST_F(SelectionCollectionInteractiveTest, HandlesNoFinalNewline)
742 {
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);
748 }
749
750 TEST_F(SelectionCollectionInteractiveTest, HandlesEmptySelections)
751 {
752     const char* const input[] = { "resname RA;", "; resname RB;;", " ", ";" };
753     runTest(-1, true, input);
754 }
755
756 TEST_F(SelectionCollectionInteractiveTest, HandlesMultipleSelectionsOnLine)
757 {
758     const char* const input[] = { "resname RA; resname RB and \\", "resname RC" };
759     runTest(2, true, input);
760 }
761
762 TEST_F(SelectionCollectionInteractiveTest, HandlesNoninteractiveInput)
763 {
764     const char* const input[] = { "foo = resname RA", "resname RB", "\"Name\" resname RC" };
765     runTest(-1, false, input);
766 }
767
768 TEST_F(SelectionCollectionInteractiveTest, HandlesSingleSelectionInputNoninteractively)
769 {
770     const char* const input[] = { "foo = resname RA", "resname RA" };
771     runTest(1, false, input);
772 }
773
774
775 /********************************************************************
776  * Tests for selection keywords
777  */
778
779 TEST_F(SelectionCollectionDataTest, HandlesAllNone)
780 {
781     static const char* const selections[] = { "all", "none" };
782     runTest(10, selections);
783 }
784
785 TEST_F(SelectionCollectionDataTest, HandlesAtomnr)
786 {
787     static const char* const selections[] = { "atomnr 1 to 3 6 to 8",
788                                               "atomnr 4 2 5 to 7",
789                                               "atomnr <= 5" };
790     runTest(10, selections);
791 }
792
793 TEST_F(SelectionCollectionDataTest, HandlesResnr)
794 {
795     static const char* const selections[] = { "resnr 1 2 5", "resid 4 to 3" };
796     runTest("simple.gro", selections);
797 }
798
799 TEST_F(SelectionCollectionDataTest, HandlesResIndex)
800 {
801     static const char* const selections[] = { "resindex 1 4", "residue 1 3" };
802     runTest("simple.pdb", selections);
803 }
804
805 TEST_F(SelectionCollectionDataTest, HandlesMolIndex)
806 {
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());
813 }
814
815 TEST_F(SelectionCollectionDataTest, HandlesAtomname)
816 {
817     static const char* const selections[] = { "name CB", "atomname S1 S2" };
818     runTest("simple.gro", selections);
819 }
820
821 TEST_F(SelectionCollectionDataTest, HandlesPdbAtomname)
822 {
823     static const char* const selections[] = {
824         "name HG21", "name 1HG2", "pdbname HG21 CB", "pdbatomname 1HG2"
825     };
826     runTest("simple.pdb", selections);
827 }
828
829
830 TEST_F(SelectionCollectionDataTest, HandlesAtomtype)
831 {
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());
839 }
840
841 TEST_F(SelectionCollectionDataTest, HandlesChain)
842 {
843     static const char* const selections[] = { "chain A", "chain B" };
844     runTest("simple.pdb", selections);
845 }
846
847 TEST_F(SelectionCollectionDataTest, HandlesMass)
848 {
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)
855     {
856         atoms.atom[i].m = 1.0 + i;
857     }
858     atoms.haveMass = TRUE;
859     ASSERT_NO_FATAL_FAILURE(setTopology());
860     ASSERT_NO_FATAL_FAILURE(runCompiler());
861 }
862
863 TEST_F(SelectionCollectionDataTest, HandlesCharge)
864 {
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)
870     {
871         atoms.atom[i].q = i / 10.0;
872     }
873     // Ensure exact representation of 0.5 is used, so that the test is
874     // reproducible.
875     atoms.atom[5].q  = 0.5;
876     atoms.haveCharge = TRUE;
877     ASSERT_NO_FATAL_FAILURE(setTopology());
878     ASSERT_NO_FATAL_FAILURE(runCompiler());
879 }
880
881 TEST_F(SelectionCollectionDataTest, HandlesAltLoc)
882 {
883     static const char* const selections[] = { "altloc \" \"", "altloc A" };
884     runTest("simple.pdb", selections);
885 }
886
887 TEST_F(SelectionCollectionDataTest, HandlesInsertCode)
888 {
889     static const char* const selections[] = { "insertcode \" \"", "insertcode A" };
890     runTest("simple.pdb", selections);
891 }
892
893 TEST_F(SelectionCollectionDataTest, HandlesOccupancy)
894 {
895     static const char* const selections[] = { "occupancy 1", "occupancy < .5" };
896     runTest("simple.pdb", selections);
897 }
898
899 TEST_F(SelectionCollectionDataTest, HandlesBeta)
900 {
901     static const char* const selections[] = { "beta 0", "beta >= 0.3" };
902     runTest("simple.pdb", selections);
903 }
904
905 TEST_F(SelectionCollectionDataTest, HandlesResname)
906 {
907     static const char* const selections[] = { "resname RA", "resname RB RC" };
908     runTest("simple.gro", selections);
909 }
910
911 TEST_F(SelectionCollectionDataTest, HandlesCoordinateKeywords)
912 {
913     static const char* const selections[] = { "x < 3", "y >= 3", "x {-1 to 2}" };
914     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
915     runTest("simple.gro", selections);
916 }
917
918
919 TEST_F(SelectionCollectionDataTest, HandlesSameResidue)
920 {
921     static const char* const selections[] = { "same residue as atomnr 1 4 12" };
922     runTest("simple.gro", selections);
923 }
924
925
926 TEST_F(SelectionCollectionDataTest, HandlesSameResidueName)
927 {
928     static const char* const selections[] = { "same resname as atomnr 1 14" };
929     runTest("simple.gro", selections);
930 }
931
932
933 TEST_F(SelectionCollectionDataTest, HandlesPositionKeywords)
934 {
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);
942 }
943
944
945 TEST_F(SelectionCollectionDataTest, HandlesDistanceKeyword)
946 {
947     static const char* const selections[] = { "distance from cog of resnr 1 < 2" };
948     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
949     runTest("simple.gro", selections);
950 }
951
952
953 TEST_F(SelectionCollectionDataTest, HandlesMinDistanceKeyword)
954 {
955     static const char* const selections[] = { "mindistance from resnr 1 < 2" };
956     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
957     runTest("simple.gro", selections);
958 }
959
960
961 TEST_F(SelectionCollectionDataTest, HandlesWithinKeyword)
962 {
963     static const char* const selections[] = { "within 1 of resnr 2" };
964     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
965     runTest("simple.gro", selections);
966 }
967
968
969 TEST_F(SelectionCollectionDataTest, HandlesInSolidAngleKeyword)
970 {
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"
975     };
976     setFlags(TestFlags() | efDontTestCompiledAtoms | efTestEvaluation);
977     runTest("sphere.gro", selections);
978 }
979
980
981 TEST_F(SelectionCollectionDataTest, HandlesPermuteModifier)
982 {
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);
989 }
990
991
992 TEST_F(SelectionCollectionDataTest, HandlesPlusModifier)
993 {
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"
998     };
999     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms
1000              | efTestPositionMapping);
1001     runTest("simple.gro", selections);
1002 }
1003
1004
1005 TEST_F(SelectionCollectionDataTest, HandlesMergeModifier)
1006 {
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"
1011     };
1012     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms
1013              | efTestPositionMapping);
1014     runTest("simple.gro", selections);
1015 }
1016
1017
1018 /********************************************************************
1019  * Tests for generic selection evaluation
1020  */
1021
1022 TEST_F(SelectionCollectionDataTest, ComputesMassesAndCharges)
1023 {
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)
1031     {
1032         atoms.atom[i].m = 1.0 + i / 100.0;
1033         atoms.atom[i].q = -(1.0 + i / 100.0);
1034     }
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());
1041 }
1042
1043 TEST_F(SelectionCollectionDataTest, ComputesMassesAndChargesWithoutTopology)
1044 {
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);
1048 }
1049
1050 TEST_F(SelectionCollectionDataTest, HandlesFramesWithAtomSubsets)
1051 {
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());
1063 }
1064
1065
1066 /********************************************************************
1067  * Tests for selection syntactic constructs
1068  */
1069
1070 TEST_F(SelectionCollectionDataTest, HandlesSelectionNames)
1071 {
1072     static const char* const selections[] = { "\"GroupSelection\" group \"GrpA\"",
1073                                               "\"DynamicSelection\" x < 5",
1074                                               "y < 3" };
1075     setFlags(TestFlags() | efTestSelectionNames);
1076     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1077     runTest(10, selections);
1078 }
1079
1080 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelections)
1081 {
1082     static const char* const selections[] = { "group \"GrpA\"",
1083                                               "GrpB",
1084                                               "1",
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);
1094 }
1095
1096 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelectionsDelayed)
1097 {
1098     static const char* const selections[] = {
1099         "group \"GrpA\"", "GrpB", "1", "group \"GrpB\" and resname RB"
1100     };
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());
1106 }
1107
1108 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelections)
1109 {
1110     static const char* const selections[] = { "foo = group \"GrpUnsorted\"",
1111                                               "group \"GrpUnsorted\"",
1112                                               "GrpUnsorted",
1113                                               "2",
1114                                               "res_cog of group \"GrpUnsorted\"",
1115                                               "group \"GrpUnsorted\" permute 2 1",
1116                                               "foo" };
1117     setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMapping | efTestSelectionNames);
1118     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
1119     runTest("simple.gro", selections);
1120 }
1121
1122 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelectionsDelayed)
1123 {
1124     static const char* const selections[] = { "foo = group \"GrpUnsorted\"",
1125                                               "group \"GrpUnsorted\"",
1126                                               "GrpUnsorted",
1127                                               "2",
1128                                               "res_cog of group \"GrpUnsorted\"",
1129                                               "group \"GrpUnsorted\" permute 2 1",
1130                                               "foo" };
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());
1135 }
1136
1137
1138 TEST_F(SelectionCollectionDataTest, HandlesConstantPositions)
1139 {
1140     static const char* const selections[] = { "[1, -2, 3.5]" };
1141     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionMapping);
1142     runTest("simple.gro", selections);
1143 }
1144
1145
1146 TEST_F(SelectionCollectionDataTest, HandlesConstantPositionsWithModifiers)
1147 {
1148     static const char* const selections[] = { "[0, 0, 0] plus [0, 1, 0]" };
1149     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionMapping);
1150     runTest("simple.gro", selections);
1151 }
1152
1153
1154 TEST_F(SelectionCollectionDataTest, HandlesWithinConstantPositions)
1155 {
1156     static const char* const selections[] = { "within 1 of [2, 1, 0]" };
1157     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1158     runTest("simple.gro", selections);
1159 }
1160
1161
1162 TEST_F(SelectionCollectionDataTest, HandlesOverlappingIntegerRanges)
1163 {
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));
1166 }
1167
1168
1169 TEST_F(SelectionCollectionDataTest, HandlesOverlappingRealRanges)
1170 {
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)
1177     {
1178         atoms.atom[i].q = i / 10.0 - 0.5;
1179     }
1180     atoms.haveCharge = TRUE;
1181     ASSERT_NO_FATAL_FAILURE(setTopology());
1182     ASSERT_NO_FATAL_FAILURE(runCompiler());
1183 }
1184
1185
1186 TEST_F(SelectionCollectionDataTest, HandlesForcedStringMatchingMode)
1187 {
1188     static const char* const selections[] = { "name = S1 \"C?\"", "name ? S1 \"C?\"" };
1189     runTest("simple.gro", selections);
1190 }
1191
1192
1193 TEST_F(SelectionCollectionDataTest, HandlesWildcardMatching)
1194 {
1195     static const char* const selections[] = { "name \"S?\"", "name ? \"S?\"" };
1196     runTest("simple.gro", selections);
1197 }
1198
1199
1200 TEST_F(SelectionCollectionDataTest, HandlesRegexMatching)
1201 {
1202     static const char* const selections[] = { "resname \"R[BD]\"", "resname ~ \"R[BD]\"" };
1203     runTest("simple.gro", selections);
1204 }
1205
1206
1207 TEST_F(SelectionCollectionDataTest, HandlesBasicBoolean)
1208 {
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"
1215     };
1216     runTest(10, selections);
1217 }
1218
1219
1220 TEST_F(SelectionCollectionDataTest, HandlesDynamicAtomValuedParameters)
1221 {
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)"
1225     };
1226     setFlags(TestFlags() | efTestEvaluation);
1227     runTest("simple.gro", selections);
1228 }
1229
1230
1231 TEST_F(SelectionCollectionDataTest, HandlesEmptySelectionWithUnevaluatedExpressions)
1232 {
1233     static const char* const selections[] = { "none and x > 2", "none and same resname as resnr 2" };
1234     runTest("simple.gro", selections);
1235 }
1236
1237
1238 TEST_F(SelectionCollectionDataTest, HandlesEmptyReferenceForSame)
1239 {
1240     static const char* const selections[] = { "same residue as none", "same resname as none" };
1241     runTest("simple.gro", selections);
1242 }
1243
1244
1245 TEST_F(SelectionCollectionDataTest, HandlesPositionModifiersForKeywords)
1246 {
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);
1250 }
1251
1252
1253 TEST_F(SelectionCollectionDataTest, HandlesPositionModifiersForMethods)
1254 {
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);
1259 }
1260
1261
1262 TEST_F(SelectionCollectionDataTest, HandlesKeywordOfPositions)
1263 {
1264     static const char* const selections[] = { "x < y of cog of resnr 2" };
1265     setFlags(TestFlags() | efTestEvaluation);
1266     runTest("simple.gro", selections);
1267 }
1268
1269 TEST_F(SelectionCollectionDataTest, HandlesKeywordOfPositionsInArithmetic)
1270 {
1271     static const char* const selections[] = { "x - y of cog of resnr 2 < 0" };
1272     setFlags(TestFlags() | efTestEvaluation);
1273     runTest("simple.gro", selections);
1274 }
1275
1276
1277 TEST_F(SelectionCollectionDataTest, HandlesNumericComparisons)
1278 {
1279     static const char* const selections[] = {
1280         "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1281     };
1282     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1283     runTest("simple.gro", selections);
1284 }
1285
1286
1287 TEST_F(SelectionCollectionDataTest, HandlesArithmeticExpressions)
1288 {
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);
1292 }
1293
1294
1295 TEST_F(SelectionCollectionDataTest, HandlesNumericVariables)
1296 {
1297     static const char* const selections[] = {
1298         "value = x + y", "value <= 4", "index = resnr", "index < 3"
1299     };
1300     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1301     runTest("simple.gro", selections);
1302 }
1303
1304
1305 TEST_F(SelectionCollectionDataTest, HandlesComplexNumericVariables)
1306 {
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"
1310     };
1311     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1312     runTest("simple.gro", selections);
1313 }
1314
1315
1316 TEST_F(SelectionCollectionDataTest, HandlesPositionVariables)
1317 {
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"
1321     };
1322     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1323     runTest("simple.gro", selections);
1324 }
1325
1326
1327 TEST_F(SelectionCollectionDataTest, HandlesPositionVariableInModifier)
1328 {
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);
1334 }
1335
1336
1337 TEST_F(SelectionCollectionDataTest, HandlesConstantPositionInVariable)
1338 {
1339     static const char* const selections[] = { "constpos = [1.0, 2.5, 0.5]",
1340                                               "constpos",
1341                                               "within 2 of constpos" };
1342     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates | efTestPositionAtoms);
1343     runTest("simple.gro", selections);
1344 }
1345
1346
1347 TEST_F(SelectionCollectionDataTest, HandlesNumericConstantsInVariables)
1348 {
1349     static const char* const selections[] = { "constint = 4",
1350                                               "constreal1 = 0.5",
1351                                               "constreal2 = 2.7",
1352                                               "resnr < constint",
1353                                               "x + constreal1 < constreal2" };
1354     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1355     runTest("simple.gro", selections);
1356 }
1357
1358
1359 /********************************************************************
1360  * Tests for complex boolean syntax
1361  */
1362
1363 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysis)
1364 {
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))"
1371     };
1372     runTest(10, selections);
1373 }
1374
1375
1376 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithVariables)
1377 {
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);
1383 }
1384
1385
1386 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithMoreVariables)
1387 {
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);
1395 }
1396
1397
1398 /********************************************************************
1399  * Tests for complex subexpression cases
1400  *
1401  * These tests use some knowledge of the implementation to trigger different
1402  * paths in the code.
1403  */
1404
1405 TEST_F(SelectionCollectionDataTest, HandlesUnusedVariables)
1406 {
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);
1412 }
1413
1414
1415 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithStaticEvaluationGroups)
1416 {
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);
1421 }
1422
1423
1424 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups)
1425 {
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"
1428     };
1429     runTest(10, selections);
1430 }
1431
1432
1433 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups2)
1434 {
1435     static const char* const selections[] = { "foo = atomnr 1 to 8 and x < 10",
1436                                               "atomnr 1 to 5 and y < 10 and foo",
1437                                               "foo" };
1438     setFlags(TestFlags() | efTestEvaluation);
1439     runTest("simple.gro", selections);
1440 }
1441
1442 /*******************************************************************
1443  * Tests for copy validation.
1444  *
1445  * These tests ensure that copies of a SelectionCollection behave as the original while being
1446  * independently evaluated.
1447  */
1448 TEST_F(SelectionCollectionDataTest, CopiedSelectionWorksPreCompilation)
1449 {
1450     static const char* const selections[] = {
1451         "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1452     };
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_)
1458     {
1459         selNames.emplace_back(sel.name());
1460     }
1461     // Swap copied selection with original and update selections, to reuse the testing machinery.
1462     gmx::SelectionCollection sc2(sc_);
1463     sc_ = sc2;
1464     std::vector<gmx::Selection> sel2;
1465     for (const std::string_view selName : selNames)
1466     {
1467         std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1468         ASSERT_TRUE(maybeSel.has_value());
1469         sel2.push_back(*maybeSel);
1470     }
1471     sel_ = std::move(sel2);
1472
1473     ASSERT_NO_FATAL_FAILURE(runCompiler());
1474     ASSERT_NO_FATAL_FAILURE(runEvaluate());
1475     ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1476 }
1477
1478 TEST_F(SelectionCollectionDataTest, CopiedSelectionWorksPostCompilation)
1479 {
1480     static const char* const selections[] = {
1481         "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1482     };
1483     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1484     ASSERT_NO_FATAL_FAILURE(runParser(selections));
1485     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1486
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_)
1491     {
1492         selNames.emplace_back(sel.name());
1493     }
1494     gmx::SelectionCollection sc2(sc_);
1495     sc_ = sc2;
1496     std::vector<gmx::Selection> sel2;
1497     for (const std::string_view selName : selNames)
1498     {
1499         std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1500         ASSERT_TRUE(maybeSel.has_value());
1501         sel2.push_back(*maybeSel);
1502     }
1503     sel_ = std::move(sel2);
1504
1505     ASSERT_NO_FATAL_FAILURE(runEvaluate());
1506     ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1507 }
1508
1509 TEST_F(SelectionCollectionDataTest, CopiedSelectionsAreIndependent)
1510 {
1511     static const char* const selections[] = {
1512         "x > 2", "2 < x", "y > resnr", "resnr < 2.5", "2.5 > resnr"
1513     };
1514     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1515     ASSERT_NO_FATAL_FAILURE(runParser(selections));
1516     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
1517
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));
1522
1523     ASSERT_NO_FATAL_FAILURE(runEvaluate());
1524     ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
1525 }
1526
1527 TEST_F(SelectionCollectionDataTest, CopiedSelectionWithIndexPostCompilation)
1528 {
1529     static const char* const selections[] = { "group \"GrpA\"",
1530                                               "GrpB",
1531                                               "1",
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_)
1543     {
1544         selNames.emplace_back(sel.name());
1545     }
1546     gmx::SelectionCollection sc2(sc_);
1547     sc_ = sc2;
1548     std::vector<gmx::Selection> sel2;
1549     for (const std::string_view selName : selNames)
1550     {
1551         std::optional<gmx::Selection> maybeSel = sc_.selection(selName);
1552         ASSERT_TRUE(maybeSel.has_value());
1553         sel2.push_back(*maybeSel);
1554     }
1555     sel_ = std::move(sel2);
1556     checkCompiled();
1557 }
1558
1559 } // namespace