Merge release-5-0 into master
[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,2011,2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal \file
36  * \brief
37  * Tests selection parsing and compilation.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_selection
41  */
42 #include <gtest/gtest.h>
43
44 #include "gromacs/options/basicoptions.h"
45 #include "gromacs/options/options.h"
46 #include "gromacs/selection/indexutil.h"
47 #include "gromacs/selection/selectioncollection.h"
48 #include "gromacs/selection/selection.h"
49 #include "gromacs/topology/topology.h"
50 #include "gromacs/utility/arrayref.h"
51 #include "gromacs/utility/exceptions.h"
52 #include "gromacs/utility/flags.h"
53 #include "gromacs/utility/gmxregex.h"
54 #include "gromacs/utility/stringutil.h"
55
56 #include "testutils/refdata.h"
57 #include "testutils/testasserts.h"
58 #include "testutils/testfilemanager.h"
59 #include "testutils/testoptions.h"
60
61 #include "toputils.h"
62
63 namespace
64 {
65
66 /********************************************************************
67  * Test fixture for selection testing
68  */
69
70 class SelectionCollectionTest : public ::testing::Test
71 {
72     public:
73         static int               s_debugLevel;
74
75         SelectionCollectionTest();
76         ~SelectionCollectionTest();
77
78         void setAtomCount(int natoms)
79         {
80             ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, natoms));
81         }
82         void loadTopology(const char *filename);
83         void setTopology();
84         void loadIndexGroups(const char *filename);
85
86         gmx::test::TopologyManager  topManager_;
87         gmx::SelectionCollection    sc_;
88         gmx::SelectionList          sel_;
89         t_topology                 *top_;
90         t_trxframe                 *frame_;
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()
107     : top_(NULL), frame_(NULL), grps_(NULL)
108 {
109     topManager_.requestFrame();
110     sc_.setDebugLevel(s_debugLevel);
111     sc_.setReferencePosType("atom");
112     sc_.setOutputPosType("atom");
113 }
114
115 SelectionCollectionTest::~SelectionCollectionTest()
116 {
117     if (grps_ != NULL)
118     {
119         gmx_ana_indexgrps_free(grps_);
120     }
121 }
122
123 void
124 SelectionCollectionTest::loadTopology(const char *filename)
125 {
126     topManager_.loadTopology(filename);
127     setTopology();
128 }
129
130 void
131 SelectionCollectionTest::setTopology()
132 {
133     top_   = topManager_.topology();
134     frame_ = topManager_.frame();
135
136     ASSERT_NO_THROW_GMX(sc_.setTopology(top_, -1));
137 }
138
139 void
140 SelectionCollectionTest::loadIndexGroups(const char *filename)
141 {
142     GMX_RELEASE_ASSERT(grps_ == NULL,
143                        "External groups can only be loaded once");
144     std::string fullpath =
145         gmx::test::TestFileManager::getInputFilePath(filename);
146     gmx_ana_indexgrps_init(&grps_, NULL, fullpath.c_str());
147     sc_.setIndexGroups(grps_);
148 }
149
150
151 /********************************************************************
152  * Test fixture for selection testing with reference data
153  */
154
155 class SelectionCollectionDataTest : public SelectionCollectionTest
156 {
157     public:
158         enum TestFlag
159         {
160             efTestEvaluation            = 1<<0,
161             efTestPositionAtoms         = 1<<1,
162             efTestPositionCoordinates   = 1<<2,
163             efTestPositionMapping       = 1<<3,
164             efTestPositionMasses        = 1<<4,
165             efTestPositionCharges       = 1<<5,
166             efTestSelectionNames        = 1<<6,
167             efDontTestCompiledAtoms     = 1<<8
168         };
169         typedef gmx::FlagsTemplate<TestFlag> TestFlags;
170
171         SelectionCollectionDataTest()
172             : checker_(data_.rootChecker()), count_(0), framenr_(0)
173         {
174         }
175
176         void setFlags(TestFlags flags) { flags_ = flags; }
177
178         void runParser(const gmx::ConstArrayRef<const char *> &selections);
179         void runCompiler();
180         void runEvaluate();
181         void runEvaluateFinal();
182
183         void runTest(int                                     natoms,
184                      const gmx::ConstArrayRef<const char *> &selections);
185         void runTest(const char                             *filename,
186                      const gmx::ConstArrayRef<const char *> &selections);
187
188     private:
189         static void checkSelection(gmx::test::TestReferenceChecker *checker,
190                                    const gmx::Selection &sel, TestFlags flags);
191
192         void checkCompiled();
193
194         gmx::test::TestReferenceData    data_;
195         gmx::test::TestReferenceChecker checker_;
196         size_t                          count_;
197         int                             framenr_;
198         TestFlags                       flags_;
199 };
200
201
202 void
203 SelectionCollectionDataTest::checkSelection(
204         gmx::test::TestReferenceChecker *checker,
205         const gmx::Selection &sel, TestFlags flags)
206 {
207     using gmx::test::TestReferenceChecker;
208
209     {
210         gmx::ConstArrayRef<int> atoms = sel.atomIndices();
211         checker->checkSequence(atoms.begin(), atoms.end(), "Atoms");
212     }
213     if (flags.test(efTestPositionAtoms)
214         || flags.test(efTestPositionCoordinates)
215         || flags.test(efTestPositionMapping)
216         || flags.test(efTestPositionMasses)
217         || flags.test(efTestPositionCharges))
218     {
219         TestReferenceChecker compound(
220                 checker->checkSequenceCompound("Positions", sel.posCount()));
221         for (int i = 0; i < sel.posCount(); ++i)
222         {
223             TestReferenceChecker          poscompound(compound.checkCompound("Position", NULL));
224             const gmx::SelectionPosition &p = sel.position(i);
225             if (flags.test(efTestPositionAtoms))
226             {
227                 gmx::ConstArrayRef<int> atoms = p.atomIndices();
228                 poscompound.checkSequence(atoms.begin(), atoms.end(), "Atoms");
229             }
230             if (flags.test(efTestPositionCoordinates))
231             {
232                 poscompound.checkVector(p.x(), "Coordinates");
233             }
234             if (flags.test(efTestPositionMapping))
235             {
236                 poscompound.checkInteger(p.refId(), "RefId");
237                 poscompound.checkInteger(p.mappedId(), "MappedId");
238             }
239             if (flags.test(efTestPositionMasses))
240             {
241                 poscompound.checkReal(p.mass(), "Mass");
242             }
243             if (flags.test(efTestPositionCharges))
244             {
245                 poscompound.checkReal(p.charge(), "Charge");
246             }
247         }
248     }
249 }
250
251
252 void
253 SelectionCollectionDataTest::runParser(
254         const gmx::ConstArrayRef<const char *> &selections)
255 {
256     using gmx::test::TestReferenceChecker;
257
258     TestReferenceChecker compound(checker_.checkCompound("ParsedSelections", "Parsed"));
259     size_t               varcount = 0;
260     count_ = 0;
261     for (size_t i = 0; i < selections.size(); ++i)
262     {
263         SCOPED_TRACE(std::string("Parsing selection \"")
264                      + selections[i] + "\"");
265         gmx::SelectionList result;
266         ASSERT_NO_THROW_GMX(result = sc_.parseFromString(selections[i]));
267         sel_.insert(sel_.end(), result.begin(), result.end());
268         if (sel_.size() == count_)
269         {
270             std::string          id = gmx::formatString("Variable%d", static_cast<int>(varcount + 1));
271             TestReferenceChecker varcompound(
272                     compound.checkCompound("ParsedVariable", id.c_str()));
273             varcompound.checkString(selections[i], "Input");
274             ++varcount;
275         }
276         else
277         {
278             std::string          id = gmx::formatString("Selection%d", static_cast<int>(count_ + 1));
279             TestReferenceChecker selcompound(
280                     compound.checkCompound("ParsedSelection", id.c_str()));
281             selcompound.checkString(selections[i], "Input");
282             if (flags_.test(efTestSelectionNames))
283             {
284                 selcompound.checkString(sel_[count_].name(), "Name");
285             }
286             selcompound.checkString(sel_[count_].selectionText(), "Text");
287             selcompound.checkBoolean(sel_[count_].isDynamic(), "Dynamic");
288             ++count_;
289         }
290     }
291 }
292
293
294 void
295 SelectionCollectionDataTest::runCompiler()
296 {
297     ASSERT_NO_THROW_GMX(sc_.compile());
298     ASSERT_EQ(count_, sel_.size());
299     checkCompiled();
300 }
301
302
303 void
304 SelectionCollectionDataTest::checkCompiled()
305 {
306     using gmx::test::TestReferenceChecker;
307     const TestFlags      mask = ~TestFlags(efTestPositionCoordinates);
308
309     TestReferenceChecker compound(checker_.checkCompound("CompiledSelections", "Compiled"));
310     for (size_t i = 0; i < count_; ++i)
311     {
312         SCOPED_TRACE(std::string("Checking selection \"") +
313                      sel_[i].selectionText() + "\"");
314         std::string          id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
315         TestReferenceChecker selcompound(
316                 compound.checkCompound("Selection", id.c_str()));
317         if (flags_.test(efTestSelectionNames))
318         {
319             selcompound.checkString(sel_[i].name(), "Name");
320         }
321         if (!flags_.test(efDontTestCompiledAtoms))
322         {
323             checkSelection(&selcompound, sel_[i], flags_ & mask);
324         }
325     }
326 }
327
328
329 void
330 SelectionCollectionDataTest::runEvaluate()
331 {
332     using gmx::test::TestReferenceChecker;
333
334     ++framenr_;
335     ASSERT_NO_THROW_GMX(sc_.evaluate(frame_, NULL));
336     std::string          frame = gmx::formatString("Frame%d", framenr_);
337     TestReferenceChecker compound(
338             checker_.checkCompound("EvaluatedSelections", frame.c_str()));
339     for (size_t i = 0; i < count_; ++i)
340     {
341         SCOPED_TRACE(std::string("Checking selection \"") +
342                      sel_[i].selectionText() + "\"");
343         std::string          id = gmx::formatString("Selection%d", static_cast<int>(i + 1));
344         TestReferenceChecker selcompound(
345                 compound.checkCompound("Selection", id.c_str()));
346         checkSelection(&selcompound, sel_[i], flags_);
347     }
348 }
349
350
351 void
352 SelectionCollectionDataTest::runEvaluateFinal()
353 {
354     ASSERT_NO_THROW_GMX(sc_.evaluateFinal(framenr_));
355     checkCompiled();
356 }
357
358
359 void
360 SelectionCollectionDataTest::runTest(
361         int natoms, const gmx::ConstArrayRef<const char *> &selections)
362 {
363     ASSERT_NO_FATAL_FAILURE(runParser(selections));
364     ASSERT_NO_FATAL_FAILURE(setAtomCount(natoms));
365     ASSERT_NO_FATAL_FAILURE(runCompiler());
366 }
367
368
369 void
370 SelectionCollectionDataTest::runTest(
371         const char *filename, const gmx::ConstArrayRef<const char *> &selections)
372 {
373     ASSERT_NO_FATAL_FAILURE(runParser(selections));
374     ASSERT_NO_FATAL_FAILURE(loadTopology(filename));
375     ASSERT_NO_FATAL_FAILURE(runCompiler());
376     if (flags_.test(efTestEvaluation))
377     {
378         ASSERT_NO_FATAL_FAILURE(runEvaluate());
379         ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
380     }
381 }
382
383
384 /********************************************************************
385  * Tests for SelectionCollection functionality without reference data
386  */
387
388 TEST_F(SelectionCollectionTest, HandlesNoSelections)
389 {
390     EXPECT_FALSE(sc_.requiresTopology());
391     EXPECT_NO_THROW_GMX(sc_.compile());
392 }
393
394 TEST_F(SelectionCollectionTest, HandlesVelocityAndForceRequests)
395 {
396     ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr 1 to 10; none"));
397     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
398     ASSERT_EQ(2U, sel_.size());
399     ASSERT_NO_THROW_GMX(sel_[0].setEvaluateVelocities(true));
400     ASSERT_NO_THROW_GMX(sel_[1].setEvaluateVelocities(true));
401     ASSERT_NO_THROW_GMX(sel_[0].setEvaluateForces(true));
402     ASSERT_NO_THROW_GMX(sel_[1].setEvaluateForces(true));
403     ASSERT_NO_THROW_GMX(sc_.compile());
404     EXPECT_TRUE(sel_[0].hasVelocities());
405     EXPECT_TRUE(sel_[1].hasVelocities());
406     EXPECT_TRUE(sel_[0].hasForces());
407     EXPECT_TRUE(sel_[1].hasForces());
408 }
409
410 TEST_F(SelectionCollectionTest, ParsesSelectionsFromFile)
411 {
412     ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromFile(
413                                     gmx::test::TestFileManager::getInputFilePath("selfile.dat")));
414     // These should match the contents of selfile.dat
415     ASSERT_EQ(2U, sel_.size());
416     EXPECT_STREQ("resname RA RB", sel_[0].selectionText());
417     EXPECT_STREQ("resname RB RC", sel_[1].selectionText());
418 }
419
420 TEST_F(SelectionCollectionTest, HandlesAtypicalWhitespace)
421 {
422     ASSERT_NO_THROW_GMX(sel_ = sc_.parseFromString("atomnr\n1\r\nto\t10;\vatomnr 3\f to 14\r"));
423     ASSERT_EQ(2U, sel_.size());
424     EXPECT_STREQ("atomnr 1 to 10", sel_[0].selectionText());
425     // TODO: Get rid of the trailing whitespace.
426     EXPECT_STREQ("atomnr 3 to 14 ", sel_[1].selectionText());
427 }
428
429 TEST_F(SelectionCollectionTest, HandlesInvalidRegularExpressions)
430 {
431     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
432     EXPECT_THROW_GMX({
433                          sc_.parseFromString("resname ~ \"R[A\"");
434                          sc_.compile();
435                      }, gmx::InvalidInputError);
436 }
437
438 TEST_F(SelectionCollectionTest, HandlesUnsupportedRegularExpressions)
439 {
440     if (!gmx::Regex::isSupported())
441     {
442         ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
443         EXPECT_THROW_GMX({
444                              sc_.parseFromString("resname \"R[AD]\"");
445                              sc_.compile();
446                          }, gmx::InvalidInputError);
447     }
448 }
449
450 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue)
451 {
452     EXPECT_THROW_GMX(sc_.parseFromString("mindist from atomnr 1 cutoff"),
453                      gmx::InvalidInputError);
454 }
455
456 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue2)
457 {
458     EXPECT_THROW_GMX(sc_.parseFromString("within 1 of"),
459                      gmx::InvalidInputError);
460 }
461
462 TEST_F(SelectionCollectionTest, HandlesMissingMethodParamValue3)
463 {
464     EXPECT_THROW_GMX(sc_.parseFromString("within of atomnr 1"),
465                      gmx::InvalidInputError);
466 }
467
468 // TODO: Tests for more parser errors
469
470 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser1)
471 {
472     ASSERT_NO_THROW_GMX(sc_.setIndexGroups(NULL));
473     EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
474     EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
475 }
476
477 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceParser2)
478 {
479     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
480     EXPECT_THROW_GMX(sc_.parseFromString("group \"foo\""), gmx::InconsistentInputError);
481     EXPECT_THROW_GMX(sc_.parseFromString("4"), gmx::InconsistentInputError);
482 }
483
484 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed1)
485 {
486     ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"foo\""));
487     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
488     EXPECT_THROW_GMX(sc_.setIndexGroups(NULL), gmx::InconsistentInputError);
489     EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
490 }
491
492 TEST_F(SelectionCollectionTest, HandlesUnknownGroupReferenceDelayed2)
493 {
494     ASSERT_NO_THROW_GMX(sc_.parseFromString("group 4; group \"foo\""));
495     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
496     EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
497     EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
498 }
499
500 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReference)
501 {
502     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
503     EXPECT_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""),
504                      gmx::InconsistentInputError);
505     EXPECT_THROW_GMX(sc_.parseFromString("group 2 or atomnr 2 to 5"),
506                      gmx::InconsistentInputError);
507     EXPECT_THROW_GMX(sc_.parseFromString("within 1 of group 2"),
508                      gmx::InconsistentInputError);
509 }
510
511 TEST_F(SelectionCollectionTest, HandlesUnsortedGroupReferenceDelayed)
512 {
513     ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group \"GrpUnsorted\""));
514     ASSERT_NO_THROW_GMX(sc_.parseFromString("atomnr 1 to 3 and group 2"));
515     EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
516     // TODO: Add a separate check in the selection compiler for a safer API
517     // (makes sense in the future if the compiler needs the information for
518     // other purposes as well).
519     // EXPECT_THROW_GMX(sc_.compile(), gmx::APIError);
520 }
521
522 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroup)
523 {
524     ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, 5));
525     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
526     EXPECT_THROW_GMX(sc_.parseFromString("group \"GrpB\""), gmx::InconsistentInputError);
527 }
528
529 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed)
530 {
531     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
532     ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
533     EXPECT_THROW_GMX(sc_.setTopology(NULL, 5), gmx::InconsistentInputError);
534 }
535
536 TEST_F(SelectionCollectionTest, HandlesOutOfRangeAtomIndexInGroupDelayed2)
537 {
538     ASSERT_NO_THROW_GMX(sc_.setTopology(NULL, 5));
539     ASSERT_NO_THROW_GMX(sc_.parseFromString("group \"GrpB\""));
540     EXPECT_THROW_GMX(loadIndexGroups("simple.ndx"), gmx::InconsistentInputError);
541 }
542
543 TEST_F(SelectionCollectionTest, RecoversFromMissingMoleculeInfo)
544 {
545     ASSERT_NO_THROW_GMX(sc_.parseFromString("molindex 1 to 5"));
546     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
547     EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
548 }
549
550 TEST_F(SelectionCollectionTest, RecoversFromMissingAtomTypes)
551 {
552     ASSERT_NO_THROW_GMX(sc_.parseFromString("type CA"));
553     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
554     EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
555 }
556
557 TEST_F(SelectionCollectionTest, RecoversFromMissingPDBInfo)
558 {
559     ASSERT_NO_THROW_GMX(sc_.parseFromString("altloc A"));
560     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
561     EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
562 }
563
564 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation)
565 {
566     ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 1 1"));
567     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
568     EXPECT_THROW_GMX(sc_.compile(), gmx::InvalidInputError);
569 }
570
571 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation2)
572 {
573     ASSERT_NO_THROW_GMX(sc_.parseFromString("all permute 3 2 1"));
574     ASSERT_NO_FATAL_FAILURE(setAtomCount(10));
575     EXPECT_THROW_GMX(sc_.compile(), gmx::InconsistentInputError);
576 }
577
578 TEST_F(SelectionCollectionTest, RecoversFromInvalidPermutation3)
579 {
580     ASSERT_NO_THROW_GMX(sc_.parseFromString("x < 1.5 permute 3 2 1"));
581     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
582     ASSERT_NO_THROW_GMX(sc_.compile());
583     EXPECT_THROW_GMX(sc_.evaluate(frame_, NULL), gmx::InconsistentInputError);
584 }
585
586 // TODO: Tests for evaluation errors
587
588
589 /********************************************************************
590  * Tests for selection keywords
591  */
592
593 TEST_F(SelectionCollectionDataTest, HandlesAllNone)
594 {
595     static const char * const selections[] = {
596         "all",
597         "none"
598     };
599     runTest(10, selections);
600 }
601
602 TEST_F(SelectionCollectionDataTest, HandlesAtomnr)
603 {
604     static const char * const selections[] = {
605         "atomnr 1 to 3 6 to 8",
606         "atomnr 4 2 5 to 7",
607         "atomnr <= 5"
608     };
609     runTest(10, selections);
610 }
611
612 TEST_F(SelectionCollectionDataTest, HandlesResnr)
613 {
614     static const char * const selections[] = {
615         "resnr 1 2 5",
616         "resid 4 to 3"
617     };
618     runTest("simple.gro", selections);
619 }
620
621 TEST_F(SelectionCollectionDataTest, HandlesResIndex)
622 {
623     static const char * const selections[] = {
624         "resindex 1 4",
625         "residue 1 3"
626     };
627     runTest("simple.pdb", selections);
628 }
629
630 TEST_F(SelectionCollectionDataTest, HandlesMolIndex)
631 {
632     static const char * const selections[] = {
633         "molindex 1 4",
634         "molecule 2 3 5"
635     };
636     ASSERT_NO_FATAL_FAILURE(runParser(selections));
637     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
638     topManager_.initUniformMolecules(3);
639     ASSERT_NO_FATAL_FAILURE(runCompiler());
640 }
641
642 TEST_F(SelectionCollectionDataTest, HandlesAtomname)
643 {
644     static const char * const selections[] = {
645         "name CB",
646         "atomname S1 S2"
647     };
648     runTest("simple.gro", selections);
649 }
650
651 TEST_F(SelectionCollectionDataTest, HandlesPdbAtomname)
652 {
653     static const char * const selections[] = {
654         "name HG21",
655         "name 1HG2",
656         "pdbname HG21 CB",
657         "pdbatomname 1HG2"
658     };
659     runTest("simple.pdb", selections);
660 }
661
662
663 TEST_F(SelectionCollectionDataTest, HandlesAtomtype)
664 {
665     static const char * const selections[] = {
666         "atomtype CA"
667     };
668     ASSERT_NO_FATAL_FAILURE(runParser(selections));
669     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
670     const char *const types[] = { "CA", "SA", "SB" };
671     topManager_.initAtomTypes(types);
672     ASSERT_NO_FATAL_FAILURE(runCompiler());
673 }
674
675 TEST_F(SelectionCollectionDataTest, HandlesChain)
676 {
677     static const char * const selections[] = {
678         "chain A",
679         "chain B"
680     };
681     runTest("simple.pdb", selections);
682 }
683
684 TEST_F(SelectionCollectionDataTest, HandlesMass)
685 {
686     static const char * const selections[] = {
687         "mass > 5"
688     };
689     ASSERT_NO_FATAL_FAILURE(runParser(selections));
690     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
691     for (int i = 0; i < top_->atoms.nr; ++i)
692     {
693         top_->atoms.atom[i].m = 1.0 + i;
694     }
695     ASSERT_NO_FATAL_FAILURE(runCompiler());
696 }
697
698 TEST_F(SelectionCollectionDataTest, HandlesCharge)
699 {
700     static const char * const selections[] = {
701         "charge < 0.5"
702     };
703     ASSERT_NO_FATAL_FAILURE(runParser(selections));
704     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
705     for (int i = 0; i < top_->atoms.nr; ++i)
706     {
707         top_->atoms.atom[i].q = i / 10.0;
708     }
709     ASSERT_NO_FATAL_FAILURE(runCompiler());
710 }
711
712 TEST_F(SelectionCollectionDataTest, HandlesAltLoc)
713 {
714     static const char * const selections[] = {
715         "altloc \" \"",
716         "altloc A"
717     };
718     runTest("simple.pdb", selections);
719 }
720
721 TEST_F(SelectionCollectionDataTest, HandlesInsertCode)
722 {
723     static const char * const selections[] = {
724         "insertcode \" \"",
725         "insertcode A"
726     };
727     runTest("simple.pdb", selections);
728 }
729
730 TEST_F(SelectionCollectionDataTest, HandlesOccupancy)
731 {
732     static const char * const selections[] = {
733         "occupancy 1",
734         "occupancy < .5"
735     };
736     runTest("simple.pdb", selections);
737 }
738
739 TEST_F(SelectionCollectionDataTest, HandlesBeta)
740 {
741     static const char * const selections[] = {
742         "beta 0",
743         "beta >= 0.3"
744     };
745     runTest("simple.pdb", selections);
746 }
747
748 TEST_F(SelectionCollectionDataTest, HandlesResname)
749 {
750     static const char * const selections[] = {
751         "resname RA",
752         "resname RB RC"
753     };
754     runTest("simple.gro", selections);
755 }
756
757 TEST_F(SelectionCollectionDataTest, HandlesCoordinateKeywords)
758 {
759     static const char * const selections[] = {
760         "x < 3",
761         "y >= 3",
762         "x {-1 to 2}"
763     };
764     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
765     runTest("simple.gro", selections);
766 }
767
768
769 TEST_F(SelectionCollectionDataTest, HandlesSameResidue)
770 {
771     static const char * const selections[] = {
772         "same residue as atomnr 1 4 12"
773     };
774     runTest("simple.gro", selections);
775 }
776
777
778 TEST_F(SelectionCollectionDataTest, HandlesSameResidueName)
779 {
780     static const char * const selections[] = {
781         "same resname as atomnr 1 14"
782     };
783     runTest("simple.gro", selections);
784 }
785
786
787 TEST_F(SelectionCollectionDataTest, HandlesPositionKeywords)
788 {
789     static const char * const selections[] = {
790         "cog of resnr 1 3",
791         "res_cog of name CB and resnr 1 3",
792         "whole_res_cog of name CB and resnr 1 3",
793         "part_res_cog of x < 3",
794         "dyn_res_cog of x < 3"
795     };
796     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
797              | efTestPositionAtoms);
798     runTest("simple.gro", selections);
799 }
800
801
802 TEST_F(SelectionCollectionDataTest, HandlesDistanceKeyword)
803 {
804     static const char * const selections[] = {
805         "distance from cog of resnr 1 < 2"
806     };
807     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
808     runTest("simple.gro", selections);
809 }
810
811
812 TEST_F(SelectionCollectionDataTest, HandlesMinDistanceKeyword)
813 {
814     static const char * const selections[] = {
815         "mindistance from resnr 1 < 2"
816     };
817     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
818     runTest("simple.gro", selections);
819 }
820
821
822 TEST_F(SelectionCollectionDataTest, HandlesWithinKeyword)
823 {
824     static const char * const selections[] = {
825         "within 1 of resnr 2"
826     };
827     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
828     runTest("simple.gro", selections);
829 }
830
831
832 TEST_F(SelectionCollectionDataTest, HandlesInSolidAngleKeyword)
833 {
834     // Both of these should evaluate to empty on a correct implementation.
835     static const char * const selections[] = {
836         "resname TP and not insolidangle center cog of resname C span resname R cutoff 20",
837         "resname TN and insolidangle center cog of resname C span resname R cutoff 20"
838     };
839     setFlags(TestFlags() | efDontTestCompiledAtoms | efTestEvaluation);
840     runTest("sphere.gro", selections);
841 }
842
843
844 TEST_F(SelectionCollectionDataTest, HandlesPermuteModifier)
845 {
846     static const char * const selections[] = {
847         "all permute 3 1 2",
848         "res_cog of resnr 1 to 4 permute 2 1",
849         "name CB S1 and res_cog x < 3 permute 2 1"
850     };
851     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
852              | efTestPositionAtoms | efTestPositionMapping);
853     runTest("simple.gro", selections);
854 }
855
856
857 TEST_F(SelectionCollectionDataTest, HandlesPlusModifier)
858 {
859     static const char * const selections[] = {
860         "name S2 plus name S1",
861         "res_cog of resnr 2 plus res_cog of resnr 1 plus res_cog of resnr 3",
862         "name S1 and y < 3 plus res_cog of x < 2.5"
863     };
864     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
865              | efTestPositionAtoms | efTestPositionMapping);
866     runTest("simple.gro", selections);
867 }
868
869
870 TEST_F(SelectionCollectionDataTest, HandlesMergeModifier)
871 {
872     static const char * const selections[] = {
873         "name S2 merge name S1",
874         "resnr 1 2 and name S2 merge resnr 1 2 and name S1 merge res_cog of resnr 1 2",
875         "name S1 and x < 2.5 merge res_cog of x < 2.5"
876     };
877     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
878              | efTestPositionAtoms | efTestPositionMapping);
879     runTest("simple.gro", selections);
880 }
881
882
883 /********************************************************************
884  * Tests for generic selection evaluation
885  */
886
887 TEST_F(SelectionCollectionDataTest, ComputesMassesAndCharges)
888 {
889     static const char * const selections[] = {
890         "name CB",
891         "y > 2",
892         "res_cog of y > 2"
893     };
894     setFlags(TestFlags() | efTestEvaluation | efTestPositionAtoms
895              | efTestPositionMasses | efTestPositionCharges);
896     ASSERT_NO_FATAL_FAILURE(runParser(selections));
897     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
898     for (int i = 0; i < top_->atoms.nr; ++i)
899     {
900         top_->atoms.atom[i].m =   1.0 + i / 100.0;
901         top_->atoms.atom[i].q = -(1.0 + i / 100.0);
902     }
903     ASSERT_NO_FATAL_FAILURE(runCompiler());
904     ASSERT_NO_FATAL_FAILURE(runEvaluate());
905     ASSERT_NO_FATAL_FAILURE(runEvaluateFinal());
906 }
907
908 TEST_F(SelectionCollectionDataTest, ComputesMassesAndChargesWithoutTopology)
909 {
910     static const char * const selections[] = {
911         "atomnr 1 to 3 8 to 9",
912         "y > 2",
913         "cog of (y > 2)"
914     };
915     setFlags(TestFlags() | efTestPositionAtoms
916              | efTestPositionMasses | efTestPositionCharges);
917     runTest(10, selections);
918 }
919
920
921 /********************************************************************
922  * Tests for selection syntactic constructs
923  */
924
925 TEST_F(SelectionCollectionDataTest, HandlesSelectionNames)
926 {
927     static const char * const selections[] = {
928         "\"GroupSelection\" group \"GrpA\"",
929         "\"DynamicSelection\" x < 5",
930         "y < 3"
931     };
932     setFlags(TestFlags() | efTestSelectionNames);
933     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
934     runTest(10, selections);
935 }
936
937 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelections)
938 {
939     static const char * const selections[] = {
940         "group \"GrpA\"",
941         "GrpB",
942         "1",
943         "group \"GrpB\" and resname RB"
944     };
945     setFlags(TestFlags() | efTestSelectionNames);
946     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
947     runTest("simple.gro", selections);
948 }
949
950 TEST_F(SelectionCollectionDataTest, HandlesIndexGroupsInSelectionsDelayed)
951 {
952     static const char * const selections[] = {
953         "group \"GrpA\"",
954         "GrpB",
955         "1",
956         "group \"GrpB\" and resname RB"
957     };
958     setFlags(TestFlags() | efTestSelectionNames);
959     ASSERT_NO_FATAL_FAILURE(runParser(selections));
960     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
961     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
962     ASSERT_NO_FATAL_FAILURE(runCompiler());
963 }
964
965 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelections)
966 {
967     static const char * const selections[] = {
968         "foo = group \"GrpUnsorted\"",
969         "group \"GrpUnsorted\"",
970         "GrpUnsorted",
971         "2",
972         "res_cog of group \"GrpUnsorted\"",
973         "group \"GrpUnsorted\" permute 2 1",
974         "foo"
975     };
976     setFlags(TestFlags() | efTestPositionAtoms | efTestPositionMapping
977              | efTestSelectionNames);
978     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
979     runTest("simple.gro", selections);
980 }
981
982 TEST_F(SelectionCollectionDataTest, HandlesUnsortedIndexGroupsInSelectionsDelayed)
983 {
984     static const char * const selections[] = {
985         "foo = group \"GrpUnsorted\"",
986         "group \"GrpUnsorted\"",
987         "GrpUnsorted",
988         "2",
989         "res_cog of group \"GrpUnsorted\"",
990         "group \"GrpUnsorted\" permute 2 1",
991         "foo"
992     };
993     ASSERT_NO_FATAL_FAILURE(runParser(selections));
994     ASSERT_NO_FATAL_FAILURE(loadTopology("simple.gro"));
995     ASSERT_NO_THROW_GMX(loadIndexGroups("simple.ndx"));
996     ASSERT_NO_FATAL_FAILURE(runCompiler());
997 }
998
999 TEST_F(SelectionCollectionDataTest, HandlesConstantPositions)
1000 {
1001     static const char * const selections[] = {
1002         "[1, -2, 3.5]"
1003     };
1004     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1005     runTest("simple.gro", selections);
1006 }
1007
1008
1009 TEST_F(SelectionCollectionDataTest, HandlesWithinConstantPositions)
1010 {
1011     static const char * const selections[] = {
1012         "within 1 of [2, 1, 0]"
1013     };
1014     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1015     runTest("simple.gro", selections);
1016 }
1017
1018
1019 TEST_F(SelectionCollectionDataTest, HandlesForcedStringMatchingMode)
1020 {
1021     static const char * const selections[] = {
1022         "name = S1 \"C?\"",
1023         "name ? S1 \"C?\""
1024     };
1025     runTest("simple.gro", selections);
1026 }
1027
1028
1029 TEST_F(SelectionCollectionDataTest, HandlesWildcardMatching)
1030 {
1031     static const char * const selections[] = {
1032         "name \"S?\"",
1033         "name ? \"S?\""
1034     };
1035     runTest("simple.gro", selections);
1036 }
1037
1038
1039 TEST_F(SelectionCollectionDataTest, HandlesRegexMatching)
1040 {
1041     static const char * const selections[] = {
1042         "resname \"R[BD]\"",
1043         "resname ~ \"R[BD]\""
1044     };
1045     if (gmx::Regex::isSupported())
1046     {
1047         runTest("simple.gro", selections);
1048     }
1049 }
1050
1051
1052 TEST_F(SelectionCollectionDataTest, HandlesBasicBoolean)
1053 {
1054     static const char * const selections[] = {
1055         "atomnr 1 to 5 and atomnr 2 to 7",
1056         "atomnr 1 to 5 or not atomnr 3 to 8",
1057         "not not atomnr 1 to 5 and atomnr 2 to 6 and not not atomnr 3 to 7",
1058         "atomnr 1 to 5 and (atomnr 2 to 7 and atomnr 3 to 6)",
1059         "x < 5 and atomnr 1 to 5 and y < 3 and atomnr 2 to 4"
1060     };
1061     runTest(10, selections);
1062 }
1063
1064
1065 TEST_F(SelectionCollectionDataTest, HandlesDynamicAtomValuedParameters)
1066 {
1067     static const char * const selections[] = {
1068         "same residue as (atomnr 3 5 13 or y > 5)",
1069         "(resnr 1 3 5 or x > 10) and same residue as (atomnr 3 5 13 or z > 5)"
1070     };
1071     setFlags(TestFlags() | efTestEvaluation);
1072     runTest("simple.gro", selections);
1073 }
1074
1075
1076 TEST_F(SelectionCollectionDataTest, HandlesEmptySelectionWithUnevaluatedExpressions)
1077 {
1078     static const char * const selections[] = {
1079         "none and x > 2",
1080         "none and same resname as resnr 2"
1081     };
1082     runTest("simple.gro", selections);
1083 }
1084
1085
1086 TEST_F(SelectionCollectionDataTest, HandlesNumericComparisons)
1087 {
1088     static const char * const selections[] = {
1089         "x > 2",
1090         "2 < x",
1091         "y > resnr",
1092         "resnr < 2.5",
1093         "2.5 > resnr"
1094     };
1095     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1096     runTest("simple.gro", selections);
1097 }
1098
1099
1100 TEST_F(SelectionCollectionDataTest, HandlesArithmeticExpressions)
1101 {
1102     static const char * const selections[] = {
1103         "x+1 > 3",
1104         "(y-1)^2 <= 1",
1105         "x+--1 > 3",
1106         "-x+-1 < -3"
1107     };
1108     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1109     runTest("simple.gro", selections);
1110 }
1111
1112
1113 TEST_F(SelectionCollectionDataTest, HandlesNumericVariables)
1114 {
1115     static const char * const selections[] = {
1116         "value = x + y",
1117         "value <= 4",
1118         "index = resnr",
1119         "index < 3"
1120     };
1121     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1122     runTest("simple.gro", selections);
1123 }
1124
1125
1126 TEST_F(SelectionCollectionDataTest, HandlesComplexNumericVariables)
1127 {
1128     static const char * const selections[] = {
1129         "value = x + y",
1130         "resname RA and value <= 4",
1131         "resname RA RB and x < 3 and value <= 4",
1132         "index = atomnr",
1133         "resname RA and index < 3",
1134         "resname RB and y < 3 and index < 6"
1135     };
1136     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1137     runTest("simple.gro", selections);
1138 }
1139
1140
1141 TEST_F(SelectionCollectionDataTest, HandlesPositionVariables)
1142 {
1143     static const char * const selections[] = {
1144         "foo = res_cog of resname RA",
1145         "foo",
1146         "within 1 of foo",
1147         "bar = cog of resname RA",
1148         "bar",
1149         "within 1 of bar"
1150     };
1151     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1152     runTest("simple.gro", selections);
1153 }
1154
1155
1156 TEST_F(SelectionCollectionDataTest, HandlesConstantPositionInVariable)
1157 {
1158     static const char * const selections[] = {
1159         "constpos = [1.0, 2.5, 0.5]",
1160         "constpos",
1161         "within 2 of constpos"
1162     };
1163     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates
1164              | efTestPositionAtoms);
1165     runTest("simple.gro", selections);
1166 }
1167
1168
1169 TEST_F(SelectionCollectionDataTest, HandlesNumericConstantsInVariables)
1170 {
1171     static const char * const selections[] = {
1172         "constint = 4",
1173         "constreal1 = 0.5",
1174         "constreal2 = 2.7",
1175         "resnr < constint",
1176         "x + constreal1 < constreal2"
1177     };
1178     setFlags(TestFlags() | efTestEvaluation | efTestPositionCoordinates);
1179     runTest("simple.gro", selections);
1180 }
1181
1182
1183 /********************************************************************
1184  * Tests for complex boolean syntax
1185  */
1186
1187 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysis)
1188 {
1189     static const char * const selections[] = {
1190         "atomnr 1 to 5 and atomnr 2 to 7 and x < 2",
1191         "atomnr 1 to 5 and (atomnr 4 to 7 or x < 2)",
1192         "atomnr 1 to 5 and y < 3 and (atomnr 4 to 7 or x < 2)",
1193         "atomnr 1 to 5 and not (atomnr 4 to 7 or x < 2)",
1194         "atomnr 1 to 5 or (atomnr 4 to 6 and (atomnr 5 to 7 or x < 2))"
1195     };
1196     runTest(10, selections);
1197 }
1198
1199
1200 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithVariables)
1201 {
1202     static const char * const selections[] = {
1203         "foo = atomnr 4 to 7 or x < 2",
1204         "atomnr 1 to 4 and foo",
1205         "atomnr 2 to 6 and y < 3 and foo",
1206         "atomnr 6 to 10 and not foo"
1207     };
1208     runTest(10, selections);
1209 }
1210
1211
1212 TEST_F(SelectionCollectionDataTest, HandlesBooleanStaticAnalysisWithMoreVariables)
1213 {
1214     static const char * const selections[] = {
1215         "foo = atomnr 4 to 7",
1216         "bar = foo and x < 2",
1217         "bar2 = foo and y < 2",
1218         "atomnr 1 to 4 and bar",
1219         "atomnr 2 to 6 and y < 3 and bar2",
1220         "atomnr 6 to 10 and not foo"
1221     };
1222     runTest(10, selections);
1223 }
1224
1225
1226 /********************************************************************
1227  * Tests for complex subexpression cases
1228  *
1229  * These tests use some knowledge of the implementation to trigger different
1230  * paths in the code.
1231  */
1232
1233 TEST_F(SelectionCollectionDataTest, HandlesUnusedVariables)
1234 {
1235     static const char * const selections[] = {
1236         "unused1 = atomnr 1 to 3",
1237         "foo = atomnr 4 to 7",
1238         "atomnr 1 to 6 and foo",
1239         "unused2 = atomnr 3 to 5"
1240     };
1241     runTest(10, selections);
1242 }
1243
1244
1245 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithStaticEvaluationGroups)
1246 {
1247     static const char * const selections[] = {
1248         "foo = atomnr 4 to 7 and x < 2",
1249         "atomnr 1 to 5 and foo",
1250         "atomnr 3 to 7 and foo"
1251     };
1252     runTest(10, selections);
1253 }
1254
1255
1256 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups)
1257 {
1258     static const char * const selections[] = {
1259         "foo = atomnr 4 to 7 and x < 2",
1260         "atomnr 1 to 6 and foo",
1261         "within 1 of foo",
1262         "foo"
1263     };
1264     runTest(10, selections);
1265 }
1266
1267
1268 TEST_F(SelectionCollectionDataTest, HandlesVariablesWithMixedEvaluationGroups2)
1269 {
1270     static const char * const selections[] = {
1271         "foo = atomnr 1 to 8 and x < 10",
1272         "atomnr 1 to 5 and y < 10 and foo",
1273         "foo"
1274     };
1275     setFlags(TestFlags() | efTestEvaluation);
1276     runTest("simple.gro", selections);
1277 }
1278
1279
1280 } // namespace