/*
+ * This file is part of the GROMACS molecular simulation package.
*
- * This source code is part of
+ * Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by
+ * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
+ * and including many others, as listed in the AUTHORS file in the
+ * top-level source directory and at http://www.gromacs.org.
*
- * G R O M A C S
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
*
- * GROningen MAchine for Chemical Simulations
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
*
- * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
- * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
- * Copyright (c) 2001-2009, The GROMACS development team,
- * check out http://www.gromacs.org for more information.
-
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
- * If you want to redistribute modifications, please consider that
- * scientific software is very special. Version control is crucial -
- * bugs must be traceable. We will be happy to consider code for
- * inclusion in the official distribution, but derived work must not
- * be called official GROMACS. Details are found in the README & COPYING
- * files - if they are missing, get the official version at www.gromacs.org.
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
*
* To help us fund GROMACS development, we humbly ask that you cite
- * the papers on the package - you can find them in the top README file.
- *
- * For more info, check our website at http://www.gromacs.org
+ * the research papers on the package. Check out http://www.gromacs.org.
*/
/*! \internal \file
* \brief
* Implements gmx::SelectionCollection.
*
- * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
* \ingroup module_selection
*/
+#include "gmxpre.h"
+
#include "selectioncollection.h"
+#include <cctype>
#include <cstdio>
+#include <string>
+#include <vector>
+
#include <boost/shared_ptr.hpp>
+#include "gromacs/fileio/trx.h"
#include "gromacs/legacyheaders/oenv.h"
-#include "gromacs/legacyheaders/smalloc.h"
-#include "gromacs/legacyheaders/xvgr.h"
-
+#include "gromacs/onlinehelp/helpmanager.h"
+#include "gromacs/onlinehelp/helpwritercontext.h"
#include "gromacs/options/basicoptions.h"
#include "gromacs/options/options.h"
#include "gromacs/selection/selection.h"
+#include "gromacs/selection/selhelp.h"
+#include "gromacs/topology/topology.h"
#include "gromacs/utility/exceptions.h"
#include "gromacs/utility/file.h"
#include "gromacs/utility/gmxassert.h"
#include "gromacs/utility/messagestringcollector.h"
+#include "gromacs/utility/smalloc.h"
#include "gromacs/utility/stringutil.h"
#include "compiler.h"
#include "scanner.h"
#include "selectioncollection-impl.h"
#include "selelem.h"
-#include "selhelp.h"
#include "selmethod.h"
#include "symrec.h"
*/
SelectionCollection::Impl::Impl()
- : debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
+ : maxAtomIndex_(0), debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
{
sc_.nvars = 0;
sc_.varstrs = NULL;
{
fprintf(stderr, "> ");
}
- if (!infile->readLine(line))
+ if (!infile->readLineWithTrailingSpace(line))
{
return false;
}
- while(endsWith(*line, "\\\n"))
+ while (endsWith(*line, "\\\n"))
{
line->resize(line->length() - 2);
if (bInteractive)
std::string buffer;
// Return value ignored, buffer remains empty and works correctly
// if there is nothing to read.
- infile->readLine(&buffer);
+ infile->readLineWithTrailingSpace(&buffer);
line->append(buffer);
}
if (endsWith(*line, "\n"))
int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
bool bInteractive)
{
- int status = YYPUSH_MORE;
+ int status = YYPUSH_MORE;
int prevToken = 0;
do
{
YYSTYPE value;
- int token = _gmx_sel_yylex(&value, scanner);
+ int token = _gmx_sel_yylex(&value, scanner);
if (bInteractive)
{
if (token == 0)
return status;
}
+/*! \brief
+ * Print current status in response to empty line in interactive input.
+ *
+ * \param[in] sc Selection collection data structure.
+ * \param[in] grps Available index groups.
+ * \param[in] firstSelection Index of first selection from this interactive
+ * session.
+ * \param[in] maxCount Maximum number of selections.
+ * \param[in] context Context to print for what the selections are for.
+ * \param[in] bFirst Whether this is the header that is printed before
+ * any user input.
+ *
+ * Prints the available index groups and currently provided selections.
+ */
+void printCurrentStatus(gmx_ana_selcollection_t *sc, gmx_ana_indexgrps_t *grps,
+ size_t firstSelection, int maxCount,
+ const std::string &context, bool bFirst)
+{
+ if (grps != NULL)
+ {
+ std::fprintf(stderr, "Available static index groups:\n");
+ gmx_ana_indexgrps_print(stderr, grps, 0);
+ }
+ std::fprintf(stderr, "Specify ");
+ if (maxCount < 0)
+ {
+ std::fprintf(stderr, "any number of selections");
+ }
+ else if (maxCount == 1)
+ {
+ std::fprintf(stderr, "a selection");
+ }
+ else
+ {
+ std::fprintf(stderr, "%d selections", maxCount);
+ }
+ std::fprintf(stderr, "%s%s:\n",
+ context.empty() ? "" : " ", context.c_str());
+ std::fprintf(stderr,
+ "(one per line, <enter> for status/groups, 'help' for help%s)\n",
+ maxCount < 0 ? ", Ctrl-D to end" : "");
+ if (!bFirst && (sc->nvars > 0 || sc->sel.size() > firstSelection))
+ {
+ std::fprintf(stderr, "Currently provided selections:\n");
+ for (int i = 0; i < sc->nvars; ++i)
+ {
+ std::fprintf(stderr, " %s\n", sc->varstrs[i]);
+ }
+ for (size_t i = firstSelection; i < sc->sel.size(); ++i)
+ {
+ std::fprintf(stderr, " %2d. %s\n",
+ static_cast<int>(i - firstSelection + 1),
+ sc->sel[i]->selectionText());
+ }
+ if (maxCount > 0)
+ {
+ const int remaining
+ = maxCount - static_cast<int>(sc->sel.size() - firstSelection);
+ std::fprintf(stderr, "(%d more selection%s required)\n",
+ remaining, remaining > 1 ? "s" : "");
+ }
+ }
+}
+
+/*! \brief
+ * Prints selection help in interactive selection input.
+ *
+ * \param[in] sc Selection collection data structure.
+ * \param[in] line Line of user input requesting help (starting with `help`).
+ *
+ * Initializes the selection help if not yet initialized, and finds the help
+ * topic based on words on the input line.
+ */
+void printHelp(gmx_ana_selcollection_t *sc, const std::string &line)
+{
+ if (sc->rootHelp.get() == NULL)
+ {
+ sc->rootHelp = createSelectionHelpTopic();
+ }
+ HelpWriterContext context(&File::standardError(),
+ eHelpOutputFormat_Console);
+ HelpManager manager(*sc->rootHelp, context);
+ try
+ {
+ std::vector<std::string> topic = splitString(line);
+ std::vector<std::string>::const_iterator value;
+ // First item in the list is the 'help' token.
+ for (value = topic.begin() + 1; value != topic.end(); ++value)
+ {
+ manager.enterTopic(*value);
+ }
+ }
+ catch (const InvalidInputError &ex)
+ {
+ fprintf(stderr, "%s\n", ex.what());
+ return;
+ }
+ manager.writeCurrentTopic();
+}
+
/*! \brief
* Helper function that runs the parser once the tokenizer has been
* initialized.
* algorithm designed for interactive input.
* \param[in] maxnr Maximum number of selections to parse
* (if -1, parse as many as provided by the user).
+ * \param[in] context Context to print for what the selections are for.
* \returns Vector of parsed selections.
* \throws std::bad_alloc if out of memory.
* \throws InvalidInputError if there is a parsing error.
* Used internally to implement parseFromStdin(), parseFromFile() and
* parseFromString().
*/
-SelectionList runParser(yyscan_t scanner, bool bStdIn, int maxnr)
+SelectionList runParser(yyscan_t scanner, bool bStdIn, int maxnr,
+ const std::string &context)
{
- boost::shared_ptr<void> scannerGuard(scanner, &_gmx_sel_free_lexer);
- gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner);
+ boost::shared_ptr<void> scannerGuard(scanner, &_gmx_sel_free_lexer);
+ gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner);
+ gmx_ana_indexgrps_t *grps = _gmx_sel_lexer_indexgrps(scanner);
- MessageStringCollector errors;
+ MessageStringCollector errors;
_gmx_sel_set_lexer_error_reporter(scanner, &errors);
- int oldCount = sc->sel.size();
- bool bOk = false;
+ size_t oldCount = sc->sel.size();
+ bool bOk = false;
{
boost::shared_ptr<_gmx_sel_yypstate> parserState(
_gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete);
if (bStdIn)
{
- File &stdinFile(File::standardInput());
- bool bInteractive = _gmx_sel_is_lexer_interactive(scanner);
+ File &stdinFile(File::standardInput());
+ const bool bInteractive = _gmx_sel_is_lexer_interactive(scanner);
+ if (bInteractive)
+ {
+ printCurrentStatus(sc, grps, oldCount, maxnr, context, true);
+ }
std::string line;
- int status;
+ int status;
while (promptLine(&stdinFile, bInteractive, &line))
{
+ if (bInteractive)
+ {
+ line = stripString(line);
+ if (line.empty())
+ {
+ printCurrentStatus(sc, grps, oldCount, maxnr, context, false);
+ continue;
+ }
+ if (startsWith(line, "help")
+ && (line[4] == 0 || std::isspace(line[4])))
+ {
+ printHelp(sc, line);
+ continue;
+ }
+ }
line.append("\n");
_gmx_sel_set_lex_input_str(scanner, line.c_str());
status = runParserLoop(scanner, parserState.get(), true);
GMX_THROW(InvalidInputError(errors.toString()));
}
- SelectionList result;
+ SelectionList result;
SelectionDataList::const_iterator i;
result.reserve(nr);
for (i = sc->sel.begin() + oldCount; i != sc->sel.end(); ++i)
return result;
}
-} // namespace
+/*! \brief
+ * Checks that index groups have valid atom indices.
+ *
+ * \param[in] root Root of selection tree to process.
+ * \param[in] natoms Maximum number of atoms that the selections are set
+ * to evaluate.
+ * \param errors Object for reporting any error messages.
+ * \throws std::bad_alloc if out of memory.
+ *
+ * Recursively checks the selection tree for index groups.
+ * Each found group is checked that it only contains atom indices that match
+ * the topology/maximum number of atoms set for the selection collection.
+ * Any issues are reported to \p errors.
+ */
+void checkExternalGroups(const SelectionTreeElementPointer &root,
+ int natoms,
+ ExceptionInitializer *errors)
+{
+ if (root->type == SEL_CONST && root->v.type == GROUP_VALUE)
+ {
+ try
+ {
+ root->checkIndexGroup(natoms);
+ }
+ catch (const UserInputError &)
+ {
+ errors->addCurrentExceptionAsNested();
+ }
+ }
+
+ SelectionTreeElementPointer child = root->child;
+ while (child)
+ {
+ checkExternalGroups(child, natoms, errors);
+ child = child->next;
+ }
+}
+
+} // namespace
void SelectionCollection::Impl::resolveExternalGroups(
const SelectionTreeElementPointer &root,
- MessageStringCollector *errors)
+ ExceptionInitializer *errors)
{
if (root->type == SEL_GROUPREF)
{
- bool bOk = true;
- if (grps_ == NULL)
+ try
{
- // TODO: Improve error messages
- errors->append("Unknown group referenced in a selection");
- bOk = false;
+ root->resolveIndexGroupReference(grps_, sc_.gall.isize);
}
- else if (root->u.gref.name != NULL)
+ catch (const UserInputError &)
{
- char *name = root->u.gref.name;
- if (!gmx_ana_indexgrps_find(&root->u.cgrp, grps_, name))
- {
- // TODO: Improve error messages
- errors->append("Unknown group referenced in a selection");
- bOk = false;
- }
- else
- {
- sfree(name);
- }
- }
- else
- {
- if (!gmx_ana_indexgrps_extract(&root->u.cgrp, grps_,
- root->u.gref.id))
- {
- // TODO: Improve error messages
- errors->append("Unknown group referenced in a selection");
- bOk = false;
- }
- }
- if (bOk)
- {
- root->type = SEL_CONST;
- root->setName(root->u.cgrp.name);
+ errors->addCurrentExceptionAsNested();
}
}
while (child)
{
resolveExternalGroups(child, errors);
- child = child->next;
+ root->flags |= (child->flags & SEL_UNSORTED);
+ child = child->next;
}
}
void
SelectionCollection::initOptions(Options *options)
{
- static const char * const debug_levels[]
- = {"no", "basic", "compile", "eval", "full", NULL};
- /*
- static const char * const desc[] = {
- "This program supports selections in addition to traditional",
- "index files. Use [TT]-select help[tt] for additional information,",
- "or type 'help' in the selection prompt.",
- NULL,
- };
- options.setDescription(desc);
- */
+ const char * const debug_levels[]
+ = { "no", "basic", "compile", "eval", "full" };
+
+ bool bAllowNonAtomOutput = false;
+ SelectionDataList::const_iterator iter;
+ for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
+ {
+ const internal::SelectionData &sel = **iter;
+ if (!sel.hasFlag(efSelection_OnlyAtoms))
+ {
+ bAllowNonAtomOutput = true;
+ }
+ }
const char *const *postypes = PositionCalculationCollection::typeEnumValues;
- options->addOption(StringOption("selrpos").enumValue(postypes)
+ options->addOption(StringOption("selrpos")
+ .enumValueFromNullTerminatedArray(postypes)
.store(&impl_->rpost_).defaultValue(postypes[0])
.description("Selection reference positions"));
- options->addOption(StringOption("seltype").enumValue(postypes)
- .store(&impl_->spost_).defaultValue(postypes[0])
- .description("Default selection output positions"));
+ if (bAllowNonAtomOutput)
+ {
+ options->addOption(StringOption("seltype")
+ .enumValueFromNullTerminatedArray(postypes)
+ .store(&impl_->spost_).defaultValue(postypes[0])
+ .description("Default selection output positions"));
+ }
+ else
+ {
+ impl_->spost_ = postypes[0];
+ }
GMX_RELEASE_ASSERT(impl_->debugLevel_ >= 0 && impl_->debugLevel_ <= 4,
"Debug level out of range");
options->addOption(StringOption("seldebug").hidden(impl_->debugLevel_ == 0)
SelectionCollection::setTopology(t_topology *top, int natoms)
{
GMX_RELEASE_ASSERT(natoms > 0 || top != NULL,
- "The number of atoms must be given if there is no topology");
+ "The number of atoms must be given if there is no topology");
// Get the number of atoms from the topology if it is not given.
if (natoms <= 0)
{
natoms = top->atoms.nr;
}
+ if (impl_->bExternalGroupsSet_)
+ {
+ ExceptionInitializer errors("Invalid index group references encountered");
+ SelectionTreeElementPointer root = impl_->sc_.root;
+ while (root)
+ {
+ checkExternalGroups(root, natoms, &errors);
+ root = root->next;
+ }
+ if (errors.hasNestedExceptions())
+ {
+ GMX_THROW(InconsistentInputError(errors));
+ }
+ }
gmx_ana_selcollection_t *sc = &impl_->sc_;
// Do this first, as it allocates memory, while the others don't throw.
- gmx_ana_index_init_simple(&sc->gall, natoms, NULL);
+ gmx_ana_index_init_simple(&sc->gall, natoms);
sc->pcc.setTopology(top);
sc->top = top;
}
{
GMX_RELEASE_ASSERT(grps == NULL || !impl_->bExternalGroupsSet_,
"Can only set external groups once or clear them afterwards");
- impl_->grps_ = grps;
+ impl_->grps_ = grps;
impl_->bExternalGroupsSet_ = true;
- MessageStringCollector errors;
+ ExceptionInitializer errors("Invalid index group reference(s)");
SelectionTreeElementPointer root = impl_->sc_.root;
while (root)
{
impl_->resolveExternalGroups(root, &errors);
+ root->checkUnsortedAtoms(true, &errors);
root = root->next;
}
- if (!errors.isEmpty())
+ if (errors.hasNestedExceptions())
{
- GMX_THROW(InvalidInputError(errors.toString()));
+ GMX_THROW(InconsistentInputError(errors));
+ }
+ for (size_t i = 0; i < impl_->sc_.sel.size(); ++i)
+ {
+ impl_->sc_.sel[i]->refreshName();
}
}
SelectionList
-SelectionCollection::parseFromStdin(int nr, bool bInteractive)
+SelectionCollection::parseFromStdin(int nr, bool bInteractive,
+ const std::string &context)
{
yyscan_t scanner;
_gmx_sel_init_lexer(&scanner, &impl_->sc_, bInteractive, nr,
impl_->bExternalGroupsSet_,
impl_->grps_);
- return runParser(scanner, true, nr);
+ return runParser(scanner, true, nr, context);
}
try
{
yyscan_t scanner;
- File file(filename, "r");
+ File file(filename, "r");
// TODO: Exception-safe way of using the lexer.
_gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1,
impl_->bExternalGroupsSet_,
impl_->grps_);
_gmx_sel_set_lex_input_file(scanner, file.handle());
- return runParser(scanner, false, -1);
+ return runParser(scanner, false, -1, std::string());
}
catch (GromacsException &ex)
{
ex.prependContext(formatString(
- "Error in parsing selections from file '%s'",
- filename.c_str()));
+ "Error in parsing selections from file '%s'",
+ filename.c_str()));
throw;
}
}
impl_->bExternalGroupsSet_,
impl_->grps_);
_gmx_sel_set_lex_input_str(scanner, str.c_str());
- return runParser(scanner, false, -1);
+ return runParser(scanner, false, -1, std::string());
}
impl_->sc_.pcc.printTree(stderr);
std::fprintf(stderr, "\n");
}
+
+ // TODO: It would be nicer to associate the name of the selection option
+ // (if available) to the error message.
+ SelectionDataList::const_iterator iter;
+ for (iter = impl_->sc_.sel.begin(); iter != impl_->sc_.sel.end(); ++iter)
+ {
+ const internal::SelectionData &sel = **iter;
+ if (sel.hasFlag(efSelection_OnlyAtoms))
+ {
+ if (!sel.hasOnlyAtoms())
+ {
+ std::string message = formatString(
+ "Selection '%s' does not evaluate to individual atoms. "
+ "This is not allowed in this context.",
+ sel.selectionText());
+ GMX_THROW(InvalidInputError(message));
+ }
+ }
+ if (sel.hasFlag(efSelection_DisallowEmpty))
+ {
+ if (sel.posCount() == 0)
+ {
+ std::string message = formatString(
+ "Selection '%s' never matches any atoms.",
+ sel.selectionText());
+ GMX_THROW(InvalidInputError(message));
+ }
+ }
+ }
}
void
SelectionCollection::evaluate(t_trxframe *fr, t_pbc *pbc)
{
+ if (fr->natoms <= impl_->maxAtomIndex_)
+ {
+ std::string message = formatString(
+ "Trajectory has less atoms (%d) than what is required for "
+ "evaluating the provided selections (atoms up to index %d "
+ "are required).", fr->natoms, impl_->maxAtomIndex_ + 1);
+ GMX_THROW(InconsistentInputError(message));
+ }
impl_->sc_.pcc.initFrame();
SelectionEvaluator evaluator;
}
}
-// static
-HelpTopicPointer
-SelectionCollection::createDefaultHelpTopic()
-{
- return createSelectionHelpTopic();
-}
-
} // namespace gmx