Simplify SIMD compiler flag construction
[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,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.
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/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"
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(const t_block *mols, int natoms, const int *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*gmx::power3(y*f) - gmx::square(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+gmx::square(y)-gmx::power3(y))/(gmx::power3(1-y)));
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)/gmx::square(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 = gmx::square(1-ebn);
204         return gmx::square(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/std::expm1(bhn) - std::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/std::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     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;
288
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
293     t_pargs             pa[]     = {
294         { "-v", FALSE, etBOOL, {&bVerbose},
295           "Be loud and noisy." },
296         { "-recip", FALSE, etBOOL, {&bRecip},
297           "Use cm^-1 on X-axis instead of 1/ps for DoS plots." },
298         { "-abs", FALSE, etBOOL, {&bAbsolute},
299           "Use the absolute value of the Fourier transform of the VACF as the Density of States. Default is to use the real component only" },
300         { "-normdos", FALSE, etBOOL, {&bNormalizeDos},
301           "Normalize the DoS such that it adds up to 3N. This should usually not be necessary." },
302         { "-T", FALSE, etREAL, {&Temp},
303           "Temperature in the simulation" },
304         { "-toler", FALSE, etREAL, {&toler},
305           "[HIDDEN]Tolerance when computing the fluidicity using bisection algorithm" }
306     };
307
308     t_filenm            fnm[] = {
309         { efTRN, "-f",    NULL,    ffREAD  },
310         { efTPR, "-s",    NULL,    ffREAD  },
311         { efNDX, NULL,    NULL,    ffOPTRD },
312         { efXVG, "-vacf", "vacf",  ffWRITE },
313         { efXVG, "-mvacf", "mvacf", ffWRITE },
314         { efXVG, "-dos",  "dos",   ffWRITE },
315         { efLOG, "-g",    "dos",   ffWRITE },
316     };
317 #define NFILE asize(fnm)
318     int                 npargs;
319     t_pargs            *ppa;
320     const char         *DoSlegend[] = {
321         "DoS(v)", "DoS(v)[Solid]", "DoS(v)[Diff]"
322     };
323
324     npargs = asize(pa);
325     ppa    = add_acf_pargs(&npargs, pa);
326     if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME,
327                            NFILE, fnm, npargs, ppa, asize(desc), desc,
328                            asize(bugs), bugs, &oenv))
329     {
330         return 0;
331     }
332
333     beta = 1/(Temp*BOLTZ);
334
335     fplog = gmx_fio_fopen(ftp2fn(efLOG, NFILE, fnm), "w");
336     fprintf(fplog, "Doing density of states analysis based on trajectory.\n");
337     please_cite(fplog, "Pascal2011a");
338     please_cite(fplog, "Caleman2011b");
339
340     read_tps_conf(ftp2fn(efTPR, NFILE, fnm), &top, &ePBC, NULL, NULL, box, TRUE);
341
342     /* Handle index groups */
343     get_index(&top.atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &grpNatoms, &index, &grpname);
344
345     V     = det(box);
346     tmass = 0;
347     for (i = 0; i < grpNatoms; i++)
348     {
349         tmass += top.atoms.atom[index[i]].m;
350     }
351
352     Natom = grpNatoms;
353     Nmol  = calcMoleculesInIndexGroup(&top.mols, top.atoms.nr, index, grpNatoms);
354     gnx   = Natom*DIM;
355
356     /* Correlation stuff */
357     snew(c1, gnx);
358     for (i = 0; (i < gnx); i++)
359     {
360         c1[i] = NULL;
361     }
362
363     read_first_frame(oenv, &status, ftp2fn(efTRN, NFILE, fnm), &fr, TRX_NEED_V);
364     t0 = fr.time;
365
366     n_alloc = 0;
367     nframes = 0;
368     Vsum    = 0;
369     nV      = 0;
370     do
371     {
372         if (fr.bBox)
373         {
374             V      = det(fr.box);
375             Vsum  += V;
376             nV++;
377         }
378         if (nframes >= n_alloc)
379         {
380             n_alloc += 100;
381             for (i = 0; i < gnx; i++)
382             {
383                 srenew(c1[i], n_alloc);
384             }
385         }
386         for (i = 0; i < gnx; i += DIM)
387         {
388             c1[i+XX][nframes] = fr.v[index[i/DIM]][XX];
389             c1[i+YY][nframes] = fr.v[index[i/DIM]][YY];
390             c1[i+ZZ][nframes] = fr.v[index[i/DIM]][ZZ];
391         }
392
393         t1 = fr.time;
394
395         nframes++;
396     }
397     while (read_next_frame(oenv, status, &fr));
398
399     close_trj(status);
400
401     dt = (t1-t0)/(nframes-1);
402     if (nV > 0)
403     {
404         V = Vsum/nV;
405     }
406     if (bVerbose)
407     {
408         printf("Going to do %d fourier transforms of length %d. Hang on.\n",
409                gnx, nframes);
410     }
411     /* Unfortunately the -normalize program option for the autocorrelation
412      * function calculation is added as a hack with a static variable in the
413      * autocorrelation.c source. That would work if we called the normal
414      * do_autocorr(), but this routine overrides that by directly calling
415      * the low-level functionality. That unfortunately leads to ignoring the
416      * default value for the option (which is to normalize).
417      * Since the absolute value seems to be important for the subsequent
418      * analysis below, we detect the value directly from the option, calculate
419      * the autocorrelation without normalization, and then apply the
420      * normalization just to the autocorrelation output
421      * (or not, if the user asked for a non-normalized autocorrelation).
422      */
423     normalizeAutocorrelation = opt2parg_bool("-normalize", npargs, ppa);
424
425     /* Note that we always disable normalization here, regardless of user settings */
426     low_do_autocorr(NULL, oenv, NULL, nframes, gnx, nframes, c1, dt, eacNormal, 0, FALSE,
427                     FALSE, FALSE, -1, -1, 0);
428     snew(dos, DOS_NR);
429     for (j = 0; (j < DOS_NR); j++)
430     {
431         snew(dos[j], nframes+4);
432     }
433
434     if (bVerbose)
435     {
436         printf("Going to merge the ACFs into the mass-weighted and plain ACF\n");
437     }
438     for (i = 0; (i < gnx); i += DIM)
439     {
440         mi = top.atoms.atom[index[i/DIM]].m;
441         for (j = 0; (j < nframes/2); j++)
442         {
443             c1j            = (c1[i+XX][j] + c1[i+YY][j] + c1[i+ZZ][j]);
444             dos[VACF][j]  += c1j/Natom;
445             dos[MVACF][j] += mi*c1j;
446         }
447     }
448
449     fp = xvgropen(opt2fn("-vacf", NFILE, fnm), "Velocity autocorrelation function",
450                   "Time (ps)", "C(t)", oenv);
451     snew(tt, nframes/2);
452
453     invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
454
455     for (j = 0; (j < nframes/2); j++)
456     {
457         tt[j] = j*dt;
458         fprintf(fp, "%10g  %10g\n", tt[j], dos[VACF][j] * invNormalize);
459     }
460     xvgrclose(fp);
461
462     fp = xvgropen(opt2fn("-mvacf", NFILE, fnm), "Mass-weighted velocity autocorrelation function",
463                   "Time (ps)", "C(t)", oenv);
464
465     invNormalize = normalizeAutocorrelation ? 1.0/dos[VACF][0] : 1.0;
466
467     for (j = 0; (j < nframes/2); j++)
468     {
469         fprintf(fp, "%10g  %10g\n", tt[j], dos[MVACF][j] * invNormalize);
470     }
471     xvgrclose(fp);
472
473     if ((fftcode = gmx_fft_init_1d_real(&fft, nframes/2,
474                                         GMX_FFT_FLAG_NONE)) != 0)
475     {
476         gmx_fatal(FARGS, "gmx_fft_init_1d_real returned %d", fftcode);
477     }
478     if ((fftcode = gmx_fft_1d_real(fft, GMX_FFT_REAL_TO_COMPLEX,
479                                    (void *)dos[MVACF], (void *)dos[DOS])) != 0)
480     {
481         gmx_fatal(FARGS, "gmx_fft_1d_real returned %d", fftcode);
482     }
483
484     /* First compute the DoS */
485     /* Magic factor of 8 included now. */
486     bfac = 8*dt*beta/2;
487     dos2 = 0;
488     snew(nu, nframes/4);
489     for (j = 0; (j < nframes/4); j++)
490     {
491         nu[j] = 2*j/(t1-t0);
492         dos2 += gmx::square(dos[DOS][2*j]) + gmx::square(dos[DOS][2*j+1]);
493         if (bAbsolute)
494         {
495             dos[DOS][j] = bfac*std::hypot(dos[DOS][2*j], dos[DOS][2*j+1]);
496         }
497         else
498         {
499             dos[DOS][j] = bfac*dos[DOS][2*j];
500         }
501     }
502     /* Normalize it */
503     dostot = evaluate_integral(nframes/4, nu, dos[DOS], NULL, nframes/4, &stddev);
504     if (bNormalizeDos)
505     {
506         for (j = 0; (j < nframes/4); j++)
507         {
508             dos[DOS][j] *= 3*Natom/dostot;
509         }
510     }
511
512     /* Now analyze it */
513     DoS0 = dos[DOS][0];
514
515     /* Note this eqn. is incorrect in Pascal2011a! */
516     Delta = ((2*DoS0/(9*Natom))*std::sqrt(M_PI*BOLTZ*Temp*Natom/tmass)*
517              std::pow((Natom/V), 1.0/3.0)*std::pow(6.0/M_PI, 2.0/3.0));
518     f     = calc_fluidicity(Delta, toler);
519     y     = calc_y(f, Delta, toler);
520     z     = calc_compress(y);
521     Sig   = BOLTZ*(5.0/2.0+std::log(2*M_PI*BOLTZ*Temp/(gmx::square(PLANCK))*V/(f*Natom)));
522     Shs   = Sig+calc_Shs(f, y);
523     rho   = (tmass*AMU)/(V*NANO*NANO*NANO);
524     sigHS = std::cbrt(6*y*V/(M_PI*Natom));
525
526     fprintf(fplog, "System = \"%s\"\n", *top.name);
527     fprintf(fplog, "Nmol = %d\n", Nmol);
528     fprintf(fplog, "Natom = %d\n", Natom);
529     fprintf(fplog, "dt = %g ps\n", dt);
530     fprintf(fplog, "tmass = %g amu\n", tmass);
531     fprintf(fplog, "V = %g nm^3\n", V);
532     fprintf(fplog, "rho = %g g/l\n", rho);
533     fprintf(fplog, "T = %g K\n", Temp);
534     fprintf(fplog, "beta = %g mol/kJ\n", beta);
535
536     fprintf(fplog, "\nDoS parameters\n");
537     fprintf(fplog, "Delta = %g\n", Delta);
538     fprintf(fplog, "fluidicity = %g\n", f);
539     fprintf(fplog, "hard sphere packing fraction = %g\n", y);
540     fprintf(fplog, "hard sphere compressibility = %g\n", z);
541     fprintf(fplog, "ideal gas entropy = %g\n", Sig);
542     fprintf(fplog, "hard sphere entropy = %g\n", Shs);
543     fprintf(fplog, "sigma_HS = %g nm\n", sigHS);
544     fprintf(fplog, "DoS0 = %g\n", DoS0);
545     fprintf(fplog, "Dos2 = %g\n", dos2);
546     fprintf(fplog, "DoSTot = %g\n", dostot);
547
548     /* Now compute solid (2) and diffusive (3) components */
549     fp = xvgropen(opt2fn("-dos", NFILE, fnm), "Density of states",
550                   bRecip ? "E (cm\\S-1\\N)" : "\\f{12}n\\f{4} (1/ps)",
551                   "\\f{4}S(\\f{12}n\\f{4})", oenv);
552     xvgr_legend(fp, asize(DoSlegend), DoSlegend, oenv);
553     recip_fac = bRecip ? (1e7/SPEED_OF_LIGHT) : 1.0;
554     for (j = 0; (j < nframes/4); j++)
555     {
556         dos[DOS_DIFF][j]  = DoS0/(1+gmx::square(DoS0*M_PI*nu[j]/(6*f*Natom)));
557         dos[DOS_SOLID][j] = dos[DOS][j]-dos[DOS_DIFF][j];
558         fprintf(fp, "%10g  %10g  %10g  %10g\n",
559                 recip_fac*nu[j],
560                 dos[DOS][j]/recip_fac,
561                 dos[DOS_SOLID][j]/recip_fac,
562                 dos[DOS_DIFF][j]/recip_fac);
563     }
564     xvgrclose(fp);
565
566     /* Finally analyze the results! */
567     wCdiff = 0.5;
568     wSdiff = Shs/(3*BOLTZ); /* Is this correct? */
569     wEdiff = 0.5;
570     wAdiff = wEdiff-wSdiff;
571     for (j = 0; (j < nframes/4); j++)
572     {
573         dos[DOS_CP][j] = (dos[DOS_DIFF][j]*wCdiff +
574                           dos[DOS_SOLID][j]*wCsolid(nu[j], beta));
575         dos[DOS_S][j]  = (dos[DOS_DIFF][j]*wSdiff +
576                           dos[DOS_SOLID][j]*wSsolid(nu[j], beta));
577         dos[DOS_A][j]  = (dos[DOS_DIFF][j]*wAdiff +
578                           dos[DOS_SOLID][j]*wAsolid(nu[j], beta));
579         dos[DOS_E][j]  = (dos[DOS_DIFF][j]*wEdiff +
580                           dos[DOS_SOLID][j]*wEsolid(nu[j], beta));
581     }
582     DiffCoeff = evaluate_integral(nframes/2, tt, dos[VACF], NULL, nframes/2, &stddev);
583     DiffCoeff = 1000*DiffCoeff/3.0;
584     fprintf(fplog, "Diffusion coefficient from VACF %g 10^-5 cm^2/s\n",
585             DiffCoeff);
586     fprintf(fplog, "Diffusion coefficient from DoS %g 10^-5 cm^2/s\n",
587             1000*DoS0/(12*tmass*beta));
588
589     cP = BOLTZ * evaluate_integral(nframes/4, nu, dos[DOS_CP], NULL,
590                                    nframes/4, &stddev);
591     fprintf(fplog, "Heat capacity %g J/mol K\n", 1000*cP/Nmol);
592     fprintf(fplog, "\nArrivederci!\n");
593     gmx_fio_fclose(fplog);
594
595     do_view(oenv, ftp2fn(efXVG, NFILE, fnm), "-nxy");
596
597     return 0;
598 }