2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013,2014, 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/options.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(Options *options,
249 TrajectoryAnalysisSettings *settings);
250 virtual void optionsFinished(Options *options,
251 TrajectoryAnalysisSettings *settings);
252 virtual void initAnalysis(const TrajectoryAnalysisSettings &settings,
253 const TopologyInformation &top);
255 virtual void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
256 TrajectoryAnalysisModuleData *pdata);
258 virtual void finishAnalysis(int nframes);
259 virtual void writeOutput();
262 void initFromSelections(const SelectionList &sel1,
263 const SelectionList &sel2);
264 void checkSelections(const SelectionList &sel1,
265 const SelectionList &sel2) const;
269 SelectionOptionInfo *sel1info_;
270 SelectionOptionInfo *sel2info_;
271 std::string fnAverage_;
273 std::string fnHistogram_;
279 AnalysisData angles_;
280 AnalysisDataFrameAverageModulePointer averageModule_;
281 AnalysisDataSimpleHistogramModulePointer histogramModule_;
283 std::vector<int> angleCount_;
286 std::vector<std::vector<RVec> > vt0_;
288 // Copy and assign disallowed by base.
292 : TrajectoryAnalysisModule(AngleInfo::name, AngleInfo::shortDescription),
293 sel1info_(NULL), sel2info_(NULL), binWidth_(1.0), natoms1_(0), natoms2_(0)
295 averageModule_.reset(new AnalysisDataFrameAverageModule());
296 angles_.addModule(averageModule_);
297 histogramModule_.reset(new AnalysisDataSimpleHistogramModule());
298 angles_.addModule(histogramModule_);
300 registerAnalysisDataset(&angles_, "angle");
301 registerBasicDataset(averageModule_.get(), "average");
302 registerBasicDataset(&histogramModule_->averager(), "histogram");
307 Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
309 static const char *const desc[] = {
310 "[THISMODULE] computes different types of angles between vectors.",
311 "It supports both vectors defined by two positions and normals of",
312 "planes defined by three positions.",
313 "The z axis or the local normal of a sphere can also be used as",
314 "one of the vectors.",
315 "There are also convenience options 'angle' and 'dihedral' for",
316 "calculating bond angles and dihedrals defined by three/four",
318 "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
319 "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
320 "should not be specified.",
321 "In this case, [TT]-group1[tt] should specify one or more selections,",
322 "and each should contain triplets or quartets of positions that define",
323 "the angles to be calculated.[PAR]",
324 "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
325 "should specify selections that contain either pairs ([TT]vector[tt])",
326 "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
327 "set the endpoints of the vector, and for planes, the three positions",
328 "are used to calculate the normal of the plane. In both cases,",
329 "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
330 "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
331 "specify another set of vectors. [TT]-group1[tt] and [TT]-group2[tt]",
332 "should specify the same number of selections. It is also allowed to",
333 "only have a single selection for one of the options, in which case",
334 "the same selection is used with each selection in the other group.",
335 "Similarly, for each selection in [TT]-group1[tt], the corresponding",
336 "selection in [TT]-group2[tt] should specify the same number of",
337 "vectors or a single vector. In the latter case, the angle is",
338 "calculated between that single vector and each vector from the other",
340 "With [TT]-g2 sphnorm[tt], each selection in [TT]-group2[tt] should",
341 "specify a single position that is the center of the sphere.",
342 "The second vector is calculated as the vector from the center to the",
343 "midpoint of the positions specified by [TT]-group1[tt].[PAR]",
344 "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
345 "between the first vectors and the positive Z axis are calculated.[PAR]",
346 "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
347 "are calculated from the vectors as they are in the first frame.[PAR]",
348 "There are three options for output:",
349 "[TT]-oav[tt] writes an xvg file with the time and the average angle",
351 "[TT]-oall[tt] writes all the individual angles.",
352 "[TT]-oh[tt] writes a histogram of the angles. The bin width can be",
353 "set with [TT]-binw[tt].",
354 "For [TT]-oav[tt] and [TT]-oh[tt], separate average/histogram is",
355 "computed for each selection in [TT]-group1[tt]."
357 static const char *const cGroup1TypeEnum[] =
358 { "angle", "dihedral", "vector", "plane" };
359 static const char *const cGroup2TypeEnum[] =
360 { "none", "vector", "plane", "t0", "z", "sphnorm" };
362 options->setDescription(desc);
364 options->addOption(FileNameOption("oav").filetype(eftPlot).outputFile()
365 .store(&fnAverage_).defaultBasename("angaver")
366 .description("Average angles as a function of time"));
367 options->addOption(FileNameOption("oall").filetype(eftPlot).outputFile()
368 .store(&fnAll_).defaultBasename("angles")
369 .description("All angles as a function of time"));
370 options->addOption(FileNameOption("oh").filetype(eftPlot).outputFile()
371 .store(&fnHistogram_).defaultBasename("anghist")
372 .description("Histogram of the angles"));
374 options->addOption(StringOption("g1").enumValue(cGroup1TypeEnum)
375 .defaultEnumIndex(0).store(&g1type_)
376 .description("Type of analysis/first vector group"));
377 options->addOption(StringOption("g2").enumValue(cGroup2TypeEnum)
378 .defaultEnumIndex(0).store(&g2type_)
379 .description("Type of second vector group"));
380 options->addOption(DoubleOption("binw").store(&binWidth_)
381 .description("Binwidth for -oh in degrees"));
383 sel1info_ = options->addOption(SelectionOption("group1")
384 .required().dynamicMask().storeVector(&sel1_)
386 .description("First analysis/vector selection"));
387 sel2info_ = options->addOption(SelectionOption("group2")
388 .dynamicMask().storeVector(&sel2_)
390 .description("Second analysis/vector selection"));
395 Angle::optionsFinished(Options *options, TrajectoryAnalysisSettings * /* settings */)
397 const bool bSingle = (g1type_[0] == 'a' || g1type_[0] == 'd');
399 if (bSingle && g2type_[0] != 'n')
401 GMX_THROW(InconsistentInputError("Cannot use a second group (-g2) with "
402 "-g1 angle or dihedral"));
404 if (bSingle && options->isSet("group2"))
406 GMX_THROW(InconsistentInputError("Cannot provide a second selection "
407 "(-group2) with -g1 angle or dihedral"));
409 if (!bSingle && g2type_[0] == 'n')
411 GMX_THROW(InconsistentInputError("Should specify a second group (-g2) "
412 "if the first group is not an angle or a dihedral"));
415 // Set up the number of positions per angle.
418 case 'a': natoms1_ = 3; break;
419 case 'd': natoms1_ = 4; break;
420 case 'v': natoms1_ = 2; break;
421 case 'p': natoms1_ = 3; break;
423 GMX_THROW(InternalError("invalid -g1 value"));
427 case 'n': natoms2_ = 0; break;
428 case 'v': natoms2_ = 2; break;
429 case 'p': natoms2_ = 3; break;
430 case 't': natoms2_ = 0; break;
431 case 'z': natoms2_ = 0; break;
432 case 's': natoms2_ = 1; break;
434 GMX_THROW(InternalError("invalid -g2 value"));
436 if (natoms2_ == 0 && options->isSet("group2"))
438 GMX_THROW(InconsistentInputError("Cannot provide a second selection (-group2) with -g2 t0 or z"));
440 // TODO: If bSingle is not set, the second selection option should be
446 Angle::initFromSelections(const SelectionList &sel1,
447 const SelectionList &sel2)
449 const int angleGroups = std::max(sel1.size(), sel2.size());
450 const bool bHasSecondSelection = natoms2_ > 0;
452 if (bHasSecondSelection && sel1.size() != sel2.size()
453 && std::min(sel1.size(), sel2.size()) != 1)
455 GMX_THROW(InconsistentInputError(
456 "-group1 and -group2 should specify the same number of selections"));
459 AnglePositionIterator iter1(sel1, natoms1_);
460 AnglePositionIterator iter2(sel2, natoms2_);
461 for (int g = 0; g < angleGroups; ++g, iter1.nextGroup(), iter2.nextGroup())
463 const int posCount1 = iter1.currentSelection().posCount();
464 if (natoms1_ > 1 && posCount1 % natoms1_ != 0)
466 GMX_THROW(InconsistentInputError(formatString(
467 "Number of positions in selection %d in the first group not divisible by %d",
468 static_cast<int>(g + 1), natoms1_)));
470 const int angleCount1 = posCount1 / natoms1_;
471 int angleCount = angleCount1;
473 if (bHasSecondSelection)
475 const int posCount2 = iter2.currentSelection().posCount();
476 if (natoms2_ > 1 && posCount2 % natoms2_ != 0)
478 GMX_THROW(InconsistentInputError(formatString(
479 "Number of positions in selection %d in the second group not divisible by %d",
480 static_cast<int>(g + 1), natoms2_)));
482 if (g2type_[0] == 's' && posCount2 != 1)
484 GMX_THROW(InconsistentInputError(
485 "The second group should contain a single position with -g2 sphnorm"));
488 const int angleCount2 = posCount2 / natoms2_;
489 angleCount = std::max(angleCount1, angleCount2);
490 if (angleCount1 != angleCount2
491 && std::min(angleCount1, angleCount2) != 1)
493 GMX_THROW(InconsistentInputError(
494 "Number of vectors defined by the two groups are not the same"));
497 angleCount_.push_back(angleCount);
503 Angle::checkSelections(const SelectionList &sel1,
504 const SelectionList &sel2) const
506 AnglePositionIterator iter1(sel1, natoms1_);
507 AnglePositionIterator iter2(sel2, natoms2_);
508 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
510 if (iter1.isDynamic() || (iter2.hasValue() && iter2.isDynamic()))
512 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
515 if (!iter1.allValuesConsistentlySelected())
519 if (!iter2.allValuesConsistentlySelected())
523 if (angleCount_[g] > 1)
525 if (iter1.hasSingleValue() && !iter1.currentValuesSelected())
529 if (iter2.hasSingleValue() && !iter2.currentValuesSelected())
535 && (angleCount_[g] == 1
536 || (!iter1.hasSingleValue() && !iter2.hasSingleValue()))
537 && iter1.currentValuesSelected() != iter2.currentValuesSelected())
543 std::string message =
544 formatString("Dynamic selection %d does not select "
545 "a consistent set of angles over the frames",
546 static_cast<int>(g + 1));
547 GMX_THROW(InconsistentInputError(message));
556 Angle::initAnalysis(const TrajectoryAnalysisSettings &settings,
557 const TopologyInformation & /* top */)
559 initFromSelections(sel1_, sel2_);
561 // checkSelections() ensures that both selection lists have the same size.
562 angles_.setDataSetCount(angleCount_.size());
563 for (size_t i = 0; i < angleCount_.size(); ++i)
565 angles_.setColumnCount(i, angleCount_[i]);
567 double histogramMin = (g1type_ == "dihedral" ? -180.0 : 0);
568 histogramModule_->init(histogramFromRange(histogramMin, 180.0)
569 .binWidth(binWidth_).includeAll());
573 vt0_.resize(sel1_.size());
574 for (size_t g = 0; g < sel1_.size(); ++g)
576 vt0_[g].resize(sel1_[g].posCount() / natoms1_);
580 if (!fnAverage_.empty())
582 AnalysisDataPlotModulePointer plotm(
583 new AnalysisDataPlotModule(settings.plotSettings()));
584 plotm->setFileName(fnAverage_);
585 plotm->setTitle("Average angle");
586 plotm->setXAxisIsTime();
587 plotm->setYLabel("Angle (degrees)");
588 // TODO: Consider adding information about the second selection,
589 // and/or a subtitle describing what kind of angle this is.
590 for (size_t g = 0; g < sel1_.size(); ++g)
592 plotm->appendLegend(sel1_[g].name());
594 averageModule_->addModule(plotm);
599 AnalysisDataPlotModulePointer plotm(
600 new AnalysisDataPlotModule(settings.plotSettings()));
601 plotm->setFileName(fnAll_);
602 plotm->setTitle("Angle");
603 plotm->setXAxisIsTime();
604 plotm->setYLabel("Angle (degrees)");
605 // TODO: Add legends? (there can be a massive amount of columns)
606 angles_.addModule(plotm);
609 if (!fnHistogram_.empty())
611 AnalysisDataPlotModulePointer plotm(
612 new AnalysisDataPlotModule(settings.plotSettings()));
613 plotm->setFileName(fnHistogram_);
614 plotm->setTitle("Angle histogram");
615 plotm->setXLabel("Angle (degrees)");
616 plotm->setYLabel("Probability");
617 // TODO: Consider adding information about the second selection,
618 // and/or a subtitle describing what kind of angle this is.
619 for (size_t g = 0; g < sel1_.size(); ++g)
621 plotm->appendLegend(sel1_[g].name());
623 histogramModule_->averager().addModule(plotm);
628 //! Helper method to calculate a vector from two or three positions.
630 calc_vec(int natoms, rvec x[], t_pbc *pbc, rvec xout, rvec cout)
637 pbc_dx(pbc, x[1], x[0], xout);
641 rvec_sub(x[1], x[0], xout);
643 svmul(0.5, xout, cout);
644 rvec_add(x[0], cout, cout);
651 pbc_dx(pbc, x[1], x[0], v1);
652 pbc_dx(pbc, x[2], x[0], v2);
656 rvec_sub(x[1], x[0], v1);
657 rvec_sub(x[2], x[0], v2);
660 rvec_add(x[0], x[1], cout);
661 rvec_add(cout, x[2], cout);
662 svmul(1.0/3.0, cout, cout);
666 GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
672 Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
673 TrajectoryAnalysisModuleData *pdata)
675 AnalysisDataHandle dh = pdata->dataHandle(angles_);
676 const SelectionList &sel1 = pdata->parallelSelections(sel1_);
677 const SelectionList &sel2 = pdata->parallelSelections(sel2_);
679 checkSelections(sel1, sel2);
681 dh.startFrame(frnr, fr.time);
683 AnglePositionIterator iter1(sel1, natoms1_);
684 AnglePositionIterator iter2(sel2, natoms2_);
685 for (size_t g = 0; g < angleCount_.size(); ++g, iter1.nextGroup(), iter2.nextGroup())
697 copy_rvec(sel2_[g].position(0).x(), c2);
701 for (int n = 0; n < angleCount_[g]; ++n, iter1.nextValue(), iter2.nextValue())
705 // checkSelections() ensures that this reflects all the involved
707 const bool bPresent =
708 iter1.currentValuesSelected() && iter2.currentValuesSelected();
709 iter1.getCurrentPositions(x);
715 pbc_dx(pbc, x[0], x[1], v1);
716 pbc_dx(pbc, x[2], x[1], v2);
720 rvec_sub(x[0], x[1], v1);
721 rvec_sub(x[2], x[1], v2);
723 angle = gmx_angle(v1, v2);
730 pbc_dx(pbc, x[0], x[1], dx[0]);
731 pbc_dx(pbc, x[2], x[1], dx[1]);
732 pbc_dx(pbc, x[2], x[3], dx[2]);
736 rvec_sub(x[0], x[1], dx[0]);
737 rvec_sub(x[2], x[1], dx[1]);
738 rvec_sub(x[2], x[3], dx[2]);
740 cprod(dx[0], dx[1], v1);
741 cprod(dx[1], dx[2], v2);
742 angle = gmx_angle(v1, v2);
743 real ipr = iprod(dx[0], v2);
752 calc_vec(natoms1_, x, pbc, v1, c1);
757 iter2.getCurrentPositions(x);
758 calc_vec(natoms2_, x, pbc, v2, c2);
761 // FIXME: This is not parallelizable.
764 copy_rvec(v1, vt0_[g][n]);
766 copy_rvec(vt0_[g][n], v2);
769 c1[XX] = c1[YY] = 0.0;
774 pbc_dx(pbc, c1, c2, v2);
778 rvec_sub(c1, c2, v2);
782 GMX_THROW(InternalError("invalid -g2 value"));
784 angle = gmx_angle(v1, v2);
787 GMX_THROW(InternalError("invalid -g1 value"));
789 dh.setPoint(n, angle * RAD2DEG, bPresent);
797 Angle::finishAnalysis(int /*nframes*/)
799 AbstractAverageHistogram &averageHistogram = histogramModule_->averager();
800 averageHistogram.normalizeProbability();
801 averageHistogram.done();
812 const char AngleInfo::name[] = "gangle";
813 const char AngleInfo::shortDescription[] =
816 TrajectoryAnalysisModulePointer AngleInfo::create()
818 return TrajectoryAnalysisModulePointer(new Angle);
821 } // namespace analysismodules