Split lines with many copyright years
[gromacs.git] / src / gromacs / gmxana / gmx_potential.cpp
blob943ab872ee76f7d075a2a48b21d29eb240b124a4
1 /*
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.
38 #include "gmxpre.h"
40 #include <cctype>
41 #include <cmath>
42 #include <cstring>
44 #include "gromacs/commandline/pargs.h"
45 #include "gromacs/commandline/viewit.h"
46 #include "gromacs/fileio/trxio.h"
47 #include "gromacs/fileio/xvgr.h"
48 #include "gromacs/gmxana/gmx_ana.h"
49 #include "gromacs/gmxana/princ.h"
50 #include "gromacs/math/functions.h"
51 #include "gromacs/math/utilities.h"
52 #include "gromacs/math/vec.h"
53 #include "gromacs/pbcutil/rmpbc.h"
54 #include "gromacs/topology/index.h"
55 #include "gromacs/topology/topology.h"
56 #include "gromacs/utility/arraysize.h"
57 #include "gromacs/utility/cstringutil.h"
58 #include "gromacs/utility/fatalerror.h"
59 #include "gromacs/utility/futil.h"
60 #include "gromacs/utility/smalloc.h"
62 #define EPS0 8.85419E-12
63 #define ELC 1.60219E-19
65 /****************************************************************************/
66 /* This program calculates the electrostatic potential across the box by */
67 /* determining the charge density in slices of the box and integrating these*/
68 /* twice. */
69 /* Peter Tieleman, April 1995 */
70 /* It now also calculates electrostatic potential in spherical micelles, */
71 /* using \frac{1}{r}\frac{d^2r\Psi}{r^2} = - \frac{\rho}{\epsilon_0} */
72 /* This probably sucks but it seems to work. */
73 /****************************************************************************/
75 static int ce = 0, cb = 0;
77 /* this routine integrates the array data and returns the resulting array */
78 /* routine uses simple trapezoid rule */
79 static void p_integrate(double* result, const double data[], int ndata, double slWidth)
81 int i, slice;
82 double sum;
84 if (ndata <= 2)
86 fprintf(stderr,
87 "Warning: nr of slices very small. This will result"
88 "in nonsense.\n");
91 fprintf(stderr, "Integrating from slice %d to slice %d\n", cb, ndata - ce);
93 for (slice = cb; slice < (ndata - ce); slice++)
95 sum = 0;
96 for (i = cb; i < slice; i++)
98 sum += slWidth * (data[i] + 0.5 * (data[i + 1] - data[i]));
100 result[slice] = sum;
104 static void calc_potential(const char* fn,
105 int** index,
106 int gnx[],
107 double*** slPotential,
108 double*** slCharge,
109 double*** slField,
110 int* nslices,
111 const t_topology* top,
112 int ePBC,
113 int axis,
114 int nr_grps,
115 double* slWidth,
116 double fudge_z,
117 gmx_bool bSpherical,
118 gmx_bool bCorrect,
119 const gmx_output_env_t* oenv)
121 rvec* x0; /* coordinates without pbc */
122 matrix box; /* box (3x3) */
123 int natoms; /* nr. atoms in trj */
124 t_trxstatus* status;
125 int i, n, /* loop indices */
126 teller = 0, ax1 = 0, ax2 = 0, nr_frames = 0, /* number of frames */
127 slice; /* current slice */
128 double slVolume; /* volume of slice for spherical averaging */
129 double qsum, nn;
130 real t;
131 double z;
132 rvec xcm;
133 gmx_rmpbc_t gpbc = nullptr;
135 switch (axis)
137 case 0:
138 ax1 = 1;
139 ax2 = 2;
140 break;
141 case 1:
142 ax1 = 0;
143 ax2 = 2;
144 break;
145 case 2:
146 ax1 = 0;
147 ax2 = 1;
148 break;
149 default: gmx_fatal(FARGS, "Invalid axes. Terminating\n");
152 if ((natoms = read_first_x(oenv, &status, fn, &t, &x0, box)) == 0)
154 gmx_fatal(FARGS, "Could not read coordinates from statusfile\n");
157 if (!*nslices)
159 *nslices = static_cast<int>(box[axis][axis] * 10.0); /* default value */
161 fprintf(stderr, "\nDividing the box in %d slices\n", *nslices);
163 snew(*slField, nr_grps);
164 snew(*slCharge, nr_grps);
165 snew(*slPotential, nr_grps);
167 for (i = 0; i < nr_grps; i++)
169 snew((*slField)[i], *nslices);
170 snew((*slCharge)[i], *nslices);
171 snew((*slPotential)[i], *nslices);
175 gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
177 /*********** Start processing trajectory ***********/
180 *slWidth = box[axis][axis] / static_cast<real>((*nslices));
181 teller++;
182 gmx_rmpbc(gpbc, natoms, box, x0);
184 /* calculate position of center of mass based on group 1 */
185 calc_xcm(x0, gnx[0], index[0], top->atoms.atom, xcm, FALSE);
186 svmul(-1, xcm, xcm);
188 for (n = 0; n < nr_grps; n++)
190 /* Check whether we actually have all positions of the requested index
191 * group in the trajectory file */
192 if (gnx[n] > natoms)
194 gmx_fatal(FARGS,
195 "You selected a group with %d atoms, but only %d atoms\n"
196 "were found in the trajectory.\n",
197 gnx[n], natoms);
199 for (i = 0; i < gnx[n]; i++) /* loop over all atoms in index file */
201 if (bSpherical)
203 rvec_add(x0[index[n][i]], xcm, x0[index[n][i]]);
204 /* only distance from origin counts, not sign */
205 slice = static_cast<int>(norm(x0[index[n][i]]) / (*slWidth));
207 /* this is a nice check for spherical groups but not for
208 all water in a cubic box since a lot will fall outside
209 the sphere
210 if (slice > (*nslices))
212 fprintf(stderr,"Warning: slice = %d\n",slice);
215 (*slCharge)[n][slice] += top->atoms.atom[index[n][i]].q;
217 else
219 z = x0[index[n][i]][axis];
220 z = z + fudge_z;
221 if (z < 0)
223 z += box[axis][axis];
225 if (z > box[axis][axis])
227 z -= box[axis][axis];
229 /* determine which slice atom is in */
230 slice = static_cast<int>((z / (*slWidth)));
231 (*slCharge)[n][slice] += top->atoms.atom[index[n][i]].q;
235 nr_frames++;
236 } while (read_next_x(oenv, status, &t, x0, box));
238 gmx_rmpbc_done(gpbc);
240 /*********** done with status file **********/
241 close_trx(status);
243 /* slCharge now contains the total charge per slice, summed over all
244 frames. Now divide by nr_frames and integrate twice
248 if (bSpherical)
250 fprintf(stderr,
251 "\n\nRead %d frames from trajectory. Calculating potential"
252 "in spherical coordinates\n",
253 nr_frames);
255 else
257 fprintf(stderr, "\n\nRead %d frames from trajectory. Calculating potential\n", nr_frames);
260 for (n = 0; n < nr_grps; n++)
262 for (i = 0; i < *nslices; i++)
264 if (bSpherical)
266 /* charge per volume is now the summed charge, divided by the nr
267 of frames and by the volume of the slice it's in, 4pi r^2 dr
269 slVolume = 4 * M_PI * gmx::square(i) * gmx::square(*slWidth) * *slWidth;
270 if (slVolume == 0)
272 (*slCharge)[n][i] = 0;
274 else
276 (*slCharge)[n][i] = (*slCharge)[n][i] / (nr_frames * slVolume);
279 else
281 /* get charge per volume */
282 (*slCharge)[n][i] = (*slCharge)[n][i] * (*nslices)
283 / (static_cast<real>(nr_frames) * box[axis][axis]
284 * box[ax1][ax1] * box[ax2][ax2]);
287 /* Now we have charge densities */
290 if (bCorrect && !bSpherical)
292 for (n = 0; n < nr_grps; n++)
294 nn = 0;
295 qsum = 0;
296 for (i = 0; i < *nslices; i++)
298 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
300 nn++;
301 qsum += (*slCharge)[n][i];
304 qsum /= nn;
305 for (i = 0; i < *nslices; i++)
307 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
309 (*slCharge)[n][i] -= qsum;
315 for (n = 0; n < nr_grps; n++)
317 /* integrate twice to get field and potential */
318 p_integrate((*slField)[n], (*slCharge)[n], *nslices, *slWidth);
322 if (bCorrect && !bSpherical)
324 for (n = 0; n < nr_grps; n++)
326 nn = 0;
327 qsum = 0;
328 for (i = 0; i < *nslices; i++)
330 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
332 nn++;
333 qsum += (*slField)[n][i];
336 qsum /= nn;
337 for (i = 0; i < *nslices; i++)
339 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
341 (*slField)[n][i] -= qsum;
347 for (n = 0; n < nr_grps; n++)
349 p_integrate((*slPotential)[n], (*slField)[n], *nslices, *slWidth);
352 /* Now correct for eps0 and in spherical case for r*/
353 for (n = 0; n < nr_grps; n++)
355 for (i = 0; i < *nslices; i++)
357 if (bSpherical)
359 (*slPotential)[n][i] = ELC * (*slPotential)[n][i] * -1.0E9 / (EPS0 * i * (*slWidth));
360 (*slField)[n][i] = ELC * (*slField)[n][i] * 1E18 / (EPS0 * i * (*slWidth));
362 else
364 (*slPotential)[n][i] = ELC * (*slPotential)[n][i] * -1.0E9 / EPS0;
365 (*slField)[n][i] = ELC * (*slField)[n][i] * 1E18 / EPS0;
370 sfree(x0); /* free memory used by coordinate array */
373 static void plot_potential(double* potential[],
374 double* charge[],
375 double* field[],
376 const char* afile,
377 const char* bfile,
378 const char* cfile,
379 int nslices,
380 int nr_grps,
381 const char* const grpname[],
382 double slWidth,
383 const gmx_output_env_t* oenv)
385 FILE *pot, /* xvgr file with potential */
386 *cha, /* xvgr file with charges */
387 *fie; /* xvgr files with fields */
388 char buf[256]; /* for xvgr title */
389 int slice, n;
391 sprintf(buf, "Electrostatic Potential");
392 pot = xvgropen(afile, buf, "Box (nm)", "Potential (V)", oenv);
393 xvgr_legend(pot, nr_grps, grpname, oenv);
395 sprintf(buf, "Charge Distribution");
396 cha = xvgropen(bfile, buf, "Box (nm)", "Charge density (q/nm\\S3\\N)", oenv);
397 xvgr_legend(cha, nr_grps, grpname, oenv);
399 sprintf(buf, "Electric Field");
400 fie = xvgropen(cfile, buf, "Box (nm)", "Field (V/nm)", oenv);
401 xvgr_legend(fie, nr_grps, grpname, oenv);
403 for (slice = cb; slice < (nslices - ce); slice++)
405 fprintf(pot, "%20.16g ", slice * slWidth);
406 fprintf(cha, "%20.16g ", slice * slWidth);
407 fprintf(fie, "%20.16g ", slice * slWidth);
408 for (n = 0; n < nr_grps; n++)
410 fprintf(pot, " %20.16g", potential[n][slice]);
411 fprintf(fie, " %20.16g", field[n][slice] / 1e9); /* convert to V/nm */
412 fprintf(cha, " %20.16g", charge[n][slice]);
414 fprintf(pot, "\n");
415 fprintf(cha, "\n");
416 fprintf(fie, "\n");
419 xvgrclose(pot);
420 xvgrclose(cha);
421 xvgrclose(fie);
424 int gmx_potential(int argc, char* argv[])
426 const char* desc[] = {
427 "[THISMODULE] computes the electrostatical potential across the box. The potential is",
428 "calculated by first summing the charges per slice and then integrating",
429 "twice of this charge distribution. Periodic boundaries are not taken",
430 "into account. Reference of potential is taken to be the left side of",
431 "the box. It is also possible to calculate the potential in spherical",
432 "coordinates as function of r by calculating a charge distribution in",
433 "spherical slices and twice integrating them. epsilon_r is taken as 1,",
434 "but 2 is more appropriate in many cases."
436 gmx_output_env_t* oenv;
437 static int axis = 2; /* normal to memb. default z */
438 static const char* axtitle = "Z";
439 static int nslices = 10; /* nr of slices defined */
440 static int ngrps = 1;
441 static gmx_bool bSpherical = FALSE; /* default is bilayer types */
442 static real fudge_z = 0; /* translate coordinates */
443 static gmx_bool bCorrect = false;
444 t_pargs pa[] = {
445 { "-d",
446 FALSE,
447 etSTR,
448 { &axtitle },
449 "Take the normal on the membrane in direction X, Y or Z." },
450 { "-sl",
451 FALSE,
452 etINT,
453 { &nslices },
454 "Calculate potential as function of boxlength, dividing the box"
455 " in this number of slices." },
456 { "-cb",
457 FALSE,
458 etINT,
459 { &cb },
460 "Discard this number of first slices of box for integration" },
461 { "-ce",
462 FALSE,
463 etINT,
464 { &ce },
465 "Discard this number of last slices of box for integration" },
466 { "-tz",
467 FALSE,
468 etREAL,
469 { &fudge_z },
470 "Translate all coordinates by this distance in the direction of the box" },
471 { "-spherical", FALSE, etBOOL, { &bSpherical }, "Calculate in spherical coordinates" },
472 { "-ng", FALSE, etINT, { &ngrps }, "Number of groups to consider" },
473 { "-correct",
474 FALSE,
475 etBOOL,
476 { &bCorrect },
477 "Assume net zero charge of groups to improve accuracy" }
479 const char* bugs[] = { "Discarding slices for integration should not be necessary." };
481 double **potential, /* potential per slice */
482 **charge, /* total charge per slice */
483 **field, /* field per slice */
484 slWidth; /* width of one slice */
485 char** grpname; /* groupnames */
486 int* ngx; /* sizes of groups */
487 t_topology* top; /* topology */
488 int ePBC;
489 int** index; /* indices for all groups */
490 t_filenm fnm[] = {
491 /* files for g_order */
492 { efTRX, "-f", nullptr, ffREAD }, /* trajectory file */
493 { efNDX, nullptr, nullptr, ffREAD }, /* index file */
494 { efTPR, nullptr, nullptr, ffREAD }, /* topology file */
495 { efXVG, "-o", "potential", ffWRITE }, /* xvgr output file */
496 { efXVG, "-oc", "charge", ffWRITE }, /* xvgr output file */
497 { efXVG, "-of", "field", ffWRITE }, /* xvgr output file */
500 #define NFILE asize(fnm)
502 if (!parse_common_args(&argc, argv, PCA_CAN_VIEW | PCA_CAN_TIME, NFILE, fnm, asize(pa), pa,
503 asize(desc), desc, asize(bugs), bugs, &oenv))
505 return 0;
508 /* Calculate axis */
509 axis = toupper(axtitle[0]) - 'X';
511 top = read_top(ftp2fn(efTPR, NFILE, fnm), &ePBC); /* read topology file */
513 snew(grpname, ngrps);
514 snew(index, ngrps);
515 snew(ngx, ngrps);
517 rd_index(ftp2fn(efNDX, NFILE, fnm), ngrps, ngx, index, grpname);
520 calc_potential(ftp2fn(efTRX, NFILE, fnm), index, ngx, &potential, &charge, &field, &nslices,
521 top, ePBC, axis, ngrps, &slWidth, fudge_z, bSpherical, bCorrect, oenv);
523 plot_potential(potential, charge, field, opt2fn("-o", NFILE, fnm), opt2fn("-oc", NFILE, fnm),
524 opt2fn("-of", NFILE, fnm), nslices, ngrps, grpname, slWidth, oenv);
526 do_view(oenv, opt2fn("-o", NFILE, fnm), nullptr); /* view xvgr file */
527 do_view(oenv, opt2fn("-oc", NFILE, fnm), nullptr); /* view xvgr file */
528 do_view(oenv, opt2fn("-of", NFILE, fnm), nullptr); /* view xvgr file */
530 return 0;