Merge release-5-0 into release-5-1
[alexxy/gromacs.git] / src / gromacs / analysisdata / datamodulemanager.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2010,2011,2012,2013,2014, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source 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::AnalysisDataModuleManager.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_analysisdata
41  */
42 #include "gmxpre.h"
43
44 #include "datamodulemanager.h"
45
46 #include <vector>
47
48 #include "gromacs/analysisdata/abstractdata.h"
49 #include "gromacs/analysisdata/dataframe.h"
50 #include "gromacs/analysisdata/datamodule.h"
51 #include "gromacs/analysisdata/paralleloptions.h"
52 #include "gromacs/utility/exceptions.h"
53 #include "gromacs/utility/gmxassert.h"
54
55 namespace gmx
56 {
57
58 /********************************************************************
59  * AnalysisDataModuleManager::Impl
60  */
61
62 /*! \internal \brief
63  * Private implementation class for AnalysisDataModuleManager.
64  *
65  * \ingroup module_analysisdata
66  */
67 class AnalysisDataModuleManager::Impl
68 {
69     public:
70         //! Stores information about an attached module.
71         struct ModuleInfo
72         {
73             //! Initializes the module information.
74             explicit ModuleInfo(AnalysisDataModulePointer module)
75                 : module(module), bParallel(false)
76             {
77             }
78
79             //! Pointer to the actual module.
80             AnalysisDataModulePointer   module;
81             //! Whether the module supports parallel processing.
82             bool                        bParallel;
83         };
84
85         //! Shorthand for list of modules added to the data.
86         typedef std::vector<ModuleInfo> ModuleList;
87
88         //! Describes the current state of the notification methods.
89         enum State
90         {
91             eNotStarted, //!< Initial state (nothing called).
92             eInData,     //!< notifyDataStart() called, no frame in progress.
93             eInFrame,    //!< notifyFrameStart() called, but notifyFrameFinish() not.
94             eFinished    //!< notifyDataFinish() called.
95         };
96
97         Impl();
98
99         /*! \brief
100          * Checks whether a module is compatible with a given data property.
101          *
102          * \param[in] module   Module to check.
103          * \param[in] property Property to check.
104          * \param[in] bSet     Value of the property to check against.
105          * \throws    APIError if \p module is not compatible with the data.
106          */
107         void checkModuleProperty(const AnalysisDataModuleInterface &module,
108                                  DataProperty property, bool bSet) const;
109         /*! \brief
110          * Checks whether a module is compatible with the data properties.
111          *
112          * \param[in] module Module to check.
113          * \throws    APIError if \p module is not compatible with the data.
114          *
115          * Does not currently check the actual data (e.g., missing values), but
116          * only the dimensionality and other preset properties of the data.
117          */
118         void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
119
120         /*! \brief
121          * Present data already added to the data object to a module.
122          *
123          * \param[in] data   Data object to read data from.
124          * \param[in] module Module to present the data to.
125          * \throws    APIError if \p module is not compatible with the data.
126          * \throws    APIError if all data is not available through
127          *      getDataFrame().
128          * \throws    unspecified Any exception thrown by \p module in its data
129          *      notification methods.
130          *
131          * Uses getDataFrame() in \p data to access all data in the object, and
132          * calls the notification functions in \p module as if the module had
133          * been registered to the data object when the data was added.
134          */
135         void presentData(AbstractAnalysisData        *data,
136                          AnalysisDataModuleInterface *module);
137
138         //! List of modules added to the data.
139         ModuleList              modules_;
140         //! Properties of the owning data for module checking.
141         bool                    bDataProperty_[eDataPropertyNR];
142         //! true if all modules support missing data.
143         bool                    bAllowMissing_;
144         //! true if there are modules that do not support parallel processing.
145         bool                    bSerialModules_;
146         //! true if there are modules that support parallel processing.
147         bool                    bParallelModules_;
148
149         /*! \brief
150          * Current state of the notification methods.
151          *
152          * This is used together with \a currIndex_ for sanity checks on the
153          * input data; invalid call sequences trigger asserts.
154          * The state of these variables does not otherwise affect the behavior
155          * of this class; this is the reason they can be changed in const
156          * methods.
157          */
158         //! Whether notifyDataStart() has been called.
159         mutable State           state_;
160         //! Index of currently active frame or the next frame if not in frame.
161         mutable int             currIndex_;
162 };
163
164 AnalysisDataModuleManager::Impl::Impl()
165     : bAllowMissing_(true), bSerialModules_(false), bParallelModules_(false),
166       state_(eNotStarted), currIndex_(0)
167 {
168     // This must be in sync with how AbstractAnalysisData is actually
169     // initialized.
170     for (int i = 0; i < eDataPropertyNR; ++i)
171     {
172         bDataProperty_[i] = false;
173     }
174 }
175
176 void
177 AnalysisDataModuleManager::Impl::checkModuleProperty(
178         const AnalysisDataModuleInterface &module,
179         DataProperty property, bool bSet) const
180 {
181     bool      bOk   = true;
182     const int flags = module.flags();
183     switch (property)
184     {
185         case eMultipleDataSets:
186             if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipleDataSets))
187             {
188                 bOk = false;
189             }
190             break;
191         case eMultipleColumns:
192             if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMulticolumn))
193             {
194                 bOk = false;
195             }
196             break;
197         case eMultipoint:
198             if ((bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipoint))
199                 || (!bSet && (flags & AnalysisDataModuleInterface::efOnlyMultipoint)))
200             {
201                 bOk = false;
202             }
203             break;
204         default:
205             GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
206     }
207     if (!bOk)
208     {
209         GMX_THROW(APIError("Data module not compatible with data object properties"));
210     }
211 }
212
213 void
214 AnalysisDataModuleManager::Impl::checkModuleProperties(
215         const AnalysisDataModuleInterface &module) const
216 {
217     for (int i = 0; i < eDataPropertyNR; ++i)
218     {
219         checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
220     }
221 }
222
223 void
224 AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData        *data,
225                                              AnalysisDataModuleInterface *module)
226 {
227     if (state_ == eNotStarted)
228     {
229         return;
230     }
231     GMX_RELEASE_ASSERT(state_ != eInFrame,
232                        "Cannot apply a modules in mid-frame");
233     module->dataStarted(data);
234     const bool bCheckMissing = bAllowMissing_
235         && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
236     for (int i = 0; i < data->frameCount(); ++i)
237     {
238         AnalysisDataFrameRef frame = data->getDataFrame(i);
239         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
240         // TODO: Check all frames before doing anything for slightly better
241         // exception behavior.
242         if (bCheckMissing && !frame.allPresent())
243         {
244             GMX_THROW(APIError("Missing data not supported by a module"));
245         }
246         module->frameStarted(frame.header());
247         for (int j = 0; j < frame.pointSetCount(); ++j)
248         {
249             module->pointsAdded(frame.pointSet(j));
250         }
251         module->frameFinished(frame.header());
252         module->frameFinishedSerial(frame.header().index());
253     }
254     if (state_ == eFinished)
255     {
256         module->dataFinished();
257     }
258 }
259
260 /********************************************************************
261  * AnalysisDataModuleManager
262  */
263
264 AnalysisDataModuleManager::AnalysisDataModuleManager()
265     : impl_(new Impl())
266 {
267 }
268
269 AnalysisDataModuleManager::~AnalysisDataModuleManager()
270 {
271 }
272
273 void
274 AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
275 {
276     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
277                        "Cannot change data properties after data has been started");
278     if (impl_->bDataProperty_[property] != bSet)
279     {
280         Impl::ModuleList::const_iterator i;
281         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
282         {
283             impl_->checkModuleProperty(*i->module, property, bSet);
284         }
285         impl_->bDataProperty_[property] = bSet;
286     }
287 }
288
289 void
290 AnalysisDataModuleManager::addModule(AbstractAnalysisData      *data,
291                                      AnalysisDataModulePointer  module)
292 {
293     impl_->checkModuleProperties(*module);
294     // TODO: Ensure that the system does not end up in an inconsistent state by
295     // adding a module in mid-data during parallel processing (probably best to
296     // prevent alltogether).
297     GMX_RELEASE_ASSERT(impl_->state_ != Impl::eInFrame,
298                        "Cannot add a data module in mid-frame");
299     impl_->presentData(data, module.get());
300
301     if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
302     {
303         impl_->bAllowMissing_ = false;
304     }
305     impl_->modules_.push_back(Impl::ModuleInfo(module));
306 }
307
308 void
309 AnalysisDataModuleManager::applyModule(AbstractAnalysisData        *data,
310                                        AnalysisDataModuleInterface *module)
311 {
312     impl_->checkModuleProperties(*module);
313     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eFinished,
314                        "Data module can only be applied to ready data");
315     impl_->presentData(data, module);
316 }
317
318
319 bool
320 AnalysisDataModuleManager::hasSerialModules() const
321 {
322     GMX_ASSERT(impl_->state_ != Impl::eNotStarted,
323                "Module state not accessible before data is started");
324     return impl_->bSerialModules_;
325 }
326
327
328 void
329 AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData *data)
330 {
331     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
332                        "notifyDataStart() called more than once");
333     for (int d = 0; d < data->dataSetCount(); ++d)
334     {
335         GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
336                            "Data column count is not set");
337     }
338     impl_->state_            = Impl::eInData;
339     impl_->bSerialModules_   = !impl_->modules_.empty();
340     impl_->bParallelModules_ = false;
341
342     Impl::ModuleList::const_iterator i;
343     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
344     {
345         // This should not fail, since addModule() and
346         // dataPropertyAboutToChange() already do the checks, but kept here to
347         // catch potential bugs (perhaps it would be best to assert on failure).
348         impl_->checkModuleProperties(*i->module);
349         i->module->dataStarted(data);
350     }
351 }
352
353
354 void
355 AnalysisDataModuleManager::notifyParallelDataStart(
356         AbstractAnalysisData              *data,
357         const AnalysisDataParallelOptions &options)
358 {
359     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
360                        "notifyDataStart() called more than once");
361     for (int d = 0; d < data->dataSetCount(); ++d)
362     {
363         GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
364                            "Data column count is not set");
365     }
366     impl_->state_            = Impl::eInData;
367     impl_->bSerialModules_   = false;
368     impl_->bParallelModules_ = false;
369
370     Impl::ModuleList::iterator i;
371     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
372     {
373         // This should not fail, since addModule() and
374         // dataPropertyAboutToChange() already do the checks, but kept here to
375         // catch potential bugs (perhaps it would be best to assert on failure).
376         impl_->checkModuleProperties(*i->module);
377         i->bParallel = i->module->parallelDataStarted(data, options);
378         if (i->bParallel)
379         {
380             impl_->bParallelModules_ = true;
381         }
382         else
383         {
384             impl_->bSerialModules_ = true;
385         }
386     }
387 }
388
389
390 void
391 AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader &header) const
392 {
393     GMX_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
394     GMX_ASSERT(header.index() == impl_->currIndex_, "Out of order frames");
395     impl_->state_     = Impl::eInFrame;
396
397     if (impl_->bSerialModules_)
398     {
399         Impl::ModuleList::const_iterator i;
400         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
401         {
402             if (!i->bParallel)
403             {
404                 i->module->frameStarted(header);
405             }
406         }
407     }
408 }
409
410 void
411 AnalysisDataModuleManager::notifyParallelFrameStart(
412         const AnalysisDataFrameHeader &header) const
413 {
414     if (impl_->bParallelModules_)
415     {
416         Impl::ModuleList::const_iterator i;
417         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
418         {
419             if (i->bParallel)
420             {
421                 i->module->frameStarted(header);
422             }
423         }
424     }
425 }
426
427
428 void
429 AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
430 {
431     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
432     // TODO: Add checks for column spans (requires passing the information
433     // about the column counts from somewhere).
434     //GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
435     //           "Invalid columns");
436     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
437                "Points do not correspond to current frame");
438     if (impl_->bSerialModules_)
439     {
440         if (!impl_->bAllowMissing_ && !points.allPresent())
441         {
442             GMX_THROW(APIError("Missing data not supported by a module"));
443         }
444
445         Impl::ModuleList::const_iterator i;
446         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
447         {
448             if (!i->bParallel)
449             {
450                 i->module->pointsAdded(points);
451             }
452         }
453     }
454 }
455
456
457 void
458 AnalysisDataModuleManager::notifyParallelPointsAdd(
459         const AnalysisDataPointSetRef &points) const
460 {
461     // TODO: Add checks for column spans (requires passing the information
462     // about the column counts from somewhere).
463     //GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
464     //           "Invalid columns");
465     if (impl_->bParallelModules_)
466     {
467         if (!impl_->bAllowMissing_ && !points.allPresent())
468         {
469             GMX_THROW(APIError("Missing data not supported by a module"));
470         }
471
472         Impl::ModuleList::const_iterator i;
473         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
474         {
475             if (i->bParallel)
476             {
477                 i->module->pointsAdded(points);
478             }
479         }
480     }
481 }
482
483
484 void
485 AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader &header) const
486 {
487     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
488     GMX_ASSERT(header.index() == impl_->currIndex_,
489                "Header does not correspond to current frame");
490     // TODO: Add a check for the frame count in the source data including this
491     // frame.
492     impl_->state_ = Impl::eInData;
493     ++impl_->currIndex_;
494
495     if (impl_->bSerialModules_)
496     {
497         Impl::ModuleList::const_iterator i;
498         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
499         {
500             if (!i->bParallel)
501             {
502                 i->module->frameFinished(header);
503             }
504         }
505     }
506     Impl::ModuleList::const_iterator i;
507     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
508     {
509         i->module->frameFinishedSerial(header.index());
510     }
511 }
512
513
514 void
515 AnalysisDataModuleManager::notifyParallelFrameFinish(
516         const AnalysisDataFrameHeader &header) const
517 {
518     if (impl_->bParallelModules_)
519     {
520         Impl::ModuleList::const_iterator i;
521         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
522         {
523             if (i->bParallel)
524             {
525                 i->module->frameFinished(header);
526             }
527         }
528     }
529 }
530
531
532 void
533 AnalysisDataModuleManager::notifyDataFinish() const
534 {
535     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
536     impl_->state_ = Impl::eFinished;
537
538     Impl::ModuleList::const_iterator i;
539     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
540     {
541         i->module->dataFinished();
542     }
543 }
544
545 } // namespace gmx