Note: this is documentation for an old release. View the latest documentation at docs.fenicsproject.org/dolfinx/v0.9.0/cpp/doxygen/dd/dd0/geometry_2utils_8h_source.html
DOLFINx 0.7.3
DOLFINx C++ interface
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 <array>
12#include <concepts>
13#include <cstdint>
14#include <deque>
15#include <dolfinx/graph/AdjacencyList.h>
16#include <dolfinx/mesh/Mesh.h>
17#include <map>
18#include <numeric>
19#include <span>
20#include <vector>
21
22namespace dolfinx::geometry
23{
24
35template <std::floating_point T>
36std::vector<T> shortest_vector(const mesh::Mesh<T>& mesh, int dim,
37 std::span<const std::int32_t> entities,
38 std::span<const T> points)
39{
40 const int tdim = mesh.topology()->dim();
41 const mesh::Geometry<T>& geometry = mesh.geometry();
42 if (geometry.cmaps().size() > 1)
43 throw std::runtime_error("Mixed topology not supported");
44
45 std::span<const T> geom_dofs = geometry.x();
46 auto x_dofmap = geometry.dofmap();
47 std::vector<T> shortest_vectors;
48 shortest_vectors.reserve(3 * entities.size());
49 if (dim == tdim)
50 {
51 for (std::size_t e = 0; e < entities.size(); e++)
52 {
53 // Check that we have sent in valid entities, i.e. that they exist in the
54 // local dofmap. One gets a cryptical memory segfault if entities is -1
55 assert(entities[e] >= 0);
56 auto dofs
57 = MDSPAN_IMPL_STANDARD_NAMESPACE::MDSPAN_IMPL_PROPOSED_NAMESPACE::
58 submdspan(x_dofmap, entities[e],
59 MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent);
60 std::vector<T> nodes(3 * dofs.size());
61 for (std::size_t i = 0; i < dofs.size(); ++i)
62 {
63 const std::int32_t pos = 3 * dofs[i];
64 for (std::size_t j = 0; j < 3; ++j)
65 nodes[3 * i + j] = geom_dofs[pos + j];
66 }
67
68 std::array<T, 3> d
69 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
70 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
71 }
72 }
73 else
74 {
75 mesh.topology_mutable()->create_connectivity(dim, tdim);
76 mesh.topology_mutable()->create_connectivity(tdim, dim);
77 auto e_to_c = mesh.topology()->connectivity(dim, tdim);
78 assert(e_to_c);
79 auto c_to_e = mesh.topology_mutable()->connectivity(tdim, dim);
80 assert(c_to_e);
81 for (std::size_t e = 0; e < entities.size(); e++)
82 {
83 const std::int32_t index = entities[e];
84
85 // Find attached cell
86 assert(e_to_c->num_links(index) > 0);
87 const std::int32_t c = e_to_c->links(index)[0];
88
89 // Find local number of entity wrt cell
90 auto cell_entities = c_to_e->links(c);
91 auto it0 = std::find(cell_entities.begin(), cell_entities.end(), index);
92 assert(it0 != cell_entities.end());
93 const int local_cell_entity = std::distance(cell_entities.begin(), it0);
94
95 // Tabulate geometry dofs for the entity
96 auto dofs = MDSPAN_IMPL_STANDARD_NAMESPACE::
97 MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan(
98 x_dofmap, c, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent);
99 const std::vector<int> entity_dofs
100 = geometry.cmaps()[0].create_dof_layout().entity_closure_dofs(
101 dim, local_cell_entity);
102 std::vector<T> nodes(3 * entity_dofs.size());
103 for (std::size_t i = 0; i < entity_dofs.size(); i++)
104 {
105 const std::int32_t pos = 3 * dofs[entity_dofs[i]];
106 for (std::size_t j = 0; j < 3; ++j)
107 nodes[3 * i + j] = geom_dofs[pos + j];
108 }
109
110 std::array<T, 3> d
111 = compute_distance_gjk<T>(points.subspan(3 * e, 3), nodes);
112 shortest_vectors.insert(shortest_vectors.end(), d.begin(), d.end());
113 }
114 }
115
116 return shortest_vectors;
117}
118
125template <std::floating_point T>
126T compute_squared_distance_bbox(std::span<const T, 6> b,
127 std::span<const T, 3> x)
128{
129 auto b0 = b.template subspan<0, 3>();
130 auto b1 = b.template subspan<3, 3>();
131 return std::transform_reduce(x.begin(), x.end(), b0.begin(), 0.0,
132 std::plus<>{},
133 [](auto x, auto b)
134 {
135 auto dx = x - b;
136 return dx > 0 ? 0 : dx * dx;
137 })
138 + std::transform_reduce(x.begin(), x.end(), b1.begin(), 0.0,
139 std::plus<>{},
140 [](auto x, auto b)
141 {
142 auto dx = x - b;
143 return dx < 0 ? 0 : dx * dx;
144 });
145}
146
162template <std::floating_point T>
163std::vector<T> squared_distance(const mesh::Mesh<T>& mesh, int dim,
164 std::span<const std::int32_t> entities,
165 std::span<const T> points)
166{
167 std::vector<T> v = shortest_vector(mesh, dim, entities, points);
168 std::vector<T> d(v.size() / 3, 0);
169 for (std::size_t i = 0; i < d.size(); ++i)
170 for (std::size_t j = 0; j < 3; ++j)
171 d[i] += v[3 * i + j] * v[3 * i + j];
172 return d;
173}
174
175namespace impl
176{
178constexpr bool is_leaf(std::array<int, 2> bbox)
179{
180 // Leaf nodes are marked by setting child_0 equal to child_1
181 return bbox[0] == bbox[1];
182}
183
188template <std::floating_point T>
189constexpr bool point_in_bbox(const std::array<T, 6>& b, std::span<const T, 3> x)
190{
191 constexpr T rtol = 1e-14;
192 bool in = true;
193 for (std::size_t i = 0; i < 3; i++)
194 {
195 T eps = rtol * (b[i + 3] - b[i]);
196 in &= x[i] >= (b[i] - eps);
197 in &= x[i] <= (b[i + 3] + eps);
198 }
199
200 return in;
201}
202
206template <std::floating_point T>
207constexpr bool bbox_in_bbox(std::span<const T, 6> a, std::span<const T, 6> b)
208{
209 constexpr T rtol = 1e-14;
210 auto a0 = a.template subspan<0, 3>();
211 auto a1 = a.template subspan<3, 3>();
212 auto b0 = b.template subspan<0, 3>();
213 auto b1 = b.template subspan<3, 3>();
214
215 bool in = true;
216 for (std::size_t i = 0; i < 3; i++)
217 {
218 T eps = rtol * (b1[i] - b0[i]);
219 in &= a1[i] >= (b0[i] - eps);
220 in &= a0[i] <= (b1[i] + eps);
221 }
222
223 return in;
224}
225
227template <std::floating_point T>
228std::pair<std::int32_t, T>
229_compute_closest_entity(const geometry::BoundingBoxTree<T>& tree,
230 std::span<const T, 3> point, std::int32_t node,
231 const mesh::Mesh<T>& mesh, std::int32_t closest_entity,
232 T R2)
233{
234 // Get children of current bounding box node (child_1 denotes entity
235 // index for leaves)
236 const std::array<int, 2> bbox = tree.bbox(node);
237 T r2;
238 if (is_leaf(bbox))
239 {
240 // If point cloud tree the exact distance is easy to compute
241 if (tree.tdim() == 0)
242 {
243 std::array<T, 6> diff = tree.get_bbox(node);
244 for (std::size_t k = 0; k < 3; ++k)
245 diff[k] -= point[k];
246 r2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
247 }
248 else
249 {
250 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
251
252 // If bounding box closer than previous closest entity, use gjk to
253 // obtain exact distance to the convex hull of the entity
254 if (r2 <= R2)
255 {
256 r2 = squared_distance<T>(mesh, tree.tdim(),
257 std::span(std::next(bbox.begin(), 1), 1),
258 point)
259 .front();
260 }
261 }
262
263 // If entity is closer than best result so far, return it
264 if (r2 <= R2)
265 {
266 closest_entity = bbox.back();
267 R2 = r2;
268 }
269
270 return {closest_entity, R2};
271 }
272 else
273 {
274 // If bounding box is outside radius, then don't search further
275 r2 = compute_squared_distance_bbox<T>(tree.get_bbox(node), point);
276 if (r2 > R2)
277 return {closest_entity, R2};
278
279 // Check both children
280 // We use R2 (as opposed to r2), as a bounding box can be closer
281 // than the actual entity
282 std::pair<int, T> p0 = _compute_closest_entity(tree, point, bbox.front(),
283 mesh, closest_entity, R2);
284 std::pair<int, T> p1 = _compute_closest_entity(tree, point, bbox.back(),
285 mesh, p0.first, p0.second);
286 return p1;
287 }
288}
289
295template <std::floating_point T>
296void _compute_collisions_point(const geometry::BoundingBoxTree<T>& tree,
297 std::span<const T, 3> p,
298 std::vector<std::int32_t>& entities)
299{
300 std::deque<std::int32_t> stack;
301 std::int32_t next = tree.num_bboxes() - 1;
302 while (next != -1)
303 {
304 const std::array<int, 2> bbox = tree.bbox(next);
305 if (is_leaf(bbox) and point_in_bbox(tree.get_bbox(next), p))
306 {
307 // If box is a leaf node then add it to the list of colliding
308 // entities
309 entities.push_back(bbox[1]);
310 next = -1;
311 }
312 else
313 {
314 // Check whether the point collides with child nodes (left and
315 // right)
316 bool left = point_in_bbox(tree.get_bbox(bbox[0]), p);
317 bool right = point_in_bbox(tree.get_bbox(bbox[1]), p);
318 if (left and right)
319 {
320 // If the point collides with both child nodes, add the right
321 // node to the stack (for later visiting) and continue the tree
322 // traversal with the left subtree
323 stack.push_back(bbox[1]);
324 next = bbox[0];
325 }
326 else if (left)
327 {
328 // Traverse the current node's left subtree
329 next = bbox[0];
330 }
331 else if (right)
332 {
333 // Traverse the current node's right subtree
334 next = bbox[1];
335 }
336 else
337 next = -1;
338 }
339
340 // If tree traversal reaches a dead end (box is a leaf node or no
341 // collision detected), check the stack for deferred subtrees
342 if (next == -1 and !stack.empty())
343 {
344 next = stack.back();
345 stack.pop_back();
346 }
347 }
348}
349
350// Compute collisions with tree (recursive)
351template <std::floating_point T>
352void _compute_collisions_tree(const geometry::BoundingBoxTree<T>& A,
353 const geometry::BoundingBoxTree<T>& B,
354 std::int32_t node_A, std::int32_t node_B,
355 std::vector<std::int32_t>& entities)
356{
357 // If bounding boxes don't collide, then don't search further
358 if (!bbox_in_bbox<T>(A.get_bbox(node_A), B.get_bbox(node_B)))
359 return;
360
361 // Get bounding boxes for current nodes
362 const std::array<std::int32_t, 2> bbox_A = A.bbox(node_A);
363 const std::array<std::int32_t, 2> bbox_B = B.bbox(node_B);
364
365 // Check whether we've reached a leaf in A or B
366 const bool is_leaf_A = is_leaf(bbox_A);
367 const bool is_leaf_B = is_leaf(bbox_B);
368 if (is_leaf_A and is_leaf_B)
369 {
370 // If both boxes are leaves (which we know collide), then add them
371 // child_1 denotes entity for leaves
372 entities.push_back(bbox_A[1]);
373 entities.push_back(bbox_B[1]);
374 }
375 else if (is_leaf_A)
376 {
377 // If we reached the leaf in A, then descend B
378 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
379 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
380 }
381 else if (is_leaf_B)
382 {
383 // If we reached the leaf in B, then descend A
384 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
385 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
386 }
387 else if (node_A > node_B)
388 {
389 // At this point, we know neither is a leaf so descend the largest
390 // tree first. Note that nodes are added in reverse order with the
391 // top bounding box at the end so the largest tree (the one with the
392 // the most boxes left to traverse) has the largest node number.
393 _compute_collisions_tree(A, B, bbox_A[0], node_B, entities);
394 _compute_collisions_tree(A, B, bbox_A[1], node_B, entities);
395 }
396 else
397 {
398 _compute_collisions_tree(A, B, node_A, bbox_B[0], entities);
399 _compute_collisions_tree(A, B, node_A, bbox_B[1], entities);
400 }
401
402 // Note that cases above can be collected in fewer cases but this way
403 // the logic is easier to follow.
404}
405
406} // namespace impl
407
414template <std::floating_point T>
416 std::span<const std::int32_t> entities)
417{
418 LOG(INFO) << "Building point search tree to accelerate distance queries for "
419 "a given topological dimension and subset of entities.";
420
421 const std::vector<T> midpoints
422 = mesh::compute_midpoints(mesh, tdim, entities);
423 std::vector<std::pair<std::array<T, 3>, std::int32_t>> points(
424 entities.size());
425 for (std::size_t i = 0; i < points.size(); ++i)
426 {
427 for (std::size_t j = 0; j < 3; ++j)
428 points[i].first[j] = midpoints[3 * i + j];
429 points[i].second = entities[i];
430 }
431
432 // Build tree
433 return BoundingBoxTree(points);
434}
435
441template <std::floating_point T>
442std::vector<std::int32_t> compute_collisions(const BoundingBoxTree<T>& tree0,
443 const BoundingBoxTree<T>& tree1)
444{
445 // Call recursive find function
446 std::vector<std::int32_t> entities;
447 if (tree0.num_bboxes() > 0 and tree1.num_bboxes() > 0)
448 {
449 impl::_compute_collisions_tree(tree0, tree1, tree0.num_bboxes() - 1,
450 tree1.num_bboxes() - 1, entities);
451 }
452
453 return entities;
454}
455
466template <std::floating_point T>
468compute_collisions(const BoundingBoxTree<T>& tree, std::span<const T> points)
469{
470 if (tree.num_bboxes() > 0)
471 {
472 std::vector<std::int32_t> entities, offsets(points.size() / 3 + 1, 0);
473 entities.reserve(points.size() / 3);
474 for (std::size_t p = 0; p < points.size() / 3; ++p)
475 {
476 impl::_compute_collisions_point(
477 tree, std::span<const T, 3>(points.data() + 3 * p, 3), entities);
478 offsets[p + 1] = entities.size();
479 }
480
481 return graph::AdjacencyList(std::move(entities), std::move(offsets));
482 }
483 else
484 {
486 std::vector<std::int32_t>(),
487 std::vector<std::int32_t>(points.size() / 3 + 1, 0));
488 }
489}
490
508template <std::floating_point T>
510 std::span<const std::int32_t> cells,
511 std::array<T, 3> point, T tol)
512{
513 if (mesh.geometry().cmaps().size() > 1)
514 throw std::runtime_error("Mixed topology not supported");
515
516 if (cells.empty())
517 return -1;
518 else
519 {
520 const mesh::Geometry<T>& geometry = mesh.geometry();
521 std::span<const T> geom_dofs = geometry.x();
522 auto x_dofmap = geometry.dofmap();
523 const std::size_t num_nodes = x_dofmap.extent(1);
524 std::vector<T> coordinate_dofs(num_nodes * 3);
525 for (auto cell : cells)
526 {
527 auto dofs = MDSPAN_IMPL_STANDARD_NAMESPACE::
528 MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan(
529 x_dofmap, cell, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent);
530 for (std::size_t i = 0; i < num_nodes; ++i)
531 {
532 std::copy_n(std::next(geom_dofs.begin(), 3 * dofs[i]), 3,
533 std::next(coordinate_dofs.begin(), 3 * i));
534 }
535
536 std::array<T, 3> shortest_vector
537 = compute_distance_gjk<T>(point, coordinate_dofs);
538 T d2 = std::reduce(shortest_vector.begin(), shortest_vector.end(), T(0),
539 [](auto d, auto e) { return d + e * e; });
540 if (d2 < tol)
541 return cell;
542 }
543
544 return -1;
545 }
546}
547
560template <std::floating_point T>
561std::vector<std::int32_t>
563 const BoundingBoxTree<T>& midpoint_tree,
564 const mesh::Mesh<T>& mesh, std::span<const T> points)
565{
566 if (tree.num_bboxes() == 0)
567 return std::vector<std::int32_t>(points.size() / 3, -1);
568
569 std::vector<std::int32_t> entities;
570 entities.reserve(points.size() / 3);
571 for (std::size_t i = 0; i < points.size() / 3; ++i)
572 {
573 // Use midpoint tree to find initial closest entity to the point.
574 // Start by using a leaf node as the initial guess for the input
575 // entity
576 std::array<int, 2> leaf0 = midpoint_tree.bbox(0);
577 assert(impl::is_leaf(leaf0));
578 std::array<T, 6> diff = midpoint_tree.get_bbox(0);
579 for (std::size_t k = 0; k < 3; ++k)
580 diff[k] -= points[3 * i + k];
581 T R2 = diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2];
582
583 // Use a recursive search through the bounding box tree
584 // to find determine the entity with the closest midpoint.
585 // As the midpoint tree only consist of points, the distance
586 // queries are lightweight.
587 const auto [m_index, m_distance2] = impl::_compute_closest_entity(
588 midpoint_tree, std::span<const T, 3>(points.data() + 3 * i, 3),
589 midpoint_tree.num_bboxes() - 1, mesh, leaf0[0], R2);
590
591 // Use a recursives search through the bounding box tree to
592 // determine which entity is actually closest.
593 // Uses the entity with the closest midpoint as initial guess, and
594 // the distance from the midpoint to the point of interest as the
595 // initial search radius.
596 const auto [index, distance2] = impl::_compute_closest_entity(
597 tree, std::span<const T, 3>(points.data() + 3 * i, 3),
598 tree.num_bboxes() - 1, mesh, m_index, m_distance2);
599
600 entities.push_back(index);
601 }
602
603 return entities;
604}
605
621template <std::floating_point T>
623 const mesh::Mesh<T>& mesh,
624 const graph::AdjacencyList<std::int32_t>& candidate_cells,
625 std::span<const T> points)
626{
627 std::vector<std::int32_t> offsets = {0};
628 offsets.reserve(candidate_cells.num_nodes() + 1);
629 std::vector<std::int32_t> colliding_cells;
630 constexpr T eps2 = 1e-12;
631 const int tdim = mesh.topology()->dim();
632 for (std::int32_t i = 0; i < candidate_cells.num_nodes(); i++)
633 {
634 auto cells = candidate_cells.links(i);
635 std::vector<T> _point(3 * cells.size());
636 for (std::size_t j = 0; j < cells.size(); ++j)
637 for (std::size_t k = 0; k < 3; ++k)
638 _point[3 * j + k] = points[3 * i + k];
639
640 std::vector distances_sq = squared_distance<T>(mesh, tdim, cells, _point);
641 for (std::size_t j = 0; j < cells.size(); j++)
642 if (distances_sq[j] < eps2)
643 colliding_cells.push_back(cells[j]);
644
645 offsets.push_back(colliding_cells.size());
646 }
647
648 return graph::AdjacencyList(std::move(colliding_cells), std::move(offsets));
649}
650
668
679template <std::floating_point T>
680std::tuple<std::vector<std::int32_t>, std::vector<std::int32_t>, std::vector<T>,
681 std::vector<std::int32_t>>
682determine_point_ownership(const mesh::Mesh<T>& mesh, std::span<const T> points,
683 T padding)
684{
685 MPI_Comm comm = mesh.comm();
686
687 // Create a global bounding-box tree to find candidate processes with
688 // cells that could collide with the points
689 const int tdim = mesh.topology()->dim();
690 auto cell_map = mesh.topology()->index_map(tdim);
691 const std::int32_t num_cells = cell_map->size_local();
692 // NOTE: Should we send the cells in as input?
693 std::vector<std::int32_t> cells(num_cells, 0);
694 std::iota(cells.begin(), cells.end(), 0);
695 BoundingBoxTree bb(mesh, tdim, cells, padding);
696 BoundingBoxTree global_bbtree = bb.create_global_tree(comm);
697
698 // Compute collisions:
699 // For each point in `x` get the processes it should be sent to
700 graph::AdjacencyList collisions = compute_collisions(global_bbtree, points);
701
702 // Get unique list of outgoing ranks
703 std::vector<std::int32_t> out_ranks = collisions.array();
704 std::sort(out_ranks.begin(), out_ranks.end());
705 out_ranks.erase(std::unique(out_ranks.begin(), out_ranks.end()),
706 out_ranks.end());
707 // Compute incoming edges (source processes)
708 std::vector in_ranks = dolfinx::MPI::compute_graph_edges_nbx(comm, out_ranks);
709 std::sort(in_ranks.begin(), in_ranks.end());
710
711 // Create neighborhood communicator in forward direction
712 MPI_Comm forward_comm;
713 MPI_Dist_graph_create_adjacent(
714 comm, in_ranks.size(), in_ranks.data(), MPI_UNWEIGHTED, out_ranks.size(),
715 out_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &forward_comm);
716
717 // Compute map from global mpi rank to neighbor rank, "collisions"
718 // uses global rank
719 std::map<std::int32_t, std::int32_t> rank_to_neighbor;
720 for (std::size_t i = 0; i < out_ranks.size(); i++)
721 rank_to_neighbor[out_ranks[i]] = i;
722
723 // Count the number of points to send per neighbor process
724 std::vector<std::int32_t> send_sizes(out_ranks.size());
725 for (std::size_t i = 0; i < points.size() / 3; ++i)
726 for (auto p : collisions.links(i))
727 send_sizes[rank_to_neighbor[p]] += 3;
728
729 // Compute receive sizes
730 std::vector<std::int32_t> recv_sizes(in_ranks.size());
731 send_sizes.reserve(1);
732 recv_sizes.reserve(1);
733 MPI_Request sizes_request;
734 MPI_Ineighbor_alltoall(send_sizes.data(), 1, MPI_INT, recv_sizes.data(), 1,
735 MPI_INT, forward_comm, &sizes_request);
736
737 // Compute sending offsets
738 std::vector<std::int32_t> send_offsets(send_sizes.size() + 1, 0);
739 std::partial_sum(send_sizes.begin(), send_sizes.end(),
740 std::next(send_offsets.begin(), 1));
741
742 // Pack data to send and store unpack map
743 std::vector<T> send_data(send_offsets.back());
744 std::vector<std::int32_t> counter(send_sizes.size(), 0);
745 // unpack map: [index in adj list][pos in x]
746 std::vector<std::int32_t> unpack_map(send_offsets.back() / 3);
747 for (std::size_t i = 0; i < points.size(); i += 3)
748 {
749 for (auto p : collisions.links(i / 3))
750 {
751 int neighbor = rank_to_neighbor[p];
752 int pos = send_offsets[neighbor] + counter[neighbor];
753 auto it = std::next(send_data.begin(), pos);
754 std::copy_n(std::next(points.begin(), i), 3, it);
755 unpack_map[pos / 3] = i / 3;
756 counter[neighbor] += 3;
757 }
758 }
759
760 MPI_Wait(&sizes_request, MPI_STATUS_IGNORE);
761 std::vector<std::int32_t> recv_offsets(in_ranks.size() + 1, 0);
762 std::partial_sum(recv_sizes.begin(), recv_sizes.end(),
763 std::next(recv_offsets.begin(), 1));
764
765 std::vector<T> received_points((std::size_t)recv_offsets.back());
766 MPI_Neighbor_alltoallv(
767 send_data.data(), send_sizes.data(), send_offsets.data(),
768 dolfinx::MPI::mpi_type<T>(), received_points.data(), recv_sizes.data(),
769 recv_offsets.data(), dolfinx::MPI::mpi_type<T>(), forward_comm);
770
771 // Get mesh geometry for closest entity
772 const mesh::Geometry<T>& geometry = mesh.geometry();
773 if (geometry.cmaps().size() > 1)
774 throw std::runtime_error("Mixed topology not supported");
775 std::span<const T> geom_dofs = geometry.x();
776 auto x_dofmap = geometry.dofmap();
777
778 // Compute candidate cells for collisions (and extrapolation)
779 const graph::AdjacencyList<std::int32_t> candidate_collisions
780 = compute_collisions(bb, std::span<const T>(received_points.data(),
781 received_points.size()));
782
783 // Each process checks which points collides with a cell on the process
784 const int rank = dolfinx::MPI::rank(comm);
785 std::vector<std::int32_t> cell_indicator(received_points.size() / 3);
786 std::vector<std::int32_t> closest_cells(received_points.size() / 3);
787 for (std::size_t p = 0; p < received_points.size(); p += 3)
788 {
789 std::array<T, 3> point;
790 std::copy_n(std::next(received_points.begin(), p), 3, point.begin());
791 // Find first collding cell among the cells with colliding bounding boxes
792 const int colliding_cell = geometry::compute_first_colliding_cell(
793 mesh, candidate_collisions.links(p / 3), point,
794 10 * std::numeric_limits<T>::epsilon());
795 // If a collding cell is found, store the rank of the current process
796 // which will be sent back to the owner of the point
797 cell_indicator[p / 3] = (colliding_cell >= 0) ? rank : -1;
798 // Store the cell index for lookup once the owning processes has determined
799 // the ownership of the point
800 closest_cells[p / 3] = colliding_cell;
801 }
802
803 // Create neighborhood communicator in the reverse direction: send
804 // back col to requesting processes
805 MPI_Comm reverse_comm;
806 MPI_Dist_graph_create_adjacent(
807 comm, out_ranks.size(), out_ranks.data(), MPI_UNWEIGHTED, in_ranks.size(),
808 in_ranks.data(), MPI_UNWEIGHTED, MPI_INFO_NULL, false, &reverse_comm);
809
810 // Reuse sizes and offsets from first communication set
811 // but divide by three
812 {
813 auto rescale = [](auto& x)
814 {
815 std::transform(x.cbegin(), x.cend(), x.begin(),
816 [](auto e) { return (e / 3); });
817 };
818 rescale(recv_sizes);
819 rescale(recv_offsets);
820 rescale(send_sizes);
821 rescale(send_offsets);
822
823 // The communication is reversed, so swap recv to send offsets
824 std::swap(recv_sizes, send_sizes);
825 std::swap(recv_offsets, send_offsets);
826 }
827
828 std::vector<std::int32_t> recv_ranks(recv_offsets.back());
829 MPI_Neighbor_alltoallv(cell_indicator.data(), send_sizes.data(),
830 send_offsets.data(), MPI_INT32_T, recv_ranks.data(),
831 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
832 reverse_comm);
833
834 std::vector<std::int32_t> point_owners(points.size() / 3, -1);
835 for (std::size_t i = 0; i < unpack_map.size(); i++)
836 {
837 const std::int32_t pos = unpack_map[i];
838 // Only insert new owner if no owner has previously been found
839 if (recv_ranks[i] >= 0 && point_owners[pos] == -1)
840 point_owners[pos] = recv_ranks[i];
841 }
842
843 // Create extrapolation marker for those points already sent to other
844 // process
845 std::vector<std::uint8_t> send_extrapolate(recv_offsets.back());
846 for (std::int32_t i = 0; i < recv_offsets.back(); i++)
847 {
848 const std::int32_t pos = unpack_map[i];
849 send_extrapolate[i] = point_owners[pos] == -1;
850 }
851
852 // Swap communication direction, to send extrapolation marker to other
853 // processes
854 std::swap(send_sizes, recv_sizes);
855 std::swap(send_offsets, recv_offsets);
856 std::vector<std::uint8_t> dest_extrapolate(recv_offsets.back());
857 MPI_Neighbor_alltoallv(send_extrapolate.data(), send_sizes.data(),
858 send_offsets.data(), MPI_UINT8_T,
859 dest_extrapolate.data(), recv_sizes.data(),
860 recv_offsets.data(), MPI_UINT8_T, forward_comm);
861
862 std::vector<T> squared_distances(received_points.size() / 3, -1);
863
864 for (std::size_t i = 0; i < dest_extrapolate.size(); i++)
865 {
866 if (dest_extrapolate[i] == 1)
867 {
868 assert(closest_cells[i] == -1);
869 std::array<T, 3> point;
870 std::copy_n(std::next(received_points.begin(), 3 * i), 3, point.begin());
871
872 // Find shortest distance among cells with colldiing bounding box
873 T shortest_distance = std::numeric_limits<T>::max();
874 std::int32_t closest_cell = -1;
875 for (auto cell : candidate_collisions.links(i))
876 {
877 auto dofs = MDSPAN_IMPL_STANDARD_NAMESPACE::
878 MDSPAN_IMPL_PROPOSED_NAMESPACE::submdspan(
879 x_dofmap, cell, MDSPAN_IMPL_STANDARD_NAMESPACE::full_extent);
880 std::vector<T> nodes(3 * dofs.size());
881 for (std::size_t j = 0; j < dofs.size(); ++j)
882 {
883 const int pos = 3 * dofs[j];
884 for (std::size_t k = 0; k < 3; ++k)
885 nodes[3 * j + k] = geom_dofs[pos + k];
886 }
887 const std::array<T, 3> d = compute_distance_gjk<T>(
888 std::span<const T>(point.data(), point.size()), nodes);
889 if (T current_distance = d[0] * d[0] + d[1] * d[1] + d[2] * d[2];
890 current_distance < shortest_distance)
891 {
892 shortest_distance = current_distance;
893 closest_cell = cell;
894 }
895 }
896 closest_cells[i] = closest_cell;
897 squared_distances[i] = shortest_distance;
898 }
899 }
900
901 std::swap(recv_sizes, send_sizes);
902 std::swap(recv_offsets, send_offsets);
903
904 // Get distances from closest entity of points that were on the other process
905 std::vector<T> recv_distances(recv_offsets.back());
906 MPI_Neighbor_alltoallv(
907 squared_distances.data(), send_sizes.data(), send_offsets.data(),
908 dolfinx::MPI::mpi_type<T>(), recv_distances.data(), recv_sizes.data(),
909 recv_offsets.data(), dolfinx::MPI::mpi_type<T>(), reverse_comm);
910
911 // Update point ownership with extrapolation information
912 std::vector<T> closest_distance(unpack_map.size(),
913 std::numeric_limits<T>::max());
914 for (std::size_t i = 0; i < out_ranks.size(); i++)
915 {
916 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
917 {
918 const std::int32_t pos = unpack_map[j];
919 auto current_dist = recv_distances[j];
920 // Update if closer than previous guess and was found
921 if (auto d = closest_distance[pos];
922 (current_dist > 0) and (current_dist < d))
923 {
924 point_owners[pos] = out_ranks[i];
925 closest_distance[pos] = current_dist;
926 }
927 }
928 }
929
930 // Communication is reversed again to send dest ranks to all processes
931 std::swap(send_sizes, recv_sizes);
932 std::swap(send_offsets, recv_offsets);
933
934 // Pack ownership data
935 std::vector<std::int32_t> send_owners(send_offsets.back());
936 std::fill(counter.begin(), counter.end(), 0);
937 for (std::size_t i = 0; i < points.size() / 3; ++i)
938 {
939 for (auto p : collisions.links(i))
940 {
941 int neighbor = rank_to_neighbor[p];
942 send_owners[send_offsets[neighbor] + counter[neighbor]++]
943 = point_owners[i];
944 }
945 }
946
947 // Send ownership info
948 std::vector<std::int32_t> dest_ranks(recv_offsets.back());
949 MPI_Neighbor_alltoallv(send_owners.data(), send_sizes.data(),
950 send_offsets.data(), MPI_INT32_T, dest_ranks.data(),
951 recv_sizes.data(), recv_offsets.data(), MPI_INT32_T,
952 forward_comm);
953
954 // Unpack dest ranks if point owner is this rank
955 std::vector<std::int32_t> owned_recv_ranks;
956 owned_recv_ranks.reserve(recv_offsets.back());
957 std::vector<T> owned_recv_points;
958 std::vector<std::int32_t> owned_recv_cells;
959 for (std::size_t i = 0; i < in_ranks.size(); i++)
960 {
961 for (std::int32_t j = recv_offsets[i]; j < recv_offsets[i + 1]; j++)
962 {
963 if (rank == dest_ranks[j])
964 {
965 owned_recv_ranks.push_back(in_ranks[i]);
966 owned_recv_points.insert(
967 owned_recv_points.end(), std::next(received_points.cbegin(), 3 * j),
968 std::next(received_points.cbegin(), 3 * (j + 1)));
969 owned_recv_cells.push_back(closest_cells[j]);
970 }
971 }
972 }
973
974 MPI_Comm_free(&forward_comm);
975 MPI_Comm_free(&reverse_comm);
976
977 return std::make_tuple(point_owners, owned_recv_ranks, owned_recv_points,
978 owned_recv_cells);
979}
980
981} // namespace dolfinx::geometry
Axis-Aligned bounding box binary tree. It is used to find entities in a collection (often a mesh::Mes...
Definition BoundingBoxTree.h:205
BoundingBoxTree create_global_tree(MPI_Comm comm) const
Compute a global bounding tree (collective on comm) This can be used to find which process a point mi...
Definition BoundingBoxTree.h:325
std::int32_t num_bboxes() const
Return number of bounding boxes.
Definition BoundingBoxTree.h:361
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:313
std::array< std::int32_t, 2 > bbox(std::size_t node) const
Get bounding box child nodes.
Definition BoundingBoxTree.h:381
This class provides a static adjacency list data structure. It is commonly used to store directed gra...
Definition AdjacencyList.h:28
std::span< T > links(std::size_t node)
Get the links (edges) for given node.
Definition AdjacencyList.h:112
const std::vector< T > & array() const
Return contiguous array of links for all nodes (const version)
Definition AdjacencyList.h:129
std::int32_t num_nodes() const
Get the number of nodes.
Definition AdjacencyList.h:97
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:33
MDSPAN_IMPL_STANDARD_NAMESPACE::mdspan< const std::int32_t, MDSPAN_IMPL_STANDARD_NAMESPACE::dextents< std::size_t, 2 > > dofmap() const
DOF map.
Definition Geometry.h:94
std::span< const value_type > x() const
Access geometry degrees-of-freedom data (const version).
Definition Geometry.h:113
const std::vector< fem::CoordinateElement< value_type > > & cmaps() const
The elements that describes the geometry maps.
Definition Geometry.h:125
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:23
std::shared_ptr< Topology > topology_mutable() const
Get mesh topology if one really needs the mutable version.
Definition Mesh.h:72
std::shared_ptr< Topology > topology()
Get mesh topology.
Definition Mesh.h:64
Geometry< T > & geometry()
Get mesh geometry.
Definition Mesh.h:76
MPI_Comm comm() const
Mesh MPI communicator.
Definition Mesh.h:84
std::vector< int > compute_graph_edges_nbx(MPI_Comm comm, std::span< const int > edges)
Determine incoming graph edges using the NBX consensus algorithm.
Definition MPI.cpp:164
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:64
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:21
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:163
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:442
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)
Given a set of cells, find the first one that collides with a point.
Definition utils.h:509
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:126
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:622
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:36
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:415
std::tuple< std::vector< std::int32_t >, std::vector< std::int32_t >, std::vector< T >, std::vector< std::int32_t > > determine_point_ownership(const mesh::Mesh< T > &mesh, std::span< const T > points, T padding)
Given a set of points, determine which process is colliding, using the GJK algorithm on cells to dete...
Definition utils.h:682
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:562
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:397