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, 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.
44 #include "gromacs/commandline/pargs.h"
46 #include "gromacs/utility/smalloc.h"
48 #include "gromacs/math/vec.h"
51 #include "gromacs/utility/futil.h"
54 #include "gromacs/fileio/xvgr.h"
58 #include "mtop_util.h"
62 #include "gromacs/linearalgebra/eigensolver.h"
63 #include "gromacs/linearalgebra/mtxio.h"
64 #include "gromacs/linearalgebra/sparsematrix.h"
66 static double cv_corr(double nu, double T)
68 double x = PLANCK*nu/(BOLTZ*T);
77 return BOLTZ*KILO*(ex*sqr(x)/sqr(ex-1) - 1);
81 static double u_corr(double nu, double T)
83 double x = PLANCK*nu/(BOLTZ*T);
92 return BOLTZ*T*(0.5*x - 1 + x/(ex-1));
96 static int get_nharm_mt(gmx_moltype_t *mt)
98 static int harm_func[] = { F_BONDS };
102 for (i = 0; (i < asize(harm_func)); i++)
105 nh += mt->ilist[ft].nr/(interaction_function[ft].nratoms+1);
110 static int get_nvsite_mt(gmx_moltype_t *mt)
112 static int vs_func[] = {
113 F_VSITE2, F_VSITE3, F_VSITE3FD, F_VSITE3FAD,
114 F_VSITE3OUT, F_VSITE4FD, F_VSITE4FDN, F_VSITEN
119 for (i = 0; (i < asize(vs_func)); i++)
122 nh += mt->ilist[ft].nr/(interaction_function[ft].nratoms+1);
127 static int get_nharm(gmx_mtop_t *mtop, int *nvsites)
133 for (j = 0; (j < mtop->nmolblock); j++)
135 mt = mtop->molblock[j].type;
136 nh += mtop->molblock[j].nmol * get_nharm_mt(&(mtop->moltype[mt]));
137 nv += mtop->molblock[j].nmol * get_nvsite_mt(&(mtop->moltype[mt]));
144 nma_full_hessian(real * hess,
157 natoms = top->atoms.nr;
159 /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
163 for (i = 0; (i < natoms); i++)
165 for (j = 0; (j < DIM); j++)
167 for (k = 0; (k < natoms); k++)
169 mass_fac = gmx_invsqrt(top->atoms.atom[i].m*top->atoms.atom[k].m);
170 for (l = 0; (l < DIM); l++)
172 hess[(i*DIM+j)*ndim+k*DIM+l] *= mass_fac;
179 /* call diagonalization routine. */
181 fprintf(stderr, "\nDiagonalizing to find vectors %d through %d...\n", begin, end);
184 eigensolver(hess, ndim, begin-1, end-1, eigenvalues, eigenvectors);
186 /* And scale the output eigenvectors */
187 if (bM && eigenvectors != NULL)
189 for (i = 0; i < (end-begin+1); i++)
191 for (j = 0; j < natoms; j++)
193 mass_fac = gmx_invsqrt(top->atoms.atom[j].m);
194 for (k = 0; (k < DIM); k++)
196 eigenvectors[i*ndim+j*DIM+k] *= mass_fac;
206 nma_sparse_hessian(gmx_sparsematrix_t * sparse_hessian,
220 natoms = top->atoms.nr;
223 /* Cannot check symmetry since we only store half matrix */
224 /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
228 for (iatom = 0; (iatom < natoms); iatom++)
230 for (j = 0; (j < DIM); j++)
233 for (k = 0; k < sparse_hessian->ndata[row]; k++)
235 col = sparse_hessian->data[row][k].col;
237 mass_fac = gmx_invsqrt(top->atoms.atom[iatom].m*top->atoms.atom[katom].m);
238 sparse_hessian->data[row][k].value *= mass_fac;
243 fprintf(stderr, "\nDiagonalizing to find eigenvectors 1 through %d...\n", neig);
246 sparse_eigensolver(sparse_hessian, neig, eigenvalues, eigenvectors, 10000000);
248 /* Scale output eigenvectors */
249 if (bM && eigenvectors != NULL)
251 for (i = 0; i < neig; i++)
253 for (j = 0; j < natoms; j++)
255 mass_fac = gmx_invsqrt(top->atoms.atom[j].m);
256 for (k = 0; (k < DIM); k++)
258 eigenvectors[i*ndim+j*DIM+k] *= mass_fac;
267 int gmx_nmeig(int argc, char *argv[])
269 const char *desc[] = {
270 "[THISMODULE] calculates the eigenvectors/values of a (Hessian) matrix,",
271 "which can be calculated with [gmx-mdrun].",
272 "The eigenvectors are written to a trajectory file ([TT]-v[tt]).",
273 "The structure is written first with t=0. The eigenvectors",
274 "are written as frames with the eigenvector number as timestamp.",
275 "The eigenvectors can be analyzed with [gmx-anaeig].",
276 "An ensemble of structures can be generated from the eigenvectors with",
277 "[gmx-nmens]. When mass weighting is used, the generated eigenvectors",
278 "will be scaled back to plain Cartesian coordinates before generating the",
279 "output. In this case, they will no longer be exactly orthogonal in the",
280 "standard Cartesian norm, but in the mass-weighted norm they would be.[PAR]",
281 "This program can be optionally used to compute quantum corrections to heat capacity",
282 "and enthalpy by providing an extra file argument [TT]-qcorr[tt]. See the GROMACS",
283 "manual, Chapter 1, for details. The result includes subtracting a harmonic",
284 "degree of freedom at the given temperature.",
285 "The total correction is printed on the terminal screen.",
286 "The recommended way of getting the corrections out is:[PAR]",
287 "[TT]gmx nmeig -s topol.tpr -f nm.mtx -first 7 -last 10000 -T 300 -qc [-constr][tt][PAR]",
288 "The [TT]-constr[tt] option should be used when bond constraints were used during the",
289 "simulation [BB]for all the covalent bonds[bb]. If this is not the case, ",
290 "you need to analyze the [TT]quant_corr.xvg[tt] file yourself.[PAR]",
291 "To make things more flexible, the program can also take virtual sites into account",
292 "when computing quantum corrections. When selecting [TT]-constr[tt] and",
293 "[TT]-qc[tt], the [TT]-begin[tt] and [TT]-end[tt] options will be set automatically as well.",
294 "Again, if you think you know it better, please check the [TT]eigenfreq.xvg[tt]",
298 static gmx_bool bM = TRUE, bCons = FALSE;
299 static int begin = 1, end = 50, maxspec = 4000;
300 static real T = 298.15, width = 1;
303 { "-m", FALSE, etBOOL, {&bM},
304 "Divide elements of Hessian by product of sqrt(mass) of involved "
305 "atoms prior to diagonalization. This should be used for 'Normal Modes' "
307 { "-first", FALSE, etINT, {&begin},
308 "First eigenvector to write away" },
309 { "-last", FALSE, etINT, {&end},
310 "Last eigenvector to write away" },
311 { "-maxspec", FALSE, etINT, {&maxspec},
312 "Highest frequency (1/cm) to consider in the spectrum" },
313 { "-T", FALSE, etREAL, {&T},
314 "Temperature for computing quantum heat capacity and enthalpy when using normal mode calculations to correct classical simulations" },
315 { "-constr", FALSE, etBOOL, {&bCons},
316 "If constraints were used in the simulation but not in the normal mode analysis (this is the recommended way of doing it) you will need to set this for computing the quantum corrections." },
317 { "-width", FALSE, etREAL, {&width},
318 "Width (sigma) of the gaussian peaks (1/cm) when generating a spectrum" }
320 FILE *out, *qc, *spec;
329 real rdum, mass_fac, qcvtot, qutot, qcv, qu;
330 int natoms, ndim, nrow, ncol, count, nharm, nvsite;
332 int i, j, k, l, d, gnx;
336 int version, generation;
337 real value, omega, nu;
338 real factor_gmx_to_omega2;
339 real factor_omega_to_wavenumber;
340 real *spectrum = NULL;
343 const char *qcleg[] = {
344 "Heat Capacity cV (J/mol K)",
345 "Enthalpy H (kJ/mol)"
347 real * full_hessian = NULL;
348 gmx_sparsematrix_t * sparse_hessian = NULL;
351 { efMTX, "-f", "hessian", ffREAD },
352 { efTPX, NULL, NULL, ffREAD },
353 { efXVG, "-of", "eigenfreq", ffWRITE },
354 { efXVG, "-ol", "eigenval", ffWRITE },
355 { efXVG, "-os", "spectrum", ffOPTWR },
356 { efXVG, "-qc", "quant_corr", ffOPTWR },
357 { efTRN, "-v", "eigenvec", ffWRITE }
359 #define NFILE asize(fnm)
361 if (!parse_common_args(&argc, argv, PCA_BE_NICE,
362 NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, NULL, &oenv))
367 /* Read tpr file for volume and number of harmonic terms */
368 read_tpxheader(ftp2fn(efTPX, NFILE, fnm), &tpx, TRUE, &version, &generation);
369 snew(top_x, tpx.natoms);
371 read_tpx(ftp2fn(efTPX, NFILE, fnm), NULL, box, &natoms,
372 top_x, NULL, NULL, &mtop);
375 nharm = get_nharm(&mtop, &nvsite);
382 top = gmx_mtop_t_to_t_topology(&mtop);
387 if (opt2bSet("-qc", NFILE, fnm))
389 begin = 7+DIM*nvsite;
400 printf("Using begin = %d and end = %d\n", begin, end);
402 /*open Hessian matrix */
403 gmx_mtxio_read(ftp2fn(efMTX, NFILE, fnm), &nrow, &ncol, &full_hessian, &sparse_hessian);
405 /* Memory for eigenvalues and eigenvectors (begin..end) */
406 snew(eigenvalues, nrow);
407 snew(eigenvectors, nrow*(end-begin+1));
409 /* If the Hessian is in sparse format we can calculate max (ndim-1) eigenvectors,
410 * and they must start at the first one. If this is not valid we convert to full matrix
411 * storage, but warn the user that we might run out of memory...
413 if ((sparse_hessian != NULL) && (begin != 1 || end == ndim))
417 fprintf(stderr, "Cannot use sparse Hessian with first eigenvector != 1.\n");
419 else if (end == ndim)
421 fprintf(stderr, "Cannot use sparse Hessian to calculate all eigenvectors.\n");
424 fprintf(stderr, "Will try to allocate memory and convert to full matrix representation...\n");
426 snew(full_hessian, nrow*ncol);
427 for (i = 0; i < nrow*ncol; i++)
432 for (i = 0; i < sparse_hessian->nrow; i++)
434 for (j = 0; j < sparse_hessian->ndata[i]; j++)
436 k = sparse_hessian->data[i][j].col;
437 value = sparse_hessian->data[i][j].value;
438 full_hessian[i*ndim+k] = value;
439 full_hessian[k*ndim+i] = value;
442 gmx_sparsematrix_destroy(sparse_hessian);
443 sparse_hessian = NULL;
444 fprintf(stderr, "Converted sparse to full matrix storage.\n");
447 if (full_hessian != NULL)
449 /* Using full matrix storage */
450 nma_full_hessian(full_hessian, nrow, bM, &top, begin, end,
451 eigenvalues, eigenvectors);
455 /* Sparse memory storage, allocate memory for eigenvectors */
456 snew(eigenvectors, ncol*end);
457 nma_sparse_hessian(sparse_hessian, bM, &top, end, eigenvalues, eigenvectors);
460 /* check the output, first 6 eigenvalues should be reasonably small */
462 for (i = begin-1; (i < 6); i++)
464 if (fabs(eigenvalues[i]) > 1.0e-3)
471 fprintf(stderr, "\nOne of the lowest 6 eigenvalues has a non-zero value.\n");
472 fprintf(stderr, "This could mean that the reference structure was not\n");
473 fprintf(stderr, "properly energy minimized.\n");
476 /* now write the output */
477 fprintf (stderr, "Writing eigenvalues...\n");
478 out = xvgropen(opt2fn("-ol", NFILE, fnm),
479 "Eigenvalues", "Eigenvalue index", "Eigenvalue [Gromacs units]",
481 if (output_env_get_print_xvgr_codes(oenv))
485 fprintf(out, "@ subtitle \"mass weighted\"\n");
489 fprintf(out, "@ subtitle \"not mass weighted\"\n");
493 for (i = 0; i <= (end-begin); i++)
495 fprintf (out, "%6d %15g\n", begin+i, eigenvalues[i]);
500 if (opt2bSet("-qc", NFILE, fnm))
502 qc = xvgropen(opt2fn("-qc", NFILE, fnm), "Quantum Corrections", "Eigenvector index", "", oenv);
503 xvgr_legend(qc, asize(qcleg), qcleg, oenv);
510 printf("Writing eigenfrequencies - negative eigenvalues will be set to zero.\n");
512 out = xvgropen(opt2fn("-of", NFILE, fnm),
513 "Eigenfrequencies", "Eigenvector index", "Wavenumber [cm\\S-1\\N]",
515 if (output_env_get_print_xvgr_codes(oenv))
519 fprintf(out, "@ subtitle \"mass weighted\"\n");
523 fprintf(out, "@ subtitle \"not mass weighted\"\n");
528 if (opt2bSet("-os", NFILE, fnm) && (maxspec > 0))
530 snew(spectrum, maxspec);
531 spec = xvgropen(opt2fn("-os", NFILE, fnm),
532 "Vibrational spectrum based on harmonic approximation",
533 "\\f{12}w\\f{4} (cm\\S-1\\N)",
534 "Intensity [Gromacs units]",
536 for (i = 0; (i < maxspec); i++)
542 /* Gromacs units are kJ/(mol*nm*nm*amu),
543 * where amu is the atomic mass unit.
545 * For the eigenfrequencies we want to convert this to spectroscopic absorption
546 * wavenumbers given in cm^(-1), which is the frequency divided by the speed of
547 * light. Do this by first converting to omega^2 (units 1/s), take the square
548 * root, and finally divide by the speed of light (nm/ps in gromacs).
550 factor_gmx_to_omega2 = 1.0E21/(AVOGADRO*AMU);
551 factor_omega_to_wavenumber = 1.0E-5/(2.0*M_PI*SPEED_OF_LIGHT);
553 for (i = begin; (i <= end); i++)
555 value = eigenvalues[i-begin];
560 omega = sqrt(value*factor_gmx_to_omega2);
561 nu = 1e-12*omega/(2*M_PI);
562 value = omega*factor_omega_to_wavenumber;
563 fprintf (out, "%6d %15g\n", i, value);
566 wfac = eigenvalues[i-begin]/(width*sqrt(2*M_PI));
567 for (j = 0; (j < maxspec); j++)
569 spectrum[j] += wfac*exp(-sqr(j-value)/(2*sqr(width)));
574 qcv = cv_corr(nu, T);
581 fprintf (qc, "%6d %15g %15g\n", i, qcv, qu);
589 for (j = 0; (j < maxspec); j++)
591 fprintf(spec, "%10g %10g\n", 1.0*j, spectrum[j]);
597 printf("Quantum corrections for harmonic degrees of freedom\n");
598 printf("Use appropriate -first and -last options to get reliable results.\n");
599 printf("There were %d constraints and %d vsites in the simulation\n",
601 printf("Total correction to cV = %g J/mol K\n", qcvtot);
602 printf("Total correction to H = %g kJ/mol\n", qutot);
604 please_cite(stdout, "Caleman2011b");
606 /* Writing eigenvectors. Note that if mass scaling was used, the eigenvectors
607 * were scaled back from mass weighted cartesian to plain cartesian in the
608 * nma_full_hessian() or nma_sparse_hessian() routines. Mass scaled vectors
609 * will not be strictly orthogonal in plain cartesian scalar products.
611 write_eigenvectors(opt2fn("-v", NFILE, fnm), natoms, eigenvectors, FALSE, begin, end,
612 eWXR_NO, NULL, FALSE, top_x, bM, eigenvalues);