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