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