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