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