DOLFINx 0.12.0.0
DOLFINx C++
Loading...
Searching...
No Matches
plaza.h
1// Copyright (C) 2014-2018 Chris Richardson
2// Copyright (C) 2026 Garth N. Wells
3//
4// This file is part of DOLFINx (https://www.fenicsproject.org)
5//
6// SPDX-License-Identifier: LGPL-3.0-or-later
7
8#include "dolfinx/common/IndexMap.h"
9#include "dolfinx/graph/AdjacencyList.h"
10#include "dolfinx/mesh/Mesh.h"
11#include "dolfinx/mesh/Topology.h"
12#include "dolfinx/mesh/utils.h"
13#include "option.h"
14#include "utils.h"
15#include <algorithm>
16#include <cmath>
17#include <cstdint>
18#include <optional>
19#include <span>
20#include <stdexcept>
21#include <tuple>
22#include <utility>
23#include <vector>
24
25#pragma once
26
33{
34namespace impl
35{
39template <int tdim>
40auto compute_parent_facets(std::span<const std::int32_t> simplex_set)
41{
42 static_assert(tdim == 2 or tdim == 3);
43 assert(simplex_set.size() % (tdim + 1) == 0);
44 using parent_facet_t
45 = std::conditional_t<tdim == 2, std::array<std::int8_t, 12>,
46 std::array<std::int8_t, 32>>;
47 parent_facet_t parent_facet;
48 parent_facet.fill(-1);
49 assert(simplex_set.size() <= parent_facet.size());
50
51 // Index lookups in 'indices' for the child vertices that occur on
52 // each parent facet in 2D and 3D. In 2D each edge has 3 child
53 // vertices, and in 3D each triangular facet has six child vertices.
54 constexpr std::array<std::array<int, 3>, 3> facet_table_2d{
55 {{1, 2, 3}, {0, 2, 4}, {0, 1, 5}}};
56
57 constexpr std::array<std::array<int, 6>, 4> facet_table_3d{
58 {{1, 2, 3, 4, 5, 6},
59 {0, 2, 3, 4, 7, 8},
60 {0, 1, 3, 5, 7, 9},
61 {0, 1, 2, 6, 8, 9}}};
62
63 const int ncells = simplex_set.size() / (tdim + 1);
64 for (int fpi = 0; fpi < (tdim + 1); ++fpi)
65 {
66 // For each child cell, consider all facets
67 for (int cc = 0; cc < ncells; ++cc)
68 {
69 for (int fci = 0; fci < (tdim + 1); ++fci)
70 {
71 // Indices of all vertices on child facet, sorted
72 std::array<int, tdim> cf, set_output;
73
74 int num_common_vertices;
75 if constexpr (tdim == 2)
76 {
77 for (int j = 0; j < tdim; ++j)
78 cf[j] = simplex_set[cc * 3 + facet_table_2d[fci][j]];
79
80 std::ranges::sort(cf);
81 auto [last1, last2, it_last] = std::ranges::set_intersection(
82 facet_table_2d[fpi], cf, set_output.begin());
83 num_common_vertices
84 = std::ranges::distance(set_output.begin(), it_last);
85 }
86 else
87 {
88 for (int j = 0; j < tdim; ++j)
89 cf[j] = simplex_set[cc * 4 + facet_table_3d[fci][j]];
90
91 std::ranges::sort(cf);
92 auto [last1, last2, it_last] = std::ranges::set_intersection(
93 facet_table_3d[fpi], cf, set_output.begin());
94 num_common_vertices
95 = std::ranges::distance(set_output.begin(), it_last);
96 }
97
98 if (num_common_vertices == tdim)
99 {
100 assert(parent_facet[cc * (tdim + 1) + fci] == -1);
101 // Child facet "fci" of cell cc, lies on parent facet "fpi"
102 parent_facet[cc * (tdim + 1) + fci] = fpi;
103 }
104 }
105 }
106 }
107
108 return parent_facet;
109}
110
129std::pair<std::array<std::int32_t, 32>, std::size_t>
130get_simplices(std::span<const std::int64_t> indices,
131 std::span<const std::int32_t> longest_edge, int tdim,
132 bool uniform);
133
136void enforce_rules(MPI_Comm comm, const graph::AdjacencyList<int>& shared_edges,
137 std::span<std::int8_t> marked_edges,
138 const mesh::Topology& topology,
139 std::span<const std::int32_t> long_edge);
140
150template <std::floating_point T>
151std::pair<std::vector<std::int32_t>, std::vector<std::int8_t>>
152face_long_edge(const mesh::Mesh<T>& mesh)
153{
154 const int tdim = mesh.topology()->dim();
155 // FIXME: cleanup these calls? Some of the happen internally again.
156 mesh.topology_mutable()->create_entities(1);
157 mesh.topology_mutable()->create_entities(2);
158 mesh.topology_mutable()->create_connectivity(2, 1);
159 mesh.topology_mutable()->create_connectivity(1, tdim);
160 mesh.topology_mutable()->create_connectivity(tdim, 2);
161
162 std::int64_t num_faces = mesh.topology()->index_map(2)->size_local()
163 + mesh.topology()->index_map(2)->num_ghosts();
164
165 // Storage for face-local index of longest edge
166 std::vector<std::int32_t> long_edge(num_faces);
167 std::vector<std::int8_t> edge_ratio_ok;
168
169 // Check mesh face quality (may be used in 2D to switch to "uniform"
170 // refinement)
171 const T min_ratio = std::sqrt(2.0) / 2.0;
172 if (tdim == 2)
173 edge_ratio_ok.resize(num_faces);
174
175 auto x_dofmap = mesh.geometry().dofmaps().front();
176
177 auto c_to_v = mesh.topology()->connectivity(tdim, 0);
178 assert(c_to_v);
179 auto e_to_c = mesh.topology()->connectivity(1, tdim);
180 assert(e_to_c);
181 auto e_to_v = mesh.topology()->connectivity(1, 0);
182 assert(e_to_v);
183
184 // Store all edge lengths in Mesh to save recalculating for each Face
185 auto map_e = mesh.topology()->index_map(1);
186 assert(map_e);
187 std::vector<T> edge_length(map_e->size_local() + map_e->num_ghosts());
188 for (std::size_t e = 0; e < edge_length.size(); ++e)
189 {
190 // Get first attached cell
191 auto cells = e_to_c->links(e);
192 assert(!cells.empty());
193 auto cell_vertices = c_to_v->links(cells.front());
194 auto edge_vertices = e_to_v->links(e);
195
196 // Find local index of edge vertices in the cell geometry map
197 auto it0 = std::find(cell_vertices.begin(), cell_vertices.end(),
198 edge_vertices[0]);
199 assert(it0 != cell_vertices.end());
200 const std::size_t local0
201 = std::ranges::distance(cell_vertices.begin(), it0);
202 auto it1 = std::find(cell_vertices.begin(), cell_vertices.end(),
203 edge_vertices[1]);
204 assert(it1 != cell_vertices.end());
205 const std::size_t local1
206 = std::ranges::distance(cell_vertices.begin(), it1);
207
208 auto x_dofs = md::submdspan(x_dofmap, cells.front(), md::full_extent);
209 std::span<const T, 3> x0(mesh.geometry().x().data() + 3 * x_dofs[local0],
210 3);
211 std::span<const T, 3> x1(mesh.geometry().x().data() + 3 * x_dofs[local1],
212 3);
213
214 // Compute length of edge between vertex x0 and x1
215 edge_length[e] = std::sqrt(std::transform_reduce(
216 x0.begin(), x0.end(), x1.begin(), 0.0, std::plus<>(),
217 [](auto x0, auto x1) { return (x0 - x1) * (x0 - x1); }));
218 }
219
220 // Get longest edge of each face
221 auto f_to_v = mesh.topology()->connectivity(2, 0);
222 assert(f_to_v);
223 auto f_to_e = mesh.topology()->connectivity(2, 1);
224 assert(f_to_e);
225 const std::vector global_indices
226 = mesh.topology()->index_map(0)->global_indices();
227 for (int f = 0; f < f_to_v->num_nodes(); ++f)
228 {
229 auto face_edges = f_to_e->links(f);
230
231 std::int32_t imax = 0;
232 T max_len = 0.0;
233 T min_len = std::numeric_limits<T>::max();
234
235 for (int i = 0; i < 3; ++i)
236 {
237 const T e_len = edge_length[face_edges[i]];
238 min_len = std::min(e_len, min_len);
239 if (e_len > max_len)
240 {
241 max_len = e_len;
242 imax = i;
243 }
244 else if (tdim == 3 and e_len == max_len)
245 {
246 // If edges are the same length, compare global index of
247 // opposite vertex. Only important so that tetrahedral faces
248 // have a matching refinement pattern across processes.
249 auto vertices = f_to_v->links(f);
250 const int vmax = vertices[imax];
251 const int vi = vertices[i];
252 if (global_indices[vi] > global_indices[vmax])
253 imax = i;
254 }
255 }
256
257 // Only save edge ratio in 2D
258 if (tdim == 2)
259 edge_ratio_ok[f] = (min_len / max_len >= min_ratio);
260
261 long_edge[f] = face_edges[imax];
262 }
263
264 return std::pair(std::move(long_edge), std::move(edge_ratio_ok));
265}
266
291template <std::floating_point T>
292std::tuple<graph::AdjacencyList<std::int64_t>, std::vector<T>,
293 std::array<std::size_t, 2>, std::optional<std::vector<std::int32_t>>,
294 std::optional<std::vector<std::int8_t>>>
295compute_refinement(MPI_Comm neighbor_comm,
296 std::span<const std::int8_t> marked_edges,
297 const graph::AdjacencyList<int>& shared_edges,
298 const mesh::Mesh<T>& mesh,
299 std::span<const std::int32_t> long_edge,
300 std::span<const std::int8_t> edge_ratio_ok, Option option)
301{
302 int tdim = mesh.topology()->dim();
303 int num_cell_edges = tdim * 3 - 3;
304 int num_cell_vertices = tdim + 1;
305
306 bool compute_facets = option_parent_facet(option);
307 bool compute_parent_cell = option_parent_cell(option);
308
309 // Make new vertices in parallel
310 const auto [new_vertex_map, new_vertex_coords, xshape]
311 = create_new_vertices(neighbor_comm, shared_edges, mesh, marked_edges);
312
313 std::optional<std::vector<std::int32_t>> parent_cell(std::nullopt);
314 if (compute_parent_cell)
315 parent_cell.emplace();
316
317 std::optional<std::vector<std::int8_t>> parent_facet(std::nullopt);
318 if (compute_facets)
319 parent_facet.emplace();
320
321 std::vector<std::int64_t> indices(num_cell_vertices + num_cell_edges);
322
323 auto map_c = mesh.topology()->index_map(tdim);
324 assert(map_c);
325 auto c_to_v = mesh.topology()->connectivity(tdim, 0);
326 assert(c_to_v);
327 auto c_to_e = mesh.topology()->connectivity(tdim, 1);
328 assert(c_to_e);
329 auto c_to_f = mesh.topology()->connectivity(tdim, 2);
330 assert(c_to_f);
331
332 std::int32_t num_new_vertices_local = std::count(
333 marked_edges.begin(),
334 marked_edges.begin() + mesh.topology()->index_map(1)->size_local(), true);
335
336 std::vector<std::int64_t> global_indices
337 = adjust_indices(*mesh.topology()->index_map(0), num_new_vertices_local);
338
339 const std::int32_t num_cells = map_c->size_local();
340
341 // Refine cells with marked edges.
342 const std::size_t max_children = std::size_t(1) << tdim;
343 std::vector<std::int64_t> cell_topology;
344 cell_topology.reserve(static_cast<std::size_t>(num_cells) * max_children
345 * num_cell_vertices);
346 if (compute_parent_cell)
347 parent_cell->reserve(static_cast<std::size_t>(num_cells) * max_children);
348 if (compute_facets)
349 {
350 parent_facet->reserve(static_cast<std::size_t>(num_cells) * max_children
351 * num_cell_vertices);
352 }
353
354 // Longest edge of each face in cell-local indexing.
355 std::vector<std::int32_t> longest_edge;
356 for (int c = 0; c < num_cells; ++c)
357 {
358 // Create vector of indices in the order [vertices][edges], 3+3 in
359 // 2D, 4+6 in 3D
360
361 // Copy vertices
362 auto vertices = c_to_v->links(c);
363 for (std::size_t v = 0; v < vertices.size(); ++v)
364 indices[v] = global_indices[vertices[v]];
365
366 // Get cell-local indices of marked edges
367 auto edges = c_to_e->links(c);
368 bool no_edge_marked = true;
369 for (std::size_t ei = 0; ei < edges.size(); ++ei)
370 {
371 if (marked_edges[edges[ei]])
372 {
373 no_edge_marked = false;
374 auto nv = new_vertex_map.links(edges[ei]);
375 assert(nv.size() == 1);
376 indices[num_cell_vertices + ei] = nv[0];
377 }
378 else
379 indices[num_cell_vertices + ei] = -1;
380 }
381
382 if (no_edge_marked)
383 {
384 // Copy over existing cell to new topology
385 for (auto v : vertices)
386 cell_topology.push_back(global_indices[v]);
387
388 if (compute_parent_cell)
389 parent_cell->push_back(c);
390
391 if (compute_facets)
392 {
393 if (tdim == 3)
394 parent_facet->insert(parent_facet->end(), {0, 1, 2, 3});
395 else
396 parent_facet->insert(parent_facet->end(), {0, 1, 2});
397 }
398 }
399 else
400 {
401 // Need longest edges of each face in cell local indexing. NB in
402 // 2D the face is the cell itself, and there is just one entry.
403 longest_edge.clear();
404 for (auto f : c_to_f->links(c))
405 longest_edge.push_back(long_edge[f]);
406
407 // Convert to cell local index
408 for (std::int32_t& p : longest_edge)
409 {
410 for (std::size_t ej = 0; ej < edges.size(); ++ej)
411 {
412 if (p == edges[ej])
413 {
414 p = ej;
415 break;
416 }
417 }
418 }
419
420 const bool uniform = (tdim == 2) ? edge_ratio_ok[c] : false;
421 const auto [simplex_set_b, simplex_set_size]
422 = get_simplices(indices, longest_edge, tdim, uniform);
423 std::span<const std::int32_t> simplex_set(simplex_set_b.data(),
424 simplex_set_size);
425
426 // Save parent index
427 const std::int32_t ncells = simplex_set.size() / num_cell_vertices;
428 if (compute_parent_cell)
429 {
430 for (std::int32_t i = 0; i < ncells; ++i)
431 parent_cell->push_back(c);
432 }
433
434 if (compute_facets)
435 {
436 if (tdim == 3)
437 {
438 auto npf = compute_parent_facets<3>(simplex_set);
439 parent_facet->insert(parent_facet->end(), npf.begin(),
440 std::next(npf.begin(), simplex_set.size()));
441 }
442 else
443 {
444 auto npf = compute_parent_facets<2>(simplex_set);
445 parent_facet->insert(parent_facet->end(), npf.begin(),
446 std::next(npf.begin(), simplex_set.size()));
447 }
448 }
449
450 // Convert from cell local index to mesh index and add to cells
451 for (std::int32_t v : simplex_set)
452 cell_topology.push_back(indices[v]);
453 }
454 }
455
456 assert(cell_topology.size() % num_cell_vertices == 0);
457 std::vector<std::int32_t> offsets(
458 cell_topology.size() / num_cell_vertices + 1, 0);
459 for (std::size_t i = 0; i < offsets.size() - 1; ++i)
460 offsets[i + 1] = offsets[i] + num_cell_vertices;
461 graph::AdjacencyList cell_adj(std::move(cell_topology), std::move(offsets));
462
463 return {std::move(cell_adj), std::move(new_vertex_coords), xshape,
464 std::move(parent_cell), std::move(parent_facet)};
465}
466} // namespace impl
467
477template <std::floating_point T>
478std::tuple<graph::AdjacencyList<std::int64_t>, std::vector<T>,
479 std::array<std::size_t, 2>, std::optional<std::vector<std::int32_t>>,
480 std::optional<std::vector<std::int8_t>>>
482 std::optional<std::span<const std::int32_t>> edges,
483 Option option)
484{
485 common::Timer t0("PLAZA: refine");
486 auto topology = mesh.topology();
487 assert(topology);
488
489 if (topology->cell_type() != mesh::CellType::triangle
490 and topology->cell_type() != mesh::CellType::tetrahedron)
491 {
492 throw std::invalid_argument("Cell type not supported");
493 }
494
495 auto map_e = topology->index_map(1);
496 if (!map_e)
497 throw std::runtime_error("Edges must be initialised");
498
499 // Ranks sharing an owned or ghost edge with this rank, and for each
500 // edge the sharing ranks as positions in ranks
501 auto [ranks, _data, _offsets] = common::compute_sharing_neighbourhood(*map_e);
502 graph::AdjacencyList<int> edge_ranks(std::move(_data), std::move(_offsets));
503
504 // Get number of neighbors
505 std::vector<std::int8_t> marked_edges(
506 map_e->size_local() + map_e->num_ghosts(), !edges.has_value());
507 std::vector<std::vector<std::int32_t>> marked_for_update(ranks.size());
508
509 if (edges)
510 {
511 for (auto edge : edges.value())
512 {
513 if (!marked_edges[edge])
514 {
515 marked_edges[edge] = true;
516
517 // If it is a shared edge, add all sharing neighbors to update set
518 for (int rank : edge_ranks.links(edge))
519 marked_for_update[rank].push_back(edge);
520 }
521 }
522 }
523
524 MPI_Comm comm;
525 MPI_Dist_graph_create_adjacent(mesh.comm(), ranks.size(), ranks.data(),
526 MPI_UNWEIGHTED, ranks.size(), ranks.data(),
527 MPI_UNWEIGHTED, MPI_INFO_NULL, false, &comm);
528
529 // Communicate any shared edges
530 update_logical_edgefunction(comm, marked_for_update, marked_edges, *map_e);
531
532 // Enforce rules about refinement (i.e. if any edge is marked in a
533 // triangle, then the longest edge must also be marked).
534 const auto [long_edge, edge_ratio_ok] = impl::face_long_edge(mesh);
535 impl::enforce_rules(comm, edge_ranks, marked_edges, *topology, long_edge);
536
537 auto [cell_adj, new_vertex_coords, xshape, parent_cell, parent_facet]
538 = impl::compute_refinement(comm, marked_edges, edge_ranks, mesh,
539 long_edge, edge_ratio_ok, option);
540 MPI_Comm_free(&comm);
541
542 return {std::move(cell_adj), std::move(new_vertex_coords), xshape,
543 std::move(parent_cell), std::move(parent_facet)};
544}
545
546} // namespace dolfinx::refinement::plaza
Timer for measuring and logging elapsed time durations.
Definition Timer.h:41
This class provides a static adjacency list data structure.
Definition AdjacencyList.h:41
std::span< LinkData > links(std::size_t node)
Get the links (edges) for given node.
Definition AdjacencyList.h:169
A Mesh consists of a set of connected and numbered mesh topological entities, and geometry data.
Definition Mesh.h:25
Topology stores the topology of a mesh, consisting of mesh entities and connectivity (incidence relat...
Definition Topology.h:49
Functions supporting mesh operations.
std::tuple< std::vector< int >, std::vector< int >, std::vector< std::int32_t > > compute_sharing_neighbourhood(const IndexMap &imap)
Compute the symmetric neighbourhood of ranks that share an owned or ghost index with the caller.
Definition IndexMap.cpp:984
Mesh data structures and algorithms on meshes.
Definition DofMap.h:32
Plaza mesh refinement.
Definition plaza.h:33
std::tuple< graph::AdjacencyList< std::int64_t >, std::vector< T >, std::array< std::size_t, 2 >, std::optional< std::vector< std::int32_t > >, std::optional< std::vector< std::int8_t > > > compute_refinement_data(const mesh::Mesh< T > &mesh, std::optional< std::span< const std::int32_t > > edges, Option option)
Definition plaza.h:481
std::tuple< graph::AdjacencyList< std::int64_t >, std::vector< T >, std::array< std::size_t, 2 > > create_new_vertices(MPI_Comm comm, const graph::AdjacencyList< int > &shared_edges, const mesh::Mesh< T > &mesh, std::span< const std::int8_t > marked_edges)
Add new vertex for each marked edge, and create new_vertex_coordinates and global_edge->new_vertex ma...
Definition utils.h:148
constexpr bool option_parent_cell(Option a)
Check if parent_cell flag is set.
Definition option.h:45
Option
Options for data to compute during mesh refinement.
Definition option.h:16
@ parent_cell
Definition option.h:20
@ parent_facet
Definition option.h:18
constexpr bool option_parent_facet(Option a)
Check if parent_facet flag is set.
Definition option.h:36
std::vector< std::int64_t > adjust_indices(const common::IndexMap &map, std::int32_t n)
Given an index map, add "n" extra indices at the end of local range.
Definition utils.cpp:105
void update_logical_edgefunction(MPI_Comm comm, const std::vector< std::vector< std::int32_t > > &marked_for_update, std::span< std::int8_t > marked_edges, const common::IndexMap &map)
Communicate edge markers between processes that share edges.
Definition utils.cpp:49