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)");
323 averageModule_->addModule(plotm);
328 AnalysisDataPlotModulePointer plotm(
329 new AnalysisDataPlotModule(settings.plotSettings()));
330 plotm->setFileName(fnAll_);
331 plotm->setTitle("Angle");
332 plotm->setXAxisIsTime();
333 plotm->setYLabel("Angle (degrees)");
334 // TODO: Add legends? (there can be a massive amount of columns)
335 angles_.addModule(plotm);
338 if (!fnHistogram_.empty())
340 AnalysisDataPlotModulePointer plotm(
341 new AnalysisDataPlotModule(settings.plotSettings()));
342 plotm->setFileName(fnHistogram_);
343 plotm->setTitle("Angle histogram");
344 plotm->setXLabel("Angle (degrees)");
345 plotm->setYLabel("Probability");
347 histogramModule_->averager().addModule(plotm);
352 //! Helper method to process selections into an array of coordinates.
354 copy_pos(const SelectionList &sel, int natoms, int g, int first, rvec x[])
356 for (int k = 0; k < natoms; ++k)
358 copy_rvec(sel[g].position(first + k).x(), x[k]);
363 //! Helper method to calculate a vector from two or three positions.
365 calc_vec(int natoms, rvec x[], t_pbc *pbc, rvec xout, rvec cout)
372 pbc_dx(pbc, x[1], x[0], xout);
376 rvec_sub(x[1], x[0], xout);
378 svmul(0.5, xout, cout);
379 rvec_add(x[0], cout, cout);
386 pbc_dx(pbc, x[1], x[0], v1);
387 pbc_dx(pbc, x[2], x[0], v2);
391 rvec_sub(x[1], x[0], v1);
392 rvec_sub(x[2], x[0], v2);
395 rvec_add(x[0], x[1], cout);
396 rvec_add(cout, x[2], cout);
397 svmul(1.0/3.0, cout, cout);
401 GMX_RELEASE_ASSERT(false, "Incorrectly initialized number of atoms");
407 Angle::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
408 TrajectoryAnalysisModuleData *pdata)
410 AnalysisDataHandle dh = pdata->dataHandle(angles_);
411 const SelectionList &sel1 = pdata->parallelSelections(sel1_);
412 const SelectionList &sel2 = pdata->parallelSelections(sel2_);
414 checkSelections(sel1, sel2);
416 dh.startFrame(frnr, fr.time);
418 for (size_t g = 0; g < sel1_.size(); ++g)
430 copy_rvec(sel2_[g].position(0).x(), c2);
434 for (int i = 0, j = 0, n = 0;
435 i < sel1[g].posCount();
436 i += natoms1_, j += natoms2_, ++n)
440 // checkSelections() ensures that this reflects all the involved
442 bool bPresent = sel1[g].position(i).selected();
443 copy_pos(sel1, natoms1_, g, i, x);
449 pbc_dx(pbc, x[0], x[1], v1);
450 pbc_dx(pbc, x[2], x[1], v2);
454 rvec_sub(x[0], x[1], v1);
455 rvec_sub(x[2], x[1], v2);
457 angle = gmx_angle(v1, v2);
464 pbc_dx(pbc, x[0], x[1], dx[0]);
465 pbc_dx(pbc, x[2], x[1], dx[1]);
466 pbc_dx(pbc, x[2], x[3], dx[2]);
470 rvec_sub(x[0], x[1], dx[0]);
471 rvec_sub(x[2], x[1], dx[1]);
472 rvec_sub(x[2], x[3], dx[2]);
474 cprod(dx[0], dx[1], v1);
475 cprod(dx[1], dx[2], v2);
476 angle = gmx_angle(v1, v2);
477 real ipr = iprod(dx[0], v2);
486 calc_vec(natoms1_, x, pbc, v1, c1);
491 copy_pos(sel2, natoms2_, 0, j, x);
492 calc_vec(natoms2_, x, pbc, v2, c2);
495 // FIXME: This is not parallelizable.
498 copy_rvec(v1, vt0_[g][n]);
500 copy_rvec(vt0_[g][n], v2);
503 c1[XX] = c1[YY] = 0.0;
508 pbc_dx(pbc, c1, c2, v2);
512 rvec_sub(c1, c2, v2);
516 GMX_THROW(InternalError("invalid -g2 value"));
518 angle = gmx_angle(v1, v2);
521 GMX_THROW(InternalError("invalid -g1 value"));
523 dh.setPoint(n, angle * RAD2DEG, bPresent);
531 Angle::finishAnalysis(int /*nframes*/)
533 AbstractAverageHistogram &averageHistogram = histogramModule_->averager();
534 averageHistogram.normalizeProbability();
535 averageHistogram.done();
544 } // namespace modules
546 } // namespace gmxana