Add coolquotes
[gromacs.git] / src / gromacs / trajectoryanalysis / modules / surfacearea.cpp
blob7a6155af68fbadfbc1480daf4be4bb723bd6d738
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-2007, The GROMACS development team.
6 * Copyright (c) 2013,2014,2015,2017,2018, 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.
37 #include "gmxpre.h"
39 #include "surfacearea.h"
41 #include <cmath>
42 #include <cstdio>
43 #include <cstdlib>
44 #include <cstring>
46 #include <algorithm>
47 #include <vector>
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"
58 using namespace gmx;
60 #define UNSP_ICO_DOD 9
61 #define UNSP_ICO_ARC 10
63 #define FOURPI (4.*M_PI)
64 #define TORAD(A) ((A)*0.017453293)
65 #define DP_TOL 0.001
67 static real safe_asin(real f)
69 if ( (fabs(f) < 1.00) )
71 return( asin(f) );
73 GMX_ASSERT(fabs(f) - 1.0 > DP_TOL, "Invalid argument");
74 return(M_PI_2);
77 /* routines for dot distributions on the surface of the unit sphere */
78 static real icosaeder_vertices(real *xus)
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;
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)
104 real xd, yd, zd, dd, d1, d2, s, x, y, z;
105 real phi, sphi, cphi;
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");
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");
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;
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;
130 /* densit...required dots per unit sphere */
131 static std::vector<real> ico_dot_arc(int densit)
133 /* dot distribution on a unit sphere based on an icosaeder *
134 * great circle average refining of icosahedral face */
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;
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");
147 std::vector<real> xus(3*ndot);
148 const real rh = icosaeder_vertices(xus.data());
150 if (tess > 1)
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++)
157 for (j = i+1; j < 12; j++)
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)
164 continue;
166 for (tl = 1; tl < tess; tl++)
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++;
176 /* calculate tessalation of icosaeder faces */
177 for (i = 0; i < 10; i++)
179 for (j = i+1; j < 11; j++)
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)
186 continue;
189 for (k = j+1; k < 12; k++)
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)
196 continue;
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)
203 continue;
205 for (tl = 1; tl < tess-1; tl++)
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);
214 for (tl2 = 1; tl2 < tess-tl; tl2++)
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) */
250 return xus;
251 } /* end of routine ico_dot_arc */
253 /* densit...required dots per unit sphere */
254 static std::vector<real> ico_dot_dod(int densit)
256 /* dot distribution on a unit sphere based on an icosaeder *
257 * great circle average refining of icosahedral face */
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;
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");
270 std::vector<real> xus(3*ndot);
271 const real rh = icosaeder_vertices(xus.data());
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++)
279 for (j = i+1; j < 11; j++)
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)
286 continue;
288 for (k = j+1; k < 12; k++)
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)
295 continue;
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)
302 continue;
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++;
314 if (tess > 1)
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.));
322 /* calculate tessalation of mixed edges */
323 for (i = 0; i < 31; i++)
325 j1 = 12; j2 = 32; a = ai_d;
326 if (i >= 12)
328 j1 = i+1; a = adod;
330 for (j = j1; j < j2; j++)
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)
337 continue;
339 for (tl = 1; tl < tess; tl++)
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++;
350 /* calculate tessalation of pentakisdodecahedron faces */
351 for (i = 0; i < 12; i++)
353 for (j = 12; j < 31; j++)
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)
360 continue;
363 for (k = j+1; k < 32; k++)
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)
370 continue;
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)
377 continue;
379 for (tl = 1; tl < tess-1; tl++)
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);
388 for (tl2 = 1; tl2 < tess-tl; tl2++)
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) */
424 return xus;
425 } /* end of routine ico_dot_dod */
427 static int unsp_type(int densit)
429 int i1, i2;
430 i1 = 1;
431 while (10*i1*i1+2 < densit)
433 i1++;
435 i2 = 1;
436 while (30*i2*i2+2 < densit)
438 i2++;
440 if (10*i1*i1-2 < 30*i2*i2-2)
442 return UNSP_ICO_ARC;
444 else
446 return UNSP_ICO_DOD;
450 static std::vector<real> make_unsp(int densit, int cubus)
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;
457 int mode = unsp_type(densit);
458 std::vector<real> xus;
459 if (mode == UNSP_ICO_ARC)
461 xus = ico_dot_arc(densit);
463 else if (mode == UNSP_ICO_DOD)
465 xus = ico_dot_dod(densit);
467 else
469 GMX_RELEASE_ASSERT(false, "Invalid unit sphere mode");
472 const int ndot = static_cast<int>(xus.size())/3;
474 /* determine distribution of points in elementary cubes */
475 if (cubus)
477 ico_cube = cubus;
479 else
481 i = 1;
482 while (i*i*i*2 < ndot)
484 i++;
486 ico_cube = std::max(i-1, 0);
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++)
493 i = std::max(static_cast<int>(floor((1.+xus[3*l])/del_cube)), 0);
494 if (i >= ico_cube)
496 i = ico_cube-1;
498 j = std::max(static_cast<int>(floor((1.+xus[1+3*l])/del_cube)), 0);
499 if (j >= ico_cube)
501 j = ico_cube-1;
503 k = std::max(static_cast<int>(floor((1.+xus[2+3*l])/del_cube)), 0);
504 if (k >= ico_cube)
506 k = ico_cube-1;
508 ijk = i+j*ico_cube+k*ico_cube*ico_cube;
509 work[l] = ijk;
512 snew(ico_wk, 2*ico_cube_cb+1);
514 ico_pt = ico_wk+ico_cube_cb;
515 for (l = 0; l < ndot; l++)
517 ico_wk[work[l]]++; /* dots per elementary cube */
520 /* reordering of the coordinate array in accordance with box number */
521 tn = 0;
522 for (i = 0; i < ico_cube; i++)
524 for (j = 0; j < ico_cube; j++)
526 for (k = 0; k < ico_cube; k++)
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++)
534 if (ijk == work[l])
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++;
544 *(ico_wk+ijk) = tl;
545 } /* cycle k */
546 } /* cycle j */
547 } /* cycle i */
548 sfree(ico_wk);
549 sfree(work);
551 return xus;
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)
563 const real dotarea = FOURPI/static_cast<real>(n_dot);
565 if (debug)
567 fprintf(debug, "nsc_dclm: n_dot=%5d %9.3f\n", n_dot, dotarea);
570 /* start with neighbour list */
571 /* calculate neighbour list with the box algorithm */
572 if (nat == 0)
574 return;
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)
581 vol = 0.;
583 if (mode & FLAG_DOTS)
585 maxdots = (3*n_dot*nat)/10;
586 snew(dots, maxdots);
587 lfnr = 0;
589 if (mode & FLAG_ATOM_AREA)
591 snew(atom_area, nat);
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)
604 const int iat = index[i];
605 xs += coords[iat][XX];
606 ys += coords[iat][YY];
607 zs += coords[iat][ZZ];
609 xs /= nat;
610 ys /= nat;
611 zs /= nat;
613 AnalysisNeighborhoodPositions pos(coords, radius.size());
614 pos.indexed(constArrayRefFromArray(index, nat));
615 AnalysisNeighborhoodSearch nbsearch(nb->initSearch(pbc, pos));
617 std::vector<int> wkdot(n_dot);
619 for (int i = 0; i < nat; ++i)
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))
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))
636 continue;
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)
654 if (wkdot[j] && iprod(&xus[3*j], dx) > refdot)
656 --currDotCount;
657 wkdot[j] = 0;
662 const real a = aisq * dotarea * currDotCount;
663 area = area + a;
664 if (mode & FLAG_ATOM_AREA)
666 atom_area[i] = a;
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)
673 for (int l = 0; l < n_dot; l++)
675 if (wkdot[l])
677 lfnr++;
678 if (maxdots <= 3*lfnr+1)
680 maxdots = maxdots+n_dot*3;
681 srenew(dots, maxdots);
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;
689 if (mode & FLAG_VOLUME)
691 real dx = 0.0, dy = 0.0, dz = 0.0;
692 for (int l = 0; l < n_dot; l++)
694 if (wkdot[l])
696 dx = dx+xus[3*l];
697 dy = dy+xus[1+3*l];
698 dz = dz+xus[2+3*l];
701 vol = vol+aisq*(dx*(xi-xs)+dy*(yi-ys)+dz*(zi-zs) + ai*currDotCount);
705 if (mode & FLAG_VOLUME)
707 *value_of_vol = vol*FOURPI/(3.*n_dot);
709 if (mode & FLAG_DOTS)
711 *nu_dots = lfnr;
712 *lidots = dots;
714 if (mode & FLAG_ATOM_AREA)
716 *at_area = atom_area;
718 *value_of_area = area;
720 if (debug)
722 fprintf(debug, "area=%8.3f\n", area);
726 namespace gmx
729 class SurfaceAreaCalculator::Impl
731 public:
732 Impl() : flags_(0)
736 std::vector<real> unitSphereDots_;
737 ArrayRef<const real> radius_;
738 int flags_;
739 mutable AnalysisNeighborhood nb_;
742 SurfaceAreaCalculator::SurfaceAreaCalculator()
743 : impl_(new Impl())
747 SurfaceAreaCalculator::~SurfaceAreaCalculator()
751 void SurfaceAreaCalculator::setDotCount(int dotCount)
753 impl_->unitSphereDots_ = make_unsp(dotCount, 4);
756 void SurfaceAreaCalculator::setRadii(const ArrayRef<const real> &radius)
758 impl_->radius_ = radius;
759 if (!radius.empty())
761 const real maxRadius = *std::max_element(radius.begin(), radius.end());
762 impl_->nb_.setCutoff(2*maxRadius);
766 void SurfaceAreaCalculator::setCalculateVolume(bool bVolume)
768 if (bVolume)
770 impl_->flags_ |= FLAG_VOLUME;
772 else
774 impl_->flags_ &= ~FLAG_VOLUME;
778 void SurfaceAreaCalculator::setCalculateAtomArea(bool bAtomArea)
780 if (bAtomArea)
782 impl_->flags_ |= FLAG_ATOM_AREA;
784 else
786 impl_->flags_ &= ~FLAG_ATOM_AREA;
790 void SurfaceAreaCalculator::setCalculateSurfaceDots(bool bDots)
792 if (bDots)
794 impl_->flags_ |= FLAG_DOTS;
796 else
798 impl_->flags_ &= ~FLAG_DOTS;
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
807 flags |= impl_->flags_;
808 *area = 0;
809 if (volume == nullptr)
811 flags &= ~FLAG_VOLUME;
813 else
815 *volume = 0;
817 if (at_area == nullptr)
819 flags &= ~FLAG_ATOM_AREA;
821 else
823 *at_area = nullptr;
825 if (lidots == nullptr)
827 flags &= ~FLAG_DOTS;
829 else
831 *lidots = nullptr;
833 if (n_dots == nullptr)
835 flags &= ~FLAG_DOTS;
837 else
839 *n_dots = 0;
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);
847 } // namespace gmx