Compadre 1.7.0
Loading...
Searching...
No Matches
Compadre_PointCloudSearch.hpp
Go to the documentation of this file.
1// @HEADER
2// *****************************************************************************
3// Compadre: COMpatible PArticle Discretization and REmap Toolkit
4//
5// Copyright 2018 NTESS and the Compadre contributors.
6// SPDX-License-Identifier: BSD-2-Clause
7// *****************************************************************************
8// @HEADER
9#ifndef _COMPADRE_POINTCLOUDSEARCH_HPP_
10#define _COMPADRE_POINTCLOUDSEARCH_HPP_
11
12#include "Compadre_Typedefs.hpp"
14#include "nanoflann.hpp"
15#include <Kokkos_Core.hpp>
16#include <memory>
17
18namespace Compadre {
19
20//class sort_indices
21//{
22// private:
23// double* mparr;
24// public:
25// sort_indices(double* parr) : mparr(parr) {}
26// bool operator()(int i, int j) const { return mparr[i]<mparr[j]; }
27//};
28
29//! Custom RadiusResultSet for nanoflann that uses pre-allocated space for indices and radii
30//! instead of using std::vec for std::pairs
31template <typename _DistanceType, typename _IndexType = size_t>
33
34 public:
35
36 typedef _DistanceType DistanceType;
37 typedef _IndexType IndexType;
38
44
46 DistanceType radius_,
47 DistanceType* r_dist_, IndexType* i_dist_, const IndexType max_size_)
48 : radius(radius_), count(0), r_dist(r_dist_), i_dist(i_dist_), max_size(max_size_) {
49 init();
50 }
51
52 void init() {}
53
54 void clear() { count = 0; }
55
56 size_t size() const { return count; }
57
58 bool full() const { return true; }
59
60 bool addPoint(DistanceType dist, IndexType index) {
61
62 if (dist < radius) {
63 // would throw an exception here if count>=max_size, but this code is
64 // called inside of a parallel region so only an abort is possible,
65 // but this situation is recoverable
66 //
67 // instead, we increase count, but there is nowhere to store neighbors
68 // since we are working with pre-allocated space
69 // this will be discovered after returning from the search by comparing
70 // the count against the pre-allocate space dimensions
71 if (count<max_size) {
72 i_dist[count] = index;
73 r_dist[count] = dist;
74 }
75 count++;
76 }
77 return true;
78
79 }
80
81 DistanceType worstDist() const { return radius; }
82
83 std::pair<IndexType, DistanceType> worst_item() const {
84 // just to verify this isn't ever called
85 compadre_kernel_assert_release(false && "worst_item() should not be called.");
86 }
87
88 void sort() {
89 // puts closest neighbor as the first entry in the neighbor list
90 // leaves the rest unsorted
91
92 if (count > 0) {
93
94 // alternate sort for every entry, not currently being used
95 //int indices[count];
96 //for (int i=0; i<count; ++i) {
97 // indices[i] = i;
98 //}
99 //std::sort(indices, indices+count, sort_indices(r_dist));
100 //std::vector<IndexType> tmp_indices(count);
101 //std::vector<DistanceType> tmp_r(count);
102 //for (int i=0; i<count; ++i) {
103 // tmp_indices[i] = i_dist[indices[i]];
104 // tmp_r[i] = r_dist[indices[i]];
105 //}
106 //for (int i=0; i<count; ++i) {
107 // i_dist[i] = tmp_indices[i];
108 // r_dist[i] = tmp_r[i];
109 //}
110 IndexType loop_max = (count < max_size) ? count : max_size;
111
112 DistanceType best_distance = std::numeric_limits<DistanceType>::max();
113 IndexType best_distance_index = 0;
114 int best_index = -1;
115 for (IndexType i=0; i<loop_max; ++i) {
116 if (r_dist[i] < best_distance) {
117 best_distance = r_dist[i];
118 best_distance_index = i_dist[i];
119 best_index = i;
120 }
121 }
122
123 if (best_index != 0) {
124 auto tmp_ind = i_dist[0];
125 i_dist[0] = best_distance_index;
126 i_dist[best_index] = tmp_ind;
127 }
128 }
129 }
130};
131
132
133//! PointCloudSearch generates neighbor lists and window sizes for each target site
134/*!
135* Search methods can be run in dry-run mode, or not.
136*
137* #### When in dry-run mode:
138*
139* `neighbors_list` will be populated with number of neighbors found for each target site.
140*
141* This allows a user to know memory allocation needed before storage of neighbor indices.
142*
143* #### When not in dry-run mode:
144*
145* `neighbors_list_offsets` will be populated with offsets for values (dependent on method) determined by neighbor_list.
146* If a 2D view for `neighbors_list` is used, then \f$ N(i,j+1) \f$ will store the \f$ j^{th} \f$ neighbor of \f$ i \f$,
147* and \f$ N(i,0) \f$ will store the number of neighbors for target \f$ i \f$.
148*
149*/
150template <typename view_type>
152
153 public:
154
155 typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PointCloudSearch<view_type> >,
157 typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PointCloudSearch<view_type> >,
159 typedef nanoflann::KDTreeSingleIndexAdaptor<nanoflann::L2_Simple_Adaptor<double, PointCloudSearch<view_type> >,
161
162 protected:
163
164 //! source site coordinates
165 view_type _src_pts_view;
168
169 std::shared_ptr<tree_type_1d> _tree_1d;
170 std::shared_ptr<tree_type_2d> _tree_2d;
171 std::shared_ptr<tree_type_3d> _tree_3d;
172
173 public:
174
175 PointCloudSearch(view_type src_pts_view, const local_index_type dimension = -1,
176 const local_index_type max_leaf = -1)
177 : _src_pts_view(src_pts_view),
178 _dim((dimension < 0) ? src_pts_view.extent(1) : dimension),
179 _max_leaf((max_leaf < 0) ? 10 : max_leaf) {
180 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename view_type::memory_space>::accessible==1)
181 && "Views passed to PointCloudSearch at construction should be accessible from the host.");
182 };
183
185
186 //! Returns a liberal estimated upper bound on number of neighbors to be returned by a neighbor search
187 //! for a given choice of dimension, basis size, and epsilon_multiplier. Assumes quasiuniform distribution
188 //! of points. This result can be used to size a preallocated neighbor_lists kokkos view.
189 static inline int getEstimatedNumberNeighborsUpperBound(int unisolvency_size, const int dimension, const double epsilon_multiplier) {
190 int multiplier = 1;
191 if (dimension==1) multiplier = 2;
192 return multiplier * 2.0 * unisolvency_size * std::pow(epsilon_multiplier, dimension) + 1; // +1 is for the number of neighbors entry needed in the first entry of each row
193 }
194
195 //! Bounding box query method required by Nanoflann.
196 template <class BBOX> bool kdtree_get_bbox(BBOX& bb) const {return false;}
197
198 //! Returns the number of source sites
199 inline int kdtree_get_point_count() const {return _src_pts_view.extent(0);}
200
201 //! Returns the coordinate value of a point
202 inline double kdtree_get_pt(const int idx, int dim) const {return _src_pts_view(idx,dim);}
203
204 //! Returns the distance between a point and a source site, given its index
205 inline double kdtree_distance(const double* queryPt, const int idx, long long sz) const {
206
207 double distance = 0;
208 for (int i=0; i<_dim; ++i) {
209 distance += (_src_pts_view(idx,i)-queryPt[i])*(_src_pts_view(idx,i)-queryPt[i]);
210 }
211 return std::sqrt(distance);
212
213 }
214
216 if (_dim==1) {
217 _tree_1d = std::make_shared<tree_type_1d>(1, *this, nanoflann::KDTreeSingleIndexAdaptorParams(_max_leaf));
218 _tree_1d->buildIndex();
219 } else if (_dim==2) {
220 _tree_2d = std::make_shared<tree_type_2d>(2, *this, nanoflann::KDTreeSingleIndexAdaptorParams(_max_leaf));
221 _tree_2d->buildIndex();
222 } else if (_dim==3) {
223 _tree_3d = std::make_shared<tree_type_3d>(3, *this, nanoflann::KDTreeSingleIndexAdaptorParams(_max_leaf));
224 _tree_3d->buildIndex();
225 }
226 }
227
228 /*! \brief Generates neighbor lists of 2D view by performing a radius search
229 where the radius to be searched is in the epsilons view.
230 If uniform_radius is given, then this overrides the epsilons view radii sizes.
231 Accepts 2D neighbor_lists without number_of_neighbors_list.
232 \param is_dry_run [in] - whether to do a dry-run (find neighbors, but don't store)
233 \param trg_pts_view [in] - target coordinates from which to seek neighbors
234 \param neighbor_lists [out] - 2D view of neighbor lists to be populated from search
235 \param epsilons [in/out] - radius to search, overwritten if uniform_radius != 0
236 \param uniform_radius [in] - double != 0 determines whether to overwrite all epsilons for uniform search
237 \param max_search_radius [in] - largest valid search (useful only for MPI jobs if halo size exists)
238 */
239 template <typename trg_view_type, typename neighbor_lists_view_type, typename epsilons_view_type>
240 size_t generate2DNeighborListsFromRadiusSearch(bool is_dry_run, trg_view_type trg_pts_view,
241 neighbor_lists_view_type neighbor_lists, epsilons_view_type epsilons,
242 const double uniform_radius = 0.0, double max_search_radius = 0.0) {
243
244 // function does not populate epsilons, they must be prepopulated
245
246 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename trg_view_type::memory_space>::accessible==1) &&
247 "Target coordinates view passed to generate2DNeighborListsFromRadiusSearch should be accessible from the host.");
248 compadre_assert_release((((int)trg_pts_view.extent(1))>=_dim) &&
249 "Target coordinates view passed to generate2DNeighborListsFromRadiusSearch must have \
250 second dimension as large as _dim.");
251 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename neighbor_lists_view_type::memory_space>::accessible==1) &&
252 "Views passed to generate2DNeighborListsFromRadiusSearch should be accessible from the host.");
253 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename epsilons_view_type::memory_space>::accessible==1) &&
254 "Views passed to generate2DNeighborListsFromRadiusSearch should be accessible from the host.");
255
256 // loop size
257 const int num_target_sites = trg_pts_view.extent(0);
258
259 if ((!_tree_1d && _dim==1) || (!_tree_2d && _dim==2) || (!_tree_3d && _dim==3)) {
260 this->generateKDTree();
261 }
262
263 // check neighbor lists and epsilons view sizes
264 compadre_assert_release((neighbor_lists.extent(0)==(size_t)num_target_sites
265 && neighbor_lists.extent(1)>=1)
266 && "neighbor lists View does not have large enough dimensions");
267 compadre_assert_release((neighbor_lists_view_type::rank==2) && "neighbor_lists must be a 2D Kokkos view.");
268
269 compadre_assert_release((epsilons.extent(0)==(size_t)num_target_sites)
270 && "epsilons View does not have the correct dimension");
271
272 typedef Kokkos::View<double*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
273 scratch_double_view;
274
275 typedef Kokkos::View<size_t*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
276 scratch_int_view;
277
278 // determine scratch space size needed
279 int team_scratch_size = 0;
280 team_scratch_size += scratch_double_view::shmem_size(neighbor_lists.extent(1)); // distances
281 team_scratch_size += scratch_int_view::shmem_size(neighbor_lists.extent(1)); // indices
282 team_scratch_size += scratch_double_view::shmem_size(_dim); // target coordinate
283
284 auto dim = _dim;
285 // maximum number of neighbors found over all target sites' neighborhoods
286 size_t max_num_neighbors = 0;
287 // part 2. do radius search using window size from knn search
288 // each row of neighbor lists is a neighbor list for the target site corresponding to that row
289 auto scratch_level = (team_scratch_size > 32000) ? 1 : 0;
290 // respects serial scratch size limit of 32k
291 // from kokkos/core/src/Serial/Kokkos_Serial_Parallel_Team.hpp
292 auto policy = host_team_policy(num_target_sites, Kokkos::AUTO)
293 .set_scratch_size(scratch_level /*shared memory level*/, Kokkos::PerTeam(team_scratch_size));
294 auto radius_search = [=,*this](const host_member_type& teamMember, size_t& t_max_num_neighbors) {
295
296 // make unmanaged scratch views
297 scratch_double_view neighbor_distances(teamMember.team_scratch(scratch_level /*shared memory*/), neighbor_lists.extent(1));
298 scratch_int_view neighbor_indices(teamMember.team_scratch(scratch_level /*shared memory*/), neighbor_lists.extent(1));
299 scratch_double_view this_target_coord(teamMember.team_scratch(scratch_level /*shared memory*/), dim);
300
301 size_t neighbors_found = 0;
302
303 const int i = teamMember.league_rank();
304
305 // set epsilons if radius is specified
306 if (uniform_radius > 0) epsilons(i) = uniform_radius;
307
308 // needs furthest neighbor's distance for next portion
309 compadre_kernel_assert_release((epsilons(i)<=max_search_radius || max_search_radius==0) && "max_search_radius given (generally derived from the size of a halo region), and search radius needed would exceed this max_search_radius.");
310
311 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, neighbor_lists.extent(1)), [&](const int j) {
312 neighbor_indices(j) = 0;
313 neighbor_distances(j) = -1.0;
314 });
315 teamMember.team_barrier();
316
317 Kokkos::single(Kokkos::PerTeam(teamMember), [&] () {
318 // target_coords is LayoutLeft on device and its HostMirror, so giving a pointer to
319 // this data would lead to a wrong result if the device is a GPU
320
321 for (int j=0; j<dim; ++j) {
322 this_target_coord(j) = trg_pts_view(i,j);
323 }
324
325 nanoflann::SearchParams sp; // default parameters
326 Compadre::RadiusResultSet<double> rrs(epsilons(i)*epsilons(i), neighbor_distances.data(), neighbor_indices.data(), neighbor_lists.extent(1));
327 if (dim==1) {
328 neighbors_found = _tree_1d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
329 } else if (dim==2) {
330 neighbors_found = _tree_2d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
331 } else if (dim==3) {
332 neighbors_found = _tree_3d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
333 }
334
335 t_max_num_neighbors = (neighbors_found > t_max_num_neighbors) ? neighbors_found : t_max_num_neighbors;
336
337 // the number of neighbors is stored in column zero of the neighbor lists 2D array
338 neighbor_lists(i,0) = neighbors_found;
339
340 // epsilons already scaled and then set by search radius
341 });
342 teamMember.team_barrier();
343
344 // loop_bound so that we don't write into memory we don't have allocated
345 int loop_bound = (neighbors_found < neighbor_lists.extent(1)-1) ? neighbors_found : neighbor_lists.extent(1)-1;
346 // loop over each neighbor index and fill with a value
347 if (!is_dry_run) {
348 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, loop_bound), [&](const int j) {
349 // cast to an whatever data type the 2D array of neighbor lists is using
350 neighbor_lists(i,j+1) = static_cast<typename std::remove_pointer<typename std::remove_pointer<typename neighbor_lists_view_type::data_type>::type>::type>(neighbor_indices(j));
351 });
352 teamMember.team_barrier();
353 }
354
355 };
356 Kokkos::parallel_reduce("radius search 2D", policy, radius_search, Kokkos::Max<size_t>(max_num_neighbors));
357 Kokkos::fence();
358
359 // check if max_num_neighbors will fit onto pre-allocated space
360 compadre_assert_release((neighbor_lists.extent(1) >= (max_num_neighbors+1) || is_dry_run)
361 && "neighbor_lists does not contain enough columns for the maximum number of neighbors needing to be stored.");
362
363 return max_num_neighbors;
364 }
365
366 /*! \brief Generates compressed row neighbor lists by performing a radius search
367 where the radius to be searched is in the epsilons view.
368 If uniform_radius is given, then this overrides the epsilons view radii sizes.
369 Accepts 1D neighbor_lists with 1D number_of_neighbors_list.
370 \param is_dry_run [in] - whether to do a dry-run (find neighbors, but don't store)
371 \param trg_pts_view [in] - target coordinates from which to seek neighbors
372 \param neighbor_lists [out] - 1D view of neighbor lists to be populated from search
373 \param number_of_neighbors_list [in/out] - number of neighbors for each target site
374 \param epsilons [in/out] - radius to search, overwritten if uniform_radius != 0
375 \param uniform_radius [in] - double != 0 determines whether to overwrite all epsilons for uniform search
376 \param max_search_radius [in] - largest valid search (useful only for MPI jobs if halo size exists)
377 */
378 template <typename trg_view_type, typename neighbor_lists_view_type, typename epsilons_view_type>
379 size_t generateCRNeighborListsFromRadiusSearch(bool is_dry_run, trg_view_type trg_pts_view,
380 neighbor_lists_view_type neighbor_lists, neighbor_lists_view_type number_of_neighbors_list,
381 epsilons_view_type epsilons, const double uniform_radius = 0.0, double max_search_radius = 0.0) {
382
383 // function does not populate epsilons, they must be prepopulated
384
385 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename trg_view_type::memory_space>::accessible==1) &&
386 "Target coordinates view passed to generateCRNeighborListsFromRadiusSearch should be accessible from the host.");
387 compadre_assert_release((((int)trg_pts_view.extent(1))>=_dim) &&
388 "Target coordinates view passed to generateCRNeighborListsFromRadiusSearch must have \
389 second dimension as large as _dim.");
390 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename neighbor_lists_view_type::memory_space>::accessible==1) &&
391 "Views passed to generateCRNeighborListsFromRadiusSearch should be accessible from the host.");
392 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename epsilons_view_type::memory_space>::accessible==1) &&
393 "Views passed to generateCRNeighborListsFromRadiusSearch should be accessible from the host.");
394
395 // loop size
396 const int num_target_sites = trg_pts_view.extent(0);
397
398 if ((!_tree_1d && _dim==1) || (!_tree_2d && _dim==2) || (!_tree_3d && _dim==3)) {
399 this->generateKDTree();
400 }
401
402 compadre_assert_release((number_of_neighbors_list.extent(0)==(size_t)num_target_sites)
403 && "number_of_neighbors_list or neighbor lists View does not have large enough dimensions");
404 compadre_assert_release((neighbor_lists_view_type::rank==1) && "neighbor_lists must be a 1D Kokkos view.");
405
406 typedef Kokkos::View<global_index_type*, typename neighbor_lists_view_type::array_layout,
407 typename neighbor_lists_view_type::memory_space, typename neighbor_lists_view_type::memory_traits> row_offsets_view_type;
408 row_offsets_view_type row_offsets;
409 int max_neighbor_list_row_storage_size = 1;
410 if (!is_dry_run) {
411 auto nla = CreateNeighborLists(neighbor_lists, number_of_neighbors_list);
412 max_neighbor_list_row_storage_size = nla.getMaxNumNeighbors();
413 Kokkos::resize(row_offsets, num_target_sites);
414 Kokkos::fence();
415 Kokkos::parallel_for(Kokkos::RangePolicy<host_execution_space>(0,num_target_sites), [&](const int i) {
416 row_offsets(i) = nla.getRowOffsetHost(i);
417 });
418 Kokkos::fence();
419 }
420
421 compadre_assert_release((epsilons.extent(0)==(size_t)num_target_sites)
422 && "epsilons View does not have the correct dimension");
423
424 typedef Kokkos::View<double*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
425 scratch_double_view;
426
427 typedef Kokkos::View<size_t*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
428 scratch_int_view;
429
430 // determine scratch space size needed
431 int team_scratch_size = 0;
432 team_scratch_size += scratch_double_view::shmem_size(max_neighbor_list_row_storage_size); // distances
433 team_scratch_size += scratch_int_view::shmem_size(max_neighbor_list_row_storage_size); // indices
434 team_scratch_size += scratch_double_view::shmem_size(_dim); // target coordinate
435
436 auto dim = _dim;
437 // part 2. do radius search using window size from knn search
438 // each row of neighbor lists is a neighbor list for the target site corresponding to that row
439 auto scratch_level = (team_scratch_size > 32000) ? 1 : 0;
440 // respects serial scratch size limit of 32k
441 // from kokkos/core/src/Serial/Kokkos_Serial_Parallel_Team.hpp
442 auto policy = host_team_policy(num_target_sites, Kokkos::AUTO)
443 .set_scratch_size(scratch_level /*shared memory level*/, Kokkos::PerTeam(team_scratch_size));
444 auto radius_search = [=,*this](const host_member_type& teamMember) {
445
446 // make unmanaged scratch views
447 scratch_double_view neighbor_distances(teamMember.team_scratch(scratch_level /*shared memory*/), max_neighbor_list_row_storage_size);
448 scratch_int_view neighbor_indices(teamMember.team_scratch(scratch_level /*shared memory*/), max_neighbor_list_row_storage_size);
449 scratch_double_view this_target_coord(teamMember.team_scratch(scratch_level /*shared memory*/), dim);
450
451 size_t neighbors_found = 0;
452
453 const int i = teamMember.league_rank();
454
455 // set epsilons if radius is specified
456 if (uniform_radius > 0) epsilons(i) = uniform_radius;
457
458 // needs furthest neighbor's distance for next portion
459 compadre_kernel_assert_release((epsilons(i)<=max_search_radius || max_search_radius==0) && "max_search_radius given (generally derived from the size of a halo region), and search radius needed would exceed this max_search_radius.");
460
461 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, max_neighbor_list_row_storage_size), [&](const int j) {
462 neighbor_indices(j) = 0;
463 neighbor_distances(j) = -1.0;
464 });
465 teamMember.team_barrier();
466
467 Kokkos::single(Kokkos::PerTeam(teamMember), [&] () {
468 // target_coords is LayoutLeft on device and its HostMirror, so giving a pointer to
469 // this data would lead to a wrong result if the device is a GPU
470
471 for (int j=0; j<dim; ++j) {
472 this_target_coord(j) = trg_pts_view(i,j);
473 }
474
475 nanoflann::SearchParams sp; // default parameters
476 Compadre::RadiusResultSet<double> rrs(epsilons(i)*epsilons(i), neighbor_distances.data(), neighbor_indices.data(), max_neighbor_list_row_storage_size);
477 if (dim==1) {
478 neighbors_found = _tree_1d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
479 } else if (dim==2) {
480 neighbors_found = _tree_2d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
481 } else if (dim==3) {
482 neighbors_found = _tree_3d->template radiusSearchCustomCallback<Compadre::RadiusResultSet<double> >(this_target_coord.data(), rrs, sp) ;
483 }
484
485 // we check that neighbors found doesn't differ from dry-run or we store neighbors_found
486 // no check that neighbors found stay the same if uniform_radius specified (!=0)
487 if (is_dry_run || uniform_radius!=0.0) {
488 number_of_neighbors_list(i) = neighbors_found;
489 } else {
490 compadre_kernel_assert_debug((neighbors_found==(size_t)number_of_neighbors_list(i))
491 && "Number of neighbors found changed since dry-run.");
492 }
493
494 // epsilons already scaled and then set by search radius
495 });
496 teamMember.team_barrier();
497
498 // loop_bound so that we don't write into memory we don't have allocated
499 int loop_bound = neighbors_found;
500 // loop over each neighbor index and fill with a value
501 if (!is_dry_run) {
502 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, loop_bound), [&](const int j) {
503 // cast to an whatever data type the 2D array of neighbor lists is using
504 neighbor_lists(row_offsets(i)+j) = static_cast<typename std::remove_pointer<typename std::remove_pointer<typename neighbor_lists_view_type::data_type>::type>::type>(neighbor_indices(j));
505 });
506 teamMember.team_barrier();
507 }
508
509 };
510 Kokkos::parallel_for("radius search CR", policy, radius_search);
511 Kokkos::fence();
512 auto nla = CreateNeighborLists(number_of_neighbors_list);
513 return nla.getTotalNeighborsOverAllListsHost();
514 }
515
516
517 /*! \brief Generates neighbor lists as 2D view by performing a k-nearest neighbor search
518 Only accepts 2D neighbor_lists without number_of_neighbors_list.
519 \param is_dry_run [in] - whether to do a dry-run (find neighbors, but don't store)
520 \param trg_pts_view [in] - target coordinates from which to seek neighbors
521 \param neighbor_lists [out] - 2D view of neighbor lists to be populated from search
522 \param epsilons [in/out] - radius to search, overwritten if uniform_radius != 0
523 \param neighbors_needed [in] - k neighbors needed as a minimum
524 \param epsilon_multiplier [in] - distance to kth neighbor multiplied by epsilon_multiplier for follow-on radius search
525 \param max_search_radius [in] - largest valid search (useful only for MPI jobs if halo size exists)
526 \param verify_unisolvency [in] - verify at least neighbors_needed for unisolvency are found
527 */
528 template <typename trg_view_type, typename neighbor_lists_view_type, typename epsilons_view_type>
529 size_t generate2DNeighborListsFromKNNSearch(bool is_dry_run, trg_view_type trg_pts_view,
530 neighbor_lists_view_type neighbor_lists, epsilons_view_type epsilons,
531 const int neighbors_needed, const double epsilon_multiplier = 1.6,
532 double max_search_radius = 0.0, const bool verify_unisolvency = true) {
533
534 // First, do a knn search (removes need for guessing initial search radius)
535
536 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename trg_view_type::memory_space>::accessible==1) &&
537 "Target coordinates view passed to generate2DNeighborListsFromKNNSearch should be accessible from the host.");
538 compadre_assert_release((((int)trg_pts_view.extent(1))>=_dim) &&
539 "Target coordinates view passed to generate2DNeighborListsFromRadiusSearch must have \
540 second dimension as large as _dim.");
541 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename neighbor_lists_view_type::memory_space>::accessible==1) &&
542 "Views passed to generate2DNeighborListsFromKNNSearch should be accessible from the host.");
543 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename epsilons_view_type::memory_space>::accessible==1) &&
544 "Views passed to generate2DNeighborListsFromKNNSearch should be accessible from the host.");
545
546 // loop size
547 const int num_target_sites = trg_pts_view.extent(0);
548
549 if ((!_tree_1d && _dim==1) || (!_tree_2d && _dim==2) || (!_tree_3d && _dim==3)) {
550 this->generateKDTree();
551 }
552 Kokkos::fence();
553
554 compadre_assert_release((num_target_sites==0 || // sizes don't matter when there are no targets
555 (neighbor_lists.extent(0)==(size_t)num_target_sites
556 && neighbor_lists.extent(1)>=(size_t)(neighbors_needed+1)))
557 && "neighbor lists View does not have large enough dimensions");
558 compadre_assert_release((neighbor_lists_view_type::rank==2) && "neighbor_lists must be a 2D Kokkos view.");
559
560 compadre_assert_release((epsilons.extent(0)==(size_t)num_target_sites)
561 && "epsilons View does not have the correct dimension");
562
563 typedef Kokkos::View<double*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
564 scratch_double_view;
565
566 typedef Kokkos::View<size_t*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
567 scratch_int_view;
568
569 // determine scratch space size needed
570 int team_scratch_size = 0;
571 team_scratch_size += scratch_double_view::shmem_size(neighbor_lists.extent(1)); // distances
572 team_scratch_size += scratch_int_view::shmem_size(neighbor_lists.extent(1)); // indices
573 team_scratch_size += scratch_double_view::shmem_size(_dim); // target coordinate
574
575 auto dim = _dim;
576 // minimum number of neighbors found over all target sites' neighborhoods
577 size_t min_num_neighbors = 0;
578 //
579 // part 1. do knn search for neighbors needed for unisolvency
580 // each row of neighbor lists is a neighbor list for the target site corresponding to that row
581 //
582 // as long as neighbor_lists can hold the number of neighbors_needed, we don't need to check
583 // that the maximum number of neighbors will fit into neighbor_lists
584 //
585 auto scratch_level = (team_scratch_size > 32000) ? 1 : 0;
586 // respects serial scratch size limit of 32k
587 // from kokkos/core/src/Serial/Kokkos_Serial_Parallel_Team.hpp
588 auto policy = host_team_policy(num_target_sites, Kokkos::AUTO)
589 .set_scratch_size(scratch_level /*shared memory level*/, Kokkos::PerTeam(team_scratch_size));
590 auto knn_search = [=,*this](const host_member_type& teamMember, size_t& t_min_num_neighbors) {
591
592 // make unmanaged scratch views
593 scratch_double_view neighbor_distances(teamMember.team_scratch(scratch_level /*shared memory*/), neighbor_lists.extent(1));
594 scratch_int_view neighbor_indices(teamMember.team_scratch(scratch_level /*shared memory*/), neighbor_lists.extent(1));
595 scratch_double_view this_target_coord(teamMember.team_scratch(scratch_level /*shared memory*/), dim);
596
597 size_t neighbors_found = 0;
598
599 const int i = teamMember.league_rank();
600
601 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, neighbor_lists.extent(1)), [=](const int j) {
602 neighbor_indices(j) = 0;
603 neighbor_distances(j) = -1.0;
604 });
605
606 teamMember.team_barrier();
607 Kokkos::single(Kokkos::PerTeam(teamMember), [&] () {
608 // target_coords is LayoutLeft on device and its HostMirror, so giving a pointer to
609 // this data would lead to a wrong result if the device is a GPU
610
611 for (int j=0; j<dim; ++j) {
612 this_target_coord(j) = trg_pts_view(i,j);
613 }
614
615 if (dim==1) {
616 neighbors_found = _tree_1d->knnSearch(this_target_coord.data(), neighbors_needed,
617 neighbor_indices.data(), neighbor_distances.data()) ;
618 } else if (dim==2) {
619 neighbors_found = _tree_2d->knnSearch(this_target_coord.data(), neighbors_needed,
620 neighbor_indices.data(), neighbor_distances.data()) ;
621 } else if (dim==3) {
622 neighbors_found = _tree_3d->knnSearch(this_target_coord.data(), neighbors_needed,
623 neighbor_indices.data(), neighbor_distances.data()) ;
624 }
625
626 // get minimum number of neighbors found over all target sites' neighborhoods
627 t_min_num_neighbors = (neighbors_found < t_min_num_neighbors) ? neighbors_found : t_min_num_neighbors;
628
629 // scale by epsilon_multiplier to window from location where the last neighbor was found
630 epsilons(i) = (neighbor_distances(neighbors_found-1) > 0) ?
631 std::sqrt(neighbor_distances(neighbors_found-1))*epsilon_multiplier : 1e-14*epsilon_multiplier;
632 // the only time the second case using 1e-14 is used is when either zero neighbors or exactly one
633 // neighbor (neighbor is target site) is found. when the follow on radius search is conducted, the one
634 // neighbor (target site) will not be found if left at 0, so any positive amount will do, however 1e-14
635 // should is small enough to ensure that other neighbors are not found
636
637 // needs furthest neighbor's distance for next portion
638 compadre_kernel_assert_release((neighbors_found<neighbor_lists.extent(1) || is_dry_run)
639 && "Neighbors found exceeded storage capacity in neighbor list.");
640 compadre_kernel_assert_release((epsilons(i)<=max_search_radius || max_search_radius==0 || is_dry_run)
641 && "max_search_radius given (generally derived from the size of a halo region), \
642 and search radius needed would exceed this max_search_radius.");
643 // neighbor_distances stores squared distances from neighbor to target, as returned by nanoflann
644 });
645 };
646 Kokkos::parallel_reduce("knn search 2D", policy, knn_search, Kokkos::Min<size_t>(min_num_neighbors));
647
648 // if no target sites, then min_num_neighbors is set to neighbors_needed
649 // which also avoids min_num_neighbors being improperly set by min reduction
650 if (num_target_sites==0) min_num_neighbors = neighbors_needed;
651
652 if (verify_unisolvency) {
653 // Next, check that we found the neighbors_needed number that we require for unisolvency
654 compadre_assert_release((num_target_sites==0 || (min_num_neighbors>=(size_t)neighbors_needed))
655 && "Neighbor search failed to find number of neighbors needed for unisolvency.");
656 }
657
658 // call a radius search using values now stored in epsilons
659 size_t max_num_neighbors = generate2DNeighborListsFromRadiusSearch(is_dry_run, trg_pts_view, neighbor_lists,
660 epsilons, 0.0 /*don't set uniform radius*/, max_search_radius);
661
662 return max_num_neighbors;
663 }
664
665 /*! \brief Generates compressed row neighbor lists by performing a k-nearest neighbor search
666 Only accepts 1D neighbor_lists with 1D number_of_neighbors_list.
667 \param is_dry_run [in] - whether to do a dry-run (find neighbors, but don't store)
668 \param trg_pts_view [in] - target coordinates from which to seek neighbors
669 \param neighbor_lists [out] - 1D view of neighbor lists to be populated from search
670 \param number_of_neighbors_list [in/out] - number of neighbors for each target site
671 \param epsilons [in/out] - radius to search, overwritten if uniform_radius != 0
672 \param neighbors_needed [in] - k neighbors needed as a minimum
673 \param epsilon_multiplier [in] - distance to kth neighbor multiplied by epsilon_multiplier for follow-on radius search
674 \param max_search_radius [in] - largest valid search (useful only for MPI jobs if halo size exists)
675 \param verify_unisolvency [in] - verify at least neighbors_needed for unisolvency are found
676 */
677 template <typename trg_view_type, typename neighbor_lists_view_type, typename epsilons_view_type>
678 size_t generateCRNeighborListsFromKNNSearch(bool is_dry_run, trg_view_type trg_pts_view,
679 neighbor_lists_view_type neighbor_lists, neighbor_lists_view_type number_of_neighbors_list,
680 epsilons_view_type epsilons, const int neighbors_needed, const double epsilon_multiplier = 1.6,
681 double max_search_radius = 0.0, const bool verify_unisolvency = true) {
682
683 // First, do a knn search (removes need for guessing initial search radius)
684
685 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename trg_view_type::memory_space>::accessible==1) &&
686 "Target coordinates view passed to generateCRNeighborListsFromKNNSearch should be accessible from the host.");
687 compadre_assert_release((((int)trg_pts_view.extent(1))>=_dim) &&
688 "Target coordinates view passed to generateCRNeighborListsFromRadiusSearch must have \
689 second dimension as large as _dim.");
690 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename neighbor_lists_view_type::memory_space>::accessible==1) &&
691 "Views passed to generateCRNeighborListsFromKNNSearch should be accessible from the host.");
692 compadre_assert_release((Kokkos::SpaceAccessibility<host_execution_space, typename epsilons_view_type::memory_space>::accessible==1) &&
693 "Views passed to generateCRNeighborListsFromKNNSearch should be accessible from the host.");
694
695 // loop size
696 const int num_target_sites = trg_pts_view.extent(0);
697
698 if ((!_tree_1d && _dim==1) || (!_tree_2d && _dim==2) || (!_tree_3d && _dim==3)) {
699 this->generateKDTree();
700 }
701 Kokkos::fence();
702
703 compadre_assert_release((number_of_neighbors_list.extent(0)==(size_t)num_target_sites )
704 && "number_of_neighbors_list or neighbor lists View does not have large enough dimensions");
705 compadre_assert_release((neighbor_lists_view_type::rank==1) && "neighbor_lists must be a 1D Kokkos view.");
706
707 // if dry-run, neighbors_needed, else max over previous dry-run
708 int max_neighbor_list_row_storage_size = neighbors_needed;
709 if (!is_dry_run) {
710 auto nla = CreateNeighborLists(neighbor_lists, number_of_neighbors_list);
711 max_neighbor_list_row_storage_size = nla.getMaxNumNeighbors();
712 }
713
714 compadre_assert_release((epsilons.extent(0)==(size_t)num_target_sites)
715 && "epsilons View does not have the correct dimension");
716
717 typedef Kokkos::View<double*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
718 scratch_double_view;
719
720 typedef Kokkos::View<size_t*, host_scratch, Kokkos::MemoryTraits<Kokkos::Unmanaged> >
721 scratch_int_view;
722
723 // determine scratch space size needed
724 int team_scratch_size = 0;
725 team_scratch_size += scratch_double_view::shmem_size(max_neighbor_list_row_storage_size); // distances
726 team_scratch_size += scratch_int_view::shmem_size(max_neighbor_list_row_storage_size); // indices
727 team_scratch_size += scratch_double_view::shmem_size(_dim); // target coordinate
728
729 auto dim = _dim;
730 // minimum number of neighbors found over all target sites' neighborhoods
731 size_t min_num_neighbors = 0;
732 //
733 // part 1. do knn search for neighbors needed for unisolvency
734 // each row of neighbor lists is a neighbor list for the target site corresponding to that row
735 //
736 // as long as neighbor_lists can hold the number of neighbors_needed, we don't need to check
737 // that the maximum number of neighbors will fit into neighbor_lists
738 //
739 auto scratch_level = (team_scratch_size > 32000) ? 1 : 0;
740 // respects serial scratch size limit of 32k
741 // from kokkos/core/src/Serial/Kokkos_Serial_Parallel_Team.hpp
742 auto policy = host_team_policy(num_target_sites, Kokkos::AUTO)
743 .set_scratch_size(scratch_level /*shared memory level*/, Kokkos::PerTeam(team_scratch_size));
744 auto knn_search = [=,*this](const host_member_type& teamMember, size_t& t_min_num_neighbors) {
745
746 // make unmanaged scratch views
747 scratch_double_view neighbor_distances(teamMember.team_scratch(scratch_level /*shared memory*/), max_neighbor_list_row_storage_size);
748 scratch_int_view neighbor_indices(teamMember.team_scratch(scratch_level /*shared memory*/), max_neighbor_list_row_storage_size);
749 scratch_double_view this_target_coord(teamMember.team_scratch(scratch_level /*shared memory*/), dim);
750
751 size_t neighbors_found = 0;
752
753 const int i = teamMember.league_rank();
754
755 Kokkos::parallel_for(Kokkos::TeamThreadRange(teamMember, max_neighbor_list_row_storage_size), [=](const int j) {
756 neighbor_indices(j) = 0;
757 neighbor_distances(j) = -1.0;
758 });
759
760 teamMember.team_barrier();
761 Kokkos::single(Kokkos::PerTeam(teamMember), [&] () {
762 // target_coords is LayoutLeft on device and its HostMirror, so giving a pointer to
763 // this data would lead to a wrong result if the device is a GPU
764
765 for (int j=0; j<dim; ++j) {
766 this_target_coord(j) = trg_pts_view(i,j);
767 }
768
769 if (dim==1) {
770 neighbors_found = _tree_1d->knnSearch(this_target_coord.data(), neighbors_needed,
771 neighbor_indices.data(), neighbor_distances.data()) ;
772 } else if (dim==2) {
773 neighbors_found = _tree_2d->knnSearch(this_target_coord.data(), neighbors_needed,
774 neighbor_indices.data(), neighbor_distances.data()) ;
775 } else if (dim==3) {
776 neighbors_found = _tree_3d->knnSearch(this_target_coord.data(), neighbors_needed,
777 neighbor_indices.data(), neighbor_distances.data()) ;
778 }
779
780 // get minimum number of neighbors found over all target sites' neighborhoods
781 t_min_num_neighbors = (neighbors_found < t_min_num_neighbors) ? neighbors_found : t_min_num_neighbors;
782
783 // scale by epsilon_multiplier to window from location where the last neighbor was found
784 epsilons(i) = (neighbor_distances(neighbors_found-1) > 0) ?
785 std::sqrt(neighbor_distances(neighbors_found-1))*epsilon_multiplier : 1e-14*epsilon_multiplier;
786 // the only time the second case using 1e-14 is used is when either zero neighbors or exactly one
787 // neighbor (neighbor is target site) is found. when the follow on radius search is conducted, the one
788 // neighbor (target site) will not be found if left at 0, so any positive amount will do, however 1e-14
789 // should is small enough to ensure that other neighbors are not found
790
791 compadre_kernel_assert_release((epsilons(i)<=max_search_radius || max_search_radius==0 || is_dry_run)
792 && "max_search_radius given (generally derived from the size of a halo region), \
793 and search radius needed would exceed this max_search_radius.");
794 // neighbor_distances stores squared distances from neighbor to target, as returned by nanoflann
795 });
796 };
797 Kokkos::parallel_reduce("knn search CR", policy, knn_search, Kokkos::Min<size_t>(min_num_neighbors));
798
799 // if no target sites, then min_num_neighbors is set to neighbors_needed
800 // which also avoids min_num_neighbors being improperly set by min reduction
801 if (num_target_sites==0) min_num_neighbors = neighbors_needed;
802
803 if (verify_unisolvency) {
804 // Next, check that we found the neighbors_needed number that we require for unisolvency
805 compadre_assert_release((num_target_sites==0 || (min_num_neighbors>=(size_t)neighbors_needed))
806 && "Neighbor search failed to find number of neighbors needed for unisolvency.");
807 }
808
809 // call a radius search using values now stored in epsilons
810 generateCRNeighborListsFromRadiusSearch(is_dry_run, trg_pts_view, neighbor_lists,
811 number_of_neighbors_list, epsilons, 0.0 /*don't set uniform radius*/, max_search_radius);
812
813 auto nla = CreateNeighborLists(number_of_neighbors_list);
814 return nla.getTotalNeighborsOverAllListsHost();
815 }
816}; // PointCloudSearch
817
818//! CreatePointCloudSearch allows for the construction of an object of type PointCloudSearch with template deduction
819template <typename view_type>
820PointCloudSearch<view_type> CreatePointCloudSearch(view_type src_view, const local_index_type dimensions = -1, const local_index_type max_leaf = -1) {
821 return PointCloudSearch<view_type>(src_view, dimensions, max_leaf);
822}
823
824} // Compadre
825
826#endif
#define compadre_kernel_assert_debug(condition)
#define compadre_kernel_assert_release(condition)
compadre_kernel_assert_release is similar to compadre_assert_release, but is a call on the device,...
#define compadre_assert_release(condition)
compadre_assert_release is used for assertions that should always be checked, but generally are not e...
PointCloudSearch generates neighbor lists and window sizes for each target site.
bool kdtree_get_bbox(BBOX &bb) const
Bounding box query method required by Nanoflann.
double kdtree_get_pt(const int idx, int dim) const
Returns the coordinate value of a point.
std::shared_ptr< tree_type_1d > _tree_1d
size_t generateCRNeighborListsFromRadiusSearch(bool is_dry_run, trg_view_type trg_pts_view, neighbor_lists_view_type neighbor_lists, neighbor_lists_view_type number_of_neighbors_list, epsilons_view_type epsilons, const double uniform_radius=0.0, double max_search_radius=0.0)
Generates compressed row neighbor lists by performing a radius search where the radius to be searched...
double kdtree_distance(const double *queryPt, const int idx, long long sz) const
Returns the distance between a point and a source site, given its index.
view_type _src_pts_view
source site coordinates
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PointCloudSearch< view_type > >, PointCloudSearch< view_type >, 1 > tree_type_1d
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PointCloudSearch< view_type > >, PointCloudSearch< view_type >, 2 > tree_type_2d
static int getEstimatedNumberNeighborsUpperBound(int unisolvency_size, const int dimension, const double epsilon_multiplier)
Returns a liberal estimated upper bound on number of neighbors to be returned by a neighbor search fo...
size_t generate2DNeighborListsFromRadiusSearch(bool is_dry_run, trg_view_type trg_pts_view, neighbor_lists_view_type neighbor_lists, epsilons_view_type epsilons, const double uniform_radius=0.0, double max_search_radius=0.0)
Generates neighbor lists of 2D view by performing a radius search where the radius to be searched is ...
int kdtree_get_point_count() const
Returns the number of source sites.
size_t generateCRNeighborListsFromKNNSearch(bool is_dry_run, trg_view_type trg_pts_view, neighbor_lists_view_type neighbor_lists, neighbor_lists_view_type number_of_neighbors_list, epsilons_view_type epsilons, const int neighbors_needed, const double epsilon_multiplier=1.6, double max_search_radius=0.0, const bool verify_unisolvency=true)
Generates compressed row neighbor lists by performing a k-nearest neighbor search Only accepts 1D nei...
nanoflann::KDTreeSingleIndexAdaptor< nanoflann::L2_Simple_Adaptor< double, PointCloudSearch< view_type > >, PointCloudSearch< view_type >, 3 > tree_type_3d
PointCloudSearch(view_type src_pts_view, const local_index_type dimension=-1, const local_index_type max_leaf=-1)
size_t generate2DNeighborListsFromKNNSearch(bool is_dry_run, trg_view_type trg_pts_view, neighbor_lists_view_type neighbor_lists, epsilons_view_type epsilons, const int neighbors_needed, const double epsilon_multiplier=1.6, double max_search_radius=0.0, const bool verify_unisolvency=true)
Generates neighbor lists as 2D view by performing a k-nearest neighbor search Only accepts 2D neighbo...
std::shared_ptr< tree_type_3d > _tree_3d
std::shared_ptr< tree_type_2d > _tree_2d
Custom RadiusResultSet for nanoflann that uses pre-allocated space for indices and radii instead of u...
RadiusResultSet(DistanceType radius_, DistanceType *r_dist_, IndexType *i_dist_, const IndexType max_size_)
std::pair< IndexType, DistanceType > worst_item() const
bool addPoint(DistanceType dist, IndexType index)
PointCloudSearch< view_type > CreatePointCloudSearch(view_type src_view, const local_index_type dimensions=-1, const local_index_type max_leaf=-1)
CreatePointCloudSearch allows for the construction of an object of type PointCloudSearch with templat...
NeighborLists< view_type > CreateNeighborLists(view_type number_of_neighbors_list)
CreateNeighborLists allows for the construction of an object of type NeighborLists with template dedu...
Kokkos::TeamPolicy< host_execution_space > host_team_policy
host_team_policy::member_type host_member_type
std::size_t global_index_type