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, 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
)),
82 //! Pointer to the actual module.
83 AnalysisDataModulePointer module
;
84 //! Whether the module supports parallel processing.
88 //! Shorthand for list of modules added to the data.
89 typedef std::vector
<ModuleInfo
> ModuleList
;
91 //! Describes the current state of the notification methods.
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.
103 * Checks whether a module is compatible with a given data property.
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.
110 static void checkModuleProperty(const IAnalysisDataModule
& module
, DataProperty property
, bool bSet
);
112 * Checks whether a module is compatible with the data properties.
114 * \param[in] module Module to check.
115 * \throws APIError if \p module is not compatible with the data.
117 * Does not currently check the actual data (e.g., missing values), but
118 * only the dimensionality and other preset properties of the data.
120 void checkModuleProperties(const IAnalysisDataModule
& module
) const;
123 * Present data already added to the data object to a module.
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
130 * \throws unspecified Any exception thrown by \p module in its data
131 * notification methods.
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.
137 void presentData(AbstractAnalysisData
* data
, IAnalysisDataModule
* module
);
139 //! List of modules added to the data.
141 //! Properties of the owning data for module checking.
142 bool bDataProperty_
[eDataPropertyNR
];
143 //! true if all modules support missing data.
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_
;
151 * Current state of the notification methods.
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
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_
;
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),
176 void AnalysisDataModuleManager::Impl::checkModuleProperty(const IAnalysisDataModule
& module
,
177 DataProperty property
,
181 const int flags
= module
.flags();
184 case eMultipleDataSets
:
185 if (bSet
&& !(flags
& IAnalysisDataModule::efAllowMultipleDataSets
))
190 case eMultipleColumns
:
191 if (bSet
&& !(flags
& IAnalysisDataModule::efAllowMulticolumn
))
197 if ((bSet
&& !(flags
& IAnalysisDataModule::efAllowMultipoint
))
198 || (!bSet
&& (flags
& IAnalysisDataModule::efOnlyMultipoint
)))
203 default: GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
207 GMX_THROW(APIError("Data module not compatible with data object properties"));
211 void AnalysisDataModuleManager::Impl::checkModuleProperties(const IAnalysisDataModule
& module
) const
213 for (int i
= 0; i
< eDataPropertyNR
; ++i
)
215 checkModuleProperty(module
, static_cast<DataProperty
>(i
), bDataProperty_
[i
]);
219 void AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData
* data
, IAnalysisDataModule
* module
)
221 if (state_
== eNotStarted
)
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
)
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())
237 GMX_THROW(APIError("Missing data not supported by a module"));
239 module
->frameStarted(frame
.header());
240 for (int j
= 0; j
< frame
.pointSetCount(); ++j
)
242 module
->pointsAdded(frame
.pointSet(j
));
244 module
->frameFinished(frame
.header());
245 module
->frameFinishedSerial(frame
.header().index());
247 if (state_
== eFinished
)
249 module
->dataFinished();
253 /********************************************************************
254 * AnalysisDataModuleManager
257 AnalysisDataModuleManager::AnalysisDataModuleManager() : impl_(new Impl()) {}
259 AnalysisDataModuleManager::~AnalysisDataModuleManager() {}
261 void AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property
, bool bSet
)
263 GMX_RELEASE_ASSERT(impl_
->state_
== Impl::eNotStarted
,
264 "Cannot change data properties after data has been started");
265 if (impl_
->bDataProperty_
[property
] != bSet
)
267 Impl::ModuleList::const_iterator i
;
268 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
270 impl_
->checkModuleProperty(*i
->module
, property
, bSet
);
272 impl_
->bDataProperty_
[property
] = bSet
;
276 void AnalysisDataModuleManager::addModule(AbstractAnalysisData
* data
, const AnalysisDataModulePointer
& module
)
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());
285 if (!(module
->flags() & IAnalysisDataModule::efAllowMissing
))
287 impl_
->bAllowMissing_
= false;
289 impl_
->modules_
.emplace_back(module
);
292 void AnalysisDataModuleManager::applyModule(AbstractAnalysisData
* data
, IAnalysisDataModule
* module
)
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
);
301 bool AnalysisDataModuleManager::hasSerialModules() const
303 GMX_ASSERT(impl_
->state_
!= Impl::eNotStarted
,
304 "Module state not accessible before data is started");
305 return impl_
->bSerialModules_
;
309 void AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData
* data
)
311 GMX_RELEASE_ASSERT(impl_
->state_
== Impl::eNotStarted
,
312 "notifyDataStart() called more than once");
313 for (int d
= 0; d
< data
->dataSetCount(); ++d
)
315 GMX_RELEASE_ASSERT(data
->columnCount(d
) > 0, "Data column count is not set");
317 impl_
->state_
= Impl::eInData
;
318 impl_
->bSerialModules_
= !impl_
->modules_
.empty();
319 impl_
->bParallelModules_
= false;
321 Impl::ModuleList::const_iterator i
;
322 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
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
);
333 void AnalysisDataModuleManager::notifyParallelDataStart(AbstractAnalysisData
* data
,
334 const AnalysisDataParallelOptions
& options
)
336 GMX_RELEASE_ASSERT(impl_
->state_
== Impl::eNotStarted
,
337 "notifyDataStart() called more than once");
338 for (int d
= 0; d
< data
->dataSetCount(); ++d
)
340 GMX_RELEASE_ASSERT(data
->columnCount(d
) > 0, "Data column count is not set");
342 impl_
->state_
= Impl::eInData
;
343 impl_
->bSerialModules_
= false;
344 impl_
->bParallelModules_
= false;
346 Impl::ModuleList::iterator i
;
347 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
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
);
356 impl_
->bParallelModules_
= true;
360 impl_
->bSerialModules_
= true;
366 void AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader
& header
) const
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
;
372 if (impl_
->bSerialModules_
)
374 Impl::ModuleList::const_iterator i
;
375 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
379 i
->module
->frameStarted(header
);
385 void AnalysisDataModuleManager::notifyParallelFrameStart(const AnalysisDataFrameHeader
& header
) const
387 if (impl_
->bParallelModules_
)
389 Impl::ModuleList::const_iterator i
;
390 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
394 i
->module
->frameStarted(header
);
401 void AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef
& points
) const
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_
)
412 if (!impl_
->bAllowMissing_
&& !points
.allPresent())
414 GMX_THROW(APIError("Missing data not supported by a module"));
417 Impl::ModuleList::const_iterator i
;
418 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
422 i
->module
->pointsAdded(points
);
429 void AnalysisDataModuleManager::notifyParallelPointsAdd(const AnalysisDataPointSetRef
& points
) const
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_
)
437 if (!impl_
->bAllowMissing_
&& !points
.allPresent())
439 GMX_THROW(APIError("Missing data not supported by a module"));
442 Impl::ModuleList::const_iterator i
;
443 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
447 i
->module
->pointsAdded(points
);
454 void AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader
& header
) const
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
460 impl_
->state_
= Impl::eInData
;
463 if (impl_
->bSerialModules_
)
465 Impl::ModuleList::const_iterator i
;
466 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
470 i
->module
->frameFinished(header
);
474 Impl::ModuleList::const_iterator i
;
475 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
477 i
->module
->frameFinishedSerial(header
.index());
482 void AnalysisDataModuleManager::notifyParallelFrameFinish(const AnalysisDataFrameHeader
& header
) const
484 if (impl_
->bParallelModules_
)
486 Impl::ModuleList::const_iterator i
;
487 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
491 i
->module
->frameFinished(header
);
498 void AnalysisDataModuleManager::notifyDataFinish() const
500 GMX_RELEASE_ASSERT(impl_
->state_
== Impl::eInData
, "Invalid call sequence");
501 impl_
->state_
= Impl::eFinished
;
503 Impl::ModuleList::const_iterator i
;
504 for (i
= impl_
->modules_
.begin(); i
!= impl_
->modules_
.end(); ++i
)
506 i
->module
->dataFinished();