2 * This file is part of the GROMACS molecular simulation package.
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.
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.
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.
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.
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.
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.
37 * Implements gmx::AnalysisDataModuleManager.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_analysisdata
44 #include "datamodulemanager.h"
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"
58 /********************************************************************
59 * AnalysisDataModuleManager::Impl
63 * Private implementation class for AnalysisDataModuleManager.
65 * \ingroup module_analysisdata
67 class AnalysisDataModuleManager::Impl
70 //! Stores information about an attached module.
73 //! Initializes the module information.
74 explicit ModuleInfo(AnalysisDataModulePointer module)
75 : module(module), bParallel(false)
79 //! Pointer to the actual module.
80 AnalysisDataModulePointer module;
81 //! Whether the module supports parallel processing.
85 //! Shorthand for list of modules added to the data.
86 typedef std::vector<ModuleInfo> ModuleList;
88 //! Describes the current state of the notification methods.
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.
100 * Checks whether a module is compatible with a given data property.
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.
107 void checkModuleProperty(const AnalysisDataModuleInterface &module,
108 DataProperty property, bool bSet) const;
110 * Checks whether a module is compatible with the data properties.
112 * \param[in] module Module to check.
113 * \throws APIError if \p module is not compatible with the data.
115 * Does not currently check the actual data (e.g., missing values), but
116 * only the dimensionality and other preset properties of the data.
118 void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
121 * Present data already added to the data object to a module.
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
128 * \throws unspecified Any exception thrown by \p module in its data
129 * notification methods.
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.
135 void presentData(AbstractAnalysisData *data,
136 AnalysisDataModuleInterface *module);
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 : bAllowMissing_(true), bSerialModules_(false), bParallelModules_(false),
166 state_(eNotStarted), currIndex_(0)
168 // This must be in sync with how AbstractAnalysisData is actually
170 for (int i = 0; i < eDataPropertyNR; ++i)
172 bDataProperty_[i] = false;
177 AnalysisDataModuleManager::Impl::checkModuleProperty(
178 const AnalysisDataModuleInterface &module,
179 DataProperty property, bool bSet) const
182 const int flags = module.flags();
185 case eMultipleDataSets:
186 if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipleDataSets))
191 case eMultipleColumns:
192 if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMulticolumn))
198 if ((bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipoint))
199 || (!bSet && (flags & AnalysisDataModuleInterface::efOnlyMultipoint)))
205 GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
209 GMX_THROW(APIError("Data module not compatible with data object properties"));
214 AnalysisDataModuleManager::Impl::checkModuleProperties(
215 const AnalysisDataModuleInterface &module) const
217 for (int i = 0; i < eDataPropertyNR; ++i)
219 checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
224 AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData *data,
225 AnalysisDataModuleInterface *module)
227 if (state_ == eNotStarted)
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)
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())
244 GMX_THROW(APIError("Missing data not supported by a module"));
246 module->frameStarted(frame.header());
247 for (int j = 0; j < frame.pointSetCount(); ++j)
249 module->pointsAdded(frame.pointSet(j));
251 module->frameFinished(frame.header());
252 module->frameFinishedSerial(frame.header().index());
254 if (state_ == eFinished)
256 module->dataFinished();
260 /********************************************************************
261 * AnalysisDataModuleManager
264 AnalysisDataModuleManager::AnalysisDataModuleManager()
269 AnalysisDataModuleManager::~AnalysisDataModuleManager()
274 AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
276 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
277 "Cannot change data properties after data has been started");
278 if (impl_->bDataProperty_[property] != bSet)
280 Impl::ModuleList::const_iterator i;
281 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
283 impl_->checkModuleProperty(*i->module, property, bSet);
285 impl_->bDataProperty_[property] = bSet;
290 AnalysisDataModuleManager::addModule(AbstractAnalysisData *data,
291 AnalysisDataModulePointer module)
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());
301 if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
303 impl_->bAllowMissing_ = false;
305 impl_->modules_.push_back(Impl::ModuleInfo(module));
309 AnalysisDataModuleManager::applyModule(AbstractAnalysisData *data,
310 AnalysisDataModuleInterface *module)
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);
320 AnalysisDataModuleManager::hasSerialModules() const
322 GMX_ASSERT(impl_->state_ != Impl::eNotStarted,
323 "Module state not accessible before data is started");
324 return impl_->bSerialModules_;
329 AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData *data)
331 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
332 "notifyDataStart() called more than once");
333 for (int d = 0; d < data->dataSetCount(); ++d)
335 GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
336 "Data column count is not set");
338 impl_->state_ = Impl::eInData;
339 impl_->bSerialModules_ = !impl_->modules_.empty();
340 impl_->bParallelModules_ = false;
342 Impl::ModuleList::const_iterator i;
343 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
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);
355 AnalysisDataModuleManager::notifyParallelDataStart(
356 AbstractAnalysisData *data,
357 const AnalysisDataParallelOptions &options)
359 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
360 "notifyDataStart() called more than once");
361 for (int d = 0; d < data->dataSetCount(); ++d)
363 GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
364 "Data column count is not set");
366 impl_->state_ = Impl::eInData;
367 impl_->bSerialModules_ = false;
368 impl_->bParallelModules_ = false;
370 Impl::ModuleList::iterator i;
371 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
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);
380 impl_->bParallelModules_ = true;
384 impl_->bSerialModules_ = true;
391 AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader &header) const
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;
397 if (impl_->bSerialModules_)
399 Impl::ModuleList::const_iterator i;
400 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
404 i->module->frameStarted(header);
411 AnalysisDataModuleManager::notifyParallelFrameStart(
412 const AnalysisDataFrameHeader &header) const
414 if (impl_->bParallelModules_)
416 Impl::ModuleList::const_iterator i;
417 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
421 i->module->frameStarted(header);
429 AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
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_)
440 if (!impl_->bAllowMissing_ && !points.allPresent())
442 GMX_THROW(APIError("Missing data not supported by a module"));
445 Impl::ModuleList::const_iterator i;
446 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
450 i->module->pointsAdded(points);
458 AnalysisDataModuleManager::notifyParallelPointsAdd(
459 const AnalysisDataPointSetRef &points) const
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_)
467 if (!impl_->bAllowMissing_ && !points.allPresent())
469 GMX_THROW(APIError("Missing data not supported by a module"));
472 Impl::ModuleList::const_iterator i;
473 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
477 i->module->pointsAdded(points);
485 AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader &header) const
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
492 impl_->state_ = Impl::eInData;
495 if (impl_->bSerialModules_)
497 Impl::ModuleList::const_iterator i;
498 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
502 i->module->frameFinished(header);
506 Impl::ModuleList::const_iterator i;
507 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
509 i->module->frameFinishedSerial(header.index());
515 AnalysisDataModuleManager::notifyParallelFrameFinish(
516 const AnalysisDataFrameHeader &header) const
518 if (impl_->bParallelModules_)
520 Impl::ModuleList::const_iterator i;
521 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
525 i->module->frameFinished(header);
533 AnalysisDataModuleManager::notifyDataFinish() const
535 GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
536 impl_->state_ = Impl::eFinished;
538 Impl::ModuleList::const_iterator i;
539 for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
541 i->module->dataFinished();