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