SYCL: Avoid using no_init read accessor in rocFFT
[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)), bParallel(false)
78         {
79         }
80
81         //! Pointer to the actual module.
82         AnalysisDataModulePointer module;
83         //! Whether the module supports parallel processing.
84         bool bParallel;
85     };
86
87     //! Shorthand for list of modules added to the data.
88     typedef std::vector<ModuleInfo> ModuleList;
89
90     //! Describes the current state of the notification methods.
91     enum State
92     {
93         eNotStarted, //!< Initial state (nothing called).
94         eInData,     //!< notifyDataStart() called, no frame in progress.
95         eInFrame,    //!< notifyFrameStart() called, but notifyFrameFinish() not.
96         eFinished    //!< notifyDataFinish() called.
97     };
98
99     Impl();
100
101     /*! \brief
102      * Checks whether a module is compatible with a given data property.
103      *
104      * \param[in] module   Module to check.
105      * \param[in] property Property to check.
106      * \param[in] bSet     Value of the property to check against.
107      * \throws    APIError if \p module is not compatible with the data.
108      */
109     static void checkModuleProperty(const IAnalysisDataModule& module, DataProperty property, bool bSet);
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, IAnalysisDataModule* module) const;
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     bDataProperty_(), // This must be in sync with how AbstractAnalysisData
166                       // is actually initialized.
167     bAllowMissing_(true),
168     bSerialModules_(false),
169     bParallelModules_(false),
170     state_(eNotStarted),
171     currIndex_(0)
172 {
173 }
174
175 void AnalysisDataModuleManager::Impl::checkModuleProperty(const IAnalysisDataModule& module,
176                                                           DataProperty               property,
177                                                           bool                       bSet)
178 {
179     bool      bOk   = true;
180     const int flags = module.flags();
181     switch (property)
182     {
183         case eMultipleDataSets:
184             if (bSet && !(flags & IAnalysisDataModule::efAllowMultipleDataSets))
185             {
186                 bOk = false;
187             }
188             break;
189         case eMultipleColumns:
190             if (bSet && !(flags & IAnalysisDataModule::efAllowMulticolumn))
191             {
192                 bOk = false;
193             }
194             break;
195         case eMultipoint:
196             if ((bSet && !(flags & IAnalysisDataModule::efAllowMultipoint))
197                 || (!bSet && (flags & IAnalysisDataModule::efOnlyMultipoint)))
198             {
199                 bOk = false;
200             }
201             break;
202         default: 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 AnalysisDataModuleManager::Impl::checkModuleProperties(const IAnalysisDataModule& module) const
211 {
212     for (int i = 0; i < eDataPropertyNR; ++i)
213     {
214         checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
215     }
216 }
217
218 void AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData* data, IAnalysisDataModule* module) const
219 {
220     if (state_ == eNotStarted)
221     {
222         return;
223     }
224     GMX_RELEASE_ASSERT(state_ != eInFrame, "Cannot apply a modules in mid-frame");
225     module->dataStarted(data);
226     const bool bCheckMissing =
227             bAllowMissing_ && ((module->flags() & IAnalysisDataModule::efAllowMissing) == 0);
228     for (int i = 0; i < data->frameCount(); ++i)
229     {
230         AnalysisDataFrameRef frame = data->getDataFrame(i);
231         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
232         // TODO: Check all frames before doing anything for slightly better
233         // exception behavior.
234         if (bCheckMissing && !frame.allPresent())
235         {
236             GMX_THROW(APIError("Missing data not supported by a module"));
237         }
238         module->frameStarted(frame.header());
239         for (int j = 0; j < frame.pointSetCount(); ++j)
240         {
241             module->pointsAdded(frame.pointSet(j));
242         }
243         module->frameFinished(frame.header());
244         module->frameFinishedSerial(frame.header().index());
245     }
246     if (state_ == eFinished)
247     {
248         module->dataFinished();
249     }
250 }
251
252 /********************************************************************
253  * AnalysisDataModuleManager
254  */
255
256 AnalysisDataModuleManager::AnalysisDataModuleManager() : impl_(new Impl()) {}
257
258 AnalysisDataModuleManager::~AnalysisDataModuleManager() {}
259
260 void AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
261 {
262     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
263                        "Cannot change data properties after data has been started");
264     if (impl_->bDataProperty_[property] != bSet)
265     {
266         Impl::ModuleList::const_iterator i;
267         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
268         {
269             impl_->checkModuleProperty(*i->module, property, bSet);
270         }
271         impl_->bDataProperty_[property] = bSet;
272     }
273 }
274
275 void AnalysisDataModuleManager::addModule(AbstractAnalysisData* data, const AnalysisDataModulePointer& module)
276 {
277     impl_->checkModuleProperties(*module);
278     // TODO: Ensure that the system does not end up in an inconsistent state by
279     // adding a module in mid-data during parallel processing (probably best to
280     // prevent alltogether).
281     GMX_RELEASE_ASSERT(impl_->state_ != Impl::eInFrame, "Cannot add a data module in mid-frame");
282     impl_->presentData(data, module.get());
283
284     if (!(module->flags() & IAnalysisDataModule::efAllowMissing))
285     {
286         impl_->bAllowMissing_ = false;
287     }
288     impl_->modules_.emplace_back(module);
289 }
290
291 void AnalysisDataModuleManager::applyModule(AbstractAnalysisData* data, IAnalysisDataModule* module)
292 {
293     impl_->checkModuleProperties(*module);
294     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eFinished,
295                        "Data module can only be applied to ready data");
296     impl_->presentData(data, module);
297 }
298
299
300 bool AnalysisDataModuleManager::hasSerialModules() const
301 {
302     GMX_ASSERT(impl_->state_ != Impl::eNotStarted,
303                "Module state not accessible before data is started");
304     return impl_->bSerialModules_;
305 }
306
307
308 void AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData* data)
309 {
310     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
311                        "notifyDataStart() called more than once");
312     for (int d = 0; d < data->dataSetCount(); ++d)
313     {
314         GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
315     }
316     impl_->state_            = Impl::eInData;
317     impl_->bSerialModules_   = !impl_->modules_.empty();
318     impl_->bParallelModules_ = false;
319
320     Impl::ModuleList::const_iterator i;
321     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
322     {
323         // This should not fail, since addModule() and
324         // dataPropertyAboutToChange() already do the checks, but kept here to
325         // catch potential bugs (perhaps it would be best to assert on failure).
326         impl_->checkModuleProperties(*i->module);
327         i->module->dataStarted(data);
328     }
329 }
330
331
332 void AnalysisDataModuleManager::notifyParallelDataStart(AbstractAnalysisData*              data,
333                                                         const AnalysisDataParallelOptions& options)
334 {
335     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
336                        "notifyDataStart() called more than once");
337     for (int d = 0; d < data->dataSetCount(); ++d)
338     {
339         GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
340     }
341     impl_->state_            = Impl::eInData;
342     impl_->bSerialModules_   = false;
343     impl_->bParallelModules_ = false;
344
345     Impl::ModuleList::iterator i;
346     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
347     {
348         // This should not fail, since addModule() and
349         // dataPropertyAboutToChange() already do the checks, but kept here to
350         // catch potential bugs (perhaps it would be best to assert on failure).
351         impl_->checkModuleProperties(*i->module);
352         i->bParallel = i->module->parallelDataStarted(data, options);
353         if (i->bParallel)
354         {
355             impl_->bParallelModules_ = true;
356         }
357         else
358         {
359             impl_->bSerialModules_ = true;
360         }
361     }
362 }
363
364
365 void AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader& header) const
366 {
367     GMX_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
368     GMX_ASSERT(header.index() == impl_->currIndex_, "Out of order frames");
369     impl_->state_ = Impl::eInFrame;
370
371     if (impl_->bSerialModules_)
372     {
373         Impl::ModuleList::const_iterator i;
374         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
375         {
376             if (!i->bParallel)
377             {
378                 i->module->frameStarted(header);
379             }
380         }
381     }
382 }
383
384 void AnalysisDataModuleManager::notifyParallelFrameStart(const AnalysisDataFrameHeader& header) const
385 {
386     if (impl_->bParallelModules_)
387     {
388         Impl::ModuleList::const_iterator i;
389         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
390         {
391             if (i->bParallel)
392             {
393                 i->module->frameStarted(header);
394             }
395         }
396     }
397 }
398
399
400 void AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef& points) const
401 {
402     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
403     // TODO: Add checks for column spans (requires passing the information
404     // about the column counts from somewhere).
405     // GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
406     //           "Invalid columns");
407     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
408                "Points do not correspond to current frame");
409     if (impl_->bSerialModules_)
410     {
411         if (!impl_->bAllowMissing_ && !points.allPresent())
412         {
413             GMX_THROW(APIError("Missing data not supported by a module"));
414         }
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->pointsAdded(points);
422             }
423         }
424     }
425 }
426
427
428 void AnalysisDataModuleManager::notifyParallelPointsAdd(const AnalysisDataPointSetRef& points) const
429 {
430     // TODO: Add checks for column spans (requires passing the information
431     // about the column counts from somewhere).
432     // GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
433     //           "Invalid columns");
434     if (impl_->bParallelModules_)
435     {
436         if (!impl_->bAllowMissing_ && !points.allPresent())
437         {
438             GMX_THROW(APIError("Missing data not supported by a module"));
439         }
440
441         Impl::ModuleList::const_iterator i;
442         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
443         {
444             if (i->bParallel)
445             {
446                 i->module->pointsAdded(points);
447             }
448         }
449     }
450 }
451
452
453 void AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader& header) const
454 {
455     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
456     GMX_ASSERT(header.index() == impl_->currIndex_, "Header does not correspond to current frame");
457     // TODO: Add a check for the frame count in the source data including this
458     // frame.
459     impl_->state_ = Impl::eInData;
460     ++impl_->currIndex_;
461
462     if (impl_->bSerialModules_)
463     {
464         Impl::ModuleList::const_iterator i;
465         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
466         {
467             if (!i->bParallel)
468             {
469                 i->module->frameFinished(header);
470             }
471         }
472     }
473     Impl::ModuleList::const_iterator i;
474     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
475     {
476         i->module->frameFinishedSerial(header.index());
477     }
478 }
479
480
481 void AnalysisDataModuleManager::notifyParallelFrameFinish(const AnalysisDataFrameHeader& header) const
482 {
483     if (impl_->bParallelModules_)
484     {
485         Impl::ModuleList::const_iterator i;
486         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
487         {
488             if (i->bParallel)
489             {
490                 i->module->frameFinished(header);
491             }
492         }
493     }
494 }
495
496
497 void AnalysisDataModuleManager::notifyDataFinish() const
498 {
499     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
500     impl_->state_ = Impl::eFinished;
501
502     Impl::ModuleList::const_iterator i;
503     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
504     {
505         i->module->dataFinished();
506     }
507 }
508
509 } // namespace gmx