ryujin 2.1.1 revision 955e869188d49b3c97ca7b1cf4fd9ceb0e6f46ef
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#include <compile_time_options.h>
9
10#include "discretization.h"
11#include "solution_transfer.h"
12#if DEAL_II_VERSION_GTE(9, 6, 0)
14#endif
15
16#include <deal.II/base/config.h>
17#include <deal.II/distributed/tria.h>
18#include <deal.II/dofs/dof_accessor.h>
19#include <deal.II/dofs/dof_tools.h>
20#if DEAL_II_VERSION_GTE(9, 6, 0)
21#include <deal.II/grid/cell_status.h>
22#endif
23#include <deal.II/grid/tria_accessor.h>
24#include <deal.II/grid/tria_iterator.h>
25#include <deal.II/lac/block_vector.h>
26#include <deal.II/lac/la_parallel_block_vector.h>
27#include <deal.II/lac/la_parallel_vector.h>
28#include <deal.II/lac/petsc_block_vector.h>
29#include <deal.II/lac/petsc_vector.h>
30#include <deal.II/lac/trilinos_parallel_block_vector.h>
31#include <deal.II/lac/trilinos_vector.h>
32#include <deal.II/lac/vector.h>
33#include <deal.II/matrix_free/fe_point_evaluation.h>
34
35
36namespace ryujin
37{
38 template <typename Description, int dim, typename Number>
40 const MPIEnsemble &mpi_ensemble,
41 const OfflineData<dim, Number> &offline_data,
42 const HyperbolicSystem &hyperbolic_system,
43 const ParabolicSystem &parabolic_system)
44 : mpi_ensemble_(mpi_ensemble)
45 , offline_data_(&offline_data)
46 , hyperbolic_system_(&hyperbolic_system)
47 , parabolic_system_(&parabolic_system)
48 , handle_(dealii::numbers::invalid_unsigned_int)
49
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 AssertThrow(have_distributed_triangulation<dim>,
142 dealii::ExcMessage(
143 "The SolutionTransfer class is not implemented for a "
144 "distributed::shared::Triangulation which we use in 1D"));
145
146 const auto &discretization = offline_data_->discretization();
147 auto &triangulation = *discretization.triangulation_; /* writable */
148
149 Assert(handle_ == dealii::numbers::invalid_unsigned_int,
150 dealii::ExcMessage(
151 "You can only add one solution per SolutionTransfer object."));
152
153 /*
154 * Add a register_data_attach callback that
155 */
156
157 handle_ = triangulation.register_data_attach(
158 [this, &old_state_vector](const auto cell,
159 const dealii::CellStatus status) {
160 const auto &dof_handler = offline_data_->dof_handler();
161 const auto dof_cell = typename dealii::DoFHandler<dim>::cell_iterator(
162 &cell->get_triangulation(),
163 cell->level(),
164 cell->index(),
165 &dof_handler);
166
167 const auto &U = std::get<0>(old_state_vector);
168
169 /*
170 * Collect values for packing:
171 */
172
173 const auto n_dofs_per_cell = dof_handler.get_fe().n_dofs_per_cell();
174 std::vector<state_type> state_values(n_dofs_per_cell);
175
176 switch (status) {
177 case dealii::CellStatus::cell_will_persist:
178 [[fallthrough]];
179 case dealii::CellStatus::cell_will_be_refined: {
180 /*
181 * For both cases we need state values from the currently
182 * active cell:
183 */
184
185 Assert(dof_cell->is_active(), dealii::ExcInternalError());
186 std::vector<dealii::types::global_dof_index> dof_indices(
187 n_dofs_per_cell);
188 dof_cell->get_dof_indices(dof_indices);
189
190 std::transform(std::begin(dof_indices),
191 std::end(dof_indices),
192 std::begin(state_values),
193 [&](const auto i) {
194 const auto U_i = get_tensor(U, i);
195 return U_i;
196 });
197 } break;
198
199 case dealii::CellStatus::children_will_be_coarsened: {
200 /*
201 * We need to project values from the active child cells up to
202 * the present parent cell that will become active after
203 * coarsening.
204 */
205
206 Assert(dof_cell->has_children(), dealii::ExcInternalError());
207
208 const auto &discretization = offline_data_->discretization();
209 const auto &finite_element = discretization.finite_element();
210 const auto &mapping = discretization.mapping();
211 const auto &quadrature = discretization.quadrature();
212
213 dealii::FEValues<dim> fe_values(
214 mapping,
215 finite_element,
216 quadrature,
217 dealii::update_values | dealii::update_JxW_values |
218 dealii::update_quadrature_points);
219
220 const auto polynomial_space =
221 dealii::internal::FEPointEvaluation::get_polynomial_space(
222 finite_element);
223
224 std::vector<dealii::Point<dim, Number>> unit_points(
225 quadrature.size());
226 /*
227 * for Number == float we need a temporary vector for the
228 * transform_points_real_to_unit_cell() function:
229 */
230 std::vector<dealii::Point<dim>> unit_points_temp(
231 std::is_same_v<Number, float> ? quadrature.size() : 0);
232
233 /* Step 1: build up right hand side by iterating over children: */
234
235 std::vector<state_type> state_values_quad(quadrature.size());
236 std::vector<state_type> local_rhs(n_dofs_per_cell);
237
238 std::vector<dealii::types::global_dof_index> dof_indices(
239 n_dofs_per_cell);
240
241 for (unsigned int child = 0; child < dof_cell->n_children();
242 ++child) {
243 const auto child_cell = dof_cell->child(child);
244 Assert(child_cell->is_active(), dealii::ExcInternalError());
245
246 fe_values.reinit(child_cell);
247
248 if constexpr (std::is_same_v<Number, float>) {
249 mapping.transform_points_real_to_unit_cell(
250 dof_cell,
251 fe_values.get_quadrature_points(),
252 unit_points_temp);
253 std::transform(std::begin(unit_points_temp),
254 std::end(unit_points_temp),
255 std::begin(unit_points),
256 [](const auto &x) { return x; });
257 } else {
258 mapping.transform_points_real_to_unit_cell(
259 dof_cell, fe_values.get_quadrature_points(), unit_points);
260 }
261
262 child_cell->get_dof_indices(dof_indices);
263
264 for (auto &it : state_values_quad)
265 it = state_type{};
266
267 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
268 const auto U_i = get_tensor(U, dof_indices[i]);
269 for (unsigned int q = 0; q < quadrature.size(); ++q) {
270 state_values_quad[q] += U_i * fe_values.shape_value(i, q);
271 }
272 }
273
274 for (unsigned int q = 0; q < quadrature.size(); ++q)
275 state_values_quad[q] *= fe_values.JxW(q);
276
277 for (unsigned int q = 0; q < quadrature.size(); ++q) {
278 const unsigned int n_shapes = polynomial_space.size();
279 AssertIndexRange(n_shapes, 10);
280 dealii::ndarray<Number, 10, 2, dim> shapes;
281 // Evaluate 1d polynomials and their derivatives
282 std::array<Number, dim> point;
283 for (unsigned int d = 0; d < dim; ++d)
284 point[d] = unit_points[q][d];
285 for (unsigned int i = 0; i < n_shapes; ++i)
286 polynomial_space[i].values_of_array(point, 1, &shapes[i][0]);
287
288 Assert(finite_element.degree == 1, dealii::ExcNotImplemented());
289
291 /*is linear*/ true,
292 dim,
293 Number,
294 state_type>(shapes.data(),
295 n_shapes,
296 state_values_quad[q],
297 local_rhs.data(),
298 unit_points[q],
299 true);
300 }
301 }
302
303 /* Step 2: solve with inverse mass matrix on coarse cell: */
304
305 fe_values.reinit(dof_cell);
306
307 dealii::FullMatrix<double> mij(n_dofs_per_cell, n_dofs_per_cell);
308 dealii::Vector<double> mi(n_dofs_per_cell);
309 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
310 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
311 double sum = 0;
312 for (unsigned int q = 0; q < quadrature.size(); ++q)
313 sum += fe_values.shape_value(i, q) *
314 fe_values.shape_value(j, q) * fe_values.JxW(q);
315 mij(i, j) = sum;
316 mi(i) += sum;
317 }
318 }
319
320 mij.gauss_jordan();
321
322 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
323 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
324 state_values[i] += mij(i, j) * local_rhs[j];
325 }
326 }
327 } break;
328
329 case dealii::CellStatus::cell_invalid:
330 Assert(false, dealii::ExcInternalError());
331 __builtin_trap();
332 break;
333 }
334
335 return pack_state_values(state_values);
336 },
337 /* returns_variable_size_data =*/false);
338#endif
339 }
340
341
342 template <typename Description, int dim, typename Number>
344 StateVector &new_state_vector [[maybe_unused]])
345 {
346#ifdef DEBUG_OUTPUT
347 std::cout << "SolutionTransfer<Description, dim, Number>::project()"
348 << std::endl;
349#endif
350
351#if !DEAL_II_VERSION_GTE(9, 6, 0)
352 AssertThrow(
353 false,
354 dealii::ExcMessage(
355 "The SolutionTransfer class needs deal.II version 9.6.0 or newer"));
356
357#else
358
359 AssertThrow(have_distributed_triangulation<dim>,
360 dealii::ExcMessage(
361 "The SolutionTransfer class is not implemented for a "
362 "distributed::shared::Triangulation which we use in 1D"));
363
364 const auto &scalar_partitioner = offline_data_->scalar_partitioner();
365 const auto &affine_constraints = offline_data_->affine_constraints();
366
367 const auto &discretization = offline_data_->discretization();
368 auto &triangulation = *discretization.triangulation_; /* writable */
369
370 Assert(
371 handle_ != dealii::numbers::invalid_unsigned_int,
372 dealii::ExcMessage(
373 "Cannot project() a state vector without valid handle. "
374 "prepare_projection() or set_handle() have to be called first."));
375
376 /*
377 * Reconstruct and project state vector:
378 */
379
380 ScalarVector projected_mass;
381 projected_mass.reinit(offline_data_->scalar_partitioner());
382 HyperbolicVector projected_state;
383 projected_state.reinit(offline_data_->hyperbolic_vector_partitioner());
384
385 triangulation.notify_ready_to_unpack( //
386 handle_,
387 [this, &projected_mass, &projected_state](
388 const auto &cell,
389 const dealii::CellStatus status,
390 const auto &data_range) {
391 const auto &dof_handler = offline_data_->dof_handler();
392 const auto dof_cell = typename dealii::DoFHandler<dim>::cell_iterator(
393 &cell->get_triangulation(),
394 cell->level(),
395 cell->index(),
396 &dof_handler);
397
398 /*
399 * Retrieve packed values and project onto cell:
400 */
401
402 const auto n_dofs_per_cell = dof_handler.get_fe().n_dofs_per_cell();
403 std::vector<dealii::types::global_dof_index> dof_indices(
404 n_dofs_per_cell);
405
406 const auto state_values = unpack_state_values<state_type>(data_range);
407
408 switch (status) {
409 case dealii::CellStatus::cell_will_persist:
410 [[fallthrough]];
411 case dealii::CellStatus::children_will_be_coarsened: {
412 /*
413 * For both cases we distribute stored state_values to the
414 * projected_state and projected_mass vectors.
415 */
416
417 Assert(dof_cell->is_active(), dealii::ExcInternalError());
418 dof_cell->get_dof_indices(dof_indices);
419
420 const auto &discretization = offline_data_->discretization();
421 const auto &finite_element = discretization.finite_element();
422 const auto &mapping = discretization.mapping();
423 const auto &quadrature = discretization.quadrature();
424
425 dealii::FEValues<dim> fe_values(mapping,
426 finite_element,
427 quadrature,
428 dealii::update_values |
429 dealii::update_JxW_values);
430
431 fe_values.reinit(dof_cell);
432
433 dealii::Vector<double> mi(n_dofs_per_cell);
434 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
435 double sum = 0;
436 for (unsigned int q = 0; q < quadrature.size(); ++q)
437 sum += fe_values.shape_value(i, q) * fe_values.JxW(q);
438 mi(i) += sum;
439 }
440
441 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
442 const auto global_i = dof_indices[i];
443 add_tensor(projected_state, mi(i) * state_values[i], global_i);
444 projected_mass(global_i) += mi(i);
445 }
446
447 } break;
448
449 case dealii::CellStatus::cell_will_be_refined: {
450 /*
451 * We are on a (non active) cell that has been refined. Project
452 * onto the children and do a local mass projection there:
453 */
454
455 Assert(dof_cell->has_children(), dealii::ExcInternalError());
456
457 const auto &discretization = offline_data_->discretization();
458 const auto &finite_element = discretization.finite_element();
459 const auto &mapping = discretization.mapping();
460 const auto &quadrature = discretization.quadrature();
461
462 dealii::FEValues<dim> fe_values(
463 mapping,
464 finite_element,
465 quadrature,
466 dealii::update_values | dealii::update_JxW_values |
467 dealii::update_quadrature_points);
468
469 const auto polynomial_space =
470 dealii::internal::FEPointEvaluation::get_polynomial_space(
471 finite_element);
472 std::vector<dealii::Point<dim, Number>> unit_points(
473 quadrature.size());
474 /*
475 * for Number == float we need a temporary vector for the
476 * transform_points_real_to_unit_cell() function:
477 */
478 std::vector<dealii::Point<dim>> unit_points_temp(
479 std::is_same_v<Number, float> ? quadrature.size() : 0);
480
481 dealii::FullMatrix<double> mij(n_dofs_per_cell, n_dofs_per_cell);
482 dealii::Vector<double> mi(n_dofs_per_cell);
483 std::vector<state_type> local_rhs(n_dofs_per_cell);
484
485 for (unsigned int child = 0; child < dof_cell->n_children();
486 ++child) {
487 const auto child_cell = dof_cell->child(child);
488
489 Assert(child_cell->is_active(), dealii::ExcInternalError());
490 child_cell->get_dof_indices(dof_indices);
491
492 /* Step 1: build up right hand side on child cell: */
493
494 fe_values.reinit(child_cell);
495
496 if constexpr (std::is_same_v<Number, float>) {
497 mapping.transform_points_real_to_unit_cell(
498 dof_cell,
499 fe_values.get_quadrature_points(),
500 unit_points_temp);
501 std::transform(std::begin(unit_points_temp),
502 std::end(unit_points_temp),
503 std::begin(unit_points),
504 [](const auto &x) { return x; });
505 } else {
506 mapping.transform_points_real_to_unit_cell(
507 dof_cell, fe_values.get_quadrature_points(), unit_points);
508 }
509
510 for (auto &it : local_rhs)
511 it = state_type{};
512
513 for (unsigned int q = 0; q < quadrature.size(); ++q) {
514 Assert(finite_element.degree == 1, dealii::ExcNotImplemented());
515 auto coefficient =
516 dealii::internal::evaluate_tensor_product_value(
517 polynomial_space,
518 make_const_array_view(state_values),
519 unit_points[q],
520 /*is linear*/ true);
521 coefficient *= fe_values.JxW(q);
522
523 for (unsigned int i = 0; i < n_dofs_per_cell; ++i)
524 local_rhs[i] += coefficient * fe_values.shape_value(i, q);
525 }
526
527 /* Step 2: solve with inverse mass matrix on child cell: */
528
529 mi = 0.;
530 mij = 0.;
531 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
532 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
533 double sum = 0;
534 for (unsigned int q = 0; q < quadrature.size(); ++q)
535 sum += fe_values.shape_value(i, q) *
536 fe_values.shape_value(j, q) * fe_values.JxW(q);
537 mij(i, j) = sum;
538 mi(i) += sum;
539 }
540 }
541
542 mij.gauss_jordan();
543
544 for (unsigned int i = 0; i < n_dofs_per_cell; ++i) {
545 state_type U_i;
546 for (unsigned int j = 0; j < n_dofs_per_cell; ++j) {
547 U_i += mij(i, j) * local_rhs[j];
548 }
549
550 const auto global_i = dof_indices[i];
551 add_tensor(projected_state, mi(i) * U_i, global_i);
552 projected_mass(global_i) += mi(i);
553 }
554 } /*child*/
555 } break;
556
557 case dealii::CellStatus::cell_invalid:
558 Assert(false, dealii::ExcInternalError());
559 __builtin_trap();
560 break;
561 }
562 });
563
564 /*
565 * Distribute values, take the weighted average of unconstrained
566 * degrees of freedom, and store the result in new_U:
567 */
568
569 auto &new_U = std::get<0>(new_state_vector);
570 const auto n_locally_owned = offline_data_->n_locally_owned();
571
572 // We have to perform the following operation twice, so let's create a
573 // small lambda for it.
574 const auto update_new_state_vector = [&]() {
575 projected_mass.compress(dealii::VectorOperation::add);
576 projected_state.compress(dealii::VectorOperation::add);
577
578 for (unsigned int local_i = 0; local_i < n_locally_owned; ++local_i) {
579 const auto global_i = scalar_partitioner->local_to_global(local_i);
580 if (affine_constraints.is_constrained(global_i))
581 continue;
582
583 const auto U_i = projected_state.get_tensor(local_i);
584 const auto m_i = projected_mass.local_element(local_i);
585 new_U.write_tensor(U_i / m_i, local_i);
586 }
587 new_U.update_ghost_values();
588 };
589
590 update_new_state_vector();
591
592 /*
593 * Now redistribute the mass defect introduced by constrained degrees
594 * of freedom. This mostly affects hanging nodes neighboring a
595 * coarsened cell. Here, cell-wise mass projection might lead to a
596 * value that differs from the algebraic relationship expressed by our
597 * affine constraints. Thus, we first compute the defect and then we
598 * redistribute it to all degrees of freedom on the constraint line.
599 */
600
601 for (const auto &line : affine_constraints.get_lines()) {
602 const auto global_i = line.index;
603 const auto local_i = scalar_partitioner->global_to_local(global_i);
604
605 /* Only operate on a locally owned, constrained degree of freedom: */
606 if (local_i >= n_locally_owned)
607 continue;
608
609 /* The result of the mass projection: */
610 const auto m_i_star = projected_mass.local_element(local_i);
611 const auto U_i_star = projected_state.get_tensor(local_i) / m_i_star;
612
613 /* The value obtained from the affine constraints object: */
614 state_type U_i_interp;
615 for (const auto &[global_k, c_k] : line.entries) {
616 const auto local_k = scalar_partitioner->global_to_local(global_k);
617 U_i_interp += c_k * new_U.get_tensor(local_k);
618 }
619
620 /* And distribute the defect: */
621 const auto defect = U_i_star - U_i_interp;
622 for (const auto &[global_k, c_k] : line.entries) {
623 const auto local_k = scalar_partitioner->global_to_local(global_k);
624 const auto U_j = new_U.get_tensor(local_k);
625
626 projected_state.add_tensor(c_k * m_i_star * (U_j + defect), local_k);
627 projected_mass.local_element(local_k) += c_k * m_i_star;
628 }
629 }
630
631 update_new_state_vector();
632
633#ifdef DEBUG
634 /*
635 * Sanity check: Final masses must agree:
636 */
637 const auto &lumped_mass_matrix = offline_data_->lumped_mass_matrix();
638 for (unsigned int local_i = 0; local_i < n_locally_owned; ++local_i) {
639 const auto global_i = scalar_partitioner->local_to_global(local_i);
640 if (affine_constraints.is_constrained(global_i))
641 continue;
642
643 const auto m_i = projected_mass.local_element(local_i);
644 const auto m_i_reference = lumped_mass_matrix.local_element(local_i);
645 Assert(std::abs(m_i - m_i_reference) < 1.e-10,
646 dealii::ExcMessage(
647 "SolutionTransfer::projection(): something went wrong. Final "
648 "masses do not agree with those computed in OfflineData."));
649 }
650#endif
651#endif
652 }
653} // namespace ryujin
typename View::HyperbolicVector HyperbolicVector
Vectors::ScalarVector< Number > ScalarVector
typename Description::ParabolicSystem ParabolicSystem
SolutionTransfer(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system)
void prepare_projection(const StateVector &old_state_vector)
typename Description::HyperbolicSystem HyperbolicSystem
void project(StateVector &new_state_vector)
typename View::StateVector StateVector
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)