clang-tidy: readability-non-const-parameter (2/2)
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_rmsf.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,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.
10  *
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.
15  *
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.
20  *
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.
25  *
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.
33  *
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.
36  */
37 #include "gmxpre.h"
38
39 #include <cassert>
40 #include <cmath>
41 #include <cstring>
42
43 #include "gromacs/commandline/pargs.h"
44 #include "gromacs/commandline/viewit.h"
45 #include "gromacs/fileio/confio.h"
46 #include "gromacs/fileio/pdbio.h"
47 #include "gromacs/fileio/trxio.h"
48 #include "gromacs/fileio/xvgr.h"
49 #include "gromacs/gmxana/gmx_ana.h"
50 #include "gromacs/gmxana/princ.h"
51 #include "gromacs/linearalgebra/eigensolver.h"
52 #include "gromacs/math/do_fit.h"
53 #include "gromacs/math/functions.h"
54 #include "gromacs/math/utilities.h"
55 #include "gromacs/math/vec.h"
56 #include "gromacs/pbcutil/rmpbc.h"
57 #include "gromacs/topology/index.h"
58 #include "gromacs/topology/topology.h"
59 #include "gromacs/utility/arraysize.h"
60 #include "gromacs/utility/cstringutil.h"
61 #include "gromacs/utility/futil.h"
62 #include "gromacs/utility/smalloc.h"
63
64 static real find_pdb_bfac(const t_atoms *atoms, t_resinfo *ri, char *atomnm)
65 {
66     char rresnm[8];
67     int  i;
68
69     std::strcpy(rresnm, *ri->name);
70     rresnm[3] = '\0';
71     for (i = 0; (i < atoms->nr); i++)
72     {
73         if ((ri->nr == atoms->resinfo[atoms->atom[i].resind].nr) &&
74             (ri->ic == atoms->resinfo[atoms->atom[i].resind].ic) &&
75             (std::strcmp(*atoms->resinfo[atoms->atom[i].resind].name, rresnm) == 0) &&
76             (std::strstr(*atoms->atomname[i], atomnm) != nullptr))
77         {
78             break;
79         }
80     }
81     if (i == atoms->nr)
82     {
83         fprintf(stderr, "\rCan not find %s%d-%s in pdbfile\n",
84                 rresnm, ri->nr, atomnm);
85         fflush(stderr);
86         return 0.0;
87     }
88
89     return atoms->pdbinfo[i].bfac;
90 }
91
92 static void correlate_aniso(const char *fn, t_atoms *ref, t_atoms *calc,
93                             const gmx_output_env_t *oenv)
94 {
95     FILE *fp;
96     int   i, j;
97
98     fp = xvgropen(fn, "Correlation between X-Ray and Computed Uij", "X-Ray",
99                   "Computed", oenv);
100     for (i = 0; (i < ref->nr); i++)
101     {
102         if (ref->pdbinfo[i].bAnisotropic)
103         {
104             for (j = U11; (j <= U23); j++)
105             {
106                 fprintf(fp, "%10d  %10d\n", ref->pdbinfo[i].uij[j], calc->pdbinfo[i].uij[j]);
107             }
108         }
109     }
110     xvgrclose(fp);
111 }
112
113 static void average_residues(double f[], double **U, int uind,
114                              int isize, const int index[], const real w_rls[],
115                              const t_atoms *atoms)
116 {
117     int    i, j, start;
118     double av, m;
119
120     start = 0;
121     av    = 0;
122     m     = 0;
123     if (!f)
124     {
125         assert(U);
126     }
127     for (i = 0; i < isize; i++)
128     {
129         av += w_rls[index[i]]*(f != nullptr ? f[i] : U[i][uind]);
130         m  += w_rls[index[i]];
131         if (i+1 == isize ||
132             atoms->atom[index[i]].resind != atoms->atom[index[i+1]].resind)
133         {
134             av /= m;
135             if (f != nullptr)
136             {
137                 for (j = start; j <= i; j++)
138                 {
139                     f[i] = av;
140                 }
141             }
142             else
143             {
144                 for (j = start; j <= i; j++)
145                 {
146                     U[j][uind] = av;
147                 }
148             }
149             start = i+1;
150             av    = 0;
151             m     = 0;
152         }
153     }
154 }
155
156 static void print_dir(FILE *fp, real *Uaver)
157 {
158     real eigvec[DIM*DIM];
159     real tmp[DIM*DIM];
160     rvec eigval;
161     int  d, m;
162
163     fprintf(fp, "MSF     X         Y         Z\n");
164     for (d = 0; d < DIM; d++)
165     {
166         fprintf(fp, " %c ", 'X'+d-XX);
167         for (m = 0; m < DIM; m++)
168         {
169             fprintf(fp, " %9.2e", Uaver[3*m+d]);
170         }
171         fprintf(fp, "%s\n", m == DIM ? " (nm^2)" : "");
172     }
173
174     for (m = 0; m < DIM*DIM; m++)
175     {
176         tmp[m] = Uaver[m];
177     }
178
179
180     eigensolver(tmp, DIM, 0, DIM, eigval, eigvec);
181
182     fprintf(fp, "\n             Eigenvectors\n\n");
183     fprintf(fp, "Eigv  %-8.2e %-8.2e %-8.2e (nm^2)\n\n",
184             eigval[2], eigval[1], eigval[0]);
185     for (d = 0; d < DIM; d++)
186     {
187         fprintf(fp, "  %c   ", 'X'+d-XX);
188         for (m = DIM-1; m >= 0; m--)
189         {
190             fprintf(fp, "%7.4f  ", eigvec[3*m+d]);
191         }
192         fprintf(fp, "\n");
193     }
194 }
195
196 int gmx_rmsf(int argc, char *argv[])
197 {
198     const char       *desc[] = {
199         "[THISMODULE] computes the root mean square fluctuation (RMSF, i.e. standard ",
200         "deviation) of atomic positions in the trajectory (supplied with [TT]-f[tt])",
201         "after (optionally) fitting to a reference frame (supplied with [TT]-s[tt]).[PAR]",
202         "With option [TT]-oq[tt] the RMSF values are converted to B-factor",
203         "values, which are written to a [REF].pdb[ref] file. By default, the coordinates",
204         "in this output file are taken from the structure file provided with [TT]-s[tt],"
205         "although you can also use coordinates read from a different [REF].pdb[ref] file"
206         "provided with [TT]-q[tt]. There is very little error checking, so in this case"
207         "it is your responsibility to make sure all atoms in the structure file"
208         "and [REF].pdb[ref] file correspond exactly to each other.[PAR]",
209         "Option [TT]-ox[tt] writes the B-factors to a file with the average",
210         "coordinates in the trajectory.[PAR]",
211         "With the option [TT]-od[tt] the root mean square deviation with",
212         "respect to the reference structure is calculated.[PAR]",
213         "With the option [TT]-aniso[tt], [THISMODULE] will compute anisotropic",
214         "temperature factors and then it will also output average coordinates",
215         "and a [REF].pdb[ref] file with ANISOU records (corresonding to the [TT]-oq[tt]",
216         "or [TT]-ox[tt] option). Please note that the U values",
217         "are orientation-dependent, so before comparison with experimental data",
218         "you should verify that you fit to the experimental coordinates.[PAR]",
219         "When a [REF].pdb[ref] input file is passed to the program and the [TT]-aniso[tt]",
220         "flag is set",
221         "a correlation plot of the Uij will be created, if any anisotropic",
222         "temperature factors are present in the [REF].pdb[ref] file.[PAR]",
223         "With option [TT]-dir[tt] the average MSF (3x3) matrix is diagonalized.",
224         "This shows the directions in which the atoms fluctuate the most and",
225         "the least."
226     };
227     static gmx_bool   bRes    = FALSE, bAniso = FALSE, bFit = TRUE;
228     t_pargs           pargs[] = {
229         { "-res", FALSE, etBOOL, {&bRes},
230           "Calculate averages for each residue" },
231         { "-aniso", FALSE, etBOOL, {&bAniso},
232           "Compute anisotropic termperature factors" },
233         { "-fit", FALSE, etBOOL, {&bFit},
234           "Do a least squares superposition before computing RMSF. Without this you must make sure that the reference structure and the trajectory match." }
235     };
236     int               natom;
237     int               i, m, teller = 0;
238     real              t, *w_rls;
239
240     t_topology        top;
241     int               ePBC;
242     t_atoms          *pdbatoms, *refatoms;
243
244     matrix            box, pdbbox;
245     rvec             *x, *pdbx, *xref;
246     t_trxstatus      *status;
247     const char       *label;
248
249     FILE             *fp;         /* the graphics file */
250     const char       *devfn, *dirfn;
251     int               resind;
252
253     gmx_bool          bReadPDB;
254     int              *index;
255     int               isize;
256     char             *grpnames;
257
258     real              bfac, pdb_bfac, *Uaver;
259     double          **U, *xav;
260     int               aid;
261     rvec             *rmsd_x = nullptr;
262     double           *rmsf, invcount, totmass;
263     int               d;
264     real              count = 0;
265     rvec              xcm;
266     gmx_rmpbc_t       gpbc = nullptr;
267
268     gmx_output_env_t *oenv;
269
270     const char       *leg[2] = { "MD", "X-Ray" };
271
272     t_filenm          fnm[] = {
273         { efTRX, "-f",  nullptr,     ffREAD  },
274         { efTPS, nullptr,  nullptr,     ffREAD  },
275         { efNDX, nullptr,  nullptr,     ffOPTRD },
276         { efPDB, "-q",  nullptr,     ffOPTRD },
277         { efPDB, "-oq", "bfac",   ffOPTWR },
278         { efPDB, "-ox", "xaver",  ffOPTWR },
279         { efXVG, "-o",  "rmsf",   ffWRITE },
280         { efXVG, "-od", "rmsdev", ffOPTWR },
281         { efXVG, "-oc", "correl", ffOPTWR },
282         { efLOG, "-dir", "rmsf",  ffOPTWR }
283     };
284 #define NFILE asize(fnm)
285
286     if (!parse_common_args(&argc, argv, PCA_CAN_TIME | PCA_CAN_VIEW,
287                            NFILE, fnm, asize(pargs), pargs, asize(desc), desc, 0, nullptr,
288                            &oenv))
289     {
290         return 0;
291     }
292
293     bReadPDB = ftp2bSet(efPDB, NFILE, fnm);
294     devfn    = opt2fn_null("-od", NFILE, fnm);
295     dirfn    = opt2fn_null("-dir", NFILE, fnm);
296
297     read_tps_conf(ftp2fn(efTPS, NFILE, fnm), &top, &ePBC, &xref, nullptr, box, TRUE);
298     const char *title = *top.name;
299     snew(w_rls, top.atoms.nr);
300
301     fprintf(stderr, "Select group(s) for root mean square calculation\n");
302     get_index(&top.atoms, ftp2fn_null(efNDX, NFILE, fnm), 1, &isize, &index, &grpnames);
303
304     /* Set the weight */
305     for (i = 0; i < isize; i++)
306     {
307         w_rls[index[i]] = top.atoms.atom[index[i]].m;
308     }
309
310     /* Malloc the rmsf arrays */
311     snew(xav, isize*DIM);
312     snew(U, isize);
313     for (i = 0; i < isize; i++)
314     {
315         snew(U[i], DIM*DIM);
316     }
317     snew(rmsf, isize);
318     if (devfn)
319     {
320         snew(rmsd_x, isize);
321     }
322
323     if (bReadPDB)
324     {
325         t_topology *top_pdb;
326         snew(top_pdb, 1);
327         /* Read coordinates twice */
328         read_tps_conf(opt2fn("-q", NFILE, fnm), top_pdb, nullptr, nullptr, nullptr, pdbbox, FALSE);
329         snew(pdbatoms, 1);
330         *pdbatoms = top_pdb->atoms;
331         read_tps_conf(opt2fn("-q", NFILE, fnm), top_pdb, nullptr, &pdbx, nullptr, pdbbox, FALSE);
332         /* TODO Should this assert that top_pdb->atoms.nr == top.atoms.nr?
333          * See discussion at https://gerrit.gromacs.org/#/c/6430/1 */
334         title = *top_pdb->name;
335         snew(refatoms, 1);
336         *refatoms = top_pdb->atoms;
337         sfree(top_pdb);
338     }
339     else
340     {
341         pdbatoms  = &top.atoms;
342         refatoms  = &top.atoms;
343         pdbx      = xref;
344         snew(pdbatoms->pdbinfo, pdbatoms->nr);
345         pdbatoms->havePdbInfo = TRUE;
346         copy_mat(box, pdbbox);
347     }
348
349     if (bFit)
350     {
351         sub_xcm(xref, isize, index, top.atoms.atom, xcm, FALSE);
352     }
353
354     natom = read_first_x(oenv, &status, ftp2fn(efTRX, NFILE, fnm), &t, &x, box);
355
356     if (bFit)
357     {
358         gpbc = gmx_rmpbc_init(&top.idef, ePBC, natom);
359     }
360
361     /* Now read the trj again to compute fluctuations */
362     teller = 0;
363     do
364     {
365         if (bFit)
366         {
367             /* Remove periodic boundary */
368             gmx_rmpbc(gpbc, natom, box, x);
369
370             /* Set center of mass to zero */
371             sub_xcm(x, isize, index, top.atoms.atom, xcm, FALSE);
372
373             /* Fit to reference structure */
374             do_fit(natom, w_rls, xref, x);
375         }
376
377         /* Calculate Anisotropic U Tensor */
378         for (i = 0; i < isize; i++)
379         {
380             aid = index[i];
381             for (d = 0; d < DIM; d++)
382             {
383                 xav[i*DIM + d] += x[aid][d];
384                 for (m = 0; m < DIM; m++)
385                 {
386                     U[i][d*DIM + m] += x[aid][d]*x[aid][m];
387                 }
388             }
389         }
390
391         if (devfn)
392         {
393             /* Calculate RMS Deviation */
394             for (i = 0; (i < isize); i++)
395             {
396                 aid = index[i];
397                 for (d = 0; (d < DIM); d++)
398                 {
399                     rmsd_x[i][d] += gmx::square(x[aid][d]-xref[aid][d]);
400                 }
401             }
402         }
403         count += 1.0;
404         teller++;
405     }
406     while (read_next_x(oenv, status, &t, x, box));
407     close_trx(status);
408
409     if (bFit)
410     {
411         gmx_rmpbc_done(gpbc);
412     }
413
414
415     invcount = 1.0/count;
416     snew(Uaver, DIM*DIM);
417     totmass = 0;
418     for (i = 0; i < isize; i++)
419     {
420         for (d = 0; d < DIM; d++)
421         {
422             xav[i*DIM + d] *= invcount;
423         }
424         for (d = 0; d < DIM; d++)
425         {
426             for (m = 0; m < DIM; m++)
427             {
428                 U[i][d*DIM + m] = U[i][d*DIM + m]*invcount
429                     - xav[i*DIM + d]*xav[i*DIM + m];
430                 Uaver[3*d+m] += top.atoms.atom[index[i]].m*U[i][d*DIM + m];
431             }
432         }
433         totmass += top.atoms.atom[index[i]].m;
434     }
435     for (d = 0; d < DIM*DIM; d++)
436     {
437         Uaver[d] /= totmass;
438     }
439
440     if (bRes)
441     {
442         for (d = 0; d < DIM*DIM; d++)
443         {
444             average_residues(nullptr, U, d, isize, index, w_rls, &top.atoms);
445         }
446     }
447
448     if (bAniso)
449     {
450         for (i = 0; i < isize; i++)
451         {
452             aid = index[i];
453             pdbatoms->pdbinfo[aid].bAnisotropic = TRUE;
454             pdbatoms->pdbinfo[aid].uij[U11]     = static_cast<int>(1e6*U[i][XX*DIM + XX]);
455             pdbatoms->pdbinfo[aid].uij[U22]     = static_cast<int>(1e6*U[i][YY*DIM + YY]);
456             pdbatoms->pdbinfo[aid].uij[U33]     = static_cast<int>(1e6*U[i][ZZ*DIM + ZZ]);
457             pdbatoms->pdbinfo[aid].uij[U12]     = static_cast<int>(1e6*U[i][XX*DIM + YY]);
458             pdbatoms->pdbinfo[aid].uij[U13]     = static_cast<int>(1e6*U[i][XX*DIM + ZZ]);
459             pdbatoms->pdbinfo[aid].uij[U23]     = static_cast<int>(1e6*U[i][YY*DIM + ZZ]);
460         }
461     }
462     if (bRes)
463     {
464         label = "Residue";
465     }
466     else
467     {
468         label = "Atom";
469     }
470
471     for (i = 0; i < isize; i++)
472     {
473         rmsf[i] = U[i][XX*DIM + XX] + U[i][YY*DIM + YY] + U[i][ZZ*DIM + ZZ];
474     }
475
476     if (dirfn)
477     {
478         fprintf(stdout, "\n");
479         print_dir(stdout, Uaver);
480         fp = gmx_ffopen(dirfn, "w");
481         print_dir(fp, Uaver);
482         gmx_ffclose(fp);
483     }
484
485     for (i = 0; i < isize; i++)
486     {
487         sfree(U[i]);
488     }
489     sfree(U);
490
491     /* Write RMSF output */
492     if (bReadPDB)
493     {
494         bfac = 8.0*M_PI*M_PI/3.0*100;
495         fp   = xvgropen(ftp2fn(efXVG, NFILE, fnm), "B-Factors",
496                         label, "(A\\b\\S\\So\\N\\S2\\N)", oenv);
497         xvgr_legend(fp, 2, leg, oenv);
498         for (i = 0; (i < isize); i++)
499         {
500             if (!bRes || i+1 == isize ||
501                 top.atoms.atom[index[i]].resind != top.atoms.atom[index[i+1]].resind)
502             {
503                 resind    = top.atoms.atom[index[i]].resind;
504                 pdb_bfac  = find_pdb_bfac(pdbatoms, &top.atoms.resinfo[resind],
505                                           *(top.atoms.atomname[index[i]]));
506
507                 fprintf(fp, "%5d  %10.5f  %10.5f\n",
508                         bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i]+1, rmsf[i]*bfac,
509                         pdb_bfac);
510             }
511         }
512         xvgrclose(fp);
513     }
514     else
515     {
516         fp = xvgropen(ftp2fn(efXVG, NFILE, fnm), "RMS fluctuation", label, "(nm)", oenv);
517         for (i = 0; i < isize; i++)
518         {
519             if (!bRes || i+1 == isize ||
520                 top.atoms.atom[index[i]].resind != top.atoms.atom[index[i+1]].resind)
521             {
522                 fprintf(fp, "%5d %8.4f\n",
523                         bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i]+1, std::sqrt(rmsf[i]));
524             }
525         }
526         xvgrclose(fp);
527     }
528
529     for (i = 0; i < isize; i++)
530     {
531         pdbatoms->pdbinfo[index[i]].bfac = 800*M_PI*M_PI/3.0*rmsf[i];
532     }
533
534     if (devfn)
535     {
536         for (i = 0; i < isize; i++)
537         {
538             rmsf[i] = (rmsd_x[i][XX]+rmsd_x[i][YY]+rmsd_x[i][ZZ])/count;
539         }
540         if (bRes)
541         {
542             average_residues(rmsf, nullptr, 0, isize, index, w_rls, &top.atoms);
543         }
544         /* Write RMSD output */
545         fp = xvgropen(devfn, "RMS Deviation", label, "(nm)", oenv);
546         for (i = 0; i < isize; i++)
547         {
548             if (!bRes || i+1 == isize ||
549                 top.atoms.atom[index[i]].resind != top.atoms.atom[index[i+1]].resind)
550             {
551                 fprintf(fp, "%5d %8.4f\n",
552                         bRes ? top.atoms.resinfo[top.atoms.atom[index[i]].resind].nr : index[i]+1, std::sqrt(rmsf[i]));
553             }
554         }
555         xvgrclose(fp);
556     }
557
558     if (opt2bSet("-oq", NFILE, fnm))
559     {
560         /* Write a .pdb file with B-factors and optionally anisou records */
561         for (i = 0; i < isize; i++)
562         {
563             rvec_inc(pdbx[index[i]], xcm);
564         }
565         write_sto_conf_indexed(opt2fn("-oq", NFILE, fnm), title, pdbatoms, pdbx,
566                                nullptr, ePBC, pdbbox, isize, index);
567     }
568     if (opt2bSet("-ox", NFILE, fnm))
569     {
570         rvec *bFactorX;
571         snew(bFactorX, top.atoms.nr);
572         for (i = 0; i < isize; i++)
573         {
574             for (d = 0; d < DIM; d++)
575             {
576                 bFactorX[index[i]][d] = xcm[d] + xav[i*DIM + d];
577             }
578         }
579         /* Write a .pdb file with B-factors and optionally anisou records */
580         write_sto_conf_indexed(opt2fn("-ox", NFILE, fnm), title, pdbatoms, bFactorX, nullptr,
581                                ePBC, pdbbox, isize, index);
582         sfree(bFactorX);
583     }
584     if (bAniso)
585     {
586         correlate_aniso(opt2fn("-oc", NFILE, fnm), refatoms, pdbatoms, oenv);
587         do_view(oenv, opt2fn("-oc", NFILE, fnm), "-nxy");
588     }
589     do_view(oenv, opt2fn("-o", NFILE, fnm), "-nxy");
590     if (devfn)
591     {
592         do_view(oenv, opt2fn("-od", NFILE, fnm), "-nxy");
593     }
594
595     return 0;
596 }