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 <deque>
17#include <dolfinx/graph/AdjacencyList.h>
18#include <dolfinx/mesh/Mesh.h>
19#include <limits>
20#include <map>
21#include <numeric>
22#include <optional>
23#include <span>
24#include <utility>
25#include <vector>
26
27namespace dolfinx::geometry
28{
32template <std::floating_point T>
34{
35 std::vector<int> src_owner;
37 std::vector<int>
39 std::vector<T> dest_points;
40 std::vector<std::int32_t>
43};
44
55template <std::floating_point T>
56std::vector<T> shortest_vector(const mesh::Mesh<T>& mesh, int dim,
57 std::span<const std::int32_t> entities,
58 std::span<const T> points)
59{
60 const int tdim = mesh.topology()->dim();
61 const mesh::Geometry<T>& geometry = mesh.geometry();
62
63 std::span<const T> geom_dofs = geometry.x();
64 auto x_dofmap = geometry.dofmaps().front();
65 std::vector<T> shortest_vectors;
66 shortest_vectors.reserve(3 * entities.size());
67 if (dim == tdim)
68 {
69 for (std::size_t e = 0; e < entities.size(); e++)
70 {
71 // Check that we have sent in valid entities, i.e. that they exist in the
72 // local dofmap. One gets a cryptical memory segfault if entities is -1
73 assert(entities[e] >= 0);
74 auto dofs = md::submdspan(x_dofmap, entities[e], md::full_extent);
75 std::vector<T> nodes(3 * dofs.size());
76 for (std::size_t i = 0; i < dofs.size(); ++i)
77 {
78 const std::int32_t pos = 3 * dofs[i];
79 for (std::size_t j = 0; j < 3; ++j)
80 nodes[3 * i + j] = geom_dofs[pos + j];
81 }
82
83 std::array<T, 3> d
84 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
85 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
86 }
87 }
88 else
89 {
90 mesh.topology_mutable()->create_connectivity(dim, tdim);
91 mesh.topology_mutable()->create_connectivity(tdim, dim);
92 auto e_to_c = mesh.topology()->connectivity(dim, tdim);
93 assert(e_to_c);
94 auto c_to_e = mesh.topology_mutable()->connectivity(tdim, dim);
95 assert(c_to_e);
96 for (std::size_t e = 0; e < entities.size(); e++)
97 {
98 const std::int32_t index = entities[e];
99
100 // Find attached cell
101 assert(e_to_c->num_links(index) > 0);
102 const std::int32_t c = e_to_c->links(index)[0];
103
104 // Find local number of entity wrt cell
105 auto cell_entities = c_to_e->links(c);
106 auto it0 = std::find(cell_entities.begin(), cell_entities.end(), index);
107 assert(it0 != cell_entities.end());
108 const int local_cell_entity
109 = std::ranges::distance(cell_entities.begin(), it0);
110
111 // Tabulate geometry dofs for the entity
112 auto dofs = md::submdspan(x_dofmap, c, md::full_extent);
113 const std::vector<int> entity_dofs
114 = geometry.cmaps().front().create_dof_layout().entity_closure_dofs(
115 dim, local_cell_entity);
116 std::vector<T> nodes(3 * entity_dofs.size());
117 for (std::size_t i = 0; i < entity_dofs.size(); i++)
118 {
119 const std::int32_t pos = 3 * dofs[entity_dofs[i]];
120 for (std::size_t j = 0; j < 3; ++j)
121 nodes[3 * i + j] = geom_dofs[pos + j];
122 }
123
124 std::array<T, 3> d
125 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
126 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
127 }
128 }
129
130 return shortest_vectors;
131}
132
139template <std::floating_point T>
140T compute_squared_distance_bbox(std::span<const T, 6> b,
141 std::span<const T, 3> x)
142{
143 auto b0 = b.template subspan<0, 3>();
144 auto b1 = b.template subspan<3, 3>();
145 return std::transform_reduce(x.begin(), x.end(), b0.begin(), 0.0,
146 std::plus<>{},
147 [](auto x, auto b)
148 {
149 auto dx = x - b;
150 return dx > 0 ? 0 : dx * dx;
151 })
152 + std::transform_reduce(x.begin(), x.end(), b1.begin(), 0.0,
153 std::plus<>{},
154 [](auto x, auto b)
155 {
156 auto dx = x - b;
157 return dx < 0 ? 0 : dx * dx;
158 });
159}
160
176template <std::floating_point T>
177std::vector<T> squared_distance(const mesh::Mesh<T>& mesh, int dim,
178 std::span<const std::int32_t> entities,
179 std::span<const T> points)
180{
181 std::vector<T> v = shortest_vector(mesh, dim, entities, points);
182 std::vector<T> d(v.size() / 3, 0);
183 for (std::size_t i = 0; i < d.size(); ++i)
184 for (std::size_t j = 0; j < 3; ++j)
185 d[i] += v[3 * i + j] * v[3 * i + j];
186 return d;
187}
188
189namespace impl
190{
192constexpr bool is_leaf(std::array<int, 2> bbox)
193{
194 // Leaf nodes are marked by setting child_0 equal to child_1
195 return bbox[0] == bbox[1];
196}
197
202template <std::floating_point T>
203constexpr bool point_in_bbox(std::span<const T, 6> b, std::span<const T, 3> x)
204{
205 constexpr T rtol = 1e-14;
206 bool in = true;
207 for (std::size_t i = 0; i < 3; i++)
208 {
209 T eps = rtol * (b[i + 3] - b[i]);
210 in &= (x[i] >= (b[i] - eps)) && (x[i] <= (b[i + 3] + eps));
211 if (!in)
212 break;
213 }
214
215 return in;
216}
217
221template <std::floating_point T>
222constexpr bool bbox_in_bbox(std::span<const T, 6> a, std::span<const T, 6> b)
223{
224 constexpr T rtol = 1e-14;
225 auto a0 = a.template subspan<0, 3>();
226 auto a1 = a.template subspan<3, 3>();
227 auto b0 = b.template subspan<0, 3>();
228 auto b1 = b.template subspan<3, 3>();
229
230 bool in = true;
231 for (std::size_t i = 0; i < 3; i++)
232 {
233 T eps = rtol * (b1[i] - b0[i]);
234 in &= a1[i] >= (b0[i] - eps);
235 in &= a0[i] <= (b1[i] + eps);
236 }
237
238 return in;
239}
240
242template <std::floating_point T>
243std::pair<std::int32_t, T>
244_compute_closest_entity(const geometry::BoundingBoxTree<T>& tree,
245 std::span<const T, 3> point, std::int32_t node,
246 const mesh::Mesh<T>& mesh, std::int32_t closest_entity,
247 T R2)
248{
249 // Get children of current bounding box node (child_1 denotes entity
250 // index for leaves)
251 const std::array<int, 2> bbox = tree.bbox(node);
252 T r2;
253 if (is_leaf(bbox))
254 {
255 // If point cloud tree the exact distance is easy to compute
256 if (tree.tdim() == 0)
257 {
258 std::array<T, 6> diff = tree.get_bbox(node);
259 for (std::size_t k = 0; k < 3; ++k)
260 diff[k] -= point[k];
261 r2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
262 }
263 else
264 {
265 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
266
267 // If bounding box closer than previous closest entity, use gjk to
268 // obtain exact distance to the convex hull of the entity
269 if (r2 <= R2)
270 {
271 r2 = squared_distance<T>(mesh, tree.tdim(),
272 std::span(std::next(bbox.begin(), 1), 1),
273 point)
274 .front();
275 }
276 }
277
278 // If entity is closer than best result so far, return it
279 if (r2 <= R2)
280 {
281 closest_entity = bbox.back();
282 R2 = r2;
283 }
284
285 return {closest_entity, R2};
286 }
287 else
288 {
289 // If bounding box is outside radius, then don't search further
290 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
291 if (r2 > R2)
292 return {closest_entity, R2};
293
294 // Check both children. We use R2 (as opposed to r2), as a bounding
295 // box can be closer than the actual entity.
296 std::pair<int, T> p0 = _compute_closest_entity(tree, point, bbox.front(),
297 mesh, closest_entity, R2);
298 std::pair<int, T> p1 = _compute_closest_entity(tree, point, bbox.back(),
299 mesh, p0.first, p0.second);
300 return p1;
301 }
302}
303
309template <std::floating_point T>
310void _compute_collisions_point(const geometry::BoundingBoxTree<T>& tree,
311 std::span<const T, 3> p,
312 std::vector<std::int32_t>& entities)
313{
314 std::deque<std::int32_t> stack;
315 std::int32_t next = tree.num_bboxes() - 1;
316 std::span<const T> coords = tree.bbox_coordinates();
317 auto view_bbox = [&coords](std::size_t node)
318 { return std::span<const T, 6>(coords.data() + 6 * node, 6); };
319 while (next != -1)
320 {
321 if (std::array bbox = tree.bbox(next);
322 is_leaf(bbox) and point_in_bbox(view_bbox(next), p))
323 {
324 // If box is a leaf node then add it to the list of colliding
325 // entities
326 entities.push_back(bbox[1]);
327 next = -1;
328 }
329 else
330 {
331 // Check whether the point collides with child nodes (left and
332 // right)
333 bool left = point_in_bbox(view_bbox(bbox[0]), p);
334 bool right = point_in_bbox(view_bbox(bbox[1]), p);
335 if (left and right)
336 {
337 // If the point collides with both child nodes, add the right
338 // node to the stack (for later visiting) and continue the tree
339 // traversal with the left subtree
340 stack.push_back(bbox[1]);
341 next = bbox[0];
342 }
343 else if (left)
344 {
345 // Traverse the current node's left subtree
346 next = bbox[0];
347 }
348 else if (right)
349 {
350 // Traverse the current node's right subtree
351 next = bbox[1];
352 }
353 else
354 next = -1;
355 }
356
357 // If tree traversal reaches a dead end (box is a leaf node or no
358 // collision detected), check the stack for deferred subtrees
359 if (next == -1 and !stack.empty())
360 {
361 next = stack.back();
362 stack.pop_back();
363 }
364 }
365}
366
367// Compute collisions with tree (recursive)
368template <std::floating_point T>
369void _compute_collisions_tree(const geometry::BoundingBoxTree<T>& A,
370 const geometry::BoundingBoxTree<T>& B,
371 std::int32_t node_A, std::int32_t node_B,
372 std::vector<std::int32_t>& entities)
373{
374 // If bounding boxes don't collide, then don't search further
375 if (!bbox_in_bbox<T>(A.get_bbox(node_A), B.get_bbox(node_B)))
376 return;
377
378 // Get bounding boxes for current nodes
379 const std::array<std::int32_t, 2> bbox_A = A.bbox(node_A);
380 const std::array<std::int32_t, 2> bbox_B = B.bbox(node_B);
381
382 // Check whether we've reached a leaf in A or B
383 const bool is_leaf_A = is_leaf(bbox_A);
384 const bool is_leaf_B = is_leaf(bbox_B);
385 if (is_leaf_A and is_leaf_B)
386 {
387 // If both boxes are leaves (which we know collide), then add them
388 // child_1 denotes entity for leaves
389 entities.push_back(bbox_A[1]);
390 entities.push_back(bbox_B[1]);
391 }
392 else if (is_leaf_A)
393 {
394 // If we reached the leaf in A, then descend B
395 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
396 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
397 }
398 else if (is_leaf_B)
399 {
400 // If we reached the leaf in B, then descend A
401 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
402 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
403 }
404 else if (node_A > node_B)
405 {
406 // At this point, we know neither is a leaf so descend the largest
407 // tree first. Note that nodes are added in reverse order with the
408 // top bounding box at the end so the largest tree (the one with the
409 // the most boxes left to traverse) has the largest node number.
410 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
411 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
412 }
413 else
414 {
415 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
416 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
417 }
418
419 // Note that cases above can be collected in fewer cases but this way
420 // the logic is easier to follow.
421}
422
423} // namespace impl
424
431template <std::floating_point T>
433 std::span<const std::int32_t> entities)
434{
435 spdlog::info("Building point search tree to accelerate distance queries for "
436 "a given topological dimension and subset of entities.");
437
438 const std::vector<T> midpoints
439 = mesh::compute_midpoints(mesh, tdim, entities);
440 std::vector<std::pair<std::array<T, 3>, std::int32_t>> points(
441 entities.size());
442 for (std::size_t i = 0; i < points.size(); ++i)
443 {
444 for (std::size_t j = 0; j < 3; ++j)
445 points[i].first[j] = midpoints[3 * i + j];
446 points[i].second = entities[i];
447 }
448
449 // Build tree
450 return BoundingBoxTree(points);
451}
452
458template <std::floating_point T>
459std::vector<std::int32_t> compute_collisions(const BoundingBoxTree<T>& tree0,
460 const BoundingBoxTree<T>& tree1)
461{
462 // Call recursive find function
463 std::vector<std::int32_t> entities;
464 if (tree0.num_bboxes() > 0 and tree1.num_bboxes() > 0)
465 {
466 impl::_compute_collisions_tree(tree0, tree1, tree0.num_bboxes() - 1,
467 tree1.num_bboxes() - 1, entities);
468 }
469
470 return entities;
471}
472
483template <std::floating_point T>
485compute_collisions(const BoundingBoxTree<T>& tree, std::span<const T> points)
486{
487 if (tree.num_bboxes() > 0)
488 {
489 std::vector<std::int32_t> entities, offsets(points.size() / 3 + 1, 0);
490 entities.reserve(points.size() / 3);
491 for (std::size_t p = 0; p < points.size() / 3; ++p)
492 {
493 impl::_compute_collisions_point(
494 tree, std::span<const T, 3>(points.data() + 3 * p, 3), entities);
495 offsets[p + 1] = entities.size();
496 }
497
498 return graph::AdjacencyList(std::move(entities), std::move(offsets));
499 }
500 else
501 {
503 std::vector<std::int32_t>(),
504 std::vector<std::int32_t>(points.size() / 3 + 1, 0));
505 }
506}
507
533template <std::floating_point T>
535 std::span<const std::int32_t> cells,
536 std::array<T, 3> point, T tol,
537 std::span<T> coordinate_dofs)
538{
539 if (cells.empty())
540 return -1;
541
542 const mesh::Geometry<T>& geometry = mesh.geometry();
543 std::span<const T> geom_dofs = geometry.x();
544 auto x_dofmap = geometry.dofmaps().front();
545 const std::size_t num_nodes = x_dofmap.extent(1);
546 assert(coordinate_dofs.size() >= num_nodes * 3);
547 std::span<T> cell_dofs = coordinate_dofs.first(num_nodes * 3);
548 for (auto cell : cells)
549 {
550 auto dofs = md::submdspan(x_dofmap, cell, md::full_extent);
551 for (std::size_t i = 0; i < num_nodes; ++i)
552 {
553 std::copy_n(std::next(geom_dofs.begin(), 3 * dofs[i]), 3,
554 std::next(cell_dofs.begin(), 3 * i));
555 }
556
557 std::array<T, 3> shortest_vector
558 = compute_distance_gjk<T>(point, cell_dofs);
559 T d2 = std::reduce(shortest_vector.begin(), shortest_vector.end(), T(0),
560 [](auto d, auto e) { return d + e * e; });
561 if (d2 < tol)
562 return cell;
563 }
564
565 return -1;
566}
567
580template <std::floating_point T>
581std::vector<std::int32_t>
583 const BoundingBoxTree<T>& midpoint_tree,
584 const mesh::Mesh<T>& mesh, std::span<const T> points)
585{
586 if (tree.num_bboxes() == 0)
587 return std::vector<std::int32_t>(points.size() / 3, -1);
588
589 std::vector<std::int32_t> entities;
590 entities.reserve(points.size() / 3);
591 for (std::size_t i = 0; i < points.size() / 3; ++i)
592 {
593 // Use midpoint tree to find initial closest entity to the point.
594 // Start by using a leaf node as the initial guess for the input
595 // entity
596 std::array<int, 2> leaf0 = midpoint_tree.bbox(0);
597 assert(impl::is_leaf(leaf0));
598 std::array<T, 6> diff = midpoint_tree.get_bbox(0);
599 for (std::size_t k = 0; k < 3; ++k)
600 diff[k] -= points[3 * i + k];
601 T R2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
602
603 // Use a recursive search through the bounding box tree
604 // to find determine the entity with the closest midpoint.
605 // As the midpoint tree only consist of points, the distance
606 // queries are lightweight.
607 const auto [m_index, m_distance2] = impl::_compute_closest_entity(
608 midpoint_tree, std::span<const T, 3>(points.data() + 3 * i, 3),
609 midpoint_tree.num_bboxes() - 1, mesh, leaf0[0], R2);
610
611 // Use a recursives search through the bounding box tree to
612 // determine which entity is actually closest.
613 // Uses the entity with the closest midpoint as initial guess, and
614 // the distance from the midpoint to the point of interest as the
615 // initial search radius.
616 const auto [index, distance2] = impl::_compute_closest_entity(
617 tree, std::span<const T, 3>(points.data() + 3 * i, 3),
618 tree.num_bboxes() - 1, mesh, m_index, m_distance2);
619
620 entities.push_back(index);
621 }
622
623 return entities;
624}
625
641template <std::floating_point T>
643 const mesh::Mesh<T>& mesh,
644 const graph::AdjacencyList<std::int32_t>& candidate_cells,
645 std::span<const T> points)
646{
647 std::vector<std::int32_t> offsets = {0};
648 offsets.reserve(candidate_cells.num_nodes() + 1);
649 std::vector<std::int32_t> colliding_cells;
650 constexpr T eps2 = 1e-12;
651 const int tdim = mesh.topology()->dim();
652 for (std::int32_t i = 0; i < candidate_cells.num_nodes(); i++)
653 {
654 auto cells = candidate_cells.links(i);
655 std::vector<T> _point(3 * cells.size());
656 for (std::size_t j = 0; j < cells.size(); ++j)
657 for (std::size_t k = 0; k < 3; ++k)
658 _point[3 * j + k] = points[3 * i + k];
659
660 std::vector distances_sq = squared_distance<T>(mesh, tdim, cells, _point);
661 for (std::size_t j = 0; j < cells.size(); j++)
662 if (distances_sq[j] < eps2)
663 colliding_cells.push_back(cells[j]);
664
665 offsets.push_back(colliding_cells.size());
666 }
667
668 return graph::AdjacencyList(std::move(colliding_cells), std::move(offsets));
669}
670
712template <std::floating_point T>
713PointOwnershipData<T>
714determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points,
715 T padding,
716 std::optional<std::span<const std::int32_t>> cells,
717 bool find_closest_cell = true)
718{
719 MPI_Comm comm = mesh.comm();
720
721 const int tdim = mesh.topology()->dim();
722
723 std::vector<std::int32_t> local_cells;
724 if (not(cells.has_value()))
725 {
726 auto cell_map = mesh.topology()->index_map(tdim);
727 local_cells.resize(cell_map->size_local());
728 std::iota(local_cells.begin(), local_cells.end(), 0);
729 cells
730 = std::span<const std::int32_t>(local_cells.data(), local_cells.size());
731 }
732 // Create a global bounding-box tree to find candidate processes with
733 // cells that could collide with the points
734 BoundingBoxTree bb(mesh, tdim, padding, cells.value());
735 BoundingBoxTree global_bbtree = bb.create_global_tree(comm);
736
737 // Compute collisions:
738 // For each point in `points` get the processes it should be sent to
739 graph::AdjacencyList collisions = compute_collisions(global_bbtree, points);
740
741 // Get unique list of outgoing ranks
742 std::vector<std::int32_t> out_ranks = collisions.array();
743 std::ranges::sort(out_ranks);
744 auto [unique_end, range_end] = std::ranges::unique(out_ranks);
745 out_ranks.erase(unique_end, range_end);
746
747 // Compute incoming edges (source processes)
748 std::vector in_ranks = dolfinx::MPI::compute_graph_edges_nbx(comm, out_ranks);
749 std::ranges::sort(in_ranks);
750
751 // Create neighborhood communicator in forward direction
752 MPI_Comm forward_comm;
753 MPI_Dist_graph_create_adjacent(
754 comm, in_ranks.size(), in_ranks.data(), MPI_UNWEIGHTED, out_ranks.size(),
755 out_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &forward_comm);
756
757 // Compute map from global mpi rank to neighbor rank, "collisions"
758 // uses global rank
759 std::map<std::int32_t, std::int32_t> rank_to_neighbor;
760 for (std::size_t i = 0; i < out_ranks.size(); i++)
761 rank_to_neighbor[out_ranks[i]] = i;
762
763 // Count the number of points to send per neighbor process
764 std::vector<std::int32_t> send_sizes(out_ranks.size());
765 for (std::size_t i = 0; i < points.size() / 3; ++i)
766 for (std::int32_t p : collisions.links(i))
767 send_sizes[rank_to_neighbor[p]] += 3;
768
769 // Compute receive sizes
770 std::vector<std::int32_t> recv_sizes(in_ranks.size());
771 send_sizes.reserve(1);
772 recv_sizes.reserve(1);
773 MPI_Request sizes_request;
774 MPI_Ineighbor_alltoall(send_sizes.data(), 1, MPI_INT, recv_sizes.data(), 1,
775 MPI_INT, forward_comm, &sizes_request);
776
777 // Compute sending offsets
778 std::vector<std::int32_t> send_offsets(send_sizes.size() + 1, 0);
779 std::partial_sum(send_sizes.begin(), send_sizes.end(),
780 std::next(send_offsets.begin(), 1));
781
782 // Pack data to send and store unpack map
783 std::vector<T> send_data(send_offsets.back());
784 std::vector<std::int32_t> counter(send_sizes.size(), 0);
785 // unpack map: [index in adj list][pos in x]
786 std::vector<std::int32_t> unpack_map(send_offsets.back() / 3);
787 for (std::size_t i = 0; i < points.size(); i += 3)
788 {
789 for (std::int32_t p : collisions.links(i / 3))
790 {
791 int neighbor = rank_to_neighbor[p];
792 int pos = send_offsets[neighbor] + counter[neighbor];
793 auto it = std::next(send_data.begin(), pos);
794 std::copy_n(std::next(points.begin(), i), 3, it);
795 unpack_map[pos / 3] = i / 3;
796 counter[neighbor] += 3;
797 }
798 }
799
800 MPI_Wait(&sizes_request, MPI_STATUS_IGNORE);
801 std::vector<std::int32_t> recv_offsets(in_ranks.size() + 1, 0);
802 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
803 std::next(recv_offsets.begin(), 1));
804
805 std::vector<T> received_points((std::size_t)recv_offsets.back());
806 MPI_Neighbor_alltoallv(
807 send_data.data(), send_sizes.data(), send_offsets.data(),
808 dolfinx::MPI::mpi_t<T>, received_points.data(), recv_sizes.data(),
809 recv_offsets.data(), dolfinx::MPI::mpi_t<T>, forward_comm);
810
811 // Get mesh geometry for closest entity
812 const mesh::Geometry<T>& geometry = mesh.geometry();
813 std::span<const T> geom_dofs = geometry.x();
814 auto x_dofmap = geometry.dofmaps().front();
815
816 // Compute candidate cells for collisions (and extrapolation)
817 const graph::AdjacencyList<std::int32_t> candidate_collisions
818 = compute_collisions(bb, std::span<const T>(received_points.data(),
819 received_points.size()));
820
821 // Scratch buffer reused across points below to avoid a per-point
822 // allocation, both for the initial collision check and (further down)
823 // for the closest-cell fallback.
824 std::vector<T> coordinate_dofs(3 * x_dofmap.extent(1));
825
826 // Each process checks which points collide with a cell on the process
827 const int rank = dolfinx::MPI::rank(comm);
828 std::vector<std::int32_t> cell_indicator(received_points.size() / 3);
829 std::vector<std::int32_t> closest_cells(received_points.size() / 3);
830 for (std::size_t p = 0; p < received_points.size(); p += 3)
831 {
832 std::array<T, 3> point;
833 std::copy_n(std::next(received_points.begin(), p), 3, point.begin());
834 // Find first colliding cell among the cells with colliding bounding boxes
835 const int colliding_cell = geometry::compute_first_colliding_cell(
836 mesh, candidate_collisions.links(p / 3), point,
837 10 * std::numeric_limits<T>::epsilon(), std::span<T>(coordinate_dofs));
838 // If a collding cell is found, store the rank of the current process
839 // which will be sent back to the owner of the point
840 cell_indicator[p / 3] = (colliding_cell >= 0) ? rank : -1;
841 // Store the cell index for lookup once the owning processes has determined
842 // the ownership of the point
843 closest_cells[p / 3] = colliding_cell;
844 }
845
846 // Create neighborhood communicator in the reverse direction: send
847 // back col to requesting processes
848 MPI_Comm reverse_comm;
849 MPI_Dist_graph_create_adjacent(
850 comm, out_ranks.size(), out_ranks.data(), MPI_UNWEIGHTED, in_ranks.size(),
851 in_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &reverse_comm);
852
853 // Reuse sizes and offsets from first communication set
854 // but divide by three
855 {
856 auto rescale = [](auto& x)
857 { std::ranges::transform(x, x.begin(), [](auto e) { return (e / 3); }); };
858 rescale(recv_sizes);
859 rescale(recv_offsets);
860 rescale(send_sizes);
861 rescale(send_offsets);
862
863 // The communication is reversed, so swap recv to send offsets
864 std::swap(recv_sizes, send_sizes);
865 std::swap(recv_offsets, send_offsets);
866 }
867
868 std::vector<std::int32_t> recv_ranks(recv_offsets.back());
869 MPI_Neighbor_alltoallv(cell_indicator.data(), send_sizes.data(),
870 send_offsets.data(), MPI_INT32_T, recv_ranks.data(),
871 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
872 reverse_comm);
873
874 std::vector<int> point_owners(points.size() / 3, -1);
875 for (std::size_t i = 0; i < unpack_map.size(); i++)
876 {
877 const std::int32_t pos = unpack_map[i];
878 // Only insert new owner if no owner has previously been found
879 if (recv_ranks[i] >= 0 && point_owners[pos] == -1)
880 point_owners[pos] = recv_ranks[i];
881 }
882
883 if (find_closest_cell)
884 {
885 // Marker for those points already sent to another process that still
886 // need a closest-cell fallback (no owner was found above)
887 std::vector<std::uint8_t> send_needs_closest_cell(recv_offsets.back());
888 for (std::int32_t i = 0; i < recv_offsets.back(); i++)
889 {
890 const std::int32_t pos = unpack_map[i];
891 send_needs_closest_cell[i] = point_owners[pos] == -1;
892 }
893
894 // Swap communication direction, to send the marker to other processes
895 std::swap(send_sizes, recv_sizes);
896 std::swap(send_offsets, recv_offsets);
897 std::vector<std::uint8_t> dest_needs_closest_cell(recv_offsets.back());
898 MPI_Neighbor_alltoallv(send_needs_closest_cell.data(), send_sizes.data(),
899 send_offsets.data(), MPI_UINT8_T,
900 dest_needs_closest_cell.data(), recv_sizes.data(),
901 recv_offsets.data(), MPI_UINT8_T, forward_comm);
902
903 std::vector<T> squared_distances(received_points.size() / 3, -1);
904
905 for (std::size_t i = 0; i < dest_needs_closest_cell.size(); i++)
906 {
907 if (dest_needs_closest_cell[i] == 1)
908 {
909 assert(closest_cells[i] == -1);
910 std::array<T, 3> point;
911 std::copy_n(std::next(received_points.begin(), 3 * i), 3,
912 point.begin());
913
914 // Find shortest distance among cells with colliding bounding box
915 T shortest_distance = std::numeric_limits<T>::max();
916 std::int32_t closest_cell = -1;
917 for (auto cell : candidate_collisions.links(i))
918 {
919 auto dofs = md::submdspan(x_dofmap, cell, md::full_extent);
920 for (std::size_t j = 0; j < dofs.size(); ++j)
921 {
922 const int pos = 3 * dofs[j];
923 for (std::size_t k = 0; k < 3; ++k)
924 coordinate_dofs[3 * j + k] = geom_dofs[pos + k];
925 }
926 const std::array<T, 3> d = compute_distance_gjk<T>(
927 std::span<const T>(point.data(), point.size()),
928 std::span<const T>(coordinate_dofs.data(), 3 * dofs.size()));
929 if (T current_distance = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
930 current_distance < shortest_distance)
931 {
932 shortest_distance = current_distance;
933 closest_cell = cell;
934 }
935 }
936 closest_cells[i] = closest_cell;
937 squared_distances[i] = shortest_distance;
938 }
939 }
940
941 std::swap(recv_sizes, send_sizes);
942 std::swap(recv_offsets, send_offsets);
943
944 // Get distances from closest entity of points that were on the other
945 // process
946 std::vector<T> recv_distances(recv_offsets.back());
947 MPI_Neighbor_alltoallv(
948 squared_distances.data(), send_sizes.data(), send_offsets.data(),
949 dolfinx::MPI::mpi_t<T>, recv_distances.data(), recv_sizes.data(),
950 recv_offsets.data(), dolfinx::MPI::mpi_t<T>, reverse_comm);
951
952 // Update point ownership with extrapolation information
953 std::vector<T> closest_distance(point_owners.size(),
954 std::numeric_limits<T>::max());
955 for (std::size_t i = 0; i < out_ranks.size(); i++)
956 {
957 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
958 {
959 const std::int32_t pos = unpack_map[j];
960 T current_dist = recv_distances[j];
961 // Update if closer than previous guess and was found (current_dist
962 // is -1, not a valid squared distance, if no candidate cell was
963 // found on the sending rank)
964 if (T d = closest_distance[pos];
965 (current_dist >= 0) and (current_dist < d))
966 {
967 point_owners[pos] = out_ranks[i];
968 closest_distance[pos] = current_dist;
969 }
970 }
971 }
972 }
973
974 // Communication is reversed again to send dest ranks to all processes
975 std::swap(send_sizes, recv_sizes);
976 std::swap(send_offsets, recv_offsets);
977
978 // Pack ownership data
979 std::vector<std::int32_t> send_owners(send_offsets.back());
980 std::ranges::fill(counter, 0);
981 for (std::size_t i = 0; i < points.size() / 3; ++i)
982 {
983 for (std::int32_t p : collisions.links(i))
984 {
985 int neighbor = rank_to_neighbor[p];
986 send_owners[send_offsets[neighbor] + counter[neighbor]++]
987 = point_owners[i];
988 }
989 }
990
991 // Send ownership info
992 std::vector<std::int32_t> dest_ranks(recv_offsets.back());
993 MPI_Neighbor_alltoallv(send_owners.data(), send_sizes.data(),
994 send_offsets.data(), MPI_INT32_T, dest_ranks.data(),
995 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
996 forward_comm);
997
998 // Unpack dest ranks if point owner is this rank
999 std::vector<int> owned_recv_ranks;
1000 owned_recv_ranks.reserve(recv_offsets.back());
1001 std::vector<T> owned_recv_points;
1002 std::vector<std::int32_t> owned_recv_cells;
1003 for (std::size_t i = 0; i < in_ranks.size(); i++)
1004 {
1005 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
1006 {
1007 if (rank == dest_ranks[j])
1008 {
1009 owned_recv_ranks.push_back(in_ranks[i]);
1010 owned_recv_points.insert(
1011 owned_recv_points.end(), std::next(received_points.cbegin(), 3 * j),
1012 std::next(received_points.cbegin(), 3 * (j + 1)));
1013 owned_recv_cells.push_back(closest_cells[j]);
1014 }
1015 }
1016 }
1017
1018 MPI_Comm_free(&forward_comm);
1019 MPI_Comm_free(&reverse_comm);
1020 return PointOwnershipData<T>{.src_owner = std::move(point_owners),
1021 .dest_owners = std::move(owned_recv_ranks),
1022 .dest_points = std::move(owned_recv_points),
1023 .dest_cells = std::move(owned_recv_cells)};
1024}
1025
1026} // namespace dolfinx::geometry
Definition BoundingBoxTree.h:208
BoundingBoxTree create_global_tree(MPI_Comm comm) const
Definition BoundingBoxTree.h:337
std::int32_t num_bboxes() const
Return number of bounding boxes.
Definition BoundingBoxTree.h:373
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:325
std::array< std::int32_t, 2 > bbox(std::size_t node) const
Definition BoundingBoxTree.h:407
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:37
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:23
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:320
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:291
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:24
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:177
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:459
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:140
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:642
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:56
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:432
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:714
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:582
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:369
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:534
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:454
Information on the ownership of points distributed across processes.
Definition utils.h:34
std::vector< T > dest_points
Points that are owned by current process.
Definition utils.h:39
std::vector< std::int32_t > dest_cells
Definition utils.h:41
std::vector< int > dest_owners
Ranks that sent dest_points to current process.
Definition utils.h:38
std::vector< int > src_owner
Definition utils.h:35