libMesh
Loading...
Searching...
No Matches
point_locator_nanoflann.C
Go to the documentation of this file.
1// The libMesh Finite Element Library.
2// Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3
4// This library is free software; you can redistribute it and/or
5// modify it under the terms of the GNU Lesser General Public
6// License as published by the Free Software Foundation; either
7// version 2.1 of the License, or (at your option) any later version.
8
9// This library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12// Lesser General Public License for more details.
13
14// You should have received a copy of the GNU Lesser General Public
15// License along with this library; if not, write to the Free Software
16// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18
19#include "libmesh/libmesh_config.h"
20
21// This class is not defined unless libmesh is built with Nanoflann support
22#ifdef LIBMESH_HAVE_NANOFLANN
23
24// libmesh includes
25#include "libmesh/point_locator_nanoflann.h"
26#include "libmesh/elem.h"
27#include "libmesh/libmesh_logging.h"
28#include "libmesh/mesh_base.h"
29#include "libmesh/mesh_tools.h"
30#include "libmesh/int_range.h" // make_range
31
32// C++ includes
33#include <array>
34#include <numeric> // std::iota
35#include <algorithm> // std::sort
36
37namespace libMesh
38{
39
41 const PointLocatorBase * master) :
42 PointLocatorBase (mesh, master),
43 _out_of_mesh_mode(false),
44 _num_results(8)
45{
46 this->init();
47}
48
49
50
52
53
54
55void
57{
58 this->_initialized = false;
59 this->_out_of_mesh_mode = false;
60
61 // reset() actually frees the memory if we are master, otherwise it
62 // just reduces the ref. count.
63 _elems.reset();
64 _point_cloud.reset();
65 _kd_tree.reset();
66}
67
68
69
70void
72{
73 LOG_SCOPE("init()", "PointLocatorNanoflann");
74
75 if (!_initialized)
76 {
77 // If _master == nullptr, then we _are_ the master, and thus
78 // responsible for initializing.
79 bool we_are_master = (_master == nullptr);
80
81 // If we are the master PointLocator, fill in the _point_cloud
82 // data structure with active, local element vertex averages.
83 if (we_are_master)
84 {
85 _elems = std::make_shared<std::vector<const Elem *>>();
86 _point_cloud = std::make_shared<std::vector<Point>>();
87
88 // Make the KD-Tree out of active element vertex averages.
89 //
90 // Note: we use active elements rather than active+local
91 // elements since we sometimes need to be able to locate
92 // points in ghosted elements (for example in Periodic BCs).
93 // Active elements are also natural to use in the
94 // DistributedMesh case. The size of the KD-Tree on each
95 // processor therefore scales with the number of elements on
96 // each processor in either case.
97 //
98 // Note 2: the approximate amount of space we should reserve
99 // here is going to be different for ReplicatedMesh
100 // (n_active_elem()) vs. DistributedMesh (n_active_local_elem())
101 // so we just let the containers resize themselves
102 // automatically.
103 for (const auto & elem : _mesh.active_element_ptr_range())
104 {
105 _elems->push_back(elem);
106 _point_cloud->push_back(elem->vertex_average());
107 }
108
109 // Construct the KD-Tree
110 _kd_tree = std::make_shared<kd_tree_t>
111 (LIBMESH_DIM, *this, nanoflann::KDTreeSingleIndexAdaptorParams(/*max leaf=*/10));
112
113 _kd_tree->buildIndex();
114 }
115 else // we are not master
116 {
117 // Cast the _master to the appropriate type, and point to
118 // its data arrays.
119 const auto my_master =
120 cast_ptr<const PointLocatorNanoflann *>(this->_master);
121
122 // Point our data structures at the master's
123 _elems = my_master->_elems;
124 _point_cloud = my_master->_point_cloud;
125 _kd_tree = my_master->_kd_tree;
126 }
127
128 // We are initialized now
129 this->_initialized = true;
130 }
131}
132
133nanoflann::KNNResultSet<Real>
135 std::size_t num_results) const
136{
137 // We are searching for the Point(s) closest to Point p.
138 //
139 // TODO: The kd_tree's findNeighbors() routine needs a pointer to
140 // Real of length LIBMESH_DIM. It might be convenient if libMesh
141 // Points had a data() member that provided this, for now we just
142 // copy the coordinates into a std::array of the appropriate size.
143 std::array<Real, LIBMESH_DIM> query_pt;
144 for (int i=0; i<LIBMESH_DIM; ++i)
145 query_pt[i] = p(i);
146
147 // Allocate storage for the indices and distances
148 _ret_index.resize(num_results);
149 _out_dist_sqr.resize(num_results);
150
151 // nanoflann::KNNResultSet cannot be resized/reused easily, I think
152 // it is just meant to be re-created for each search.
153 nanoflann::KNNResultSet<Real> result_set(num_results);
154
155 // Initialize the result_set
156 result_set.init(_ret_index.data(), _out_dist_sqr.data());
157
158 // Do the search
159 // We leave all the SearchParams() ctor args on their default values
160 // (note that the first arg is ignored anyway).
161 _kd_tree->findNeighbors(result_set, query_pt.data(), nanoflann::SearchParameters());
162
163 return result_set;
164}
165
166
167
168const Elem *
170 const std::set<subdomain_id_type> * allowed_subdomains) const
171{
173
174 LOG_SCOPE("operator()", "PointLocatorNanoflann");
175
176 // Keep track of the number of elements checked in detail. This can
177 // be useful for determining an optimal _num_results for
178 // specific applications, by looking at the average and best/worse
179 // case of each linear search.
180 unsigned int n_elems_checked = 0;
181
182 // If a containing Elem is found locally, we will set this pointer.
183 const Elem * found_elem = nullptr;
184
185 auto result_set = this->kd_tree_find_neighbors(p, _num_results);
186
187 // The results from Nanoflann are already sorted by (squared)
188 // distance, but within that list of results, there may be some
189 // vertex averages which are equidistant from the searched-for
190 // Point. Therefore, we will now indirect_sort the results based on
191 // elem id, so that the lowest-id Elem from the class of Elems which
192 // are the same distance away from the search Point is always
193 // selected.
194
195 // operator< comparison lambda used in the indirect sort. The
196 // inputs to this vector are indices of the result_set array.
197 auto comp = [this](std::size_t lhs, std::size_t rhs) -> bool
198 {
199 // First we sort by squared distance
200 if (_out_dist_sqr[lhs] < _out_dist_sqr[rhs])
201 return true;
202 if (_out_dist_sqr[rhs] < _out_dist_sqr[lhs])
203 return false;
204
205 // If we made it here without returning, then the Points were
206 // equidistant, so we sort based on Elem id instead.
207 auto nanoflann_index_lhs = _ret_index[lhs];
208 auto elem_id_lhs = (*_elems)[nanoflann_index_lhs]->id();
209
210 auto nanoflann_index_rhs = _ret_index[rhs];
211 auto elem_id_rhs = (*_elems)[nanoflann_index_rhs]->id();
212
213 if (elem_id_lhs < elem_id_rhs)
214 return true;
215
216 return false;
217 };
218
219 // Set up the indices and do the indirect sort. The results are
220 // stored in _b so that _b[0] is the first result to check, _b[1]
221 // is second, etc.
222 _b.resize(result_set.size());
223 std::iota(_b.begin(), _b.end(), 0);
224 std::sort(_b.begin(), _b.end(), comp);
225
226 // Note: we use result_set.size() here rather than
227 // _num_results, in case the result returns fewer than
228 // _num_results results!
229 for (auto r : make_range(result_set.size()))
230 {
231 // Translate the Nanoflann index, which is from [0..n_points),
232 // into the corresponding Elem id from the mesh. Note that we
233 // use index _b[r] instead of r, since we want to process the
234 // Elems in order based on Elem id.
235 auto nanoflann_index = _ret_index[_b[r]];
236 const Elem * candidate_elem = (*_elems)[nanoflann_index];
237
238 // Debugging: print the results
239 // libMesh::err << "Vertex average/Elem id = " << candidate_elem->id()
240 // << ", dist^2 = " << _out_dist_sqr[r]
241 // << std::endl;
242
243 // Before we even check whether the candidate Elem actually
244 // contains the Point, we may need to check whether the
245 // candidate Elem is from an allowed subdomain. If the
246 // candidate Elem is not from an allowed subdomain, we continue
247 // to the next one.
248 if (allowed_subdomains && !allowed_subdomains->count(candidate_elem->subdomain_id()))
249 {
250 // Debugging
251 // libMesh::err << "Elem " << candidate_elem->id() << " was not from an allowed subdomain, continuing search." << std::endl;
252 continue;
253 }
254
255 // If we made it here, then the candidate Elem is from an
256 // allowed subdomain, so let's next check whether it contains
257 // the point. If the user set a custom tolerance, then we
258 // actually check close_to_point() rather than contains_point(),
259 // since this latter function warns about using non-default
260 // tolerances, but otherwise does the same test.
261 bool inside = _use_contains_point_tol ?
262 candidate_elem->close_to_point(p, _contains_point_tol) :
263 candidate_elem->contains_point(p);
264
265 // Increment the number of elements checked
266 n_elems_checked++;
267
268 // If the point is inside an Elem from an allowed subdomain, we are done.
269 if (inside)
270 {
271 // Debugging: report the number of Elems checked
272 // libMesh::err << "Checked " << n_elems_checked << " nearby Elems before finding a containing Elem." << std::endl;
273
274 found_elem = candidate_elem;
275 break;
276 }
277 } // end for(r)
278
279 // If we made it here, then at least one of the following happened:
280 // .) All the candidate elements were from non-allowed subdomains.
281 // .) The Point was not inside _any_ of the _num_results candidate Elems.
282 // Thus, if we are not in _out_of_mesh_mode, throw an error,
283 // otherwise return nullptr to indicate that no suitable element was
284 // found.
285 if (!_out_of_mesh_mode && !found_elem)
286 {
287 // Debugging: we are about to throw an error, but before we do,
288 // print information about the closest elements (by vertex average
289 // distance) that the Point was not found in.
290 // for (auto r : make_range(result_set.size()))
291 // {
292 // auto nanoflann_index = _ret_index[_b[r]];
293 // const Elem * candidate_elem = (*_elems)[nanoflann_index];
294 //
295 // libMesh::err << "Vertex average/Elem id = " << candidate_elem->id()
296 // << ", dist = " << std::sqrt(_out_dist_sqr[_b[r]])
297 // << std::endl;
298 // } // end for(r)
299
300
301 libmesh_error_msg("Point " << p << " was not contained within the closest " << n_elems_checked <<
302 " elems (by vertex average distance), and _out_of_mesh_mode was not enabled.");
303 }
304
305 return found_elem;
306}
307
308
309void
311 std::set<const Elem *> & candidate_elements,
312 const std::set<subdomain_id_type> * allowed_subdomains) const
313{
315
316 LOG_SCOPE("operator() returning set", "PointLocatorNanoflann");
317
318 // Make sure the output set is initially empty
319 candidate_elements.clear();
320
321 // Keep track of the number of elements checked in detail
322 // unsigned int n_elems_checked = 0;
323
324 // Do the KD-Tree search
325 auto result_set = this->kd_tree_find_neighbors(p, _num_results);
326
327 for (auto r : make_range(result_set.size()))
328 {
329 // Translate the Nanoflann index, which is from [0..n_points),
330 // into the corresponding Elem id from the mesh.
331 auto nanoflann_index = _ret_index[r];
332 const Elem * candidate_elem = (*_elems)[nanoflann_index];
333
334 // Before we even check whether the candidate Elem actually
335 // contains the Point, we may need to check whether the
336 // candidate Elem is from an allowed subdomain. If the
337 // candidate Elem is not from an allowed subdomain, we continue
338 // to the next one.
339 if (allowed_subdomains && !allowed_subdomains->count(candidate_elem->subdomain_id()))
340 continue;
341
342 // If we made it here, then the candidate Elem is from an
343 // allowed subdomain, so let's next check whether it contains
344 // the point. If the user set a custom tolerance, then we
345 // actually check close_to_point() rather than contains_point(),
346 // since this latter function warns about using non-default
347 // tolerances, but otherwise does the same test.
348 bool inside = _use_contains_point_tol ?
349 candidate_elem->close_to_point(p, _contains_point_tol) :
350 candidate_elem->contains_point(p);
351
352 // Increment the number of elements checked
353 // n_elems_checked++;
354
355 // If the point is contained in/close to an Elem from an
356 // allowed subdomain, add it to the list.
357 if (inside)
358 candidate_elements.insert(candidate_elem);
359 } // end for(r)
360
361 // Debugging: for performance reasons, it may be useful to print the
362 // number of Elems actually checked during the search.
363 // libMesh::err << "Checked " << n_elems_checked << " nearby Elems before finding a containing Elem." << std::endl;
364}
365
366
367void
369{
370 // Out-of-mesh mode should now work properly even on meshes with
371 // non-affine elements.
372 _out_of_mesh_mode = true;
373}
374
375
376void
381
382
383
384std::size_t
389
390
391
392void
394{
395 // Must request at least 1 result
396 _num_results = std::max(static_cast<std::size_t>(1), val);
397}
398
399//
400// Required Nanoflann APIs
401//
402
404{
405 return _point_cloud->size();
406}
407
408
409
412 const std::size_t idx_p2,
413 std::size_t libmesh_dbg_var(size)) const
414{
415 // We only consider LIBMESH_DIM dimensional KD-Trees, so just make
416 // sure we were called consistently.
417 libmesh_assert_equal_to(size, LIBMESH_DIM);
418
419 // Construct a libmesh Point object from the LIBMESH_DIM-dimensional
420 // input object, p1.
421 Point point1;
422 for (int i=0; i<LIBMESH_DIM; ++i)
423 point1(i) = p1[i];
424
425 // Compute Euclidean distance, squared
426 return (point1 - (*_point_cloud)[idx_p2]).norm_sq();
427}
428
429
430
432PointLocatorNanoflann::kdtree_get_pt(const std::size_t idx, int dim) const
433{
434 libmesh_assert_less (idx, _point_cloud->size());
435 libmesh_assert_less (dim, LIBMESH_DIM);
436
437 return (*_point_cloud)[idx](dim);
438}
439
440} // namespace libMesh
441
442#endif
unsigned int dim
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual bool contains_point(const Point &p, Real tol=TOLERANCE) const
Definition elem.C:2784
subdomain_id_type subdomain_id() const
Definition elem.h:2591
virtual bool close_to_point(const Point &p, Real tol) const
Definition elem.C:2809
This is the MeshBase class.
Definition mesh_base.h:81
This is the base class for point locators.
Real _contains_point_tol
The tolerance to use when locating an element in the tree.
bool _initialized
true when properly initialized, false otherwise.
bool _use_contains_point_tol
true if we will use a user-specified tolerance for locating the element.
const MeshBase & _mesh
constant reference to the mesh in which the point is looked for.
const PointLocatorBase * _master
Const pointer to our master, initialized to nullptr if none given.
virtual void enable_out_of_mesh_mode() override final
Enables out-of-mesh mode.
virtual void init() override final
Initializes the locator, so that the operator() methods can be used.
virtual void clear() override final
Restore to PointLocator to a just-constructed state.
PointLocatorNanoflann(const MeshBase &mesh, const PointLocatorBase *master=nullptr)
Constructor.
Real coord_t
Floating point type used for storing coordinates.
coord_t kdtree_distance(const coord_t *p1, const std::size_t idx_p2, std::size_t size) const
Returns the squared distance between the vector (p1[0], p1[1], p1[2]) and the vertex average of Elem ...
bool _out_of_mesh_mode
true if out-of-mesh mode is enabled.
std::size_t _num_results
The number of results returned by Nanoflann when operator() is called.
virtual ~PointLocatorNanoflann()
Destructor.
std::vector< std::size_t > _ret_index
The operator() functions on PointLocator-derived classes are const, so to avoid re-allocating these r...
std::vector< std::size_t > _b
Vector of indices used by indirect sort.
std::size_t kdtree_get_point_count() const
Must return the number of data points.
coord_t kdtree_get_pt(const std::size_t idx, int dim) const
Returns the dim'th component of the idx'th vertex average.
std::shared_ptr< std::vector< const Elem * > > _elems
Lists of Points and ids which make up the "point cloud" that is to be searched via Nanoflann.
std::shared_ptr< std::vector< Point > > _point_cloud
virtual const Elem * operator()(const Point &p, const std::set< subdomain_id_type > *allowed_subdomains=nullptr) const override final
Locates the element in which the point with global coordinates p is located, optionally restricted to...
nanoflann::KNNResultSet< Real > kd_tree_find_neighbors(const Point &p, std::size_t num_results) const
Helper function that wraps the call to the KDTree's findNeighbors() routine.
std::size_t get_num_results() const
Set/get the number of results returned by each Nanoflann findNeighbors() search.
virtual void disable_out_of_mesh_mode() override final
Disables out-of-mesh mode (default).
std::shared_ptr< kd_tree_t > _kd_tree
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
auto norm_sq() const
MeshBase & mesh
The libMesh namespace provides an interface to certain functionality in the library.
libmesh_assert(ctx)
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition int_range.h:176