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/log.h>
25#include <dolfinx/common/sort.h>
26#include <dolfinx/graph/AdjacencyList.h>
27#include <dolfinx/graph/ordering.h>
28#include <dolfinx/graph/partition.h>
29#include <exception>
30#include <format>
31#include <iterator>
32#include <memory>
33#include <mpi.h>
34#include <numeric>
35#include <optional>
36#include <ranges>
37#include <span>
38#include <stdexcept>
39#include <string_view>
40#include <tuple>
41#include <type_traits>
42#include <utility>
43#include <variant>
44#include <vector>
45
48
49namespace dolfinx::fem
50{
52}
53
54namespace dolfinx::mesh
55{
56enum class CellType : std::int8_t;
57
58namespace impl
59{
65template <typename T>
66void reorder_list(std::span<T> list, std::span<const std::int32_t> nodemap)
67{
68 if (nodemap.empty())
69 return;
70
71 assert(list.size() % nodemap.size() == 0);
72 std::size_t degree = list.size() / nodemap.size();
73 const std::vector<T> orig(list.begin(), list.end());
74 for (std::size_t n = 0; n < nodemap.size(); ++n)
75 {
76 std::span links_old(orig.data() + n * degree, degree);
77 auto links_new = list.subspan(nodemap[n] * degree, degree);
78 std::ranges::copy(links_old, links_new.begin());
79 }
80}
81
95template <std::floating_point T>
96std::tuple<std::vector<std::int32_t>, std::vector<T>, std::vector<std::int32_t>>
98 std::span<const std::int32_t> facets)
99{
100 auto topology = mesh.topology();
101 assert(topology);
102 const int tdim = topology->dim();
103 if (dim == tdim)
104 {
105 throw std::invalid_argument(
106 "Cannot use mesh::locate_entities_boundary (boundary) for cells.");
107 }
108
109 // Build set of vertices on boundary and set of boundary entities
110 mesh.topology_mutable()->create_connectivity(tdim - 1, 0);
111 mesh.topology_mutable()->create_connectivity(tdim - 1, dim);
112 std::vector<std::int32_t> vertices, entities;
113 {
114 auto f_to_v = topology->connectivity(tdim - 1, 0);
115 assert(f_to_v);
116 auto f_to_e = topology->connectivity(tdim - 1, dim);
117 assert(f_to_e);
118 for (auto f : facets)
119 {
120 auto v = f_to_v->links(f);
121 vertices.insert(vertices.end(), v.begin(), v.end());
122 auto e = f_to_e->links(f);
123 entities.insert(entities.end(), e.begin(), e.end());
124 }
125
126 // Build vector of boundary vertices
127 {
128 std::ranges::sort(vertices);
129 auto [unique_end, range_end] = std::ranges::unique(vertices);
130 vertices.erase(unique_end, range_end);
131 }
132
133 {
134 std::ranges::sort(entities);
135 auto [unique_end, range_end] = std::ranges::unique(entities);
136 entities.erase(unique_end, range_end);
137 }
138 }
139
140 // Get geometry data
141 auto x_dofmap = mesh.geometry().dofmaps().front();
142 std::span<const T> x_nodes = mesh.geometry().x();
143
144 // Get all vertex 'node' indices
145 mesh.topology_mutable()->create_connectivity(0, tdim);
146 mesh.topology_mutable()->create_connectivity(tdim, 0);
147 auto v_to_c = topology->connectivity(0, tdim);
148 assert(v_to_c);
149 auto c_to_v = topology->connectivity(tdim, 0);
150 assert(c_to_v);
151 std::vector<T> x_vertices(3 * vertices.size(), -1.0);
152 std::vector<std::int32_t> vertex_to_pos(v_to_c->num_nodes(), -1);
153 for (std::size_t i = 0; i < vertices.size(); ++i)
154 {
155 const std::int32_t v = vertices[i];
156
157 // Get first cell and find position
158 const std::int32_t c = v_to_c->links(v).front();
159 auto cell_vertices = c_to_v->links(c);
160 auto it = std::ranges::find(cell_vertices, v);
161 assert(it != cell_vertices.end());
162 const std::size_t local_pos
163 = std::ranges::distance(cell_vertices.begin(), it);
164
165 auto dofs = md::submdspan(x_dofmap, c, md::full_extent);
166 for (std::size_t j = 0; j < 3; ++j)
167 x_vertices[j * vertices.size() + i] = x_nodes[3 * dofs[local_pos] + j];
168 vertex_to_pos[v] = i;
169 }
170
171 return {std::move(entities), std::move(x_vertices), std::move(vertex_to_pos)};
172}
173
174} // namespace impl
175
193std::vector<std::int32_t> exterior_facet_indices(const Topology& topology,
194 int facet_type_idx);
195
211std::vector<std::int32_t> exterior_facet_indices(const Topology& topology);
212
213namespace impl
214{
248std::vector<std::int64_t> reorder_cells(
249 const graph::Reorder& reorder_fn, std::span<const double> cell_centroids,
250 int gdim, std::optional<std::int32_t> max_facet_to_cell_links,
251 const std::vector<CellType>& celltypes,
252 const std::vector<fem::ElementDofLayout>& doflayouts,
253 const std::vector<std::vector<int>>& ghost_owners,
254 std::vector<std::vector<std::int64_t>>& cells,
255 std::vector<std::span<std::int64_t>>& cells_v,
256 std::vector<std::vector<std::int64_t>>& original_idx, int num_threads);
257} // namespace impl
258
268std::vector<std::int64_t> extract_topology(CellType cell_type,
269 const fem::ElementDofLayout& layout,
270 std::span<const std::int64_t> cells);
271
283bool is_vertex_dof_layout(CellType cell_type,
284 const fem::ElementDofLayout& layout);
285
299template <std::floating_point T>
300std::vector<T> h(const Mesh<T>& mesh, std::span<const std::int32_t> entities,
301 int dim)
302{
303 if (entities.empty())
304 return std::vector<T>();
305 if (dim == 0)
306 return std::vector<T>(entities.size(), 0);
307
308 // Get the geometry dofs for the vertices of each entity
309 const auto [vertex_xdofs, xdof_shape]
310 = entities_to_geometry(mesh, dim, entities, false);
311
312 // Get the geometry coordinate
313 std::span<const T> x = mesh.geometry().x();
314
315 // Function to compute the length of (p0 - p1)
316 auto delta_norm = [](auto&& p0, auto&& p1)
317 {
318 T norm = 0;
319 for (std::size_t i = 0; i < 3; ++i)
320 norm += (p0[i] - p1[i]) * (p0[i] - p1[i]);
321 return std::sqrt(norm);
322 };
323
324 // Compute greatest distance between any to vertices
325 assert(dim > 0);
326 std::vector<T> h(entities.size(), 0);
327 for (std::size_t e = 0; e < entities.size(); ++e)
328 {
329 // Get geometry 'dof' for each vertex of entity e
330 std::span<const std::int32_t> e_vertices(
331 vertex_xdofs.data() + e * xdof_shape[1], xdof_shape[1]);
332
333 // Compute maximum distance between any two vertices
334 for (std::size_t i = 0; i < e_vertices.size(); ++i)
335 {
336 std::span<const T, 3> p0(x.data() + 3 * e_vertices[i], 3);
337 for (std::size_t j = i + 1; j < e_vertices.size(); ++j)
338 {
339 std::span<const T, 3> p1(x.data() + 3 * e_vertices[j], 3);
340 h[e] = std::max(h[e], delta_norm(p0, p1));
341 }
342 }
343 }
344
345 return h;
346}
347
351template <std::floating_point T>
352std::vector<T> cell_normals(const Mesh<T>& mesh, int dim,
353 std::span<const std::int32_t> entities)
354{
355 if (entities.empty())
356 return std::vector<T>();
357
358 auto topology = mesh.topology();
359 assert(topology);
360 if (topology->cell_type() == CellType::prism and dim == 2)
361 {
362 throw std::invalid_argument(
363 "Cell normal computation for prism cells not yet supported.");
364 }
365
366 const int gdim = mesh.geometry().dim();
367 const CellType type = cell_entity_type(topology->cell_type(), dim, 0);
368
369 // Find geometry nodes for topology entities
370 std::span<const T> x = mesh.geometry().x();
371 const auto [geometry_entities, eshape]
372 = entities_to_geometry(mesh, dim, entities, false);
373
374 std::vector<T> n(entities.size() * 3);
375 switch (type)
376 {
377 case CellType::interval:
378 {
379 if (gdim > 2)
380 throw std::invalid_argument("Interval cell normal undefined in 3D.");
381 for (std::size_t i = 0; i < entities.size(); ++i)
382 {
383 // Get the two vertices as points
384 std::array vertices{geometry_entities[i * eshape[1]],
385 geometry_entities[i * eshape[1] + 1]};
386 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
387 std::span<const T, 3>(x.data() + 3 * vertices[1], 3)};
388
389 // Define normal by rotating tangent counter-clockwise
390 std::array<T, 3> t;
391 std::ranges::transform(p[1], p[0], t.begin(),
392 [](auto x, auto y) { return x - y; });
393
394 T norm = std::sqrt(t[0] * t[0] + t[1] * t[1]);
395 std::span<T, 3> ni(n.data() + 3 * i, 3);
396 ni[0] = -t[1] / norm;
397 ni[1] = t[0] / norm;
398 ni[2] = 0.0;
399 }
400 return n;
401 }
402 case CellType::triangle:
403 {
404 for (std::size_t i = 0; i < entities.size(); ++i)
405 {
406 // Get the three vertices as points
407 std::array vertices = {geometry_entities[i * eshape[1] + 0],
408 geometry_entities[i * eshape[1] + 1],
409 geometry_entities[i * eshape[1] + 2]};
410 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
411 std::span<const T, 3>(x.data() + 3 * vertices[1], 3),
412 std::span<const T, 3>(x.data() + 3 * vertices[2], 3)};
413
414 // Compute (p1 - p0) and (p2 - p0)
415 std::array<T, 3> dp1, dp2;
416 std::ranges::transform(p[1], p[0], dp1.begin(),
417 [](auto x, auto y) { return x - y; });
418 std::ranges::transform(p[2], p[0], dp2.begin(),
419 [](auto x, auto y) { return x - y; });
420
421 // Define cell normal via cross product of first two edges
422 std::array<T, 3> ni = math::cross(dp1, dp2);
423 T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]);
424 std::ranges::transform(ni, std::next(n.begin(), 3 * i),
425 [norm](auto x) { return x / norm; });
426 }
427
428 return n;
429 }
430 case CellType::quadrilateral:
431 {
432 // TODO: check
433 for (std::size_t i = 0; i < entities.size(); ++i)
434 {
435 // Get the three vertices as points
436 std::array vertices = {geometry_entities[i * eshape[1] + 0],
437 geometry_entities[i * eshape[1] + 1],
438 geometry_entities[i * eshape[1] + 2]};
439 std::array p = {std::span<const T, 3>(x.data() + 3 * vertices[0], 3),
440 std::span<const T, 3>(x.data() + 3 * vertices[1], 3),
441 std::span<const T, 3>(x.data() + 3 * vertices[2], 3)};
442
443 // Compute (p1 - p0) and (p2 - p0)
444 std::array<T, 3> dp1, dp2;
445 std::ranges::transform(p[1], p[0], dp1.begin(),
446 [](auto x, auto y) { return x - y; });
447 std::ranges::transform(p[2], p[0], dp2.begin(),
448 [](auto x, auto y) { return x - y; });
449
450 // Define cell normal via cross product of first two edges
451 std::array<T, 3> ni = math::cross(dp1, dp2);
452 T norm = std::sqrt(ni[0] * ni[0] + ni[1] * ni[1] + ni[2] * ni[2]);
453 std::ranges::transform(ni, std::next(n.begin(), 3 * i),
454 [norm](auto x) { return x / norm; });
455 }
456
457 return n;
458 }
459 default:
460 throw std::invalid_argument(
461 "cell_normal not supported for this cell type.");
462 }
463}
464
474template <std::floating_point T>
475std::vector<T> compute_midpoints(const Mesh<T>& mesh, int dim,
476 std::span<const std::int32_t> entities)
477{
478 if (entities.empty())
479 return std::vector<T>();
480
481 std::span<const T> x = mesh.geometry().x();
482
483 // Build map from entity -> geometry dof
484 const auto [e_to_g, eshape]
485 = entities_to_geometry(mesh, dim, entities, false);
486
487 std::vector<T> x_mid(entities.size() * 3, 0);
488 for (std::size_t e = 0; e < entities.size(); ++e)
489 {
490 std::span<T, 3> p(x_mid.data() + 3 * e, 3);
491 std::span<const std::int32_t> rows(e_to_g.data() + e * eshape[1],
492 eshape[1]);
493 for (auto row : rows)
494 {
495 std::span<const T, 3> xg(x.data() + 3 * row, 3);
496 std::ranges::transform(p, xg, p.begin(),
497 [size = rows.size()](auto x, auto y)
498 { return x + y / size; });
499 }
500 }
501
502 return x_mid;
503}
504
505namespace impl
506{
511template <std::floating_point T>
512std::pair<std::vector<T>, std::array<std::size_t, 2>>
514{
515 auto topology = mesh.topology();
516 assert(topology);
517 const int tdim = topology->dim();
518
519 // Create entities and connectivities
520
521 // Get all vertex 'node' indices
522 const std::int32_t num_vertices = topology->index_map(0)->size_local()
523 + topology->index_map(0)->num_ghosts();
524
525 std::vector<std::int32_t> vertex_to_node(num_vertices);
526 for (int cell_type_idx = 0,
527 num_cell_types = topology->entity_types(tdim).size();
528 cell_type_idx < num_cell_types; ++cell_type_idx)
529 {
530 auto x_dofmap = mesh.geometry().dofmaps().at(cell_type_idx);
531 auto c_to_v = topology->connectivity({tdim, cell_type_idx}, {0, 0});
532 assert(c_to_v);
533 for (int c = 0; c < c_to_v->num_nodes(); ++c)
534 {
535 auto x_dofs = md::submdspan(x_dofmap, c, md::full_extent);
536 auto vertices = c_to_v->links(c);
537 for (std::size_t i = 0; i < vertices.size(); ++i)
538 vertex_to_node[vertices[i]] = x_dofs[i];
539 }
540 }
541
542 // Pack coordinates of vertices
543 std::span<const T> x_nodes = mesh.geometry().x();
544 std::vector<T> x_vertices(3 * vertex_to_node.size(), 0.0);
545 for (std::size_t i = 0; i < vertex_to_node.size(); ++i)
546 {
547 std::int32_t pos = 3 * vertex_to_node[i];
548 for (std::size_t j = 0; j < 3; ++j)
549 x_vertices[j * vertex_to_node.size() + i] = x_nodes[pos + j];
550 }
551
552 return {std::move(x_vertices), {3, vertex_to_node.size()}};
553}
554
555} // namespace impl
556
558template <typename Fn, typename T>
559concept MarkerFn = std::is_invocable_r<
560 std::vector<std::int8_t>, Fn,
561 md::mdspan<const T,
562 md::extents<std::size_t, 3, md::dynamic_extent>>>::value;
563
581template <std::floating_point T, MarkerFn<T> U>
582std::vector<std::int32_t> locate_entities(const Mesh<T>& mesh, int dim,
583 U marker, int entity_type_idx)
584{
585
586 using cmdspan3x_t
587 = md::mdspan<const T, md::extents<std::size_t, 3, md::dynamic_extent>>;
588
589 // Run marker function on vertex coordinates
590 const auto [xdata, xshape] = impl::compute_vertex_coords(mesh);
591
592 cmdspan3x_t x(xdata.data(), xshape);
593 const std::vector<std::int8_t> marked = marker(x);
594 if (marked.size() != x.extent(1))
595 throw std::invalid_argument("Length of array of markers is wrong.");
596
597 auto topology = mesh.topology();
598 assert(topology);
599 const int tdim = topology->dim();
600
601 if (entity_type_idx < 0
602 or static_cast<std::size_t>(entity_type_idx)
603 >= topology->entity_types(dim).size())
604 {
605 throw std::out_of_range(
606 "entity_type_idx out of range for Topology::entity_types(dim).");
607 }
608
609 mesh.topology_mutable()->create_entities(dim);
610 if (dim < tdim)
611 mesh.topology_mutable()->create_connectivity(dim, 0);
612
613 // Iterate over entities of dimension 'dim' to build vector of marked
614 // entities
615 auto e_to_v = topology->connectivity({dim, entity_type_idx}, {0, 0});
616 assert(e_to_v);
617 std::vector<std::int32_t> entities;
618 for (int e = 0; e < e_to_v->num_nodes(); ++e)
619 {
620 // Iterate over entity vertices
621 bool all_vertices_marked = true;
622 for (std::int32_t v : e_to_v->links(e))
623 {
624 if (!marked[v])
625 {
626 all_vertices_marked = false;
627 break;
628 }
629 }
630
631 if (all_vertices_marked)
632 entities.push_back(e);
633 }
634
635 return entities;
636}
637
653template <std::floating_point T, MarkerFn<T> U>
654std::vector<std::int32_t> locate_entities(const Mesh<T>& mesh, int dim,
655 U marker)
656{
657 const int num_entity_types = mesh.topology()->entity_types(dim).size();
658 if (num_entity_types > 1)
659 {
660 throw std::runtime_error(
661 "Multiple entity types of this dimension. Specify entity type index");
662 }
663 return locate_entities(mesh, dim, marker, 0);
664}
665
692template <std::floating_point T, MarkerFn<T> U>
693std::vector<std::int32_t> locate_entities_boundary(const Mesh<T>& mesh, int dim,
694 U marker)
695{
696 // TODO Rewrite this function, it should be possible to simplify considerably
697 auto topology = mesh.topology();
698 assert(topology);
699 int tdim = topology->dim();
700 if (dim == tdim)
701 {
702 throw std::invalid_argument(
703 "Cannot use mesh::locate_entities_boundary (boundary) for cells.");
704 }
705
706 // Compute list of boundary facets
707 mesh.topology_mutable()->create_entities(tdim - 1);
708 mesh.topology_mutable()->create_connectivity(tdim - 1, tdim);
709 std::vector<std::int32_t> boundary_facets = exterior_facet_indices(*topology);
710 mesh.topology_mutable()->create_entities(dim);
711
712 using cmdspan3x_t
713 = md::mdspan<const T, md::extents<std::size_t, 3, md::dynamic_extent>>;
714
715 // Run marker function on the vertex coordinates
716 auto [facet_entities, xdata, vertex_to_pos]
717 = impl::compute_vertex_coords_boundary(mesh, dim, boundary_facets);
718 cmdspan3x_t x(xdata.data(), 3, xdata.size() / 3);
719 std::vector<std::int8_t> marked = marker(x);
720 if (marked.size() != x.extent(1))
721 throw std::invalid_argument("Length of array of markers is wrong.");
722
723 // Loop over entities and check vertex markers
724 auto e_to_v = topology->connectivity(dim, 0);
725 assert(e_to_v);
726 std::vector<std::int32_t> entities;
727 for (auto e : facet_entities)
728 {
729 // Iterate over entity vertices
730 bool all_vertices_marked = true;
731 for (auto v : e_to_v->links(e))
732 {
733 const std::int32_t pos = vertex_to_pos[v];
734 if (!marked[pos])
735 {
736 all_vertices_marked = false;
737 break;
738 }
739 }
740
741 // Mark facet with all vertices marked
742 if (all_vertices_marked)
743 entities.push_back(e);
744 }
745
746 return entities;
747}
748
772template <std::floating_point T>
773std::pair<std::vector<std::int32_t>, std::array<std::size_t, 2>>
775 std::span<const std::int32_t> entities,
776 bool permute = false)
777{
778 auto topology = mesh.topology();
779 assert(topology);
780 CellType cell_type = topology->cell_type();
781 if ((cell_type == CellType::prism or cell_type == CellType::pyramid)
782 and dim == 2)
783 {
784 throw std::invalid_argument("mesh::entities_to_geometry for prism/pyramid "
785 "cell facets not yet supported.");
786 }
787
788 const int tdim = topology->dim();
789 const Geometry<T>& geometry = mesh.geometry();
790 auto xdofs = geometry.dofmaps().front();
791
792 // Get the DOF layout and the number of DOFs per entity
793 const fem::CoordinateElement<T>& coord_ele = geometry.cmaps().front();
794 if (dim < tdim and coord_ele.is_discontinuous())
795 {
796 throw std::invalid_argument(
797 "mesh::entities_to_geometry for sub-entities of a cell is not "
798 "supported for a discontinuous geometry, which has no coordinate "
799 "degrees-of-freedom associated with sub-entities.");
800 }
801
802 const fem::ElementDofLayout layout = coord_ele.create_dof_layout();
803 const std::size_t num_entity_dofs = layout.entity_closure_dofs(dim, 0).size();
804 std::vector<std::int32_t> entity_xdofs;
805 entity_xdofs.reserve(entities.size() * num_entity_dofs);
806 std::array<std::size_t, 2> eshape{entities.size(), num_entity_dofs};
807
808 // Get the element's closure DOFs
809 const std::vector<std::vector<std::vector<int>>>& closure_dofs_all
810 = layout.entity_closure_dofs_all();
811
812 // Special case when dim == tdim (cells)
813 if (dim == tdim)
814 {
815 for (std::int32_t c : entities)
816 {
817 // Extract degrees of freedom
818 auto x_c = md::submdspan(xdofs, c, md::full_extent);
819 for (std::int32_t entity_dof : closure_dofs_all[tdim][0])
820 entity_xdofs.push_back(x_c[entity_dof]);
821 }
822
823 return {std::move(entity_xdofs), eshape};
824 }
825
826 assert(dim != tdim);
827
828 auto e_to_c = topology->connectivity(dim, tdim);
829 if (!e_to_c)
830 {
831 throw std::runtime_error(std::format(
832 "Entity-to-cell connectivity has not been computed. Missing dims "
833 "{}->{}",
834 dim, tdim));
835 }
836
837 auto c_to_e = topology->connectivity(tdim, dim);
838 if (!c_to_e)
839 {
840 throw std::runtime_error(std::format(
841 "Cell-to-entity connectivity has not been computed. Missing dims "
842 "{}->{}",
843 tdim, dim));
844 }
845
846 // Get the cell info, which is needed to permute the closure dofs
847 std::span<const std::uint32_t> cell_info;
848 if (permute)
849 cell_info = std::span(mesh.topology()->get_cell_permutation_info());
850
851 // Closure DOF count is the same for every local entity of dimension
852 // `dim` (the one case where it isn't, prism/pyramid facets, is
853 // rejected above), so size once and reuse across entities.
854 std::vector<std::int32_t> closure_dofs(num_entity_dofs);
855 for (std::int32_t e : entities)
856 {
857 // Get a cell connected to the entity
858 if (e_to_c->links(e).empty())
859 throw std::runtime_error("No cell incident to entity.");
860 std::int32_t c = e_to_c->links(e).front();
861
862 // Get the local index of the entity
863 std::span<const std::int32_t> cell_entities = c_to_e->links(c);
864 auto it = std::find(cell_entities.begin(), cell_entities.end(), e);
865 assert(it != cell_entities.end());
866 std::size_t local_entity = std::ranges::distance(cell_entities.begin(), it);
867
868 // Cell sub-entities must be permuted so that their local
869 // orientation agrees with their global orientation
870 const std::vector<int>& e_closure_dofs
871 = closure_dofs_all[dim][local_entity];
872 assert(e_closure_dofs.size() == closure_dofs.size());
873 std::ranges::copy(e_closure_dofs, closure_dofs.begin());
874 if (permute)
875 {
876 mesh::CellType entity_type
877 = mesh::cell_entity_type(cell_type, dim, local_entity);
878 coord_ele.permute_subentity_closure(closure_dofs, cell_info[c],
879 entity_type, local_entity);
880 }
881
882 // Extract degrees of freedom
883 auto x_c = md::submdspan(xdofs, c, md::full_extent);
884 for (std::int32_t entity_dof : closure_dofs)
885 entity_xdofs.push_back(x_c[entity_dof]);
886 }
887
888 return {std::move(entity_xdofs), eshape};
889}
890
899std::vector<std::int32_t>
900compute_incident_entities(const Topology& topology,
901 std::span<const std::int32_t> entities, int d0,
902 int d1);
903
904namespace impl
905{
921template <typename F>
922void try_locally(F&& fn, std::exception_ptr& error)
923{
924 if (error)
925 return;
926
927 try
928 {
929 fn();
930 }
931 catch (...)
932 {
933 error = std::current_exception();
934 }
935}
936
959inline void mpi_check(MPI_Comm comm, std::string_view op_name,
960 const std::exception_ptr& error)
961{
962 int failed = error ? 1 : 0;
963 int any_failed = 0;
964 MPI_Allreduce(&failed, &any_failed, 1, MPI_INT, MPI_MAX, comm);
965 if (error)
966 std::rethrow_exception(error);
967 else if (any_failed)
968 {
969 throw std::runtime_error(
970 std::format("{} failed on another rank.", op_name));
971 }
972}
973
997template <std::floating_point T>
998std::vector<double> cell_centroids_local(
999 std::span<const int> num_vertices_per_cell,
1000 const std::vector<std::span<const std::int64_t>>& cells,
1001 const boost::unordered_flat_map<std::int64_t, std::size_t>& node_to_pos,
1002 std::span<const T> coords, int gdim)
1003{
1004 std::size_t num_cells = 0;
1005 for (std::size_t i = 0; i < cells.size(); ++i)
1006 num_cells += cells[i].size() / num_vertices_per_cell[i];
1007 std::vector<double> centroid(gdim * num_cells, 0);
1008
1009 std::size_t c0 = 0;
1010 for (std::size_t i = 0; i < cells.size(); ++i)
1011 {
1012 const int nv = num_vertices_per_cell[i];
1013 const double w = 1.0 / nv;
1014 for (std::size_t c = 0; c < cells[i].size() / nv; ++c)
1015 {
1016 for (int v = 0; v < nv; ++v)
1017 {
1018 auto it = node_to_pos.find(cells[i][nv * c + v]);
1019 assert(it != node_to_pos.end());
1020 std::size_t pos = it->second;
1021 for (int d = 0; d < gdim; ++d)
1022 centroid[gdim * (c0 + c) + d] += w * coords[gdim * pos + d];
1023 }
1024 }
1025
1026 c0 += cells[i].size() / nv;
1027 }
1028
1029 return centroid;
1030}
1031
1055template <std::floating_point T>
1056std::vector<double>
1058 std::span<const int> num_vertices_per_cell,
1059 const std::vector<std::span<const std::int64_t>>& cells,
1060 MPI_Comm commg, std::span<const T> x, int gdim)
1061{
1062 // Vertices of the cells on this rank, sorted and with duplicates
1063 // removed, and the coordinates for them
1064 std::vector<std::int64_t> nodes;
1065 {
1066 std::size_t size = 0;
1067 for (std::span<const std::int64_t> c : cells)
1068 size += c.size();
1069 nodes.reserve(size);
1070 for (std::span<const std::int64_t> c : cells)
1071 nodes.insert(nodes.end(), c.begin(), c.end());
1072 dolfinx::radix_sort(nodes);
1073 auto [unique_end, range_end] = std::ranges::unique(nodes);
1074 nodes.erase(unique_end, range_end);
1075 }
1076 const std::vector<T> coords
1077 = dolfinx::MPI::distribute_data(comm, nodes, commg, x, gdim);
1078
1079 // Hash map from global vertex index to its position in `nodes` (and
1080 // so its row in `coords`), turning the many repeated cell-vertex
1081 // lookups below into an O(1) average lookup rather than an
1082 // O(log(nodes.size())) binary search each time -- most vertices are
1083 // shared by several cells, so the same key is looked up repeatedly.
1084 boost::unordered_flat_map<std::int64_t, std::size_t> node_to_pos;
1085 node_to_pos.reserve(nodes.size());
1086 for (std::size_t i = 0; i < nodes.size(); ++i)
1087 node_to_pos.emplace(nodes[i], i);
1088
1089 return cell_centroids_local(num_vertices_per_cell, cells, node_to_pos,
1090 std::span<const T>(coords), gdim);
1091}
1092
1141template <std::floating_point T>
1142std::tuple<std::vector<std::vector<std::int64_t>>,
1143 std::vector<std::vector<std::int64_t>>,
1144 std::vector<std::vector<int>>>
1145partition_cells(MPI_Comm comm, MPI_Comm commt,
1146 const std::vector<std::span<const std::int64_t>>& cells,
1147 const std::vector<CellType>& celltypes,
1148 const std::vector<fem::ElementDofLayout>& doflayouts,
1149 bool p1_geometry, const graph::Partitioner& partitioner,
1150 bool ghosting,
1151 std::optional<std::int32_t> max_facet_to_cell_links,
1152 int num_threads, MPI_Comm commg, std::span<const T> x,
1153 std::array<std::size_t, 2> xshape)
1154{
1155 const std::int32_t num_cell_types = cells.size();
1156 std::vector<std::vector<std::int64_t>> cells1(num_cell_types);
1157 std::vector<std::vector<std::int64_t>> original_idx1(num_cell_types);
1158 std::vector<std::vector<int>> ghost_owners(num_cell_types);
1159 if (graph::has_partitioner(partitioner.fn))
1160 {
1161 logger().info("Using partitioner with cell data ({} cell types)",
1162 num_cell_types);
1164 std::exception_ptr error;
1165
1166 // Geometric data can be distributed on ranks that do not participate in
1167 // topology partitioning. Gather cell centroids collectively over `comm`
1168 // so that every rank in `commg` participates in the coordinate exchange.
1169 std::vector<double> centroid;
1170 const bool needs_centroids
1171 = std::holds_alternative<graph::geom_partition_fn>(partitioner.fn)
1172 or std::holds_alternative<graph::hybrid_partition_fn>(partitioner.fn);
1173 std::vector<std::vector<std::int64_t>> topology(num_cell_types);
1174 std::vector<std::span<const std::int64_t>> topology_view(num_cell_types);
1175 if (needs_centroids or commt != MPI_COMM_NULL)
1176 {
1177 for (std::int32_t i = 0; i < num_cell_types; ++i)
1178 {
1179 if (p1_geometry)
1180 topology_view[i] = cells[i];
1181 else
1182 {
1183 topology[i] = extract_topology(celltypes[i], doflayouts[i], cells[i]);
1184 topology_view[i] = topology[i];
1185 }
1186 }
1187 }
1188
1189 if (needs_centroids)
1190 {
1191 std::vector<int> num_vertices_per_cell;
1192 num_vertices_per_cell.reserve(celltypes.size());
1193 std::ranges::transform(celltypes,
1194 std::back_inserter(num_vertices_per_cell),
1195 [](CellType c) { return num_cell_vertices(c); });
1196 centroid = compute_cell_centroids(comm, num_vertices_per_cell,
1197 topology_view, commg, x, xshape[1]);
1198 }
1199
1200 if (std::holds_alternative<graph::geom_partition_fn>(partitioner.fn))
1201 {
1203 [&dest, &comm, &centroid, &partitioner, &xshape]
1204 {
1205 int size = dolfinx::MPI::size(comm);
1206 const auto& p = std::get<graph::geom_partition_fn>(partitioner.fn);
1208 p(comm, size, std::span<const double>(centroid), xshape[1],
1209 partitioner.node_weights),
1210 1);
1211 },
1212 error);
1213 }
1214
1215 if (commt != MPI_COMM_NULL)
1216 {
1217 // A partitioner such as graph::parmetis::geom_partitioner (which
1218 // requires nparts to equal the number of ranks calling it) can
1219 // throw only on the ranks with commt != MPI_COMM_NULL, which may
1220 // be a strict subset of comm (e.g. cells built on rank 0 only).
1221 // mpi_check below turns that into a comm-wide decision before any
1222 // rank reaches the graph::build::distribute collective, or a throw
1223 // here would leave the rest of comm blocked on it forever.
1225 [&dest, &comm, &commt, &celltypes, &topology_view,
1226 &max_facet_to_cell_links, &num_threads, &partitioner, &centroid,
1227 &ghosting]
1228 {
1229 int size = dolfinx::MPI::size(comm);
1230 // Shared by the graph::partition_fn and
1231 // graph::hybrid_partition_fn alternatives below: neither has any
1232 // other way to obtain the mesh dual graph.
1233 auto dual_graph
1234 = [&commt, &celltypes, &topology_view, &max_facet_to_cell_links,
1235 &num_threads]() -> graph::AdjacencyList<std::int64_t>
1236 {
1237 return build_dual_graph(commt, celltypes, topology_view,
1238 max_facet_to_cell_links, num_threads);
1239 };
1240
1241 // `dest` is already correct for graph::geom_partition_fn (set
1242 // above); skip the visit's dispatch (and the AdjacencyList
1243 // copy an unconditional assignment would cost) for that case.
1244 if (!std::holds_alternative<graph::geom_partition_fn>(
1245 partitioner.fn))
1246 {
1247 dest = std::visit(
1248 [&dual_graph, &commt, size, &centroid, &partitioner,
1249 &ghosting](
1250 const auto& p) -> graph::AdjacencyList<std::int32_t>
1251 {
1252 using P = std::decay_t<decltype(p)>;
1253 if constexpr (std::is_same_v<P, graph::hybrid_partition_fn>)
1254 {
1255 return p(commt, size, dual_graph(),
1256 std::span<const double>(centroid),
1257 partitioner.node_weights, std::nullopt,
1258 ghosting);
1259 }
1260 else if constexpr (std::is_same_v<P, graph::partition_fn>)
1261 {
1262 return p(commt, size, dual_graph(),
1263 partitioner.node_weights, std::nullopt,
1264 ghosting);
1265 }
1266 else
1267 {
1268 // std::visit still requires this branch to
1269 // compile for graph::geom_partition_fn.
1270 static_assert(
1271 std::is_same_v<P, graph::geom_partition_fn>);
1272 throw std::logic_error("Unreachable.");
1273 }
1274 },
1275 partitioner.fn);
1276 }
1277 },
1278 error);
1279 }
1280
1281 mpi_check(comm, "Cell partitioning", error);
1282
1283 std::int32_t cell_offset = 0;
1284 for (std::int32_t i = 0; i < num_cell_types; ++i)
1285 {
1286 std::size_t num_cell_nodes = doflayouts[i].num_dofs();
1287 std::size_t num_cells = cells[i].size() / num_cell_nodes;
1288
1289 // Extract destination AdjacencyList for this cell type
1290 std::vector<std::int32_t> offsets_i(
1291 std::next(dest.offsets().begin(), cell_offset),
1292 std::next(dest.offsets().begin(), cell_offset + num_cells + 1));
1293 std::vector<std::int32_t> data_i(
1294 std::next(dest.array().begin(), offsets_i.front()),
1295 std::next(dest.array().begin(), offsets_i.back()));
1296 const std::int32_t offset_0 = offsets_i.front();
1297 std::ranges::transform(offsets_i, offsets_i.begin(),
1298 [offset_0](std::int32_t j)
1299 { return j - offset_0; });
1300 graph::AdjacencyList<std::int32_t> dest_i(data_i, offsets_i);
1301 cell_offset += num_cells;
1302
1303 // Distribute cells (topology, includes higher-order 'nodes') to
1304 // destination rank
1305 std::vector<int> src_ranks;
1306 std::tie(cells1[i], src_ranks, original_idx1[i], ghost_owners[i])
1307 = graph::build::distribute(comm, cells[i],
1308 {num_cells, num_cell_nodes}, dest_i);
1309 logger().debug("Got {} cells from distribution", cells1[i].size());
1310 }
1311 }
1312 else // No partitioning: keep cells on their current rank
1313 {
1314 // Count cells of each type on this rank. Each cell still needs a
1315 // globally unique index (assigned below), even though it is not
1316 // being redistributed, and the counts are needed first to size
1317 // `original_idx1` and to determine this rank's share via the
1318 // exclusive scan that follows.
1319 std::int64_t num_owned = 0;
1320 for (std::int32_t i = 0; i < num_cell_types; ++i)
1321 {
1322 cells1[i] = std::vector<std::int64_t>(cells[i].begin(), cells[i].end());
1323 std::int32_t num_cell_nodes = doflayouts[i].num_dofs();
1324 original_idx1[i].resize(cells1[i].size() / num_cell_nodes);
1325 num_owned += original_idx1[i].size();
1326 }
1327
1328 // Assign a globally unique index to each cell. `global_offset`
1329 // starts as the number of cells owned by lower-ranked processes
1330 // (from the exclusive scan), and is advanced by each cell type's
1331 // count in turn so that the numbering is contiguous across cell
1332 // types too.
1333 std::int64_t global_offset = 0;
1334 MPI_Exscan(&num_owned, &global_offset, 1, MPI_INT64_T, MPI_SUM, comm);
1335 for (std::int32_t i = 0; i < num_cell_types; ++i)
1336 {
1337 std::iota(original_idx1[i].begin(), original_idx1[i].end(),
1338 global_offset);
1339 global_offset += original_idx1[i].size();
1340 }
1341 }
1342
1343 return {std::move(cells1), std::move(original_idx1), std::move(ghost_owners)};
1344}
1345} // namespace impl
1346
1401template <typename U>
1403 MPI_Comm comm, MPI_Comm commt,
1404 std::vector<std::span<const std::int64_t>> cells,
1405 const std::vector<fem::CoordinateElement<
1406 typename std::remove_reference_t<typename U::value_type>>>& elements,
1407 MPI_Comm commg, const U& x, std::array<std::size_t, 2> xshape,
1408 const graph::Partitioner& partitioner, GhostMode ghost_mode,
1409 std::optional<std::int32_t> max_facet_to_cell_links, int num_threads,
1410 const graph::Reorder& reorder_fn = graph::Reorder{})
1411{
1412 using T = typename std::remove_reference_t<typename U::value_type>;
1413
1414 if (cells.size() != elements.size())
1415 {
1416 throw std::invalid_argument(
1417 "Number of cell arrays and elements must match.");
1418 }
1419 std::vector<CellType> celltypes;
1420 std::ranges::transform(elements, std::back_inserter(celltypes),
1421 [](auto& e) { return e.cell_shape(); });
1422 std::vector<fem::ElementDofLayout> doflayouts;
1423 std::ranges::transform(elements, std::back_inserter(doflayouts),
1424 [](auto& e) { return e.create_dof_layout(); });
1425 for (std::size_t i = 0; i < cells.size(); ++i)
1426 {
1427 if (cells[i].size() % doflayouts[i].num_dofs() != 0)
1428 {
1429 throw std::invalid_argument("Cell array size is not a multiple of the "
1430 "number of nodes per cell.");
1431 }
1432 }
1433
1434 // Note: `extract_topology` extracts topology data, i.e. just the
1435 // vertices. For other elements the filtered lists may have 'gaps',
1436 // i.e. the indices might not be contiguous.
1437 //
1438 // For 'P1 geometry' the extraction is the identity operator, and cell
1439 // node data is used directly as cell topology. This avoids copies of
1440 // the (large) cell array, and lets the geometry node indices be taken
1441 // from the topology vertices rather than re-derived by sorting the
1442 // cell array (see below).
1443 const bool p1_geometry = std::ranges::all_of(
1444 std::views::iota(std::size_t(0), elements.size()),
1445 [&celltypes, &doflayouts](std::size_t i)
1446 { return is_vertex_dof_layout(celltypes[i], doflayouts[i]); });
1447
1448 const std::int32_t num_cell_types = cells.size();
1449
1450 // Partition cells across ranks of `comm` (or, if `partitioner` is not
1451 // callable, keep them on their current rank and just assign each a
1452 // globally unique index)
1453 const bool ghosting = (ghost_mode != GhostMode::none);
1454 auto [cells1, original_idx1, ghost_owners] = impl::partition_cells(
1455 comm, commt, cells, celltypes, doflayouts, p1_geometry, partitioner,
1456 ghosting, max_facet_to_cell_links, num_threads, commg,
1457 std::span<const T>(x), xshape);
1458
1459 // Extract cell 'topology', i.e. extract the vertices for each cell
1460 // and discard any 'higher-order' nodes. `cells1_v_storage` is empty
1461 // for 'P1 geometry', where `cells1_v` views `cells1` directly.
1462 std::vector<std::vector<std::int64_t>> cells1_v_storage(num_cell_types);
1463 std::vector<std::span<std::int64_t>> cells1_v(num_cell_types);
1464 for (std::int32_t i = 0; i < num_cell_types; ++i)
1465 {
1466 if (p1_geometry)
1467 cells1_v[i] = cells1[i];
1468 else
1469 {
1470 cells1_v_storage[i]
1471 = extract_topology(celltypes[i], doflayouts[i], cells1[i]);
1472 cells1_v[i] = cells1_v_storage[i];
1473 }
1474
1475 logger().info("Extract basic topology: {}->{}", cells1[i].size(),
1476 cells1_v[i].size());
1477 }
1478
1479 // Re-order cells and get boundary vertices. The re-ordering is done
1480 // on the cell topology, i.e. the vertex indices, and the higher-order
1481 // nodes are re-ordered accordingly.
1482 // Centroids are required only by a callable graph::reorder_geom_fn.
1483 // As elsewhere, `reorder_fn` is assumed to hold the same alternative
1484 // on every rank, so an empty function skips the collective exchange
1485 // everywhere; impl::reorder_cells then throws, and the failure is
1486 // made comm-wide by try_locally/mpi_check below.
1487 std::vector<double> cell_centroids;
1488 if (const graph::reorder_geom_fn* fn
1489 = std::get_if<graph::reorder_geom_fn>(&reorder_fn);
1490 fn and *fn)
1491 {
1492 std::vector<int> num_cell_vertices;
1493 num_cell_vertices.reserve(celltypes.size());
1494 std::ranges::transform(celltypes, std::back_inserter(num_cell_vertices),
1495 [](CellType c)
1496 { return mesh::num_cell_vertices(c); });
1497
1498 std::vector<std::span<const std::int64_t>> cells1_v_owned;
1499 cells1_v_owned.reserve(cells1_v.size());
1500 for (std::size_t i = 0; i < cells1_v.size(); ++i)
1501 {
1502 const int num_cell_vertices_i = num_cell_vertices[i];
1503 const std::size_t num_owned_cells
1504 = cells1_v[i].size() / num_cell_vertices_i - ghost_owners[i].size();
1505 cells1_v_owned.emplace_back(cells1_v[i].data(),
1506 num_owned_cells * num_cell_vertices_i);
1507 }
1508
1509 cell_centroids
1510 = impl::compute_cell_centroids(comm, num_cell_vertices, cells1_v_owned,
1511 commg, std::span<const T>(x), xshape[1]);
1512 }
1513
1514 std::vector<std::int64_t> boundary_v;
1515 std::exception_ptr error;
1517 [&boundary_v, &reorder_fn, &cell_centroids, &xshape,
1518 &max_facet_to_cell_links, &celltypes, &doflayouts, &ghost_owners,
1519 &cells1, &cells1_v, &original_idx1, &num_threads]
1520 {
1521 boundary_v = impl::reorder_cells(reorder_fn, cell_centroids, xshape[1],
1522 max_facet_to_cell_links, celltypes,
1523 doflayouts, ghost_owners, cells1,
1524 cells1_v, original_idx1, num_threads);
1525 },
1526 error);
1527 impl::mpi_check(comm, "Cell reordering", error);
1528
1529 logger().debug("Got {} boundary vertices", boundary_v.size());
1530
1531 // Create Topology
1532 std::vector<std::span<const std::int64_t>> cells1_v_span(cells1_v.begin(),
1533 cells1_v.end());
1534 std::vector<std::span<const std::int64_t>> original_idx1_span;
1535 std::ranges::transform(original_idx1, std::back_inserter(original_idx1_span),
1536 [](auto& c) { return std::span(c); });
1537 std::vector<std::span<const int>> ghost_owners_span;
1538 std::ranges::transform(ghost_owners, std::back_inserter(ghost_owners_span),
1539 [](auto& c) { return std::span(c); });
1540
1541 // Note: `vertex_index` holds the sorted input global indices of the
1542 // topology vertices, which for 'P1 geometry' are exactly the geometry
1543 // node indices required below.
1544 auto [topology, vertex_index] = mesh::impl::create_topology(
1545 comm, celltypes, cells1_v_span, original_idx1_span, ghost_owners_span,
1546 boundary_v, num_threads);
1547
1548 // Create connectivities required higher-order geometries for creating
1549 // a Geometry object
1550 for (int i = 0; i < num_cell_types; ++i)
1551 {
1552 const auto& entity_dofs = doflayouts[i].entity_dofs_all();
1553 for (int dim = 1; dim < topology.dim(); ++dim)
1554 {
1555 // Accumulate count of all dofs on this dimension
1556 int dim_sum
1557 = std::accumulate(entity_dofs[dim].begin(), entity_dofs[dim].end(), 0,
1558 [](int c, auto v) { return c + v.size(); });
1559
1560 logger().debug("Counting entity dofs, dim={}: {}", dim, dim_sum);
1561 if (dim_sum > 0)
1562 topology.create_entities(dim, num_threads);
1563 }
1564
1565 if (elements[i].needs_dof_permutations())
1566 topology.create_cell_permutations(num_threads);
1567 }
1568
1569 // Cell 'node' indices (global), as a single flat array. This is
1570 // `cells1` for a single cell type, and concatenated otherwise.
1571 std::vector<std::int64_t> nodes2_storage;
1572 std::span<const std::int64_t> nodes2;
1573 if (num_cell_types == 1)
1574 nodes2 = cells1.front();
1575 else
1576 {
1577 std::size_t size = 0;
1578 for (const std::vector<std::int64_t>& c : cells1)
1579 size += c.size();
1580 nodes2_storage.reserve(size);
1581 for (const std::vector<std::int64_t>& c : cells1)
1582 nodes2_storage.insert(nodes2_storage.end(), c.begin(), c.end());
1583 nodes2 = nodes2_storage;
1584 }
1585
1586 // Sorted list of unique (global) node indices. For 'P1 geometry' the
1587 // nodes are the vertices, which `create_topology` has already sorted
1588 // and made unique, so re-deriving them from the (much larger) cell
1589 // array is avoided.
1590 std::vector<std::int64_t> nodes1;
1591 if (p1_geometry)
1592 nodes1 = std::move(vertex_index);
1593 else
1594 {
1595 nodes1.assign(nodes2.begin(), nodes2.end());
1596 dolfinx::radix_sort(nodes1);
1597 auto [unique_end, range_end] = std::ranges::unique(nodes1);
1598 nodes1.erase(unique_end, range_end);
1599 }
1600
1601 std::vector coords
1602 = dolfinx::MPI::distribute_data(comm, nodes1, commg, x, xshape[1]);
1603
1604 // Create geometry object
1605 Geometry geometry
1606 = create_geometry(topology, elements, nodes1, nodes2, coords, xshape[1]);
1607
1608#ifndef NDEBUG
1609 // Nodes in `x` that no cell references are dropped silently.
1610 {
1611 std::int64_t num_nodes_local = xshape[0];
1612 std::int64_t num_nodes = 0;
1613 int err = MPI_Allreduce(&num_nodes_local, &num_nodes, 1,
1614 dolfinx::MPI::mpi_t<std::int64_t>, MPI_SUM, comm);
1615 dolfinx::MPI::check_error(comm, err);
1616 if (std::int64_t num_used = geometry.index_map()->size_global();
1617 num_used != num_nodes and dolfinx::MPI::rank(comm) == 0)
1618 {
1619 logger().warn("{} of {} input geometry nodes are not referenced by any "
1620 "cell and have been dropped.",
1621 num_nodes - num_used, num_nodes);
1622 }
1623 }
1624#endif
1625
1626 return Mesh(comm, std::make_shared<Topology>(std::move(topology)),
1627 std::move(geometry));
1628}
1629
1678template <typename U>
1680 MPI_Comm comm, MPI_Comm commt, std::span<const std::int64_t> cells,
1682 typename std::remove_reference_t<typename U::value_type>>& element,
1683 MPI_Comm commg, const U& x, std::array<std::size_t, 2> xshape,
1684 const graph::Partitioner& partitioner, GhostMode ghost_mode,
1685 std::optional<std::int32_t> max_facet_to_cell_links, int num_threads,
1686 const graph::Reorder& reorder_fn = graph::Reorder{})
1687{
1688 return create_mesh(comm, commt, std::vector{cells}, std::vector{element},
1689 commg, x, xshape, partitioner, ghost_mode,
1690 max_facet_to_cell_links, num_threads, reorder_fn);
1691}
1692
1715template <typename U>
1717create_mesh(MPI_Comm comm, std::span<const std::int64_t> cells,
1719 std::remove_reference_t<typename U::value_type>>& element,
1720 const U& x, std::array<std::size_t, 2> xshape, GhostMode ghost_mode,
1721 std::optional<std::int32_t> max_facet_to_cell_links = 2)
1722{
1723 // A single rank has nothing to partition, so skip the default
1724 // partitioner and just assign global indices.
1725 graph::Partitioner partitioner
1726 = dolfinx::MPI::size(comm) == 1
1727 ? graph::Partitioner{.fn = graph::partition_fn(nullptr)}
1729 return create_mesh(comm, comm, std::vector{cells}, std::vector{element}, comm,
1730 x, xshape, partitioner, ghost_mode,
1731 max_facet_to_cell_links, 1);
1732}
1733
1749template <std::floating_point T>
1750std::pair<Geometry<T>, std::vector<int32_t>>
1752 std::span<const std::int32_t> subentity_to_entity)
1753{
1754 const Geometry<T>& geometry = mesh.geometry();
1755
1756 // Get the geometry dofs in the sub-geometry based on the entities in
1757 // sub-geometry
1758 const std::vector<std::int32_t> x_indices
1759 = entities_to_geometry(mesh, dim, subentity_to_entity, true).first;
1760
1761 std::vector<std::int32_t> sub_x_dofs = x_indices;
1762 std::ranges::sort(sub_x_dofs);
1763 auto [unique_end, range_end] = std::ranges::unique(sub_x_dofs);
1764 sub_x_dofs.erase(unique_end, range_end);
1765
1766 // Get the sub-geometry dofs owned by this process
1767 auto x_index_map = geometry.index_map();
1768 assert(x_index_map);
1769
1770 std::shared_ptr<common::IndexMap> sub_x_dof_index_map;
1771 std::vector<std::int32_t> subx_to_x_dofmap;
1772 {
1773 // An owner change is permitted here: a coordinate dof may be needed
1774 // by a sub-mesh cell on a ghosting rank but not on its owner.
1775 auto [map, new_to_old, owners_changed] = common::create_sub_index_map(
1776 *x_index_map, sub_x_dofs, common::IndexMapOrder::any);
1777 sub_x_dof_index_map = std::make_shared<common::IndexMap>(std::move(map));
1778 subx_to_x_dofmap = std::move(new_to_old);
1779 }
1780
1781 // Create sub-geometry coordinates
1782 std::span<const T> x = geometry.x();
1783 std::int32_t sub_num_x_dofs = subx_to_x_dofmap.size();
1784 std::vector<T> sub_x(3 * sub_num_x_dofs);
1785 for (std::int32_t i = 0; i < sub_num_x_dofs; ++i)
1786 {
1787 std::copy_n(std::next(x.begin(), 3 * subx_to_x_dofmap[i]), 3,
1788 std::next(sub_x.begin(), 3 * i));
1789 }
1790
1791 // Create geometry to sub-geometry map
1792 std::vector<std::int32_t> x_to_subx_dof_map(
1793 x_index_map->size_local() + x_index_map->num_ghosts(), -1);
1794 for (std::size_t i = 0; i < subx_to_x_dofmap.size(); ++i)
1795 x_to_subx_dof_map[subx_to_x_dofmap[i]] = i;
1796
1797 // Create sub-geometry dofmap
1798 std::vector<std::int32_t> sub_x_dofmap;
1799 sub_x_dofmap.reserve(x_indices.size());
1800 std::ranges::transform(x_indices, std::back_inserter(sub_x_dofmap),
1801 [&x_to_subx_dof_map](auto x_dof)
1802 {
1803 assert(x_to_subx_dof_map[x_dof] != -1);
1804 return x_to_subx_dof_map[x_dof];
1805 });
1806
1807 // Sub-geometry coordinate element
1808 CellType sub_xcell
1809 = cell_entity_type(geometry.cmaps().front().cell_shape(), dim, 0);
1810
1811 // Special handling of point meshes, as they only support constant
1812 // basis functions
1813 int degree
1814 = (sub_xcell == CellType::point) ? 0 : geometry.cmaps().front().degree();
1815 fem::CoordinateElement<T> sub_cmap(sub_xcell, degree,
1816 geometry.cmaps().front().variant());
1817
1818 // Sub-geometry input_global_indices
1819 const std::vector<std::int64_t>& igi = geometry.input_global_indices();
1820 std::vector<std::int64_t> sub_igi;
1821 sub_igi.reserve(subx_to_x_dofmap.size());
1822 std::ranges::transform(subx_to_x_dofmap, std::back_inserter(sub_igi),
1823 [&igi](auto sub_x_dof) { return igi[sub_x_dof]; });
1824
1825 // Create geometry
1826 return {Geometry(
1827 sub_x_dof_index_map,
1828 std::vector<std::vector<std::int32_t>>{std::move(sub_x_dofmap)},
1829 {sub_cmap}, std::move(sub_x), geometry.dim(), std::move(sub_igi)),
1830 std::move(subx_to_x_dofmap)};
1831}
1832
1842template <std::floating_point T>
1843std::tuple<Mesh<T>, EntityMap, EntityMap, std::vector<std::int32_t>>
1845 std::span<const std::int32_t> entities)
1846{
1847 if (dim < 0 or dim > mesh.topology()->dim())
1848 throw std::invalid_argument("dim out of range for mesh topology.");
1849
1850 // Create sub-topology
1851 mesh.topology_mutable()->create_connectivity(dim, 0);
1852 auto [topology, subentity_to_entity, subvertex_to_vertex]
1853 = mesh::create_subtopology(*mesh.topology(), dim, entities);
1854
1855 // Create sub-geometry
1856 const int tdim = mesh.topology()->dim();
1857 mesh.topology_mutable()->create_entities(dim);
1858 mesh.topology_mutable()->create_connectivity(dim, tdim);
1859 mesh.topology_mutable()->create_connectivity(tdim, dim);
1860 mesh.topology_mutable()->create_cell_permutations();
1861 auto [geometry, subx_to_x_dofmap]
1862 = mesh::create_subgeometry(mesh, dim, subentity_to_entity);
1863
1864 Mesh<T> submesh
1865 = Mesh(mesh.comm(), std::make_shared<Topology>(std::move(topology)),
1866 std::move(geometry));
1867 EntityMap entity_map(mesh.topology(), submesh.topology(), dim,
1868 subentity_to_entity);
1869 EntityMap vertex_map(mesh.topology(), submesh.topology(), 0,
1870 subvertex_to_vertex);
1871 return {std::move(submesh), std::move(entity_map), std::move(vertex_map),
1872 std::move(subx_to_x_dofmap)};
1873}
1874
1886template <typename T>
1888 const MeshTags<T>& tags,
1889 std::shared_ptr<const dolfinx::mesh::Topology> submesh_topology,
1890 const EntityMap& cell_map, const EntityMap& vertex_map)
1891{
1892 int tag_dim = tags.dim();
1893 int submesh_tdim = submesh_topology->dim();
1894 auto topology = tags.topology();
1895 if (tag_dim > submesh_tdim)
1896 {
1897 throw std::invalid_argument("Tag dimension must be less than or equal to "
1898 "submesh dimension");
1899 }
1900
1901 // Validate that cell_map/vertex_map relate `topology` (the tags'
1902 // parent topology) to `submesh_topology`, and have the dimension
1903 // this function assumes.
1904 if (cell_map.dim() != submesh_tdim)
1905 {
1906 throw std::invalid_argument(
1907 "cell_map dimension must equal the submesh topology dimension.");
1908 }
1909 if (cell_map.topology() != topology)
1910 {
1911 throw std::invalid_argument(
1912 "cell_map topology must match tags.topology().");
1913 }
1914 if (cell_map.sub_topology() != submesh_topology)
1915 {
1916 throw std::invalid_argument(
1917 "cell_map sub_topology must match submesh_topology.");
1918 }
1919 if (vertex_map.dim() != 0)
1920 throw std::invalid_argument("vertex_map dimension must be 0.");
1921 if (vertex_map.topology() != topology)
1922 {
1923 throw std::invalid_argument(
1924 "vertex_map topology must match tags.topology().");
1925 }
1926 if (vertex_map.sub_topology() != submesh_topology)
1927 {
1928 throw std::invalid_argument(
1929 "vertex_map sub_topology must match submesh_topology.");
1930 }
1931 std::shared_ptr<const dolfinx::common::IndexMap> sub_cell_imap
1932 = submesh_topology->index_map(submesh_tdim);
1933 if (!sub_cell_imap)
1934 {
1935 throw std::runtime_error(
1936 std::format("Entities of dimension {} does not exist in mesh topology.",
1937 submesh_tdim));
1938 }
1939
1940 // Create a map from parent entity to submesh cell
1941 std::int32_t submesh_num_cells
1942 = sub_cell_imap->size_local() + sub_cell_imap->num_ghosts();
1943 auto sub_cells = std::ranges::views::iota(0, submesh_num_cells);
1944 std::vector<std::int32_t> sub_cell_to_parent_entity
1945 = cell_map.sub_topology_to_topology(sub_cells, false);
1946
1947 // Create a full lookup for all cells on the parent mesh, as the tag can have
1948 // entities that are not in the submesh
1949 auto parent_entity_imap = topology->index_map(submesh_tdim);
1950 if (!parent_entity_imap)
1951 {
1952 throw std::runtime_error(std::format(
1953 "Entities of dimension {} does not exist in parent mesh topology.",
1954 submesh_tdim));
1955 }
1956 std::size_t num_parent_entities
1957 = parent_entity_imap->size_local() + parent_entity_imap->num_ghosts();
1958 std::vector<std::int32_t> parent_entity_to_sub_cell(num_parent_entities, -1);
1959 for (std::size_t i = 0; i < sub_cell_to_parent_entity.size(); ++i)
1960 {
1961 parent_entity_to_sub_cell[sub_cell_to_parent_entity[i]]
1962 = static_cast<std::int32_t>(i);
1963 }
1964
1965 // Get map from submesh vertex to parent vertex
1966 std::vector<std::int32_t> sub_to_parent_vertex;
1967 {
1968 auto sub_vertex_map = submesh_topology->index_map(0);
1969 std::int32_t num_sub_vertices
1970 = sub_vertex_map->size_local() + sub_vertex_map->num_ghosts();
1971 auto sub_vertices = std::ranges::views::iota(0, num_sub_vertices);
1972
1973 sub_to_parent_vertex
1974 = vertex_map.sub_topology_to_topology(sub_vertices, false);
1975 }
1976 // Access various connectivity maps
1977 auto sub_e_to_v = submesh_topology->connectivity(tag_dim, 0);
1978 auto sub_c_to_e = submesh_topology->connectivity(submesh_tdim, tag_dim);
1979 auto sub_entity_imap = submesh_topology->index_map(tag_dim);
1980 auto e_to_v = topology->connectivity(tag_dim, 0);
1981 std::shared_ptr<const dolfinx::graph::AdjacencyList<std::int32_t>>
1982 e_to_sub_cell = nullptr;
1983 if (tag_dim != submesh_tdim)
1984 {
1985 e_to_sub_cell = topology->connectivity(tag_dim, submesh_tdim);
1986 if (!e_to_sub_cell)
1987 {
1988 throw std::runtime_error(
1989 std::format("Missing connectivity between {} and {} in parent mesh",
1990 tag_dim, submesh_tdim));
1991 }
1992 }
1993
1994 if (!sub_e_to_v)
1995 {
1996 throw std::runtime_error(std::format(
1997 "Missing connectivity between {} and {} in submesh", tag_dim, 0));
1998 }
1999 if (!sub_c_to_e)
2000 {
2001 throw std::runtime_error(
2002 std::format("Missing connectivity between {} and {} in submesh",
2003 submesh_tdim, tag_dim));
2004 }
2005 if (!sub_entity_imap)
2006 {
2007 throw std::runtime_error(std::format(
2008 "Entities of dimension {} does not exist in submesh topology.",
2009 tag_dim));
2010 }
2011 if (!e_to_v)
2012 {
2013 throw std::runtime_error(
2014 std::format("Missing connectivity between {} and 0", tag_dim));
2015 }
2016
2017 // Prepare sub entity to parent map
2018 std::size_t num_sub_entities
2019 = sub_entity_imap->size_local() + sub_entity_imap->num_ghosts();
2020 std::vector<T> submesh_values(num_sub_entities);
2021 std::vector<std::int8_t> submesh_value_found(num_sub_entities, 0);
2022 std::vector<std::int32_t> submesh_indices(num_sub_entities);
2023 std::iota(submesh_indices.begin(), submesh_indices.end(), 0);
2024
2025 std::span<const std::int32_t> tagged_entities = tags.indices();
2026 std::span<const T> tagged_values = tags.values();
2027 // For each entity in the tag, find all cells of the submesh connected to this
2028 // entity
2029 for (std::size_t i = 0; i < tagged_entities.size(); ++i)
2030 {
2031 auto find_and_map_sub_entity
2032 = [tag_dim, submesh_tdim, &e_to_v, &parent_entity_to_sub_cell,
2033 &sub_to_parent_vertex, &sub_e_to_v, &sub_c_to_e,
2034 &e_to_sub_cell](std::int32_t entity)
2035 {
2036 // Fast exit if the tag dimension is the same as the submesh dimension,
2037 // as we can directly map the parent entity to the submesh cell
2038 if (tag_dim == submesh_tdim)
2039 return parent_entity_to_sub_cell[entity];
2040
2041 // Given an entity in the parent meshtag, find all submesh-cells that are
2042 // entities in parent mesh that contain this entity.
2043 auto entity_vertices = e_to_v->links(entity);
2044 auto parent_sub_cells = e_to_sub_cell->links(entity);
2045 auto submesh_cells
2046 = parent_sub_cells
2047 | std::views::transform([&parent_entity_to_sub_cell](auto c)
2048 { return parent_entity_to_sub_cell[c]; })
2049 | std::views::filter([](auto sub_cell) { return sub_cell != -1; });
2050 for (auto sub_cell : submesh_cells)
2051 {
2052 for (auto sub_entity : sub_c_to_e->links(sub_cell))
2053 {
2054 // Convert submesh entity vertices to parent vertices
2055 auto parent_vertices
2056 = sub_e_to_v->links(sub_entity)
2057 | std::views::transform([&sub_to_parent_vertex](auto v)
2058 { return sub_to_parent_vertex[v]; });
2059
2060 // Check if all parent vertices of the submesh entity are in the
2061 // parent entity
2062 bool entity_matches = std::ranges::all_of(
2063 parent_vertices,
2064 [&entity_vertices](auto p_v)
2065 {
2066 // With C++23 this can use std::ranges::contains
2067 return std::ranges::find(entity_vertices, p_v)
2068 != std::ranges::end(entity_vertices);
2069 });
2070
2071 // If a match is found, apply values and exit the lambda immediately
2072 if (entity_matches)
2073 return sub_entity;
2074 }
2075 }
2076 return -1;
2077 };
2078
2079 // Execute the search for the current entity
2080 std::int32_t sub_entity = find_and_map_sub_entity(tagged_entities[i]);
2081 if (sub_entity != -1)
2082 {
2083 submesh_values[sub_entity] = tagged_values[i];
2084 submesh_value_found[sub_entity] = 1;
2085 }
2086 }
2087
2088 // Filter out the entities that were never mapped. Tracked with a
2089 // separate flag rather than a sentinel value, since any T value
2090 // (including numeric_limits<T>::max()) is a legitimate tag value.
2091 std::vector<std::int32_t> filtered_indices;
2092 std::vector<T> filtered_values;
2093 filtered_indices.reserve(num_sub_entities);
2094 filtered_values.reserve(num_sub_entities);
2095 for (std::size_t i = 0; i < submesh_values.size(); ++i)
2096 {
2097 if (submesh_value_found[i])
2098 {
2099 filtered_indices.push_back(submesh_indices[i]);
2100 filtered_values.push_back(submesh_values[i]);
2101 }
2102 }
2103 filtered_indices.shrink_to_fit();
2104 filtered_values.shrink_to_fit();
2105 MeshTags<T> new_meshtag(submesh_topology, tag_dim, filtered_indices,
2106 filtered_values, tags.name());
2107 return new_meshtag;
2108}
2109
2110} // namespace dolfinx::mesh
Definition CoordinateElement.h:39
ElementDofLayout create_dof_layout() const
Compute and return the dof layout.
Definition CoordinateElement.cpp:82
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:70
bool is_discontinuous() const
Check if the element is the discontinuous version of the coordinate element.
Definition CoordinateElement.cpp:246
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:36
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:128
int dim() const
Get the topological dimension of the entities related by this EntityMap.
Definition EntityMap.cpp:18
std::shared_ptr< const Topology > sub_topology() const
Get the sub-topology.
Definition EntityMap.cpp:25
std::shared_ptr< const Topology > topology() const
Get the (parent) topology.
Definition EntityMap.cpp:20
Geometry stores the geometry imposed on a mesh.
Definition Geometry.h:39
MeshTags associate values with mesh topology entities.
Definition MeshTags.h:37
const std::string & name() const
Return name.
Definition MeshTags.h:124
std::span< const std::int32_t > indices() const
Definition MeshTags.h:112
std::span< const T > values() const
Values attached to topology entities.
Definition MeshTags.h:115
int dim() const
Return topological dimension of tagged entities.
Definition MeshTags.h:118
std::shared_ptr< const Topology > topology() const
Return topology.
Definition MeshTags.h:121
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
std::shared_ptr< Topology > topology()
Get mesh topology.
Definition Mesh.h:74
Requirements on function for geometry marking.
Definition utils.h:559
Small, foundational mesh types (enums, etc.) with minimal dependencies.
void mpi_check(MPI_Comm comm, std::string_view op_name, const std::exception_ptr &error)
Collectively propagate a local failure captured by try_locally to every rank of comm,...
Definition utils.h:959
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:66
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:97
std::vector< std::int64_t > reorder_cells(const graph::Reorder &reorder_fn, std::span< const double > cell_centroids, int gdim, std::optional< std::int32_t > max_facet_to_cell_links, const std::vector< CellType > &celltypes, const std::vector< fem::ElementDofLayout > &doflayouts, const std::vector< std::vector< int > > &ghost_owners, std::vector< std::vector< std::int64_t > > &cells, std::vector< std::span< std::int64_t > > &cells_v, std::vector< std::vector< std::int64_t > > &original_idx, int num_threads)
Find a mesh's process boundary vertices, reordering cells for locality as a side effect.
Definition utils.cpp:35
std::vector< double > cell_centroids_local(std::span< const int > num_vertices_per_cell, const std::vector< std::span< const std::int64_t > > &cells, const boost::unordered_flat_map< std::int64_t, std::size_t > &node_to_pos, std::span< const T > coords, int gdim)
Compute the centroid of each cell from vertex coordinates already available locally.
Definition utils.h:998
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:1145
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:513
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:1057
void try_locally(F &&fn, std::exception_ptr &error)
Run fn locally, capturing any exception it throws instead of letting it propagate.
Definition utils.h:922
MPI_Datatype mpi_t
Retrieves the MPI data type associated to the provided type.
Definition MPI.h:326
void check_error(MPI_Comm comm, int code) noexcept
Check MPI error code. If the error code is not equal to MPI_SUCCESS, then std::abort is called.
Definition MPI.cpp:89
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:876
int rank(MPI_Comm comm)
Return process rank for the communicator.
Definition MPI.cpp:73
void error(PetscErrorCode error_code, std::string_view petsc_function, std::source_location loc=std::source_location::current())
Print error message for a PETSc call that returned an error and throw a std::runtime_error.
Definition petsc.cpp:21
@ any
Allow arbitrary ghost-index ordering.
Definition IndexMap.h:29
std::tuple< IndexMap, std::vector< std::int32_t >, bool > create_sub_index_map(const IndexMap &imap, std::span< const std::int32_t > indices, IndexMapOrder order=IndexMapOrder::any)
Create an index map from a subset of an existing map.
Definition IndexMap.cpp:941
void cells(la::SparsityPattern &pattern, const std::pair< R0, R1 > &cells, std::array< std::reference_wrapper< const DofMap >, 2 > dofmaps)
Iterate over cells and insert entries into sparsity pattern.
Definition sparsitybuild.h:40
Finite element method functionality.
Definition assemble_expression_impl.h:28
Geometry data structures and algorithms.
Definition BoundingBoxTree.h:26
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:158
std::variant< reorder_graph_fn, reorder_geom_fn > Reorder
A graph or geometric reordering function for mesh cells.
Definition ordering.h:59
bool has_partitioner(const AnyPartitionFunction &partitioner)
Whether an AnyPartitionFunction holds a callable partitioner.
Definition partition.cpp:131
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
std::function< std::vector< std::int32_t >( std::span< const double > x, int gdim)> reorder_geom_fn
Signature of functions that reorder points from their positions.
Definition ordering.h:52
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
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 graph::Reorder &reorder_fn=graph::Reorder{})
Create a distributed mesh::Mesh from mesh data and using the provided graph partitioning function for...
Definition utils.h:1402
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:913
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:352
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:1684
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:1844
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:287
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:304
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:693
CellType
Cell type identifier.
Definition cell_types.h:24
int num_cell_vertices(CellType type)
Number vertices for a cell type.
Definition cell_types.cpp:106
std::vector< T > h(const Mesh< T > &mesh, std::span< const std::int32_t > entities, int dim)
Compute greatest distance between any two geometry nodes of the mesh entities (h).
Definition utils.h:300
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:1751
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:242
MeshTags< T > transfer_meshtags_to_submesh(const MeshTags< T > &tags, std::shared_ptr< const dolfinx::mesh::Topology > submesh_topology, const EntityMap &cell_map, const EntityMap &vertex_map)
Transfer a meshtags object from a parent to a submesh.
Definition utils.h:1887
std::pair< std::vector< std::int32_t >, std::array< std::size_t, 2 > > entities_to_geometry(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities, bool permute=false)
Compute the geometry degrees of freedom associated with the closure of a given set of cell entities.
Definition utils.h:774
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:346
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:253
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:582
std::vector< T > compute_midpoints(const Mesh< T > &mesh, int dim, std::span< const std::int32_t > entities)
Compute the midpoints for mesh entities of a given dimension.
Definition utils.h:475
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:113
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
auto logger() -> spdlog::logger &
Dolfinx's logger.
Definition log.cpp:24
An AnyPartitionFunction together with the node weights it should be called with, if any.
Definition partition.h:156