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