Let gmx nmeig print thermochemistry.
[gromacs.git] / src / gromacs / gmxana / gmx_nmeig.cpp
bloba0ba7e9d925328358800c143fb1f536ae2afba0c
1 /*
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,2015,2016,2017,2018, 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.
37 #include "gmxpre.h"
39 #include <cassert>
40 #include <cmath>
41 #include <cstring>
43 #include <vector>
45 #include "gromacs/commandline/pargs.h"
46 #include "gromacs/fileio/mtxio.h"
47 #include "gromacs/fileio/tpxio.h"
48 #include "gromacs/fileio/xvgr.h"
49 #include "gromacs/gmxana/eigio.h"
50 #include "gromacs/gmxana/gmx_ana.h"
51 #include "gromacs/gmxana/gstat.h"
52 #include "gromacs/gmxana/princ.h"
53 #include "gromacs/linearalgebra/eigensolver.h"
54 #include "gromacs/linearalgebra/sparsematrix.h"
55 #include "gromacs/math/functions.h"
56 #include "gromacs/math/units.h"
57 #include "gromacs/math/vec.h"
58 #include "gromacs/math/vecdump.h"
59 #include "gromacs/topology/ifunc.h"
60 #include "gromacs/topology/mtop_util.h"
61 #include "gromacs/topology/topology.h"
62 #include "gromacs/utility/arraysize.h"
63 #include "gromacs/utility/fatalerror.h"
64 #include "gromacs/utility/futil.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/pleasecite.h"
67 #include "gromacs/utility/smalloc.h"
69 #include "thermochemistry.h"
71 static double cv_corr(double nu, double T)
73 double x = PLANCK*nu/(BOLTZ*T);
74 double ex = std::exp(x);
76 if (nu <= 0)
78 return BOLTZ*KILO;
80 else
82 return BOLTZ*KILO*(ex*gmx::square(x)/gmx::square(ex-1) - 1);
86 static double u_corr(double nu, double T)
88 double x = PLANCK*nu/(BOLTZ*T);
89 double ex = std::exp(x);
91 if (nu <= 0)
93 return BOLTZ*T;
95 else
97 return BOLTZ*T*(0.5*x - 1 + x/(ex-1));
101 static size_t get_nharm_mt(const gmx_moltype_t *mt)
103 static int harm_func[] = { F_BONDS };
104 int i, ft;
105 size_t nh = 0;
107 for (i = 0; (i < asize(harm_func)); i++)
109 ft = harm_func[i];
110 nh += mt->ilist[ft].nr/(interaction_function[ft].nratoms+1);
112 return nh;
115 static int get_nharm(const gmx_mtop_t *mtop)
117 int nh = 0;
119 for (const gmx_molblock_t &molb : mtop->molblock)
121 nh += molb.nmol * get_nharm_mt(&(mtop->moltype[molb.type]));
123 return nh;
126 static void
127 nma_full_hessian(real *hess,
128 int ndim,
129 gmx_bool bM,
130 const t_topology *top,
131 const std::vector<size_t> &atom_index,
132 int begin,
133 int end,
134 real *eigenvalues,
135 real *eigenvectors)
137 real mass_fac;
139 /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
141 if (bM)
143 for (size_t i = 0; (i < atom_index.size()); i++)
145 size_t ai = atom_index[i];
146 for (size_t j = 0; (j < DIM); j++)
148 for (size_t k = 0; (k < atom_index.size()); k++)
150 size_t ak = atom_index[k];
151 mass_fac = gmx::invsqrt(top->atoms.atom[ai].m*top->atoms.atom[ak].m);
152 for (size_t l = 0; (l < DIM); l++)
154 hess[(i*DIM+j)*ndim+k*DIM+l] *= mass_fac;
161 /* call diagonalization routine. */
163 fprintf(stderr, "\nDiagonalizing to find vectors %d through %d...\n", begin, end);
164 fflush(stderr);
166 eigensolver(hess, ndim, begin-1, end-1, eigenvalues, eigenvectors);
168 /* And scale the output eigenvectors */
169 if (bM && eigenvectors != nullptr)
171 for (int i = 0; i < (end-begin+1); i++)
173 for (size_t j = 0; j < atom_index.size(); j++)
175 size_t aj = atom_index[j];
176 mass_fac = gmx::invsqrt(top->atoms.atom[aj].m);
177 for (size_t k = 0; (k < DIM); k++)
179 eigenvectors[i*ndim+j*DIM+k] *= mass_fac;
188 static void
189 nma_sparse_hessian(gmx_sparsematrix_t *sparse_hessian,
190 gmx_bool bM,
191 const t_topology *top,
192 const std::vector<size_t> &atom_index,
193 int neig,
194 real *eigenvalues,
195 real *eigenvectors)
197 int i, k;
198 int row, col;
199 real mass_fac;
200 int katom;
201 size_t ndim;
203 ndim = DIM*atom_index.size();
205 /* Cannot check symmetry since we only store half matrix */
206 /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
208 GMX_RELEASE_ASSERT(sparse_hessian != nullptr, "NULL matrix pointer provided to nma_sparse_hessian");
210 if (bM)
212 for (size_t iatom = 0; (iatom < atom_index.size()); iatom++)
214 size_t ai = atom_index[iatom];
215 for (size_t j = 0; (j < DIM); j++)
217 row = DIM*iatom+j;
218 for (k = 0; k < sparse_hessian->ndata[row]; k++)
220 col = sparse_hessian->data[row][k].col;
221 katom = col/3;
222 size_t ak = atom_index[katom];
223 mass_fac = gmx::invsqrt(top->atoms.atom[ai].m*top->atoms.atom[ak].m);
224 sparse_hessian->data[row][k].value *= mass_fac;
229 fprintf(stderr, "\nDiagonalizing to find eigenvectors 1 through %d...\n", neig);
230 fflush(stderr);
232 sparse_eigensolver(sparse_hessian, neig, eigenvalues, eigenvectors, 10000000);
234 /* Scale output eigenvectors */
235 if (bM && eigenvectors != nullptr)
237 for (i = 0; i < neig; i++)
239 for (size_t j = 0; j < atom_index.size(); j++)
241 size_t aj = atom_index[j];
242 mass_fac = gmx::invsqrt(top->atoms.atom[aj].m);
243 for (k = 0; (k < DIM); k++)
245 eigenvectors[i*ndim+j*DIM+k] *= mass_fac;
253 /* Returns a pointer for eigenvector storage */
254 static real *allocateEigenvectors(int nrow, int first, int last,
255 bool ignoreBegin)
257 int numVector;
258 if (ignoreBegin)
260 numVector = last;
262 else
264 numVector = last - first + 1;
266 size_t vectorsSize = static_cast<size_t>(nrow)*static_cast<size_t>(numVector);
267 /* We can't have more than INT_MAX elements.
268 * Relaxing this restriction probably requires changing lots of loop
269 * variable types in the linear algebra code.
271 if (vectorsSize > INT_MAX)
273 gmx_fatal(FARGS, "You asked to store %d eigenvectors of size %d, which requires more than the supported %d elements; %sdecrease -last",
274 numVector, nrow, INT_MAX,
275 ignoreBegin ? "" : "increase -first and/or ");
278 real *eigenvectors;
279 snew(eigenvectors, vectorsSize);
281 return eigenvectors;
284 /*! \brief Compute heat capacity due to translational motion
286 static double calcTranslationalHeatCapacity()
288 return RGAS*1.5;
291 /*! \brief Compute internal energy due to translational motion
293 static double calcTranslationalInternalEnergy(double T)
295 return BOLTZ*T*1.5;
298 /*! \brief Compute heat capacity due to rotational motion
300 * \param[in] linear Should be TRUE if this is a linear molecule
301 * \param[in] T Temperature
302 * \return The heat capacity at constant volume
304 static double calcRotationalInternalEnergy(gmx_bool linear, double T)
306 if (linear)
308 return BOLTZ*T;
310 else
312 return BOLTZ*T*1.5;
316 /*! \brief Compute heat capacity due to rotational motion
318 * \param[in] linear Should be TRUE if this is a linear molecule
319 * \return The heat capacity at constant volume
321 static double calcRotationalHeatCapacity(gmx_bool linear)
323 if (linear)
325 return RGAS;
327 else
329 return RGAS*1.5;
333 static void analyzeThermochemistry(FILE *fp,
334 const t_topology &top,
335 rvec top_x[],
336 const std::vector<size_t> &atom_index,
337 real eigfreq[],
338 real T,
339 real P,
340 int sigma_r,
341 real scale_factor,
342 real linear_toler)
344 std::vector<int> index;
345 for (auto &ai : atom_index)
347 index.push_back(static_cast<int>(ai));
350 rvec xcm;
351 double tmass = calc_xcm(top_x, index.size(),
352 index.data(), top.atoms.atom, xcm, FALSE);
353 double Strans = calcTranslationalEntropy(tmass, T, P);
354 std::vector<gmx::RVec> x_com;
355 x_com.resize(top.atoms.nr);
356 for (int i = 0; i < top.atoms.nr; i++)
358 copy_rvec(top_x[i], x_com[i]);
360 (void) sub_xcm(as_rvec_array(x_com.data()), index.size(), index.data(),
361 top.atoms.atom, xcm, FALSE);
363 rvec inertia;
364 matrix trans;
365 principal_comp(index.size(), index.data(), top.atoms.atom,
366 as_rvec_array(x_com.data()), trans, inertia);
367 bool linear = (inertia[XX]/inertia[YY] < linear_toler &&
368 inertia[XX]/inertia[ZZ] < linear_toler);
369 // (kJ/mol ps)^2/(Dalton nm^2 kJ/mol K) =
370 // KILO kg m^2 ps^2/(s^2 mol g/mol nm^2 K) =
371 // KILO^2 10^18 / 10^24 K = 1/K
372 double rot_const = gmx::square(PLANCK)/(8*gmx::square(M_PI)*BOLTZ);
373 // Rotational temperature (1/K)
374 rvec theta = { 0, 0, 0 };
375 if (linear)
377 // For linear molecules the first element of the inertia
378 // vector is zero.
379 theta[0] = rot_const/inertia[1];
381 else
383 for (int m = 0; m < DIM; m++)
385 theta[m] = rot_const/inertia[m];
388 if (debug)
390 pr_rvec(debug, 0, "inertia", inertia, DIM, TRUE);
391 pr_rvec(debug, 0, "theta", theta, DIM, TRUE);
392 pr_rvecs(debug, 0, "trans", trans, DIM);
393 fprintf(debug, "linear molecule = %s\n", linear ? "true" : "no");
395 size_t nFreq = index.size()*DIM;
396 auto eFreq = gmx::arrayRefFromArray(eigfreq, nFreq);
397 double Svib = calcQuasiHarmonicEntropy(eFreq, T, linear, scale_factor);
399 double Srot = calcRotationalEntropy(T, top.atoms.nr,
400 linear, theta, sigma_r);
401 fprintf(fp, "Translational entropy %g J/mol K\n", Strans);
402 fprintf(fp, "Rotational entropy %g J/mol K\n", Srot);
403 fprintf(fp, "Vibrational entropy %g J/mol K\n", Svib);
404 fprintf(fp, "Total entropy %g J/mol K\n", Svib+Strans+Srot);
405 double HeatCapacity = (calcTranslationalHeatCapacity() +
406 calcRotationalHeatCapacity(linear) +
407 calcVibrationalHeatCapacity(eFreq, T, linear, scale_factor));
408 fprintf(fp, "Heat capacity %g J/mol K\n", HeatCapacity);
409 double Evib = (calcTranslationalInternalEnergy(T) +
410 calcRotationalInternalEnergy(linear, T) +
411 calcVibrationalInternalEnergy(eFreq, T, linear, scale_factor));
412 fprintf(fp, "Internal energy %g kJ/mol\n", Evib);
416 int gmx_nmeig(int argc, char *argv[])
418 const char *desc[] = {
419 "[THISMODULE] calculates the eigenvectors/values of a (Hessian) matrix,",
420 "which can be calculated with [gmx-mdrun].",
421 "The eigenvectors are written to a trajectory file ([TT]-v[tt]).",
422 "The structure is written first with t=0. The eigenvectors",
423 "are written as frames with the eigenvector number and eigenvalue",
424 "written as step number and timestamp, respectively.",
425 "The eigenvectors can be analyzed with [gmx-anaeig].",
426 "An ensemble of structures can be generated from the eigenvectors with",
427 "[gmx-nmens]. When mass weighting is used, the generated eigenvectors",
428 "will be scaled back to plain Cartesian coordinates before generating the",
429 "output. In this case, they will no longer be exactly orthogonal in the",
430 "standard Cartesian norm, but in the mass-weighted norm they would be.[PAR]",
431 "This program can be optionally used to compute quantum corrections to heat capacity",
432 "and enthalpy by providing an extra file argument [TT]-qcorr[tt]. See the GROMACS",
433 "manual, Chapter 1, for details. The result includes subtracting a harmonic",
434 "degree of freedom at the given temperature.",
435 "The total correction is printed on the terminal screen.",
436 "The recommended way of getting the corrections out is:[PAR]",
437 "[TT]gmx nmeig -s topol.tpr -f nm.mtx -first 7 -last 10000 -T 300 -qc [-constr][tt][PAR]",
438 "The [TT]-constr[tt] option should be used when bond constraints were used during the",
439 "simulation [BB]for all the covalent bonds[bb]. If this is not the case, ",
440 "you need to analyze the [TT]quant_corr.xvg[tt] file yourself.[PAR]",
441 "To make things more flexible, the program can also take virtual sites into account",
442 "when computing quantum corrections. When selecting [TT]-constr[tt] and",
443 "[TT]-qc[tt], the [TT]-begin[tt] and [TT]-end[tt] options will be set automatically as well.[PAR]",
444 "Based on a harmonic analysis of the normal mode frequencies,",
445 "thermochemical properties S0 (Standard Entropy),",
446 "Cv (Heat capacity at constant volume) and the internal energy can be",
447 "computed, much in the same manner as popular quantum chemistry",
448 "programs."
451 static gmx_bool bM = TRUE, bCons = FALSE;
452 static int begin = 1, end = 50, maxspec = 4000, sigma_r = 1;
453 static real T = 298.15, width = 1, P = 1, scale_factor = 1;
454 static real linear_toler = 1e-5;
456 t_pargs pa[] =
458 { "-m", FALSE, etBOOL, {&bM},
459 "Divide elements of Hessian by product of sqrt(mass) of involved "
460 "atoms prior to diagonalization. This should be used for 'Normal Modes' "
461 "analysis" },
462 { "-first", FALSE, etINT, {&begin},
463 "First eigenvector to write away" },
464 { "-last", FALSE, etINT, {&end},
465 "Last eigenvector to write away. -1 is use all dimensions." },
466 { "-maxspec", FALSE, etINT, {&maxspec},
467 "Highest frequency (1/cm) to consider in the spectrum" },
468 { "-T", FALSE, etREAL, {&T},
469 "Temperature for computing entropy, quantum heat capacity and enthalpy when using normal mode calculations to correct classical simulations" },
470 { "-P", FALSE, etREAL, {&P},
471 "Pressure (bar) when computing entropy" },
472 { "-sigma", FALSE, etINT, {&sigma_r},
473 "Number of symmetric copies used when computing entropy. E.g. for water the number is 2, for NH3 it is 3 and for methane it is 12." },
474 { "-scale", FALSE, etREAL, {&scale_factor},
475 "Factor to scale frequencies before computing thermochemistry values" },
476 { "-linear_toler", FALSE, etREAL, {&linear_toler},
477 "Tolerance for determining whether a compound is linear as determined from the ration of the moments inertion Ix/Iy and Ix/Iz." },
478 { "-constr", FALSE, etBOOL, {&bCons},
479 "If constraints were used in the simulation but not in the normal mode analysis you will need to set this for computing the quantum corrections." },
480 { "-width", FALSE, etREAL, {&width},
481 "Width (sigma) of the gaussian peaks (1/cm) when generating a spectrum" }
483 FILE *out, *qc, *spec;
484 t_topology top;
485 gmx_mtop_t mtop;
486 rvec *top_x;
487 matrix box;
488 real *eigenvalues;
489 real *eigenvectors;
490 real qcvtot, qutot, qcv, qu;
491 int i, j, k;
492 t_tpxheader tpx;
493 real value, omega, nu;
494 real factor_gmx_to_omega2;
495 real factor_omega_to_wavenumber;
496 real *spectrum = nullptr;
497 real wfac;
498 gmx_output_env_t *oenv;
499 const char *qcleg[] = {
500 "Heat Capacity cV (J/mol K)",
501 "Enthalpy H (kJ/mol)"
503 real * full_hessian = nullptr;
504 gmx_sparsematrix_t * sparse_hessian = nullptr;
506 t_filenm fnm[] = {
507 { efMTX, "-f", "hessian", ffREAD },
508 { efTPR, nullptr, nullptr, ffREAD },
509 { efXVG, "-of", "eigenfreq", ffWRITE },
510 { efXVG, "-ol", "eigenval", ffWRITE },
511 { efXVG, "-os", "spectrum", ffOPTWR },
512 { efXVG, "-qc", "quant_corr", ffOPTWR },
513 { efTRN, "-v", "eigenvec", ffWRITE }
515 #define NFILE asize(fnm)
517 if (!parse_common_args(&argc, argv, 0,
518 NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
520 return 0;
523 /* Read tpr file for volume and number of harmonic terms */
524 read_tpxheader(ftp2fn(efTPR, NFILE, fnm), &tpx, TRUE);
525 snew(top_x, tpx.natoms);
527 int natoms_tpx;
528 read_tpx(ftp2fn(efTPR, NFILE, fnm), nullptr, box, &natoms_tpx,
529 top_x, nullptr, &mtop);
530 int nharm = 0;
531 if (bCons)
533 nharm = get_nharm(&mtop);
535 std::vector<size_t> atom_index = get_atom_index(&mtop);
537 top = gmx_mtop_t_to_t_topology(&mtop, true);
539 bM = TRUE;
540 int ndim = DIM*atom_index.size();
542 if (opt2bSet("-qc", NFILE, fnm))
544 begin = 7;
545 end = ndim;
547 if (begin < 1)
549 begin = 1;
551 if (end == -1 || end > ndim)
553 end = ndim;
555 printf("Using begin = %d and end = %d\n", begin, end);
557 /*open Hessian matrix */
558 int nrow, ncol;
559 gmx_mtxio_read(ftp2fn(efMTX, NFILE, fnm), &nrow, &ncol, &full_hessian, &sparse_hessian);
561 /* If the Hessian is in sparse format we can calculate max (ndim-1) eigenvectors,
562 * If this is not valid we convert to full matrix storage,
563 * but warn the user that we might run out of memory...
565 if ((sparse_hessian != nullptr) && (end == ndim))
567 fprintf(stderr, "Cannot use sparse Hessian to calculate all eigenvectors.\n");
569 fprintf(stderr, "Will try to allocate memory and convert to full matrix representation...\n");
571 size_t hessianSize = static_cast<size_t>(nrow)*static_cast<size_t>(ncol);
572 /* Allowing Hessians larger than INT_MAX probably only makes sense
573 * with (OpenMP) parallel diagonalization routines, since with a single
574 * thread it will takes months.
576 if (hessianSize > INT_MAX)
578 gmx_fatal(FARGS, "Hessian size is %d x %d, which is larger than the maximum allowed %d elements.",
579 nrow, ncol, INT_MAX);
581 snew(full_hessian, hessianSize);
582 for (i = 0; i < nrow*ncol; i++)
584 full_hessian[i] = 0;
587 for (i = 0; i < sparse_hessian->nrow; i++)
589 for (j = 0; j < sparse_hessian->ndata[i]; j++)
591 k = sparse_hessian->data[i][j].col;
592 value = sparse_hessian->data[i][j].value;
593 full_hessian[i*ndim+k] = value;
594 full_hessian[k*ndim+i] = value;
597 gmx_sparsematrix_destroy(sparse_hessian);
598 sparse_hessian = nullptr;
599 fprintf(stderr, "Converted sparse to full matrix storage.\n");
602 snew(eigenvalues, nrow);
604 if (full_hessian != nullptr)
606 /* Using full matrix storage */
607 eigenvectors = allocateEigenvectors(nrow, begin, end, false);
609 nma_full_hessian(full_hessian, nrow, bM, &top, atom_index, begin, end,
610 eigenvalues, eigenvectors);
612 else
614 assert(sparse_hessian);
615 /* Sparse memory storage, allocate memory for eigenvectors */
616 eigenvectors = allocateEigenvectors(nrow, begin, end, true);
618 nma_sparse_hessian(sparse_hessian, bM, &top, atom_index, end, eigenvalues, eigenvectors);
621 /* check the output, first 6 eigenvalues should be reasonably small */
622 gmx_bool bSuck = FALSE;
623 for (i = begin-1; (i < 6); i++)
625 if (std::abs(eigenvalues[i]) > 1.0e-3)
627 bSuck = TRUE;
630 if (bSuck)
632 fprintf(stderr, "\nOne of the lowest 6 eigenvalues has a non-zero value.\n");
633 fprintf(stderr, "This could mean that the reference structure was not\n");
634 fprintf(stderr, "properly energy minimized.\n");
637 /* now write the output */
638 fprintf (stderr, "Writing eigenvalues...\n");
639 out = xvgropen(opt2fn("-ol", NFILE, fnm),
640 "Eigenvalues", "Eigenvalue index", "Eigenvalue [Gromacs units]",
641 oenv);
642 if (output_env_get_print_xvgr_codes(oenv))
644 if (bM)
646 fprintf(out, "@ subtitle \"mass weighted\"\n");
648 else
650 fprintf(out, "@ subtitle \"not mass weighted\"\n");
654 for (i = 0; i <= (end-begin); i++)
656 fprintf (out, "%6d %15g\n", begin+i, eigenvalues[i]);
658 xvgrclose(out);
661 if (opt2bSet("-qc", NFILE, fnm))
663 qc = xvgropen(opt2fn("-qc", NFILE, fnm), "Quantum Corrections", "Eigenvector index", "", oenv);
664 xvgr_legend(qc, asize(qcleg), qcleg, oenv);
665 qcvtot = qutot = 0;
667 else
669 qc = nullptr;
671 printf("Writing eigenfrequencies - negative eigenvalues will be set to zero.\n");
673 out = xvgropen(opt2fn("-of", NFILE, fnm),
674 "Eigenfrequencies", "Eigenvector index", "Wavenumber [cm\\S-1\\N]",
675 oenv);
676 if (output_env_get_print_xvgr_codes(oenv))
678 if (bM)
680 fprintf(out, "@ subtitle \"mass weighted\"\n");
682 else
684 fprintf(out, "@ subtitle \"not mass weighted\"\n");
687 /* Spectrum ? */
688 spec = nullptr;
689 if (opt2bSet("-os", NFILE, fnm) && (maxspec > 0))
691 snew(spectrum, maxspec);
692 spec = xvgropen(opt2fn("-os", NFILE, fnm),
693 "Vibrational spectrum based on harmonic approximation",
694 "\\f{12}w\\f{4} (cm\\S-1\\N)",
695 "Intensity [Gromacs units]",
696 oenv);
697 for (i = 0; (i < maxspec); i++)
699 spectrum[i] = 0;
703 /* Gromacs units are kJ/(mol*nm*nm*amu),
704 * where amu is the atomic mass unit.
706 * For the eigenfrequencies we want to convert this to spectroscopic absorption
707 * wavenumbers given in cm^(-1), which is the frequency divided by the speed of
708 * light. Do this by first converting to omega^2 (units 1/s), take the square
709 * root, and finally divide by the speed of light (nm/ps in gromacs).
711 factor_gmx_to_omega2 = 1.0E21/(AVOGADRO*AMU);
712 factor_omega_to_wavenumber = 1.0E-5/(2.0*M_PI*SPEED_OF_LIGHT);
714 for (i = begin; (i <= end); i++)
716 value = eigenvalues[i-begin];
717 if (value < 0)
719 value = 0;
721 omega = std::sqrt(value*factor_gmx_to_omega2);
722 nu = 1e-12*omega/(2*M_PI);
723 value = omega*factor_omega_to_wavenumber;
724 fprintf (out, "%6d %15g\n", i, value);
725 if (nullptr != spec)
727 wfac = eigenvalues[i-begin]/(width*std::sqrt(2*M_PI));
728 for (j = 0; (j < maxspec); j++)
730 spectrum[j] += wfac*std::exp(-gmx::square(j-value)/(2*gmx::square(width)));
733 if (nullptr != qc)
735 qcv = cv_corr(nu, T);
736 qu = u_corr(nu, T);
737 if (i > end-nharm)
739 qcv += BOLTZ*KILO;
740 qu += BOLTZ*T;
742 fprintf (qc, "%6d %15g %15g\n", i, qcv, qu);
743 qcvtot += qcv;
744 qutot += qu;
747 xvgrclose(out);
748 if (nullptr != spec)
750 for (j = 0; (j < maxspec); j++)
752 fprintf(spec, "%10g %10g\n", 1.0*j, spectrum[j]);
754 xvgrclose(spec);
756 if (nullptr != qc)
758 printf("Quantum corrections for harmonic degrees of freedom\n");
759 printf("Use appropriate -first and -last options to get reliable results.\n");
760 printf("There were %d constraints in the simulation\n", nharm);
761 printf("Total correction to cV = %g J/mol K\n", qcvtot);
762 printf("Total correction to H = %g kJ/mol\n", qutot);
763 xvgrclose(qc);
764 please_cite(stdout, "Caleman2011b");
766 /* Writing eigenvectors. Note that if mass scaling was used, the eigenvectors
767 * were scaled back from mass weighted cartesian to plain cartesian in the
768 * nma_full_hessian() or nma_sparse_hessian() routines. Mass scaled vectors
769 * will not be strictly orthogonal in plain cartesian scalar products.
771 const real *eigenvectorPtr;
772 if (full_hessian != nullptr)
774 eigenvectorPtr = eigenvectors;
776 else
778 /* The sparse matrix diagonalization store all eigenvectors up to end */
779 eigenvectorPtr = eigenvectors + (begin - 1)*atom_index.size();
781 write_eigenvectors(opt2fn("-v", NFILE, fnm), atom_index.size(), eigenvectorPtr, FALSE, begin, end,
782 eWXR_NO, nullptr, FALSE, top_x, bM, eigenvalues);
784 if (begin == 1)
786 analyzeThermochemistry(stdout, top, top_x, atom_index, eigenvalues,
787 T, P, sigma_r, scale_factor, linear_toler);
789 else
791 printf("Cannot compute entropy when -first = %d\n", begin);
794 return 0;