Merge branch release-5-1 into release-2016
[gromacs.git] / src / gromacs / gmxana / gmx_dos.cpp
blob88a3667a365e329fa6ad3854fef321f92686f3f9
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2011,2012,2013,2014,2015,2016, 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.
35 #include "gmxpre.h"
37 #include <cmath>
38 #include <cstdio>
39 #include <cstdlib>
40 #include <cstring>
42 #include "gromacs/commandline/pargs.h"
43 #include "gromacs/commandline/viewit.h"
44 #include "gromacs/correlationfunctions/autocorr.h"
45 #include "gromacs/correlationfunctions/integrate.h"
46 #include "gromacs/fft/fft.h"
47 #include "gromacs/fileio/confio.h"
48 #include "gromacs/fileio/gmxfio.h"
49 #include "gromacs/fileio/trxio.h"
50 #include "gromacs/fileio/xvgr.h"
51 #include "gromacs/gmxana/gmx_ana.h"
52 #include "gromacs/math/functions.h"
53 #include "gromacs/math/units.h"
54 #include "gromacs/math/utilities.h"
55 #include "gromacs/math/vec.h"
56 #include "gromacs/topology/index.h"
57 #include "gromacs/topology/topology.h"
58 #include "gromacs/trajectory/trajectoryframe.h"
59 #include "gromacs/utility/arraysize.h"
60 #include "gromacs/utility/fatalerror.h"
61 #include "gromacs/utility/futil.h"
62 #include "gromacs/utility/pleasecite.h"
63 #include "gromacs/utility/smalloc.h"
65 enum {
66 VACF, MVACF, DOS, DOS_SOLID, DOS_DIFF, DOS_CP, DOS_S, DOS_A, DOS_E, DOS_NR
69 static int calcMoleculesInIndexGroup(const t_block *mols, int natoms, const int *index, int nindex)
71 int i = 0;
72 int mol = 0;
73 int nMol = 0;
74 int j;
76 while (i < nindex)
78 while (index[i] > mols->index[mol])
80 mol++;
81 if (mol >= mols->nr)
83 gmx_fatal(FARGS, "Atom index out of range: %d", index[i]+1);
86 for (j = mols->index[mol]; j < mols->index[mol+1]; j++)
88 if (index[i] != j)
90 gmx_fatal(FARGS, "The index group does not consist of whole molecules");
92 i++;
93 if (i > natoms)
95 gmx_fatal(FARGS, "Index contains atom numbers larger than the topology");
98 nMol++;
100 return nMol;
103 static double FD(double Delta, double f)
105 return (2*std::pow(Delta, -4.5)*std::pow(f, 7.5) -
106 6*std::pow(Delta, -3.0)*std::pow(f, 5.0) -
107 std::pow(Delta, -1.5)*std::pow(f, 3.5) +
108 6*std::pow(Delta, -1.5)*std::pow(f, 2.5) +
109 2*f - 2);
112 static double YYY(double f, double y)
114 return (2*gmx::power3(y*f) - gmx::square(f)*y*(1+6*y) +
115 (2+6*y)*f - 2);
118 static double calc_compress(double y)
120 if (y == 1)
122 return 0;
124 return ((1+y+gmx::square(y)-gmx::power3(y))/(gmx::power3(1-y)));
127 static double bisector(double Delta, double tol,
128 double ff0, double ff1,
129 double ff(double, double))
131 double fd, f, f0, f1;
132 double tolmin = 1e-8;
134 f0 = ff0;
135 f1 = ff1;
136 if (tol < tolmin)
138 fprintf(stderr, "Unrealistic tolerance %g for bisector. Setting it to %g\n", tol, tolmin);
139 tol = tolmin;
144 f = (f0+f1)*0.5;
145 fd = ff(Delta, f);
146 if (fd < 0)
148 f0 = f;
150 else if (fd > 0)
152 f1 = f;
154 else
156 return f;
159 while ((f1-f0) > tol);
161 return f;
164 static double calc_fluidicity(double Delta, double tol)
166 return bisector(Delta, tol, 0, 1, FD);
169 static double calc_y(double f, double Delta, double toler)
171 double y1, y2;
173 y1 = std::pow(f/Delta, 1.5);
174 y2 = bisector(f, toler, 0, 10000, YYY);
175 if (std::abs((y1-y2)/(y1+y2)) > 100*toler)
177 fprintf(stderr, "Inconsistency computing y: y1 = %f, y2 = %f, using y1.\n",
178 y1, y2);
181 return y1;
184 static double calc_Shs(double f, double y)
186 double fy = f*y;
188 return BOLTZ*(std::log(calc_compress(fy)) + fy*(3*fy-4)/gmx::square(1-fy));
191 static real wCsolid(real nu, real beta)
193 real bhn = beta*PLANCK*nu;
194 real ebn, koko;
196 if (bhn == 0)
198 return 1.0;
200 else
202 ebn = std::exp(bhn);
203 koko = gmx::square(1-ebn);
204 return gmx::square(bhn)*ebn/koko;
208 static real wSsolid(real nu, real beta)
210 real bhn = beta*PLANCK*nu;
212 if (bhn == 0)
214 return 1;
216 else
218 return bhn/std::expm1(bhn) - std::log1p(-std::exp(-bhn));
222 static real wAsolid(real nu, real beta)
224 real bhn = beta*PLANCK*nu;
226 if (bhn == 0)
228 return 0;
230 else
232 return std::log((1-std::exp(-bhn))/(std::exp(-bhn/2))) - std::log(bhn);
236 static real wEsolid(real nu, real beta)
238 real bhn = beta*PLANCK*nu;
240 if (bhn == 0)
242 return 1;
244 else
246 return bhn/2 + bhn/std::expm1(bhn)-1;
250 int gmx_dos(int argc, char *argv[])
252 const char *desc[] = {
253 "[THISMODULE] computes the Density of States from a simulations.",
254 "In order for this to be meaningful the velocities must be saved",
255 "in the trajecotry with sufficiently high frequency such as to cover",
256 "all vibrations. For flexible systems that would be around a few fs",
257 "between saving. Properties based on the DoS are printed on the",
258 "standard output."
259 "Note that the density of states is calculated from the mass-weighted",
260 "autocorrelation, and by default only from the square of the real",
261 "component rather than absolute value. This means the shape can differ",
262 "substantially from the plain vibrational power spectrum you can",
263 "calculate with gmx velacc."
265 const char *bugs[] = {
266 "This program needs a lot of memory: total usage equals the number of atoms times 3 times number of frames times 4 (or 8 when run in double precision)."
268 FILE *fp, *fplog;
269 t_topology top;
270 int ePBC = -1;
271 t_trxframe fr;
272 matrix box;
273 int gnx;
274 real t0, t1;
275 t_trxstatus *status;
276 int nV, nframes, n_alloc, i, j, fftcode, Nmol, Natom;
277 double rho, dt, Vsum, V, tmass, dostot, dos2;
278 real **c1, **dos, mi, beta, bfac, *nu, *tt, stddev, c1j;
279 gmx_output_env_t *oenv;
280 gmx_fft_t fft;
281 double cP, DiffCoeff, Delta, f, y, z, sigHS, Shs, Sig, DoS0, recip_fac;
282 double wCdiff, wSdiff, wAdiff, wEdiff;
283 int grpNatoms;
284 int *index;
285 char *grpname;
286 double invNormalize;
287 gmx_bool normalizeAutocorrelation;
289 static gmx_bool bVerbose = TRUE, bAbsolute = FALSE, bNormalizeDos = FALSE;
290 static gmx_bool bRecip = FALSE;
291 static real Temp = 298.15, toler = 1e-6;
292 int min_frames = 100;
294 t_pargs pa[] = {
295 { "-v", FALSE, etBOOL, {&bVerbose},
296 "Be loud and noisy." },
297 { "-recip", FALSE, etBOOL, {&bRecip},
298 "Use cm^-1 on X-axis instead of 1/ps for DoS plots." },
299 { "-abs", FALSE, etBOOL, {&bAbsolute},
300 "Use the absolute value of the Fourier transform of the VACF as the Density of States. Default is to use the real component only" },
301 { "-normdos", FALSE, etBOOL, {&bNormalizeDos},
302 "Normalize the DoS such that it adds up to 3N. This should usually not be necessary." },
303 { "-T", FALSE, etREAL, {&Temp},
304 "Temperature in the simulation" },
305 { "-toler", FALSE, etREAL, {&toler},
306 "[HIDDEN]Tolerance when computing the fluidicity using bisection algorithm" }
309 t_filenm fnm[] = {
310 { efTRN, "-f", NULL, ffREAD },
311 { efTPR, "-s", NULL, ffREAD },
312 { efNDX, NULL, NULL, ffOPTRD },
313 { efXVG, "-vacf", "vacf", ffWRITE },
314 { efXVG, "-mvacf", "mvacf", ffWRITE },
315 { efXVG, "-dos", "dos", ffWRITE },
316 { efLOG, "-g", "dos", ffWRITE },
318 #define NFILE asize(fnm)
319 int npargs;
320 t_pargs *ppa;
321 const char *DoSlegend[] = {
322 "DoS(v)", "DoS(v)[Solid]", "DoS(v)[Diff]"
325 npargs = asize(pa);
326 ppa = add_acf_pargs(&npargs, pa);
327 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME,
328 NFILE, fnm, npargs, ppa, asize(desc), desc,
329 asize(bugs), bugs, &oenv))
331 return 0;
334 beta = 1/(Temp*BOLTZ);
336 fplog = gmx_fio_fopen(ftp2fn(efLOG, NFILE, fnm), "w");
337 fprintf(fplog, "Doing density of states analysis based on trajectory.\n");
338 please_cite(fplog, "Pascal2011a");
339 please_cite(fplog, "Caleman2011b");
341 read_tps_conf(ftp2fn(efTPR, NFILE, fnm), &top, &ePBC, NULL, NULL, box, TRUE);
343 /* Handle index groups */
344 get_index(&top.atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &grpNatoms, &index, &grpname);
346 V = det(box);
347 tmass = 0;
348 for (i = 0; i < grpNatoms; i++)
350 tmass += top.atoms.atom[index[i]].m;
353 Natom = grpNatoms;
354 Nmol = calcMoleculesInIndexGroup(&top.mols, top.atoms.nr, index, grpNatoms);
355 gnx = Natom*DIM;
357 /* Correlation stuff */
358 snew(c1, gnx);
359 for (i = 0; (i < gnx); i++)
361 c1[i] = NULL;
364 read_first_frame(oenv, &status, ftp2fn(efTRN, NFILE, fnm), &fr, TRX_NEED_V);
365 t0 = fr.time;
367 n_alloc = 0;
368 nframes = 0;
369 Vsum = 0;
370 nV = 0;
373 if (fr.bBox)
375 V = det(fr.box);
376 Vsum += V;
377 nV++;
379 if (nframes >= n_alloc)
381 n_alloc += 100;
382 for (i = 0; i < gnx; i++)
384 srenew(c1[i], n_alloc);
387 for (i = 0; i < gnx; i += DIM)
389 c1[i+XX][nframes] = fr.v[index[i/DIM]][XX];
390 c1[i+YY][nframes] = fr.v[index[i/DIM]][YY];
391 c1[i+ZZ][nframes] = fr.v[index[i/DIM]][ZZ];
394 t1 = fr.time;
396 nframes++;
398 while (read_next_frame(oenv, status, &fr));
400 close_trj(status);
402 if (nframes < min_frames)
404 gmx_fatal(FARGS, "You need at least %d frames in the trajectory and you only have %d.", min_frames, nframes);
406 dt = (t1-t0)/(nframes-1);
407 if (nV > 0)
409 V = Vsum/nV;
411 if (bVerbose)
413 printf("Going to do %d fourier transforms of length %d. Hang on.\n",
414 gnx, nframes);
416 /* Unfortunately the -normalize program option for the autocorrelation
417 * function calculation is added as a hack with a static variable in the
418 * autocorrelation.c source. That would work if we called the normal
419 * do_autocorr(), but this routine overrides that by directly calling
420 * the low-level functionality. That unfortunately leads to ignoring the
421 * default value for the option (which is to normalize).
422 * Since the absolute value seems to be important for the subsequent
423 * analysis below, we detect the value directly from the option, calculate
424 * the autocorrelation without normalization, and then apply the
425 * normalization just to the autocorrelation output
426 * (or not, if the user asked for a non-normalized autocorrelation).
428 normalizeAutocorrelation = opt2parg_bool("-normalize", npargs, ppa);
430 /* Note that we always disable normalization here, regardless of user settings */
431 low_do_autocorr(NULL, oenv, NULL, nframes, gnx, nframes, c1, dt, eacNormal, 0, FALSE,
432 FALSE, FALSE, -1, -1, 0);
433 snew(dos, DOS_NR);
434 for (j = 0; (j < DOS_NR); j++)
436 snew(dos[j], nframes+4);
439 if (bVerbose)
441 printf("Going to merge the ACFs into the mass-weighted and plain ACF\n");
443 for (i = 0; (i < gnx); i += DIM)
445 mi = top.atoms.atom[index[i/DIM]].m;
446 for (j = 0; (j < nframes/2); j++)
448 c1j = (c1[i+XX][j] + c1[i+YY][j] + c1[i+ZZ][j]);
449 dos[VACF][j] += c1j/Natom;
450 dos[MVACF][j] += mi*c1j;
454 fp = xvgropen(opt2fn("-vacf", NFILE, fnm), "Velocity autocorrelation function",
455 "Time (ps)", "C(t)", oenv);
456 snew(tt, nframes/2);
458 invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
460 for (j = 0; (j < nframes/2); j++)
462 tt[j] = j*dt;
463 fprintf(fp, "%10g %10g\n", tt[j], dos[VACF][j] * invNormalize);
465 xvgrclose(fp);
467 fp = xvgropen(opt2fn("-mvacf", NFILE, fnm), "Mass-weighted velocity autocorrelation function",
468 "Time (ps)", "C(t)", oenv);
470 invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
472 for (j = 0; (j < nframes/2); j++)
474 fprintf(fp, "%10g %10g\n", tt[j], dos[MVACF][j] * invNormalize);
476 xvgrclose(fp);
478 if ((fftcode = gmx_fft_init_1d_real(&fft, nframes/2,
479 GMX_FFT_FLAG_NONE)) != 0)
481 gmx_fatal(FARGS, "gmx_fft_init_1d_real returned %d", fftcode);
483 if ((fftcode = gmx_fft_1d_real(fft, GMX_FFT_REAL_TO_COMPLEX,
484 (void *)dos[MVACF], (void *)dos[DOS])) != 0)
486 gmx_fatal(FARGS, "gmx_fft_1d_real returned %d", fftcode);
489 /* First compute the DoS */
490 /* Magic factor of 8 included now. */
491 bfac = 8*dt*beta/2;
492 dos2 = 0;
493 snew(nu, nframes/4);
494 for (j = 0; (j < nframes/4); j++)
496 nu[j] = 2*j/(t1-t0);
497 dos2 += gmx::square(dos[DOS][2*j]) + gmx::square(dos[DOS][2*j+1]);
498 if (bAbsolute)
500 dos[DOS][j] = bfac*std::hypot(dos[DOS][2*j], dos[DOS][2*j+1]);
502 else
504 dos[DOS][j] = bfac*dos[DOS][2*j];
507 /* Normalize it */
508 dostot = evaluate_integral(nframes/4, nu, dos[DOS], NULL, nframes/4, &stddev);
509 if (bNormalizeDos)
511 for (j = 0; (j < nframes/4); j++)
513 dos[DOS][j] *= 3*Natom/dostot;
517 /* Now analyze it */
518 DoS0 = dos[DOS][0];
520 /* Note this eqn. is incorrect in Pascal2011a! */
521 Delta = ((2*DoS0/(9*Natom))*std::sqrt(M_PI*BOLTZ*Temp*Natom/tmass)*
522 std::pow((Natom/V), 1.0/3.0)*std::pow(6.0/M_PI, 2.0/3.0));
523 f = calc_fluidicity(Delta, toler);
524 y = calc_y(f, Delta, toler);
525 z = calc_compress(y);
526 Sig = BOLTZ*(5.0/2.0+std::log(2*M_PI*BOLTZ*Temp/(gmx::square(PLANCK))*V/(f*Natom)));
527 Shs = Sig+calc_Shs(f, y);
528 rho = (tmass*AMU)/(V*NANO*NANO*NANO);
529 sigHS = std::cbrt(6*y*V/(M_PI*Natom));
531 fprintf(fplog, "System = \"%s\"\n", *top.name);
532 fprintf(fplog, "Nmol = %d\n", Nmol);
533 fprintf(fplog, "Natom = %d\n", Natom);
534 fprintf(fplog, "dt = %g ps\n", dt);
535 fprintf(fplog, "tmass = %g amu\n", tmass);
536 fprintf(fplog, "V = %g nm^3\n", V);
537 fprintf(fplog, "rho = %g g/l\n", rho);
538 fprintf(fplog, "T = %g K\n", Temp);
539 fprintf(fplog, "beta = %g mol/kJ\n", beta);
541 fprintf(fplog, "\nDoS parameters\n");
542 fprintf(fplog, "Delta = %g\n", Delta);
543 fprintf(fplog, "fluidicity = %g\n", f);
544 fprintf(fplog, "hard sphere packing fraction = %g\n", y);
545 fprintf(fplog, "hard sphere compressibility = %g\n", z);
546 fprintf(fplog, "ideal gas entropy = %g\n", Sig);
547 fprintf(fplog, "hard sphere entropy = %g\n", Shs);
548 fprintf(fplog, "sigma_HS = %g nm\n", sigHS);
549 fprintf(fplog, "DoS0 = %g\n", DoS0);
550 fprintf(fplog, "Dos2 = %g\n", dos2);
551 fprintf(fplog, "DoSTot = %g\n", dostot);
553 /* Now compute solid (2) and diffusive (3) components */
554 fp = xvgropen(opt2fn("-dos", NFILE, fnm), "Density of states",
555 bRecip ? "E (cm\\S-1\\N)" : "\\f{12}n\\f{4} (1/ps)",
556 "\\f{4}S(\\f{12}n\\f{4})", oenv);
557 xvgr_legend(fp, asize(DoSlegend), DoSlegend, oenv);
558 recip_fac = bRecip ? (1e7/SPEED_OF_LIGHT) : 1.0;
559 for (j = 0; (j < nframes/4); j++)
561 dos[DOS_DIFF][j] = DoS0/(1+gmx::square(DoS0*M_PI*nu[j]/(6*f*Natom)));
562 dos[DOS_SOLID][j] = dos[DOS][j]-dos[DOS_DIFF][j];
563 fprintf(fp, "%10g %10g %10g %10g\n",
564 recip_fac*nu[j],
565 dos[DOS][j]/recip_fac,
566 dos[DOS_SOLID][j]/recip_fac,
567 dos[DOS_DIFF][j]/recip_fac);
569 xvgrclose(fp);
571 /* Finally analyze the results! */
572 wCdiff = 0.5;
573 wSdiff = Shs/(3*BOLTZ); /* Is this correct? */
574 wEdiff = 0.5;
575 wAdiff = wEdiff-wSdiff;
576 for (j = 0; (j < nframes/4); j++)
578 dos[DOS_CP][j] = (dos[DOS_DIFF][j]*wCdiff +
579 dos[DOS_SOLID][j]*wCsolid(nu[j], beta));
580 dos[DOS_S][j] = (dos[DOS_DIFF][j]*wSdiff +
581 dos[DOS_SOLID][j]*wSsolid(nu[j], beta));
582 dos[DOS_A][j] = (dos[DOS_DIFF][j]*wAdiff +
583 dos[DOS_SOLID][j]*wAsolid(nu[j], beta));
584 dos[DOS_E][j] = (dos[DOS_DIFF][j]*wEdiff +
585 dos[DOS_SOLID][j]*wEsolid(nu[j], beta));
587 DiffCoeff = evaluate_integral(nframes/2, tt, dos[VACF], NULL, nframes/2, &stddev);
588 DiffCoeff = 1000*DiffCoeff/3.0;
589 fprintf(fplog, "Diffusion coefficient from VACF %g 10^-5 cm^2/s\n",
590 DiffCoeff);
591 fprintf(fplog, "Diffusion coefficient from DoS %g 10^-5 cm^2/s\n",
592 1000*DoS0/(12*tmass*beta));
594 cP = BOLTZ * evaluate_integral(nframes/4, nu, dos[DOS_CP], NULL,
595 nframes/4, &stddev);
596 fprintf(fplog, "Heat capacity %g J/mol K\n", 1000*cP/Nmol);
597 fprintf(fplog, "\nArrivederci!\n");
598 gmx_fio_fclose(fplog);
600 do_view(oenv, ftp2fn(efXVG, NFILE, fnm), "-nxy");
602 return 0;