AbstractAnalysisData module handling into a separate class.
[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,2011,2012,2013, by the GROMACS development team, led by
5  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6  * others, as listed in the AUTHORS file in the top-level source
7  * directory and at http://www.gromacs.org.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 /*! \internal \file
36  * \brief
37  * Implements gmx::AnalysisDataModuleManager.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_analysisdata
41  */
42 #include "gromacs/analysisdata/datamodulemanager.h"
43
44 #include <vector>
45
46 #include "gromacs/analysisdata/abstractdata.h"
47 #include "gromacs/analysisdata/dataframe.h"
48 #include "gromacs/analysisdata/datamodule.h"
49 #include "gromacs/utility/exceptions.h"
50 #include "gromacs/utility/gmxassert.h"
51
52 namespace gmx
53 {
54
55 /********************************************************************
56  * AnalysisDataModuleManager::Impl
57  */
58
59 /*! \internal \brief
60  * Private implementation class for AnalysisDataModuleManager.
61  *
62  * \ingroup module_analysisdata
63  */
64 class AnalysisDataModuleManager::Impl
65 {
66     public:
67         //! Shorthand for list of modules added to the data.
68         typedef std::vector<AnalysisDataModulePointer> ModuleList;
69
70         //! Describes the current state of the notification methods.
71         enum State
72         {
73             eNotStarted, //!< Initial state (nothing called).
74             eInData,     //!< notifyDataStart() called, no frame in progress.
75             eInFrame,    //!< notifyFrameStart() called, but notifyFrameFinish() not.
76             eFinished    //!< notifyDataFinish() called.
77         };
78
79         Impl();
80
81         /*! \brief
82          * Checks whether a module is compatible with a given data property.
83          *
84          * \param[in] module   Module to check.
85          * \param[in] property Property to check.
86          * \param[in] bSet     Value of the property to check against.
87          * \throws    APIError if \p module is not compatible with the data.
88          */
89         void checkModuleProperty(const AnalysisDataModuleInterface &module,
90                                  DataProperty property, bool bSet) const;
91         /*! \brief
92          * Checks whether a module is compatible with the data properties.
93          *
94          * \param[in] module Module to check.
95          * \throws    APIError if \p module is not compatible with the data.
96          *
97          * Does not currently check the actual data (e.g., missing values), but
98          * only the dimensionality and other preset properties of the data.
99          */
100         void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
101
102         /*! \brief
103          * Present data already added to the data object to a module.
104          *
105          * \param[in] data   Data object to read data from.
106          * \param[in] module Module to present the data to.
107          * \throws    APIError if \p module is not compatible with the data.
108          * \throws    APIError if all data is not available through
109          *      getDataFrame().
110          * \throws    unspecified Any exception thrown by \p module in its data
111          *      notification methods.
112          *
113          * Uses getDataFrame() in \p data to access all data in the object, and
114          * calls the notification functions in \p module as if the module had
115          * been registered to the data object when the data was added.
116          */
117         void presentData(AbstractAnalysisData        *data,
118                          AnalysisDataModuleInterface *module);
119
120         //! List of modules added to the data.
121         ModuleList              modules_;
122         //! Properties of the owning data for module checking.
123         bool                    bDataProperty_[eDataPropertyNR];
124         //! true if all modules support missing data.
125         bool                    bAllowMissing_;
126
127         /*! \brief
128          * Current state of the notification methods.
129          *
130          * This is used together with \a currIndex_ for sanity checks on the
131          * input data; invalid call sequences trigger asserts.
132          * The state of these variables does not otherwise affect the behavior
133          * of this class; this is the reason they can be changed in const
134          * methods.
135          */
136         //! Whether notifyDataStart() has been called.
137         mutable State           state_;
138         //! Index of currently active frame or the next frame if not in frame.
139         mutable int             currIndex_;
140 };
141
142 AnalysisDataModuleManager::Impl::Impl()
143     : bAllowMissing_(true), state_(eNotStarted), currIndex_(0)
144 {
145     // This must be in sync with how AbstractAnalysisData is actually
146     // initialized.
147     for (int i = 0; i < eDataPropertyNR; ++i)
148     {
149         bDataProperty_[i] = false;
150     }
151 }
152
153 void
154 AnalysisDataModuleManager::Impl::checkModuleProperty(
155         const AnalysisDataModuleInterface &module,
156         DataProperty property, bool bSet) const
157 {
158     bool      bOk   = true;
159     const int flags = module.flags();
160     switch (property)
161     {
162         case eMultipleDataSets:
163             if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipleDataSets))
164             {
165                 bOk = false;
166             }
167             break;
168         case eMultipleColumns:
169             if (bSet && !(flags & AnalysisDataModuleInterface::efAllowMulticolumn))
170             {
171                 bOk = false;
172             }
173             break;
174         case eMultipoint:
175             if ((bSet && !(flags & AnalysisDataModuleInterface::efAllowMultipoint))
176                 || (!bSet && (flags & AnalysisDataModuleInterface::efOnlyMultipoint)))
177             {
178                 bOk = false;
179             }
180             break;
181         default:
182             GMX_RELEASE_ASSERT(false, "Invalid data property enumeration");
183     }
184     if (!bOk)
185     {
186         GMX_THROW(APIError("Data module not compatible with data object properties"));
187     }
188 }
189
190 void
191 AnalysisDataModuleManager::Impl::checkModuleProperties(
192         const AnalysisDataModuleInterface &module) const
193 {
194     for (int i = 0; i < eDataPropertyNR; ++i)
195     {
196         checkModuleProperty(module, static_cast<DataProperty>(i), bDataProperty_[i]);
197     }
198 }
199
200 void
201 AnalysisDataModuleManager::Impl::presentData(AbstractAnalysisData        *data,
202                                              AnalysisDataModuleInterface *module)
203 {
204     if (state_ == eNotStarted)
205     {
206         return;
207     }
208     GMX_RELEASE_ASSERT(state_ != eInFrame,
209                        "Cannot apply a modules in mid-frame");
210     module->dataStarted(data);
211     const bool bCheckMissing = bAllowMissing_
212         && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
213     for (int i = 0; i < data->frameCount(); ++i)
214     {
215         AnalysisDataFrameRef frame = data->getDataFrame(i);
216         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
217         // TODO: Check all frames before doing anything for slightly better
218         // exception behavior.
219         if (bCheckMissing && !frame.allPresent())
220         {
221             GMX_THROW(APIError("Missing data not supported by a module"));
222         }
223         module->frameStarted(frame.header());
224         for (int j = 0; j < frame.pointSetCount(); ++j)
225         {
226             module->pointsAdded(frame.pointSet(j));
227         }
228         module->frameFinished(frame.header());
229     }
230     if (state_ == eFinished)
231     {
232         module->dataFinished();
233     }
234 }
235
236 /********************************************************************
237  * AnalysisDataModuleManager
238  */
239
240 AnalysisDataModuleManager::AnalysisDataModuleManager()
241     : impl_(new Impl())
242 {
243 }
244
245 AnalysisDataModuleManager::~AnalysisDataModuleManager()
246 {
247 }
248
249 void
250 AnalysisDataModuleManager::dataPropertyAboutToChange(DataProperty property, bool bSet)
251 {
252     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
253                        "Cannot change data properties after data has been started");
254     if (impl_->bDataProperty_[property] != bSet)
255     {
256         Impl::ModuleList::const_iterator i;
257         for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
258         {
259             impl_->checkModuleProperty(**i, property, bSet);
260         }
261         impl_->bDataProperty_[property] = bSet;
262     }
263 }
264
265 void
266 AnalysisDataModuleManager::addModule(AbstractAnalysisData      *data,
267                                      AnalysisDataModulePointer  module)
268 {
269     impl_->checkModuleProperties(*module);
270     GMX_RELEASE_ASSERT(impl_->state_ != Impl::eInFrame,
271                        "Cannot add a data module in mid-frame");
272     impl_->presentData(data, module.get());
273
274     if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
275     {
276         impl_->bAllowMissing_ = false;
277     }
278     impl_->modules_.push_back(module);
279 }
280
281 void
282 AnalysisDataModuleManager::applyModule(AbstractAnalysisData        *data,
283                                        AnalysisDataModuleInterface *module)
284 {
285     impl_->checkModuleProperties(*module);
286     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eFinished,
287                        "Data module can only be applied to ready data");
288     impl_->presentData(data, module);
289 }
290
291
292 void
293 AnalysisDataModuleManager::notifyDataStart(AbstractAnalysisData *data) const
294 {
295     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eNotStarted,
296                        "notifyDataStart() called more than once");
297     for (int d = 0; d < data->dataSetCount(); ++d)
298     {
299         GMX_RELEASE_ASSERT(data->columnCount(d) > 0,
300                            "Data column count is not set");
301     }
302     impl_->state_ = Impl::eInData;
303
304     Impl::ModuleList::const_iterator i;
305     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
306     {
307         // This should not fail, since addModule() and
308         // dataPropertyAboutToChange() already do the checks, but kept here to
309         // catch potential bugs (perhaps it would be best to assert on failure).
310         impl_->checkModuleProperties(**i);
311         (*i)->dataStarted(data);
312     }
313 }
314
315
316 void
317 AnalysisDataModuleManager::notifyFrameStart(const AnalysisDataFrameHeader &header) const
318 {
319     GMX_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
320     GMX_ASSERT(header.index() == impl_->currIndex_, "Out of order frames");
321     impl_->state_     = Impl::eInFrame;
322
323     Impl::ModuleList::const_iterator i;
324     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
325     {
326         (*i)->frameStarted(header);
327     }
328 }
329
330
331 void
332 AnalysisDataModuleManager::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
333 {
334     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
335     // TODO: Add checks for column spans (requires passing the information
336     // about the column counts from somewhere).
337     //GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
338     //           "Invalid columns");
339     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
340                "Points do not correspond to current frame");
341     if (!impl_->bAllowMissing_ && !points.allPresent())
342     {
343         GMX_THROW(APIError("Missing data not supported by a module"));
344     }
345
346     Impl::ModuleList::const_iterator i;
347     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
348     {
349         (*i)->pointsAdded(points);
350     }
351 }
352
353
354 void
355 AnalysisDataModuleManager::notifyFrameFinish(const AnalysisDataFrameHeader &header) const
356 {
357     GMX_ASSERT(impl_->state_ == Impl::eInFrame, "notifyFrameStart() not called");
358     GMX_ASSERT(header.index() == impl_->currIndex_,
359                "Header does not correspond to current frame");
360     // TODO: Add a check for the frame count in the source data including this
361     // frame.
362     impl_->state_ = Impl::eInData;
363     ++impl_->currIndex_;
364
365     Impl::ModuleList::const_iterator i;
366     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
367     {
368         (*i)->frameFinished(header);
369     }
370 }
371
372
373 void
374 AnalysisDataModuleManager::notifyDataFinish() const
375 {
376     GMX_RELEASE_ASSERT(impl_->state_ == Impl::eInData, "Invalid call sequence");
377     impl_->state_ = Impl::eFinished;
378
379     Impl::ModuleList::const_iterator i;
380     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
381     {
382         (*i)->dataFinished();
383     }
384 }
385
386 } // namespace gmx