Sort all includes in src/gromacs
[alexxy/gromacs.git] / src / gromacs / selection / selectioncollection.cpp
index b7dbfdd01224ebf341b3859e64adb85e752884f3..f4bfd210a0663e69171982c1769970f3d5a9dc5f 100644 (file)
@@ -1,60 +1,70 @@
 /*
+ * 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
  */
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
+#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/selectioncollection.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"
@@ -64,7 +74,6 @@
 #include "scanner.h"
 #include "selectioncollection-impl.h"
 #include "selelem.h"
-#include "selhelp.h"
 #include "selmethod.h"
 #include "symrec.h"
 
@@ -76,7 +85,7 @@ namespace gmx
  */
 
 SelectionCollection::Impl::Impl()
-    : debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
+    : maxAtomIndex_(0), debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
 {
     sc_.nvars     = 0;
     sc_.varstrs   = NULL;
@@ -134,11 +143,11 @@ bool promptLine(File *infile, bool bInteractive, std::string *line)
     {
         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)
@@ -148,7 +157,7 @@ bool promptLine(File *infile, bool bInteractive, std::string *line)
         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"))
@@ -176,12 +185,12 @@ bool promptLine(File *infile, bool bInteractive, std::string *line)
 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)
@@ -204,6 +213,106 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
     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.
@@ -213,6 +322,7 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
  *      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.
@@ -220,27 +330,48 @@ int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
  * 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);
@@ -283,7 +414,7 @@ early_termination:
         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)
@@ -293,51 +424,61 @@ early_termination:
     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();
         }
     }
 
@@ -345,7 +486,8 @@ void SelectionCollection::Impl::resolveExternalGroups(
     while (child)
     {
         resolveExternalGroups(child, errors);
-        child = child->next;
+        root->flags |= (child->flags & SEL_UNSORTED);
+        child        = child->next;
     }
 }
 
@@ -368,25 +510,36 @@ SelectionCollection::~SelectionCollection()
 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)
@@ -432,15 +585,29 @@ void
 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;
 }
@@ -451,19 +618,24 @@ SelectionCollection::setIndexGroups(gmx_ana_indexgrps_t *grps)
 {
     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();
     }
 }
 
@@ -511,14 +683,15 @@ SelectionCollection::requiresTopology() const
 
 
 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);
 }
 
 
@@ -529,19 +702,19 @@ SelectionCollection::parseFromFile(const std::string &filename)
     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;
     }
 }
@@ -556,7 +729,7 @@ SelectionCollection::parseFromString(const std::string &str)
                         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());
 }
 
 
@@ -593,12 +766,49 @@ SelectionCollection::compile()
         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;
@@ -651,11 +861,4 @@ SelectionCollection::printXvgrInfo(FILE *out, output_env_t oenv) const
     }
 }
 
-// static
-HelpTopicPointer
-SelectionCollection::createDefaultHelpTopic()
-{
-    return createSelectionHelpTopic();
-}
-
 } // namespace gmx