DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
BoundingBoxTree.h
1// Copyright (C) 2013-2026 Chris N. Richardson, Anders Logg, Garth N. Wells,
2// Jørgen S. Dokken, Sarah Roggendorf
3//
4// This file is part of DOLFINx (https://www.fenicsproject.org)
5//
6// SPDX-License-Identifier: LGPL-3.0-or-later
7
8#pragma once
9
10#include <algorithm>
11#include <array>
12#include <cassert>
13#include <cstdint>
14#include <dolfinx/common/log.h>
15#include <dolfinx/mesh/utils.h>
16#include <format>
17#include <iterator>
18#include <mpi.h>
19#include <optional>
20#include <span>
21#include <stdexcept>
22#include <string>
23#include <vector>
24
26{
27namespace impl_bb
28{
29//-----------------------------------------------------------------------------
30// Compute bounding box of mesh entity. The bounding box is defined by (lower
31// left corner, top right corner). Storage flattened row-major
32template <std::floating_point T>
33std::array<T, 6> compute_bbox_of_entity(const mesh::Mesh<T>& mesh, int dim,
34 std::int32_t index)
35{
36 // Get the geometrical indices for the mesh entity
37 std::span<const T> xg = mesh.geometry().x();
38
39 // FIXME: return of small dynamic array is expensive
40 std::span<const std::int32_t> entity(&index, 1);
41 const std::vector<std::int32_t> vertex_indices
42 = mesh::entities_to_geometry(mesh, dim, entity, false).first;
43
44 std::array<T, 6> b;
45 std::span<T, 3> b0(b.data(), 3);
46 std::span<T, 3> b1(b.data() + 3, 3);
47
48 std::copy_n(std::next(xg.begin(), 3 * vertex_indices.front()), 3, b0.begin());
49 std::copy_n(std::next(xg.begin(), 3 * vertex_indices.front()), 3, b1.begin());
50
51 // Compute min and max over vertices
52 for (std::int32_t local_vertex : vertex_indices)
53 {
54 for (std::size_t j = 0; j < 3; ++j)
55 {
56 b0[j] = std::min(b0[j], xg[3 * local_vertex + j]);
57 b1[j] = std::max(b1[j], xg[3 * local_vertex + j]);
58 }
59 }
60
61 return b;
62}
63//-----------------------------------------------------------------------------
64// Compute bounding box of bounding boxes. Each bounding box is defined as a
65// tuple (corners, entity_index). The corners of the bounding box is flattened
66// row-major as (lower left corner, top right corner).
67template <std::floating_point T>
68std::array<T, 6> compute_bbox_of_bboxes(
69 std::span<const std::pair<std::array<T, 6>, std::int32_t>> leaf_bboxes)
70{
71 // Compute min and max over remaining boxes
72 std::array<T, 6> b = leaf_bboxes.front().first;
73 for (auto [box, _] : leaf_bboxes)
74 {
75 std::transform(box.cbegin(), std::next(box.cbegin(), 3), b.cbegin(),
76 b.begin(), [](auto a, auto b) { return std::min(a, b); });
77 std::transform(std::next(box.cbegin(), 3), box.cend(),
78 std::next(b.cbegin(), 3), std::next(b.begin(), 3),
79 [](auto a, auto b) { return std::max(a, b); });
80 }
81
82 return b;
83}
84//------------------------------------------------------------------------------
85template <std::floating_point T>
86std::int32_t _build_from_leaf(
87 std::span<std::pair<std::array<T, 6>, std::int32_t>> leaf_bboxes,
88 std::vector<int>& bboxes, std::vector<T>& bbox_coordinates)
89{
90 if (leaf_bboxes.size() == 1)
91 {
92 // Reached leaf
93
94 // Get bounding box coordinates for leaf
95 const auto [b, entity_index] = leaf_bboxes.front();
96
97 // Store bounding box data
98 bboxes.push_back(entity_index);
99 bboxes.push_back(entity_index);
100 std::copy_n(b.begin(), 6, std::back_inserter(bbox_coordinates));
101 return bboxes.size() / 2 - 1;
102 }
103 else
104 {
105 // Compute bounding box of all bounding boxes
106 std::array b = compute_bbox_of_bboxes<T>(leaf_bboxes);
107
108 // Sort bounding boxes along longest axis
109 std::array<T, 3> b_diff;
110 std::transform(std::next(b.cbegin(), 3), b.cend(), b.cbegin(),
111 b_diff.begin(), std::minus<T>());
112 const std::size_t axis = std::ranges::distance(
113 b_diff.begin(), std::max_element(b_diff.begin(), b_diff.end()));
114
115 auto middle = std::next(leaf_bboxes.begin(), leaf_bboxes.size() / 2);
116 std::nth_element(leaf_bboxes.begin(), middle, leaf_bboxes.end(),
117 [axis](auto& p0, auto& p1) -> bool
118 {
119 auto x0 = p0.first[axis] + p0.first[3 + axis];
120 auto x1 = p1.first[axis] + p1.first[3 + axis];
121 return x0 < x1;
122 });
123
124 // Split bounding boxes into two groups and call recursively
125 assert(!leaf_bboxes.empty());
126 std::size_t part = leaf_bboxes.size() / 2;
127 std::int32_t bbox0
128 = _build_from_leaf(leaf_bboxes.first(part), bboxes, bbox_coordinates);
129 std::int32_t bbox1 = _build_from_leaf(
130 leaf_bboxes.last(leaf_bboxes.size() - part), bboxes, bbox_coordinates);
131
132 // Store bounding box data. Note that root box will be added last.
133 bboxes.push_back(bbox0);
134 bboxes.push_back(bbox1);
135 std::copy_n(b.begin(), 6, std::back_inserter(bbox_coordinates));
136 return bboxes.size() / 2 - 1;
137 }
138}
139//-----------------------------------------------------------------------------
140template <std::floating_point T>
141std::pair<std::vector<std::int32_t>, std::vector<T>> build_from_leaf(
142 std::vector<std::pair<std::array<T, 6>, std::int32_t>>& leaf_bboxes)
143{
144 assert(!leaf_bboxes.empty());
145 std::vector<std::int32_t> bboxes;
146 std::vector<T> bbox_coordinates;
147
148 // A binary tree with N leaves has 2N - 1 nodes.
149 const std::size_t num_nodes = 2 * leaf_bboxes.size() - 1;
150 bboxes.reserve(2 * num_nodes);
151 bbox_coordinates.reserve(6 * num_nodes);
152
153 impl_bb::_build_from_leaf<T>(leaf_bboxes, bboxes, bbox_coordinates);
154 return {std::move(bboxes), std::move(bbox_coordinates)};
155}
156//-----------------------------------------------------------------------------
157template <std::floating_point T>
158std::int32_t
159_build_from_point(std::span<std::pair<std::array<T, 3>, std::int32_t>> points,
160 std::vector<std::int32_t>& bboxes,
161 std::vector<T>& bbox_coordinates)
162{
163 // Reached leaf
164 if (points.size() == 1)
165 {
166 // Store bounding box data
167
168 // Index of entity contained in leaf
169 const std::int32_t c1 = points[0].second;
170 bboxes.push_back(c1);
171 bboxes.push_back(c1);
172 bbox_coordinates.insert(bbox_coordinates.end(), points[0].first.begin(),
173 points[0].first.end());
174 bbox_coordinates.insert(bbox_coordinates.end(), points[0].first.begin(),
175 points[0].first.end());
176 return bboxes.size() / 2 - 1;
177 }
178
179 // Compute bounding box of all points
180 auto [min, max] = std::ranges::minmax_element(points);
181 std::array<T, 3> b0 = min->first;
182 std::array<T, 3> b1 = max->first;
183
184 // Sort bounding boxes along longest axis
185 std::array<T, 3> b_diff;
186 std::ranges::transform(b1, b0, b_diff.begin(), std::minus<T>());
187 const std::size_t axis
188 = std::ranges::distance(b_diff.begin(), std::ranges::max_element(b_diff));
189
190 auto middle = std::next(points.begin(), points.size() / 2);
191 std::nth_element(points.begin(), middle, points.end(),
192 [axis](auto& p0, auto&& p1) -> bool
193 { return p0.first[axis] < p1.first[axis]; });
194
195 // Split bounding boxes into two groups and call recursively
196 assert(!points.empty());
197 std::size_t part = points.size() / 2;
198 std::int32_t bbox0
199 = _build_from_point(points.first(part), bboxes, bbox_coordinates);
200 std::int32_t bbox1 = _build_from_point(points.last(points.size() - part),
201 bboxes, bbox_coordinates);
202
203 // Store bounding box data. Note that root box will be added last.
204 bboxes.push_back(bbox0);
205 bboxes.push_back(bbox1);
206 bbox_coordinates.insert(bbox_coordinates.end(), b0.begin(), b0.end());
207 bbox_coordinates.insert(bbox_coordinates.end(), b1.begin(), b1.end());
208 return bboxes.size() / 2 - 1;
209}
210//-----------------------------------------------------------------------------
211} // namespace impl_bb
212
215template <std::floating_point T>
217{
218private:
224 static std::vector<std::int32_t> range(mesh::Topology& topology, int tdim)
225 {
226 topology.create_entities(tdim);
227 auto map = topology.index_map(tdim);
228 assert(map);
229 const std::int32_t num_entities = map->size_local() + map->num_ghosts();
230 std::vector<std::int32_t> r(num_entities);
231 std::iota(r.begin(), r.end(), 0);
232 return r;
233 }
234
235public:
245 BoundingBoxTree(const mesh::Mesh<T>& mesh, int tdim, double padding,
246 std::optional<std::span<const std::int32_t>> entities
247 = std::nullopt)
248 : _tdim(tdim)
249 {
250 // Initialize entities of given dimension if they don't exist
251 mesh.topology_mutable()->create_entities(tdim);
252
253 // Get input entities. If not provided, get all local entities of the given
254 // dimension (including ghosts)
255 std::span<const std::int32_t> entities_span;
256 std::optional<std::vector<std::int32_t>> local_range(std::nullopt);
257 if (entities)
258 entities_span = entities.value();
259 else
260 {
261 local_range.emplace(range(*mesh.topology_mutable(), tdim));
262 entities_span = std::span<const std::int32_t>(local_range->data(),
263 local_range->size());
264 }
265
266 if (tdim < 0 or tdim > mesh.topology()->dim())
267 {
268 throw std::invalid_argument(
269 "Dimension must be non-negative and less than or "
270 "equal to the topological dimension of the mesh");
271 }
272
273 mesh.topology_mutable()->create_connectivity(tdim, mesh.topology()->dim());
274
275 // Create bounding boxes for all mesh entities (leaves)
276 std::vector<std::pair<std::array<T, 6>, std::int32_t>> leaf_bboxes;
277 leaf_bboxes.reserve(entities_span.size());
278 for (std::int32_t e : entities_span)
279 {
280 std::array<T, 6> b = impl_bb::compute_bbox_of_entity(mesh, tdim, e);
281 std::transform(b.cbegin(), std::next(b.cbegin(), 3), b.begin(),
282 [padding](auto x) { return x - padding; });
283 std::transform(std::next(b.begin(), 3), b.end(), std::next(b.begin(), 3),
284 [padding](auto x) { return x + padding; });
285 leaf_bboxes.emplace_back(b, e);
286 }
287
288 // Recursively build the bounding box tree from the leaves
289 if (!leaf_bboxes.empty())
290 {
291 std::tie(_bboxes, _bbox_coordinates)
292 = impl_bb::build_from_leaf(leaf_bboxes);
293 }
294
295 logger().info("Computed bounding box tree with {} nodes for {} entities",
296 num_bboxes(), entities_span.size());
297 }
298
302 BoundingBoxTree(std::vector<std::pair<std::array<T, 3>, std::int32_t>> points)
303 : _tdim(0)
304 {
305 // Recursively build the bounding box tree from the leaves
306 if (!points.empty())
307 {
308 _bboxes.clear();
309
310 // A binary tree with N leaves has 2N - 1 nodes.
311 const std::size_t num_nodes = 2 * points.size() - 1;
312 _bboxes.reserve(2 * num_nodes);
313 _bbox_coordinates.reserve(6 * num_nodes);
314
315 impl_bb::_build_from_point(std::span(points), _bboxes, _bbox_coordinates);
316 }
317
318 logger().info("Computed bounding box tree with {} nodes for {} points.",
319 num_bboxes(), points.size());
320 }
321
322 // Copy constructor (deleted)
323 BoundingBoxTree(const BoundingBoxTree& tree) = delete;
324
327
329 ~BoundingBoxTree() = default;
330
331 // Copy assignment (deleted)
332 BoundingBoxTree& operator=(const BoundingBoxTree& other) = delete;
333
336
342 std::array<T, 6> get_bbox(std::size_t node) const
343 {
344 std::array<T, 6> x;
345 std::copy_n(_bbox_coordinates.data() + 6 * node, 6, x.begin());
346 return x;
347 }
348
355 {
356 // Build tree for each rank
357 const int mpi_size = dolfinx::MPI::size(comm);
358
359 // Send root node coordinates to all processes
360 // This is to counteract the fact that a process might have 0 bounding box
361 // causing false positives on process collisions around (0,0,0)
362 constexpr T max_val = std::numeric_limits<T>::max();
363 std::array<T, 6> send_bbox
364 = {max_val, max_val, max_val, max_val, max_val, max_val};
365 if (num_bboxes() > 0)
366 std::copy_n(std::prev(_bbox_coordinates.end(), 6), 6, send_bbox.begin());
367 std::vector<T> recv_bbox(mpi_size * 6);
368 MPI_Allgather(send_bbox.data(), 6, dolfinx::MPI::mpi_t<T>, recv_bbox.data(),
369 6, dolfinx::MPI::mpi_t<T>, comm);
370
371 std::vector<std::pair<std::array<T, 6>, std::int32_t>> _recv_bbox(mpi_size);
372 for (std::size_t i = 0; i < _recv_bbox.size(); ++i)
373 {
374 std::copy_n(std::next(recv_bbox.begin(), 6 * i), 6,
375 _recv_bbox[i].first.begin());
376 _recv_bbox[i].second = i;
377 }
378
379 auto [global_bboxes, global_coords] = impl_bb::build_from_leaf(_recv_bbox);
380 BoundingBoxTree global_tree(std::move(global_bboxes),
381 std::move(global_coords));
382
383 logger().info("Computed global bounding box tree with {} boxes.",
384 global_tree.num_bboxes());
385
386 return global_tree;
387 }
388
390 std::int32_t num_bboxes() const { return _bboxes.size() / 2; }
391
397 std::span<const T> bbox_coordinates() const { return _bbox_coordinates; }
398
404 std::span<T> bbox_coordinates() { return _bbox_coordinates; }
405
407 int tdim() const { return _tdim; }
408
410 std::string str() const
411 {
412 std::string s;
413 tree_print(s, _bboxes.size() / 2 - 1);
414 return s;
415 }
416
424 std::array<std::int32_t, 2> bbox(std::size_t node) const
425 {
426 assert(2 * node + 1 < _bboxes.size());
427 return {_bboxes[2 * node], _bboxes[2 * node + 1]};
428 }
429
430private:
431 // Constructor
432 BoundingBoxTree(std::vector<std::int32_t>&& bboxes,
433 std::vector<T>&& bbox_coords)
434 : _tdim(0), _bboxes(bboxes), _bbox_coordinates(bbox_coords)
435 {
436 // Do nothing
437 }
438
439 // Topological dimension of leaf entities
440 int _tdim;
441
442 // Print out recursively, for debugging
443 void tree_print(std::string& s, std::int32_t i) const
444 {
445 s += "[";
446 for (std::size_t j = 0; j < 2; ++j)
447 {
448 for (std::size_t k = 0; k < 3; ++k)
449 {
450 std::format_to(std::back_inserter(s), "{:.6} ",
451 _bbox_coordinates[6 * i + j * 3 + k]);
452 }
453 if (j == 0)
454 s += "]->[";
455 }
456 s += "]\n";
457
458 if (_bboxes[2 * i] == _bboxes[2 * i + 1])
459 {
460 std::format_to(std::back_inserter(s), "leaf containing entity ({})",
461 _bboxes[2 * i + 1]);
462 }
463 else
464 {
465 s += "{";
466 tree_print(s, _bboxes[2 * i]);
467 s += ", \n";
468 tree_print(s, _bboxes[2 * i + 1]);
469 s += "}\n";
470 }
471 }
472
473 // List of bounding boxes (parent-child-entity relations)
474 std::vector<std::int32_t> _bboxes;
475
476 // List of bounding box coordinates
477 std::vector<T> _bbox_coordinates;
478};
479} // namespace dolfinx::geometry
Definition BoundingBoxTree.h:217
BoundingBoxTree(std::vector< std::pair< std::array< T, 3 >, std::int32_t > > points)
Definition BoundingBoxTree.h:302
std::span< T > bbox_coordinates()
Access coordinates of lower and upper corners of bounding boxes (non-const version).
Definition BoundingBoxTree.h:404
BoundingBoxTree & operator=(BoundingBoxTree &&other)=default
Move assignment.
BoundingBoxTree create_global_tree(MPI_Comm comm) const
Definition BoundingBoxTree.h:354
BoundingBoxTree(const mesh::Mesh< T > &mesh, int tdim, double padding, std::optional< std::span< const std::int32_t > > entities=std::nullopt)
Definition BoundingBoxTree.h:245
int tdim() const
Topological dimension of leaf entities.
Definition BoundingBoxTree.h:407
BoundingBoxTree(BoundingBoxTree &&tree)=default
Move constructor.
std::int32_t num_bboxes() const
Return number of bounding boxes.
Definition BoundingBoxTree.h:390
std::array< T, 6 > get_bbox(std::size_t node) const
Return bounding box coordinates for a given node in the tree,.
Definition BoundingBoxTree.h:342
std::array< std::int32_t, 2 > bbox(std::size_t node) const
Definition BoundingBoxTree.h:424
~BoundingBoxTree()=default
Destructor.
std::span< const T > bbox_coordinates() const
Access coordinates of lower and upper corners of bounding boxes (const version).
Definition BoundingBoxTree.h:397
std::string str() const
Print out for debugging.
Definition BoundingBoxTree.h:410
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
Topology stores the topology of a mesh, consisting of mesh entities and connectivity (incidence relat...
Definition Topology.h:49
std::shared_ptr< const common::IndexMap > index_map(int dim) const
Get the IndexMap that describes the parallel distribution of the mesh entities.
Definition Topology.cpp:937
bool create_entities(int dim, int num_threads=1)
Create entities of given topological dimension.
Definition Topology.cpp:1090
Functions supporting mesh operations.
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
int size(MPI_Comm comm)
Definition MPI.cpp:81
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:26
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
std::pair< std::vector< std::int32_t >, std::array< std::size_t, 2 > > entities_to_geometry(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities, bool permute=false)
Compute the geometry degrees of freedom associated with the closure of a given set of cell entities.
Definition utils.h:774
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24