2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2010,2011,2012,2013,2014,2015, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements gmx::SelectionCollection.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_selection
44 #include "selectioncollection.h"
52 #include <boost/scoped_ptr.hpp>
53 #include <boost/shared_ptr.hpp>
55 #include "gromacs/fileio/trx.h"
56 #include "gromacs/legacyheaders/oenv.h"
57 #include "gromacs/onlinehelp/helpmanager.h"
58 #include "gromacs/onlinehelp/helpwritercontext.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/options.h"
61 #include "gromacs/selection/selection.h"
62 #include "gromacs/selection/selhelp.h"
63 #include "gromacs/topology/topology.h"
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/filestream.h"
66 #include "gromacs/utility/gmxassert.h"
67 #include "gromacs/utility/smalloc.h"
68 #include "gromacs/utility/stringutil.h"
69 #include "gromacs/utility/textwriter.h"
76 #include "selectioncollection-impl.h"
78 #include "selmethod.h"
84 /********************************************************************
85 * SelectionCollection::Impl
88 SelectionCollection::Impl::Impl()
89 : maxAtomIndex_(0), debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
94 gmx_ana_index_clear(&sc_.gall);
96 sc_.symtab.reset(new SelectionParserSymbolTable);
97 gmx_ana_selmethod_register_defaults(sc_.symtab.get());
101 SelectionCollection::Impl::~Impl()
104 // The tree must be freed before the SelectionData objects, since the
105 // tree may hold references to the position data in SelectionData.
108 for (int i = 0; i < sc_.nvars; ++i)
110 sfree(sc_.varstrs[i]);
113 gmx_ana_index_deinit(&sc_.gall);
116 _gmx_sel_mempool_destroy(sc_.mempool);
122 SelectionCollection::Impl::clearSymbolTable()
132 * Reads a single selection line from stdin.
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.
139 * Handles line continuation, reading also the continuing line(s) in one call.
141 bool promptLine(TextInputStream *inputStream, TextWriter *statusWriter,
144 if (statusWriter != NULL)
146 statusWriter->writeString("> ");
148 if (!inputStream->readLine(line))
152 while (endsWith(*line, "\\\n"))
154 line->resize(line->length() - 2);
155 if (statusWriter != NULL)
157 statusWriter->writeString("... ");
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);
165 if (endsWith(*line, "\n"))
167 line->resize(line->length() - 1);
169 else if (statusWriter != NULL)
171 statusWriter->writeLine();
177 * Helper function for tokenizing the input and pushing them to the parser.
179 * \param scanner Tokenizer data structure.
180 * \param parserState Parser data structure.
181 * \param[in] bInteractive Whether to operate in interactive mode.
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).
187 int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
190 int status = YYPUSH_MORE;
196 int token = _gmx_sel_yylex(&value, &location, scanner);
203 // Empty commands cause the interactive parser to print out
204 // status information. This avoids producing those unnecessarily,
205 // e.g., from "resname RA;;".
206 if (prevToken == CMD_SEP && token == CMD_SEP)
212 status = _gmx_sel_yypush_parse(parserState, token, &value, &location, scanner);
214 while (status == YYPUSH_MORE);
215 _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
220 * Print current status in response to empty line in interactive input.
222 * \param[in] writer Writer to use for the output.
223 * \param[in] sc Selection collection data structure.
224 * \param[in] grps Available index groups.
225 * \param[in] firstSelection Index of first selection from this interactive
227 * \param[in] maxCount Maximum number of selections.
228 * \param[in] context Context to print for what the selections are for.
229 * \param[in] bFirst Whether this is the header that is printed before
232 * Prints the available index groups and currently provided selections.
234 void printCurrentStatus(TextWriter *writer, gmx_ana_selcollection_t *sc,
235 gmx_ana_indexgrps_t *grps, size_t firstSelection,
236 int maxCount, const std::string &context, bool bFirst)
240 writer->writeLine("Available static index groups:");
241 gmx_ana_indexgrps_print(writer, grps, 0);
243 writer->writeString("Specify ");
246 writer->writeString("any number of selections");
248 else if (maxCount == 1)
250 writer->writeString("a selection");
254 writer->writeString(formatString("%d selections", maxCount));
256 writer->writeString(formatString("%s%s:\n",
257 context.empty() ? "" : " ", context.c_str()));
258 writer->writeString(formatString(
259 "(one per line, <enter> for status/groups, 'help' for help%s)\n",
260 maxCount < 0 ? ", Ctrl-D to end" : ""));
261 if (!bFirst && (sc->nvars > 0 || sc->sel.size() > firstSelection))
263 writer->writeLine("Currently provided selections:");
264 for (int i = 0; i < sc->nvars; ++i)
266 writer->writeString(formatString(" %s\n", sc->varstrs[i]));
268 for (size_t i = firstSelection; i < sc->sel.size(); ++i)
270 writer->writeString(formatString(
272 static_cast<int>(i - firstSelection + 1),
273 sc->sel[i]->selectionText()));
278 = maxCount - static_cast<int>(sc->sel.size() - firstSelection);
279 writer->writeString(formatString(
280 "(%d more selection%s required)\n",
281 remaining, remaining > 1 ? "s" : ""));
287 * Prints selection help in interactive selection input.
289 * \param[in] writer Writer to use for the output.
290 * \param[in] sc Selection collection data structure.
291 * \param[in] line Line of user input requesting help (starting with `help`).
293 * Initializes the selection help if not yet initialized, and finds the help
294 * topic based on words on the input line.
296 void printHelp(TextWriter *writer, gmx_ana_selcollection_t *sc,
297 const std::string &line)
299 if (sc->rootHelp.get() == NULL)
301 sc->rootHelp = createSelectionHelpTopic();
303 HelpWriterContext context(&writer->stream(), eHelpOutputFormat_Console);
304 HelpManager manager(*sc->rootHelp, context);
307 std::vector<std::string> topic = splitString(line);
308 std::vector<std::string>::const_iterator value;
309 // First item in the list is the 'help' token.
310 for (value = topic.begin() + 1; value != topic.end(); ++value)
312 manager.enterTopic(*value);
315 catch (const InvalidInputError &ex)
317 writer->writeLine(ex.what());
320 manager.writeCurrentTopic();
324 * Helper function that runs the parser once the tokenizer has been
327 * \param[in,out] scanner Scanner data structure.
328 * \param[in] inputStream Stream to use for input (currently only with
329 * `bInteractive==true`).
330 * \param[in] bInteractive Whether to use a line-based reading
331 * algorithm designed for interactive input.
332 * \param[in] maxnr Maximum number of selections to parse
333 * (if -1, parse as many as provided by the user).
334 * \param[in] context Context to print for what the selections are for.
335 * \returns Vector of parsed selections.
336 * \throws std::bad_alloc if out of memory.
337 * \throws InvalidInputError if there is a parsing error.
339 * Used internally to implement parseInteractive(), parseFromFile() and
342 SelectionList runParser(yyscan_t scanner, TextInputStream *inputStream,
343 bool bInteractive, int maxnr, const std::string &context)
345 boost::shared_ptr<void> scannerGuard(scanner, &_gmx_sel_free_lexer);
346 gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner);
347 gmx_ana_indexgrps_t *grps = _gmx_sel_lexer_indexgrps(scanner);
349 size_t oldCount = sc->sel.size();
351 boost::shared_ptr<_gmx_sel_yypstate> parserState(
352 _gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete);
355 TextWriter *statusWriter = _gmx_sel_lexer_get_status_writer(scanner);
356 if (statusWriter != NULL)
358 printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, true);
362 while (promptLine(inputStream, statusWriter, &line))
364 if (statusWriter != NULL)
366 line = stripString(line);
369 printCurrentStatus(statusWriter, sc, grps, oldCount, maxnr, context, false);
372 if (startsWith(line, "help")
373 && (line[4] == 0 || std::isspace(line[4])))
375 printHelp(statusWriter, sc, line);
380 _gmx_sel_set_lex_input_str(scanner, line.c_str());
381 status = runParserLoop(scanner, parserState.get(), true);
382 if (status != YYPUSH_MORE)
384 // TODO: Check if there is more input, and issue an
385 // error/warning if some input was ignored.
386 goto early_termination;
391 status = _gmx_sel_yypush_parse(parserState.get(), 0, NULL,
394 // TODO: Remove added selections from the collection if parsing failed?
395 _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
397 GMX_RELEASE_ASSERT(status == 0,
398 "Parser errors should have resulted in an exception");
402 int status = runParserLoop(scanner, parserState.get(), false);
403 GMX_RELEASE_ASSERT(status == 0,
404 "Parser errors should have resulted in an exception");
407 scannerGuard.reset();
408 int nr = sc->sel.size() - oldCount;
409 if (maxnr > 0 && nr != maxnr)
412 = formatString("Too few selections provided; got %d, expected %d",
414 GMX_THROW(InvalidInputError(message));
417 SelectionList result;
418 SelectionDataList::const_iterator i;
420 for (i = sc->sel.begin() + oldCount; i != sc->sel.end(); ++i)
422 result.push_back(Selection(i->get()));
428 * Checks that index groups have valid atom indices.
430 * \param[in] root Root of selection tree to process.
431 * \param[in] natoms Maximum number of atoms that the selections are set
433 * \param errors Object for reporting any error messages.
434 * \throws std::bad_alloc if out of memory.
436 * Recursively checks the selection tree for index groups.
437 * Each found group is checked that it only contains atom indices that match
438 * the topology/maximum number of atoms set for the selection collection.
439 * Any issues are reported to \p errors.
441 void checkExternalGroups(const SelectionTreeElementPointer &root,
443 ExceptionInitializer *errors)
445 if (root->type == SEL_CONST && root->v.type == GROUP_VALUE)
449 root->checkIndexGroup(natoms);
451 catch (const UserInputError &)
453 errors->addCurrentExceptionAsNested();
457 SelectionTreeElementPointer child = root->child;
460 checkExternalGroups(child, natoms, errors);
468 void SelectionCollection::Impl::resolveExternalGroups(
469 const SelectionTreeElementPointer &root,
470 ExceptionInitializer *errors)
473 if (root->type == SEL_GROUPREF)
477 root->resolveIndexGroupReference(grps_, sc_.gall.isize);
479 catch (const UserInputError &)
481 errors->addCurrentExceptionAsNested();
485 SelectionTreeElementPointer child = root->child;
488 resolveExternalGroups(child, errors);
489 root->flags |= (child->flags & SEL_UNSORTED);
495 /********************************************************************
496 * SelectionCollection
499 SelectionCollection::SelectionCollection()
505 SelectionCollection::~SelectionCollection()
511 SelectionCollection::initOptions(Options *options)
513 const char * const debug_levels[]
514 = { "no", "basic", "compile", "eval", "full" };
516 bool bAllowNonAtomOutput = false;
517 SelectionDataList::const_iterator iter;
518 for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
520 const internal::SelectionData &sel = **iter;
521 if (!sel.hasFlag(efSelection_OnlyAtoms))
523 bAllowNonAtomOutput = true;
527 const char *const *postypes = PositionCalculationCollection::typeEnumValues;
528 options->addOption(StringOption("selrpos")
529 .enumValueFromNullTerminatedArray(postypes)
530 .store(&impl_->rpost_).defaultValue(postypes[0])
531 .description("Selection reference positions"));
532 if (bAllowNonAtomOutput)
534 options->addOption(StringOption("seltype")
535 .enumValueFromNullTerminatedArray(postypes)
536 .store(&impl_->spost_).defaultValue(postypes[0])
537 .description("Default selection output positions"));
541 impl_->spost_ = postypes[0];
543 GMX_RELEASE_ASSERT(impl_->debugLevel_ >= 0 && impl_->debugLevel_ <= 4,
544 "Debug level out of range");
545 options->addOption(StringOption("seldebug").hidden(impl_->debugLevel_ == 0)
546 .enumValue(debug_levels)
547 .defaultValue(debug_levels[impl_->debugLevel_])
548 .storeEnumIndex(&impl_->debugLevel_)
549 .description("Print out selection trees for debugging"));
554 SelectionCollection::setReferencePosType(const char *type)
556 GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
557 // Check that the type is valid, throw if it is not.
558 e_poscalc_t dummytype;
560 PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
561 impl_->rpost_ = type;
566 SelectionCollection::setOutputPosType(const char *type)
568 GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
569 // Check that the type is valid, throw if it is not.
570 e_poscalc_t dummytype;
572 PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
573 impl_->spost_ = type;
578 SelectionCollection::setDebugLevel(int debugLevel)
580 impl_->debugLevel_ = debugLevel;
585 SelectionCollection::setTopology(t_topology *top, int natoms)
587 GMX_RELEASE_ASSERT(natoms > 0 || top != NULL,
588 "The number of atoms must be given if there is no topology");
589 // Get the number of atoms from the topology if it is not given.
592 natoms = top->atoms.nr;
594 if (impl_->bExternalGroupsSet_)
596 ExceptionInitializer errors("Invalid index group references encountered");
597 SelectionTreeElementPointer root = impl_->sc_.root;
600 checkExternalGroups(root, natoms, &errors);
603 if (errors.hasNestedExceptions())
605 GMX_THROW(InconsistentInputError(errors));
608 gmx_ana_selcollection_t *sc = &impl_->sc_;
609 // Do this first, as it allocates memory, while the others don't throw.
610 gmx_ana_index_init_simple(&sc->gall, natoms);
611 sc->pcc.setTopology(top);
617 SelectionCollection::setIndexGroups(gmx_ana_indexgrps_t *grps)
619 GMX_RELEASE_ASSERT(grps == NULL || !impl_->bExternalGroupsSet_,
620 "Can only set external groups once or clear them afterwards");
622 impl_->bExternalGroupsSet_ = true;
624 ExceptionInitializer errors("Invalid index group reference(s)");
625 SelectionTreeElementPointer root = impl_->sc_.root;
628 impl_->resolveExternalGroups(root, &errors);
629 root->checkUnsortedAtoms(true, &errors);
632 if (errors.hasNestedExceptions())
634 GMX_THROW(InconsistentInputError(errors));
636 for (size_t i = 0; i < impl_->sc_.sel.size(); ++i)
638 impl_->sc_.sel[i]->refreshName();
644 SelectionCollection::requiresTopology() const
649 if (!impl_->rpost_.empty())
652 // Should not throw, because has been checked earlier.
653 PositionCalculationCollection::typeFromEnum(impl_->rpost_.c_str(),
655 if (type != POS_ATOM)
660 if (!impl_->spost_.empty())
663 // Should not throw, because has been checked earlier.
664 PositionCalculationCollection::typeFromEnum(impl_->spost_.c_str(),
666 if (type != POS_ATOM)
672 SelectionTreeElementPointer sel = impl_->sc_.root;
675 if (_gmx_selelem_requires_top(*sel))
685 SelectionCollection::parseFromStdin(int count, bool bInteractive,
686 const std::string &context)
688 return parseInteractive(count, &StandardInputStream::instance(),
689 bInteractive ? &TextOutputFile::standardError() : NULL,
694 SelectionCollection::parseInteractive(int count,
695 TextInputStream *inputStream,
696 TextOutputStream *statusStream,
697 const std::string &context)
701 boost::scoped_ptr<TextWriter> statusWriter;
702 if (statusStream != NULL)
704 statusWriter.reset(new TextWriter(statusStream));
706 _gmx_sel_init_lexer(&scanner, &impl_->sc_, statusWriter.get(),
707 count, impl_->bExternalGroupsSet_, impl_->grps_);
708 return runParser(scanner, inputStream, true, count, context);
713 SelectionCollection::parseFromFile(const std::string &filename)
719 TextInputFile file(filename);
720 // TODO: Exception-safe way of using the lexer.
721 _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1,
722 impl_->bExternalGroupsSet_,
724 _gmx_sel_set_lex_input_file(scanner, file.handle());
725 return runParser(scanner, NULL, false, -1, std::string());
727 catch (GromacsException &ex)
729 ex.prependContext(formatString(
730 "Error in parsing selections from file '%s'",
738 SelectionCollection::parseFromString(const std::string &str)
742 _gmx_sel_init_lexer(&scanner, &impl_->sc_, NULL, -1,
743 impl_->bExternalGroupsSet_,
745 _gmx_sel_set_lex_input_str(scanner, str.c_str());
746 return runParser(scanner, NULL, false, -1, std::string());
751 SelectionCollection::compile()
753 if (impl_->sc_.top == NULL && requiresTopology())
755 GMX_THROW(InconsistentInputError("Selection requires topology information, but none provided"));
757 if (!impl_->bExternalGroupsSet_)
759 setIndexGroups(NULL);
761 if (impl_->debugLevel_ >= 1)
763 printTree(stderr, false);
766 SelectionCompiler compiler;
767 compiler.compile(this);
769 if (impl_->debugLevel_ >= 1)
771 std::fprintf(stderr, "\n");
772 printTree(stderr, false);
773 std::fprintf(stderr, "\n");
774 impl_->sc_.pcc.printTree(stderr);
775 std::fprintf(stderr, "\n");
777 impl_->sc_.pcc.initEvaluation();
778 if (impl_->debugLevel_ >= 1)
780 impl_->sc_.pcc.printTree(stderr);
781 std::fprintf(stderr, "\n");
784 // TODO: It would be nicer to associate the name of the selection option
785 // (if available) to the error message.
786 SelectionDataList::const_iterator iter;
787 for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
789 const internal::SelectionData &sel = **iter;
790 if (sel.hasFlag(efSelection_OnlyAtoms))
792 if (!sel.hasOnlyAtoms())
794 std::string message = formatString(
795 "Selection '%s' does not evaluate to individual atoms. "
796 "This is not allowed in this context.",
797 sel.selectionText());
798 GMX_THROW(InvalidInputError(message));
801 if (sel.hasFlag(efSelection_DisallowEmpty))
803 if (sel.posCount() == 0)
805 std::string message = formatString(
806 "Selection '%s' never matches any atoms.",
807 sel.selectionText());
808 GMX_THROW(InvalidInputError(message));
816 SelectionCollection::evaluate(t_trxframe *fr, t_pbc *pbc)
818 if (fr->natoms <= impl_->maxAtomIndex_)
820 std::string message = formatString(
821 "Trajectory has less atoms (%d) than what is required for "
822 "evaluating the provided selections (atoms up to index %d "
823 "are required).", fr->natoms, impl_->maxAtomIndex_ + 1);
824 GMX_THROW(InconsistentInputError(message));
826 impl_->sc_.pcc.initFrame();
828 SelectionEvaluator evaluator;
829 evaluator.evaluate(this, fr, pbc);
831 if (impl_->debugLevel_ >= 3)
833 std::fprintf(stderr, "\n");
834 printTree(stderr, true);
840 SelectionCollection::evaluateFinal(int nframes)
842 SelectionEvaluator evaluator;
843 evaluator.evaluateFinal(this, nframes);
848 SelectionCollection::printTree(FILE *fp, bool bValues) const
850 SelectionTreeElementPointer sel = impl_->sc_.root;
853 _gmx_selelem_print_tree(fp, *sel, bValues, 0);
860 SelectionCollection::printXvgrInfo(FILE *out, output_env_t oenv) const
862 if (output_env_get_xvg_format(oenv) != exvgNONE)
864 const gmx_ana_selcollection_t &sc = impl_->sc_;
865 std::fprintf(out, "# Selections:\n");
866 for (int i = 0; i < sc.nvars; ++i)
868 std::fprintf(out, "# %s\n", sc.varstrs[i]);
870 for (size_t i = 0; i < sc.sel.size(); ++i)
872 std::fprintf(out, "# %s\n", sc.sel[i]->selectionText());
874 std::fprintf(out, "#\n");