c5e982f579703b980b75eadf4d65317782183ed8
[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-2018, The GROMACS development team.
5  * Copyright (c) 2019,2020,2021, by the GROMACS development team, led by
6  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7  * and including many others, as listed in the AUTHORS file in the
8  * top-level source directory and at http://www.gromacs.org.
9  *
10  * GROMACS is free software; you can redistribute it and/or
11  * modify it under the terms of the GNU Lesser General Public License
12  * as published by the Free Software Foundation; either version 2.1
13  * of the License, or (at your option) any later version.
14  *
15  * GROMACS is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18  * Lesser General Public License for more details.
19  *
20  * You should have received a copy of the GNU Lesser General Public
21  * License along with GROMACS; if not, see
22  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
24  *
25  * If you want to redistribute modifications to GROMACS, please
26  * consider that scientific software is very special. Version
27  * control is crucial - bugs must be traceable. We will be happy to
28  * consider code for inclusion in the official distribution, but
29  * derived work must not be called official GROMACS. Details are found
30  * in the README & COPYING files - if they are missing, get the
31  * official version at http://www.gromacs.org.
32  *
33  * To help us fund GROMACS development, we humbly ask that you cite
34  * the research papers on the package. Check out http://www.gromacs.org.
35  */
36 /*! \internal \file
37  * \brief
38  * Implements gmx::AnalysisDataModuleManager.
39  *
40  * \author Teemu Murtola <teemu.murtola@gmail.com>
41  * \ingroup module_analysisdata
42  */
43 #include "gmxpre.h"
44
45 #include "datamodulemanager.h"
46
47 #include <utility>
48 #include <vector>
49
50 #include "gromacs/analysisdata/abstractdata.h"
51 #include "gromacs/analysisdata/dataframe.h"
52 #include "gromacs/analysisdata/datamodule.h"
53 #include "gromacs/analysisdata/paralleloptions.h"
54 #include "gromacs/utility/exceptions.h"
55 #include "gromacs/utility/gmxassert.h"
56
57 namespace gmx
58 {
59
60 /********************************************************************
61  * AnalysisDataModuleManager::Impl
62  */
63
64 /*! \internal \brief
65  * Private implementation class for AnalysisDataModuleManager.
66  *
67  * \ingroup module_analysisdata
68  */
69 class AnalysisDataModuleManager::Impl
70 {
71 public:
72     //! Stores information about an attached module.
73     struct ModuleInfo
74     {
75         //! Initializes the module information.
76         explicit ModuleInfo(AnalysisDataModulePointer module) :
77             module(std::move(module)),
78             bParallel(false)
79         {
80         }
81
82         //! Pointer to the actual module.
83         AnalysisDataModulePointer module;
84         //! Whether the module supports parallel processing.
85         bool bParallel;
86     };
87
88     //! Shorthand for list of modules added to the data.
89     typedef std::vector<ModuleInfo> ModuleList;
90
91     //! Describes the current state of the notification methods.
92     enum State
93     {
94         eNotStarted, //!< Initial state (nothing called).
95         eInData,     //!< notifyDataStart() called, no frame in progress.
96         eInFrame,    //!< notifyFrameStart() called, but notifyFrameFinish() not.
97         eFinished    //!< notifyDataFinish() called.
98     };
99
100     Impl();
101
102     /*! \brief
103      * Checks whether a module is compatible with a given data property.
104      *
105      * \param[in] module   Module to check.
106      * \param[in] property Property to check.
107      * \param[in] bSet     Value of the property to check against.
108      * \throws    APIError if \p module is not compatible with the data.
109      */
110     static void checkModuleProperty(const IAnalysisDataModule& module, DataProperty property, bool bSet);
111     /*! \brief
112      * Checks whether a module is compatible with the data properties.
113      *
114      * \param[in] module Module to check.
115      * \throws    APIError if \p module is not compatible with the data.
116      *
117      * Does not currently check the actual data (e.g., missing values), but
118      * only the dimensionality and other preset properties of the data.
119      */
120     void checkModuleProperties(const IAnalysisDataModule& module) const;
121
122     /*! \brief
123      * Present data already added to the data object to a module.
124      *
125      * \param[in] data   Data object to read data from.
126      * \param[in] module Module to present the data to.
127      * \throws    APIError if \p module is not compatible with the data.
128      * \throws    APIError if all data is not available through
129      *      getDataFrame().
130      * \throws    unspecified Any exception thrown by \p module in its data
131      *      notification methods.
132      *
133      * Uses getDataFrame() in \p data to access all data in the object, and
134      * calls the notification functions in \p module as if the module had
135      * been registered to the data object when the data was added.
136      */
137     void presentData(AbstractAnalysisData* data, IAnalysisDataModule* module) const;
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),
169     bSerialModules_(false),
170     bParallelModules_(false),
171     state_(eNotStarted),
172     currIndex_(0)
173 {
174 }
175
176 void AnalysisDataModuleManager::Impl::checkModuleProperty(const IAnalysisDataModule& module,
177                                                           DataProperty               property,
178                                                           bool                       bSet)
179 {
180     bool      bOk   = true;
181     const int flags = module.flags();
182     switch (property)
183     {
184         case eMultipleDataSets:
185             if (bSet && !(flags & IAnalysisDataModule::efAllowMultipleDataSets))
186             {
187                 bOk = false;
188             }
189             break;
190         case eMultipleColumns:
191             if (bSet && !(flags & IAnalysisDataModule::efAllowMulticolumn))
192             {
193                 bOk = false;
194             }
195             break;
196         case eMultipoint:
197             if ((bSet && !(flags & IAnalysisDataModule::efAllowMultipoint))
198                 || (!bSet && (flags & IAnalysisDataModule::efOnlyMultipoint)))
199             {
200                 bOk = false;
201             }
202             break;
203         default: GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
204     }
205     if (!bOk)
206     {
207         GMX_THROW(APIError("Data module not compatible with data object properties"));
208     }
209 }
210
211 void AnalysisDataModuleManager::Impl::checkModuleProperties(const IAnalysisDataModule& module) const
212 {
213     for (int i = 0; i < eDataPropertyNR; ++i)
214     {
215         checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
216     }
217 }
218
219 void AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData* data, IAnalysisDataModule* module) const
220 {
221     if (state_ == eNotStarted)
222     {
223         return;
224     }
225     GMX_RELEASE_ASSERT(state_ != eInFrame, "Cannot apply a modules in mid-frame");
226     module->dataStarted(data);
227     const bool bCheckMissing =
228             bAllowMissing_ && ((module->flags() & IAnalysisDataModule::efAllowMissing) == 0);
229     for (int i = 0; i < data->frameCount(); ++i)
230     {
231         AnalysisDataFrameRef frame = data->getDataFrame(i);
232         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
233         // TODO: Check all frames before doing anything for slightly better
234         // exception behavior.
235         if (bCheckMissing && !frame.allPresent())
236         {
237             GMX_THROW(APIError("Missing data not supported by a module"));
238         }
239         module->frameStarted(frame.header());
240         for (int j = 0; j < frame.pointSetCount(); ++j)
241         {
242             module->pointsAdded(frame.pointSet(j));
243         }
244         module->frameFinished(frame.header());
245         module->frameFinishedSerial(frame.header().index());
246     }
247     if (state_ == eFinished)
248     {
249         module->dataFinished();
250     }
251 }
252
253 /********************************************************************
254  * AnalysisDataModuleManager
255  */
256
257 AnalysisDataModuleManager::AnalysisDataModuleManager() : impl_(new Impl()) {}
258
259 AnalysisDataModuleManager::~AnalysisDataModuleManager() {}
260
261 void AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
262 {
263     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
264                        "Cannot change data properties after data has been started");
265     if (impl_->bDataProperty_[property] != bSet)
266     {
267         Impl::ModuleList::const_iterator i;
268         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
269         {
270             impl_->checkModuleProperty(*i->module, property, bSet);
271         }
272         impl_->bDataProperty_[property] = bSet;
273     }
274 }
275
276 void AnalysisDataModuleManager::addModule(AbstractAnalysisData* data, const AnalysisDataModulePointer& module)
277 {
278     impl_->checkModuleProperties(*module);
279     // TODO: Ensure that the system does not end up in an inconsistent state by
280     // adding a module in mid-data during parallel processing (probably best to
281     // prevent alltogether).
282     GMX_RELEASE_ASSERT(impl_->state_ != Impl::eInFrame, "Cannot add a data module in mid-frame");
283     impl_->presentData(data, module.get());
284
285     if (!(module->flags() & IAnalysisDataModule::efAllowMissing))
286     {
287         impl_->bAllowMissing_ = false;
288     }
289     impl_->modules_.emplace_back(module);
290 }
291
292 void AnalysisDataModuleManager::applyModule(AbstractAnalysisData* data, IAnalysisDataModule* module)
293 {
294     impl_->checkModuleProperties(*module);
295     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eFinished,
296                        "Data module can only be applied to ready data");
297     impl_->presentData(data, module);
298 }
299
300
301 bool AnalysisDataModuleManager::hasSerialModules() const
302 {
303     GMX_ASSERT(impl_->state_ != Impl::eNotStarted,
304                "Module state not accessible before data is started");
305     return impl_->bSerialModules_;
306 }
307
308
309 void AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData* data)
310 {
311     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
312                        "notifyDataStart() called more than once");
313     for (int d = 0; d < data->dataSetCount(); ++d)
314     {
315         GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
316     }
317     impl_->state_            = Impl::eInData;
318     impl_->bSerialModules_   = !impl_->modules_.empty();
319     impl_->bParallelModules_ = false;
320
321     Impl::ModuleList::const_iterator i;
322     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
323     {
324         // This should not fail, since addModule() and
325         // dataPropertyAboutToChange() already do the checks, but kept here to
326         // catch potential bugs (perhaps it would be best to assert on failure).
327         impl_->checkModuleProperties(*i->module);
328         i->module->dataStarted(data);
329     }
330 }
331
332
333 void AnalysisDataModuleManager::notifyParallelDataStart(AbstractAnalysisData*              data,
334                                                         const AnalysisDataParallelOptions& options)
335 {
336     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
337                        "notifyDataStart() called more than once");
338     for (int d = 0; d < data->dataSetCount(); ++d)
339     {
340         GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
341     }
342     impl_->state_            = Impl::eInData;
343     impl_->bSerialModules_   = false;
344     impl_->bParallelModules_ = false;
345
346     Impl::ModuleList::iterator i;
347     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
348     {
349         // This should not fail, since addModule() and
350         // dataPropertyAboutToChange() already do the checks, but kept here to
351         // catch potential bugs (perhaps it would be best to assert on failure).
352         impl_->checkModuleProperties(*i->module);
353         i->bParallel = i->module->parallelDataStarted(data, options);
354         if (i->bParallel)
355         {
356             impl_->bParallelModules_ = true;
357         }
358         else
359         {
360             impl_->bSerialModules_ = true;
361         }
362     }
363 }
364
365
366 void AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader& header) const
367 {
368     GMX_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
369     GMX_ASSERT(header.index() == impl_->currIndex_, "Out of order frames");
370     impl_->state_ = Impl::eInFrame;
371
372     if (impl_->bSerialModules_)
373     {
374         Impl::ModuleList::const_iterator i;
375         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
376         {
377             if (!i->bParallel)
378             {
379                 i->module->frameStarted(header);
380             }
381         }
382     }
383 }
384
385 void AnalysisDataModuleManager::notifyParallelFrameStart(const AnalysisDataFrameHeader& header) const
386 {
387     if (impl_->bParallelModules_)
388     {
389         Impl::ModuleList::const_iterator i;
390         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
391         {
392             if (i->bParallel)
393             {
394                 i->module->frameStarted(header);
395             }
396         }
397     }
398 }
399
400
401 void AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef& points) const
402 {
403     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
404     // TODO: Add checks for column spans (requires passing the information
405     // about the column counts from somewhere).
406     // GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
407     //           "Invalid columns");
408     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
409                "Points do not correspond to current frame");
410     if (impl_->bSerialModules_)
411     {
412         if (!impl_->bAllowMissing_ && !points.allPresent())
413         {
414             GMX_THROW(APIError("Missing data not supported by a module"));
415         }
416
417         Impl::ModuleList::const_iterator i;
418         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
419         {
420             if (!i->bParallel)
421             {
422                 i->module->pointsAdded(points);
423             }
424         }
425     }
426 }
427
428
429 void AnalysisDataModuleManager::notifyParallelPointsAdd(const AnalysisDataPointSetRef& points) const
430 {
431     // TODO: Add checks for column spans (requires passing the information
432     // about the column counts from somewhere).
433     // GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
434     //           "Invalid columns");
435     if (impl_->bParallelModules_)
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 AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader& header) const
455 {
456     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
457     GMX_ASSERT(header.index() == impl_->currIndex_, "Header does not correspond to current frame");
458     // TODO: Add a check for the frame count in the source data including this
459     // frame.
460     impl_->state_ = Impl::eInData;
461     ++impl_->currIndex_;
462
463     if (impl_->bSerialModules_)
464     {
465         Impl::ModuleList::const_iterator i;
466         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
467         {
468             if (!i->bParallel)
469             {
470                 i->module->frameFinished(header);
471             }
472         }
473     }
474     Impl::ModuleList::const_iterator i;
475     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
476     {
477         i->module->frameFinishedSerial(header.index());
478     }
479 }
480
481
482 void AnalysisDataModuleManager::notifyParallelFrameFinish(const AnalysisDataFrameHeader& header) const
483 {
484     if (impl_->bParallelModules_)
485     {
486         Impl::ModuleList::const_iterator i;
487         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
488         {
489             if (i->bParallel)
490             {
491                 i->module->frameFinished(header);
492             }
493         }
494     }
495 }
496
497
498 void AnalysisDataModuleManager::notifyDataFinish() const
499 {
500     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
501     impl_->state_ = Impl::eFinished;
502
503     Impl::ModuleList::const_iterator i;
504     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
505     {
506         i->module->dataFinished();
507     }
508 }
509
510 } // namespace gmx