2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013,2014,2015, by the GROMACS development team, led by
5 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6 * and including many others, as listed in the AUTHORS file in the
7 * top-level source directory and at http://www.gromacs.org.
9 * GROMACS is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU Lesser General Public License
11 * as published by the Free Software Foundation; either version 2.1
12 * of the License, or (at your option) any later version.
14 * GROMACS is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 * Lesser General Public License for more details.
19 * You should have received a copy of the GNU Lesser General Public
20 * License along with GROMACS; if not, see
21 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 * If you want to redistribute modifications to GROMACS, please
25 * consider that scientific software is very special. Version
26 * control is crucial - bugs must be traceable. We will be happy to
27 * consider code for inclusion in the official distribution, but
28 * derived work must not be called official GROMACS. Details are found
29 * in the README & COPYING files - if they are missing, get the
30 * official version at http://www.gromacs.org.
32 * To help us fund GROMACS development, we humbly ask that you cite
33 * the research papers on the package. Check out http://www.gromacs.org.
37 * Implements gmx::analysismodules::Angle.
39 * \author Teemu Murtola <teemu.murtola@gmail.com>
40 * \ingroup module_trajectoryanalysis
50 #include "gromacs/analysisdata/analysisdata.h"
51 #include "gromacs/analysisdata/modules/average.h"
52 #include "gromacs/analysisdata/modules/histogram.h"
53 #include "gromacs/analysisdata/modules/plot.h"
54 #include "gromacs/fileio/trx.h"
55 #include "gromacs/math/vec.h"
56 #include "gromacs/options/basicoptions.h"
57 #include "gromacs/options/filenameoption.h"
58 #include "gromacs/options/ioptionscontainer.h"
59 #include "gromacs/pbcutil/pbc.h"
60 #include "gromacs/selection/selection.h"
61 #include "gromacs/selection/selectionoption.h"
62 #include "gromacs/trajectoryanalysis/analysissettings.h"
63 #include "gromacs/utility/arrayref.h"
64 #include "gromacs/utility/exceptions.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/stringutil.h"
71 namespace analysismodules
77 /********************************************************************
82 * Helper to encapsulate logic for looping over input selections.
84 * This class provides two-dimensional iteration:
85 * - Over _angle groups_, corresponding to an input selection. If the input
86 * selection list contains a single selection, that selection gets used
87 * for all angle groups.
88 * - Within a group, over _values_, each consisting of a fixed number of
89 * selection positions. If there is only a single value within a selection,
90 * that value is returned over and over again.
91 * This transparently provides the semantics of using a single selection/vector
92 * to compute angles against multiple selections/vectors as described in the
95 * This class isn't perferctly self-contained and requires the caller to know
96 * some of the internals to use it properly, but it serves its purpose for this
97 * single analysis tool by simplifying the loops.
98 * Some methods have also been tailored to allow the caller to use it a bit
101 * \ingroup module_trajectoryanalysis
103 class AnglePositionIterator
107 * Creates an iterator to loop over input selection positions.
109 * \param[in] selections List of selections.
110 * \param[in] posCountPerValue Number of selection positions that
111 * constitute a single value for the iteration.
113 * If \p selections is empty, and/or \p posCountPerValue is zero, the
114 * iterator can still be advanced and hasValue()/hasSingleValue()
115 * called, but values cannot be accessed.
117 AnglePositionIterator(const SelectionList &selections,
118 int posCountPerValue)
119 : selections_(selections), posCountPerValue_(posCountPerValue),
120 currentSelection_(0), nextPosition_(0)
124 //! Advances the iterator to the next group of angles.
127 if (selections_.size() > 1)
133 //! Advances the iterator to the next angle in the current group.
136 if (!hasSingleValue())
138 nextPosition_ += posCountPerValue_;
143 * Returns whether this iterator represents any values.
145 * If the return value is `false`, only nextGroup(), nextValue() and
146 * hasSingleValue() are allowed to be called.
148 bool hasValue() const
150 return !selections_.empty();
153 * Returns whether the current selection only contains a single value.
155 * Returns `false` if hasValue() returns false, which allows cutting
156 * some corners in consistency checks.
158 bool hasSingleValue() const
160 return hasValue() && currentSelection().posCount() == posCountPerValue_;
162 //! Returns whether the current selection is dynamic.
163 bool isDynamic() const
165 return currentSelection().isDynamic();
168 * Returns whether positions in the current value are either all
169 * selected or all unselected.
171 bool allValuesConsistentlySelected() const
173 if (posCountPerValue_ <= 1)
177 const bool bSelected = currentPosition(0).selected();
178 for (int i = 1; i < posCountPerValue_; ++i)
180 if (currentPosition(i).selected() != bSelected)
188 * Returns whether positions in the current value are selected.
190 * Only works reliably if allValuesConsistentlySelected() returns
193 bool currentValuesSelected() const
195 return selections_.empty() || currentPosition(0).selected();
198 //! Returns the currently active selection.
199 const Selection ¤tSelection() const
201 GMX_ASSERT(currentSelection_ < static_cast<int>(selections_.size()),
202 "Accessing an invalid selection");
203 return selections_[currentSelection_];
205 //! Returns the `i`th position for the current value.
206 SelectionPosition currentPosition(int i) const
208 return currentSelection().position(nextPosition_ + i);
211 * Extracts all coordinates corresponding to the current value.
213 * \param[out] x Array to which the positions are extracted.
215 * \p x should contain at minimum the number of positions per value
216 * passed to the constructor.
218 void getCurrentPositions(rvec x[]) const
220 GMX_ASSERT(posCountPerValue_ > 0,
221 "Accessing positions for an invalid angle type");
222 GMX_ASSERT(nextPosition_ + posCountPerValue_ <= currentSelection().posCount(),
223 "Accessing an invalid position");
224 for (int i = 0; i < posCountPerValue_; ++i)
226 copy_rvec(currentPosition(i).x(), x[i]);
231 const SelectionList &selections_;
232 const int posCountPerValue_;
233 int currentSelection_;
236 GMX_DISALLOW_COPY_AND_ASSIGN(AnglePositionIterator);
239 /********************************************************************
240 * Actual analysis module
243 class Angle : public TrajectoryAnalysisModule
248 virtual void initOptions(IOptionsContainer *options,
249 TrajectoryAnalysisSettings *settings);
250 virtual void optionsFinished(TrajectoryAnalysisSettings *settings);
251 virtual void initAnalysis(const TrajectoryAnalysisSettings &settings,
252 const TopologyInformation &top);
254 virtual void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
255 TrajectoryAnalysisModuleData *pdata);
257 virtual void finishAnalysis(int nframes);
258 virtual void writeOutput();
261 void initFromSelections(const SelectionList &sel1,
262 const SelectionList &sel2);
263 void checkSelections(const SelectionList &sel1,
264 const SelectionList &sel2) const;
268 SelectionOptionInfo *sel1info_;
269 SelectionOptionInfo *sel2info_;
270 std::string fnAverage_;
272 std::string fnHistogram_;
278 AnalysisData angles_;
279 AnalysisDataFrameAverageModulePointer averageModule_;
280 AnalysisDataSimpleHistogramModulePointer histogramModule_;
282 std::vector<int> angleCount_;
285 std::vector<std::vector<RVec> > vt0_;
287 // Copy and assign disallowed by base.
291 : TrajectoryAnalysisModule(AngleInfo::name, AngleInfo::shortDescription),
292 sel1info_(NULL), sel2info_(NULL), binWidth_(1.0), natoms1_(0), natoms2_(0)
294 averageModule_.reset(new AnalysisDataFrameAverageModule());
295 angles_.addModule(averageModule_);
296 histogramModule_.reset(new AnalysisDataSimpleHistogramModule());
297 angles_.addModule(histogramModule_);
299 registerAnalysisDataset(&angles_, "angle");
300 registerBasicDataset(averageModule_.get(), "average");
301 registerBasicDataset(&histogramModule_->averager(), "histogram");
306 Angle::initOptions(IOptionsContainer *options, TrajectoryAnalysisSettings *settings)
308 static const char *const desc[] = {
309 "[THISMODULE] computes different types of angles between vectors.",
310 "It supports both vectors defined by two positions and normals of",
311 "planes defined by three positions.",
312 "The z axis or the local normal of a sphere can also be used as",
313 "one of the vectors.",
314 "There are also convenience options 'angle' and 'dihedral' for",
315 "calculating bond angles and dihedrals defined by three/four",
317 "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
318 "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
319 "should not be specified.",
320 "In this case, [TT]-group1[tt] should specify one or more selections,",
321 "and each should contain triplets or quartets of positions that define",
322 "the angles to be calculated.[PAR]",
323 "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
324 "should specify selections that contain either pairs ([TT]vector[tt])",
325 "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
326 "set the endpoints of the vector, and for planes, the three positions",
327 "are used to calculate the normal of the plane. In both cases,",
328 "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
329 "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
330 "specify another set of vectors. [TT]-group1[tt] and [TT]-group2[tt]",
331 "should specify the same number of selections. It is also allowed to",
332 "only have a single selection for one of the options, in which case",
333 "the same selection is used with each selection in the other group.",
334 "Similarly, for each selection in [TT]-group1[tt], the corresponding",
335 "selection in [TT]-group2[tt] should specify the same number of",
336 "vectors or a single vector. In the latter case, the angle is",
337 "calculated between that single vector and each vector from the other",
339 "With [TT]-g2 sphnorm[tt], each selection in [TT]-group2[tt] should",
340 "specify a single position that is the center of the sphere.",
341 "The second vector is calculated as the vector from the center to the",
342 "midpoint of the positions specified by [TT]-group1[tt].[PAR]",
343 "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
344 "between the first vectors and the positive Z axis are calculated.[PAR]",
345 "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
346 "are calculated from the vectors as they are in the first frame.[PAR]",
347 "There are three options for output:",
348 "[TT]-oav[tt] writes an xvg file with the time and the average angle",
350 "[TT]-oall[tt] writes all the individual angles.",
351 "[TT]-oh[tt] writes a histogram of the angles. The bin width can be",
352 "set with [TT]-binw[tt].",
353 "For [TT]-oav[tt] and [TT]-oh[tt], separate average/histogram is",
354 "computed for each selection in [TT]-group1[tt]."
356 static const char *const cGroup1TypeEnum[] =
357 { "angle", "dihedral", "vector", "plane" };
358 static const char *const cGroup2TypeEnum[] =
359 { "none", "vector", "plane", "t0", "z", "sphnorm" };
361 settings->setHelpText(desc);
363 options->addOption(FileNameOption("oav").filetype(eftPlot).outputFile()
364 .store(&fnAverage_).defaultBasename("angaver")
365 .description("Average angles as a function of time"));
366 options->addOption(FileNameOption("oall").filetype(eftPlot).outputFile()
367 .store(&fnAll_).defaultBasename("angles")
368 .description("All angles as a function of time"));
369 options->addOption(FileNameOption("oh").filetype(eftPlot).outputFile()
370 .store(&fnHistogram_).defaultBasename("anghist")
371 .description("Histogram of the angles"));
373 options->addOption(StringOption("g1").enumValue(cGroup1TypeEnum)
374 .defaultEnumIndex(0).store(&g1type_)
375 .description("Type of analysis/first vector group"));
376 options->addOption(StringOption("g2").enumValue(cGroup2TypeEnum)
377 .defaultEnumIndex(0).store(&g2type_)
378 .description("Type of second vector group"));
379 options->addOption(DoubleOption("binw").store(&binWidth_)
380 .description("Binwidth for -oh in degrees"));
382 sel1info_ = options->addOption(SelectionOption("group1")
383 .required().dynamicMask().storeVector(&sel1_)
385 .description("First analysis/vector selection"));
386 sel2info_ = options->addOption(SelectionOption("group2")
387 .dynamicMask().storeVector(&sel2_)
389 .description("Second analysis/vector selection"));
394 Angle::optionsFinished(TrajectoryAnalysisSettings * /* settings */)
396 const bool bSingle = (g1type_[0] == 'a' || g1type_[0] == 'd');
398 if (bSingle && g2type_[0] != 'n')
400 GMX_THROW(InconsistentInputError("Cannot use a second group (-g2) with "
401 "-g1 angle or dihedral"));
403 if (bSingle && sel2info_->isSet())
405 GMX_THROW(InconsistentInputError("Cannot provide a second selection "
406 "(-group2) with -g1 angle or dihedral"));
408 if (!bSingle && g2type_[0] == 'n')
410 GMX_THROW(InconsistentInputError("Should specify a second group (-g2) "
411 "if the first group is not an angle or a dihedral"));
414 // Set up the number of positions per angle.
417 case 'a': natoms1_ = 3; break;
418 case 'd': natoms1_ = 4; break;
419 case 'v': natoms1_ = 2; break;
420 case 'p': natoms1_ = 3; break;
422 GMX_THROW(InternalError("invalid -g1 value"));
426 case 'n': natoms2_ = 0; break;
427 case 'v': natoms2_ = 2; break;
428 case 'p': natoms2_ = 3; break;
429 case 't': natoms2_ = 0; break;
430 case 'z': natoms2_ = 0; break;
431 case 's': natoms2_ = 1; break;
433 GMX_THROW(InternalError("invalid -g2 value"));
435 if (natoms2_ == 0 && sel2info_->isSet())
437 GMX_THROW(InconsistentInputError("Cannot provide a second selection (-group2) with -g2 t0 or z"));
439 // TODO: If bSingle is not set, the second selection option should be
445 Angle::initFromSelections(const SelectionList &sel1,
446 const SelectionList &sel2)
448 const int angleGroups = std::max(sel1.size(), sel2.size());
449 const bool bHasSecondSelection = natoms2_ > 0;
451 if (bHasSecondSelection && sel1.size() != sel2.size()
452 && std::min(sel1.size(), sel2.size()) != 1)
454 GMX_THROW(InconsistentInputError(
455 "-group1 and -group2 should specify the same number of selections"));
458 AnglePositionIterator iter1(sel1, natoms1_);
459 AnglePositionIterator iter2(sel2, natoms2_);
460 for (int g = 0; g < angleGroups; ++g, iter1.nextGroup(), iter2.nextGroup())
462 const int posCount1 = iter1.currentSelection().posCount();
463 if (natoms1_ > 1 && posCount1 % natoms1_ != 0)
465 GMX_THROW(InconsistentInputError(formatString(
466 "Number of positions in selection %d in the first group not divisible by %d",
467 static_cast<int>(g + 1), natoms1_)));
469 const int angleCount1 = posCount1 / natoms1_;
470 int angleCount = angleCount1;
472 if (bHasSecondSelection)
474 const int posCount2 = iter2.currentSelection().posCount();
475 if (natoms2_ > 1 && posCount2 % natoms2_ != 0)
477 GMX_THROW(InconsistentInputError(formatString(
478 "Number of positions in selection %d in the second group not divisible by %d",
479 static_cast<int>(g + 1), natoms2_)));
481 if (g2type_[0] == 's' && posCount2 != 1)
483 GMX_THROW(InconsistentInputError(
484 "The second group should contain a single position with -g2 sphnorm"));
487 const int angleCount2 = posCount2 / natoms2_;
488 angleCount = std::max(angleCount1, angleCount2);
489 if (angleCount1 != angleCount2
490 && std::min(angleCount1, angleCount2) != 1)
492 GMX_THROW(InconsistentInputError(
493 "Number of vectors defined by the two groups are not the same"));
496 angleCount_.push_back(angleCount);
502 Angle::checkSelections(const SelectionList &sel1,
503 const SelectionList &sel2) const
505 AnglePositionIterator iter1(sel1, natoms1_);
506 AnglePositionIterator iter2(sel2, natoms2_);
507 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
509 if (iter1.isDynamic() || (iter2.hasValue() && iter2.isDynamic()))
511 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
514 if (!iter1.allValuesConsistentlySelected())
518 if (!iter2.allValuesConsistentlySelected())
522 if (angleCount_[g] > 1)
524 if (iter1.hasSingleValue() && !iter1.currentValuesSelected())
528 if (iter2.hasSingleValue() && !iter2.currentValuesSelected())
534 && (angleCount_[g] == 1
535 || (!iter1.hasSingleValue() && !iter2.hasSingleValue()))
536 && iter1.currentValuesSelected() != iter2.currentValuesSelected())
542 std::string message =
543 formatString("Dynamic selection %d does not select "
544 "a consistent set of angles over the frames",
545 static_cast<int>(g + 1));
546 GMX_THROW(InconsistentInputError(message));
555 Angle::initAnalysis(const TrajectoryAnalysisSettings &settings,
556 const TopologyInformation & /* top */)
558 initFromSelections(sel1_, sel2_);
560 // checkSelections() ensures that both selection lists have the same size.
561 angles_.setDataSetCount(angleCount_.size());
562 for (size_t i = 0; i < angleCount_.size(); ++i)
564 angles_.setColumnCount(i, angleCount_[i]);
566 double histogramMin = (g1type_ == "dihedral" ? -180.0 : 0);
567 histogramModule_->init(histogramFromRange(histogramMin, 180.0)
568 .binWidth(binWidth_).includeAll());
572 vt0_.resize(sel1_.size());
573 for (size_t g = 0; g < sel1_.size(); ++g)
575 vt0_[g].resize(sel1_[g].posCount() / natoms1_);
579 if (!fnAverage_.empty())
581 AnalysisDataPlotModulePointer plotm(
582 new AnalysisDataPlotModule(settings.plotSettings()));
583 plotm->setFileName(fnAverage_);
584 plotm->setTitle("Average angle");
585 plotm->setXAxisIsTime();
586 plotm->setYLabel("Angle (degrees)");
587 // TODO: Consider adding information about the second selection,
588 // and/or a subtitle describing what kind of angle this is.
589 for (size_t g = 0; g < sel1_.size(); ++g)
591 plotm->appendLegend(sel1_[g].name());
593 averageModule_->addModule(plotm);
598 AnalysisDataPlotModulePointer plotm(
599 new AnalysisDataPlotModule(settings.plotSettings()));
600 plotm->setFileName(fnAll_);
601 plotm->setTitle("Angle");
602 plotm->setXAxisIsTime();
603 plotm->setYLabel("Angle (degrees)");
604 // TODO: Add legends? (there can be a massive amount of columns)
605 angles_.addModule(plotm);
608 if (!fnHistogram_.empty())
610 AnalysisDataPlotModulePointer plotm(
611 new AnalysisDataPlotModule(settings.plotSettings()));
612 plotm->setFileName(fnHistogram_);
613 plotm->setTitle("Angle histogram");
614 plotm->setXLabel("Angle (degrees)");
615 plotm->setYLabel("Probability");
616 // TODO: Consider adding information about the second selection,
617 // and/or a subtitle describing what kind of angle this is.
618 for (size_t g = 0; g < sel1_.size(); ++g)
620 plotm->appendLegend(sel1_[g].name());
622 histogramModule_->averager().addModule(plotm);
627 //! Helper method to calculate a vector from two or three positions.
629 calc_vec(int natoms, rvec x[], t_pbc *pbc, rvec xout, rvec cout)
636 pbc_dx(pbc, x[1], x[0], xout);
640 rvec_sub(x[1], x[0], xout);
642 svmul(0.5, xout, cout);
643 rvec_add(x[0], cout, cout);
650 pbc_dx(pbc, x[1], x[0], v1);
651 pbc_dx(pbc, x[2], x[0], v2);
655 rvec_sub(x[1], x[0], v1);
656 rvec_sub(x[2], x[0], v2);
659 rvec_add(x[0], x[1], cout);
660 rvec_add(cout, x[2], cout);
661 svmul(1.0/3.0, cout, cout);
665 GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
671 Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
672 TrajectoryAnalysisModuleData *pdata)
674 AnalysisDataHandle dh = pdata->dataHandle(angles_);
675 const SelectionList &sel1 = pdata->parallelSelections(sel1_);
676 const SelectionList &sel2 = pdata->parallelSelections(sel2_);
678 checkSelections(sel1, sel2);
680 dh.startFrame(frnr, fr.time);
682 AnglePositionIterator iter1(sel1, natoms1_);
683 AnglePositionIterator iter2(sel2, natoms2_);
684 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
689 // v2 & c2 are conditionally set in the switch statement below, and conditionally
690 // used in a different switch statement later. Apparently the clang static analyzer
691 // thinks there are cases where they can be used uninitialzed (which I cannot find),
692 // but to avoid trouble if we ever change just one of the switch statements it
693 // makes sense to clear them outside the first switch.
704 copy_rvec(sel2_[g].position(0).x(), c2);
712 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
715 // x[] will be assigned below based on the number of atoms used to initialize iter1,
716 // which in turn should correspond perfectly to g1type_[0] (which determines how many we read),
717 // but unsurprisingly the static analyzer chokes a bit on that.
721 // checkSelections() ensures that this reflects all the involved
723 const bool bPresent =
724 iter1.currentValuesSelected() && iter2.currentValuesSelected();
725 iter1.getCurrentPositions(x);
731 pbc_dx(pbc, x[0], x[1], v1);
732 pbc_dx(pbc, x[2], x[1], v2);
736 rvec_sub(x[0], x[1], v1);
737 rvec_sub(x[2], x[1], v2);
739 angle = gmx_angle(v1, v2);
746 pbc_dx(pbc, x[0], x[1], dx[0]);
747 pbc_dx(pbc, x[2], x[1], dx[1]);
748 pbc_dx(pbc, x[2], x[3], dx[2]);
752 rvec_sub(x[0], x[1], dx[0]);
753 rvec_sub(x[2], x[1], dx[1]);
754 rvec_sub(x[2], x[3], dx[2]);
756 cprod(dx[0], dx[1], v1);
757 cprod(dx[1], dx[2], v2);
758 angle = gmx_angle(v1, v2);
759 real ipr = iprod(dx[0], v2);
768 calc_vec(natoms1_, x, pbc, v1, c1);
773 iter2.getCurrentPositions(x);
774 calc_vec(natoms2_, x, pbc, v2, c2);
777 // FIXME: This is not parallelizable.
780 copy_rvec(v1, vt0_[g][n]);
782 copy_rvec(vt0_[g][n], v2);
785 c1[XX] = c1[YY] = 0.0;
790 pbc_dx(pbc, c1, c2, v2);
794 rvec_sub(c1, c2, v2);
798 GMX_THROW(InternalError("invalid -g2 value"));
800 angle = gmx_angle(v1, v2);
803 GMX_THROW(InternalError("invalid -g1 value"));
805 dh.setPoint(n, angle * RAD2DEG, bPresent);
813 Angle::finishAnalysis(int /*nframes*/)
815 AbstractAverageHistogram &averageHistogram = histogramModule_->averager();
816 averageHistogram.normalizeProbability();
817 averageHistogram.done();
828 const char AngleInfo::name[] = "gangle";
829 const char AngleInfo::shortDescription[] =
832 TrajectoryAnalysisModulePointer AngleInfo::create()
834 return TrajectoryAnalysisModulePointer(new Angle);
837 } // namespace analysismodules