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, 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.
51 #include "gromacs/fileio/futil.h"
52 #include "gromacs/commandline/pargs.h"
53 #include "gromacs/fileio/tpxio.h"
54 #include "gromacs/fileio/trxio.h"
62 #include "gromacs/fileio/matio.h"
66 static void check_box_c(matrix box)
68 if (fabs(box[ZZ][XX]) > GMX_REAL_EPS*box[ZZ][ZZ] ||
69 fabs(box[ZZ][YY]) > GMX_REAL_EPS*box[ZZ][ZZ])
72 "The last box vector is not parallel to the z-axis: %f %f %f",
73 box[ZZ][XX], box[ZZ][YY], box[ZZ][ZZ]);
77 static void calc_comg(int is, int *coi, int *index, gmx_bool bMass, t_atom *atom,
78 rvec *x, rvec *x_comg)
84 if (bMass && atom == NULL)
86 gmx_fatal(FARGS, "No masses available while mass weighting was requested");
89 for (c = 0; c < is; c++)
93 for (i = coi[c]; i < coi[c+1]; i++)
98 for (d = 0; d < DIM; d++)
100 xc[d] += m*x[index[i]][d];
106 rvec_inc(xc, x[index[i]]);
110 svmul(1/mtot, xc, x_comg[c]);
114 static void split_group(int isize, int *index, char *grpname,
115 t_topology *top, char type,
116 int *is_out, int **coi_out)
118 t_block *mols = NULL;
121 int cur, mol, res, i, a, i1;
123 /* Split up the group in molecules or residues */
130 atom = top->atoms.atom;
133 gmx_fatal(FARGS, "Unknown rdf option '%s'", type);
139 for (i = 0; i < isize; i++)
144 /* Check if the molecule number has changed */
145 i1 = mols->index[mol+1];
149 i1 = mols->index[mol+1];
157 else if (type == 'r')
159 /* Check if the residue index has changed */
160 res = atom[a].resind;
170 printf("Group '%s' of %d atoms consists of %d %s\n",
172 (type == 'm' ? "molecules" : "residues"));
178 static void do_rdf(const char *fnNDX, const char *fnTPS, const char *fnTRX,
179 const char *fnRDF, const char *fnCNRDF, const char *fnHQ,
180 gmx_bool bCM, const char *close,
181 const char **rdft, gmx_bool bXY, gmx_bool bPBC, gmx_bool bNormalize,
182 real cutoff, real binwidth, real fade, int ng,
183 const output_env_t oenv)
187 char outf1[STRLEN], outf2[STRLEN];
188 char title[STRLEN], gtitle[STRLEN], refgt[30];
189 int g, natoms, i, ii, j, k, nbin, j0, j1, n, nframes;
192 int *isize, isize_cm = 0, nrdf = 0, max_i, isize0, isize_g;
193 atom_id **index, *index_cm = NULL;
194 #if (defined SIZEOF_LONG_LONG_INT) && (SIZEOF_LONG_LONG_INT >= 8)
199 real t, rmax2, cut2, r, r2, r2ii, invhbinw, normfac;
200 real segvol, spherevol, prev_spherevol, **rdf;
201 rvec *x, dx, *x0 = NULL, *x_i1, xi;
202 real *inv_segvol, invvol, invvol_sum, rho;
203 gmx_bool bClose, *bExcl, bTop, bNonSelfExcl;
206 atom_id ix, jx, ***pairs;
207 t_topology *top = NULL;
208 int ePBC = -1, ePBCrdf = -1;
209 t_block *mols = NULL;
213 gmx_rmpbc_t gpbc = NULL;
214 int *is = NULL, **coi = NULL, cur, mol, i1, res, a;
218 bClose = (close[0] != 'n');
223 bTop = read_tps_conf(fnTPS, title, top, &ePBC, &x, NULL, box, TRUE);
224 if (bTop && !(bCM || bClose))
226 /* get exclusions from topology */
227 excl = &(top->excls);
233 fprintf(stderr, "\nSelect a reference group and %d group%s\n",
234 ng, ng == 1 ? "" : "s");
237 get_index(&(top->atoms), fnNDX, ng+1, isize, index, grpname);
238 atom = top->atoms.atom;
242 rd_index(fnNDX, ng+1, isize, index, grpname);
251 split_group(isize[0], index[0], grpname[0], top, close[0], &is[0], &coi[0]);
254 if (rdft[0][0] != 'a')
256 /* Split up all the groups in molecules or residues */
259 for (g = ((bCM || bClose) ? 1 : 0); g < ng+1; g++)
261 split_group(isize[g], index[g], grpname[g], top, rdft[0][0], &is[g], &coi[g]);
268 snew(coi[0], is[0]+1);
270 coi[0][1] = isize[0];
274 else if (bClose || rdft[0][0] != 'a')
284 natoms = read_first_x(oenv, &status, fnTRX, &t, &x, box);
287 gmx_fatal(FARGS, "Could not read coordinates from statusfile\n");
291 /* check with topology */
292 if (natoms > top->atoms.nr)
294 gmx_fatal(FARGS, "Trajectory (%d atoms) does not match topology (%d atoms)",
295 natoms, top->atoms.nr);
298 /* check with index groups */
299 for (i = 0; i < ng+1; i++)
301 for (j = 0; j < isize[i]; j++)
303 if (index[i][j] >= natoms)
305 gmx_fatal(FARGS, "Atom index (%d) in index group %s (%d atoms) larger "
306 "than number of atoms in trajectory (%d atoms)",
307 index[i][j], grpname[i], isize[i], natoms);
312 /* initialize some handy things */
315 ePBC = guess_ePBC(box);
317 copy_mat(box, box_pbc);
324 case epbcXY: ePBCrdf = epbcXY; break;
325 case epbcNONE: ePBCrdf = epbcNONE; break;
327 gmx_fatal(FARGS, "xy-rdf's are not supported for pbc type'%s'",
331 /* Make sure the z-height does not influence the cut-off */
332 box_pbc[ZZ][ZZ] = 2*max(box[XX][XX], box[YY][YY]);
340 rmax2 = 0.99*0.99*max_cutoff2(bXY ? epbcXY : epbcXYZ, box_pbc);
344 rmax2 = sqr(3*max(box[XX][XX], max(box[YY][YY], box[ZZ][ZZ])));
348 fprintf(debug, "rmax2 = %g\n", rmax2);
351 /* We use the double amount of bins, so we can correctly
352 * write the rdf and rdf_cn output at i*binwidth values.
354 nbin = (int)(sqrt(rmax2) * 2 / binwidth);
355 invhbinw = 2.0 / binwidth;
364 for (g = 0; g < ng; g++)
366 if (isize[g+1] > max_i)
371 /* this is THE array */
372 snew(count[g], nbin+1);
374 /* make pairlist array for groups and exclusions */
375 snew(pairs[g], isize[0]);
376 snew(npairs[g], isize[0]);
377 for (i = 0; i < isize[0]; i++)
379 /* We can only have exclusions with atomic rdfs */
380 if (!(bCM || bClose || rdft[0][0] != 'a'))
383 for (j = 0; j < natoms; j++)
390 for (j = excl->index[ix]; j < excl->index[ix+1]; j++)
392 bExcl[excl->a[j]] = TRUE;
396 snew(pairs[g][i], isize[g+1]);
397 bNonSelfExcl = FALSE;
398 for (j = 0; j < isize[g+1]; j++)
403 pairs[g][i][k++] = jx;
407 /* Check if we have exclusions other than self exclusions */
414 srenew(pairs[g][i], npairs[g][i]);
418 /* Save a LOT of memory and some cpu cycles */
434 if (bPBC && (NULL != top))
436 gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
441 /* Must init pbc every step because of pressure coupling */
442 copy_mat(box, box_pbc);
447 gmx_rmpbc(gpbc, natoms, box, x);
452 clear_rvec(box_pbc[ZZ]);
454 set_pbc(&pbc, ePBCrdf, box_pbc);
458 /* Set z-size to 1 so we get the surface iso the volume */
462 invvol = 1/det(box_pbc);
463 invvol_sum += invvol;
467 /* Calculate center of mass of the whole group */
468 calc_comg(is[0], coi[0], index[0], TRUE, atom, x, x0);
470 else if (!bClose && rdft[0][0] != 'a')
472 calc_comg(is[0], coi[0], index[0], rdft[0][6] == 'm', atom, x, x0);
475 for (g = 0; g < ng; g++)
477 if (rdft[0][0] == 'a')
479 /* Copy the indexed coordinates to a continuous array */
480 for (i = 0; i < isize[g+1]; i++)
482 copy_rvec(x[index[g+1][i]], x_i1[i]);
487 /* Calculate the COMs/COGs and store in x_i1 */
488 calc_comg(is[g+1], coi[g+1], index[g+1], rdft[0][6] == 'm', atom, x, x_i1);
491 for (i = 0; i < isize0; i++)
495 /* Special loop, since we need to determine the minimum distance
496 * over all selected atoms in the reference molecule/residue.
498 if (rdft[0][0] == 'a')
500 isize_g = isize[g+1];
506 for (j = 0; j < isize_g; j++)
509 /* Loop over the selected atoms in the reference molecule */
510 for (ii = coi[0][i]; ii < coi[0][i+1]; ii++)
514 pbc_dx(&pbc, x[index[0][ii]], x_i1[j], dx);
518 rvec_sub(x[index[0][ii]], x_i1[j], dx);
522 r2ii = dx[XX]*dx[XX] + dx[YY]*dx[YY];
526 r2ii = iprod(dx, dx);
533 if (r2 > cut2 && r2 <= rmax2)
535 count[g][(int)(sqrt(r2)*invhbinw)]++;
541 /* Real rdf between points in space */
542 if (bCM || rdft[0][0] != 'a')
544 copy_rvec(x0[i], xi);
548 copy_rvec(x[index[0][i]], xi);
550 if (rdft[0][0] == 'a' && npairs[g][i] >= 0)
552 /* Expensive loop, because of indexing */
553 for (j = 0; j < npairs[g][i]; j++)
558 pbc_dx(&pbc, xi, x[jx], dx);
562 rvec_sub(xi, x[jx], dx);
567 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
573 if (r2 > cut2 && r2 <= rmax2)
575 count[g][(int)(sqrt(r2)*invhbinw)]++;
581 /* Cheaper loop, no exclusions */
582 if (rdft[0][0] == 'a')
584 isize_g = isize[g+1];
590 for (j = 0; j < isize_g; j++)
594 pbc_dx(&pbc, xi, x_i1[j], dx);
598 rvec_sub(xi, x_i1[j], dx);
602 r2 = dx[XX]*dx[XX] + dx[YY]*dx[YY];
608 if (r2 > cut2 && r2 <= rmax2)
610 count[g][(int)(sqrt(r2)*invhbinw)]++;
619 while (read_next_x(oenv, status, &t, x, box));
620 fprintf(stderr, "\n");
622 if (bPBC && (NULL != top))
624 gmx_rmpbc_done(gpbc);
632 invvol = invvol_sum/nframes;
634 /* Calculate volume of sphere segments or length of circle segments */
635 snew(inv_segvol, (nbin+1)/2);
637 for (i = 0; (i < (nbin+1)/2); i++)
639 r = (i + 0.5)*binwidth;
642 spherevol = M_PI*r*r;
646 spherevol = (4.0/3.0)*M_PI*r*r*r;
648 segvol = spherevol-prev_spherevol;
649 inv_segvol[i] = 1.0/segvol;
650 prev_spherevol = spherevol;
654 for (g = 0; g < ng; g++)
656 /* We have to normalize by dividing by the number of frames */
657 if (rdft[0][0] == 'a')
659 normfac = 1.0/(nframes*invvol*isize0*isize[g+1]);
663 normfac = 1.0/(nframes*invvol*isize0*is[g+1]);
666 /* Do the normalization */
667 nrdf = max((nbin+1)/2, 1+2*fade/binwidth);
669 for (i = 0; i < (nbin+1)/2; i++)
678 j = count[g][i*2-1] + count[g][i*2];
680 if ((fade > 0) && (r >= fade))
682 rdf[g][i] = 1 + (j*inv_segvol[i]*normfac-1)*exp(-16*sqr(r/fade-1));
688 rdf[g][i] = j*inv_segvol[i]*normfac;
692 rdf[g][i] = j/(binwidth*isize0*nframes);
696 for (; (i < nrdf); i++)
702 if (rdft[0][0] == 'a')
704 sprintf(gtitle, "Radial distribution");
708 sprintf(gtitle, "Radial distribution of %s %s",
709 rdft[0][0] == 'm' ? "molecule" : "residue",
710 rdft[0][6] == 'm' ? "COM" : "COG");
712 fp = xvgropen(fnRDF, gtitle, "r", "", oenv);
715 sprintf(refgt, " %s", "COM");
719 sprintf(refgt, " closest atom in %s.", close);
723 sprintf(refgt, "%s", "");
727 if (output_env_get_print_xvgr_codes(oenv))
729 fprintf(fp, "@ subtitle \"%s%s - %s\"\n", grpname[0], refgt, grpname[1]);
734 if (output_env_get_print_xvgr_codes(oenv))
736 fprintf(fp, "@ subtitle \"reference %s%s\"\n", grpname[0], refgt);
738 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
740 for (i = 0; (i < nrdf); i++)
742 fprintf(fp, "%10g", i*binwidth);
743 for (g = 0; g < ng; g++)
745 fprintf(fp, " %10g", rdf[g][i]);
751 do_view(oenv, fnRDF, NULL);
753 /* h(Q) function: fourier transform of rdf */
757 real *hq, *integrand, Q;
759 /* Get a better number density later! */
760 rho = isize[1]*invvol;
762 snew(integrand, nrdf);
763 for (i = 0; (i < nhq); i++)
767 for (j = 1; (j < nrdf); j++)
770 integrand[j] = (Q == 0) ? 1.0 : sin(Q*r)/(Q*r);
771 integrand[j] *= 4.0*M_PI*rho*r*r*(rdf[0][j]-1.0);
773 hq[i] = print_and_integrate(debug, nrdf, binwidth, integrand, NULL, 0);
775 fp = xvgropen(fnHQ, "h(Q)", "Q(/nm)", "h(Q)", oenv);
776 for (i = 0; (i < nhq); i++)
778 fprintf(fp, "%10g %10g\n", i*0.5, hq[i]);
781 do_view(oenv, fnHQ, NULL);
788 normfac = 1.0/(isize0*nframes);
789 fp = xvgropen(fnCNRDF, "Cumulative Number RDF", "r", "number", oenv);
792 if (output_env_get_print_xvgr_codes(oenv))
794 fprintf(fp, "@ subtitle \"%s-%s\"\n", grpname[0], grpname[1]);
799 if (output_env_get_print_xvgr_codes(oenv))
801 fprintf(fp, "@ subtitle \"reference %s\"\n", grpname[0]);
803 xvgr_legend(fp, ng, (const char**)(grpname+1), oenv);
806 for (i = 0; (i <= nbin/2); i++)
808 fprintf(fp, "%10g", i*binwidth);
809 for (g = 0; g < ng; g++)
811 fprintf(fp, " %10g", (real)((double)sum[g]*normfac));
814 sum[g] += count[g][i*2] + count[g][i*2+1];
822 do_view(oenv, fnCNRDF, NULL);
825 for (g = 0; g < ng; g++)
833 int gmx_rdf(int argc, char *argv[])
835 const char *desc[] = {
836 "The structure of liquids can be studied by either neutron or X-ray",
837 "scattering. The most common way to describe liquid structure is by a",
838 "radial distribution function. However, this is not easy to obtain from",
839 "a scattering experiment.[PAR]",
840 "[THISMODULE] calculates radial distribution functions in different ways.",
841 "The normal method is around a (set of) particle(s), the other methods",
842 "are around the center of mass of a set of particles ([TT]-com[tt])",
843 "or to the closest particle in a set ([TT]-surf[tt]).",
844 "With all methods, the RDF can also be calculated around axes parallel",
845 "to the [IT]z[it]-axis with option [TT]-xy[tt].",
846 "With option [TT]-surf[tt] normalization can not be used.[PAR]",
847 "The option [TT]-rdf[tt] sets the type of RDF to be computed.",
848 "Default is for atoms or particles, but one can also select center",
849 "of mass or geometry of molecules or residues. In all cases, only",
850 "the atoms in the index groups are taken into account.",
851 "For molecules and/or the center of mass option, a run input file",
853 "Weighting other than COM or COG can currently only be achieved",
854 "by providing a run input file with different masses.",
855 "Options [TT]-com[tt] and [TT]-surf[tt] also work in conjunction",
856 "with [TT]-rdf[tt].[PAR]",
857 "If a run input file is supplied ([TT]-s[tt]) and [TT]-rdf[tt] is set",
858 "to [TT]atom[tt], exclusions defined",
859 "in that file are taken into account when calculating the RDF.",
860 "The option [TT]-cut[tt] is meant as an alternative way to avoid",
861 "intramolecular peaks in the RDF plot.",
862 "It is however better to supply a run input file with a higher number of",
863 "exclusions. For e.g. benzene a topology, setting nrexcl to 5",
864 "would eliminate all intramolecular contributions to the RDF.",
865 "Note that all atoms in the selected groups are used, also the ones",
866 "that don't have Lennard-Jones interactions.[PAR]",
867 "Option [TT]-cn[tt] produces the cumulative number RDF,",
868 "i.e. the average number of particles within a distance r.[PAR]"
870 static gmx_bool bCM = FALSE, bXY = FALSE, bPBC = TRUE, bNormalize = TRUE;
871 static real cutoff = 0, binwidth = 0.002, fade = 0.0;
872 static int ngroups = 1;
874 static const char *closet[] = { NULL, "no", "mol", "res", NULL };
875 static const char *rdft[] = { NULL, "atom", "mol_com", "mol_cog", "res_com", "res_cog", NULL };
878 { "-bin", FALSE, etREAL, {&binwidth},
880 { "-com", FALSE, etBOOL, {&bCM},
881 "RDF with respect to the center of mass of first group" },
882 { "-surf", FALSE, etENUM, {closet},
883 "RDF with respect to the surface of the first group" },
884 { "-rdf", FALSE, etENUM, {rdft},
886 { "-pbc", FALSE, etBOOL, {&bPBC},
887 "Use periodic boundary conditions for computing distances. Without PBC the maximum range will be three times the largest box edge." },
888 { "-norm", FALSE, etBOOL, {&bNormalize},
889 "Normalize for volume and density" },
890 { "-xy", FALSE, etBOOL, {&bXY},
891 "Use only the x and y components of the distance" },
892 { "-cut", FALSE, etREAL, {&cutoff},
893 "Shortest distance (nm) to be considered"},
894 { "-ng", FALSE, etINT, {&ngroups},
895 "Number of secondary groups to compute RDFs around a central group" },
896 { "-fade", FALSE, etREAL, {&fade},
897 "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." }
899 #define NPA asize(pa)
900 const char *fnTPS, *fnNDX;
904 { efTRX, "-f", NULL, ffREAD },
905 { efTPS, NULL, NULL, ffOPTRD },
906 { efNDX, NULL, NULL, ffOPTRD },
907 { efXVG, "-o", "rdf", ffWRITE },
908 { efXVG, "-cn", "rdf_cn", ffOPTWR },
909 { efXVG, "-hq", "hq", ffOPTWR },
911 #define NFILE asize(fnm)
912 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME | PCA_BE_NICE,
913 NFILE, fnm, NPA, pa, asize(desc), desc, 0, NULL, &oenv))
918 if (bCM || closet[0][0] != 'n' || rdft[0][0] == 'm' || rdft[0][6] == 'm')
920 fnTPS = ftp2fn(efTPS, NFILE, fnm);
924 fnTPS = ftp2fn_null(efTPS, NFILE, fnm);
926 fnNDX = ftp2fn_null(efNDX, NFILE, fnm);
928 if (!fnTPS && !fnNDX)
930 gmx_fatal(FARGS, "Neither index file nor topology file specified\n"
934 if (closet[0][0] != 'n')
938 gmx_fatal(FARGS, "Can not have both -com and -surf");
942 fprintf(stderr, "Turning of normalization because of option -surf\n");
947 do_rdf(fnNDX, fnTPS, ftp2fn(efTRX, NFILE, fnm),
948 opt2fn("-o", NFILE, fnm), opt2fn_null("-cn", NFILE, fnm),
949 opt2fn_null("-hq", NFILE, fnm),
950 bCM, closet[0], rdft, bXY, bPBC, bNormalize, cutoff, binwidth, fade, ngroups,