04abdb911b6b00f1755ac0cd1f7270d5fac4b888
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_dos.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2011,2012,2013,2014,2015, 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.
8  *
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.
13  *
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.
18  *
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.
23  *
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.
31  *
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.
34  */
35 #include "gmxpre.h"
36
37 #include <cmath>
38 #include <cstdio>
39 #include <cstdlib>
40 #include <cstring>
41
42 #include "gromacs/commandline/pargs.h"
43 #include "gromacs/correlationfunctions/autocorr.h"
44 #include "gromacs/correlationfunctions/integrate.h"
45 #include "gromacs/fft/fft.h"
46 #include "gromacs/fileio/confio.h"
47 #include "gromacs/fileio/gmxfio.h"
48 #include "gromacs/fileio/trxio.h"
49 #include "gromacs/fileio/xvgr.h"
50 #include "gromacs/gmxana/gmx_ana.h"
51 #include "gromacs/legacyheaders/copyrite.h"
52 #include "gromacs/legacyheaders/macros.h"
53 #include "gromacs/legacyheaders/txtdump.h"
54 #include "gromacs/legacyheaders/typedefs.h"
55 #include "gromacs/legacyheaders/viewit.h"
56 #include "gromacs/math/units.h"
57 #include "gromacs/math/utilities.h"
58 #include "gromacs/math/vec.h"
59 #include "gromacs/topology/index.h"
60 #include "gromacs/utility/fatalerror.h"
61 #include "gromacs/utility/futil.h"
62 #include "gromacs/utility/smalloc.h"
63 #include "gmx_ana.h"
64
65 enum {
66     VACF, MVACF, DOS, DOS_SOLID, DOS_DIFF, DOS_CP, DOS_S, DOS_A, DOS_E, DOS_NR
67 };
68
69 static int calcMoleculesInIndexGroup(t_block *mols, int natoms, atom_id *index, int nindex)
70 {
71     int   i    = 0;
72     int   mol  = 0;
73     int   nMol = 0;
74     int   j;
75
76     while (i < nindex)
77     {
78         while (index[i] > mols->index[mol])
79         {
80             mol++;
81             if (mol >= mols->nr)
82             {
83                 gmx_fatal(FARGS, "Atom index out of range: %d", index[i]+1);
84             }
85         }
86         for (j = mols->index[mol]; j < mols->index[mol+1]; j++)
87         {
88             if (index[i] != j)
89             {
90                 gmx_fatal(FARGS, "The index group does not consist of whole molecules");
91             }
92             i++;
93             if (i == natoms)
94             {
95                 gmx_fatal(FARGS, "Index contains atom numbers larger than the topology");
96             }
97         }
98         nMol++;
99     }
100     return nMol;
101 }
102
103 static double FD(double Delta, double f)
104 {
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);
110 }
111
112 static double YYY(double f, double y)
113 {
114     return (2*std::pow(y*f, 3.0) - sqr(f)*y*(1+6*y) +
115             (2+6*y)*f - 2);
116 }
117
118 static double calc_compress(double y)
119 {
120     if (y == 1)
121     {
122         return 0;
123     }
124     return ((1+y+sqr(y)-std::pow(y, 3.0))/(std::pow(1-y, 3.0)));
125 }
126
127 static double bisector(double Delta, double tol,
128                        double ff0, double ff1,
129                        double ff(double, double))
130 {
131     double fd, f, f0, f1;
132     double tolmin = 1e-8;
133
134     f0 = ff0;
135     f1 = ff1;
136     if (tol < tolmin)
137     {
138         fprintf(stderr, "Unrealistic tolerance %g for bisector. Setting it to %g\n", tol, tolmin);
139         tol = tolmin;
140     }
141
142     do
143     {
144         f   = (f0+f1)*0.5;
145         fd  = ff(Delta, f);
146         if (fd < 0)
147         {
148             f0 = f;
149         }
150         else if (fd > 0)
151         {
152             f1 = f;
153         }
154         else
155         {
156             return f;
157         }
158     }
159     while ((f1-f0) > tol);
160
161     return f;
162 }
163
164 static double calc_fluidicity(double Delta, double tol)
165 {
166     return bisector(Delta, tol, 0, 1, FD);
167 }
168
169 static double calc_y(double f, double Delta, double toler)
170 {
171     double y1, y2;
172
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)
176     {
177         fprintf(stderr, "Inconsistency computing y: y1 = %f, y2 = %f, using y1.\n",
178                 y1, y2);
179     }
180
181     return y1;
182 }
183
184 static double calc_Shs(double f, double y)
185 {
186     double fy  = f*y;
187
188     return BOLTZ*(std::log(calc_compress(fy)) + fy*(3*fy-4)/sqr(1-fy));
189 }
190
191 static real wCsolid(real nu, real beta)
192 {
193     real bhn = beta*PLANCK*nu;
194     real ebn, koko;
195
196     if (bhn == 0)
197     {
198         return 1.0;
199     }
200     else
201     {
202         ebn  = std::exp(bhn);
203         koko = sqr(1-ebn);
204         return sqr(bhn)*ebn/koko;
205     }
206 }
207
208 static real wSsolid(real nu, real beta)
209 {
210     real bhn = beta*PLANCK*nu;
211
212     if (bhn == 0)
213     {
214         return 1;
215     }
216     else
217     {
218         return bhn/gmx_expm1(bhn) - gmx_log1p(-std::exp(-bhn));
219     }
220 }
221
222 static real wAsolid(real nu, real beta)
223 {
224     real bhn = beta*PLANCK*nu;
225
226     if (bhn == 0)
227     {
228         return 0;
229     }
230     else
231     {
232         return std::log((1-std::exp(-bhn))/(std::exp(-bhn/2))) - std::log(bhn);
233     }
234 }
235
236 static real wEsolid(real nu, real beta)
237 {
238     real bhn = beta*PLANCK*nu;
239
240     if (bhn == 0)
241     {
242         return 1;
243     }
244     else
245     {
246         return bhn/2 + bhn/gmx_expm1(bhn)-1;
247     }
248 }
249
250 int gmx_dos(int argc, char *argv[])
251 {
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."
264     };
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)."
267     };
268     FILE               *fp, *fplog;
269     t_topology          top;
270     int                 ePBC = -1;
271     t_trxframe          fr;
272     matrix              box;
273     int                 gnx;
274     char                title[256];
275     real                t0, t1;
276     t_trxstatus        *status;
277     int                 nV, nframes, n_alloc, i, j, fftcode, Nmol, Natom;
278     double              rho, dt, Vsum, V, tmass, dostot, dos2;
279     real              **c1, **dos, mi, beta, bfac, *nu, *tt, stddev, c1j;
280     output_env_t        oenv;
281     gmx_fft_t           fft;
282     double              cP, DiffCoeff, Delta, f, y, z, sigHS, Shs, Sig, DoS0, recip_fac;
283     double              wCdiff, wSdiff, wAdiff, wEdiff;
284     int                 grpNatoms;
285     atom_id            *index;
286     char               *grpname;
287     double              invNormalize;
288     gmx_bool            normalizeAutocorrelation;
289
290     static     gmx_bool bVerbose = TRUE, bAbsolute = FALSE, bNormalizeDos = FALSE;
291     static     gmx_bool bRecip   = FALSE;
292     static     real     Temp     = 298.15, toler = 1e-6;
293
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" }
307     };
308
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 },
317     };
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]"
323     };
324
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))
330     {
331         return 0;
332     }
333
334     beta = 1/(Temp*BOLTZ);
335
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");
340
341     read_tps_conf(ftp2fn(efTPR, NFILE, fnm), title, &top, &ePBC, NULL, NULL, box, TRUE);
342
343     /* Handle index groups */
344     get_index(&top.atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &grpNatoms, &index, &grpname);
345
346     V     = det(box);
347     tmass = 0;
348     for (i = 0; i < grpNatoms; i++)
349     {
350         tmass += top.atoms.atom[index[i]].m;
351     }
352
353     Natom = grpNatoms;
354     Nmol  = calcMoleculesInIndexGroup(&top.mols, top.atoms.nr, index, grpNatoms);
355     gnx   = Natom*DIM;
356
357     /* Correlation stuff */
358     snew(c1, gnx);
359     for (i = 0; (i < gnx); i++)
360     {
361         c1[i] = NULL;
362     }
363
364     read_first_frame(oenv, &status, ftp2fn(efTRN, NFILE, fnm), &fr, TRX_NEED_V);
365     t0 = fr.time;
366
367     n_alloc = 0;
368     nframes = 0;
369     Vsum    = 0;
370     nV      = 0;
371     do
372     {
373         if (fr.bBox)
374         {
375             V      = det(fr.box);
376             Vsum  += V;
377             nV++;
378         }
379         if (nframes >= n_alloc)
380         {
381             n_alloc += 100;
382             for (i = 0; i < gnx; i++)
383             {
384                 srenew(c1[i], n_alloc);
385             }
386         }
387         for (i = 0; i < gnx; i += DIM)
388         {
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];
392         }
393
394         t1 = fr.time;
395
396         nframes++;
397     }
398     while (read_next_frame(oenv, status, &fr));
399
400     close_trj(status);
401
402     dt = (t1-t0)/(nframes-1);
403     if (nV > 0)
404     {
405         V = Vsum/nV;
406     }
407     if (bVerbose)
408     {
409         printf("Going to do %d fourier transforms of length %d. Hang on.\n",
410                gnx, nframes);
411     }
412     /* Unfortunately the -normalize program option for the autocorrelation
413      * function calculation is added as a hack with a static variable in the
414      * autocorrelation.c source. That would work if we called the normal
415      * do_autocorr(), but this routine overrides that by directly calling
416      * the low-level functionality. That unfortunately leads to ignoring the
417      * default value for the option (which is to normalize).
418      * Since the absolute value seems to be important for the subsequent
419      * analysis below, we detect the value directly from the option, calculate
420      * the autocorrelation without normalization, and then apply the
421      * normalization just to the autocorrelation output
422      * (or not, if the user asked for a non-normalized autocorrelation).
423      */
424     normalizeAutocorrelation = opt2parg_bool("-normalize", npargs, ppa);
425
426     /* Note that we always disable normalization here, regardless of user settings */
427     low_do_autocorr(NULL, oenv, NULL, nframes, gnx, nframes, c1, dt, eacNormal, 0, FALSE,
428                     FALSE, FALSE, -1, -1, 0);
429     snew(dos, DOS_NR);
430     for (j = 0; (j < DOS_NR); j++)
431     {
432         snew(dos[j], nframes+4);
433     }
434
435     if (bVerbose)
436     {
437         printf("Going to merge the ACFs into the mass-weighted and plain ACF\n");
438     }
439     for (i = 0; (i < gnx); i += DIM)
440     {
441         mi = top.atoms.atom[index[i/DIM]].m;
442         for (j = 0; (j < nframes/2); j++)
443         {
444             c1j            = (c1[i+XX][j] + c1[i+YY][j] + c1[i+ZZ][j]);
445             dos[VACF][j]  += c1j/Natom;
446             dos[MVACF][j] += mi*c1j;
447         }
448     }
449
450     fp = xvgropen(opt2fn("-vacf", NFILE, fnm), "Velocity autocorrelation function",
451                   "Time (ps)", "C(t)", oenv);
452     snew(tt, nframes/2);
453
454     invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
455
456     for (j = 0; (j < nframes/2); j++)
457     {
458         tt[j] = j*dt;
459         fprintf(fp, "%10g  %10g\n", tt[j], dos[VACF][j] * invNormalize);
460     }
461     xvgrclose(fp);
462
463     fp = xvgropen(opt2fn("-mvacf", NFILE, fnm), "Mass-weighted velocity autocorrelation function",
464                   "Time (ps)", "C(t)", oenv);
465
466     invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
467
468     for (j = 0; (j < nframes/2); j++)
469     {
470         fprintf(fp, "%10g  %10g\n", tt[j], dos[MVACF][j] * invNormalize);
471     }
472     xvgrclose(fp);
473
474     if ((fftcode = gmx_fft_init_1d_real(&fft, nframes/2,
475                                         GMX_FFT_FLAG_NONE)) != 0)
476     {
477         gmx_fatal(FARGS, "gmx_fft_init_1d_real returned %d", fftcode);
478     }
479     if ((fftcode = gmx_fft_1d_real(fft, GMX_FFT_REAL_TO_COMPLEX,
480                                    (void *)dos[MVACF], (void *)dos[DOS])) != 0)
481     {
482         gmx_fatal(FARGS, "gmx_fft_1d_real returned %d", fftcode);
483     }
484
485     /* First compute the DoS */
486     /* Magic factor of 8 included now. */
487     bfac = 8*dt*beta/2;
488     dos2 = 0;
489     snew(nu, nframes/4);
490     for (j = 0; (j < nframes/4); j++)
491     {
492         nu[j] = 2*j/(t1-t0);
493         dos2 += sqr(dos[DOS][2*j]) + sqr(dos[DOS][2*j+1]);
494         if (bAbsolute)
495         {
496             dos[DOS][j] = bfac*std::sqrt(sqr(dos[DOS][2*j]) + sqr(dos[DOS][2*j+1]));
497         }
498         else
499         {
500             dos[DOS][j] = bfac*dos[DOS][2*j];
501         }
502     }
503     /* Normalize it */
504     dostot = evaluate_integral(nframes/4, nu, dos[DOS], NULL, nframes/4, &stddev);
505     if (bNormalizeDos)
506     {
507         for (j = 0; (j < nframes/4); j++)
508         {
509             dos[DOS][j] *= 3*Natom/dostot;
510         }
511     }
512
513     /* Now analyze it */
514     DoS0 = dos[DOS][0];
515
516     /* Note this eqn. is incorrect in Pascal2011a! */
517     Delta = ((2*DoS0/(9*Natom))*std::sqrt(M_PI*BOLTZ*Temp*Natom/tmass)*
518              std::pow((Natom/V), 1.0/3.0)*std::pow(6.0/M_PI, 2.0/3.0));
519     f     = calc_fluidicity(Delta, toler);
520     y     = calc_y(f, Delta, toler);
521     z     = calc_compress(y);
522     Sig   = BOLTZ*(5.0/2.0+std::log(2*M_PI*BOLTZ*Temp/(sqr(PLANCK))*V/(f*Natom)));
523     Shs   = Sig+calc_Shs(f, y);
524     rho   = (tmass*AMU)/(V*NANO*NANO*NANO);
525     sigHS = std::pow(6*y*V/(M_PI*Natom), 1.0/3.0);
526
527     fprintf(fplog, "System = \"%s\"\n", title);
528     fprintf(fplog, "Nmol = %d\n", Nmol);
529     fprintf(fplog, "Natom = %d\n", Natom);
530     fprintf(fplog, "dt = %g ps\n", dt);
531     fprintf(fplog, "tmass = %g amu\n", tmass);
532     fprintf(fplog, "V = %g nm^3\n", V);
533     fprintf(fplog, "rho = %g g/l\n", rho);
534     fprintf(fplog, "T = %g K\n", Temp);
535     fprintf(fplog, "beta = %g mol/kJ\n", beta);
536
537     fprintf(fplog, "\nDoS parameters\n");
538     fprintf(fplog, "Delta = %g\n", Delta);
539     fprintf(fplog, "fluidicity = %g\n", f);
540     fprintf(fplog, "hard sphere packing fraction = %g\n", y);
541     fprintf(fplog, "hard sphere compressibility = %g\n", z);
542     fprintf(fplog, "ideal gas entropy = %g\n", Sig);
543     fprintf(fplog, "hard sphere entropy = %g\n", Shs);
544     fprintf(fplog, "sigma_HS = %g nm\n", sigHS);
545     fprintf(fplog, "DoS0 = %g\n", DoS0);
546     fprintf(fplog, "Dos2 = %g\n", dos2);
547     fprintf(fplog, "DoSTot = %g\n", dostot);
548
549     /* Now compute solid (2) and diffusive (3) components */
550     fp = xvgropen(opt2fn("-dos", NFILE, fnm), "Density of states",
551                   bRecip ? "E (cm\\S-1\\N)" : "\\f{12}n\\f{4} (1/ps)",
552                   "\\f{4}S(\\f{12}n\\f{4})", oenv);
553     xvgr_legend(fp, asize(DoSlegend), DoSlegend, oenv);
554     recip_fac = bRecip ? (1e7/SPEED_OF_LIGHT) : 1.0;
555     for (j = 0; (j < nframes/4); j++)
556     {
557         dos[DOS_DIFF][j]  = DoS0/(1+sqr(DoS0*M_PI*nu[j]/(6*f*Natom)));
558         dos[DOS_SOLID][j] = dos[DOS][j]-dos[DOS_DIFF][j];
559         fprintf(fp, "%10g  %10g  %10g  %10g\n",
560                 recip_fac*nu[j],
561                 dos[DOS][j]/recip_fac,
562                 dos[DOS_SOLID][j]/recip_fac,
563                 dos[DOS_DIFF][j]/recip_fac);
564     }
565     xvgrclose(fp);
566
567     /* Finally analyze the results! */
568     wCdiff = 0.5;
569     wSdiff = Shs/(3*BOLTZ); /* Is this correct? */
570     wEdiff = 0.5;
571     wAdiff = wEdiff-wSdiff;
572     for (j = 0; (j < nframes/4); j++)
573     {
574         dos[DOS_CP][j] = (dos[DOS_DIFF][j]*wCdiff +
575                           dos[DOS_SOLID][j]*wCsolid(nu[j], beta));
576         dos[DOS_S][j]  = (dos[DOS_DIFF][j]*wSdiff +
577                           dos[DOS_SOLID][j]*wSsolid(nu[j], beta));
578         dos[DOS_A][j]  = (dos[DOS_DIFF][j]*wAdiff +
579                           dos[DOS_SOLID][j]*wAsolid(nu[j], beta));
580         dos[DOS_E][j]  = (dos[DOS_DIFF][j]*wEdiff +
581                           dos[DOS_SOLID][j]*wEsolid(nu[j], beta));
582     }
583     DiffCoeff = evaluate_integral(nframes/2, tt, dos[VACF], NULL, nframes/2, &stddev);
584     DiffCoeff = 1000*DiffCoeff/3.0;
585     fprintf(fplog, "Diffusion coefficient from VACF %g 10^-5 cm^2/s\n",
586             DiffCoeff);
587     fprintf(fplog, "Diffusion coefficient from DoS %g 10^-5 cm^2/s\n",
588             1000*DoS0/(12*tmass*beta));
589
590     cP = BOLTZ * evaluate_integral(nframes/4, nu, dos[DOS_CP], NULL,
591                                    nframes/4, &stddev);
592     fprintf(fplog, "Heat capacity %g J/mol K\n", 1000*cP/Nmol);
593     fprintf(fplog, "\nArrivederci!\n");
594     gmx_fio_fclose(fplog);
595
596     do_view(oenv, ftp2fn(efXVG, NFILE, fnm), "-nxy");
597
598     return 0;
599 }