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