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