4ad20f4e2cb92cf6471e04e25e66c9c1b9e4b991
[alexxy/gromacs.git] / src / gromacs / selection / sm_insolidangle.cpp
1 /*
2  * This file is part of the GROMACS molecular simulation package.
3  *
4  * Copyright (c) 2009,2010,2011,2012,2013,2014,2015,2016,2017,2018,2019, by the GROMACS development team, led by
5  * Mark Abraham, David van der Spoel, Berk Hess, and Erik Lindahl,
6  * and including many others, as listed in the AUTHORS file in the
7  * top-level source directory and at http://www.gromacs.org.
8  *
9  * GROMACS is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public License
11  * as published by the Free Software Foundation; either version 2.1
12  * of the License, or (at your option) any later version.
13  *
14  * GROMACS is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with GROMACS; if not, see
21  * http://www.gnu.org/licenses, or write to the Free Software Foundation,
22  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA.
23  *
24  * If you want to redistribute modifications to GROMACS, please
25  * consider that scientific software is very special. Version
26  * control is crucial - bugs must be traceable. We will be happy to
27  * consider code for inclusion in the official distribution, but
28  * derived work must not be called official GROMACS. Details are found
29  * in the README & COPYING files - if they are missing, get the
30  * official version at http://www.gromacs.org.
31  *
32  * To help us fund GROMACS development, we humbly ask that you cite
33  * the research papers on the package. Check out http://www.gromacs.org.
34  */
35 /*! \internal
36  * \page page_module_selection_insolidangle Selection method: insolidangle
37  *
38  * This method selects a subset of particles that are located in a solid
39  * angle defined by a center and a set of points.
40  * The solid angle is constructed as a union of small cones whose axis
41  * goes through the center and a point.
42  * So there's such a cone for each position, and a
43  * point is in the solid angle if it lies within any of these cones.
44  * The width of the cones can be adjusted.
45  *
46  * The method is implemented by partitioning the surface of the unit sphere
47  * into bins using the polar coordinates \f$(\theta, \phi)\f$.
48  * The partitioning is always uniform in the zenith angle \f$\theta\f$,
49  * while the partitioning in the azimuthal angle \f$\phi\f$ varies.
50  * For each reference point, the unit vector from the center to the point
51  * is constructed, and it is stored in all the bins that overlap with the
52  * cone defined by the point.
53  * Bins that are completely covered by a single cone are marked as such.
54  * Checking whether a point is in the solid angle is then straightforward
55  * with this data structure: one finds the bin that corresponds to the point,
56  * and checks whether the bin is completely covered. If it is not, one
57  * additionally needs to check whether it is within the specified cutoff of
58  * any of the stored points.
59  *
60  * The above construction gives quite a lot of flexibility for constructing
61  * the bins without modifying the rest of the code.
62  * The current (quite inefficient) implementation is discussed below, but
63  * it should be optimized to get the most out of the code.
64  *
65  * The current way of constructing the bins constructs the boundaries
66  * statically: the bin size in the zenith direction is set to approximately
67  * half the angle cutoff, and the bins in the azimuthal direction have
68  * sizes such that the shortest edge of the bin is approximately equal to
69  * half the angle cutoff (for the regions close to the poles, a single bin
70  * is used).
71  * Each reference point is then added to the bins as follows:
72  *  -# Find the zenith angle range that is spanned by the cone centered at the
73  *     point (this is simple addition/subtraction).
74  *  -# Calculate the maximal span of the cone in the azimuthal direction using
75  *     the formula
76  *     \f[\sin \Delta \phi_{max} = \frac{\sin \alpha}{\sin \theta}\f]
77  *     (a sine formula in spherical coordinates),
78  *     where \f$\alpha\f$ is the width of the cone and \f$\theta\f$ is the
79  *     zenith angle of the cone center.
80  *     Similarly, the zenith angle at which this extent is achieved is
81  *     calculated using
82  *     \f[\cos \theta_{max} = \frac{\cos \theta}{\cos \alpha}\f]
83  *     (Pythagoras's theorem in spherical coordinates).
84  *  -# For each zenith angle bin that is at least partially covered by the
85  *     cone, calculate the span of the cone at the edges using
86  *     \f[\sin^2 \frac{\Delta \phi}{2} = \frac{\sin^2 \frac{\alpha}{2} - \sin^2 \frac{\theta -
87  * \theta'}{2}}{\sin \theta \sin \theta'}\f] (distance in spherical geometry), where \f$\theta'\f$
88  * is the zenith angle of the bin edge. Treat zenith angle bins that are completely covered by the
89  * cone (in the case that the cone is centered close to the pole) as a special case.
90  *  -# Using the values calculated above, loop through the azimuthal bins that
91  *     are partially or completely covered by the cone and update them.
92  *
93  * The total solid angle (for covered fraction calculations) is estimated by
94  * taking the total area of completely covered bins plus
95  * half the area of partially covered bins.
96  * The second one is an approximation, but should give reasonable estimates
97  * for the averages as well as in cases where the bin size is small.
98  */
99 /*! \internal \file
100  * \brief
101  * Implements the \ref sm_insolidangle "insolidangle" selection method.
102  *
103  * \todo
104  * The implementation could be optimized quite a bit.
105  *
106  * \todo
107  * Move the covered fraction stuff somewhere else and make it more generic
108  * (along the lines it is handled in selection.h and trajana.h in the old C
109  * API).
110  *
111  * \author Teemu Murtola <teemu.murtola@gmail.com>
112  * \ingroup module_selection
113  */
114 #include "gmxpre.h"
115
116 #include <cmath>
117
118 #include <algorithm>
119
120 #include "gromacs/math/functions.h"
121 #include "gromacs/math/units.h"
122 #include "gromacs/math/utilities.h"
123 #include "gromacs/math/vec.h"
124 #include "gromacs/pbcutil/pbc.h"
125 #include "gromacs/selection/indexutil.h"
126 #include "gromacs/selection/selection.h"
127 #include "gromacs/utility/arraysize.h"
128 #include "gromacs/utility/exceptions.h"
129 #include "gromacs/utility/smalloc.h"
130
131 #include "position.h"
132 #include "selelem.h"
133 #include "selmethod.h"
134 #include "selmethod_impl.h"
135
136 using std::max;
137 using std::min;
138
139 /*! \internal
140  * \brief
141  * Internal data structure for the \p insolidangle selection method.
142  *
143  * \see \c t_partition
144  *
145  * \ingroup module_selection
146  */
147 typedef struct
148 {
149     /** Left edge of the partition. */
150     real left;
151     /** Bin index corresponding to this partition. */
152     int bin;
153 } t_partition_item;
154
155 /*! \internal
156  * \brief
157  * Internal data structure for the \p insolidangle selection method.
158  *
159  * Describes the surface partitioning within one slice along the zenith angle.
160  * The slice from azimuthal angle \p p[i].left to \p p[i+1].left belongs to
161  * bin \p p[i].bin.
162  *
163  * \ingroup module_selection
164  */
165 typedef struct
166 {
167     /** Number of partition items (\p p contains \p n+1 items). */
168     int n;
169     /** Array of partition edges and corresponding bins. */
170     t_partition_item* p;
171 } t_partition;
172
173 /*! \internal
174  * \brief
175  * Internal data structure for the \p insolidangle selection method.
176  *
177  * Contains the reference points that partially cover a certain region on the
178  * surface of the unit sphere.
179  * If \p n is -1, the whole region described by the bin is covered.
180  *
181  * \ingroup module_selection
182  */
183 typedef struct
184 {
185     /** Number of points in the array \p x, -1 if whole bin covered. */
186     int n;
187     /** Number of elements allocated for \p x. */
188     int n_alloc;
189     /** Array of points that partially cover the bin. */
190     rvec* x;
191 } t_spheresurfacebin;
192
193 /*! \internal
194  * \brief
195  * Data structure for the \p insolidangle selection method.
196  *
197  * All angle values are in the units of radians.
198  *
199  * \ingroup module_selection
200  */
201 typedef struct
202 {
203     /** Center of the solid angle. */
204     gmx_ana_pos_t center;
205     /** Positions that span the solid angle. */
206     gmx_ana_pos_t span;
207     /** Cutoff angle. */
208     real angcut;
209     /** Estimate of the covered fraction. */
210     real cfrac;
211
212     /** Cutoff for the cosine (equals cos(angcut)). */
213     real distccut;
214     /** Bin size to be used as the target bin size when constructing the bins. */
215     real targetbinsize;
216
217     /** Number of bins in the \p tbin array. */
218     int ntbins;
219     /** Size of one bin in the zenith angle direction. */
220     real tbinsize;
221     /** Array of zenith angle slices. */
222     t_partition* tbin;
223     /** Number of elements allocated for the \p bin array. */
224     int maxbins;
225     /** Number of elements used in the \p bin array. */
226     int nbins;
227     /** Array of individual bins. */
228     t_spheresurfacebin* bin;
229 } t_methoddata_insolidangle;
230
231 /*! \brief
232  * Allocates data for the \p insolidangle selection method.
233  *
234  * \param[in]     npar  Not used (should be 3).
235  * \param[in,out] param Method parameters (should point to
236  *   \ref smparams_insolidangle).
237  * \returns Pointer to the allocated data (\ref t_methoddata_insolidangle).
238  *
239  * Allocates memory for a \ref t_methoddata_insolidangle structure and
240  * initializes the parameter as follows:
241  *  - \p center defines the value for t_methoddata_insolidangle::center.
242  *  - \p span   defines the value for t_methoddata_insolidangle::span.
243  *  - \p cutoff defines the value for t_methoddata_insolidangle::angcut.
244  */
245 static void* init_data_insolidangle(int npar, gmx_ana_selparam_t* param);
246 /*! \brief
247  * Initializes the \p insolidangle selection method.
248  *
249  * \param   top  Not used.
250  * \param   npar Not used.
251  * \param   param Not used.
252  * \param   data Pointer to \ref t_methoddata_insolidangle to initialize.
253  * \returns 0 on success, -1 on failure.
254  *
255  * Converts t_methoddata_insolidangle::angcut to radians and allocates
256  * and allocates memory for the bins used during the evaluation.
257  */
258 static void init_insolidangle(const gmx_mtop_t* top, int npar, gmx_ana_selparam_t* param, void* data);
259 /** Frees the data allocated for the \p insolidangle selection method. */
260 static void free_data_insolidangle(void* data);
261 /*! \brief
262  * Initializes the evaluation of the \p insolidangle selection method for a frame.
263  *
264  * \param[in]  context Evaluation context.
265  * \param      data    Should point to a \ref t_methoddata_insolidangle.
266  *
267  * Creates a lookup structure that enables fast queries of whether a point
268  * is within the solid angle or not.
269  */
270 static void init_frame_insolidangle(const gmx::SelMethodEvalContext& context, void* data);
271 /** Internal helper function for evaluate_insolidangle(). */
272 static bool accept_insolidangle(rvec x, const t_pbc* pbc, void* data);
273 /** Evaluates the \p insolidangle selection method. */
274 static void evaluate_insolidangle(const gmx::SelMethodEvalContext& context,
275                                   gmx_ana_pos_t*                   pos,
276                                   gmx_ana_selvalue_t*              out,
277                                   void*                            data);
278
279 /** Calculates the distance between unit vectors. */
280 static real sph_distc(rvec x1, rvec x2);
281 /** Does a binary search on a \p t_partition to find a bin for a value. */
282 static int find_partition_bin(t_partition* p, real value);
283 /** Finds a bin that corresponds to a location on the unit sphere surface. */
284 static int find_surface_bin(t_methoddata_insolidangle* surf, rvec x);
285 /** Clears/initializes the bins on the unit sphere surface. */
286 static void clear_surface_points(t_methoddata_insolidangle* surf);
287 /** Frees memory allocated for storing the reference points in the surface bins. */
288 static void free_surface_points(t_methoddata_insolidangle* surf);
289 /** Adds a reference point to a given bin. */
290 static void add_surface_point(t_methoddata_insolidangle* surf, int tbin, int pbin, rvec x);
291 /** Marks a bin as completely covered. */
292 static void mark_surface_covered(t_methoddata_insolidangle* surf, int tbin, int pbin);
293 /** Helper function for store_surface_point() to update a single zenith angle bin. */
294 static void update_surface_bin(t_methoddata_insolidangle* surf,
295                                int                        tbin,
296                                real                       phi,
297                                real                       pdelta1,
298                                real                       pdelta2,
299                                real                       pdeltamax,
300                                rvec                       x);
301 /** Adds a single reference point and updates the surface bins. */
302 static void store_surface_point(t_methoddata_insolidangle* surf, rvec x);
303 /*! \brief
304  * Optimizes the surface bins for faster searching.
305  *
306  * \param[in,out] surf Surface data structure.
307  *
308  * Currently, this function does nothing.
309  */
310 static void optimize_surface_points(t_methoddata_insolidangle* surf);
311 /** Estimates the area covered by the reference cones. */
312 static real estimate_covered_fraction(t_methoddata_insolidangle* surf);
313 /** Checks whether a point lies within a solid angle. */
314 static bool is_surface_covered(t_methoddata_insolidangle* surf, rvec x);
315
316 /** Parameters for the \p insolidangle selection method. */
317 static gmx_ana_selparam_t smparams_insolidangle[] = {
318     { "center", { POS_VALUE, 1, { nullptr } }, nullptr, SPAR_DYNAMIC },
319     { "span", { POS_VALUE, -1, { nullptr } }, nullptr, SPAR_DYNAMIC | SPAR_VARNUM },
320     { "cutoff", { REAL_VALUE, 1, { nullptr } }, nullptr, SPAR_OPTIONAL },
321 };
322
323 /** Help text for the \p insolidangle selection method. */
324 static const char* const help_insolidangle[] = {
325     "::",
326     "",
327     "  insolidangle center POS span POS_EXPR [cutoff REAL]",
328     "",
329     "This keyword selects atoms that are within [TT]REAL[tt] degrees",
330     "(default=5) of any position in [TT]POS_EXPR[tt] as seen from [TT]POS[tt]",
331     "a position expression that evaluates to a single position), i.e., atoms",
332     "in the solid angle spanned by the positions in [TT]POS_EXPR[tt] and",
333     "centered at [TT]POS[tt].[PAR]",
334
335     "Technically, the solid angle is constructed as a union of small cones",
336     "whose tip is at [TT]POS[tt] and the axis goes through a point in",
337     "[TT]POS_EXPR[tt]. There is such a cone for each position in",
338     "[TT]POS_EXPR[tt], and point is in the solid angle if it lies within any",
339     "of these cones. The cutoff determines the width of the cones.",
340 };
341
342 /** Selection method data for the \p insolidangle method. */
343 gmx_ana_selmethod_t sm_insolidangle = {
344     "insolidangle",
345     GROUP_VALUE,
346     SMETH_DYNAMIC,
347     asize(smparams_insolidangle),
348     smparams_insolidangle,
349     &init_data_insolidangle,
350     nullptr,
351     &init_insolidangle,
352     nullptr,
353     &free_data_insolidangle,
354     &init_frame_insolidangle,
355     nullptr,
356     &evaluate_insolidangle,
357     { "insolidangle center POS span POS_EXPR [cutoff REAL]", "Selecting atoms in a solid angle",
358       asize(help_insolidangle), help_insolidangle },
359 };
360
361 static void* init_data_insolidangle(int /* npar */, gmx_ana_selparam_t* param)
362 {
363     t_methoddata_insolidangle* data = new t_methoddata_insolidangle();
364     data->angcut                    = 5.0;
365     data->cfrac                     = 0.0;
366
367     data->distccut      = 0.0;
368     data->targetbinsize = 0.0;
369
370     data->ntbins   = 0;
371     data->tbinsize = 0.0;
372     data->tbin     = nullptr;
373     data->maxbins  = 0;
374     data->nbins    = 0;
375     data->bin      = nullptr;
376
377     param[0].val.u.p = &data->center;
378     param[1].val.u.p = &data->span;
379     param[2].val.u.r = &data->angcut;
380     return data;
381 }
382
383 static void init_insolidangle(const gmx_mtop_t* /* top */,
384                               int /* npar */,
385                               gmx_ana_selparam_t* /* param */,
386                               void* data)
387 {
388     t_methoddata_insolidangle* surf = static_cast<t_methoddata_insolidangle*>(data);
389     int                        i, c;
390
391     if (surf->angcut <= 0)
392     {
393         GMX_THROW(gmx::InvalidInputError("Angle cutoff should be > 0"));
394     }
395
396     surf->angcut *= DEG2RAD;
397
398     surf->distccut      = -std::cos(surf->angcut);
399     surf->targetbinsize = surf->angcut / 2;
400     surf->ntbins        = static_cast<int>(M_PI / surf->targetbinsize);
401     surf->tbinsize      = (180.0 / surf->ntbins) * DEG2RAD;
402
403     snew(surf->tbin, static_cast<int>(M_PI / surf->tbinsize) + 1);
404     surf->maxbins = 0;
405     for (i = 0; i < surf->ntbins; ++i)
406     {
407         c = static_cast<int>(std::max(std::sin(surf->tbinsize * i), std::sin(surf->tbinsize * (i + 1)))
408                              * M_2PI / surf->targetbinsize)
409             + 1;
410         snew(surf->tbin[i].p, c + 1);
411         surf->maxbins += c;
412     }
413     surf->nbins = 0;
414     snew(surf->bin, surf->maxbins);
415 }
416
417 /*!
418  * \param data Data to free (should point to a \ref t_methoddata_insolidangle).
419  *
420  * Frees the memory allocated for \c t_methoddata_insolidangle::center and
421  * \c t_methoddata_insolidangle::span, as well as the memory for the internal
422  * bin structure.
423  */
424 static void free_data_insolidangle(void* data)
425 {
426     t_methoddata_insolidangle* d = static_cast<t_methoddata_insolidangle*>(data);
427     int                        i;
428
429     if (d->tbin)
430     {
431         for (i = 0; i < d->ntbins; ++i)
432         {
433             sfree(d->tbin[i].p);
434         }
435         sfree(d->tbin);
436     }
437     free_surface_points(d);
438     sfree(d->bin);
439     delete d;
440 }
441
442 static void init_frame_insolidangle(const gmx::SelMethodEvalContext& context, void* data)
443 {
444     t_methoddata_insolidangle* d = static_cast<t_methoddata_insolidangle*>(data);
445     rvec                       dx;
446     int                        i;
447
448     free_surface_points(d);
449     clear_surface_points(d);
450     for (i = 0; i < d->span.count(); ++i)
451     {
452         if (context.pbc)
453         {
454             pbc_dx(context.pbc, d->span.x[i], d->center.x[0], dx);
455         }
456         else
457         {
458             rvec_sub(d->span.x[i], d->center.x[0], dx);
459         }
460         unitv(dx, dx);
461         store_surface_point(d, dx);
462     }
463     optimize_surface_points(d);
464     d->cfrac = -1;
465 }
466
467 /*!
468  * \param[in] x    Test point.
469  * \param[in] pbc  PBC data (if NULL, no PBC are used).
470  * \param[in] data Pointer to a \c t_methoddata_insolidangle data structure.
471  * \returns   true if \p x is within the solid angle, false otherwise.
472  */
473 static bool accept_insolidangle(rvec x, const t_pbc* pbc, void* data)
474 {
475     t_methoddata_insolidangle* d = static_cast<t_methoddata_insolidangle*>(data);
476     rvec                       dx;
477
478     if (pbc)
479     {
480         pbc_dx(pbc, x, d->center.x[0], dx);
481     }
482     else
483     {
484         rvec_sub(x, d->center.x[0], dx);
485     }
486     unitv(dx, dx);
487     return is_surface_covered(d, dx);
488 }
489
490 /*!
491  * See sel_updatefunc() for description of the parameters.
492  * \p data should point to a \c t_methoddata_insolidangle.
493  *
494  * Calculates which atoms in \p g are within the solid angle spanned by
495  * \c t_methoddata_insolidangle::span and centered at
496  * \c t_methoddata_insolidangle::center, and stores the result in \p out->u.g.
497  */
498 static void evaluate_insolidangle(const gmx::SelMethodEvalContext& context,
499                                   gmx_ana_pos_t*                   pos,
500                                   gmx_ana_selvalue_t*              out,
501                                   void*                            data)
502 {
503     out->u.g->isize = 0;
504     for (int b = 0; b < pos->count(); ++b)
505     {
506         if (accept_insolidangle(pos->x[b], context.pbc, data))
507         {
508             gmx_ana_pos_add_to_group(out->u.g, pos, b);
509         }
510     }
511 }
512
513 /*!
514  * \param[in] sel Selection element to query.
515  * \returns   true if the covered fraction can be estimated for \p sel with
516  *   _gmx_selelem_estimate_coverfrac(), false otherwise.
517  */
518 bool _gmx_selelem_can_estimate_cover(const gmx::SelectionTreeElement& sel)
519 {
520     if (sel.type == SEL_BOOLEAN && sel.u.boolt == BOOL_OR)
521     {
522         return false;
523     }
524     bool                             bFound    = false;
525     bool                             bDynFound = false;
526     gmx::SelectionTreeElementPointer child     = sel.child;
527     while (child)
528     {
529         if (child->type == SEL_EXPRESSION)
530         {
531             if (child->u.expr.method->name == sm_insolidangle.name)
532             {
533                 if (bFound || bDynFound)
534                 {
535                     return false;
536                 }
537                 bFound = true;
538             }
539             else if (child->u.expr.method && (child->u.expr.method->flags & SMETH_DYNAMIC))
540             {
541                 if (bFound)
542                 {
543                     return false;
544                 }
545                 bDynFound = true;
546             }
547         }
548         else if (!_gmx_selelem_can_estimate_cover(*child))
549         {
550             return false;
551         }
552         child = child->next;
553     }
554     return true;
555 }
556
557 /*!
558  * \param[in] sel Selection for which the fraction should be calculated.
559  * \returns Fraction of angles covered by the selection (between zero and one).
560  *
561  * The return value is undefined if _gmx_selelem_can_estimate_cover() returns
562  * false.
563  * Should be called after gmx_ana_evaluate_selections() has been called for the
564  * frame.
565  */
566 real _gmx_selelem_estimate_coverfrac(const gmx::SelectionTreeElement& sel)
567 {
568     real cfrac;
569
570     if (sel.type == SEL_EXPRESSION && sel.u.expr.method->name == sm_insolidangle.name)
571     {
572         t_methoddata_insolidangle* d = static_cast<t_methoddata_insolidangle*>(sel.u.expr.mdata);
573         if (d->cfrac < 0)
574         {
575             d->cfrac = estimate_covered_fraction(d);
576         }
577         return d->cfrac;
578     }
579     if (sel.type == SEL_BOOLEAN && sel.u.boolt == BOOL_NOT)
580     {
581         cfrac = _gmx_selelem_estimate_coverfrac(*sel.child);
582         if (cfrac < 1.0)
583         {
584             return 1 - cfrac;
585         }
586         return 1;
587     }
588
589     /* Here, we assume that the selection is simple enough */
590     gmx::SelectionTreeElementPointer child = sel.child;
591     while (child)
592     {
593         cfrac = _gmx_selelem_estimate_coverfrac(*child);
594         if (cfrac < 1.0)
595         {
596             return cfrac;
597         }
598         child = child->next;
599     }
600     return 1.0;
601 }
602
603 /*!
604  * \param[in] x1  Unit vector 1.
605  * \param[in] x2  Unit vector 2.
606  * \returns   Minus the dot product of \p x1 and \p x2.
607  *
608  * This function is used internally to calculate the distance between the
609  * unit vectors \p x1 and \p x2 to find out whether \p x2 is within the
610  * cone centered at \p x1. Currently, the cosine of the angle is used
611  * for efficiency, and the minus is there to make it behave like a normal
612  * distance (larger values mean longer distances).
613  */
614 static real sph_distc(rvec x1, rvec x2)
615 {
616     return -iprod(x1, x2);
617 }
618
619 /*!
620  * \param[in] p     Partition to search.
621  * \param[in] value Value to search for.
622  * \returns   The partition index in \p p that contains \p value.
623  *
624  * If \p value is outside the range of \p p, the first/last index is returned.
625  * Otherwise, the return value \c i satisfies \c p->p[i].left<=value and
626  * \c p->p[i+1].left>value
627  */
628 static int find_partition_bin(t_partition* p, real value)
629 {
630     int pmin, pmax, pbin;
631
632     /* Binary search the partition */
633     pmin = 0;
634     pmax = p->n;
635     while (pmax > pmin + 1)
636     {
637         pbin = pmin + (pmax - pmin) / 2;
638         if (p->p[pbin].left <= value)
639         {
640             pmin = pbin;
641         }
642         else
643         {
644             pmax = pbin;
645         }
646     }
647     pbin = pmin;
648     return pbin;
649 }
650
651 /*!
652  * \param[in] surf  Surface data structure to search.
653  * \param[in] x     Unit vector to find.
654  * \returns   The bin index that contains \p x.
655  *
656  * The return value is an index to the \p surf->bin array.
657  */
658 static int find_surface_bin(t_methoddata_insolidangle* surf, rvec x)
659 {
660     real theta, phi;
661     int  tbin, pbin;
662
663     theta = acos(x[ZZ]);
664     phi   = atan2(x[YY], x[XX]);
665     tbin  = static_cast<int>(std::floor(theta / surf->tbinsize));
666     if (tbin >= surf->ntbins)
667     {
668         tbin = surf->ntbins - 1;
669     }
670     pbin = find_partition_bin(&surf->tbin[tbin], phi);
671     return surf->tbin[tbin].p[pbin].bin;
672 }
673
674 /*!
675  * \param[in,out] surf Surface data structure.
676  *
677  * Clears the reference points from the bins and (re)initializes the edges
678  * of the azimuthal bins.
679  */
680 static void clear_surface_points(t_methoddata_insolidangle* surf)
681 {
682     int i, j, c;
683
684     surf->nbins = 0;
685     for (i = 0; i < surf->ntbins; ++i)
686     {
687         c = static_cast<int>(std::min(std::sin(surf->tbinsize * i), std::sin(surf->tbinsize * (i + 1)))
688                              * M_2PI / surf->targetbinsize)
689             + 1;
690         if (c <= 0)
691         {
692             c = 1;
693         }
694         surf->tbin[i].n = c;
695         for (j = 0; j < c; ++j)
696         {
697             surf->tbin[i].p[j].left  = -M_PI + j * M_2PI / c - 0.0001;
698             surf->tbin[i].p[j].bin   = surf->nbins;
699             surf->bin[surf->nbins].n = 0;
700             surf->nbins++;
701         }
702         surf->tbin[i].p[c].left = M_PI + 0.0001;
703         surf->tbin[i].p[c].bin  = -1;
704     }
705 }
706
707 /*!
708  * \param[in,out] surf Surface data structure.
709  */
710 static void free_surface_points(t_methoddata_insolidangle* surf)
711 {
712     int i;
713
714     for (i = 0; i < surf->nbins; ++i)
715     {
716         if (surf->bin[i].x)
717         {
718             sfree(surf->bin[i].x);
719         }
720         surf->bin[i].n_alloc = 0;
721         surf->bin[i].x       = nullptr;
722     }
723 }
724
725 /*!
726  * \param[in,out] surf Surface data structure.
727  * \param[in]     tbin Bin number in the zenith angle direction.
728  * \param[in]     pbin Bin number in the azimuthal angle direction.
729  * \param[in]     x    Point to store.
730  */
731 static void add_surface_point(t_methoddata_insolidangle* surf, int tbin, int pbin, rvec x)
732 {
733     int bin;
734
735     bin = surf->tbin[tbin].p[pbin].bin;
736     /* Return if bin is already completely covered */
737     if (surf->bin[bin].n == -1)
738     {
739         return;
740     }
741     /* Allocate more space if necessary */
742     if (surf->bin[bin].n == surf->bin[bin].n_alloc)
743     {
744         surf->bin[bin].n_alloc += 10;
745         srenew(surf->bin[bin].x, surf->bin[bin].n_alloc);
746     }
747     /* Add the point to the bin */
748     copy_rvec(x, surf->bin[bin].x[surf->bin[bin].n]);
749     ++surf->bin[bin].n;
750 }
751
752 /*!
753  * \param[in,out] surf Surface data structure.
754  * \param[in]     tbin Bin number in the zenith angle direction.
755  * \param[in]     pbin Bin number in the azimuthal angle direction.
756  */
757 static void mark_surface_covered(t_methoddata_insolidangle* surf, int tbin, int pbin)
758 {
759     int bin;
760
761     bin              = surf->tbin[tbin].p[pbin].bin;
762     surf->bin[bin].n = -1;
763 }
764
765 /*!
766  * \param[in,out] surf      Surface data structure.
767  * \param[in]     tbin      Bin number in the zenith angle direction.
768  * \param[in]     phi       Azimuthal angle of \p x.
769  * \param[in]     pdelta1   Width of the cone at the lower edge of \p tbin.
770  * \param[in]     pdelta2   Width of the cone at the uppper edge of \p tbin.
771  * \param[in]     pdeltamax Max. width of the cone inside \p tbin.
772  * \param[in]     x         Point to store (should have unit length).
773  */
774 static void update_surface_bin(t_methoddata_insolidangle* surf,
775                                int                        tbin,
776                                real                       phi,
777                                real                       pdelta1,
778                                real                       pdelta2,
779                                real                       pdeltamax,
780                                rvec                       x)
781 {
782     real pdelta, phi1, phi2;
783     int  pbin1, pbin2, pbiniter, pbin;
784
785     /* Find the edges of the bins affected */
786     pdelta = max(max(pdelta1, pdelta2), pdeltamax);
787     phi1   = phi - pdelta;
788     if (phi1 >= -M_PI)
789     {
790         pbin  = find_partition_bin(&surf->tbin[tbin], phi1);
791         pbin1 = pbin;
792     }
793     else
794     {
795         pbin  = find_partition_bin(&surf->tbin[tbin], phi1 + M_2PI);
796         pbin1 = pbin - surf->tbin[tbin].n;
797     }
798     phi2 = phi + pdelta;
799     if (phi2 <= M_PI)
800     {
801         pbin2 = find_partition_bin(&surf->tbin[tbin], phi2);
802     }
803     else
804     {
805         pbin2 = find_partition_bin(&surf->tbin[tbin], phi2 - M_2PI);
806         pbin2 += surf->tbin[tbin].n;
807     }
808     ++pbin2;
809     if (pbin2 - pbin1 > surf->tbin[tbin].n)
810     {
811         pbin2 = pbin1 + surf->tbin[tbin].n;
812     }
813     /* Find the edges of completely covered region */
814     pdelta = min(pdelta1, pdelta2);
815     phi1   = phi - pdelta;
816     if (phi1 < -M_PI)
817     {
818         phi1 += M_2PI;
819     }
820     phi2 = phi + pdelta;
821     /* Loop over all affected bins */
822     for (pbiniter = pbin1; pbiniter != pbin2; ++pbiniter, ++pbin)
823     {
824         /* Wrap bin around if end reached */
825         if (pbin == surf->tbin[tbin].n)
826         {
827             pbin = 0;
828             phi1 -= M_2PI;
829             phi2 -= M_2PI;
830         }
831         /* Check if bin is completely covered and update */
832         if (surf->tbin[tbin].p[pbin].left >= phi1 && surf->tbin[tbin].p[pbin + 1].left <= phi2)
833         {
834             mark_surface_covered(surf, tbin, pbin);
835         }
836         else
837         {
838             add_surface_point(surf, tbin, pbin, x);
839         }
840     }
841 }
842
843 /*!
844  * \param[in,out] surf Surface data structure.
845  * \param[in]     x    Point to store (should have unit length).
846  *
847  * Finds all the bins covered by the cone centered at \p x and calls
848  * update_surface_bin() to update them.
849  */
850 static void store_surface_point(t_methoddata_insolidangle* surf, rvec x)
851 {
852     real theta, phi;
853     real pdeltamax, tmax;
854     real theta1, theta2, pdelta1, pdelta2;
855     int  tbin;
856
857     theta = acos(x[ZZ]);
858     phi   = atan2(x[YY], x[XX]);
859     /* Find the maximum extent in the phi direction */
860     if (theta <= surf->angcut)
861     {
862         pdeltamax = M_PI;
863         tmax      = 0;
864     }
865     else if (theta >= M_PI - surf->angcut)
866     {
867         pdeltamax = M_PI;
868         tmax      = M_PI;
869     }
870     else
871     {
872         pdeltamax = std::asin(sin(surf->angcut) / sin(theta));
873         tmax      = std::acos(cos(theta) / cos(surf->angcut));
874     }
875     /* Find the first affected bin */
876     tbin   = max(static_cast<int>(std::floor((theta - surf->angcut) / surf->tbinsize)), 0);
877     theta1 = tbin * surf->tbinsize;
878     if (theta1 < theta - surf->angcut)
879     {
880         pdelta1 = 0;
881     }
882     else
883     {
884         pdelta1 = M_PI;
885     }
886     /* Loop through all affected bins */
887     while (tbin < std::ceil((theta + surf->angcut) / surf->tbinsize) && tbin < surf->ntbins)
888     {
889         /* Calculate the next boundaries */
890         theta2 = (tbin + 1) * surf->tbinsize;
891         if (theta2 > theta + surf->angcut)
892         {
893             /* The circle is completely outside the cone */
894             pdelta2 = 0;
895         }
896         else if (theta2 <= -(theta - surf->angcut) || theta2 >= M_2PI - (theta + surf->angcut)
897                  || tbin == surf->ntbins - 1)
898         {
899             /* The circle is completely inside the cone, or we are in the
900              * 360 degree bin covering the pole. */
901             pdelta2 = M_PI;
902         }
903         else
904         {
905             /* TODO: This formula is numerically unstable if theta is very
906              * close to the pole.  In practice, it probably does not matter
907              * much, but it would be nicer to adjust the theta bin boundaries
908              * such that the case above catches this instead of falling through
909              * here. */
910             pdelta2 = 2
911                       * asin(std::sqrt((gmx::square(std::sin(surf->angcut / 2))
912                                         - gmx::square(std::sin((theta2 - theta) / 2)))
913                                        / (sin(theta) * sin(theta2))));
914         }
915         /* Update the bin */
916         if (tmax >= theta1 && tmax <= theta2)
917         {
918             update_surface_bin(surf, tbin, phi, pdelta1, pdelta2, pdeltamax, x);
919         }
920         else
921         {
922             update_surface_bin(surf, tbin, phi, pdelta1, pdelta2, 0, x);
923         }
924         /* Next bin */
925         theta1  = theta2;
926         pdelta1 = pdelta2;
927         ++tbin;
928     }
929 }
930
931 static void optimize_surface_points(t_methoddata_insolidangle* /* surf */)
932 {
933     /* TODO: Implement */
934 }
935
936 /*!
937  * \param[in] surf Surface data structure.
938  * \returns   An estimate for the area covered by the reference points.
939  */
940 static real estimate_covered_fraction(t_methoddata_insolidangle* surf)
941 {
942     int  t, p, n;
943     real cfrac, tfrac, pfrac;
944
945     cfrac = 0.0;
946     for (t = 0; t < surf->ntbins; ++t)
947     {
948         tfrac = std::cos(t * surf->tbinsize) - std::cos((t + 1) * surf->tbinsize);
949         for (p = 0; p < surf->tbin[t].n; ++p)
950         {
951             pfrac = surf->tbin[t].p[p + 1].left - surf->tbin[t].p[p].left;
952             n     = surf->bin[surf->tbin[t].p[p].bin].n;
953             if (n == -1) /* Bin completely covered */
954             {
955                 cfrac += tfrac * pfrac;
956             }
957             else if (n > 0) /* Bin partially covered */
958             {
959                 cfrac += tfrac * pfrac / 2; /* A rough estimate */
960             }
961         }
962     }
963     return cfrac / (4 * M_PI);
964 }
965
966 /*!
967  * \param[in] surf  Surface data structure to search.
968  * \param[in] x     Unit vector to check.
969  * \returns   true if \p x is within the solid angle, false otherwise.
970  */
971 static bool is_surface_covered(t_methoddata_insolidangle* surf, rvec x)
972 {
973     int bin, i;
974
975     bin = find_surface_bin(surf, x);
976     /* Check for completely covered bin */
977     if (surf->bin[bin].n == -1)
978     {
979         return true;
980     }
981     /* Check each point that partially covers the bin */
982     for (i = 0; i < surf->bin[bin].n; ++i)
983     {
984         if (sph_distc(x, surf->bin[bin].x[i]) < surf->distccut)
985         {
986             return true;
987         }
988     }
989     return false;
990 }