2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
38 * Implements gmx::AnalysisDataModuleManager.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_analysisdata
45 #include "datamodulemanager.h"
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"
60 /********************************************************************
61 * AnalysisDataModuleManager::Impl
65 * Private implementation class for AnalysisDataModuleManager.
67 * \ingroup module_analysisdata
69 class AnalysisDataModuleManager::Impl
72 //! Stores information about an attached module.
75 //! Initializes the module information.
76 explicit ModuleInfo(AnalysisDataModulePointer module) :
77 module(std::move(module)), bParallel(false)
81 //! Pointer to the actual module.
82 AnalysisDataModulePointer module;
83 //! Whether the module supports parallel processing.
87 //! Shorthand for list of modules added to the data.
88 typedef std::vector<ModuleInfo> ModuleList;
90 //! Describes the current state of the notification methods.
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.
102 * Checks whether a module is compatible with a given data property.
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.
109 static void checkModuleProperty(const IAnalysisDataModule& module, DataProperty property, bool bSet);
111 * Checks whether a module is compatible with the data properties.
113 * \param[in] module Module to check.
114 * \throws APIError if \p module is not compatible with the data.
116 * Does not currently check the actual data (e.g., missing values), but
117 * only the dimensionality and other preset properties of the data.
119 void checkModuleProperties(const IAnalysisDataModule& module) const;
122 * Present data already added to the data object to a module.
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
129 * \throws unspecified Any exception thrown by \p module in its data
130 * notification methods.
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.
136 void presentData(AbstractAnalysisData* data, IAnalysisDataModule* module) const;
138 //! List of modules added to the data.
140 //! Properties of the owning data for module checking.
141 bool bDataProperty_[eDataPropertyNR];
142 //! true if all modules support missing data.
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_;
150 * Current state of the notification methods.
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
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_;
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),
175 void AnalysisDataModuleManager::Impl::checkModuleProperty(const IAnalysisDataModule& module,
176 DataProperty property,
180 const int flags = module.flags();
183 case eMultipleDataSets:
184 if (bSet && !(flags & IAnalysisDataModule::efAllowMultipleDataSets))
189 case eMultipleColumns:
190 if (bSet && !(flags & IAnalysisDataModule::efAllowMulticolumn))
196 if ((bSet && !(flags & IAnalysisDataModule::efAllowMultipoint))
197 || (!bSet && (flags & IAnalysisDataModule::efOnlyMultipoint)))
202 default: GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
206 GMX_THROW(APIError("Data module not compatible with data object properties"));
210 void AnalysisDataModuleManager::Impl::checkModuleProperties(const IAnalysisDataModule& module) const
212 for (int i = 0; i < eDataPropertyNR; ++i)
214 checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
218 void AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData* data, IAnalysisDataModule* module) const
220 if (state_ == eNotStarted)
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)
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())
236 GMX_THROW(APIError("Missing data not supported by a module"));
238 module->frameStarted(frame.header());
239 for (int j = 0; j < frame.pointSetCount(); ++j)
241 module->pointsAdded(frame.pointSet(j));
243 module->frameFinished(frame.header());
244 module->frameFinishedSerial(frame.header().index());
246 if (state_ == eFinished)
248 module->dataFinished();
252 /********************************************************************
253 * AnalysisDataModuleManager
256 AnalysisDataModuleManager::AnalysisDataModuleManager() : impl_(new Impl()) {}
258 AnalysisDataModuleManager::~AnalysisDataModuleManager() {}
260 void AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
262 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
263 "Cannot change data properties after data has been started");
264 if (impl_->bDataProperty_[property] != bSet)
266 Impl::ModuleList::const_iterator i;
267 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
269 impl_->checkModuleProperty(*i->module, property, bSet);
271 impl_->bDataProperty_[property] = bSet;
275 void AnalysisDataModuleManager::addModule(AbstractAnalysisData* data, const AnalysisDataModulePointer& module)
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());
284 if (!(module->flags() & IAnalysisDataModule::efAllowMissing))
286 impl_->bAllowMissing_ = false;
288 impl_->modules_.emplace_back(module);
291 void AnalysisDataModuleManager::applyModule(AbstractAnalysisData* data, IAnalysisDataModule* module)
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);
300 bool AnalysisDataModuleManager::hasSerialModules() const
302 GMX_ASSERT(impl_->state_ != Impl::eNotStarted,
303 "Module state not accessible before data is started");
304 return impl_->bSerialModules_;
308 void AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData* data)
310 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
311 "notifyDataStart() called more than once");
312 for (int d = 0; d < data->dataSetCount(); ++d)
314 GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
316 impl_->state_ = Impl::eInData;
317 impl_->bSerialModules_ = !impl_->modules_.empty();
318 impl_->bParallelModules_ = false;
320 Impl::ModuleList::const_iterator i;
321 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
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);
332 void AnalysisDataModuleManager::notifyParallelDataStart(AbstractAnalysisData* data,
333 const AnalysisDataParallelOptions& options)
335 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
336 "notifyDataStart() called more than once");
337 for (int d = 0; d < data->dataSetCount(); ++d)
339 GMX_RELEASE_ASSERT(data->columnCount(d) > 0, "Data column count is not set");
341 impl_->state_ = Impl::eInData;
342 impl_->bSerialModules_ = false;
343 impl_->bParallelModules_ = false;
345 Impl::ModuleList::iterator i;
346 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
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);
355 impl_->bParallelModules_ = true;
359 impl_->bSerialModules_ = true;
365 void AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader& header) const
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;
371 if (impl_->bSerialModules_)
373 Impl::ModuleList::const_iterator i;
374 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
378 i->module->frameStarted(header);
384 void AnalysisDataModuleManager::notifyParallelFrameStart(const AnalysisDataFrameHeader& header) const
386 if (impl_->bParallelModules_)
388 Impl::ModuleList::const_iterator i;
389 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
393 i->module->frameStarted(header);
400 void AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef& points) const
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_)
411 if (!impl_->bAllowMissing_ && !points.allPresent())
413 GMX_THROW(APIError("Missing data not supported by a module"));
416 Impl::ModuleList::const_iterator i;
417 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
421 i->module->pointsAdded(points);
428 void AnalysisDataModuleManager::notifyParallelPointsAdd(const AnalysisDataPointSetRef& points) const
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_)
436 if (!impl_->bAllowMissing_ && !points.allPresent())
438 GMX_THROW(APIError("Missing data not supported by a module"));
441 Impl::ModuleList::const_iterator i;
442 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
446 i->module->pointsAdded(points);
453 void AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader& header) const
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
459 impl_->state_ = Impl::eInData;
462 if (impl_->bSerialModules_)
464 Impl::ModuleList::const_iterator i;
465 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
469 i->module->frameFinished(header);
473 Impl::ModuleList::const_iterator i;
474 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
476 i->module->frameFinishedSerial(header.index());
481 void AnalysisDataModuleManager::notifyParallelFrameFinish(const AnalysisDataFrameHeader& header) const
483 if (impl_->bParallelModules_)
485 Impl::ModuleList::const_iterator i;
486 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
490 i->module->frameFinished(header);
497 void AnalysisDataModuleManager::notifyDataFinish() const
499 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
500 impl_->state_ = Impl::eFinished;
502 Impl::ModuleList::const_iterator i;
503 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
505 i->module->dataFinished();