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