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