ryujin 2.1.1 revision 46bf70e400e423a8ffffe8300887eeb35b8dfb2c
solution_transfer.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception or LGPL-2.1-or-later
3// Copyright (C) 2024 - 2024 by the ryujin authors
4//
5
6#pragma once
7
8
9#include "openmp.h"
10#include "solution_transfer.h"
11#if DEAL_II_VERSION_GTE(9, 6, 0)
13#endif
14
15#include <deal.II/base/exceptions.h>
16#include <deal.II/distributed/tria.h>
17#include <deal.II/dofs/dof_accessor.h>
18#include <deal.II/dofs/dof_tools.h>
19#if DEAL_II_VERSION_GTE(9, 6, 0)
20#include <deal.II/grid/cell_status.h>
21#endif
22#include <deal.II/grid/tria_accessor.h>
23#include <deal.II/grid/tria_iterator.h>
24#include <deal.II/lac/block_vector.h>
25#include <deal.II/lac/la_parallel_block_vector.h>
26#include <deal.II/lac/la_parallel_vector.h>
27#include <deal.II/lac/petsc_block_vector.h>
28#include <deal.II/lac/petsc_vector.h>
29#include <deal.II/lac/trilinos_parallel_block_vector.h>
30#include <deal.II/lac/trilinos_vector.h>
31#include <deal.II/lac/vector.h>
32#include <deal.II/matrix_free/fe_point_evaluation.h>
33
34namespace ryujin
35{
36 template <typename Description, int dim, typename Number>
38 const MPIEnsemble &mpi_ensemble,
39 const OfflineData<dim, Number> &offline_data,
40 const HyperbolicSystem &hyperbolic_system,
41 const ParabolicSystem &parabolic_system,
42 const std::string &subsection /* = "/SolutionTransfer" */)
43 : ParameterAcceptor(subsection)
44 , limiter_parameters_(subsection + "/mass transfer limiter")
45 , mpi_ensemble_(mpi_ensemble)
46 , offline_data_(&offline_data)
47 , hyperbolic_system_(&hyperbolic_system)
48 , parabolic_system_(&parabolic_system)
49 , handle_(dealii::numbers::invalid_unsigned_int)
50 {
51 }
52
53
54 namespace
55 {
59 template <typename state_type>
60 std::vector<char>
61 pack_state_values(const std::vector<state_type> &state_values)
62 {
63 std::vector<char> buffer(sizeof(state_type) * state_values.size());
64 std::memcpy(buffer.data(), state_values.data(), buffer.size());
65 return buffer;
66 }
67
68
72 template <typename state_type>
73 std::vector<state_type> unpack_state_values(
74 const boost::iterator_range<std::vector<char>::const_iterator>
75 &data_range)
76 {
77 const std::size_t n_bytes = data_range.size();
78 Assert(n_bytes % sizeof(state_type) == 0, dealii::ExcInternalError());
79 std::vector<state_type> state_values(n_bytes / sizeof(state_type));
80 std::memcpy(state_values.data(),
81 &data_range[0],
82 state_values.size() * sizeof(state_type));
83 return state_values;
84 }
85 } // namespace
86
87
88 template <typename Description, int dim, typename Number>
89 inline DEAL_II_ALWAYS_INLINE auto
90 SolutionTransfer<Description, dim, Number>::get_tensor(
91 const HyperbolicVector &U, const dealii::types::global_dof_index global_i)
92 -> state_type
93 {
94 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
95 const auto &affine_constraints = offline_data_->affine_constraints();
96 const auto local_i = scalar_partitioner->global_to_local(global_i);
97 if (affine_constraints.is_constrained(global_i)) {
98 state_type result;
99 const auto &line = *affine_constraints.get_constraint_entries(global_i);
100 for (const auto &[global_k, c_k] : line) {
101 const auto local_k = scalar_partitioner->global_to_local(global_k);
102 result += c_k * U.get_tensor(local_k);
103 }
104 return result;
105 } else {
106 return U.get_tensor(local_i);
107 }
108 }
109
110
111 template <typename Description, int dim, typename Number>
112 inline DEAL_II_ALWAYS_INLINE void
113 SolutionTransfer<Description, dim, Number>::add_tensor(
114 HyperbolicVector &U,
115 const state_type &new_U_i,
116 const dealii::types::global_dof_index global_i)
117 {
118 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
119 const auto local_i = scalar_partitioner->global_to_local(global_i);
120 U.add_tensor(new_U_i, local_i);
121 }
122
123
124 template <typename Description, int dim, typename Number>
126 const StateVector &old_state_vector [[maybe_unused]])
127 {
128#ifdef DEBUG_OUTPUT
129 std::cout
130 << "SolutionTransfer<Description, dim, Number>::prepare_projection()"
131 << std::endl;
132#endif
133
134#if !DEAL_II_VERSION_GTE(9, 6, 0)
135 AssertThrow(
136 false,
137 dealii::ExcMessage(
138 "The SolutionTransfer class needs deal.II version 9.6.0 or newer"));
139
140#else
141 const auto &discretization = offline_data_->discretization();
142 auto &triangulation = *discretization.triangulation_; /* writable */
143
144 Assert(handle_ == dealii::numbers::invalid_unsigned_int,
145 dealii::ExcMessage(
146 "You can only add one solution per SolutionTransfer object."));
147
148 /*
149 * -----------------------------------------------------------------------
150 * Cell-level projection to parent cells and packing data:
151 * -----------------------------------------------------------------------
152 */
153
154 handle_ = triangulation.register_data_attach(
155 [this, &old_state_vector](const auto cell,
156 const dealii::CellStatus status) {
157 const auto &dof_handler = offline_data_->dof_handler();
158 const auto dof_cell = typename dealii::DoFHandler<dim>::cell_iterator(
159 &cell->get_triangulation(),
160 cell->level(),
161 cell->index(),
162 &dof_handler);
163
164 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
165
166 const auto &U = std::get<0>(old_state_vector);
167 /* precomputed needs to be valid for bounds computation */
168 const auto &precomputed = std::get<1>(old_state_vector);
169
170 using Limiter = typename Description::template Limiter<dim, Number>;
171 const Limiter limiter(
172 *hyperbolic_system_, limiter_parameters_, precomputed);
173
174 /*
175 * Collect state values for packing:
176 */
177
178 const auto n_dofs_per_cell = dof_cell->get_fe().n_dofs_per_cell();
179 std::vector<state_type> state_values(n_dofs_per_cell);
180
181 switch (status) {
182 case dealii::CellStatus::cell_will_persist:
183 [[fallthrough]];
184 case dealii::CellStatus::cell_will_be_refined: {
185 /*
186 * For both cases we need state values from the currently
187 * active cell:
188 */
189
190 Assert(dof_cell->is_active(), dealii::ExcInternalError());
191 std::vector<dealii::types::global_dof_index> dof_indices(
192 n_dofs_per_cell);
193 dof_cell->get_dof_indices(dof_indices);
194
195 std::transform(
196 std::begin(dof_indices),
197 std::end(dof_indices),
198 std::begin(state_values),
199 [&](const auto global_i) { return get_tensor(U, global_i); });
200 } break;
201
202 case dealii::CellStatus::children_will_be_coarsened: {
203 /*
204 * We need to project values from the active child cells up to
205 * the present parent cell that will become active after
206 * coarsening.
207 */
208
209 Assert(dof_cell->has_children(), dealii::ExcInternalError());
210
211 const auto &discretization = offline_data_->discretization();
212 const auto index = dof_cell->active_fe_index();
213 const auto &finite_element = discretization.finite_element()[index];
214 const auto &mapping = discretization.mapping()[index];
215 const auto &quadrature = discretization.quadrature()[index];
216
217 dealii::FEValues<dim> fe_values(
218 mapping,
219 finite_element,
220 quadrature,
221 dealii::update_values | dealii::update_JxW_values |
222 dealii::update_quadrature_points);
223
224 const auto polynomial_space =
225 dealii::internal::FEPointEvaluation::get_polynomial_space(
226 finite_element);
227
228 std::vector<dealii::Point<dim, Number>> unit_points(
229 quadrature.size());
230 /*
231 * for Number == float we need a temporary vector for the
232 * transform_points_real_to_unit_cell() function:
233 */
234 std::vector<dealii::Point<dim>> unit_points_temp(
235 std::is_same_v<Number, float> ? quadrature.size() : 0);
236
237 /* Step 1: build up right hand side by iterating over children: */
238
239 std::vector<state_type> state_values_quad(quadrature.size());
240 std::vector<state_type> local_rhs(n_dofs_per_cell);
241
242 std::vector<dealii::types::global_dof_index> dof_indices(
243 n_dofs_per_cell);
244
245 Bounds bounds;
246
247 for (unsigned int child = 0; child < dof_cell->n_children();
248 ++child) {
249 const auto child_cell = dof_cell->child(child);
250
251 Assert(child_cell->is_active(), dealii::ExcInternalError());
252 Assert(dof_cell->active_fe_index() ==
253 child_cell->active_fe_index(),
254 dealii::ExcMessage("SolutionTransfer: projection between "
255 "different FE space is not set up."));
256
257 fe_values.reinit(child_cell);
258
259 if constexpr (std::is_same_v<Number, float>) {
260 mapping.transform_points_real_to_unit_cell(
261 dof_cell,
262 fe_values.get_quadrature_points(),
263 unit_points_temp);
264 std::transform(std::begin(unit_points_temp),
265 std::end(unit_points_temp),
266 std::begin(unit_points),
267 [](const auto &x) { return x; });
268 } else {
269 mapping.transform_points_real_to_unit_cell(
270 dof_cell, fe_values.get_quadrature_points(), unit_points);
271 }
272
273 child_cell->get_dof_indices(dof_indices);
274
275 /* We want a "left fold first" for the bounds: */
276 if (child == 0 &&
277 std::begin(dof_indices) != std::end(dof_indices)) {
278 const auto global_i = dof_indices[0];
279 const auto U_i = get_tensor(U, global_i);
280 const auto local_i =
281 scalar_partitioner->global_to_local(global_i);
282 bounds = limiter.projection_bounds_from_state(local_i, U_i);
283 }
284
285 for (auto &it : state_values_quad)
286 it = state_type{};
287
288 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
289 const auto global_i = dof_indices[i];
290 const auto U_i = get_tensor(U, global_i);
291 const auto local_i =
292 scalar_partitioner->global_to_local(global_i);
293 const auto bounds_i =
294 limiter.projection_bounds_from_state(local_i, U_i);
295 bounds = limiter.combine_bounds(bounds, bounds_i);
296
297 for (unsigned int q = 0; q < quadrature.size(); ++q) {
298 state_values_quad[q] += U_i * fe_values.shape_value(i, q);
299 }
300 }
301
302 for (unsigned int q = 0; q < quadrature.size(); ++q)
303 state_values_quad[q] *= fe_values.JxW(q);
304
305 for (unsigned int q = 0; q < quadrature.size(); ++q) {
306 const unsigned int n_shapes = polynomial_space.size();
307 AssertIndexRange(n_shapes, 10);
308 dealii::ndarray<Number, 10, 2, dim> shapes;
309 // Evaluate 1d polynomials and their derivatives
310 std::array<Number, dim> point;
311 for (unsigned int d = 0; d < dim; ++d)
312 point[d] = unit_points[q][d];
313 for (unsigned int i = 0; i < n_shapes; ++i)
314 polynomial_space[i].values_of_array(point, 1, &shapes[i][0]);
315
316 Assert(finite_element.degree == 1, dealii::ExcNotImplemented());
317
319 /*is linear*/ true,
320 dim,
321 Number,
322 state_type>(shapes.data(),
323 n_shapes,
324 state_values_quad[q],
325 local_rhs.data(),
326 unit_points[q],
327 true);
328 }
329 }
330
331 /* Step 2: construct inverse mass matrices: */
332
333 fe_values.reinit(dof_cell);
334
335 dealii::FullMatrix<double> mass_inverse(n_dofs_per_cell,
336 n_dofs_per_cell);
337 dealii::Vector<double> lumped_mass(n_dofs_per_cell);
338 dealii::Vector<double> lumped_mass_inverse(n_dofs_per_cell);
339
340 auto total_mass = Number(0.);
341 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
342 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
343 double sum = 0;
344 for (unsigned int q = 0; q < quadrature.size(); ++q)
345 sum += fe_values.shape_value(i, q) *
346 fe_values.shape_value(j, q) * fe_values.JxW(q);
347 mass_inverse(i, j) = sum;
348 lumped_mass(i) += sum;
349 }
350 lumped_mass_inverse(i) = Number(1.) / lumped_mass(i);
351 total_mass += lumped_mass(i);
352 }
353 mass_inverse.gauss_jordan();
354
355 /* Step 3: compute low-order update and P_ij matrix: */
356
357 bounds = limiter.fully_relax_bounds(bounds, total_mass);
358
359 std::vector<state_type> pij_matrix(n_dofs_per_cell *
360 n_dofs_per_cell);
361 dealii::FullMatrix<Number> lij_matrix(n_dofs_per_cell,
362 n_dofs_per_cell);
363
364 const auto kappa_inverse = Number(n_dofs_per_cell);
365 const auto kappa = Number(1.) / kappa_inverse;
366
367 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
368 const state_type U_i = lumped_mass_inverse(i) * local_rhs[i];
369 state_values[i] = U_i;
370
371 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
372 const auto kronecker_ij = Number(i == j ? 1. : 0.);
373 const auto b_ij =
374 lumped_mass(i) * mass_inverse(i, j) - kronecker_ij;
375 const auto b_ji =
376 lumped_mass(j) * mass_inverse(i, j) - kronecker_ij;
377 const auto P_ij = kappa_inverse * lumped_mass_inverse(i) *
378 (b_ij * local_rhs[j] - b_ji * local_rhs[i]);
379 pij_matrix[n_dofs_per_cell * i + j] = P_ij;
380 }
381 }
382
383 /* Step 4: compute l_ij matrix and apply limited update: */
384
385 const auto n_iterations = limiter_parameters_.iterations();
386 for (unsigned int pass = 0; pass < n_iterations; ++pass) {
387
388 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
389 const auto &U_i = state_values[i];
390
391 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
392 const auto &P_ij = pij_matrix[n_dofs_per_cell * i + j];
393 const auto &[l_ij, check] = limiter.limit(bounds, U_i, P_ij);
394 lij_matrix(i, j) = l_ij;
395
396#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
397 AssertThrow(
398 check,
399 dealii::ExcMessage(
400 "Error: low-order state out of bounds in "
401 "register_data_attach / children_will_be_coarsened"));
402#endif
403 }
404 }
405
406 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
407 auto &U_i = state_values[i];
408
409 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
410 const auto l_ij =
411 std::min(lij_matrix(i, j), lij_matrix(j, i));
412 auto &P_ij = pij_matrix[n_dofs_per_cell * i + j];
413 U_i += kappa * l_ij * P_ij;
414 P_ij -= l_ij * P_ij;
415 }
416
417#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
418 const auto view =
419 hyperbolic_system_->template view<dim, Number>();
420 AssertThrow(
421 view.is_admissible(U_i),
422 dealii::ExcMessage(
423 "Error: inadmissible state encountered in "
424 "register_data_attach / children_will_be_coarsened"));
425#endif
426 }
427 }
428 } break;
429
430 case dealii::CellStatus::cell_invalid:
431 Assert(false, dealii::ExcInternalError());
432 __builtin_trap();
433 break;
434 }
435
436 return pack_state_values(state_values);
437 },
438 /* returns_variable_size_data =*/false);
439#endif
440 }
441
442
443 template <typename Description, int dim, typename Number>
445 StateVector &new_state_vector [[maybe_unused]])
446 {
447#ifdef DEBUG_OUTPUT
448 std::cout << "SolutionTransfer<Description, dim, Number>::project()"
449 << std::endl;
450#endif
451
452#if !DEAL_II_VERSION_GTE(9, 6, 0)
453 AssertThrow(
454 false,
455 dealii::ExcMessage(
456 "The SolutionTransfer class needs deal.II version 9.6.0 or newer"));
457
458#else
459
460 const auto &discretization = offline_data_->discretization();
461 auto &triangulation = *discretization.triangulation_; /* writable */
462
463 Assert(
464 handle_ != dealii::numbers::invalid_unsigned_int,
465 dealii::ExcMessage(
466 "Cannot project() a state vector without valid handle. "
467 "prepare_projection() or set_handle() have to be called first."));
468
469 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
470 const auto &affine_constraints = offline_data_->affine_constraints();
471 const auto n_locally_owned = offline_data_->n_locally_owned();
472
473
474 ScalarVector projected_mass;
475 projected_mass.reinit(offline_data_->scalar_partitioner());
476 HyperbolicVector projected_state;
477 projected_state.reinit(offline_data_->hyperbolic_vector_partitioner());
478
479 /*
480 * We only need to construct entries in a pik_matrix for a small subset
481 * of affected degrees of freedom for which we have to construct the
482 * entire pik_matrix first for the limiting process (in contrast to the
483 * entirely cell-local limiting done before). Let's simply use a map.
484 */
485 std::map<std::tuple<unsigned int /*i*/, unsigned int /*k*/>, state_type>
486 pik_matrix;
487 std::map<unsigned int /*i*/, Bounds> bounds_map;
488
489 ScalarVector kappa;
490 kappa.reinit(offline_data_->scalar_partitioner());
491
492 /*
493 * -----------------------------------------------------------------------
494 * Unpacking data and cell-level interpolation/projection to child cells:
495 * -----------------------------------------------------------------------
496 */
497
498 triangulation.notify_ready_to_unpack( //
499 handle_,
500 [this, &projected_mass, &projected_state](
501 const auto &cell,
502 const dealii::CellStatus status,
503 const auto &data_range) {
504 const auto &dof_handler = offline_data_->dof_handler();
505 const auto dof_cell = typename dealii::DoFHandler<dim>::cell_iterator(
506 &cell->get_triangulation(),
507 cell->level(),
508 cell->index(),
509 &dof_handler);
510
511 /*
512 * Retrieve packed values and project onto cell:
513 */
514
515 const auto n_dofs_per_cell = dof_cell->get_fe().n_dofs_per_cell();
516 std::vector<dealii::types::global_dof_index> dof_indices(
517 n_dofs_per_cell);
518
519 const auto state_values = unpack_state_values<state_type>(data_range);
520
521 switch (status) {
522 case dealii::CellStatus::cell_will_persist:
523 [[fallthrough]];
524 case dealii::CellStatus::children_will_be_coarsened: {
525 /*
526 * For both cases we distribute stored state_values to the
527 * projected_state and projected_mass vectors.
528 */
529
530 Assert(dof_cell->is_active(), dealii::ExcInternalError());
531 dof_cell->get_dof_indices(dof_indices);
532
533 const auto &discretization = offline_data_->discretization();
534 const auto index = dof_cell->active_fe_index();
535 const auto &finite_element = discretization.finite_element()[index];
536 const auto &mapping = discretization.mapping()[index];
537 const auto &quadrature = discretization.quadrature()[index];
538
539 dealii::FEValues<dim> fe_values(mapping,
540 finite_element,
541 quadrature,
542 dealii::update_values |
543 dealii::update_JxW_values);
544
545 fe_values.reinit(dof_cell);
546
547 dealii::Vector<double> mi(n_dofs_per_cell);
548 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
549 double sum = 0;
550 for (unsigned int q = 0; q < quadrature.size(); ++q)
551 sum += fe_values.shape_value(i, q) * fe_values.JxW(q);
552 mi(i) += sum;
553 }
554
555 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
556 const auto global_i = dof_indices[i];
557 add_tensor(projected_state, mi(i) * state_values[i], global_i);
558 projected_mass(global_i) += mi(i);
559 }
560
561 } break;
562
563 case dealii::CellStatus::cell_will_be_refined: {
564 /*
565 * We are on a (non active) cell that has been refined. Project
566 * onto the children and do a local mass projection there:
567 */
568
569 Assert(dof_cell->has_children(), dealii::ExcInternalError());
570
571 const auto &discretization = offline_data_->discretization();
572 const auto index = dof_cell->active_fe_index();
573 const auto &finite_element = discretization.finite_element()[index];
574 const auto &mapping = discretization.mapping()[index];
575 const auto &quadrature = discretization.quadrature()[index];
576
577 dealii::FEValues<dim> fe_values(
578 mapping,
579 finite_element,
580 quadrature,
581 dealii::update_values | dealii::update_JxW_values |
582 dealii::update_quadrature_points);
583
584 const auto polynomial_space =
585 dealii::internal::FEPointEvaluation::get_polynomial_space(
586 finite_element);
587 std::vector<dealii::Point<dim, Number>> unit_points(
588 quadrature.size());
589 /*
590 * for Number == float we need a temporary vector for the
591 * transform_points_real_to_unit_cell() function:
592 */
593 std::vector<dealii::Point<dim>> unit_points_temp(
594 std::is_same_v<Number, float> ? quadrature.size() : 0);
595
596 dealii::FullMatrix<double> mass_inverse(n_dofs_per_cell,
597 n_dofs_per_cell);
598 dealii::Vector<double> lumped_mass(n_dofs_per_cell);
599 std::vector<state_type> local_rhs(n_dofs_per_cell);
600
601 for (unsigned int child = 0; child < dof_cell->n_children();
602 ++child) {
603 const auto child_cell = dof_cell->child(child);
604
605 Assert(child_cell->is_active(), dealii::ExcInternalError());
606 Assert(dof_cell->active_fe_index() ==
607 child_cell->active_fe_index(),
608 dealii::ExcMessage("SolutionTransfer: projection between "
609 "different FE space is not set up."));
610
611 child_cell->get_dof_indices(dof_indices);
612
613 /* Step 1: build up right hand side on child cell: */
614
615 fe_values.reinit(child_cell);
616
617 if constexpr (std::is_same_v<Number, float>) {
618 mapping.transform_points_real_to_unit_cell(
619 dof_cell,
620 fe_values.get_quadrature_points(),
621 unit_points_temp);
622 std::transform(std::begin(unit_points_temp),
623 std::end(unit_points_temp),
624 std::begin(unit_points),
625 [](const auto &x) { return x; });
626 } else {
627 mapping.transform_points_real_to_unit_cell(
628 dof_cell, fe_values.get_quadrature_points(), unit_points);
629 }
630
631 for (auto &it : local_rhs)
632 it = state_type{};
633
634 for (unsigned int q = 0; q < quadrature.size(); ++q) {
635 Assert(finite_element.degree == 1, dealii::ExcNotImplemented());
636 auto coefficient =
637 dealii::internal::evaluate_tensor_product_value(
638 polynomial_space,
639 make_const_array_view(state_values),
640 unit_points[q],
641 /*is linear*/ true);
642 coefficient *= fe_values.JxW(q);
643
644 for (unsigned int i = 0; i < n_dofs_per_cell; ++i)
645 local_rhs[i] += coefficient * fe_values.shape_value(i, q);
646 }
647
648 /* Step 2: solve with inverse mass matrix on child cell: */
649
650 mass_inverse = Number(0.);
651 lumped_mass = Number(0.);
652 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
653 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
654 double sum = 0;
655 for (unsigned int q = 0; q < quadrature.size(); ++q)
656 sum += fe_values.shape_value(i, q) *
657 fe_values.shape_value(j, q) * fe_values.JxW(q);
658 mass_inverse(i, j) = sum;
659 lumped_mass(i) += sum;
660 }
661 }
662 mass_inverse.gauss_jordan();
663
664 /* Step 3: compute high order update and write back: */
665
666 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
667 state_type U_i;
668 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
669 U_i += mass_inverse(i, j) * local_rhs[j];
670 }
671
672#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
673 const auto view =
674 hyperbolic_system_->template view<dim, Number>();
675 AssertThrow(view.is_admissible(U_i),
676 dealii::ExcMessage(
677 "Error: inadmissible state encountered in "
678 "ready_to_unpack / cell_will_be_refined"));
679#endif
680 const auto global_i = dof_indices[i];
681 add_tensor(projected_state, lumped_mass(i) * U_i, global_i);
682 projected_mass(global_i) += lumped_mass(i);
683 }
684 } /*child*/
685
686 } break;
687
688 case dealii::CellStatus::cell_invalid:
689 Assert(false, dealii::ExcInternalError());
690 __builtin_trap();
691 break;
692 }
693 });
694
695 projected_mass.compress(dealii::VectorOperation::add);
696 projected_state.compress(dealii::VectorOperation::add);
697
698 /*
699 * -----------------------------------------------------------------------
700 * Redistribute masses to satisfy hanging-node constraints:
701 *
702 * Now redistribute the mass defect introduced by constrained degrees
703 * of freedom. This mostly affects hanging nodes neighboring a
704 * coarsened cell. Here, cell-wise mass projection might lead to a
705 * value for a constrained degree of freedom that differs from the
706 * algebraic relationship expressed by our affine constraints. Thus, we
707 * first compute the defect and then we redistribute it to all degrees
708 * of freedom on the constraint line.
709 * -----------------------------------------------------------------------
710 */
711
712 auto &new_U = std::get<0>(new_state_vector);
713
714 /*
715 * A small lambda that takes the weighted average of all degrees of
716 * freedom, and stores the result in new_U:
717 */
718 const auto update_new_state_vector = [&]() {
719 for (unsigned int local_i = 0; local_i < n_locally_owned; ++local_i) {
720
721 const auto mU_i = projected_state.get_tensor(local_i);
722 const auto m_i = projected_mass.local_element(local_i);
723
724#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
725 const auto view = hyperbolic_system_->template view<dim, Number>();
726 AssertThrow(
727 view.is_admissible(mU_i / m_i),
728 dealii::ExcMessage("Error: inadmissible state encountered in "
729 "update_new_state_vector()"));
730#endif
731
732 new_U.write_tensor(mU_i / m_i, local_i);
733 }
734 new_U.update_ghost_values();
735 };
736
737 update_new_state_vector();
738
739 /* precomputed needs to be valid for bounds computation */
740
741 const auto &precomputed = std::get<1>(new_state_vector);
742
743 const auto update_precomputed_values = [&]() {
744 const unsigned int n_export_indices = offline_data_->n_export_indices();
745 const unsigned int n_internal = offline_data_->n_locally_internal();
746 const unsigned int n_owned = offline_data_->n_locally_owned();
747 const auto &sparsity_simd = offline_data_->sparsity_pattern_simd();
748 unsigned int channel = 10;
749 using VA = dealii::VectorizedArray<Number>;
750 static constexpr auto n_precomputation_cycles =
751 View::n_precomputation_cycles;
752
753 new_U.update_ghost_values();
754
755 if constexpr (n_precomputation_cycles != 0) {
756 for (unsigned int cycle = 0; cycle < n_precomputation_cycles; ++cycle) {
757
758 SynchronizationDispatch synchronization_dispatch([&]() {
759 precomputed.update_ghost_values_start(channel++);
760 precomputed.update_ghost_values_finish();
761 });
762
764
765 auto loop = [&](auto sentinel,
766 unsigned int left,
767 unsigned int right) {
768 using T = decltype(sentinel);
769
770 /* Stored thread locally: */
771 bool thread_ready = false;
772
773 const auto view = hyperbolic_system_->template view<dim, T>();
774 view.precomputation_loop(
775 cycle,
776 [&](const unsigned int i) {
777 synchronization_dispatch.check(
778 thread_ready, i >= n_export_indices && i < n_internal);
779 },
780 sparsity_simd,
781 new_state_vector,
782 left,
783 right,
784 /*skip_constrained_dofs*/ false);
785 };
786
787 /* Parallel non-vectorized loop: */
788 loop(Number(), n_internal, n_owned);
789 /* Parallel vectorized SIMD loop: */
790 loop(VA(), 0, n_internal);
791
793 }
794 }
795 };
796
797 update_precomputed_values();
798
799 using Limiter = typename Description::template Limiter<dim, Number>;
800 const Limiter limiter(
801 *hyperbolic_system_, limiter_parameters_, precomputed);
802
803 /*
804 * Step 1: compute low-order update P_ij matrix, and bounds:
805 *
806 * We compute limiter bounds as a single value over the constraint
807 * line. This makes sense as we need to limit the update for each
808 * affected (unconstrained) degree of freedom of a constraint line with
809 * a single limiter value anyway to ensure mass conservation.
810 * (Incidentally, this avoids having to update a global, distributed
811 * bounds vector over all MPI ranks.)
812 */
813
814 for (const auto &line : affine_constraints.get_lines()) {
815 const auto global_i = line.index;
816 const auto local_i = scalar_partitioner->global_to_local(global_i);
817
818 /* Only operate on a locally owned, constrained degree of freedom: */
819 if (local_i >= n_locally_owned)
820 continue;
821
822 /* The result of the mass projection: */
823 const auto m_i_star = projected_mass.local_element(local_i);
824 const auto U_i_star = projected_state.get_tensor(local_i) / m_i_star;
825
826 auto &bounds = bounds_map[local_i]; /* by reference */
827 bounds = limiter.projection_bounds_from_state(local_i, U_i_star);
828
829 /* The value obtained from the affine constraints object: */
830 state_type U_i_interp;
831 for (const auto &[global_k, c_k] : line.entries) {
832 const auto local_k = scalar_partitioner->global_to_local(global_k);
833 U_i_interp += c_k * new_U.get_tensor(local_k);
834 }
835
836 /* And redistribute low order update: */
837 for (const auto &[global_k, c_k] : line.entries) {
838 const auto local_k = scalar_partitioner->global_to_local(global_k);
839 const auto U_k = new_U.get_tensor(local_k);
840
841 const auto bounds_k =
842 limiter.projection_bounds_from_state(local_k, U_k);
843 bounds = limiter.combine_bounds(bounds, bounds_k);
844
845 projected_state.add_tensor(c_k * m_i_star * U_i_star, local_k);
846 projected_mass.local_element(local_k) += c_k * m_i_star;
847
848 kappa.local_element(local_k) += Number(1.);
849 pik_matrix[{local_i, local_k}] = c_k * m_i_star * (U_k - U_i_interp);
850 }
851 }
852
853 /* Compress vectors, recalculate unconstrained states: */
854 projected_mass.compress(dealii::VectorOperation::add);
855 projected_state.compress(dealii::VectorOperation::add);
856 kappa.compress(dealii::VectorOperation::add);
857 update_new_state_vector();
858
859 /* Redistribute ghost layer for masses and kappa: */
860 projected_mass.update_ghost_values();
861 kappa.update_ghost_values();
862
863 /* Step 2: Apply limiter: */
864
865 const auto n_iterations = limiter_parameters_.iterations();
866 for (unsigned int pass = 0; pass < n_iterations; ++pass) {
867
868 /* Update precomputed values for bounds correction: */
869 update_precomputed_values();
870
871 for (const auto &line : affine_constraints.get_lines()) {
872 const auto global_i = line.index;
873 const auto local_i = scalar_partitioner->global_to_local(global_i);
874
875 /* Only operate on a locally owned, constrained degree of freedom: */
876 if (local_i >= n_locally_owned)
877 continue;
878
879 /*
880 * We are computing bounds only over a local constraint line
881 * without recombining such bounds per (unconstrained) degree of
882 * freedom globally. We avoid doing the latter because it would
883 * require a custom "VectorOperation" invoking
884 * Limiter::combine_bounds(), which we currently do not have at our
885 * disposal.
886 *
887 * As a simple workaround we simply recompute bounds for the
888 * constraint line after the low-order update and each limiter pass
889 * and recombine those into the stored value.
890 */
891
892 auto &bounds = bounds_map[local_i]; /* by reference */
893 auto total_mass = Number(0.);
894 for (const auto &[global_k, c_k] : line.entries) {
895 const auto local_k = scalar_partitioner->global_to_local(global_k);
896 const auto U_k = new_U.get_tensor(local_k);
897 const auto bounds_k =
898 limiter.projection_bounds_from_state(local_k, U_k);
899 bounds = limiter.combine_bounds(bounds, bounds_k);
900
901 const auto m_k = projected_mass.local_element(local_k);
902 total_mass += m_k;
903 }
904
905 auto l = Number(1.);
906
907 /* Apply relaxation: */
908 const auto relaxed_bounds =
909 limiter.fully_relax_bounds(bounds, total_mass);
910
911 /* Compute limiter values: */
912
913 for (const auto &[global_k, c_k] : line.entries) {
914 const auto local_k = scalar_partitioner->global_to_local(global_k);
915 const auto kappa_k = kappa.local_element(local_k);
916 const auto m_k = projected_mass.local_element(local_k);
917 const auto U_k = new_U.get_tensor(local_k);
918 const auto P_ik = pik_matrix[{local_i, local_k}] * kappa_k / m_k;
919
920 const auto &[l_k, check] = limiter.limit(relaxed_bounds, U_k, P_ik);
921#ifdef DEBUG_EXPENSIVE_BOUNDS_CHECK
922 AssertThrow(check,
923 dealii::ExcMessage("Error: low-order state out of bounds "
924 "in project / state redistribution"));
925#endif
926 l = std::min(l, l_k);
927 }
928
929 /* Apply limiter values: */
930
931 for (const auto &[global_k, c_k] : line.entries) {
932 const auto local_k = scalar_partitioner->global_to_local(global_k);
933 auto &mP_ik = pik_matrix[{local_i, local_k}];
934 projected_state.add_tensor(l * mP_ik, local_k);
935 mP_ik -= l * mP_ik;
936 }
937 }
938
939 /* Compress state vector, recalculate unconstrained states: */
940 projected_state.compress(dealii::VectorOperation::add);
941 update_new_state_vector();
942 }
943
944 /* Zero out constrained degrees of freedom: */
945 for (unsigned int local_i = 0; local_i < n_locally_owned; ++local_i) {
946 const auto global_i = scalar_partitioner->local_to_global(local_i);
947 if (affine_constraints.is_constrained(global_i))
948 new_U.write_tensor(state_type{}, local_i);
949 }
950 new_U.update_ghost_values();
951
952#ifdef DEBUG_SYMMETRY_CHECK
953 /*
954 * Sanity check: Final masses must agree:
955 */
956 const auto &lumped_mass_matrix = offline_data_->lumped_mass_matrix();
957 for (unsigned int local_i = 0; local_i < n_locally_owned; ++local_i) {
958 const auto global_i = scalar_partitioner->local_to_global(local_i);
959 if (affine_constraints.is_constrained(global_i))
960 continue;
961
962 const auto m_i = projected_mass.local_element(local_i);
963 const auto m_i_reference = lumped_mass_matrix.local_element(local_i);
964 Assert(std::abs(m_i - m_i_reference) < 1.e-10,
965 dealii::ExcMessage(
966 "SolutionTransfer::projection(): something went wrong. Final "
967 "masses do not agree with those computed in OfflineData."));
968 }
969#endif
970#endif
971 }
972} // namespace ryujin
Bounds fully_relax_bounds(const Bounds &bounds, const Number &hd) const
Definition: limiter.h:236
Bounds combine_bounds(const Bounds &bounds_left, const Bounds &bounds_right) const
Definition: limiter.h:224
Bounds projection_bounds_from_state(const unsigned int i, const state_type &U_i) const
Definition: limiter.h:214
std::tuple< Number, bool > limit(const Bounds &bounds, const state_type &U, const state_type &P, const Number t_min=Number(0.), const Number t_max=Number(1.)) const
typename View::HyperbolicVector HyperbolicVector
SolutionTransfer(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system, const std::string &subsection="/SolutionTransfer")
Vectors::ScalarVector< Number > ScalarVector
typename Description::ParabolicSystem ParabolicSystem
void prepare_projection(const StateVector &old_state_vector)
typename Description::HyperbolicSystem HyperbolicSystem
typename Description::template Limiter< dim, double >::Bounds Bounds
void project(StateVector &new_state_vector)
typename View::StateVector StateVector
#define RYUJIN_PARALLEL_REGION_BEGIN
Definition: openmp.h:54
#define RYUJIN_PARALLEL_REGION_END
Definition: openmp.h:63
void integrate_tensor_product_value(const dealii::ndarray< Number, 2, dim > *shapes, const unsigned int n_shapes, const Number2 &value, Number2 *values, const dealii::Point< dim, Number > &p, const bool do_add)
DEAL_II_ALWAYS_INLINE FT add(const FT &flux_left_ij, const FT &flux_right_ij)