Base support for multiple analysisdata data sets.
authorTeemu Murtola <teemu.murtola@gmail.com>
Fri, 7 Jun 2013 11:17:52 +0000 (14:17 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Sun, 11 Aug 2013 10:54:14 +0000 (12:54 +0200)
Make it possible for an AbstractAnalysisData object to contain multiple
data sets.  See #1010 for the rationale for implementing this.

The main changes are:
- Support for specifying the number of data sets and the number of
  columns for each data set in AbstractAnalysisData and classes deriving
  from it.  At the same time, improved checking the constraints for
  analysis data modules, so that the restrictions for calling the
  various set*() methods are now more uniform.
- Modeling multiple data sets in the dataframe.h interfaces: now the
  point set has an additional attribute, specifying the data set.
  Data with multiple data sets now always has multiple point sets.
  There is exactly one per data set if it is not multipoint.
- Support for storage in AnalysisDataStorage. This is straighforward
  extension of multipoint storage.
- Support for creating the data sets in AnalysisDataStorage and
  AnalysisData.
- Adjust unit test framework to support at least basic testing of
  multiple data sets.

Will implement support for multiple data sets in the modules, including
the angle trajectory analysis module, in a separate change to keep the
amount of simultaneous changes limited.

Part of #1010.

Change-Id: Ieb373638d25ecd885d563c467bff893dd47b23b5

27 files changed:
share/template/template.cpp
src/gromacs/analysisdata/abstractdata.cpp
src/gromacs/analysisdata/abstractdata.h
src/gromacs/analysisdata/analysisdata.cpp
src/gromacs/analysisdata/analysisdata.h
src/gromacs/analysisdata/arraydata.cpp
src/gromacs/analysisdata/arraydata.h
src/gromacs/analysisdata/dataframe.cpp
src/gromacs/analysisdata/dataframe.h
src/gromacs/analysisdata/datamodule.h
src/gromacs/analysisdata/dataproxy.cpp
src/gromacs/analysisdata/datastorage.cpp
src/gromacs/analysisdata/datastorage.h
src/gromacs/analysisdata/modules/average.cpp
src/gromacs/analysisdata/modules/displacement.cpp
src/gromacs/analysisdata/modules/histogram.cpp
src/gromacs/analysisdata/tests/analysisdata.cpp
src/gromacs/analysisdata/tests/arraydata.cpp
src/gromacs/analysisdata/tests/average.cpp
src/gromacs/analysisdata/tests/histogram.cpp
src/gromacs/trajectoryanalysis/modules/angle.cpp
src/gromacs/trajectoryanalysis/modules/distance.cpp
src/gromacs/trajectoryanalysis/modules/freevolume.cpp
src/gromacs/trajectoryanalysis/modules/select.cpp
src/testutils/datatest.cpp
src/testutils/datatest.h
src/testutils/mock_datamodule.cpp

index 7d0a833ab1f81b80fb8045a8fca8d0c703a388b4..28bbd6ee88d6b64d0d430d09763debee18b51d09 100644 (file)
@@ -128,7 +128,7 @@ AnalysisTemplate::initAnalysis(const TrajectoryAnalysisSettings &settings,
 {
     nb_.setCutoff(cutoff_);
 
-    data_.setColumnCount(sel_.size());
+    data_.setColumnCount(0, sel_.size());
 
     avem_.reset(new AnalysisDataAverageModule());
     data_.addModule(avem_);
index 2ef54e21f33423ee7629c41be2f8d4eb4c21db2e..301665f0a18fd44db3b1446d312f4077752665b2 100644 (file)
@@ -71,13 +71,26 @@ class AbstractAnalysisData::Impl
 
         Impl();
 
+        //! Returns whether any data set has more than one column.
+        bool isMultiColumn() const;
+
+        /*! \brief
+         * Checks whether a module is compatible with the data properties.
+         *
+         * \param[in] module Module to check.
+         * \throws    APIError if \p module is not compatible with the data.
+         *
+         * Does not check the actual data (e.g., missing values), but only the
+         * dimensionality and other preset properties of the data.
+         */
+        void checkModuleProperties(const AnalysisDataModuleInterface &module) const;
+
         /*! \brief
          * Present data already added to the data object to a module.
          *
          * \param[in] data   Data object to read data from.
          * \param[in] module Module to present the data to.
-         * \throws    APIError if \p module is not compatible with the data
-         *      object.
+         * \throws    APIError if \p module is not compatible with the data.
          * \throws    APIError if all data is not available through
          *      getDataFrame().
          * \throws    unspecified Any exception thrown by \p module in its data
@@ -90,6 +103,10 @@ class AbstractAnalysisData::Impl
         void presentData(AbstractAnalysisData        *data,
                          AnalysisDataModuleInterface *module);
 
+        //! Column counts for each data set in the data.
+        std::vector<int>        columnCounts_;
+        //! Whether the data is multipoint.
+        bool                    bMultipoint_;
         //! List of modules added to the data.
         ModuleList              modules_;
         //! true if all modules support missing data.
@@ -111,10 +128,45 @@ class AbstractAnalysisData::Impl
 };
 
 AbstractAnalysisData::Impl::Impl()
-    : bAllowMissing_(true), bDataStart_(false), bInData_(false), bInFrame_(false),
+    : bMultipoint_(false), bAllowMissing_(true),
+      bDataStart_(false), bInData_(false), bInFrame_(false),
       currIndex_(-1), nframes_(0)
 {
+    columnCounts_.push_back(0);
+}
+
+bool
+AbstractAnalysisData::Impl::isMultiColumn() const
+{
+    std::vector<int>::const_iterator i;
+    for (i = columnCounts_.begin(); i != columnCounts_.end(); ++i)
+    {
+        if (*i > 1)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+//! Helper macro for testing module flags.
+#define TEST_MODULE_FLAG(flags, flagname) \
+    ((flags) & AnalysisDataModuleInterface::flagname)
+void
+AbstractAnalysisData::Impl::checkModuleProperties(
+        const AnalysisDataModuleInterface &module) const
+{
+    const int flags = module.flags();
+    if ((!TEST_MODULE_FLAG(flags, efAllowMulticolumn) && isMultiColumn()) ||
+        (!TEST_MODULE_FLAG(flags, efAllowMultipoint)  && bMultipoint_) ||
+        ( TEST_MODULE_FLAG(flags, efOnlyMultipoint)   && !bMultipoint_) ||
+        (!TEST_MODULE_FLAG(flags, efAllowMultipleDataSets)
+         && columnCounts_.size() > 1U))
+    {
+        GMX_THROW(APIError("Data module not compatible with data object properties"));
+    }
 }
+#undef TEST_MODULE_FLAGS
 
 void
 AbstractAnalysisData::Impl::presentData(AbstractAnalysisData        *data,
@@ -152,7 +204,7 @@ AbstractAnalysisData::Impl::presentData(AbstractAnalysisData        *data,
  */
 /*! \cond libapi */
 AbstractAnalysisData::AbstractAnalysisData()
-    : impl_(new Impl()), columnCount_(0), bMultiPoint_(false)
+    : impl_(new Impl())
 {
 }
 //! \endcond
@@ -161,6 +213,33 @@ AbstractAnalysisData::~AbstractAnalysisData()
 {
 }
 
+bool
+AbstractAnalysisData::isMultipoint() const
+{
+    return impl_->bMultipoint_;
+}
+
+int
+AbstractAnalysisData::dataSetCount() const
+{
+    return impl_->columnCounts_.size();
+}
+
+int
+AbstractAnalysisData::columnCount(int dataSet) const
+{
+    GMX_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
+               "Out of range data set index");
+    return impl_->columnCounts_[dataSet];
+}
+
+int
+AbstractAnalysisData::columnCount() const
+{
+    GMX_ASSERT(dataSetCount() == 1,
+               "Convenience method not available for multiple data sets");
+    return columnCount(0);
+}
 
 int
 AbstractAnalysisData::frameCount() const
@@ -207,12 +286,7 @@ AbstractAnalysisData::requestStorage(int nframes)
 void
 AbstractAnalysisData::addModule(AnalysisDataModulePointer module)
 {
-    if ((columnCount() > 1 && !(module->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
-        || (isMultipoint() && !(module->flags() & AnalysisDataModuleInterface::efAllowMultipoint))
-        || (!isMultipoint() && (module->flags() & AnalysisDataModuleInterface::efOnlyMultipoint)))
-    {
-        GMX_THROW(APIError("Data module not compatible with data object properties"));
-    }
+    impl_->checkModuleProperties(*module);
 
     if (impl_->bDataStart_)
     {
@@ -232,7 +306,7 @@ void
 AbstractAnalysisData::addColumnModule(int col, int span,
                                       AnalysisDataModulePointer module)
 {
-    GMX_RELEASE_ASSERT(col >= 0 && span >= 1 && col + span <= columnCount_,
+    GMX_RELEASE_ASSERT(col >= 0 && span >= 1,
                        "Invalid columns specified for a column module");
     if (impl_->bDataStart_)
     {
@@ -249,12 +323,7 @@ AbstractAnalysisData::addColumnModule(int col, int span,
 void
 AbstractAnalysisData::applyModule(AnalysisDataModuleInterface *module)
 {
-    if ((columnCount() > 1 && !(module->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
-        || (isMultipoint() && !(module->flags() & AnalysisDataModuleInterface::efAllowMultipoint))
-        || (!isMultipoint() && (module->flags() & AnalysisDataModuleInterface::efOnlyMultipoint)))
-    {
-        GMX_THROW(APIError("Data module not compatible with data object properties"));
-    }
+    impl_->checkModuleProperties(*module);
     GMX_RELEASE_ASSERT(impl_->bDataStart_ && !impl_->bInData_,
                        "Data module can only be applied to ready data");
 
@@ -263,25 +332,31 @@ AbstractAnalysisData::applyModule(AnalysisDataModuleInterface *module)
 
 /*! \cond libapi */
 void
-AbstractAnalysisData::setColumnCount(int columnCount)
+AbstractAnalysisData::setDataSetCount(int dataSetCount)
 {
+    GMX_RELEASE_ASSERT(dataSetCount > 0, "Invalid data column count");
+    GMX_RELEASE_ASSERT(!impl_->bDataStart_,
+                       "Data set count cannot be changed after data has been added");
+    impl_->columnCounts_.resize(dataSetCount);
+}
+
+void
+AbstractAnalysisData::setColumnCount(int dataSet, int columnCount)
+{
+    GMX_RELEASE_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
+                       "Out of range data set index");
     GMX_RELEASE_ASSERT(columnCount > 0, "Invalid data column count");
-    GMX_RELEASE_ASSERT(columnCount_ == 0 || impl_->modules_.empty(),
-                       "Data column count cannot be changed after modules are added");
     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
                        "Data column count cannot be changed after data has been added");
-    columnCount_ = columnCount;
+    impl_->columnCounts_[dataSet] = columnCount;
 }
 
-
 void
 AbstractAnalysisData::setMultipoint(bool multipoint)
 {
-    GMX_RELEASE_ASSERT(impl_->modules_.empty(),
-                       "Data type cannot be changed after modules are added");
     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
                        "Data type cannot be changed after data has been added");
-    bMultiPoint_ = multipoint;
+    impl_->bMultipoint_ = multipoint;
 }
 
 
@@ -294,16 +369,17 @@ AbstractAnalysisData::notifyDataStart()
 {
     GMX_RELEASE_ASSERT(!impl_->bDataStart_,
                        "notifyDataStart() called more than once");
-    GMX_RELEASE_ASSERT(columnCount_ > 0, "Data column count is not set");
+    for (int d = 0; d < dataSetCount(); ++d)
+    {
+        GMX_RELEASE_ASSERT(columnCount(d) > 0,
+                           "Data column count is not set");
+    }
     impl_->bDataStart_ = impl_->bInData_ = true;
 
     Impl::ModuleList::const_iterator i;
     for (i = impl_->modules_.begin(); i != impl_->modules_.end(); ++i)
     {
-        if (columnCount_ > 1 && !((*i)->flags() & AnalysisDataModuleInterface::efAllowMulticolumn))
-        {
-            GMX_THROW(APIError("Data module not compatible with data object properties"));
-        }
+        impl_->checkModuleProperties(**i);
         (*i)->dataStarted(this);
     }
 }
@@ -333,7 +409,8 @@ AbstractAnalysisData::notifyPointsAdd(const AnalysisDataPointSetRef &points) con
 {
     GMX_ASSERT(impl_->bInData_, "notifyDataStart() not called");
     GMX_ASSERT(impl_->bInFrame_, "notifyFrameStart() not called");
-    GMX_ASSERT(points.lastColumn() < columnCount(), "Invalid columns");
+    GMX_ASSERT(points.lastColumn() < columnCount(points.dataSetIndex()),
+               "Invalid columns");
     GMX_ASSERT(points.frameIndex() == impl_->currIndex_,
                "Points do not correspond to current frame");
     if (!impl_->bAllowMissing_ && !points.allPresent())
index ea0ff3041a52e228ffffb75ed06bd31ad173c5b5..3a9a8de7ecf2c9fc59527a87b7decce399a4fe63 100644 (file)
@@ -65,9 +65,9 @@ typedef boost::shared_ptr<AnalysisDataModuleInterface> AnalysisDataModulePointer
  * Abstract base class for all objects that provide data.
  *
  * The public interface includes methods for querying the data (isMultipoint(),
- * columnCount(), frameCount(), tryGetDataFrame(), getDataFrame(),
- * requestStorage()) and methods for using modules for processing the data
- * (addModule(), addColumnModule(), applyModule()).
+ * dataSetCount(), columnCount(), frameCount(), tryGetDataFrame(),
+ * getDataFrame(), requestStorage()) and methods for using modules for
+ * processing the data (addModule(), addColumnModule(), applyModule()).
  *
  * Notice that even for non-const objects, the interface does not provide any
  * means of altering the data.  It is only possible to add modules, making it
@@ -77,9 +77,10 @@ typedef boost::shared_ptr<AnalysisDataModuleInterface> AnalysisDataModulePointer
  *
  * \if libapi
  * This class also provides protected methods for use in derived classes.
- * The properties returned by isMultipoint() and columnCount() must be set using
- * setMultipoint() and setColumnCount(), and notify*() methods must be used to
- * report when data becomes available for modules to process it.
+ * The properties returned by isMultipoint(), dataSetCount(), and columnCount()
+ * must be set using setMultipoint(), setDataSetCount(), and setColumnCount(),
+ * and notify*() methods must be used to report when data becomes available for
+ * modules to process it.
  * There are also two protected pure virtual methods that need to be
  * implemented to provide access to stored data: requestStorageInternal() and
  * tryGetDataFrameInternal().
@@ -135,13 +136,31 @@ class AbstractAnalysisData
          *
          * Does not throw.
          */
-        bool isMultipoint() const { return bMultiPoint_; }
+        bool isMultipoint() const;
         /*! \brief
-         * Returns the number of columns in the data.
+         * Returns the number of data sets in the data object.
+         *
+         * \returns The number of data sets in the data.
+         *
+         * If the number is not yet known, returns 0.
+         * The returned value does not change after modules have been notified
+         * of data start, but may change multiple times before that, depending
+         * on the actual data class.
+         * \if libapi
+         * Derived classes should set the number of columns with
+         * setDataSetCount(), within the above limitations.
+         * \endif
          *
+         * Does not throw.
+         */
+        int dataSetCount() const;
+        /*! \brief
+         * Returns the number of columns in a data set.
+         *
+         * \param[in] dataSet Zero-based index of the data set to query.
          * \returns The number of columns in the data.
          *
-         * If the number of columns is yet known, returns 0.
+         * If the number of columns is not yet known, returns 0.
          * The returned value does not change after modules have been notified
          * of data start, but may change multiple times before that, depending
          * on the actual data class.
@@ -152,7 +171,20 @@ class AbstractAnalysisData
          *
          * Does not throw.
          */
-        int columnCount() const { return columnCount_; }
+        int columnCount(int dataSet) const;
+        /*! \brief
+         * Returns the number of columns in the data.
+         *
+         * \returns The number of columns in the data.
+         *
+         * This is a convenience method for data objects with a single data set.
+         * Can only be called if dataSetCount() == 1.
+         *
+         * Does not throw.
+         *
+         * \see columnCount(int)
+         */
+        int columnCount() const;
         /*! \brief
          * Returns the total number of frames in the data.
          *
@@ -239,10 +271,17 @@ class AbstractAnalysisData
          * \param     module  Module to add.
          * \throws    APIError in same situations as addModule().
          *
+         * Currently, all data sets are filtered using the same column mask.
+         *
          * \todo
          * This method doesn't currently work in all cases with multipoint
-         * data.  In particular, if the added module requests storage and uses
-         * getDataFrame(), it will behave unpredictably (most likely asserts).
+         * data or with multiple data sets.  In particular, if the added module
+         * requests storage and uses getDataFrame(), it will behave
+         * unpredictably (most likely asserts).
+         *
+         * \todo
+         * Generalize this method to multiple data sets (e.g., for adding
+         * modules that only process a single data set).
          *
          * \see addModule()
          */
@@ -277,33 +316,52 @@ class AbstractAnalysisData
         AbstractAnalysisData();
 
         /*! \brief
-         * Sets the number of columns.
+         * Sets the number of data sets.
+         *
+         * \param[in] dataSetCount  Number of data sets (must be > 0).
+         *
+         * It not called, the data object has a single data set.
+         * Can be called only before notifyDataStart().
+         * Multiple calls are allowed before that point; the last call takes
+         * effect.
+         *
+         * Does not throw, but this may change with the todo item in
+         * setColumnCount().
+         *
+         * \see dataSetCount()
+         */
+        void setDataSetCount(int dataSetCount);
+        /*! \brief
+         * Sets the number of columns for a data set.
          *
-         * \param[in] columnCount  Number of columns in the data (must be > 0).
+         * \param[in] dataSet      Zero-based index of the data set.
+         * \param[in] columnCount  Number of columns in \p dataSet (must be > 0).
          *
-         * Can be called only before notifyDataStart(), otherwise asserts.
-         * Multiple calls are only allowed if all of them occur before
-         * addModule() has been called, otherwise asserts (a single call
-         * can occur after addModule() if no calls have been made earlier).
+         * Must be called at least once before notifyDataStart() for each data
+         * set.
+         * Can be called only before notifyDataStart().
+         * Multiple calls are allowed before that point; the last call takes
+         * effect.
          *
          * Does not throw, but this may change with the below todo item.
          *
          * \todo
-         * Consider whether the semantics with respect to addModule() and
-         * notifyDataStart(), and the performed checks, are suitable for all
-         * purposes.
+         * Consider whether the call should check the modules that have already
+         * been added (currently it is only done in notifyDataStart()).
          *
          * \see columnCount()
          */
-        void setColumnCount(int columnCount);
+        void setColumnCount(int dataSet, int columnCount);
         /*! \brief
          * Sets whether the data has multiple points per column in a frame.
          *
          * \param[in] multipoint  Whether multiple points per column are
          *     possible.
          *
-         * Can be called only before addModule() or notifyDataStart(),
-         * otherwise asserts.
+         * If not called, only a single point per column is allowed.
+         * Can be called only before notifyDataStart().
+         * Multiple calls are allowed before that point; the last call takes
+         * effect.
          *
          * Does not throw, but this may change with the todo item in
          * setColumnCount().
@@ -425,8 +483,6 @@ class AbstractAnalysisData
         class Impl;
 
         PrivateImplPointer<Impl> impl_;
-        int                      columnCount_;
-        bool                     bMultiPoint_;
 
         /*! \brief
          * Needed to provide access to notification methods.
index 2ec211576956197d42a54203e8e5aacbf1c32937..105a7b59fe1cb23b992e72388145447bc8ba0bd5 100644 (file)
@@ -125,12 +125,20 @@ AnalysisData::~AnalysisData()
 
 
 void
-AnalysisData::setColumnCount(int ncol)
+AnalysisData::setDataSetCount(int dataSetCount)
 {
-    GMX_RELEASE_ASSERT(ncol > 0, "Number of columns must be positive");
     GMX_RELEASE_ASSERT(impl_->handles_.empty(),
                        "Cannot change data dimensionality after creating handles");
-    AbstractAnalysisData::setColumnCount(ncol);
+    AbstractAnalysisData::setDataSetCount(dataSetCount);
+}
+
+
+void
+AnalysisData::setColumnCount(int dataSet, int columnCount)
+{
+    GMX_RELEASE_ASSERT(impl_->handles_.empty(),
+                       "Cannot change data dimensionality after creating handles");
+    AbstractAnalysisData::setColumnCount(dataSet, columnCount);
 }
 
 
@@ -226,6 +234,16 @@ AnalysisDataHandle::startFrame(int index, real x, real dx)
 }
 
 
+void
+AnalysisDataHandle::selectDataSet(int index)
+{
+    GMX_RELEASE_ASSERT(impl_ != NULL, "Invalid data handle used");
+    GMX_RELEASE_ASSERT(impl_->currentFrame_ != NULL,
+                       "selectDataSet() called without calling startFrame()");
+    impl_->currentFrame_->selectDataSet(index);
+}
+
+
 void
 AnalysisDataHandle::setPoint(int column, real value, bool bPresent)
 {
index bb5f570e0cd878305d5289064cba18c5a2918d57..451cee4a598b74e37def4d65d150258c7ce08e1e 100644 (file)
@@ -56,8 +56,8 @@ class AnalysisDataParallelOptions;
  *
  * This is the main class used to implement parallelizable data processing in
  * analysis tools.  It is used by first creating an object and setting its
- * properties using setColumnCount() and setMultipoint(), and attaching
- * necessary modules using addModule() etc.  Then one or more
+ * properties using setDataSetCount(), setColumnCount() and setMultipoint(),
+ * and attaching necessary modules using addModule() etc.  Then one or more
  * AnalysisDataHandle objects can be created using startData().  Each data
  * handle can then be independently used to provide data frames (each frame
  * must be provided by a single handle, but different frames can be freely
@@ -96,18 +96,32 @@ class AnalysisData : public AbstractAnalysisData
         virtual ~AnalysisData();
 
         /*! \brief
-         * Sets the number of columns in the data.
+         * Sets the number of data sets.
          *
-         * \param[in] ncol  Number of columns in the data (must be > 0).
+         * \param[in] dataSetCount  Number of data sets (must be > 0).
          *
-         * Must be called before startData(), and can be called multiple times
-         * before modules are added.
          * Must not be called after startData() has been called.
+         * If not called, a single data set is assumed.
+         * If called multiple times, the last call takes effect.
          *
          * Does not currently throw, but this may change for the case that
          * modules have already been added.
          */
-        void setColumnCount(int ncol);
+        void setDataSetCount(int dataSetCount);
+        /*! \brief
+         * Sets the number of columns in a data set.
+         *
+         * \param[in] dataSet      Zero-based data set index.
+         * \param[in] columnCount  Number of columns in the data (must be > 0).
+         *
+         * Must be called before startData() for each data set.
+         * Must not be called after startData() has been called.
+         * If called multiple times for a data set, the last call takes effect.
+         *
+         * Does not currently throw, but this may change for the case that
+         * modules have already been added.
+         */
+        void setColumnCount(int dataSet, int columnCount);
         /*! \brief
          * Sets whether the data contains multiple points per column per frame.
          *
@@ -116,8 +130,7 @@ class AnalysisData : public AbstractAnalysisData
          *
          * If this method is not called, the data is not multipoint.
          *
-         * Must not be called after modules have been added or startData() has
-         * been called.
+         * Must not be called after startData() has been called.
          *
          * Does not currently throw, but this may change for the case that
          * modules have already been added.
@@ -190,15 +203,18 @@ class AnalysisDataHandleImpl;
  * called.
  *
  * For simple (non-multipoint) data, within a frame values can be set using
- * setPoint() and setPoints().  Setting the same column multiple times
- * overrides previously set values.  When the frame is finished, attached
- * modules are notified.
+ * selectDataSet(), setPoint() and setPoints().  Setting the same column in the
+ * same data set multiple times overrides previously set values.
+ * When the frame is finished, attached modules are notified.
  *
  * Multipoint data works otherwise similarly, but requires finishPointSet() to
  * be called for each set of points for which the modules need to be notified.
  * Each point set starts empty (after startFrame() or finishPointSet()), and
- * values can be set using setPoint()/setPoints().  finishPointSet() must also
- * be called for the last point set just before finishFrame().
+ * values can be set using setPoint()/setPoints().
+ * A single point set can contain values only for a single data set, which must
+ * be selected with selectDataSet() before setting any values.
+ * finishPointSet() must also be called for the last point set just before
+ * finishFrame().
  *
  * This class works like a pointer type: copying and assignment is lightweight,
  * and all copies work interchangeably, accessing the same internal handle.
@@ -244,6 +260,18 @@ class AnalysisDataHandle
          * is not finished).
          */
         void startFrame(int index, real x, real dx = 0.0);
+        /*! \brief
+         * Selects a data set for subsequent setPoint()/setPoints() calls.
+         *
+         * \param[in] index  Zero-based data set index.
+         *
+         * After startFrame(), the first data set is always selected.
+         * The set value is remembered until the end of the current frame, also
+         * across finishPointSet() calls.
+         *
+         * Does not throw.
+         */
+        void selectDataSet(int index);
         /*! \brief
          * Set a value for a single column for the current frame.
          *
index 4e5b5f3cdc6786f5af4971adb0b574c2d00d6cd6..f8eca8ccaf119288d22348792b6c868b9a20c57c 100644 (file)
@@ -51,7 +51,7 @@ namespace gmx
 {
 
 AbstractAnalysisArrayData::AbstractAnalysisArrayData()
-    : rowCount_(0), pointSetInfo_(0, 0, 0), xstart_(0.0), xstep_(1.0),
+    : rowCount_(0), pointSetInfo_(0, 0, 0, 0), xstart_(0.0), xstep_(1.0),
       bReady_(false)
 {
 }
@@ -89,8 +89,8 @@ AbstractAnalysisArrayData::setColumnCount(int ncols)
 {
     GMX_RELEASE_ASSERT(!isAllocated(),
                        "Cannot change column count after data has been allocated");
-    AbstractAnalysisData::setColumnCount(ncols);
-    pointSetInfo_ = AnalysisDataPointSetInfo(0, ncols, 0);
+    AbstractAnalysisData::setColumnCount(0, ncols);
+    pointSetInfo_ = AnalysisDataPointSetInfo(0, ncols, 0, 0);
 }
 
 
index be0a8785700bae3f25aed5ef1186c91d38c03ecb..8febbc7ee78829486c9954236cb9792aca71ba1f 100644 (file)
@@ -68,6 +68,9 @@ namespace gmx
  * \todo
  * Add methods to take full advantage of AnalysisDataValue features.
  *
+ * \todo
+ * Add support for multiple data sets.
+ *
  * \inlibraryapi
  * \ingroup module_analysisdata
  */
index 51663a71d7f61dc74eea1c6e018e88d8080d792d..1f386234750a9fcc1915c9685c65cb393ff0ab8d 100644 (file)
@@ -71,7 +71,9 @@ AnalysisDataPointSetRef::AnalysisDataPointSetRef(
         const AnalysisDataFrameHeader  &header,
         const AnalysisDataPointSetInfo &pointSetInfo,
         const AnalysisDataValuesRef    &values)
-    : header_(header), firstColumn_(pointSetInfo.firstColumn()),
+    : header_(header),
+      dataSetIndex_(pointSetInfo.dataSetIndex()),
+      firstColumn_(pointSetInfo.firstColumn()),
       values_(&*values.begin() + pointSetInfo.valueOffset(),
               pointSetInfo.valueCount())
 {
@@ -83,7 +85,8 @@ AnalysisDataPointSetRef::AnalysisDataPointSetRef(
 AnalysisDataPointSetRef::AnalysisDataPointSetRef(
         const AnalysisDataFrameHeader        &header,
         const std::vector<AnalysisDataValue> &values)
-    : header_(header), firstColumn_(0), values_(values.begin(), values.end())
+    : header_(header), dataSetIndex_(0), firstColumn_(0),
+      values_(values.begin(), values.end())
 {
     GMX_ASSERT(header_.isValid(),
                "Invalid point set reference should not be constructed");
@@ -92,7 +95,8 @@ AnalysisDataPointSetRef::AnalysisDataPointSetRef(
 
 AnalysisDataPointSetRef::AnalysisDataPointSetRef(
         const AnalysisDataPointSetRef &points, int firstColumn, int columnCount)
-    : header_(points.header()), firstColumn_(0)
+    : header_(points.header()), dataSetIndex_(points.dataSetIndex()),
+      firstColumn_(0)
 {
     GMX_ASSERT(firstColumn >= 0, "Invalid first column");
     GMX_ASSERT(columnCount >= 0, "Invalid column count");
index 1205fe98e0c00100206c41bd41c87ececa8b5e9e..a4562de9d776e781491f723ee804834482ecf76a 100644 (file)
@@ -297,25 +297,29 @@ class AnalysisDataPointSetInfo
     public:
         //! Construct point set data object with the given values.
         AnalysisDataPointSetInfo(int valueOffset, int valueCount,
-                                 int firstColumn)
+                                 int dataSetIndex, int firstColumn)
             : valueOffset_(valueOffset), valueCount_(valueCount),
-              firstColumn_(firstColumn)
+              dataSetIndex_(dataSetIndex), firstColumn_(firstColumn)
         {
-            GMX_ASSERT(valueOffset >= 0, "Negative value offsets are invalid");
-            GMX_ASSERT(valueCount  >= 0, "Negative value counts are invalid");
-            GMX_ASSERT(firstColumn >= 0, "Negative column indices are invalid");
+            GMX_ASSERT(valueOffset  >= 0, "Negative value offsets are invalid");
+            GMX_ASSERT(valueCount   >= 0, "Negative value counts are invalid");
+            GMX_ASSERT(dataSetIndex >= 0, "Negative data set indices are invalid");
+            GMX_ASSERT(firstColumn  >= 0, "Negative column indices are invalid");
         }
 
         //! Returns the offset of the first value in the referenced value array.
         int valueOffset() const { return valueOffset_; }
         //! Returns the number of values in this point set.
         int valueCount() const { return valueCount_; }
+        //! Returns the data set index for this point set.
+        int dataSetIndex() const { return dataSetIndex_; }
         //! Returns the index of the first column in this point set.
         int firstColumn() const { return firstColumn_; }
 
     private:
         int                     valueOffset_;
         int                     valueCount_;
+        int                     dataSetIndex_;
         int                     firstColumn_;
 };
 
@@ -415,6 +419,11 @@ class AnalysisDataPointSetRef
         {
             return header_.dx();
         }
+        //! Returns zero-based index of the dataset that this set is part of.
+        int dataSetIndex() const
+        {
+            return dataSetIndex_;
+        }
         //! Returns zero-based index of the first column included in this set.
         int firstColumn() const
         {
@@ -487,6 +496,7 @@ class AnalysisDataPointSetRef
 
     private:
         AnalysisDataFrameHeader header_;
+        int                     dataSetIndex_;
         int                     firstColumn_;
         AnalysisDataValuesRef   values_;
 };
index b04bea345d6eb38ca5df2ce45c0ec83fb398cbc3..9ca0f7b3e85fad3f1199b43aa3e29b975b914b67 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2010,2011,2012, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
  * others, as listed in the AUTHORS file in the top-level source
  * directory and at http://www.gromacs.org.
@@ -82,13 +82,15 @@ class AnalysisDataModuleInterface
          */
         enum {
             //! The module can process multipoint data.
-            efAllowMultipoint    = 0x01,
+            efAllowMultipoint           = 1<<0,
             //! The module does not make sense for non-multipoint data.
-            efOnlyMultipoint     = 0x02,
+            efOnlyMultipoint            = 1<<1,
             //! The module can process data with more than one column.
-            efAllowMulticolumn   = 0x04,
+            efAllowMulticolumn          = 1<<2,
             //! The module can process data with missing points.
-            efAllowMissing       = 0x08,
+            efAllowMissing              = 1<<3,
+            //! The module can process data with multiple data sets.
+            efAllowMultipleDataSets     = 1<<4
         };
 
         virtual ~AnalysisDataModuleInterface() {};
index 8eb4a342c800d485858dd8f33444657e1190ca5c..5b015ccc96f9e1cda0ba2666025fc707edb7eaf8 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2010,2011,2012, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
  * others, as listed in the AUTHORS file in the top-level source
  * directory and at http://www.gromacs.org.
@@ -51,9 +51,8 @@ AnalysisDataProxy::AnalysisDataProxy(int firstColumn, int columnSpan,
                                      AbstractAnalysisData *data)
     : source_(*data), firstColumn_(firstColumn), columnSpan_(columnSpan)
 {
-    GMX_RELEASE_ASSERT(data, "Source data must not be NULL");
+    GMX_RELEASE_ASSERT(data != NULL, "Source data must not be NULL");
     GMX_RELEASE_ASSERT(firstColumn >= 0 && columnSpan > 0, "Invalid proxy column");
-    setColumnCount(columnSpan);
     setMultipoint(source_.isMultipoint());
 }
 
@@ -80,7 +79,8 @@ AnalysisDataProxy::requestStorageInternal(int nframes)
 int
 AnalysisDataProxy::flags() const
 {
-    return efAllowMultipoint | efAllowMulticolumn | efAllowMissing;
+    return efAllowMultipoint | efAllowMulticolumn | efAllowMissing
+           | efAllowMultipleDataSets;
 }
 
 
@@ -88,8 +88,11 @@ void
 AnalysisDataProxy::dataStarted(AbstractAnalysisData *data)
 {
     GMX_RELEASE_ASSERT(data == &source_, "Source data mismatch");
-    GMX_RELEASE_ASSERT(firstColumn_ + columnSpan_ <= source_.columnCount(),
-                       "Invalid column(s) specified");
+    setDataSetCount(data->dataSetCount());
+    for (int i = 0; i < data->dataSetCount(); ++i)
+    {
+        setColumnCount(i, columnSpan_);
+    }
     notifyDataStart();
 }
 
index 1b80d95430284930e39c0122d346400b84ad63a4..94d443e9adf72fa688b7088126d798efa0382cb8 100644 (file)
@@ -107,8 +107,6 @@ class AnalysisDataStorage::Impl
 
         Impl();
 
-        //! Returns the number of columns in the attached data.
-        int columnCount() const;
         //! Returns whether the storage is set to use multipoint data.
         bool isMultipoint() const;
         /*! \brief
@@ -349,7 +347,8 @@ class AnalysisDataStorageFrameData
         /*! \brief
          * Adds a new point set to this frame.
          */
-        void addPointSet(int firstColumn, ValueIterator begin, ValueIterator end);
+        void addPointSet(int dataSetIndex, int firstColumn,
+                         ValueIterator begin, ValueIterator end);
         /*! \brief
          * Finalizes the frame during AnalysisDataStorage::finishFrame().
          *
@@ -401,14 +400,6 @@ AnalysisDataStorage::Impl::Impl()
 }
 
 
-int
-AnalysisDataStorage::Impl::columnCount() const
-{
-    GMX_ASSERT(data_ != NULL, "columnCount() called too early");
-    return data_->columnCount();
-}
-
-
 bool
 AnalysisDataStorage::Impl::isMultipoint() const
 {
@@ -489,7 +480,7 @@ AnalysisDataStorage::Impl::getFrameBuilder()
 {
     if (builders_.empty())
     {
-        return FrameBuilderPointer(new AnalysisDataStorageFrame(columnCount()));
+        return FrameBuilderPointer(new AnalysisDataStorageFrame(*data_));
     }
     FrameBuilderPointer builder(move(builders_.back()));
     builders_.pop_back();
@@ -596,8 +587,14 @@ AnalysisDataStorageFrameData::AnalysisDataStorageFrameData(
     // so initialize it only once here.
     if (!baseData().isMultipoint())
     {
-        int columnCount = baseData().columnCount();
-        pointSets_.push_back(AnalysisDataPointSetInfo(0, columnCount, 0));
+        int offset = 0;
+        for (int i = 0; i < baseData().dataSetCount(); ++i)
+        {
+            int columnCount = baseData().columnCount(i);
+            pointSets_.push_back(
+                    AnalysisDataPointSetInfo(offset, columnCount, i, 0));
+            offset += columnCount;
+        }
     }
 }
 
@@ -625,17 +622,19 @@ AnalysisDataStorageFrameData::startFrame(
     header_         = header;
     builder_        = move(builder);
     builder_->data_ = this;
+    builder_->selectDataSet(0);
 }
 
 
 void
-AnalysisDataStorageFrameData::addPointSet(int firstColumn,
+AnalysisDataStorageFrameData::addPointSet(int dataSetIndex, int firstColumn,
                                           ValueIterator begin, ValueIterator end)
 {
     const int valueCount  = end - begin;
     if (storageImpl().shouldNotifyImmediately())
     {
-        AnalysisDataPointSetInfo pointSetInfo(0, valueCount, firstColumn);
+        AnalysisDataPointSetInfo pointSetInfo(0, valueCount,
+                                              dataSetIndex, firstColumn);
         storageImpl().notifyPointSet(
                 AnalysisDataPointSetRef(header(), pointSetInfo,
                                         AnalysisDataValuesRef(begin, end)));
@@ -643,7 +642,8 @@ AnalysisDataStorageFrameData::addPointSet(int firstColumn,
     else
     {
         pointSets_.push_back(
-                AnalysisDataPointSetInfo(values_.size(), valueCount, firstColumn));
+                AnalysisDataPointSetInfo(values_.size(), valueCount,
+                                         dataSetIndex, firstColumn));
         std::copy(begin, end, std::back_inserter(values_));
     }
 }
@@ -655,7 +655,7 @@ AnalysisDataStorageFrameData::finishFrame(bool bMultipoint)
     status_ = eFinished;
     if (!bMultipoint)
     {
-        GMX_RELEASE_ASSERT(pointSets_.size() == 1U,
+        GMX_RELEASE_ASSERT(static_cast<int>(pointSets_.size()) == baseData().dataSetCount(),
                            "Point sets created for non-multipoint data");
         values_ = builder_->values_;
         builder_->clearValues();
@@ -687,9 +687,17 @@ AnalysisDataStorageFrameData::pointSet(int index) const
  * AnalysisDataStorageFrame
  */
 
-AnalysisDataStorageFrame::AnalysisDataStorageFrame(int columnCount)
-    : data_(NULL), values_(columnCount), bPointSetInProgress_(false)
+AnalysisDataStorageFrame::AnalysisDataStorageFrame(
+        const AbstractAnalysisData &data)
+    : data_(NULL), currentDataSet_(0), currentOffset_(0),
+      columnCount_(data.columnCount(0)), bPointSetInProgress_(false)
 {
+    int totalColumnCount = 0;
+    for (int i = 0; i < data.dataSetCount(); ++i)
+    {
+        totalColumnCount += data.columnCount(i);
+    }
+    values_.resize(totalColumnCount);
 }
 
 
@@ -713,6 +721,26 @@ AnalysisDataStorageFrame::clearValues()
 }
 
 
+void
+AnalysisDataStorageFrame::selectDataSet(int index)
+{
+    GMX_RELEASE_ASSERT(data_ != NULL, "Invalid frame accessed");
+    const AbstractAnalysisData &baseData = data_->baseData();
+    GMX_RELEASE_ASSERT(index >= 0 && index < baseData.dataSetCount(),
+                       "Out of range data set index");
+    GMX_RELEASE_ASSERT(!baseData.isMultipoint() || !bPointSetInProgress_,
+                       "Point sets in multipoint data cannot span data sets");
+    currentDataSet_ = index;
+    currentOffset_  = 0;
+    // TODO: Consider precalculating.
+    for (int i = 0; i < index; ++i)
+    {
+        currentOffset_ += baseData.columnCount(i);
+    }
+    columnCount_    = baseData.columnCount(index);
+}
+
+
 void
 AnalysisDataStorageFrame::finishPointSet()
 {
@@ -721,18 +749,25 @@ AnalysisDataStorageFrame::finishPointSet()
                        "Should not be called for non-multipoint data");
     if (bPointSetInProgress_)
     {
-        std::vector<AnalysisDataValue>::const_iterator begin = values_.begin();
-        std::vector<AnalysisDataValue>::const_iterator end   = values_.end();
+        std::vector<AnalysisDataValue>::const_iterator begin
+            = values_.begin() + currentOffset_;
+        std::vector<AnalysisDataValue>::const_iterator end
+            = begin + columnCount_;
+        int firstColumn = 0;
         while (begin != end && !begin->isSet())
         {
             ++begin;
+            ++firstColumn;
         }
         while (end != begin && !(end-1)->isSet())
         {
             --end;
         }
-        int firstColumn = (begin != end) ? begin - values_.begin() : 0;
-        data_->addPointSet(firstColumn, begin, end);
+        if (begin == end)
+        {
+            firstColumn = 0;
+        }
+        data_->addPointSet(currentDataSet_, firstColumn, begin, end);
     }
     clearValues();
 }
index 650a613b0fd11f0485958aec2782c727e3643ba4..26ab9640b6230d4df629abc2598098a8a964ed9e 100644 (file)
@@ -87,8 +87,23 @@ class AnalysisDataStorageFrame
          */
         ~AnalysisDataStorageFrame();
 
+        /*! \brief
+         * Select data set that all other methods operate on.
+         *
+         * \param[in] index  Zero-based data set index to select.
+         *
+         * With multipoint data, a single point set can only contain values in
+         * a single data set.
+         * With non-multipoint data, arbitrary sequences of selectDataSet() and
+         * setValue() are supported.  The full frame is notified to the modules
+         * once it is finished.
+         *
+         * Does not throw.
+         */
+        void selectDataSet(int index);
+
         //! Returns number of columns for the frame.
-        int columnCount() const { return values_.size(); }
+        int columnCount() const { return columnCount_; }
 
         /*! \brief
          * Sets value for a column.
@@ -106,7 +121,7 @@ class AnalysisDataStorageFrame
         {
             GMX_ASSERT(column >= 0 && column < columnCount(),
                        "Invalid column index");
-            values_[column].setValue(value, bPresent);
+            values_[currentOffset_ + column].setValue(value, bPresent);
             bPointSetInProgress_ = true;
         }
         /*! \brief
@@ -126,7 +141,7 @@ class AnalysisDataStorageFrame
         {
             GMX_ASSERT(column >= 0 && column < columnCount(),
                        "Invalid column index");
-            values_[column].setValue(value, error, bPresent);
+            values_[currentOffset_ + column].setValue(value, error, bPresent);
             bPointSetInProgress_ = true;
         }
         /*! \brief
@@ -144,7 +159,7 @@ class AnalysisDataStorageFrame
         {
             GMX_ASSERT(column >= 0 && column < columnCount(),
                        "Invalid column index");
-            return values_[column].value();
+            return values_[currentOffset_ + column].value();
         }
         /*! \brief
          * Access value for a column.
@@ -160,7 +175,7 @@ class AnalysisDataStorageFrame
         {
             GMX_ASSERT(column >= 0 && column < columnCount(),
                        "Invalid column index");
-            return values_[column].value();
+            return values_[currentOffset_ + column].value();
         }
         /*! \brief
          * Mark point set as finished for multipoint data.
@@ -193,9 +208,10 @@ class AnalysisDataStorageFrame
         /*! \brief
          * Create a new storage frame.
          *
-         * \param[in] columnCount  Number of columns for the frame.
+         * \param[in] data  Data object for which the frame is for
+         *      (used for data set and column counts).
          */
-        explicit AnalysisDataStorageFrame(int columnCount);
+        explicit AnalysisDataStorageFrame(const AbstractAnalysisData &data);
 
         //! Clear all column values from the frame.
         void clearValues();
@@ -205,6 +221,13 @@ class AnalysisDataStorageFrame
         //! Values for the currently in-progress point set.
         std::vector<AnalysisDataValue>          values_;
 
+        //! Index of the currently active dataset.
+        int                                     currentDataSet_;
+        //! Offset of the first value in \a values_ for the current data set.
+        int                                     currentOffset_;
+        //! Number of columns in the current data set.
+        int                                     columnCount_;
+
         //! Whether any values have been set in the current point set.
         bool                                    bPointSetInProgress_;
 
index 9640b224a98f6061836e5e8f4979e0652eba0019..61cf7454c85f979037abddf6395a3a2024cba307 100644 (file)
@@ -144,7 +144,7 @@ class AnalysisDataFrameAverageModule::Impl
 AnalysisDataFrameAverageModule::AnalysisDataFrameAverageModule()
     : impl_(new Impl())
 {
-    setColumnCount(1);
+    setColumnCount(0, 1);
 }
 
 AnalysisDataFrameAverageModule::~AnalysisDataFrameAverageModule()
index 2dd5836e3d07cd65557112c810b454c000467caf..bb13359465685fc02b8f90cff4d5c2b605097784 100644 (file)
@@ -178,7 +178,7 @@ AnalysisDataDisplacementModule::dataStarted(AbstractAnalysisData *data)
 
     int ncol = _impl->nmax / _impl->ndim + 1;
     _impl->currValues_.reserve(ncol);
-    setColumnCount(ncol);
+    setColumnCount(0, ncol);
 }
 
 
index 766ce363f8ea4258fc7e0af0564b642b8cce3881..bdac5127c42a561c309e62a150aec27f2d7fc5c1 100644 (file)
@@ -601,7 +601,7 @@ void
 AnalysisDataSimpleHistogramModule::dataStarted(AbstractAnalysisData *data)
 {
     addModule(impl_->averager_);
-    setColumnCount(settings().binCount());
+    setColumnCount(0, settings().binCount());
     notifyDataStart();
     impl_->storage_.startDataStorage(this);
 }
@@ -712,7 +712,7 @@ void
 AnalysisDataWeightedHistogramModule::dataStarted(AbstractAnalysisData *data)
 {
     addModule(impl_->averager_);
-    setColumnCount(settings().binCount());
+    setColumnCount(0, settings().binCount());
     notifyDataStart();
     impl_->storage_.startDataStorage(this);
 }
index 008dce536e342675380b8ba92891817350cf8ee3..2448bdba101da10da4c7d481abcfe6e1b64d4731 100644 (file)
@@ -73,20 +73,33 @@ namespace
 TEST(AnalysisDataInitializationTest, BasicInitialization)
 {
     gmx::AnalysisData data;
+    EXPECT_EQ(1, data.dataSetCount());
+    EXPECT_EQ(0, data.columnCount(0));
     EXPECT_EQ(0, data.columnCount());
     EXPECT_FALSE(data.isMultipoint());
     EXPECT_EQ(0, data.frameCount());
 
-    data.setColumnCount(1);
+    data.setColumnCount(0, 1);
+    EXPECT_EQ(1, data.columnCount(0));
     EXPECT_EQ(1, data.columnCount());
     EXPECT_FALSE(data.isMultipoint());
 
-    data.setColumnCount(3);
+    data.setDataSetCount(2);
+    EXPECT_EQ(2, data.dataSetCount());
+    data.setColumnCount(0, 3);
+    EXPECT_EQ(3, data.columnCount(0));
+    EXPECT_EQ(0, data.columnCount(1));
+    data.setColumnCount(1, 2);
+    EXPECT_EQ(3, data.columnCount(0));
+    EXPECT_EQ(2, data.columnCount(1));
+
+    data.setDataSetCount(1);
+    EXPECT_EQ(1, data.dataSetCount());
     data.setMultipoint(true);
     EXPECT_EQ(3, data.columnCount());
     EXPECT_TRUE(data.isMultipoint());
 
-    data.setColumnCount(1);
+    data.setColumnCount(0, 1);
     EXPECT_EQ(1, data.columnCount());
     EXPECT_TRUE(data.isMultipoint());
 }
@@ -98,7 +111,7 @@ TEST(AnalysisDataInitializationTest, BasicInitialization)
 TEST(AnalysisDataInitializationTest, ChecksMultiColumnModules)
 {
     gmx::AnalysisData data;
-    data.setColumnCount(2);
+    data.setColumnCount(0, 2);
 
     MockAnalysisDataModulePointer mod1(new MockAnalysisDataModule(0));
     EXPECT_THROW_GMX(data.addModule(mod1), gmx::APIError);
@@ -112,10 +125,10 @@ TEST(AnalysisDataInitializationTest, ChecksMultiColumnModules)
  * Tests that checking for compatibility of modules with multipoint data
  * works.
  */
-TEST(AnalysisDataInitializationTest, ChecksMultiPointModules)
+TEST(AnalysisDataInitializationTest, ChecksMultipointModules)
 {
     gmx::AnalysisData data;
-    data.setColumnCount(1);
+    data.setColumnCount(0, 1);
     data.setMultipoint(true);
 
     MockAnalysisDataModulePointer mod1(new MockAnalysisDataModule(0));
@@ -141,8 +154,9 @@ class SimpleInputData
             return singleton.data_;
         }
 
-        SimpleInputData() : data_(3, false)
+        SimpleInputData() : data_(1, false)
         {
+            data_.setColumnCount(0, 3);
             data_.addFrameWithValues(1.0,  0.0, 1.0, 2.0);
             data_.addFrameWithValues(2.0,  1.0, 1.0, 1.0);
             data_.addFrameWithValues(3.0,  2.0, 0.0, 0.0);
@@ -152,6 +166,36 @@ class SimpleInputData
         AnalysisDataTestInput  data_;
 };
 
+// Input data with multiple data sets for gmx::AnalysisData tests.
+class DataSetsInputData
+{
+    public:
+        static const AnalysisDataTestInput &get()
+        {
+            static DataSetsInputData singleton;
+            return singleton.data_;
+        }
+
+        DataSetsInputData() : data_(2, false)
+        {
+            using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 3);
+            data_.setColumnCount(1, 2);
+            AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
+            frame1.addPointSetWithValues(0, 0, 0.0, 1.0, 2.0);
+            frame1.addPointSetWithValues(1, 0, 2.1, 1.1);
+            AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
+            frame2.addPointSetWithValues(0, 0, 1.0, 1.0, 1.0);
+            frame2.addPointSetWithValues(1, 0, 0.1, 2.1);
+            AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
+            frame3.addPointSetWithValues(0, 0, 2.0, 0.0, 0.0);
+            frame3.addPointSetWithValues(1, 0, 1.1, 1.1);
+        }
+
+    private:
+        AnalysisDataTestInput  data_;
+};
+
 // Input data for multipoint gmx::AnalysisData tests.
 class MultipointInputData
 {
@@ -162,21 +206,55 @@ class MultipointInputData
             return singleton.data_;
         }
 
-        MultipointInputData() : data_(3, true)
+        MultipointInputData() : data_(1, true)
+        {
+            using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 3);
+            AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
+            frame1.addPointSetWithValues(0, 0, 0.0, 1.0, 2.0);
+            frame1.addPointSetWithValues(0, 0, 1.1, 2.1, 1.1);
+            frame1.addPointSetWithValues(0, 0, 2.2, 1.2, 0.2);
+            AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
+            frame2.addPointSetWithValues(0, 1, 1.0, 1.0);
+            frame2.addPointSetWithValues(0, 0, 2.1, 1.1, 0.1);
+            frame2.addPointSetWithValues(0, 2, 1.2);
+            AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
+            frame3.addPointSetWithValues(0, 0, 2.0, 0.0, 0.0);
+            frame3.addPointSetWithValues(0, 0, 3.1, 2.1);
+            frame3.addPointSetWithValues(0, 1, 2.2, 1.2);
+        }
+
+    private:
+        AnalysisDataTestInput  data_;
+};
+
+// Input data with multiple multipoint data sets for gmx::AnalysisData tests.
+class MultipointDataSetsInputData
+{
+    public:
+        static const AnalysisDataTestInput &get()
+        {
+            static MultipointDataSetsInputData singleton;
+            return singleton.data_;
+        }
+
+        MultipointDataSetsInputData() : data_(2, true)
         {
             using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 3);
+            data_.setColumnCount(1, 2);
             AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
-            frame1.addPointSetWithValues(0, 0.0, 1.0, 2.0);
-            frame1.addPointSetWithValues(0, 1.1, 2.1, 1.1);
-            frame1.addPointSetWithValues(0, 2.2, 1.2, 0.2);
+            frame1.addPointSetWithValues(0, 0, 0.0, 1.0, 2.0);
+            frame1.addPointSetWithValues(0, 1, 2.1, 1.1);
+            frame1.addPointSetWithValues(1, 0, 2.01, 1.01);
+            frame1.addPointSetWithValues(1, 1, 0.11);
             AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
-            frame2.addPointSetWithValues(1, 1.0, 1.0);
-            frame2.addPointSetWithValues(0, 2.1, 1.1, 0.1);
-            frame2.addPointSetWithValues(2, 1.2);
+            frame2.addPointSetWithValues(0, 0, 1.0, 1.0, 1.0);
+            frame2.addPointSetWithValues(0, 0, 0.1, 2.1);
+            frame2.addPointSetWithValues(1, 1, 1.01);
             AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
-            frame3.addPointSetWithValues(0, 2.0, 0.0, 0.0);
-            frame3.addPointSetWithValues(0, 3.1, 2.1);
-            frame3.addPointSetWithValues(1, 2.2, 1.2);
+            frame3.addPointSetWithValues(0, 0, 2.0, 0.0, 0.0);
+            frame3.addPointSetWithValues(0, 1, 1.1);
         }
 
     private:
@@ -239,7 +317,11 @@ typedef AnalysisDataCommonTest<SimpleInputData>     AnalysisDataSimpleTest;
 //! Test fixture for tests that are only applicable to multipoint data.
 typedef AnalysisDataCommonTest<MultipointInputData> AnalysisDataMultipointTest;
 //! List of input data types for tests applicable to all types of data.
-typedef ::testing::Types<SimpleInputData, MultipointInputData> AllInputDataTypes;
+typedef ::testing::Types<SimpleInputData,
+                         DataSetsInputData,
+                         MultipointInputData,
+                         MultipointDataSetsInputData>
+    AllInputDataTypes;
 TYPED_TEST_CASE(AnalysisDataCommonTest, AllInputDataTypes);
 
 /*
index 1e861432bf0ddd65ac6adb3cff99e801c3bdd627..258212a26712cf8e8b47a34e101ef1bb09f27b4e 100644 (file)
@@ -73,8 +73,9 @@ class SimpleInputData
             return singleton.data_;
         }
 
-        SimpleInputData() : data_(3, false)
+        SimpleInputData() : data_(1, false)
         {
+            data_.setColumnCount(0, 3);
             data_.addFrameWithValues(1.0,  0.0, 1.0, 2.0);
             data_.addFrameWithValues(2.0,  1.0, 1.0, 1.0);
             data_.addFrameWithValues(3.0,  2.0, 0.0, 0.0);
index e45d1c256f2fcc4406260c94fedd49833017883a..f5c2a52e176003083305d3ba5b95a1a2aa845b8a 100644 (file)
@@ -69,8 +69,9 @@ class SimpleInputData
             return singleton.data_;
         }
 
-        SimpleInputData() : data_(3, false)
+        SimpleInputData() : data_(1, false)
         {
+            data_.setColumnCount(0, 3);
             data_.addFrameWithValues(1.0,  0.0, 1.0, 2.0);
             data_.addFrameWithValues(2.0,  1.0, 1.0, 1.0);
             data_.addFrameWithValues(3.0,  2.0, 0.0, 0.0);
@@ -90,18 +91,19 @@ class MultipointInputData
             return singleton.data_;
         }
 
-        MultipointInputData() : data_(3, true)
+        MultipointInputData() : data_(1, true)
         {
             using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 3);
             AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
-            frame1.addPointSetWithValues(0, 0.0, 1.0, 2.0);
-            frame1.addPointSetWithValues(0, 1.0, 0.0);
-            frame1.addPointSetWithValues(0, 2.0);
+            frame1.addPointSetWithValues(0, 0, 0.0, 1.0, 2.0);
+            frame1.addPointSetWithValues(0, 0, 1.0, 0.0);
+            frame1.addPointSetWithValues(0, 0, 2.0);
             AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
-            frame2.addPointSetWithValues(0, 1.0, 1.0);
-            frame2.addPointSetWithValues(0, 2.0);
+            frame2.addPointSetWithValues(0, 0, 1.0, 1.0);
+            frame2.addPointSetWithValues(0, 0, 2.0);
             AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
-            frame3.addPointSetWithValues(0, 2.0, 0.0, 0.0);
+            frame3.addPointSetWithValues(0, 0, 2.0, 0.0, 0.0);
         }
 
     private:
index 7f9622ada81805ab1dd0c4849d45d3036e0a2943..b2e3481bca0e2f3fe0bab2b8203c1fa22e17305e 100644 (file)
@@ -164,18 +164,19 @@ class SimpleInputData
         SimpleInputData() : data_(1, true)
         {
             using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 1);
             AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
-            frame1.addPointSetWithValues(0, 0.7);
-            frame1.addPointSetWithValues(0, 1.1);
-            frame1.addPointSetWithValues(0, 2.3);
-            frame1.addPointSetWithValues(0, 2.9);
+            frame1.addPointSetWithValues(0, 0, 0.7);
+            frame1.addPointSetWithValues(0, 0, 1.1);
+            frame1.addPointSetWithValues(0, 0, 2.3);
+            frame1.addPointSetWithValues(0, 0, 2.9);
             AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
-            frame2.addPointSetWithValues(0, 1.3);
-            frame2.addPointSetWithValues(0, 2.2);
+            frame2.addPointSetWithValues(0, 0, 1.3);
+            frame2.addPointSetWithValues(0, 0, 2.2);
             AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
-            frame3.addPointSetWithValues(0, 3.3);
-            frame3.addPointSetWithValues(0, 1.2);
-            frame3.addPointSetWithValues(0, 1.3);
+            frame3.addPointSetWithValues(0, 0, 3.3);
+            frame3.addPointSetWithValues(0, 0, 1.2);
+            frame3.addPointSetWithValues(0, 0, 1.3);
         }
 
     private:
@@ -241,21 +242,22 @@ class WeightedInputData
             return singleton.data_;
         }
 
-        WeightedInputData() : data_(2, true)
+        WeightedInputData() : data_(1, true)
         {
             using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 2);
             AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
-            frame1.addPointSetWithValues(0, 0.7, 0.5);
-            frame1.addPointSetWithValues(0, 1.1, 1.0);
-            frame1.addPointSetWithValues(0, 2.3, 1.0);
-            frame1.addPointSetWithValues(0, 2.9, 2.0);
+            frame1.addPointSetWithValues(0, 0, 0.7, 0.5);
+            frame1.addPointSetWithValues(0, 0, 1.1, 1.0);
+            frame1.addPointSetWithValues(0, 0, 2.3, 1.0);
+            frame1.addPointSetWithValues(0, 0, 2.9, 2.0);
             AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
-            frame2.addPointSetWithValues(0, 1.3, 1.0);
-            frame2.addPointSetWithValues(0, 2.2, 3.0);
+            frame2.addPointSetWithValues(0, 0, 1.3, 1.0);
+            frame2.addPointSetWithValues(0, 0, 2.2, 3.0);
             AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
-            frame3.addPointSetWithValues(0, 3.3, 0.5);
-            frame3.addPointSetWithValues(0, 1.2, 2.0);
-            frame3.addPointSetWithValues(0, 1.3, 1.0);
+            frame3.addPointSetWithValues(0, 0, 3.3, 0.5);
+            frame3.addPointSetWithValues(0, 0, 1.2, 2.0);
+            frame3.addPointSetWithValues(0, 0, 1.3, 1.0);
         }
 
     private:
@@ -367,8 +369,9 @@ class AverageInputData
             return singleton.data_;
         }
 
-        AverageInputData() : data_(2, false)
+        AverageInputData() : data_(1, false)
         {
+            data_.setColumnCount(0, 2);
             data_.addFrameWithValues(1.0,  2.0, 1.0);
             data_.addFrameWithValues(1.5,  1.0, 1.0);
             data_.addFrameWithValues(2.0,  3.0, 2.0);
index 569a0a0cdff72efb425e558a949e7de37b594d96..4c7e9377a982f6e88f4184e8ab7092e9c9c3d019 100644 (file)
@@ -274,7 +274,7 @@ Angle::initAnalysis(const TrajectoryAnalysisSettings &settings,
 {
     checkSelections(sel1_, sel2_);
 
-    angles_.setColumnCount(sel1_[0].posCount() / natoms1_);
+    angles_.setColumnCount(0, sel1_[0].posCount() / natoms1_);
     double histogramMin = (g1type_ == "dihedral" ? -180.0 : 0);
     histogramModule_->init(histogramFromRange(histogramMin, 180.0)
                                .binWidth(binWidth_).includeAll());
index e7a7b324d9b9c059f4d1fd41e3af14c543c847cd..0cec0fa583c97c10abcc67f613f7aeac4e02a14a 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2010,2011,2012, by the GROMACS development team, led by
+ * Copyright (c) 2010,2011,2012,2013, by the GROMACS development team, led by
  * David van der Spoel, Berk Hess, Erik Lindahl, and including many
  * others, as listed in the AUTHORS file in the top-level source
  * directory and at http://www.gromacs.org.
@@ -69,7 +69,7 @@ Distance::Distance()
     : TrajectoryAnalysisModule(name, shortDescription),
       avem_(new AnalysisDataAverageModule())
 {
-    data_.setColumnCount(4);
+    data_.setColumnCount(0, 4);
     registerAnalysisDataset(&data_, "distance");
 }
 
index 1102b2ee4a761cf4fad2142047d24f4e00512edf..8b6ced9e19cfddc319365b59ee4133e79a0ee3af 100644 (file)
@@ -76,7 +76,7 @@ FreeVolume::FreeVolume()
       adata_(new AnalysisDataAverageModule())
 {
     // We only compute two numbers per frame
-    data_.setColumnCount(2);
+    data_.setColumnCount(0, 2);
     // Tell the analysis framework that this component exists
     registerAnalysisDataset(&data_, "freevolume");
     rng_         = NULL;
index 2f7c6d5a1298a75f09c86673cdc034baa30c03f9..2cc41c1c733db6f90528d98dee76b2cd0f454fcf 100644 (file)
@@ -264,7 +264,7 @@ Select::Select()
 {
     registerAnalysisDataset(&sdata_, "size");
     registerAnalysisDataset(&cdata_, "cfrac");
-    idata_.setColumnCount(2);
+    idata_.setColumnCount(0, 2);
     idata_.setMultipoint(true);
     registerAnalysisDataset(&idata_, "index");
     registerAnalysisDataset(&mdata_, "mask");
@@ -420,7 +420,7 @@ Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
     }
 
     // TODO: For large systems, a float may not have enough precision
-    sdata_.setColumnCount(sel_.size());
+    sdata_.setColumnCount(0, sel_.size());
     totsize_.reserve(sel_.size());
     for (size_t g = 0; g < sel_.size(); ++g)
     {
@@ -437,7 +437,7 @@ Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
         sdata_.addModule(plot);
     }
 
-    cdata_.setColumnCount(sel_.size());
+    cdata_.setColumnCount(0, sel_.size());
     if (!fnFrac_.empty())
     {
         AnalysisDataPlotModulePointer plot(
@@ -479,7 +479,7 @@ Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
         idata_.addModule(writer);
     }
 
-    mdata_.setColumnCount(sel_[0].posCount());
+    mdata_.setColumnCount(0, sel_[0].posCount());
     mdata_.addModule(occupancyModule_);
     if (!fnMask_.empty())
     {
index d3ad9a9795bac5104c138da165180b3cc856b05f..50ebe42247d24b8bc66203365192c8f7aea167a7 100644 (file)
@@ -63,8 +63,8 @@ namespace test
  */
 
 AnalysisDataTestInputPointSet::AnalysisDataTestInputPointSet(
-        int index, int firstColumn)
-    : index_(index), firstColumn_(firstColumn)
+        int index, int dataSetIndex, int firstColumn)
+    : index_(index), dataSetIndex_(dataSetIndex), firstColumn_(firstColumn)
 {
 }
 
@@ -79,30 +79,33 @@ AnalysisDataTestInputFrame::AnalysisDataTestInputFrame(int index, real x)
 }
 
 AnalysisDataTestInputPointSet &
-AnalysisDataTestInputFrame::addPointSet(int firstColumn)
+AnalysisDataTestInputFrame::addPointSet(int dataSet, int firstColumn)
 {
-    pointSets_.push_back(AnalysisDataTestInputPointSet(pointSets_.size(), firstColumn));
+    pointSets_.push_back(
+            AnalysisDataTestInputPointSet(pointSets_.size(),
+                                          dataSet, firstColumn));
     return pointSets_.back();
 }
 
-void AnalysisDataTestInputFrame::addPointSetWithValues(int firstColumn, real y1)
+void AnalysisDataTestInputFrame::addPointSetWithValues(
+        int dataSet, int firstColumn, real y1)
 {
-    AnalysisDataTestInputPointSet &pointSet = addPointSet(firstColumn);
+    AnalysisDataTestInputPointSet &pointSet = addPointSet(dataSet, firstColumn);
     pointSet.addValue(y1);
 }
 
-void AnalysisDataTestInputFrame::addPointSetWithValues(int firstColumn, real y1,
-                                                       real y2)
+void AnalysisDataTestInputFrame::addPointSetWithValues(
+        int dataSet, int firstColumn, real y1, real y2)
 {
-    AnalysisDataTestInputPointSet &pointSet = addPointSet(firstColumn);
+    AnalysisDataTestInputPointSet &pointSet = addPointSet(dataSet, firstColumn);
     pointSet.addValue(y1);
     pointSet.addValue(y2);
 }
 
-void AnalysisDataTestInputFrame::addPointSetWithValues(int firstColumn, real y1,
-                                                       real y2, real y3)
+void AnalysisDataTestInputFrame::addPointSetWithValues(
+        int dataSet, int firstColumn, real y1, real y2, real y3)
 {
-    AnalysisDataTestInputPointSet &pointSet = addPointSet(firstColumn);
+    AnalysisDataTestInputPointSet &pointSet = addPointSet(dataSet, firstColumn);
     pointSet.addValue(y1);
     pointSet.addValue(y2);
     pointSet.addValue(y3);
@@ -113,8 +116,8 @@ void AnalysisDataTestInputFrame::addPointSetWithValues(int firstColumn, real y1,
  * AnalysisDataTestInput
  */
 
-AnalysisDataTestInput::AnalysisDataTestInput(int columnCount, bool bMultipoint)
-    : columnCount_(columnCount), bMultipoint_(bMultipoint)
+AnalysisDataTestInput::AnalysisDataTestInput(int dataSetCount, bool bMultipoint)
+    : columnCounts_(dataSetCount), bMultipoint_(bMultipoint)
 {
 }
 
@@ -132,6 +135,14 @@ const AnalysisDataTestInputFrame &AnalysisDataTestInput::frame(int index) const
 }
 
 
+void AnalysisDataTestInput::setColumnCount(int dataSet, int columnCount)
+{
+    GMX_RELEASE_ASSERT(dataSet >= 0 && dataSet < dataSetCount(),
+                       "Out-of-range data set index");
+    columnCounts_[dataSet] = columnCount;
+}
+
+
 AnalysisDataTestInputFrame &AnalysisDataTestInput::addFrame(real x)
 {
     frames_.push_back(AnalysisDataTestInputFrame(frames_.size(), x));
@@ -141,19 +152,19 @@ AnalysisDataTestInputFrame &AnalysisDataTestInput::addFrame(real x)
 void AnalysisDataTestInput::addFrameWithValues(real x, real y1)
 {
     AnalysisDataTestInputFrame &frame = addFrame(x);
-    frame.addPointSetWithValues(0, y1);
+    frame.addPointSetWithValues(0, 0, y1);
 }
 
 void AnalysisDataTestInput::addFrameWithValues(real x, real y1, real y2)
 {
     AnalysisDataTestInputFrame &frame = addFrame(x);
-    frame.addPointSetWithValues(0, y1, y2);
+    frame.addPointSetWithValues(0, 0, y1, y2);
 }
 
 void AnalysisDataTestInput::addFrameWithValues(real x, real y1, real y2, real y3)
 {
     AnalysisDataTestInputFrame &frame = addFrame(x);
-    frame.addPointSetWithValues(0, y1, y2, y3);
+    frame.addPointSetWithValues(0, 0, y1, y2, y3);
 }
 
 
@@ -169,7 +180,11 @@ AnalysisDataTestFixture::AnalysisDataTestFixture()
 void AnalysisDataTestFixture::setupDataObject(const AnalysisDataTestInput &input,
                                               AnalysisData                *data)
 {
-    data->setColumnCount(input.columnCount());
+    data->setDataSetCount(input.dataSetCount());
+    for (int i = 0; i < input.dataSetCount(); ++i)
+    {
+        data->setColumnCount(i, input.columnCount(i));
+    }
     data->setMultipoint(input.isMultipoint());
 }
 
@@ -196,6 +211,7 @@ void AnalysisDataTestFixture::presentDataFrame(const AnalysisDataTestInput &inpu
     for (int i = 0; i < frame.pointSetCount(); ++i)
     {
         const AnalysisDataTestInputPointSet &points = frame.pointSet(i);
+        handle.selectDataSet(points.dataSetIndex());
         for (int j = 0; j < points.size(); ++j)
         {
             handle.setPoint(j + points.firstColumn(),
index 0de86506f5dc755accb69d6995835279277fb7ec..f7bf76fde3053a81b3397942665014974f8a4a7a 100644 (file)
@@ -79,6 +79,8 @@ class AnalysisDataTestInputPointSet
     public:
         //! Returns zero-based index of this point set in its frame.
         int index() const { return index_; }
+        //! Returns zero-based index of the data set of this point set.
+        int dataSetIndex() const { return dataSetIndex_; }
         //! Returns zero-based index of the first column in this point set.
         int firstColumn() const { return firstColumn_; }
         //! Returns zero-based index of the last column in this point set.
@@ -99,9 +101,11 @@ class AnalysisDataTestInputPointSet
 
     private:
         //! Creates an empty point set.
-        AnalysisDataTestInputPointSet(int index, int firstColumn);
+        AnalysisDataTestInputPointSet(int index, int dataSetIndex,
+                                      int firstColumn);
 
         int                     index_;
+        int                     dataSetIndex_;
         int                     firstColumn_;
         std::vector<real>       y_;
 
@@ -136,13 +140,15 @@ class AnalysisDataTestInputFrame
         }
 
         //! Appends an empty point set to this frame.
-        AnalysisDataTestInputPointSet &addPointSet(int firstColumn);
+        AnalysisDataTestInputPointSet &addPointSet(int dataSet, int firstColumn);
         //! Adds a point set with given values to this frame.
-        void addPointSetWithValues(int firstColumn, real y1);
+        void addPointSetWithValues(int dataSet, int firstColumn, real y1);
         //! Adds a point set with given values to this frame.
-        void addPointSetWithValues(int firstColumn, real y1, real y2);
+        void addPointSetWithValues(int dataSet, int firstColumn,
+                                   real y1, real y2);
         //! Adds a point set with given values to this frame.
-        void addPointSetWithValues(int firstColumn, real y1, real y2, real y3);
+        void addPointSetWithValues(int dataSet, int firstColumn,
+                                   real y1, real y2, real y3);
 
     private:
         //! Constructs a new frame object with the given values.
@@ -173,21 +179,28 @@ class AnalysisDataTestInput
         /*! \brief
          * Constructs empty input data.
          *
-         * \param[in] columnCount  Number of columns in the data.
+         * \param[in] dataSetCount Number of data sets in the data.
          * \param[in] bMultipoint  Whether the data will be multipoint.
+         *
+         * The column count for each data set must be set with
+         * setColumnCount().
          */
-        AnalysisDataTestInput(int columnCount, bool bMultipoint);
+        AnalysisDataTestInput(int dataSetCount, bool bMultipoint);
         ~AnalysisDataTestInput();
 
         //! Whether the input data is multipoint.
         bool isMultipoint() const { return bMultipoint_; }
-        //! Returns the number of columns in the input data.
-        int columnCount() const { return columnCount_; }
+        //! Returns the number of data sets in the input data.
+        int dataSetCount() const { return columnCounts_.size(); }
+        //! Returns the number of columns in a given data set.
+        int columnCount(int dataSet) const { return columnCounts_[dataSet]; }
         //! Returns the number of frames in the input data.
         int frameCount() const { return frames_.size(); }
         //! Returns a frame object for the given input frame.
         const AnalysisDataTestInputFrame &frame(int index) const;
 
+        //! Sets the number of columns in a data set.
+        void setColumnCount(int dataSet, int columnCount);
         //! Appends an empty frame to this data.
         AnalysisDataTestInputFrame &addFrame(real x);
         //! Adds a frame with a single point set and the given values.
@@ -198,7 +211,7 @@ class AnalysisDataTestInput
         void addFrameWithValues(real x, real y1, real y2, real y3);
 
     private:
-        int                                     columnCount_;
+        std::vector<int>                        columnCounts_;
         bool                                    bMultipoint_;
         std::vector<AnalysisDataTestInputFrame> frames_;
 };
@@ -393,11 +406,13 @@ void AnalysisDataTestFixture::setupArrayData(const AnalysisDataTestInput &input,
 {
     GMX_RELEASE_ASSERT(!input.isMultipoint(),
                        "Array data cannot be initialized from multipoint data");
-    GMX_RELEASE_ASSERT(data->columnCount() == 0 || data->columnCount() == input.columnCount(),
+    GMX_RELEASE_ASSERT(input.dataSetCount() == 1,
+                       "Array data cannot be initialized from multiple data sets");
+    GMX_RELEASE_ASSERT(data->columnCount() == 0 || data->columnCount() == input.columnCount(0),
                        "Mismatching input and target data");
     GMX_RELEASE_ASSERT(data->rowCount() == 0 || data->rowCount() == input.frameCount(),
                        "Mismatching input and target data");
-    data->setColumnCount(input.columnCount());
+    data->setColumnCount(input.columnCount(0));
     data->setRowCount(input.frameCount());
     data->allocateValues();
     for (int row = 0; row < input.frameCount(); ++row)
index 4be423ac1b24f03ade918c0751ce94a8b46447a6..da6e4ca24131872e4784282a3fc32b3479e9db7e 100644 (file)
@@ -342,6 +342,7 @@ class StaticDataPointsChecker
         {
             SCOPED_TRACE(formatString("Frame %d, point set %d",
                                       frame_->index(), points_->index()));
+            EXPECT_EQ(points_->dataSetIndex(), points.dataSetIndex());
             const int expectedFirstColumn
                 = std::max(0, points_->firstColumn() - firstcol_);
             const int expectedLastColumn
@@ -482,9 +483,7 @@ void
 MockAnalysisDataModule::setupStaticCheck(const AnalysisDataTestInput &data,
                                          AbstractAnalysisData        *source)
 {
-    GMX_RELEASE_ASSERT(data.columnCount() == source->columnCount(),
-                       "Mismatching data column count");
-    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint;
+    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint | efAllowMultipleDataSets;
 
     ::testing::InSequence dummy;
     using ::testing::_;
@@ -499,9 +498,9 @@ MockAnalysisDataModule::setupStaticCheck(const AnalysisDataTestInput &data,
         for (int ps = 0; ps < frame.pointSetCount(); ++ps)
         {
             const AnalysisDataTestInputPointSet &points = frame.pointSet(ps);
-            EXPECT_CALL(*this, pointsAdded(_))
-                .WillOnce(Invoke(StaticDataPointsChecker(&frame, &points, 0,
-                                                         data.columnCount())));
+            StaticDataPointsChecker              checker(&frame, &points, 0,
+                                                         data.columnCount(points.dataSetIndex()));
+            EXPECT_CALL(*this, pointsAdded(_)).WillOnce(Invoke(checker));
         }
         EXPECT_CALL(*this, frameFinished(_))
             .WillOnce(Invoke(StaticDataFrameHeaderChecker(&frame)));
@@ -515,11 +514,7 @@ MockAnalysisDataModule::setupStaticColumnCheck(
         const AnalysisDataTestInput &data,
         int firstcol, int n, AbstractAnalysisData *source)
 {
-    GMX_RELEASE_ASSERT(data.columnCount() == source->columnCount(),
-                       "Mismatching data column count");
-    GMX_RELEASE_ASSERT(firstcol >= 0 && n > 0 && firstcol + n <= data.columnCount(),
-                       "Out-of-range columns");
-    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint;
+    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint | efAllowMultipleDataSets;
 
     ::testing::InSequence dummy;
     using ::testing::_;
@@ -553,11 +548,9 @@ MockAnalysisDataModule::setupStaticStorageCheck(
         const AnalysisDataTestInput &data,
         int storageCount, AbstractAnalysisData *source)
 {
-    GMX_RELEASE_ASSERT(data.columnCount() == source->columnCount(),
-                       "Mismatching data column count");
     GMX_RELEASE_ASSERT(data.isMultipoint() == source->isMultipoint(),
                        "Mismatching multipoint properties");
-    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint;
+    impl_->flags_ |= efAllowMulticolumn | efAllowMultipoint | efAllowMultipleDataSets;
 
     ::testing::InSequence dummy;
     using ::testing::_;