c6fb7776dfcf0d0b40463eacc34bef9f2cc88ebd
[alexxy/gromacs.git] / src / gromacs / trajectoryanalysis / modules / surfacearea.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-2007, The GROMACS development team.
6  * Copyright (c) 2013,2014,2015,2017,2018,2019, 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.
10  *
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.
15  *
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.
20  *
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.
25  *
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.
33  *
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.
36  */
37 #include "gmxpre.h"
38
39 #include "surfacearea.h"
40
41 #include <cmath>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <cstring>
45
46 #include <algorithm>
47 #include <vector>
48
49 #include "gromacs/math/functions.h"
50 #include "gromacs/math/utilities.h"
51 #include "gromacs/math/vec.h"
52 #include "gromacs/pbcutil/pbc.h"
53 #include "gromacs/selection/nbsearch.h"
54 #include "gromacs/utility/fatalerror.h"
55 #include "gromacs/utility/gmxassert.h"
56 #include "gromacs/utility/smalloc.h"
57
58 using namespace gmx;
59
60 #define UNSP_ICO_DOD      9
61 #define UNSP_ICO_ARC     10
62
63 #define FOURPI (4.*M_PI)
64 #define TORAD(A)     ((A)*0.017453293)
65 #define DP_TOL     0.001
66
67 static real safe_asin(real f)
68 {
69     if ( (fabs(f) < 1.00) )
70     {
71         return( asin(f) );
72     }
73     GMX_ASSERT(fabs(f) - 1.0 > DP_TOL, "Invalid argument");
74     return(M_PI_2);
75 }
76
77 /* routines for dot distributions on the surface of the unit sphere */
78 static real icosaeder_vertices(real *xus)
79 {
80     const real rh = std::sqrt(1.-2.*cos(TORAD(72.)))/(1.-cos(TORAD(72.)));
81     const real rg = cos(TORAD(72.))/(1.-cos(TORAD(72.)));
82     /* icosaeder vertices */
83     xus[ 0] = 0.;                  xus[ 1] = 0.;                  xus[ 2] = 1.;
84     xus[ 3] = rh*cos(TORAD(72.));  xus[ 4] = rh*sin(TORAD(72.));  xus[ 5] = rg;
85     xus[ 6] = rh*cos(TORAD(144.)); xus[ 7] = rh*sin(TORAD(144.)); xus[ 8] = rg;
86     xus[ 9] = rh*cos(TORAD(216.)); xus[10] = rh*sin(TORAD(216.)); xus[11] = rg;
87     xus[12] = rh*cos(TORAD(288.)); xus[13] = rh*sin(TORAD(288.)); xus[14] = rg;
88     xus[15] = rh;                  xus[16] = 0;                   xus[17] = rg;
89     xus[18] = rh*cos(TORAD(36.));  xus[19] = rh*sin(TORAD(36.));  xus[20] = -rg;
90     xus[21] = rh*cos(TORAD(108.)); xus[22] = rh*sin(TORAD(108.)); xus[23] = -rg;
91     xus[24] = -rh;                 xus[25] = 0;                   xus[26] = -rg;
92     xus[27] = rh*cos(TORAD(252.)); xus[28] = rh*sin(TORAD(252.)); xus[29] = -rg;
93     xus[30] = rh*cos(TORAD(324.)); xus[31] = rh*sin(TORAD(324.)); xus[32] = -rg;
94     xus[33] = 0.;                  xus[34] = 0.;                  xus[35] = -1.;
95     return rh;
96 }
97
98
99 static void divarc(real x1, real y1, real z1,
100                    real x2, real y2, real z2,
101                    int div1, int div2, real *xr, real *yr, real *zr)
102 {
103
104     real xd, yd, zd, dd, d1, d2, s, x, y, z;
105     real phi, sphi, cphi;
106
107     xd = y1*z2-y2*z1;
108     yd = z1*x2-z2*x1;
109     zd = x1*y2-x2*y1;
110     dd = std::sqrt(xd*xd+yd*yd+zd*zd);
111     GMX_ASSERT(dd >= DP_TOL, "Rotation axis vector too short");
112
113     d1 = x1*x1+y1*y1+z1*z1;
114     d2 = x2*x2+y2*y2+z2*z2;
115     GMX_ASSERT(d1 >= 0.5, "Vector 1 too short");
116     GMX_ASSERT(d2 >= 0.5, "Vector 2 too short");
117
118     phi  = safe_asin(dd/std::sqrt(d1*d2));
119     phi  = phi*(static_cast<real>(div1))/(static_cast<real>(div2));
120     sphi = sin(phi); cphi = cos(phi);
121     s    = (x1*xd+y1*yd+z1*zd)/dd;
122
123     x   = xd*s*(1.-cphi)/dd + x1 * cphi + (yd*z1-y1*zd)*sphi/dd;
124     y   = yd*s*(1.-cphi)/dd + y1 * cphi + (zd*x1-z1*xd)*sphi/dd;
125     z   = zd*s*(1.-cphi)/dd + z1 * cphi + (xd*y1-x1*yd)*sphi/dd;
126     dd  = std::sqrt(x*x+y*y+z*z);
127     *xr = x/dd; *yr = y/dd; *zr = z/dd;
128 }
129
130 /* densit...required dots per unit sphere */
131 static std::vector<real> ico_dot_arc(int densit)
132 {
133     /* dot distribution on a unit sphere based on an icosaeder *
134      * great circle average refining of icosahedral face       */
135
136     int   i, j, k, tl, tl2, tn;
137     real  a, d, x, y, z, x2, y2, z2, x3, y3, z3;
138     real  xij, yij, zij, xji, yji, zji, xik, yik, zik, xki, yki, zki,
139           xjk, yjk, zjk, xkj, ykj, zkj;
140
141     /* calculate tessalation level */
142     a = std::sqrt(((static_cast<real>(densit))-2.)/10.);
143     const int  tess = static_cast<int>(ceil(a));
144     const int  ndot = 10*tess*tess+2;
145     GMX_RELEASE_ASSERT(ndot >= densit, "Inconsistent surface dot formula");
146
147     std::vector<real> xus(3*ndot);
148     const real        rh = icosaeder_vertices(xus.data());
149
150     if (tess > 1)
151     {
152         tn = 12;
153         a  = rh*rh*2.*(1.-cos(TORAD(72.)));
154         /* calculate tessalation of icosaeder edges */
155         for (i = 0; i < 11; i++)
156         {
157             for (j = i+1; j < 12; j++)
158             {
159                 x = xus[3*i]-xus[3*j];
160                 y = xus[1+3*i]-xus[1+3*j]; z = xus[2+3*i]-xus[2+3*j];
161                 d = x*x+y*y+z*z;
162                 if (std::fabs(a-d) > DP_TOL)
163                 {
164                     continue;
165                 }
166                 for (tl = 1; tl < tess; tl++)
167                 {
168                     GMX_ASSERT(tn < ndot, "Inconsistent precomputed surface dot count");
169                     divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
170                            xus[3*j], xus[1+3*j], xus[2+3*j],
171                            tl, tess, &xus[3*tn], &xus[1+3*tn], &xus[2+3*tn]);
172                     tn++;
173                 }
174             }
175         }
176         /* calculate tessalation of icosaeder faces */
177         for (i = 0; i < 10; i++)
178         {
179             for (j = i+1; j < 11; j++)
180             {
181                 x = xus[3*i]-xus[3*j];
182                 y = xus[1+3*i]-xus[1+3*j]; z = xus[2+3*i]-xus[2+3*j];
183                 d = x*x+y*y+z*z;
184                 if (std::fabs(a-d) > DP_TOL)
185                 {
186                     continue;
187                 }
188
189                 for (k = j+1; k < 12; k++)
190                 {
191                     x = xus[3*i]-xus[3*k];
192                     y = xus[1+3*i]-xus[1+3*k]; z = xus[2+3*i]-xus[2+3*k];
193                     d = x*x+y*y+z*z;
194                     if (std::fabs(a-d) > DP_TOL)
195                     {
196                         continue;
197                     }
198                     x = xus[3*j]-xus[3*k];
199                     y = xus[1+3*j]-xus[1+3*k]; z = xus[2+3*j]-xus[2+3*k];
200                     d = x*x+y*y+z*z;
201                     if (std::fabs(a-d) > DP_TOL)
202                     {
203                         continue;
204                     }
205                     for (tl = 1; tl < tess-1; tl++)
206                     {
207                         divarc(xus[3*j], xus[1+3*j], xus[2+3*j],
208                                xus[3*i], xus[1+3*i], xus[2+3*i],
209                                tl, tess, &xji, &yji, &zji);
210                         divarc(xus[3*k], xus[1+3*k], xus[2+3*k],
211                                xus[3*i], xus[1+3*i], xus[2+3*i],
212                                tl, tess, &xki, &yki, &zki);
213
214                         for (tl2 = 1; tl2 < tess-tl; tl2++)
215                         {
216                             divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
217                                    xus[3*j], xus[1+3*j], xus[2+3*j],
218                                    tl2, tess, &xij, &yij, &zij);
219                             divarc(xus[3*k], xus[1+3*k], xus[2+3*k],
220                                    xus[3*j], xus[1+3*j], xus[2+3*j],
221                                    tl2, tess, &xkj, &ykj, &zkj);
222                             divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
223                                    xus[3*k], xus[1+3*k], xus[2+3*k],
224                                    tess-tl-tl2, tess, &xik, &yik, &zik);
225                             divarc(xus[3*j], xus[1+3*j], xus[2+3*j],
226                                    xus[3*k], xus[1+3*k], xus[2+3*k],
227                                    tess-tl-tl2, tess, &xjk, &yjk, &zjk);
228                             divarc(xki, yki, zki, xji, yji, zji, tl2, tess-tl,
229                                    &x, &y, &z);
230                             divarc(xkj, ykj, zkj, xij, yij, zij, tl, tess-tl2,
231                                    &x2, &y2, &z2);
232                             divarc(xjk, yjk, zjk, xik, yik, zik, tl, tl+tl2,
233                                    &x3, &y3, &z3);
234                             GMX_ASSERT(tn < ndot,
235                                        "Inconsistent precomputed surface dot count");
236                             x           = x+x2+x3; y = y+y2+y3; z = z+z2+z3;
237                             d           = std::sqrt(x*x+y*y+z*z);
238                             xus[3*tn]   = x/d;
239                             xus[1+3*tn] = y/d;
240                             xus[2+3*tn] = z/d;
241                             tn++;
242                         } /* cycle tl2 */
243                     }     /* cycle tl */
244                 }         /* cycle k */
245             }             /* cycle j */
246         }                 /* cycle i */
247         GMX_ASSERT(tn == ndot, "Inconsistent precomputed surface dot count");
248     }                     /* end of if (tess > 1) */
249
250     return xus;
251 }                                  /* end of routine ico_dot_arc */
252
253 /* densit...required dots per unit sphere */
254 static std::vector<real> ico_dot_dod(int densit)
255 {
256     /* dot distribution on a unit sphere based on an icosaeder *
257      * great circle average refining of icosahedral face       */
258
259     int   i, j, k, tl, tl2, tn, tess, j1, j2;
260     real  a, d, x, y, z, x2, y2, z2, x3, y3, z3, ai_d, adod;
261     real  xij, yij, zij, xji, yji, zji, xik, yik, zik, xki, yki, zki,
262           xjk, yjk, zjk, xkj, ykj, zkj;
263
264     /* calculate tesselation level */
265     a     = std::sqrt(((static_cast<real>(densit))-2.)/30.);
266     tess  = std::max(static_cast<int>(ceil(a)), 1);
267     const int ndot = 30*tess*tess+2;
268     GMX_RELEASE_ASSERT(ndot >= densit, "Inconsistent surface dot formula");
269
270     std::vector<real> xus(3*ndot);
271     const real        rh = icosaeder_vertices(xus.data());
272
273     tn = 12;
274     /* square of the edge of an icosaeder */
275     a = rh*rh*2.*(1.-cos(TORAD(72.)));
276     /* dodecaeder vertices */
277     for (i = 0; i < 10; i++)
278     {
279         for (j = i+1; j < 11; j++)
280         {
281             x = xus[3*i]-xus[3*j];
282             y = xus[1+3*i]-xus[1+3*j]; z = xus[2+3*i]-xus[2+3*j];
283             d = x*x+y*y+z*z;
284             if (std::fabs(a-d) > DP_TOL)
285             {
286                 continue;
287             }
288             for (k = j+1; k < 12; k++)
289             {
290                 x = xus[3*i]-xus[3*k];
291                 y = xus[1+3*i]-xus[1+3*k]; z = xus[2+3*i]-xus[2+3*k];
292                 d = x*x+y*y+z*z;
293                 if (std::fabs(a-d) > DP_TOL)
294                 {
295                     continue;
296                 }
297                 x = xus[3*j]-xus[3*k];
298                 y = xus[1+3*j]-xus[1+3*k]; z = xus[2+3*j]-xus[2+3*k];
299                 d = x*x+y*y+z*z;
300                 if (std::fabs(a-d) > DP_TOL)
301                 {
302                     continue;
303                 }
304                 x         = xus[  3*i]+xus[  3*j]+xus[  3*k];
305                 y         = xus[1+3*i]+xus[1+3*j]+xus[1+3*k];
306                 z         = xus[2+3*i]+xus[2+3*j]+xus[2+3*k];
307                 d         = std::sqrt(x*x+y*y+z*z);
308                 xus[3*tn] = x/d; xus[1+3*tn] = y/d; xus[2+3*tn] = z/d;
309                 tn++;
310             }
311         }
312     }
313
314     if (tess > 1)
315     {
316         tn = 32;
317         /* square of the edge of an dodecaeder */
318         adod = 4.*(cos(TORAD(108.))-cos(TORAD(120.)))/(1.-cos(TORAD(120.)));
319         /* square of the distance of two adjacent vertices of ico- and dodecaeder */
320         ai_d = 2.*(1.-std::sqrt(1.-a/3.));
321
322         /* calculate tessalation of mixed edges */
323         for (i = 0; i < 31; i++)
324         {
325             j1 = 12; j2 = 32; a = ai_d;
326             if (i >= 12)
327             {
328                 j1 = i+1; a = adod;
329             }
330             for (j = j1; j < j2; j++)
331             {
332                 x = xus[3*i]-xus[3*j];
333                 y = xus[1+3*i]-xus[1+3*j]; z = xus[2+3*i]-xus[2+3*j];
334                 d = x*x+y*y+z*z;
335                 if (std::fabs(a-d) > DP_TOL)
336                 {
337                     continue;
338                 }
339                 for (tl = 1; tl < tess; tl++)
340                 {
341                     GMX_ASSERT(tn < ndot,
342                                "Inconsistent precomputed surface dot count");
343                     divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
344                            xus[3*j], xus[1+3*j], xus[2+3*j],
345                            tl, tess, &xus[3*tn], &xus[1+3*tn], &xus[2+3*tn]);
346                     tn++;
347                 }
348             }
349         }
350         /* calculate tessalation of pentakisdodecahedron faces */
351         for (i = 0; i < 12; i++)
352         {
353             for (j = 12; j < 31; j++)
354             {
355                 x = xus[3*i]-xus[3*j];
356                 y = xus[1+3*i]-xus[1+3*j]; z = xus[2+3*i]-xus[2+3*j];
357                 d = x*x+y*y+z*z;
358                 if (std::fabs(ai_d-d) > DP_TOL)
359                 {
360                     continue;
361                 }
362
363                 for (k = j+1; k < 32; k++)
364                 {
365                     x = xus[3*i]-xus[3*k];
366                     y = xus[1+3*i]-xus[1+3*k]; z = xus[2+3*i]-xus[2+3*k];
367                     d = x*x+y*y+z*z;
368                     if (std::fabs(ai_d-d) > DP_TOL)
369                     {
370                         continue;
371                     }
372                     x = xus[3*j]-xus[3*k];
373                     y = xus[1+3*j]-xus[1+3*k]; z = xus[2+3*j]-xus[2+3*k];
374                     d = x*x+y*y+z*z;
375                     if (std::fabs(adod-d) > DP_TOL)
376                     {
377                         continue;
378                     }
379                     for (tl = 1; tl < tess-1; tl++)
380                     {
381                         divarc(xus[3*j], xus[1+3*j], xus[2+3*j],
382                                xus[3*i], xus[1+3*i], xus[2+3*i],
383                                tl, tess, &xji, &yji, &zji);
384                         divarc(xus[3*k], xus[1+3*k], xus[2+3*k],
385                                xus[3*i], xus[1+3*i], xus[2+3*i],
386                                tl, tess, &xki, &yki, &zki);
387
388                         for (tl2 = 1; tl2 < tess-tl; tl2++)
389                         {
390                             divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
391                                    xus[3*j], xus[1+3*j], xus[2+3*j],
392                                    tl2, tess, &xij, &yij, &zij);
393                             divarc(xus[3*k], xus[1+3*k], xus[2+3*k],
394                                    xus[3*j], xus[1+3*j], xus[2+3*j],
395                                    tl2, tess, &xkj, &ykj, &zkj);
396                             divarc(xus[3*i], xus[1+3*i], xus[2+3*i],
397                                    xus[3*k], xus[1+3*k], xus[2+3*k],
398                                    tess-tl-tl2, tess, &xik, &yik, &zik);
399                             divarc(xus[3*j], xus[1+3*j], xus[2+3*j],
400                                    xus[3*k], xus[1+3*k], xus[2+3*k],
401                                    tess-tl-tl2, tess, &xjk, &yjk, &zjk);
402                             divarc(xki, yki, zki, xji, yji, zji, tl2, tess-tl,
403                                    &x, &y, &z);
404                             divarc(xkj, ykj, zkj, xij, yij, zij, tl, tess-tl2,
405                                    &x2, &y2, &z2);
406                             divarc(xjk, yjk, zjk, xik, yik, zik, tl, tl+tl2,
407                                    &x3, &y3, &z3);
408                             GMX_ASSERT(tn < ndot,
409                                        "Inconsistent precomputed surface dot count");
410                             x           = x+x2+x3; y = y+y2+y3; z = z+z2+z3;
411                             d           = std::sqrt(x*x+y*y+z*z);
412                             xus[3*tn]   = x/d;
413                             xus[1+3*tn] = y/d;
414                             xus[2+3*tn] = z/d;
415                             tn++;
416                         } /* cycle tl2 */
417                     }     /* cycle tl */
418                 }         /* cycle k */
419             }             /* cycle j */
420         }                 /* cycle i */
421         GMX_ASSERT(tn == ndot, "Inconsistent precomputed surface dot count");
422     }                     /* end of if (tess > 1) */
423
424     return xus;
425 }       /* end of routine ico_dot_dod */
426
427 static int unsp_type(int densit)
428 {
429     int i1, i2;
430     i1 = 1;
431     while (10*i1*i1+2 < densit)
432     {
433         i1++;
434     }
435     i2 = 1;
436     while (30*i2*i2+2 < densit)
437     {
438         i2++;
439     }
440     if (10*i1*i1-2 < 30*i2*i2-2)
441     {
442         return UNSP_ICO_ARC;
443     }
444     else
445     {
446         return UNSP_ICO_DOD;
447     }
448 }
449
450 static std::vector<real> make_unsp(int densit, int cubus)
451 {
452     int              *ico_wk, *ico_pt;
453     int               ico_cube, ico_cube_cb, i, j, k, l, ijk, tn, tl, tl2;
454     int              *work;
455     real              x, y, z;
456
457     int               mode = unsp_type(densit);
458     std::vector<real> xus;
459     if (mode == UNSP_ICO_ARC)
460     {
461         xus = ico_dot_arc(densit);
462     }
463     else if (mode == UNSP_ICO_DOD)
464     {
465         xus = ico_dot_dod(densit);
466     }
467     else
468     {
469         GMX_RELEASE_ASSERT(false, "Invalid unit sphere mode");
470     }
471
472     const int ndot = ssize(xus)/3;
473
474     /* determine distribution of points in elementary cubes */
475     if (cubus)
476     {
477         ico_cube = cubus;
478     }
479     else
480     {
481         i          = 1;
482         while (i*i*i*2 < ndot)
483         {
484             i++;
485         }
486         ico_cube = std::max(i-1, 0);
487     }
488     ico_cube_cb = ico_cube*ico_cube*ico_cube;
489     const real del_cube = 2./(static_cast<real>(ico_cube));
490     snew(work, ndot);
491     for (l = 0; l < ndot; l++)
492     {
493         i = std::max(static_cast<int>(floor((1.+xus[3*l])/del_cube)), 0);
494         if (i >= ico_cube)
495         {
496             i = ico_cube-1;
497         }
498         j = std::max(static_cast<int>(floor((1.+xus[1+3*l])/del_cube)), 0);
499         if (j >= ico_cube)
500         {
501             j = ico_cube-1;
502         }
503         k = std::max(static_cast<int>(floor((1.+xus[2+3*l])/del_cube)), 0);
504         if (k >= ico_cube)
505         {
506             k = ico_cube-1;
507         }
508         ijk     = i+j*ico_cube+k*ico_cube*ico_cube;
509         work[l] = ijk;
510     }
511
512     snew(ico_wk, 2*ico_cube_cb+1);
513
514     ico_pt = ico_wk+ico_cube_cb;
515     for (l = 0; l < ndot; l++)
516     {
517         ico_wk[work[l]]++; /* dots per elementary cube */
518     }
519
520     /* reordering of the coordinate array in accordance with box number */
521     tn = 0;
522     for (i = 0; i < ico_cube; i++)
523     {
524         for (j = 0; j < ico_cube; j++)
525         {
526             for (k = 0; k < ico_cube; k++)
527             {
528                 tl            = 0;
529                 tl2           = tn;
530                 ijk           = i+ico_cube*j+ico_cube*ico_cube*k;
531                 *(ico_pt+ijk) = tn;
532                 for (l = tl2; l < ndot; l++)
533                 {
534                     if (ijk == work[l])
535                     {
536                         x          = xus[3*l]; y = xus[1+3*l]; z = xus[2+3*l];
537                         xus[3*l]   = xus[3*tn];
538                         xus[1+3*l] = xus[1+3*tn]; xus[2+3*l] = xus[2+3*tn];
539                         xus[3*tn]  = x; xus[1+3*tn] = y; xus[2+3*tn] = z;
540                         ijk        = work[l]; work[l] = work[tn]; work[tn] = ijk;
541                         tn++; tl++;
542                     }
543                 }
544                 *(ico_wk+ijk) = tl;
545             } /* cycle k */
546         }     /* cycle j */
547     }         /* cycle i */
548     sfree(ico_wk);
549     sfree(work);
550
551     return xus;
552 }
553
554 static void
555 nsc_dclm_pbc(const rvec *coords, const ArrayRef<const real> &radius, int nat,
556              const real *xus, int n_dot, int mode,
557              real *value_of_area, real **at_area,
558              real *value_of_vol,
559              real **lidots, int *nu_dots,
560              int index[], AnalysisNeighborhood *nb,
561              const t_pbc *pbc)
562 {
563     const real dotarea = FOURPI/static_cast<real>(n_dot);
564
565     if (debug)
566     {
567         fprintf(debug, "nsc_dclm: n_dot=%5d %9.3f\n", n_dot, dotarea);
568     }
569
570     /* start with neighbour list */
571     /* calculate neighbour list with the box algorithm */
572     if (nat == 0)
573     {
574         return;
575     }
576     real        area = 0.0, vol = 0.0;
577     real       *dots = nullptr, *atom_area = nullptr;
578     int         lfnr = 0, maxdots = 0;
579     if (mode & FLAG_VOLUME)
580     {
581         vol = 0.;
582     }
583     if (mode & FLAG_DOTS)
584     {
585         maxdots = (3*n_dot*nat)/10;
586         snew(dots, maxdots);
587         lfnr = 0;
588     }
589     if (mode & FLAG_ATOM_AREA)
590     {
591         snew(atom_area, nat);
592     }
593
594     // Compute the center of the molecule for volume calculation.
595     // In principle, the center should not influence the results, but that is
596     // only true at the limit of infinite dot density, so this makes the
597     // results translation-invariant.
598     // With PBC, if the molecule is broken across the boundary, the computation
599     // is broken in other ways as well, so it does not need to be considered
600     // here.
601     real xs = 0.0, ys = 0.0, zs = 0.0;
602     for (int i = 0; i < nat; ++i)
603     {
604         const int iat = index[i];
605         xs += coords[iat][XX];
606         ys += coords[iat][YY];
607         zs += coords[iat][ZZ];
608     }
609     xs /= nat;
610     ys /= nat;
611     zs /= nat;
612
613     AnalysisNeighborhoodPositions pos(coords, radius.size());
614     pos.indexed(constArrayRefFromArray(index, nat));
615     AnalysisNeighborhoodSearch    nbsearch(nb->initSearch(pbc, pos));
616
617     std::vector<int>              wkdot(n_dot);
618
619     for (int i = 0; i < nat; ++i)
620     {
621         const int                      iat  = index[i];
622         const real                     ai   = radius[iat];
623         const real                     aisq = ai*ai;
624         AnalysisNeighborhoodPairSearch pairSearch(
625                 nbsearch.startPairSearch(coords[iat]));
626         AnalysisNeighborhoodPair       pair;
627         std::fill(wkdot.begin(), wkdot.end(), 1);
628         int currDotCount = n_dot;
629         while (currDotCount > 0 && pairSearch.findNextPair(&pair))
630         {
631             const int  jat = index[pair.refIndex()];
632             const real aj  = radius[jat];
633             const real d2  = pair.distance2();
634             if (iat == jat || d2 > gmx::square(ai+aj))
635             {
636                 continue;
637             }
638             const rvec &dx     = pair.dx();
639             const real  refdot = (d2 + aisq - aj*aj)/(2*ai);
640             // TODO: Consider whether micro-optimizations from the old
641             // implementation would be useful, compared to the complexity that
642             // they bring: instead of this direct loop, the neighbors were
643             // stored into a temporary array, the loop order was
644             // reversed (first over dots, then over neighbors), and for each
645             // dot, it was first checked whether the same neighbor that
646             // resulted in marking the previous dot covered would also cover
647             // this dot. This presumably plays together with sorting of the
648             // surface dots (done in make_unsp) to avoid some of the looping.
649             // Alternatively, we could keep a skip list here to avoid
650             // repeatedly looping over dots that have already marked as
651             // covered.
652             for (int j = 0; j < n_dot; ++j)
653             {
654                 if (wkdot[j] && iprod(&xus[3*j], dx) > refdot)
655                 {
656                     --currDotCount;
657                     wkdot[j] = 0;
658                 }
659             }
660         }
661
662         const real a = aisq * dotarea * currDotCount;
663         area = area + a;
664         if (mode & FLAG_ATOM_AREA)
665         {
666             atom_area[i] = a;
667         }
668         const real xi = coords[iat][XX];
669         const real yi = coords[iat][YY];
670         const real zi = coords[iat][ZZ];
671         if (mode & FLAG_DOTS)
672         {
673             for (int l = 0; l < n_dot; l++)
674             {
675                 if (wkdot[l])
676                 {
677                     lfnr++;
678                     if (maxdots <= 3*lfnr+1)
679                     {
680                         maxdots = maxdots+n_dot*3;
681                         srenew(dots, maxdots);
682                     }
683                     dots[3*lfnr-3] = ai*xus[3*l]+xi;
684                     dots[3*lfnr-2] = ai*xus[1+3*l]+yi;
685                     dots[3*lfnr-1] = ai*xus[2+3*l]+zi;
686                 }
687             }
688         }
689         if (mode & FLAG_VOLUME)
690         {
691             real dx = 0.0, dy = 0.0, dz = 0.0;
692             for (int l = 0; l < n_dot; l++)
693             {
694                 if (wkdot[l])
695                 {
696                     dx = dx+xus[3*l];
697                     dy = dy+xus[1+3*l];
698                     dz = dz+xus[2+3*l];
699                 }
700             }
701             vol = vol+aisq*(dx*(xi-xs)+dy*(yi-ys)+dz*(zi-zs) + ai*currDotCount);
702         }
703     }
704
705     if (mode & FLAG_VOLUME)
706     {
707         *value_of_vol = vol*FOURPI/(3.*n_dot);
708     }
709     if (mode & FLAG_DOTS)
710     {
711         *nu_dots = lfnr;
712         *lidots  = dots;
713     }
714     if (mode & FLAG_ATOM_AREA)
715     {
716         *at_area = atom_area;
717     }
718     *value_of_area = area;
719
720     if (debug)
721     {
722         fprintf(debug, "area=%8.3f\n", area);
723     }
724 }
725
726 namespace gmx
727 {
728
729 class SurfaceAreaCalculator::Impl
730 {
731     public:
732         Impl() : flags_(0)
733         {
734         }
735
736         std::vector<real>             unitSphereDots_;
737         ArrayRef<const real>          radius_;
738         int                           flags_;
739         mutable AnalysisNeighborhood  nb_;
740 };
741
742 SurfaceAreaCalculator::SurfaceAreaCalculator()
743     : impl_(new Impl())
744 {
745 }
746
747 SurfaceAreaCalculator::~SurfaceAreaCalculator()
748 {
749 }
750
751 void SurfaceAreaCalculator::setDotCount(int dotCount)
752 {
753     impl_->unitSphereDots_ = make_unsp(dotCount, 4);
754 }
755
756 void SurfaceAreaCalculator::setRadii(const ArrayRef<const real> &radius)
757 {
758     impl_->radius_ = radius;
759     if (!radius.empty())
760     {
761         const real maxRadius = *std::max_element(radius.begin(), radius.end());
762         impl_->nb_.setCutoff(2*maxRadius);
763     }
764 }
765
766 void SurfaceAreaCalculator::setCalculateVolume(bool bVolume)
767 {
768     if (bVolume)
769     {
770         impl_->flags_ |= FLAG_VOLUME;
771     }
772     else
773     {
774         impl_->flags_ &= ~FLAG_VOLUME;
775     }
776 }
777
778 void SurfaceAreaCalculator::setCalculateAtomArea(bool bAtomArea)
779 {
780     if (bAtomArea)
781     {
782         impl_->flags_ |= FLAG_ATOM_AREA;
783     }
784     else
785     {
786         impl_->flags_ &= ~FLAG_ATOM_AREA;
787     }
788 }
789
790 void SurfaceAreaCalculator::setCalculateSurfaceDots(bool bDots)
791 {
792     if (bDots)
793     {
794         impl_->flags_ |= FLAG_DOTS;
795     }
796     else
797     {
798         impl_->flags_ &= ~FLAG_DOTS;
799     }
800 }
801
802 void SurfaceAreaCalculator::calculate(
803         const rvec *x, const t_pbc *pbc,
804         int nat, int index[], int flags, real *area, real *volume,
805         real **at_area, real **lidots, int *n_dots) const
806 {
807     flags |= impl_->flags_;
808     *area  = 0;
809     if (volume == nullptr)
810     {
811         flags &= ~FLAG_VOLUME;
812     }
813     else
814     {
815         *volume = 0;
816     }
817     if (at_area == nullptr)
818     {
819         flags &= ~FLAG_ATOM_AREA;
820     }
821     else
822     {
823         *at_area = nullptr;
824     }
825     if (lidots == nullptr)
826     {
827         flags &= ~FLAG_DOTS;
828     }
829     else
830     {
831         *lidots = nullptr;
832     }
833     if (n_dots == nullptr)
834     {
835         flags &= ~FLAG_DOTS;
836     }
837     else
838     {
839         *n_dots = 0;
840     }
841     nsc_dclm_pbc(x, impl_->radius_, nat,
842                  &impl_->unitSphereDots_[0], impl_->unitSphereDots_.size()/3,
843                  flags, area, at_area, volume, lidots, n_dots, index,
844                  &impl_->nb_, pbc);
845 }
846
847 } // namespace gmx