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