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