DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
utils.h
1// Copyright (C) 2019-2021 Garth N. Wells and Jørgen S. Dokken
2//
3// This file is part of DOLFINx (https://www.fenicsproject.org)
4//
5// SPDX-License-Identifier: LGPL-3.0-or-later
6
7#pragma once
8
9#include "BoundingBoxTree.h"
10#include "gjk.h"
11#include <algorithm>
12#include <array>
13#include <cassert>
14#include <concepts>
15#include <cstdint>
16#include <dolfinx/common/log.h>
17#include <dolfinx/fem/CoordinateElement.h>
18#include <dolfinx/fem/ElementDofLayout.h>
19#include <dolfinx/graph/AdjacencyList.h>
20#include <dolfinx/mesh/Mesh.h>
21#include <limits>
22#include <map>
23#include <numeric>
24#include <optional>
25#include <span>
26#include <utility>
27#include <vector>
28
29namespace dolfinx::geometry
30{
34template <std::floating_point T>
36{
37 std::vector<int> src_owner;
39 std::vector<int>
41 std::vector<T> dest_points;
42 std::vector<std::int32_t>
45};
46
57template <std::floating_point T>
58std::vector<T> shortest_vector(const mesh::Mesh<T>& mesh, int dim,
59 std::span<const std::int32_t> entities,
60 std::span<const T> points)
61{
62 const int tdim = mesh.topology()->dim();
63 const mesh::Geometry<T>& geometry = mesh.geometry();
64
65 std::span<const T> geom_dofs = geometry.x();
66 auto x_dofmap = geometry.dofmaps().front();
67 std::vector<T> shortest_vectors;
68 shortest_vectors.reserve(3 * entities.size());
69 if (dim == tdim)
70 {
71 for (std::size_t e = 0; e < entities.size(); e++)
72 {
73 // Check that we have sent in valid entities, i.e. that they exist in the
74 // local dofmap. One gets a cryptical memory segfault if entities is -1
75 assert(entities[e] >= 0);
76 auto dofs = md::submdspan(x_dofmap, entities[e], md::full_extent);
77 std::vector<T> nodes(3 * dofs.size());
78 for (std::size_t i = 0; i < dofs.size(); ++i)
79 {
80 const std::int32_t pos = 3 * dofs[i];
81 for (std::size_t j = 0; j < 3; ++j)
82 nodes[3 * i + j] = geom_dofs[pos + j];
83 }
84
85 std::array<T, 3> d
86 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
87 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
88 }
89 }
90 else
91 {
92 mesh.topology_mutable()->create_connectivity(dim, tdim);
93 mesh.topology_mutable()->create_connectivity(tdim, dim);
94 auto e_to_c = mesh.topology()->connectivity(dim, tdim);
95 assert(e_to_c);
96 auto c_to_e = mesh.topology_mutable()->connectivity(tdim, dim);
97 assert(c_to_e);
98 for (std::size_t e = 0; e < entities.size(); e++)
99 {
100 const std::int32_t index = entities[e];
101
102 // Find attached cell
103 assert(e_to_c->num_links(index) > 0);
104 const std::int32_t c = e_to_c->links(index)[0];
105
106 // Find local number of entity wrt cell
107 auto cell_entities = c_to_e->links(c);
108 auto it0 = std::find(cell_entities.begin(), cell_entities.end(), index);
109 assert(it0 != cell_entities.end());
110 const int local_cell_entity
111 = std::ranges::distance(cell_entities.begin(), it0);
112
113 // Tabulate geometry dofs for the entity
114 auto dofs = md::submdspan(x_dofmap, c, md::full_extent);
115 const std::vector<int> entity_dofs
116 = geometry.cmaps().front().create_dof_layout().entity_closure_dofs(
117 dim, local_cell_entity);
118 std::vector<T> nodes(3 * entity_dofs.size());
119 for (std::size_t i = 0; i < entity_dofs.size(); i++)
120 {
121 const std::int32_t pos = 3 * dofs[entity_dofs[i]];
122 for (std::size_t j = 0; j < 3; ++j)
123 nodes[3 * i + j] = geom_dofs[pos + j];
124 }
125
126 std::array<T, 3> d
127 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
128 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
129 }
130 }
131
132 return shortest_vectors;
133}
134
141template <std::floating_point T>
142T compute_squared_distance_bbox(std::span<const T, 6> b,
143 std::span<const T, 3> x)
144{
145 auto b0 = b.template subspan<0, 3>();
146 auto b1 = b.template subspan<3, 3>();
147 return std::transform_reduce(x.begin(), x.end(), b0.begin(), 0.0,
148 std::plus<>{},
149 [](auto x, auto b)
150 {
151 auto dx = x - b;
152 return dx > 0 ? 0 : dx * dx;
153 })
154 + std::transform_reduce(x.begin(), x.end(), b1.begin(), 0.0,
155 std::plus<>{},
156 [](auto x, auto b)
157 {
158 auto dx = x - b;
159 return dx < 0 ? 0 : dx * dx;
160 });
161}
162
178template <std::floating_point T>
179std::vector<T> squared_distance(const mesh::Mesh<T>& mesh, int dim,
180 std::span<const std::int32_t> entities,
181 std::span<const T> points)
182{
183 std::vector<T> v = shortest_vector(mesh, dim, entities, points);
184 std::vector<T> d(v.size() / 3, 0);
185 for (std::size_t i = 0; i < d.size(); ++i)
186 for (std::size_t j = 0; j < 3; ++j)
187 d[i] += v[3 * i + j] * v[3 * i + j];
188 return d;
189}
190
191namespace impl
192{
194constexpr bool is_leaf(std::array<std::int32_t, 2> bbox)
195{
196 // Leaf nodes are marked by setting child_0 equal to child_1
197 return bbox[0] == bbox[1];
198}
199
204template <std::floating_point T>
205constexpr bool point_in_bbox(std::span<const T, 6> b, std::span<const T, 3> x)
206{
207 constexpr T rtol = 1e-14;
208 bool in = true;
209 for (std::size_t i = 0; i < 3; i++)
210 {
211 T eps = rtol * (b[i + 3] - b[i]);
212 in &= (x[i] >= (b[i] - eps)) && (x[i] <= (b[i + 3] + eps));
213 if (!in)
214 break;
215 }
216
217 return in;
218}
219
223template <std::floating_point T>
224constexpr bool bbox_in_bbox(std::span<const T, 6> a, std::span<const T, 6> b)
225{
226 constexpr T rtol = 1e-14;
227 auto a0 = a.template subspan<0, 3>();
228 auto a1 = a.template subspan<3, 3>();
229 auto b0 = b.template subspan<0, 3>();
230 auto b1 = b.template subspan<3, 3>();
231
232 bool in = true;
233 for (std::size_t i = 0; i < 3; i++)
234 {
235 T eps = rtol * (b1[i] - b0[i]);
236 in &= a1[i] >= (b0[i] - eps);
237 in &= a0[i] <= (b1[i] + eps);
238 }
239
240 return in;
241}
242
245template <std::floating_point T>
247{
248 std::vector<T> nodes;
254};
255
265template <std::floating_point T>
266T squared_distance_entity(const mesh::Mesh<T>& mesh, int dim,
267 std::int32_t entity, std::span<const T, 3> point,
269{
270 const int tdim = mesh.topology()->dim();
271 const mesh::Geometry<T>& geometry = mesh.geometry();
272 std::span<const T> geom_dofs = geometry.x();
273 auto x_dofmap = geometry.dofmaps().front();
274
275 std::array<T, 3> d;
276 if (dim == tdim)
277 {
278 assert(entity >= 0);
279 auto dofs = md::submdspan(x_dofmap, entity, md::full_extent);
280 assert(3 * dofs.size() <= scratch.nodes.size());
281 for (std::size_t i = 0; i < dofs.size(); ++i)
282 {
283 const std::int32_t pos = 3 * dofs[i];
284 for (std::size_t j = 0; j < 3; ++j)
285 scratch.nodes[3 * i + j] = geom_dofs[pos + j];
286 }
287
289 point, std::span<const T>(scratch.nodes.data(), 3 * dofs.size()));
290 }
291 else
292 {
293 mesh.topology_mutable()->create_connectivity(dim, tdim);
294 mesh.topology_mutable()->create_connectivity(tdim, dim);
295 auto e_to_c = mesh.topology()->connectivity(dim, tdim);
296 assert(e_to_c);
297 auto c_to_e = mesh.topology_mutable()->connectivity(tdim, dim);
298 assert(c_to_e);
299
300 // Find attached cell
301 assert(e_to_c->num_links(entity) > 0);
302 const std::int32_t c = e_to_c->links(entity)[0];
303
304 // Find local number of entity wrt cell
305 auto cell_entities = c_to_e->links(c);
306 auto it0 = std::find(cell_entities.begin(), cell_entities.end(), entity);
307 assert(it0 != cell_entities.end());
308 const int local_cell_entity
309 = std::ranges::distance(cell_entities.begin(), it0);
310
311 // Tabulate geometry dofs for the entity
312 auto dofs = md::submdspan(x_dofmap, c, md::full_extent);
313 const std::vector<int>& entity_dofs
314 = scratch.cmap_dof_layout.entity_closure_dofs(dim, local_cell_entity);
315 assert(3 * entity_dofs.size() <= scratch.nodes.size());
316 for (std::size_t i = 0; i < entity_dofs.size(); i++)
317 {
318 const std::int32_t pos = 3 * dofs[entity_dofs[i]];
319 for (std::size_t j = 0; j < 3; ++j)
320 scratch.nodes[3 * i + j] = geom_dofs[pos + j];
321 }
322
324 point,
325 std::span<const T>(scratch.nodes.data(), 3 * entity_dofs.size()));
326 }
327
328 return d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
329}
330
332template <std::floating_point T>
333std::pair<std::int32_t, T>
334_compute_closest_entity(const geometry::BoundingBoxTree<T>& tree,
335 std::span<const T, 3> point, std::int32_t node,
336 const mesh::Mesh<T>& mesh, std::int32_t closest_entity,
337 T R2, ClosestEntityScratch<T>& scratch)
338{
339 // Get children of current bounding box node (child_1 denotes entity
340 // index for leaves)
341 const std::array<std::int32_t, 2> bbox = tree.bbox(node);
342 T r2;
343 if (is_leaf(bbox))
344 {
345 // If point cloud tree the exact distance is easy to compute
346 if (tree.tdim() == 0)
347 {
348 std::array<T, 6> diff = tree.get_bbox(node);
349 for (std::size_t k = 0; k < 3; ++k)
350 diff[k] -= point[k];
351 r2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
352 }
353 else
354 {
355 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
356
357 // If bounding box closer than previous closest entity, use gjk to
358 // obtain exact distance to the convex hull of the entity
359 if (r2 <= R2)
360 {
361 r2 = squared_distance_entity(mesh, tree.tdim(), bbox.back(), point,
362 scratch);
363 }
364 }
365
366 // If entity is closer than best result so far, return it
367 if (r2 <= R2)
368 {
369 closest_entity = bbox.back();
370 R2 = r2;
371 }
372
373 return {closest_entity, R2};
374 }
375 else
376 {
377 // If bounding box is outside radius, then don't search further
378 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
379 if (r2 > R2)
380 return {closest_entity, R2};
381
382 // Check both children. We use R2 (as opposed to r2), as a bounding
383 // box can be closer than the actual entity.
384 std::pair<std::int32_t, T> p0 = _compute_closest_entity(
385 tree, point, bbox.front(), mesh, closest_entity, R2, scratch);
386 std::pair<std::int32_t, T> p1 = _compute_closest_entity(
387 tree, point, bbox.back(), mesh, p0.first, p0.second, scratch);
388 return p1;
389 }
390}
391
398template <std::floating_point T>
399void _compute_collisions_point(const geometry::BoundingBoxTree<T>& tree,
400 std::span<const T, 3> p,
401 std::vector<std::int32_t>& entities,
402 std::vector<std::int32_t>& stack)
403{
404 assert(stack.empty());
405 std::int32_t next = tree.num_bboxes() - 1;
406 std::span<const T> coords = tree.bbox_coordinates();
407 auto view_bbox = [&coords](std::int32_t node)
408 { return std::span<const T, 6>(coords.data() + 6 * node, 6); };
409 while (next != -1)
410 {
411 if (std::array bbox = tree.bbox(next);
412 is_leaf(bbox) and point_in_bbox(view_bbox(next), p))
413 {
414 // If box is a leaf node then add it to the list of colliding
415 // entities
416 entities.push_back(bbox[1]);
417 next = -1;
418 }
419 else
420 {
421 // Check whether the point collides with child nodes (left and
422 // right)
423 bool left = point_in_bbox(view_bbox(bbox[0]), p);
424 bool right = point_in_bbox(view_bbox(bbox[1]), p);
425 if (left and right)
426 {
427 // If the point collides with both child nodes, add the right
428 // node to the stack (for later visiting) and continue the tree
429 // traversal with the left subtree
430 stack.push_back(bbox[1]);
431 next = bbox[0];
432 }
433 else if (left)
434 {
435 // Traverse the current node's left subtree
436 next = bbox[0];
437 }
438 else if (right)
439 {
440 // Traverse the current node's right subtree
441 next = bbox[1];
442 }
443 else
444 next = -1;
445 }
446
447 // If tree traversal reaches a dead end (box is a leaf node or no
448 // collision detected), check the stack for deferred subtrees
449 if (next == -1 and !stack.empty())
450 {
451 next = stack.back();
452 stack.pop_back();
453 }
454 }
455}
456
457// Compute collisions with tree (recursive)
458template <std::floating_point T>
459void _compute_collisions_tree(const geometry::BoundingBoxTree<T>& A,
460 const geometry::BoundingBoxTree<T>& B,
461 std::int32_t node_A, std::int32_t node_B,
462 std::vector<std::int32_t>& entities)
463{
464 // If bounding boxes don't collide, then don't search further
465 if (!bbox_in_bbox<T>(A.get_bbox(node_A), B.get_bbox(node_B)))
466 return;
467
468 // Get bounding boxes for current nodes
469 const std::array<std::int32_t, 2> bbox_A = A.bbox(node_A);
470 const std::array<std::int32_t, 2> bbox_B = B.bbox(node_B);
471
472 // Check whether we've reached a leaf in A or B
473 const bool is_leaf_A = is_leaf(bbox_A);
474 const bool is_leaf_B = is_leaf(bbox_B);
475 if (is_leaf_A and is_leaf_B)
476 {
477 // If both boxes are leaves (which we know collide), then add them
478 // child_1 denotes entity for leaves
479 entities.push_back(bbox_A[1]);
480 entities.push_back(bbox_B[1]);
481 }
482 else if (is_leaf_A)
483 {
484 // If we reached the leaf in A, then descend B
485 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
486 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
487 }
488 else if (is_leaf_B)
489 {
490 // If we reached the leaf in B, then descend A
491 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
492 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
493 }
494 else if (node_A > node_B)
495 {
496 // At this point, we know neither is a leaf so descend the largest
497 // tree first. Note that nodes are added in reverse order with the
498 // top bounding box at the end so the largest tree (the one with the
499 // the most boxes left to traverse) has the largest node number.
500 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
501 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
502 }
503 else
504 {
505 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
506 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
507 }
508
509 // Note that cases above can be collected in fewer cases but this way
510 // the logic is easier to follow.
511}
512
513} // namespace impl
514
521template <std::floating_point T>
523 std::span<const std::int32_t> entities)
524{
525 logger().info("Building point search tree to accelerate distance queries for "
526 "a given topological dimension and subset of entities.");
527
528 const std::vector<T> midpoints
529 = mesh::compute_midpoints(mesh, tdim, entities);
530 std::vector<std::pair<std::array<T, 3>, std::int32_t>> points(
531 entities.size());
532 for (std::size_t i = 0; i < points.size(); ++i)
533 {
534 for (std::size_t j = 0; j < 3; ++j)
535 points[i].first[j] = midpoints[3 * i + j];
536 points[i].second = entities[i];
537 }
538
539 // Build tree
540 return BoundingBoxTree(points);
541}
542
548template <std::floating_point T>
549std::vector<std::int32_t> compute_collisions(const BoundingBoxTree<T>& tree0,
550 const BoundingBoxTree<T>& tree1)
551{
552 // Call recursive find function
553 std::vector<std::int32_t> entities;
554 if (tree0.num_bboxes() > 0 and tree1.num_bboxes() > 0)
555 {
556 impl::_compute_collisions_tree(tree0, tree1, tree0.num_bboxes() - 1,
557 tree1.num_bboxes() - 1, entities);
558 }
559
560 return entities;
561}
562
573template <std::floating_point T>
575compute_collisions(const BoundingBoxTree<T>& tree, std::span<const T> points)
576{
577 if (tree.num_bboxes() > 0)
578 {
579 std::vector<std::int32_t> entities, offsets(points.size() / 3 + 1, 0);
580 entities.reserve(points.size() / 3);
581
582 // Scratch stack reused across points.
583 std::vector<std::int32_t> stack;
584 for (std::size_t p = 0; p < points.size() / 3; ++p)
585 {
586 impl::_compute_collisions_point(
587 tree, std::span<const T, 3>(points.data() + 3 * p, 3), entities,
588 stack);
589 offsets[p + 1] = entities.size();
590 }
591
592 return graph::AdjacencyList(std::move(entities), std::move(offsets));
593 }
594 else
595 {
597 std::vector<std::int32_t>(),
598 std::vector<std::int32_t>(points.size() / 3 + 1, 0));
599 }
600}
601
627template <std::floating_point T>
629 std::span<const std::int32_t> cells,
630 std::array<T, 3> point, T tol,
631 std::span<T> coordinate_dofs)
632{
633 if (cells.empty())
634 return -1;
635
636 const mesh::Geometry<T>& geometry = mesh.geometry();
637 std::span<const T> geom_dofs = geometry.x();
638 auto x_dofmap = geometry.dofmaps().front();
639 const std::size_t num_nodes = x_dofmap.extent(1);
640 assert(coordinate_dofs.size() >= num_nodes * 3);
641 std::span<T> cell_dofs = coordinate_dofs.first(num_nodes * 3);
642 for (auto cell : cells)
643 {
644 auto dofs = md::submdspan(x_dofmap, cell, md::full_extent);
645 for (std::size_t i = 0; i < num_nodes; ++i)
646 {
647 std::copy_n(std::next(geom_dofs.begin(), 3 * dofs[i]), 3,
648 std::next(cell_dofs.begin(), 3 * i));
649 }
650
651 std::array<T, 3> shortest_vector
652 = compute_distance_gjk<T>(point, cell_dofs);
653 T d2 = std::reduce(shortest_vector.begin(), shortest_vector.end(), T(0),
654 [](auto d, auto e) { return d + e * e; });
655 if (d2 < tol)
656 return cell;
657 }
658
659 return -1;
660}
661
674template <std::floating_point T>
675std::vector<std::int32_t>
677 const BoundingBoxTree<T>& midpoint_tree,
678 const mesh::Mesh<T>& mesh, std::span<const T> points)
679{
680 if (tree.num_bboxes() == 0)
681 return std::vector<std::int32_t>(points.size() / 3, -1);
682
683 std::vector<std::int32_t> entities;
684 entities.reserve(points.size() / 3);
685
686 const fem::ElementDofLayout cmap_dof_layout
687 = mesh.geometry().cmaps().front().create_dof_layout();
688
689 // Scratch buffer sized for the largest coordinate element, to avoid
690 // repeated resizing in the closest-entity search (mixed topology may
691 // have more than one coordinate element).
692 std::size_t max_nodes = 0;
693 for (const fem::CoordinateElement<T>& cmap : mesh.geometry().cmaps())
694 max_nodes = std::max(max_nodes, static_cast<std::size_t>(cmap.dim()));
695 impl::ClosestEntityScratch<T> scratch{std::vector<T>(3 * max_nodes),
696 cmap_dof_layout};
697 for (std::size_t i = 0; i < points.size() / 3; ++i)
698 {
699 // Use midpoint tree to find initial closest entity to the point.
700 // Start by using a leaf node as the initial guess for the input
701 // entity
702 std::array<std::int32_t, 2> leaf0 = midpoint_tree.bbox(0);
703 assert(impl::is_leaf(leaf0));
704 std::array<T, 6> diff = midpoint_tree.get_bbox(0);
705 for (std::size_t k = 0; k < 3; ++k)
706 diff[k] -= points[3 * i + k];
707 T R2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
708
709 // Use a recursive search through the bounding box tree
710 // to find determine the entity with the closest midpoint.
711 // As the midpoint tree only consist of points, the distance
712 // queries are lightweight.
713 const auto [m_index, m_distance2] = impl::_compute_closest_entity(
714 midpoint_tree, std::span<const T, 3>(points.data() + 3 * i, 3),
715 midpoint_tree.num_bboxes() - 1, mesh, leaf0[0], R2, scratch);
716
717 // Use a recursives search through the bounding box tree to
718 // determine which entity is actually closest.
719 // Uses the entity with the closest midpoint as initial guess, and
720 // the distance from the midpoint to the point of interest as the
721 // initial search radius.
722 const auto [index, distance2] = impl::_compute_closest_entity(
723 tree, std::span<const T, 3>(points.data() + 3 * i, 3),
724 tree.num_bboxes() - 1, mesh, m_index, m_distance2, scratch);
725
726 entities.push_back(index);
727 }
728
729 return entities;
730}
731
747template <std::floating_point T>
749 const mesh::Mesh<T>& mesh,
750 const graph::AdjacencyList<std::int32_t>& candidate_cells,
751 std::span<const T> points)
752{
753 std::vector<std::int32_t> offsets = {0};
754 offsets.reserve(candidate_cells.num_nodes() + 1);
755 std::vector<std::int32_t> colliding_cells;
756 constexpr T eps2 = 1e-12;
757 const int tdim = mesh.topology()->dim();
758 for (std::int32_t i = 0; i < candidate_cells.num_nodes(); i++)
759 {
760 auto cells = candidate_cells.links(i);
761 std::vector<T> _point(3 * cells.size());
762 for (std::size_t j = 0; j < cells.size(); ++j)
763 for (std::size_t k = 0; k < 3; ++k)
764 _point[3 * j + k] = points[3 * i + k];
765
766 std::vector distances_sq = squared_distance<T>(mesh, tdim, cells, _point);
767 for (std::size_t j = 0; j < cells.size(); j++)
768 if (distances_sq[j] < eps2)
769 colliding_cells.push_back(cells[j]);
770
771 offsets.push_back(colliding_cells.size());
772 }
773
774 return graph::AdjacencyList(std::move(colliding_cells), std::move(offsets));
775}
776
818template <std::floating_point T>
819PointOwnershipData<T>
820determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points,
821 T padding,
822 std::optional<std::span<const std::int32_t>> cells,
823 bool find_closest_cell = true)
824{
825 MPI_Comm comm = mesh.comm();
826
827 const int tdim = mesh.topology()->dim();
828
829 std::vector<std::int32_t> local_cells;
830 if (not(cells.has_value()))
831 {
832 auto cell_map = mesh.topology()->index_map(tdim);
833 local_cells.resize(cell_map->size_local());
834 std::iota(local_cells.begin(), local_cells.end(), 0);
835 cells
836 = std::span<const std::int32_t>(local_cells.data(), local_cells.size());
837 }
838 // Create a global bounding-box tree to find candidate processes with
839 // cells that could collide with the points
840 BoundingBoxTree bb(mesh, tdim, padding, cells.value());
841 BoundingBoxTree global_bbtree = bb.create_global_tree(comm);
842
843 // Compute collisions:
844 // For each point in `points` get the processes it should be sent to
845 graph::AdjacencyList collisions = compute_collisions(global_bbtree, points);
846
847 // Get unique list of outgoing ranks
848 std::vector<std::int32_t> out_ranks = collisions.array();
849 std::ranges::sort(out_ranks);
850 auto [unique_end, range_end] = std::ranges::unique(out_ranks);
851 out_ranks.erase(unique_end, range_end);
852
853 // Compute incoming edges (source processes)
854 std::vector in_ranks = dolfinx::MPI::compute_graph_edges_nbx(comm, out_ranks);
855 std::ranges::sort(in_ranks);
856
857 // Create neighborhood communicator in forward direction
858 MPI_Comm forward_comm;
859 MPI_Dist_graph_create_adjacent(
860 comm, in_ranks.size(), in_ranks.data(), MPI_UNWEIGHTED, out_ranks.size(),
861 out_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &forward_comm);
862
863 // Compute map from global mpi rank to neighbor rank, "collisions"
864 // uses global rank
865 std::map<std::int32_t, std::int32_t> rank_to_neighbor;
866 for (std::size_t i = 0; i < out_ranks.size(); i++)
867 rank_to_neighbor[out_ranks[i]] = i;
868
869 // Count the number of points to send per neighbor process
870 std::vector<std::int32_t> send_sizes(out_ranks.size());
871 for (std::size_t i = 0; i < points.size() / 3; ++i)
872 for (std::int32_t p : collisions.links(i))
873 send_sizes[rank_to_neighbor[p]] += 3;
874
875 // Compute receive sizes
876 std::vector<std::int32_t> recv_sizes(in_ranks.size());
877 send_sizes.reserve(1);
878 recv_sizes.reserve(1);
879 MPI_Request sizes_request;
880 MPI_Ineighbor_alltoall(send_sizes.data(), 1, MPI_INT, recv_sizes.data(), 1,
881 MPI_INT, forward_comm, &sizes_request);
882
883 // Compute sending offsets
884 std::vector<std::int32_t> send_offsets(send_sizes.size() + 1, 0);
885 std::partial_sum(send_sizes.begin(), send_sizes.end(),
886 std::next(send_offsets.begin(), 1));
887
888 // Pack data to send and store unpack map
889 std::vector<T> send_data(send_offsets.back());
890 std::vector<std::int32_t> counter(send_sizes.size(), 0);
891 // unpack map: [index in adj list][pos in x]
892 std::vector<std::int32_t> unpack_map(send_offsets.back() / 3);
893 for (std::size_t i = 0; i < points.size(); i += 3)
894 {
895 for (std::int32_t p : collisions.links(i / 3))
896 {
897 std::int32_t neighbor = rank_to_neighbor[p];
898 std::int32_t pos = send_offsets[neighbor] + counter[neighbor];
899 auto it = std::next(send_data.begin(), pos);
900 std::copy_n(std::next(points.begin(), i), 3, it);
901 unpack_map[pos / 3] = i / 3;
902 counter[neighbor] += 3;
903 }
904 }
905
906 MPI_Wait(&sizes_request, MPI_STATUS_IGNORE);
907 std::vector<std::int32_t> recv_offsets(in_ranks.size() + 1, 0);
908 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
909 std::next(recv_offsets.begin(), 1));
910
911 std::vector<T> received_points((std::size_t)recv_offsets.back());
912 MPI_Neighbor_alltoallv(
913 send_data.data(), send_sizes.data(), send_offsets.data(),
914 dolfinx::MPI::mpi_t<T>, received_points.data(), recv_sizes.data(),
915 recv_offsets.data(), dolfinx::MPI::mpi_t<T>, forward_comm);
916
917 // Get mesh geometry for closest entity
918 const mesh::Geometry<T>& geometry = mesh.geometry();
919 std::span<const T> geom_dofs = geometry.x();
920 auto x_dofmap = geometry.dofmaps().front();
921
922 // Compute candidate cells for collisions (and extrapolation)
923 const graph::AdjacencyList<std::int32_t> candidate_collisions
924 = compute_collisions(bb, std::span<const T>(received_points.data(),
925 received_points.size()));
926
927 // Scratch buffer reused across points below to avoid a per-point
928 // allocation, both for the initial collision check and (further down)
929 // for the closest-cell fallback.
930 std::vector<T> coordinate_dofs(3 * x_dofmap.extent(1));
931
932 // Each process checks which points collide with a cell on the process
933 const int rank = dolfinx::MPI::rank(comm);
934 std::vector<std::int32_t> cell_indicator(received_points.size() / 3);
935 std::vector<std::int32_t> closest_cells(received_points.size() / 3);
936 for (std::size_t p = 0; p < received_points.size(); p += 3)
937 {
938 std::array<T, 3> point;
939 std::copy_n(std::next(received_points.begin(), p), 3, point.begin());
940 // Find first colliding cell among the cells with colliding bounding boxes
941 const std::int32_t colliding_cell = geometry::compute_first_colliding_cell(
942 mesh, candidate_collisions.links(p / 3), point,
943 10 * std::numeric_limits<T>::epsilon(), std::span<T>(coordinate_dofs));
944 // If a collding cell is found, store the rank of the current process
945 // which will be sent back to the owner of the point
946 cell_indicator[p / 3] = (colliding_cell >= 0) ? rank : -1;
947 // Store the cell index for lookup once the owning processes has determined
948 // the ownership of the point
949 closest_cells[p / 3] = colliding_cell;
950 }
951
952 // Create neighborhood communicator in the reverse direction: send
953 // back col to requesting processes
954 MPI_Comm reverse_comm;
955 MPI_Dist_graph_create_adjacent(
956 comm, out_ranks.size(), out_ranks.data(), MPI_UNWEIGHTED, in_ranks.size(),
957 in_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &reverse_comm);
958
959 // Reuse sizes and offsets from first communication set
960 // but divide by three
961 {
962 auto rescale = [](auto& x)
963 { std::ranges::transform(x, x.begin(), [](auto e) { return (e / 3); }); };
964 rescale(recv_sizes);
965 rescale(recv_offsets);
966 rescale(send_sizes);
967 rescale(send_offsets);
968
969 // The communication is reversed, so swap recv to send offsets
970 std::swap(recv_sizes, send_sizes);
971 std::swap(recv_offsets, send_offsets);
972 }
973
974 std::vector<std::int32_t> recv_ranks(recv_offsets.back());
975 MPI_Neighbor_alltoallv(cell_indicator.data(), send_sizes.data(),
976 send_offsets.data(), MPI_INT32_T, recv_ranks.data(),
977 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
978 reverse_comm);
979
980 std::vector<int> point_owners(points.size() / 3, -1);
981 for (std::size_t i = 0; i < unpack_map.size(); i++)
982 {
983 const std::int32_t pos = unpack_map[i];
984 // Only insert new owner if no owner has previously been found
985 if (recv_ranks[i] >= 0 && point_owners[pos] == -1)
986 point_owners[pos] = recv_ranks[i];
987 }
988
989 if (find_closest_cell)
990 {
991 // Marker for those points already sent to another process that still
992 // need a closest-cell fallback (no owner was found above)
993 std::vector<std::uint8_t> send_needs_closest_cell(recv_offsets.back());
994 for (std::int32_t i = 0; i < recv_offsets.back(); i++)
995 {
996 const std::int32_t pos = unpack_map[i];
997 send_needs_closest_cell[i] = point_owners[pos] == -1;
998 }
999
1000 // Swap communication direction, to send the marker to other processes
1001 std::swap(send_sizes, recv_sizes);
1002 std::swap(send_offsets, recv_offsets);
1003 std::vector<std::uint8_t> dest_needs_closest_cell(recv_offsets.back());
1004 MPI_Neighbor_alltoallv(send_needs_closest_cell.data(), send_sizes.data(),
1005 send_offsets.data(), MPI_UINT8_T,
1006 dest_needs_closest_cell.data(), recv_sizes.data(),
1007 recv_offsets.data(), MPI_UINT8_T, forward_comm);
1008
1009 std::vector<T> squared_distances(received_points.size() / 3, -1);
1010
1011 for (std::size_t i = 0; i < dest_needs_closest_cell.size(); i++)
1012 {
1013 if (dest_needs_closest_cell[i] == 1)
1014 {
1015 assert(closest_cells[i] == -1);
1016 std::array<T, 3> point;
1017 std::copy_n(std::next(received_points.begin(), 3 * i), 3,
1018 point.begin());
1019
1020 // Find shortest distance among cells with colliding bounding box
1021 T shortest_distance = std::numeric_limits<T>::max();
1022 std::int32_t closest_cell = -1;
1023 for (auto cell : candidate_collisions.links(i))
1024 {
1025 auto dofs = md::submdspan(x_dofmap, cell, md::full_extent);
1026 for (std::size_t j = 0; j < dofs.size(); ++j)
1027 {
1028 const int pos = 3 * dofs[j];
1029 for (std::size_t k = 0; k < 3; ++k)
1030 coordinate_dofs[3 * j + k] = geom_dofs[pos + k];
1031 }
1032 const std::array<T, 3> d = compute_distance_gjk<T>(
1033 std::span<const T>(point.data(), point.size()),
1034 std::span<const T>(coordinate_dofs.data(), 3 * dofs.size()));
1035 if (T current_distance = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
1036 current_distance < shortest_distance)
1037 {
1038 shortest_distance = current_distance;
1039 closest_cell = cell;
1040 }
1041 }
1042 closest_cells[i] = closest_cell;
1043 squared_distances[i] = shortest_distance;
1044 }
1045 }
1046
1047 std::swap(recv_sizes, send_sizes);
1048 std::swap(recv_offsets, send_offsets);
1049
1050 // Get distances from closest entity of points that were on the other
1051 // process
1052 std::vector<T> recv_distances(recv_offsets.back());
1053 MPI_Neighbor_alltoallv(
1054 squared_distances.data(), send_sizes.data(), send_offsets.data(),
1055 dolfinx::MPI::mpi_t<T>, recv_distances.data(), recv_sizes.data(),
1056 recv_offsets.data(), dolfinx::MPI::mpi_t<T>, reverse_comm);
1057
1058 // Update point ownership with extrapolation information
1059 std::vector<T> closest_distance(point_owners.size(),
1060 std::numeric_limits<T>::max());
1061 for (std::size_t i = 0; i < out_ranks.size(); i++)
1062 {
1063 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
1064 {
1065 const std::int32_t pos = unpack_map[j];
1066 T current_dist = recv_distances[j];
1067 // Update if closer than previous guess and was found (current_dist
1068 // is -1, not a valid squared distance, if no candidate cell was
1069 // found on the sending rank)
1070 if (T d = closest_distance[pos];
1071 (current_dist >= 0) and (current_dist < d))
1072 {
1073 point_owners[pos] = out_ranks[i];
1074 closest_distance[pos] = current_dist;
1075 }
1076 }
1077 }
1078 }
1079
1080 // Communication is reversed again to send dest ranks to all processes
1081 std::swap(send_sizes, recv_sizes);
1082 std::swap(send_offsets, recv_offsets);
1083
1084 // Pack ownership data
1085 std::vector<std::int32_t> send_owners(send_offsets.back());
1086 std::ranges::fill(counter, 0);
1087 for (std::size_t i = 0; i < points.size() / 3; ++i)
1088 {
1089 for (std::int32_t p : collisions.links(i))
1090 {
1091 std::int32_t neighbor = rank_to_neighbor[p];
1092 send_owners[send_offsets[neighbor] + counter[neighbor]++]
1093 = point_owners[i];
1094 }
1095 }
1096
1097 // Send ownership info
1098 std::vector<std::int32_t> dest_ranks(recv_offsets.back());
1099 MPI_Neighbor_alltoallv(send_owners.data(), send_sizes.data(),
1100 send_offsets.data(), MPI_INT32_T, dest_ranks.data(),
1101 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
1102 forward_comm);
1103
1104 // Unpack dest ranks if point owner is this rank
1105 std::vector<int> owned_recv_ranks;
1106 owned_recv_ranks.reserve(recv_offsets.back());
1107 std::vector<T> owned_recv_points;
1108 std::vector<std::int32_t> owned_recv_cells;
1109 for (std::size_t i = 0; i < in_ranks.size(); i++)
1110 {
1111 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
1112 {
1113 if (rank == dest_ranks[j])
1114 {
1115 owned_recv_ranks.push_back(in_ranks[i]);
1116 owned_recv_points.insert(
1117 owned_recv_points.end(), std::next(received_points.cbegin(), 3 * j),
1118 std::next(received_points.cbegin(), 3 * (j + 1)));
1119 owned_recv_cells.push_back(closest_cells[j]);
1120 }
1121 }
1122 }
1123
1124 MPI_Comm_free(&forward_comm);
1125 MPI_Comm_free(&reverse_comm);
1126 return PointOwnershipData<T>{.src_owner = std::move(point_owners),
1127 .dest_owners = std::move(owned_recv_ranks),
1128 .dest_points = std::move(owned_recv_points),
1129 .dest_cells = std::move(owned_recv_cells)};
1130}
1131
1132} // namespace dolfinx::geometry
Definition CoordinateElement.h:39
Definition ElementDofLayout.h:31
const std::vector< int > & entity_closure_dofs(int dim, int entity_index) const
Definition ElementDofLayout.cpp:65
Definition BoundingBoxTree.h:217
BoundingBoxTree create_global_tree(MPI_Comm comm) const
Definition BoundingBoxTree.h:354
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
This class provides a static adjacency list data structure.
Definition AdjacencyList.h:41
const std::vector< LinkData > & array() const
Return contiguous array of links for all nodes (const version).
Definition AdjacencyList.h:188
std::span< LinkData > links(std::size_t node)
Get the links (edges) for given node.
Definition AdjacencyList.h:169
std::int32_t num_nodes() const
Get the number of nodes.
Definition AdjacencyList.h:154
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:39
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
std::vector< int > compute_graph_edges_nbx(MPI_Comm comm, std::span< const int > edges, int tag=static_cast< int >(tag::consensus_nbx))
Determine incoming graph edges using the NBX consensus algorithm.
Definition MPI.cpp:294
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:26
std::vector< T > squared_distance(const mesh::Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities, std::span< const T > points)
Compute the squared distance between a point and a mesh entity.
Definition utils.h:179
std::vector< std::int32_t > compute_collisions(const BoundingBoxTree< T > &tree0, const BoundingBoxTree< T > &tree1)
Compute all collisions between two bounding box trees.
Definition utils.h:549
T compute_squared_distance_bbox(std::span< const T, 6 > b, std::span< const T, 3 > x)
Compute squared distance between point and bounding box.
Definition utils.h:142
graph::AdjacencyList< std::int32_t > compute_colliding_cells(const mesh::Mesh< T > &mesh, const graph::AdjacencyList< std::int32_t > &candidate_cells, std::span< const T > points)
Compute which cells collide with a point.
Definition utils.h:748
std::vector< T > shortest_vector(const mesh::Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities, std::span< const T > points)
Compute the shortest vector from a mesh entity to a point.
Definition utils.h:58
BoundingBoxTree< T > create_midpoint_tree(const mesh::Mesh< T > &mesh, int tdim, std::span< const std::int32_t > entities)
Create a bounding box tree for the midpoints of a subset of entities.
Definition utils.h:522
PointOwnershipData< T > determine_point_ownership(const mesh::Mesh< T > &mesh, std::span< const T > points, T padding, std::optional< std::span< const std::int32_t > > cells, bool find_closest_cell=true)
Determine, for a set of points, the owning process of the cell (if any) that contains each point.
Definition utils.h:820
std::vector< std::int32_t > compute_closest_entity(const BoundingBoxTree< T > &tree, const BoundingBoxTree< T > &midpoint_tree, const mesh::Mesh< T > &mesh, std::span< const T > points)
Compute closest mesh entity to a point.
Definition utils.h:676
std::array< T, 3 > compute_distance_gjk(std::span< const T > p0, std::span< const T > q0)
Compute the distance between two convex bodies p0 and q0, each defined by a set of points.
Definition gjk.h:372
std::int32_t compute_first_colliding_cell(const mesh::Mesh< T > &mesh, std::span< const std::int32_t > cells, std::array< T, 3 > point, T tol, std::span< T > coordinate_dofs)
Given a set of cells, find the first one that collides with a point.
Definition utils.h:628
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
std::vector< T > compute_midpoints(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities)
Compute the midpoints for mesh entities of a given dimension.
Definition utils.h:475
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24
Information on the ownership of points distributed across processes.
Definition utils.h:36
std::vector< T > dest_points
Points that are owned by current process.
Definition utils.h:41
std::vector< std::int32_t > dest_cells
Definition utils.h:43
std::vector< int > dest_owners
Ranks that sent dest_points to current process.
Definition utils.h:40
std::vector< int > src_owner
Definition utils.h:37
std::vector< T > nodes
Definition utils.h:248
const fem::ElementDofLayout & cmap_dof_layout
Coordinate-element dof layout.
Definition utils.h:253