DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
utils.h
Go to the documentation of this file.
1// Copyright (C) 2019-2026 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 "EntityMap.h"
10#include "Mesh.h"
11#include "MeshTags.h"
12#include "Topology.h"
13#include "graphbuild.h"
14#include "types.h"
15#include <algorithm>
16#include <array>
17#include <basix/mdspan.hpp>
18#include <boost/unordered/unordered_flat_map.hpp>
19#include <cassert>
20#include <concepts>
21#include <cstdint>
22#include <dolfinx/common/MPI.h>
23#include <dolfinx/common/Timer.h>
24#include <dolfinx/common/sort.h>
25#include <dolfinx/graph/AdjacencyList.h>
26#include <dolfinx/graph/ordering.h>
27#include <dolfinx/graph/partition.h>
28#include <format>
29#include <functional>
30#include <mpi.h>
31#include <numeric>
32#include <optional>
33#include <ranges>
34#include <span>
35#include <stdexcept>
36#include <variant>
37#include <vector>
38
41
42namespace dolfinx::fem
43{
45}
46
47namespace dolfinx::mesh
48{
49enum class CellType : std::int8_t;
50
51namespace impl
52{
58template <typename T>
59void reorder_list(std::span<T> list, std::span<const std::int32_t> nodemap)
60{
61 if (nodemap.empty())
62 return;
63
64 assert(list.size() % nodemap.size() == 0);
65 std::size_t degree = list.size() / nodemap.size();
66 const std::vector<T> orig(list.begin(), list.end());
67 for (std::size_t n = 0; n < nodemap.size(); ++n)
68 {
69 std::span links_old(orig.data() + n * degree, degree);
70 auto links_new = list.subspan(nodemap[n] * degree, degree);
71 std::ranges::copy(links_old, links_new.begin());
72 }
73}
74
88template <std::floating_point T>
89std::tuple<std::vector<std::int32_t>, std::vector<T>, std::vector<std::int32_t>>
91 std::span<const std::int32_t> facets)
92{
93 auto topology = mesh.topology();
94 assert(topology);
95 const int tdim = topology->dim();
96 if (dim == tdim)
97 {
98 throw std::runtime_error(
99 "Cannot use mesh::locate_entities_boundary (boundary) for cells.");
100 }
101
102 // Build set of vertices on boundary and set of boundary entities
103 mesh.topology_mutable()->create_connectivity(tdim - 1, 0);
104 mesh.topology_mutable()->create_connectivity(tdim - 1, dim);
105 std::vector<std::int32_t> vertices, entities;
106 {
107 auto f_to_v = topology->connectivity(tdim - 1, 0);
108 assert(f_to_v);
109 auto f_to_e = topology->connectivity(tdim - 1, dim);
110 assert(f_to_e);
111 for (auto f : facets)
112 {
113 auto v = f_to_v->links(f);
114 vertices.insert(vertices.end(), v.begin(), v.end());
115 auto e = f_to_e->links(f);
116 entities.insert(entities.end(), e.begin(), e.end());
117 }
118
119 // Build vector of boundary vertices
120 {
121 std::ranges::sort(vertices);
122 auto [unique_end, range_end] = std::ranges::unique(vertices);
123 vertices.erase(unique_end, range_end);
124 }
125
126 {
127 std::ranges::sort(entities);
128 auto [unique_end, range_end] = std::ranges::unique(entities);
129 entities.erase(unique_end, range_end);
130 }
131 }
132
133 // Get geometry data
134 auto x_dofmap = mesh.geometry().dofmaps().front();
135 std::span<const T> x_nodes = mesh.geometry().x();
136
137 // Get all vertex 'node' indices
138 mesh.topology_mutable()->create_connectivity(0, tdim);
139 mesh.topology_mutable()->create_connectivity(tdim, 0);
140 auto v_to_c = topology->connectivity(0, tdim);
141 assert(v_to_c);
142 auto c_to_v = topology->connectivity(tdim, 0);
143 assert(c_to_v);
144 std::vector<T> x_vertices(3 * vertices.size(), -1.0);
145 std::vector<std::int32_t> vertex_to_pos(v_to_c->num_nodes(), -1);
146 for (std::size_t i = 0; i < vertices.size(); ++i)
147 {
148 const std::int32_t v = vertices[i];
149
150 // Get first cell and find position
151 const std::int32_t c = v_to_c->links(v).front();
152 auto cell_vertices = c_to_v->links(c);
153 auto it = std::ranges::find(cell_vertices, v);
154 assert(it != cell_vertices.end());
155 const std::size_t local_pos
156 = std::ranges::distance(cell_vertices.begin(), it);
157
158 auto dofs = md::submdspan(x_dofmap, c, md::full_extent);
159 for (std::size_t j = 0; j < 3; ++j)
160 x_vertices[j * vertices.size() + i] = x_nodes[3 * dofs[local_pos] + j];
161 vertex_to_pos[v] = i;
162 }
163
164 return {std::move(entities), std::move(x_vertices), std::move(vertex_to_pos)};
165}
166
167} // namespace impl
168
182std::vector<std::int32_t> exterior_facet_indices(const Topology& topology,
183 int facet_type_idx);
184
196std::vector<std::int32_t> exterior_facet_indices(const Topology& topology);
197
202using CellReorderFunction = std::function<std::vector<std::int32_t>(
204
205namespace impl
206{
237std::vector<std::int64_t>
238reorder_cells(const CellReorderFunction& reorder_fn,
239 std::optional<std::int32_t> max_facet_to_cell_links,
240 const std::vector<CellType>& celltypes,
241 const std::vector<fem::ElementDofLayout>& doflayouts,
242 const std::vector<std::vector<int>>& ghost_owners,
243 std::vector<std::vector<std::int64_t>>& cells,
244 std::vector<std::span<std::int64_t>>& cells_v,
245 std::vector<std::vector<std::int64_t>>& original_idx,
246 int num_threads);
247} // namespace impl
248
258std::vector<std::int64_t> extract_topology(CellType cell_type,
259 const fem::ElementDofLayout& layout,
260 std::span<const std::int64_t> cells);
261
273bool is_vertex_dof_layout(CellType cell_type,
274 const fem::ElementDofLayout& layout);
275
284template <std::floating_point T>
285std::vector<T> h(const Mesh<T>& mesh, std::span<const std::int32_t> entities,
286 int dim)
287{
288 if (entities.empty())
289 return std::vector<T>();
290 if (dim == 0)
291 return std::vector<T>(entities.size(), 0);
292
293 // Get the geometry dofs for the vertices of each entity
294 const auto [vertex_xdofs, xdof_shape]
295 = entities_to_geometry(mesh, dim, entities, false);
296
297 // Get the geometry coordinate
298 std::span<const T> x = mesh.geometry().x();
299
300 // Function to compute the length of (p0 - p1)
301 auto delta_norm = [](auto&& p0, auto&& p1)
302 {
303 T norm = 0;
304 for (std::size_t i = 0; i < 3; ++i)
305 norm += (p0[i] - p1[i]) * (p0[i] - p1[i]);
306 return std::sqrt(norm);
307 };
308
309 // Compute greatest distance between any to vertices
310 assert(dim > 0);
311 std::vector<T> h(entities.size(), 0);
312 for (std::size_t e = 0; e < entities.size(); ++e)
313 {
314 // Get geometry 'dof' for each vertex of entity e
315 std::span<const std::int32_t> e_vertices(
316 vertex_xdofs.data() + e * xdof_shape[1], xdof_shape[1]);
317
318 // Compute maximum distance between any two vertices
319 for (std::size_t i = 0; i < e_vertices.size(); ++i)
320 {
321 std::span<const T, 3> p0(x.data() + 3 * e_vertices[i], 3);
322 for (std::size_t j = i + 1; j < e_vertices.size(); ++j)
323 {
324 std::span<const T, 3> p1(x.data() + 3 * e_vertices[j], 3);
325 h[e] = std::max(h[e], delta_norm(p0, p1));
326 }
327 }
328 }
329
330 return h;
331}
332
336template <std::floating_point T>
337std::vector<T> cell_normals(const Mesh<T>& mesh, int dim,
338 std::span<const std::int32_t> entities)
339{
340 if (entities.empty())
341 return std::vector<T>();
342
343 auto topology = mesh.topology();
344 assert(topology);
345 if (topology->cell_type() == CellType::prism and dim == 2)
346 {
347 throw std::runtime_error(
348 "Cell normal computation for prism cells not yet supported.");
349 }
350
351 const int gdim = mesh.geometry().dim();
352 const CellType type = cell_entity_type(topology->cell_type(), dim, 0);
353
354 // Find geometry nodes for topology entities
355 std::span<const T> x = mesh.geometry().x();
356 const auto [geometry_entities, eshape]
357 = entities_to_geometry(mesh, dim, entities, false);
358
359 std::vector<T> n(entities.size() * 3);
360 switch (type)
361 {
362 case CellType::interval:
363 {
364 if (gdim > 2)
365 throw std::invalid_argument("Interval cell normal undefined in 3D.");
366 for (std::size_t i = 0; i < entities.size(); ++i)
367 {
368 // Get the two vertices as points
369 std::array vertices{geometry_entities[i * eshape[1]],
370 geometry_entities[i * eshape[1] + 1]};
371 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
372 std::span<const T, 3>(x.data() + 3 * vertices[1], 3)};
373
374 // Define normal by rotating tangent counter-clockwise
375 std::array<T, 3> t;
376 std::ranges::transform(p[1], p[0], t.begin(),
377 [](auto x, auto y) { return x - y; });
378
379 T norm = std::sqrt(t[0] * t[0] + t[1] * t[1]);
380 std::span<T, 3> ni(n.data() + 3 * i, 3);
381 ni[0] = -t[1] / norm;
382 ni[1] = t[0] / norm;
383 ni[2] = 0.0;
384 }
385 return n;
386 }
387 case CellType::triangle:
388 {
389 for (std::size_t i = 0; i < entities.size(); ++i)
390 {
391 // Get the three vertices as points
392 std::array vertices = {geometry_entities[i * eshape[1] + 0],
393 geometry_entities[i * eshape[1] + 1],
394 geometry_entities[i * eshape[1] + 2]};
395 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
396 std::span<const T, 3>(x.data() + 3 * vertices[1], 3),
397 std::span<const T, 3>(x.data() + 3 * vertices[2], 3)};
398
399 // Compute (p1 - p0) and (p2 - p0)
400 std::array<T, 3> dp1, dp2;
401 std::ranges::transform(p[1], p[0], dp1.begin(),
402 [](auto x, auto y) { return x - y; });
403 std::ranges::transform(p[2], p[0], dp2.begin(),
404 [](auto x, auto y) { return x - y; });
405
406 // Define cell normal via cross product of first two edges
407 std::array<T, 3> ni = math::cross(dp1, dp2);
408 T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]);
409 std::ranges::transform(ni, std::next(n.begin(), 3 * i),
410 [norm](auto x) { return x / norm; });
411 }
412
413 return n;
414 }
415 case CellType::quadrilateral:
416 {
417 // TODO: check
418 for (std::size_t i = 0; i < entities.size(); ++i)
419 {
420 // Get the three vertices as points
421 std::array vertices = {geometry_entities[i * eshape[1] + 0],
422 geometry_entities[i * eshape[1] + 1],
423 geometry_entities[i * eshape[1] + 2]};
424 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
425 std::span<const T, 3>(x.data() + 3 * vertices[1], 3),
426 std::span<const T, 3>(x.data() + 3 * vertices[2], 3)};
427
428 // Compute (p1 - p0) and (p2 - p0)
429 std::array<T, 3> dp1, dp2;
430 std::ranges::transform(p[1], p[0], dp1.begin(),
431 [](auto x, auto y) { return x - y; });
432 std::ranges::transform(p[2], p[0], dp2.begin(),
433 [](auto x, auto y) { return x - y; });
434
435 // Define cell normal via cross product of first two edges
436 std::array<T, 3> ni = math::cross(dp1, dp2);
437 T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]);
438 std::ranges::transform(ni, std::next(n.begin(), 3 * i),
439 [norm](auto x) { return x / norm; });
440 }
441
442 return n;
443 }
444 default:
445 throw std::invalid_argument(
446 "cell_normal not supported for this cell type.");
447 }
448}
449
453template <std::floating_point T>
454std::vector<T> compute_midpoints(const Mesh<T>& mesh, int dim,
455 std::span<const std::int32_t> entities)
456{
457 if (entities.empty())
458 return std::vector<T>();
459
460 std::span<const T> x = mesh.geometry().x();
461
462 // Build map from entity -> geometry dof
463 const auto [e_to_g, eshape]
464 = entities_to_geometry(mesh, dim, entities, false);
465
466 std::vector<T> x_mid(entities.size() * 3, 0);
467 for (std::size_t e = 0; e < entities.size(); ++e)
468 {
469 std::span<T, 3> p(x_mid.data() + 3 * e, 3);
470 std::span<const std::int32_t> rows(e_to_g.data() + e * eshape[1],
471 eshape[1]);
472 for (auto row : rows)
473 {
474 std::span<const T, 3> xg(x.data() + 3 * row, 3);
475 std::ranges::transform(p, xg, p.begin(),
476 [size = rows.size()](auto x, auto y)
477 { return x + y / size; });
478 }
479 }
480
481 return x_mid;
482}
483
484namespace impl
485{
490template <std::floating_point T>
491std::pair<std::vector<T>, std::array<std::size_t, 2>>
493{
494 auto topology = mesh.topology();
495 assert(topology);
496 const int tdim = topology->dim();
497
498 // Create entities and connectivities
499
500 // Get all vertex 'node' indices
501 const std::int32_t num_vertices = topology->index_map(0)->size_local()
502 + topology->index_map(0)->num_ghosts();
503
504 std::vector<std::int32_t> vertex_to_node(num_vertices);
505 for (int cell_type_idx = 0,
506 num_cell_types = topology->entity_types(tdim).size();
507 cell_type_idx < num_cell_types; ++cell_type_idx)
508 {
509 auto x_dofmap = mesh.geometry().dofmaps().at(cell_type_idx);
510 auto c_to_v = topology->connectivity({tdim, cell_type_idx}, {0, 0});
511 assert(c_to_v);
512 for (int c = 0; c < c_to_v->num_nodes(); ++c)
513 {
514 auto x_dofs = md::submdspan(x_dofmap, c, md::full_extent);
515 auto vertices = c_to_v->links(c);
516 for (std::size_t i = 0; i < vertices.size(); ++i)
517 vertex_to_node[vertices[i]] = x_dofs[i];
518 }
519 }
520
521 // Pack coordinates of vertices
522 std::span<const T> x_nodes = mesh.geometry().x();
523 std::vector<T> x_vertices(3 * vertex_to_node.size(), 0.0);
524 for (std::size_t i = 0; i < vertex_to_node.size(); ++i)
525 {
526 std::int32_t pos = 3 * vertex_to_node[i];
527 for (std::size_t j = 0; j < 3; ++j)
528 x_vertices[j * vertex_to_node.size() + i] = x_nodes[pos + j];
529 }
530
531 return {std::move(x_vertices), {3, vertex_to_node.size()}};
532}
533
534} // namespace impl
535
537template <typename Fn, typename T>
538concept MarkerFn = std::is_invocable_r<
539 std::vector<std::int8_t>, Fn,
540 md::mdspan<const T,
541 md::extents<std::size_t, 3, md::dynamic_extent>>>::value;
542
558template <std::floating_point T, MarkerFn<T> U>
559std::vector<std::int32_t> locate_entities(const Mesh<T>& mesh, int dim,
560 U marker, int entity_type_idx)
561{
562
563 using cmdspan3x_t
564 = md::mdspan<const T, md::extents<std::size_t, 3, md::dynamic_extent>>;
565
566 // Run marker function on vertex coordinates
567 const auto [xdata, xshape] = impl::compute_vertex_coords(mesh);
568
569 cmdspan3x_t x(xdata.data(), xshape);
570 const std::vector<std::int8_t> marked = marker(x);
571 if (marked.size() != x.extent(1))
572 throw std::runtime_error("Length of array of markers is wrong.");
573
574 auto topology = mesh.topology();
575 assert(topology);
576 const int tdim = topology->dim();
577
578 mesh.topology_mutable()->create_entities(dim);
579 if (dim < tdim)
580 mesh.topology_mutable()->create_connectivity(dim, 0);
581
582 // Iterate over entities of dimension 'dim' to build vector of marked
583 // entities
584 auto e_to_v = topology->connectivity({dim, entity_type_idx}, {0, 0});
585 assert(e_to_v);
586 std::vector<std::int32_t> entities;
587 for (int e = 0; e < e_to_v->num_nodes(); ++e)
588 {
589 // Iterate over entity vertices
590 bool all_vertices_marked = true;
591 for (std::int32_t v : e_to_v->links(e))
592 {
593 if (!marked[v])
594 {
595 all_vertices_marked = false;
596 break;
597 }
598 }
599
600 if (all_vertices_marked)
601 entities.push_back(e);
602 }
603
604 return entities;
605}
606
620template <std::floating_point T, MarkerFn<T> U>
621std::vector<std::int32_t> locate_entities(const Mesh<T>& mesh, int dim,
622 U marker)
623{
624 const int num_entity_types = mesh.topology()->entity_types(dim).size();
625 if (num_entity_types > 1)
626 {
627 throw std::runtime_error(
628 "Multiple entity types of this dimension. Specify entity type index");
629 }
630 return locate_entities(mesh, dim, marker, 0);
631}
632
656template <std::floating_point T, MarkerFn<T> U>
657std::vector<std::int32_t> locate_entities_boundary(const Mesh<T>& mesh, int dim,
658 U marker)
659{
660 // TODO Rewrite this function, it should be possible to simplify considerably
661 auto topology = mesh.topology();
662 assert(topology);
663 int tdim = topology->dim();
664 if (dim == tdim)
665 {
666 throw std::runtime_error(
667 "Cannot use mesh::locate_entities_boundary (boundary) for cells.");
668 }
669
670 // Compute list of boundary facets
671 mesh.topology_mutable()->create_entities(tdim - 1);
672 mesh.topology_mutable()->create_connectivity(tdim - 1, tdim);
673 std::vector<std::int32_t> boundary_facets = exterior_facet_indices(*topology);
674
675 using cmdspan3x_t
676 = md::mdspan<const T, md::extents<std::size_t, 3, md::dynamic_extent>>;
677
678 // Run marker function on the vertex coordinates
679 auto [facet_entities, xdata, vertex_to_pos]
680 = impl::compute_vertex_coords_boundary(mesh, dim, boundary_facets);
681 cmdspan3x_t x(xdata.data(), 3, xdata.size() / 3);
682 std::vector<std::int8_t> marked = marker(x);
683 if (marked.size() != x.extent(1))
684 throw std::runtime_error("Length of array of markers is wrong.");
685
686 // Loop over entities and check vertex markers
687 mesh.topology_mutable()->create_entities(dim);
688 auto e_to_v = topology->connectivity(dim, 0);
689 assert(e_to_v);
690 std::vector<std::int32_t> entities;
691 for (auto e : facet_entities)
692 {
693 // Iterate over entity vertices
694 bool all_vertices_marked = true;
695 for (auto v : e_to_v->links(e))
696 {
697 const std::int32_t pos = vertex_to_pos[v];
698 if (!marked[pos])
699 {
700 all_vertices_marked = false;
701 break;
702 }
703 }
704
705 // Mark facet with all vertices marked
706 if (all_vertices_marked)
707 entities.push_back(e);
708 }
709
710 return entities;
711}
712
731template <std::floating_point T>
732std::pair<std::vector<std::int32_t>, std::array<std::size_t, 2>>
734 std::span<const std::int32_t> entities,
735 bool permute = false)
736{
737 auto topology = mesh.topology();
738 assert(topology);
739 CellType cell_type = topology->cell_type();
740 if ((cell_type == CellType::prism or cell_type == CellType::pyramid)
741 and dim == 2)
742 {
743 throw std::runtime_error("mesh::entities_to_geometry for prism/pyramid "
744 "cell facets not yet supported.");
745 }
746
747 const int tdim = topology->dim();
748 const Geometry<T>& geometry = mesh.geometry();
749 auto xdofs = geometry.dofmaps().front();
750
751 // Get the DOF layout and the number of DOFs per entity
752 const fem::CoordinateElement<T>& coord_ele = geometry.cmaps().front();
753 const fem::ElementDofLayout layout = coord_ele.create_dof_layout();
754 const std::size_t num_entity_dofs = layout.entity_closure_dofs(dim, 0).size();
755 std::vector<std::int32_t> entity_xdofs;
756 entity_xdofs.reserve(entities.size() * num_entity_dofs);
757 std::array<std::size_t, 2> eshape{entities.size(), num_entity_dofs};
758
759 // Get the element's closure DOFs
760 const std::vector<std::vector<std::vector<int>>>& closure_dofs_all
761 = layout.entity_closure_dofs_all();
762
763 // Special case when dim == tdim (cells)
764 if (dim == tdim)
765 {
766 for (std::int32_t c : entities)
767 {
768 // Extract degrees of freedom
769 auto x_c = md::submdspan(xdofs, c, md::full_extent);
770 for (std::int32_t entity_dof : closure_dofs_all[tdim][0])
771 entity_xdofs.push_back(x_c[entity_dof]);
772 }
773
774 return {std::move(entity_xdofs), eshape};
775 }
776
777 assert(dim != tdim);
778
779 auto e_to_c = topology->connectivity(dim, tdim);
780 if (!e_to_c)
781 {
782 throw std::runtime_error(std::format(
783 "Entity-to-cell connectivity has not been computed. Missing dims "
784 "{}->{}",
785 dim, tdim));
786 }
787
788 auto c_to_e = topology->connectivity(tdim, dim);
789 if (!c_to_e)
790 {
791 throw std::runtime_error(std::format(
792 "Cell-to-entity connectivity has not been computed. Missing dims "
793 "{}->{}",
794 tdim, dim));
795 }
796
797 // Get the cell info, which is needed to permute the closure dofs
798 std::span<const std::uint32_t> cell_info;
799 if (permute)
800 cell_info = std::span(mesh.topology()->get_cell_permutation_info());
801
802 for (std::int32_t e : entities)
803 {
804 // Get a cell connected to the entity
805 assert(!e_to_c->links(e).empty());
806 std::int32_t c = e_to_c->links(e).front();
807
808 // Get the local index of the entity
809 std::span<const std::int32_t> cell_entities = c_to_e->links(c);
810 auto it = std::find(cell_entities.begin(), cell_entities.end(), e);
811 assert(it != cell_entities.end());
812 std::size_t local_entity = std::ranges::distance(cell_entities.begin(), it);
813
814 // Cell sub-entities must be permuted so that their local
815 // orientation agrees with their global orientation
816 std::vector<std::int32_t> closure_dofs(closure_dofs_all[dim][local_entity]);
817 if (permute)
818 {
819 mesh::CellType entity_type
820 = mesh::cell_entity_type(cell_type, dim, local_entity);
821 coord_ele.permute_subentity_closure(closure_dofs, cell_info[c],
822 entity_type, local_entity);
823 }
824
825 // Extract degrees of freedom
826 auto x_c = md::submdspan(xdofs, c, md::full_extent);
827 for (std::int32_t entity_dof : closure_dofs)
828 entity_xdofs.push_back(x_c[entity_dof]);
829 }
830
831 return {std::move(entity_xdofs), eshape};
832}
833
841std::vector<std::int32_t>
842compute_incident_entities(const Topology& topology,
843 std::span<const std::int32_t> entities, int d0,
844 int d1);
845
846namespace impl
847{
872template <std::floating_point T>
873std::vector<double>
875 std::span<const int> num_vertices_per_cell,
876 const std::vector<std::span<const std::int64_t>>& cells,
877 MPI_Comm commg, std::span<const T> x, int gdim)
878{
879 // Vertices of the cells on this rank, sorted and with duplicates
880 // removed, and the coordinates for them
881 std::vector<std::int64_t> nodes;
882 {
883 std::size_t size = 0;
884 for (std::span<const std::int64_t> c : cells)
885 size += c.size();
886 nodes.reserve(size);
887 for (std::span<const std::int64_t> c : cells)
888 nodes.insert(nodes.end(), c.begin(), c.end());
889 dolfinx::radix_sort(nodes);
890 auto [unique_end, range_end] = std::ranges::unique(nodes);
891 nodes.erase(unique_end, range_end);
892 }
893 const std::vector<T> coords
894 = dolfinx::MPI::distribute_data(comm, nodes, commg, x, gdim);
895
896 // Hash map from global vertex index to its position in `nodes` (and
897 // so its row in `coords`), turning the many repeated cell-vertex
898 // lookups below into an O(1) average lookup rather than an
899 // O(log(nodes.size())) binary search each time -- most vertices are
900 // shared by several cells, so the same key is looked up repeatedly.
901 boost::unordered_flat_map<std::int64_t, std::size_t> node_to_pos;
902 node_to_pos.reserve(nodes.size());
903 for (std::size_t i = 0; i < nodes.size(); ++i)
904 node_to_pos.emplace(nodes[i], i);
905
906 // Cell 'centroids', i.e. the mean of the cell vertex positions
907 std::size_t num_cells = 0;
908 for (std::size_t i = 0; i < cells.size(); ++i)
909 num_cells += cells[i].size() / num_vertices_per_cell[i];
910 std::vector<double> centroid(gdim * num_cells, 0);
911
912 std::size_t c0 = 0;
913 for (std::size_t i = 0; i < cells.size(); ++i)
914 {
915 const int nv = num_vertices_per_cell[i];
916 const double w = 1.0 / nv;
917 for (std::size_t c = 0; c < cells[i].size() / nv; ++c)
918 {
919 for (int v = 0; v < nv; ++v)
920 {
921 auto it = node_to_pos.find(cells[i][nv * c + v]);
922 assert(it != node_to_pos.end());
923 std::size_t pos = it->second;
924 for (int d = 0; d < gdim; ++d)
925 centroid[gdim * (c0 + c) + d] += w * coords[gdim * pos + d];
926 }
927 }
928
929 c0 += cells[i].size() / nv;
930 }
931
932 return centroid;
933}
934
983template <std::floating_point T>
984std::tuple<std::vector<std::vector<std::int64_t>>,
985 std::vector<std::vector<std::int64_t>>,
986 std::vector<std::vector<int>>>
987partition_cells(MPI_Comm comm, MPI_Comm commt,
988 const std::vector<std::span<const std::int64_t>>& cells,
989 const std::vector<CellType>& celltypes,
990 const std::vector<fem::ElementDofLayout>& doflayouts,
991 bool p1_geometry, const graph::Partitioner& partitioner,
992 bool ghosting,
993 std::optional<std::int32_t> max_facet_to_cell_links,
994 int num_threads, MPI_Comm commg, std::span<const T> x,
995 std::array<std::size_t, 2> xshape)
996{
997 const std::int32_t num_cell_types = cells.size();
998 std::vector<std::vector<std::int64_t>> cells1(num_cell_types);
999 std::vector<std::vector<std::int64_t>> original_idx1(num_cell_types);
1000 std::vector<std::vector<int>> ghost_owners(num_cell_types);
1001 if (graph::has_partitioner(partitioner.fn))
1002 {
1003 spdlog::info("Using partitioner with cell data ({} cell types)",
1004 num_cell_types);
1006 int failed = 0;
1007 std::string error_msg;
1008
1009 // Geometric data can be distributed on ranks that do not participate in
1010 // topology partitioning. Gather cell centroids collectively over `comm`
1011 // so that every rank in `commg` participates in the coordinate exchange.
1012 std::vector<double> centroid;
1013 const bool needs_centroids
1014 = std::holds_alternative<graph::geom_partition_fn>(partitioner.fn)
1015 or std::holds_alternative<graph::hybrid_partition_fn>(partitioner.fn);
1016 std::vector<std::vector<std::int64_t>> topology(num_cell_types);
1017 std::vector<std::span<const std::int64_t>> topology_view(num_cell_types);
1018 if (needs_centroids or commt != MPI_COMM_NULL)
1019 {
1020 for (std::int32_t i = 0; i < num_cell_types; ++i)
1021 {
1022 if (p1_geometry)
1023 topology_view[i] = cells[i];
1024 else
1025 {
1026 topology[i] = extract_topology(celltypes[i], doflayouts[i], cells[i]);
1027 topology_view[i] = topology[i];
1028 }
1029 }
1030 }
1031
1032 if (needs_centroids)
1033 {
1034 std::vector<int> num_vertices_per_cell;
1035 std::ranges::transform(celltypes,
1036 std::back_inserter(num_vertices_per_cell),
1037 [](CellType c) { return num_cell_vertices(c); });
1038 centroid = compute_cell_centroids(comm, num_vertices_per_cell,
1039 topology_view, commg, x, xshape[1]);
1040 }
1041
1042 if (std::holds_alternative<graph::geom_partition_fn>(partitioner.fn))
1043 {
1044 try
1045 {
1046 int size = dolfinx::MPI::size(comm);
1047 const auto& p = std::get<graph::geom_partition_fn>(partitioner.fn);
1049 p(comm, size, std::span<const double>(centroid), xshape[1],
1050 partitioner.node_weights),
1051 1);
1052 }
1053 catch (const std::exception& e)
1054 {
1055 failed = 1;
1056 error_msg = e.what();
1057 }
1058 }
1059
1060 if (commt != MPI_COMM_NULL)
1061 {
1062 try
1063 {
1064 int size = dolfinx::MPI::size(comm);
1065 // Shared by the graph::partition_fn and
1066 // graph::hybrid_partition_fn alternatives below: neither has any
1067 // other way to obtain the mesh dual graph.
1068 auto dual_graph = [&]() -> graph::AdjacencyList<std::int64_t>
1069 {
1070 return build_dual_graph(commt, celltypes, topology_view,
1071 max_facet_to_cell_links, num_threads);
1072 };
1073
1074 dest = std::visit(
1075 [&](const auto& p) -> graph::AdjacencyList<std::int32_t>
1076 {
1077 using P = std::decay_t<decltype(p)>;
1078 if constexpr (std::is_same_v<P, graph::hybrid_partition_fn>)
1079 {
1080 return p(commt, size, dual_graph(),
1081 std::span<const double>(centroid),
1082 partitioner.node_weights, std::nullopt, ghosting);
1083 }
1084 else if constexpr (std::is_same_v<P, graph::partition_fn>)
1085 {
1086 return p(commt, size, dual_graph(), partitioner.node_weights,
1087 std::nullopt, ghosting);
1088 }
1089 else
1090 return dest;
1091 },
1092 partitioner.fn);
1093 }
1094 catch (const std::exception& e)
1095 {
1096 // A partitioner such as graph::parmetis::geom_partitioner (which
1097 // requires nparts to equal the number of ranks calling it) can
1098 // throw only on the ranks with commt != MPI_COMM_NULL, which may
1099 // be a strict subset of comm (e.g. cells built on rank 0 only).
1100 // Turn that into a comm-wide decision below before any rank
1101 // reaches the graph::build::distribute collective, or a throw
1102 // here would leave the rest of comm blocked on it forever.
1103 failed = 1;
1104 error_msg = e.what();
1105 }
1106 }
1107
1108 int any_failed = 0;
1109 MPI_Allreduce(&failed, &any_failed, 1, MPI_INT, MPI_MAX, comm);
1110 if (any_failed)
1111 {
1112 throw std::runtime_error(
1113 failed ? "Cell partitioning failed: " + error_msg
1114 : "Cell partitioning failed on another rank.");
1115 }
1116
1117 std::int32_t cell_offset = 0;
1118 for (std::int32_t i = 0; i < num_cell_types; ++i)
1119 {
1120 std::size_t num_cell_nodes = doflayouts[i].num_dofs();
1121 if (cells[i].size() % num_cell_nodes != 0)
1122 {
1123 throw std::runtime_error("Cell array size is not a multiple of the "
1124 "number of nodes per cell.");
1125 }
1126 std::size_t num_cells = cells[i].size() / num_cell_nodes;
1127
1128 // Extract destination AdjacencyList for this cell type
1129 std::vector<std::int32_t> offsets_i(
1130 std::next(dest.offsets().begin(), cell_offset),
1131 std::next(dest.offsets().begin(), cell_offset + num_cells + 1));
1132 std::vector<std::int32_t> data_i(
1133 std::next(dest.array().begin(), offsets_i.front()),
1134 std::next(dest.array().begin(), offsets_i.back()));
1135 const std::int32_t offset_0 = offsets_i.front();
1136 std::ranges::transform(offsets_i, offsets_i.begin(),
1137 [offset_0](std::int32_t j)
1138 { return j - offset_0; });
1139 graph::AdjacencyList<std::int32_t> dest_i(data_i, offsets_i);
1140 cell_offset += num_cells;
1141
1142 // Distribute cells (topology, includes higher-order 'nodes') to
1143 // destination rank
1144 std::vector<int> src_ranks;
1145 std::tie(cells1[i], src_ranks, original_idx1[i], ghost_owners[i])
1146 = graph::build::distribute(comm, cells[i],
1147 {num_cells, num_cell_nodes}, dest_i);
1148 spdlog::debug("Got {} cells from distribution", cells1[i].size());
1149 }
1150 }
1151 else // No partitioning: keep cells on their current rank
1152 {
1153 // Count cells of each type on this rank. Each cell still needs a
1154 // globally unique index (assigned below), even though it is not
1155 // being redistributed, and the counts are needed first to size
1156 // `original_idx1` and to determine this rank's share via the
1157 // exclusive scan that follows.
1158 std::int64_t num_owned = 0;
1159 for (std::int32_t i = 0; i < num_cell_types; ++i)
1160 {
1161 cells1[i] = std::vector<std::int64_t>(cells[i].begin(), cells[i].end());
1162 std::int32_t num_cell_nodes = doflayouts[i].num_dofs();
1163 if (cells1[i].size() % num_cell_nodes != 0)
1164 {
1165 throw std::runtime_error("Cell array size is not a multiple of the "
1166 "number of nodes per cell.");
1167 }
1168 original_idx1[i].resize(cells1[i].size() / num_cell_nodes);
1169 num_owned += original_idx1[i].size();
1170 }
1171
1172 // Assign a globally unique index to each cell. `global_offset`
1173 // starts as the number of cells owned by lower-ranked processes
1174 // (from the exclusive scan), and is advanced by each cell type's
1175 // count in turn so that the numbering is contiguous across cell
1176 // types too.
1177 std::int64_t global_offset = 0;
1178 MPI_Exscan(&num_owned, &global_offset, 1, MPI_INT64_T, MPI_SUM, comm);
1179 for (std::int32_t i = 0; i < num_cell_types; ++i)
1180 {
1181 std::iota(original_idx1[i].begin(), original_idx1[i].end(),
1182 global_offset);
1183 global_offset += original_idx1[i].size();
1184 }
1185 }
1186
1187 return {std::move(cells1), std::move(original_idx1), std::move(ghost_owners)};
1188}
1189} // namespace impl
1190
1245template <typename U>
1247 MPI_Comm comm, MPI_Comm commt,
1248 std::vector<std::span<const std::int64_t>> cells,
1249 const std::vector<fem::CoordinateElement<
1250 typename std::remove_reference_t<typename U::value_type>>>& elements,
1251 MPI_Comm commg, const U& x, std::array<std::size_t, 2> xshape,
1252 const graph::Partitioner& partitioner, GhostMode ghost_mode,
1253 std::optional<std::int32_t> max_facet_to_cell_links, int num_threads,
1254 const CellReorderFunction& reorder_fn = graph::reorder_rcm)
1255{
1256 using T = typename std::remove_reference_t<typename U::value_type>;
1257
1258 if (cells.size() != elements.size())
1259 throw std::runtime_error("Number of cell arrays and elements must match.");
1260 std::vector<CellType> celltypes;
1261 std::ranges::transform(elements, std::back_inserter(celltypes),
1262 [](auto& e) { return e.cell_shape(); });
1263 std::vector<fem::ElementDofLayout> doflayouts;
1264 std::ranges::transform(elements, std::back_inserter(doflayouts),
1265 [](auto& e) { return e.create_dof_layout(); });
1266
1267 // Note: `extract_topology` extracts topology data, i.e. just the
1268 // vertices. For other elements the filtered lists may have 'gaps',
1269 // i.e. the indices might not be contiguous.
1270 //
1271 // For 'P1 geometry' the extraction is the identity operator, and cell
1272 // node data is used directly as cell topology. This avoids copies of
1273 // the (large) cell array, and lets the geometry node indices be taken
1274 // from the topology vertices rather than re-derived by sorting the
1275 // cell array (see below).
1276 const bool p1_geometry = std::ranges::all_of(
1277 std::views::iota(std::size_t(0), elements.size()),
1278 [&celltypes, &doflayouts](std::size_t i)
1279 { return is_vertex_dof_layout(celltypes[i], doflayouts[i]); });
1280
1281 const std::int32_t num_cell_types = cells.size();
1282
1283 // Partition cells across ranks of `comm` (or, if `partitioner` is not
1284 // callable, keep them on their current rank and just assign each a
1285 // globally unique index)
1286 const bool ghosting = (ghost_mode != GhostMode::none);
1287 auto [cells1, original_idx1, ghost_owners] = impl::partition_cells(
1288 comm, commt, cells, celltypes, doflayouts, p1_geometry, partitioner,
1289 ghosting, max_facet_to_cell_links, num_threads, commg,
1290 std::span<const T>(x), xshape);
1291
1292 // Extract cell 'topology', i.e. extract the vertices for each cell
1293 // and discard any 'higher-order' nodes. `cells1_v_storage` is empty
1294 // for 'P1 geometry', where `cells1_v` views `cells1` directly.
1295 std::vector<std::vector<std::int64_t>> cells1_v_storage(num_cell_types);
1296 std::vector<std::span<std::int64_t>> cells1_v(num_cell_types);
1297 for (std::int32_t i = 0; i < num_cell_types; ++i)
1298 {
1299 if (p1_geometry)
1300 cells1_v[i] = cells1[i];
1301 else
1302 {
1303 cells1_v_storage[i]
1304 = extract_topology(celltypes[i], doflayouts[i], cells1[i]);
1305 cells1_v[i] = cells1_v_storage[i];
1306 }
1307
1308 spdlog::info("Extract basic topology: {}->{}", cells1[i].size(),
1309 cells1_v[i].size());
1310 }
1311
1312 // Re-order cells and get boundary vertices. The re-ordering is done
1313 // on the cell topology, i.e. the vertex indices, and the higher-order
1314 // nodes are re-ordered accordingly.
1315 const std::vector<std::int64_t> boundary_v = impl::reorder_cells(
1316 reorder_fn, max_facet_to_cell_links, celltypes, doflayouts, ghost_owners,
1317 cells1, cells1_v, original_idx1, num_threads);
1318
1319 spdlog::debug("Got {} boundary vertices", boundary_v.size());
1320
1321 // Create Topology
1322 std::vector<std::span<const std::int64_t>> cells1_v_span(cells1_v.begin(),
1323 cells1_v.end());
1324 std::vector<std::span<const std::int64_t>> original_idx1_span;
1325 std::ranges::transform(original_idx1, std::back_inserter(original_idx1_span),
1326 [](auto& c) { return std::span(c); });
1327 std::vector<std::span<const int>> ghost_owners_span;
1328 std::ranges::transform(ghost_owners, std::back_inserter(ghost_owners_span),
1329 [](auto& c) { return std::span(c); });
1330
1331 // Note: `vertex_index` holds the sorted input global indices of the
1332 // topology vertices, which for 'P1 geometry' are exactly the geometry
1333 // node indices required below.
1334 auto [topology, vertex_index] = mesh::impl::create_topology(
1335 comm, celltypes, cells1_v_span, original_idx1_span, ghost_owners_span,
1336 boundary_v, num_threads);
1337
1338 // Create connectivities required higher-order geometries for creating
1339 // a Geometry object
1340 for (int i = 0; i < num_cell_types; ++i)
1341 {
1342 const auto& entity_dofs = doflayouts[i].entity_dofs_all();
1343 for (int dim = 1; dim < topology.dim(); ++dim)
1344 {
1345 // Accumulate count of all dofs on this dimension
1346 int dim_sum
1347 = std::accumulate(entity_dofs[dim].begin(), entity_dofs[dim].end(), 0,
1348 [](int c, auto v) { return c + v.size(); });
1349
1350 spdlog::debug("Counting entity dofs, dim={}: {}", dim, dim_sum);
1351 if (dim_sum > 0)
1352 topology.create_entities(dim);
1353 }
1354
1355 if (elements[i].needs_dof_permutations())
1356 topology.create_entity_permutations();
1357 }
1358
1359 // Cell 'node' indices (global), as a single flat array. This is
1360 // `cells1` for a single cell type, and concatenated otherwise.
1361 std::vector<std::int64_t> nodes2_storage;
1362 std::span<const std::int64_t> nodes2;
1363 if (num_cell_types == 1)
1364 nodes2 = cells1.front();
1365 else
1366 {
1367 std::size_t size = 0;
1368 for (const std::vector<std::int64_t>& c : cells1)
1369 size += c.size();
1370 nodes2_storage.reserve(size);
1371 for (const std::vector<std::int64_t>& c : cells1)
1372 nodes2_storage.insert(nodes2_storage.end(), c.begin(), c.end());
1373 nodes2 = nodes2_storage;
1374 }
1375
1376 // Sorted list of unique (global) node indices. For 'P1 geometry' the
1377 // nodes are the vertices, which `create_topology` has already sorted
1378 // and made unique, so re-deriving them from the (much larger) cell
1379 // array is avoided.
1380 std::vector<std::int64_t> nodes1;
1381 if (p1_geometry)
1382 nodes1 = std::move(vertex_index);
1383 else
1384 {
1385 nodes1.assign(nodes2.begin(), nodes2.end());
1386 dolfinx::radix_sort(nodes1);
1387 auto [unique_end, range_end] = std::ranges::unique(nodes1);
1388 nodes1.erase(unique_end, range_end);
1389 }
1390
1391 std::vector coords
1392 = dolfinx::MPI::distribute_data(comm, nodes1, commg, x, xshape[1]);
1393
1394 // Create geometry object
1396 = create_geometry(topology, elements, nodes1, nodes2, coords, xshape[1]);
1397
1398 return Mesh(comm, std::make_shared<Topology>(std::move(topology)),
1399 std::move(geometry));
1400}
1401
1448template <typename U>
1450 MPI_Comm comm, MPI_Comm commt, std::span<const std::int64_t> cells,
1452 typename std::remove_reference_t<typename U::value_type>>& element,
1453 MPI_Comm commg, const U& x, std::array<std::size_t, 2> xshape,
1454 const graph::Partitioner& partitioner, GhostMode ghost_mode,
1455 std::optional<std::int32_t> max_facet_to_cell_links, int num_threads,
1456 const CellReorderFunction& reorder_fn = graph::reorder_rcm)
1457{
1458 return create_mesh(comm, commt, std::vector{cells}, std::vector{element},
1459 commg, x, xshape, partitioner, ghost_mode,
1460 max_facet_to_cell_links, num_threads, reorder_fn);
1461}
1462
1483template <typename U>
1484Mesh<typename std::remove_reference_t<typename U::value_type>>
1485create_mesh(MPI_Comm comm, std::span<const std::int64_t> cells,
1487 std::remove_reference_t<typename U::value_type>>& elements,
1488 const U& x, std::array<std::size_t, 2> xshape, GhostMode ghost_mode,
1489 std::optional<std::int32_t> max_facet_to_cell_links = 2)
1490{
1491 // A single rank has nothing to partition, so skip the default
1492 // partitioner and just assign global indices.
1493 graph::Partitioner partitioner
1494 = dolfinx::MPI::size(comm) == 1
1495 ? graph::Partitioner{.fn = graph::partition_fn(nullptr)}
1497 return create_mesh(comm, comm, std::vector{cells}, std::vector{elements},
1498 comm, x, xshape, partitioner, ghost_mode,
1499 max_facet_to_cell_links, 1);
1500}
1501
1515template <std::floating_point T>
1516std::pair<Geometry<T>, std::vector<int32_t>>
1518 std::span<const std::int32_t> subentity_to_entity)
1519{
1520 const Geometry<T>& geometry = mesh.geometry();
1521
1522 // Get the geometry dofs in the sub-geometry based on the entities in
1523 // sub-geometry
1524 const fem::ElementDofLayout layout
1525 = geometry.cmaps().front().create_dof_layout();
1526
1527 const std::vector<std::int32_t> x_indices
1528 = entities_to_geometry(mesh, dim, subentity_to_entity, true).first;
1529
1530 std::vector<std::int32_t> sub_x_dofs = x_indices;
1531 std::ranges::sort(sub_x_dofs);
1532 auto [unique_end, range_end] = std::ranges::unique(sub_x_dofs);
1533 sub_x_dofs.erase(unique_end, range_end);
1534
1535 // Get the sub-geometry dofs owned by this process
1536 auto x_index_map = geometry.index_map();
1537 assert(x_index_map);
1538
1539 std::shared_ptr<common::IndexMap> sub_x_dof_index_map;
1540 std::vector<std::int32_t> subx_to_x_dofmap;
1541 {
1542 auto [map, new_to_old] = common::create_sub_index_map(
1543 *x_index_map, sub_x_dofs, common::IndexMapOrder::any, true);
1544 sub_x_dof_index_map = std::make_shared<common::IndexMap>(std::move(map));
1545 subx_to_x_dofmap = std::move(new_to_old);
1546 }
1547
1548 // Create sub-geometry coordinates
1549 std::span<const T> x = geometry.x();
1550 std::int32_t sub_num_x_dofs = subx_to_x_dofmap.size();
1551 std::vector<T> sub_x(3 * sub_num_x_dofs);
1552 for (std::int32_t i = 0; i < sub_num_x_dofs; ++i)
1553 {
1554 std::copy_n(std::next(x.begin(), 3 * subx_to_x_dofmap[i]), 3,
1555 std::next(sub_x.begin(), 3 * i));
1556 }
1557
1558 // Create geometry to sub-geometry map
1559 std::vector<std::int32_t> x_to_subx_dof_map(
1560 x_index_map->size_local() + x_index_map->num_ghosts(), -1);
1561 for (std::size_t i = 0; i < subx_to_x_dofmap.size(); ++i)
1562 x_to_subx_dof_map[subx_to_x_dofmap[i]] = i;
1563
1564 // Create sub-geometry dofmap
1565 std::vector<std::int32_t> sub_x_dofmap;
1566 sub_x_dofmap.reserve(x_indices.size());
1567 std::ranges::transform(x_indices, std::back_inserter(sub_x_dofmap),
1568 [&x_to_subx_dof_map](auto x_dof)
1569 {
1570 assert(x_to_subx_dof_map[x_dof] != -1);
1571 return x_to_subx_dof_map[x_dof];
1572 });
1573
1574 // Sub-geometry coordinate element
1575 CellType sub_xcell
1576 = cell_entity_type(geometry.cmaps().front().cell_shape(), dim, 0);
1577
1578 // Special handling of point meshes, as they only support constant
1579 // basis functions
1580 int degree
1581 = (sub_xcell == CellType::point) ? 0 : geometry.cmaps().front().degree();
1582 fem::CoordinateElement<T> sub_cmap(sub_xcell, degree,
1583 geometry.cmaps().front().variant());
1584
1585 // Sub-geometry input_global_indices
1586 const std::vector<std::int64_t>& igi = geometry.input_global_indices();
1587 std::vector<std::int64_t> sub_igi;
1588 sub_igi.reserve(subx_to_x_dofmap.size());
1589 std::ranges::transform(subx_to_x_dofmap, std::back_inserter(sub_igi),
1590 [&igi](auto sub_x_dof) { return igi[sub_x_dof]; });
1591
1592 // Create geometry
1593 return {Geometry(
1594 sub_x_dof_index_map,
1595 std::vector<std::vector<std::int32_t>>{std::move(sub_x_dofmap)},
1596 {sub_cmap}, std::move(sub_x), geometry.dim(), std::move(sub_igi)),
1597 std::move(subx_to_x_dofmap)};
1598}
1599
1609template <std::floating_point T>
1610std::tuple<Mesh<T>, EntityMap, EntityMap, std::vector<std::int32_t>>
1612 std::span<const std::int32_t> entities)
1613{
1614 // Create sub-topology
1615 mesh.topology_mutable()->create_connectivity(dim, 0);
1616 auto [topology, subentity_to_entity, subvertex_to_vertex]
1617 = mesh::create_subtopology(*mesh.topology(), dim, entities);
1618
1619 // Create sub-geometry
1620 const int tdim = mesh.topology()->dim();
1621 mesh.topology_mutable()->create_entities(dim);
1622 mesh.topology_mutable()->create_connectivity(dim, tdim);
1623 mesh.topology_mutable()->create_connectivity(tdim, dim);
1624 mesh.topology_mutable()->create_entity_permutations();
1625 auto [geometry, subx_to_x_dofmap]
1626 = mesh::create_subgeometry(mesh, dim, subentity_to_entity);
1627
1628 Mesh<T> submesh
1629 = Mesh(mesh.comm(), std::make_shared<Topology>(std::move(topology)),
1630 std::move(geometry));
1631 EntityMap entity_map(mesh.topology(), submesh.topology(), dim,
1632 subentity_to_entity);
1633 EntityMap vertex_map(mesh.topology(), submesh.topology(), 0,
1634 subvertex_to_vertex);
1635 return {std::move(submesh), std::move(entity_map), std::move(vertex_map),
1636 std::move(subx_to_x_dofmap)};
1637}
1638
1646template <typename T>
1648 const MeshTags<T>& tags,
1649 std::shared_ptr<const dolfinx::mesh::Topology> submesh_topology,
1650 const EntityMap& vertex_map, const EntityMap& cell_map)
1651{
1652 int tag_dim = tags.dim();
1653 int submesh_tdim = submesh_topology->dim();
1654 auto topology = tags.topology();
1655 if (tag_dim > submesh_tdim)
1656 {
1657 throw std::runtime_error("Tag dimension must be less than or equal to "
1658 "submesh dimension");
1659 }
1660 std::shared_ptr<const dolfinx::common::IndexMap> sub_cell_imap
1661 = submesh_topology->index_map(submesh_tdim);
1662 if (!sub_cell_imap)
1663 {
1664 throw std::runtime_error(
1665 std::format("Entities of dimension {} does not exist in mesh topology.",
1666 submesh_tdim));
1667 }
1668
1669 // Create a map from parent entity to submesh cell
1670 std::int32_t submesh_num_cells
1671 = sub_cell_imap->size_local() + sub_cell_imap->num_ghosts();
1672 auto sub_cells = std::ranges::views::iota(0, submesh_num_cells);
1673 std::vector<std::int32_t> sub_cell_to_parent_entity
1674 = cell_map.sub_topology_to_topology(sub_cells, false);
1675
1676 // Create a full lookup for all cells on the parent mesh, as the tag can have
1677 // entities that are not in the submesh
1678 auto parent_entity_imap = topology->index_map(submesh_tdim);
1679 if (!parent_entity_imap)
1680 {
1681 throw std::runtime_error(std::format(
1682 "Entities of dimension {} does not exist in parent mesh topology.",
1683 submesh_tdim));
1684 }
1685 std::size_t num_parent_entities
1686 = parent_entity_imap->size_local() + parent_entity_imap->num_ghosts();
1687 std::vector<std::int32_t> parent_entity_to_sub_cell(num_parent_entities, -1);
1688 for (std::size_t i = 0; i < sub_cell_to_parent_entity.size(); ++i)
1689 parent_entity_to_sub_cell[sub_cell_to_parent_entity[i]]
1690 = static_cast<std::int32_t>(i);
1691
1692 // Get map from submesh vertex to parent vertex
1693 std::vector<std::int32_t> sub_to_parent_vertex;
1694 {
1695 auto sub_vertex_map = submesh_topology->index_map(0);
1696 std::int32_t num_sub_vertices
1697 = sub_vertex_map->size_local() + sub_vertex_map->num_ghosts();
1698 auto sub_vertices = std::ranges::views::iota(0, num_sub_vertices);
1699
1700 sub_to_parent_vertex
1701 = vertex_map.sub_topology_to_topology(sub_vertices, false);
1702 }
1703 // Access various connectivity maps
1704 auto sub_e_to_v = submesh_topology->connectivity(tag_dim, 0);
1705 auto sub_c_to_e = submesh_topology->connectivity(submesh_tdim, tag_dim);
1706 auto sub_entity_imap = submesh_topology->index_map(tag_dim);
1707 auto e_to_v = topology->connectivity(tag_dim, 0);
1708 std::shared_ptr<const dolfinx::graph::AdjacencyList<std::int32_t>>
1709 e_to_sub_cell = nullptr;
1710 if (tag_dim != submesh_tdim)
1711 {
1712 e_to_sub_cell = topology->connectivity(tag_dim, submesh_tdim);
1713 if (!e_to_sub_cell)
1714 {
1715 throw std::runtime_error(
1716 std::format("Missing connectivity between {} and {} in parent mesh",
1717 tag_dim, submesh_tdim));
1718 }
1719 }
1720
1721 if (!sub_e_to_v)
1722 {
1723 throw std::runtime_error(std::format(
1724 "Missing connectivity between {} and {} in submesh", tag_dim, 0));
1725 }
1726 if (!sub_c_to_e)
1727 {
1728 throw std::runtime_error(
1729 std::format("Missing connectivity between {} and {} in submesh",
1730 submesh_tdim, tag_dim));
1731 }
1732 if (!sub_entity_imap)
1733 {
1734 throw std::runtime_error(std::format(
1735 "Entities of dimension {} does not exist in submesh topology.",
1736 tag_dim));
1737 }
1738 if (!e_to_v)
1739 {
1740 throw std::runtime_error(
1741 std::format("Missing connectivity between {} and 0", tag_dim));
1742 }
1743
1744 // Prepare sub entity to parent map
1745 std::size_t num_sub_entities
1746 = sub_entity_imap->size_local() + sub_entity_imap->num_ghosts();
1747 constexpr T max_val = std::numeric_limits<T>::max();
1748 std::vector<T> submesh_values(num_sub_entities, max_val);
1749 std::vector<std::int32_t> submesh_indices(num_sub_entities);
1750 std::iota(submesh_indices.begin(), submesh_indices.end(), 0);
1751
1752 std::span<const std::int32_t> tagged_entities = tags.indices();
1753 std::span<const T> tagged_values = tags.values();
1754 // For each entity in the tag, find all cells of the submesh connected to this
1755 // entity
1756 for (std::size_t i = 0; i < tagged_entities.size(); ++i)
1757 {
1758 auto find_and_map_sub_entity
1759 = [tag_dim, submesh_tdim, &e_to_v, &parent_entity_to_sub_cell,
1760 &sub_to_parent_vertex, &sub_e_to_v, &sub_c_to_e,
1761 &e_to_sub_cell](std::int32_t entity)
1762 {
1763 // Fast exit if the tag dimension is the same as the submesh dimension,
1764 // as we can directly map the parent entity to the submesh cell
1765 if (tag_dim == submesh_tdim)
1766 return parent_entity_to_sub_cell[entity];
1767
1768 // Given an entity in the parent meshtag, find all submesh-cells that are
1769 // entities in parent mesh that contain this entity.
1770 auto entity_vertices = e_to_v->links(entity);
1771 auto parent_sub_cells = e_to_sub_cell->links(entity);
1772 auto submesh_cells
1773 = parent_sub_cells
1774 | std::views::transform([&parent_entity_to_sub_cell](auto c)
1775 { return parent_entity_to_sub_cell[c]; })
1776 | std::views::filter([](auto sub_cell) { return sub_cell != -1; });
1777 for (auto sub_cell : submesh_cells)
1778 {
1779 for (auto sub_entity : sub_c_to_e->links(sub_cell))
1780 {
1781 // Convert submesh entity vertices to parent vertices
1782 auto parent_vertices
1783 = sub_e_to_v->links(sub_entity)
1784 | std::views::transform([&sub_to_parent_vertex](auto v)
1785 { return sub_to_parent_vertex[v]; });
1786
1787 // Check if all parent vertices of the submesh entity are in the
1788 // parent entity
1789 bool entity_matches = std::ranges::all_of(
1790 parent_vertices,
1791 [&entity_vertices](auto p_v)
1792 {
1793 // With C++23 this can use std::ranges::contains
1794 return std::ranges::find(entity_vertices, p_v)
1795 != std::ranges::end(entity_vertices);
1796 });
1797
1798 // If a match is found, apply values and exit the lambda immediately
1799 if (entity_matches)
1800 return sub_entity;
1801 }
1802 }
1803 return -1;
1804 };
1805
1806 // Execute the search for the current entity
1807 std::int32_t sub_entity = find_and_map_sub_entity(tagged_entities[i]);
1808 if (sub_entity != -1)
1809 submesh_values[sub_entity] = tagged_values[i];
1810 }
1811
1812 // Filter out the entities that were never mapped (values still equal max)
1813 std::vector<std::int32_t> filtered_indices;
1814 std::vector<T> filtered_values;
1815 filtered_indices.reserve(num_sub_entities);
1816 filtered_values.reserve(num_sub_entities);
1817 for (std::size_t i = 0; i < submesh_values.size(); ++i)
1818 {
1819 if (submesh_values[i] != max_val)
1820 {
1821 filtered_indices.push_back(submesh_indices[i]);
1822 filtered_values.push_back(submesh_values[i]);
1823 }
1824 }
1825 filtered_indices.shrink_to_fit();
1826 filtered_values.shrink_to_fit();
1827 MeshTags<T> new_meshtag(submesh_topology, tag_dim, filtered_indices,
1828 filtered_values, tags.name());
1829 return new_meshtag;
1830}
1831
1832} // namespace dolfinx::mesh
Definition CoordinateElement.h:38
ElementDofLayout create_dof_layout() const
Compute and return the dof layout.
Definition CoordinateElement.cpp:79
void permute_subentity_closure(std::span< std::int32_t > d, std::uint32_t cell_info, mesh::CellType entity_type, int entity_index) const
Given the closure DOFs of a cell sub-entity in reference ordering, this function computes the permut...
Definition CoordinateElement.cpp:68
Definition ElementDofLayout.h:31
const std::vector< int > & entity_closure_dofs(int dim, int entity_index) const
Definition ElementDofLayout.cpp:65
const std::vector< std::vector< std::vector< int > > > & entity_closure_dofs_all() const
Definition ElementDofLayout.cpp:77
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
const std::vector< std::int32_t > & offsets() const
Offset for each node in array() (const version).
Definition AdjacencyList.h:194
A bidirectional map relating entities in one topology to another.
Definition EntityMap.h:22
std::vector< std::int32_t > sub_topology_to_topology(CellRange auto &&entities, bool inverse) const
Map entities between the sub-topology and the parent topology.
Definition EntityMap.h:104
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:37
MeshTags associate values with mesh topology entities.
Definition MeshTags.h:33
const std::string & name() const
Return name.
Definition MeshTags.h:115
std::span< const std::int32_t > indices() const
Definition MeshTags.h:103
std::span< const T > values() const
Values attached to topology entities.
Definition MeshTags.h:106
int dim() const
Return topological dimension of tagged entities.
Definition MeshTags.h:109
std::shared_ptr< const Topology > topology() const
Return topology.
Definition MeshTags.h:112
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()
Get mesh topology.
Definition Mesh.h:69
Requirements on function for geometry marking.
Definition utils.h:538
Small, foundational mesh types (enums, etc.) with minimal dependencies.
void reorder_list(std::span< T > list, std::span< const std::int32_t > nodemap)
Re-order the nodes of a fixed-degree adjacency list.
Definition utils.h:59
std::tuple< std::vector< std::int32_t >, std::vector< T >, std::vector< std::int32_t > > compute_vertex_coords_boundary(const mesh::Mesh< T > &mesh, int dim, std::span< const std::int32_t > facets)
Compute the coordinates of 'vertices' for entities of a given dimension that are attached to specifie...
Definition utils.h:90
std::tuple< std::vector< std::vector< std::int64_t > >, std::vector< std::vector< std::int64_t > >, std::vector< std::vector< int > > > partition_cells(MPI_Comm comm, MPI_Comm commt, const std::vector< std::span< const std::int64_t > > &cells, const std::vector< CellType > &celltypes, const std::vector< fem::ElementDofLayout > &doflayouts, bool p1_geometry, const graph::Partitioner &partitioner, bool ghosting, std::optional< std::int32_t > max_facet_to_cell_links, int num_threads, MPI_Comm commg, std::span< const T > x, std::array< std::size_t, 2 > xshape)
Partition cells across ranks of comm, or, if partitioner does not hold a callable function,...
Definition utils.h:987
std::pair< std::vector< T >, std::array< std::size_t, 2 > > compute_vertex_coords(const mesh::Mesh< T > &mesh)
The coordinates for all 'vertices' in the mesh.
Definition utils.h:492
std::vector< double > compute_cell_centroids(MPI_Comm comm, std::span< const int > num_vertices_per_cell, const std::vector< std::span< const std::int64_t > > &cells, MPI_Comm commg, std::span< const T > x, int gdim)
Compute the centroid of each cell from its vertex positions.
Definition utils.h:874
int size(MPI_Comm comm)
Definition MPI.cpp:81
std::vector< std::ranges::range_value_t< U > > distribute_data(MPI_Comm comm0, std::span< const std::int64_t > indices, MPI_Comm comm1, const U &x, int shape1)
Distribute rows of a row-major array to the ranks that require them, via the post office pattern.
Definition MPI.h:734
std::pair< IndexMap, std::vector< std::int32_t > > create_sub_index_map(const IndexMap &imap, std::span< const std::int32_t > indices, IndexMapOrder order=IndexMapOrder::any, bool allow_owner_change=false)
Create a new index map from a subset of indices in an existing index map.
Definition IndexMap.cpp:825
@ any
Allow arbitrary ordering of ghost indices in sub-maps.
Definition IndexMap.h:27
Finite element method functionality.
Definition assemble_expression_impl.h:24
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:24
std::tuple< graph::AdjacencyList< std::int64_t >, std::vector< int >, std::vector< std::int64_t >, std::vector< int > > distribute(MPI_Comm comm, const graph::AdjacencyList< std::int64_t > &list, const graph::AdjacencyList< std::int32_t > &destinations)
Distribute adjacency list nodes to destination ranks.
Definition partition.cpp:157
bool has_partitioner(const AnyPartitionFunction &partitioner)
Whether an AnyPartitionFunction holds a callable partitioner.
Definition partition.cpp:130
std::vector< std::int32_t > reorder_rcm(const graph::AdjacencyList< std::int32_t > &graph)
Re-order a graph using the Reverse Cuthill-McKee algorithm.
Definition ordering.cpp:149
AdjacencyList< typename std::decay_t< U >::value_type, V > regular_adjacency_list(U &&data, int degree)
Construct a constant degree (valency) adjacency list.
Definition AdjacencyList.h:262
std::function< graph::AdjacencyList< std::int32_t >( MPI_Comm, int, const AdjacencyList< std::int64_t > &, std::optional< std::span< const std::int32_t > >, std::optional< std::span< const std::int32_t > >, bool)> partition_fn
Signature of functions for computing the parallel partitioning of a distributed graph,...
Definition partition.h:38
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
graph::AdjacencyList< std::int64_t > build_dual_graph(MPI_Comm comm, std::span< const CellType > celltypes, const std::vector< std::span< const std::int64_t > > &cells, std::optional< std::int32_t > max_facet_to_cell_links, int num_threads=1)
Build distributed mesh dual graph (cell-cell connections via facets) from minimal mesh data.
Definition graphbuild.cpp:897
MeshTags< T > transfer_meshtags_to_submesh(const MeshTags< T > &tags, std::shared_ptr< const dolfinx::mesh::Topology > submesh_topology, const EntityMap &vertex_map, const EntityMap &cell_map)
Transfer a meshtags object from a parent to a submesh.
Definition utils.h:1647
std::vector< T > cell_normals(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities)
Compute normal to given cell (viewed as embedded in 3D).
Definition utils.h:337
std::function< std::vector< std::int32_t >( const graph::AdjacencyList< std::int32_t > &)> CellReorderFunction
Function that reorders (locally) cells that are owned by this process. It takes the local mesh dual g...
Definition utils.h:202
std::tuple< Topology, std::vector< int32_t >, std::vector< int32_t > > create_subtopology(const Topology &topology, int dim, std::span< const std::int32_t > entities)
Create a topology for a subset of entities of a given topological dimension.
Definition Topology.cpp:1541
std::tuple< Mesh< T >, EntityMap, EntityMap, std::vector< std::int32_t > > create_submesh(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities)
Create a new mesh consisting of a subset of entities in a mesh.
Definition utils.h:1611
bool is_vertex_dof_layout(CellType cell_type, const fem::ElementDofLayout &layout)
Check if extract_topology is the identity operation for a dof layout, i.e. the cell 'nodes' are exact...
Definition utils.cpp:237
std::vector< std::int32_t > exterior_facet_indices(const Topology &topology, int facet_type_idx)
Compute the indices of all exterior facets that are owned by the caller.
Definition utils.cpp:254
std::vector< std::int32_t > locate_entities_boundary(const Mesh< T > &mesh, int dim, U marker)
Compute indices of all mesh entities that are attached to an owned boundary facet and evaluate to tru...
Definition utils.h:657
CellType
Cell type identifier.
Definition cell_types.h:22
int num_cell_vertices(CellType type)
Number vertices for a cell type.
Definition cell_types.cpp:100
std::vector< T > h(const Mesh< T > &mesh, std::span< const std::int32_t > entities, int dim)
Compute greatest distance between any two vertices of the mesh entities (h).
Definition utils.h:285
std::pair< Geometry< T >, std::vector< int32_t > > create_subgeometry(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > subentity_to_entity)
Create a sub-geometry from a mesh and a subset of mesh entities to be included.
Definition utils.h:1517
Geometry< typename std::remove_reference_t< typename U::value_type > > create_geometry(const Topology &topology, const std::vector< fem::CoordinateElement< std::remove_reference_t< typename U::value_type > > > &elements, std::span< const std::int64_t > nodes, std::span< const std::int64_t > xdofs, const U &x, int dim, const std::function< std::vector< int >(const graph::AdjacencyList< std::int32_t > &)> &reorder_fn=nullptr)
Build Geometry from input data.
Definition Geometry.h:235
std::pair< std::vector< std::int32_t >, std::array< std::size_t, 2 > > entities_to_geometry(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities, bool permute=false)
Compute the geometry degrees of freedom associated with the closure of a given set of cell entities.
Definition utils.h:733
std::vector< std::int32_t > compute_incident_entities(const Topology &topology, std::span< const std::int32_t > entities, int d0, int d1)
Compute incident entities.
Definition utils.cpp:296
std::vector< std::int64_t > extract_topology(CellType cell_type, const fem::ElementDofLayout &layout, std::span< const std::int64_t > cells)
Extract topology from cell data, i.e. extract cell vertices.
Definition utils.cpp:208
std::vector< std::int32_t > locate_entities(const Mesh< T > &mesh, int dim, U marker, int entity_type_idx)
Compute indices of all mesh entities that evaluate to true for the provided geometric marking functio...
Definition utils.h:559
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
Mesh< typename std::remove_reference_t< typename U::value_type > > create_mesh(MPI_Comm comm, MPI_Comm commt, std::vector< std::span< const std::int64_t > > cells, const std::vector< fem::CoordinateElement< typename std::remove_reference_t< typename U::value_type > > > &elements, MPI_Comm commg, const U &x, std::array< std::size_t, 2 > xshape, const graph::Partitioner &partitioner, GhostMode ghost_mode, std::optional< std::int32_t > max_facet_to_cell_links, int num_threads, const CellReorderFunction &reorder_fn=graph::reorder_rcm)
Create a distributed mesh::Mesh from mesh data and using the provided graph partitioning function for...
Definition utils.h:1246
GhostMode
Enum for different partitioning ghost modes.
Definition types.h:19
CellType cell_entity_type(CellType type, int d, int index)
Return type of cell for entity of dimension d at given entity index.
Definition cell_types.h:111
constexpr void radix_sort(R &&range, P proj={})
Sort a range with radix sorting algorithm. The bucket size is determined by the number of bits to sor...
Definition sort.h:81
An AnyPartitionFunction together with the node weights it should be called with, if any.
Definition partition.h:156