Merge "Merge branch release-4-6 into master"
[alexxy/gromacs.git] / src / gromacs / analysisdata / abstractdata.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::AbstractAnalysisData.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_analysisdata
41  */
42 #include "gromacs/analysisdata/abstractdata.h"
43
44 #include <vector>
45
46 #include "gromacs/analysisdata/datamodule.h"
47 #include "gromacs/utility/exceptions.h"
48 #include "gromacs/utility/gmxassert.h"
49 #include "gromacs/utility/uniqueptr.h"
50
51 #include "dataframe.h"
52 #include "dataproxy.h"
53
54 namespace gmx
55 {
56
57 /********************************************************************
58  * AbstractAnalysisData::Impl
59  */
60
61 /*! \internal \brief
62  * Private implementation class for AbstractAnalysisData.
63  *
64  * \ingroup module_analysisdata
65  */
66 class AbstractAnalysisData::Impl
67 {
68     public:
69         //! Shorthand for list of modules added to the data.
70         typedef std::vector<AnalysisDataModulePointer> ModuleList;
71
72         Impl();
73
74         //! Returns whether any data set has more than one column.
75         bool isMultiColumn() const;
76
77         /*! \brief
78          * Checks whether a module is compatible with the data properties.
79          *
80          * \param[in] module Module to check.
81          * \throws    APIError if \p module is not compatible with the data.
82          *
83          * Does not check the actual data (e.g., missing values), but only the
84          * dimensionality and other preset properties of the data.
85          */
86         void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
87
88         /*! \brief
89          * Present data already added to the data object to a module.
90          *
91          * \param[in] data   Data object to read data from.
92          * \param[in] module Module to present the data to.
93          * \throws    APIError if \p module is not compatible with the data.
94          * \throws    APIError if all data is not available through
95          *      getDataFrame().
96          * \throws    unspecified Any exception thrown by \p module in its data
97          *      notification methods.
98          *
99          * Uses getDataFrame() in \p data to access all data in the object, and
100          * calls the notification functions in \p module as if the module had
101          * been registered to the data object when the data was added.
102          */
103         void presentData(AbstractAnalysisData        *data,
104                          AnalysisDataModuleInterface *module);
105
106         //! Column counts for each data set in the data.
107         std::vector<int>        columnCounts_;
108         //! Whether the data is multipoint.
109         bool                    bMultipoint_;
110         //! List of modules added to the data.
111         ModuleList              modules_;
112         //! true if all modules support missing data.
113         bool                    bAllowMissing_;
114         //! Whether notifyDataStart() has been called.
115         mutable bool            bDataStart_;
116         //! Whether new data is being added.
117         mutable bool            bInData_;
118         //! Whether data for a frame is being added.
119         mutable bool            bInFrame_;
120         //! Index of the currently active frame.
121         mutable int             currIndex_;
122         /*! \brief
123          * Total number of frames in the data.
124          *
125          * The counter is incremented in notifyFrameFinish().
126          */
127         int                     nframes_;
128 };
129
130 AbstractAnalysisData::Impl::Impl()
131     : bMultipoint_(false), bAllowMissing_(true),
132       bDataStart_(false), bInData_(false), bInFrame_(false),
133       currIndex_(-1), nframes_(0)
134 {
135     columnCounts_.push_back(0);
136 }
137
138 bool
139 AbstractAnalysisData::Impl::isMultiColumn() const
140 {
141     std::vector<int>::const_iterator i;
142     for (i = columnCounts_.begin(); i != columnCounts_.end(); ++i)
143     {
144         if (*i > 1)
145         {
146             return true;
147         }
148     }
149     return false;
150 }
151
152 //! Helper macro for testing module flags.
153 #define TEST_MODULE_FLAG(flags, flagname) \
154     ((flags) & AnalysisDataModuleInterface::flagname)
155 void
156 AbstractAnalysisData::Impl::checkModuleProperties(
157         const AnalysisDataModuleInterface &module) const
158 {
159     const int flags = module.flags();
160     if ((!TEST_MODULE_FLAG(flags, efAllowMulticolumn) && isMultiColumn()) ||
161         (!TEST_MODULE_FLAG(flags, efAllowMultipoint)  && bMultipoint_) ||
162         ( TEST_MODULE_FLAG(flags, efOnlyMultipoint)   && !bMultipoint_) ||
163         (!TEST_MODULE_FLAG(flags, efAllowMultipleDataSets)
164          && columnCounts_.size() > 1U))
165     {
166         GMX_THROW(APIError("Data module not compatible with data object properties"));
167     }
168 }
169 #undef TEST_MODULE_FLAGS
170
171 void
172 AbstractAnalysisData::Impl::presentData(AbstractAnalysisData        *data,
173                                         AnalysisDataModuleInterface *module)
174 {
175     module->dataStarted(data);
176     bool bCheckMissing = bAllowMissing_
177         && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
178     for (int i = 0; i < data->frameCount(); ++i)
179     {
180         AnalysisDataFrameRef frame = data->getDataFrame(i);
181         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
182         // TODO: Check all frames before doing anything for slightly better
183         // exception behavior.
184         if (bCheckMissing && !frame.allPresent())
185         {
186             GMX_THROW(APIError("Missing data not supported by a module"));
187         }
188         module->frameStarted(frame.header());
189         for (int j = 0; j < frame.pointSetCount(); ++j)
190         {
191             module->pointsAdded(frame.pointSet(j));
192         }
193         module->frameFinished(frame.header());
194     }
195     if (!bInData_)
196     {
197         module->dataFinished();
198     }
199 }
200
201
202 /********************************************************************
203  * AbstractAnalysisData
204  */
205 /*! \cond libapi */
206 AbstractAnalysisData::AbstractAnalysisData()
207     : impl_(new Impl())
208 {
209 }
210 //! \endcond
211
212 AbstractAnalysisData::~AbstractAnalysisData()
213 {
214 }
215
216 bool
217 AbstractAnalysisData::isMultipoint() const
218 {
219     return impl_->bMultipoint_;
220 }
221
222 int
223 AbstractAnalysisData::dataSetCount() const
224 {
225     return impl_->columnCounts_.size();
226 }
227
228 int
229 AbstractAnalysisData::columnCount(int dataSet) const
230 {
231     GMX_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
232                "Out of range data set index");
233     return impl_->columnCounts_[dataSet];
234 }
235
236 int
237 AbstractAnalysisData::columnCount() const
238 {
239     GMX_ASSERT(dataSetCount() == 1,
240                "Convenience method not available for multiple data sets");
241     return columnCount(0);
242 }
243
244 int
245 AbstractAnalysisData::frameCount() const
246 {
247     return impl_->nframes_;
248 }
249
250
251 AnalysisDataFrameRef
252 AbstractAnalysisData::tryGetDataFrame(int index) const
253 {
254     if (index < 0 || index >= frameCount())
255     {
256         return AnalysisDataFrameRef();
257     }
258     return tryGetDataFrameInternal(index);
259 }
260
261
262 AnalysisDataFrameRef
263 AbstractAnalysisData::getDataFrame(int index) const
264 {
265     AnalysisDataFrameRef frame = tryGetDataFrame(index);
266     if (!frame.isValid())
267     {
268         GMX_THROW(APIError("Invalid frame accessed"));
269     }
270     return frame;
271 }
272
273
274 bool
275 AbstractAnalysisData::requestStorage(int nframes)
276 {
277     GMX_RELEASE_ASSERT(nframes >= -1, "Invalid number of frames requested");
278     if (nframes == 0)
279     {
280         return true;
281     }
282     return requestStorageInternal(nframes);
283 }
284
285
286 void
287 AbstractAnalysisData::addModule(AnalysisDataModulePointer module)
288 {
289     impl_->checkModuleProperties(*module);
290
291     if (impl_->bDataStart_)
292     {
293         GMX_RELEASE_ASSERT(!impl_->bInFrame_,
294                            "Cannot add data modules in mid-frame");
295         impl_->presentData(this, module.get());
296     }
297     if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
298     {
299         impl_->bAllowMissing_ = false;
300     }
301     impl_->modules_.push_back(module);
302 }
303
304
305 void
306 AbstractAnalysisData::addColumnModule(int col, int span,
307                                       AnalysisDataModulePointer module)
308 {
309     GMX_RELEASE_ASSERT(col >= 0 && span >= 1,
310                        "Invalid columns specified for a column module");
311     if (impl_->bDataStart_)
312     {
313         GMX_THROW(NotImplementedError("Cannot add column modules after data"));
314     }
315
316     boost::shared_ptr<AnalysisDataProxy> proxy(
317             new AnalysisDataProxy(col, span, this));
318     proxy->addModule(module);
319     addModule(proxy);
320 }
321
322
323 void
324 AbstractAnalysisData::applyModule(AnalysisDataModuleInterface *module)
325 {
326     impl_->checkModuleProperties(*module);
327     GMX_RELEASE_ASSERT(impl_->bDataStart_ && !impl_->bInData_,
328                        "Data module can only be applied to ready data");
329
330     impl_->presentData(this, module);
331 }
332
333 /*! \cond libapi */
334 void
335 AbstractAnalysisData::setDataSetCount(int dataSetCount)
336 {
337     GMX_RELEASE_ASSERT(dataSetCount > 0, "Invalid data column count");
338     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
339                        "Data set count cannot be changed after data has been added");
340     impl_->columnCounts_.resize(dataSetCount);
341 }
342
343 void
344 AbstractAnalysisData::setColumnCount(int dataSet, int columnCount)
345 {
346     GMX_RELEASE_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
347                        "Out of range data set index");
348     GMX_RELEASE_ASSERT(columnCount > 0, "Invalid data column count");
349     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
350                        "Data column count cannot be changed after data has been added");
351     impl_->columnCounts_[dataSet] = columnCount;
352 }
353
354 void
355 AbstractAnalysisData::setMultipoint(bool multipoint)
356 {
357     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
358                        "Data type cannot be changed after data has been added");
359     impl_->bMultipoint_ = multipoint;
360 }
361
362
363 /*! \internal
364  * This method is not const because the dataStarted() methods of the attached
365  * modules can request storage of the data.
366  */
367 void
368 AbstractAnalysisData::notifyDataStart()
369 {
370     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
371                        "notifyDataStart() called more than once");
372     for (int d = 0; d < dataSetCount(); ++d)
373     {
374         GMX_RELEASE_ASSERT(columnCount(d) > 0,
375                            "Data column count is not set");
376     }
377     impl_->bDataStart_ = impl_->bInData_ = true;
378
379     Impl::ModuleList::const_iterator i;
380     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
381     {
382         impl_->checkModuleProperties(**i);
383         (*i)->dataStarted(this);
384     }
385 }
386
387
388 void
389 AbstractAnalysisData::notifyFrameStart(const AnalysisDataFrameHeader &header) const
390 {
391     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
392     GMX_ASSERT(!impl_->bInFrame_,
393                "notifyFrameStart() called while inside a frame");
394     GMX_ASSERT(header.index() == impl_->nframes_,
395                "Out of order frames");
396     impl_->bInFrame_  = true;
397     impl_->currIndex_ = header.index();
398
399     Impl::ModuleList::const_iterator i;
400     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
401     {
402         (*i)->frameStarted(header);
403     }
404 }
405
406
407 void
408 AbstractAnalysisData::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
409 {
410     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
411     GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
412     GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
413                "Invalid columns");
414     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
415                "Points do not correspond to current frame");
416     if (!impl_->bAllowMissing_ && !points.allPresent())
417     {
418         GMX_THROW(APIError("Missing data not supported by a module"));
419     }
420
421     Impl::ModuleList::const_iterator i;
422     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
423     {
424         (*i)->pointsAdded(points);
425     }
426 }
427
428
429 void
430 AbstractAnalysisData::notifyFrameFinish(const AnalysisDataFrameHeader &header)
431 {
432     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
433     GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
434     GMX_ASSERT(header.index() == impl_->currIndex_,
435                "Header does not correspond to current frame");
436     impl_->bInFrame_  = false;
437     impl_->currIndex_ = -1;
438
439     // Increment the counter before notifications to allow frame access from
440     // modules.
441     ++impl_->nframes_;
442
443     Impl::ModuleList::const_iterator i;
444     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
445     {
446         (*i)->frameFinished(header);
447     }
448 }
449
450
451 void
452 AbstractAnalysisData::notifyDataFinish() const
453 {
454     GMX_RELEASE_ASSERT(impl_->bInData_, "notifyDataStart() not called");
455     GMX_RELEASE_ASSERT(!impl_->bInFrame_,
456                        "notifyDataFinish() called while inside a frame");
457     impl_->bInData_ = false;
458
459     Impl::ModuleList::const_iterator i;
460     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
461     {
462         (*i)->dataFinished();
463     }
464 }
465 //! \endcond
466
467 } // namespace gmx