ryujin 2.1.1 revision 4bf2aee841e245e84ddb1251f8ca4f7f06dda1bb
Loading...
Searching...
No Matches
cut_cell_primitives.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2025 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include <compile_time_options.h>
9
10#include <reference_cell.h>
11
12#include <deal.II/base/mpi.h>
13#include <deal.II/base/parameter_acceptor.h>
14#include <deal.II/distributed/tria_base.h>
15#include <deal.II/grid/grid_tools.h>
16#include <deal.II/grid/tria.h>
17#include <deal.II/grid/tria_description.h>
18
19#include <algorithm>
20#include <array>
21#include <bit>
22#include <cmath>
23#include <cstdint>
24#include <limits>
25#include <map>
26#include <optional>
27#include <tuple>
28#include <vector>
29
30namespace ryujin
31{
55 template <int dim>
56 class MeshAlignment : dealii::ParameterAcceptor
57 {
58 public:
59 MeshAlignment(const std::string &subsection)
60 : ParameterAcceptor(subsection)
61 {
62 acceptable_deviation_ = 0.1;
63 this->add_parameter(
64 "acceptable deviation",
65 acceptable_deviation_,
66 "mesh alignment: a corner of the elevation profile that is cut "
67 "off by a mesh edge or cell diagonal is captured by moving a "
68 "vertex onto it if the orthogonal deviation of the profile from "
69 "the chord exceeds this fraction of the cell size");
70
71 minimal_jacobian_fraction_ = 0.25;
72 this->add_parameter(
73 "minimal jacobian fraction",
74 minimal_jacobian_fraction_,
75 "mesh alignment: a vertex is not moved if the move reduces a "
76 "corner Jacobian of an adjacent cell below this fraction of the "
77 "cell volume; edges cut by the profile are resolved regardless "
78 "as long as no cell is inverted");
79
80 max_sweeps_ = 16;
81 this->add_parameter("maximal sweeps",
82 max_sweeps_,
83 "mesh alignment: maximal number of sweeps over all "
84 "cells when snapping vertices to corners");
85 }
86
87
88 /* The coordinate direction of the height: */
89 static constexpr unsigned int profile_direction = 1;
90
91
92 /*
93 * Classify a point with respect to the profile: -1 below, 0 on the
94 * profile (up to roundoff), +1 above.
95 */
96 template <typename Callable>
97 static int vertex_state(const Callable &height,
98 const dealii::Point<dim> &point)
99 {
100 const double h = height(point);
101 const double delta = point[profile_direction] - h;
102 const double tolerance =
103 1.e-12 *
104 std::max({1., std::abs(point[0]), std::abs(point[1]), std::abs(h)});
105 if (std::abs(delta) < tolerance)
106 return 0;
107 return delta > 0. ? 1 : -1;
108 }
109
110
111 /*
112 * Send the data of every locally owned cell (unless it is empty) to its
113 * ghost copies on the other ranks.
114 */
115 template <typename T>
116 static void exchange_to_ghosts(dealii::Triangulation<dim> &triangulation,
117 std::vector<T> &data)
118 {
119 using cell_iterator =
120 typename dealii::Triangulation<dim>::active_cell_iterator;
121
122 if (dynamic_cast<dealii::parallel::DistributedTriangulationBase<dim> *>(
123 &triangulation) == nullptr)
124 return;
125
126 dealii::GridTools::
127 exchange_cell_data_to_ghosts<T, dealii::Triangulation<dim>>(
128 triangulation,
129 [&](const cell_iterator &cell) -> std::optional<T> {
130 const auto &value = data[cell->active_cell_index()];
131 if (value == T())
132 return {};
133 return value;
134 },
135 [&](const cell_iterator &cell, const T &value) {
136 data[cell->active_cell_index()] = value;
137 });
138 }
139
140
159 template <typename Callable>
160 void align_with_elevation_profile(dealii::Triangulation<dim> &triangulation,
161 const Callable &height) const
162 {
163 constexpr unsigned int y = profile_direction;
164 constexpr auto invalid = dealii::numbers::invalid_unsigned_int;
165
166 using Requests = std::map<unsigned int, dealii::Point<dim>>;
167
168 const auto distributed =
169 dynamic_cast<dealii::parallel::DistributedTriangulationBase<dim> *>(
170 &triangulation);
171
172 /*
173 * We need to collect and construct a bunch of expensive information
174 * so that we can map from a vertex back to the surrounding cells:
175 */
176
177 const auto owned =
178 dealii::GridTools::get_locally_owned_vertices(triangulation);
179 const auto vertex_to_cells =
180 dealii::GridTools::vertex_to_cell_map(triangulation);
181
182 const unsigned int n_vertices = triangulation.n_vertices();
183 const unsigned int n_cells = triangulation.n_active_cells();
184
185 /*
186 * A bunch of small lambdas for the following algorithm:
187 */
188
189 /* The state (above/below the interface) of all vertices of a cell: */
190 const auto compute_vertex_states = [&]() {
191 std::vector<int> result(n_vertices, 0);
192 for (const auto &cell : triangulation.active_cell_iterators())
193 if (!cell->is_artificial())
194 for (unsigned int i = 0; i < 4; ++i)
195 result[cell->vertex_index(i)] =
196 vertex_state(height, cell->vertex(i));
197 return result;
198 };
199
200 /* Move vertex g to p (all cells share the vertex array): */
201 const auto move_vertex = //
202 [&](const unsigned int g, const dealii::Point<dim> &p) {
203 const auto &cell = *vertex_to_cells[g].begin();
204 for (unsigned int i = 0; i < 4; ++i)
205 if (cell->vertex_index(i) == g)
206 cell->vertex(i) = p;
207 };
208
209 /*
210 * Whether vertex g may move to p:
211 * - a vertex on the left or right boundary cannot move horizontally,
212 * - all Jacobian of the cells around g must remain positive.
213 *
214 * FIXME: we detect this by temporarily moving the mesh...
215 */
216 const auto admissible = [&](const auto &cell,
217 const unsigned int i,
218 const dealii::Point<dim> &p,
219 const double floor) {
220 const auto g = cell->vertex_index(i);
221
222 /* Boundary vertices cannot move horizontally: */
223 if (p[0] != cell->vertex(i)[0]) // FIXME check with roundoff
224 for (const auto &cell : vertex_to_cells[g])
225 for (const unsigned int f : {0u, 1u})
226 if (cell->face(f)->at_boundary())
227 for (unsigned int k = 0; k < 2; ++k)
228 if (cell->face(f)->vertex_index(k) == g)
229 return false;
230
231 /* Check Jacobian: */
232 const auto saved = cell->vertex(i);
233 move_vertex(g, p);
234 double jacobian = std::numeric_limits<double>::max();
235 for (const auto &cell : vertex_to_cells[g])
236 for (unsigned int k = 0; k < 4; ++k) {
237 const auto e_x = cell->vertex(k ^ 1) - cell->vertex(k);
238 const auto e_y = cell->vertex(k ^ 2) - cell->vertex(k);
239 const double sign = (k == 1 || k == 2) ? -1. : 1.;
240 jacobian =
241 std::min(jacobian, sign * (e_x[0] * e_y[1] - e_x[1] * e_y[0]));
242 }
243 move_vertex(g, saved);
244 return jacobian > floor;
245 };
246
247 /*
248 * Request a move of vertex g to p. Of two requests for the same
249 * vertex the one with the smaller displacement wins (then the
250 * lexicographically smaller target), independently of the order in
251 * which the requests are collected:
252 */
253 const auto propose = [&](Requests &requests,
254 const auto &cell,
255 const unsigned int i,
256 const dealii::Point<dim> &p) {
257 const auto g = cell->vertex_index(i);
258 const auto key = [&](const dealii::Point<dim> &q) {
259 return std::make_tuple((q - cell->vertex(i)).norm(), q[0], q[1]);
260 };
261 const auto [it, inserted] = requests.try_emplace(g, p);
262 if (!inserted && key(p) < key(it->second))
263 it->second = p;
264 };
265
266 /* Move locally owned vertices and communicate the positions: */
267 const auto apply = [&](const Requests &requests) {
268 for (const auto &[g, p] : requests)
269 if (owned[g])
270 move_vertex(g, p);
271 if (distributed != nullptr)
272 distributed->communicate_locally_moved_vertices(owned);
273 };
274
275 /*
276 * Step 1:
277 *
278 * Resolve the edges cut by the profile, first the vertical, then the
279 * horizontal ones. The cut is located by bisection and the closer
280 * vertex is moved onto it, or the other one if the move is not
281 * admissible. We first ask for a fraction of the cell volume at
282 * every corner, and then only require that no cell is inverted.
283 */
284
285 for (const unsigned int direction : {y, 0u}) {
286 const auto vertex_states = compute_vertex_states();
287 Requests requests;
288
289 for (const auto &cell : triangulation.active_cell_iterators()) {
290 if (cell->is_artificial())
291 continue;
292
293 const double h0 = cell->diameter();
294 const unsigned int bit = 1u << direction;
295 for (unsigned int i = 0; i < 4; ++i) {
296 const auto g_a = cell->vertex_index(i);
297 const auto g_b = cell->vertex_index(i | bit);
298 if ((i & bit) != 0 || vertex_states[g_a] * vertex_states[g_b] >= 0)
299 continue;
300
301 /* Bisection: */
302 const auto a = cell->vertex(i);
303 const auto d = cell->vertex(i | bit) - a;
304 double t_a = 0., t_b = 1.;
305 for (unsigned int k = 0; k < 60; ++k) {
306 const double t = 0.5 * (t_a + t_b);
307 const auto p = a + t * d;
308 (vertex_state(height, p) == vertex_states[g_a] ? t_a : t_b) = t;
309 }
310 const double t = 0.5 * (t_a + t_b);
311 auto cut = a + t * d;
312 cut[y] = height(cut);
313
314 const auto order =
315 t <= 0.5 ? std::array{i, i | bit} : std::array{i | bit, i};
316 bool done = false;
317 for (const double floor :
318 {minimal_jacobian_fraction_ * h0 * h0, 0.})
319 for (const auto j : order)
320 if (!done && admissible(cell, j, cut, floor)) {
321 propose(requests, cell, j, cut);
322 done = true;
323 }
324 }
325 }
326
327 apply(requests);
328 }
329
330 /*
331 * Step 2:
332 *
333 * Try to "snap" vertices onto corners of the profile cut off by a
334 * chord. Every sweep is a phase in which
335 *
336 * - every locally owned cell locates the corner of each of its
337 * chords and chooses the closest admissible vertex of the cell to
338 * capture it: an endpoint sliding along the profile or a vertex on
339 * the side of the corner. The owner of a cell sees all cells around
340 * the vertices of the cell, and sends the choice to the ghost
341 * copies of the cell;
342 *
343 * - of the vertices chosen in a cell only the one with the highest
344 * (pseudo-random) priority moves, so that the admissibility checks
345 * remain valid.
346 *
347 * A snapped vertex is pinned. Sweeps are repeated until no vertex moves.
348 */
349
350 const auto priority = [&](const dealii::Point<dim> &v) {
351 /* Hash the coordinate bits (hash_combine, splitmix64 finalizer): */
352 std::uint64_t hash = 0x9e3779b97f4a7c15ull;
353 for (unsigned int d = 0; d < dim; ++d) {
354 const auto bits = std::bit_cast<std::uint64_t>(v[d]);
355 hash ^= bits + 0x9e3779b97f4a7c15ull + (hash << 6) + (hash >> 2);
356 hash *= 0xbf58476d1ce4e5b9ull;
357 hash ^= hash >> 31;
358 }
359 return hash;
360 };
361
362 constexpr std::array<std::pair<unsigned int, unsigned int>, 6> chords{
363 {{0, 1}, {2, 3}, {0, 2}, {1, 3}, {0, 3}, {1, 2}}};
364
365 const double nan = std::numeric_limits<double>::quiet_NaN();
366
367 std::vector<bool> pinned(n_vertices, false);
368
369 for (unsigned int sweep = 0; sweep < max_sweeps_; ++sweep) {
370 const auto vertex_states = compute_vertex_states();
371
372 /* The target of every vertex of a cell (NaN if not chosen): */
373 std::vector<std::vector<double>> records(n_cells);
374
375 for (const auto &cell : triangulation.active_cell_iterators()) {
376 if (!cell->is_locally_owned())
377 continue;
378
379 auto &record = records[cell->active_cell_index()];
380 const double h0 = cell->diameter();
381
382 for (const auto &[i_a, i_b] : chords) {
383 const auto g_a = cell->vertex_index(i_a);
384 const auto g_b = cell->vertex_index(i_b);
385 if (vertex_states[g_a] != 0 || vertex_states[g_b] != 0)
386 continue;
387
388 /*
389 * A diagonal only matters if the other two vertices lie on
390 * opposite sides of the profile:
391 */
392 if ((i_a ^ i_b) == 3 &&
393 vertex_states[cell->vertex_index(i_a ^ 1)] *
394 vertex_states[cell->vertex_index(i_a ^ 2)] >=
395 0)
396 continue;
397
398 /*
399 * Locate the point of maximal deviation of the profile from the
400 * chord (measured orthogonally to the chord, positive if the
401 * profile lies above), refine the search once around the best
402 * sample:
403 */
404 const auto a = cell->vertex(i_a);
405 const auto d = cell->vertex(i_b) - a;
406 double deviation = 0., t_best = 0., t_left = 0., t_right = 1.;
407 dealii::Point<dim> corner;
408 for (unsigned int level = 0; level < 2; ++level) {
409 for (unsigned int s = 1; s < 64; ++s) {
410 const double t = t_left + (t_right - t_left) * s / 64.;
411 auto p = a + t * d;
412 const double h = height(p);
413 const double delta = (h - p[y]) * std::abs(d[0]) / d.norm();
414 if (std::abs(delta) > std::abs(deviation)) {
415 deviation = delta;
416 p[y] = h;
417 corner = p;
418 t_best = t;
419 }
420 }
421 const double dt = (t_right - t_left) / 64.;
422 t_left = std::max(0., t_best - dt);
423 t_right = std::min(1., t_best + dt);
424 }
425
426 if (std::abs(deviation) < acceptable_deviation_ * h0)
427 continue;
428
429 /*
430 * Candidates are the two endpoints and the vertices on the side
431 * of the corner, the closest admissible one is recorded (if a
432 * vertex is chosen for two chords the smaller displacement
433 * wins):
434 */
435 const int side = deviation > 0. ? 1 : -1;
436 std::vector<std::tuple<double, double, double, unsigned int>>
437 candidates;
438 for (unsigned int i = 0; i < 4; ++i) {
439 const auto &v = cell->vertex(i);
440 if (i == i_a || i == i_b ||
441 vertex_states[cell->vertex_index(i)] == side)
442 candidates.emplace_back((corner - v).norm(), v[0], v[1], i);
443 }
444 std::sort(candidates.begin(), candidates.end());
445
446 for (const auto &[displacement, x, z, i] : candidates) {
447 const auto g = cell->vertex_index(i);
448 if (pinned[g] ||
449 !admissible(
450 cell, i, corner, minimal_jacobian_fraction_ * h0 * h0))
451 continue;
452 if (record.empty())
453 record.assign(4 * dim, nan);
454 const dealii::Point<dim> previous(record[2 * i],
455 record[2 * i + 1]);
456 if (std::isnan(previous[0]) ||
457 displacement < (previous - cell->vertex(i)).norm())
458 for (unsigned int k = 0; k < dim; ++k)
459 record[2 * i + k] = corner[k];
460 break;
461 }
462 }
463 }
464
465 exchange_to_ghosts(triangulation, records);
466
467 Requests requests;
468 for (const auto &cell : triangulation.active_cell_iterators()) {
469 const auto &record = records[cell->active_cell_index()];
470 if (cell->is_artificial() || record.empty())
471 continue;
472 for (unsigned int i = 0; i < 4; ++i)
473 if (!std::isnan(record[2 * i]))
474 propose(requests,
475 cell,
476 i,
477 dealii::Point<dim>(record[2 * i], record[2 * i + 1]));
478 }
479
480 /* Every locally owned cell vetoes all but one requested vertex: */
481 std::vector<unsigned int> vetoes(n_cells, 0);
482 for (const auto &cell : triangulation.active_cell_iterators()) {
483 if (!cell->is_locally_owned())
484 continue;
485 unsigned int i_best = invalid;
486 for (unsigned int i = 0; i < 4; ++i)
487 if (requests.count(cell->vertex_index(i)) != 0 &&
488 (i_best == invalid ||
489 priority(cell->vertex(i)) > priority(cell->vertex(i_best))))
490 i_best = i;
491 for (unsigned int i = 0; i < 4; ++i)
492 if (i != i_best && requests.count(cell->vertex_index(i)) != 0)
493 vetoes[cell->active_cell_index()] |= 1u << i;
494 }
495
496 exchange_to_ghosts(triangulation, vetoes);
497
498 for (const auto &cell : triangulation.active_cell_iterators())
499 if (!cell->is_artificial())
500 for (unsigned int i = 0; i < 4; ++i)
501 if ((vetoes[cell->active_cell_index()] & (1u << i)) != 0)
502 requests.erase(cell->vertex_index(i));
503
504 apply(requests);
505
506 unsigned int n_moved = 0;
507 for (const auto &[g, p] : requests) {
508 pinned[g] = true;
509 n_moved += owned[g];
510 }
511#if DEAL_II_VERSION_GTE(9, 7, 0)
512 const auto comm = triangulation.get_mpi_communicator();
513#else
514 const auto comm = triangulation.get_communicator();
515#endif
516 n_moved = dealii::Utilities::MPI::max(n_moved, comm);
517 if (n_moved == 0)
518 break;
519 }
520 }
521
522 private:
523 double acceptable_deviation_;
524 double minimal_jacobian_fraction_;
525 unsigned int max_sweeps_;
526 };
527
528
562 template <int dim>
564 {
565 public:
572 template <typename Callable>
574 dealii::Triangulation<dim> &triangulation,
575 dealii::Triangulation<dim> &temporary,
576 const Callable &height,
577 const dealii::types::boundary_id profile_boundary_id) const
578 {
579 using Alignment = MeshAlignment<dim>;
580 using Face = std::pair<unsigned int, unsigned int>;
581
582#if DEAL_II_VERSION_GTE(9, 7, 0)
583 const auto comm = temporary.get_mpi_communicator();
584#else
585 const auto comm = temporary.get_communicator();
586#endif
587 constexpr auto invalid = dealii::numbers::invalid_unsigned_int;
588
589 const auto &vertices = temporary.get_vertices();
590
591 /*
592 * A lambda that returns all faces of a (quad or tet) element as a
593 * vector of tuples of indices, in the canonical order of faces as
594 * defined by the reference cell. Here, element is a vector of
595 * (global) vertex indices.
596 */
597 const auto element_faces = [](const auto &element) {
598 const auto reference_cell =
599 dealii::ReferenceCells::n_vertices_to_reference_cell<dim>(
600 element.size());
601
602 std::vector<Face> result;
603
604 for (const auto f : reference_cell.face_indices()) {
605 const auto vertex_index_0 = reference_cell.face_to_cell_vertices(
606 f, 0, dealii::numbers::default_geometric_orientation);
607 const auto vertex_index_1 = reference_cell.face_to_cell_vertices(
608 f, 1, dealii::numbers::default_geometric_orientation);
609 result.emplace_back(
610 std::minmax(element[vertex_index_0], element[vertex_index_1]));
611 }
612
613 return result;
614 };
615
616 /*
617 * Step 1:
618 *
619 * We collect a record of every locally owned cell with a vertex
620 * above the profile. A record consists of
621 * - a (new) global (coarse) cell ID that we form,
622 * - the x and y coordinates of all vertices of the original cell,
623 * - the element (quad or tet) encoded as 3 or 4 (cell) vertex indices.
624 *
625 * Note: we need the vertex coordinates in a record as well to work
626 * around a bug in deal.II's communicate_locally_moved_vertices that
627 * fails to update some of the vertices in the ghost layer.
628 */
629
630 std::vector<std::vector<double>> records(temporary.n_active_cells());
631 dealii::types::coarse_cell_id n_elements = 0;
632
633 for (const auto &cell : temporary.active_cell_iterators()) {
634 if (!cell->is_locally_owned())
635 continue;
636
637 std::array<int, 4> state;
638 unsigned int i_above = invalid;
639 for (unsigned int i = 0; i < 4; ++i) {
640 state[i] = Alignment::vertex_state(height, cell->vertex(i));
641 if (state[i] > 0)
642 i_above = i;
643 }
644
645 if (i_above == invalid)
646 continue;
647
648 /*
649 * Reading counterclockwise deal.II enumerates vertices 0->1->3->2.
650 * Starting at index i_above we now select: the counterclockwise
651 * neighbor a, the opposite vertex c, and its other neighbor b:
652 */
653 constexpr std::array<unsigned int, 4> ccw_next_vertex{{1, 3, 0, 2}};
654 const auto a = ccw_next_vertex[i_above];
655 const auto c = ccw_next_vertex[a];
656 const auto b = ccw_next_vertex[c];
657 const auto centroid =
658 (cell->vertex(a) + cell->vertex(b) + cell->vertex(c)) / 3.;
659
660 std::vector<unsigned int> element{0, 1, 2, 3}; /* uncut quad */
661
662 /* We cut along the diagonal a-b if it separates i_above from c: */
663 const bool opposite_vertex_below = state[c] < 0;
664 const bool triangle_abc_below =
665 state[a] == 0 && state[b] == 0 && state[c] == 0 &&
666 Alignment::vertex_state(height, centroid) < 0;
667
668 if (opposite_vertex_below || triangle_abc_below)
669 element = {i_above, a, b}; /* triangle formed by removing vertex c */
670
671 auto &record = records[cell->active_cell_index()];
672 /* conversion to double is exact for n_elements < 2^53 */
673 record.push_back(static_cast<double>(n_elements++));
674 for (unsigned int i = 0; i < 4; ++i)
675 for (int d = 0; d < dim; ++d)
676 record.push_back(cell->vertex(i)[d]);
677 record.insert(record.end(), element.begin(), element.end());
678 }
679
680 /*
681 * Shift the rank-local ID by an appropriate offset to obtain a
682 * unique global coarse cell ID:
683 */
684 const auto offset =
685 dealii::Utilities::MPI::partial_and_total_sum(n_elements, comm).first;
686
687 for (auto &record : records)
688 if (!record.empty())
689 record[0] += static_cast<double>(offset);
690
691 /* We use the temporary mesh to exchange information: */
692 Alignment::exchange_to_ghosts(temporary, records);
693
694 /*
695 * Step 2:
696 *
697 * Collect all elements (quads or tests) derived from locally owned
698 * and ghost cells of the temporary triangulation and count how often
699 * every face occurs: a face occurring once is a boundary face. It
700 * inherits the boundary id of a boundary face of its cell, otherwise
701 * it lies on the profile.
702 */
703
704 std::vector<dealii::CellData<dim>> cells;
705 std::vector<dealii::types::coarse_cell_id> ids;
706 std::vector<dealii::types::subdomain_id> owners;
707 std::map<Face, std::pair<unsigned int, dealii::types::boundary_id>> faces;
708
709 for (const auto &cell : temporary.active_cell_iterators()) {
710 const auto &record = records[cell->active_cell_index()];
711 if (cell->is_artificial() || record.empty())
712 continue;
713
714 /*
715 * Work around a bug in communicate_locally_moved_vertices(): it
716 * only sends vertices moved by the owner of a cell, so a vertex of
717 * a ghost cell owned by a third rank may still be at its old position.
718 * Take the vertex coordinates from the owner instead.
719 */
720 for (unsigned int i = 0; i < 4; ++i)
721 for (int d = 0; d < dim; ++d)
722 cell->vertex(i)[d] = record[1 + i * dim + d];
723
724 dealii::CellData<dim> data;
725 data.vertices.clear();
726 /* Iterate over the "element" vertex indices: */
727 for (auto it = record.begin() + 1 + 4 * dim; it != record.end(); ++it) {
728 const auto global_index =
729 cell->vertex_index(static_cast<unsigned int>(*it));
730 data.vertices.push_back(global_index);
731 }
732
733 for (const auto &face : element_faces(data.vertices)) {
734 auto &[count, boundary_id] = faces[face];
735 ++count;
736 boundary_id = profile_boundary_id;
737 for (const auto f : cell->face_indices()) {
738 const Face cell_face = std::minmax(cell->face(f)->vertex_index(0),
739 cell->face(f)->vertex_index(1));
740 if (cell->face(f)->at_boundary() && cell_face == face)
741 boundary_id = cell->face(f)->boundary_id();
742 }
743 }
744
745 cells.push_back(data);
746 ids.push_back(static_cast<dealii::types::coarse_cell_id>(record[0]));
747 owners.push_back(cell->subdomain_id());
748 }
749
750 /*
751 * Step 3: Create a Triangulation description of the local part of the
752 * new mesh, the locally owned elements and the elements sharing a vertex
753 * with them, and create the triangulation.
754 */
755
756 const auto rank = dealii::Utilities::MPI::this_mpi_process(comm);
757
758 std::vector<bool> relevant(vertices.size(), false);
759 for (unsigned int cell = 0; cell < cells.size(); ++cell)
760 if (owners[cell] == rank)
761 for (const auto vertex_index : cells[cell].vertices)
762 relevant[vertex_index] = true;
763
764 dealii::TriangulationDescription::Description<dim> description;
765 description.cell_infos.resize(1);
766
767 std::vector<unsigned int> new_index(vertices.size(), invalid);
768
769 for (unsigned int cell = 0; cell < cells.size(); ++cell) {
770 auto data = cells[cell];
771
772 if (std::none_of(data.vertices.begin(),
773 data.vertices.end(),
774 [&](const auto vertex_index) {
775 return relevant[vertex_index];
776 }))
777 continue;
778
779 Assert( //
780 dealii::GridTools::cell_measure<dim>(vertices, data.vertices) > 0.,
781 dealii::ExcMessage(
782 "The cut cell decomposition created an inverted element."));
783
784 dealii::TriangulationDescription::CellData<dim> info;
785 info.id = dealii::CellId(ids[cell], std::vector<std::uint8_t>())
786 .template to_binary<dim>();
787 info.subdomain_id = owners[cell];
788 info.level_subdomain_id = owners[cell];
789
790 const auto element_face = element_faces(data.vertices);
791 for (unsigned int f = 0; f < element_face.size(); ++f) {
792 const auto &[count, boundary_id] = faces[element_face[f]];
793 if (count == 1)
794 info.boundary_ids.emplace_back(f, boundary_id);
795 }
796
797 for (auto &v : data.vertices) {
798 if (new_index[v] == invalid) {
799 new_index[v] = description.coarse_cell_vertices.size();
800 description.coarse_cell_vertices.push_back(vertices[v]);
801 }
802 v = new_index[v];
803 }
804
805 description.coarse_cells.push_back(data);
806 description.coarse_cell_index_to_coarse_cell_id.push_back(ids[cell]);
807 description.cell_infos[0].push_back(info);
808 }
809
810#if DEAL_II_VERSION_GTE(9, 7, 0)
811 description.comm = triangulation.get_mpi_communicator();
812#else
813 description.comm = triangulation.get_communicator();
814#endif
815 description.settings = dealii::TriangulationDescription::Settings::
816 construct_multigrid_hierarchy;
817 description.smoothing = triangulation.get_mesh_smoothing();
818
819 triangulation.clear();
820 triangulation.create_triangulation(description);
821 }
822 };
823
824} // namespace ryujin
void create_triangulation(dealii::Triangulation< dim > &triangulation, dealii::Triangulation< dim > &temporary, const Callable &height, const dealii::types::boundary_id profile_boundary_id) const
MeshAlignment(const std::string &subsection)
static int vertex_state(const Callable &height, const dealii::Point< dim > &point)
static void exchange_to_ghosts(dealii::Triangulation< dim > &triangulation, std::vector< T > &data)
static constexpr unsigned int profile_direction
void align_with_elevation_profile(dealii::Triangulation< dim > &triangulation, const Callable &height) const