Use gmx_mtop_t in selections, part 2
[gromacs.git] / src / gromacs / trajectoryanalysis / modules / rdf.cpp
blob664682e0abeaecb92e262eef4ac6f76b5a7369e4
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5 * Copyright (c) 2001-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016, by the GROMACS development team, led by
7 * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
8 * and including many others, as listed in the AUTHORS file in the
9 * top-level source directory and at http://www.gromacs.org.
11 * GROMACS is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * as published by the Free Software Foundation; either version 2.1
14 * of the License, or (at your option) any later version.
16 * GROMACS is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with GROMACS; if not, see
23 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 * If you want to redistribute modifications to GROMACS, please
27 * consider that scientific software is very special. Version
28 * control is crucial - bugs must be traceable. We will be happy to
29 * consider code for inclusion in the official distribution, but
30 * derived work must not be called official GROMACS. Details are found
31 * in the README & COPYING files - if they are missing, get the
32 * official version at http://www.gromacs.org.
34 * To help us fund GROMACS development, we humbly ask that you cite
35 * the research papers on the package. Check out http://www.gromacs.org.
37 /*! \internal \file
38 * \brief
39 * Implements gmx::analysismodules::Rdf.
41 * \author Teemu Murtola <teemu.murtola@gmail.com> (C++ conversion)
42 * \ingroup module_trajectoryanalysis
44 #include "gmxpre.h"
46 #include "rdf.h"
48 #include <cmath>
50 #include <algorithm>
51 #include <limits>
52 #include <string>
53 #include <vector>
55 #include "gromacs/analysisdata/analysisdata.h"
56 #include "gromacs/analysisdata/modules/average.h"
57 #include "gromacs/analysisdata/modules/histogram.h"
58 #include "gromacs/analysisdata/modules/plot.h"
59 #include "gromacs/math/functions.h"
60 #include "gromacs/math/utilities.h"
61 #include "gromacs/math/vec.h"
62 #include "gromacs/options/basicoptions.h"
63 #include "gromacs/options/filenameoption.h"
64 #include "gromacs/options/ioptionscontainer.h"
65 #include "gromacs/pbcutil/pbc.h"
66 #include "gromacs/selection/nbsearch.h"
67 #include "gromacs/selection/selection.h"
68 #include "gromacs/selection/selectionoption.h"
69 #include "gromacs/topology/topology.h"
70 #include "gromacs/trajectory/trajectoryframe.h"
71 #include "gromacs/trajectoryanalysis/analysismodule.h"
72 #include "gromacs/trajectoryanalysis/analysissettings.h"
73 #include "gromacs/utility/exceptions.h"
74 #include "gromacs/utility/stringutil.h"
76 namespace gmx
79 namespace analysismodules
82 namespace
85 //! \addtogroup module_trajectoryanalysis
86 //! \{
88 /********************************************************************
89 * Actual analysis module
92 //! Normalization for the computed distribution.
93 enum Normalization
95 Normalization_Rdf,
96 Normalization_NumberDensity,
97 Normalization_None
99 //! String values corresponding to Normalization.
100 const char *const c_NormalizationEnum[] = { "rdf", "number_density", "none" };
101 //! Whether to compute RDF wrt. surface of the reference group.
102 enum SurfaceType
104 SurfaceType_None,
105 SurfaceType_Molecule,
106 SurfaceType_Residue
108 //! String values corresponding to SurfaceType.
109 const char *const c_SurfaceEnum[] = { "no", "mol", "res" };
111 /*! \brief
112 * Implements `gmx rdf` trajectory analysis module.
114 class Rdf : public TrajectoryAnalysisModule
116 public:
117 Rdf();
119 virtual void initOptions(IOptionsContainer *options,
120 TrajectoryAnalysisSettings *settings);
121 virtual void optionsFinished(TrajectoryAnalysisSettings *settings);
122 virtual void initAnalysis(const TrajectoryAnalysisSettings &settings,
123 const TopologyInformation &top);
124 virtual void initAfterFirstFrame(const TrajectoryAnalysisSettings &settings,
125 const t_trxframe &fr);
127 virtual TrajectoryAnalysisModuleDataPointer startFrames(
128 const AnalysisDataParallelOptions &opt,
129 const SelectionCollection &selections);
130 virtual void analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
131 TrajectoryAnalysisModuleData *pdata);
133 virtual void finishAnalysis(int nframes);
134 virtual void writeOutput();
136 private:
137 std::string fnRdf_;
138 std::string fnCumulative_;
139 SurfaceType surface_;
140 AnalysisDataPlotSettings plotSettings_;
142 /*! \brief
143 * Reference selection to compute RDFs around.
145 * With -surf, Selection::originalIds() and Selection::mappedIds()
146 * store the index of the surface group to which that position belongs.
147 * The RDF is computed by finding the nearest position from each
148 * surface group for each position, and then binning those distances.
150 Selection refSel_;
151 /*! \brief
152 * Selections to compute RDFs for.
154 SelectionList sel_;
156 /*! \brief
157 * Raw pairwise distance data from which the RDF is computed.
159 * There is a data set for each selection in `sel_`, with a single
160 * column. Each point set will contain a single pairwise distance
161 * that contributes to the RDF.
163 AnalysisData pairDist_;
164 /*! \brief
165 * Normalization factors for each frame.
167 * The first column contains the number of positions in `refSel_` for
168 * that frame (with surface RDF, the number of groups). There are
169 * `sel_.size()` more columns, each containing the number density of
170 * positions for one selection.
172 AnalysisData normFactors_;
173 /*! \brief
174 * Histogram module that computes the actual RDF from `pairDist_`.
176 * The per-frame histograms are raw pair counts in each bin;
177 * the averager is normalized by the average number of reference
178 * positions (average of the first column of `normFactors_`).
180 AnalysisDataSimpleHistogramModulePointer pairCounts_;
181 /*! \brief
182 * Average normalization factors.
184 AnalysisDataAverageModulePointer normAve_;
185 //! Neighborhood search with `refSel_` as the reference positions.
186 AnalysisNeighborhood nb_;
188 // User input options.
189 double binwidth_;
190 double cutoff_;
191 double rmax_;
192 Normalization normalization_;
193 bool bNormalizationSet_;
194 bool bXY_;
195 bool bExclusions_;
197 // Pre-computed values for faster access during analysis.
198 real cut2_;
199 real rmax2_;
200 int surfaceGroupCount_;
202 // Copy and assign disallowed by base.
205 Rdf::Rdf()
206 : surface_(SurfaceType_None),
207 pairCounts_(new AnalysisDataSimpleHistogramModule()),
208 normAve_(new AnalysisDataAverageModule()),
209 binwidth_(0.002), cutoff_(0.0), rmax_(0.0),
210 normalization_(Normalization_Rdf), bNormalizationSet_(false), bXY_(false),
211 bExclusions_(false),
212 cut2_(0.0), rmax2_(0.0), surfaceGroupCount_(0)
214 pairDist_.setMultipoint(true);
215 pairDist_.addModule(pairCounts_);
216 registerAnalysisDataset(&pairDist_, "pairdist");
217 registerBasicDataset(pairCounts_.get(), "paircount");
219 normFactors_.addModule(normAve_);
220 registerAnalysisDataset(&normFactors_, "norm");
223 void
224 Rdf::initOptions(IOptionsContainer *options, TrajectoryAnalysisSettings *settings)
226 const char *const desc[] = {
227 "[THISMODULE] calculates radial distribution functions from one",
228 "reference set of position (set with [TT]-ref[tt]) to one or more",
229 "sets of positions (set with [TT]-sel[tt]). To compute the RDF with",
230 "respect to the closest position in a set in [TT]-ref[tt] instead, use",
231 "[TT]-surf[tt]: if set, then [TT]-ref[tt] is partitioned into sets",
232 "based on the value of [TT]-surf[tt], and the closest position in each",
233 "set is used. To compute the RDF around axes parallel to the",
234 "[IT]z[it]-axis, i.e., only in the [IT]x[it]-[IT]y[it] plane, use",
235 "[TT]-xy[tt].",
237 "To set the bin width and maximum distance to use in the RDF, use",
238 "[TT]-bin[tt] and [TT]-rmax[tt], respectively. The latter can be",
239 "used to limit the computational cost if the RDF is not of interest",
240 "up to the default (half of the box size with PBC, three times the",
241 "box size without PBC).",
243 "To use exclusions from the topology ([TT]-s[tt]), set [TT]-excl[tt]",
244 "and ensure that both [TT]-ref[tt] and [TT]-sel[tt] only select atoms.",
245 "A rougher alternative to exclude intra-molecular peaks is to set",
246 "[TT]-cut[tt] to a non-zero value to clear the RDF at small",
247 "distances.",
249 "The RDFs are normalized by 1) average number of positions in",
250 "[TT]-ref[tt] (the number of groups with [TT]-surf[tt]), 2) volume",
251 "of the bin, and 3) average particle density of [TT]-sel[tt] positions",
252 "for that selection. To change the normalization, use [TT]-norm[tt]:",
254 "* [TT]rdf[tt]: Use all factors for normalization.",
255 " This produces a normal RDF.",
256 "* [TT]number_density[tt]: Use the first two factors.",
257 " This produces a number density as a function of distance.",
258 "* [TT]none[tt]: Use only the first factor.",
259 " In this case, the RDF is only scaled with the bin width to make",
260 " the integral of the curve represent the number of pairs within a",
261 " range.",
263 "Note that exclusions do not affect the normalization: even if",
264 "[TT]-excl[tt] is set, or [TT]-ref[tt] and",
265 "[TT]-sel[tt] contain the same selection, the normalization factor",
266 "is still N*M, not N*(M-excluded).",
268 "For [TT]-surf[tt], the selection provided to [TT]-ref[tt] must",
269 "select atoms, i.e., centers of mass are not supported. Further,",
270 "[TT]-nonorm[tt] is implied, as the bins have irregular shapes and",
271 "the volume of a bin is not easily computable.",
273 "Option [TT]-cn[tt] produces the cumulative number RDF,",
274 "i.e. the average number of particles within a distance r."
277 settings->setHelpText(desc);
279 options->addOption(FileNameOption("o").filetype(eftPlot).outputFile().required()
280 .store(&fnRdf_).defaultBasename("rdf")
281 .description("Computed RDFs"));
282 options->addOption(FileNameOption("cn").filetype(eftPlot).outputFile()
283 .store(&fnCumulative_).defaultBasename("rdf_cn")
284 .description("Cumulative RDFs"));
286 options->addOption(DoubleOption("bin").store(&binwidth_)
287 .description("Bin width (nm)"));
288 options->addOption(EnumOption<Normalization>("norm").enumValue(c_NormalizationEnum)
289 .store(&normalization_)
290 .storeIsSet(&bNormalizationSet_)
291 .description("Normalization"));
292 options->addOption(BooleanOption("xy").store(&bXY_)
293 .description("Use only the x and y components of the distance"));
294 options->addOption(BooleanOption("excl").store(&bExclusions_)
295 .description("Use exclusions from topology"));
296 options->addOption(DoubleOption("cut").store(&cutoff_)
297 .description("Shortest distance (nm) to be considered"));
298 options->addOption(DoubleOption("rmax").store(&rmax_)
299 .description("Largest distance (nm) to calculate"));
301 options->addOption(EnumOption<SurfaceType>("surf").enumValue(c_SurfaceEnum)
302 .store(&surface_)
303 .description("RDF with respect to the surface of the reference"));
305 options->addOption(SelectionOption("ref").store(&refSel_).required()
306 .description("Reference selection for RDF computation"));
307 options->addOption(SelectionOption("sel").storeVector(&sel_)
308 .required().multiValue()
309 .description("Selections to compute RDFs for from the reference"));
312 void
313 Rdf::optionsFinished(TrajectoryAnalysisSettings *settings)
315 if (surface_ != SurfaceType_None)
317 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
319 if (bNormalizationSet_ && normalization_ != Normalization_None)
321 GMX_THROW(InconsistentInputError("-surf cannot be combined with -norm"));
323 normalization_ = Normalization_None;
324 if (bExclusions_)
326 GMX_THROW(InconsistentInputError("-surf cannot be combined with -excl"));
329 if (bExclusions_)
331 settings->setFlag(TrajectoryAnalysisSettings::efRequireTop);
333 if (cutoff_ < 0.0)
335 cutoff_ = 0.0;
339 void
340 Rdf::initAnalysis(const TrajectoryAnalysisSettings &settings,
341 const TopologyInformation &top)
343 pairDist_.setDataSetCount(sel_.size());
344 for (size_t i = 0; i < sel_.size(); ++i)
346 pairDist_.setColumnCount(i, 1);
348 plotSettings_ = settings.plotSettings();
349 nb_.setXYMode(bXY_);
351 normFactors_.setColumnCount(0, sel_.size() + 1);
353 const bool bSurface = (surface_ != SurfaceType_None);
354 if (bSurface)
356 if (!refSel_.hasOnlyAtoms())
358 GMX_THROW(InconsistentInputError("-surf only works with -ref that consists of atoms"));
360 const e_index_t type = (surface_ == SurfaceType_Molecule ? INDEX_MOL : INDEX_RES);
361 surfaceGroupCount_ = refSel_.initOriginalIdsToGroup(top.mtop(), type);
364 if (bExclusions_)
366 if (!refSel_.hasOnlyAtoms() || !refSel_.hasSortedAtomIndices())
368 GMX_THROW(InconsistentInputError("-excl only works with a -ref selection that consist of atoms in ascending (sorted) order"));
370 for (size_t i = 0; i < sel_.size(); ++i)
372 if (!sel_[i].hasOnlyAtoms())
374 GMX_THROW(InconsistentInputError("-excl only works with selections that consist of atoms"));
377 const t_topology *topology = top.topology();
378 if (topology->excls.nr == 0)
380 GMX_THROW(InconsistentInputError("-excl is set, but the file provided to -s does not define exclusions"));
382 nb_.setTopologyExclusions(&topology->excls);
386 void
387 Rdf::initAfterFirstFrame(const TrajectoryAnalysisSettings &settings,
388 const t_trxframe &fr)
390 // If -rmax is not provided, determine one from the box for the first frame.
391 if (rmax_ <= 0.0)
393 matrix box;
394 copy_mat(fr.box, box);
395 if (settings.hasPBC())
397 if (bXY_)
399 box[ZZ][ZZ] = 2*std::max(box[XX][XX], box[YY][YY]);
401 rmax_ = std::sqrt(0.99*0.99*max_cutoff2(bXY_ ? epbcXY : epbcXYZ, box));
403 else
405 if (bXY_)
407 clear_rvec(box[ZZ]);
409 rmax_ = 3*std::max(box[XX][XX], std::max(box[YY][YY], box[ZZ][ZZ]));
412 cut2_ = gmx::square(cutoff_);
413 rmax2_ = gmx::square(rmax_);
414 nb_.setCutoff(rmax_);
415 // We use the double amount of bins, so we can correctly
416 // write the rdf and rdf_cn output at i*binwidth values.
417 pairCounts_->init(histogramFromRange(0.0, rmax_).binWidth(binwidth_ / 2.0));
420 /*! \brief
421 * Temporary memory for use within a single-frame calculation.
423 class RdfModuleData : public TrajectoryAnalysisModuleData
425 public:
426 /*! \brief
427 * Reserves memory for the frame-local data.
429 * `surfaceGroupCount` will be zero if -surf is not specified.
431 RdfModuleData(TrajectoryAnalysisModule *module,
432 const AnalysisDataParallelOptions &opt,
433 const SelectionCollection &selections,
434 int surfaceGroupCount)
435 : TrajectoryAnalysisModuleData(module, opt, selections)
437 surfaceDist2_.resize(surfaceGroupCount);
440 virtual void finish() { finishDataHandles(); }
442 /*! \brief
443 * Minimum distance to each surface group.
445 * One entry for each group (residue/molecule, per -surf) in the
446 * reference selection.
447 * This is needed to support neighborhood searching, which may not
448 * return the reference positions in order: for each position, we need
449 * to search through all the reference positions and update this array
450 * to find the minimum distance to each surface group, and then compute
451 * the RDF from these numbers.
453 std::vector<real> surfaceDist2_;
456 TrajectoryAnalysisModuleDataPointer Rdf::startFrames(
457 const AnalysisDataParallelOptions &opt,
458 const SelectionCollection &selections)
460 return TrajectoryAnalysisModuleDataPointer(
461 new RdfModuleData(this, opt, selections, surfaceGroupCount_));
464 void
465 Rdf::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
466 TrajectoryAnalysisModuleData *pdata)
468 AnalysisDataHandle dh = pdata->dataHandle(pairDist_);
469 AnalysisDataHandle nh = pdata->dataHandle(normFactors_);
470 const Selection &refSel = pdata->parallelSelection(refSel_);
471 const SelectionList &sel = pdata->parallelSelections(sel_);
472 RdfModuleData &frameData = *static_cast<RdfModuleData *>(pdata);
473 const bool bSurface = !frameData.surfaceDist2_.empty();
475 matrix boxForVolume;
476 copy_mat(fr.box, boxForVolume);
477 if (bXY_)
479 // Set z-size to 1 so we get the surface are iso the volume
480 clear_rvec(boxForVolume[ZZ]);
481 boxForVolume[ZZ][ZZ] = 1;
483 const real inverseVolume = 1.0 / det(boxForVolume);
485 nh.startFrame(frnr, fr.time);
486 // Compute the normalization factor for the number of reference positions.
487 if (bSurface)
489 if (refSel.isDynamic())
491 // Count the number of distinct groups.
492 // This assumes that each group is continuous, which is currently
493 // the case.
494 int count = 0;
495 int prevId = -1;
496 for (int i = 0; i < refSel.posCount(); ++i)
498 const int id = refSel.position(i).mappedId();
499 if (id != prevId)
501 ++count;
502 prevId = id;
505 nh.setPoint(0, count);
507 else
509 nh.setPoint(0, surfaceGroupCount_);
512 else
514 nh.setPoint(0, refSel.posCount());
517 dh.startFrame(frnr, fr.time);
518 AnalysisNeighborhoodSearch nbsearch = nb_.initSearch(pbc, refSel);
519 for (size_t g = 0; g < sel.size(); ++g)
521 dh.selectDataSet(g);
523 if (bSurface)
525 // Special loop for surface calculation, where a separate neighbor
526 // search is done for each position in the selection, and the
527 // nearest position from each surface group is tracked.
528 std::vector<real> &surfaceDist2 = frameData.surfaceDist2_;
529 for (int i = 0; i < sel[g].posCount(); ++i)
531 std::fill(surfaceDist2.begin(), surfaceDist2.end(),
532 std::numeric_limits<real>::max());
533 AnalysisNeighborhoodPairSearch pairSearch =
534 nbsearch.startPairSearch(sel[g].position(i));
535 AnalysisNeighborhoodPair pair;
536 while (pairSearch.findNextPair(&pair))
538 const real r2 = pair.distance2();
539 const int refId = refSel.position(pair.refIndex()).mappedId();
540 if (r2 < surfaceDist2[refId])
542 surfaceDist2[refId] = r2;
545 // Accumulate the RDF from the distances to the surface.
546 for (size_t i = 0; i < surfaceDist2.size(); ++i)
548 const real r2 = surfaceDist2[i];
549 // Here, we need to check for rmax, since the value might
550 // be above the cutoff if no points were close to some
551 // surface positions.
552 if (r2 > cut2_ && r2 <= rmax2_)
554 dh.setPoint(0, std::sqrt(r2));
555 dh.finishPointSet();
560 else
562 // Standard neighborhood search over all pairs within the cutoff
563 // for the -surf no case.
564 AnalysisNeighborhoodPairSearch pairSearch = nbsearch.startPairSearch(sel[g]);
565 AnalysisNeighborhoodPair pair;
566 while (pairSearch.findNextPair(&pair))
568 const real r2 = pair.distance2();
569 if (r2 > cut2_)
571 // TODO: Consider whether the histogramming could be done with
572 // less overhead (after first measuring the overhead).
573 dh.setPoint(0, std::sqrt(r2));
574 dh.finishPointSet();
578 // Normalization factor for the number density (only used without
579 // -surf, but does not hurt to populate otherwise).
580 nh.setPoint(g + 1, sel[g].posCount() * inverseVolume);
582 dh.finishFrame();
583 nh.finishFrame();
586 void
587 Rdf::finishAnalysis(int /*nframes*/)
589 // Normalize the averager with the number of reference positions,
590 // from where the normalization propagates to all the output.
591 const real refPosCount = normAve_->average(0, 0);
592 pairCounts_->averager().scaleAll(1.0 / refPosCount);
593 pairCounts_->averager().done();
595 // TODO: Consider how these could be exposed to the testing framework
596 // through the dataset registration mechanism.
597 AverageHistogramPointer finalRdf
598 = pairCounts_->averager().resampleDoubleBinWidth(true);
600 if (normalization_ != Normalization_None)
602 // Normalize by the volume of the bins (volume of sphere segments or
603 // length of circle segments).
604 std::vector<real> invBinVolume;
605 const int nbin = finalRdf->settings().binCount();
606 invBinVolume.resize(nbin);
607 real prevSphereVolume = 0.0;
608 for (int i = 0; i < nbin; ++i)
610 const real r = (i + 0.5)*binwidth_;
611 real sphereVolume;
612 if (bXY_)
614 sphereVolume = M_PI*r*r;
616 else
618 sphereVolume = (4.0/3.0)*M_PI*r*r*r;
620 const real binVolume = sphereVolume - prevSphereVolume;
621 invBinVolume[i] = 1.0 / binVolume;
622 prevSphereVolume = sphereVolume;
624 finalRdf->scaleAllByVector(invBinVolume.data());
626 if (normalization_ == Normalization_Rdf)
628 // Normalize by particle density.
629 for (size_t g = 0; g < sel_.size(); ++g)
631 finalRdf->scaleSingle(g, 1.0 / normAve_->average(0, g + 1));
635 else
637 // With no normalization, just scale with bin width to make the
638 // integral of the curve (instead of raw bin sum) represent the pair
639 // count.
640 finalRdf->scaleAll(1.0 / binwidth_);
642 finalRdf->done();
644 // TODO: Consider if some of this should be done in writeOutput().
646 AnalysisDataPlotModulePointer plotm(
647 new AnalysisDataPlotModule(plotSettings_));
648 plotm->setFileName(fnRdf_);
649 plotm->setTitle("Radial distribution");
650 plotm->setSubtitle(formatString("reference %s", refSel_.name()));
651 plotm->setXLabel("r (nm)");
652 plotm->setYLabel("g(r)");
653 for (size_t i = 0; i < sel_.size(); ++i)
655 plotm->appendLegend(sel_[i].name());
657 finalRdf->addModule(plotm);
660 if (!fnCumulative_.empty())
662 AverageHistogramPointer cumulativeRdf
663 = pairCounts_->averager().resampleDoubleBinWidth(false);
664 cumulativeRdf->makeCumulative();
665 cumulativeRdf->done();
667 AnalysisDataPlotModulePointer plotm(
668 new AnalysisDataPlotModule(plotSettings_));
669 plotm->setFileName(fnCumulative_);
670 plotm->setTitle("Cumulative Number RDF");
671 plotm->setSubtitle(formatString("reference %s", refSel_.name()));
672 plotm->setXLabel("r (nm)");
673 plotm->setYLabel("number");
674 for (size_t i = 0; i < sel_.size(); ++i)
676 plotm->appendLegend(sel_[i].name());
678 cumulativeRdf->addModule(plotm);
682 void
683 Rdf::writeOutput()
687 //! \}
689 } // namespace
691 const char RdfInfo::name[] = "rdf";
692 const char RdfInfo::shortDescription[] =
693 "Calculate radial distribution functions";
695 TrajectoryAnalysisModulePointer RdfInfo::create()
697 return TrajectoryAnalysisModulePointer(new Rdf);
700 } // namespace analysismodules
702 } // namespace gmx