2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013, by the GROMACS development team, led by
5 * David van der Spoel, Berk Hess, Erik Lindahl, and including many
6 * others, as listed in the AUTHORS file in the top-level source
7 * 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
44 #include "gromacs/legacyheaders/pbc.h"
45 #include "gromacs/legacyheaders/vec.h"
47 #include "gromacs/analysisdata/analysisdata.h"
48 #include "gromacs/analysisdata/modules/plot.h"
49 #include "gromacs/options/basicoptions.h"
50 #include "gromacs/options/filenameoption.h"
51 #include "gromacs/options/options.h"
52 #include "gromacs/selection/selection.h"
53 #include "gromacs/selection/selectionoption.h"
54 #include "gromacs/trajectoryanalysis/analysissettings.h"
55 #include "gromacs/utility/exceptions.h"
56 #include "gromacs/utility/gmxassert.h"
57 #include "gromacs/utility/stringutil.h"
62 namespace analysismodules
65 const char Angle::name[] = "gangle";
66 const char Angle::shortDescription[] =
70 : TrajectoryAnalysisModule(name, shortDescription),
71 sel1info_(NULL), sel2info_(NULL), binWidth_(1.0), natoms1_(0), natoms2_(0)
73 averageModule_.reset(new AnalysisDataFrameAverageModule());
74 angles_.addModule(averageModule_);
75 histogramModule_.reset(new AnalysisDataSimpleHistogramModule());
76 angles_.addModule(histogramModule_);
78 registerAnalysisDataset(&angles_, "angle");
79 registerBasicDataset(averageModule_.get(), "average");
80 registerBasicDataset(&histogramModule_->averager(), "histogram");
86 for (size_t g = 0; g < vt0_.size(); ++g)
94 Angle::initOptions(Options *options, TrajectoryAnalysisSettings * /*settings*/)
96 static const char *const desc[] = {
97 "g_angle computes different types of angles between vectors.",
98 "It supports both vectors defined by two positions and normals of",
99 "planes defined by three positions.",
100 "The z axis or the local normal of a sphere can also be used as",
101 "one of the vectors.",
102 "There are also convenience options 'angle' and 'dihedral' for",
103 "calculating bond angles and dihedrals defined by three/four",
105 "The type of the angle is specified with [TT]-g1[tt] and [TT]-g2[tt].",
106 "If [TT]-g1[tt] is [TT]angle[tt] or [TT]dihedral[tt], [TT]-g2[tt]",
107 "should not be specified.",
108 "In this case, [TT]-group1[tt] should specify one or more selections,",
109 "and each should contain triplets or quartets of positions that define",
110 "the angles to be calculated.[PAR]",
111 "If [TT]-g1[tt] is [TT]vector[tt] or [TT]plane[tt], [TT]-group1[tt]",
112 "should specify selections that contain either pairs ([TT]vector[tt])",
113 "or triplets ([TT]plane[tt]) of positions. For vectors, the positions",
114 "set the endpoints of the vector, and for planes, the three positions",
115 "are used to calculate the normal of the plane. In both cases,",
116 "[TT]-g2[tt] specifies the other vector to use (see below).[PAR]",
117 "With [TT]-g2 vector[tt] or [TT]-g2 plane[tt], [TT]-group2[tt] should",
118 "specify another set of vectors. [TT]-group1[tt] and [TT]-group2[tt]",
119 "should specify the same number of selections, and for each selection",
120 "in [TT]-group1[tt], the corresponding selection in [TT]-group2[tt]",
121 "should specify the same number of vectors.[PAR]",
122 "With [TT]-g2 sphnorm[tt], each selection in [TT]-group2[tt] should",
123 "specify a single position that is the center of the sphere.",
124 "The second vector is calculated as the vector from the center to the",
125 "midpoint of the positions specified by [TT]-group1[tt].[PAR]",
126 "With [TT]-g2 z[tt], [TT]-group2[tt] is not necessary, and angles",
127 "between the first vectors and the positive Z axis are calculated.[PAR]",
128 "With [TT]-g2 t0[tt], [TT]-group2[tt] is not necessary, and angles",
129 "are calculated from the vectors as they are in the first frame.[PAR]",
130 "There are three options for output:",
131 "[TT]-oav[tt] writes an xvgr file with the time and the average angle",
133 "[TT]-oall[tt] writes all the individual angles.",
134 "[TT]-oh[tt] writes a histogram of the angles. The bin width can be",
135 "set with [TT]-binw[tt].",
136 "For [TT]-oav[tt] and [TT]-oh[tt], separate average/histogram is",
137 "computed for each selection in [TT]-group1[tt]."
139 static const char *const cGroup1TypeEnum[] =
140 { "angle", "dihedral", "vector", "plane" };
141 static const char *const cGroup2TypeEnum[] =
142 { "none", "vector", "plane", "t0", "z", "sphnorm" };
144 options->setDescription(concatenateStrings(desc));
146 options->addOption(FileNameOption("oav").filetype(eftPlot).outputFile()
147 .store(&fnAverage_).defaultBasename("angaver")
148 .description("Average angles as a function of time"));
149 options->addOption(FileNameOption("oall").filetype(eftPlot).outputFile()
150 .store(&fnAll_).defaultBasename("angles")
151 .description("All angles as a function of time"));
152 options->addOption(FileNameOption("oh").filetype(eftPlot).outputFile()
153 .store(&fnHistogram_).defaultBasename("anghist")
154 .description("Histogram of the angles"));
156 options->addOption(StringOption("g1").enumValue(cGroup1TypeEnum)
157 .defaultEnumIndex(0).store(&g1type_)
158 .description("Type of analysis/first vector group"));
159 options->addOption(StringOption("g2").enumValue(cGroup2TypeEnum)
160 .defaultEnumIndex(0).store(&g2type_)
161 .description("Type of second vector group"));
162 options->addOption(DoubleOption("binw").store(&binWidth_)
163 .description("Binwidth for -oh in degrees"));
165 sel1info_ = options->addOption(SelectionOption("group1")
166 .required().dynamicMask().storeVector(&sel1_)
168 .description("First analysis/vector selection"));
169 sel2info_ = options->addOption(SelectionOption("group2")
170 .dynamicMask().storeVector(&sel2_)
172 .description("Second analysis/vector selection"));
177 Angle::optionsFinished(Options *options, TrajectoryAnalysisSettings *settings)
179 bool bSingle = (g1type_[0] == 'a' || g1type_[0] == 'd');
181 if (bSingle && g2type_[0] != 'n')
183 GMX_THROW(InconsistentInputError("Cannot use a second group (-g2) with "
184 "-g1 angle or dihedral"));
186 if (bSingle && options->isSet("group2"))
188 GMX_THROW(InconsistentInputError("Cannot provide a second selection "
189 "(-group2) with -g1 angle or dihedral"));
191 if (!bSingle && g2type_[0] == 'n')
193 GMX_THROW(InconsistentInputError("Should specify a second group (-g2) "
194 "if the first group is not an angle or a dihedral"));
197 // Set up the number of positions per angle.
200 case 'a': natoms1_ = 3; break;
201 case 'd': natoms1_ = 4; break;
202 case 'v': natoms1_ = 2; break;
203 case 'p': natoms1_ = 3; break;
205 GMX_THROW(InternalError("invalid -g1 value"));
209 case 'n': natoms2_ = 0; break;
210 case 'v': natoms2_ = 2; break;
211 case 'p': natoms2_ = 3; break;
212 case 't': natoms2_ = 0; break;
213 case 'z': natoms2_ = 0; break;
214 case 's': natoms2_ = 1; break;
216 GMX_THROW(InternalError("invalid -g2 value"));
218 if (natoms2_ == 0 && options->isSet("group2"))
220 GMX_THROW(InconsistentInputError("Cannot provide a second selection (-group2) with -g2 t0 or z"));
226 Angle::checkSelections(const SelectionList &sel1,
227 const SelectionList &sel2) const
229 if (natoms2_ > 0 && sel1.size() != sel2.size())
231 GMX_THROW(InconsistentInputError(
232 "-group1 and -group2 should specify the same number of selections"));
235 for (size_t g = 0; g < sel1.size(); ++g)
237 const int na1 = sel1[g].posCount();
238 const int na2 = (natoms2_ > 0) ? sel2[g].posCount() : 0;
239 if (natoms1_ > 1 && na1 % natoms1_ != 0)
241 GMX_THROW(InconsistentInputError(formatString(
242 "Number of positions in selection %d in the first group not divisible by %d",
243 static_cast<int>(g + 1), natoms1_)));
245 if (natoms2_ > 1 && na2 % natoms2_ != 0)
247 GMX_THROW(InconsistentInputError(formatString(
248 "Number of positions in selection %d in the second group not divisible by %d",
249 static_cast<int>(g + 1), natoms2_)));
251 if (natoms1_ > 0 && natoms2_ > 1 && na1 / natoms1_ != na2 / natoms2_)
253 GMX_THROW(InconsistentInputError(
254 "Number of vectors defined by the two groups are not the same"));
256 if (g2type_[0] == 's' && sel2[g].posCount() != 1)
258 GMX_THROW(InconsistentInputError(
259 "The second group should contain a single position with -g2 sphnorm"));
261 if (sel1[g].isDynamic() || (natoms2_ > 0 && sel2[g].isDynamic()))
263 for (int i = 0, j = 0; i < na1; i += natoms1_, j += natoms2_)
265 const bool bSelected = sel1[g].position(i).selected();
267 for (int k = 1; k < natoms1_ && bOk; ++k)
269 bOk = (sel1[g].position(i+k).selected() == bSelected);
271 for (int k = 1; k < natoms2_ && bOk; ++k)
273 bOk = (sel2[g].position(j+k).selected() == bSelected);
277 std::string message =
278 formatString("Dynamic selection %d does not select "
279 "a consistent set of angles over the frames",
280 static_cast<int>(g + 1));
281 GMX_THROW(InconsistentInputError(message));
290 Angle::initAnalysis(const TrajectoryAnalysisSettings &settings,
291 const TopologyInformation &top)
293 checkSelections(sel1_, sel2_);
295 // checkSelections() ensures that both selection lists have the same size.
296 angles_.setDataSetCount(sel1_.size());
297 for (size_t i = 0; i < sel1_.size(); ++i)
299 angles_.setColumnCount(i, sel1_[i].posCount() / natoms1_);
301 double histogramMin = (g1type_ == "dihedral" ? -180.0 : 0);
302 histogramModule_->init(histogramFromRange(histogramMin, 180.0)
303 .binWidth(binWidth_).includeAll());
307 vt0_.resize(sel1_.size());
308 for (size_t g = 0; g < sel1_.size(); ++g)
310 vt0_[g] = new rvec[sel1_[g].posCount() / natoms1_];
314 if (!fnAverage_.empty())
316 AnalysisDataPlotModulePointer plotm(
317 new AnalysisDataPlotModule(settings.plotSettings()));
318 plotm->setFileName(fnAverage_);
319 plotm->setTitle("Average angle");
320 plotm->setXAxisIsTime();
321 plotm->setYLabel("Angle (degrees)");
322 // TODO: Consider adding information about the second selection,
323 // and/or a subtitle describing what kind of angle this is.
324 for (size_t g = 0; g < sel1_.size(); ++g)
326 plotm->appendLegend(sel1_[g].name());
328 averageModule_->addModule(plotm);
333 AnalysisDataPlotModulePointer plotm(
334 new AnalysisDataPlotModule(settings.plotSettings()));
335 plotm->setFileName(fnAll_);
336 plotm->setTitle("Angle");
337 plotm->setXAxisIsTime();
338 plotm->setYLabel("Angle (degrees)");
339 // TODO: Add legends? (there can be a massive amount of columns)
340 angles_.addModule(plotm);
343 if (!fnHistogram_.empty())
345 AnalysisDataPlotModulePointer plotm(
346 new AnalysisDataPlotModule(settings.plotSettings()));
347 plotm->setFileName(fnHistogram_);
348 plotm->setTitle("Angle histogram");
349 plotm->setXLabel("Angle (degrees)");
350 plotm->setYLabel("Probability");
351 // TODO: Consider adding information about the second selection,
352 // and/or a subtitle describing what kind of angle this is.
353 for (size_t g = 0; g < sel1_.size(); ++g)
355 plotm->appendLegend(sel1_[g].name());
357 histogramModule_->averager().addModule(plotm);
362 //! Helper method to process selections into an array of coordinates.
364 copy_pos(const SelectionList &sel, int natoms, int g, int first, rvec x[])
366 for (int k = 0; k < natoms; ++k)
368 copy_rvec(sel[g].position(first + k).x(), x[k]);
373 //! Helper method to calculate a vector from two or three positions.
375 calc_vec(int natoms, rvec x[], t_pbc *pbc, rvec xout, rvec cout)
382 pbc_dx(pbc, x[1], x[0], xout);
386 rvec_sub(x[1], x[0], xout);
388 svmul(0.5, xout, cout);
389 rvec_add(x[0], cout, cout);
396 pbc_dx(pbc, x[1], x[0], v1);
397 pbc_dx(pbc, x[2], x[0], v2);
401 rvec_sub(x[1], x[0], v1);
402 rvec_sub(x[2], x[0], v2);
405 rvec_add(x[0], x[1], cout);
406 rvec_add(cout, x[2], cout);
407 svmul(1.0/3.0, cout, cout);
411 GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
417 Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
418 TrajectoryAnalysisModuleData *pdata)
420 AnalysisDataHandle dh = pdata->dataHandle(angles_);
421 const SelectionList &sel1 = pdata->parallelSelections(sel1_);
422 const SelectionList &sel2 = pdata->parallelSelections(sel2_);
424 checkSelections(sel1, sel2);
426 dh.startFrame(frnr, fr.time);
428 for (size_t g = 0; g < sel1_.size(); ++g)
440 copy_rvec(sel2_[g].position(0).x(), c2);
444 for (int i = 0, j = 0, n = 0;
445 i < sel1[g].posCount();
446 i += natoms1_, j += natoms2_, ++n)
450 // checkSelections() ensures that this reflects all the involved
452 bool bPresent = sel1[g].position(i).selected();
453 copy_pos(sel1, natoms1_, g, i, x);
459 pbc_dx(pbc, x[0], x[1], v1);
460 pbc_dx(pbc, x[2], x[1], v2);
464 rvec_sub(x[0], x[1], v1);
465 rvec_sub(x[2], x[1], v2);
467 angle = gmx_angle(v1, v2);
474 pbc_dx(pbc, x[0], x[1], dx[0]);
475 pbc_dx(pbc, x[2], x[1], dx[1]);
476 pbc_dx(pbc, x[2], x[3], dx[2]);
480 rvec_sub(x[0], x[1], dx[0]);
481 rvec_sub(x[2], x[1], dx[1]);
482 rvec_sub(x[2], x[3], dx[2]);
484 cprod(dx[0], dx[1], v1);
485 cprod(dx[1], dx[2], v2);
486 angle = gmx_angle(v1, v2);
487 real ipr = iprod(dx[0], v2);
496 calc_vec(natoms1_, x, pbc, v1, c1);
501 copy_pos(sel2, natoms2_, 0, j, x);
502 calc_vec(natoms2_, x, pbc, v2, c2);
505 // FIXME: This is not parallelizable.
508 copy_rvec(v1, vt0_[g][n]);
510 copy_rvec(vt0_[g][n], v2);
513 c1[XX] = c1[YY] = 0.0;
518 pbc_dx(pbc, c1, c2, v2);
522 rvec_sub(c1, c2, v2);
526 GMX_THROW(InternalError("invalid -g2 value"));
528 angle = gmx_angle(v1, v2);
531 GMX_THROW(InternalError("invalid -g1 value"));
533 dh.setPoint(n, angle * RAD2DEG, bPresent);
541 Angle::finishAnalysis(int /*nframes*/)
543 AbstractAverageHistogram &averageHistogram = histogramModule_->averager();
544 averageHistogram.normalizeProbability();
545 averageHistogram.done();
554 } // namespace modules
556 } // namespace gmxana