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 by the GROMACS development team.
7 * Copyright (c) 2018,2019,2020, 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.
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.
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.
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.
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.
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.
45 #include "gromacs/commandline/pargs.h"
46 #include "gromacs/commandline/viewit.h"
47 #include "gromacs/fileio/confio.h"
48 #include "gromacs/fileio/trxio.h"
49 #include "gromacs/fileio/xvgr.h"
50 #include "gromacs/gmxana/gmx_ana.h"
51 #include "gromacs/gmxana/gstat.h"
52 #include "gromacs/math/functions.h"
53 #include "gromacs/math/utilities.h"
54 #include "gromacs/math/vec.h"
55 #include "gromacs/math/vectypes.h"
56 #include "gromacs/pbcutil/rmpbc.h"
57 #include "gromacs/statistics/statistics.h"
58 #include "gromacs/topology/index.h"
59 #include "gromacs/topology/topology.h"
60 #include "gromacs/utility/arraysize.h"
61 #include "gromacs/utility/fatalerror.h"
62 #include "gromacs/utility/futil.h"
63 #include "gromacs/utility/gmxassert.h"
64 #include "gromacs/utility/smalloc.h"
66 static constexpr double diffusionConversionFactor = 1000.0; /* Convert nm^2/ps to 10e-5 cm^2/s */
67 /* NORMAL = total diffusion coefficient (default). X,Y,Z is diffusion
68 coefficient in X,Y,Z direction. LATERAL is diffusion coefficient in
69 plane perpendicular to axis
81 // TODO : Group related fields into a struct
84 real t0; /* start time and time increment between */
85 real delta_t; /* time between restart points */
86 real beginfit, /* the begin/end time for fits as reals between */
88 real dim_factor; /* the dimensionality factor for the diffusion
90 std::vector<std::vector<real>> data; /* the displacement data. First index is the group
91 number, second is frame number */
92 std::vector<real> time; /* frame time */
93 std::vector<real> mass; /* masses for mass-weighted msd */
95 std::vector<std::vector<gmx::RVec>> x0; /* original positions */
96 std::vector<gmx::RVec> com; /* center of mass correction for each frame */
97 gmx_stats_t** lsq; /* fitting stats for individual molecule msds */
98 msd_type type; /* the type of msd to calculate (lateral, etc.)*/
99 int axis; /* the axis along which to calculate */
101 int nrestart; /* number of restart points */
102 int nmol; /* number of molecules (for bMol) */
103 int nframes; /* number of frames */
105 int ngrp; /* number of groups to use for msd calculation */
106 std::vector<int> n_offs;
107 std::vector<std::vector<int>> ndata; /* the number of msds (particles/mols) per data
117 const t_topology* top,
122 beginfit((1 - 2 * GMX_REAL_EPS) * beginfit),
123 endfit((1 + 2 * GMX_REAL_EPS) * endfit),
124 dim_factor(dim_factor),
125 data(nrgrp, std::vector<real>()),
128 type(static_cast<msd_type>(type)),
136 ndata(nrgrp, std::vector<int>())
142 for (int i = 0; i < nrgrp; i++)
150 mass.resize(nmol, 1);
156 const t_atoms* atoms = &top->atoms;
157 mass.resize(atoms->nr);
158 for (int i = 0; (i < atoms->nr); i++)
160 mass[i] = atoms->atom[i].m;
167 for (int i = 0; i < nrestart; i++)
169 for (int j = 0; j < nmol; j++)
171 gmx_stats_free(lsq[i][j]);
179 t_calc_func(t_corr* curr, int nx, const int index[], int nx0, rvec xc[], const rvec dcom, gmx_bool bTen, matrix mat);
181 static real thistime(t_corr* curr)
183 return curr->time[curr->nframes];
186 static int in_data(t_corr* curr, int nx00)
188 return curr->nframes - curr->n_offs[nx00];
191 static void corr_print(t_corr* curr,
202 const gmx_output_env_t* oenv)
207 out = xvgropen(fn, title, output_env_get_xvgr_tlabel(oenv), yaxis, oenv);
210 fprintf(out, "# MSD gathered over %g %s with %d restarts\n", msdtime,
211 output_env_get_time_unit(oenv).c_str(), curr->nrestart);
212 fprintf(out, "# Diffusion constants fitted from time %g to %g %s\n", beginfit, endfit,
213 output_env_get_time_unit(oenv).c_str());
214 for (i = 0; i < curr->ngrp; i++)
216 fprintf(out, "# D[%10s] = %.4f (+/- %.4f) (1e-5 cm^2/s)\n", grpname[i], DD[i], SigmaD[i]);
219 for (i = 0; i < curr->nframes; i++)
221 fprintf(out, "%10g", output_env_conv_time(oenv, curr->time[i]));
222 for (j = 0; j < curr->ngrp; j++)
224 fprintf(out, " %10g", curr->data[j][i]);
227 fprintf(out, " %10g %10g %10g %10g %10g %10g", curr->datam[j][i][XX][XX],
228 curr->datam[j][i][YY][YY], curr->datam[j][i][ZZ][ZZ], curr->datam[j][i][YY][XX],
229 curr->datam[j][i][ZZ][XX], curr->datam[j][i][ZZ][YY]);
237 /* called from corr_loop, to do the main calculations */
239 calc_corr(t_corr* curr, int nr, int nx, int index[], rvec xc[], gmx_bool bRmCOMM, rvec com, t_calc_func* calc1, gmx_bool bTen)
246 /* Check for new starting point */
247 if (curr->nlast < curr->nrestart)
249 if ((thistime(curr) >= (curr->nlast * curr->delta_t)) && (nr == 0))
251 std::memcpy(curr->x0[curr->nlast].data()->as_vec(), xc, curr->ncoords * sizeof(xc[0]));
252 curr->n_offs[curr->nlast] = curr->nframes;
253 copy_rvec(com, curr->com[curr->nlast]);
258 /* nx0 appears to be the number of new starting points,
259 * so for all starting points, call calc1.
261 for (nx0 = 0; (nx0 < curr->nlast); nx0++)
265 rvec_sub(com, curr->com[nx0], dcom);
271 g = calc1(curr, nx, index, nx0, xc, dcom, bTen, mat);
273 printf("g[%d]=%g\n", nx0, g);
275 curr->data[nr][in_data(curr, nx0)] += g;
278 m_add(curr->datam[nr][in_data(curr, nx0)], mat, curr->datam[nr][in_data(curr, nx0)]);
280 curr->ndata[nr][in_data(curr, nx0)]++;
284 /* the non-mass-weighted mean-squared displacement calculation */
286 calc1_norm(t_corr* curr, int nx, const int index[], int nx0, rvec xc[], const rvec dcom, gmx_bool bTen, matrix mat)
295 for (i = 0; (i < nx); i++)
302 for (m = 0; (m < DIM); m++)
304 rv[m] = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
308 for (m2 = 0; m2 <= m; m2++)
310 mat[m][m2] += rv[m] * rv[m2];
318 r = xc[ix][curr->type - X] - curr->x0[nx0][ix][curr->type - X] - dcom[curr->type - X];
322 for (m = 0; (m < DIM); m++)
326 r = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
331 default: gmx_fatal(FARGS, "Error: did not expect option value %d", curr->type);
336 msmul(mat, 1.0 / nx, mat);
341 /* calculate the com of molecules in x and put it into xa */
343 calc_mol_com(int nmol, const int* molindex, const t_block* mols, const t_atoms* atoms, rvec* x, rvec* xa)
349 for (m = 0; m < nmol; m++)
354 for (i = mols->index[mol]; i < mols->index[mol + 1]; i++)
356 mass = atoms->atom[i].m;
357 for (d = 0; d < DIM; d++)
359 xm[d] += mass * x[i][d];
363 svmul(1 / mtot, xm, xa[m]);
367 static real calc_one_mw(t_corr* curr, int ix, int nx0, rvec xc[], real* tm, const rvec dcom, gmx_bool bTen, matrix mat)
383 for (m = 0; (m < DIM); m++)
385 rv[m] = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
386 r2 += mm * rv[m] * rv[m];
389 for (m2 = 0; m2 <= m; m2++)
391 mat[m][m2] += mm * rv[m] * rv[m2];
399 r = xc[ix][curr->type - X] - curr->x0[nx0][ix][curr->type - X] - dcom[curr->type - X];
403 for (m = 0; (m < DIM); m++)
407 r = xc[ix][m] - curr->x0[nx0][ix][m] - dcom[m];
412 default: gmx_fatal(FARGS, "Options got screwed. Did not expect value %d\n", curr->type);
417 /* the normal, mass-weighted mean-squared displacement calcuation */
418 static real calc1_mw(t_corr* curr, int nx, const int index[], int nx0, rvec xc[], const rvec dcom, gmx_bool bTen, matrix mat)
425 for (i = 0; (i < nx); i++)
427 g += calc_one_mw(curr, index[i], nx0, xc, &tm, dcom, bTen, mat);
433 msmul(mat, 1 / tm, mat);
439 /* prepare the coordinates by removing periodic boundary crossings.
440 gnx = the number of atoms/molecules
442 xcur = the current coordinates
443 xprev = the previous coordinates
444 box = the box matrix */
445 static void prep_data(gmx_bool bMol, int gnx, const int index[], rvec xcur[], rvec xprev[], matrix box)
450 /* Remove periodicity */
451 for (m = 0; (m < DIM); m++)
453 hbox[m] = 0.5 * box[m][m];
456 for (i = 0; (i < gnx); i++)
467 for (m = DIM - 1; m >= 0; m--)
473 while (xcur[ind][m] - xprev[ind][m] <= -hbox[m])
475 rvec_inc(xcur[ind], box[m]);
477 while (xcur[ind][m] - xprev[ind][m] > hbox[m])
479 rvec_dec(xcur[ind], box[m]);
485 /* calculate the center of mass for a group
486 gnx = the number of atoms/molecules
488 xcur = the current coordinates
489 xprev = the previous coordinates
491 atoms = atom data (for mass)
492 com(output) = center of mass */
494 calc_com(gmx_bool bMol, int gnx, int index[], rvec xcur[], rvec xprev[], matrix box, const t_atoms* atoms, rvec com)
504 prep_data(bMol, gnx, index, xcur, xprev, box);
505 for (i = 0; (i < gnx); i++)
517 mass = atoms->atom[ind].m;
518 for (m = 0; m < DIM; m++)
520 sx[m] += mass * xcur[ind][m];
524 for (m = 0; m < DIM; m++)
526 com[m] = sx[m] / tmass;
531 static real calc1_mol(t_corr* curr,
533 const int gmx_unused index[],
541 real g, tm, gtot, tt;
543 tt = curr->time[in_data(curr, nx0)];
547 for (i = 0; (i < nx); i++)
549 g = calc_one_mw(curr, i, nx0, xc, &tm, dcom, bTen, mat);
550 /* We don't need to normalize as the mass was set to 1 */
552 if (tt >= curr->beginfit && (curr->endfit < 0 || tt <= curr->endfit))
554 gmx_stats_add_point(curr->lsq[nx0][i], tt, g, 0, 0);
557 msmul(mat, 1.0 / nx, mat);
562 static void printmol(t_corr* curr,
566 const t_topology* top,
570 const gmx_output_env_t* oenv)
575 real a, b, D, Dav, D2av, VarD, sqrtD, sqrtD_max, scale;
576 t_pdbinfo* pdbinfo = nullptr;
577 const int* mol2a = nullptr;
579 out = xvgropen(fn, "Diffusion Coefficients / Molecule", "Molecule", "D (1e-5 cm^2/s)", oenv);
583 pdbinfo = top->atoms.pdbinfo;
584 mol2a = top->mols.index;
589 for (i = 0; (i < curr->nmol); i++)
591 lsq1 = gmx_stats_init();
592 for (j = 0; (j < curr->nrestart); j++)
596 while (gmx_stats_get_point(curr->lsq[j][i], &xx, &yy, &dx, &dy, 0) == estatsOK)
598 gmx_stats_add_point(lsq1, xx, yy, dx, dy);
601 gmx_stats_get_ab(lsq1, elsqWEIGHT_NONE, &a, &b, nullptr, nullptr, nullptr, nullptr);
602 gmx_stats_free(lsq1);
603 D = a * diffusionConversionFactor / curr->dim_factor;
609 D2av += gmx::square(D);
610 fprintf(out, "%10d %10g\n", i, D);
613 sqrtD = std::sqrt(D);
614 if (sqrtD > sqrtD_max)
618 for (j = mol2a[molindex[i]]; j < mol2a[molindex[i] + 1]; j++)
620 pdbinfo[j].bfac = sqrtD;
625 do_view(oenv, fn, "-graphtype bar");
627 /* Compute variance, stddev and error */
630 VarD = D2av - gmx::square(Dav);
631 printf("<D> = %.4f Std. Dev. = %.4f Error = %.4f\n", Dav, std::sqrt(VarD),
632 std::sqrt(VarD / curr->nmol));
637 while (scale * sqrtD_max > 10)
641 while (scale * sqrtD_max < 0.1)
645 GMX_RELEASE_ASSERT(pdbinfo != nullptr, "Internal error - pdbinfo not set for PDB input");
646 for (i = 0; i < top->atoms.nr; i++)
648 pdbinfo[i].bfac *= scale;
650 write_sto_conf(fn_pdb, "molecular MSD", &top->atoms, x, nullptr, pbcType, box);
654 /* this is the main loop for the correlation type functions
655 * fx and nx are file pointers to things like read_first_x and
658 static int corr_loop(t_corr* curr,
660 const t_topology* top,
667 gmx::ArrayRef<const int> gnx_com,
673 const gmx_output_env_t* oenv)
675 rvec* x[2]; /* the coordinates to read */
676 rvec* xa[2]; /* the coordinates to calculate displacements for */
679 int natoms, i, j, cur = 0, maxframes = 0;
681 #define prev (1 - cur)
684 gmx_rmpbc_t gpbc = nullptr;
686 natoms = read_first_x(oenv, &status, fn, &curr->t0, &(x[cur]), box);
688 fprintf(stderr, "Read %d atoms for first frame\n", natoms);
690 if ((!gnx_com.empty()) && natoms < top->atoms.nr)
693 "WARNING: The trajectory only contains part of the system (%d of %d atoms) and "
694 "therefore the COM motion of only this part of the system will be removed\n",
695 natoms, top->atoms.nr);
698 snew(x[prev], natoms);
700 // if com is requested, the data structure needs to be large enough to do this
701 // to prevent overflow
702 if (bMol && gnx_com.empty())
704 curr->ncoords = curr->nmol;
705 snew(xa[0], curr->ncoords);
706 snew(xa[1], curr->ncoords);
710 curr->ncoords = natoms;
724 gpbc = gmx_rmpbc_init(&top->idef, pbcType, natoms);
727 /* the loop over all frames */
731 && ((bFirst && t_pdb < t)
732 || (!bFirst && t_pdb > t - 0.5 * (t - t_prev) && t_pdb < t + 0.5 * (t - t_prev))))
734 if (*x_pdb == nullptr)
736 snew(*x_pdb, natoms);
738 for (i = 0; i < natoms; i++)
740 copy_rvec(x[cur][i], (*x_pdb)[i]);
742 copy_mat(box, box_pdb);
746 /* check whether we've reached a restart point */
747 if (bRmod(t, curr->t0, dt))
751 curr->x0.resize(curr->nrestart);
752 curr->x0[curr->nrestart - 1].resize(curr->ncoords);
753 curr->com.resize(curr->nrestart);
754 curr->n_offs.resize(curr->nrestart);
755 srenew(curr->lsq, curr->nrestart);
756 snew(curr->lsq[curr->nrestart - 1], curr->nmol);
757 for (i = 0; i < curr->nmol; i++)
759 curr->lsq[curr->nrestart - 1][i] = gmx_stats_init();
764 fprintf(debug, "Extended data structures because of new restart %d\n", curr->nrestart);
767 /* create or extend the frame-based arrays */
768 if (curr->nframes >= maxframes - 1)
772 for (i = 0; (i < curr->ngrp); i++)
776 curr->datam[i] = nullptr;
781 for (i = 0; (i < curr->ngrp); i++)
783 curr->ndata[i].resize(maxframes);
784 curr->data[i].resize(maxframes);
787 srenew(curr->datam[i], maxframes);
789 for (j = maxframes - 10; j < maxframes; j++)
791 curr->ndata[i][j] = 0;
792 curr->data[i][j] = 0;
795 clear_mat(curr->datam[i][j]);
799 curr->time.resize(maxframes);
803 curr->time[curr->nframes] = t - curr->t0;
805 /* make the molecules whole */
808 gmx_rmpbc(gpbc, natoms, box, x[cur]);
811 /* calculate the molecules' centers of masses and put them into xa */
812 // NOTE and WARNING! If above both COM removal and individual molecules have been
813 // requested, x and xa point to the same memory, and the coordinate
814 // data becomes overwritten by the molecule data.
817 calc_mol_com(gnx[0], index[0], &top->mols, &top->atoms, x[cur], xa[cur]);
820 /* for the first frame, the previous frame is a copy of the first frame */
823 std::memcpy(xa[prev], xa[cur], curr->ncoords * sizeof(xa[prev][0]));
827 /* first remove the periodic boundary condition crossings */
828 for (i = 0; i < curr->ngrp; i++)
830 prep_data(bMol, gnx[i], index[i], xa[cur], xa[prev], box);
833 /* calculate the center of mass */
834 if (!gnx_com.empty())
836 GMX_RELEASE_ASSERT(index_com != nullptr,
837 "Center-of-mass removal must have valid index group");
838 calc_com(bMol, gnx_com[0], index_com[0], xa[cur], xa[prev], box, &top->atoms, com);
841 /* loop over all groups in index file */
842 for (i = 0; (i < curr->ngrp); i++)
844 /* calculate something useful, like mean square displacements */
845 calc_corr(curr, i, gnx[i], index[i], xa[cur], (!gnx_com.empty()), com, calc1, bTen);
851 } while (read_next_x(oenv, status, &t, x[cur], box));
852 fprintf(stderr, "\nUsed %d restart points spaced %g %s over %g %s\n\n", curr->nrestart,
853 output_env_conv_time(oenv, dt), output_env_get_time_unit(oenv).c_str(),
854 output_env_conv_time(oenv, curr->time[curr->nframes - 1]),
855 output_env_get_time_unit(oenv).c_str());
859 gmx_rmpbc_done(gpbc);
867 static void index_atom2mol(int* n, int* index, const t_block* mols)
869 int nat, i, nmol, mol, j;
877 while (index[i] > mols->index[mol])
882 gmx_fatal(FARGS, "Atom index out of range: %d", index[i] + 1);
885 for (j = mols->index[mol]; j < mols->index[mol + 1]; j++)
887 if (i >= nat || index[i] != j)
889 gmx_fatal(FARGS, "The index group does not consist of whole molecules");
896 fprintf(stderr, "Split group of %d atoms into %d molecules\n", nat, nmol);
901 static void do_corr(const char* trx_file,
902 const char* ndx_file,
903 const char* msd_file,
904 const char* mol_file,
905 const char* pdb_file,
919 const gmx_output_env_t* oenv)
921 std::unique_ptr<t_corr> msd;
922 std::vector<int> gnx, gnx_com; /* the selected groups' sizes */
923 int** index; /* selected groups' indices */
925 int i, i0, i1, j, N, nat_trx;
926 std::vector<real> SigmaD, DD;
927 real a, a2, b, r, chi2;
930 int** index_com = nullptr; /* the COM removal group atom indices */
931 char** grpname_com = nullptr; /* the COM removal group name */
935 snew(grpname, nrgrp);
937 fprintf(stderr, "\nSelect a group to calculate mean squared displacement for:\n");
938 get_index(&top->atoms, ndx_file, nrgrp, gnx.data(), index, grpname);
944 snew(grpname_com, 1);
946 fprintf(stderr, "\nNow select a group for center of mass removal:\n");
947 get_index(&top->atoms, ndx_file, 1, gnx_com.data(), index_com, grpname_com);
952 index_atom2mol(&gnx[0], index[0], &top->mols);
955 msd = std::make_unique<t_corr>(nrgrp, type, axis, dim_factor, mol_file == nullptr ? 0 : gnx[0],
956 bTen, bMW, dt, top, beginfit, endfit);
958 nat_trx = corr_loop(msd.get(), trx_file, top, pbcType, mol_file ? gnx[0] != 0 : false, gnx.data(),
959 index, (mol_file != nullptr) ? calc1_mol : (bMW ? calc1_mw : calc1_norm),
960 bTen, gnx_com, index_com, dt, t_pdb, pdb_file ? &x : nullptr, box, oenv);
962 /* Correct for the number of points */
963 for (j = 0; (j < msd->ngrp); j++)
965 for (i = 0; (i < msd->nframes); i++)
967 msd->data[j][i] /= msd->ndata[j][i];
970 msmul(msd->datam[j][i], 1.0 / msd->ndata[j][i], msd->datam[j][i]);
977 if (pdb_file && x == nullptr)
980 "\nNo frame found need time tpdb = %g ps\n"
981 "Can not write %s\n\n",
985 top->atoms.nr = nat_trx;
986 if (pdb_file && top->atoms.pdbinfo == nullptr)
988 snew(top->atoms.pdbinfo, top->atoms.nr);
990 printmol(msd.get(), mol_file, pdb_file, index[0], top, x, pbcType, box, oenv);
996 i0 = gmx::roundToInt(0.1 * (msd->nframes - 1));
997 beginfit = msd->time[i0];
1001 for (i0 = 0; i0 < msd->nframes && msd->time[i0] < beginfit; i0++) {}
1006 i1 = gmx::roundToInt(0.9 * (msd->nframes - 1)) + 1;
1007 endfit = msd->time[i1 - 1];
1011 for (i1 = i0; i1 < msd->nframes && msd->time[i1] <= endfit; i1++) {}
1013 fprintf(stdout, "Fitting from %g to %g %s\n\n", beginfit, endfit,
1014 output_env_get_time_unit(oenv).c_str());
1020 "Not enough points for fitting (%d).\n"
1021 "Can not determine the diffusion constant.\n",
1026 DD.resize(msd->ngrp);
1027 SigmaD.resize(msd->ngrp);
1028 for (j = 0; j < msd->ngrp; j++)
1032 lsq_y_ax_b(N / 2, &(msd->time[i0]), &(msd->data[j][i0]), &a, &b, &r, &chi2);
1033 lsq_y_ax_b(N / 2, &(msd->time[i0 + N / 2]), &(msd->data[j][i0 + N / 2]), &a2, &b, &r, &chi2);
1034 SigmaD[j] = std::abs(a - a2);
1040 lsq_y_ax_b(N, &(msd->time[i0]), &(msd->data[j][i0]), &(DD[j]), &b, &r, &chi2);
1041 DD[j] *= diffusionConversionFactor / msd->dim_factor;
1042 SigmaD[j] *= diffusionConversionFactor / msd->dim_factor;
1043 if (DD[j] > 0.01 && DD[j] < 1e4)
1045 fprintf(stdout, "D[%10s] %.4f (+/- %.4f) 1e-5 cm^2/s\n", grpname[j], DD[j], SigmaD[j]);
1049 fprintf(stdout, "D[%10s] %.4g (+/- %.4g) 1e-5 cm^2/s\n", grpname[j], DD[j], SigmaD[j]);
1053 /* Print mean square displacement */
1054 corr_print(msd.get(), bTen, msd_file, "Mean Square Displacement", "MSD (nm\\S2\\N)",
1055 msd->time[msd->nframes - 1], beginfit, endfit, DD.data(), SigmaD.data(), grpname, oenv);
1058 int gmx_msd(int argc, char* argv[])
1060 const char* desc[] = {
1061 "[THISMODULE] computes the mean square displacement (MSD) of atoms from",
1062 "a set of initial positions. This provides an easy way to compute",
1063 "the diffusion constant using the Einstein relation.",
1064 "The time between the reference points for the MSD calculation",
1065 "is set with [TT]-trestart[tt].",
1066 "The diffusion constant is calculated by least squares fitting a",
1067 "straight line (D*t + c) through the MSD(t) from [TT]-beginfit[tt] to",
1068 "[TT]-endfit[tt] (note that t is time from the reference positions,",
1069 "not simulation time). An error estimate given, which is the difference",
1070 "of the diffusion coefficients obtained from fits over the two halves",
1071 "of the fit interval.[PAR]",
1072 "There are three, mutually exclusive, options to determine different",
1073 "types of mean square displacement: [TT]-type[tt], [TT]-lateral[tt]",
1074 "and [TT]-ten[tt]. Option [TT]-ten[tt] writes the full MSD tensor for",
1075 "each group, the order in the output is: trace xx yy zz yx zx zy.[PAR]",
1076 "If [TT]-mol[tt] is set, [THISMODULE] plots the MSD for individual molecules",
1077 "(including making molecules whole across periodic boundaries): ",
1078 "for each individual molecule a diffusion constant is computed for ",
1079 "its center of mass. The chosen index group will be split into ",
1081 "The default way to calculate a MSD is by using mass-weighted averages.",
1082 "This can be turned off with [TT]-nomw[tt].[PAR]",
1083 "With the option [TT]-rmcomm[tt], the center of mass motion of a ",
1084 "specific group can be removed. For trajectories produced with ",
1085 "GROMACS this is usually not necessary, ",
1086 "as [gmx-mdrun] usually already removes the center of mass motion.",
1087 "When you use this option be sure that the whole system is stored",
1088 "in the trajectory file.[PAR]",
1089 "The diffusion coefficient is determined by linear regression of the MSD,",
1090 "where, unlike for the normal output of D, the times are weighted",
1091 "according to the number of reference points, i.e. short times have",
1092 "a higher weight. Also when [TT]-beginfit[tt] is -1, fitting starts at 10%",
1093 "and when [TT]-endfit[tt] is -1, fitting goes to 90%.",
1094 "Using this option one also gets an accurate error estimate",
1095 "based on the statistics between individual molecules.",
1096 "Note that this diffusion coefficient and error estimate are only",
1097 "accurate when the MSD is completely linear between",
1098 "[TT]-beginfit[tt] and [TT]-endfit[tt].[PAR]",
1099 "Option [TT]-pdb[tt] writes a [REF].pdb[ref] file with the coordinates of the frame",
1100 "at time [TT]-tpdb[tt] with in the B-factor field the square root of",
1101 "the diffusion coefficient of the molecule.",
1102 "This option implies option [TT]-mol[tt]."
1104 static const char* normtype[] = { nullptr, "no", "x", "y", "z", nullptr };
1105 static const char* axtitle[] = { nullptr, "no", "x", "y", "z", nullptr };
1106 static int ngroup = 1;
1107 static real dt = 10;
1108 static real t_pdb = 0;
1109 static real beginfit = -1;
1110 static real endfit = -1;
1111 static gmx_bool bTen = FALSE;
1112 static gmx_bool bMW = TRUE;
1113 static gmx_bool bRmCOMM = FALSE;
1115 { "-type", FALSE, etENUM, { normtype }, "Compute diffusion coefficient in one direction" },
1120 "Calculate the lateral diffusion in a plane perpendicular to" },
1121 { "-ten", FALSE, etBOOL, { &bTen }, "Calculate the full tensor" },
1122 { "-ngroup", FALSE, etINT, { &ngroup }, "Number of groups to calculate MSD for" },
1123 { "-mw", FALSE, etBOOL, { &bMW }, "Mass weighted MSD" },
1124 { "-rmcomm", FALSE, etBOOL, { &bRmCOMM }, "Remove center of mass motion" },
1125 { "-tpdb", FALSE, etTIME, { &t_pdb }, "The frame to use for option [TT]-pdb[tt] (%t)" },
1126 { "-trestart", FALSE, etTIME, { &dt }, "Time between restarting points in trajectory (%t)" },
1131 "Start time for fitting the MSD (%t), -1 is 10%" },
1132 { "-endfit", FALSE, etTIME, { &endfit }, "End time for fitting the MSD (%t), -1 is 90%" }
1136 { efTRX, nullptr, nullptr, ffREAD }, { efTPS, nullptr, nullptr, ffREAD },
1137 { efNDX, nullptr, nullptr, ffOPTRD }, { efXVG, nullptr, "msd", ffWRITE },
1138 { efXVG, "-mol", "diff_mol", ffOPTWR }, { efPDB, "-pdb", "diff_mol", ffOPTWR }
1140 #define NFILE asize(fnm)
1145 const char * trx_file, *tps_file, *ndx_file, *msd_file, *mol_file, *pdb_file;
1150 gmx_output_env_t* oenv;
1152 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_BEGIN | PCA_CAN_END | PCA_TIME_UNIT,
1153 NFILE, fnm, asize(pa), pa, asize(desc), desc, 0, nullptr, &oenv))
1157 trx_file = ftp2fn_null(efTRX, NFILE, fnm);
1158 tps_file = ftp2fn_null(efTPS, NFILE, fnm);
1159 ndx_file = ftp2fn_null(efNDX, NFILE, fnm);
1160 msd_file = ftp2fn_null(efXVG, NFILE, fnm);
1161 pdb_file = opt2fn_null("-pdb", NFILE, fnm);
1164 mol_file = opt2fn("-mol", NFILE, fnm);
1168 mol_file = opt2fn_null("-mol", NFILE, fnm);
1173 gmx_fatal(FARGS, "Must have at least 1 group (now %d)", ngroup);
1175 if (mol_file && ngroup > 1)
1177 gmx_fatal(FARGS, "With molecular msd can only have 1 group (now %d)", ngroup);
1184 fprintf(stderr, "Calculating diffusion coefficients for molecules.\n");
1187 GMX_RELEASE_ASSERT(normtype[0] != nullptr, "Options inconsistency; normtype[0] is NULL");
1188 GMX_RELEASE_ASSERT(axtitle[0] != nullptr, "Options inconsistency; axtitle[0] is NULL");
1190 if (normtype[0][0] != 'n')
1192 type = normtype[0][0] - 'x' + X; /* See defines above */
1200 if ((type == NORMAL) && (axtitle[0][0] != 'n'))
1204 axis = (axtitle[0][0] - 'x'); /* See defines above */
1211 if (bTen && type != NORMAL)
1213 gmx_fatal(FARGS, "Can only calculate the full tensor for 3D msd");
1216 bTop = read_tps_conf(tps_file, &top, &pbcType, &xdum, nullptr, box, bMW || bRmCOMM);
1217 if (mol_file && !bTop)
1219 gmx_fatal(FARGS, "Could not read a topology from %s. Try a tpr file instead.", tps_file);
1222 do_corr(trx_file, ndx_file, msd_file, mol_file, pdb_file, t_pdb, ngroup, &top, pbcType, bTen,
1223 bMW, bRmCOMM, type, dim_factor, axis, dt, beginfit, endfit, oenv);
1226 view_all(oenv, NFILE, fnm);