ryujin 2.1.1 revision b04fb4aba7bd97ddfa5994a6b4b6e8586eb15db2
Loading...
Searching...
No Matches
offline_data.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2020 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include "discretization.h"
10#include "loop.h"
12#include "offline_data.h"
13#include "scratch_data.h"
14#include "simd.h"
15
16#include <deal.II/base/graph_coloring.h>
17#include <deal.II/base/parallel.h>
18#include <deal.II/base/work_stream.h>
19#include <deal.II/dofs/dof_renumbering.h>
20#include <deal.II/dofs/dof_tools.h>
21#include <deal.II/fe/fe_nothing.h>
22#include <deal.II/fe/fe_values.h>
23#include <deal.II/grid/grid_tools.h>
24#include <deal.II/lac/dynamic_sparsity_pattern.h>
25#include <deal.II/lac/la_parallel_vector.h>
26
27namespace ryujin
28{
29 using namespace dealii;
30
31
32 template <int dim, typename Number>
34 const MPIEnsemble &mpi_ensemble,
35 const Discretization<dim> &discretization,
36 const std::string &subsection /*= "OfflineData"*/)
37 : ParameterAcceptor(subsection)
38 , mpi_ensemble_(mpi_ensemble)
39 , discretization_(&discretization)
40 {
41 treat_fe_nothing_as_boundary_ = true;
42 add_parameter(
43 "treat fe_nothing as boundary",
44 treat_fe_nothing_as_boundary_,
45 "If set to true, we treat cell with where the active finite element is "
46 "set to FE_Nothing as boundary: We do not assemble an interior jump "
47 "over such elements and add vertices touching such an element in the "
48 "boundary map. In this case, the boundary id of the interior face is "
49 "set via the material id of the cell with active FE_Nothing element.");
50
51 incidence_relaxation_even_ = 0.5;
52 add_parameter("incidence matrix relaxation even degree",
53 incidence_relaxation_even_,
54 "Scaling exponent for incidence matrix used for "
55 "discontinuous finite elements with even degree. The default "
56 "value 0.5 scales the jump penalization with (h_i+h_j)^0.5.");
57
58 incidence_relaxation_odd_ = 0.0;
59 add_parameter("incidence matrix relaxation odd degree",
60 incidence_relaxation_odd_,
61 "Scaling exponent for incidence matrix used for "
62 "discontinuous finite elements with even degree. The default "
63 "value of 0.0 sets the jump penalization to a constant 1.");
64 }
65
66
67 template <int dim, typename Number>
68 void
69 OfflineData<dim, Number>::prepare(const unsigned int problem_dimension,
70 const unsigned int n_precomputed_values)
71 {
72#ifdef DEBUG_OUTPUT
73 std::cout << "OfflineData<dim, Number>::prepare()" << std::endl;
74#endif
75
76 create_dof_handlers();
77
78 renumber_for_simd();
79
80 create_constraints_and_sparsity_pattern();
81
82 ensure_simd_stride_consistency();
83
84 create_partitioner_and_simd_sparsity(problem_dimension,
85 n_precomputed_values);
86
87 create_matrices();
88
89 if (!dof_handler().has_hp_capabilities())
90 create_multigrid_data();
91 }
92
93
94 template <int dim, typename Number>
96 {
97 const auto &triangulation = discretization_->triangulation();
98
99 if (!dof_handler_cg_)
100 dof_handler_cg_ =
101 std::make_unique<dealii::DoFHandler<dim>>(triangulation);
102
103 if (!dof_handler_dg_)
104 dof_handler_dg_ =
105 std::make_unique<dealii::DoFHandler<dim>>(triangulation);
106
107 /*
108 * Set active FE indices: This information depends on the selected
109 * geometry. Therefore, let the selected geometry object handle the
110 * setup. For a standard geometry that has only one reference element
111 * the method simply does nothing.
112 */
113
114 discretization_->selected_geometry().update_dof_handler(*dof_handler_cg_);
115 discretization_->selected_geometry().update_dof_handler(*dof_handler_dg_);
116
117 dof_handler_cg_->distribute_dofs(discretization_->finite_element_cg());
118 dof_handler_dg_->distribute_dofs(discretization_->finite_element_dg());
119 }
120
121
122 template <int dim, typename Number>
124 {
125 auto &dof_handler = this->dof_handler();
126 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
127 n_locally_owned_ = locally_owned.n_elements();
128
129 /*
130 * Initial renumbering with Cuthill McKee (because we can):
131 */
132
133 DoFRenumbering::Cuthill_McKee(dof_handler);
134
135 /*
136 * Reorder all (individual) export indices at the beginning of the
137 * locally_internal index range to achieve a better packing:
138 *
139 * Note: This function might miss export indices that come from
140 * eliminating hanging node and periodicity constraints (which we do
141 * not know at this point because they depend on the renumbering...).
142 */
144 mpi_ensemble_.ensemble_communicator(),
145 n_locally_owned_,
146 1);
147
148 /*
149 * Group degrees of freedom that have the same stencil size in warps of
150 * warp_size consecutive indices.
151 *
152 * In order to determine the stencil size we have to create a first,
153 * temporary sparsity pattern:
154 */
155 create_constraints_and_sparsity_pattern();
156 n_locally_internal_ = DoFRenumbering::internal_range(
157 dof_handler, sparsity_pattern_, warp_size);
158
159 /*
160 * Reorder all (strides of) locally internal indices that contain
161 * export indices to the start of the index range. This reordering
162 * preserves the binning introduced by
163 * DoFRenumbering::internal_range().
164 *
165 * Note: This function might miss export indices that come from
166 * eliminating hanging node and periodicity constraints (which we do
167 * not know at this point because they depend on the renumbering...).
168 * We therefore have to update n_export_indices_ later again.
169 */
170 n_export_indices_ = DoFRenumbering::export_indices_first(
171 dof_handler,
172 mpi_ensemble_.ensemble_communicator(),
173 n_locally_internal_,
174 warp_size);
175 }
176
177
178 /*
179 * Populates:
180 * affine_constraints_cg_
181 * affine_constraints_dg_
182 * n_locally_owned_
183 * sparsity_pattern_
184 */
185 template <int dim, typename Number>
186 void OfflineData<dim, Number>::create_constraints_and_sparsity_pattern()
187 {
188 /*
189 * First, we set up the (globally indexed) affine constraints object
190 * for the continuous dof handler. The affine constraints object solely
191 * stores (a) hanging node constraints, and (b) periodicity constraints.
192 */
193
194 const auto populate_affine_constraints = //
195 [&](const auto &dof_handler, auto &affine_constraints) {
196 const auto locally_relevant =
197 DoFTools::extract_locally_relevant_dofs(dof_handler);
198
199 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
200 affine_constraints.reinit(locally_owned, locally_relevant);
201
202 /*
203 * Skip distributing hanging node constraints when none are needed.
204 * This works around some non-implemented features in the library
205 * for hp constraints (current as of deal.II version 9.8).
206 */
207 if (discretization_->triangulation().has_hanging_nodes())
208 DoFTools::make_hanging_node_constraints(dof_handler,
209 affine_constraints);
210
211 /*
212 * Enforce periodic boundary conditions. We assume that the mesh is in
213 * "normal configuration."
214 */
215
216 const auto &periodic_faces =
217 discretization_->triangulation().get_periodic_face_map();
218
219 for (const auto &[left, value] : periodic_faces) {
220 const auto &[right, orientation] = value;
221
222 typename DoFHandler<dim>::cell_iterator dof_cell_left(
223 &left.first->get_triangulation(),
224 left.first->level(),
225 left.first->index(),
226 &dof_handler);
227
228 typename DoFHandler<dim>::cell_iterator dof_cell_right(
229 &right.first->get_triangulation(),
230 right.first->level(),
231 right.first->index(),
232 &dof_handler);
233
234 if constexpr (std::is_same_v<Number, double>) {
235 DoFTools::make_periodicity_constraints(
236 dof_cell_left->face(left.second),
237 dof_cell_right->face(right.second),
238 affine_constraints,
239 ComponentMask(),
240 orientation);
241 } else {
242 AssertThrow(false, dealii::ExcNotImplemented());
243 __builtin_trap();
244 }
245 }
246
247 affine_constraints.close();
248
249#ifdef DEBUG
250 {
251 /* Check that constraints are consistent in parallel: */
252 const std::vector<IndexSet> &locally_owned_dofs =
253 Utilities::MPI::all_gather(
254 mpi_ensemble_.ensemble_communicator(),
255 dof_handler.locally_owned_dofs());
256 const IndexSet locally_active =
257 dealii::DoFTools::extract_locally_active_dofs(dof_handler);
258 Assert(affine_constraints.is_consistent_in_parallel(
259 locally_owned_dofs,
260 locally_active,
261 mpi_ensemble_.ensemble_communicator(),
262 /*verbose*/ true),
263 ExcInternalError());
264 }
265#endif
266 };
267
268 populate_affine_constraints(*dof_handler_cg_, affine_constraints_cg_);
269 /* Note: for dG the affine constraints object will be empty. */
270 populate_affine_constraints(*dof_handler_dg_, affine_constraints_dg_);
271
272 /*
273 * Next, set up the (hyperbolic) sparsity pattern:
274 */
275
276 const auto &dof_handler = this->dof_handler();
277 const auto &affine_constraints = this->affine_constraints();
278 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
279 Assert(n_locally_owned_ == locally_owned.n_elements(),
280 dealii::ExcInternalError());
281
282 const auto locally_relevant =
283 DoFTools::extract_locally_relevant_dofs(dof_handler);
284
285 sparsity_pattern_.reinit(
286 dof_handler.n_dofs(), dof_handler.n_dofs(), locally_relevant);
287
288 if (discretization_->have_discontinuous_ansatz()) {
289 /*
290 * Create dG sparsity pattern:
291 */
293 dof_handler, sparsity_pattern_, affine_constraints, false);
294 } else {
295 /*
296 * Create cG sparsity pattern:
297 */
298 DoFTools::make_sparsity_pattern(
299 dof_handler, sparsity_pattern_, affine_constraints, false);
300 }
301
302 /*
303 * We have to complete the local stencil to have consistent size over
304 * all MPI ranks. Otherwise, MPI synchronization in our
305 * SparseMatrix class will fail.
306 */
307
308 SparsityTools::distribute_sparsity_pattern(
309 sparsity_pattern_,
310 locally_owned,
311 mpi_ensemble_.ensemble_communicator(),
312 locally_relevant);
313 }
314
315
316 /*
317 * Modifies:
318 * n_locally_internal_
319 */
320 template <int dim, typename Number>
321 void OfflineData<dim, Number>::ensure_simd_stride_consistency()
322 {
323 auto &dof_handler = this->dof_handler();
324
325 /*
326 * A small lambda to check for stride-level consistency of the internal
327 * index range:
328 */
329 const auto consistent_stride_range [[maybe_unused]] = [&]() {
330 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
331 const auto offset = n_locally_owned_ != 0 ? *locally_owned.begin() : 0;
332
333 unsigned int warp_row_length = 0;
334 unsigned int i = 0;
335 for (; i < n_locally_internal_; ++i) {
336 if (i % warp_size == 0) {
337 warp_row_length = sparsity_pattern_.row_length(offset + i);
338 } else {
339 if (warp_row_length != sparsity_pattern_.row_length(offset + i)) {
340 break;
341 }
342 }
343 }
344 return i / warp_size * warp_size;
345 };
346
347 /*
348 * A small lambda that performs a "logical or" over all MPI ranks:
349 */
350 const auto mpi_allreduce_logical_or = [&](const bool local_value) {
351 std::function<bool(const bool &, const bool &)> comparator =
352 [](const bool &left, const bool &right) -> bool {
353 return left || right;
354 };
355 return Utilities::MPI::all_reduce(
356 local_value, mpi_ensemble_.ensemble_communicator(), comparator);
357 };
358
359 /*
360 * We have to ensure that the locally internal numbering range is still
361 * consistent, meaning that all strides have the same stencil size.
362 * This property might not hold any more after the elimination
363 * procedure of constrained degrees of freedom (periodicity, or hanging
364 * node constraints). Therefore, the following little dance:
365 */
366
367 const auto &affine_constraints = this->affine_constraints();
368 if (mpi_allreduce_logical_or(affine_constraints.n_constraints() > 0)) {
369 if (mpi_allreduce_logical_or( //
370 consistent_stride_range() != n_locally_internal_)) {
371 /*
372 * In this case we try to fix up the numbering by pushing affected
373 * strides to the end and slightly lowering the n_locally_internal_
374 * marker.
375 */
376 n_locally_internal_ = DoFRenumbering::inconsistent_strides_last(
377 dof_handler, sparsity_pattern_, n_locally_internal_, warp_size);
378 create_constraints_and_sparsity_pattern();
379 n_locally_internal_ = consistent_stride_range();
380 }
381 }
382
383 /*
384 * Check that after all the dof manipulation and setup we still end up
385 * with indices in [0, locally_internal) that have uniform stencil size
386 * within a stride.
387 */
388 Assert(consistent_stride_range() == n_locally_internal_,
389 dealii::ExcInternalError());
390 }
391
392
393 /*
394 * Populates:
395 * n_locally_relevant_
396 * scalar_partitioner_
397 * hyperbolic_vector_partitioner_
398 * precomputed_vector_partitioner_
399 * sparsity_pattern_simd_
400 */
401 template <int dim, typename Number>
402 void OfflineData<dim, Number>::create_partitioner_and_simd_sparsity(
403 const unsigned int problem_dimension,
404 const unsigned int n_precomputed_values)
405 {
406 const auto &dof_handler = this->dof_handler();
407 const auto &affine_constraints = this->affine_constraints();
408 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
409 Assert(n_locally_owned_ == locally_owned.n_elements(),
410 dealii::ExcInternalError());
411
412 auto locally_relevant =
413 DoFTools::extract_locally_relevant_dofs(dof_handler);
414 /* Enlarge the locally relevant set to include all additional couplings: */
415 {
416 IndexSet additional_dofs(dof_handler.n_dofs());
417 for (auto &entry : sparsity_pattern_)
418 if (!locally_relevant.is_element(entry.column())) {
419 Assert(locally_owned.is_element(entry.row()), ExcInternalError());
420 additional_dofs.add_index(entry.column());
421 }
422 additional_dofs.compress();
423 locally_relevant.add_indices(additional_dofs);
424 locally_relevant.compress();
425 }
426
427 n_locally_relevant_ = locally_relevant.n_elements();
428
429 scalar_partitioner_ = std::make_shared<dealii::Utilities::MPI::Partitioner>(
430 locally_owned, locally_relevant, mpi_ensemble_.ensemble_communicator());
431
432 hyperbolic_vector_partitioner_ =
433 create_vector_partitioner(scalar_partitioner_, problem_dimension);
434
435 precomputed_vector_partitioner_ =
436 create_vector_partitioner(scalar_partitioner_, n_precomputed_values);
437
438 /*
439 * A small lambda that performs a "logical or" over all MPI ranks:
440 */
441 const auto mpi_allreduce_logical_or = [&](const bool local_value) {
442 std::function<bool(const bool &, const bool &)> comparator =
443 [](const bool &left, const bool &right) -> bool {
444 return left || right;
445 };
446 return Utilities::MPI::all_reduce(
447 local_value, mpi_ensemble_.ensemble_communicator(), comparator);
448 };
449
450 /*
451 * After eliminiating periodicity and hanging node constraints we need
452 * to update n_export_indices_ again. This happens because we need to
453 * call export_indices_first() with incomplete information (missing
454 * eliminated degrees of freedom).
455 */
456 if (mpi_allreduce_logical_or(affine_constraints.n_constraints() > 0)) {
457 /*
458 * Recalculate n_export_indices_:
459 */
460 n_export_indices_ = 0;
461 for (const auto &it : scalar_partitioner_->import_indices())
462 if (it.second <= n_locally_internal_)
463 n_export_indices_ = std::max(n_export_indices_, it.second);
464
465 n_export_indices_ =
466 (n_export_indices_ + warp_size - 1) / warp_size * warp_size;
467 }
468
469#ifdef DEBUG
470 /* Check that n_export_indices_ is valid: */
471 unsigned int control = 0;
472 for (const auto &it : scalar_partitioner_->import_indices())
473 if (it.second <= n_locally_internal_)
474 control = std::max(control, it.second);
475
476 Assert(control <= n_export_indices_, ExcInternalError());
477 Assert(n_export_indices_ <= n_locally_internal_, ExcInternalError());
478#endif
479
480 /*
481 * Set up SIMD sparsity pattern in local numbering.
482 *
483 * Nota bene: The SparsityPattern::reinit() function translates the
484 * pattern from global deal.II (typical) dof indexing to local indices.
485 */
486
487 sparsity_pattern_simd_.reinit(n_locally_internal_,
488 sparsity_pattern_,
489 scalar_partitioner_,
490 /*symmetrize_ghost_range*/ true,
492 }
493
494
495 template <int dim, typename Number>
496 void OfflineData<dim, Number>::create_matrices()
497 {
498#ifdef DEBUG_OUTPUT
499 std::cout << "OfflineData<dim, Number>::create_matrices()" << std::endl;
500#endif
501
502 /*
503 * First, (re)initialize all local matrices. All of them are assembled
504 * on the host memory space and read on either memory space, so we
505 * select the implicit_transfers policy and let the first view() on the
506 * default memory space perform the transfer:
507 */
508
509 constexpr auto policy = TransferPolicy::implicit_transfers;
510
511 mass_matrix_.reinit(sparsity_pattern_simd_, policy);
512 if (discretization_->have_discontinuous_ansatz())
513 mass_matrix_inverse_.reinit(sparsity_pattern_simd_, policy);
514
515 lumped_mass_matrix_.reinit_with_scalar_partitioner(
516 scalar_partitioner_, 1, policy);
517 lumped_mass_matrix_inverse_.reinit_with_scalar_partitioner(
518 scalar_partitioner_, 1, policy);
519
520 betaij_matrix_.reinit(sparsity_pattern_simd_, policy);
521 cij_matrix_.reinit(sparsity_pattern_simd_, policy);
522 if (discretization_->have_discontinuous_ansatz())
523 incidence_matrix_.reinit(sparsity_pattern_simd_, policy);
524
525 /*
526 * Now, assemble all matrices:
527 */
528
529 const auto &dof_handler = this->dof_handler();
530 const auto &affine_constraints = this->affine_constraints();
531
532 measure_of_omega_ = 0.;
533
534 /* The local, per-cell assembly routine: */
535 const auto local_assemble_system = [&](const auto &cell,
536 auto &scratch,
537 auto &copy) {
538 /* iterate over locally owned cells and the ghost layer */
539
540 auto &is_locally_owned = copy.is_locally_owned_;
541 auto &local_dof_indices = copy.local_dof_indices_;
542 auto &neighbor_local_dof_indices = copy.neighbor_local_dof_indices_;
543
544 auto &cell_mass_matrix = copy.cell_mass_matrix_;
545 auto &cell_mass_matrix_inverse = copy.cell_mass_matrix_inverse_;
546 auto &cell_betaij_matrix = copy.cell_betaij_matrix_;
547 auto &cell_cij_matrix = copy.cell_cij_matrix_;
548 auto &interface_cij_matrix = copy.interface_cij_matrix_;
549 auto &cell_measure = copy.cell_measure_;
550
551 auto &hp_fe_values = scratch.hp_fe_values_;
552 auto &hp_fe_face_values = scratch.hp_fe_face_values_;
553 auto &hp_fe_neighbor_face_values = scratch.hp_fe_neighbor_face_values_;
554
555 is_locally_owned = cell->is_locally_owned(); /* stored in copy object */
556 if (!is_locally_owned)
557 return;
558
559 const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
560
561 cell_mass_matrix.reinit(dofs_per_cell, dofs_per_cell);
562 cell_betaij_matrix.reinit(dofs_per_cell, dofs_per_cell);
563 for (auto &matrix : cell_cij_matrix)
564 matrix.reinit(dofs_per_cell, dofs_per_cell);
565 if (discretization_->have_discontinuous_ansatz()) {
566 cell_mass_matrix_inverse.reinit(dofs_per_cell, dofs_per_cell);
567 }
568
569 hp_fe_values.reinit(cell);
570 const auto &fe_values = hp_fe_values.get_present_fe_values();
571
572 local_dof_indices.resize(dofs_per_cell);
573 cell->get_dof_indices(local_dof_indices);
574
575 /* clear out copy data: */
576
577 cell_mass_matrix = 0.;
578 cell_betaij_matrix = 0.;
579 for (auto &matrix : cell_cij_matrix)
580 matrix = 0.;
581 if (discretization_->have_discontinuous_ansatz()) {
582 cell_mass_matrix_inverse = 0.;
583 }
584 cell_measure = 0.;
585
586 for (unsigned int q : fe_values.quadrature_point_indices()) {
587 const auto JxW = fe_values.JxW(q);
588
589 /* skip cells that have no active cells when computing domain size: */
590 if (dofs_per_cell != 0 && cell->is_locally_owned())
591 cell_measure += Number(JxW);
592
593 for (unsigned int j : fe_values.dof_indices()) {
594 const auto value_JxW = fe_values.shape_value(j, q) * JxW;
595 const auto grad_JxW = fe_values.shape_grad(j, q) * JxW;
596
597 for (unsigned int i : fe_values.dof_indices()) {
598 const auto value = fe_values.shape_value(i, q);
599 const auto grad = fe_values.shape_grad(i, q);
600
601 cell_mass_matrix(i, j) += Number(value * value_JxW);
602 cell_betaij_matrix(i, j) += Number(grad * grad_JxW);
603 for (unsigned int d = 0; d < dim; ++d)
604 cell_cij_matrix[d](i, j) += Number((value * grad_JxW)[d]);
605 } /* for i */
606 } /* for j */
607 } /* for q */
608
609 /*
610 * For a discontinuous finite element ansatz we need to assemble
611 * additional face contributions:
612 */
613
614 if (!discretization_->have_discontinuous_ansatz())
615 return;
616
617 for (const auto f_index : cell->face_indices()) {
618 const auto &face = cell->face(f_index);
619
620 /* Skip faces without neighbors... */
621 const bool has_neighbor =
622 !face->at_boundary() || cell->has_periodic_neighbor(f_index);
623 if (!has_neighbor) {
624 // set the vector of local dof indices to 0 to indicate that
625 // there is nothing to do for this face:
626 neighbor_local_dof_indices[f_index].resize(0);
627 continue;
628 }
629
630 /* Avoid artificial cells: */
631 const auto neighbor_cell = cell->neighbor_or_periodic_neighbor(f_index);
632 if (neighbor_cell->is_artificial()) {
633 // set the vector of local dof indices to 0 to indicate that
634 // there is nothing to do for this face:
635 neighbor_local_dof_indices[f_index].resize(0);
636 continue;
637 }
638
639 /* Do not assemble jumps to neighboring cells with FE_Nothing: */
640 const bool neighbor_cell_has_fe_nothing =
641 treat_fe_nothing_as_boundary_ &&
642 (dynamic_cast<const dealii::FE_Nothing<dim> *>(
643 &neighbor_cell->get_fe()) != nullptr);
644 if (neighbor_cell_has_fe_nothing) {
645 // set the vector of local dof indices to 0 to indicate that
646 // there is nothing to do for this face:
647 neighbor_local_dof_indices[f_index].resize(0);
648 continue;
649 }
650
651 hp_fe_face_values.reinit(cell, f_index);
652 const auto &fe_face_values = hp_fe_face_values.get_present_fe_values();
653
654 /* Face contribution: */
655
656 for (unsigned int q : fe_face_values.quadrature_point_indices()) {
657 const auto JxW = fe_face_values.JxW(q);
658 const auto &normal = fe_face_values.get_normal_vectors()[q];
659
660 for (unsigned int j : fe_face_values.dof_indices()) {
661 const auto value_JxW = fe_face_values.shape_value(j, q) * JxW;
662
663 for (unsigned int i : fe_face_values.dof_indices()) {
664 const auto value = fe_face_values.shape_value(i, q);
665
666 for (unsigned int d = 0; d < dim; ++d)
667 cell_cij_matrix[d](i, j) -=
668 Number(0.5 * normal[d] * value * value_JxW);
669 } /* for i */
670 } /* for j */
671 } /* for q */
672
673 /* Coupling part: */
674
675 const unsigned int f_index_neighbor =
676 cell->has_periodic_neighbor(f_index)
677 ? cell->periodic_neighbor_of_periodic_neighbor(f_index)
678 : cell->neighbor_of_neighbor(f_index);
679
680 const unsigned int neighbor_dofs_per_cell =
681 neighbor_cell->get_fe().n_dofs_per_cell();
682 neighbor_local_dof_indices[f_index].resize(neighbor_dofs_per_cell);
683 neighbor_cell->get_dof_indices(neighbor_local_dof_indices[f_index]);
684
685 for (unsigned int k = 0; k < dim; ++k) {
686 interface_cij_matrix[f_index][k].reinit(dofs_per_cell,
687 neighbor_dofs_per_cell);
688 interface_cij_matrix[f_index][k] = 0.;
689 }
690
691 hp_fe_neighbor_face_values.reinit(neighbor_cell, f_index_neighbor);
692 const auto &fe_neighbor_face_values =
693 hp_fe_neighbor_face_values.get_present_fe_values();
694
695 for (unsigned int q : fe_face_values.quadrature_point_indices()) {
696 const auto JxW = fe_face_values.JxW(q);
697 const auto &normal = fe_face_values.get_normal_vectors()[q];
698
699 /* index j for neighbor, index i for current cell: */
700 for (unsigned int j : fe_neighbor_face_values.dof_indices()) {
701 const auto value_JxW =
702 fe_neighbor_face_values.shape_value(j, q) * JxW;
703
704 for (unsigned int i : fe_face_values.dof_indices()) {
705 const auto value = fe_face_values.shape_value(i, q);
706
707 for (unsigned int d = 0; d < dim; ++d)
708 interface_cij_matrix[f_index][d](i, j) +=
709 Number(0.5 * normal[d] * value * value_JxW);
710 } /* for i */
711 } /* for j */
712 } /* for q */
713 }
714
715 /*
716 * Compute block inverse of mass matrix:
717 */
718
719 if (discretization_->have_discontinuous_ansatz()) {
720 // FIXME: rewrite with CellwiseInverseMassMatrix
721 if (!cell_mass_matrix_inverse.empty())
722 cell_mass_matrix_inverse.invert(cell_mass_matrix);
723 }
724 };
725
726 const auto copy_local_to_global = [&](const auto &copy) {
727 const auto &is_locally_owned = copy.is_locally_owned_;
728 const auto &dof_indices = copy.local_dof_indices_;
729 const auto &neighbor_dof_indices = copy.neighbor_local_dof_indices_;
730 const auto &cell_mass_matrix = copy.cell_mass_matrix_;
731 const auto &cell_mass_matrix_inverse = copy.cell_mass_matrix_inverse_;
732 const auto &cell_cij_matrix = copy.cell_cij_matrix_;
733 const auto &interface_cij_matrix = copy.interface_cij_matrix_;
734 const auto &cell_betaij_matrix = copy.cell_betaij_matrix_;
735 const auto &cell_measure = copy.cell_measure_;
736
737 if (!is_locally_owned)
738 return;
739
741 cell_mass_matrix, dof_indices, affine_constraints, mass_matrix_);
742
744 cell_cij_matrix, dof_indices, affine_constraints, cij_matrix_);
745
746 /*
747 * Workaround: We need to catch the case local_dof_indices.size() == 0
748 * because deal.II reports the wrong size in the matrix object.
749 */
750 if (dof_indices.size() != 0) {
751 for (unsigned int f_index = 0; f_index < copy.n_faces; ++f_index) {
752 if (neighbor_dof_indices[f_index].size() != 0) {
753 distribute_local_to_global(interface_cij_matrix[f_index],
754 dof_indices,
755 neighbor_dof_indices[f_index],
756 affine_constraints,
757 cij_matrix_);
758 }
759 }
760 }
761
763 cell_betaij_matrix, dof_indices, affine_constraints, betaij_matrix_);
764
765 if (discretization_->have_discontinuous_ansatz())
766 distribute_local_to_global(cell_mass_matrix_inverse,
767 dof_indices,
768 affine_constraints,
769 mass_matrix_inverse_);
770
771 measure_of_omega_ += cell_measure;
772 };
773
774 WorkStream::run(dof_handler.begin_active(),
775 dof_handler.end(),
776 local_assemble_system,
777 copy_local_to_global,
778 AssemblyScratchData<dim>(*discretization_),
779 AssemblyCopyData<dim, Number>());
780
781 mass_matrix_.view().compress(VectorOperation::add);
782 mass_matrix_.view().update_ghost_rows();
783 cij_matrix_.view().compress(VectorOperation::add);
784 cij_matrix_.view().update_ghost_rows();
785 betaij_matrix_.view().compress(VectorOperation::add);
786 betaij_matrix_.view().update_ghost_rows();
787 if (discretization_->have_discontinuous_ansatz()) {
788 mass_matrix_inverse_.view().compress(VectorOperation::add);
789 mass_matrix_inverse_.view().update_ghost_rows();
790 }
791
792 measure_of_omega_ = Utilities::MPI::sum(
793 measure_of_omega_, mpi_ensemble_.ensemble_communicator());
794
795 /*
796 * Create lumped mass matrix:
797 */
798
799 {
800 const auto sparsity_simd_view = sparsity_pattern_simd_.view();
801 const auto mass_matrix_view = mass_matrix_.view();
802 const auto lumped_mass_matrix_view =
803 lumped_mass_matrix_.template view<1>();
804 const auto lumped_mass_matrix_inverse_view =
805 lumped_mass_matrix_inverse_.template view<1>();
806
807 const auto body = [&](auto sentinel, unsigned int i) {
808 using T = decltype(sentinel);
809 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
810
811 /* Skip constrained degrees of freedom: */
812 const unsigned int row_length = sparsity_simd_view.row_length(i);
813 if (row_length == 1)
814 return;
815
816 T m_i{};
817
818 const unsigned int *js = sparsity_simd_view.columns(i);
819 for (unsigned int col_idx = 0; col_idx < row_length;
820 ++col_idx, js += stride_size) {
821
822 const auto m_ij = mass_matrix_view.template read_entry<T>(i, col_idx);
823 m_i += m_ij;
824 }
825
826 lumped_mass_matrix_view.template write_entry<T>(m_i, i);
827 lumped_mass_matrix_inverse_view. //
828 template write_entry<T>(Number(1.) / m_i, i);
829 };
830
831 cpu_simd_loop<Number>("", body, 0, n_locally_internal_, n_locally_owned_);
832
833 lumped_mass_matrix_view.update_ghost_values();
834 lumped_mass_matrix_inverse_view.update_ghost_values();
835 }
836
837 /*
838 * Assemble incidence matrix:
839 */
840
841 if (discretization_->have_discontinuous_ansatz()) {
842 const auto lumped_mass_matrix_view =
843 lumped_mass_matrix_.template view<1>();
844
845 /* The local, per-cell assembly routine: */
846 const auto local_assemble_system = [&](const auto &cell,
847 auto &scratch,
848 auto &copy) {
849 /* iterate over locally owned cells and the ghost layer */
850
851 auto &is_locally_owned = copy.is_locally_owned_;
852 auto &local_dof_indices = copy.local_dof_indices_;
853 auto &neighbor_local_dof_indices = copy.neighbor_local_dof_indices_;
854 auto &interface_incidence_matrix = copy.interface_incidence_matrix_;
855 auto &hp_fe_face_values_nodal = scratch.hp_fe_face_values_nodal_;
856 auto &hp_fe_neighbor_face_values_nodal =
857 scratch.hp_fe_neighbor_face_values_nodal_;
858
859 is_locally_owned = cell->is_locally_owned(); /* stored in copy object */
860 if (!is_locally_owned)
861 return;
862
863 const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
864
865 for (auto &matrix : interface_incidence_matrix)
866 matrix.reinit(dofs_per_cell, dofs_per_cell);
867
868 local_dof_indices.resize(dofs_per_cell);
869 cell->get_dof_indices(local_dof_indices);
870
871 /* clear out copy data: */
872 for (auto &matrix : interface_incidence_matrix)
873 matrix = 0.;
874
875 for (const auto f_index : cell->face_indices()) {
876 const auto &face = cell->face(f_index);
877
878 /* Skip faces without neighbors... */
879 const bool has_neighbor =
880 !face->at_boundary() || cell->has_periodic_neighbor(f_index);
881 if (!has_neighbor) {
882 // set the vector of local dof indices to 0 to indicate that
883 // there is nothing to do for this face:
884 neighbor_local_dof_indices[f_index].resize(0);
885 continue;
886 }
887
888 /* Avoid artificial cells: */
889 const auto neighbor_cell =
890 cell->neighbor_or_periodic_neighbor(f_index);
891 if (neighbor_cell->is_artificial()) {
892 // set the vector of local dof indices to 0 to indicate that
893 // there is nothing to do for this face:
894 neighbor_local_dof_indices[f_index].resize(0);
895 continue;
896 }
897
898 /* Do not assemble jumps to neighboring cells with FE_Nothing: */
899 const bool neighbor_cell_has_fe_nothing =
900 treat_fe_nothing_as_boundary_ &&
901 (dynamic_cast<const dealii::FE_Nothing<dim> *>(
902 &neighbor_cell->get_fe()) != nullptr);
903 if (neighbor_cell_has_fe_nothing) {
904 neighbor_local_dof_indices[f_index].resize(0);
905 continue;
906 }
907
908 const unsigned int neighbor_dofs_per_cell =
909 neighbor_cell->get_fe().n_dofs_per_cell();
910 neighbor_local_dof_indices[f_index].resize(neighbor_dofs_per_cell);
911 neighbor_cell->get_dof_indices(neighbor_local_dof_indices[f_index]);
912
913 const unsigned int f_index_neighbor =
914 cell->has_periodic_neighbor(f_index)
915 ? cell->periodic_neighbor_of_periodic_neighbor(f_index)
916 : cell->neighbor_of_neighbor(f_index);
917
918 hp_fe_face_values_nodal.reinit(cell, f_index);
919 const auto &fe_face_values_nodal =
920 hp_fe_face_values_nodal.get_present_fe_values();
921 hp_fe_neighbor_face_values_nodal.reinit(neighbor_cell,
922 f_index_neighbor);
923 const auto &fe_neighbor_face_values_nodal =
924 hp_fe_neighbor_face_values_nodal.get_present_fe_values();
925
926 /* Lumped incidence matrix: */
927
928 for (unsigned int q :
929 fe_face_values_nodal.quadrature_point_indices()) {
930 /* index j for neighbor, index i for current cell: */
931 for (unsigned int j : fe_neighbor_face_values_nodal.dof_indices()) {
932 const auto v_j = fe_neighbor_face_values_nodal.shape_value(j, q);
933 for (unsigned int i : fe_face_values_nodal.dof_indices()) {
934 const auto v_i = fe_face_values_nodal.shape_value(i, q);
935 constexpr auto eps = std::numeric_limits<Number>::epsilon();
936 if (std::abs(v_i * v_j) > 100. * eps) {
937 const auto &ansatz = discretization_->ansatz();
938
939 const auto global_i = local_dof_indices[i];
940 const auto global_j = neighbor_local_dof_indices[f_index][j];
941 const auto local_i =
942 scalar_partitioner_->global_to_local(global_i);
943 const auto local_j =
944 scalar_partitioner_->global_to_local(global_j);
945 const auto m_i = lumped_mass_matrix_view.read_entry(local_i);
946 const auto m_j = lumped_mass_matrix_view.read_entry(local_j);
947 const auto hd_ij =
948 Number(0.5) * (m_i + m_j) / measure_of_omega_;
949
950 Number r_ij = 1.0;
951
952 if (ansatz == Ansatz::dg_q2) {
953 /*
954 * For even polynomial degree we normalize the incidence
955 * matrix to (0.5 (m_i + m_j) / |Omega|) ^ (1.5 / d).
956 * Note, that we will visit every coupling
957 * pair of degrees of freedom (i, j) precisely once.
958 */
959 r_ij = std::pow(hd_ij, incidence_relaxation_even_ / dim);
960 } else {
961 /*
962 * For odd polynomial degree we normalize the incidence
963 * matrix to 1. Note, that we will visit every coupling
964 * pair of degrees of freedom (i, j) precisely once.
965 */
966 r_ij = std::pow(hd_ij, incidence_relaxation_odd_ / dim);
967 }
968
969 interface_incidence_matrix[f_index](i, j) += r_ij;
970 }
971 } /* for i */
972 } /* for j */
973 } /* for q */
974 }
975 };
976
977 const auto copy_local_to_global = [&](const auto &copy) {
978 const auto &is_locally_owned = copy.is_locally_owned_;
979 const auto &dof_indices = copy.local_dof_indices_;
980 const auto &neighbor_dof_indices = copy.neighbor_local_dof_indices_;
981 const auto &interface_incidence_matrix =
982 copy.interface_incidence_matrix_;
983
984 if (!is_locally_owned)
985 return;
986
987 /*
988 * Workaround: We need to catch the case dof_indices.size() == 0
989 * because deal.II reports the wrong size in the matrix object.
990 */
991 if (dof_indices.size() != 0) {
992 for (unsigned int f_index = 0; f_index < copy.n_faces; ++f_index) {
993 if (neighbor_dof_indices[f_index].size() != 0) {
994 distribute_local_to_global(interface_incidence_matrix[f_index],
995 dof_indices,
996 neighbor_dof_indices[f_index],
997 affine_constraints,
998 incidence_matrix_);
999 }
1000 }
1001 }
1002 };
1003
1004 WorkStream::run(dof_handler.begin_active(),
1005 dof_handler.end(),
1006 local_assemble_system,
1007 copy_local_to_global,
1008 AssemblyScratchData<dim>(*discretization_),
1009 AssemblyCopyData<dim, Number>());
1010
1011 incidence_matrix_.view().compress(VectorOperation::add);
1012 }
1013
1014 /*
1015 * Populate boundary map and collect coupling boundary pairs:
1016 */
1017 {
1018 boundary_map_ = construct_boundary_map(
1019 dof_handler.begin_active(), dof_handler.end(), *scalar_partitioner_);
1020
1021 /*
1022 * Compact the degrees of freedom stored in the boundary map into a
1023 * strictly increasing index list (that is mirrored into the default
1024 * memory space) and record for every entry of the boundary map the
1025 * position of its degree of freedom in that index list. Note that the
1026 * boundary map can contain more than one entry for the same degree of
1027 * freedom.
1028 */
1029
1030 std::vector<unsigned int> boundary_indices;
1031 std::map<unsigned int, unsigned int> position_of_index;
1032
1033 boundary_slots_.clear();
1034 boundary_slots_.reserve(boundary_map_.size());
1035
1036 for (const auto &entry : boundary_map_) {
1037 const auto index = std::get<0>(entry);
1038 const auto [it, inserted] =
1039 position_of_index.try_emplace(index, boundary_indices.size());
1040 if (inserted)
1041 boundary_indices.push_back(index);
1042 boundary_slots_.push_back(it->second);
1043 }
1044
1045 boundary_indices_.reinit(boundary_indices.size(),
1047 std::copy(boundary_indices.begin(),
1048 boundary_indices.end(),
1049 boundary_indices_.view());
1050
1051 const auto coupling_boundary_pairs = collect_coupling_boundary_pairs(
1052 dof_handler.begin_active(), dof_handler.end(), *scalar_partitioner_);
1053
1054 coupling_boundary_pairs_.reinit(coupling_boundary_pairs.size(),
1056 std::copy(coupling_boundary_pairs.begin(),
1057 coupling_boundary_pairs.end(),
1058 coupling_boundary_pairs_.view());
1059 }
1060
1061#ifdef DEBUG_SYMMETRY_CHECK
1062 /*
1063 * Verify that we have consistent mass:
1064 */
1065
1066 const auto lumped_mass_matrix_view = lumped_mass_matrix_.template view<1>();
1067
1068 double total_mass = 0.;
1069 for (unsigned int i = 0; i < n_locally_owned_; ++i)
1070 total_mass += lumped_mass_matrix_view.read_entry(i);
1071 total_mass =
1072 Utilities::MPI::sum(total_mass, mpi_ensemble_.ensemble_communicator());
1073
1074 Assert(std::abs(measure_of_omega_ - total_mass) <
1075 1.e-12 * measure_of_omega_,
1076 dealii::ExcMessage(
1077 "Total mass differs from the measure of the domain."));
1078
1079 /*
1080 * Verify that the mij_matrix_ object is consistent:
1081 */
1082
1083 const auto sparsity_simd_view = sparsity_pattern_simd_.view();
1084 const auto mass_matrix_view = mass_matrix_.view();
1085 const auto cij_matrix_view = cij_matrix_.view();
1086
1087 for (unsigned int i = 0; i < n_locally_owned_; ++i) {
1088 /* Skip constrained degrees of freedom: */
1089 const unsigned int row_length = sparsity_simd_view.row_length(i);
1090 if (row_length == 1)
1091 continue;
1092
1093 auto sum = mass_matrix_view.read_entry(i, 0) -
1094 lumped_mass_matrix_view.read_entry(i);
1095
1096 /* skip diagonal */
1097 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
1098 const unsigned int *js = sparsity_simd_view.columns(i);
1099 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1100 const auto j = js[col_idx * stride_size];
1101 Assert(j < n_locally_relevant_, dealii::ExcInternalError());
1102
1103 const auto m_ij = mass_matrix_view.read_entry(i, col_idx);
1104 if (discretization_->have_discontinuous_ansatz()) {
1105 // Interfacial coupling terms are present in the stencil but zero
1106 // in the mass matrix
1107 Assert(std::abs(m_ij) > -1.e-12, dealii::ExcInternalError());
1108 } else {
1109 Assert(std::abs(m_ij) > 1.e-12, dealii::ExcInternalError());
1110 }
1111 sum += m_ij;
1112
1113 const auto m_ji = mass_matrix_view.read_transposed_entry(i, col_idx);
1114 if (std::abs(m_ij - m_ji) >= 1.e-12) {
1115 // The m_ij matrix is not symmetric
1116 std::stringstream ss;
1117 ss << "m_ij matrix is not symmetric: " << m_ij << " <-> " << m_ji;
1118 Assert(false, dealii::ExcMessage(ss.str()));
1119 }
1120 }
1121
1122 Assert(std::abs(sum) < 1.e-12, dealii::ExcInternalError());
1123 }
1124
1125 /*
1126 * Verify that the cij_matrix_ object is consistent:
1127 */
1128
1129 for (unsigned int i = 0; i < n_locally_owned_; ++i) {
1130 /* Skip constrained degrees of freedom: */
1131 const unsigned int row_length = sparsity_simd_view.row_length(i);
1132 if (row_length == 1)
1133 continue;
1134
1135 auto sum = cij_matrix_view.read_tensor(i, 0);
1136
1137 /* skip diagonal */
1138 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
1139 const unsigned int *js = sparsity_simd_view.columns(i);
1140 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1141 const auto j = js[col_idx * stride_size];
1142 Assert(j < n_locally_relevant_, dealii::ExcInternalError());
1143
1144 const auto c_ij = cij_matrix_view.read_tensor(i, col_idx);
1145 Assert(c_ij.norm() > 1.e-12, dealii::ExcInternalError());
1146 sum += c_ij;
1147
1148 const auto c_ji = cij_matrix_view.read_transposed_tensor(i, col_idx);
1149 if ((c_ij + c_ji).norm() >= 1.e-12) {
1150 // The c_ij matrix is not symmetric, this can only happen if i
1151 // and j are both located on the boundary.
1152
1153 const CouplingDescription coupling{i, col_idx, j};
1154 const auto *begin = coupling_boundary_pairs_.view();
1155 const auto *end = begin + coupling_boundary_pairs_.size();
1156 if (std::find(begin, end, coupling) == end) {
1157 std::stringstream ss;
1158 ss << "c_ij matrix is not anti-symmetric: " << c_ij << " <-> "
1159 << c_ji;
1160 Assert(false, dealii::ExcMessage(ss.str()));
1161 }
1162 }
1163 }
1164
1165 Assert(sum.norm() < 1.e-12, dealii::ExcInternalError());
1166 }
1167#endif
1168 }
1169
1170
1171 template <int dim, typename Number>
1172 void OfflineData<dim, Number>::create_multigrid_data()
1173 {
1174#ifdef DEBUG_OUTPUT
1175 std::cout << "OfflineData<dim, Number>::create_multigrid_data()"
1176 << std::endl;
1177#endif
1178
1179 Assert(!dof_handler_cg_->has_hp_capabilities(), dealii::ExcInternalError());
1180 Assert(!dof_handler_dg_->has_hp_capabilities(), dealii::ExcInternalError());
1181
1182 dof_handler_cg_->distribute_mg_dofs();
1183 dof_handler_dg_->distribute_mg_dofs();
1184
1185 /* Now, work on data structures for hyperbolic update: */
1186
1187 auto &dof_handler = this->dof_handler();
1188
1189 const auto n_levels = dof_handler.get_triangulation().n_global_levels();
1190
1191 AffineConstraints<float> level_constraints;
1192 // TODO not yet thread-parallel and without periodicity
1193
1194 level_boundary_map_.resize(n_levels);
1195 level_lumped_mass_matrix_.resize(n_levels);
1196
1197 for (unsigned int level = 0; level < n_levels; ++level) {
1198 /* Assemble lumped mass matrix vector: */
1199
1200 const auto relevant_dofs =
1201 dealii::DoFTools::extract_locally_relevant_level_dofs(dof_handler,
1202 level);
1203
1204 const auto partitioner = std::make_shared<Utilities::MPI::Partitioner>(
1205 dof_handler.locally_owned_mg_dofs(level),
1206 relevant_dofs,
1207 mpi_ensemble_.ensemble_communicator());
1208 level_lumped_mass_matrix_[level].reinit(partitioner);
1209 std::vector<types::global_dof_index> dof_indices(
1210 dof_handler.get_fe().dofs_per_cell);
1211 dealii::Vector<Number> mass_values(dof_handler.get_fe().dofs_per_cell);
1212 dealii::hp::FEValues<dim> hp_fe_values(discretization_->mapping(),
1213 discretization_->finite_element(),
1214 discretization_->quadrature(),
1215 update_values | update_JxW_values);
1216 for (const auto &cell : dof_handler.cell_iterators_on_level(level))
1217 // TODO for assembly with dealii::SparseMatrix and local
1218 // numbering this probably has to read !cell->is_artificial()
1219 if (cell->is_locally_owned_on_level()) {
1220 hp_fe_values.reinit(cell);
1221 const auto &fe_values = hp_fe_values.get_present_fe_values();
1222 for (unsigned int i = 0; i < mass_values.size(); ++i) {
1223 double sum = 0;
1224 for (unsigned int q = 0; q < fe_values.n_quadrature_points; ++q)
1225 sum += fe_values.shape_value(i, q) * fe_values.JxW(q);
1226 mass_values(i) = sum;
1227 }
1228 cell->get_mg_dof_indices(dof_indices);
1229 level_constraints.distribute_local_to_global(
1230 mass_values, dof_indices, level_lumped_mass_matrix_[level]);
1231 }
1232 level_lumped_mass_matrix_[level].compress(VectorOperation::add);
1233
1234 /* Populate boundary map: */
1235
1236 level_boundary_map_[level] = construct_boundary_map(
1237 dof_handler.begin_mg(level), dof_handler.end_mg(level), *partitioner);
1238 }
1239 }
1240
1241
1242 template <int dim, typename Number>
1243 template <typename ITERATOR1, typename ITERATOR2>
1245 const ITERATOR1 &begin,
1246 const ITERATOR2 &end,
1247 const Utilities::MPI::Partitioner &partitioner) const -> BoundaryMap
1248 {
1249#ifdef DEBUG_OUTPUT
1250 std::cout << "OfflineData<dim, Number>::construct_boundary_map()"
1251 << std::endl;
1252#endif
1253
1254 const auto sparsity_simd_view = sparsity_pattern_simd_.view();
1255
1256 /*
1257 * Create a temporary multimap with the (local) dof index as key:
1258 */
1259
1260 using BoundaryData = std::tuple<dealii::Tensor<1, dim, Number> /*normal*/,
1261 Number /*normal mass*/,
1262 Number /*boundary mass*/,
1263 dealii::types::boundary_id /*id*/,
1264 dealii::Point<dim>> /*position*/;
1265 std::multimap<unsigned int, BoundaryData> preliminary_map;
1266
1267 std::vector<dealii::types::global_dof_index> local_dof_indices;
1268
1269 dealii::hp::FEFaceValues<dim> hp_fe_face_values(
1270 discretization_->mapping(),
1271 discretization_->finite_element(),
1272 discretization_->face_quadrature(),
1273 dealii::update_normal_vectors | dealii::update_values |
1274 dealii::update_JxW_values);
1275
1276 for (auto cell = begin; cell != end; ++cell) {
1277
1278 /*
1279 * Workaround: Make sure to iterate over the entire locally relevant
1280 * set so that we compute normals between cells with differing owners
1281 * correctly. This strategy works for 2D but will fail in 3D with
1282 * locally refined meshes and hanging nodes situated at the boundary.
1283 */
1284 if ((cell->is_active() && cell->is_artificial()) ||
1285 (!cell->is_active() && cell->is_artificial_on_level()))
1286 continue;
1287
1288 const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
1289 const auto &support_points = cell->get_fe().get_unit_support_points();
1290
1291 local_dof_indices.resize(dofs_per_cell);
1292 cell->get_active_or_mg_dof_indices(local_dof_indices);
1293
1294 for (auto f_index : cell->face_indices()) {
1295 const auto face = cell->face(f_index);
1296 auto id = face->boundary_id();
1297
1298 /*
1299 * Skip periodic boundary faces. For our algorithm these are
1300 * interior degrees of freedom (if not simultaneously located at
1301 * another boundary as well).
1302 */
1303 if (id == Boundary::periodic)
1304 continue;
1305
1306 /*
1307 * Check for interior faces neighboring a cell with FE nothing:
1308 */
1309 const bool neighbor_cell_has_fe_nothing = //
1310 treat_fe_nothing_as_boundary_ && //
1311 !face->at_boundary() && //
1312 cell->neighbor(f_index)->is_active() && //
1313 !cell->neighbor(f_index)->is_artificial() && //
1314 (dynamic_cast<const dealii::FE_Nothing<dim> *>( //
1315 &cell->neighbor(f_index)->get_fe()) != nullptr); //
1316
1317 if (neighbor_cell_has_fe_nothing) {
1318 /*
1319 * By convention, query boundary id from the material id of the
1320 * neighboring cell and then continue:
1321 */
1322 id = cell->neighbor(f_index)->material_id();
1323
1324 } else if (!face->at_boundary()) {
1325 /* Bail out if we are in the interior: */
1326 continue;
1327 }
1328
1329 hp_fe_face_values.reinit(cell, f_index);
1330 const auto &fe_face_values = hp_fe_face_values.get_present_fe_values();
1331 const auto &mapping =
1332 hp_fe_face_values.get_mapping_collection()[cell->active_fe_index()];
1333
1334 for (unsigned int j : fe_face_values.dof_indices()) {
1335 if (!cell->get_fe().has_support_on_face(j, f_index))
1336 continue;
1337
1338 Number boundary_mass = 0.;
1339 dealii::Tensor<1, dim, Number> normal;
1340
1341 for (unsigned int q : fe_face_values.quadrature_point_indices()) {
1342 const auto JxW = fe_face_values.JxW(q);
1343 const auto phi_i = fe_face_values.shape_value(j, q);
1344
1345 boundary_mass += phi_i * JxW;
1346 normal += phi_i * fe_face_values.normal_vector(q) * JxW;
1347 }
1348
1349 /*
1350 * Workaround for deal.II 9.7 and older versions:
1351 *
1352 * For simplices, has_support_on_face() seems to return the wrong
1353 * answer (always true). Thus, check whether we accumulated any
1354 * boundary mass and if not, bail out.
1355 */
1356 if (std::abs(boundary_mass) == 0.)
1357 continue;
1358
1359 const auto global_index = local_dof_indices[j];
1360 const auto index = partitioner.global_to_local(global_index);
1361
1362 /* Skip nonlocal degrees of freedom: */
1363 if (index >= n_locally_owned_)
1364 continue;
1365
1366 /* Skip constrained degrees of freedom: */
1367 const unsigned int row_length = sparsity_simd_view.row_length(index);
1368 if (row_length == 1)
1369 continue;
1370
1371 Point<dim> position =
1372 mapping.transform_unit_to_real_cell(cell, support_points[j]);
1373
1374 /*
1375 * Temporarily insert a (wrong) boundary mass value for the
1376 * normal mass. We'll fix this later.
1377 */
1378 preliminary_map.insert(
1379 {index, {normal, boundary_mass, boundary_mass, id, position}});
1380 } /* j */
1381 } /* f */
1382 } /* cell */
1383
1384 /*
1385 * Filter boundary map:
1386 *
1387 * At this point we have collected multiple cell contributions for each
1388 * boundary degree of freedom. We now merge all entries that have the
1389 * same boundary id and whose normals describe an acute angle of about
1390 * 60 degrees or less.
1391 *
1392 * FIXME: is this robust in 3D?
1393 */
1394
1395 std::multimap<unsigned int, BoundaryData> filtered_map;
1396 std::set<dealii::types::global_dof_index> boundary_dofs;
1397 for (auto entry : preliminary_map) {
1398 bool inserted = false;
1399 const auto range = filtered_map.equal_range(entry.first);
1400 for (auto it = range.first; it != range.second; ++it) {
1401 auto &[new_normal,
1402 new_normal_mass,
1403 new_boundary_mass,
1404 new_id,
1405 new_point] = entry.second;
1406 auto &[normal, normal_mass, boundary_mass, id, point] = it->second;
1407
1408 if (id != new_id)
1409 continue;
1410
1411 Assert(point.distance(new_point) < 1.0e-14, dealii::ExcInternalError());
1412
1413 if (normal * new_normal / normal.norm() / new_normal.norm() > 0.50) {
1414 /*
1415 * Both normals describe an acute angle of 85 degrees or less.
1416 * Merge the entries and continue.
1417 */
1418 normal += new_normal;
1419 boundary_mass += new_boundary_mass;
1420 inserted = true;
1421 continue;
1422
1423 } else if constexpr (dim == 2) {
1424 /*
1425 * Workaround for 2D: When enforcing slip boundary conditions
1426 * with two noncollinear vectors the resulting momentum must be
1427 * 0. But the normals don't necessarily describe an orthonormal
1428 * basis and we cannot use orthogonal projection. Therefore,
1429 * simply set the boundary type to no slip:
1430 */
1431 if (new_id == Boundary::slip) {
1432 Assert(id == Boundary::slip, dealii::ExcInternalError());
1433 new_id = Boundary::no_slip;
1434 id = Boundary::no_slip;
1435 }
1436 }
1437 }
1438 if (!inserted)
1439 filtered_map.insert(entry);
1440 }
1441
1442 /*
1443 * Normalize all normal vectors and create final boundary_map:
1444 */
1445
1446 BoundaryMap boundary_map;
1447 std::transform(
1448 std::begin(filtered_map),
1449 std::end(filtered_map),
1450 std::back_inserter(boundary_map),
1451 [&](const auto &it) -> BoundaryDescription { //
1452 auto index = it.first;
1453 const auto &[normal, normal_mass, boundary_mass, id, point] =
1454 it.second;
1455
1456 const auto new_normal_mass =
1457 normal.norm() + std::numeric_limits<Number>::epsilon();
1458 const auto new_normal = normal / new_normal_mass;
1459
1460 return {index, new_normal, new_normal_mass, boundary_mass, id, point};
1461 });
1462
1463 return boundary_map;
1464 }
1465
1466
1467 template <int dim, typename Number>
1468 template <typename ITERATOR1, typename ITERATOR2>
1470 const ITERATOR1 &begin,
1471 const ITERATOR2 &end,
1472 const Utilities::MPI::Partitioner &partitioner) const
1473 -> CouplingBoundaryPairs
1474 {
1475#ifdef DEBUG_OUTPUT
1476 std::cout << "OfflineData<dim, Number>::collect_coupling_boundary_pairs()"
1477 << std::endl;
1478#endif
1479
1480 /*
1481 * First, collect *all* locally relevant degrees of freedom that are
1482 * located on a (non periodic) boundary. We also collect constrained
1483 * degrees of freedom for the time being (and filter afterwards).
1484 */
1485
1486 std::set<unsigned int> locally_relevant_boundary_indices;
1487
1488 std::vector<dealii::types::global_dof_index> local_dof_indices;
1489
1490 for (auto cell = begin; cell != end; ++cell) {
1491
1492 /* Make sure to iterate over the entire locally relevant set: */
1493 if (cell->is_artificial())
1494 continue;
1495
1496 const auto &finite_element = cell->get_fe();
1497 const unsigned int dofs_per_cell = finite_element.dofs_per_cell;
1498 local_dof_indices.resize(dofs_per_cell);
1499 cell->get_active_or_mg_dof_indices(local_dof_indices);
1500
1501 for (auto f_index : cell->face_indices()) {
1502 const auto face = cell->face(f_index);
1503 const auto id = face->boundary_id();
1504
1505 /* Skip periodic boundary faces; see above. */
1506 if (id == Boundary::periodic)
1507 continue;
1508
1509 /*
1510 * Check for interior faces neighboring a cell with FE nothing:
1511 */
1512 const bool neighbor_cell_has_fe_nothing = //
1513 treat_fe_nothing_as_boundary_ && //
1514 !face->at_boundary() && //
1515 cell->neighbor(f_index)->is_active() && //
1516 !cell->neighbor(f_index)->is_artificial() && //
1517 (dynamic_cast<const dealii::FE_Nothing<dim> *>( //
1518 &cell->neighbor(f_index)->get_fe()) != nullptr); //
1519
1520 if (!neighbor_cell_has_fe_nothing && !face->at_boundary())
1521 continue;
1522
1523 for (unsigned int j = 0; j < dofs_per_cell; ++j) {
1524
1525 if (!cell->get_fe().has_support_on_face(j, f_index))
1526 continue;
1527
1528 const auto global_index = local_dof_indices[j];
1529 const auto index = partitioner.global_to_local(global_index);
1530
1531 /* Skip irrelevant degrees of freedom: */
1532 if (index >= n_locally_relevant_)
1533 continue;
1534
1535 locally_relevant_boundary_indices.insert(index);
1536 } /* j */
1537 } /* f */
1538 } /* cell */
1539
1540 /*
1541 * Now, collect all coupling boundary pairs:
1542 */
1543
1544 const auto sparsity_simd_view = sparsity_pattern_simd_.view();
1545
1546 CouplingBoundaryPairs result;
1547
1548 for (const auto i : locally_relevant_boundary_indices) {
1549
1550 /* Only record pairs with a left index that is locally owned: */
1551 if (i >= n_locally_owned_)
1552 continue;
1553
1554 const unsigned int row_length = sparsity_simd_view.row_length(i);
1555
1556 /* Skip all constrained degrees of freedom: */
1557 if (row_length == 1)
1558 continue;
1559
1560 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
1561 const unsigned int *js = sparsity_simd_view.columns(i);
1562 /* skip diagonal: */
1563 for (unsigned int col_idx = 1; col_idx < row_length; ++col_idx) {
1564 const auto j = js[col_idx * stride_size];
1565
1566 if (locally_relevant_boundary_indices.count(j) != 0) {
1567 result.push_back({i, col_idx, j});
1568 }
1569 }
1570 }
1571
1572 return result;
1573 }
1574
1575} /* namespace ryujin */
void prepare(const unsigned int problem_dimension, const unsigned int n_precomputed_values)
std::tuple< unsigned int, dealii::Tensor< 1, dim, Number >, Number, Number, dealii::types::boundary_id, dealii::Point< dim > > BoundaryDescription
OfflineData(const MPIEnsemble &mpi_ensemble, const Discretization< dim > &discretization, const std::string &subsection="/OfflineData")
unsigned int inconsistent_strides_last(dealii::DoFHandler< dim > &dof_handler, const dealii::DynamicSparsityPattern &sparsity, const unsigned int n_locally_internal, const std::size_t warp_size)
unsigned int export_indices_first(dealii::DoFHandler< dim > &dof_handler, const MPI_Comm &mpi_communicator, const unsigned int n_locally_internal, const std::size_t warp_size)
unsigned int internal_range(dealii::DoFHandler< dim > &dof_handler, const dealii::DynamicSparsityPattern &sparsity, const std::size_t warp_size)
void make_extended_sparsity_pattern_dg(const dealii::DoFHandler< dim > &dof_handler, SPARSITY &dsp, const dealii::AffineConstraints< Number > &affine_constraints, bool keep_constrained)
constexpr unsigned int warp_size
Definition gpu.h:46
std::shared_ptr< const dealii::Utilities::MPI::Partitioner > create_vector_partitioner(const std::shared_ptr< const dealii::Utilities::MPI::Partitioner > &scalar_partitioner, const unsigned int n_comp)
void distribute_local_to_global(const FullMatrix &cell_matrix, const std::vector< dealii::types::global_dof_index > &dof_indices_row, const std::vector< dealii::types::global_dof_index > &dof_indices_column, const dealii::AffineConstraints< Number > &affine_constraints, SparseMatrix< Number, n_comp, warp_size, simd_length > &sparse_matrix)