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