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