Lifetime statistics into gmx-select.
authorTeemu Murtola <teemu.murtola@gmail.com>
Sat, 17 Aug 2013 18:36:17 +0000 (21:36 +0300)
committerGerrit Code Review <gerrit@gerrit.gromacs.org>
Wed, 21 Aug 2013 06:22:57 +0000 (08:22 +0200)
This provides a lot more flexible alternative to g_dist -lt.

Added an analysisdata module that computes lifetime statistics and test
for it.  Currently tuned for this one application, but should be
possible to make more flexible if required.  Using this module, the
implementation in gmx-select is straightforward.

Part of #665.

Change-Id: I8bff89599c9d8a820c761b426c01ceafa9afcc5a

src/gromacs/analysisdata/modules/lifetime.cpp [new file with mode: 0644]
src/gromacs/analysisdata/modules/lifetime.h [new file with mode: 0644]
src/gromacs/analysisdata/tests/CMakeLists.txt
src/gromacs/analysisdata/tests/lifetime.cpp [new file with mode: 0644]
src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_BasicTest.xml [new file with mode: 0644]
src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_CumulativeTest.xml [new file with mode: 0644]
src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_HandlesMultipleDataSets.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/analysismodule.cpp
src/gromacs/trajectoryanalysis/modules/select.cpp
src/gromacs/trajectoryanalysis/modules/select.h
src/gromacs/trajectoryanalysis/tests/refdata/SelectModuleTest_BasicTest.xml

diff --git a/src/gromacs/analysisdata/modules/lifetime.cpp b/src/gromacs/analysisdata/modules/lifetime.cpp
new file mode 100644 (file)
index 0000000..9426ffe
--- /dev/null
@@ -0,0 +1,275 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Implements gmx::AnalysisDataLifetimeModule.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_analysisdata
+ */
+#include "lifetime.h"
+
+#include <cmath>
+
+#include <deque>
+#include <vector>
+
+#include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datastorage.h"
+
+namespace gmx
+{
+
+/********************************************************************
+ * AnalysisDataLifetimeModule
+ */
+
+/*! \internal \brief
+ * Private implementation class for AnalysisDataLifetimeModule.
+ *
+ * \ingroup module_analysisdata
+ */
+class AnalysisDataLifetimeModule::Impl
+{
+    public:
+        //! Container type for storing a histogram during the calculation.
+        typedef std::deque<int> LifetimeHistogram;
+
+        //! Initializes the implementation class with empty/default values.
+        Impl() : firstx_(0.0), lastx_(0.0), frameCount_(0), bCumulative_(false)
+        {
+        }
+
+        /*! \brief
+         * Increments a lifetime histogram with a single lifetime.
+         *
+         * \param[in] dataSet   Index of the histogram to increment.
+         * \param[in] lifetime  Lifetime to add to the histogram.
+         */
+        void addLifetime(int dataSet, int lifetime)
+        {
+            if (lifetime > 0)
+            {
+                LifetimeHistogram &histogram = lifetimeHistograms_[dataSet];
+                if (histogram.size() < static_cast<unsigned>(lifetime))
+                {
+                    histogram.resize(lifetime, 0);
+                }
+                ++histogram[lifetime - 1];
+            }
+        }
+
+        //! X value of the first frame (used for determining output spacing).
+        real                            firstx_;
+        //! X value of the last frame (used for determining output spacing).
+        real                            lastx_;
+        //! Total number of frames (used for normalization and output spacing).
+        int                             frameCount_;
+        //! Whether to add subintervals of longer intervals explicitly.
+        bool                            bCumulative_;
+        /*! \brief
+         * Length of current continuously present interval for each data column.
+         *
+         * While frame N has been processed, stores the length of an interval
+         * for each data column where that column has been continuously present
+         * up to and including frame N.
+         */
+        std::vector<std::vector<int> >  currentLifetimes_;
+        /*! \brief
+         * Accumulated lifetime histograms for each data set.
+         */
+        std::vector<LifetimeHistogram>  lifetimeHistograms_;
+};
+
+AnalysisDataLifetimeModule::AnalysisDataLifetimeModule()
+    : impl_(new Impl())
+{
+}
+
+AnalysisDataLifetimeModule::~AnalysisDataLifetimeModule()
+{
+}
+
+void AnalysisDataLifetimeModule::setCumulative(bool bCumulative)
+{
+    impl_->bCumulative_ = bCumulative;
+}
+
+int AnalysisDataLifetimeModule::flags() const
+{
+    return efAllowMulticolumn | efAllowMissing | efAllowMultipleDataSets;
+}
+
+void
+AnalysisDataLifetimeModule::dataStarted(AbstractAnalysisData *data)
+{
+    impl_->currentLifetimes_.reserve(data->dataSetCount());
+    impl_->lifetimeHistograms_.reserve(data->dataSetCount());
+    for (int i = 0; i < data->dataSetCount(); ++i)
+    {
+        impl_->currentLifetimes_.push_back(std::vector<int>(data->columnCount(i), 0));
+        impl_->lifetimeHistograms_.push_back(std::deque<int>());
+    }
+}
+
+void
+AnalysisDataLifetimeModule::frameStarted(const AnalysisDataFrameHeader &header)
+{
+    if (header.index() == 0)
+    {
+        impl_->firstx_ = header.x();
+    }
+    impl_->lastx_ = header.x();
+    ++impl_->frameCount_;
+    // TODO: Check the input for even spacing.
+}
+
+void
+AnalysisDataLifetimeModule::pointsAdded(const AnalysisDataPointSetRef &points)
+{
+    const int dataSet = points.dataSetIndex();
+    // This assumption is strictly not necessary, but this is how the
+    // framework works currently, and makes the code below simpler.
+    GMX_ASSERT(points.firstColumn() == 0
+               && points.lastColumn() == static_cast<int>(impl_->currentLifetimes_[dataSet].size()) - 1,
+               "Point set should cover all columns");
+    for (int i = 0; i < points.columnCount(); ++i)
+    {
+        // TODO: Perhaps add control over how this is determined?
+        const bool bPresent = points.present(i) && points.y(i) > 0.0;
+        if (bPresent)
+        {
+            ++impl_->currentLifetimes_[dataSet][i];
+        }
+        else if (impl_->currentLifetimes_[dataSet][i] > 0)
+        {
+            impl_->addLifetime(dataSet, impl_->currentLifetimes_[dataSet][i]);
+            impl_->currentLifetimes_[dataSet][i] = 0;
+        }
+    }
+}
+
+void
+AnalysisDataLifetimeModule::frameFinished(const AnalysisDataFrameHeader & /*header*/)
+{
+}
+
+void
+AnalysisDataLifetimeModule::dataFinished()
+{
+    // Need to process the elements present in the last frame explicitly.
+    for (size_t i = 0; i < impl_->currentLifetimes_.size(); ++i)
+    {
+        for (size_t j = 0; j < impl_->currentLifetimes_[i].size(); ++j)
+        {
+            impl_->addLifetime(i, impl_->currentLifetimes_[i][j]);
+        }
+    }
+    impl_->currentLifetimes_.clear();
+
+    if (impl_->bCumulative_)
+    {
+        // Sum up subintervals of longer intervals into the histograms
+        // if explicitly requested.
+        std::vector<Impl::LifetimeHistogram>::iterator histogram;
+        for (histogram  = impl_->lifetimeHistograms_.begin();
+             histogram != impl_->lifetimeHistograms_.end();
+             ++histogram)
+        {
+            Impl::LifetimeHistogram::iterator shorter, longer;
+            for (shorter = histogram->begin(); shorter != histogram->end(); ++shorter)
+            {
+                int subIntervalCount = 2;
+                for (longer = shorter + 1; longer != histogram->end();
+                     ++longer, ++subIntervalCount)
+                {
+                    // Interval of length shorter contains (longer - shorter + 1)
+                    // continuous intervals of length longer.
+                    *shorter += subIntervalCount * (*longer);
+                }
+            }
+        }
+    }
+
+    // X spacing is determined by averaging from the first and last frame
+    // instead of first two frames to avoid rounding issues.
+    const real spacing =
+        (impl_->frameCount_ > 1)
+        ? (impl_->lastx_ - impl_->firstx_) / (impl_->frameCount_ - 1)
+        : 0.0;
+    setXAxis(0.0, spacing);
+
+    // Determine output dimensionality to cover all the histograms.
+    setColumnCount(impl_->lifetimeHistograms_.size());
+    std::vector<Impl::LifetimeHistogram>::const_iterator histogram;
+    size_t maxLifetime = 1;
+    for (histogram  = impl_->lifetimeHistograms_.begin();
+         histogram != impl_->lifetimeHistograms_.end();
+         ++histogram)
+    {
+        maxLifetime = std::max(maxLifetime, histogram->size());
+    }
+    setRowCount(maxLifetime);
+
+    // Fill up the output data from the histograms.
+    allocateValues();
+    int column = 0;
+    for (histogram  = impl_->lifetimeHistograms_.begin();
+         histogram != impl_->lifetimeHistograms_.end();
+         ++histogram, ++column)
+    {
+        int row = 0;
+        Impl::LifetimeHistogram::const_iterator i;
+        for (i = histogram->begin(); i != histogram->end(); ++i, ++row)
+        {
+            // Normalize by the number of frames, taking into account the
+            // length of the interval (interval of length N cannot start in
+            // N-1 last frames).  row is always smaller than frameCount_
+            // because of the histograms have at most frameCount_ entries.
+            const real normalized = *i / static_cast<real>(impl_->frameCount_ - row);
+            value(row, column).setValue(normalized);
+        }
+        // Pad the rest of the histogram with zeros to match the longest
+        // histogram.
+        for (; row < rowCount(); ++row)
+        {
+            value(row, column).setValue(0.0);
+        }
+    }
+    impl_->lifetimeHistograms_.clear();
+    valuesReady();
+}
+
+} // namespace gmx
diff --git a/src/gromacs/analysisdata/modules/lifetime.h b/src/gromacs/analysisdata/modules/lifetime.h
new file mode 100644 (file)
index 0000000..4e225dc
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \file
+ * \brief
+ * Declares gmx::AnalysisDataLifetimeModule.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \inpublicapi
+ * \ingroup module_analysisdata
+ */
+#ifndef GMX_ANALYSISDATA_MODULES_LIFETIME_H
+#define GMX_ANALYSISDATA_MODULES_LIFETIME_H
+
+#include "../arraydata.h"
+#include "../datamodule.h"
+#include "../../utility/common.h"
+
+namespace gmx
+{
+
+/*! \brief
+ * Data module for computing lifetime histograms for columns in input data.
+ *
+ * The input data set is treated as a boolean array: each value that is present
+ * (AnalysisDataValue::isPresent() returns true) and is >0 is treated as
+ * present, other values are treated as absent.
+ * For each input data set, analyzes the columns to identify the intervals
+ * where a column is continuously present.
+ * Produces a histogram from the lengths of these intervals.
+ * Input data should have frames with evenly spaced x values.
+ *
+ * Output data contains one column for each data set in the input data.
+ * This column gives the lifetime histogram for the corresponding data set.
+ * x axis in the output is spaced the same as in the input data, and extends
+ * as long as required to cover all the histograms.
+ * Histograms are padded with zeros as required to be of the same length.
+ * setCumulative() can be used to alter the handling of subintervals in the
+ * output histogram.
+ *
+ * The output data becomes available only after the input data has been
+ * finished.
+ *
+ * \inpublicapi
+ * \ingroup module_analysisdata
+ */
+class AnalysisDataLifetimeModule : public AbstractAnalysisArrayData,
+                                   public AnalysisDataModuleInterface
+{
+    public:
+        AnalysisDataLifetimeModule();
+        virtual ~AnalysisDataLifetimeModule();
+
+        /*! \brief
+         * Sets a cumulative histogram mode.
+         *
+         * \param[in] bCumulative If true, all subintervals of a long
+         *   interval are also explicitly added into the histogram.
+         *
+         * Does not throw.
+         */
+        void setCumulative(bool bCumulative);
+
+        virtual int flags() const;
+
+        virtual void dataStarted(AbstractAnalysisData *data);
+        virtual void frameStarted(const AnalysisDataFrameHeader &header);
+        virtual void pointsAdded(const AnalysisDataPointSetRef &points);
+        virtual void frameFinished(const AnalysisDataFrameHeader &header);
+        virtual void dataFinished();
+
+    private:
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+//! Smart pointer to manage an AnalysisDataLifetimeModule object.
+typedef boost::shared_ptr<AnalysisDataLifetimeModule>
+    AnalysisDataLifetimeModulePointer;
+
+} // namespace gmx
+
+#endif
index 1d607cce4c7ff8fdb2a188f9dfa25ae31bd07dd6..9c9bef91bab5c4e069567e9b774fe6b7ab58bcd7 100644 (file)
@@ -1,7 +1,7 @@
 #
 # This file is part of the GROMACS molecular simulation package.
 #
-# Copyright (c) 2011,2012, by the GROMACS development team, led by
+# Copyright (c) 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.
@@ -36,4 +36,5 @@ gmx_add_unit_test(AnalysisDataUnitTests analysisdata-test
                   analysisdata.cpp
                   arraydata.cpp
                   average.cpp
-                  histogram.cpp)
+                  histogram.cpp
+                  lifetime.cpp)
diff --git a/src/gromacs/analysisdata/tests/lifetime.cpp b/src/gromacs/analysisdata/tests/lifetime.cpp
new file mode 100644 (file)
index 0000000..fb7f8e1
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * This file is part of the GROMACS molecular simulation package.
+ *
+ * Copyright (c) 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.
+ *
+ * GROMACS is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; either version 2.1
+ * of the License, or (at your option) any later version.
+ *
+ * GROMACS is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GROMACS; if not, see
+ * http://www.gnu.org/licenses, or write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
+ *
+ * If you want to redistribute modifications to GROMACS, please
+ * consider that scientific software is very special. Version
+ * control is crucial - bugs must be traceable. We will be happy to
+ * consider code for inclusion in the official distribution, but
+ * derived work must not be called official GROMACS. Details are found
+ * in the README & COPYING files - if they are missing, get the
+ * official version at http://www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the research papers on the package. Check out http://www.gromacs.org.
+ */
+/*! \internal \file
+ * \brief
+ * Tests for functionality of analysis data lifetime module.
+ *
+ * These tests check that gmx::AnalysisDataLifetimeModule computes lifetimes
+ * correctly with simple input data.
+ * Checking is done using gmx::test::AnalysisDataTestFixture and reference
+ * data.  Also the input data is written to the reference data to catch
+ * out-of-date reference.
+ *
+ * \author Teemu Murtola <teemu.murtola@gmail.com>
+ * \ingroup module_analysisdata
+ */
+#include <gtest/gtest.h>
+
+#include "gromacs/analysisdata/analysisdata.h"
+#include "gromacs/analysisdata/modules/lifetime.h"
+
+#include "testutils/datatest.h"
+#include "testutils/testasserts.h"
+
+using gmx::test::AnalysisDataTestInput;
+
+namespace
+{
+
+// Simple input data for gmx::AnalysisDataLifetimeModule tests.
+class SimpleInputData
+{
+    public:
+        static const AnalysisDataTestInput &get()
+        {
+            static SimpleInputData singleton;
+            return singleton.data_;
+        }
+
+        SimpleInputData() : data_(1, false)
+        {
+            data_.setColumnCount(0, 3);
+            data_.addFrameWithValues(1.0,  1.0, 1.0, 1.0);
+            data_.addFrameWithValues(2.0,  1.0, 0.0, 1.0);
+            data_.addFrameWithValues(3.0,  0.0, 1.0, 1.0);
+        }
+
+    private:
+        AnalysisDataTestInput  data_;
+};
+
+// Input data with multiple data sets for gmx::AnalysisDataLifetimeModule tests.
+class MultiDataSetInputData
+{
+    public:
+        static const AnalysisDataTestInput &get()
+        {
+            static MultiDataSetInputData singleton;
+            return singleton.data_;
+        }
+
+        MultiDataSetInputData() : data_(2, false)
+        {
+            using gmx::test::AnalysisDataTestInputFrame;
+            data_.setColumnCount(0, 2);
+            data_.setColumnCount(1, 2);
+            AnalysisDataTestInputFrame &frame1 = data_.addFrame(1.0);
+            frame1.addPointSetWithValues(0, 0, 1.0, 1.0);
+            frame1.addPointSetWithValues(1, 0, 0.0, 0.0);
+            AnalysisDataTestInputFrame &frame2 = data_.addFrame(2.0);
+            frame2.addPointSetWithValues(0, 0, 1.0, 0.0);
+            frame2.addPointSetWithValues(1, 0, 1.0, 0.0);
+            AnalysisDataTestInputFrame &frame3 = data_.addFrame(3.0);
+            frame3.addPointSetWithValues(0, 0, 1.0, 0.0);
+            frame3.addPointSetWithValues(1, 0, 1.0, 1.0);
+        }
+
+    private:
+        AnalysisDataTestInput  data_;
+};
+
+
+/********************************************************************
+ * Tests for gmx::AnalysisDataLifetimeModule.
+ */
+
+//! Test fixture for gmx::AnalysisDataLifetimeModule.
+typedef gmx::test::AnalysisDataTestFixture LifetimeModuleTest;
+
+TEST_F(LifetimeModuleTest, BasicTest)
+{
+    const AnalysisDataTestInput &input = SimpleInputData::get();
+    gmx::AnalysisData            data;
+    ASSERT_NO_THROW_GMX(setupDataObject(input, &data));
+
+    gmx::AnalysisDataLifetimeModulePointer module(
+            new gmx::AnalysisDataLifetimeModule);
+    module->setCumulative(false);
+    data.addModule(module);
+
+    ASSERT_NO_THROW_GMX(addStaticCheckerModule(input, &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("InputData", &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("Lifetime", module.get()));
+    ASSERT_NO_THROW_GMX(presentAllData(input, &data));
+}
+
+TEST_F(LifetimeModuleTest, CumulativeTest)
+{
+    const AnalysisDataTestInput &input = SimpleInputData::get();
+    gmx::AnalysisData            data;
+    ASSERT_NO_THROW_GMX(setupDataObject(input, &data));
+
+    gmx::AnalysisDataLifetimeModulePointer module(
+            new gmx::AnalysisDataLifetimeModule);
+    module->setCumulative(true);
+    data.addModule(module);
+
+    ASSERT_NO_THROW_GMX(addStaticCheckerModule(input, &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("InputData", &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("Lifetime", module.get()));
+    ASSERT_NO_THROW_GMX(presentAllData(input, &data));
+}
+
+TEST_F(LifetimeModuleTest, HandlesMultipleDataSets)
+{
+    const AnalysisDataTestInput &input = MultiDataSetInputData::get();
+    gmx::AnalysisData            data;
+    ASSERT_NO_THROW_GMX(setupDataObject(input, &data));
+
+    gmx::AnalysisDataLifetimeModulePointer module(
+            new gmx::AnalysisDataLifetimeModule);
+    module->setCumulative(false);
+    data.addModule(module);
+
+    ASSERT_NO_THROW_GMX(addStaticCheckerModule(input, &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("InputData", &data));
+    ASSERT_NO_THROW_GMX(addReferenceCheckerModule("Lifetime", module.get()));
+    ASSERT_NO_THROW_GMX(presentAllData(input, &data));
+}
+
+} // namespace
diff --git a/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_BasicTest.xml b/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_BasicTest.xml
new file mode 100644 (file)
index 0000000..8196671
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <AnalysisData Name="InputData">
+    <DataFrame Name="Frame0">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+  <AnalysisData Name="Lifetime">
+    <DataFrame Name="Frame0">
+      <Real Name="X">0.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">0.666667</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">0.500000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+</ReferenceData>
diff --git a/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_CumulativeTest.xml b/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_CumulativeTest.xml
new file mode 100644 (file)
index 0000000..bda456c
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <AnalysisData Name="InputData">
+    <DataFrame Name="Frame0">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <DataValues>
+        <Int Name="Count">3</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+  <AnalysisData Name="Lifetime">
+    <DataFrame Name="Frame0">
+      <Real Name="X">0.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">2.333333</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">1.500000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+</ReferenceData>
diff --git a/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_HandlesMultipleDataSets.xml b/src/gromacs/analysisdata/tests/refdata/LifetimeModuleTest_HandlesMultipleDataSets.xml
new file mode 100644 (file)
index 0000000..913f42c
--- /dev/null
@@ -0,0 +1,113 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <AnalysisData Name="InputData">
+    <DataFrame Name="Frame0">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">0</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">1</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">0</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+      </DataValues>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">0</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+      </DataValues>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <Int Name="DataSet">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+  <AnalysisData Name="Lifetime">
+    <DataFrame Name="Frame0">
+      <Real Name="X">0.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <DataValue>
+          <Real Name="Value">0.333333</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.333333</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">1.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.500000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">2.000000</Real>
+      <DataValues>
+        <Int Name="Count">2</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+        </DataValue>
+      </DataValues>
+    </DataFrame>
+  </AnalysisData>
+</ReferenceData>
index 6e35c2d7d7cbb3a6d4b762ee96fe7aa864f32c63..c18667934fbe4fa8d4137f00c9af35ccbedfaca5 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.
@@ -338,6 +338,7 @@ AbstractAnalysisData &TrajectoryAnalysisModule::datasetFromName(const char *name
 void TrajectoryAnalysisModule::registerBasicDataset(AbstractAnalysisData *data,
                                                     const char           *name)
 {
+    GMX_RELEASE_ASSERT(data != NULL, "Attempting to register NULL data");
     // TODO: Strong exception safety should be possible to implement.
     GMX_RELEASE_ASSERT(impl_->datasets_.find(name) == impl_->datasets_.end(),
                        "Duplicate data set name registered");
index 2cc41c1c733db6f90528d98dee76b2cd0f454fcf..56137bce3a6bc64c577abdbff42df443e608d5e7 100644 (file)
@@ -260,7 +260,9 @@ Select::Select()
     : TrajectoryAnalysisModule(name, shortDescription),
       selOpt_(NULL),
       bDump_(false), bTotNorm_(false), bFracNorm_(false), bResInd_(false),
-      top_(NULL), occupancyModule_(new AnalysisDataAverageModule())
+      bCumulativeLifetimes_(true), top_(NULL),
+      occupancyModule_(new AnalysisDataAverageModule()),
+      lifetimeModule_(new AnalysisDataLifetimeModule())
 {
     registerAnalysisDataset(&sdata_, "size");
     registerAnalysisDataset(&cdata_, "cfrac");
@@ -270,6 +272,7 @@ Select::Select()
     registerAnalysisDataset(&mdata_, "mask");
     occupancyModule_->setXAxis(1.0, 1.0);
     registerBasicDataset(occupancyModule_.get(), "occupancy");
+    registerBasicDataset(lifetimeModule_.get(), "lifetime");
 }
 
 
@@ -338,8 +341,13 @@ Select::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
         "present, with [TT]maxsel[tt] all atoms possibly selected by the",
         "selection are present, and with [TT]selected[tt] only atoms that are",
         "selected at least in one frame are present.[PAR]",
-        "With [TT]-om[tt], [TT]-of[tt] and [TT]-ofpdb[tt], only one selection",
-        "can be provided. [TT]-om[tt] and [TT]-of[tt] only accept dynamic",
+        "With [TT]-olt[tt], a histogram is produced that shows the number of",
+        "selected positions as a function of the time the position was",
+        "continuously selected. [TT]-cumlt[tt] can be used to control whether",
+        "subintervals of longer intervals are included in the histogram.[PAR]",
+        "With [TT]-om[tt], [TT]-of[tt], [TT]-olt[tt], and [TT]-ofpdb[tt],",
+        "only one selection can be provided.",
+        "[TT]-om[tt], [TT]-of[tt], and [TT]-olt[tt] only accept dynamic",
         "selections."
     };
 
@@ -366,6 +374,9 @@ Select::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
     options->addOption(FileNameOption("ofpdb").filetype(eftPDB).outputFile()
                            .store(&fnPDB_).defaultBasename("occupancy")
                            .description("PDB file with occupied fraction for selected positions"));
+    options->addOption(FileNameOption("olt").filetype(eftPlot).outputFile()
+                           .store(&fnLifetime_).defaultBasename("lifetime")
+                           .description("Lifetime histogram"));
 
     selOpt_ = options->addOption(SelectionOption("select").storeVector(&sel_)
                                      .required().multiValue()
@@ -385,6 +396,8 @@ Select::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
     options->addOption(StringOption("pdbatoms").store(&pdbAtoms_)
                            .enumValue(cPDBAtomsEnum).defaultEnumIndex(0)
                            .description("Atoms to write with -ofpdb"));
+    options->addOption(BooleanOption("cumlt").store(&bCumulativeLifetimes_)
+                           .description("Cumulate subintervals of longer intervals in -olt"));
 }
 
 void
@@ -397,7 +410,8 @@ Select::optionsFinished(Options                     * /*options*/,
         settings->setFlag(TrajectoryAnalysisSettings::efUseTopX);
     }
     if ((!fnIndex_.empty() && bDump_)
-        || !fnMask_.empty() || !fnOccupancy_.empty() || !fnPDB_.empty())
+        || !fnMask_.empty() || !fnOccupancy_.empty() || !fnPDB_.empty()
+        || !fnLifetime_.empty())
     {
         selOpt_->setValueCount(1);
     }
@@ -407,10 +421,11 @@ void
 Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
                      const TopologyInformation        &top)
 {
-    if (!sel_[0].isDynamic() && (!fnMask_.empty() || !fnOccupancy_.empty()))
+    if (!sel_[0].isDynamic()
+        && (!fnMask_.empty() || !fnOccupancy_.empty() || !fnLifetime_.empty()))
     {
         GMX_THROW(InconsistentInputError(
-                          "-om or -of are not meaningful with a static selection"));
+                          "-om, -of, or -olt are not meaningful with a static selection"));
     }
     bResInd_ = (resNumberType_ == "index");
 
@@ -479,8 +494,11 @@ Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
         idata_.addModule(writer);
     }
 
+    // TODO: Write out the mask and lifetimes for all the selections.
     mdata_.setColumnCount(0, sel_[0].posCount());
     mdata_.addModule(occupancyModule_);
+    mdata_.addModule(lifetimeModule_);
+    lifetimeModule_->setCumulative(bCumulativeLifetimes_);
     if (!fnMask_.empty())
     {
         AnalysisDataPlotModulePointer plot(
@@ -504,6 +522,16 @@ Select::initAnalysis(const TrajectoryAnalysisSettings &settings,
         plot->setYLabel("Occupied fraction");
         occupancyModule_->addColumnModule(0, 1, plot);
     }
+    if (!fnLifetime_.empty())
+    {
+        AnalysisDataPlotModulePointer plot(
+                new AnalysisDataPlotModule(settings.plotSettings()));
+        plot->setFileName(fnLifetime_);
+        plot->setTitle("Lifetime histogram");
+        plot->setXAxisIsTime();
+        plot->setYLabel("Number of occurrences");
+        lifetimeModule_->addModule(plot);
+    }
 
     top_ = &top;
 }
index a7cdac9f287b90c1fb28de97b953791d04e2113d..22210a33e7488de8d006a4207dc9a6d7503acb76 100644 (file)
@@ -1,7 +1,7 @@
 /*
  * This file is part of the GROMACS molecular simulation package.
  *
- * Copyright (c) 2011,2012, by the GROMACS development team, led by
+ * Copyright (c) 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.
@@ -48,6 +48,7 @@
 #include "../analysismodule.h"
 #include "gromacs/analysisdata/analysisdata.h"
 #include "gromacs/analysisdata/modules/average.h"
+#include "gromacs/analysisdata/modules/lifetime.h"
 #include "gromacs/selection/selection.h"
 
 namespace gmx
@@ -81,30 +82,33 @@ class Select : public TrajectoryAnalysisModule
         virtual void writeOutput();
 
     private:
-        SelectionList                    sel_;
-        SelectionOptionInfo             *selOpt_;
-
-        std::string                      fnSize_;
-        std::string                      fnFrac_;
-        std::string                      fnIndex_;
-        std::string                      fnNdx_;
-        std::string                      fnMask_;
-        std::string                      fnOccupancy_;
-        std::string                      fnPDB_;
-        bool                             bDump_;
-        bool                             bTotNorm_;
-        bool                             bFracNorm_;
-        bool                             bResInd_;
-        std::string                      resNumberType_;
-        std::string                      pdbAtoms_;
-
-        const TopologyInformation       *top_;
-        std::vector<int>                 totsize_;
-        AnalysisData                     sdata_;
-        AnalysisData                     cdata_;
-        AnalysisData                     idata_;
-        AnalysisData                     mdata_;
-        AnalysisDataAverageModulePointer occupancyModule_;
+        SelectionList                       sel_;
+        SelectionOptionInfo                *selOpt_;
+
+        std::string                         fnSize_;
+        std::string                         fnFrac_;
+        std::string                         fnIndex_;
+        std::string                         fnNdx_;
+        std::string                         fnMask_;
+        std::string                         fnOccupancy_;
+        std::string                         fnPDB_;
+        std::string                         fnLifetime_;
+        bool                                bDump_;
+        bool                                bTotNorm_;
+        bool                                bFracNorm_;
+        bool                                bResInd_;
+        bool                                bCumulativeLifetimes_;
+        std::string                         resNumberType_;
+        std::string                         pdbAtoms_;
+
+        const TopologyInformation          *top_;
+        std::vector<int>                    totsize_;
+        AnalysisData                        sdata_;
+        AnalysisData                        cdata_;
+        AnalysisData                        idata_;
+        AnalysisData                        mdata_;
+        AnalysisDataAverageModulePointer    occupancyModule_;
+        AnalysisDataLifetimeModulePointer   lifetimeModule_;
 };
 
 } // namespace analysismodules
index 2854405eb8dd085d805435568224caaa937da9d3..962856f019ae31a6017749cbd66c0a0c86cabb7d 100644 (file)
         </DataValues>
       </DataFrame>
     </AnalysisData>
+    <AnalysisData Name="lifetime">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <DataValues>
+          <Int Name="Count">1</Int>
+          <DataValue>
+            <Real Name="Value">8.000000</Real>
+          </DataValue>
+        </DataValues>
+      </DataFrame>
+      <DataFrame Name="Frame1">
+        <Real Name="X">0.000000</Real>
+        <DataValues>
+          <Int Name="Count">1</Int>
+          <DataValue>
+            <Real Name="Value">3.000000</Real>
+          </DataValue>
+        </DataValues>
+      </DataFrame>
+    </AnalysisData>
     <AnalysisData Name="mask">
       <DataFrame Name="Frame0">
         <Real Name="X">0.000000</Real>