Merge branch 'release-4-6'
[alexxy/gromacs.git] / src / gromacs / analysisdata / datastorage.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2012, 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 classes in datastorage.h and paralleloptions.h.
38  *
39  * \author Teemu Murtola <teemu.murtola@gmail.com>
40  * \ingroup module_analysisdata
41  */
42 #include "datastorage.h"
43
44 #include <limits>
45 #include <vector>
46
47 #include "gromacs/analysisdata/abstractdata.h"
48 #include "gromacs/analysisdata/dataframe.h"
49 #include "gromacs/analysisdata/paralleloptions.h"
50 #include "gromacs/utility/exceptions.h"
51 #include "gromacs/utility/gmxassert.h"
52 #include "gromacs/utility/uniqueptr.h"
53
54 namespace gmx
55 {
56
57 /********************************************************************
58  * AnalysisDataParallelOptions
59  */
60
61 AnalysisDataParallelOptions::AnalysisDataParallelOptions()
62     : parallelizationFactor_(1)
63 {
64 }
65
66
67 AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFactor)
68     : parallelizationFactor_(parallelizationFactor)
69 {
70     GMX_RELEASE_ASSERT(parallelizationFactor >= 1,
71                        "Invalid parallelization factor");
72 }
73
74
75 /********************************************************************
76  * AnalysisDataStorage::Impl
77  */
78
79 /*! \internal \brief
80  * Private implementation class for AnalysisDataStorage.
81  *
82  * \ingroup module_analysisdata
83  */
84 class AnalysisDataStorage::Impl
85 {
86     public:
87         //! Smart pointer type for managing a stored frame.
88         typedef gmx_unique_ptr<AnalysisDataStorageFrame>::type FramePointer;
89
90         /*! \brief
91          * Stored information about a single stored frame.
92          *
93          * Methods in this class do not throw.
94          */
95         struct StoredFrame
96         {
97             //! Indicates what operations have been performed on a frame.
98             enum Status
99             {
100                 eMissing,  //!< Frame has not yet been started.
101                 eStarted,  //!< startFrame() has been called.
102                 eFinished, //!< finishFrame() has been called.
103                 eNotified  //!< Appropriate notifications have been sent.
104             };
105
106             //! Constructs an object that manages a given frame object.
107             explicit StoredFrame(AnalysisDataStorageFrame *frame)
108                 : frame(frame), status(eMissing)
109             {
110             }
111             //! Whether the frame has been started with startFrame().
112             bool isStarted() const { return status >= eStarted; }
113             //! Whether the frame has been finished with finishFrame().
114             bool isFinished() const { return status >= eFinished; }
115             //! Whether all notifications have been sent.
116             bool isNotified() const { return status >= eNotified; }
117             //! Whether the frame is ready to be available outside the storage.
118             bool isAvailable() const { return status >= eFinished; }
119
120             /*! \brief
121              * Actual frame data.
122              *
123              * Never NULL.
124              */
125             FramePointer              frame;
126             //! In what state the frame currently is.
127             Status                    status;
128         };
129
130         //! Shorthand for a list of data frames that are currently stored.
131         typedef std::vector<StoredFrame> FrameList;
132
133         Impl();
134
135         //! Returns the number of columns in the attached data.
136         int columnCount() const;
137         //! Returns whether the storage is set to use multipoint data.
138         bool isMultipoint() const;
139         /*! \brief
140          * Whether storage of all frames has been requested.
141          *
142          * Storage of all frames also works as expected if \a storageLimit_ is
143          * used in comparisons directly, but this method should be used to
144          * check how to manage \a frames_.
145          */
146         bool storeAll() const
147         {
148             return storageLimit_ == std::numeric_limits<int>::max();
149         }
150         //! Returns the index of the oldest frame that may be currently stored.
151         int firstStoredIndex() const;
152         /*! \brief
153          * Computes index into \a frames_ for accessing frame \p index.
154          *
155          * \param[in]  index  Zero-based frame index.
156          * \retval  -1 if \p index is not available in \a frames_.
157          *
158          * Does not throw.
159          */
160         int computeStorageLocation(int index) const;
161
162         /*! \brief
163          * Computes an index into \a frames_ that is one past the last frame
164          * stored.
165          *
166          * Does not throw.
167          */
168         size_t endStorageLocation() const;
169
170         /*! \brief
171          * Extends \a frames_ to a new size.
172          *
173          * \throws std::bad_alloc if out of memory.
174          */
175         void extendBuffer(AnalysisDataStorage *storage, size_t newSize);
176         /*! \brief
177          * Remove oldest frame from the storage to make space for a new one.
178          *
179          * Increments \a firstFrameLocation_ and reinitializes the frame that
180          * was made unavailable by this operation.
181          *
182          * Does not throw.
183          *
184          * \see frames_
185          */
186         void rotateBuffer();
187
188         /*! \brief
189          * Calls notification method in \a data_.
190          *
191          * \throws    unspecified  Any exception thrown by
192          *      AbstractAnalysisData::notifyPointsAdd().
193          */
194         void notifyPointSet(const AnalysisDataPointSetRef &points);
195         /*! \brief
196          * Calls notification methods for new frames.
197          *
198          * \param[in] firstLocation  First frame to consider.
199          * \throws    unspecified  Any exception thrown by frame notification
200          *      methods in AbstractAnalysisData.
201          *
202          * Notifies \a data_ of new frames (from \p firstLocation and after
203          * that) if all previous frames have already been notified.
204          * Also rotates the \a frames_ buffer as necessary.
205          */
206         void notifyNextFrames(size_t firstLocation);
207
208         //! Data object to use for notification calls.
209         AbstractAnalysisData   *data_;
210         /*! \brief
211          * Whether the storage has been set to allow multipoint.
212          *
213          * Should be possible to remove once full support for multipoint data
214          * has been implemented;  isMultipoint() can simply return
215          * \c data_->isMultipoint() in that case.
216          */
217         bool                    bMultipoint_;
218         /*! \brief
219          * Number of past frames that need to be stored.
220          *
221          * Always non-negative.  If storage of all frames has been requested,
222          * this is set to a large number.
223          */
224         int                     storageLimit_;
225         /*! \brief
226          * Number of future frames that may need to be started.
227          *
228          * Should always be at least one.
229          *
230          * \see AnalysisDataStorage::startFrame()
231          */
232         int                     pendingLimit_;
233         /*! \brief
234          * Data frames that are currently stored.
235          *
236          * If storage of all frames has been requested, this is simply a vector
237          * of frames up to the latest frame that has been started.
238          * In this case, \a firstFrameLocation_ is always zero.
239          *
240          * If storage of all frames is not requested, this is a ring buffer of
241          * frames of size \c n=storageLimit_+pendingLimit_+1.  If a frame with
242          * index \c index is currently stored, its location is
243          * \c index%frames_.size().
244          * When at most \a storageLimit_ first frames have been finished,
245          * this contains storage for the first \c n-1 frames.
246          * When more than \a storageLimit_ first frames have been finished,
247          * the oldest stored frame is stored in the location
248          * \a firstFrameLocation_, and \a storageLimit_ frames starting from
249          * this location are the last finished frames.  \a pendingLimit_ frames
250          * follow, and some of these may be in progress or finished.
251          * There is always one unused frame in the buffer, which is initialized
252          * such that when \a firstFrameLocation_ is incremented, it becomes
253          * valid.  This makes it easier to rotate the buffer in concurrent
254          * access scenarions (which are not yet otherwise implemented).
255          */
256         FrameList               frames_;
257         //! Location of oldest frame in \a frames_.
258         size_t                  firstFrameLocation_;
259         /*! \brief
260          * Index of next frame that will be added to \a frames_.
261          *
262          * If all frames are not stored, this will be the index of the unused
263          * frame (see \a frames_).
264          */
265         int                     nextIndex_;
266 };
267
268 AnalysisDataStorage::Impl::Impl()
269     : data_(NULL), bMultipoint_(false),
270       storageLimit_(0), pendingLimit_(1), firstFrameLocation_(0), nextIndex_(0)
271 {
272 }
273
274
275 int
276 AnalysisDataStorage::Impl::columnCount() const
277 {
278     GMX_ASSERT(data_ != NULL, "columnCount() called too early");
279     return data_->columnCount();
280 }
281
282
283 bool
284 AnalysisDataStorage::Impl::isMultipoint() const
285 {
286     return bMultipoint_;
287 }
288
289
290 int
291 AnalysisDataStorage::Impl::firstStoredIndex() const
292 {
293     return frames_[firstFrameLocation_].frame->frameIndex();
294 }
295
296
297 int
298 AnalysisDataStorage::Impl::computeStorageLocation(int index) const
299 {
300     if (index < firstStoredIndex() || index >= nextIndex_)
301     {
302         return -1;
303     }
304     return index % frames_.size();
305 }
306
307
308 size_t
309 AnalysisDataStorage::Impl::endStorageLocation() const
310 {
311     if (storeAll())
312     {
313         return frames_.size();
314     }
315     if (frames_[0].frame->frameIndex() == 0 || firstFrameLocation_ == 0)
316     {
317         return frames_.size() - 1;
318     }
319     return firstFrameLocation_ - 1;
320 }
321
322
323 void
324 AnalysisDataStorage::Impl::extendBuffer(AnalysisDataStorage *storage,
325                                         size_t               newSize)
326 {
327     frames_.reserve(newSize);
328     while (frames_.size() < newSize)
329     {
330         frames_.push_back(StoredFrame(
331                                   new AnalysisDataStorageFrame(storage, columnCount(), nextIndex_)));
332         ++nextIndex_;
333     }
334     // The unused frame should not be included in the count.
335     if (!storeAll())
336     {
337         --nextIndex_;
338     }
339 }
340
341
342 void
343 AnalysisDataStorage::Impl::rotateBuffer()
344 {
345     GMX_ASSERT(!storeAll(),
346                "No need to rotate internal buffer if everything is stored");
347     size_t prevFirst = firstFrameLocation_;
348     size_t nextFirst = prevFirst + 1;
349     if (nextFirst == frames_.size())
350     {
351         nextFirst = 0;
352     }
353     firstFrameLocation_ = nextFirst;
354     StoredFrame &prevFrame = frames_[prevFirst];
355     prevFrame.status         = StoredFrame::eMissing;
356     prevFrame.frame->header_ = AnalysisDataFrameHeader(nextIndex_ + 1, 0.0, 0.0);
357     prevFrame.frame->clearValues();
358     ++nextIndex_;
359 }
360
361
362 void
363 AnalysisDataStorage::Impl::notifyPointSet(const AnalysisDataPointSetRef &points)
364 {
365     data_->notifyPointsAdd(points);
366 }
367
368
369 void
370 AnalysisDataStorage::Impl::notifyNextFrames(size_t firstLocation)
371 {
372     if (firstLocation != firstFrameLocation_)
373     {
374         // firstLocation can only be zero here if !storeAll() because
375         // firstFrameLocation_ is always zero for storeAll()
376         int prevIndex =
377             (firstLocation == 0 ? frames_.size() - 1 : firstLocation - 1);
378         if (!frames_[prevIndex].isNotified())
379         {
380             return;
381         }
382     }
383     size_t i   = firstLocation;
384     size_t end = endStorageLocation();
385     while (i != end)
386     {
387         Impl::StoredFrame &storedFrame = frames_[i];
388         if (!storedFrame.isFinished())
389         {
390             break;
391         }
392         if (storedFrame.status == StoredFrame::eFinished)
393         {
394             data_->notifyFrameStart(storedFrame.frame->header());
395             data_->notifyPointsAdd(storedFrame.frame->currentPoints());
396             data_->notifyFrameFinish(storedFrame.frame->header());
397             storedFrame.status = StoredFrame::eNotified;
398             if (storedFrame.frame->frameIndex() >= storageLimit_)
399             {
400                 rotateBuffer();
401             }
402         }
403         ++i;
404         if (!storeAll() && i >= frames_.size())
405         {
406             i = 0;
407         }
408     }
409 }
410
411
412 /********************************************************************
413  * AnalysisDataStorageFrame
414  */
415
416 AnalysisDataStorageFrame::AnalysisDataStorageFrame(AnalysisDataStorage *storage,
417                                                    int columnCount, int index)
418     : storage_(*storage), header_(index, 0.0, 0.0), values_(columnCount)
419 {
420 }
421
422
423 AnalysisDataStorageFrame::~AnalysisDataStorageFrame()
424 {
425 }
426
427
428 AnalysisDataPointSetRef
429 AnalysisDataStorageFrame::currentPoints() const
430 {
431     std::vector<AnalysisDataValue>::const_iterator begin = values_.begin();
432     std::vector<AnalysisDataValue>::const_iterator end   = values_.end();
433     while (begin != end && !begin->isSet())
434     {
435         ++begin;
436     }
437     while (end != begin && !(end-1)->isSet())
438     {
439         --end;
440     }
441     int firstColumn = (begin != end) ? begin - values_.begin() : 0;
442     return AnalysisDataPointSetRef(header_, firstColumn,
443                                    AnalysisDataValuesRef(begin, end));
444 }
445
446
447 void
448 AnalysisDataStorageFrame::clearValues()
449 {
450     std::vector<AnalysisDataValue>::iterator i;
451     for (i = values_.begin(); i != values_.end(); ++i)
452     {
453         i->clear();
454     }
455 }
456
457
458 void
459 AnalysisDataStorageFrame::finishPointSet()
460 {
461     storage_.impl_->notifyPointSet(currentPoints());
462     clearValues();
463 }
464
465
466 /********************************************************************
467  * AnalysisDataStorage
468  */
469
470 AnalysisDataStorage::AnalysisDataStorage()
471     : impl_(new Impl())
472 {
473 }
474
475
476 AnalysisDataStorage::~AnalysisDataStorage()
477 {
478 }
479
480
481 void
482 AnalysisDataStorage::setMultipoint(bool bMultipoint)
483 {
484     if (bMultipoint && impl_->storageLimit_ > 0)
485     {
486         GMX_THROW(APIError("Storage of multipoint data not supported"));
487     }
488     impl_->bMultipoint_ = bMultipoint;
489 }
490
491
492 void
493 AnalysisDataStorage::setParallelOptions(const AnalysisDataParallelOptions &opt)
494 {
495     impl_->pendingLimit_ = 2 * opt.parallelizationFactor() - 1;
496 }
497
498
499 AnalysisDataFrameRef
500 AnalysisDataStorage::tryGetDataFrame(int index) const
501 {
502     if (impl_->isMultipoint())
503     {
504         return AnalysisDataFrameRef();
505     }
506     int storageIndex = impl_->computeStorageLocation(index);
507     if (storageIndex == -1)
508     {
509         return AnalysisDataFrameRef();
510     }
511     const Impl::StoredFrame &storedFrame = impl_->frames_[storageIndex];
512     if (!storedFrame.isAvailable())
513     {
514         return AnalysisDataFrameRef();
515     }
516     const Impl::FramePointer &frame = storedFrame.frame;
517     return AnalysisDataFrameRef(frame->header(), frame->values_);
518 }
519
520
521 bool
522 AnalysisDataStorage::requestStorage(int nframes)
523 {
524     if (impl_->isMultipoint())
525     {
526         return false;
527     }
528
529     // Handle the case when everything needs to be stored.
530     if (nframes == -1)
531     {
532         impl_->storageLimit_ = std::numeric_limits<int>::max();
533         return true;
534     }
535     // Check whether an earlier call has requested more storage.
536     if (nframes < impl_->storageLimit_)
537     {
538         return true;
539     }
540     impl_->storageLimit_ = nframes;
541     return true;
542 }
543
544
545 void
546 AnalysisDataStorage::startDataStorage(AbstractAnalysisData *data)
547 {
548     // Data needs to be set before calling extendBuffer()
549     impl_->data_ = data;
550     setMultipoint(data->isMultipoint());
551     if (!impl_->storeAll())
552     {
553         impl_->extendBuffer(this, impl_->storageLimit_ + impl_->pendingLimit_ + 1);
554     }
555 }
556
557
558 AnalysisDataStorageFrame &
559 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
560 {
561     GMX_ASSERT(header.isValid(), "Invalid header");
562     Impl::StoredFrame *storedFrame;
563     if (impl_->storeAll())
564     {
565         size_t size = header.index() + 1;
566         if (impl_->frames_.size() < size)
567         {
568             impl_->extendBuffer(this, size);
569         }
570         storedFrame = &impl_->frames_[header.index()];
571     }
572     else
573     {
574         int storageIndex = impl_->computeStorageLocation(header.index());
575         if (storageIndex == -1)
576         {
577             GMX_THROW(APIError("Out of bounds frame index"));
578         }
579         storedFrame = &impl_->frames_[storageIndex];
580     }
581     GMX_RELEASE_ASSERT(!storedFrame->isStarted(),
582                        "startFrame() called twice for the same frame");
583     GMX_RELEASE_ASSERT(storedFrame->frame->frameIndex() == header.index(),
584                        "Inconsistent internal frame indexing");
585     storedFrame->status         = Impl::StoredFrame::eStarted;
586     storedFrame->frame->header_ = header;
587     if (impl_->isMultipoint())
588     {
589         impl_->data_->notifyFrameStart(header);
590     }
591     return *storedFrame->frame;
592 }
593
594
595 AnalysisDataStorageFrame &
596 AnalysisDataStorage::startFrame(int index, real x, real dx)
597 {
598     return startFrame(AnalysisDataFrameHeader(index, x, dx));
599 }
600
601
602 AnalysisDataStorageFrame &
603 AnalysisDataStorage::currentFrame(int index)
604 {
605     int                storageIndex = impl_->computeStorageLocation(index);
606     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
607     Impl::StoredFrame &storedFrame = impl_->frames_[storageIndex];
608     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
609                        "currentFrame() called for frame before startFrame()");
610     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
611                        "currentFrame() called for frame after finishFrame()");
612     GMX_RELEASE_ASSERT(storedFrame.frame->frameIndex() == index,
613                        "Inconsistent internal frame indexing");
614     return *storedFrame.frame;
615 }
616
617
618 void
619 AnalysisDataStorage::finishFrame(int index)
620 {
621     int                storageIndex = impl_->computeStorageLocation(index);
622     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
623     Impl::StoredFrame &storedFrame = impl_->frames_[storageIndex];
624     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
625                        "finishFrame() called for frame before startFrame()");
626     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
627                        "finishFrame() called twice for the same frame");
628     GMX_RELEASE_ASSERT(storedFrame.frame->frameIndex() == index,
629                        "Inconsistent internal frame indexing");
630     storedFrame.status = Impl::StoredFrame::eFinished;
631     if (impl_->isMultipoint())
632     {
633         // TODO: Check that the last point set has been finished
634         impl_->data_->notifyFrameFinish(storedFrame.frame->header());
635         if (storedFrame.frame->frameIndex() >= impl_->storageLimit_)
636         {
637             impl_->rotateBuffer();
638         }
639     }
640     else
641     {
642         impl_->notifyNextFrames(storageIndex);
643     }
644 }
645
646
647 void
648 AnalysisDataStorage::finishFrame(const AnalysisDataStorageFrame &frame)
649 {
650     finishFrame(frame.frameIndex());
651 }
652
653 } // namespace gmx