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