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