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