Merge "Merge release-4-6 into master"
[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, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * 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 "selectioncollection.h"
43
44 #include <cstdio>
45
46 #include <boost/shared_ptr.hpp>
47
48 #include "gromacs/legacyheaders/oenv.h"
49 #include "gromacs/legacyheaders/smalloc.h"
50 #include "gromacs/legacyheaders/xvgr.h"
51
52 #include "gromacs/options/basicoptions.h"
53 #include "gromacs/options/options.h"
54 #include "gromacs/selection/selection.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/file.h"
57 #include "gromacs/utility/gmxassert.h"
58 #include "gromacs/utility/messagestringcollector.h"
59 #include "gromacs/utility/stringutil.h"
60
61 #include "compiler.h"
62 #include "mempool.h"
63 #include "parser.h"
64 #include "poscalc.h"
65 #include "scanner.h"
66 #include "selectioncollection-impl.h"
67 #include "selelem.h"
68 #include "selhelp.h"
69 #include "selmethod.h"
70 #include "symrec.h"
71
72 namespace gmx
73 {
74
75 /********************************************************************
76  * SelectionCollection::Impl
77  */
78
79 SelectionCollection::Impl::Impl()
80     : debugLevel_(0), bExternalGroupsSet_(false), grps_(NULL)
81 {
82     sc_.nvars     = 0;
83     sc_.varstrs   = NULL;
84     sc_.top       = NULL;
85     gmx_ana_index_clear(&sc_.gall);
86     sc_.mempool   = NULL;
87     sc_.symtab.reset(new SelectionParserSymbolTable);
88     gmx_ana_selmethod_register_defaults(sc_.symtab.get());
89 }
90
91
92 SelectionCollection::Impl::~Impl()
93 {
94     clearSymbolTable();
95     // The tree must be freed before the SelectionData objects, since the
96     // tree may hold references to the position data in SelectionData.
97     sc_.root.reset();
98     sc_.sel.clear();
99     for (int i = 0; i < sc_.nvars; ++i)
100     {
101         sfree(sc_.varstrs[i]);
102     }
103     sfree(sc_.varstrs);
104     gmx_ana_index_deinit(&sc_.gall);
105     if (sc_.mempool)
106     {
107         _gmx_sel_mempool_destroy(sc_.mempool);
108     }
109 }
110
111
112 void
113 SelectionCollection::Impl::clearSymbolTable()
114 {
115     sc_.symtab.reset();
116 }
117
118
119 namespace
120 {
121
122 /*! \brief
123  * Reads a single selection line from stdin.
124  *
125  * \param[in]  infile        File to read from (typically File::standardInput()).
126  * \param[in]  bInteractive  Whether to print interactive prompts.
127  * \param[out] line          The read line in stored here.
128  * \returns true if something was read, false if at end of input.
129  *
130  * Handles line continuation, reading also the continuing line(s) in one call.
131  */
132 bool promptLine(File *infile, bool bInteractive, std::string *line)
133 {
134     if (bInteractive)
135     {
136         fprintf(stderr, "> ");
137     }
138     if (!infile->readLine(line))
139     {
140         return false;
141     }
142     while (endsWith(*line, "\\\n"))
143     {
144         line->resize(line->length() - 2);
145         if (bInteractive)
146         {
147             fprintf(stderr, "... ");
148         }
149         std::string buffer;
150         // Return value ignored, buffer remains empty and works correctly
151         // if there is nothing to read.
152         infile->readLine(&buffer);
153         line->append(buffer);
154     }
155     if (endsWith(*line, "\n"))
156     {
157         line->resize(line->length() - 1);
158     }
159     else if (bInteractive)
160     {
161         fprintf(stderr, "\n");
162     }
163     return true;
164 }
165
166 /*! \brief
167  * Helper function for tokenizing the input and pushing them to the parser.
168  *
169  * \param     scanner       Tokenizer data structure.
170  * \param     parserState   Parser data structure.
171  * \param[in] bInteractive  Whether to operate in interactive mode.
172  *
173  * Repeatedly reads tokens using \p scanner and pushes them to the parser with
174  * \p parserState until there is no more input, or until enough input is given
175  * (only in interactive mode).
176  */
177 int runParserLoop(yyscan_t scanner, _gmx_sel_yypstate *parserState,
178                   bool bInteractive)
179 {
180     int status    = YYPUSH_MORE;
181     int prevToken = 0;
182     do
183     {
184         YYSTYPE value;
185         int     token = _gmx_sel_yylex(&value, scanner);
186         if (bInteractive)
187         {
188             if (token == 0)
189             {
190                 break;
191             }
192             // Empty commands cause the interactive parser to print out
193             // status information. This avoids producing those unnecessarily,
194             // e.g., from "resname RA;;".
195             if (prevToken == CMD_SEP && token == CMD_SEP)
196             {
197                 continue;
198             }
199             prevToken = token;
200         }
201         status = _gmx_sel_yypush_parse(parserState, token, &value, scanner);
202     }
203     while (status == YYPUSH_MORE);
204     _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
205     return status;
206 }
207
208 /*! \brief
209  * Helper function that runs the parser once the tokenizer has been
210  * initialized.
211  *
212  * \param[in,out] scanner Scanner data structure.
213  * \param[in]     bStdIn  Whether to use a line-based reading
214  *      algorithm designed for interactive input.
215  * \param[in]     maxnr   Maximum number of selections to parse
216  *      (if -1, parse as many as provided by the user).
217  * \returns       Vector of parsed selections.
218  * \throws        std::bad_alloc if out of memory.
219  * \throws        InvalidInputError if there is a parsing error.
220  *
221  * Used internally to implement parseFromStdin(), parseFromFile() and
222  * parseFromString().
223  */
224 SelectionList runParser(yyscan_t scanner, bool bStdIn, int maxnr)
225 {
226     boost::shared_ptr<void>  scannerGuard(scanner, &_gmx_sel_free_lexer);
227     gmx_ana_selcollection_t *sc = _gmx_sel_lexer_selcollection(scanner);
228
229     MessageStringCollector   errors;
230     _gmx_sel_set_lexer_error_reporter(scanner, &errors);
231
232     int  oldCount = sc->sel.size();
233     bool bOk      = false;
234     {
235         boost::shared_ptr<_gmx_sel_yypstate> parserState(
236                 _gmx_sel_yypstate_new(), &_gmx_sel_yypstate_delete);
237         if (bStdIn)
238         {
239             File       &stdinFile(File::standardInput());
240             bool        bInteractive = _gmx_sel_is_lexer_interactive(scanner);
241             std::string line;
242             int         status;
243             while (promptLine(&stdinFile, bInteractive, &line))
244             {
245                 line.append("\n");
246                 _gmx_sel_set_lex_input_str(scanner, line.c_str());
247                 status = runParserLoop(scanner, parserState.get(), true);
248                 if (status != YYPUSH_MORE)
249                 {
250                     // TODO: Check if there is more input, and issue an
251                     // error/warning if some input was ignored.
252                     goto early_termination;
253                 }
254                 if (!errors.isEmpty() && bInteractive)
255                 {
256                     fprintf(stderr, "%s", errors.toString().c_str());
257                     errors.clear();
258                 }
259             }
260             status = _gmx_sel_yypush_parse(parserState.get(), 0, NULL,
261                                            scanner);
262             _gmx_sel_lexer_rethrow_exception_if_occurred(scanner);
263 early_termination:
264             bOk = (status == 0);
265         }
266         else
267         {
268             int status = runParserLoop(scanner, parserState.get(), false);
269             bOk = (status == 0);
270         }
271     }
272     scannerGuard.reset();
273     int nr = sc->sel.size() - oldCount;
274     if (maxnr > 0 && nr != maxnr)
275     {
276         bOk = false;
277         errors.append("Too few selections provided");
278     }
279
280     // TODO: Remove added selections from the collection if parsing failed?
281     if (!bOk || !errors.isEmpty())
282     {
283         GMX_ASSERT(!bOk && !errors.isEmpty(), "Inconsistent error reporting");
284         GMX_THROW(InvalidInputError(errors.toString()));
285     }
286
287     SelectionList                     result;
288     SelectionDataList::const_iterator i;
289     result.reserve(nr);
290     for (i = sc->sel.begin() + oldCount; i != sc->sel.end(); ++i)
291     {
292         result.push_back(Selection(i->get()));
293     }
294     return result;
295 }
296
297 }   // namespace
298
299
300 void SelectionCollection::Impl::resolveExternalGroups(
301         const SelectionTreeElementPointer &root,
302         MessageStringCollector            *errors)
303 {
304
305     if (root->type == SEL_GROUPREF)
306     {
307         bool bOk = true;
308         if (grps_ == NULL)
309         {
310             // TODO: Improve error messages
311             errors->append("Unknown group referenced in a selection");
312             bOk = false;
313         }
314         else if (root->u.gref.name != NULL)
315         {
316             char *name = root->u.gref.name;
317             if (!gmx_ana_indexgrps_find(&root->u.cgrp, grps_, name))
318             {
319                 // TODO: Improve error messages
320                 errors->append("Unknown group referenced in a selection");
321                 bOk = false;
322             }
323             else
324             {
325                 sfree(name);
326             }
327         }
328         else
329         {
330             if (!gmx_ana_indexgrps_extract(&root->u.cgrp, grps_,
331                                            root->u.gref.id))
332             {
333                 // TODO: Improve error messages
334                 errors->append("Unknown group referenced in a selection");
335                 bOk = false;
336             }
337         }
338         if (bOk)
339         {
340             root->type = SEL_CONST;
341             root->setName(root->u.cgrp.name);
342         }
343     }
344
345     SelectionTreeElementPointer child = root->child;
346     while (child)
347     {
348         resolveExternalGroups(child, errors);
349         child = child->next;
350     }
351 }
352
353
354 /********************************************************************
355  * SelectionCollection
356  */
357
358 SelectionCollection::SelectionCollection()
359     : impl_(new Impl)
360 {
361 }
362
363
364 SelectionCollection::~SelectionCollection()
365 {
366 }
367
368
369 void
370 SelectionCollection::initOptions(Options *options)
371 {
372     const char * const debug_levels[]
373         = {"no", "basic", "compile", "eval", "full" };
374     /*
375        static const char * const desc[] = {
376         "This program supports selections in addition to traditional",
377         "index files. Use [TT]-select help[tt] for additional information,",
378         "or type 'help' in the selection prompt.",
379         NULL,
380        };
381        options.setDescription(desc);
382      */
383
384     const char *const *postypes = PositionCalculationCollection::typeEnumValues;
385     options->addOption(StringOption("selrpos")
386                            .enumValueFromNullTerminatedArray(postypes)
387                            .store(&impl_->rpost_).defaultValue(postypes[0])
388                            .description("Selection reference positions"));
389     options->addOption(StringOption("seltype")
390                            .enumValueFromNullTerminatedArray(postypes)
391                            .store(&impl_->spost_).defaultValue(postypes[0])
392                            .description("Default selection output positions"));
393     GMX_RELEASE_ASSERT(impl_->debugLevel_ >= 0 && impl_->debugLevel_ <= 4,
394                        "Debug level out of range");
395     options->addOption(StringOption("seldebug").hidden(impl_->debugLevel_ == 0)
396                            .enumValue(debug_levels)
397                            .defaultValue(debug_levels[impl_->debugLevel_])
398                            .storeEnumIndex(&impl_->debugLevel_)
399                            .description("Print out selection trees for debugging"));
400 }
401
402
403 void
404 SelectionCollection::setReferencePosType(const char *type)
405 {
406     GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
407     // Check that the type is valid, throw if it is not.
408     e_poscalc_t  dummytype;
409     int          dummyflags;
410     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
411     impl_->rpost_ = type;
412 }
413
414
415 void
416 SelectionCollection::setOutputPosType(const char *type)
417 {
418     GMX_RELEASE_ASSERT(type != NULL, "Cannot assign NULL position type");
419     // Check that the type is valid, throw if it is not.
420     e_poscalc_t  dummytype;
421     int          dummyflags;
422     PositionCalculationCollection::typeFromEnum(type, &dummytype, &dummyflags);
423     impl_->spost_ = type;
424 }
425
426
427 void
428 SelectionCollection::setDebugLevel(int debugLevel)
429 {
430     impl_->debugLevel_ = debugLevel;
431 }
432
433
434 void
435 SelectionCollection::setTopology(t_topology *top, int natoms)
436 {
437     GMX_RELEASE_ASSERT(natoms > 0 || top != NULL,
438                        "The number of atoms must be given if there is no topology");
439     // Get the number of atoms from the topology if it is not given.
440     if (natoms <= 0)
441     {
442         natoms = top->atoms.nr;
443     }
444     gmx_ana_selcollection_t *sc = &impl_->sc_;
445     // Do this first, as it allocates memory, while the others don't throw.
446     gmx_ana_index_init_simple(&sc->gall, natoms, NULL);
447     sc->pcc.setTopology(top);
448     sc->top = top;
449 }
450
451
452 void
453 SelectionCollection::setIndexGroups(gmx_ana_indexgrps_t *grps)
454 {
455     GMX_RELEASE_ASSERT(grps == NULL || !impl_->bExternalGroupsSet_,
456                        "Can only set external groups once or clear them afterwards");
457     impl_->grps_               = grps;
458     impl_->bExternalGroupsSet_ = true;
459
460     MessageStringCollector      errors;
461     SelectionTreeElementPointer root = impl_->sc_.root;
462     while (root)
463     {
464         impl_->resolveExternalGroups(root, &errors);
465         root = root->next;
466     }
467     if (!errors.isEmpty())
468     {
469         GMX_THROW(InvalidInputError(errors.toString()));
470     }
471 }
472
473
474 bool
475 SelectionCollection::requiresTopology() const
476 {
477     e_poscalc_t  type;
478     int          flags;
479
480     if (!impl_->rpost_.empty())
481     {
482         flags = 0;
483         // Should not throw, because has been checked earlier.
484         PositionCalculationCollection::typeFromEnum(impl_->rpost_.c_str(),
485                                                     &type, &flags);
486         if (type != POS_ATOM)
487         {
488             return true;
489         }
490     }
491     if (!impl_->spost_.empty())
492     {
493         flags = 0;
494         // Should not throw, because has been checked earlier.
495         PositionCalculationCollection::typeFromEnum(impl_->spost_.c_str(),
496                                                     &type, &flags);
497         if (type != POS_ATOM)
498         {
499             return true;
500         }
501     }
502
503     SelectionTreeElementPointer sel = impl_->sc_.root;
504     while (sel)
505     {
506         if (_gmx_selelem_requires_top(*sel))
507         {
508             return true;
509         }
510         sel = sel->next;
511     }
512     return false;
513 }
514
515
516 SelectionList
517 SelectionCollection::parseFromStdin(int nr, bool bInteractive)
518 {
519     yyscan_t scanner;
520
521     _gmx_sel_init_lexer(&scanner, &impl_->sc_, bInteractive, nr,
522                         impl_->bExternalGroupsSet_,
523                         impl_->grps_);
524     return runParser(scanner, true, nr);
525 }
526
527
528 SelectionList
529 SelectionCollection::parseFromFile(const std::string &filename)
530 {
531
532     try
533     {
534         yyscan_t scanner;
535         File     file(filename, "r");
536         // TODO: Exception-safe way of using the lexer.
537         _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1,
538                             impl_->bExternalGroupsSet_,
539                             impl_->grps_);
540         _gmx_sel_set_lex_input_file(scanner, file.handle());
541         return runParser(scanner, false, -1);
542     }
543     catch (GromacsException &ex)
544     {
545         ex.prependContext(formatString(
546                                   "Error in parsing selections from file '%s'",
547                                   filename.c_str()));
548         throw;
549     }
550 }
551
552
553 SelectionList
554 SelectionCollection::parseFromString(const std::string &str)
555 {
556     yyscan_t scanner;
557
558     _gmx_sel_init_lexer(&scanner, &impl_->sc_, false, -1,
559                         impl_->bExternalGroupsSet_,
560                         impl_->grps_);
561     _gmx_sel_set_lex_input_str(scanner, str.c_str());
562     return runParser(scanner, false, -1);
563 }
564
565
566 void
567 SelectionCollection::compile()
568 {
569     if (impl_->sc_.top == NULL && requiresTopology())
570     {
571         GMX_THROW(InconsistentInputError("Selection requires topology information, but none provided"));
572     }
573     if (!impl_->bExternalGroupsSet_)
574     {
575         setIndexGroups(NULL);
576     }
577     if (impl_->debugLevel_ >= 1)
578     {
579         printTree(stderr, false);
580     }
581
582     SelectionCompiler compiler;
583     compiler.compile(this);
584
585     if (impl_->debugLevel_ >= 1)
586     {
587         std::fprintf(stderr, "\n");
588         printTree(stderr, false);
589         std::fprintf(stderr, "\n");
590         impl_->sc_.pcc.printTree(stderr);
591         std::fprintf(stderr, "\n");
592     }
593     impl_->sc_.pcc.initEvaluation();
594     if (impl_->debugLevel_ >= 1)
595     {
596         impl_->sc_.pcc.printTree(stderr);
597         std::fprintf(stderr, "\n");
598     }
599 }
600
601
602 void
603 SelectionCollection::evaluate(t_trxframe *fr, t_pbc *pbc)
604 {
605     impl_->sc_.pcc.initFrame();
606
607     SelectionEvaluator evaluator;
608     evaluator.evaluate(this, fr, pbc);
609
610     if (impl_->debugLevel_ >= 3)
611     {
612         std::fprintf(stderr, "\n");
613         printTree(stderr, true);
614     }
615 }
616
617
618 void
619 SelectionCollection::evaluateFinal(int nframes)
620 {
621     SelectionEvaluator evaluator;
622     evaluator.evaluateFinal(this, nframes);
623 }
624
625
626 void
627 SelectionCollection::printTree(FILE *fp, bool bValues) const
628 {
629     SelectionTreeElementPointer sel = impl_->sc_.root;
630     while (sel)
631     {
632         _gmx_selelem_print_tree(fp, *sel, bValues, 0);
633         sel = sel->next;
634     }
635 }
636
637
638 void
639 SelectionCollection::printXvgrInfo(FILE *out, output_env_t oenv) const
640 {
641     if (output_env_get_xvg_format(oenv) != exvgNONE)
642     {
643         const gmx_ana_selcollection_t &sc = impl_->sc_;
644         std::fprintf(out, "# Selections:\n");
645         for (int i = 0; i < sc.nvars; ++i)
646         {
647             std::fprintf(out, "#   %s\n", sc.varstrs[i]);
648         }
649         for (size_t i = 0; i < sc.sel.size(); ++i)
650         {
651             std::fprintf(out, "#   %s\n", sc.sel[i]->selectionText());
652         }
653         std::fprintf(out, "#\n");
654     }
655 }
656
657 // static
658 HelpTopicPointer
659 SelectionCollection::createDefaultHelpTopic()
660 {
661     return createSelectionHelpTopic();
662 }
663
664 } // namespace gmx