Clean up g_ana angle implementation.
authorTeemu Murtola <teemu.murtola@gmail.com>
Thu, 27 Sep 2012 19:13:18 +0000 (22:13 +0300)
committerTeemu Murtola <teemu.murtola@gmail.com>
Mon, 15 Oct 2012 15:13:40 +0000 (18:13 +0300)
- Remove -split1 and -split2 options, as the 'merge' selection keyword
  can be used for the same effect.
- Remove -multi option, as the same can be interpreted from the number
  of input selections (not currently supported, though).
- Change output options, such that averages and individual angles are
  written to different files, allowing the removal of -all option.
- Add tests.
- Add some TODO comments for things still missing.

Supporting changes:
- Add an analysis data module that computes per-frame averages and tests
  for the module.
- Also add tests with multipoint data for the average modules and fix an
  issue in mock_datamodule.cpp (now this is somewhat unrelated, but some
  intermediate version of this change was more tightly coupled to
  multipoint data).

Change-Id: I0c62c2c54a0b3a0ff269ea0c2d6150c31479f3eb

18 files changed:
src/gromacs/analysisdata/modules/average.cpp
src/gromacs/analysisdata/modules/average.h
src/gromacs/analysisdata/tests/average.cpp
src/gromacs/analysisdata/tests/refdata/AverageModuleTest_HandlesMultipointData.xml [new file with mode: 0644]
src/gromacs/analysisdata/tests/refdata/FrameAverageModuleTest_BasicTest.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/modules/angle.cpp
src/gromacs/trajectoryanalysis/modules/angle.h
src/gromacs/trajectoryanalysis/tests/CMakeLists.txt
src/gromacs/trajectoryanalysis/tests/angle.cpp [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/angle.gro [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesDihedrals.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesPlaneZAxisAngles.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesSimpleAngles.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPairAngles.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPlanePairAngles.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorSphereNormalZAxisAngles.xml [new file with mode: 0644]
src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorTimeZeroAngles.xml [new file with mode: 0644]
src/testutils/mock_datamodule.cpp

index 3fd117e7db0432e74dbe4515c51c47fbe09ec8fb..4b4658d9155f40ff48999407b2caff1048689c71 100644 (file)
  * \author Teemu Murtola <teemu.murtola@cbr.su.se>
  * \ingroup module_analysisdata
  */
-#include "gromacs/analysisdata/modules/average.h"
+#include "average.h"
 
 #include <cmath>
 
 #include "gromacs/analysisdata/dataframe.h"
+#include "gromacs/analysisdata/datastorage.h"
 
 namespace gmx
 {
 
+/********************************************************************
+ * AnalysisDataAverageModule
+ */
+
 AnalysisDataAverageModule::AnalysisDataAverageModule()
 {
     setColumnCount(2);
 }
 
-
 AnalysisDataAverageModule::~AnalysisDataAverageModule()
 {
 }
 
-
 int
 AnalysisDataAverageModule::flags() const
 {
     return efAllowMultipoint | efAllowMulticolumn | efAllowMissing;
 }
 
-
 void
 AnalysisDataAverageModule::dataStarted(AbstractAnalysisData *data)
 {
@@ -71,13 +73,11 @@ AnalysisDataAverageModule::dataStarted(AbstractAnalysisData *data)
     nsamples_.resize(nrows);
 }
 
-
 void
 AnalysisDataAverageModule::frameStarted(const AnalysisDataFrameHeader & /*header*/)
 {
 }
 
-
 void
 AnalysisDataAverageModule::pointsAdded(const AnalysisDataPointSetRef &points)
 {
@@ -94,13 +94,11 @@ AnalysisDataAverageModule::pointsAdded(const AnalysisDataPointSetRef &points)
     }
 }
 
-
 void
 AnalysisDataAverageModule::frameFinished(const AnalysisDataFrameHeader & /*header*/)
 {
 }
 
-
 void
 AnalysisDataAverageModule::dataFinished()
 {
@@ -114,18 +112,108 @@ AnalysisDataAverageModule::dataFinished()
     valuesReady();
 }
 
-
 real
 AnalysisDataAverageModule::average(int index) const
 {
     return value(index, 0);
 }
 
-
 real
 AnalysisDataAverageModule::stddev(int index) const
 {
     return value(index, 1);
 }
 
+
+/********************************************************************
+ * AnalysisDataFrameAverageModule
+ */
+
+class AnalysisDataFrameAverageModule::Impl
+{
+    public:
+        //! Storage implementation object.
+        AnalysisDataStorage     storage_;
+        //! Number of samples in a frame.
+        int                     sampleCount_;
+};
+
+AnalysisDataFrameAverageModule::AnalysisDataFrameAverageModule()
+    : impl_(new Impl())
+{
+    setColumnCount(1);
+}
+
+AnalysisDataFrameAverageModule::~AnalysisDataFrameAverageModule()
+{
+}
+
+int
+AnalysisDataFrameAverageModule::flags() const
+{
+    return efAllowMultipoint | efAllowMulticolumn | efAllowMissing;
+}
+
+void
+AnalysisDataFrameAverageModule::dataStarted(AbstractAnalysisData *data)
+{
+    notifyDataStart();
+    impl_->storage_.startDataStorage(this);
+}
+
+void
+AnalysisDataFrameAverageModule::frameStarted(const AnalysisDataFrameHeader &header)
+{
+    impl_->sampleCount_ = 0;
+    AnalysisDataStorageFrame &frame = impl_->storage_.startFrame(header);
+    frame.setValue(0, 0.0);
+}
+
+void
+AnalysisDataFrameAverageModule::pointsAdded(const AnalysisDataPointSetRef &points)
+{
+    AnalysisDataStorageFrame &frame =
+        impl_->storage_.currentFrame(points.frameIndex());
+    for (int i = 0; i < points.columnCount(); ++i)
+    {
+        if (points.present(i))
+        {
+            const real y = points.y(i);
+            frame.value(0) += y;
+            impl_->sampleCount_ += 1;
+        }
+    }
+}
+
+void
+AnalysisDataFrameAverageModule::frameFinished(const AnalysisDataFrameHeader &header)
+{
+    AnalysisDataStorageFrame &frame =
+        impl_->storage_.currentFrame(header.index());
+    const int samples = impl_->sampleCount_;
+    if (samples > 0)
+    {
+        frame.value(0) /= samples;
+    }
+    impl_->storage_.finishFrame(header.index());
+}
+
+void
+AnalysisDataFrameAverageModule::dataFinished()
+{
+    notifyDataFinish();
+}
+
+AnalysisDataFrameRef
+AnalysisDataFrameAverageModule::tryGetDataFrameInternal(int index) const
+{
+    return impl_->storage_.tryGetDataFrame(index);
+}
+
+bool
+AnalysisDataFrameAverageModule::requestStorageInternal(int nframes)
+{
+    return impl_->storage_.requestStorage(nframes);
+}
+
 } // namespace gmx
index 70a9552fc73cf09b190d83b7b4747578e54ab1cf..910d10cf475fd0ee186b56ed5487c3e133b6505c 100644 (file)
 
 #include <vector>
 
+#include "../abstractdata.h"
 #include "../arraydata.h"
 #include "../datamodule.h"
+#include "../../utility/common.h"
 
 namespace gmx
 {
@@ -94,6 +96,48 @@ class AnalysisDataAverageModule : public AbstractAnalysisArrayData,
 typedef boost::shared_ptr<AnalysisDataAverageModule>
         AnalysisDataAverageModulePointer;
 
+/*! \brief
+ * Data module for averaging of columns for each frame.
+ *
+ * Output data has the same number of frames as the input data, but only one
+ * column.
+ * Each frame in the output contains the average of the column values in the
+ * corresponding frame of the input data.
+ *
+ * Multipoint data and missing data points are both supported. The average
+ * is always calculated over all data points present in a column.
+ *
+ * \inpublicapi
+ * \ingroup module_analysisdata
+ */
+class AnalysisDataFrameAverageModule : public AbstractAnalysisData,
+                                       public AnalysisDataModuleInterface
+{
+    public:
+        AnalysisDataFrameAverageModule();
+        virtual ~AnalysisDataFrameAverageModule();
+
+        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:
+        virtual AnalysisDataFrameRef tryGetDataFrameInternal(int index) const;
+        virtual bool requestStorageInternal(int nframes);
+
+        class Impl;
+
+        PrivateImplPointer<Impl> impl_;
+};
+
+//! Smart pointer to manage an AnalysisDataFrameAverageModule object.
+typedef boost::shared_ptr<AnalysisDataFrameAverageModule>
+        AnalysisDataFrameAverageModulePointer;
+
 } // namespace gmx
 
 #endif
index fb410f8d519fb5d4baa6093c56802ecd6cfd69ed..4d53486c1e3e9a8956c67b9217b4a45beb6a150d 100644 (file)
  */
 /*! \internal \file
  * \brief
- * Tests for functionality of gmx::AnalysisDataAverageModule
+ * Tests for functionality of analysis data averaging modules.
  *
- * These tests check that gmx::AnalysisDataAverageModule computes averages
- * correctly with simple input data.
+ * These tests check that gmx::AnalysisDataAverageModule and
+ * gmx::AnalysisDataFrameAverageModule compute averages 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.
 namespace
 {
 
-/********************************************************************
- * Tests for gmx::AnalysisDataAverageModule.
- */
-
-//! Test fixture for gmx::AnalysisDataAverageModule.
-typedef gmx::test::AnalysisDataTestFixture AverageModuleTest;
-
 using gmx::test::END_OF_FRAME;
+using gmx::test::MPSTOP;
 //! Input data for gmx::AnalysisDataAverageModule tests.
 const real inputdata[] = {
     1.0,  0.0, 1.0, 2.0, END_OF_FRAME,
     2.0,  1.0, 1.0, 1.0, END_OF_FRAME,
     3.0,  2.0, 0.0, 0.0, END_OF_FRAME
 };
+//! Multipoint input data for gmx::AnalysisDataAverageModule tests.
+const real mpinputdata[] = {
+    1.0,  0.0, 1.0, 2.0, MPSTOP,
+          1.0, 0.0, MPSTOP,
+          2.0, END_OF_FRAME,
+    2.0,  1.0, 1.0, MPSTOP,
+          2.0, END_OF_FRAME,
+    3.0,  2.0, 0.0, 0.0, END_OF_FRAME
+};
+
+
+/********************************************************************
+ * Tests for gmx::AnalysisDataAverageModule.
+ */
+
+//! Test fixture for gmx::AnalysisDataAverageModule.
+typedef gmx::test::AnalysisDataTestFixture AverageModuleTest;
 
 TEST_F(AverageModuleTest, BasicTest)
 {
@@ -81,6 +93,22 @@ TEST_F(AverageModuleTest, BasicTest)
     ASSERT_NO_THROW(presentAllData(input, &data));
 }
 
+TEST_F(AverageModuleTest, HandlesMultipointData)
+{
+    gmx::test::AnalysisDataTestInput input(mpinputdata);
+    gmx::AnalysisData data;
+    data.setColumnCount(input.columnCount());
+    data.setMultipoint(true);
+    gmx::AnalysisDataAverageModulePointer module(
+            new gmx::AnalysisDataAverageModule);
+    data.addModule(module);
+
+    ASSERT_NO_THROW(addStaticCheckerModule(input, &data));
+    ASSERT_NO_THROW(addReferenceCheckerModule("InputData", &data));
+    ASSERT_NO_THROW(addReferenceCheckerModule("Average", module.get()));
+    ASSERT_NO_THROW(presentAllData(input, &data));
+}
+
 TEST_F(AverageModuleTest, CanCustomizeXAxis)
 {
     gmx::test::AnalysisDataTestInput input(inputdata);
@@ -96,4 +124,26 @@ TEST_F(AverageModuleTest, CanCustomizeXAxis)
     ASSERT_NO_THROW(presentAllData(input, &data));
 }
 
+/********************************************************************
+ * Tests for gmx::AnalysisDataFrameAverageModule.
+ */
+
+//! Test fixture for gmx::AnalysisDataFrameAverageModule.
+typedef gmx::test::AnalysisDataTestFixture FrameAverageModuleTest;
+
+TEST_F(FrameAverageModuleTest, BasicTest)
+{
+    gmx::test::AnalysisDataTestInput input(inputdata);
+    gmx::AnalysisData data;
+    data.setColumnCount(input.columnCount());
+    gmx::AnalysisDataFrameAverageModulePointer module(
+            new gmx::AnalysisDataFrameAverageModule);
+    data.addModule(module);
+
+    ASSERT_NO_THROW(addStaticCheckerModule(input, &data));
+    ASSERT_NO_THROW(addReferenceCheckerModule("InputData", &data));
+    ASSERT_NO_THROW(addReferenceCheckerModule("FrameAverage", module.get()));
+    ASSERT_NO_THROW(presentAllData(input, &data));
+}
+
 } // namespace
diff --git a/src/gromacs/analysisdata/tests/refdata/AverageModuleTest_HandlesMultipointData.xml b/src/gromacs/analysisdata/tests/refdata/AverageModuleTest_HandlesMultipointData.xml
new file mode 100644 (file)
index 0000000..d082711
--- /dev/null
@@ -0,0 +1,127 @@
+<?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>
+      <Sequence Name="Y">
+        <Int Name="Length">3</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+      <Sequence Name="Y">
+        <Int Name="Length">2</Int>
+        <Int Name="FirstColumn">0</Int>
+        <Int Name="LastColumn">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+      <Sequence Name="Y">
+        <Int Name="Length">1</Int>
+        <Int Name="FirstColumn">0</Int>
+        <Int Name="LastColumn">0</Int>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">2</Int>
+        <Int Name="FirstColumn">0</Int>
+        <Int Name="LastColumn">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+      <Sequence Name="Y">
+        <Int Name="Length">1</Int>
+        <Int Name="FirstColumn">0</Int>
+        <Int Name="LastColumn">0</Int>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">3</Int>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+  </AnalysisData>
+  <AnalysisData Name="Average">
+    <DataFrame Name="Frame0">
+      <Real Name="X">0.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">2</Int>
+        <DataValue>
+          <Real Name="Value">1.333333</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.745356</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">1.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">2</Int>
+        <DataValue>
+          <Real Name="Value">0.500000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.500000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">2.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">2</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+  </AnalysisData>
+</ReferenceData>
diff --git a/src/gromacs/analysisdata/tests/refdata/FrameAverageModuleTest_BasicTest.xml b/src/gromacs/analysisdata/tests/refdata/FrameAverageModuleTest_BasicTest.xml
new file mode 100644 (file)
index 0000000..359d012
--- /dev/null
@@ -0,0 +1,89 @@
+<?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>
+      <Sequence Name="Y">
+        <Int Name="Length">3</Int>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">3</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">3</Int>
+        <DataValue>
+          <Real Name="Value">2.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+        <DataValue>
+          <Real Name="Value">0.000000</Real>
+          <Real Name="Error">0.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+  </AnalysisData>
+  <AnalysisData Name="FrameAverage">
+    <DataFrame Name="Frame0">
+      <Real Name="X">1.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame1">
+      <Real Name="X">2.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">1</Int>
+        <DataValue>
+          <Real Name="Value">1.000000</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+    <DataFrame Name="Frame2">
+      <Real Name="X">3.000000</Real>
+      <Sequence Name="Y">
+        <Int Name="Length">1</Int>
+        <DataValue>
+          <Real Name="Value">0.666667</Real>
+        </DataValue>
+      </Sequence>
+    </DataFrame>
+  </AnalysisData>
+</ReferenceData>
index dd8c1f9b955c9e9caadcf7f3799d14024550aeec..e874674b210eb7185ebb818930524ac6ead4f5d2 100644 (file)
  */
 #include "angle.h"
 
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include "pbc.h"
-#include "vec.h"
+#include "gromacs/legacyheaders/pbc.h"
+#include "gromacs/legacyheaders/vec.h"
 
 #include "gromacs/analysisdata/analysisdata.h"
 #include "gromacs/analysisdata/modules/plot.h"
@@ -68,17 +64,22 @@ const char Angle::shortDescription[] =
 
 Angle::Angle()
     : TrajectoryAnalysisModule(name, shortDescription),
-      sel1info_(NULL), sel2info_(NULL),
-      bSplit1_(false), bSplit2_(false), bMulti_(false), bAll_(false),
-      bDumpDist_(false), natoms1_(0), natoms2_(0), vt0_(NULL)
+      sel1info_(NULL), sel2info_(NULL), natoms1_(0), natoms2_(0)
 {
-    registerAnalysisDataset(&data_, "angle");
+    averageModule_.reset(new AnalysisDataFrameAverageModule());
+    angles_.addModule(averageModule_);
+
+    registerAnalysisDataset(&angles_, "angle");
+    registerBasicDataset(averageModule_.get(), "average");
 }
 
 
 Angle::~Angle()
 {
-    delete[] vt0_;
+    for (size_t g = 0; g < vt0_.size(); ++g)
+    {
+        delete [] vt0_[g];
+    }
 }
 
 
@@ -97,31 +98,36 @@ Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
         "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
         "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
         "should not be specified.",
-        "In this case, one selection is required, and it should contain",
-        "triplets or quartets of positions that define the angles to be",
-        "calculated.",
-        "If [TT]-g1[tt] is not [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
-        "should not be [TT]none[tt], and the two options define two vectors",
-        "for the calculation. For vectors ([TT]vector[tt]), a selection with",
-        "pairs of positions is required, and for planes ([TT]plane[tt]),",
-        "triplets of positions are required.",
-        "If both vectors are specified by positions, the number of vectors",
-        "should be the same in both selections.",
-        "[TT]-g2 sphnorm[tt] requires a reference selection that defines",
-        "the center of the sphere.",
-        "[TT]-g2 z[tt] does not require any selection.[PAR]",
-        "With [TT]-split1[tt], the positions for [TT]-g1[tt] are specified",
-        "using N separate selections with M positions each, instead of the",
-        "default M*N positions in one selection.",
-        "[TT]-split2[tt] does the same for [TT]-g2[tt].[PAR]",
+        "In this case, [TT]-group1[tt] should specify one selection,",
+        "and it should contain triplets or quartets of positions that define",
+        "the angles to be calculated.[PAR]",
+        "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
+        "should specify a selection that has either pairs ([TT]vector[tt])",
+        "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
+        "set the endpoints of the vector, and for planes, the three positions",
+        "are used to calculate the normal of the plane. In both cases,",
+        "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
+        "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
+        "specify another set of vectors. Both selections should specify the",
+        "same number of vectors.[PAR]",
+        "With [TT]-g2 sphnorm[tt], [TT]-group2[tt] should specify a single",
+        "position that is the center of the sphere. The second vector is then",
+        "calculated as the vector from the center to the midpoint of the",
+        "positions specified by [TT]-group1[tt].[PAR]",
+        "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
+        "between the first vectors and the positive Z axis are calculated.[PAR]",
+        "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
+        "are calculated from the vectors as they are in the first frame.[PAR]",
         "There are two options for output:",
-        "[TT]-o[tt] writes an xvgr file with the time and the average angle",
+        "[TT]-oav[tt] writes an xvgr file with the time and the average angle",
         "for each frame.",
-        "With [TT]-all[tt], also the individual angles are written (only",
-        "supported for static selections).",
+        "[TT]-oall[tt] writes all the individual angles."
+        /* TODO: Consider if the dump option is necessary and how to best
+         * implement it.
         "[TT]-od[tt] can be used to dump all the individual angles,",
         "each on a separate line. This format is better suited for",
         "further processing, e.g., if angles from multiple runs are needed."
+        */
     };
     static const char *const cGroup1TypeEnum[] =
         { "angle", "dihedral", "vector", "plane", NULL };
@@ -130,12 +136,13 @@ Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
 
     options->setDescription(concatenateStrings(desc));
 
-    options->addOption(FileNameOption("o").filetype(eftPlot).outputFile()
-                           .store(&fnAngle_).defaultBasename("angle")
-                           .description("Computed angles"));
-    options->addOption(FileNameOption("od").filetype(eftPlot).outputFile()
-                           .store(&fnDump_).defaultBasename("angdump")
-                           .description("Individual angles on separate lines"));
+    options->addOption(FileNameOption("oav").filetype(eftPlot).outputFile()
+                           .store(&fnAverage_).defaultBasename("angaver")
+                           .description("Average angles as a function of time"));
+    options->addOption(FileNameOption("oall").filetype(eftPlot).outputFile()
+                           .store(&fnAll_).defaultBasename("angles")
+                           .description("All angles as a function of time"));
+    // TODO: Add histogram output.
 
     options->addOption(StringOption("g1").enumValue(cGroup1TypeEnum)
         .defaultEnumIndex(0).store(&g1type_)
@@ -143,22 +150,18 @@ Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
     options->addOption(StringOption("g2").enumValue(cGroup2TypeEnum)
         .defaultEnumIndex(0).store(&g2type_)
         .description("Type of second vector group"));
-    options->addOption(BooleanOption("split1").store(&bSplit1_)
-        .description("Each position of first group in separate selection"));
-    options->addOption(BooleanOption("split2").store(&bSplit2_)
-        .description("Each position of second group in separate selection"));
-    options->addOption(BooleanOption("multi").store(&bMulti_)
-        .description("Analyze multiple sets of angles/dihedrals"));
-    options->addOption(BooleanOption("all").store(&bAll_)
-        .description("Print individual angles together with the average"));
-    options->addOption(BooleanOption("dumpd").store(&bDumpDist_)
-        .description("Write also distances with -od"));
-
-    sel1info_ = options->addOption(SelectionOption("group1").multiValue()
-        .required().dynamicOnlyWhole().storeVector(&sel1_)
+
+    // TODO: Allow multiple angles to be computed in one invocation.
+    // Most of the code already supports it, but requires a solution for
+    // Redmine issue #1010.
+    // TODO: Consider what is the best way to support dynamic selections.
+    // Again, most of the code already supports it, but it needs to be
+    // considered how should -oall work, and additional checks should be added.
+    sel1info_ = options->addOption(SelectionOption("group1")
+        .required().onlyStatic().storeVector(&sel1_)
         .description("First analysis/vector selection"));
-    sel2info_ = options->addOption(SelectionOption("group2").multiValue()
-        .dynamicOnlyWhole().storeVector(&sel2_)
+    sel2info_ = options->addOption(SelectionOption("group2")
+        .onlyStatic().storeVector(&sel2_)
         .description("Second analysis/vector selection"));
 }
 
@@ -166,7 +169,6 @@ Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
 void
 Angle::optionsFinished(Options *options, TrajectoryAnalysisSettings *settings)
 {
-    // Validity checks.
     bool bSingle = (g1type_[0] == 'a' || g1type_[0] == 'd');
 
     if (bSingle && g2type_[0] != 'n')
@@ -184,28 +186,6 @@ Angle::optionsFinished(Options *options, TrajectoryAnalysisSettings *settings)
         GMX_THROW(InconsistentInputError("Should specify a second group (-g2) "
                                          "if the first group is not an angle or a dihedral"));
     }
-    if (bSingle && bDumpDist_)
-    {
-        GMX_THROW(InconsistentInputError("Cannot calculate distances with -g1 angle or dihedral"));
-        // bDumpDist_ = false;
-    }
-    if (bMulti_ && !bSingle)
-    {
-        GMX_THROW(InconsistentInputError("-mult can only be combined with -g1 angle or dihedral"));
-    }
-    if (bMulti_ && bSplit1_)
-    {
-        GMX_THROW(InconsistentInputError("-mult can not be combined with -split1"));
-    }
-    if (bMulti_ && bAll_)
-    {
-        GMX_THROW(InconsistentInputError("-mult and -all are mutually exclusive options"));
-    }
-
-    if (bAll_)
-    {
-        sel1info_->setOnlyStatic(true);
-    }
 
     // Set up the number of positions per angle.
     switch (g1type_[0])
@@ -232,15 +212,6 @@ Angle::optionsFinished(Options *options, TrajectoryAnalysisSettings *settings)
     {
         GMX_THROW(InconsistentInputError("Cannot provide a second selection (-group2) with -g2 t0 or z"));
     }
-
-    if (!bMulti_)
-    {
-        sel1info_->setValueCount(bSplit1_ ? natoms1_ : 1);
-    }
-    if (natoms2_ > 0)
-    {
-        sel2info_->setValueCount(bSplit2_ ? natoms2_ : 1);
-    }
 }
 
 
@@ -248,80 +219,38 @@ void
 Angle::checkSelections(const SelectionList &sel1,
                        const SelectionList &sel2) const
 {
-    if (bMulti_)
-    {
-        for (size_t g = 0; g < sel1.size(); ++g)
-        {
-            if (sel1[g].posCount() % natoms1_ != 0)
-            {
-                GMX_THROW(InconsistentInputError(formatString(
-                    "Number of positions in selection %d not divisible by %d",
-                    static_cast<int>(g + 1), natoms1_)));
-            }
-        }
-        return;
-    }
-
-    int na1 = sel1[0].posCount();
-    int na2 = (natoms2_ > 0) ? sel2[0].posCount() : 0;
-
-    if (!bSplit1_ && natoms1_ > 1 && na1 % natoms1_ != 0)
+    if (natoms2_ > 0 && sel1.size() != sel2.size())
     {
-        GMX_THROW(InconsistentInputError(formatString(
-            "Number of positions in the first group not divisible by %d",
-            natoms1_)));
-    }
-    if (!bSplit2_ && natoms2_ > 1 && na2 % natoms2_ != 0)
-    {
-        GMX_THROW(InconsistentInputError(formatString(
-            "Number of positions in the second group not divisible by %d",
-            natoms2_)));
+        GMX_THROW(InconsistentInputError(
+                    "-group1 and -group2 should specify the same number of selections"));
     }
 
-    if (bSplit1_)
+    for (size_t g = 0; g < sel1.size(); ++g)
     {
-        for (int g = 1; g < natoms1_; ++g)
+        int na1 = sel1[g].posCount();
+        int na2 = (natoms2_ > 0) ? sel2[g].posCount() : 0;
+        if (natoms1_ > 1 && na1 % natoms1_ != 0)
         {
-            if (sel1[g].posCount() != na1)
-            {
-                GMX_THROW(InconsistentInputError(
-                          "All selections in the first group should contain "
-                          "the same number of positions"));
-            }
+            GMX_THROW(InconsistentInputError(formatString(
+                "Number of positions in selection %d in the first group not divisible by %d",
+                static_cast<int>(g + 1), natoms1_)));
         }
-    }
-    else
-    {
-        na1 /= natoms1_;
-    }
-    if (natoms2_ > 1)
-    {
-        if (bSplit2_)
+        if (natoms2_ > 1 && na2 % natoms2_ != 0)
         {
-            for (int g = 1; g < natoms2_; ++g)
-            {
-                if (sel2[g].posCount() != na2)
-                {
-                    GMX_THROW(InconsistentInputError(
-                              "All selections in the second group should contain "
-                              "the same number of positions"));
-                }
-            }
+            GMX_THROW(InconsistentInputError(formatString(
+                "Number of positions in selection %d in the second group not divisible by %d",
+                static_cast<int>(g + 1), natoms2_)));
         }
-        else
+        if (natoms1_ > 0 && natoms2_ > 1 && na1 / natoms1_ != na2 / natoms2_)
         {
-            na2 /= natoms2_;
+            GMX_THROW(InconsistentInputError(
+                      "Number of vectors defined by the two groups are not the same"));
+        }
+        if (g2type_[0] == 's' && sel2[g].posCount() != 1)
+        {
+            GMX_THROW(InconsistentInputError(
+                      "The second group should contain a single position with -g2 sphnorm"));
         }
-    }
-    if (natoms1_ > 0 && natoms2_ > 1 && na1 != na2)
-    {
-        GMX_THROW(InconsistentInputError(
-                  "Number of vectors defined by the two groups are not the same"));
-    }
-    if (g2type_[0] == 's' && sel2[0].posCount() != 1)
-    {
-        GMX_THROW(InconsistentInputError(
-                  "The second group should contain a single position with -g2 sphnorm"));
     }
 }
 
@@ -332,67 +261,55 @@ Angle::initAnalysis(const TrajectoryAnalysisSettings &settings,
 {
     checkSelections(sel1_, sel2_);
 
-    if (bMulti_)
-    {
-        data_.setColumnCount(sel1_.size());
-    }
-    else if (bAll_)
+    angles_.setColumnCount(sel1_[0].posCount() / natoms1_);
+
+    if (g2type_ == "t0")
     {
-        int na = sel1_[0].posCount();
-        if (!bSplit1_)
+        vt0_.resize(sel1_.size());
+        for (size_t g = 0; g < sel1_.size(); ++g)
         {
-            na /= natoms1_;
+            vt0_[g] = new rvec[sel1_[g].posCount() / natoms1_];
         }
-        data_.setColumnCount(na + 1);
     }
-    else
+
+    if (!fnAverage_.empty())
     {
-        data_.setColumnCount(1);
+        AnalysisDataPlotModulePointer plotm(
+            new AnalysisDataPlotModule(settings.plotSettings()));
+        plotm->setFileName(fnAverage_);
+        plotm->setTitle("Average angle");
+        plotm->setXAxisIsTime();
+        plotm->setYLabel("Angle (degrees)");
+        // TODO: Add legends
+        averageModule_->addModule(plotm);
     }
 
-    if (g2type_ == "t0")
+    if (!fnAll_.empty())
     {
-        int na = sel1_[0].posCount();
-        if (!bSplit1_)
-        {
-            na /= natoms1_;
-        }
-        vt0_ = new rvec[na];
+        AnalysisDataPlotModulePointer plotm(
+            new AnalysisDataPlotModule(settings.plotSettings()));
+        plotm->setFileName(fnAll_);
+        plotm->setTitle("Angle");
+        plotm->setXAxisIsTime();
+        plotm->setYLabel("Angle (degrees)");
+        // TODO: Add legends? (there can be a massive amount of columns)
+        angles_.addModule(plotm);
     }
-
-    AnalysisDataPlotModulePointer plotm(
-        new AnalysisDataPlotModule(settings.plotSettings()));
-    plotm->setFileName(fnAngle_);
-    plotm->setTitle("Angle");
-    plotm->setXAxisIsTime();
-    plotm->setYLabel("Angle (degrees)");
-    data_.addModule(plotm);
 }
 
 
 //! Helper method to process selections into an array of coordinates.
 static void
-copy_pos(const SelectionList &sel, bool bSplit, int natoms,
-         int firstg, int first, rvec x[])
+copy_pos(const SelectionList &sel, int natoms, int g, int first, rvec x[])
 {
-    if (bSplit)
+    for (int k = 0; k < natoms; ++k)
     {
-        for (int k = 0; k < natoms; ++k)
-        {
-            copy_rvec(sel[firstg + k].position(first).x(), x[k]);
-        }
-    }
-    else
-    {
-        for (int k = 0; k < natoms; ++k)
-        {
-            copy_rvec(sel[firstg].position(first + k).x(), x[k]);
-        }
+        copy_rvec(sel[g].position(first + k).x(), x[k]);
     }
 }
 
 
-//! Helper method to calculate a vector from two or three positions..
+//! Helper method to calculate a vector from two or three positions.
 static void
 calc_vec(int natoms, rvec x[], t_pbc *pbc, rvec xout, rvec cout)
 {
@@ -438,42 +355,36 @@ void
 Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
                     TrajectoryAnalysisModuleData *pdata)
 {
-    AnalysisDataHandle       dh = pdata->dataHandle(data_);
+    AnalysisDataHandle       dh = pdata->dataHandle(angles_);
     const SelectionList     &sel1 = pdata->parallelSelections(sel1_);
     const SelectionList     &sel2 = pdata->parallelSelections(sel2_);
 
     checkSelections(sel1, sel2);
 
-    rvec  v1, v2;
-    rvec  c1, c2;
-    switch (g2type_[0])
-    {
-        case 'z':
-            clear_rvec(v2);
-            v2[ZZ] = 1.0;
-            clear_rvec(c2);
-            break;
-        case 's':
-            copy_rvec(sel2_[0].position(0).x(), c2);
-            break;
-    }
-
     dh.startFrame(frnr, fr.time);
 
-    int incr1 = bSplit1_ ? 1 : natoms1_;
-    int incr2 = bSplit2_ ? 1 : natoms2_;
-    int ngrps = bMulti_ ? sel1_.size() : 1;
-
-    for (int g = 0; g < ngrps; ++g)
+    for (size_t g = 0; g < sel1_.size(); ++g)
     {
-        real ave = 0.0;
-        int n = 0;
-        int i, j;
-        for (i = j = 0; i < sel1[g].posCount(); i += incr1)
+        rvec  v1, v2;
+        rvec  c1, c2;
+        switch (g2type_[0])
+        {
+            case 'z':
+                clear_rvec(v2);
+                v2[ZZ] = 1.0;
+                clear_rvec(c2);
+                break;
+            case 's':
+                copy_rvec(sel2_[g].position(0).x(), c2);
+                break;
+        }
+        for (int i = 0, j = 0, n = 0;
+             i < sel1[g].posCount();
+             i += natoms1_, j += natoms2_, ++n)
         {
             rvec x[4];
             real angle;
-            copy_pos(sel1, bSplit1_, natoms1_, g, i, x);
+            copy_pos(sel1, natoms1_, g, i, x);
             switch (g1type_[0])
             {
                 case 'a':
@@ -520,17 +431,16 @@ Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
                     {
                         case 'v':
                         case 'p':
-                            copy_pos(sel2, bSplit2_, natoms2_, 0, j, x);
+                            copy_pos(sel2, natoms2_, 0, j, x);
                             calc_vec(natoms2_, x, pbc, v2, c2);
-                            j += incr2;
                             break;
                         case 't':
                             // FIXME: This is not parallelizable.
                             if (frnr == 0)
                             {
-                                copy_rvec(v1, vt0_[n]);
+                                copy_rvec(v1, vt0_[g][n]);
                             }
-                            copy_rvec(vt0_[n], v2);
+                            copy_rvec(vt0_[g][n], v2);
                             break;
                         case 'z':
                             c1[XX] = c1[YY] = 0.0;
@@ -553,8 +463,8 @@ Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
                 default:
                     GMX_THROW(InternalError("invalid -g1 value"));
             }
-            angle *= RAD2DEG;
-            /* TODO: add support for -od and -dumpd 
+            /* TODO: Should we also calculate distances like g_sgangle?
+             * Could be better to leave that for a separate tool.
             real dist = 0.0;
             if (bDumpDist_)
             {
@@ -570,18 +480,8 @@ Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
                 }
             }
             */
-            if (bAll_)
-            {
-                dh.setPoint(n + 1, angle);
-            }
-            ave += angle;
-            ++n;
-        }
-        if (n > 0)
-        {
-            ave /= n;
+            dh.setPoint(n, angle * RAD2DEG);
         }
-        dh.setPoint(g, ave);
     }
     dh.finishFrame();
 }
index 6cf7479b07f7963c2bdb0a33f4aef2d8b1564a53..0ad1b9d6efd0ab05d72280d6f5179aae7555cc38 100644 (file)
 #define GMX_TRAJECTORYANALYSIS_MODULES_ANGLE_H
 
 #include <string>
+#include <vector>
 
 #include "../analysismodule.h"
 #include "gromacs/analysisdata/analysisdata.h"
+#include "gromacs/analysisdata/modules/average.h"
 #include "gromacs/selection/selection.h"
 
 namespace gmx
@@ -82,21 +84,18 @@ class Angle : public TrajectoryAnalysisModule
         SelectionList           sel2_;
         SelectionOptionInfo    *sel1info_;
         SelectionOptionInfo    *sel2info_;
-        std::string             fnAngle_;
-        std::string             fnDump_;
+        std::string             fnAverage_;
+        std::string             fnAll_;
 
         std::string             g1type_;
         std::string             g2type_;
-        bool                    bSplit1_;
-        bool                    bSplit2_;
-        bool                    bMulti_;
-        bool                    bAll_;
-        bool                    bDumpDist_;
 
-        AnalysisData            data_;
+        AnalysisData            angles_;
+        AnalysisDataFrameAverageModulePointer averageModule_;
         int                     natoms1_;
         int                     natoms2_;
-        rvec                   *vt0_;
+        // TODO: It is not possible to put rvec into a container.
+        std::vector<rvec *>     vt0_;
 
         // Copy and assign disallowed by base.
 };
index dc96c435de30b5dbc237b9c0408b7d1643b5db90..a12049f9397684ae106f6b0a7898872f61a6bf46 100644 (file)
@@ -1,5 +1,6 @@
 gmx_add_unit_test(TrajectoryAnalysisUnitTests trajectoryanalysis-test
                   moduletest.cpp
+                  angle.cpp
                   select.cpp)
 
 add_executable(test_selection test_selection.cpp)
diff --git a/src/gromacs/trajectoryanalysis/tests/angle.cpp b/src/gromacs/trajectoryanalysis/tests/angle.cpp
new file mode 100644 (file)
index 0000000..18d5a0e
--- /dev/null
@@ -0,0 +1,135 @@
+/*
+ *
+ *                This source code is part of
+ *
+ *                 G   R   O   M   A   C   S
+ *
+ *          GROningen MAchine for Chemical Simulations
+ *
+ * Written by David van der Spoel, Erik Lindahl, Berk Hess, and others.
+ * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
+ * Copyright (c) 2001-2009, The GROMACS development team,
+ * check out http://www.gromacs.org for more information.
+
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * If you want to redistribute modifications, 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 www.gromacs.org.
+ *
+ * To help us fund GROMACS development, we humbly ask that you cite
+ * the papers on the package - you can find them in the top README file.
+ *
+ * For more info, check our website at http://www.gromacs.org
+ */
+/*! \internal \file
+ * \brief
+ * Tests for functionality of the "angle" trajectory analysis module.
+ *
+ * \author Teemu Murtola <teemu.murtola@cbr.su.se>
+ * \ingroup module_trajectoryanalysis
+ */
+#include <gtest/gtest.h>
+
+#include "gromacs/trajectoryanalysis/modules/angle.h"
+
+#include "testutils/cmdlinetest.h"
+
+#include "moduletest.h"
+
+namespace
+{
+
+using gmx::test::CommandLine;
+
+/********************************************************************
+ * Tests for gmx::analysismodules::Angle.
+ */
+
+//! Test fixture for the angle analysis module.
+typedef gmx::test::TrajectoryAnalysisModuleTestFixture<gmx::analysismodules::Angle>
+        AngleModuleTest;
+
+TEST_F(AngleModuleTest, ComputesSimpleAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "angle", "-group1", "resname RA1 RA2 and name A1 A2 A3"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesDihedrals)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "dihedral", "-group1", "resname RD1 RD2 RD3 and name A1 A2 A3 A4"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesVectorPairAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "vector", "-group1", "resname RV1 RV2 and name A1 A2",
+        "-g2", "vector", "-group2", "resname RV3 RV4 and name A1 A2"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesVectorPlanePairAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "vector", "-group1", "resname RV1 RV2 and name A1 A2",
+        "-g2", "plane",  "-group2", "resname RP1 RP2 and name A1 A2 A3"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesPlaneZAxisAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "plane", "-group1", "resname RP1 RP2 and name A1 A2 A3",
+        "-g2", "z"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesVectorSphereNormalZAxisAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "vector",  "-group1", "resname RV1 RV2 and name A1 A2",
+        "-g2", "sphnorm", "-group2", "cog of resname RS"
+    };
+    setTopology("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+TEST_F(AngleModuleTest, ComputesVectorTimeZeroAngles)
+{
+    const char *const cmdline[] = {
+        "angle",
+        "-g1", "vector", "-group1", "resname RV1 RV2 RV3 RV4 and name A1 A2",
+        "-g2", "t0"
+    };
+    setTopology("angle.gro");
+    setTrajectory("angle.gro");
+    runTest(CommandLine::create(cmdline));
+}
+
+} // namespace
diff --git a/src/gromacs/trajectoryanalysis/tests/angle.gro b/src/gromacs/trajectoryanalysis/tests/angle.gro
new file mode 100644 (file)
index 0000000..2bd0e81
--- /dev/null
@@ -0,0 +1,72 @@
+Test system for different angles
+ 33
+    1RA1     A1    1   0.000   0.000   0.000
+    1RA1     A2    2   1.000   0.000   0.000
+    1RA1     A3    3   1.500   0.500   0.000
+    2RA2     A1    4   0.000   0.000   1.000
+    2RA2     A2    5   1.000   0.000   1.000
+    2RA2     A3    6   0.500   0.500   1.000
+    3RD1     A1    7   0.000   0.000   2.000
+    3RD1     A2    8   1.000   1.000   2.000
+    3RD1     A3    9   2.000   1.000   2.000
+    3RD1     A4   10   3.000   1.500   2.500
+    4RD2     A1   11   0.000   0.000   3.000
+    4RD2     A2   12   1.000   1.000   3.000
+    4RD2     A3   13   2.000   1.000   3.000
+    4RD2     A4   14   3.000   0.500   2.500
+    5RD3     A1   15   0.000   0.000   4.000
+    5RD3     A2   16   1.000   1.000   4.000
+    5RD3     A3   17   2.000   1.000   4.000
+    5RD3     A4   18   3.000   2.000   4.000
+    6RV1     A1   19   7.000   5.000   5.000
+    6RV1     A2   20   8.000   6.000   5.000
+    7RV2     A1   21   7.000   7.000   5.000
+    7RV2     A2   22   7.000   7.000   6.000
+    8RV3     A1   23   6.000   5.000   7.000
+    8RV3     A2   24   6.000   6.000   7.000
+    9RV4     A1   25   8.000   8.000   7.000
+    9RV4     A2   26   8.000   9.000   7.000
+   10RP1     A1   27   4.000   4.000   8.000
+   10RP1     A2   28   4.000   5.000   8.000
+   10RP1     A3   29   5.000   4.000   8.000
+   11RP2     A1   30   6.000   6.000   8.000
+   11RP2     A2   31   7.000   6.000   8.000
+   11RP2     A3   32   6.000   7.000   9.000
+   12RS      CC   33   5.000   5.000   5.000
+  10.00000  10.00000  10.00000
+Test system for different angles
+ 33
+    1RA1     A1    1   0.000   0.000   0.000
+    1RA1     A2    2   1.000   0.000   0.000
+    1RA1     A3    3   1.500   0.500   0.000
+    2RA2     A1    4   0.000   0.000   1.000
+    2RA2     A2    5   1.000   0.000   1.000
+    2RA2     A3    6   0.500   0.500   1.000
+    3RD1     A1    7   0.000   0.000   2.000
+    3RD1     A2    8   1.000   1.000   2.000
+    3RD1     A3    9   2.000   1.000   2.000
+    3RD1     A4   10   3.000   1.500   2.500
+    4RD2     A1   11   0.000   0.000   3.000
+    4RD2     A2   12   1.000   1.000   3.000
+    4RD2     A3   13   2.000   1.000   3.000
+    4RD2     A4   14   3.000   0.500   2.500
+    5RD3     A1   15   0.000   0.000   4.000
+    5RD3     A2   16   1.000   1.000   4.000
+    5RD3     A3   17   2.000   1.000   4.000
+    5RD3     A4   18   3.000   2.000   4.000
+    6RV1     A1   19   6.000   4.000   4.000
+    6RV1     A2   20   7.000   5.000   4.000
+    7RV2     A1   21   7.000   7.000   5.000
+    7RV2     A2   22   8.000   7.000   6.000
+    8RV3     A1   23   6.000   5.000   7.000
+    8RV3     A2   24   6.000   4.000   7.000
+    9RV4     A1   25   8.000   8.000   7.000
+    9RV4     A2   26   9.000   8.000   7.000
+   10RP1     A1   27   4.000   4.000   8.000
+   10RP1     A2   28   4.000   5.000   8.000
+   10RP1     A3   29   5.000   4.000   8.000
+   11RP2     A1   30   6.000   6.000   8.000
+   11RP2     A2   31   7.000   6.000   8.000
+   11RP2     A3   32   6.000   7.000   9.000
+   12RS      CC   33   5.000   5.000   5.000
+  10.00000  10.00000  10.00000
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesDihedrals.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesDihedrals.xml
new file mode 100644 (file)
index 0000000..98261d2
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 dihedral -group1 'resname RD1 RD2 RD3 and name A1 A2 A3 A4'</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">3</Int>
+          <DataValue>
+            <Real Name="Value">-135.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">180.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">30.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesPlaneZAxisAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesPlaneZAxisAngles.xml
new file mode 100644 (file)
index 0000000..7729162
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 plane -group1 'resname RP1 RP2 and name A1 A2 A3' -g2 z</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">2</Int>
+          <DataValue>
+            <Real Name="Value">180.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">112.500000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesSimpleAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesSimpleAngles.xml
new file mode 100644 (file)
index 0000000..ef108bf
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 angle -group1 'resname RA1 RA2 and name A1 A2 A3'</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">2</Int>
+          <DataValue>
+            <Real Name="Value">135.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">90.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPairAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPairAngles.xml
new file mode 100644 (file)
index 0000000..1f85175
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 vector -group1 'resname RV1 RV2 and name A1 A2' -g2 vector -group2 'resname RV3 RV4 and name A1 A2'</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">2</Int>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">90.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">67.500000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPlanePairAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorPlanePairAngles.xml
new file mode 100644 (file)
index 0000000..2ca17c5
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 vector -group1 'resname RV1 RV2 and name A1 A2' -g2 plane -group2 'resname RP1 RP2 and name A1 A2 A3'</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">2</Int>
+          <DataValue>
+            <Real Name="Value">90.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">67.500000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorSphereNormalZAxisAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorSphereNormalZAxisAngles.xml
new file mode 100644 (file)
index 0000000..ec17c6c
--- /dev/null
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 vector -group1 'resname RV1 RV2 and name A1 A2' -g2 sphnorm -group2 'cog of resname RS'</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">2</Int>
+          <DataValue>
+            <Real Name="Value">33.690067</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">79.975014</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">56.832542</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
diff --git a/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorTimeZeroAngles.xml b/src/gromacs/trajectoryanalysis/tests/refdata/AngleModuleTest_ComputesVectorTimeZeroAngles.xml
new file mode 100644 (file)
index 0000000..cddcb73
--- /dev/null
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/xsl" href="referencedata.xsl"?>
+<ReferenceData>
+  <String Name="CommandLine">angle -g1 vector -group1 'resname RV1 RV2 RV3 RV4 and name A1 A2' -g2 t0</String>
+  <OutputData Name="Data">
+    <AnalysisData Name="angle">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">4</Int>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+      <DataFrame Name="Frame1">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">4</Int>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">45.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">180.000000</Real>
+          </DataValue>
+          <DataValue>
+            <Real Name="Value">90.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+    <AnalysisData Name="average">
+      <DataFrame Name="Frame0">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">0.000000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+      <DataFrame Name="Frame1">
+        <Real Name="X">0.000000</Real>
+        <Sequence Name="Y">
+          <Int Name="Length">1</Int>
+          <DataValue>
+            <Real Name="Value">78.750000</Real>
+          </DataValue>
+        </Sequence>
+      </DataFrame>
+    </AnalysisData>
+  </OutputData>
+</ReferenceData>
index 19ec7d2242ac824a493d0a972436ae94bb12c0dc..0d379fb6c7ff5aec922f448e9928a1f30462982f 100644 (file)
@@ -474,7 +474,7 @@ MockAnalysisDataModule::setupStaticCheck(const AnalysisDataTestInput &data,
             const AnalysisDataTestInputPointSet &points = frame.points(ps);
             EXPECT_CALL(*this, pointsAdded(_))
                 .WillOnce(Invoke(StaticDataPointsChecker(&frame, &points, 0,
-                                                         data.columnCount())));
+                                                         points.size())));
         }
         EXPECT_CALL(*this, frameFinished(_))
             .WillOnce(Invoke(StaticDataFrameHeaderChecker(&frame)));