Add gmx convert-trj
[gromacs.git] / src / gromacs / gmxana / gmx_msd.cpp
blob5e6c1d4a64bd455b33d619862e3f3f9e9e19695a
1 /*
2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 1991-2000, University of Groningen, The Netherlands.
5 * Copyright (c) 2001-2004, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2016,2017,2018,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.
11 * GROMACS is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public License
13 * as published by the Free Software Foundation; either version 2.1
14 * of the License, or (at your option) any later version.
16 * GROMACS is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
21 * You should have received a copy of the GNU Lesser General Public
22 * License along with GROMACS; if not, see
23 * http://www.gnu.org/licenses, or write to the Free Software Foundation,
24 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26 * If you want to redistribute modifications to GROMACS, please
27 * consider that scientific software is very special. Version
28 * control is crucial - bugs must be traceable. We will be happy to
29 * consider code for inclusion in the official distribution, but
30 * derived work must not be called official GROMACS. Details are found
31 * in the README & COPYING files - if they are missing, get the
32 * official version at http://www.gromacs.org.
34 * To help us fund GROMACS development, we humbly ask that you cite
35 * the research papers on the package. Check out http://www.gromacs.org.
37 #include "gmxpre.h"
39 #include <cmath>
40 #include <cstring>
42 #include <memory>
44 #include "gromacs/commandline/pargs.h"
45 #include "gromacs/commandline/viewit.h"
46 #include "gromacs/fileio/confio.h"
47 #include "gromacs/fileio/trxio.h"
48 #include "gromacs/fileio/xvgr.h"
49 #include "gromacs/gmxana/gmx_ana.h"
50 #include "gromacs/gmxana/gstat.h"
51 #include "gromacs/math/functions.h"
52 #include "gromacs/math/utilities.h"
53 #include "gromacs/math/vec.h"
54 #include "gromacs/math/vectypes.h"
55 #include "gromacs/pbcutil/rmpbc.h"
56 #include "gromacs/statistics/statistics.h"
57 #include "gromacs/topology/index.h"
58 #include "gromacs/topology/topology.h"
59 #include "gromacs/utility/arraysize.h"
60 #include "gromacs/utility/fatalerror.h"
61 #include "gromacs/utility/futil.h"
62 #include "gromacs/utility/gmxassert.h"
63 #include "gromacs/utility/smalloc.h"
65 static constexpr double diffusionConversionFactor = 1000.0; /* Convert nm^2/ps to 10e-5 cm^2/s */
66 /* NORMAL = total diffusion coefficient (default). X,Y,Z is diffusion
67 coefficient in X,Y,Z direction. LATERAL is diffusion coefficient in
68 plane perpendicular to axis
70 typedef enum {
71 NOT_USED, NORMAL, X, Y, Z, LATERAL
72 } msd_type;
74 // TODO : Group related fields into a struct
75 struct t_corr {
76 real t0; /* start time and time increment between */
77 real delta_t; /* time between restart points */
78 real beginfit, /* the begin/end time for fits as reals between */
79 endfit; /* 0 and 1 */
80 real dim_factor; /* the dimensionality factor for the diffusion
81 constant */
82 std::vector< std::vector<real> > data; /* the displacement data. First index is the group
83 number, second is frame number */
84 std::vector<real> time; /* frame time */
85 std::vector<real> mass; /* masses for mass-weighted msd */
86 matrix **datam;
87 std::vector< std::vector<gmx::RVec> > x0; /* original positions */
88 std::vector<gmx::RVec> com; /* center of mass correction for each frame */
89 gmx_stats_t **lsq; /* fitting stats for individual molecule msds */
90 msd_type type; /* the type of msd to calculate (lateral, etc.)*/
91 int axis; /* the axis along which to calculate */
92 int ncoords;
93 int nrestart; /* number of restart points */
94 int nmol; /* number of molecules (for bMol) */
95 int nframes; /* number of frames */
96 int nlast;
97 int ngrp; /* number of groups to use for msd calculation */
98 std::vector<int> n_offs;
99 std::vector< std::vector<int> > ndata; /* the number of msds (particles/mols) per data
100 point. */
101 t_corr(int nrgrp, int type, int axis, real dim_factor, int nrmol,
102 gmx_bool bTen, gmx_bool bMass, real dt, const t_topology *top,
103 real beginfit, real endfit) :
104 t0(0),
105 delta_t(dt),
106 beginfit((1 - 2*GMX_REAL_EPS)*beginfit),
107 endfit((1 + 2*GMX_REAL_EPS)*endfit),
108 dim_factor(dim_factor),
109 data(nrgrp, std::vector<real>()),
110 datam(nullptr),
111 lsq(nullptr),
112 type(static_cast<msd_type>(type)),
113 axis(axis),
114 ncoords(0),
115 nrestart(0),
116 nmol(nrmol),
117 nframes(0),
118 nlast(0),
119 ngrp(nrgrp),
120 ndata(nrgrp, std::vector<int>())
123 if (bTen)
125 snew(datam, nrgrp);
126 for (int i = 0; i < nrgrp; i++)
128 datam[i] = nullptr;
132 if (nmol > 0)
134 mass.resize(nmol, 1);
136 else
138 if (bMass)
140 const t_atoms *atoms = &top->atoms;
141 mass.resize(atoms->nr);
142 for (int i = 0; (i < atoms->nr); i++)
144 mass[i] = atoms->atom[i].m;
149 ~t_corr()
151 for (int i = 0; i < nrestart; i++)
153 for (int j = 0; j < nmol; j++)
155 gmx_stats_free(lsq[i][j]);
158 sfree(lsq);
162 typedef real t_calc_func (t_corr *curr, int nx, const int index[], int nx0, rvec xc[],
163 const rvec dcom, gmx_bool bTen, matrix mat);
165 static real thistime(t_corr *curr)
167 return curr->time[curr->nframes];
170 static int in_data(t_corr *curr, int nx00)
172 return curr->nframes-curr->n_offs[nx00];
175 static void corr_print(t_corr *curr, gmx_bool bTen, const char *fn, const char *title,
176 const char *yaxis,
177 real msdtime, real beginfit, real endfit,
178 real *DD, real *SigmaD, char *grpname[],
179 const gmx_output_env_t *oenv)
181 FILE *out;
182 int i, j;
184 out = xvgropen(fn, title, output_env_get_xvgr_tlabel(oenv), yaxis, oenv);
185 if (DD)
187 fprintf(out, "# MSD gathered over %g %s with %d restarts\n",
188 msdtime, output_env_get_time_unit(oenv).c_str(), curr->nrestart);
189 fprintf(out, "# Diffusion constants fitted from time %g to %g %s\n",
190 beginfit, endfit, output_env_get_time_unit(oenv).c_str());
191 for (i = 0; i < curr->ngrp; i++)
193 fprintf(out, "# D[%10s] = %.4f (+/- %.4f) (1e-5 cm^2/s)\n",
194 grpname[i], DD[i], SigmaD[i]);
197 for (i = 0; i < curr->nframes; i++)
199 fprintf(out, "%10g", output_env_conv_time(oenv, curr->time[i]));
200 for (j = 0; j < curr->ngrp; j++)
202 fprintf(out, " %10g", curr->data[j][i]);
203 if (bTen)
205 fprintf(out, " %10g %10g %10g %10g %10g %10g",
206 curr->datam[j][i][XX][XX],
207 curr->datam[j][i][YY][YY],
208 curr->datam[j][i][ZZ][ZZ],
209 curr->datam[j][i][YY][XX],
210 curr->datam[j][i][ZZ][XX],
211 curr->datam[j][i][ZZ][YY]);
214 fprintf(out, "\n");
216 xvgrclose(out);
219 /* called from corr_loop, to do the main calculations */
220 static void calc_corr(t_corr *curr, int nr, int nx, int index[], rvec xc[],
221 gmx_bool bRmCOMM, rvec com, t_calc_func *calc1, gmx_bool bTen)
223 int nx0;
224 real g;
225 matrix mat;
226 rvec dcom;
228 /* Check for new starting point */
229 if (curr->nlast < curr->nrestart)
231 if ((thistime(curr) >= (curr->nlast*curr->delta_t)) && (nr == 0))
233 std::memcpy(curr->x0[curr->nlast].data()->as_vec(), xc, curr->ncoords*sizeof(xc[0]));
234 curr->n_offs[curr->nlast] = curr->nframes;
235 copy_rvec(com, curr->com[curr->nlast]);
236 curr->nlast++;
240 /* nx0 appears to be the number of new starting points,
241 * so for all starting points, call calc1.
243 for (nx0 = 0; (nx0 < curr->nlast); nx0++)
245 if (bRmCOMM)
247 rvec_sub(com, curr->com[nx0], dcom);
249 else
251 clear_rvec(dcom);
253 g = calc1(curr, nx, index, nx0, xc, dcom, bTen, mat);
254 #ifdef DEBUG2
255 printf("g[%d]=%g\n", nx0, g);
256 #endif
257 curr->data[nr][in_data(curr, nx0)] += g;
258 if (bTen)
260 m_add(curr->datam[nr][in_data(curr, nx0)], mat,
261 curr->datam[nr][in_data(curr, nx0)]);
263 curr->ndata[nr][in_data(curr, nx0)]++;
267 /* the non-mass-weighted mean-squared displacement calculation */
268 static real calc1_norm(t_corr *curr, int nx, const int index[], int nx0, rvec xc[],
269 const rvec dcom, gmx_bool bTen, matrix mat)
271 int i, ix, m, m2;
272 real g, r, r2;
273 rvec rv;
275 g = 0.0;
276 clear_mat(mat);
278 for (i = 0; (i < nx); i++)
280 ix = index[i];
281 r2 = 0.0;
282 switch (curr->type)
284 case NORMAL:
285 for (m = 0; (m < DIM); m++)
287 rv[m] = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
288 r2 += rv[m]*rv[m];
289 if (bTen)
291 for (m2 = 0; m2 <= m; m2++)
293 mat[m][m2] += rv[m]*rv[m2];
297 break;
298 case X:
299 case Y:
300 case Z:
301 r = xc[ix][curr->type-X] - curr->x0[nx0][ix][curr->type-X] -
302 dcom[curr->type-X];
303 r2 += r*r;
304 break;
305 case LATERAL:
306 for (m = 0; (m < DIM); m++)
308 if (m != curr->axis)
310 r = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
311 r2 += r*r;
314 break;
315 default:
316 gmx_fatal(FARGS, "Error: did not expect option value %d", curr->type);
318 g += r2;
320 g /= nx;
321 msmul(mat, 1.0/nx, mat);
323 return g;
326 /* calculate the com of molecules in x and put it into xa */
327 static void calc_mol_com(int nmol, const int *molindex, const t_block *mols, const t_atoms *atoms,
328 rvec *x, rvec *xa)
330 int m, mol, i, d;
331 rvec xm;
332 real mass, mtot;
334 for (m = 0; m < nmol; m++)
336 mol = molindex[m];
337 clear_rvec(xm);
338 mtot = 0;
339 for (i = mols->index[mol]; i < mols->index[mol+1]; i++)
341 mass = atoms->atom[i].m;
342 for (d = 0; d < DIM; d++)
344 xm[d] += mass*x[i][d];
346 mtot += mass;
348 svmul(1/mtot, xm, xa[m]);
352 static real calc_one_mw(t_corr *curr, int ix, int nx0, rvec xc[], real *tm,
353 const rvec dcom, gmx_bool bTen, matrix mat)
355 real r2, r, mm;
356 rvec rv;
357 int m, m2;
359 mm = curr->mass[ix];
360 if (mm == 0)
362 return 0;
364 (*tm) += mm;
365 r2 = 0.0;
366 switch (curr->type)
368 case NORMAL:
369 for (m = 0; (m < DIM); m++)
371 rv[m] = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
372 r2 += mm*rv[m]*rv[m];
373 if (bTen)
375 for (m2 = 0; m2 <= m; m2++)
377 mat[m][m2] += mm*rv[m]*rv[m2];
381 break;
382 case X:
383 case Y:
384 case Z:
385 r = xc[ix][curr->type-X] - curr->x0[nx0][ix][curr->type-X] -
386 dcom[curr->type-X];
387 r2 = mm*r*r;
388 break;
389 case LATERAL:
390 for (m = 0; (m < DIM); m++)
392 if (m != curr->axis)
394 r = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
395 r2 += mm*r*r;
398 break;
399 default:
400 gmx_fatal(FARGS, "Options got screwed. Did not expect value %d\n", curr->type);
401 } /* end switch */
402 return r2;
405 /* the normal, mass-weighted mean-squared displacement calcuation */
406 static real calc1_mw(t_corr *curr, int nx, const int index[], int nx0, rvec xc[],
407 const rvec dcom, gmx_bool bTen, matrix mat)
409 int i;
410 real g, tm;
412 g = tm = 0.0;
413 clear_mat(mat);
414 for (i = 0; (i < nx); i++)
416 g += calc_one_mw(curr, index[i], nx0, xc, &tm, dcom, bTen, mat);
419 g /= tm;
420 if (bTen)
422 msmul(mat, 1/tm, mat);
425 return g;
428 /* prepare the coordinates by removing periodic boundary crossings.
429 gnx = the number of atoms/molecules
430 index = the indices
431 xcur = the current coordinates
432 xprev = the previous coordinates
433 box = the box matrix */
434 static void prep_data(gmx_bool bMol, int gnx, const int index[],
435 rvec xcur[], rvec xprev[], matrix box)
437 int i, m, ind;
438 rvec hbox;
440 /* Remove periodicity */
441 for (m = 0; (m < DIM); m++)
443 hbox[m] = 0.5*box[m][m];
446 for (i = 0; (i < gnx); i++)
448 if (bMol)
450 ind = i;
452 else
454 ind = index[i];
457 for (m = DIM-1; m >= 0; m--)
459 if (hbox[m] == 0)
461 continue;
463 while (xcur[ind][m]-xprev[ind][m] <= -hbox[m])
465 rvec_inc(xcur[ind], box[m]);
467 while (xcur[ind][m]-xprev[ind][m] > hbox[m])
469 rvec_dec(xcur[ind], box[m]);
475 /* calculate the center of mass for a group
476 gnx = the number of atoms/molecules
477 index = the indices
478 xcur = the current coordinates
479 xprev = the previous coordinates
480 box = the box matrix
481 atoms = atom data (for mass)
482 com(output) = center of mass */
483 static void calc_com(gmx_bool bMol, int gnx, int index[],
484 rvec xcur[], rvec xprev[], matrix box, const t_atoms *atoms,
485 rvec com)
487 int i, m, ind;
488 real mass;
489 double tmass;
490 dvec sx;
492 clear_dvec(sx);
493 tmass = 0;
495 prep_data(bMol, gnx, index, xcur, xprev, box);
496 for (i = 0; (i < gnx); i++)
498 if (bMol)
500 ind = i;
502 else
504 ind = index[i];
508 mass = atoms->atom[ind].m;
509 for (m = 0; m < DIM; m++)
511 sx[m] += mass*xcur[ind][m];
513 tmass += mass;
515 for (m = 0; m < DIM; m++)
517 com[m] = sx[m]/tmass;
522 static real calc1_mol(t_corr *curr, int nx, const int gmx_unused index[], int nx0, rvec xc[],
523 const rvec dcom, gmx_bool bTen, matrix mat)
525 int i;
526 real g, tm, gtot, tt;
528 tt = curr->time[in_data(curr, nx0)];
529 gtot = 0;
530 tm = 0;
531 clear_mat(mat);
532 for (i = 0; (i < nx); i++)
534 g = calc_one_mw(curr, i, nx0, xc, &tm, dcom, bTen, mat);
535 /* We don't need to normalize as the mass was set to 1 */
536 gtot += g;
537 if (tt >= curr->beginfit && (curr->endfit < 0 || tt <= curr->endfit))
539 gmx_stats_add_point(curr->lsq[nx0][i], tt, g, 0, 0);
542 msmul(mat, 1.0/nx, mat);
544 return gtot/nx;
547 static void printmol(t_corr *curr, const char *fn,
548 const char *fn_pdb, const int *molindex, const t_topology *top,
549 rvec *x, int ePBC, matrix box, const gmx_output_env_t *oenv)
551 FILE *out;
552 gmx_stats_t lsq1;
553 int i, j;
554 real a, b, D, Dav, D2av, VarD, sqrtD, sqrtD_max, scale;
555 t_pdbinfo *pdbinfo = nullptr;
556 const int *mol2a = nullptr;
558 out = xvgropen(fn, "Diffusion Coefficients / Molecule", "Molecule", "D (1e-5 cm^2/s)", oenv);
560 if (fn_pdb)
562 pdbinfo = top->atoms.pdbinfo;
563 mol2a = top->mols.index;
566 Dav = D2av = 0;
567 sqrtD_max = 0;
568 for (i = 0; (i < curr->nmol); i++)
570 lsq1 = gmx_stats_init();
571 for (j = 0; (j < curr->nrestart); j++)
573 real xx, yy, dx, dy;
575 while (gmx_stats_get_point(curr->lsq[j][i], &xx, &yy, &dx, &dy, 0) == estatsOK)
577 gmx_stats_add_point(lsq1, xx, yy, dx, dy);
580 gmx_stats_get_ab(lsq1, elsqWEIGHT_NONE, &a, &b, nullptr, nullptr, nullptr, nullptr);
581 gmx_stats_free(lsq1);
582 D = a*diffusionConversionFactor/curr->dim_factor;
583 if (D < 0)
585 D = 0;
587 Dav += D;
588 D2av += gmx::square(D);
589 fprintf(out, "%10d %10g\n", i, D);
590 if (pdbinfo)
592 sqrtD = std::sqrt(D);
593 if (sqrtD > sqrtD_max)
595 sqrtD_max = sqrtD;
597 for (j = mol2a[molindex[i]]; j < mol2a[molindex[i]+1]; j++)
599 pdbinfo[j].bfac = sqrtD;
603 xvgrclose(out);
604 do_view(oenv, fn, "-graphtype bar");
606 /* Compute variance, stddev and error */
607 Dav /= curr->nmol;
608 D2av /= curr->nmol;
609 VarD = D2av - gmx::square(Dav);
610 printf("<D> = %.4f Std. Dev. = %.4f Error = %.4f\n",
611 Dav, std::sqrt(VarD), std::sqrt(VarD/curr->nmol));
613 if (fn_pdb && x)
615 scale = 1;
616 while (scale*sqrtD_max > 10)
618 scale *= 0.1;
620 while (scale*sqrtD_max < 0.1)
622 scale *= 10;
624 GMX_RELEASE_ASSERT(pdbinfo != nullptr, "Internal error - pdbinfo not set for PDB input");
625 for (i = 0; i < top->atoms.nr; i++)
627 pdbinfo[i].bfac *= scale;
629 write_sto_conf(fn_pdb, "molecular MSD", &top->atoms, x, nullptr, ePBC, box);
633 /* this is the main loop for the correlation type functions
634 * fx and nx are file pointers to things like read_first_x and
635 * read_next_x
637 static int corr_loop(t_corr *curr, const char *fn, const t_topology *top, int ePBC,
638 gmx_bool bMol, int gnx[], int *index[],
639 t_calc_func *calc1, gmx_bool bTen, gmx::ArrayRef<const int> gnx_com, int *index_com[],
640 real dt, real t_pdb, rvec **x_pdb, matrix box_pdb,
641 const gmx_output_env_t *oenv)
643 rvec *x[2]; /* the coordinates to read */
644 rvec *xa[2]; /* the coordinates to calculate displacements for */
645 rvec com = {0};
646 real t, t_prev = 0;
647 int natoms, i, j, cur = 0, maxframes = 0;
648 t_trxstatus *status;
649 #define prev (1-cur)
650 matrix box;
651 gmx_bool bFirst;
652 gmx_rmpbc_t gpbc = nullptr;
654 natoms = read_first_x(oenv, &status, fn, &curr->t0, &(x[cur]), box);
655 #ifdef DEBUG
656 fprintf(stderr, "Read %d atoms for first frame\n", natoms);
657 #endif
658 if ((!gnx_com.empty()) && natoms < top->atoms.nr)
660 fprintf(stderr, "WARNING: The trajectory only contains part of the system (%d of %d atoms) and therefore the COM motion of only this part of the system will be removed\n", natoms, top->atoms.nr);
663 snew(x[prev], natoms);
665 // if com is requested, the data structure needs to be large enough to do this
666 // to prevent overflow
667 if (bMol && gnx_com.empty())
669 curr->ncoords = curr->nmol;
670 snew(xa[0], curr->ncoords);
671 snew(xa[1], curr->ncoords);
673 else
675 curr->ncoords = natoms;
676 xa[0] = x[0];
677 xa[1] = x[1];
680 bFirst = TRUE;
681 t = curr->t0;
682 if (x_pdb)
684 *x_pdb = nullptr;
687 if (bMol)
689 gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
692 /* the loop over all frames */
695 if (x_pdb && ((bFirst && t_pdb < t) ||
696 (!bFirst &&
697 t_pdb > t - 0.5*(t - t_prev) &&
698 t_pdb < t + 0.5*(t - t_prev))))
700 if (*x_pdb == nullptr)
702 snew(*x_pdb, natoms);
704 for (i = 0; i < natoms; i++)
706 copy_rvec(x[cur][i], (*x_pdb)[i]);
708 copy_mat(box, box_pdb);
712 /* check whether we've reached a restart point */
713 if (bRmod(t, curr->t0, dt))
715 curr->nrestart++;
717 curr->x0.resize(curr->nrestart);
718 curr->x0[curr->nrestart-1].resize(curr->ncoords);
719 curr->com.resize(curr->nrestart);
720 curr->n_offs.resize(curr->nrestart);
721 srenew(curr->lsq, curr->nrestart);
722 snew(curr->lsq[curr->nrestart-1], curr->nmol);
723 for (i = 0; i < curr->nmol; i++)
725 curr->lsq[curr->nrestart-1][i] = gmx_stats_init();
728 if (debug)
730 fprintf(debug, "Extended data structures because of new restart %d\n",
731 curr->nrestart);
734 /* create or extend the frame-based arrays */
735 if (curr->nframes >= maxframes-1)
737 if (maxframes == 0)
739 for (i = 0; (i < curr->ngrp); i++)
741 if (bTen)
743 curr->datam[i] = nullptr;
747 maxframes += 10;
748 for (i = 0; (i < curr->ngrp); i++)
750 curr->ndata[i].resize(maxframes);
751 curr->data[i].resize(maxframes);
752 if (bTen)
754 srenew(curr->datam[i], maxframes);
756 for (j = maxframes-10; j < maxframes; j++)
758 curr->ndata[i][j] = 0;
759 curr->data[i][j] = 0;
760 if (bTen)
762 clear_mat(curr->datam[i][j]);
766 curr->time.resize(maxframes);
769 /* set the time */
770 curr->time[curr->nframes] = t - curr->t0;
772 /* make the molecules whole */
773 if (bMol)
775 gmx_rmpbc(gpbc, natoms, box, x[cur]);
778 /* calculate the molecules' centers of masses and put them into xa */
779 // NOTE and WARNING! If above both COM removal and individual molecules have been
780 // requested, x and xa point to the same memory, and the coordinate
781 // data becomes overwritten by the molecule data.
782 if (bMol)
784 calc_mol_com(gnx[0], index[0], &top->mols, &top->atoms, x[cur], xa[cur]);
787 /* for the first frame, the previous frame is a copy of the first frame */
788 if (bFirst)
790 std::memcpy(xa[prev], xa[cur], curr->ncoords*sizeof(xa[prev][0]));
791 bFirst = FALSE;
794 /* first remove the periodic boundary condition crossings */
795 for (i = 0; i < curr->ngrp; i++)
797 prep_data(bMol, gnx[i], index[i], xa[cur], xa[prev], box);
800 /* calculate the center of mass */
801 if (!gnx_com.empty())
803 GMX_RELEASE_ASSERT(index_com != nullptr, "Center-of-mass removal must have valid index group");
804 calc_com(bMol, gnx_com[0], index_com[0], xa[cur], xa[prev], box,
805 &top->atoms, com);
808 /* loop over all groups in index file */
809 for (i = 0; (i < curr->ngrp); i++)
811 /* calculate something useful, like mean square displacements */
812 calc_corr(curr, i, gnx[i], index[i], xa[cur], (!gnx_com.empty()), com,
813 calc1, bTen);
815 cur = prev;
816 t_prev = t;
818 curr->nframes++;
820 while (read_next_x(oenv, status, &t, x[cur], box));
821 fprintf(stderr, "\nUsed %d restart points spaced %g %s over %g %s\n\n",
822 curr->nrestart,
823 output_env_conv_time(oenv, dt), output_env_get_time_unit(oenv).c_str(),
824 output_env_conv_time(oenv, curr->time[curr->nframes-1]),
825 output_env_get_time_unit(oenv).c_str() );
827 if (bMol)
829 gmx_rmpbc_done(gpbc);
832 close_trx(status);
834 return natoms;
837 static void index_atom2mol(int *n, int *index, const t_block *mols)
839 int nat, i, nmol, mol, j;
841 nat = *n;
842 i = 0;
843 nmol = 0;
844 mol = 0;
845 while (i < nat)
847 while (index[i] > mols->index[mol])
849 mol++;
850 if (mol >= mols->nr)
852 gmx_fatal(FARGS, "Atom index out of range: %d", index[i]+1);
855 for (j = mols->index[mol]; j < mols->index[mol+1]; j++)
857 if (i >= nat || index[i] != j)
859 gmx_fatal(FARGS, "The index group does not consist of whole molecules");
861 i++;
863 index[nmol++] = mol;
866 fprintf(stderr, "Split group of %d atoms into %d molecules\n", nat, nmol);
868 *n = nmol;
871 static void do_corr(const char *trx_file, const char *ndx_file, const char *msd_file,
872 const char *mol_file, const char *pdb_file, real t_pdb,
873 int nrgrp, t_topology *top, int ePBC,
874 gmx_bool bTen, gmx_bool bMW, gmx_bool bRmCOMM,
875 int type, real dim_factor, int axis,
876 real dt, real beginfit, real endfit, const gmx_output_env_t *oenv)
878 std::unique_ptr<t_corr> msd;
879 std::vector<int> gnx, gnx_com; /* the selected groups' sizes */
880 int **index; /* selected groups' indices */
881 char **grpname;
882 int i, i0, i1, j, N, nat_trx;
883 std::vector<real> SigmaD, DD;
884 real a, a2, b, r, chi2;
885 rvec *x = nullptr;
886 matrix box;
887 int **index_com = nullptr; /* the COM removal group atom indices */
888 char **grpname_com = nullptr; /* the COM removal group name */
890 gnx.resize(nrgrp);
891 snew(index, nrgrp);
892 snew(grpname, nrgrp);
894 fprintf(stderr, "\nSelect a group to calculate mean squared displacement for:\n");
895 get_index(&top->atoms, ndx_file, nrgrp, gnx.data(), index, grpname);
897 if (bRmCOMM)
899 gnx_com.resize(1);
900 snew(index_com, 1);
901 snew(grpname_com, 1);
903 fprintf(stderr, "\nNow select a group for center of mass removal:\n");
904 get_index(&top->atoms, ndx_file, 1, gnx_com.data(), index_com, grpname_com);
907 if (mol_file)
909 index_atom2mol(&gnx[0], index[0], &top->mols);
912 msd = std::make_unique<t_corr>(nrgrp, type, axis, dim_factor,
913 mol_file == nullptr ? 0 : gnx[0],
914 bTen, bMW, dt, top, beginfit, endfit);
916 nat_trx =
917 corr_loop(msd.get(), trx_file, top, ePBC, mol_file ? gnx[0] != 0 : false, gnx.data(), index,
918 (mol_file != nullptr) ? calc1_mol : (bMW ? calc1_mw : calc1_norm),
919 bTen, gnx_com, index_com, dt, t_pdb,
920 pdb_file ? &x : nullptr, box, oenv);
922 /* Correct for the number of points */
923 for (j = 0; (j < msd->ngrp); j++)
925 for (i = 0; (i < msd->nframes); i++)
927 msd->data[j][i] /= msd->ndata[j][i];
928 if (bTen)
930 msmul(msd->datam[j][i], 1.0/msd->ndata[j][i], msd->datam[j][i]);
935 if (mol_file)
937 if (pdb_file && x == nullptr)
939 fprintf(stderr, "\nNo frame found need time tpdb = %g ps\n"
940 "Can not write %s\n\n", t_pdb, pdb_file);
942 i = top->atoms.nr;
943 top->atoms.nr = nat_trx;
944 if (pdb_file && top->atoms.pdbinfo == nullptr)
946 snew(top->atoms.pdbinfo, top->atoms.nr);
948 printmol(msd.get(), mol_file, pdb_file, index[0], top, x, ePBC, box, oenv);
949 top->atoms.nr = i;
952 if (beginfit == -1)
954 i0 = gmx::roundToInt(0.1*(msd->nframes - 1));
955 beginfit = msd->time[i0];
957 else
959 for (i0 = 0; i0 < msd->nframes && msd->time[i0] < beginfit; i0++)
965 if (endfit == -1)
967 i1 = gmx::roundToInt(0.9*(msd->nframes - 1)) + 1;
968 endfit = msd->time[i1-1];
970 else
972 for (i1 = i0; i1 < msd->nframes && msd->time[i1] <= endfit; i1++)
977 fprintf(stdout, "Fitting from %g to %g %s\n\n", beginfit, endfit,
978 output_env_get_time_unit(oenv).c_str());
980 N = i1-i0;
981 if (N <= 2)
983 fprintf(stdout, "Not enough points for fitting (%d).\n"
984 "Can not determine the diffusion constant.\n", N);
986 else
988 DD.resize(msd->ngrp);
989 SigmaD.resize(msd->ngrp);
990 for (j = 0; j < msd->ngrp; j++)
992 if (N >= 4)
994 lsq_y_ax_b(N/2, &(msd->time[i0]), &(msd->data[j][i0]), &a, &b, &r, &chi2);
995 lsq_y_ax_b(N/2, &(msd->time[i0+N/2]), &(msd->data[j][i0+N/2]), &a2, &b, &r, &chi2);
996 SigmaD[j] = std::abs(a-a2);
998 else
1000 SigmaD[j] = 0;
1002 lsq_y_ax_b(N, &(msd->time[i0]), &(msd->data[j][i0]), &(DD[j]), &b, &r, &chi2);
1003 DD[j] *= diffusionConversionFactor/msd->dim_factor;
1004 SigmaD[j] *= diffusionConversionFactor/msd->dim_factor;
1005 if (DD[j] > 0.01 && DD[j] < 1e4)
1007 fprintf(stdout, "D[%10s] %.4f (+/- %.4f) 1e-5 cm^2/s\n",
1008 grpname[j], DD[j], SigmaD[j]);
1010 else
1012 fprintf(stdout, "D[%10s] %.4g (+/- %.4g) 1e-5 cm^2/s\n",
1013 grpname[j], DD[j], SigmaD[j]);
1017 /* Print mean square displacement */
1018 corr_print(msd.get(), bTen, msd_file,
1019 "Mean Square Displacement",
1020 "MSD (nm\\S2\\N)",
1021 msd->time[msd->nframes-1], beginfit, endfit, DD.data(), SigmaD.data(), grpname, oenv);
1024 int gmx_msd(int argc, char *argv[])
1026 const char *desc[] = {
1027 "[THISMODULE] computes the mean square displacement (MSD) of atoms from",
1028 "a set of initial positions. This provides an easy way to compute",
1029 "the diffusion constant using the Einstein relation.",
1030 "The time between the reference points for the MSD calculation",
1031 "is set with [TT]-trestart[tt].",
1032 "The diffusion constant is calculated by least squares fitting a",
1033 "straight line (D*t + c) through the MSD(t) from [TT]-beginfit[tt] to",
1034 "[TT]-endfit[tt] (note that t is time from the reference positions,",
1035 "not simulation time). An error estimate given, which is the difference",
1036 "of the diffusion coefficients obtained from fits over the two halves",
1037 "of the fit interval.[PAR]",
1038 "There are three, mutually exclusive, options to determine different",
1039 "types of mean square displacement: [TT]-type[tt], [TT]-lateral[tt]",
1040 "and [TT]-ten[tt]. Option [TT]-ten[tt] writes the full MSD tensor for",
1041 "each group, the order in the output is: trace xx yy zz yx zx zy.[PAR]",
1042 "If [TT]-mol[tt] is set, [THISMODULE] plots the MSD for individual molecules",
1043 "(including making molecules whole across periodic boundaries): ",
1044 "for each individual molecule a diffusion constant is computed for ",
1045 "its center of mass. The chosen index group will be split into ",
1046 "molecules.[PAR]",
1047 "The default way to calculate a MSD is by using mass-weighted averages.",
1048 "This can be turned off with [TT]-nomw[tt].[PAR]",
1049 "With the option [TT]-rmcomm[tt], the center of mass motion of a ",
1050 "specific group can be removed. For trajectories produced with ",
1051 "GROMACS this is usually not necessary, ",
1052 "as [gmx-mdrun] usually already removes the center of mass motion.",
1053 "When you use this option be sure that the whole system is stored",
1054 "in the trajectory file.[PAR]",
1055 "The diffusion coefficient is determined by linear regression of the MSD,",
1056 "where, unlike for the normal output of D, the times are weighted",
1057 "according to the number of reference points, i.e. short times have",
1058 "a higher weight. Also when [TT]-beginfit[tt] is -1, fitting starts at 10%",
1059 "and when [TT]-endfit[tt] is -1, fitting goes to 90%.",
1060 "Using this option one also gets an accurate error estimate",
1061 "based on the statistics between individual molecules.",
1062 "Note that this diffusion coefficient and error estimate are only",
1063 "accurate when the MSD is completely linear between",
1064 "[TT]-beginfit[tt] and [TT]-endfit[tt].[PAR]",
1065 "Option [TT]-pdb[tt] writes a [REF].pdb[ref] file with the coordinates of the frame",
1066 "at time [TT]-tpdb[tt] with in the B-factor field the square root of",
1067 "the diffusion coefficient of the molecule.",
1068 "This option implies option [TT]-mol[tt]."
1070 static const char *normtype[] = { nullptr, "no", "x", "y", "z", nullptr };
1071 static const char *axtitle[] = { nullptr, "no", "x", "y", "z", nullptr };
1072 static int ngroup = 1;
1073 static real dt = 10;
1074 static real t_pdb = 0;
1075 static real beginfit = -1;
1076 static real endfit = -1;
1077 static gmx_bool bTen = FALSE;
1078 static gmx_bool bMW = TRUE;
1079 static gmx_bool bRmCOMM = FALSE;
1080 t_pargs pa[] = {
1081 { "-type", FALSE, etENUM, {normtype},
1082 "Compute diffusion coefficient in one direction" },
1083 { "-lateral", FALSE, etENUM, {axtitle},
1084 "Calculate the lateral diffusion in a plane perpendicular to" },
1085 { "-ten", FALSE, etBOOL, {&bTen},
1086 "Calculate the full tensor" },
1087 { "-ngroup", FALSE, etINT, {&ngroup},
1088 "Number of groups to calculate MSD for" },
1089 { "-mw", FALSE, etBOOL, {&bMW},
1090 "Mass weighted MSD" },
1091 { "-rmcomm", FALSE, etBOOL, {&bRmCOMM},
1092 "Remove center of mass motion" },
1093 { "-tpdb", FALSE, etTIME, {&t_pdb},
1094 "The frame to use for option [TT]-pdb[tt] (%t)" },
1095 { "-trestart", FALSE, etTIME, {&dt},
1096 "Time between restarting points in trajectory (%t)" },
1097 { "-beginfit", FALSE, etTIME, {&beginfit},
1098 "Start time for fitting the MSD (%t), -1 is 10%" },
1099 { "-endfit", FALSE, etTIME, {&endfit},
1100 "End time for fitting the MSD (%t), -1 is 90%" }
1103 t_filenm fnm[] = {
1104 { efTRX, nullptr, nullptr, ffREAD },
1105 { efTPS, nullptr, nullptr, ffREAD },
1106 { efNDX, nullptr, nullptr, ffOPTRD },
1107 { efXVG, nullptr, "msd", ffWRITE },
1108 { efXVG, "-mol", "diff_mol", ffOPTWR },
1109 { efPDB, "-pdb", "diff_mol", ffOPTWR }
1111 #define NFILE asize(fnm)
1113 t_topology top;
1114 int ePBC;
1115 matrix box;
1116 const char *trx_file, *tps_file, *ndx_file, *msd_file, *mol_file, *pdb_file;
1117 rvec *xdum;
1118 gmx_bool bTop;
1119 int axis, type;
1120 real dim_factor;
1121 gmx_output_env_t *oenv;
1123 if (!parse_common_args(&argc, argv,
1124 PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END | PCA_TIME_UNIT,
1125 NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
1127 return 0;
1129 trx_file = ftp2fn_null(efTRX, NFILE, fnm);
1130 tps_file = ftp2fn_null(efTPS, NFILE, fnm);
1131 ndx_file = ftp2fn_null(efNDX, NFILE, fnm);
1132 msd_file = ftp2fn_null(efXVG, NFILE, fnm);
1133 pdb_file = opt2fn_null("-pdb", NFILE, fnm);
1134 if (pdb_file)
1136 mol_file = opt2fn("-mol", NFILE, fnm);
1138 else
1140 mol_file = opt2fn_null("-mol", NFILE, fnm);
1143 if (ngroup < 1)
1145 gmx_fatal(FARGS, "Must have at least 1 group (now %d)", ngroup);
1147 if (mol_file && ngroup > 1)
1149 gmx_fatal(FARGS, "With molecular msd can only have 1 group (now %d)",
1150 ngroup);
1154 if (mol_file)
1156 bMW = TRUE;
1157 fprintf(stderr, "Calculating diffusion coefficients for molecules.\n");
1160 GMX_RELEASE_ASSERT(normtype[0] != nullptr, "Options inconsistency; normtype[0] is NULL");
1161 GMX_RELEASE_ASSERT(axtitle[0] != nullptr, "Options inconsistency; axtitle[0] is NULL");
1163 if (normtype[0][0] != 'n')
1165 type = normtype[0][0] - 'x' + X; /* See defines above */
1166 dim_factor = 2.0;
1168 else
1170 type = NORMAL;
1171 dim_factor = 6.0;
1173 if ((type == NORMAL) && (axtitle[0][0] != 'n'))
1175 type = LATERAL;
1176 dim_factor = 4.0;
1177 axis = (axtitle[0][0] - 'x'); /* See defines above */
1179 else
1181 axis = 0;
1184 if (bTen && type != NORMAL)
1186 gmx_fatal(FARGS, "Can only calculate the full tensor for 3D msd");
1189 bTop = read_tps_conf(tps_file, &top, &ePBC, &xdum, nullptr, box, bMW || bRmCOMM);
1190 if (mol_file && !bTop)
1192 gmx_fatal(FARGS,
1193 "Could not read a topology from %s. Try a tpr file instead.",
1194 tps_file);
1197 do_corr(trx_file, ndx_file, msd_file, mol_file, pdb_file, t_pdb, ngroup,
1198 &top, ePBC, bTen, bMW, bRmCOMM, type, dim_factor, axis, dt, beginfit, endfit,
1199 oenv);
1201 done_top(&top);
1202 view_all(oenv, NFILE, fnm);
1204 return 0;