2 * This file is part of the GROMACS molecular simulation package.
4 * Copyright (c) 2009,2010,2011,2012,2013,2014,2015,2016,2017, 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.
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.
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.
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.
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.
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.
36 * \page page_module_selection_insolidangle Selection method: insolidangle
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.
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.
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.
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
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
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
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.
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.
102 * Implements the \ref sm_insolidangle "insolidangle" selection method.
105 * The implementation could be optimized quite a bit.
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
112 * \author Teemu Murtola <teemu.murtola@gmail.com>
113 * \ingroup module_selection
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"
134 #include "selmethod.h"
141 * Internal data structure for the \p insolidangle selection method.
143 * \see \c t_partition
145 * \ingroup module_selection
149 /** Left edge of the partition. */
151 /** Bin index corresponding to this partition. */
157 * Internal data structure for the \p insolidangle selection method.
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
163 * \ingroup module_selection
167 /** Number of partition items (\p p contains \p n+1 items). */
169 /** Array of partition edges and corresponding bins. */
175 * Internal data structure for the \p insolidangle selection method.
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.
181 * \ingroup module_selection
185 /** Number of points in the array \p x, -1 if whole bin covered. */
187 /** Number of elements allocated for \p x. */
189 /** Array of points that partially cover the bin. */
191 } t_spheresurfacebin;
195 * Data structure for the \p insolidangle selection method.
197 * All angle values are in the units of radians.
199 * \ingroup module_selection
203 /** Center of the solid angle. */
204 gmx_ana_pos_t center;
205 /** Positions that span the solid angle. */
209 /** Estimate of the covered fraction. */
212 /** Cutoff for the cosine (equals cos(angcut)). */
214 /** Bin size to be used as the target bin size when constructing the bins. */
217 /** Number of bins in the \p tbin array. */
219 /** Size of one bin in the zenith angle direction. */
221 /** Array of zenith angle slices. */
223 /** Number of elements allocated for the \p bin array. */
225 /** Number of elements used in the \p bin array. */
227 /** Array of individual bins. */
228 t_spheresurfacebin *bin;
229 } t_methoddata_insolidangle;
232 * Allocates data for the \p insolidangle selection method.
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).
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.
246 init_data_insolidangle(int npar, gmx_ana_selparam_t *param);
248 * Initializes the \p insolidangle selection method.
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.
256 * Converts t_methoddata_insolidangle::angcut to radians and allocates
257 * and allocates memory for the bins used during the evaluation.
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. */
263 free_data_insolidangle(void *data);
265 * Initializes the evaluation of the \p insolidangle selection method for a frame.
267 * \param[in] context Evaluation context.
268 * \param data Should point to a \ref t_methoddata_insolidangle.
270 * Creates a lookup structure that enables fast queries of whether a point
271 * is within the solid angle or not.
274 init_frame_insolidangle(const gmx::SelMethodEvalContext &context, void *data);
275 /** Internal helper function for evaluate_insolidangle(). */
277 accept_insolidangle(rvec x, const t_pbc *pbc, void *data);
278 /** Evaluates the \p insolidangle selection method. */
280 evaluate_insolidangle(const gmx::SelMethodEvalContext &context,
281 gmx_ana_pos_t *pos, gmx_ana_selvalue_t *out, void *data);
283 /** Calculates the distance between unit vectors. */
285 sph_distc(rvec x1, rvec x2);
286 /** Does a binary search on a \p t_partition to find a bin for a value. */
288 find_partition_bin(t_partition *p, real value);
289 /** Finds a bin that corresponds to a location on the unit sphere surface. */
291 find_surface_bin(t_methoddata_insolidangle *surf, rvec x);
292 /** Clears/initializes the bins on the unit sphere surface. */
294 clear_surface_points(t_methoddata_insolidangle *surf);
295 /** Frees memory allocated for storing the reference points in the surface bins. */
297 free_surface_points(t_methoddata_insolidangle *surf);
298 /** Adds a reference point to a given bin. */
300 add_surface_point(t_methoddata_insolidangle *surf, int tbin, int pbin, rvec x);
301 /** Marks a bin as completely covered. */
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. */
306 update_surface_bin(t_methoddata_insolidangle *surf, int tbin,
307 real phi, real pdelta1, real pdelta2, real pdeltamax,
309 /** Adds a single reference point and updates the surface bins. */
311 store_surface_point(t_methoddata_insolidangle *surf, rvec x);
313 * Optimizes the surface bins for faster searching.
315 * \param[in,out] surf Surface data structure.
317 * Currently, this function does nothing.
320 optimize_surface_points(t_methoddata_insolidangle *surf);
321 /** Estimates the area covered by the reference cones. */
323 estimate_covered_fraction(t_methoddata_insolidangle *surf);
324 /** Checks whether a point lies within a solid angle. */
326 is_surface_covered(t_methoddata_insolidangle *surf, rvec x);
328 /** Parameters for the \p insolidangle selection method. */
329 static gmx_ana_selparam_t smparams_insolidangle[] = {
330 {"center", {POS_VALUE, 1, {nullptr}}, nullptr, SPAR_DYNAMIC},
331 {"span", {POS_VALUE, -1, {nullptr}}, nullptr, SPAR_DYNAMIC | SPAR_VARNUM},
332 {"cutoff", {REAL_VALUE, 1, {nullptr}}, nullptr, SPAR_OPTIONAL},
335 /** Help text for the \p insolidangle selection method. */
336 static const char *const help_insolidangle[] = {
339 " insolidangle center POS span POS_EXPR [cutoff REAL]",
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]"
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.",
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,
362 &free_data_insolidangle,
363 &init_frame_insolidangle,
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},
372 init_data_insolidangle(int /* npar */, gmx_ana_selparam_t *param)
374 t_methoddata_insolidangle *data = new t_methoddata_insolidangle();
375 // cppcheck-suppress uninitdata
377 // cppcheck-suppress uninitdata
380 // cppcheck-suppress uninitdata
381 data->distccut = 0.0;
382 // cppcheck-suppress uninitdata
383 data->targetbinsize = 0.0;
385 // cppcheck-suppress uninitdata
387 // cppcheck-suppress uninitdata
388 data->tbinsize = 0.0;
389 // cppcheck-suppress uninitdata
390 data->tbin = nullptr;
391 // cppcheck-suppress uninitdata
393 // cppcheck-suppress uninitdata
395 // cppcheck-suppress uninitdata
398 param[0].val.u.p = &data->center;
399 param[1].val.u.p = &data->span;
400 param[2].val.u.r = &data->angcut;
405 init_insolidangle(const gmx_mtop_t * /* top */, int /* npar */, gmx_ana_selparam_t * /* param */, void *data)
407 t_methoddata_insolidangle *surf = (t_methoddata_insolidangle *)data;
410 if (surf->angcut <= 0)
412 GMX_THROW(gmx::InvalidInputError("Angle cutoff should be > 0"));
415 surf->angcut *= DEG2RAD;
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;
422 snew(surf->tbin, static_cast<int>(M_PI / surf->tbinsize) + 1);
424 for (i = 0; i < surf->ntbins; ++i)
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);
433 snew(surf->bin, surf->maxbins);
437 * \param data Data to free (should point to a \ref t_methoddata_insolidangle).
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
444 free_data_insolidangle(void *data)
446 t_methoddata_insolidangle *d = (t_methoddata_insolidangle *)data;
451 for (i = 0; i < d->ntbins; ++i)
457 free_surface_points(d);
463 init_frame_insolidangle(const gmx::SelMethodEvalContext &context, void *data)
465 t_methoddata_insolidangle *d = (t_methoddata_insolidangle *)data;
469 free_surface_points(d);
470 clear_surface_points(d);
471 for (i = 0; i < d->span.count(); ++i)
475 pbc_dx(context.pbc, d->span.x[i], d->center.x[0], dx);
479 rvec_sub(d->span.x[i], d->center.x[0], dx);
482 store_surface_point(d, dx);
484 optimize_surface_points(d);
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.
495 accept_insolidangle(rvec x, const t_pbc *pbc, void *data)
497 t_methoddata_insolidangle *d = (t_methoddata_insolidangle *)data;
502 pbc_dx(pbc, x, d->center.x[0], dx);
506 rvec_sub(x, d->center.x[0], dx);
509 return is_surface_covered(d, dx);
513 * See sel_updatefunc() for description of the parameters.
514 * \p data should point to a \c t_methoddata_insolidangle.
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.
521 evaluate_insolidangle(const gmx::SelMethodEvalContext &context,
522 gmx_ana_pos_t *pos, gmx_ana_selvalue_t *out, void *data)
525 for (int b = 0; b < pos->count(); ++b)
527 if (accept_insolidangle(pos->x[b], context.pbc, data))
529 gmx_ana_pos_add_to_group(out->u.g, pos, b);
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.
540 _gmx_selelem_can_estimate_cover(const gmx::SelectionTreeElement &sel)
542 if (sel.type == SEL_BOOLEAN && sel.u.boolt == BOOL_OR)
547 bool bDynFound = false;
548 gmx::SelectionTreeElementPointer child = sel.child;
551 if (child->type == SEL_EXPRESSION)
553 if (child->u.expr.method->name == sm_insolidangle.name)
555 if (bFound || bDynFound)
561 else if (child->u.expr.method
562 && (child->u.expr.method->flags & SMETH_DYNAMIC))
571 else if (!_gmx_selelem_can_estimate_cover(*child))
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).
584 * The return value is undefined if _gmx_selelem_can_estimate_cover() returns
586 * Should be called after gmx_ana_evaluate_selections() has been called for the
590 _gmx_selelem_estimate_coverfrac(const gmx::SelectionTreeElement &sel)
594 if (sel.type == SEL_EXPRESSION && sel.u.expr.method->name == sm_insolidangle.name)
596 t_methoddata_insolidangle *d = (t_methoddata_insolidangle *)sel.u.expr.mdata;
599 d->cfrac = estimate_covered_fraction(d);
603 if (sel.type == SEL_BOOLEAN && sel.u.boolt == BOOL_NOT)
605 cfrac = _gmx_selelem_estimate_coverfrac(*sel.child);
613 /* Here, we assume that the selection is simple enough */
614 gmx::SelectionTreeElementPointer child = sel.child;
617 cfrac = _gmx_selelem_estimate_coverfrac(*child);
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.
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).
639 sph_distc(rvec x1, rvec x2)
641 return -iprod(x1, x2);
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.
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
654 find_partition_bin(t_partition *p, real value)
656 int pmin, pmax, pbin;
658 /* Binary search the partition */
659 pmin = 0; pmax = p->n;
660 while (pmax > pmin + 1)
662 pbin = pmin + (pmax - pmin) / 2;
663 if (p->p[pbin].left <= value)
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.
681 * The return value is an index to the \p surf->bin array.
684 find_surface_bin(t_methoddata_insolidangle *surf, rvec x)
690 phi = atan2(x[YY], x[XX]);
691 tbin = static_cast<int>(floor(theta / surf->tbinsize));
692 if (tbin >= surf->ntbins)
694 tbin = surf->ntbins - 1;
696 pbin = find_partition_bin(&surf->tbin[tbin], phi);
697 return surf->tbin[tbin].p[pbin].bin;
701 * \param[in,out] surf Surface data structure.
703 * Clears the reference points from the bins and (re)initializes the edges
704 * of the azimuthal bins.
707 clear_surface_points(t_methoddata_insolidangle *surf)
712 for (i = 0; i < surf->ntbins; ++i)
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;
722 for (j = 0; j < c; ++j)
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;
729 surf->tbin[i].p[c].left = M_PI + 0.0001;
730 surf->tbin[i].p[c].bin = -1;
735 * \param[in,out] surf Surface data structure.
738 free_surface_points(t_methoddata_insolidangle *surf)
742 for (i = 0; i < surf->nbins; ++i)
746 sfree(surf->bin[i].x);
748 surf->bin[i].n_alloc = 0;
749 surf->bin[i].x = nullptr;
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.
760 add_surface_point(t_methoddata_insolidangle *surf, int tbin, int pbin, rvec x)
764 bin = surf->tbin[tbin].p[pbin].bin;
765 /* Return if bin is already completely covered */
766 if (surf->bin[bin].n == -1)
770 /* Allocate more space if necessary */
771 if (surf->bin[bin].n == surf->bin[bin].n_alloc)
773 surf->bin[bin].n_alloc += 10;
774 srenew(surf->bin[bin].x, surf->bin[bin].n_alloc);
776 /* Add the point to the bin */
777 copy_rvec(x, surf->bin[bin].x[surf->bin[bin].n]);
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.
787 mark_surface_covered(t_methoddata_insolidangle *surf, int tbin, int pbin)
791 bin = surf->tbin[tbin].p[pbin].bin;
792 surf->bin[bin].n = -1;
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).
805 update_surface_bin(t_methoddata_insolidangle *surf, int tbin,
806 real phi, real pdelta1, real pdelta2, real pdeltamax,
809 real pdelta, phi1, phi2;
810 int pbin1, pbin2, pbiniter, pbin;
812 /* Find the edges of the bins affected */
813 pdelta = max(max(pdelta1, pdelta2), pdeltamax);
817 pbin = find_partition_bin(&surf->tbin[tbin], phi1);
822 pbin = find_partition_bin(&surf->tbin[tbin], phi1 + M_2PI);
823 pbin1 = pbin - surf->tbin[tbin].n;
828 pbin2 = find_partition_bin(&surf->tbin[tbin], phi2);
832 pbin2 = find_partition_bin(&surf->tbin[tbin], phi2 - M_2PI);
833 pbin2 += surf->tbin[tbin].n;
836 if (pbin2 - pbin1 > surf->tbin[tbin].n)
838 pbin2 = pbin1 + surf->tbin[tbin].n;
840 /* Find the edges of completely covered region */
841 pdelta = min(pdelta1, pdelta2);
848 /* Loop over all affected bins */
849 for (pbiniter = pbin1; pbiniter != pbin2; ++pbiniter, ++pbin)
851 /* Wrap bin around if end reached */
852 if (pbin == surf->tbin[tbin].n)
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)
862 mark_surface_covered(surf, tbin, pbin);
866 add_surface_point(surf, tbin, pbin, x);
872 * \param[in,out] surf Surface data structure.
873 * \param[in] x Point to store (should have unit length).
875 * Finds all the bins covered by the cone centered at \p x and calls
876 * update_surface_bin() to update them.
879 store_surface_point(t_methoddata_insolidangle *surf, rvec x)
882 real pdeltamax, tmax;
883 real theta1, theta2, pdelta1, pdelta2;
887 phi = atan2(x[YY], x[XX]);
888 /* Find the maximum extent in the phi direction */
889 if (theta <= surf->angcut)
894 else if (theta >= M_PI - surf->angcut)
901 pdeltamax = std::asin(sin(surf->angcut) / sin(theta));
902 tmax = std::acos(cos(theta) / cos(surf->angcut));
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)
915 /* Loop through all affected bins */
916 while (tbin < ceil((theta + surf->angcut) / surf->tbinsize)
917 && tbin < surf->ntbins)
919 /* Calculate the next boundaries */
920 theta2 = (tbin+1) * surf->tbinsize;
921 if (theta2 > theta + surf->angcut)
923 /* The circle is completely outside the cone */
926 else if (theta2 <= -(theta - surf->angcut)
927 || theta2 >= M_2PI - (theta + surf->angcut)
928 || tbin == surf->ntbins - 1)
930 /* The circle is completely inside the cone, or we are in the
931 * 360 degree bin covering the pole. */
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
941 pdelta2 = 2*asin(std::sqrt(
942 (gmx::square(sin(surf->angcut/2)) - gmx::square(sin((theta2-theta)/2))) /
943 (sin(theta) * sin(theta2))));
946 if (tmax >= theta1 && tmax <= theta2)
948 update_surface_bin(surf, tbin, phi, pdelta1, pdelta2, pdeltamax, x);
952 update_surface_bin(surf, tbin, phi, pdelta1, pdelta2, 0, x);
962 optimize_surface_points(t_methoddata_insolidangle * /* surf */)
964 /* TODO: Implement */
968 * \param[in] surf Surface data structure.
969 * \returns An estimate for the area covered by the reference points.
972 estimate_covered_fraction(t_methoddata_insolidangle *surf)
975 real cfrac, tfrac, pfrac;
978 for (t = 0; t < surf->ntbins; ++t)
980 tfrac = cos(t * surf->tbinsize) - cos((t+1) * surf->tbinsize);
981 for (p = 0; p < surf->tbin[t].n; ++p)
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 */
987 cfrac += tfrac * pfrac;
989 else if (n > 0) /* Bin partially covered */
991 cfrac += tfrac * pfrac / 2; /* A rough estimate */
995 return cfrac / (4*M_PI);
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.
1004 is_surface_covered(t_methoddata_insolidangle *surf, rvec x)
1008 bin = find_surface_bin(surf, x);
1009 /* Check for completely covered bin */
1010 if (surf->bin[bin].n == -1)
1014 /* Check each point that partially covers the bin */
1015 for (i = 0; i < surf->bin[bin].n; ++i)
1017 if (sph_distc(x, surf->bin[bin].x[i]) < surf->distccut)