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