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