2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011-2018, The GROMACS development team.
5 * Copyright (c) 2019,2020, by the GROMACS development team, led by
6 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
7 * and including many others, as listed in the AUTHORS file in the
8 * top-level source directory and at http://www.gromacs.org.
10 * GROMACS is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public License
12 * as published by the Free Software Foundation; either version 2.1
13 * of the License, or (at your option) any later version.
15 * GROMACS is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with GROMACS; if not, see
22 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
23 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
25 * If you want to redistribute modifications to GROMACS, please
26 * consider that scientific software is very special. Version
27 * control is crucial - bugs must be traceable. We will be happy to
28 * consider code for inclusion in the official distribution, but
29 * derived work must not be called official GROMACS. Details are found
30 * in the README & COPYING files - if they are missing, get the
31 * official version at http://www.gromacs.org.
33 * To help us fund GROMACS development, we humbly ask that you cite
34 * the research papers on the package. Check out http://www.gromacs.org.
38 * Implements gmx::analysismodules::Angle.
40 * \author Teemu Murtola <teemu.murtola@gmail.com>
41 * \ingroup module_trajectoryanalysis
52 #include "gromacs/analysisdata/analysisdata.h"
53 #include "gromacs/analysisdata/modules/average.h"
54 #include "gromacs/analysisdata/modules/histogram.h"
55 #include "gromacs/analysisdata/modules/plot.h"
56 #include "gromacs/math/units.h"
57 #include "gromacs/math/vec.h"
58 #include "gromacs/options/basicoptions.h"
59 #include "gromacs/options/filenameoption.h"
60 #include "gromacs/options/ioptionscontainer.h"
61 #include "gromacs/pbcutil/pbc.h"
62 #include "gromacs/selection/selection.h"
63 #include "gromacs/selection/selectionoption.h"
64 #include "gromacs/trajectory/trajectoryframe.h"
65 #include "gromacs/trajectoryanalysis/analysissettings.h"
66 #include "gromacs/utility/arrayref.h"
67 #include "gromacs/utility/enumerationhelpers.h"
68 #include "gromacs/utility/exceptions.h"
69 #include "gromacs/utility/gmxassert.h"
70 #include "gromacs/utility/stringutil.h"
75 namespace analysismodules
81 /********************************************************************
86 * Helper to encapsulate logic for looping over input selections.
88 * This class provides two-dimensional iteration:
89 * - Over _angle groups_, corresponding to an input selection. If the input
90 * selection list contains a single selection, that selection gets used
91 * for all angle groups.
92 * - Within a group, over _values_, each consisting of a fixed number of
93 * selection positions. If there is only a single value within a selection,
94 * that value is returned over and over again.
95 * This transparently provides the semantics of using a single selection/vector
96 * to compute angles against multiple selections/vectors as described in the
99 * This class isn't perferctly self-contained and requires the caller to know
100 * some of the internals to use it properly, but it serves its purpose for this
101 * single analysis tool by simplifying the loops.
102 * Some methods have also been tailored to allow the caller to use it a bit
105 * \ingroup module_trajectoryanalysis
107 class AnglePositionIterator
111 * Creates an iterator to loop over input selection positions.
113 * \param[in] selections List of selections.
114 * \param[in] posCountPerValue Number of selection positions that
115 * constitute a single value for the iteration.
117 * If \p selections is empty, and/or \p posCountPerValue is zero, the
118 * iterator can still be advanced and hasValue()/hasSingleValue()
119 * called, but values cannot be accessed.
121 AnglePositionIterator(const SelectionList& selections, int posCountPerValue) :
122 selections_(selections),
123 posCountPerValue_(posCountPerValue),
124 currentSelection_(0),
129 //! Advances the iterator to the next group of angles.
132 if (selections_.size() > 1)
138 //! Advances the iterator to the next angle in the current group.
141 if (!hasSingleValue())
143 nextPosition_ += posCountPerValue_;
148 * Returns whether this iterator represents any values.
150 * If the return value is `false`, only nextGroup(), nextValue() and
151 * hasSingleValue() are allowed to be called.
153 bool hasValue() const { return !selections_.empty(); }
155 * Returns whether the current selection only contains a single value.
157 * Returns `false` if hasValue() returns false, which allows cutting
158 * some corners in consistency checks.
160 bool hasSingleValue() const
162 return hasValue() && currentSelection().posCount() == posCountPerValue_;
164 //! Returns whether the current selection is dynamic.
165 bool isDynamic() const { return currentSelection().isDynamic(); }
167 * Returns whether positions in the current value are either all
168 * selected or all unselected.
170 bool allValuesConsistentlySelected() const
172 if (posCountPerValue_ <= 1)
176 const bool bSelected = currentPosition(0).selected();
177 for (int i = 1; i < posCountPerValue_; ++i)
179 if (currentPosition(i).selected() != bSelected)
187 * Returns whether positions in the current value are selected.
189 * Only works reliably if allValuesConsistentlySelected() returns
192 bool currentValuesSelected() const
194 return selections_.empty() || currentPosition(0).selected();
197 //! Returns the currently active selection.
198 const Selection& currentSelection() const
200 GMX_ASSERT(currentSelection_ < ssize(selections_), "Accessing an invalid selection");
201 return selections_[currentSelection_];
203 //! Returns the `i`th position for the current value.
204 SelectionPosition currentPosition(int i) const
206 return currentSelection().position(nextPosition_ + i);
209 * Extracts all coordinates corresponding to the current value.
211 * \param[out] x Array to which the positions are extracted.
213 * \p x should contain at minimum the number of positions per value
214 * passed to the constructor.
216 void getCurrentPositions(rvec x[]) const
218 GMX_ASSERT(posCountPerValue_ > 0, "Accessing positions for an invalid angle type");
219 GMX_ASSERT(nextPosition_ + posCountPerValue_ <= currentSelection().posCount(),
220 "Accessing an invalid position");
221 for (int i = 0; i < posCountPerValue_; ++i)
223 copy_rvec(currentPosition(i).x(), x[i]);
228 const SelectionList& selections_;
229 const int posCountPerValue_;
230 int currentSelection_;
233 GMX_DISALLOW_COPY_AND_ASSIGN(AnglePositionIterator);
236 /********************************************************************
237 * Actual analysis module
240 //! How to interpret the selections in -group1.
241 enum class Group1Type : int
249 //! How to interpret the selections in -group2.
250 enum class Group2Type : int
260 //! String values corresponding to Group1Type.
261 const EnumerationArray<Group1Type, const char*> c_group1TypeEnumNames = {
262 { "angle", "dihedral", "vector", "plane" }
264 //! String values corresponding to Group2Type.
265 const EnumerationArray<Group2Type, const char*> c_group2TypeEnumNames = {
266 { "none", "vector", "plane", "t0", "z", "sphnorm" }
269 class Angle : public TrajectoryAnalysisModule
274 void initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings) override;
275 void optionsFinished(TrajectoryAnalysisSettings* settings) override;
276 void initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top) override;
278 void analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata) override;
280 void finishAnalysis(int nframes) override;
281 void writeOutput() override;
284 void initFromSelections(const SelectionList& sel1, const SelectionList& sel2);
285 void checkSelections(const SelectionList& sel1, const SelectionList& sel2) const;
289 SelectionOptionInfo* sel1info_;
290 SelectionOptionInfo* sel2info_;
291 std::string fnAverage_;
293 std::string fnHistogram_;
299 AnalysisData angles_;
300 AnalysisDataFrameAverageModulePointer averageModule_;
301 AnalysisDataSimpleHistogramModulePointer histogramModule_;
303 std::vector<int> angleCount_;
306 std::vector<std::vector<RVec>> vt0_;
308 // Copy and assign disallowed by base.
314 g1type_(Group1Type::Angle),
315 g2type_(Group2Type::None),
320 averageModule_ = std::make_unique<AnalysisDataFrameAverageModule>();
321 angles_.addModule(averageModule_);
322 histogramModule_ = std::make_unique<AnalysisDataSimpleHistogramModule>();
323 angles_.addModule(histogramModule_);
325 registerAnalysisDataset(&angles_, "angle");
326 registerBasicDataset(averageModule_.get(), "average");
327 registerBasicDataset(&histogramModule_->averager(), "histogram");
331 void Angle::initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings)
333 static const char* const desc[] = {
334 "[THISMODULE] computes different types of angles between vectors.",
335 "It supports both vectors defined by two positions and normals of",
336 "planes defined by three positions.",
337 "The z axis or the local normal of a sphere can also be used as",
338 "one of the vectors.",
339 "There are also convenience options 'angle' and 'dihedral' for",
340 "calculating bond angles and dihedrals defined by three/four",
342 "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
343 "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
344 "should not be specified.",
345 "In this case, [TT]-group1[tt] should specify one or more selections,",
346 "and each should contain triplets or quartets of positions that define",
347 "the angles to be calculated.[PAR]",
348 "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
349 "should specify selections that contain either pairs ([TT]vector[tt])",
350 "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
351 "set the endpoints of the vector, and for planes, the three positions",
352 "are used to calculate the normal of the plane. In both cases,",
353 "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
354 "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
355 "specify another set of vectors. [TT]-group1[tt] and [TT]-group2[tt]",
356 "should specify the same number of selections. It is also allowed to",
357 "only have a single selection for one of the options, in which case",
358 "the same selection is used with each selection in the other group.",
359 "Similarly, for each selection in [TT]-group1[tt], the corresponding",
360 "selection in [TT]-group2[tt] should specify the same number of",
361 "vectors or a single vector. In the latter case, the angle is",
362 "calculated between that single vector and each vector from the other",
364 "With [TT]-g2 sphnorm[tt], each selection in [TT]-group2[tt] should",
365 "specify a single position that is the center of the sphere.",
366 "The second vector is calculated as the vector from the center to the",
367 "midpoint of the positions specified by [TT]-group1[tt].[PAR]",
368 "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
369 "between the first vectors and the positive Z axis are calculated.[PAR]",
370 "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
371 "are calculated from the vectors as they are in the first frame.[PAR]",
372 "There are three options for output:",
373 "[TT]-oav[tt] writes an xvg file with the time and the average angle",
375 "[TT]-oall[tt] writes all the individual angles.",
376 "[TT]-oh[tt] writes a histogram of the angles. The bin width can be",
377 "set with [TT]-binw[tt].",
378 "For [TT]-oav[tt] and [TT]-oh[tt], separate average/histogram is",
379 "computed for each selection in [TT]-group1[tt]."
382 settings->setHelpText(desc);
384 options->addOption(FileNameOption("oav")
388 .defaultBasename("angaver")
389 .description("Average angles as a function of time"));
390 options->addOption(FileNameOption("oall")
394 .defaultBasename("angles")
395 .description("All angles as a function of time"));
396 options->addOption(FileNameOption("oh")
399 .store(&fnHistogram_)
400 .defaultBasename("anghist")
401 .description("Histogram of the angles"));
403 options->addOption(EnumOption<Group1Type>("g1")
404 .enumValue(c_group1TypeEnumNames)
406 .description("Type of analysis/first vector group"));
408 EnumOption<Group2Type>("g2").enumValue(c_group2TypeEnumNames).store(&g2type_).description("Type of second vector group"));
410 DoubleOption("binw").store(&binWidth_).description("Binwidth for -oh in degrees"));
412 sel1info_ = options->addOption(
413 SelectionOption("group1").required().dynamicMask().storeVector(&sel1_).multiValue().description(
414 "First analysis/vector selection"));
415 sel2info_ = options->addOption(
416 SelectionOption("group2").dynamicMask().storeVector(&sel2_).multiValue().description(
417 "Second analysis/vector selection"));
421 void Angle::optionsFinished(TrajectoryAnalysisSettings* /* settings */)
423 const bool bSingle = (g1type_ == Group1Type::Angle || g1type_ == Group1Type::Dihedral);
425 if (bSingle && g2type_ != Group2Type::None)
428 InconsistentInputError("Cannot use a second group (-g2) with "
429 "-g1 angle or dihedral"));
431 if (bSingle && sel2info_->isSet())
434 InconsistentInputError("Cannot provide a second selection "
435 "(-group2) with -g1 angle or dihedral"));
437 if (!bSingle && g2type_ == Group2Type::None)
440 InconsistentInputError("Should specify a second group (-g2) "
441 "if the first group is not an angle or a dihedral"));
444 // Set up the number of positions per angle.
447 case Group1Type::Angle: natoms1_ = 3; break;
448 case Group1Type::Dihedral: natoms1_ = 4; break;
449 case Group1Type::Vector: natoms1_ = 2; break;
450 case Group1Type::Plane: natoms1_ = 3; break;
451 default: GMX_THROW(InternalError("invalid -g1 value"));
455 case Group2Type::None: natoms2_ = 0; break;
456 case Group2Type::Vector: natoms2_ = 2; break;
457 case Group2Type::Plane: natoms2_ = 3; break;
458 case Group2Type::TimeZero: // Intended to fall through
459 case Group2Type::Z: natoms2_ = 0; break;
460 case Group2Type::SphereNormal: natoms2_ = 1; break;
461 default: GMX_THROW(InternalError("invalid -g2 value"));
463 if (natoms2_ == 0 && sel2info_->isSet())
465 GMX_THROW(InconsistentInputError(
466 "Cannot provide a second selection (-group2) with -g2 t0 or z"));
468 // TODO: If bSingle is not set, the second selection option should be
473 void Angle::initFromSelections(const SelectionList& sel1, const SelectionList& sel2)
475 const int angleGroups = std::max(sel1.size(), sel2.size());
476 const bool bHasSecondSelection = natoms2_ > 0;
478 if (bHasSecondSelection && sel1.size() != sel2.size() && std::min(sel1.size(), sel2.size()) != 1)
480 GMX_THROW(InconsistentInputError(
481 "-group1 and -group2 should specify the same number of selections"));
484 AnglePositionIterator iter1(sel1, natoms1_);
485 AnglePositionIterator iter2(sel2, natoms2_);
486 for (int g = 0; g < angleGroups; ++g, iter1.nextGroup(), iter2.nextGroup())
488 const int posCount1 = iter1.currentSelection().posCount();
489 if (natoms1_ > 1 && posCount1 % natoms1_ != 0)
491 GMX_THROW(InconsistentInputError(formatString(
492 "Number of positions in selection %d in the first group not divisible by %d",
493 static_cast<int>(g + 1),
496 const int angleCount1 = posCount1 / natoms1_;
497 int angleCount = angleCount1;
499 if (bHasSecondSelection)
501 const int posCount2 = iter2.currentSelection().posCount();
502 if (natoms2_ > 1 && posCount2 % natoms2_ != 0)
504 GMX_THROW(InconsistentInputError(
505 formatString("Number of positions in selection %d in the second group not "
507 static_cast<int>(g + 1),
510 if (g2type_ == Group2Type::SphereNormal && posCount2 != 1)
512 GMX_THROW(InconsistentInputError(
513 "The second group should contain a single position with -g2 sphnorm"));
516 const int angleCount2 = posCount2 / natoms2_;
517 angleCount = std::max(angleCount1, angleCount2);
518 if (angleCount1 != angleCount2 && std::min(angleCount1, angleCount2) != 1)
520 GMX_THROW(InconsistentInputError(
521 "Number of vectors defined by the two groups are not the same"));
524 angleCount_.push_back(angleCount);
529 void Angle::checkSelections(const SelectionList& sel1, const SelectionList& sel2) const
531 AnglePositionIterator iter1(sel1, natoms1_);
532 AnglePositionIterator iter2(sel2, natoms2_);
533 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
535 if (iter1.isDynamic() || (iter2.hasValue() && iter2.isDynamic()))
537 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
540 if (!iter1.allValuesConsistentlySelected())
544 if (!iter2.allValuesConsistentlySelected())
548 if (angleCount_[g] > 1)
550 if (iter1.hasSingleValue() && !iter1.currentValuesSelected())
554 if (iter2.hasSingleValue() && !iter2.currentValuesSelected())
560 && (angleCount_[g] == 1 || (!iter1.hasSingleValue() && !iter2.hasSingleValue()))
561 && iter1.currentValuesSelected() != iter2.currentValuesSelected())
567 std::string message = formatString(
568 "Dynamic selection %d does not select "
569 "a consistent set of angles over the frames",
570 static_cast<int>(g + 1));
571 GMX_THROW(InconsistentInputError(message));
579 void Angle::initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& /* top */)
581 initFromSelections(sel1_, sel2_);
583 // checkSelections() ensures that both selection lists have the same size.
584 angles_.setDataSetCount(angleCount_.size());
585 for (size_t i = 0; i < angleCount_.size(); ++i)
587 angles_.setColumnCount(i, angleCount_[i]);
589 double histogramMin = (g1type_ == Group1Type::Dihedral ? -180.0 : 0);
590 histogramModule_->init(histogramFromRange(histogramMin, 180.0).binWidth(binWidth_).includeAll());
592 if (g2type_ == Group2Type::TimeZero)
594 vt0_.resize(sel1_.size());
595 for (size_t g = 0; g < sel1_.size(); ++g)
597 vt0_[g].resize(sel1_[g].posCount() / natoms1_);
601 if (!fnAverage_.empty())
603 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
604 plotm->setFileName(fnAverage_);
605 plotm->setTitle("Average angle");
606 plotm->setXAxisIsTime();
607 plotm->setYLabel("Angle (degrees)");
608 // TODO: Consider adding information about the second selection,
609 // and/or a subtitle describing what kind of angle this is.
610 for (size_t g = 0; g < sel1_.size(); ++g)
612 plotm->appendLegend(sel1_[g].name());
614 averageModule_->addModule(plotm);
619 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
620 plotm->setFileName(fnAll_);
621 plotm->setTitle("Angle");
622 plotm->setXAxisIsTime();
623 plotm->setYLabel("Angle (degrees)");
624 // TODO: Add legends? (there can be a massive amount of columns)
625 angles_.addModule(plotm);
628 if (!fnHistogram_.empty())
630 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
631 plotm->setFileName(fnHistogram_);
632 plotm->setTitle("Angle histogram");
633 plotm->setXLabel("Angle (degrees)");
634 plotm->setYLabel("Probability");
635 // TODO: Consider adding information about the second selection,
636 // and/or a subtitle describing what kind of angle this is.
637 for (size_t g = 0; g < sel1_.size(); ++g)
639 plotm->appendLegend(sel1_[g].name());
641 histogramModule_->averager().addModule(plotm);
646 //! Helper method to calculate a vector from two or three positions.
647 void calc_vec(int natoms, rvec x[], t_pbc* pbc, rvec xout, rvec cout)
654 pbc_dx(pbc, x[1], x[0], xout);
658 rvec_sub(x[1], x[0], xout);
660 svmul(0.5, xout, cout);
661 rvec_add(x[0], cout, cout);
668 pbc_dx(pbc, x[1], x[0], v1);
669 pbc_dx(pbc, x[2], x[0], v2);
673 rvec_sub(x[1], x[0], v1);
674 rvec_sub(x[2], x[0], v2);
677 rvec_add(x[0], x[1], cout);
678 rvec_add(cout, x[2], cout);
679 svmul(1.0 / 3.0, cout, cout);
682 default: GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
687 void Angle::analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata)
689 AnalysisDataHandle dh = pdata->dataHandle(angles_);
690 const SelectionList& sel1 = TrajectoryAnalysisModuleData::parallelSelections(sel1_);
691 const SelectionList& sel2 = TrajectoryAnalysisModuleData::parallelSelections(sel2_);
693 checkSelections(sel1, sel2);
695 dh.startFrame(frnr, fr.time);
697 AnglePositionIterator iter1(sel1, natoms1_);
698 AnglePositionIterator iter2(sel2, natoms2_);
699 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
704 // v2 & c2 are conditionally set in the switch statement below, and conditionally
705 // used in a different switch statement later. Apparently the clang static analyzer
706 // thinks there are cases where they can be used uninitialzed (which I cannot find),
707 // but to avoid trouble if we ever change just one of the switch statements it
708 // makes sense to clear them outside the first switch.
715 case Group2Type::Z: v2[ZZ] = 1.0; break;
716 case Group2Type::SphereNormal: copy_rvec(sel2_[g].position(0).x(), c2); break;
723 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
726 // x[] will be assigned below based on the number of atoms used to initialize iter1,
727 // which in turn should correspond perfectly to g1type_ (which determines how many we
728 // read), but unsurprisingly the static analyzer chokes a bit on that.
732 // checkSelections() ensures that this reflects all the involved
734 const bool bPresent = iter1.currentValuesSelected() && iter2.currentValuesSelected();
735 iter1.getCurrentPositions(x);
738 case Group1Type::Angle:
741 pbc_dx(pbc, x[0], x[1], v1);
742 pbc_dx(pbc, x[2], x[1], v2);
746 rvec_sub(x[0], x[1], v1);
747 rvec_sub(x[2], x[1], v2);
749 angle = gmx_angle(v1, v2);
751 case Group1Type::Dihedral:
756 pbc_dx(pbc, x[0], x[1], dx[0]);
757 pbc_dx(pbc, x[2], x[1], dx[1]);
758 pbc_dx(pbc, x[2], x[3], dx[2]);
762 rvec_sub(x[0], x[1], dx[0]);
763 rvec_sub(x[2], x[1], dx[1]);
764 rvec_sub(x[2], x[3], dx[2]);
766 cprod(dx[0], dx[1], v1);
767 cprod(dx[1], dx[2], v2);
768 angle = gmx_angle(v1, v2);
769 real ipr = iprod(dx[0], v2);
776 case Group1Type::Vector:
777 case Group1Type::Plane:
778 calc_vec(natoms1_, x, pbc, v1, c1);
781 case Group2Type::Vector:
782 case Group2Type::Plane:
783 iter2.getCurrentPositions(x);
784 calc_vec(natoms2_, x, pbc, v2, c2);
786 case Group2Type::TimeZero:
787 // FIXME: This is not parallelizable.
790 copy_rvec(v1, vt0_[g][n]);
792 copy_rvec(vt0_[g][n], v2);
794 case Group2Type::Z: c1[XX] = c1[YY] = 0.0; break;
795 case Group2Type::SphereNormal:
798 pbc_dx(pbc, c1, c2, v2);
802 rvec_sub(c1, c2, v2);
805 default: GMX_THROW(InternalError("invalid -g2 value"));
807 angle = gmx_angle(v1, v2);
809 default: GMX_THROW(InternalError("invalid -g1 value"));
811 dh.setPoint(n, angle * RAD2DEG, bPresent);
818 void Angle::finishAnalysis(int /*nframes*/)
820 AbstractAverageHistogram& averageHistogram = histogramModule_->averager();
821 averageHistogram.normalizeProbability();
822 averageHistogram.done();
826 void Angle::writeOutput() {}
830 const char AngleInfo::name[] = "gangle";
831 const char AngleInfo::shortDescription[] = "Calculate angles";
833 TrajectoryAnalysisModulePointer AngleInfo::create()
835 return TrajectoryAnalysisModulePointer(new Angle);
838 } // namespace analysismodules