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         /*! \brief
75          * Present data already added to the data object to a module.
76          *
77          * \param[in] data   Data object to read data from.
78          * \param[in] module Module to present the data to.
79          * \throws    APIError if \p module is not compatible with the data
80          *      object.
81          * \throws    APIError if all data is not available through
82          *      getDataFrame().
83          * \throws    unspecified Any exception thrown by \p module in its data
84          *      notification methods.
85          *
86          * Uses getDataFrame() in \p data to access all data in the object, and
87          * calls the notification functions in \p module as if the module had
88          * been registered to the data object when the data was added.
89          */
90         void presentData(AbstractAnalysisData        *data,
91                          AnalysisDataModuleInterface *module);
92
93         //! List of modules added to the data.
94         ModuleList              modules_;
95         //! true if all modules support missing data.
96         bool                    bAllowMissing_;
97         //! Whether notifyDataStart() has been called.
98         mutable bool            bDataStart_;
99         //! Whether new data is being added.
100         mutable bool            bInData_;
101         //! Whether data for a frame is being added.
102         mutable bool            bInFrame_;
103         //! Index of the currently active frame.
104         mutable int             currIndex_;
105         /*! \brief
106          * Total number of frames in the data.
107          *
108          * The counter is incremented in notifyFrameFinish().
109          */
110         int                     nframes_;
111 };
112
113 AbstractAnalysisData::Impl::Impl()
114     : bAllowMissing_(true), bDataStart_(false), bInData_(false), bInFrame_(false),
115       currIndex_(-1), nframes_(0)
116 {
117 }
118
119 void
120 AbstractAnalysisData::Impl::presentData(AbstractAnalysisData        *data,
121                                         AnalysisDataModuleInterface *module)
122 {
123     module->dataStarted(data);
124     bool bCheckMissing = bAllowMissing_
125         && !(module->flags() & AnalysisDataModuleInterface::efAllowMissing);
126     for (int i = 0; i < data->frameCount(); ++i)
127     {
128         AnalysisDataFrameRef frame = data->getDataFrame(i);
129         GMX_RELEASE_ASSERT(frame.isValid(), "Invalid data frame returned");
130         // TODO: Check all frames before doing anything for slightly better
131         // exception behavior.
132         if (bCheckMissing && !frame.allPresent())
133         {
134             GMX_THROW(APIError("Missing data not supported by a module"));
135         }
136         module->frameStarted(frame.header());
137         for (int j = 0; j < frame.pointSetCount(); ++j)
138         {
139             module->pointsAdded(frame.pointSet(j));
140         }
141         module->frameFinished(frame.header());
142     }
143     if (!bInData_)
144     {
145         module->dataFinished();
146     }
147 }
148
149
150 /********************************************************************
151  * AbstractAnalysisData
152  */
153 /*! \cond libapi */
154 AbstractAnalysisData::AbstractAnalysisData()
155     : impl_(new Impl()), columnCount_(0), bMultiPoint_(false)
156 {
157 }
158 //! \endcond
159
160 AbstractAnalysisData::~AbstractAnalysisData()
161 {
162 }
163
164
165 int
166 AbstractAnalysisData::frameCount() const
167 {
168     return impl_->nframes_;
169 }
170
171
172 AnalysisDataFrameRef
173 AbstractAnalysisData::tryGetDataFrame(int index) const
174 {
175     if (index < 0 || index >= frameCount())
176     {
177         return AnalysisDataFrameRef();
178     }
179     return tryGetDataFrameInternal(index);
180 }
181
182
183 AnalysisDataFrameRef
184 AbstractAnalysisData::getDataFrame(int index) const
185 {
186     AnalysisDataFrameRef frame = tryGetDataFrame(index);
187     if (!frame.isValid())
188     {
189         GMX_THROW(APIError("Invalid frame accessed"));
190     }
191     return frame;
192 }
193
194
195 bool
196 AbstractAnalysisData::requestStorage(int nframes)
197 {
198     GMX_RELEASE_ASSERT(nframes >= -1, "Invalid number of frames requested");
199     if (nframes == 0)
200     {
201         return true;
202     }
203     return requestStorageInternal(nframes);
204 }
205
206
207 void
208 AbstractAnalysisData::addModule(AnalysisDataModulePointer module)
209 {
210     if ((columnCount() > 1 && !(module->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
211         || (isMultipoint() && !(module->flags() & AnalysisDataModuleInterface::efAllowMultipoint))
212         || (!isMultipoint() && (module->flags() & AnalysisDataModuleInterface::efOnlyMultipoint)))
213     {
214         GMX_THROW(APIError("Data module not compatible with data object properties"));
215     }
216
217     if (impl_->bDataStart_)
218     {
219         GMX_RELEASE_ASSERT(!impl_->bInFrame_,
220                            "Cannot add data modules in mid-frame");
221         impl_->presentData(this, module.get());
222     }
223     if (!(module->flags() & AnalysisDataModuleInterface::efAllowMissing))
224     {
225         impl_->bAllowMissing_ = false;
226     }
227     impl_->modules_.push_back(module);
228 }
229
230
231 void
232 AbstractAnalysisData::addColumnModule(int col, int span,
233                                       AnalysisDataModulePointer module)
234 {
235     GMX_RELEASE_ASSERT(col >= 0 && span >= 1 && col + span <= columnCount_,
236                        "Invalid columns specified for a column module");
237     if (impl_->bDataStart_)
238     {
239         GMX_THROW(NotImplementedError("Cannot add column modules after data"));
240     }
241
242     boost::shared_ptr<AnalysisDataProxy> proxy(
243             new AnalysisDataProxy(col, span, this));
244     proxy->addModule(module);
245     addModule(proxy);
246 }
247
248
249 void
250 AbstractAnalysisData::applyModule(AnalysisDataModuleInterface *module)
251 {
252     if ((columnCount() > 1 && !(module->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
253         || (isMultipoint() && !(module->flags() & AnalysisDataModuleInterface::efAllowMultipoint))
254         || (!isMultipoint() && (module->flags() & AnalysisDataModuleInterface::efOnlyMultipoint)))
255     {
256         GMX_THROW(APIError("Data module not compatible with data object properties"));
257     }
258     GMX_RELEASE_ASSERT(impl_->bDataStart_ && !impl_->bInData_,
259                        "Data module can only be applied to ready data");
260
261     impl_->presentData(this, module);
262 }
263
264 /*! \cond libapi */
265 void
266 AbstractAnalysisData::setColumnCount(int columnCount)
267 {
268     GMX_RELEASE_ASSERT(columnCount > 0, "Invalid data column count");
269     GMX_RELEASE_ASSERT(columnCount_ == 0 || impl_->modules_.empty(),
270                        "Data column count cannot be changed after modules are added");
271     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
272                        "Data column count cannot be changed after data has been added");
273     columnCount_ = columnCount;
274 }
275
276
277 void
278 AbstractAnalysisData::setMultipoint(bool multipoint)
279 {
280     GMX_RELEASE_ASSERT(impl_->modules_.empty(),
281                        "Data type cannot be changed after modules are added");
282     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
283                        "Data type cannot be changed after data has been added");
284     bMultiPoint_ = multipoint;
285 }
286
287
288 /*! \internal
289  * This method is not const because the dataStarted() methods of the attached
290  * modules can request storage of the data.
291  */
292 void
293 AbstractAnalysisData::notifyDataStart()
294 {
295     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
296                        "notifyDataStart() called more than once");
297     GMX_RELEASE_ASSERT(columnCount_ > 0, "Data column count is not set");
298     impl_->bDataStart_ = impl_->bInData_ = true;
299
300     Impl::ModuleList::const_iterator i;
301     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
302     {
303         if (columnCount_ > 1 && !((*i)->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
304         {
305             GMX_THROW(APIError("Data module not compatible with data object properties"));
306         }
307         (*i)->dataStarted(this);
308     }
309 }
310
311
312 void
313 AbstractAnalysisData::notifyFrameStart(const AnalysisDataFrameHeader &header) const
314 {
315     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
316     GMX_ASSERT(!impl_->bInFrame_,
317                "notifyFrameStart() called while inside a frame");
318     GMX_ASSERT(header.index() == impl_->nframes_,
319                "Out of order frames");
320     impl_->bInFrame_  = true;
321     impl_->currIndex_ = header.index();
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 AbstractAnalysisData::notifyPointsAdd(const AnalysisDataPointSetRef &points) const
333 {
334     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
335     GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
336     GMX_ASSERT(points.lastColumn() < columnCount(), "Invalid columns");
337     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
338                "Points do not correspond to current frame");
339     if (!impl_->bAllowMissing_ && !points.allPresent())
340     {
341         GMX_THROW(APIError("Missing data not supported by a module"));
342     }
343
344     Impl::ModuleList::const_iterator i;
345     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
346     {
347         (*i)->pointsAdded(points);
348     }
349 }
350
351
352 void
353 AbstractAnalysisData::notifyFrameFinish(const AnalysisDataFrameHeader &header)
354 {
355     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
356     GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
357     GMX_ASSERT(header.index() == impl_->currIndex_,
358                "Header does not correspond to current frame");
359     impl_->bInFrame_  = false;
360     impl_->currIndex_ = -1;
361
362     // Increment the counter before notifications to allow frame access from
363     // modules.
364     ++impl_->nframes_;
365
366     Impl::ModuleList::const_iterator i;
367     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
368     {
369         (*i)->frameFinished(header);
370     }
371 }
372
373
374 void
375 AbstractAnalysisData::notifyDataFinish() const
376 {
377     GMX_RELEASE_ASSERT(impl_->bInData_, "notifyDataStart() not called");
378     GMX_RELEASE_ASSERT(!impl_->bInFrame_,
379                        "notifyDataFinish() called while inside a frame");
380     impl_->bInData_ = false;
381
382     Impl::ModuleList::const_iterator i;
383     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
384     {
385         (*i)->dataFinished();
386     }
387 }
388 //! \endcond
389
390 } // namespace gmx