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-2007, 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.
44 #include "gromacs/commandline/filenm.h"
45 #include "gromacs/commandline/pargs.h"
46 #include "gromacs/fileio/matio.h"
47 #include "gromacs/fileio/tpxio.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/gmxlib/nrnb.h"
53 #include "gromacs/math/units.h"
54 #include "gromacs/math/vec.h"
55 #include "gromacs/pbcutil/pbc.h"
56 #include "gromacs/topology/index.h"
57 #include "gromacs/topology/mtop_lookup.h"
58 #include "gromacs/topology/mtop_util.h"
59 #include "gromacs/topology/topology.h"
60 #include "gromacs/trajectory/trajectoryframe.h"
61 #include "gromacs/utility/arraysize.h"
62 #include "gromacs/utility/cstringutil.h"
63 #include "gromacs/utility/fatalerror.h"
64 #include "gromacs/utility/futil.h"
65 #include "gromacs/utility/gmxassert.h"
66 #include "gromacs/utility/smalloc.h"
68 static void clust_size(const char* ndx,
87 const gmx_output_env_t* oenv)
89 FILE * fp, *gp, *hp, *tp;
93 rvec * x = nullptr, *v = nullptr, dx;
95 gmx_bool bSame, bTPRwarn = TRUE;
99 gmx_mtop_t* mtop = nullptr;
100 PbcType pbcType = PbcType::Unset;
103 /* Cluster size distribution (matrix) */
104 real** cs_dist = nullptr;
105 real tf, dx2, cut2, *t_x = nullptr, *t_y, cmid, cmax, cav, ekin;
106 int i, j, k, ai, aj, ci, cj, nframe, nclust, n_x, max_size = 0;
107 int * clust_index, *clust_size, max_clust_size, max_clust_ind, nav, nhisto;
108 t_rgb rlo = { 1.0, 1.0, 1.0 };
109 int frameCounter = 0;
112 clear_trxframe(&fr, TRUE);
113 auto timeLabel = output_env_get_time_label(oenv);
114 tf = output_env_get_time_factor(oenv);
115 fp = xvgropen(ncl, "Number of clusters", timeLabel, "N", oenv);
116 gp = xvgropen(acl, "Average cluster size", timeLabel, "#molecules", oenv);
117 hp = xvgropen(mcl, "Max cluster size", timeLabel, "#molecules", oenv);
118 tp = xvgropen(tempf, "Temperature of largest cluster", timeLabel, "T (K)", oenv);
120 if (!read_first_frame(oenv, &status, trx, &fr, TRX_NEED_X | TRX_READ_V))
130 mtop = new gmx_mtop_t;
131 tpxh = readTpxHeader(tpr, true);
132 if (tpxh.natoms != natoms)
134 gmx_fatal(FARGS, "tpr (%d atoms) and trajectory (%d atoms) do not match!", tpxh.natoms, natoms);
136 pbcType = read_tpx(tpr, nullptr, nullptr, &natoms, nullptr, nullptr, mtop);
144 tfac = ndf / (3.0 * natoms);
147 gmx::RangePartitioning mols;
152 printf("Using molecules rather than atoms. Not reading index file %s\n", ndx);
154 GMX_RELEASE_ASSERT(mtop != nullptr, "Trying to access mtop->mols from NULL mtop pointer");
155 mols = gmx_mtop_molecules(*mtop);
157 /* Make dummy index */
158 nindex = mols.numBlocks();
160 for (i = 0; (i < nindex); i++)
168 rd_index(ndx, 1, &nindex, &index, &gname);
172 snew(clust_index, nindex);
173 snew(clust_size, nindex);
178 for (i = 0; (i < nindex); i++)
187 if ((nskip == 0) || ((nskip > 0) && ((nframe % nskip) == 0)))
191 set_pbc(&pbc, pbcType, fr.box);
196 /* Put all atoms/molecules in their own cluster, with size 1 */
197 for (i = 0; (i < nindex); i++)
199 /* Cluster index is indexed with atom index number */
201 /* Cluster size is indexed with cluster number */
205 /* Loop over atoms */
206 for (i = 0; (i < nindex); i++)
211 /* Loop over atoms (only half a matrix) */
212 for (j = i + 1; (j < nindex); j++)
216 /* If they are not in the same cluster already */
221 /* Compute distance */
224 GMX_RELEASE_ASSERT(mols.numBlocks() > 0,
225 "Cannot access index[] from empty mols");
227 for (ii = mols.block(ai).begin(); !bSame && ii < mols.block(ai).end(); ii++)
229 for (jj = mols.block(aj).begin(); !bSame && jj < mols.block(aj).end(); jj++)
233 pbc_dx(&pbc, x[ii], x[jj], dx);
237 rvec_sub(x[ii], x[jj], dx);
240 bSame = (dx2 < cut2);
248 pbc_dx(&pbc, x[ai], x[aj], dx);
252 rvec_sub(x[ai], x[aj], dx);
255 bSame = (dx2 < cut2);
257 /* If distance less than cut-off */
260 /* Merge clusters: check for all atoms whether they are in
261 * cluster cj and if so, put them in ci
263 for (k = 0; (k < nindex); k++)
265 if (clust_index[k] == cj)
267 if (clust_size[cj] <= 0)
270 "negative cluster size %d for element %d",
295 frameTime = ++frameCounter;
297 t_x[n_x - 1] = frameTime * tf;
298 srenew(cs_dist, n_x);
299 snew(cs_dist[n_x - 1], nindex);
303 for (i = 0; (i < nindex); i++)
306 if (ci > max_clust_size)
314 cs_dist[n_x - 1][ci - 1] += 1.0;
315 max_size = std::max(max_size, ci);
323 fprintf(fp, "%14.6e %10d\n", frameTime, nclust);
326 fprintf(gp, "%14.6e %10.3f\n", frameTime, cav / nav);
328 fprintf(hp, "%14.6e %10d\n", frameTime, max_clust_size);
330 /* Analyse velocities, if present */
337 printf("You need a [REF].tpr[ref] file to analyse temperatures\n");
344 /* Loop over clusters and for each cluster compute 1/2 m v^2 */
345 if (max_clust_ind >= 0)
348 for (i = 0; (i < nindex); i++)
350 if (clust_index[i] == max_clust_ind)
353 real m = mtopGetAtomMass(mtop, ai, &molb);
354 ekin += 0.5 * m * iprod(v[ai], v[ai]);
357 temp = (ekin * 2.0) / (3.0 * tfac * max_clust_size * BOLTZ);
358 fprintf(tp, "%10.3f %10.3f\n", frameTime, temp);
363 } while (read_next_frame(oenv, status, &fr));
371 if (max_clust_ind >= 0)
373 fp = gmx_ffopen(mcn, "w");
374 fprintf(fp, "[ max_clust ]\n");
375 for (i = 0; (i < nindex); i++)
377 if (clust_index[i] == max_clust_ind)
381 GMX_RELEASE_ASSERT(mols.numBlocks() > 0,
382 "Cannot access index[] from empty mols");
383 for (int j : mols.block(i))
385 fprintf(fp, "%d\n", j + 1);
390 fprintf(fp, "%d\n", index[i] + 1);
397 /* Print the real distribution cluster-size/numer, averaged over the trajectory. */
398 fp = xvgropen(histo, "Cluster size distribution", "Cluster size", "()", oenv);
400 fprintf(fp, "%5d %8.3f\n", 0, 0.0);
401 for (j = 0; (j < max_size); j++)
404 for (i = 0; (i < n_x); i++)
406 nelem += cs_dist[i][j];
408 fprintf(fp, "%5d %8.3f\n", j + 1, nelem / n_x);
409 nhisto += static_cast<int>((j + 1) * nelem / n_x);
411 fprintf(fp, "%5d %8.3f\n", j + 1, 0.0);
414 fprintf(stderr, "Total number of atoms in clusters = %d\n", nhisto);
416 /* Look for the smallest entry that is not zero
417 * This will make that zero is white, and not zero is coloured.
421 for (i = 0; (i < n_x); i++)
423 for (j = 0; (j < max_size); j++)
425 if ((cs_dist[i][j] > 0) && (cs_dist[i][j] < cmid))
427 cmid = cs_dist[i][j];
429 cmax = std::max(cs_dist[i][j], cmax);
432 fprintf(stderr, "cmid: %g, cmax: %g, max_size: %d\n", cmid, cmax, max_size);
434 fp = gmx_ffopen(xpm, "w");
437 "Cluster size distribution",
456 for (i = 0; (i < n_x); i++)
458 for (j = 0; (j < max_size); j++)
460 cs_dist[i][j] *= (j + 1);
461 if ((cs_dist[i][j] > 0) && (cs_dist[i][j] < cmid))
463 cmid = cs_dist[i][j];
465 cmax = std::max(cs_dist[i][j], cmax);
468 fprintf(stderr, "cmid: %g, cmax: %g, max_size: %d\n", cmid, cmax, max_size);
469 fp = gmx_ffopen(xpmw, "w");
472 "Weighted cluster size distribution",
492 for (i = 0; (i < n_x); i++)
502 int gmx_clustsize(int argc, char* argv[])
504 const char* desc[] = {
505 "[THISMODULE] computes the size distributions of molecular/atomic clusters in",
506 "the gas phase. The output is given in the form of an [REF].xpm[ref] file.",
507 "The total number of clusters is written to an [REF].xvg[ref] file.[PAR]",
508 "When the [TT]-mol[tt] option is given clusters will be made out of",
509 "molecules rather than atoms, which allows clustering of large molecules.",
510 "In this case an index file would still contain atom numbers",
511 "or your calculation will die with a SEGV.[PAR]",
512 "When velocities are present in your trajectory, the temperature of",
513 "the largest cluster will be printed in a separate [REF].xvg[ref] file assuming",
514 "that the particles are free to move. If you are using constraints,",
515 "please correct the temperature. For instance water simulated with SHAKE",
516 "or SETTLE will yield a temperature that is 1.5 times too low. You can",
517 "compensate for this with the [TT]-ndf[tt] option. Remember to take the removal",
518 "of center of mass motion into account.[PAR]",
519 "The [TT]-mc[tt] option will produce an index file containing the",
520 "atom numbers of the largest cluster."
527 gmx_bool bMol = FALSE;
528 gmx_bool bPBC = TRUE;
529 rvec rlo = { 1.0, 1.0, 0.0 };
530 rvec rhi = { 0.0, 0.0, 1.0 };
532 gmx_output_env_t* oenv;
539 "Largest distance (nm) to be considered in a cluster" },
544 "Cluster molecules rather than atoms (needs [REF].tpr[ref] file)" },
545 { "-pbc", FALSE, etBOOL, { &bPBC }, "Use periodic boundary conditions" },
546 { "-nskip", FALSE, etINT, { &nskip }, "Number of frames to skip between writing" },
551 "Number of levels of grey in [REF].xpm[ref] output" },
556 "Number of degrees of freedom of the entire system for temperature calculation. "
557 "If not set, the number of atoms times three is used." },
562 "RGB values for the color of the lowest occupied cluster size" },
567 "RGB values for the color of the highest occupied cluster size" }
569 #define NPA asize(pa)
570 const char *fnNDX, *fnTPR;
574 { efTRX, "-f", nullptr, ffREAD }, { efTPR, nullptr, nullptr, ffOPTRD },
575 { efNDX, nullptr, nullptr, ffOPTRD }, { efXPM, "-o", "csize", ffWRITE },
576 { efXPM, "-ow", "csizew", ffWRITE }, { efXVG, "-nc", "nclust", ffWRITE },
577 { efXVG, "-mc", "maxclust", ffWRITE }, { efXVG, "-ac", "avclust", ffWRITE },
578 { efXVG, "-hc", "histo-clust", ffWRITE }, { efXVG, "-temp", "temp", ffOPTWR },
579 { efNDX, "-mcn", "maxclust", ffOPTWR }
581 #define NFILE asize(fnm)
583 if (!parse_common_args(
584 &argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME | PCA_TIME_UNIT, NFILE, fnm, NPA, pa, asize(desc), desc, 0, nullptr, &oenv))
589 fnNDX = ftp2fn_null(efNDX, NFILE, fnm);
597 fnTPR = ftp2fn_null(efTPR, NFILE, fnm);
600 gmx_fatal(FARGS, "You need a tpr file for the -mol option");
604 ftp2fn(efTRX, NFILE, fnm),
605 opt2fn("-o", NFILE, fnm),
606 opt2fn("-ow", NFILE, fnm),
607 opt2fn("-nc", NFILE, fnm),
608 opt2fn("-ac", NFILE, fnm),
609 opt2fn("-mc", NFILE, fnm),
610 opt2fn("-hc", NFILE, fnm),
611 opt2fn("-temp", NFILE, fnm),
612 opt2fn("-mcn", NFILE, fnm),
624 output_env_done(oenv);