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