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,2021, 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/utilities.h"
58 #include "gromacs/math/vec.h"
59 #include "gromacs/options/basicoptions.h"
60 #include "gromacs/options/filenameoption.h"
61 #include "gromacs/options/ioptionscontainer.h"
62 #include "gromacs/pbcutil/pbc.h"
63 #include "gromacs/selection/selection.h"
64 #include "gromacs/selection/selectionoption.h"
65 #include "gromacs/trajectory/trajectoryframe.h"
66 #include "gromacs/trajectoryanalysis/analysissettings.h"
67 #include "gromacs/utility/arrayref.h"
68 #include "gromacs/utility/classhelpers.h"
69 #include "gromacs/utility/enumerationhelpers.h"
70 #include "gromacs/utility/exceptions.h"
71 #include "gromacs/utility/gmxassert.h"
72 #include "gromacs/utility/stringutil.h"
77 namespace analysismodules
83 /********************************************************************
88 * Helper to encapsulate logic for looping over input selections.
90 * This class provides two-dimensional iteration:
91 * - Over _angle groups_, corresponding to an input selection. If the input
92 * selection list contains a single selection, that selection gets used
93 * for all angle groups.
94 * - Within a group, over _values_, each consisting of a fixed number of
95 * selection positions. If there is only a single value within a selection,
96 * that value is returned over and over again.
97 * This transparently provides the semantics of using a single selection/vector
98 * to compute angles against multiple selections/vectors as described in the
101 * This class isn't perferctly self-contained and requires the caller to know
102 * some of the internals to use it properly, but it serves its purpose for this
103 * single analysis tool by simplifying the loops.
104 * Some methods have also been tailored to allow the caller to use it a bit
107 * \ingroup module_trajectoryanalysis
109 class AnglePositionIterator
113 * Creates an iterator to loop over input selection positions.
115 * \param[in] selections List of selections.
116 * \param[in] posCountPerValue Number of selection positions that
117 * constitute a single value for the iteration.
119 * If \p selections is empty, and/or \p posCountPerValue is zero, the
120 * iterator can still be advanced and hasValue()/hasSingleValue()
121 * called, but values cannot be accessed.
123 AnglePositionIterator(const SelectionList& selections, int posCountPerValue) :
124 selections_(selections),
125 posCountPerValue_(posCountPerValue),
126 currentSelection_(0),
131 //! Advances the iterator to the next group of angles.
134 if (selections_.size() > 1)
140 //! Advances the iterator to the next angle in the current group.
143 if (!hasSingleValue())
145 nextPosition_ += posCountPerValue_;
150 * Returns whether this iterator represents any values.
152 * If the return value is `false`, only nextGroup(), nextValue() and
153 * hasSingleValue() are allowed to be called.
155 bool hasValue() const { return !selections_.empty(); }
157 * Returns whether the current selection only contains a single value.
159 * Returns `false` if hasValue() returns false, which allows cutting
160 * some corners in consistency checks.
162 bool hasSingleValue() const
164 return hasValue() && currentSelection().posCount() == posCountPerValue_;
166 //! Returns whether the current selection is dynamic.
167 bool isDynamic() const { return currentSelection().isDynamic(); }
169 * Returns whether positions in the current value are either all
170 * selected or all unselected.
172 bool allValuesConsistentlySelected() const
174 if (posCountPerValue_ <= 1)
178 const bool bSelected = currentPosition(0).selected();
179 for (int i = 1; i < posCountPerValue_; ++i)
181 if (currentPosition(i).selected() != bSelected)
189 * Returns whether positions in the current value are selected.
191 * Only works reliably if allValuesConsistentlySelected() returns
194 bool currentValuesSelected() const
196 return selections_.empty() || currentPosition(0).selected();
199 //! Returns the currently active selection.
200 const Selection& currentSelection() const
202 GMX_ASSERT(currentSelection_ < ssize(selections_), "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, "Accessing positions for an invalid angle type");
221 GMX_ASSERT(nextPosition_ + posCountPerValue_ <= currentSelection().posCount(),
222 "Accessing an invalid position");
223 for (int i = 0; i < posCountPerValue_; ++i)
225 copy_rvec(currentPosition(i).x(), x[i]);
230 const SelectionList& selections_;
231 const int posCountPerValue_;
232 int currentSelection_;
235 GMX_DISALLOW_COPY_AND_ASSIGN(AnglePositionIterator);
238 /********************************************************************
239 * Actual analysis module
242 //! How to interpret the selections in -group1.
243 enum class Group1Type : int
251 //! How to interpret the selections in -group2.
252 enum class Group2Type : int
262 //! String values corresponding to Group1Type.
263 const EnumerationArray<Group1Type, const char*> c_group1TypeEnumNames = {
264 { "angle", "dihedral", "vector", "plane" }
266 //! String values corresponding to Group2Type.
267 const EnumerationArray<Group2Type, const char*> c_group2TypeEnumNames = {
268 { "none", "vector", "plane", "t0", "z", "sphnorm" }
271 class Angle : public TrajectoryAnalysisModule
276 void initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings) override;
277 void optionsFinished(TrajectoryAnalysisSettings* settings) override;
278 void initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& top) override;
280 void analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata) override;
282 void finishAnalysis(int nframes) override;
283 void writeOutput() override;
286 void initFromSelections(const SelectionList& sel1, const SelectionList& sel2);
287 void checkSelections(const SelectionList& sel1, const SelectionList& sel2) const;
291 SelectionOptionInfo* sel1info_;
292 SelectionOptionInfo* sel2info_;
293 std::string fnAverage_;
295 std::string fnHistogram_;
301 AnalysisData angles_;
302 AnalysisDataFrameAverageModulePointer averageModule_;
303 AnalysisDataSimpleHistogramModulePointer histogramModule_;
305 std::vector<int> angleCount_;
308 std::vector<std::vector<RVec>> vt0_;
310 // Copy and assign disallowed by base.
316 g1type_(Group1Type::Angle),
317 g2type_(Group2Type::None),
322 averageModule_ = std::make_unique<AnalysisDataFrameAverageModule>();
323 angles_.addModule(averageModule_);
324 histogramModule_ = std::make_unique<AnalysisDataSimpleHistogramModule>();
325 angles_.addModule(histogramModule_);
327 registerAnalysisDataset(&angles_, "angle");
328 registerBasicDataset(averageModule_.get(), "average");
329 registerBasicDataset(&histogramModule_->averager(), "histogram");
333 void Angle::initOptions(IOptionsContainer* options, TrajectoryAnalysisSettings* settings)
335 static const char* const desc[] = {
336 "[THISMODULE] computes different types of angles between vectors.",
337 "It supports both vectors defined by two positions and normals of",
338 "planes defined by three positions.",
339 "The z axis or the local normal of a sphere can also be used as",
340 "one of the vectors.",
341 "There are also convenience options 'angle' and 'dihedral' for",
342 "calculating bond angles and dihedrals defined by three/four",
344 "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
345 "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
346 "should not be specified.",
347 "In this case, [TT]-group1[tt] should specify one or more selections,",
348 "and each should contain triplets or quartets of positions that define",
349 "the angles to be calculated.[PAR]",
350 "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
351 "should specify selections that contain either pairs ([TT]vector[tt])",
352 "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
353 "set the endpoints of the vector, and for planes, the three positions",
354 "are used to calculate the normal of the plane. In both cases,",
355 "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
356 "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
357 "specify another set of vectors. [TT]-group1[tt] and [TT]-group2[tt]",
358 "should specify the same number of selections. It is also allowed to",
359 "only have a single selection for one of the options, in which case",
360 "the same selection is used with each selection in the other group.",
361 "Similarly, for each selection in [TT]-group1[tt], the corresponding",
362 "selection in [TT]-group2[tt] should specify the same number of",
363 "vectors or a single vector. In the latter case, the angle is",
364 "calculated between that single vector and each vector from the other",
366 "With [TT]-g2 sphnorm[tt], each selection in [TT]-group2[tt] should",
367 "specify a single position that is the center of the sphere.",
368 "The second vector is calculated as the vector from the center to the",
369 "midpoint of the positions specified by [TT]-group1[tt].[PAR]",
370 "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
371 "between the first vectors and the positive Z axis are calculated.[PAR]",
372 "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
373 "are calculated from the vectors as they are in the first frame.[PAR]",
374 "There are three options for output:",
375 "[TT]-oav[tt] writes an xvg file with the time and the average angle",
377 "[TT]-oall[tt] writes all the individual angles.",
378 "[TT]-oh[tt] writes a histogram of the angles. The bin width can be",
379 "set with [TT]-binw[tt].",
380 "For [TT]-oav[tt] and [TT]-oh[tt], separate average/histogram is",
381 "computed for each selection in [TT]-group1[tt]."
384 settings->setHelpText(desc);
386 options->addOption(FileNameOption("oav")
390 .defaultBasename("angaver")
391 .description("Average angles as a function of time"));
392 options->addOption(FileNameOption("oall")
396 .defaultBasename("angles")
397 .description("All angles as a function of time"));
398 options->addOption(FileNameOption("oh")
401 .store(&fnHistogram_)
402 .defaultBasename("anghist")
403 .description("Histogram of the angles"));
405 options->addOption(EnumOption<Group1Type>("g1")
406 .enumValue(c_group1TypeEnumNames)
408 .description("Type of analysis/first vector group"));
410 EnumOption<Group2Type>("g2").enumValue(c_group2TypeEnumNames).store(&g2type_).description("Type of second vector group"));
412 DoubleOption("binw").store(&binWidth_).description("Binwidth for -oh in degrees"));
414 sel1info_ = options->addOption(
415 SelectionOption("group1").required().dynamicMask().storeVector(&sel1_).multiValue().description(
416 "First analysis/vector selection"));
417 sel2info_ = options->addOption(
418 SelectionOption("group2").dynamicMask().storeVector(&sel2_).multiValue().description(
419 "Second analysis/vector selection"));
423 void Angle::optionsFinished(TrajectoryAnalysisSettings* /* settings */)
425 const bool bSingle = (g1type_ == Group1Type::Angle || g1type_ == Group1Type::Dihedral);
427 if (bSingle && g2type_ != Group2Type::None)
430 InconsistentInputError("Cannot use a second group (-g2) with "
431 "-g1 angle or dihedral"));
433 if (bSingle && sel2info_->isSet())
436 InconsistentInputError("Cannot provide a second selection "
437 "(-group2) with -g1 angle or dihedral"));
439 if (!bSingle && g2type_ == Group2Type::None)
442 InconsistentInputError("Should specify a second group (-g2) "
443 "if the first group is not an angle or a dihedral"));
446 // Set up the number of positions per angle.
449 case Group1Type::Angle: natoms1_ = 3; break;
450 case Group1Type::Dihedral: natoms1_ = 4; break;
451 case Group1Type::Vector: natoms1_ = 2; break;
452 case Group1Type::Plane: natoms1_ = 3; break;
453 default: GMX_THROW(InternalError("invalid -g1 value"));
457 case Group2Type::None: natoms2_ = 0; break;
458 case Group2Type::Vector: natoms2_ = 2; break;
459 case Group2Type::Plane: natoms2_ = 3; break;
460 case Group2Type::TimeZero: // Intended to fall through
461 case Group2Type::Z: natoms2_ = 0; break;
462 case Group2Type::SphereNormal: natoms2_ = 1; break;
463 default: GMX_THROW(InternalError("invalid -g2 value"));
465 if (natoms2_ == 0 && sel2info_->isSet())
467 GMX_THROW(InconsistentInputError(
468 "Cannot provide a second selection (-group2) with -g2 t0 or z"));
470 // TODO: If bSingle is not set, the second selection option should be
475 void Angle::initFromSelections(const SelectionList& sel1, const SelectionList& sel2)
477 const int angleGroups = std::max(sel1.size(), sel2.size());
478 const bool bHasSecondSelection = natoms2_ > 0;
480 if (bHasSecondSelection && sel1.size() != sel2.size() && std::min(sel1.size(), sel2.size()) != 1)
482 GMX_THROW(InconsistentInputError(
483 "-group1 and -group2 should specify the same number of selections"));
486 AnglePositionIterator iter1(sel1, natoms1_);
487 AnglePositionIterator iter2(sel2, natoms2_);
488 for (int g = 0; g < angleGroups; ++g, iter1.nextGroup(), iter2.nextGroup())
490 const int posCount1 = iter1.currentSelection().posCount();
491 if (natoms1_ > 1 && posCount1 % natoms1_ != 0)
493 GMX_THROW(InconsistentInputError(formatString(
494 "Number of positions in selection %d in the first group not divisible by %d",
495 static_cast<int>(g + 1),
498 const int angleCount1 = posCount1 / natoms1_;
499 int angleCount = angleCount1;
501 if (bHasSecondSelection)
503 const int posCount2 = iter2.currentSelection().posCount();
504 if (natoms2_ > 1 && posCount2 % natoms2_ != 0)
506 GMX_THROW(InconsistentInputError(
507 formatString("Number of positions in selection %d in the second group not "
509 static_cast<int>(g + 1),
512 if (g2type_ == Group2Type::SphereNormal && posCount2 != 1)
514 GMX_THROW(InconsistentInputError(
515 "The second group should contain a single position with -g2 sphnorm"));
518 const int angleCount2 = posCount2 / natoms2_;
519 angleCount = std::max(angleCount1, angleCount2);
520 if (angleCount1 != angleCount2 && std::min(angleCount1, angleCount2) != 1)
522 GMX_THROW(InconsistentInputError(
523 "Number of vectors defined by the two groups are not the same"));
526 angleCount_.push_back(angleCount);
531 void Angle::checkSelections(const SelectionList& sel1, const SelectionList& sel2) const
533 AnglePositionIterator iter1(sel1, natoms1_);
534 AnglePositionIterator iter2(sel2, natoms2_);
535 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
537 if (iter1.isDynamic() || (iter2.hasValue() && iter2.isDynamic()))
539 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
542 if (!iter1.allValuesConsistentlySelected())
546 if (!iter2.allValuesConsistentlySelected())
550 if (angleCount_[g] > 1)
552 if (iter1.hasSingleValue() && !iter1.currentValuesSelected())
556 if (iter2.hasSingleValue() && !iter2.currentValuesSelected())
562 && (angleCount_[g] == 1 || (!iter1.hasSingleValue() && !iter2.hasSingleValue()))
563 && iter1.currentValuesSelected() != iter2.currentValuesSelected())
569 std::string message = formatString(
570 "Dynamic selection %d does not select "
571 "a consistent set of angles over the frames",
572 static_cast<int>(g + 1));
573 GMX_THROW(InconsistentInputError(message));
581 void Angle::initAnalysis(const TrajectoryAnalysisSettings& settings, const TopologyInformation& /* top */)
583 initFromSelections(sel1_, sel2_);
585 // checkSelections() ensures that both selection lists have the same size.
586 angles_.setDataSetCount(angleCount_.size());
587 for (size_t i = 0; i < angleCount_.size(); ++i)
589 angles_.setColumnCount(i, angleCount_[i]);
591 double histogramMin = (g1type_ == Group1Type::Dihedral ? -180.0 : 0);
592 histogramModule_->init(histogramFromRange(histogramMin, 180.0).binWidth(binWidth_).includeAll());
594 if (g2type_ == Group2Type::TimeZero)
596 vt0_.resize(sel1_.size());
597 for (size_t g = 0; g < sel1_.size(); ++g)
599 vt0_[g].resize(sel1_[g].posCount() / natoms1_);
603 if (!fnAverage_.empty())
605 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
606 plotm->setFileName(fnAverage_);
607 plotm->setTitle("Average angle");
608 plotm->setXAxisIsTime();
609 plotm->setYLabel("Angle (degrees)");
610 // TODO: Consider adding information about the second selection,
611 // and/or a subtitle describing what kind of angle this is.
612 for (size_t g = 0; g < sel1_.size(); ++g)
614 plotm->appendLegend(sel1_[g].name());
616 averageModule_->addModule(plotm);
621 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
622 plotm->setFileName(fnAll_);
623 plotm->setTitle("Angle");
624 plotm->setXAxisIsTime();
625 plotm->setYLabel("Angle (degrees)");
626 // TODO: Add legends? (there can be a massive amount of columns)
627 angles_.addModule(plotm);
630 if (!fnHistogram_.empty())
632 AnalysisDataPlotModulePointer plotm(new AnalysisDataPlotModule(settings.plotSettings()));
633 plotm->setFileName(fnHistogram_);
634 plotm->setTitle("Angle histogram");
635 plotm->setXLabel("Angle (degrees)");
636 plotm->setYLabel("Probability");
637 // TODO: Consider adding information about the second selection,
638 // and/or a subtitle describing what kind of angle this is.
639 for (size_t g = 0; g < sel1_.size(); ++g)
641 plotm->appendLegend(sel1_[g].name());
643 histogramModule_->averager().addModule(plotm);
648 //! Helper method to calculate a vector from two or three positions.
649 void calc_vec(int natoms, rvec x[], t_pbc* pbc, rvec xout, rvec cout)
656 pbc_dx(pbc, x[1], x[0], xout);
660 rvec_sub(x[1], x[0], xout);
662 svmul(0.5, xout, cout);
663 rvec_add(x[0], cout, cout);
670 pbc_dx(pbc, x[1], x[0], v1);
671 pbc_dx(pbc, x[2], x[0], v2);
675 rvec_sub(x[1], x[0], v1);
676 rvec_sub(x[2], x[0], v2);
679 rvec_add(x[0], x[1], cout);
680 rvec_add(cout, x[2], cout);
681 svmul(1.0 / 3.0, cout, cout);
684 default: GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
689 void Angle::analyzeFrame(int frnr, const t_trxframe& fr, t_pbc* pbc, TrajectoryAnalysisModuleData* pdata)
691 AnalysisDataHandle dh = pdata->dataHandle(angles_);
692 const SelectionList& sel1 = TrajectoryAnalysisModuleData::parallelSelections(sel1_);
693 const SelectionList& sel2 = TrajectoryAnalysisModuleData::parallelSelections(sel2_);
695 checkSelections(sel1, sel2);
697 dh.startFrame(frnr, fr.time);
699 AnglePositionIterator iter1(sel1, natoms1_);
700 AnglePositionIterator iter2(sel2, natoms2_);
701 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
706 // v2 & c2 are conditionally set in the switch statement below, and conditionally
707 // used in a different switch statement later. Apparently the clang static analyzer
708 // thinks there are cases where they can be used uninitialzed (which I cannot find),
709 // but to avoid trouble if we ever change just one of the switch statements it
710 // makes sense to clear them outside the first switch.
717 case Group2Type::Z: v2[ZZ] = 1.0; break;
718 case Group2Type::SphereNormal: copy_rvec(sel2_[g].position(0).x(), c2); break;
725 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
728 // x[] will be assigned below based on the number of atoms used to initialize iter1,
729 // which in turn should correspond perfectly to g1type_ (which determines how many we
730 // read), but unsurprisingly the static analyzer chokes a bit on that.
734 // checkSelections() ensures that this reflects all the involved
736 const bool bPresent = iter1.currentValuesSelected() && iter2.currentValuesSelected();
737 iter1.getCurrentPositions(x);
740 case Group1Type::Angle:
743 pbc_dx(pbc, x[0], x[1], v1);
744 pbc_dx(pbc, x[2], x[1], v2);
748 rvec_sub(x[0], x[1], v1);
749 rvec_sub(x[2], x[1], v2);
751 angle = gmx_angle(v1, v2);
753 case Group1Type::Dihedral:
758 pbc_dx(pbc, x[0], x[1], dx[0]);
759 pbc_dx(pbc, x[2], x[1], dx[1]);
760 pbc_dx(pbc, x[2], x[3], dx[2]);
764 rvec_sub(x[0], x[1], dx[0]);
765 rvec_sub(x[2], x[1], dx[1]);
766 rvec_sub(x[2], x[3], dx[2]);
768 cprod(dx[0], dx[1], v1);
769 cprod(dx[1], dx[2], v2);
770 angle = gmx_angle(v1, v2);
771 real ipr = iprod(dx[0], v2);
778 case Group1Type::Vector:
779 case Group1Type::Plane:
780 calc_vec(natoms1_, x, pbc, v1, c1);
783 case Group2Type::Vector:
784 case Group2Type::Plane:
785 iter2.getCurrentPositions(x);
786 calc_vec(natoms2_, x, pbc, v2, c2);
788 case Group2Type::TimeZero:
789 // FIXME: This is not parallelizable.
792 copy_rvec(v1, vt0_[g][n]);
794 copy_rvec(vt0_[g][n], v2);
796 case Group2Type::Z: c1[XX] = c1[YY] = 0.0; break;
797 case Group2Type::SphereNormal:
800 pbc_dx(pbc, c1, c2, v2);
804 rvec_sub(c1, c2, v2);
807 default: GMX_THROW(InternalError("invalid -g2 value"));
809 angle = gmx_angle(v1, v2);
811 default: GMX_THROW(InternalError("invalid -g1 value"));
813 dh.setPoint(n, angle * gmx::c_rad2Deg, bPresent);
820 void Angle::finishAnalysis(int /*nframes*/)
822 AbstractAverageHistogram& averageHistogram = histogramModule_->averager();
823 averageHistogram.normalizeProbability();
824 averageHistogram.done();
828 void Angle::writeOutput() {}
832 const char AngleInfo::name[] = "gangle";
833 const char AngleInfo::shortDescription[] = "Calculate angles";
835 TrajectoryAnalysisModulePointer AngleInfo::create()
837 return TrajectoryAnalysisModulePointer(new Angle);
840 } // namespace analysismodules