164f9b877d0d74e0f6e034805157142b7fea67be
[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,2013,2014,2015,2016,2017,2018, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source 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 "gmxpre.h"
43
44 #include "datastorage.h"
45
46 #include <algorithm>
47 #include <iterator>
48 #include <limits>
49 #include <memory>
50 #include <vector>
51
52 #include "gromacs/analysisdata/abstractdata.h"
53 #include "gromacs/analysisdata/dataframe.h"
54 #include "gromacs/analysisdata/datamodulemanager.h"
55 #include "gromacs/analysisdata/paralleloptions.h"
56 #include "gromacs/compat/make_unique.h"
57 #include "gromacs/utility/exceptions.h"
58 #include "gromacs/utility/gmxassert.h"
59
60 namespace gmx
61 {
62
63 /********************************************************************
64  * AnalysisDataParallelOptions
65  */
66
67 AnalysisDataParallelOptions::AnalysisDataParallelOptions()
68     : parallelizationFactor_(1)
69 {
70 }
71
72
73 AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFactor)
74     : parallelizationFactor_(parallelizationFactor)
75 {
76     GMX_RELEASE_ASSERT(parallelizationFactor >= 1,
77                        "Invalid parallelization factor");
78 }
79
80
81 /********************************************************************
82  * AnalysisDataStorageImpl declaration
83  */
84
85 namespace internal
86 {
87
88 //! Smart pointer type for managing a storage frame builder.
89 typedef std::unique_ptr<AnalysisDataStorageFrame>
90     AnalysisDataFrameBuilderPointer;
91
92 /*! \internal \brief
93  * Private implementation class for AnalysisDataStorage.
94  *
95  * \ingroup module_analysisdata
96  */
97 class AnalysisDataStorageImpl
98 {
99     public:
100         //! Smart pointer type for managing a stored frame.
101         typedef std::unique_ptr<AnalysisDataStorageFrameData> FramePointer;
102
103         //! Shorthand for a list of data frames that are currently stored.
104         typedef std::vector<FramePointer> FrameList;
105         //! Shorthand for a list of currently unused storage frame builders.
106         typedef std::vector<AnalysisDataFrameBuilderPointer> FrameBuilderList;
107
108         AnalysisDataStorageImpl();
109
110         //! Returns whether the storage is set to use multipoint data.
111         bool isMultipoint() const;
112         /*! \brief
113          * Whether storage of all frames has been requested.
114          *
115          * Storage of all frames also works as expected if \a storageLimit_ is
116          * used in comparisons directly, but this method should be used to
117          * check how to manage \a frames_.
118          */
119         bool storeAll() const
120         {
121             return storageLimit_ == std::numeric_limits<int>::max();
122         }
123         //! Returns the index of the oldest frame that may be currently stored.
124         int firstStoredIndex() const;
125         //! Returns the index of the first frame that is not fully notified.
126         int firstUnnotifiedIndex() const { return firstUnnotifiedIndex_; }
127         /*! \brief
128          * Computes index into \a frames_ for accessing frame \p index.
129          *
130          * \param[in]  index  Zero-based frame index.
131          * \retval  -1 if \p index is not available in \a frames_.
132          *
133          * Does not throw.
134          */
135         int computeStorageLocation(int index) const;
136
137         /*! \brief
138          * Computes an index into \a frames_ that is one past the last frame
139          * stored.
140          *
141          * Does not throw.
142          */
143         size_t endStorageLocation() const;
144
145         /*! \brief
146          * Extends \a frames_ to a new size.
147          *
148          * \throws std::bad_alloc if out of memory.
149          */
150         void extendBuffer(size_t newSize);
151         /*! \brief
152          * Remove oldest frame from the storage to make space for a new one.
153          *
154          * Increments \a firstFrameLocation_ and reinitializes the frame that
155          * was made unavailable by this operation.
156          *
157          * Does not throw.
158          *
159          * \see frames_
160          */
161         void rotateBuffer();
162
163         /*! \brief
164          * Returns a frame builder object for use with a new frame.
165          *
166          * \throws std::bad_alloc if out of memory.
167          */
168         AnalysisDataFrameBuilderPointer getFrameBuilder();
169
170         /*! \brief
171          * Returns whether notifications should be immediately fired.
172          *
173          * This is used to optimize multipoint handling for non-parallel cases,
174          * where it is not necessary to store even a single frame.
175          *
176          * Does not throw.
177          */
178         bool shouldNotifyImmediately() const
179         {
180             return isMultipoint() && storageLimit_ == 0 && pendingLimit_ == 1;
181         }
182         /*! \brief
183          * Returns whether data needs to be stored at all.
184          *
185          * This is used to optimize multipoint handling for parallel cases
186          * (where shouldNotifyImmediately() returns false),
187          * where it is not necessary to store even a single frame.
188          *
189          * \todo
190          * This could be extended to non-multipoint data as well.
191          *
192          * Does not throw.
193          */
194         bool needStorage() const
195         {
196             return storageLimit_ > 0 || (pendingLimit_ > 1 && modules_->hasSerialModules());
197         }
198         //! Implementation for AnalysisDataStorage::finishFrame().
199         void finishFrame(int index);
200         /*! \brief
201          * Implementation for AnalysisDataStorage::finishFrameSerial().
202          */
203         void finishFrameSerial(int index);
204
205
206         //! Parent data object to access data dimensionality etc.
207         const AbstractAnalysisData *data_;
208         //! Manager to use for notification calls.
209         AnalysisDataModuleManager  *modules_;
210         /*! \brief
211          * Number of past frames that need to be stored.
212          *
213          * Always non-negative.  If storage of all frames has been requested,
214          * this is set to a large number.
215          */
216         int                     storageLimit_;
217         /*! \brief
218          * Number of future frames that may need to be started.
219          *
220          * Should always be at least one.
221          *
222          * \todo
223          * Get rid of this alltogether, as it is no longer used much.
224          *
225          * \see AnalysisDataStorage::startFrame()
226          */
227         int                     pendingLimit_;
228         /*! \brief
229          * Data frames that are currently stored.
230          *
231          * If storage of all frames has been requested, this is simply a vector
232          * of frames up to the latest frame that has been started.
233          * In this case, \a firstFrameLocation_ is always zero.
234          *
235          * If storage of all frames is not requested, this is a ring buffer of
236          * frames of size \c n=storageLimit_+pendingLimit_+1.  If a frame with
237          * index \c index is currently stored, its location is
238          * \c index%frames_.size().
239          * When at most \a storageLimit_ first frames have been finished,
240          * this contains storage for the first \c n-1 frames.
241          * When more than \a storageLimit_ first frames have been finished,
242          * the oldest stored frame is stored in the location
243          * \a firstFrameLocation_, and \a storageLimit_ frames starting from
244          * this location are the last finished frames.  \a pendingLimit_ frames
245          * follow, and some of these may be in progress or finished.
246          * There is always one unused frame in the buffer, which is initialized
247          * such that when \a firstFrameLocation_ is incremented, it becomes
248          * valid.  This makes it easier to rotate the buffer in concurrent
249          * access scenarions (which are not yet otherwise implemented).
250          */
251         FrameList               frames_;
252         //! Location of oldest frame in \a frames_.
253         size_t                  firstFrameLocation_;
254         //! Index of the first frame that is not fully notified.
255         int                     firstUnnotifiedIndex_;
256         /*! \brief
257          * Currently unused frame builders.
258          *
259          * The builders are cached to avoid repeatedly allocating memory for
260          * them.  Typically, there are as many builders as there are concurrent
261          * users of the storage object.  Whenever a frame is started, a builder
262          * is pulled from this pool by getFrameBuilder() (a new one is created
263          * if none are available), and assigned for that frame.  When that
264          * frame is finished, the builder is returned to this pool.
265          */
266         FrameBuilderList        builders_;
267         /*! \brief
268          * Index of next frame that will be added to \a frames_.
269          *
270          * If all frames are not stored, this will be the index of the unused
271          * frame (see \a frames_).
272          */
273         int                     nextIndex_;
274 };
275
276 /********************************************************************
277  * AnalysisDataStorageFrameImpl declaration
278  */
279
280 /*! \internal
281  * \brief
282  * Internal representation for a single stored frame.
283  *
284  * It is implemented such that the frame header is always valid, i.e.,
285  * header().isValid() returns always true.
286  *
287  * Methods in this class do not throw unless otherwise indicated.
288  *
289  * \ingroup module_analysisdata
290  */
291 class AnalysisDataStorageFrameData
292 {
293     public:
294         //! Indicates what operations have been performed on a frame.
295         enum Status
296         {
297             eMissing,  //!< Frame has not yet been started.
298             eStarted,  //!< startFrame() has been called.
299             eFinished, //!< finishFrame() has been called.
300             eNotified  //!< Appropriate notifications have been sent.
301         };
302
303         /*! \brief
304          * Create a new storage frame.
305          *
306          * \param     storageImpl  Storage object this frame belongs to.
307          * \param[in] index        Zero-based index for the frame.
308          */
309         AnalysisDataStorageFrameData(AnalysisDataStorageImpl *storageImpl,
310                                      int                      index);
311
312         //! Whether the frame has been started with startFrame().
313         bool isStarted() const { return status_ >= eStarted; }
314         //! Whether the frame has been finished with finishFrame().
315         bool isFinished() const { return status_ >= eFinished; }
316         //! Whether all notifications have been sent.
317         bool isNotified() const { return status_ >= eNotified; }
318         //! Whether the frame is ready to be available outside the storage.
319         bool isAvailable() const { return status_ >= eFinished; }
320
321         //! Marks the frame as notified.
322         void markNotified() { status_ = eNotified; }
323
324         //! Returns the storage implementation object.
325         AnalysisDataStorageImpl &storageImpl() const { return storageImpl_; }
326         //! Returns the underlying data object (for data dimensionalities etc.).
327         const AbstractAnalysisData &baseData() const { return *storageImpl().data_; }
328
329         //! Returns header for the frame.
330         const AnalysisDataFrameHeader &header() const { return header_; }
331         //! Returns zero-based index of the frame.
332         int frameIndex() const { return header().index(); }
333         //! Returns the number of point sets for the frame.
334         int pointSetCount() const { return pointSets_.size(); }
335
336         //! Clears the frame for reusing as a new frame.
337         void clearFrame(int newIndex);
338         /*! \brief
339          * Initializes the frame during AnalysisDataStorage::startFrame().
340          *
341          * \param[in] header  Header to use for the new frame.
342          * \param[in] builder Builder object to use.
343          */
344         void startFrame(const AnalysisDataFrameHeader   &header,
345                         AnalysisDataFrameBuilderPointer  builder);
346         //! Returns the builder for this frame.
347         AnalysisDataStorageFrame &builder() const
348         {
349             GMX_ASSERT(builder_, "Accessing builder for not-in-progress frame");
350             return *builder_;
351         }
352         /*! \brief
353          * Adds a new point set to this frame.
354          */
355         void addPointSet(int dataSetIndex, int firstColumn,
356                          ArrayRef<const AnalysisDataValue> v);
357         /*! \brief
358          * Finalizes the frame during AnalysisDataStorage::finishFrame().
359          *
360          * \returns The builder object used by the frame, for reusing it for
361          *      other frames.
362          */
363         AnalysisDataFrameBuilderPointer finishFrame(bool bMultipoint);
364
365         //! Returns frame reference to this frame.
366         AnalysisDataFrameRef frameReference() const
367         {
368             return AnalysisDataFrameRef(header_, values_, pointSets_);
369         }
370         //! Returns point set reference to a given point set.
371         AnalysisDataPointSetRef pointSet(int index) const;
372
373     private:
374         //! Storage object that contains this frame.
375         AnalysisDataStorageImpl                &storageImpl_;
376         //! Header for the frame.
377         AnalysisDataFrameHeader                 header_;
378         //! Values for the frame.
379         std::vector<AnalysisDataValue>          values_;
380         //! Information about each point set in the frame.
381         std::vector<AnalysisDataPointSetInfo>   pointSets_;
382         /*! \brief
383          * Builder object for the frame.
384          *
385          * Non-NULL when the frame is in progress, i.e., has been started but
386          * not yet finished.
387          */
388         AnalysisDataFrameBuilderPointer         builder_;
389         //! In what state the frame currently is.
390         Status                                  status_;
391
392         GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisDataStorageFrameData);
393 };
394
395 /********************************************************************
396  * AnalysisDataStorageImpl implementation
397  */
398
399 AnalysisDataStorageImpl::AnalysisDataStorageImpl()
400     : data_(nullptr), modules_(nullptr),
401       storageLimit_(0), pendingLimit_(1),
402       firstFrameLocation_(0), firstUnnotifiedIndex_(0), nextIndex_(0)
403 {
404 }
405
406
407 bool
408 AnalysisDataStorageImpl::isMultipoint() const
409 {
410     GMX_ASSERT(data_ != nullptr, "isMultipoint() called too early");
411     return data_->isMultipoint();
412 }
413
414
415 int
416 AnalysisDataStorageImpl::firstStoredIndex() const
417 {
418     return frames_[firstFrameLocation_]->frameIndex();
419 }
420
421
422 int
423 AnalysisDataStorageImpl::computeStorageLocation(int index) const
424 {
425     if (index < firstStoredIndex() || index >= nextIndex_)
426     {
427         return -1;
428     }
429     return index % frames_.size();
430 }
431
432
433 size_t
434 AnalysisDataStorageImpl::endStorageLocation() const
435 {
436     if (storeAll())
437     {
438         return frames_.size();
439     }
440     if (frames_[0]->frameIndex() == 0 || firstFrameLocation_ == 0)
441     {
442         return frames_.size() - 1;
443     }
444     return firstFrameLocation_ - 1;
445 }
446
447
448 void
449 AnalysisDataStorageImpl::extendBuffer(size_t newSize)
450 {
451     frames_.reserve(newSize);
452     while (frames_.size() < newSize)
453     {
454         frames_.push_back(compat::make_unique<AnalysisDataStorageFrameData>(this, nextIndex_));
455         ++nextIndex_;
456     }
457     // The unused frame should not be included in the count.
458     if (!storeAll())
459     {
460         --nextIndex_;
461     }
462 }
463
464
465 void
466 AnalysisDataStorageImpl::rotateBuffer()
467 {
468     GMX_ASSERT(!storeAll(),
469                "No need to rotate internal buffer if everything is stored");
470     size_t prevFirst = firstFrameLocation_;
471     size_t nextFirst = prevFirst + 1;
472     if (nextFirst == frames_.size())
473     {
474         nextFirst = 0;
475     }
476     firstFrameLocation_ = nextFirst;
477     frames_[prevFirst]->clearFrame(nextIndex_ + 1);
478     ++nextIndex_;
479 }
480
481
482 AnalysisDataFrameBuilderPointer
483 AnalysisDataStorageImpl::getFrameBuilder()
484 {
485     if (builders_.empty())
486     {
487         return AnalysisDataFrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
488     }
489     AnalysisDataFrameBuilderPointer builder(std::move(builders_.back()));
490     builders_.pop_back();
491     return builder;
492 }
493
494
495 void
496 AnalysisDataStorageImpl::finishFrame(int index)
497 {
498     const int storageIndex = computeStorageLocation(index);
499     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
500
501     AnalysisDataStorageFrameData &storedFrame = *frames_[storageIndex];
502     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
503                        "finishFrame() called for frame before startFrame()");
504     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
505                        "finishFrame() called twice for the same frame");
506     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
507                        "Inconsistent internal frame indexing");
508     builders_.push_back(storedFrame.finishFrame(isMultipoint()));
509     modules_->notifyParallelFrameFinish(storedFrame.header());
510     if (pendingLimit_ == 1)
511     {
512         finishFrameSerial(index);
513     }
514 }
515
516
517 void
518 AnalysisDataStorageImpl::finishFrameSerial(int index)
519 {
520     GMX_RELEASE_ASSERT(index == firstUnnotifiedIndex_,
521                        "Out of order finisFrameSerial() calls");
522     const int storageIndex = computeStorageLocation(index);
523     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
524
525     AnalysisDataStorageFrameData &storedFrame = *frames_[storageIndex];
526     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
527                        "Inconsistent internal frame indexing");
528     GMX_RELEASE_ASSERT(storedFrame.isFinished(),
529                        "finishFrameSerial() called before finishFrame()");
530     GMX_RELEASE_ASSERT(!storedFrame.isNotified(),
531                        "finishFrameSerial() called twice for the same frame");
532     // Increment before the notifications to make the frame available
533     // in the module callbacks.
534     ++firstUnnotifiedIndex_;
535     if (shouldNotifyImmediately())
536     {
537         modules_->notifyFrameFinish(storedFrame.header());
538     }
539     else
540     {
541         modules_->notifyFrameStart(storedFrame.header());
542         for (int j = 0; j < storedFrame.pointSetCount(); ++j)
543         {
544             modules_->notifyPointsAdd(storedFrame.pointSet(j));
545         }
546         modules_->notifyFrameFinish(storedFrame.header());
547     }
548     storedFrame.markNotified();
549     if (storedFrame.frameIndex() >= storageLimit_)
550     {
551         rotateBuffer();
552     }
553 }
554
555
556 /********************************************************************
557  * AnalysisDataStorageFrame implementation
558  */
559
560 AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
561         AnalysisDataStorageImpl *storageImpl,
562         int                      index)
563     : storageImpl_(*storageImpl), header_(index, 0.0, 0.0), status_(eMissing)
564 {
565     GMX_RELEASE_ASSERT(storageImpl->data_ != nullptr,
566                        "Storage frame constructed before data started");
567     // With non-multipoint data, the point set structure is static,
568     // so initialize it only once here.
569     if (!baseData().isMultipoint())
570     {
571         int offset = 0;
572         for (int i = 0; i < baseData().dataSetCount(); ++i)
573         {
574             int columnCount = baseData().columnCount(i);
575             pointSets_.emplace_back(offset, columnCount, i, 0);
576             offset += columnCount;
577         }
578     }
579 }
580
581
582 void
583 AnalysisDataStorageFrameData::clearFrame(int newIndex)
584 {
585     GMX_RELEASE_ASSERT(!builder_, "Should not clear an in-progress frame");
586     status_ = eMissing;
587     header_ = AnalysisDataFrameHeader(newIndex, 0.0, 0.0);
588     values_.clear();
589     if (baseData().isMultipoint())
590     {
591         pointSets_.clear();
592     }
593 }
594
595
596 void
597 AnalysisDataStorageFrameData::startFrame(
598         const AnalysisDataFrameHeader   &header,
599         AnalysisDataFrameBuilderPointer  builder)
600 {
601     status_         = eStarted;
602     header_         = header;
603     builder_        = std::move(builder);
604     builder_->data_ = this;
605     builder_->selectDataSet(0);
606 }
607
608
609 void
610 AnalysisDataStorageFrameData::addPointSet(int dataSetIndex, int firstColumn,
611                                           ArrayRef<const AnalysisDataValue> v)
612 {
613     const int                valueCount = v.size();
614     AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
615                                           dataSetIndex, firstColumn);
616     AnalysisDataPointSetRef  pointSet(header(), pointSetInfo, v);
617     storageImpl().modules_->notifyParallelPointsAdd(pointSet);
618     if (storageImpl().shouldNotifyImmediately())
619     {
620         storageImpl().modules_->notifyPointsAdd(pointSet);
621     }
622     else if (storageImpl().needStorage())
623     {
624         pointSets_.emplace_back(values_.size(), valueCount,
625                                 dataSetIndex, firstColumn);
626         std::copy(v.begin(), v.end(), std::back_inserter(values_));
627     }
628 }
629
630
631 AnalysisDataFrameBuilderPointer
632 AnalysisDataStorageFrameData::finishFrame(bool bMultipoint)
633 {
634     status_ = eFinished;
635     if (!bMultipoint)
636     {
637         GMX_RELEASE_ASSERT(static_cast<int>(pointSets_.size()) == baseData().dataSetCount(),
638                            "Point sets created for non-multipoint data");
639         values_ = builder_->values_;
640         builder_->clearValues();
641         for (int i = 0; i < pointSetCount(); ++i)
642         {
643             storageImpl().modules_->notifyParallelPointsAdd(pointSet(i));
644         }
645     }
646     else
647     {
648         GMX_RELEASE_ASSERT(!builder_->bPointSetInProgress_,
649                            "Unfinished point set");
650     }
651     AnalysisDataFrameBuilderPointer builder(std::move(builder_));
652     builder_.reset();
653     return builder;
654 }
655
656
657 AnalysisDataPointSetRef
658 AnalysisDataStorageFrameData::pointSet(int index) const
659 {
660     GMX_ASSERT(index >= 0 && index < pointSetCount(),
661                "Invalid point set index");
662     return AnalysisDataPointSetRef(
663             header_, pointSets_[index], values_);
664 }
665
666 }   // namespace internal
667
668 /********************************************************************
669  * AnalysisDataStorageFrame
670  */
671
672 AnalysisDataStorageFrame::AnalysisDataStorageFrame(
673         const AbstractAnalysisData &data)
674     : data_(nullptr), currentDataSet_(0), currentOffset_(0),
675       columnCount_(data.columnCount(0)), bPointSetInProgress_(false)
676 {
677     int totalColumnCount = 0;
678     for (int i = 0; i < data.dataSetCount(); ++i)
679     {
680         totalColumnCount += data.columnCount(i);
681     }
682     values_.resize(totalColumnCount);
683 }
684
685
686 AnalysisDataStorageFrame::~AnalysisDataStorageFrame()
687 {
688 }
689
690
691 void
692 AnalysisDataStorageFrame::clearValues()
693 {
694     if (bPointSetInProgress_)
695     {
696         std::vector<AnalysisDataValue>::iterator i;
697         for (i = values_.begin(); i != values_.end(); ++i)
698         {
699             i->clear();
700         }
701     }
702     bPointSetInProgress_ = false;
703 }
704
705
706 void
707 AnalysisDataStorageFrame::selectDataSet(int index)
708 {
709     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
710     const AbstractAnalysisData &baseData = data_->baseData();
711     GMX_RELEASE_ASSERT(index >= 0 && index < baseData.dataSetCount(),
712                        "Out of range data set index");
713     GMX_RELEASE_ASSERT(!baseData.isMultipoint() || !bPointSetInProgress_,
714                        "Point sets in multipoint data cannot span data sets");
715     currentDataSet_ = index;
716     currentOffset_  = 0;
717     // TODO: Consider precalculating.
718     for (int i = 0; i < index; ++i)
719     {
720         currentOffset_ += baseData.columnCount(i);
721     }
722     columnCount_    = baseData.columnCount(index);
723 }
724
725
726 void
727 AnalysisDataStorageFrame::finishPointSet()
728 {
729     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
730     GMX_RELEASE_ASSERT(data_->baseData().isMultipoint(),
731                        "Should not be called for non-multipoint data");
732     if (bPointSetInProgress_)
733     {
734         size_t begin       = currentOffset_;
735         size_t end         = begin + columnCount_;
736         int    firstColumn = 0;
737         while (begin != end && !values_[begin].isSet())
738         {
739             ++begin;
740             ++firstColumn;
741         }
742         while (end != begin && !values_[end-1].isSet())
743         {
744             --end;
745         }
746         if (begin == end)
747         {
748             firstColumn = 0;
749         }
750         data_->addPointSet(currentDataSet_, firstColumn,
751                            makeConstArrayRef(values_).
752                                subArray(begin, end-begin));
753     }
754     clearValues();
755 }
756
757
758 void
759 AnalysisDataStorageFrame::finishFrame()
760 {
761     GMX_RELEASE_ASSERT(data_ != nullptr, "Invalid frame accessed");
762     data_->storageImpl().finishFrame(data_->frameIndex());
763 }
764
765
766 /********************************************************************
767  * AnalysisDataStorage
768  */
769
770 AnalysisDataStorage::AnalysisDataStorage()
771     : impl_(new Impl())
772 {
773 }
774
775
776 AnalysisDataStorage::~AnalysisDataStorage()
777 {
778 }
779
780
781 int
782 AnalysisDataStorage::frameCount() const
783 {
784     return impl_->firstUnnotifiedIndex();
785 }
786
787
788 AnalysisDataFrameRef
789 AnalysisDataStorage::tryGetDataFrame(int index) const
790 {
791     int storageIndex = impl_->computeStorageLocation(index);
792     if (storageIndex == -1)
793     {
794         return AnalysisDataFrameRef();
795     }
796     const internal::AnalysisDataStorageFrameData &storedFrame
797         = *impl_->frames_[storageIndex];
798     if (!storedFrame.isAvailable())
799     {
800         return AnalysisDataFrameRef();
801     }
802     return storedFrame.frameReference();
803 }
804
805
806 bool
807 AnalysisDataStorage::requestStorage(int nframes)
808 {
809     // Handle the case when everything needs to be stored.
810     if (nframes == -1)
811     {
812         impl_->storageLimit_ = std::numeric_limits<int>::max();
813         return true;
814     }
815     // Check whether an earlier call has requested more storage.
816     if (nframes < impl_->storageLimit_)
817     {
818         return true;
819     }
820     impl_->storageLimit_ = nframes;
821     return true;
822 }
823
824
825 void
826 AnalysisDataStorage::startDataStorage(AbstractAnalysisData      *data,
827                                       AnalysisDataModuleManager *modules)
828 {
829     modules->notifyDataStart(data);
830     // Data needs to be set before calling extendBuffer()
831     impl_->data_    = data;
832     impl_->modules_ = modules;
833     if (!impl_->storeAll())
834     {
835         // 2 = pending limit (1) + 1
836         impl_->extendBuffer(impl_->storageLimit_ + 2);
837     }
838 }
839
840
841 void
842 AnalysisDataStorage::startParallelDataStorage(
843         AbstractAnalysisData              *data,
844         AnalysisDataModuleManager         *modules,
845         const AnalysisDataParallelOptions &options)
846 {
847     const int pendingLimit = options.parallelizationFactor();
848     impl_->pendingLimit_   = pendingLimit;
849     modules->notifyParallelDataStart(data, options);
850     // Data needs to be set before calling extendBuffer()
851     impl_->data_    = data;
852     impl_->modules_ = modules;
853     if (!impl_->storeAll())
854     {
855         impl_->extendBuffer(impl_->storageLimit_ + pendingLimit + 1);
856     }
857 }
858
859
860 AnalysisDataStorageFrame &
861 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
862 {
863     GMX_ASSERT(header.isValid(), "Invalid header");
864     internal::AnalysisDataStorageFrameData *storedFrame;
865     if (impl_->storeAll())
866     {
867         size_t size = header.index() + 1;
868         if (impl_->frames_.size() < size)
869         {
870             impl_->extendBuffer(size);
871         }
872         storedFrame = impl_->frames_[header.index()].get();
873     }
874     else
875     {
876         int storageIndex = impl_->computeStorageLocation(header.index());
877         if (storageIndex == -1)
878         {
879             GMX_THROW(APIError("Out of bounds frame index"));
880         }
881         storedFrame = impl_->frames_[storageIndex].get();
882     }
883     GMX_RELEASE_ASSERT(!storedFrame->isStarted(),
884                        "startFrame() called twice for the same frame");
885     GMX_RELEASE_ASSERT(storedFrame->frameIndex() == header.index(),
886                        "Inconsistent internal frame indexing");
887     storedFrame->startFrame(header, impl_->getFrameBuilder());
888     impl_->modules_->notifyParallelFrameStart(header);
889     if (impl_->shouldNotifyImmediately())
890     {
891         impl_->modules_->notifyFrameStart(header);
892     }
893     return storedFrame->builder();
894 }
895
896
897 AnalysisDataStorageFrame &
898 AnalysisDataStorage::startFrame(int index, real x, real dx)
899 {
900     return startFrame(AnalysisDataFrameHeader(index, x, dx));
901 }
902
903
904 AnalysisDataStorageFrame &
905 AnalysisDataStorage::currentFrame(int index)
906 {
907     const int storageIndex = impl_->computeStorageLocation(index);
908     GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
909
910     internal::AnalysisDataStorageFrameData &storedFrame = *impl_->frames_[storageIndex];
911     GMX_RELEASE_ASSERT(storedFrame.isStarted(),
912                        "currentFrame() called for frame before startFrame()");
913     GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
914                        "currentFrame() called for frame after finishFrame()");
915     GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
916                        "Inconsistent internal frame indexing");
917     return storedFrame.builder();
918 }
919
920
921 void
922 AnalysisDataStorage::finishFrame(int index)
923 {
924     impl_->finishFrame(index);
925 }
926
927 void
928 AnalysisDataStorage::finishFrameSerial(int index)
929 {
930     if (impl_->pendingLimit_ > 1)
931     {
932         impl_->finishFrameSerial(index);
933     }
934 }
935
936 void
937 AnalysisDataStorage::finishDataStorage()
938 {
939     // TODO: Check that all frames have been finished etc.
940     impl_->builders_.clear();
941     impl_->modules_->notifyDataFinish();
942 }
943
944 } // namespace gmx