2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 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::Freevolume.
39 * \author David van der Spoel <david.vanderspoel@icm.uu.se>
40 * \ingroup module_trajectoryanalysis
42 #include "freevolume.h"
44 #include "gromacs/legacyheaders/pbc.h"
45 #include "gromacs/legacyheaders/vec.h"
46 #include "gromacs/legacyheaders/atomprop.h"
47 #include "gromacs/legacyheaders/copyrite.h"
49 #include "gromacs/analysisdata/analysisdata.h"
50 #include "gromacs/analysisdata/modules/plot.h"
51 #include "gromacs/options/basicoptions.h"
52 #include "gromacs/options/filenameoption.h"
53 #include "gromacs/options/options.h"
54 #include "gromacs/selection/selection.h"
55 #include "gromacs/selection/selectionoption.h"
56 #include "gromacs/trajectoryanalysis/analysissettings.h"
57 #include "gromacs/utility/exceptions.h"
58 #include "gromacs/utility/stringutil.h"
63 namespace analysismodules
66 const char FreeVolume::name[] = "freevolume";
67 const char FreeVolume::shortDescription[] =
68 "Calculate free volume";
70 // Constructor. Here it is important to initialize the pointer to
71 // subclasses that are elements of the main class. Here we have only
72 // one. The type of this depends on what kind of tool you need.
73 // Here we only have simple value/time kind of data.
74 FreeVolume::FreeVolume()
75 : TrajectoryAnalysisModule(name, shortDescription),
76 adata_(new AnalysisDataAverageModule())
78 // We only compute two numbers per frame
79 data_.setColumnCount(0, 2);
80 // Tell the analysis framework that this component exists
81 registerAnalysisDataset(&data_, "freevolume");
92 FreeVolume::~FreeVolume()
94 // Destroy C structures where there is no automatic memory release
95 // C++ takes care of memory in classes (hopefully)
98 gmx_rng_destroy(rng_);
104 FreeVolume::initOptions(Options *options,
105 TrajectoryAnalysisSettings *settings)
107 static const char *const desc[] = {
108 "g_freevol can calculate the free volume in a box as",
109 "a function of time. The free volume is",
110 "plotted as a fraction of the total volume.",
111 "The program tries to insert a probe with a given radius,",
112 "into the simulations box and if the distance between the",
113 "probe and any atom is less than the sums of the",
114 "van der Waals radii of both atoms, the position is",
115 "considered to be occupied, i.e. non-free. By using a",
116 "probe radius of 0, the true free volume is computed.",
117 "By using a larger radius, e.g. 0.14 nm, roughly corresponding",
118 "to a water molecule, the free volume for a hypothetical",
119 "particle with that size will be produced.",
120 "Note however, that since atoms are treated as hard-spheres",
121 "these number are very approximate, and typically only",
122 "relative changes are meaningful, for instance by doing a",
123 "series of simulations at different temperature.[PAR]",
124 "The group specified by the selection is considered to",
125 "delineate non-free volume.",
126 "The number of insertions per unit of volume is important",
127 "to get a converged result. About 1000/nm^3 yields an overall",
128 "standard deviation that is determined by the fluctuations in",
129 "the trajectory rather than by the fluctuations due to the",
130 "random numbers.[PAR]",
131 "The results are critically dependent on the van der Waals radii;",
132 "we recommend to use the values due to Bondi (1964).[PAR]",
133 "The Fractional Free Volume (FFV) that some authors like to use",
134 "is given by 1 - 1.3*(1-Free Volume). This value is printed on",
138 // Add the descriptive text (program help text) to the options
139 options->setDescription(concatenateStrings(desc));
141 // Add option for optional output file
142 options->addOption(FileNameOption("o").filetype(eftPlot).outputFile()
143 .store(&fnFreevol_).defaultBasename("freevolume")
144 .description("Computed free volume"));
146 // Add option for selecting a subset of atoms
147 options->addOption(SelectionOption("select").required().valueCount(1)
151 // Add option for the probe radius and initialize it
152 options->addOption(DoubleOption("radius").store(&probeRadius_)
153 .description("Radius of the probe to be inserted (nm, 0 yields the true free volume)"));
155 // Add option for the random number seed and initialize it to
156 // generate a value automatically
157 options->addOption(IntegerOption("seed").store(&seed_)
158 .description("Seed for random number generator."));
160 // Add option to determine number of insertion trials per frame
161 options->addOption(IntegerOption("ninsert").store(&ninsert_)
162 .description("Number of probe insertions per cubic nm to try for each frame in the trajectory."));
164 // Control input settings
165 settings->setFlags(TrajectoryAnalysisSettings::efRequireTop |
166 TrajectoryAnalysisSettings::efNoUserPBC);
167 settings->setPBC(true);
172 FreeVolume::initAnalysis(const TrajectoryAnalysisSettings &settings,
173 const TopologyInformation &top)
175 // Add the module that will contain the averaging and the time series
176 // for our calculation
177 data_.addModule(adata_);
179 // Add a module for plotting the data automatically at the end of
180 // the calculation. With this in place you only have to add data
181 // points to the data et.
182 AnalysisDataPlotModulePointer plotm_(new AnalysisDataPlotModule());
183 plotm_->setSettings(settings.plotSettings());
184 plotm_->setFileName(fnFreevol_);
185 plotm_->setTitle("Free Volume");
186 plotm_->setXAxisIsTime();
187 plotm_->setYLabel("Free Volume (%)");
188 plotm_->appendLegend("Free Volume");
189 plotm_->appendLegend("Volume");
191 data_.addModule(plotm_);
196 gmx_atomprop_t aps = gmx_atomprop_init();
197 t_atoms *atoms = &(top.topology()->atoms);
199 // Compute total mass
201 for (int i = 0; (i < atoms->nr); i++)
203 mtot_ += atoms->atom[i].m;
206 // Extracts number of molecules
207 nmol_ = top.topology()->mols.nr;
209 // Loop over atoms in the selection using an iterator
210 const int maxnovdw = 10;
211 ConstArrayRef<int> atomind = sel_.atomIndices();
212 for (ConstArrayRef<int>::iterator ai = atomind.begin(); (ai < atomind.end()); ++ai)
214 // Dereference the iterator to obtain an atom number
218 // Lookup the Van der Waals radius of this atom
219 int resnr = atoms->atom[i].resind;
220 if (TRUE == gmx_atomprop_query(aps, epropVDW,
221 *(atoms->resinfo[resnr].name),
222 *(atoms->atomname[i]),
225 vdw_radius_.push_back(value);
234 if (nnovdw < maxnovdw)
236 fprintf(stderr, "Could not determine VDW radius for %s-%s. Set to zero.\n",
237 *(atoms->resinfo[resnr].name),
238 *(atoms->atomname[i]));
240 vdw_radius_.push_back(0.0);
243 gmx_atomprop_destroy(aps);
245 // Increase cutoff by proberadius to make sure we do not miss
247 cutoff_ += probeRadius_;
249 if (nnovdw >= maxnovdw)
251 fprintf(stderr, "Could not determine VDW radius for %d particles. These were set to zero.\n", nnovdw);
254 // Print parameters to output. Maybe should make dependent on
256 printf("cutoff = %g nm\n", cutoff_);
257 printf("probe_radius = %g nm\n", probeRadius_);
258 printf("seed = %d\n", seed_);
259 printf("ninsert = %d probes per nm^3\n", ninsert_);
261 // Initiate the random number generator
262 rng_ = gmx_rng_init(seed_);
264 // Initiate the neighborsearching code
265 nb_.setCutoff(cutoff_);
269 FreeVolume::analyzeFrame(int frnr, const t_trxframe &fr, t_pbc *pbc,
270 TrajectoryAnalysisModuleData *pdata)
272 AnalysisDataHandle dh = pdata->dataHandle(data_);
273 const Selection &sel = pdata->parallelSelection(sel_);
275 GMX_RELEASE_ASSERT(NULL != pbc, "You have no periodic boundary conditions");
277 // Analysis framework magic
278 dh.startFrame(frnr, fr.time);
280 // Compute volume and number of insertions to perform
281 real V = det(fr.box);
282 int Ninsert = static_cast<int>(ninsert_*V);
284 // Use neighborsearching tools!
285 AnalysisNeighborhoodSearch nbsearch = nb_.initSearch(pbc, sel);
287 // Then loop over insertions
289 for (int i = 0; (i < Ninsert); i++)
293 for (int m = 0; (m < DIM); m++)
295 // Generate random number between 0 and 1
296 rand[m] = gmx_rng_uniform_real(rng_);
298 // Generate random 3D position within the box
299 mvmul(fr.box, rand, ins);
301 // Find the first reference position within the cutoff.
302 bool bOverlap = false;
303 AnalysisNeighborhoodPair pair;
304 AnalysisNeighborhoodPairSearch pairSearch = nbsearch.startPairSearch(ins);
305 while (!bOverlap && pairSearch.findNextPair(&pair))
307 int jp = pair.refIndex();
308 // Compute distance vector to first atom in the neighborlist
309 pbc_dx(pbc, ins, sel.position(jp).x(), dx);
311 // See whether the distance is smaller than allowed
312 bOverlap = (norm(dx) <
313 probeRadius_+vdw_radius_[sel.position(jp).refId()]);
319 // We found some free volume!
323 // Compute total free volume for this frame
327 frac = (100.0*NinsTot)/Ninsert;
329 // Add the free volume fraction to the data set in column 0
330 dh.setPoint(0, frac);
331 // Add the total volume to the data set in column 1
340 FreeVolume::finishAnalysis(int nframes)
342 please_cite(stdout, "Bondi1964a");
343 please_cite(stdout, "Lourenco2013a");
347 FreeVolume::writeOutput()
349 // Final results come from statistics module in analysis framework
350 double FVaver = adata_->average(0, 0);
351 double FVerror = adata_->standardDeviation(0, 0);
352 printf("Free volume %.2f +/- %.2f %%\n", FVaver, FVerror);
354 double Vaver = adata_->average(0, 1);
355 double Verror = adata_->standardDeviation(0, 1);
356 printf("Total volume %.2f +/- %.2f nm^3\n", Vaver, Verror);
358 printf("Number of molecules %d total mass %.2f Dalton\n", nmol_, mtot_);
359 double RhoAver = mtot_ / (Vaver * 1e-24 * AVOGADRO);
360 double RhoError = sqr(RhoAver / Vaver)*Verror;
361 printf("Average molar mass: %.2f Dalton\n", mtot_/nmol_);
363 double VmAver = Vaver/nmol_;
364 double VmError = Verror/nmol_;
365 printf("Density rho: %.2f +/- %.2f nm^3\n", RhoAver, RhoError);
366 printf("Molecular volume Vm assuming homogeneity: %.4f +/- %.4f nm^3\n",
369 double VvdWaver = (1-FVaver/100)*VmAver;
370 double VvdWerror = 0;
371 printf("Molecular van der Waals volume assuming homogeneity: %.4f +/- %.4f nm^3\n",
372 VvdWaver, VvdWerror);
374 double FFVaver = 1-1.3*((100-FVaver)/100);
375 double FFVerror = (FVerror/FVaver)*FFVaver;
376 printf("Fractional free volume %.3f +/- %.3f\n", FFVaver, FFVerror);
379 } // namespace analysismodules