Split lines with many copyright years
[alexxy/gromacs.git] / src / gromacs / gmxana / gmx_potential.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
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.
11  *
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.
16  *
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.
21  *
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.
26  *
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.
34  *
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.
37  */
38 #include "gmxpre.h"
39
40 #include <cctype>
41 #include <cmath>
42 #include <cstring>
43
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"
61
62 #define EPS0 8.85419E-12
63 #define ELC 1.60219E-19
64
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 /****************************************************************************/
74
75 static int ce = 0, cb = 0;
76
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)
80 {
81     int    i, slice;
82     double sum;
83
84     if (ndata <= 2)
85     {
86         fprintf(stderr,
87                 "Warning: nr of slices very small. This will result"
88                 "in nonsense.\n");
89     }
90
91     fprintf(stderr, "Integrating from slice %d to slice %d\n", cb, ndata - ce);
92
93     for (slice = cb; slice < (ndata - ce); slice++)
94     {
95         sum = 0;
96         for (i = cb; i < slice; i++)
97         {
98             sum += slWidth * (data[i] + 0.5 * (data[i + 1] - data[i]));
99         }
100         result[slice] = sum;
101     }
102 }
103
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)
120 {
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;
134
135     switch (axis)
136     {
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");
150     }
151
152     if ((natoms = read_first_x(oenv, &status, fn, &t, &x0, box)) == 0)
153     {
154         gmx_fatal(FARGS, "Could not read coordinates from statusfile\n");
155     }
156
157     if (!*nslices)
158     {
159         *nslices = static_cast<int>(box[axis][axis] * 10.0); /* default value */
160     }
161     fprintf(stderr, "\nDividing the box in %d slices\n", *nslices);
162
163     snew(*slField, nr_grps);
164     snew(*slCharge, nr_grps);
165     snew(*slPotential, nr_grps);
166
167     for (i = 0; i < nr_grps; i++)
168     {
169         snew((*slField)[i], *nslices);
170         snew((*slCharge)[i], *nslices);
171         snew((*slPotential)[i], *nslices);
172     }
173
174
175     gpbc = gmx_rmpbc_init(&top->idef, ePBC, natoms);
176
177     /*********** Start processing trajectory ***********/
178     do
179     {
180         *slWidth = box[axis][axis] / static_cast<real>((*nslices));
181         teller++;
182         gmx_rmpbc(gpbc, natoms, box, x0);
183
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);
187
188         for (n = 0; n < nr_grps; n++)
189         {
190             /* Check whether we actually have all positions of the requested index
191              * group in the trajectory file */
192             if (gnx[n] > natoms)
193             {
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);
198             }
199             for (i = 0; i < gnx[n]; i++) /* loop over all atoms in index file */
200             {
201                 if (bSpherical)
202                 {
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));
206
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))
211                        {
212                        fprintf(stderr,"Warning: slice = %d\n",slice);
213                        }
214                      */
215                     (*slCharge)[n][slice] += top->atoms.atom[index[n][i]].q;
216                 }
217                 else
218                 {
219                     z = x0[index[n][i]][axis];
220                     z = z + fudge_z;
221                     if (z < 0)
222                     {
223                         z += box[axis][axis];
224                     }
225                     if (z > box[axis][axis])
226                     {
227                         z -= box[axis][axis];
228                     }
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;
232                 }
233             }
234         }
235         nr_frames++;
236     } while (read_next_x(oenv, status, &t, x0, box));
237
238     gmx_rmpbc_done(gpbc);
239
240     /*********** done with status file **********/
241     close_trx(status);
242
243     /* slCharge now contains the total charge per slice, summed over all
244        frames. Now divide by nr_frames and integrate twice
245      */
246
247
248     if (bSpherical)
249     {
250         fprintf(stderr,
251                 "\n\nRead %d frames from trajectory. Calculating potential"
252                 "in spherical coordinates\n",
253                 nr_frames);
254     }
255     else
256     {
257         fprintf(stderr, "\n\nRead %d frames from trajectory. Calculating potential\n", nr_frames);
258     }
259
260     for (n = 0; n < nr_grps; n++)
261     {
262         for (i = 0; i < *nslices; i++)
263         {
264             if (bSpherical)
265             {
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
268                  */
269                 slVolume = 4 * M_PI * gmx::square(i) * gmx::square(*slWidth) * *slWidth;
270                 if (slVolume == 0)
271                 {
272                     (*slCharge)[n][i] = 0;
273                 }
274                 else
275                 {
276                     (*slCharge)[n][i] = (*slCharge)[n][i] / (nr_frames * slVolume);
277                 }
278             }
279             else
280             {
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]);
285             }
286         }
287         /* Now we have charge densities */
288     }
289
290     if (bCorrect && !bSpherical)
291     {
292         for (n = 0; n < nr_grps; n++)
293         {
294             nn   = 0;
295             qsum = 0;
296             for (i = 0; i < *nslices; i++)
297             {
298                 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
299                 {
300                     nn++;
301                     qsum += (*slCharge)[n][i];
302                 }
303             }
304             qsum /= nn;
305             for (i = 0; i < *nslices; i++)
306             {
307                 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
308                 {
309                     (*slCharge)[n][i] -= qsum;
310                 }
311             }
312         }
313     }
314
315     for (n = 0; n < nr_grps; n++)
316     {
317         /* integrate twice to get field and potential */
318         p_integrate((*slField)[n], (*slCharge)[n], *nslices, *slWidth);
319     }
320
321
322     if (bCorrect && !bSpherical)
323     {
324         for (n = 0; n < nr_grps; n++)
325         {
326             nn   = 0;
327             qsum = 0;
328             for (i = 0; i < *nslices; i++)
329             {
330                 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
331                 {
332                     nn++;
333                     qsum += (*slField)[n][i];
334                 }
335             }
336             qsum /= nn;
337             for (i = 0; i < *nslices; i++)
338             {
339                 if (std::abs((*slCharge)[n][i]) >= GMX_DOUBLE_MIN)
340                 {
341                     (*slField)[n][i] -= qsum;
342                 }
343             }
344         }
345     }
346
347     for (n = 0; n < nr_grps; n++)
348     {
349         p_integrate((*slPotential)[n], (*slField)[n], *nslices, *slWidth);
350     }
351
352     /* Now correct for eps0 and in spherical case for r*/
353     for (n = 0; n < nr_grps; n++)
354     {
355         for (i = 0; i < *nslices; i++)
356         {
357             if (bSpherical)
358             {
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));
361             }
362             else
363             {
364                 (*slPotential)[n][i] = ELC * (*slPotential)[n][i] * -1.0E9 / EPS0;
365                 (*slField)[n][i]     = ELC * (*slField)[n][i] * 1E18 / EPS0;
366             }
367         }
368     }
369
370     sfree(x0); /* free memory used by coordinate array */
371 }
372
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)
384 {
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;
390
391     sprintf(buf, "Electrostatic Potential");
392     pot = xvgropen(afile, buf, "Box (nm)", "Potential (V)", oenv);
393     xvgr_legend(pot, nr_grps, grpname, oenv);
394
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);
398
399     sprintf(buf, "Electric Field");
400     fie = xvgropen(cfile, buf, "Box (nm)", "Field (V/nm)", oenv);
401     xvgr_legend(fie, nr_grps, grpname, oenv);
402
403     for (slice = cb; slice < (nslices - ce); slice++)
404     {
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++)
409         {
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]);
413         }
414         fprintf(pot, "\n");
415         fprintf(cha, "\n");
416         fprintf(fie, "\n");
417     }
418
419     xvgrclose(pot);
420     xvgrclose(cha);
421     xvgrclose(fie);
422 }
423
424 int gmx_potential(int argc, char* argv[])
425 {
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."
435     };
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" }
478     };
479     const char* bugs[] = { "Discarding slices for integration should not be necessary." };
480
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    */
498     };
499
500 #define NFILE asize(fnm)
501
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))
504     {
505         return 0;
506     }
507
508     /* Calculate axis */
509     axis = toupper(axtitle[0]) - 'X';
510
511     top = read_top(ftp2fn(efTPR, NFILE, fnm), &ePBC); /* read topology file */
512
513     snew(grpname, ngrps);
514     snew(index, ngrps);
515     snew(ngx, ngrps);
516
517     rd_index(ftp2fn(efNDX, NFILE, fnm), ngrps, ngx, index, grpname);
518
519
520     calc_potential(ftp2fn(efTRX, NFILE, fnm), index, ngx, &potential, &charge, &field, &nslices,
521                    top, ePBC, axis, ngrps, &slWidth, fudge_z, bSpherical, bCorrect, oenv);
522
523     plot_potential(potential, charge, field, opt2fn("-o", NFILE, fnm), opt2fn("-oc", NFILE, fnm),
524                    opt2fn("-of", NFILE, fnm), nslices, ngrps, grpname, slWidth, oenv);
525
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 */
529
530     return 0;
531 }