7f308a16f054ae9e753f0512b9e357fff7f86653
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_nmeig.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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 by the GROMACS development team.
7  * Copyright (c) 2018,2019,2020,2021, by the GROMACS development team, led by
8  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
9  * and including many others, as listed in the AUTHORS file in the
10  * top-level source directory and at http://www.gromacs.org.
11  *
12  * GROMACS is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU Lesser General Public License
14  * as published by the Free Software Foundation; either version 2.1
15  * of the License, or (at your option) any later version.
16  *
17  * GROMACS is distributed in the hope that it will be useful,
18  * but WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * Lesser General Public License for more details.
21  *
22  * You should have received a copy of the GNU Lesser General Public
23  * License along with GROMACS; if not, see
24  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
25  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
26  *
27  * If you want to redistribute modifications to GROMACS, please
28  * consider that scientific software is very special. Version
29  * control is crucial - bugs must be traceable. We will be happy to
30  * consider code for inclusion in the official distribution, but
31  * derived work must not be called official GROMACS. Details are found
32  * in the README & COPYING files - if they are missing, get the
33  * official version at http://www.gromacs.org.
34  *
35  * To help us fund GROMACS development, we humbly ask that you cite
36  * the research papers on the package. Check out http://www.gromacs.org.
37  */
38 #include "gmxpre.h"
39
40 #include <cassert>
41 #include <cmath>
42 #include <cstring>
43
44 #include <vector>
45
46 #include "gromacs/commandline/pargs.h"
47 #include "gromacs/fileio/mtxio.h"
48 #include "gromacs/fileio/tpxio.h"
49 #include "gromacs/fileio/xvgr.h"
50 #include "gromacs/gmxana/eigio.h"
51 #include "gromacs/gmxana/gmx_ana.h"
52 #include "gromacs/gmxana/gstat.h"
53 #include "gromacs/gmxana/princ.h"
54 #include "gromacs/linearalgebra/eigensolver.h"
55 #include "gromacs/linearalgebra/sparsematrix.h"
56 #include "gromacs/math/functions.h"
57 #include "gromacs/math/units.h"
58 #include "gromacs/math/vec.h"
59 #include "gromacs/math/vecdump.h"
60 #include "gromacs/topology/ifunc.h"
61 #include "gromacs/topology/mtop_util.h"
62 #include "gromacs/topology/topology.h"
63 #include "gromacs/utility/arraysize.h"
64 #include "gromacs/utility/fatalerror.h"
65 #include "gromacs/utility/futil.h"
66 #include "gromacs/utility/gmxassert.h"
67 #include "gromacs/utility/pleasecite.h"
68 #include "gromacs/utility/smalloc.h"
69
70 #include "thermochemistry.h"
71
72 static double cv_corr(double nu, double T)
73 {
74     double x  = gmx::c_planck * nu / (gmx::c_boltz * T);
75     double ex = std::exp(x);
76
77     if (nu <= 0)
78     {
79         return gmx::c_boltz * gmx::c_kilo;
80     }
81     else
82     {
83         return gmx::c_boltz * gmx::c_kilo * (ex * gmx::square(x) / gmx::square(ex - 1) - 1);
84     }
85 }
86
87 static double u_corr(double nu, double T)
88 {
89     double x  = gmx::c_planck * nu / (gmx::c_boltz * T);
90     double ex = std::exp(x);
91
92     if (nu <= 0)
93     {
94         return gmx::c_boltz * T;
95     }
96     else
97     {
98         return gmx::c_boltz * T * (0.5 * x - 1 + x / (ex - 1));
99     }
100 }
101
102 static size_t get_nharm_mt(const gmx_moltype_t* mt)
103 {
104     static int harm_func[] = { F_BONDS };
105     int        i, ft;
106     size_t     nh = 0;
107
108     for (i = 0; (i < asize(harm_func)); i++)
109     {
110         ft = harm_func[i];
111         nh += mt->ilist[ft].size() / (interaction_function[ft].nratoms + 1);
112     }
113     return nh;
114 }
115
116 static int get_nharm(const gmx_mtop_t* mtop)
117 {
118     int nh = 0;
119
120     for (const gmx_molblock_t& molb : mtop->molblock)
121     {
122         nh += molb.nmol * get_nharm_mt(&(mtop->moltype[molb.type]));
123     }
124     return nh;
125 }
126
127 static void nma_full_hessian(real*                    hess,
128                              int                      ndim,
129                              gmx_bool                 bM,
130                              const t_topology*        top,
131                              gmx::ArrayRef<const int> atom_index,
132                              int                      begin,
133                              int                      end,
134                              real*                    eigenvalues,
135                              real*                    eigenvectors)
136 {
137     real mass_fac;
138
139     /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
140
141     if (bM)
142     {
143         for (int i = 0; (i < atom_index.ssize()); i++)
144         {
145             size_t ai = atom_index[i];
146             for (size_t j = 0; (j < DIM); j++)
147             {
148                 for (int k = 0; (k < atom_index.ssize()); k++)
149                 {
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++)
153                     {
154                         hess[(i * DIM + j) * ndim + k * DIM + l] *= mass_fac;
155                     }
156                 }
157             }
158         }
159     }
160
161     /* call diagonalization routine. */
162
163     fprintf(stderr, "\nDiagonalizing to find vectors %d through %d...\n", begin, end);
164     fflush(stderr);
165
166     eigensolver(hess, ndim, begin - 1, end - 1, eigenvalues, eigenvectors);
167
168     /* And scale the output eigenvectors */
169     if (bM && eigenvectors != nullptr)
170     {
171         for (int i = 0; i < (end - begin + 1); i++)
172         {
173             for (gmx::index j = 0; j < atom_index.ssize(); j++)
174             {
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++)
178                 {
179                     eigenvectors[i * ndim + j * DIM + k] *= mass_fac;
180                 }
181             }
182         }
183     }
184 }
185
186
187 static void nma_sparse_hessian(gmx_sparsematrix_t*      sparse_hessian,
188                                gmx_bool                 bM,
189                                const t_topology*        top,
190                                gmx::ArrayRef<const int> atom_index,
191                                int                      neig,
192                                real*                    eigenvalues,
193                                real*                    eigenvectors)
194 {
195     int    i, k;
196     int    row, col;
197     real   mass_fac;
198     int    katom;
199     size_t ndim;
200
201     ndim = DIM * atom_index.size();
202
203     /* Cannot check symmetry since we only store half matrix */
204     /* divide elements hess[i][j] by sqrt(mas[i])*sqrt(mas[j]) when required */
205
206     GMX_RELEASE_ASSERT(sparse_hessian != nullptr,
207                        "NULL matrix pointer provided to nma_sparse_hessian");
208
209     if (bM)
210     {
211         for (int iatom = 0; (iatom < atom_index.ssize()); iatom++)
212         {
213             size_t ai = atom_index[iatom];
214             for (size_t j = 0; (j < DIM); j++)
215             {
216                 row = DIM * iatom + j;
217                 for (k = 0; k < sparse_hessian->ndata[row]; k++)
218                 {
219                     col       = sparse_hessian->data[row][k].col;
220                     katom     = col / 3;
221                     size_t ak = atom_index[katom];
222                     mass_fac  = gmx::invsqrt(top->atoms.atom[ai].m * top->atoms.atom[ak].m);
223                     sparse_hessian->data[row][k].value *= mass_fac;
224                 }
225             }
226         }
227     }
228     fprintf(stderr, "\nDiagonalizing to find eigenvectors 1 through %d...\n", neig);
229     fflush(stderr);
230
231     sparse_eigensolver(sparse_hessian, neig, eigenvalues, eigenvectors, 10000000);
232
233     /* Scale output eigenvectors */
234     if (bM && eigenvectors != nullptr)
235     {
236         for (i = 0; i < neig; i++)
237         {
238             for (gmx::index j = 0; j < atom_index.ssize(); j++)
239             {
240                 size_t aj = atom_index[j];
241                 mass_fac  = gmx::invsqrt(top->atoms.atom[aj].m);
242                 for (k = 0; (k < DIM); k++)
243                 {
244                     eigenvectors[i * ndim + j * DIM + k] *= mass_fac;
245                 }
246             }
247         }
248     }
249 }
250
251
252 /* Returns a pointer for eigenvector storage */
253 static real* allocateEigenvectors(int nrow, int first, int last, bool ignoreBegin)
254 {
255     int numVector;
256     if (ignoreBegin)
257     {
258         numVector = last;
259     }
260     else
261     {
262         numVector = last - first + 1;
263     }
264     size_t vectorsSize = static_cast<size_t>(nrow) * static_cast<size_t>(numVector);
265     /* We can't have more than INT_MAX elements.
266      * Relaxing this restriction probably requires changing lots of loop
267      * variable types in the linear algebra code.
268      */
269     if (vectorsSize > INT_MAX)
270     {
271         gmx_fatal(FARGS,
272                   "You asked to store %d eigenvectors of size %d, which requires more than the "
273                   "supported %d elements; %sdecrease -last",
274                   numVector,
275                   nrow,
276                   INT_MAX,
277                   ignoreBegin ? "" : "increase -first and/or ");
278     }
279
280     real* eigenvectors;
281     snew(eigenvectors, vectorsSize);
282
283     return eigenvectors;
284 }
285
286 /*! \brief Compute heat capacity due to translational motion
287  */
288 static double calcTranslationalHeatCapacity()
289 {
290     return gmx::c_universalGasConstant * 1.5;
291 }
292
293 /*! \brief Compute internal energy due to translational motion
294  */
295 static double calcTranslationalInternalEnergy(double T)
296 {
297     return gmx::c_boltz * T * 1.5;
298 }
299
300 /*! \brief Compute heat capacity due to rotational motion
301  *
302  * \param[in] linear Should be TRUE if this is a linear molecule
303  * \param[in] T      Temperature
304  * \return The heat capacity at constant volume
305  */
306 static double calcRotationalInternalEnergy(gmx_bool linear, double T)
307 {
308     if (linear)
309     {
310         return gmx::c_boltz * T;
311     }
312     else
313     {
314         return gmx::c_boltz * T * 1.5;
315     }
316 }
317
318 /*! \brief Compute heat capacity due to rotational motion
319  *
320  * \param[in] linear Should be TRUE if this is a linear molecule
321  * \return The heat capacity at constant volume
322  */
323 static double calcRotationalHeatCapacity(gmx_bool linear)
324 {
325     if (linear)
326     {
327         return gmx::c_universalGasConstant;
328     }
329     else
330     {
331         return gmx::c_universalGasConstant * 1.5;
332     }
333 }
334
335 static void analyzeThermochemistry(FILE*                    fp,
336                                    const t_topology&        top,
337                                    rvec                     top_x[],
338                                    gmx::ArrayRef<const int> atom_index,
339                                    real                     eigfreq[],
340                                    real                     T,
341                                    real                     P,
342                                    int                      sigma_r,
343                                    real                     scale_factor,
344                                    real                     linear_toler)
345 {
346     std::vector<int> index(atom_index.begin(), atom_index.end());
347
348     rvec   xcm;
349     double tmass  = calc_xcm(top_x, index.size(), index.data(), top.atoms.atom, xcm, FALSE);
350     double Strans = calcTranslationalEntropy(tmass, T, P);
351     std::vector<gmx::RVec> x_com;
352     x_com.resize(top.atoms.nr);
353     for (int i = 0; i < top.atoms.nr; i++)
354     {
355         copy_rvec(top_x[i], x_com[i]);
356     }
357     (void)sub_xcm(as_rvec_array(x_com.data()), index.size(), index.data(), top.atoms.atom, xcm, FALSE);
358
359     rvec   inertia;
360     matrix trans;
361     principal_comp(index.size(), index.data(), top.atoms.atom, as_rvec_array(x_com.data()), trans, inertia);
362     bool linear = (inertia[XX] / inertia[YY] < linear_toler && inertia[XX] / inertia[ZZ] < linear_toler);
363     // (kJ/mol ps)^2/(Dalton nm^2 kJ/mol K) =
364     // c_kilo kg m^2 ps^2/(s^2 mol g/mol nm^2 K) =
365     // c_kilo^2 10^18 / 10^24 K = 1/K
366     double rot_const = gmx::square(gmx::c_planck) / (8 * gmx::square(M_PI) * gmx::c_boltz);
367     // Rotational temperature (1/K)
368     rvec theta = { 0, 0, 0 };
369     if (linear)
370     {
371         // For linear molecules the first element of the inertia
372         // vector is zero.
373         theta[0] = rot_const / inertia[1];
374     }
375     else
376     {
377         for (int m = 0; m < DIM; m++)
378         {
379             theta[m] = rot_const / inertia[m];
380         }
381     }
382     if (debug)
383     {
384         pr_rvec(debug, 0, "inertia", inertia, DIM, TRUE);
385         pr_rvec(debug, 0, "theta", theta, DIM, TRUE);
386         pr_rvecs(debug, 0, "trans", trans, DIM);
387         fprintf(debug, "linear molecule = %s\n", linear ? "true" : "no");
388     }
389     size_t nFreq = index.size() * DIM;
390     auto   eFreq = gmx::arrayRefFromArray(eigfreq, nFreq);
391     double Svib  = calcQuasiHarmonicEntropy(eFreq, T, linear, scale_factor);
392
393     double Srot = calcRotationalEntropy(T, top.atoms.nr, linear, theta, sigma_r);
394     fprintf(fp, "Translational entropy %g J/mol K\n", Strans);
395     fprintf(fp, "Rotational entropy    %g J/mol K\n", Srot);
396     fprintf(fp, "Vibrational entropy   %g J/mol K\n", Svib);
397     fprintf(fp, "Total entropy         %g J/mol K\n", Svib + Strans + Srot);
398     double HeatCapacity = (calcTranslationalHeatCapacity() + calcRotationalHeatCapacity(linear)
399                            + calcVibrationalHeatCapacity(eFreq, T, linear, scale_factor));
400     fprintf(fp, "Heat capacity         %g J/mol K\n", HeatCapacity);
401     double Evib = (calcTranslationalInternalEnergy(T) + calcRotationalInternalEnergy(linear, T)
402                    + calcVibrationalInternalEnergy(eFreq, T, linear, scale_factor));
403     fprintf(fp, "Internal energy       %g kJ/mol\n", Evib);
404     double ZPE = calcZeroPointEnergy(eFreq, scale_factor);
405     fprintf(fp, "Zero-point energy     %g kJ/mol\n", ZPE);
406 }
407
408
409 int gmx_nmeig(int argc, char* argv[])
410 {
411     const char* desc[] = {
412         "[THISMODULE] calculates the eigenvectors/values of a (Hessian) matrix,",
413         "which can be calculated with [gmx-mdrun].",
414         "The eigenvectors are written to a trajectory file ([TT]-v[tt]).",
415         "The structure is written first with t=0. The eigenvectors",
416         "are written as frames with the eigenvector number and eigenvalue",
417         "written as step number and timestamp, respectively.",
418         "The eigenvectors can be analyzed with [gmx-anaeig].",
419         "An ensemble of structures can be generated from the eigenvectors with",
420         "[gmx-nmens]. When mass weighting is used, the generated eigenvectors",
421         "will be scaled back to plain Cartesian coordinates before generating the",
422         "output. In this case, they will no longer be exactly orthogonal in the",
423         "standard Cartesian norm, but in the mass-weighted norm they would be.[PAR]",
424         "This program can be optionally used to compute quantum corrections to heat capacity",
425         "and enthalpy by providing an extra file argument [TT]-qcorr[tt]. See the GROMACS",
426         "manual, Chapter 1, for details. The result includes subtracting a harmonic",
427         "degree of freedom at the given temperature.",
428         "The total correction is printed on the terminal screen.",
429         "The recommended way of getting the corrections out is:[PAR]",
430         "[TT]gmx nmeig -s topol.tpr -f nm.mtx -first 7 -last 10000 -T 300 -qc [-constr][tt][PAR]",
431         "The [TT]-constr[tt] option should be used when bond constraints were used during the",
432         "simulation [BB]for all the covalent bonds[bb]. If this is not the case, ",
433         "you need to analyze the [TT]quant_corr.xvg[tt] file yourself.[PAR]",
434         "To make things more flexible, the program can also take virtual sites into account",
435         "when computing quantum corrections. When selecting [TT]-constr[tt] and",
436         "[TT]-qc[tt], the [TT]-begin[tt] and [TT]-end[tt] options will be set automatically as ",
437         "well.[PAR]",
438         "Based on a harmonic analysis of the normal mode frequencies,",
439         "thermochemical properties S0 (Standard Entropy),",
440         "Cv (Heat capacity at constant volume), Zero-point energy and the internal energy are",
441         "computed, much in the same manner as popular quantum chemistry",
442         "programs."
443     };
444
445     static gmx_bool bM = TRUE, bCons = FALSE;
446     static int      begin = 1, end = 50, maxspec = 4000, sigma_r = 1;
447     static real     T = 298.15, width = 1, P = 1, scale_factor = 1;
448     static real     linear_toler = 1e-5;
449
450     t_pargs pa[] = {
451         { "-m",
452           FALSE,
453           etBOOL,
454           { &bM },
455           "Divide elements of Hessian by product of sqrt(mass) of involved "
456           "atoms prior to diagonalization. This should be used for 'Normal Modes' "
457           "analysis" },
458         { "-first", FALSE, etINT, { &begin }, "First eigenvector to write away" },
459         { "-last",
460           FALSE,
461           etINT,
462           { &end },
463           "Last eigenvector to write away. -1 is use all dimensions." },
464         { "-maxspec",
465           FALSE,
466           etINT,
467           { &maxspec },
468           "Highest frequency (1/cm) to consider in the spectrum" },
469         { "-T",
470           FALSE,
471           etREAL,
472           { &T },
473           "Temperature for computing entropy, quantum heat capacity and enthalpy when "
474           "using normal mode calculations to correct classical simulations" },
475         { "-P", FALSE, etREAL, { &P }, "Pressure (bar) when computing entropy" },
476         { "-sigma",
477           FALSE,
478           etINT,
479           { &sigma_r },
480           "Number of symmetric copies used when computing entropy. E.g. for water the "
481           "number is 2, for NH3 it is 3 and for methane it is 12." },
482         { "-scale",
483           FALSE,
484           etREAL,
485           { &scale_factor },
486           "Factor to scale frequencies before computing thermochemistry values" },
487         { "-linear_toler",
488           FALSE,
489           etREAL,
490           { &linear_toler },
491           "Tolerance for determining whether a compound is linear as determined from the "
492           "ration of the moments inertion Ix/Iy and Ix/Iz." },
493         { "-constr",
494           FALSE,
495           etBOOL,
496           { &bCons },
497           "If constraints were used in the simulation but not in the normal mode analysis "
498           "you will need to set this for computing the quantum corrections." },
499         { "-width",
500           FALSE,
501           etREAL,
502           { &width },
503           "Width (sigma) of the gaussian peaks (1/cm) when generating a spectrum" }
504     };
505     FILE *              out, *qc, *spec;
506     t_topology          top;
507     gmx_mtop_t          mtop;
508     rvec*               top_x;
509     matrix              box;
510     real*               eigenvalues;
511     real*               eigenvectors;
512     real                qcvtot, qutot, qcv, qu;
513     int                 i, j, k;
514     real                value, omega, nu;
515     real                factor_gmx_to_omega2;
516     real                factor_omega_to_wavenumber;
517     real*               spectrum = nullptr;
518     real                wfac;
519     gmx_output_env_t*   oenv;
520     const char*         qcleg[]        = { "Heat Capacity cV (J/mol K)", "Enthalpy H (kJ/mol)" };
521     real*               full_hessian   = nullptr;
522     gmx_sparsematrix_t* sparse_hessian = nullptr;
523
524     t_filenm fnm[] = {
525         { efMTX, "-f", "hessian", ffREAD },     { efTPR, nullptr, nullptr, ffREAD },
526         { efXVG, "-of", "eigenfreq", ffWRITE }, { efXVG, "-ol", "eigenval", ffWRITE },
527         { efXVG, "-os", "spectrum", ffOPTWR },  { efXVG, "-qc", "quant_corr", ffOPTWR },
528         { efTRN, "-v", "eigenvec", ffWRITE }
529     };
530 #define NFILE asize(fnm)
531
532     if (!parse_common_args(&argc, argv, 0, NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
533     {
534         return 0;
535     }
536
537     /* Read tpr file for volume and number of harmonic terms */
538     TpxFileHeader tpx = readTpxHeader(ftp2fn(efTPR, NFILE, fnm), true);
539     snew(top_x, tpx.natoms);
540
541     int natoms_tpx;
542     read_tpx(ftp2fn(efTPR, NFILE, fnm), nullptr, box, &natoms_tpx, top_x, nullptr, &mtop);
543     int nharm = 0;
544     if (bCons)
545     {
546         nharm = get_nharm(&mtop);
547     }
548     std::vector<int> atom_index = get_atom_index(&mtop);
549
550     top = gmx_mtop_t_to_t_topology(&mtop, true);
551
552     bM       = TRUE;
553     int ndim = DIM * atom_index.size();
554
555     if (opt2bSet("-qc", NFILE, fnm))
556     {
557         begin = 7;
558         end   = ndim;
559     }
560     if (begin < 1)
561     {
562         begin = 1;
563     }
564     if (end == -1 || end > ndim)
565     {
566         end = ndim;
567     }
568     printf("Using begin = %d and end = %d\n", begin, end);
569
570     /*open Hessian matrix */
571     int nrow, ncol;
572     gmx_mtxio_read(ftp2fn(efMTX, NFILE, fnm), &nrow, &ncol, &full_hessian, &sparse_hessian);
573
574     /* If the Hessian is in sparse format we can calculate max (ndim-1) eigenvectors,
575      * If this is not valid we convert to full matrix storage,
576      * but warn the user that we might run out of memory...
577      */
578     if ((sparse_hessian != nullptr) && (end == ndim))
579     {
580         fprintf(stderr, "Cannot use sparse Hessian to calculate all eigenvectors.\n");
581
582         fprintf(stderr,
583                 "Will try to allocate memory and convert to full matrix representation...\n");
584
585         size_t hessianSize = static_cast<size_t>(nrow) * static_cast<size_t>(ncol);
586         /* Allowing  Hessians larger than INT_MAX probably only makes sense
587          * with (OpenMP) parallel diagonalization routines, since with a single
588          * thread it will takes months.
589          */
590         if (hessianSize > INT_MAX)
591         {
592             gmx_fatal(FARGS,
593                       "Hessian size is %d x %d, which is larger than the maximum allowed %d "
594                       "elements.",
595                       nrow,
596                       ncol,
597                       INT_MAX);
598         }
599         snew(full_hessian, hessianSize);
600         for (i = 0; i < nrow * ncol; i++)
601         {
602             full_hessian[i] = 0;
603         }
604
605         for (i = 0; i < sparse_hessian->nrow; i++)
606         {
607             for (j = 0; j < sparse_hessian->ndata[i]; j++)
608             {
609                 k                          = sparse_hessian->data[i][j].col;
610                 value                      = sparse_hessian->data[i][j].value;
611                 full_hessian[i * ndim + k] = value;
612                 full_hessian[k * ndim + i] = value;
613             }
614         }
615         gmx_sparsematrix_destroy(sparse_hessian);
616         sparse_hessian = nullptr;
617         fprintf(stderr, "Converted sparse to full matrix storage.\n");
618     }
619
620     snew(eigenvalues, nrow);
621
622     if (full_hessian != nullptr)
623     {
624         /* Using full matrix storage */
625         eigenvectors = allocateEigenvectors(nrow, begin, end, false);
626
627         nma_full_hessian(full_hessian, nrow, bM, &top, atom_index, begin, end, eigenvalues, eigenvectors);
628     }
629     else
630     {
631         assert(sparse_hessian);
632         /* Sparse memory storage, allocate memory for eigenvectors */
633         eigenvectors = allocateEigenvectors(nrow, begin, end, true);
634
635         nma_sparse_hessian(sparse_hessian, bM, &top, atom_index, end, eigenvalues, eigenvectors);
636     }
637
638     /* check the output, first 6 eigenvalues should be reasonably small */
639     gmx_bool bSuck = FALSE;
640     for (i = begin - 1; (i < 6); i++)
641     {
642         if (std::abs(eigenvalues[i]) > 1.0e-3)
643         {
644             bSuck = TRUE;
645         }
646     }
647     if (bSuck)
648     {
649         fprintf(stderr, "\nOne of the lowest 6 eigenvalues has a non-zero value.\n");
650         fprintf(stderr, "This could mean that the reference structure was not\n");
651         fprintf(stderr, "properly energy minimized.\n");
652     }
653
654     /* now write the output */
655     fprintf(stderr, "Writing eigenvalues...\n");
656     out = xvgropen(opt2fn("-ol", NFILE, fnm),
657                    "Eigenvalues",
658                    "Eigenvalue index",
659                    "Eigenvalue [Gromacs units]",
660                    oenv);
661     if (output_env_get_print_xvgr_codes(oenv))
662     {
663         if (bM)
664         {
665             fprintf(out, "@ subtitle \"mass weighted\"\n");
666         }
667         else
668         {
669             fprintf(out, "@ subtitle \"not mass weighted\"\n");
670         }
671     }
672
673     for (i = 0; i <= (end - begin); i++)
674     {
675         fprintf(out, "%6d %15g\n", begin + i, eigenvalues[i]);
676     }
677     xvgrclose(out);
678
679
680     if (opt2bSet("-qc", NFILE, fnm))
681     {
682         qc = xvgropen(opt2fn("-qc", NFILE, fnm), "Quantum Corrections", "Eigenvector index", "", oenv);
683         xvgr_legend(qc, asize(qcleg), qcleg, oenv);
684         qcvtot = qutot = 0;
685     }
686     else
687     {
688         qc = nullptr;
689     }
690     printf("Writing eigenfrequencies - negative eigenvalues will be set to zero.\n");
691
692     out = xvgropen(opt2fn("-of", NFILE, fnm),
693                    "Eigenfrequencies",
694                    "Eigenvector index",
695                    "Wavenumber [cm\\S-1\\N]",
696                    oenv);
697     if (output_env_get_print_xvgr_codes(oenv))
698     {
699         if (bM)
700         {
701             fprintf(out, "@ subtitle \"mass weighted\"\n");
702         }
703         else
704         {
705             fprintf(out, "@ subtitle \"not mass weighted\"\n");
706         }
707     }
708     /* Spectrum ? */
709     spec = nullptr;
710     if (opt2bSet("-os", NFILE, fnm) && (maxspec > 0))
711     {
712         snew(spectrum, maxspec);
713         spec = xvgropen(opt2fn("-os", NFILE, fnm),
714                         "Vibrational spectrum based on harmonic approximation",
715                         "\\f{12}w\\f{4} (cm\\S-1\\N)",
716                         "Intensity [Gromacs units]",
717                         oenv);
718         for (i = 0; (i < maxspec); i++)
719         {
720             spectrum[i] = 0;
721         }
722     }
723
724     /* Gromacs units are kJ/(mol*nm*nm*amu),
725      * where amu is the atomic mass unit.
726      *
727      * For the eigenfrequencies we want to convert this to spectroscopic absorption
728      * wavenumbers given in cm^(-1), which is the frequency divided by the speed of
729      * light. Do this by first converting to omega^2 (units 1/s), take the square
730      * root, and finally divide by the speed of light (nm/ps in gromacs).
731      */
732     factor_gmx_to_omega2       = 1.0E21 / (gmx::c_avogadro * gmx::c_amu);
733     factor_omega_to_wavenumber = 1.0E-5 / (2.0 * M_PI * gmx::c_speedOfLight);
734
735     value = 0;
736     for (i = begin; (i <= end); i++)
737     {
738         value = eigenvalues[i - begin];
739         if (value < 0)
740         {
741             value = 0;
742         }
743         omega = std::sqrt(value * factor_gmx_to_omega2);
744         nu    = 1e-12 * omega / (2 * M_PI);
745         value = omega * factor_omega_to_wavenumber;
746         fprintf(out, "%6d %15g\n", i, value);
747         if (nullptr != spec)
748         {
749             wfac = eigenvalues[i - begin] / (width * std::sqrt(2 * M_PI));
750             for (j = 0; (j < maxspec); j++)
751             {
752                 spectrum[j] += wfac * std::exp(-gmx::square(j - value) / (2 * gmx::square(width)));
753             }
754         }
755         if (nullptr != qc)
756         {
757             qcv = cv_corr(nu, T);
758             qu  = u_corr(nu, T);
759             if (i > end - nharm)
760             {
761                 qcv += gmx::c_boltz * gmx::c_kilo;
762                 qu += gmx::c_boltz * T;
763             }
764             fprintf(qc, "%6d %15g %15g\n", i, qcv, qu);
765             qcvtot += qcv;
766             qutot += qu;
767         }
768     }
769     xvgrclose(out);
770     if (value >= maxspec)
771     {
772         printf("WARNING: high frequencies encountered (%g cm^-1).\n", value);
773         printf("Your calculations may be incorrect due to e.g. improper minimization of\n");
774         printf("your starting structure or due to issues in your topology.\n");
775     }
776     if (nullptr != spec)
777     {
778         for (j = 0; (j < maxspec); j++)
779         {
780             fprintf(spec, "%10g  %10g\n", 1.0 * j, spectrum[j]);
781         }
782         xvgrclose(spec);
783     }
784     if (nullptr != qc)
785     {
786         printf("Quantum corrections for harmonic degrees of freedom\n");
787         printf("Use appropriate -first and -last options to get reliable results.\n");
788         printf("There were %d constraints in the simulation\n", nharm);
789         printf("Total correction to cV = %g J/mol K\n", qcvtot);
790         printf("Total correction to  H = %g kJ/mol\n", qutot);
791         xvgrclose(qc);
792         please_cite(stdout, "Caleman2011b");
793     }
794     /* Writing eigenvectors. Note that if mass scaling was used, the eigenvectors
795      * were scaled back from mass weighted cartesian to plain cartesian in the
796      * nma_full_hessian() or nma_sparse_hessian() routines. Mass scaled vectors
797      * will not be strictly orthogonal in plain cartesian scalar products.
798      */
799     const real* eigenvectorPtr;
800     if (full_hessian != nullptr)
801     {
802         eigenvectorPtr = eigenvectors;
803     }
804     else
805     {
806         /* The sparse matrix diagonalization store all eigenvectors up to end */
807         eigenvectorPtr = eigenvectors + (begin - 1) * atom_index.size();
808     }
809     write_eigenvectors(opt2fn("-v", NFILE, fnm),
810                        atom_index.size(),
811                        eigenvectorPtr,
812                        FALSE,
813                        begin,
814                        end,
815                        eWXR_NO,
816                        nullptr,
817                        FALSE,
818                        top_x,
819                        bM,
820                        eigenvalues);
821
822     if (begin == 1)
823     {
824         analyzeThermochemistry(
825                 stdout, top, top_x, atom_index, eigenvalues, T, P, sigma_r, scale_factor, linear_toler);
826         please_cite(stdout, "Spoel2018a");
827     }
828     else
829     {
830         printf("Cannot compute entropy when -first = %d\n", begin);
831     }
832
833     return 0;
834 }