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.
44 #include "gromacs/legacyheaders/typedefs.h"
45 #include "gromacs/legacyheaders/macros.h"
46 #include "gromacs/math/vec.h"
47 #include "gromacs/pbcutil/pbc.h"
48 #include "gromacs/legacyheaders/viewit.h"
49 #include "gromacs/utility/futil.h"
50 #include "gromacs/fileio/tpxio.h"
51 #include "gromacs/fileio/trxio.h"
52 #include "gromacs/topology/index.h"
53 #include "gromacs/legacyheaders/nrnb.h"
54 #include "gromacs/legacyheaders/coulomb.h"
57 #include "gromacs/legacyheaders/names.h"
59 #include "gromacs/commandline/pargs.h"
60 #include "gromacs/fileio/xvgr.h"
61 #include "gromacs/pbcutil/rmpbc.h"
62 #include "gromacs/utility/fatalerror.h"
63 #include "gromacs/utility/smalloc.h"
65 static void check_box_c(matrix box)
67 if (fabs(box[ZZ][XX]) > GMX_REAL_EPS*box[ZZ][ZZ] ||
68 fabs(box[ZZ][YY]) > GMX_REAL_EPS*box[ZZ][ZZ])
71 "The last box vector is not parallel to the z-axis: %f %f %f",
72 box[ZZ][XX], box[ZZ][YY], box[ZZ][ZZ]);
76 static void calc_comg(int is, int *coi, int *index, gmx_bool bMass, t_atom *atom,
77 rvec *x, rvec *x_comg)
83 if (bMass && atom == NULL)
85 gmx_fatal(FARGS, "No masses available while mass weighting was requested");
88 for (c = 0; c < is; c++)
92 for (i = coi[c]; i < coi[c+1]; i++)
97 for (d = 0; d < DIM; d++)
99 xc[d] += m*x[index[i]][d];
105 rvec_inc(xc, x[index[i]]);
109 svmul(1/mtot, xc, x_comg[c]);
113 static void split_group(int isize, int *index, char *grpname,
114 t_topology *top, char type,
115 int *is_out, int **coi_out)
117 t_block *mols = NULL;
120 int cur, mol, res, i, a, i1;
122 /* Split up the group in molecules or residues */
129 atom = top->atoms.atom;
132 gmx_fatal(FARGS, "Unknown rdf option '%s'", type);
138 for (i = 0; i < isize; i++)
143 /* Check if the molecule number has changed */
144 i1 = mols->index[mol+1];
148 i1 = mols->index[mol+1];
156 else if (type == 'r')
158 /* Check if the residue index has changed */
159 res = atom[a].resind;
169 printf("Group '%s' of %d atoms consists of %d %s\n",
171 (type == 'm' ? "molecules" : "residues"));
177 static void do_rdf(const char *fnNDX, const char *fnTPS, const char *fnTRX,
178 const char *fnRDF, const char *fnCNRDF, const char *fnHQ,
179 gmx_bool bCM, const char *close,
180 const char **rdft, gmx_bool bXY, gmx_bool bPBC, gmx_bool bNormalize,
181 real cutoff, real binwidth, real fade, int ng,
182 const output_env_t oenv)
186 char outf1[STRLEN], outf2[STRLEN];
187 char title[STRLEN], gtitle[STRLEN], refgt[30];
188 int g, natoms, i, ii, j, k, nbin, j0, j1, n, nframes;
191 int *isize, isize_cm = 0, nrdf = 0, max_i, isize0, isize_g;
192 atom_id **index, *index_cm = NULL;
194 real t, rmax2, cut2, r, r2, r2ii, invhbinw, normfac;
195 real segvol, spherevol, prev_spherevol, **rdf;
196 rvec *x, dx, *x0 = NULL, *x_i1, xi;
197 real *inv_segvol, invvol, invvol_sum, rho;
198 gmx_bool bClose, *bExcl, bTop, bNonSelfExcl;
201 atom_id ix, jx, ***pairs;
202 t_topology *top = NULL;
203 int ePBC = -1, ePBCrdf = -1;
204 t_block *mols = NULL;
208 gmx_rmpbc_t gpbc = NULL;
209 int *is = NULL, **coi = NULL, cur, mol, i1, res, a;
213 bClose = (close[0] != 'n');
218 bTop = read_tps_conf(fnTPS, title, top, &ePBC, &x, NULL, box, TRUE);
219 if (bTop && !(bCM || bClose))
221 /* get exclusions from topology */
222 excl = &(top->excls);
228 fprintf(stderr, "\nSelect a reference group and %d group%s\n",
229 ng, ng == 1 ? "" : "s");
232 get_index(&(top->atoms), fnNDX, ng+1, isize, index, grpname);
233 atom = top->atoms.atom;
237 rd_index(fnNDX, ng+1, isize, index, grpname);
246 split_group(isize[0], index[0], grpname[0], top, close[0], &is[0], &coi[0]);
249 if (rdft[0][0] != 'a')
251 /* Split up all the groups in molecules or residues */
254 for (g = ((bCM || bClose) ? 1 : 0); g < ng+1; g++)
256 split_group(isize[g], index[g], grpname[g], top, rdft[0][0], &is[g], &coi[g]);
263 snew(coi[0], is[0]+1);
265 coi[0][1] = isize[0];
269 else if (bClose || rdft[0][0] != 'a')
279 natoms = read_first_x(oenv, &status, fnTRX, &t, &x, box);
282 gmx_fatal(FARGS, "Could not read coordinates from statusfile\n");
286 /* check with topology */
287 if (natoms > top->atoms.nr)
289 gmx_fatal(FARGS, "Trajectory (%d atoms) does not match topology (%d atoms)",
290 natoms, top->atoms.nr);
293 /* check with index groups */
294 for (i = 0; i < ng+1; i++)
296 for (j = 0; j < isize[i]; j++)
298 if (index[i][j] >= natoms)
300 gmx_fatal(FARGS, "Atom index (%d) in index group %s (%d atoms) larger "
301 "than number of atoms in trajectory (%d atoms)",
302 index[i][j], grpname[i], isize[i], natoms);
307 /* initialize some handy things */
310 ePBC = guess_ePBC(box);
312 copy_mat(box, box_pbc);
319 case epbcXY: ePBCrdf = epbcXY; break;
320 case epbcNONE: ePBCrdf = epbcNONE; break;
322 gmx_fatal(FARGS, "xy-rdf's are not supported for pbc type'%s'",
326 /* Make sure the z-height does not influence the cut-off */
327 box_pbc[ZZ][ZZ] = 2*max(box[XX][XX], box[YY][YY]);
335 rmax2 = 0.99*0.99*max_cutoff2(bXY ? epbcXY : epbcXYZ, box_pbc);
339 rmax2 = sqr(3*max(box[XX][XX], max(box[YY][YY], box[ZZ][ZZ])));
343 fprintf(debug, "rmax2 = %g\n", rmax2);
346 /* We use the double amount of bins, so we can correctly
347 * write the rdf and rdf_cn output at i*binwidth values.
349 nbin = (int)(sqrt(rmax2) * 2 / binwidth);
350 invhbinw = 2.0 / binwidth;
359 for (g = 0; g < ng; g++)
361 if (isize[g+1] > max_i)
366 /* this is THE array */
367 snew(count[g], nbin+1);
369 /* make pairlist array for groups and exclusions */
370 snew(pairs[g], isize[0]);
371 snew(npairs[g], isize[0]);
372 for (i = 0; i < isize[0]; i++)
374 /* We can only have exclusions with atomic rdfs */
375 if (!(bCM || bClose || rdft[0][0] != 'a'))
378 for (j = 0; j < natoms; j++)
385 for (j = excl->index[ix]; j < excl->index[ix+1]; j++)
387 bExcl[excl->a[j]] = TRUE;
391 snew(pairs[g][i], isize[g+1]);
392 bNonSelfExcl = FALSE;
393 for (j = 0; j < isize[g+1]; j++)
398 pairs[g][i][k++] = jx;
402 /* Check if we have exclusions other than self exclusions */
409 srenew(pairs[g][i], npairs[g][i]);
413 /* Save a LOT of memory and some cpu cycles */
429 if (bPBC && (NULL != top))
431 gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
436 /* Must init pbc every step because of pressure coupling */
437 copy_mat(box, box_pbc);
442 gmx_rmpbc(gpbc, natoms, box, x);
447 clear_rvec(box_pbc[ZZ]);
449 set_pbc(&pbc, ePBCrdf, box_pbc);
453 /* Set z-size to 1 so we get the surface iso the volume */
457 invvol = 1/det(box_pbc);
458 invvol_sum += invvol;
462 /* Calculate center of mass of the whole group */
463 calc_comg(is[0], coi[0], index[0], TRUE, atom, x, x0);
465 else if (!bClose && rdft[0][0] != 'a')
467 calc_comg(is[0], coi[0], index[0], rdft[0][6] == 'm', atom, x, x0);
470 for (g = 0; g < ng; g++)
472 if (rdft[0][0] == 'a')
474 /* Copy the indexed coordinates to a continuous array */
475 for (i = 0; i < isize[g+1]; i++)
477 copy_rvec(x[index[g+1][i]], x_i1[i]);
482 /* Calculate the COMs/COGs and store in x_i1 */
483 calc_comg(is[g+1], coi[g+1], index[g+1], rdft[0][6] == 'm', atom, x, x_i1);
486 for (i = 0; i < isize0; i++)
490 /* Special loop, since we need to determine the minimum distance
491 * over all selected atoms in the reference molecule/residue.
493 if (rdft[0][0] == 'a')
495 isize_g = isize[g+1];
501 for (j = 0; j < isize_g; j++)
504 /* Loop over the selected atoms in the reference molecule */
505 for (ii = coi[0][i]; ii < coi[0][i+1]; ii++)
509 pbc_dx(&pbc, x[index[0][ii]], x_i1[j], dx);
513 rvec_sub(x[index[0][ii]], x_i1[j], dx);
517 r2ii = dx[XX]*dx[XX] + dx[YY]*dx[YY];
521 r2ii = iprod(dx, dx);
528 if (r2 > cut2 && r2 <= rmax2)
530 count[g][(int)(sqrt(r2)*invhbinw)]++;
536 /* Real rdf between points in space */
537 if (bCM || rdft[0][0] != 'a')
539 copy_rvec(x0[i], xi);
543 copy_rvec(x[index[0][i]], xi);
545 if (rdft[0][0] == 'a' && npairs[g][i] >= 0)
547 /* Expensive loop, because of indexing */
548 for (j = 0; j < npairs[g][i]; j++)
553 pbc_dx(&pbc, xi, x[jx], dx);
557 rvec_sub(xi, x[jx], dx);
562 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
568 if (r2 > cut2 && r2 <= rmax2)
570 count[g][(int)(sqrt(r2)*invhbinw)]++;
576 /* Cheaper loop, no exclusions */
577 if (rdft[0][0] == 'a')
579 isize_g = isize[g+1];
585 for (j = 0; j < isize_g; j++)
589 pbc_dx(&pbc, xi, x_i1[j], dx);
593 rvec_sub(xi, x_i1[j], dx);
597 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
603 if (r2 > cut2 && r2 <= rmax2)
605 count[g][(int)(sqrt(r2)*invhbinw)]++;
614 while (read_next_x(oenv, status, &t, x, box));
615 fprintf(stderr, "\n");
617 if (bPBC && (NULL != top))
619 gmx_rmpbc_done(gpbc);
627 invvol = invvol_sum/nframes;
629 /* Calculate volume of sphere segments or length of circle segments */
630 snew(inv_segvol, (nbin+1)/2);
632 for (i = 0; (i < (nbin+1)/2); i++)
634 r = (i + 0.5)*binwidth;
637 spherevol = M_PI*r*r;
641 spherevol = (4.0/3.0)*M_PI*r*r*r;
643 segvol = spherevol-prev_spherevol;
644 inv_segvol[i] = 1.0/segvol;
645 prev_spherevol = spherevol;
649 for (g = 0; g < ng; g++)
651 /* We have to normalize by dividing by the number of frames */
652 if (rdft[0][0] == 'a')
654 normfac = 1.0/(nframes*invvol*isize0*isize[g+1]);
658 normfac = 1.0/(nframes*invvol*isize0*is[g+1]);
661 /* Do the normalization */
662 nrdf = max((nbin+1)/2, 1+2*fade/binwidth);
664 for (i = 0; i < (nbin+1)/2; i++)
673 j = count[g][i*2-1] + count[g][i*2];
675 if ((fade > 0) && (r >= fade))
677 rdf[g][i] = 1 + (j*inv_segvol[i]*normfac-1)*exp(-16*sqr(r/fade-1));
683 rdf[g][i] = j*inv_segvol[i]*normfac;
687 rdf[g][i] = j/(binwidth*isize0*nframes);
691 for (; (i < nrdf); i++)
697 if (rdft[0][0] == 'a')
699 sprintf(gtitle, "Radial distribution");
703 sprintf(gtitle, "Radial distribution of %s %s",
704 rdft[0][0] == 'm' ? "molecule" : "residue",
705 rdft[0][6] == 'm' ? "COM" : "COG");
707 fp = xvgropen(fnRDF, gtitle, "r", "", oenv);
710 sprintf(refgt, " %s", "COM");
714 sprintf(refgt, " closest atom in %s.", close);
718 sprintf(refgt, "%s", "");
722 if (output_env_get_print_xvgr_codes(oenv))
724 fprintf(fp, "@ subtitle \"%s%s - %s\"\n", grpname[0], refgt, grpname[1]);
729 if (output_env_get_print_xvgr_codes(oenv))
731 fprintf(fp, "@ subtitle \"reference %s%s\"\n", grpname[0], refgt);
733 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
735 for (i = 0; (i < nrdf); i++)
737 fprintf(fp, "%10g", i*binwidth);
738 for (g = 0; g < ng; g++)
740 fprintf(fp, " %10g", rdf[g][i]);
746 do_view(oenv, fnRDF, NULL);
748 /* h(Q) function: fourier transform of rdf */
752 real *hq, *integrand, Q;
754 /* Get a better number density later! */
755 rho = isize[1]*invvol;
757 snew(integrand, nrdf);
758 for (i = 0; (i < nhq); i++)
762 for (j = 1; (j < nrdf); j++)
765 integrand[j] = (Q == 0) ? 1.0 : sin(Q*r)/(Q*r);
766 integrand[j] *= 4.0*M_PI*rho*r*r*(rdf[0][j]-1.0);
768 hq[i] = print_and_integrate(debug, nrdf, binwidth, integrand, NULL, 0);
770 fp = xvgropen(fnHQ, "h(Q)", "Q(/nm)", "h(Q)", oenv);
771 for (i = 0; (i < nhq); i++)
773 fprintf(fp, "%10g %10g\n", i*0.5, hq[i]);
776 do_view(oenv, fnHQ, NULL);
783 normfac = 1.0/(isize0*nframes);
784 fp = xvgropen(fnCNRDF, "Cumulative Number RDF", "r", "number", oenv);
787 if (output_env_get_print_xvgr_codes(oenv))
789 fprintf(fp, "@ subtitle \"%s-%s\"\n", grpname[0], grpname[1]);
794 if (output_env_get_print_xvgr_codes(oenv))
796 fprintf(fp, "@ subtitle \"reference %s\"\n", grpname[0]);
798 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
801 for (i = 0; (i <= nbin/2); i++)
803 fprintf(fp, "%10g", i*binwidth);
804 for (g = 0; g < ng; g++)
806 fprintf(fp, " %10g", (real)((double)sum[g]*normfac));
809 sum[g] += count[g][i*2] + count[g][i*2+1];
817 do_view(oenv, fnCNRDF, NULL);
820 for (g = 0; g < ng; g++)
828 int gmx_rdf(int argc, char *argv[])
830 const char *desc[] = {
831 "The structure of liquids can be studied by either neutron or X-ray",
832 "scattering. The most common way to describe liquid structure is by a",
833 "radial distribution function. However, this is not easy to obtain from",
834 "a scattering experiment.[PAR]",
835 "[THISMODULE] calculates radial distribution functions in different ways.",
836 "The normal method is around a (set of) particle(s), the other methods",
837 "are around the center of mass of a set of particles ([TT]-com[tt])",
838 "or to the closest particle in a set ([TT]-surf[tt]).",
839 "With all methods, the RDF can also be calculated around axes parallel",
840 "to the [IT]z[it]-axis with option [TT]-xy[tt].",
841 "With option [TT]-surf[tt] normalization can not be used.[PAR]",
842 "The option [TT]-rdf[tt] sets the type of RDF to be computed.",
843 "Default is for atoms or particles, but one can also select center",
844 "of mass or geometry of molecules or residues. In all cases, only",
845 "the atoms in the index groups are taken into account.",
846 "For molecules and/or the center of mass option, a run input file",
848 "Weighting other than COM or COG can currently only be achieved",
849 "by providing a run input file with different masses.",
850 "Options [TT]-com[tt] and [TT]-surf[tt] also work in conjunction",
851 "with [TT]-rdf[tt].[PAR]",
852 "If a run input file is supplied ([TT]-s[tt]) and [TT]-rdf[tt] is set",
853 "to [TT]atom[tt], exclusions defined",
854 "in that file are taken into account when calculating the RDF.",
855 "The option [TT]-cut[tt] is meant as an alternative way to avoid",
856 "intramolecular peaks in the RDF plot.",
857 "It is however better to supply a run input file with a higher number of",
858 "exclusions. For e.g. benzene a topology, setting nrexcl to 5",
859 "would eliminate all intramolecular contributions to the RDF.",
860 "Note that all atoms in the selected groups are used, also the ones",
861 "that don't have Lennard-Jones interactions.[PAR]",
862 "Option [TT]-cn[tt] produces the cumulative number RDF,",
863 "i.e. the average number of particles within a distance r.[PAR]"
865 static gmx_bool bCM = FALSE, bXY = FALSE, bPBC = TRUE, bNormalize = TRUE;
866 static real cutoff = 0, binwidth = 0.002, fade = 0.0;
867 static int ngroups = 1;
869 static const char *closet[] = { NULL, "no", "mol", "res", NULL };
870 static const char *rdft[] = { NULL, "atom", "mol_com", "mol_cog", "res_com", "res_cog", NULL };
873 { "-bin", FALSE, etREAL, {&binwidth},
875 { "-com", FALSE, etBOOL, {&bCM},
876 "RDF with respect to the center of mass of first group" },
877 { "-surf", FALSE, etENUM, {closet},
878 "RDF with respect to the surface of the first group" },
879 { "-rdf", FALSE, etENUM, {rdft},
881 { "-pbc", FALSE, etBOOL, {&bPBC},
882 "Use periodic boundary conditions for computing distances. Without PBC the maximum range will be three times the largest box edge." },
883 { "-norm", FALSE, etBOOL, {&bNormalize},
884 "Normalize for volume and density" },
885 { "-xy", FALSE, etBOOL, {&bXY},
886 "Use only the x and y components of the distance" },
887 { "-cut", FALSE, etREAL, {&cutoff},
888 "Shortest distance (nm) to be considered"},
889 { "-ng", FALSE, etINT, {&ngroups},
890 "Number of secondary groups to compute RDFs around a central group" },
891 { "-fade", FALSE, etREAL, {&fade},
892 "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." }
894 #define NPA asize(pa)
895 const char *fnTPS, *fnNDX;
899 { efTRX, "-f", NULL, ffREAD },
900 { efTPS, NULL, NULL, ffOPTRD },
901 { efNDX, NULL, NULL, ffOPTRD },
902 { efXVG, "-o", "rdf", ffWRITE },
903 { efXVG, "-cn", "rdf_cn", ffOPTWR },
904 { efXVG, "-hq", "hq", ffOPTWR },
906 #define NFILE asize(fnm)
907 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME,
908 NFILE, fnm, NPA, pa, asize(desc), desc, 0, NULL, &oenv))
913 if (bCM || closet[0][0] != 'n' || rdft[0][0] == 'm' || rdft[0][6] == 'm')
915 fnTPS = ftp2fn(efTPS, NFILE, fnm);
919 fnTPS = ftp2fn_null(efTPS, NFILE, fnm);
921 fnNDX = ftp2fn_null(efNDX, NFILE, fnm);
923 if (!fnTPS && !fnNDX)
925 gmx_fatal(FARGS, "Neither index file nor topology file specified\n"
929 if (closet[0][0] != 'n')
933 gmx_fatal(FARGS, "Can not have both -com and -surf");
937 fprintf(stderr, "Turning of normalization because of option -surf\n");
942 do_rdf(fnNDX, fnTPS, ftp2fn(efTRX, NFILE, fnm),
943 opt2fn("-o", NFILE, fnm), opt2fn_null("-cn", NFILE, fnm),
944 opt2fn_null("-hq", NFILE, fnm),
945 bCM, closet[0], rdft, bXY, bPBC, bNormalize, cutoff, binwidth, fade, ngroups,