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, 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.
42 #include "gromacs/commandline/pargs.h"
43 #include "gromacs/fileio/tpxio.h"
44 #include "gromacs/fileio/trxio.h"
45 #include "gromacs/fileio/xvgr.h"
46 #include "gromacs/gmxana/gmx_ana.h"
47 #include "gromacs/gmxana/gstat.h"
48 #include "gromacs/legacyheaders/coulomb.h"
49 #include "gromacs/legacyheaders/macros.h"
50 #include "gromacs/legacyheaders/names.h"
51 #include "gromacs/legacyheaders/nrnb.h"
52 #include "gromacs/legacyheaders/typedefs.h"
53 #include "gromacs/legacyheaders/viewit.h"
54 #include "gromacs/math/vec.h"
55 #include "gromacs/pbcutil/pbc.h"
56 #include "gromacs/pbcutil/rmpbc.h"
57 #include "gromacs/topology/index.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/futil.h"
60 #include "gromacs/utility/smalloc.h"
62 static void check_box_c(matrix box)
64 if (fabs(box[ZZ][XX]) > GMX_REAL_EPS*box[ZZ][ZZ] ||
65 fabs(box[ZZ][YY]) > GMX_REAL_EPS*box[ZZ][ZZ])
68 "The last box vector is not parallel to the z-axis: %f %f %f",
69 box[ZZ][XX], box[ZZ][YY], box[ZZ][ZZ]);
73 static void calc_comg(int is, int *coi, int *index, gmx_bool bMass, t_atom *atom,
74 rvec *x, rvec *x_comg)
80 if (bMass && atom == NULL)
82 gmx_fatal(FARGS, "No masses available while mass weighting was requested");
85 for (c = 0; c < is; c++)
89 for (i = coi[c]; i < coi[c+1]; i++)
94 for (d = 0; d < DIM; d++)
96 xc[d] += m*x[index[i]][d];
102 rvec_inc(xc, x[index[i]]);
106 svmul(1/mtot, xc, x_comg[c]);
110 static void split_group(int isize, int *index, char *grpname,
111 t_topology *top, char type,
112 int *is_out, int **coi_out)
114 t_block *mols = NULL;
117 int cur, mol, res, i, a, i1;
119 /* Split up the group in molecules or residues */
126 atom = top->atoms.atom;
129 gmx_fatal(FARGS, "Unknown rdf option '%s'", type);
135 for (i = 0; i < isize; i++)
140 /* Check if the molecule number has changed */
141 i1 = mols->index[mol+1];
145 i1 = mols->index[mol+1];
153 else if (type == 'r')
155 /* Check if the residue index has changed */
156 res = atom[a].resind;
166 printf("Group '%s' of %d atoms consists of %d %s\n",
168 (type == 'm' ? "molecules" : "residues"));
174 static void do_rdf(const char *fnNDX, const char *fnTPS, const char *fnTRX,
175 const char *fnRDF, const char *fnCNRDF, const char *fnHQ,
176 gmx_bool bCM, const char *close,
177 const char **rdft, gmx_bool bXY, gmx_bool bPBC, gmx_bool bNormalize,
178 real cutoff, real binwidth, real fade, int ng,
179 const output_env_t oenv)
183 char outf1[STRLEN], outf2[STRLEN];
184 char title[STRLEN], gtitle[STRLEN], refgt[30];
185 int g, natoms, i, ii, j, k, nbin, j0, j1, n, nframes;
188 int *isize, isize_cm = 0, nrdf = 0, max_i, isize0, isize_g;
189 atom_id **index, *index_cm = NULL;
191 real t, rmax2, cut2, r, r2, r2ii, invhbinw, normfac;
192 real segvol, spherevol, prev_spherevol, **rdf;
193 rvec *x, dx, *x0 = NULL, *x_i1, xi;
194 real *inv_segvol, invvol, invvol_sum, rho;
195 gmx_bool bClose, *bExcl, bTop, bNonSelfExcl;
198 atom_id ix, jx, ***pairs;
199 t_topology *top = NULL;
200 int ePBC = -1, ePBCrdf = -1;
201 t_block *mols = NULL;
205 gmx_rmpbc_t gpbc = NULL;
206 int *is = NULL, **coi = NULL, cur, mol, i1, res, a;
210 bClose = (close[0] != 'n');
215 bTop = read_tps_conf(fnTPS, title, top, &ePBC, &x, NULL, box, TRUE);
216 if (bTop && !(bCM || bClose))
218 /* get exclusions from topology */
219 excl = &(top->excls);
225 fprintf(stderr, "\nSelect a reference group and %d group%s\n",
226 ng, ng == 1 ? "" : "s");
229 get_index(&(top->atoms), fnNDX, ng+1, isize, index, grpname);
230 atom = top->atoms.atom;
234 rd_index(fnNDX, ng+1, isize, index, grpname);
243 split_group(isize[0], index[0], grpname[0], top, close[0], &is[0], &coi[0]);
246 if (rdft[0][0] != 'a')
248 /* Split up all the groups in molecules or residues */
251 for (g = ((bCM || bClose) ? 1 : 0); g < ng+1; g++)
253 split_group(isize[g], index[g], grpname[g], top, rdft[0][0], &is[g], &coi[g]);
260 snew(coi[0], is[0]+1);
262 coi[0][1] = isize[0];
266 else if (bClose || rdft[0][0] != 'a')
276 natoms = read_first_x(oenv, &status, fnTRX, &t, &x, box);
279 gmx_fatal(FARGS, "Could not read coordinates from statusfile\n");
283 /* check with topology */
284 if (natoms > top->atoms.nr)
286 gmx_fatal(FARGS, "Trajectory (%d atoms) does not match topology (%d atoms)",
287 natoms, top->atoms.nr);
290 /* check with index groups */
291 for (i = 0; i < ng+1; i++)
293 for (j = 0; j < isize[i]; j++)
295 if (index[i][j] >= natoms)
297 gmx_fatal(FARGS, "Atom index (%d) in index group %s (%d atoms) larger "
298 "than number of atoms in trajectory (%d atoms)",
299 index[i][j], grpname[i], isize[i], natoms);
304 /* initialize some handy things */
307 ePBC = guess_ePBC(box);
309 copy_mat(box, box_pbc);
316 case epbcXY: ePBCrdf = epbcXY; break;
317 case epbcNONE: ePBCrdf = epbcNONE; break;
319 gmx_fatal(FARGS, "xy-rdf's are not supported for pbc type'%s'",
323 /* Make sure the z-height does not influence the cut-off */
324 box_pbc[ZZ][ZZ] = 2*max(box[XX][XX], box[YY][YY]);
332 rmax2 = 0.99*0.99*max_cutoff2(bXY ? epbcXY : epbcXYZ, box_pbc);
336 rmax2 = sqr(3*max(box[XX][XX], max(box[YY][YY], box[ZZ][ZZ])));
340 fprintf(debug, "rmax2 = %g\n", rmax2);
343 /* We use the double amount of bins, so we can correctly
344 * write the rdf and rdf_cn output at i*binwidth values.
346 nbin = (int)(sqrt(rmax2) * 2 / binwidth);
347 invhbinw = 2.0 / binwidth;
356 for (g = 0; g < ng; g++)
358 if (isize[g+1] > max_i)
363 /* this is THE array */
364 snew(count[g], nbin+1);
366 /* make pairlist array for groups and exclusions */
367 snew(pairs[g], isize[0]);
368 snew(npairs[g], isize[0]);
369 for (i = 0; i < isize[0]; i++)
371 /* We can only have exclusions with atomic rdfs */
372 if (!(bCM || bClose || rdft[0][0] != 'a'))
375 for (j = 0; j < natoms; j++)
382 for (j = excl->index[ix]; j < excl->index[ix+1]; j++)
384 bExcl[excl->a[j]] = TRUE;
388 snew(pairs[g][i], isize[g+1]);
389 bNonSelfExcl = FALSE;
390 for (j = 0; j < isize[g+1]; j++)
395 pairs[g][i][k++] = jx;
399 /* Check if we have exclusions other than self exclusions */
406 srenew(pairs[g][i], npairs[g][i]);
410 /* Save a LOT of memory and some cpu cycles */
426 if (bPBC && (NULL != top))
428 gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
433 /* Must init pbc every step because of pressure coupling */
434 copy_mat(box, box_pbc);
439 gmx_rmpbc(gpbc, natoms, box, x);
444 clear_rvec(box_pbc[ZZ]);
446 set_pbc(&pbc, ePBCrdf, box_pbc);
450 /* Set z-size to 1 so we get the surface iso the volume */
454 invvol = 1/det(box_pbc);
455 invvol_sum += invvol;
459 /* Calculate center of mass of the whole group */
460 calc_comg(is[0], coi[0], index[0], TRUE, atom, x, x0);
462 else if (!bClose && rdft[0][0] != 'a')
464 calc_comg(is[0], coi[0], index[0], rdft[0][6] == 'm', atom, x, x0);
467 for (g = 0; g < ng; g++)
469 if (rdft[0][0] == 'a')
471 /* Copy the indexed coordinates to a continuous array */
472 for (i = 0; i < isize[g+1]; i++)
474 copy_rvec(x[index[g+1][i]], x_i1[i]);
479 /* Calculate the COMs/COGs and store in x_i1 */
480 calc_comg(is[g+1], coi[g+1], index[g+1], rdft[0][6] == 'm', atom, x, x_i1);
483 for (i = 0; i < isize0; i++)
487 /* Special loop, since we need to determine the minimum distance
488 * over all selected atoms in the reference molecule/residue.
490 if (rdft[0][0] == 'a')
492 isize_g = isize[g+1];
498 for (j = 0; j < isize_g; j++)
501 /* Loop over the selected atoms in the reference molecule */
502 for (ii = coi[0][i]; ii < coi[0][i+1]; ii++)
506 pbc_dx(&pbc, x[index[0][ii]], x_i1[j], dx);
510 rvec_sub(x[index[0][ii]], x_i1[j], dx);
514 r2ii = dx[XX]*dx[XX] + dx[YY]*dx[YY];
518 r2ii = iprod(dx, dx);
525 if (r2 > cut2 && r2 <= rmax2)
527 count[g][(int)(sqrt(r2)*invhbinw)]++;
533 /* Real rdf between points in space */
534 if (bCM || rdft[0][0] != 'a')
536 copy_rvec(x0[i], xi);
540 copy_rvec(x[index[0][i]], xi);
542 if (rdft[0][0] == 'a' && npairs[g][i] >= 0)
544 /* Expensive loop, because of indexing */
545 for (j = 0; j < npairs[g][i]; j++)
550 pbc_dx(&pbc, xi, x[jx], dx);
554 rvec_sub(xi, x[jx], dx);
559 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
565 if (r2 > cut2 && r2 <= rmax2)
567 count[g][(int)(sqrt(r2)*invhbinw)]++;
573 /* Cheaper loop, no exclusions */
574 if (rdft[0][0] == 'a')
576 isize_g = isize[g+1];
582 for (j = 0; j < isize_g; j++)
586 pbc_dx(&pbc, xi, x_i1[j], dx);
590 rvec_sub(xi, x_i1[j], dx);
594 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
600 if (r2 > cut2 && r2 <= rmax2)
602 count[g][(int)(sqrt(r2)*invhbinw)]++;
611 while (read_next_x(oenv, status, &t, x, box));
612 fprintf(stderr, "\n");
614 if (bPBC && (NULL != top))
616 gmx_rmpbc_done(gpbc);
624 invvol = invvol_sum/nframes;
626 /* Calculate volume of sphere segments or length of circle segments */
627 snew(inv_segvol, (nbin+1)/2);
629 for (i = 0; (i < (nbin+1)/2); i++)
631 r = (i + 0.5)*binwidth;
634 spherevol = M_PI*r*r;
638 spherevol = (4.0/3.0)*M_PI*r*r*r;
640 segvol = spherevol-prev_spherevol;
641 inv_segvol[i] = 1.0/segvol;
642 prev_spherevol = spherevol;
646 for (g = 0; g < ng; g++)
648 /* We have to normalize by dividing by the number of frames */
649 if (rdft[0][0] == 'a')
651 normfac = 1.0/(nframes*invvol*isize0*isize[g+1]);
655 normfac = 1.0/(nframes*invvol*isize0*is[g+1]);
658 /* Do the normalization */
659 nrdf = max((nbin+1)/2, 1+2*fade/binwidth);
661 for (i = 0; i < (nbin+1)/2; i++)
670 j = count[g][i*2-1] + count[g][i*2];
672 if ((fade > 0) && (r >= fade))
674 rdf[g][i] = 1 + (j*inv_segvol[i]*normfac-1)*exp(-16*sqr(r/fade-1));
680 rdf[g][i] = j*inv_segvol[i]*normfac;
684 rdf[g][i] = j/(binwidth*isize0*nframes);
688 for (; (i < nrdf); i++)
694 if (rdft[0][0] == 'a')
696 sprintf(gtitle, "Radial distribution");
700 sprintf(gtitle, "Radial distribution of %s %s",
701 rdft[0][0] == 'm' ? "molecule" : "residue",
702 rdft[0][6] == 'm' ? "COM" : "COG");
704 fp = xvgropen(fnRDF, gtitle, "r", "", oenv);
707 sprintf(refgt, " %s", "COM");
711 sprintf(refgt, " closest atom in %s.", close);
715 sprintf(refgt, "%s", "");
719 if (output_env_get_print_xvgr_codes(oenv))
721 fprintf(fp, "@ subtitle \"%s%s - %s\"\n", grpname[0], refgt, grpname[1]);
726 if (output_env_get_print_xvgr_codes(oenv))
728 fprintf(fp, "@ subtitle \"reference %s%s\"\n", grpname[0], refgt);
730 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
732 for (i = 0; (i < nrdf); i++)
734 fprintf(fp, "%10g", i*binwidth);
735 for (g = 0; g < ng; g++)
737 fprintf(fp, " %10g", rdf[g][i]);
743 do_view(oenv, fnRDF, NULL);
745 /* h(Q) function: fourier transform of rdf */
749 real *hq, *integrand, Q;
751 /* Get a better number density later! */
752 rho = isize[1]*invvol;
754 snew(integrand, nrdf);
755 for (i = 0; (i < nhq); i++)
759 for (j = 1; (j < nrdf); j++)
762 integrand[j] = (Q == 0) ? 1.0 : sin(Q*r)/(Q*r);
763 integrand[j] *= 4.0*M_PI*rho*r*r*(rdf[0][j]-1.0);
765 hq[i] = print_and_integrate(debug, nrdf, binwidth, integrand, NULL, 0);
767 fp = xvgropen(fnHQ, "h(Q)", "Q(/nm)", "h(Q)", oenv);
768 for (i = 0; (i < nhq); i++)
770 fprintf(fp, "%10g %10g\n", i*0.5, hq[i]);
773 do_view(oenv, fnHQ, NULL);
780 normfac = 1.0/(isize0*nframes);
781 fp = xvgropen(fnCNRDF, "Cumulative Number RDF", "r", "number", oenv);
784 if (output_env_get_print_xvgr_codes(oenv))
786 fprintf(fp, "@ subtitle \"%s-%s\"\n", grpname[0], grpname[1]);
791 if (output_env_get_print_xvgr_codes(oenv))
793 fprintf(fp, "@ subtitle \"reference %s\"\n", grpname[0]);
795 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
798 for (i = 0; (i <= nbin/2); i++)
800 fprintf(fp, "%10g", i*binwidth);
801 for (g = 0; g < ng; g++)
803 fprintf(fp, " %10g", (real)((double)sum[g]*normfac));
806 sum[g] += count[g][i*2] + count[g][i*2+1];
814 do_view(oenv, fnCNRDF, NULL);
817 for (g = 0; g < ng; g++)
825 int gmx_rdf(int argc, char *argv[])
827 const char *desc[] = {
828 "The structure of liquids can be studied by either neutron or X-ray",
829 "scattering. The most common way to describe liquid structure is by a",
830 "radial distribution function. However, this is not easy to obtain from",
831 "a scattering experiment.[PAR]",
832 "[THISMODULE] calculates radial distribution functions in different ways.",
833 "The normal method is around a (set of) particle(s), the other methods",
834 "are around the center of mass of a set of particles ([TT]-com[tt])",
835 "or to the closest particle in a set ([TT]-surf[tt]).",
836 "With all methods, the RDF can also be calculated around axes parallel",
837 "to the [IT]z[it]-axis with option [TT]-xy[tt].",
838 "With option [TT]-surf[tt] normalization can not be used.[PAR]",
839 "The option [TT]-rdf[tt] sets the type of RDF to be computed.",
840 "Default is for atoms or particles, but one can also select center",
841 "of mass or geometry of molecules or residues. In all cases, only",
842 "the atoms in the index groups are taken into account.",
843 "For molecules and/or the center of mass option, a run input file",
845 "Weighting other than COM or COG can currently only be achieved",
846 "by providing a run input file with different masses.",
847 "Options [TT]-com[tt] and [TT]-surf[tt] also work in conjunction",
848 "with [TT]-rdf[tt].[PAR]",
849 "If a run input file is supplied ([TT]-s[tt]) and [TT]-rdf[tt] is set",
850 "to [TT]atom[tt], exclusions defined",
851 "in that file are taken into account when calculating the RDF.",
852 "The option [TT]-cut[tt] is meant as an alternative way to avoid",
853 "intramolecular peaks in the RDF plot.",
854 "It is however better to supply a run input file with a higher number of",
855 "exclusions. For e.g. benzene a topology, setting nrexcl to 5",
856 "would eliminate all intramolecular contributions to the RDF.",
857 "Note that all atoms in the selected groups are used, also the ones",
858 "that don't have Lennard-Jones interactions.[PAR]",
859 "Option [TT]-cn[tt] produces the cumulative number RDF,",
860 "i.e. the average number of particles within a distance r.[PAR]"
862 static gmx_bool bCM = FALSE, bXY = FALSE, bPBC = TRUE, bNormalize = TRUE;
863 static real cutoff = 0, binwidth = 0.002, fade = 0.0;
864 static int ngroups = 1;
866 static const char *closet[] = { NULL, "no", "mol", "res", NULL };
867 static const char *rdft[] = { NULL, "atom", "mol_com", "mol_cog", "res_com", "res_cog", NULL };
870 { "-bin", FALSE, etREAL, {&binwidth},
872 { "-com", FALSE, etBOOL, {&bCM},
873 "RDF with respect to the center of mass of first group" },
874 { "-surf", FALSE, etENUM, {closet},
875 "RDF with respect to the surface of the first group" },
876 { "-rdf", FALSE, etENUM, {rdft},
878 { "-pbc", FALSE, etBOOL, {&bPBC},
879 "Use periodic boundary conditions for computing distances. Without PBC the maximum range will be three times the largest box edge." },
880 { "-norm", FALSE, etBOOL, {&bNormalize},
881 "Normalize for volume and density" },
882 { "-xy", FALSE, etBOOL, {&bXY},
883 "Use only the x and y components of the distance" },
884 { "-cut", FALSE, etREAL, {&cutoff},
885 "Shortest distance (nm) to be considered"},
886 { "-ng", FALSE, etINT, {&ngroups},
887 "Number of secondary groups to compute RDFs around a central group" },
888 { "-fade", FALSE, etREAL, {&fade},
889 "From this distance onwards the RDF is tranformed by g'(r) = 1 + [g(r)-1] exp(-(r/fade-1)^2 to make it go to 1 smoothly. If fade is 0.0 nothing is done." }
891 #define NPA asize(pa)
892 const char *fnTPS, *fnNDX;
896 { efTRX, "-f", NULL, ffREAD },
897 { efTPS, NULL, NULL, ffOPTRD },
898 { efNDX, NULL, NULL, ffOPTRD },
899 { efXVG, "-o", "rdf", ffWRITE },
900 { efXVG, "-cn", "rdf_cn", ffOPTWR },
901 { efXVG, "-hq", "hq", ffOPTWR },
903 #define NFILE asize(fnm)
904 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME,
905 NFILE, fnm, NPA, pa, asize(desc), desc, 0, NULL, &oenv))
910 if (bCM || closet[0][0] != 'n' || rdft[0][0] == 'm' || rdft[0][6] == 'm')
912 fnTPS = ftp2fn(efTPS, NFILE, fnm);
916 fnTPS = ftp2fn_null(efTPS, NFILE, fnm);
918 fnNDX = ftp2fn_null(efNDX, NFILE, fnm);
920 if (!fnTPS && !fnNDX)
922 gmx_fatal(FARGS, "Neither index file nor topology file specified\n"
926 if (closet[0][0] != 'n')
930 gmx_fatal(FARGS, "Can not have both -com and -surf");
934 fprintf(stderr, "Turning of normalization because of option -surf\n");
939 do_rdf(fnNDX, fnTPS, ftp2fn(efTRX, NFILE, fnm),
940 opt2fn("-o", NFILE, fnm), opt2fn_null("-cn", NFILE, fnm),
941 opt2fn_null("-hq", NFILE, fnm),
942 bCM, closet[0], rdft, bXY, bPBC, bNormalize, cutoff, binwidth, fade, ngroups,