ryujin 2.1.1 revision ee5cbcbf2346c1299c942d0e1f13b46449973c18
Loading...
Searching...
No Matches
parabolic_module.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include "laplace_operator.h"
9#include "parabolic_module.h"
10
11#include <computing_timer.h>
12#include <convenience_macros.h>
13#include <loop.h>
14#include <simd.h>
15
16#include <deal.II/dofs/dof_tools.h>
17#include <deal.II/lac/linear_operator.h>
18#include <deal.II/lac/precondition.h>
19#include <deal.II/lac/solver_cg.h>
20#include <deal.II/matrix_free/fe_evaluation.h>
21#include <deal.II/numerics/vector_tools.h>
22#include <deal.II/numerics/vector_tools.templates.h>
23
24
25namespace ryujin
26{
27 namespace EulerPoisson
28 {
29 using namespace dealii;
30
31 template <typename Description, int dim, typename Number>
33 const MPIEnsemble &mpi_ensemble,
34 const OfflineData<dim, Number> &offline_data,
35 const HyperbolicSystem &hyperbolic_system,
36 const ParabolicSystem &parabolic_system,
37 const InitialValues<Description, dim, Number> &initial_values,
38 const std::string &subsection)
39 : ParameterAcceptor(subsection)
40 , mpi_ensemble_(mpi_ensemble)
41 , hyperbolic_system_(&hyperbolic_system)
42 , parabolic_system_(&parabolic_system)
43 , offline_data_(&offline_data)
44 , initial_values_(&initial_values)
45 , id_violation_strategy_(IDViolationStrategy::warn)
46 , cycle_(0)
47 , n_iterations_gauss_(0)
48 , n_iterations_step_(0)
49 , n_restarts_(0)
50 , n_corrections_(0)
51 , n_warnings_(0)
52 , potential_initialized_(false)
53 , t_background_density_(std::numeric_limits<Number>::lowest())
54 , t_magnetic_field_(std::numeric_limits<Number>::lowest())
55 {
56 gauss_law_restart_strategy_ = GaussLawRestartStrategy::no_restart;
57 add_parameter("gauss law restart strategy",
58 gauss_law_restart_strategy_,
59 "Strategy used when restarting the gauss law. Options are "
60 "\'no restart\', \'full restart\', \'correction\', "
61 "\'static no restart\', and \'static full restart\'.");
62
63 gmg_max_iter_ = 15;
64 add_parameter("multigrid - max iter",
65 gmg_max_iter_,
66 "Maximal number of CG iterations with GMG smoother");
67
68 gmg_smoother_range_ = 8.;
69 add_parameter("multigrid - chebyshev range",
70 gmg_smoother_range_,
71 "Chebyshev smoother: eigenvalue range parameter");
72
73 gmg_smoother_max_eig_ = 2.0;
74 add_parameter("multigrid - chebyshev max eig",
75 gmg_smoother_max_eig_,
76 "Chebyshev smoother: maximal eigenvalue");
77
78 gmg_smoother_degree_ = 3;
79 add_parameter("multigrid - chebyshev degree",
80 gmg_smoother_degree_,
81 "Chebyshev smoother: degree");
82
83 gmg_smoother_n_cg_iter_ = 10;
84 add_parameter(
85 "multigrid - chebyshev cg iter",
86 gmg_smoother_n_cg_iter_,
87 "Chebyshev smoother: number of CG iterations to approximate "
88 "eigenvalue");
89
90 gmg_min_level_ = 0;
91 add_parameter(
92 "multigrid - min level",
93 gmg_min_level_,
94 "Minimal mesh level to be visited in the geometric multigrid "
95 "cycle where the coarse grid solver (Chebyshev) is called");
96
97 tolerance_ = Number(1.0e-12);
98 add_parameter("tolerance", tolerance_, "Tolerance for linear solvers");
99
100 tolerance_linfty_norm_ = false;
101 add_parameter("tolerance linfty norm",
102 tolerance_linfty_norm_,
103 "Use the l_infty norm instead of the l_2 norm for the "
104 "stopping criterion");
105
106 ElectrostaticConfigurationLibrary::
107 populate_electrostatic_configuration_list<dim, Number>(
108 electrostatic_configuration_list_,
109 parabolic_system_->subsection());
110
111 const auto populate = [this]() {
112 bool initialized = false;
113 for (auto &it : electrostatic_configuration_list_)
114
115 if (it->name() == parabolic_system_->electrostatic_configuration()) {
116 selected_electrostatic_configuration_ = it;
117 initialized = true;
118 break;
119 }
120
121 AssertThrow(initialized,
122 dealii::ExcMessage(
123 "Could not find an electrostatic configuration "
124 "description with name \"" +
125 parabolic_system_->electrostatic_configuration() +
126 "\""));
127 };
128
129 ParameterAcceptor::parse_parameters_call_back.connect(populate);
130 populate();
131 }
132
133
134 template <typename Description, int dim, typename Number>
136 {
137#ifdef DEBUG_OUTPUT
138 std::cout << "ParabolicModule<dim, Number>::prepare()" << std::endl;
139#endif
140 /*
141 * The cycle_ variabe is only used for gmg reinitialization, simply
142 * reset it to zero on prepare().
143 */
144 cycle_ = 0;
145
146 const auto &discretization = offline_data_->discretization();
147 AssertThrow(discretization.ansatz() == Ansatz::dg_q1 ||
148 discretization.ansatz() == Ansatz::cg_q1,
149 dealii::ExcMessage("The Euler-Poisson module currently only "
150 "supports cG/dg Q1 finite elements."));
151
152 AssertThrow(!offline_data_->dof_handler().has_hp_capabilities(),
153 dealii::ExcMessage(
154 "The Euler-Poisson module currently does not support "
155 "DoFHandlers set up with hp capabilities."));
156
157 potential_initialized_ = false;
158
159 /*
160 * (Re)initialize matrix free object:
161 */
162
163 typename MatrixFree<dim, Number>::AdditionalData additional_data;
164 additional_data.tasks_parallel_scheme =
165 MatrixFree<dim, Number>::AdditionalData::none;
166
167 // First index CG, second index hyperbolic ansatz
168 std::vector<const dealii::DoFHandler<dim> *> dof_handlers = {
169 &offline_data_->dof_handler_cg(), &offline_data_->dof_handler()};
170
171 create_constraints();
172 std::vector<const dealii::AffineConstraints<Number> *>
173 affine_constraints = {&affine_constraints_potential_,
174 &offline_data_->affine_constraints()};
175
176 // First index full quadrature, second index lumped quadrature
177 std::vector<dealii::Quadrature<1>> quadratures = {
178 discretization.quadrature_1d()[0],
179 discretization.nodal_quadrature_1d()[0]};
180
181 matrix_free_.reinit(discretization.mapping(),
182 dof_handlers,
183 affine_constraints,
184 quadratures,
185 additional_data);
186
187 /*
188 * (Re)initialize operators and preconditioners:
189 */
190
191 laplace_operator_.initialize(matrix_free_);
192 laplace_operator_.compute_diagonal(diagonal_preconditioner_);
193 update_operator_.initialize(matrix_free_, density_, magnetic_field_);
194
195 typename decltype(multigrid_preconditioner_)::MultigridParameters
196 parameters{gmg_max_iter_,
197 gmg_smoother_range_,
198 gmg_smoother_max_eig_,
199 gmg_smoother_degree_,
200 gmg_smoother_n_cg_iter_,
201 gmg_min_level_,
202 tolerance_};
203
204 multigrid_preconditioner_.initialize(
205 *offline_data_,
206 selected_electrostatic_configuration_->dirichlet_boundaries(),
207 parameters);
208
209 /*
210 * (Re)initialize auxiliary vectors:
211 */
212
213 const auto &potential_partitioner =
214 matrix_free_.get_dof_info(0).vector_partitioner;
215 potential_rhs_.reinit(potential_partitioner);
216
217 const auto &scalar_partitioner =
218 matrix_free_.get_dof_info(1).vector_partitioner;
219 density_.reinit(scalar_partitioner);
220 background_density_.reinit(scalar_partitioner);
221
222 magnetic_field_.reinit(dim == 2 ? 1 : dim);
223 for (unsigned int i = 0; i < magnetic_field_.n_blocks(); ++i)
224 magnetic_field_.block(i).reinit(scalar_partitioner);
225
226 velocity_rhs_.reinit(dim);
227 for (unsigned int i = 0; i < dim; ++i)
228 velocity_rhs_.block(i).reinit(scalar_partitioner);
229
230 /*
231 * Populate background fields:
232 */
233
234 if (!selected_electrostatic_configuration_->is_time_dependent()) {
235 update_background_density(Number(0.));
236 update_magnetic_field(Number(0.));
237 }
238 }
239
240
241 template <typename Description, int dim, typename Number>
243 StateVector &state_vector) const
244 {
245#ifdef DEBUG_OUTPUT
246 std::cout << "ParabolicModule<dim, Number>::reinit_state_vector()"
247 << std::endl;
248#endif
249
250 auto &[U, precomputed, V] = state_vector;
251 V.reinit(1);
252
253 auto &potential = V.block(0);
254 const auto &partitioner = matrix_free_.get_dof_info(0).vector_partitioner;
255 potential.reinit(partitioner);
256 potential = 0.;
257 }
258
259
260 template <typename Description, int dim, typename Number>
262 StateVector &state_vector, Number t) const
263 {
264#ifdef DEBUG_OUTPUT
265 std::cout << "ParabolicModule<dim, Number>::prepare_state_vector()"
266 << std::endl;
267#endif
268
269 /*
270 * We (re)compute the potential on the first step and if the restart
271 * strategy is set to full_restart or static_full_restart.
272 */
273
274 AssertThrow(gauss_law_restart_strategy_ !=
276 dealii::ExcNotImplemented());
277
278 if (!potential_initialized_ ||
279 (gauss_law_restart_strategy_ ==
281 (gauss_law_restart_strategy_ ==
283
284 compute_potential(t, state_vector);
285
286 if (!potential_initialized_ &&
287 parabolic_system_->magnetic_drift_limit())
288 enforce_magnetic_drift_velocity(state_vector);
289 potential_initialized_ = true;
290 }
291 }
292
293
294 template <typename Description, int dim, typename Number>
295 template <int stages>
297 const StateVector &old_state_vector,
298 const Number old_t,
299 std::array<std::reference_wrapper<const StateVector>,
300 stages> /*stage_state_vectors*/,
301 const std::array<Number, stages> /*stage_weights*/,
302 StateVector &new_state_vector,
303 Number tau) const
304 {
305 step(old_state_vector,
306 old_t,
307 new_state_vector,
308 tau,
309 /*crank_nicolson_extrapolation = */ false);
310 }
311
312
313 template <typename Description, int dim, typename Number>
315 const StateVector &old_state_vector,
316 const Number old_t,
317 StateVector &new_state_vector,
318 Number tau) const
319 {
320 try {
321 /* Backward Euler step to half time step, and extrapolate: */
322
323 step(old_state_vector,
324 old_t,
325 new_state_vector,
326 tau / Number(2.),
327 /*crank_nicolson_extrapolation = */ true);
328
329 } catch (Correction) {
330
331 /*
332 * Under very rare circumstances we might fail to perform a Crank
333 * Nicolson step because the extrapolation step produced
334 * inadmissible states. We could correct the update now by
335 * performing a limiting step (either convex limiting, or flux
336 * corrected transport)... but *meh*, just perform a backward Euler
337 * step:
338 */
339 step(old_state_vector,
340 old_t,
341 new_state_vector,
342 tau,
343 /*crank_nicolson_extrapolation = */ false);
344 }
345 }
346
347
348 template <typename Description, int dim, typename Number>
350 std::ostream &output) const
351 {
352 output << " [ " << std::setprecision(2) << std::fixed //
353 << n_iterations_gauss_ << " GMG gauss -- " //
354 << n_iterations_step_ << " GMG step ]" << std::endl;
355 }
356
357
358 template <typename Description, int dim, typename Number>
360 {
361#ifdef DEBUG_OUTPUT
362 std::cout << "ParabolicModule<dim, Number>::create_constraints()"
363 << std::endl;
364#endif
365
366 const auto &discretization = offline_data_->discretization();
367 const auto &dof_handler = offline_data_->dof_handler_cg();
368
369 affine_constraints_potential_.clear();
370
371 const auto locally_relevant =
372 DoFTools::extract_locally_relevant_dofs(dof_handler);
373
374 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
375 affine_constraints_potential_.reinit(locally_owned, locally_relevant);
376
377 DoFTools::make_hanging_node_constraints(offline_data_->dof_handler_cg(),
378 affine_constraints_potential_);
379
380 /*
381 * Enforce periodic boundary conditions. We assume that the mesh is in
382 * "normal configuration."
383 */
384
385 const auto &periodic_faces =
386 discretization.triangulation().get_periodic_face_map();
387
388 for (const auto &[left, value] : periodic_faces) {
389 const auto &[right, orientation] = value;
390
391 typename DoFHandler<dim>::cell_iterator dof_cell_left(
392 &left.first->get_triangulation(),
393 left.first->level(),
394 left.first->index(),
395 &dof_handler);
396
397 typename DoFHandler<dim>::cell_iterator dof_cell_right(
398 &right.first->get_triangulation(),
399 right.first->level(),
400 right.first->index(),
401 &dof_handler);
402
403 if constexpr (std::is_same_v<Number, double>) {
404 DoFTools::make_periodicity_constraints(
405 dof_cell_left->face(left.second),
406 dof_cell_right->face(right.second),
407 affine_constraints_potential_,
408 ComponentMask(),
409 orientation);
410 } else {
411 AssertThrow(false, dealii::ExcNotImplemented());
412 __builtin_trap();
413 }
414 }
415
416 for (const auto &it :
417 selected_electrostatic_configuration_->dirichlet_boundaries())
418 DoFTools::make_zero_boundary_constraints(
419 offline_data_->dof_handler_cg(), it, affine_constraints_potential_);
420
421 affine_constraints_potential_.close();
422 }
423
424
425 template <typename Description, int dim, typename Number>
426 void ParabolicModule<Description, dim, Number>::update_background_density(
427 const Number t) const
428 {
429#ifdef DEBUG_OUTPUT
430 std::cout << "ParabolicModule<dim, Number>::update_background_density()"
431 << std::endl;
432#endif
433
434 /*
435 * Skip updating the background density if t > 0 and if the fields
436 * are time independent:
437 */
438 if (!selected_electrostatic_configuration_->is_time_dependent() &&
439 (t > Number(0.)))
440 return;
441
442 /*
443 * Skip updating if we have already populated the background density
444 * for the chosen time t.
445 */
446 if (std::abs(t_background_density_ - t) < 1.e-12)
447 return;
448
449#ifdef DEBUG_OUTPUT
450 std::cout << " updating to t = " << t << std::endl;
451#endif
452
453 ComputingTimer::Scope scope("time step [X] - interpolate data vectors");
454
455 const auto &discretization = offline_data_->discretization();
456 background_density_.zero_out_ghost_values();
457 dealii::VectorTools::interpolate(
458 discretization.mapping(),
459 offline_data_->dof_handler(),
460 dealii::ScalarFunctionFromFunctionObject<dim, Number>(
461 [&](const dealii::Point<dim> &p) {
462 return selected_electrostatic_configuration_
463 ->background_density(p, t);
464 }),
465 background_density_);
466 background_density_.update_ghost_values();
467
468 t_background_density_ = t;
469 }
470
471
472 template <typename Description, int dim, typename Number>
473 void ParabolicModule<Description, dim, Number>::update_magnetic_field(
474 const Number t) const
475 {
476#ifdef DEBUG_OUTPUT
477 std::cout << "ParabolicModule<dim, Number>::update_magnetic_field()"
478 << std::endl;
479#endif
480
481 /*
482 * Skip updating the background density if t > 0 and if the fields
483 * are time independent:
484 */
485 if (!selected_electrostatic_configuration_->is_time_dependent() &&
486 (t > Number(0.)))
487 return;
488
489 /*
490 * Skip updating if we have already populated the background density
491 * for the chosen time t.
492 */
493 if (std::abs(t_magnetic_field_ - t) < 1.e-12)
494 return;
495
496#ifdef DEBUG_OUTPUT
497 std::cout << " updating to t = " << t << std::endl;
498#endif
499
500 ComputingTimer::Scope scope("time step [X] - interpolate data vectors");
501
502 const auto &discretization = offline_data_->discretization();
503 for (unsigned int k = 0; k < (dim == 2 ? 1 : dim); ++k) {
504 magnetic_field_.block(k).zero_out_ghost_values();
505 dealii::VectorTools::interpolate(
506 discretization.mapping(),
507 offline_data_->dof_handler(),
508 to_function<dim, Number>(
509 [&](const dealii::Point<dim> &p) {
510 return selected_electrostatic_configuration_->magnetic_field(
511 p, t);
512 },
513 k),
514 magnetic_field_.block(k));
515 }
516 magnetic_field_.update_ghost_values();
517
518 t_magnetic_field_ = t;
519 }
520
521
522 template <typename Description, int dim, typename Number>
523 void ParabolicModule<Description, dim, Number>::compute_potential(
524 const Number t, StateVector &state_vector) const
525 {
526#ifdef DEBUG_OUTPUT
527 std::cout << "ParabolicModule<dim, Number>::compute_potential()"
528 << std::endl;
529#endif
530 const auto U_view = std::get<0>(state_vector).view();
531 auto &V = std::get<2>(state_vector);
532 auto &potential = V.block(0);
533
534 const unsigned int n_owned = offline_data_->n_locally_owned();
535
536 constexpr unsigned int order_fe = 1;
537 constexpr unsigned int order_quad = 2;
538
539 /*
540 * -----------------------------------------------------------------------
541 * Step 1a: build right hand side for Gauss law
542 * -----------------------------------------------------------------------
543 */
544
545 ComputingTimer::Scope scope("time step [P] 1 - enforce Gauss law");
546
547 update_background_density(t);
548
549 const auto body_copy = [&](auto sentinel, unsigned int i) {
550 using T = decltype(sentinel);
551 const auto view = hyperbolic_system_->template view<dim, T>();
552 const auto U_i = U_view.template read_tensor<T>(i);
553 const auto rho_i = view.density(U_i);
554 write_entry<T>(density_, rho_i, i);
555 };
556
557 cpu_simd_loop<Number>(
558 "time_step_parabolic_1a", body_copy, 0, n_owned, n_owned);
559
560 density_.update_ghost_values();
561
562 const auto body_matrix_free = [this](const auto &data,
563 auto &dst,
564 const auto &src,
565 const auto range) {
566 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
567 fee_potential(data, /*CG*/ 0, /*lumped quadrature*/ 1);
568 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
569 fee_density(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
570 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
571 fee_background(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
572
573
574 const Number alpha = parabolic_system_->alpha();
575
576 for (unsigned int cell = range.first; cell < range.second; ++cell) {
577 fee_potential.reinit(cell);
578 fee_density.reinit(cell);
579 fee_background.reinit(cell);
580
581 fee_density.gather_evaluate(src, dealii::EvaluationFlags::values);
582 fee_background.gather_evaluate(background_density_,
583 dealii::EvaluationFlags::values);
584
585 for (unsigned int q = 0; q < fee_potential.n_q_points; ++q) {
586 const auto density_q = fee_density.get_value(q);
587 const auto background_q = fee_background.get_value(q);
588
589 const auto value = alpha * (density_q + background_q);
590 fee_potential.submit_value(value, q);
591 }
592 fee_potential.integrate_scatter(dealii::EvaluationFlags::values, dst);
593 }
594 };
595
596 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
597 body_matrix_free,
598 potential_rhs_,
599 density_,
600 /*zero destination*/ true);
601
602 /*
603 * -----------------------------------------------------------------------
604 * Step 1b: solve Poisson problem
605 * -----------------------------------------------------------------------
606 */
607
608 matrix_free_.get_affine_constraints(0).distribute(potential);
609 matrix_free_.get_affine_constraints(0).set_zero(potential_rhs_);
610
611 const auto tolerance =
612 (tolerance_linfty_norm_ ? potential_rhs_.linfty_norm()
613 : potential_rhs_.l2_norm()) *
614 tolerance_;
615
616 typename dealii::SolverCG<ScalarHostVector>::AdditionalData solver_data;
617
618 try {
619 SolverControl solver_control(gmg_max_iter_, tolerance);
620 dealii::SolverCG<ScalarHostVector> solver(solver_control, solver_data);
621 solver.solve(laplace_operator_,
622 potential,
623 potential_rhs_,
624 multigrid_preconditioner_);
625
626
627 if (potential_initialized_) {
628 /* update exponential moving average */
629 n_iterations_gauss_ =
630 0.9 * n_iterations_gauss_ + 0.1 * solver_control.last_step();
631 } else {
632 n_iterations_gauss_ = solver_control.last_step();
633 }
634
635 } catch (SolverControl::NoConvergence &) {
636 SolverControl solver_control(1000, tolerance);
637 dealii::SolverCG<ScalarHostVector> solver(solver_control, solver_data);
638
639 solver.solve(laplace_operator_,
640 potential,
641 potential_rhs_,
642 diagonal_preconditioner_);
643
644 if (potential_initialized_) {
645 /* update exponential moving average */
646 n_iterations_gauss_ *= 0.9;
647 n_iterations_gauss_ +=
648 0.1 * gmg_max_iter_ + 0.1 * solver_control.last_step();
649 } else {
650 n_iterations_gauss_ = gmg_max_iter_ + solver_control.last_step();
651 }
652
653 /* update exponential moving average, counting also GMG iterations */
654 }
655
656 matrix_free_.get_affine_constraints(0).distribute(potential);
657 }
658
659
660 template <typename Description, int dim, typename Number>
661 void
662 ParabolicModule<Description, dim, Number>::enforce_magnetic_drift_velocity(
663 StateVector &state_vector) const
664 {
665#ifdef DEBUG_OUTPUT
666 std::cout
667 << "ParabolicModule<dim, Number>::enforce_magnetic_drift_velocity()"
668 << std::endl;
669#endif
670
671 const auto U_view = std::get<0>(state_vector).view();
672 auto &V = std::get<2>(state_vector);
673 auto &potential = V.block(0);
674
675 const unsigned int n_owned = offline_data_->n_locally_owned();
676
677 const auto lumped_mass_matrix_inverse_view =
678 offline_data_->lumped_mass_matrix_inverse().view();
679
680 constexpr unsigned int order_fe = 1;
681 constexpr unsigned int order_quad = 2;
682
683 /*
684 * -----------------------------------------------------------------------
685 * Step 1c: enforce magnetic drift velocity
686 * -----------------------------------------------------------------------
687 */
688
689 update_magnetic_field(Number(0.));
690
691 /* Project gradient of potential into velocity space: */
692
693 const auto body_velocity =
694 [](const auto &data, auto &dst, const auto &src, const auto range) {
695 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
696 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
697 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
698 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
699
700 for (unsigned int cell = range.first; cell < range.second; ++cell) {
701 fee_pot.reinit(cell);
702 fee_vel.reinit(cell);
703
704 fee_pot.gather_evaluate(src, dealii::EvaluationFlags::gradients);
705 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
706 fee_vel.submit_value(fee_pot.get_gradient(q), q);
707 }
708 fee_vel.integrate_scatter(dealii::EvaluationFlags::values, dst);
709 }
710 };
711
712 matrix_free_.template cell_loop<BlockHostVector, ScalarHostVector>(
713 body_velocity,
714 velocity_rhs_,
715 potential,
716 /*zero destination*/ true);
717
718 const auto body = [&](auto sentinel, unsigned int i) {
719 using T = decltype(sentinel);
720 const auto view = hyperbolic_system_->template view<dim, T>();
721
722 const auto m_i_inv =
723 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
724
725 auto U_i = U_view.template read_tensor<T>(i);
726 const auto rho_i = view.density(U_i);
727 const auto m_i = view.momentum(U_i);
728 const auto v_i = m_i / rho_i;
729
730 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
731 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
732 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
733
734 dealii::Tensor<1, dim, T> grad_phi;
735 for (unsigned int d = 0; d < dim; ++d)
736 grad_phi[d] = m_i_inv * read_entry<T>(velocity_rhs_.block(d), i);
737
738 auto new_v_i = v_i;
739
740 if constexpr (dim == 2) {
741 new_v_i = -magnetic_field[0] * cross_product_2d(grad_phi) /
742 magnetic_field.norm_square();
743
744 } else if constexpr (dim == 3) {
745 new_v_i = -cross_product_3d(grad_phi, magnetic_field) /
746 magnetic_field.norm_square();
747 }
748
749 for (unsigned int d = 0; d < dim; ++d)
750 U_i[1 + d] = rho_i * new_v_i[d];
751
752 /* Update the total energy accordingly: */
753 if constexpr (view.have_energy_equation)
754 U_i[1 + dim] +=
755 Number(0.5) * rho_i * (new_v_i.norm_square() - v_i.norm_square());
756
757 U_view.template write_tensor<T>(U_i, i);
758 };
759
760 cpu_simd_loop<Number>(
761 "time_step_parabolic_1c", body, 0, n_owned, n_owned);
762 }
763
764
765 template <typename Description, int dim, typename Number>
766 void ParabolicModule<Description, dim, Number>::step(
767 const StateVector &old_state_vector,
768 const Number t,
769 StateVector &new_state_vector,
770 Number tau [[maybe_unused]],
771 const bool crank_nicolson_extrapolation [[maybe_unused]]) const
772 {
773#ifdef DEBUG_OUTPUT
774 std::cout << "ParabolicModule<dim, Number>::step()" << std::endl;
775 std::cout << " perform time-step with tau = " << tau << std::endl;
776 if (crank_nicolson_extrapolation)
777 std::cout << " and extrapolate to t + 2 * tau" << std::endl;
778#endif
779
780 const Number alpha = parabolic_system_->alpha();
781
782 const auto &old_U = std::get<0>(old_state_vector);
783 const auto old_U_view = old_U.view();
784 const auto &old_V = std::get<2>(old_state_vector);
785 const auto &old_potential = old_V.block(0);
786
787 auto &new_U = std::get<0>(new_state_vector);
788 auto &new_V = std::get<2>(new_state_vector);
789 auto &new_potential = new_V.block(0);
790
791 const unsigned int n_owned = offline_data_->n_locally_owned();
792
793 const auto lumped_mass_matrix_inverse_view =
794 offline_data_->lumped_mass_matrix_inverse().view();
795
796 constexpr unsigned int order_fe = 1;
797 constexpr unsigned int order_quad = 2;
798
799 /*
800 * Initialize the new potential with the old one:
801 */
802
803 new_potential = old_potential;
804
805 /*
806 * If the Gauss law restart strategy is "static full restart" or
807 * "static no restart", we skip updating the potential.
808 */
809 if ((gauss_law_restart_strategy_ !=
811 (gauss_law_restart_strategy_ !=
813
814 /*
815 * ---------------------------------------------------------------------
816 * Step 2a: build right hand side for potential update
817 *
818 * The right-hand side reads:
819 * (\nabla \varphi^n, \nabla \chi) +
820 * \tau \alpha \langle \rho^n B^{-1} v^n, \nabla \chi \rangle
821 *
822 * In case of a time-dependent background density, we add a term
823 * \theta \alpha \langle \rho_b^{n+1} - \rho_b^n, \chi \rangle to
824 * account for the time dependence. Here, t_{n+1} is the final time
825 * t_n + tau, or t_n + 2 * tau (in case of Crank Nicolson). This
826 * ensures that we are consistent with the Gauß law involution
827 * "-\Delta \varphi^{n+1} = \alpha \rho^{n+1}."
828 * ---------------------------------------------------------------------
829 */
830
831 ComputingTimer::Scope scope("time step [P] 2 - update potential");
832
833 /* Query the magnetic field at the time t + tau: */
834 update_magnetic_field(t + tau);
835
836 /*
837 * Write out density and assemble velocity part. We need density_
838 * to be set to the correct density for UpdateOperator::vmult()
839 */
840
841 const auto body_copy = [&](auto sentinel, unsigned int i) {
842 using T = decltype(sentinel);
843 const auto view = hyperbolic_system_->template view<dim, T>();
844
845 const auto U_i = old_U_view.template read_tensor<T>(i);
846 const auto rho_i = view.density(U_i);
847 const auto m_i = view.momentum(U_i);
848
849 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
850 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
851 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
852
853 const auto velocity_rhs =
854 tau * alpha * rho_i *
855 apply_B_n_inverse(magnetic_field, tau, m_i / rho_i);
856
857 write_entry<T>(density_, rho_i, i);
858 for (unsigned int d = 0; d < dim; ++d)
859 write_entry<T>(velocity_rhs_.block(d), velocity_rhs[d], i);
860 };
861
862 cpu_simd_loop<Number>(
863 "time_step_parabolic_2a", body_copy, 0, n_owned, n_owned);
864
865 density_.update_ghost_values();
866
867 /* Apply Laplace operator to right hand side: */
868
869 const auto body_laplace = [](const auto &data,
870 auto &dst,
871 const auto &src,
872 const auto range) {
873 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number> fee(
874 data, /*CG*/ 0, /*full quadrature*/ 0);
875
876 for (unsigned int cell = range.first; cell < range.second; ++cell) {
877 fee.reinit(cell);
878 fee.gather_evaluate(src, dealii::EvaluationFlags::gradients);
879
880 for (unsigned int q = 0; q < fee.n_q_points; ++q) {
881 const auto grad_potential = fee.get_gradient(q);
882 fee.submit_gradient(grad_potential, q);
883 }
884 fee.integrate_scatter(dealii::EvaluationFlags::gradients, dst);
885 }
886 };
887
888 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
889 body_laplace,
890 potential_rhs_,
891 old_potential,
892 /*zero destination*/ true);
893
894 /* Apply Velocity contribution to right hand side: */
895
896 const auto body_velocity = [](const auto &data,
897 auto &dst,
898 const auto &src,
899 const auto range) {
900 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
901 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
902 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
903 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
904
905 for (unsigned int cell = range.first; cell < range.second; ++cell) {
906 fee_pot.reinit(cell);
907 fee_vel.reinit(cell);
908
909 fee_vel.gather_evaluate(src, dealii::EvaluationFlags::values);
910
911 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
912 if constexpr (dim == 1) {
913 decltype(fee_pot.get_gradient(q)) velocity_rhs;
914 velocity_rhs[0] = fee_vel.get_value(q);
915 fee_pot.submit_gradient(velocity_rhs, q);
916 } else {
917 fee_pot.submit_gradient(fee_vel.get_value(q), q);
918 }
919 }
920 fee_pot.integrate_scatter(dealii::EvaluationFlags::gradients, dst);
921 }
922 };
923
924 matrix_free_.template cell_loop<ScalarHostVector, BlockHostVector>(
925 body_velocity,
926 potential_rhs_,
927 velocity_rhs_,
928 /*zero destination*/ false);
929
930 /* Time-dependent background density: */
931
932 if (selected_electrostatic_configuration_->is_time_dependent()) {
933
934 /*
935 * Subtract background density at time t_n:
936 */
937
938 update_background_density(t);
939
940 Number factor = (crank_nicolson_extrapolation ? -0.5 : -1.0) * alpha;
941
942 const auto body = [&factor](const auto &data,
943 auto &dst,
944 const auto &src,
945 const auto range) {
946 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
947 fee_potential(data, /*CG*/ 0, /*lumped quadrature*/ 1);
948 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
949 fee_background(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
950
951 for (unsigned int cell = range.first; cell < range.second; ++cell) {
952 fee_potential.reinit(cell);
953 fee_background.reinit(cell);
954 fee_background.gather_evaluate(src, EvaluationFlags::values);
955
956 for (unsigned int q = 0; q < fee_potential.n_q_points; ++q) {
957 const auto background_q = fee_background.get_value(q);
958 fee_potential.submit_value(factor * background_q, q);
959 }
960 fee_potential.integrate_scatter(EvaluationFlags::values, dst);
961 }
962 };
963
964 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
965 body,
966 potential_rhs_,
967 background_density_,
968 /*zero destination*/ false);
969
970 /*
971 * Add background density at time t_{n+1}:
972 */
973
974 update_background_density(
975 t + (crank_nicolson_extrapolation ? 2. : 1.) * tau);
976
977 factor *= -1.;
978
979 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
980 body,
981 potential_rhs_,
982 background_density_,
983 /*zero destination*/ false);
984 }
985
986 /*
987 * ---------------------------------------------------------------------
988 * Step 2b: solve modified poisson problem
989 * ---------------------------------------------------------------------
990 */
991
992 update_operator_.set_alpha(alpha);
993 update_operator_.set_theta_tau(tau);
994
995 matrix_free_.get_affine_constraints(0).distribute(new_potential);
996 matrix_free_.get_affine_constraints(0).set_zero(potential_rhs_);
997
998 const auto tolerance =
999 (tolerance_linfty_norm_ ? potential_rhs_.linfty_norm()
1000 : potential_rhs_.l2_norm()) *
1001 tolerance_;
1002
1003 typename dealii::SolverCG<ScalarHostVector>::AdditionalData solver_data;
1004
1005 try {
1006 SolverControl solver_control(gmg_max_iter_, tolerance);
1007 dealii::SolverCG<ScalarHostVector> solver(solver_control,
1008 solver_data);
1009 solver.solve(update_operator_,
1010 new_potential,
1011 potential_rhs_,
1012 multigrid_preconditioner_);
1013
1014 /* update exponential moving average */
1015 n_iterations_step_ =
1016 0.9 * n_iterations_step_ + 0.1 * solver_control.last_step();
1017
1018 } catch (SolverControl::NoConvergence &) {
1019 SolverControl solver_control(1000, tolerance);
1020 dealii::SolverCG<ScalarHostVector> solver(solver_control,
1021 solver_data);
1022
1023 solver.solve(update_operator_,
1024 new_potential,
1025 potential_rhs_,
1026 diagonal_preconditioner_);
1027
1028 /* update exponential moving average, counting also GMG iterations */
1029 n_iterations_step_ *= 0.9;
1030 n_iterations_step_ +=
1031 0.1 * gmg_max_iter_ + 0.1 * solver_control.last_step();
1032 }
1033
1034 matrix_free_.get_affine_constraints(0).distribute(new_potential);
1035 }
1036
1037 /*
1038 * ---------------------------------------------------------------------
1039 * Step 2c: update velocity vector field; Crank-Nicolson extrapolation
1040 * ---------------------------------------------------------------------
1041 */
1042
1043 /* Project gradient of potential into velocity space: */
1044
1045 const auto body_velocity =
1046 [](const auto &data, auto &dst, const auto &src, const auto range) {
1047 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
1048 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
1049 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
1050 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
1051
1052 for (unsigned int cell = range.first; cell < range.second; ++cell) {
1053 fee_pot.reinit(cell);
1054 fee_vel.reinit(cell);
1055
1056 fee_pot.gather_evaluate(src, dealii::EvaluationFlags::gradients);
1057 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
1058 fee_vel.submit_value(fee_pot.get_gradient(q), q);
1059 }
1060 fee_vel.integrate_scatter(dealii::EvaluationFlags::values, dst);
1061 }
1062 };
1063
1064 matrix_free_.template cell_loop<BlockHostVector, ScalarHostVector>(
1065 body_velocity,
1066 velocity_rhs_,
1067 new_potential,
1068 /*zero destination*/ true);
1069
1070 /*
1071 * Now that we have written out the gradients, copy over the old
1072 * state vector and perform the Crank-Nicolson extrapolation step on
1073 * the potential:
1074 */
1075
1076 new_U = old_U;
1077 const auto new_U_view = new_U.view();
1078
1079 if (crank_nicolson_extrapolation) {
1080 new_potential *= Number(2.);
1081 new_potential -= old_potential;
1082 }
1083
1084 /*
1085 * Update the momentum and total energy:
1086 */
1087
1088 const auto body = [&](auto sentinel, unsigned int i) {
1089 using T = decltype(sentinel);
1090 const auto view = hyperbolic_system_->template view<dim, T>();
1091
1092 const auto m_i_inv =
1093 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
1094
1095 const auto old_U_i = old_U_view.template read_tensor<T>(i);
1096 const auto rho_i = view.density(old_U_i);
1097 const auto old_m_i = view.momentum(old_U_i);
1098 const auto old_v_i = old_m_i / rho_i;
1099
1100 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
1101 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
1102 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
1103
1104 dealii::Tensor<1, dim, T> grad_phi;
1105 for (unsigned int d = 0; d < dim; ++d)
1106 grad_phi[d] = m_i_inv * read_entry<T>(velocity_rhs_.block(d), i);
1107
1108 auto new_v_i =
1109 apply_B_n_inverse(magnetic_field, tau, old_v_i - tau * grad_phi);
1110
1111 /* Perform an extrapolation step: */
1112 if (crank_nicolson_extrapolation)
1113 new_v_i = Number(2.) * new_v_i - old_v_i;
1114
1115 auto new_U_i = old_U_i;
1116 for (unsigned int d = 0; d < dim; ++d)
1117 new_U_i[1 + d] = rho_i * new_v_i[d];
1118
1119 /* Update the total energy accordingly: */
1120 if constexpr (view.have_energy_equation)
1121 new_U_i[1 + dim] += Number(0.5) * rho_i *
1122 (new_v_i.norm_square() - old_v_i.norm_square());
1123
1124 new_U_view.template write_tensor<T>(new_U_i, i);
1125 };
1126
1127 cpu_simd_loop<Number>(
1128 "time_step_parabolic_2c", body, 0, n_owned, n_owned);
1129 }
1130
1131 } // namespace EulerPoisson
1132} /* namespace ryujin */
typename Description::HyperbolicSystem HyperbolicSystem
typename Description::ParabolicSystem ParabolicSystem
typename View::StateVector StateVector
DEAL_II_ALWAYS_INLINE dealii::Tensor< 1, dim, Number > apply_B_n_inverse(const dealii::Tensor< 1,(dim==2 ? 1 :dim), Number > &magnetic_field, const Number2 &theta_tau, const dealii::Tensor< 1, dim, Number > &velocity)