2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2012,2013, by the GROMACS development team, led by
5 * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6 * others, as listed in the AUTHORS file in the top-level source
7 * directory and at http://www.gromacs.org.
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.
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.
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.
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.
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.
37 * Implements classes in datastorage.h and paralleloptions.h.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_analysisdata
42 #include "datastorage.h"
47 #include "gromacs/analysisdata/abstractdata.h"
48 #include "gromacs/analysisdata/dataframe.h"
49 #include "gromacs/analysisdata/paralleloptions.h"
50 #include "gromacs/utility/exceptions.h"
51 #include "gromacs/utility/gmxassert.h"
52 #include "gromacs/utility/uniqueptr.h"
57 /********************************************************************
58 * AnalysisDataParallelOptions
61 AnalysisDataParallelOptions::AnalysisDataParallelOptions()
62 : parallelizationFactor_(1)
67 AnalysisDataParallelOptions::AnalysisDataParallelOptions(int parallelizationFactor)
68 : parallelizationFactor_(parallelizationFactor)
70 GMX_RELEASE_ASSERT(parallelizationFactor >= 1,
71 "Invalid parallelization factor");
75 /********************************************************************
76 * AnalysisDataStorage::Impl declaration
81 //! Smart pointer type for managing a storage frame builder.
82 typedef gmx_unique_ptr<AnalysisDataStorageFrame>::type
83 AnalysisDataFrameBuilderPointer;
84 } // namespace internal
87 * Private implementation class for AnalysisDataStorage.
89 * \ingroup module_analysisdata
91 class AnalysisDataStorage::Impl
94 //! Short-hand for the internal frame data type.
95 typedef internal::AnalysisDataStorageFrameData FrameData;
96 //! Smart pointer type for managing a stored frame.
97 typedef gmx_unique_ptr<FrameData>::type FramePointer;
98 //! Short-hand for a smart pointer type to a storage frame builder.
99 typedef internal::AnalysisDataFrameBuilderPointer FrameBuilderPointer;
101 //! Shorthand for a list of data frames that are currently stored.
102 typedef std::vector<FramePointer> FrameList;
103 //! Shorthand for a list of currently unused storage frame builders.
104 typedef std::vector<FrameBuilderPointer> FrameBuilderList;
108 //! Returns the number of columns in the attached data.
109 int columnCount() const;
110 //! Returns whether the storage is set to use multipoint data.
111 bool isMultipoint() const;
113 * Whether storage of all frames has been requested.
115 * Storage of all frames also works as expected if \a storageLimit_ is
116 * used in comparisons directly, but this method should be used to
117 * check how to manage \a frames_.
119 bool storeAll() const
121 return storageLimit_ == std::numeric_limits<int>::max();
123 //! Returns the index of the oldest frame that may be currently stored.
124 int firstStoredIndex() const;
126 * Computes index into \a frames_ for accessing frame \p index.
128 * \param[in] index Zero-based frame index.
129 * \retval -1 if \p index is not available in \a frames_.
133 int computeStorageLocation(int index) const;
136 * Computes an index into \a frames_ that is one past the last frame
141 size_t endStorageLocation() const;
144 * Extends \a frames_ to a new size.
146 * \throws std::bad_alloc if out of memory.
148 void extendBuffer(size_t newSize);
150 * Remove oldest frame from the storage to make space for a new one.
152 * Increments \a firstFrameLocation_ and reinitializes the frame that
153 * was made unavailable by this operation.
162 * Returns a frame builder object for use with a new frame.
164 * \throws std::bad_alloc if out of memory.
166 FrameBuilderPointer getFrameBuilder();
169 * Calls notification method in \a data_.
171 * \throws unspecified Any exception thrown by
172 * AbstractAnalysisData::notifyPointsAdd().
174 void notifyPointSet(const AnalysisDataPointSetRef &points);
176 * Calls notification methods for new frames.
178 * \param[in] firstLocation First frame to consider.
179 * \throws unspecified Any exception thrown by frame notification
180 * methods in AbstractAnalysisData.
182 * Notifies \a data_ of new frames (from \p firstLocation and after
183 * that) if all previous frames have already been notified.
184 * Also rotates the \a frames_ buffer as necessary.
186 void notifyNextFrames(size_t firstLocation);
187 //! Implementation for AnalysisDataStorage::finishFrame().
188 void finishFrame(int index);
191 //! Data object to use for notification calls.
192 AbstractAnalysisData *data_;
194 * Whether the storage has been set to allow multipoint.
196 * Should be possible to remove once full support for multipoint data
197 * has been implemented; isMultipoint() can simply return
198 * \c data_->isMultipoint() in that case.
202 * Number of past frames that need to be stored.
204 * Always non-negative. If storage of all frames has been requested,
205 * this is set to a large number.
209 * Number of future frames that may need to be started.
211 * Should always be at least one.
213 * \see AnalysisDataStorage::startFrame()
217 * Data frames that are currently stored.
219 * If storage of all frames has been requested, this is simply a vector
220 * of frames up to the latest frame that has been started.
221 * In this case, \a firstFrameLocation_ is always zero.
223 * If storage of all frames is not requested, this is a ring buffer of
224 * frames of size \c n=storageLimit_+pendingLimit_+1. If a frame with
225 * index \c index is currently stored, its location is
226 * \c index%frames_.size().
227 * When at most \a storageLimit_ first frames have been finished,
228 * this contains storage for the first \c n-1 frames.
229 * When more than \a storageLimit_ first frames have been finished,
230 * the oldest stored frame is stored in the location
231 * \a firstFrameLocation_, and \a storageLimit_ frames starting from
232 * this location are the last finished frames. \a pendingLimit_ frames
233 * follow, and some of these may be in progress or finished.
234 * There is always one unused frame in the buffer, which is initialized
235 * such that when \a firstFrameLocation_ is incremented, it becomes
236 * valid. This makes it easier to rotate the buffer in concurrent
237 * access scenarions (which are not yet otherwise implemented).
240 //! Location of oldest frame in \a frames_.
241 size_t firstFrameLocation_;
243 * Currently unused frame builders.
245 * The builders are cached to avoid repeatedly allocating memory for
246 * them. Typically, there are as many builders as there are concurrent
247 * users of the storage object. Whenever a frame is started, a builder
248 * is pulled from this pool by getFrameBuilder() (a new one is created
249 * if none are available), and assigned for that frame. When that
250 * frame is finished, the builder is returned to this pool.
252 FrameBuilderList builders_;
254 * Index of next frame that will be added to \a frames_.
256 * If all frames are not stored, this will be the index of the unused
257 * frame (see \a frames_).
262 /********************************************************************
263 * AnalysisDataStorageFrameImpl declaration
270 * Internal representation for a single stored frame.
272 * It is implemented such that the frame header is always valid, i.e.,
273 * header().isValid() returns always true.
275 * Methods in this class do not throw unless otherwise indicated.
277 * \ingroup module_analysisdata
279 class AnalysisDataStorageFrameData
282 //! Shorthand for a iterator into storage value containers.
283 typedef std::vector<AnalysisDataValue>::const_iterator ValueIterator;
285 //! Indicates what operations have been performed on a frame.
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.
295 * Create a new storage frame.
297 * \param storageImpl Storage object this frame belongs to.
298 * \param[in] index Zero-based index for the frame.
300 AnalysisDataStorageFrameData(AnalysisDataStorage::Impl *storageImpl,
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; }
312 //! Marks the frame as notified.
313 void markNotified() { status_ = eNotified; }
315 //! Returns the storage implementation object.
316 AnalysisDataStorage::Impl &storageImpl() const { return storageImpl_; }
317 //! Returns the underlying data object (for data dimensionalities etc.).
318 const AbstractAnalysisData &baseData() const { return *storageImpl().data_; }
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(); }
325 //! Clears the frame for reusing as a new frame.
326 void clearFrame(int newIndex);
328 * Initializes the frame during AnalysisDataStorage::startFrame().
330 * \param[in] header Header to use for the new frame.
331 * \param[in] builder Builder object to use.
333 void startFrame(const AnalysisDataFrameHeader &header,
334 AnalysisDataFrameBuilderPointer builder);
335 //! Returns the builder for this frame.
336 AnalysisDataStorageFrame &builder() const
338 GMX_ASSERT(builder_, "Accessing builder for not-in-progress frame");
342 * Finalizes the frame during AnalysisDataStorage::finishFrame().
344 * \returns The builder object used by the frame, for reusing it for
347 AnalysisDataFrameBuilderPointer finishFrame();
349 //! Returns frame reference to this frame.
350 AnalysisDataFrameRef frameReference() const
352 return AnalysisDataFrameRef(header_, values_);
354 //! Returns point set reference to currently set values.
355 AnalysisDataPointSetRef currentPoints() const;
358 //! Storage object that contains this frame.
359 AnalysisDataStorage::Impl &storageImpl_;
360 //! Header for the frame.
361 AnalysisDataFrameHeader header_;
362 //! Values for the frame.
363 std::vector<AnalysisDataValue> values_;
365 * Builder object for the frame.
367 * Non-NULL when the frame is in progress, i.e., has been started but
370 AnalysisDataFrameBuilderPointer builder_;
371 //! In what state the frame currently is.
374 GMX_DISALLOW_COPY_AND_ASSIGN(AnalysisDataStorageFrameData);
376 friend class gmx::AnalysisDataStorageFrame;
379 } // namespace internal
381 /********************************************************************
382 * AnalysisDataStorage::Impl implementation
385 AnalysisDataStorage::Impl::Impl()
386 : data_(NULL), bMultipoint_(false),
387 storageLimit_(0), pendingLimit_(1), firstFrameLocation_(0), nextIndex_(0)
393 AnalysisDataStorage::Impl::columnCount() const
395 GMX_ASSERT(data_ != NULL, "columnCount() called too early");
396 return data_->columnCount();
401 AnalysisDataStorage::Impl::isMultipoint() const
408 AnalysisDataStorage::Impl::firstStoredIndex() const
410 return frames_[firstFrameLocation_]->frameIndex();
415 AnalysisDataStorage::Impl::computeStorageLocation(int index) const
417 if (index < firstStoredIndex() || index >= nextIndex_)
421 return index % frames_.size();
426 AnalysisDataStorage::Impl::endStorageLocation() const
430 return frames_.size();
432 if (frames_[0]->frameIndex() == 0 || firstFrameLocation_ == 0)
434 return frames_.size() - 1;
436 return firstFrameLocation_ - 1;
441 AnalysisDataStorage::Impl::extendBuffer(size_t newSize)
443 frames_.reserve(newSize);
444 while (frames_.size() < newSize)
446 frames_.push_back(FramePointer(new FrameData(this, nextIndex_)));
449 // The unused frame should not be included in the count.
458 AnalysisDataStorage::Impl::rotateBuffer()
460 GMX_ASSERT(!storeAll(),
461 "No need to rotate internal buffer if everything is stored");
462 size_t prevFirst = firstFrameLocation_;
463 size_t nextFirst = prevFirst + 1;
464 if (nextFirst == frames_.size())
468 firstFrameLocation_ = nextFirst;
469 frames_[prevFirst]->clearFrame(nextIndex_ + 1);
474 internal::AnalysisDataFrameBuilderPointer
475 AnalysisDataStorage::Impl::getFrameBuilder()
477 if (builders_.empty())
479 return FrameBuilderPointer(new AnalysisDataStorageFrame(columnCount()));
481 FrameBuilderPointer builder(move(builders_.back()));
482 builders_.pop_back();
483 return move(builder);
488 AnalysisDataStorage::Impl::notifyPointSet(const AnalysisDataPointSetRef &points)
490 data_->notifyPointsAdd(points);
495 AnalysisDataStorage::Impl::notifyNextFrames(size_t firstLocation)
497 if (firstLocation != firstFrameLocation_)
499 // firstLocation can only be zero here if !storeAll() because
500 // firstFrameLocation_ is always zero for storeAll()
502 (firstLocation == 0 ? frames_.size() - 1 : firstLocation - 1);
503 if (!frames_[prevIndex]->isNotified())
508 size_t i = firstLocation;
509 size_t end = endStorageLocation();
512 Impl::FrameData &storedFrame = *frames_[i];
513 if (!storedFrame.isFinished())
517 if (!storedFrame.isNotified())
519 data_->notifyFrameStart(storedFrame.header());
520 data_->notifyPointsAdd(storedFrame.currentPoints());
521 data_->notifyFrameFinish(storedFrame.header());
522 storedFrame.markNotified();
523 if (storedFrame.frameIndex() >= storageLimit_)
529 if (!storeAll() && i >= frames_.size())
538 AnalysisDataStorage::Impl::finishFrame(int index)
540 int storageIndex = computeStorageLocation(index);
541 GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
542 Impl::FrameData &storedFrame = *frames_[storageIndex];
543 GMX_RELEASE_ASSERT(storedFrame.isStarted(),
544 "finishFrame() called for frame before startFrame()");
545 GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
546 "finishFrame() called twice for the same frame");
547 GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
548 "Inconsistent internal frame indexing");
549 builders_.push_back(storedFrame.finishFrame());
552 // TODO: Check that the last point set has been finished
553 data_->notifyFrameFinish(storedFrame.header());
554 if (storedFrame.frameIndex() >= storageLimit_)
561 notifyNextFrames(storageIndex);
566 /********************************************************************
567 * AnalysisDataStorageFrame implementation
573 AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
574 AnalysisDataStorage::Impl *storageImpl,
576 : storageImpl_(*storageImpl), header_(index, 0.0, 0.0), status_(eMissing)
582 AnalysisDataStorageFrameData::clearFrame(int newIndex)
584 GMX_RELEASE_ASSERT(!builder_, "Should not clear an in-progress frame");
586 header_ = AnalysisDataFrameHeader(newIndex, 0.0, 0.0);
592 AnalysisDataStorageFrameData::startFrame(
593 const AnalysisDataFrameHeader &header,
594 AnalysisDataFrameBuilderPointer builder)
598 builder_ = move(builder);
599 builder_->data_ = this;
603 AnalysisDataFrameBuilderPointer
604 AnalysisDataStorageFrameData::finishFrame()
607 values_ = builder_->values_;
608 builder_->clearValues();
609 AnalysisDataFrameBuilderPointer builder(move(builder_));
611 return move(builder);
615 AnalysisDataPointSetRef
616 AnalysisDataStorageFrameData::currentPoints() const
618 std::vector<AnalysisDataValue>::const_iterator begin = values_.begin();
619 std::vector<AnalysisDataValue>::const_iterator end = values_.end();
620 while (begin != end && !begin->isSet())
624 while (end != begin && !(end-1)->isSet())
628 int firstColumn = (begin != end) ? begin - values_.begin() : 0;
629 return AnalysisDataPointSetRef(header_, firstColumn,
630 AnalysisDataValuesRef(begin, end));
633 } // namespace internal
635 /********************************************************************
636 * AnalysisDataStorageFrame
639 AnalysisDataStorageFrame::AnalysisDataStorageFrame(int columnCount)
640 : data_(NULL), values_(columnCount)
645 AnalysisDataStorageFrame::~AnalysisDataStorageFrame()
651 AnalysisDataStorageFrame::clearValues()
653 std::vector<AnalysisDataValue>::iterator i;
654 for (i = values_.begin(); i != values_.end(); ++i)
662 AnalysisDataStorageFrame::finishPointSet()
664 GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
665 GMX_RELEASE_ASSERT(data_->baseData().isMultipoint(),
666 "Should not be called for non-multipoint data");
667 data_->values_ = values_;
668 data_->storageImpl().notifyPointSet(data_->currentPoints());
674 AnalysisDataStorageFrame::finishFrame()
676 GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
677 data_->storageImpl().finishFrame(data_->frameIndex());
681 /********************************************************************
682 * AnalysisDataStorage
685 AnalysisDataStorage::AnalysisDataStorage()
691 AnalysisDataStorage::~AnalysisDataStorage()
697 AnalysisDataStorage::setMultipoint(bool bMultipoint)
699 if (bMultipoint && impl_->storageLimit_ > 0)
701 GMX_THROW(APIError("Storage of multipoint data not supported"));
703 impl_->bMultipoint_ = bMultipoint;
708 AnalysisDataStorage::setParallelOptions(const AnalysisDataParallelOptions &opt)
710 impl_->pendingLimit_ = 2 * opt.parallelizationFactor() - 1;
715 AnalysisDataStorage::tryGetDataFrame(int index) const
717 if (impl_->isMultipoint())
719 return AnalysisDataFrameRef();
721 int storageIndex = impl_->computeStorageLocation(index);
722 if (storageIndex == -1)
724 return AnalysisDataFrameRef();
726 const Impl::FrameData &storedFrame = *impl_->frames_[storageIndex];
727 if (!storedFrame.isAvailable())
729 return AnalysisDataFrameRef();
731 return storedFrame.frameReference();
736 AnalysisDataStorage::requestStorage(int nframes)
738 if (impl_->isMultipoint())
743 // Handle the case when everything needs to be stored.
746 impl_->storageLimit_ = std::numeric_limits<int>::max();
749 // Check whether an earlier call has requested more storage.
750 if (nframes < impl_->storageLimit_)
754 impl_->storageLimit_ = nframes;
760 AnalysisDataStorage::startDataStorage(AbstractAnalysisData *data)
762 // Data needs to be set before calling extendBuffer()
764 setMultipoint(data->isMultipoint());
765 if (!impl_->storeAll())
767 impl_->extendBuffer(impl_->storageLimit_ + impl_->pendingLimit_ + 1);
772 AnalysisDataStorageFrame &
773 AnalysisDataStorage::startFrame(const AnalysisDataFrameHeader &header)
775 GMX_ASSERT(header.isValid(), "Invalid header");
776 Impl::FrameData *storedFrame;
777 if (impl_->storeAll())
779 size_t size = header.index() + 1;
780 if (impl_->frames_.size() < size)
782 impl_->extendBuffer(size);
784 storedFrame = impl_->frames_[header.index()].get();
788 int storageIndex = impl_->computeStorageLocation(header.index());
789 if (storageIndex == -1)
791 GMX_THROW(APIError("Out of bounds frame index"));
793 storedFrame = impl_->frames_[storageIndex].get();
795 GMX_RELEASE_ASSERT(!storedFrame->isStarted(),
796 "startFrame() called twice for the same frame");
797 GMX_RELEASE_ASSERT(storedFrame->frameIndex() == header.index(),
798 "Inconsistent internal frame indexing");
799 storedFrame->startFrame(header, impl_->getFrameBuilder());
800 if (impl_->isMultipoint())
802 impl_->data_->notifyFrameStart(header);
804 return storedFrame->builder();
808 AnalysisDataStorageFrame &
809 AnalysisDataStorage::startFrame(int index, real x, real dx)
811 return startFrame(AnalysisDataFrameHeader(index, x, dx));
815 AnalysisDataStorageFrame &
816 AnalysisDataStorage::currentFrame(int index)
818 int storageIndex = impl_->computeStorageLocation(index);
819 GMX_RELEASE_ASSERT(storageIndex >= 0, "Out of bounds frame index");
820 Impl::FrameData &storedFrame = *impl_->frames_[storageIndex];
821 GMX_RELEASE_ASSERT(storedFrame.isStarted(),
822 "currentFrame() called for frame before startFrame()");
823 GMX_RELEASE_ASSERT(!storedFrame.isFinished(),
824 "currentFrame() called for frame after finishFrame()");
825 GMX_RELEASE_ASSERT(storedFrame.frameIndex() == index,
826 "Inconsistent internal frame indexing");
827 return storedFrame.builder();
832 AnalysisDataStorage::finishFrame(int index)
834 impl_->finishFrame(index);