c2a6218fb86c65b22ccac31f40e8922840634087
[alexxy/gromacs.git] / src / gromacs / selection / selectioncollection.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2012,2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
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  * Implements gmx::SelectionCollection.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_selection
41  */
42 #include "gmxpre.h"
43
44 #include "selectioncollection.h"
45
46 #include <cctype>
47 #include <cstdio>
48
49 #include <memory>
50 #include <string>
51 #include <vector>
52
53 #include "gromacs/onlinehelp/helpmanager.h"
54 #include "gromacs/onlinehelp/helpwritercontext.h"
55 #include "gromacs/options/basicoptions.h"
56 #include "gromacs/options/ioptionscontainer.h"
57 #include "gromacs/selection/selection.h"
58 #include "gromacs/selection/selhelp.h"
59 #include "gromacs/topology/mtop_util.h"
60 #include "gromacs/topology/topology.h"
61 #include "gromacs/trajectory/trajectoryframe.h"
62 #include "gromacs/utility/exceptions.h"
63 #include "gromacs/utility/filestream.h"
64 #include "gromacs/utility/gmxassert.h"
65 #include "gromacs/utility/smalloc.h"
66 #include "gromacs/utility/stringutil.h"
67 #include "gromacs/utility/textwriter.h"
68
69 #include "compiler.h"
70 #include "mempool.h"
71 #include "parser.h"
72 #include "poscalc.h"
73 #include "scanner.h"
74 #include "selectioncollection-impl.h"
75 #include "selelem.h"
76 #include "selmethod.h"
77 #include "symrec.h"
78
79 namespace gmx
80 {
81
82 /********************************************************************
83  * SelectionCollection::Impl
84  */
85
86 SelectionCollection::Impl::Impl()
87     : debugLevel_(0), bExternalGroupsSet_(false), grps_(nullptr)
88 {
89     sc_.nvars     = 0;
90     sc_.varstrs   = nullptr;
91     sc_.top       = nullptr;
92     gmx_ana_index_clear(&sc_.gall);
93     sc_.mempool   = nullptr;
94     sc_.symtab    = std::make_unique<SelectionParserSymbolTable>();
95     gmx_ana_index_clear(&requiredAtoms_);
96     gmx_ana_selmethod_register_defaults(sc_.symtab.get());
97 }
98
99
100 SelectionCollection::Impl::~Impl()
101 {
102     clearSymbolTable();
103     // The tree must be freed before the SelectionData objects, since the
104     // tree may hold references to the position data in SelectionData.
105     sc_.root.reset();
106     sc_.sel.clear();
107     for (int i = 0; i < sc_.nvars; ++i)
108     {
109         sfree(sc_.varstrs[i]);
110     }
111     sfree(sc_.varstrs);
112     gmx_ana_index_deinit(&sc_.gall);
113     if (sc_.mempool)
114     {
115         _gmx_sel_mempool_destroy(sc_.mempool);
116     }
117     gmx_ana_index_deinit(&requiredAtoms_);
118 }
119
120
121 void
122 SelectionCollection::Impl::clearSymbolTable()
123 {
124     sc_.symtab.reset();
125 }
126
127
128 namespace
129 {
130
131 /*! \brief
132  * Reads a single selection line from stdin.
133  *
134  * \param[in]  inputStream   Stream to read from (typically the StandardInputStream).
135  * \param[in]  statusWriter  Stream to print prompts to (if NULL, no output is done).
136  * \param[out] line          The read line in stored here.
137  * \returns true if something was read, false if at end of input.
138  *
139  * Handles line continuation, reading also the continuing line(s) in one call.
140  */
141 bool promptLine(TextInputStream *inputStream, TextWriter *statusWriter,
142                 std::string *line)
143 {
144     if (statusWriter != nullptr)
145     {
146         statusWriter->writeString("> ");
147     }
148     if (!inputStream->readLine(line))
149     {
150         return false;
151     }
152     while (endsWith(*line, "\\\n"))
153     {
154         line->resize(line->length() - 2);
155         if (statusWriter != nullptr)
156         {
157             statusWriter->writeString("... ");
158         }
159         std::string buffer;
160         // Return value ignored, buffer remains empty and works correctly
161         // if there is nothing to read.
162         inputStream->readLine(&buffer);
163         line->append(buffer);
164     }
165     if (endsWith(*line, "\n"))
166     {
167         line->resize(line->length() - 1);
168     }
169     else if (statusWriter != nullptr)
170     {
171         statusWriter->writeLine();
172     }
173     return true;
174 }
175
176 /*! \brief
177  * Helper function for tokenizing the input and pushing them to the parser.
178  *
179  * \param     scanner       Tokenizer data structure.
180  * \param     parserState   Parser data structure.
181  * \param[in] bInteractive  Whether to operate in interactive mode.
182  *
183  * Repeatedly reads tokens using \p scanner and pushes them to the parser with
184  * \p parserState until there is no more input, or until enough input is given
185  * (only in interactive mode).
186  */
187 int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
188                   bool bInteractive)
189 {
190     int status    = YYPUSH_MORE;
191     do
192     {
193         YYSTYPE value;
194         YYLTYPE location;
195         int     token = _gmx_sel_yylex(&value, &location, scanner);
196         if (bInteractive && token == 0)
197         {
198             break;
199         }
200         status = _gmx_sel_yypush_parse(parserState, token, &value, &location, scanner);
201     }
202     while (status == YYPUSH_MORE);
203     _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
204     return status;
205 }
206
207 /*! \brief
208  * Print current status in response to empty line in interactive input.
209  *
210  * \param[in] writer         Writer to use for the output.
211  * \param[in] sc             Selection collection data structure.
212  * \param[in] grps           Available index groups.
213  * \param[in] firstSelection Index of first selection from this interactive
214  *     session.
215  * \param[in] maxCount       Maximum number of selections.
216  * \param[in] context        Context to print for what the selections are for.
217  * \param[in] bFirst         Whether this is the header that is printed before
218  *     any user input.
219  *
220  * Prints the available index groups and currently provided selections.
221  */
222 void printCurrentStatus(TextWriter *writer, gmx_ana_selcollection_t *sc,
223                         gmx_ana_indexgrps_t *grps, size_t firstSelection,
224                         int maxCount, const std::string &context, bool bFirst)
225 {
226     if (grps != nullptr)
227     {
228         writer->writeLine("Available static index groups:");
229         gmx_ana_indexgrps_print(writer, grps, 0);
230     }
231     writer->writeString("Specify ");
232     if (maxCount < 0)
233     {
234         writer->writeString("any number of selections");
235     }
236     else if (maxCount == 1)
237     {
238         writer->writeString("a selection");
239     }
240     else
241     {
242         writer->writeString(formatString("%d selections", maxCount));
243     }
244     writer->writeString(formatString("%s%s:\n",
245                                      context.empty() ? "" : " ", context.c_str()));
246     writer->writeString(formatString(
247                                 "(one per line, <enter> for status/groups, 'help' for help%s)\n",
248                                 maxCount < 0 ? ", Ctrl-D to end" : ""));
249     if (!bFirst && (sc->nvars > 0 || sc->sel.size() > firstSelection))
250     {
251         writer->writeLine("Currently provided selections:");
252         for (int i = 0; i < sc->nvars; ++i)
253         {
254             writer->writeString(formatString("     %s\n", sc->varstrs[i]));
255         }
256         for (size_t i = firstSelection; i < sc->sel.size(); ++i)
257         {
258             writer->writeString(formatString(
259                                         " %2d. %s\n",
260                                         static_cast<int>(i - firstSelection + 1),
261                                         sc->sel[i]->selectionText()));
262         }
263         if (maxCount > 0)
264         {
265             const int remaining
266                 = maxCount - static_cast<int>(sc->sel.size() - firstSelection);
267             writer->writeString(formatString(
268                                         "(%d more selection%s required)\n",
269                                         remaining, remaining > 1 ? "s" : ""));
270         }
271     }
272 }
273
274 /*! \brief
275  * Prints selection help in interactive selection input.
276  *
277  * \param[in] writer Writer to use for the output.
278  * \param[in] sc    Selection collection data structure.
279  * \param[in] line  Line of user input requesting help (starting with `help`).
280  *
281  * Initializes the selection help if not yet initialized, and finds the help
282  * topic based on words on the input line.
283  */
284 void printHelp(TextWriter *writer, gmx_ana_selcollection_t *sc,
285                const std::string &line)
286 {
287     if (sc->rootHelp.get() == nullptr)
288     {
289         sc->rootHelp = createSelectionHelpTopic();
290     }
291     HelpWriterContext context(writer, eHelpOutputFormat_Console);
292     HelpManager       manager(*sc->rootHelp, context);
293     try
294     {
295         std::vector<std::string>                 topic = splitString(line);
296         std::vector<std::string>::const_iterator value;
297         // First item in the list is the 'help' token.
298         for (value = topic.begin() + 1; value != topic.end(); ++value)
299         {
300             manager.enterTopic(*value);
301         }
302     }
303     catch (const InvalidInputError &ex)
304     {
305         writer->writeLine(ex.what());
306         return;
307     }
308     manager.writeCurrentTopic();
309 }
310
311 /*! \brief
312  * Helper function that runs the parser once the tokenizer has been
313  * initialized.
314  *
315  * \param[in,out] scanner       Scanner data structure.
316  * \param[in]     inputStream   Stream to use for input (currently only with
317  *      `bInteractive==true`).
318  * \param[in]     bInteractive  Whether to use a line-based reading
319  *      algorithm designed for interactive input.
320  * \param[in]     maxnr   Maximum number of selections to parse
321  *      (if -1, parse as many as provided by the user).
322  * \param[in]     context Context to print for what the selections are for.
323  * \returns       Vector of parsed selections.
324  * \throws        std::bad_alloc if out of memory.
325  * \throws        InvalidInputError if there is a parsing error.
326  *
327  * Used internally to implement parseInteractive(), parseFromFile() and
328  * parseFromString().
329  */
330 SelectionList runParser(yyscan_t scanner, TextInputStream *inputStream,
331                         bool bInteractive, int maxnr, const std::string &context)
332 {
333     std::shared_ptr<void>    scannerGuard(scanner, &_gmx_sel_free_lexer);
334     gmx_ana_selcollection_t *sc   = _gmx_sel_lexer_selcollection(scanner);
335     gmx_ana_indexgrps_t     *grps = _gmx_sel_lexer_indexgrps(scanner);
336
337     size_t                   oldCount = sc->sel.size();
338     {
339         std::shared_ptr<_gmx_sel_yypstate> parserState(
340                 _gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete);
341         if (bInteractive)
342         {
343             TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner);
344             if (statusWriter != nullptr)
345             {
346                 printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, true);
347             }
348             std::string line;
349             int         status;
350             while (promptLine(inputStream, statusWriter, &line))
351             {
352                 if (statusWriter != nullptr)
353                 {
354                     line = stripString(line);
355                     if (line.empty())
356                     {
357                         printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, false);
358                         continue;
359                     }
360                     if (startsWith(line, "help")
361                         && (line[4] == 0 || (std::isspace(line[4]) != 0)))
362                     {
363                         printHelp(statusWriter, sc, line);
364                         continue;
365                     }
366                 }
367                 line.append("\n");
368                 _gmx_sel_set_lex_input_str(scanner, line.c_str());
369                 status = runParserLoop(scanner, parserState.get(), true);
370                 if (status != YYPUSH_MORE)
371                 {
372                     // TODO: Check if there is more input, and issue an
373                     // error/warning if some input was ignored.
374                     goto early_termination;
375                 }
376             }
377             {
378                 YYLTYPE location;
379                 status = _gmx_sel_yypush_parse(parserState.get(), 0, nullptr,
380                                                &location, scanner);
381             }
382             // TODO: Remove added selections from the collection if parsing failed?
383             _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
384 early_termination:
385             GMX_RELEASE_ASSERT(status == 0,
386                                "Parser errors should have resulted in an exception");
387         }
388         else
389         {
390             int status = runParserLoop(scanner, parserState.get(), false);
391             GMX_RELEASE_ASSERT(status == 0,
392                                "Parser errors should have resulted in an exception");
393         }
394     }
395     scannerGuard.reset();
396     int nr = sc->sel.size() - oldCount;
397     if (maxnr > 0 && nr != maxnr)
398     {
399         std::string message
400             = formatString("Too few selections provided; got %d, expected %d",
401                            nr, maxnr);
402         GMX_THROW(InvalidInputError(message));
403     }
404
405     SelectionList                     result;
406     SelectionDataList::const_iterator i;
407     result.reserve(nr);
408     for (i = sc->sel.begin() + oldCount; i != sc->sel.end(); ++i)
409     {
410         result.emplace_back(i->get());
411     }
412     return result;
413 }
414
415 /*! \brief
416  * Checks that index groups have valid atom indices.
417  *
418  * \param[in]    root    Root of selection tree to process.
419  * \param[in]    natoms  Maximum number of atoms that the selections are set
420  *     to evaluate.
421  * \param        errors  Object for reporting any error messages.
422  * \throws std::bad_alloc if out of memory.
423  *
424  * Recursively checks the selection tree for index groups.
425  * Each found group is checked that it only contains atom indices that match
426  * the topology/maximum number of atoms set for the selection collection.
427  * Any issues are reported to \p errors.
428  */
429 void checkExternalGroups(const SelectionTreeElementPointer &root,
430                          int                                natoms,
431                          ExceptionInitializer              *errors)
432 {
433     if (root->type == SEL_CONST && root->v.type == GROUP_VALUE)
434     {
435         try
436         {
437             root->checkIndexGroup(natoms);
438         }
439         catch (const UserInputError &)
440         {
441             errors->addCurrentExceptionAsNested();
442         }
443     }
444
445     SelectionTreeElementPointer child = root->child;
446     while (child)
447     {
448         checkExternalGroups(child, natoms, errors);
449         child = child->next;
450     }
451 }
452
453 //! Checks whether the given topology properties are available.
454 void checkTopologyProperties(const gmx_mtop_t                  *top,
455                              const SelectionTopologyProperties &props)
456 {
457     if (top == nullptr)
458     {
459         if (props.hasAny())
460         {
461             GMX_THROW(InconsistentInputError("Selection requires topology information, but none provided"));
462         }
463         return;
464     }
465     if (props.needsMasses && !gmx_mtop_has_masses(top))
466     {
467         GMX_THROW(InconsistentInputError("Selection requires mass information, but it is not available in the topology"));
468     }
469 }
470
471 }   // namespace
472
473
474 void SelectionCollection::Impl::resolveExternalGroups(
475         const SelectionTreeElementPointer &root,
476         ExceptionInitializer              *errors)
477 {
478
479     if (root->type == SEL_GROUPREF)
480     {
481         try
482         {
483             root->resolveIndexGroupReference(grps_, sc_.gall.isize);
484         }
485         catch (const UserInputError &)
486         {
487             errors->addCurrentExceptionAsNested();
488         }
489     }
490
491     SelectionTreeElementPointer child = root->child;
492     while (child)
493     {
494         resolveExternalGroups(child, errors);
495         root->flags |= (child->flags & SEL_UNSORTED);
496         child        = child->next;
497     }
498 }
499
500
501 bool SelectionCollection::Impl::areForcesRequested() const
502 {
503     for (const auto &sel : sc_.sel)
504     {
505         if (sel->hasFlag(gmx::efSelection_EvaluateForces))
506         {
507             return true;
508         }
509     }
510     return false;
511 }
512
513
514 SelectionTopologyProperties
515 SelectionCollection::Impl::requiredTopologyPropertiesForPositionType(
516         const std::string &post, bool forces) const
517 {
518     SelectionTopologyProperties props;
519     if (!post.empty())
520     {
521         switch (PositionCalculationCollection::requiredTopologyInfoForType(post.c_str(), forces))
522         {
523             case PositionCalculationCollection::RequiredTopologyInfo::None:
524                 break;
525             case PositionCalculationCollection::RequiredTopologyInfo::Topology:
526                 props.merge(SelectionTopologyProperties::topology());
527                 break;
528             case PositionCalculationCollection::RequiredTopologyInfo::TopologyAndMasses:
529                 props.merge(SelectionTopologyProperties::masses());
530                 break;
531         }
532     }
533     return props;
534 }
535
536
537 /********************************************************************
538  * SelectionCollection
539  */
540
541 SelectionCollection::SelectionCollection()
542     : impl_(new Impl)
543 {
544 }
545
546
547 SelectionCollection::~SelectionCollection()
548 {
549 }
550
551
552 void
553 SelectionCollection::initOptions(IOptionsContainer   *options,
554                                  SelectionTypeOption  selectionTypeOption)
555 {
556     const char * const debug_levels[]
557         = { "no", "basic", "compile", "eval", "full" };
558
559     const char *const *postypes = PositionCalculationCollection::typeEnumValues;
560     options->addOption(StringOption("selrpos")
561                            .enumValueFromNullTerminatedArray(postypes)
562                            .store(&impl_->rpost_).defaultValue(postypes[0])
563                            .description("Selection reference positions"));
564     if (selectionTypeOption == IncludeSelectionTypeOption)
565     {
566         options->addOption(StringOption("seltype")
567                                .enumValueFromNullTerminatedArray(postypes)
568                                .store(&impl_->spost_).defaultValue(postypes[0])
569                                .description("Default selection output positions"));
570     }
571     else
572     {
573         impl_->spost_ = postypes[0];
574     }
575     GMX_RELEASE_ASSERT(impl_->debugLevel_ >= 0 && impl_->debugLevel_ <= 4,
576                        "Debug level out of range");
577     options->addOption(EnumIntOption("seldebug").hidden(impl_->debugLevel_ == 0)
578                            .enumValue(debug_levels).store(&impl_->debugLevel_)
579                            .description("Print out selection trees for debugging"));
580 }
581
582
583 void
584 SelectionCollection::setReferencePosType(const char *type)
585 {
586     GMX_RELEASE_ASSERT(type != nullptr, "Cannot assign NULL position type");
587     // Check that the type is valid, throw if it is not.
588     e_poscalc_t  dummytype;
589     int          dummyflags;
590     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
591     impl_->rpost_ = type;
592 }
593
594
595 void
596 SelectionCollection::setOutputPosType(const char *type)
597 {
598     GMX_RELEASE_ASSERT(type != nullptr, "Cannot assign NULL position type");
599     // Check that the type is valid, throw if it is not.
600     e_poscalc_t  dummytype;
601     int          dummyflags;
602     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
603     impl_->spost_ = type;
604 }
605
606
607 void
608 SelectionCollection::setDebugLevel(int debugLevel)
609 {
610     impl_->debugLevel_ = debugLevel;
611 }
612
613
614 void
615 SelectionCollection::setTopology(gmx_mtop_t *top, int natoms)
616 {
617     GMX_RELEASE_ASSERT(natoms > 0 || top != nullptr,
618                        "The number of atoms must be given if there is no topology");
619     checkTopologyProperties(top, requiredTopologyProperties());
620     // Get the number of atoms from the topology if it is not given.
621     if (natoms <= 0)
622     {
623         natoms = top->natoms;
624     }
625     if (impl_->bExternalGroupsSet_)
626     {
627         ExceptionInitializer        errors("Invalid index group references encountered");
628         SelectionTreeElementPointer root = impl_->sc_.root;
629         while (root)
630         {
631             checkExternalGroups(root, natoms, &errors);
632             root = root->next;
633         }
634         if (errors.hasNestedExceptions())
635         {
636             GMX_THROW(InconsistentInputError(errors));
637         }
638     }
639     gmx_ana_selcollection_t *sc = &impl_->sc_;
640     // Do this first, as it allocates memory, while the others don't throw.
641     gmx_ana_index_init_simple(&sc->gall, natoms);
642     sc->top = top;
643     sc->pcc.setTopology(top);
644 }
645
646
647 void
648 SelectionCollection::setIndexGroups(gmx_ana_indexgrps_t *grps)
649 {
650     GMX_RELEASE_ASSERT(grps == nullptr || !impl_->bExternalGroupsSet_,
651                        "Can only set external groups once or clear them afterwards");
652     impl_->grps_               = grps;
653     impl_->bExternalGroupsSet_ = true;
654
655     ExceptionInitializer        errors("Invalid index group reference(s)");
656     SelectionTreeElementPointer root = impl_->sc_.root;
657     while (root)
658     {
659         impl_->resolveExternalGroups(root, &errors);
660         root->checkUnsortedAtoms(true, &errors);
661         root = root->next;
662     }
663     if (errors.hasNestedExceptions())
664     {
665         GMX_THROW(InconsistentInputError(errors));
666     }
667     for (size_t i = 0; i < impl_->sc_.sel.size(); ++i)
668     {
669         impl_->sc_.sel[i]->refreshName();
670     }
671 }
672
673 SelectionTopologyProperties
674 SelectionCollection::requiredTopologyProperties() const
675 {
676     SelectionTopologyProperties props;
677
678     // These should not throw, because has been checked earlier.
679     props.merge(impl_->requiredTopologyPropertiesForPositionType(impl_->rpost_, false));
680     const bool forcesRequested = impl_->areForcesRequested();
681     props.merge(impl_->requiredTopologyPropertiesForPositionType(impl_->spost_,
682                                                                  forcesRequested));
683
684     SelectionTreeElementPointer sel = impl_->sc_.root;
685     while (sel && !props.hasAll())
686     {
687         props.merge(sel->requiredTopologyProperties());
688         sel = sel->next;
689     }
690     return props;
691 }
692
693
694 bool
695 SelectionCollection::requiresIndexGroups() const
696 {
697     SelectionTreeElementPointer sel = impl_->sc_.root;
698     while (sel)
699     {
700         if (sel->requiresIndexGroups())
701         {
702             return true;
703         }
704         sel = sel->next;
705     }
706     return false;
707 }
708
709
710 SelectionList
711 SelectionCollection::parseFromStdin(int count, bool bInteractive,
712                                     const std::string &context)
713 {
714     return parseInteractive(count, &StandardInputStream::instance(),
715                             bInteractive ? &TextOutputFile::standardError() : nullptr,
716                             context);
717 }
718
719 namespace
720 {
721
722 //! Helper function to initialize status writer for interactive selection parsing.
723 std::unique_ptr<TextWriter> initStatusWriter(TextOutputStream *statusStream)
724 {
725     std::unique_ptr<TextWriter> statusWriter;
726     if (statusStream != nullptr)
727     {
728         statusWriter = std::make_unique<TextWriter>(statusStream);
729         statusWriter->wrapperSettings().setLineLength(78);
730     }
731     return statusWriter;
732 }
733
734 }   // namespace
735
736 SelectionList
737 SelectionCollection::parseInteractive(int                count,
738                                       TextInputStream   *inputStream,
739                                       TextOutputStream  *statusStream,
740                                       const std::string &context)
741 {
742     yyscan_t scanner;
743
744     const std::unique_ptr<TextWriter> statusWriter(
745             initStatusWriter(statusStream));
746     _gmx_sel_init_lexer(&scanner, &impl_->sc_, statusWriter.get(),
747                         count, impl_->bExternalGroupsSet_, impl_->grps_);
748     return runParser(scanner, inputStream, true, count, context);
749 }
750
751
752 SelectionList
753 SelectionCollection::parseFromFile(const std::string &filename)
754 {
755
756     try
757     {
758         yyscan_t      scanner;
759         TextInputFile file(filename);
760         // TODO: Exception-safe way of using the lexer.
761         _gmx_sel_init_lexer(&scanner, &impl_->sc_, nullptr, -1,
762                             impl_->bExternalGroupsSet_,
763                             impl_->grps_);
764         _gmx_sel_set_lex_input_file(scanner, file.handle());
765         return runParser(scanner, nullptr, false, -1, std::string());
766     }
767     catch (GromacsException &ex)
768     {
769         ex.prependContext(formatString(
770                                   "Error in parsing selections from file '%s'",
771                                   filename.c_str()));
772         throw;
773     }
774 }
775
776
777 SelectionList
778 SelectionCollection::parseFromString(const std::string &str)
779 {
780     yyscan_t scanner;
781
782     _gmx_sel_init_lexer(&scanner, &impl_->sc_, nullptr, -1,
783                         impl_->bExternalGroupsSet_,
784                         impl_->grps_);
785     _gmx_sel_set_lex_input_str(scanner, str.c_str());
786     return runParser(scanner, nullptr, false, -1, std::string());
787 }
788
789
790 void
791 SelectionCollection::compile()
792 {
793     checkTopologyProperties(impl_->sc_.top, requiredTopologyProperties());
794     if (!impl_->bExternalGroupsSet_)
795     {
796         setIndexGroups(nullptr);
797     }
798     if (impl_->debugLevel_ >= 1)
799     {
800         printTree(stderr, false);
801     }
802
803     SelectionCompiler compiler;
804     compiler.compile(this);
805
806     if (impl_->debugLevel_ >= 1)
807     {
808         std::fprintf(stderr, "\n");
809         printTree(stderr, false);
810         std::fprintf(stderr, "\n");
811         impl_->sc_.pcc.printTree(stderr);
812         std::fprintf(stderr, "\n");
813     }
814     impl_->sc_.pcc.initEvaluation();
815     if (impl_->debugLevel_ >= 1)
816     {
817         impl_->sc_.pcc.printTree(stderr);
818         std::fprintf(stderr, "\n");
819     }
820
821     // TODO: It would be nicer to associate the name of the selection option
822     // (if available) to the error message.
823     SelectionDataList::const_iterator iter;
824     for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
825     {
826         const internal::SelectionData &sel = **iter;
827         if (sel.hasFlag(efSelection_OnlyAtoms))
828         {
829             if (!sel.hasOnlyAtoms())
830             {
831                 std::string message = formatString(
832                             "Selection '%s' does not evaluate to individual atoms. "
833                             "This is not allowed in this context.",
834                             sel.selectionText());
835                 GMX_THROW(InvalidInputError(message));
836             }
837             if (sel.hasFlag(efSelection_OnlySorted))
838             {
839                 if (!sel.hasSortedAtomIndices())
840                 {
841                     const std::string message = formatString(
842                                 "Selection '%s' does not evaluate to atoms in an "
843                                 "ascending (sorted) order. "
844                                 "This is not allowed in this context.",
845                                 sel.selectionText());
846                     GMX_THROW(InvalidInputError(message));
847                 }
848             }
849         }
850         if (sel.hasFlag(efSelection_DisallowEmpty))
851         {
852             if (sel.posCount() == 0)
853             {
854                 std::string message = formatString(
855                             "Selection '%s' never matches any atoms.",
856                             sel.selectionText());
857                 GMX_THROW(InvalidInputError(message));
858             }
859         }
860     }
861     impl_->rpost_.clear();
862     impl_->spost_.clear();
863 }
864
865
866 void
867 SelectionCollection::evaluate(t_trxframe *fr, t_pbc *pbc)
868 {
869     checkTopologyProperties(impl_->sc_.top, requiredTopologyProperties());
870     if (fr->bIndex)
871     {
872         gmx_ana_index_t g;
873         gmx_ana_index_set(&g, fr->natoms, fr->index, 0);
874         GMX_RELEASE_ASSERT(gmx_ana_index_check_sorted(&g),
875                            "Only trajectories with atoms in ascending order "
876                            "are currently supported");
877         if (!gmx_ana_index_contains(&g, &impl_->requiredAtoms_))
878         {
879             const std::string message = formatString(
880                         "Trajectory does not contain all atoms required for "
881                         "evaluating the provided selections.");
882             GMX_THROW(InconsistentInputError(message));
883         }
884     }
885     else
886     {
887         const int maxAtomIndex = gmx_ana_index_get_max_index(&impl_->requiredAtoms_);
888         if (fr->natoms <= maxAtomIndex)
889         {
890             const std::string message = formatString(
891                         "Trajectory has less atoms (%d) than what is required for "
892                         "evaluating the provided selections (atoms up to index %d "
893                         "are required).", fr->natoms, maxAtomIndex + 1);
894             GMX_THROW(InconsistentInputError(message));
895         }
896     }
897     impl_->sc_.pcc.initFrame(fr);
898
899     SelectionEvaluator evaluator;
900     evaluator.evaluate(this, fr, pbc);
901
902     if (impl_->debugLevel_ >= 3)
903     {
904         std::fprintf(stderr, "\n");
905         printTree(stderr, true);
906     }
907 }
908
909
910 void
911 SelectionCollection::evaluateFinal(int nframes)
912 {
913     SelectionEvaluator evaluator;
914     evaluator.evaluateFinal(this, nframes);
915 }
916
917
918 void
919 SelectionCollection::printTree(FILE *fp, bool bValues) const
920 {
921     SelectionTreeElementPointer sel = impl_->sc_.root;
922     while (sel)
923     {
924         _gmx_selelem_print_tree(fp, *sel, bValues, 0);
925         sel = sel->next;
926     }
927 }
928
929
930 void
931 SelectionCollection::printXvgrInfo(FILE *out) const
932 {
933     const gmx_ana_selcollection_t &sc = impl_->sc_;
934     std::fprintf(out, "# Selections:\n");
935     for (int i = 0; i < sc.nvars; ++i)
936     {
937         std::fprintf(out, "#   %s\n", sc.varstrs[i]);
938     }
939     for (size_t i = 0; i < sc.sel.size(); ++i)
940     {
941         std::fprintf(out, "#   %s\n", sc.sel[i]->selectionText());
942     }
943     std::fprintf(out, "#\n");
944 }
945
946 } // namespace gmx