ryujin 2.1.1 revision 71cdc42292164f8095c0bd62c4ea75ebcfac58fa
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.resize(1);
252
253 const auto &partitioner = matrix_free_.get_dof_info(0).vector_partitioner;
254 V[0].reinit_with_scalar_partitioner(partitioner);
255 V[0].deal_ii_vector() = 0.;
256 }
257
258
259 template <typename Description, int dim, typename Number>
261 StateVector &state_vector, Number t) const
262 {
263#ifdef DEBUG_OUTPUT
264 std::cout << "ParabolicModule<dim, Number>::prepare_state_vector()"
265 << std::endl;
266#endif
267
268 /*
269 * We (re)compute the potential on the first step and if the restart
270 * strategy is set to full_restart or static_full_restart.
271 */
272
273 AssertThrow(gauss_law_restart_strategy_ !=
275 dealii::ExcNotImplemented());
276
277 if (!potential_initialized_ ||
278 (gauss_law_restart_strategy_ ==
280 (gauss_law_restart_strategy_ ==
282
283 compute_potential(t, state_vector);
284
285 if (!potential_initialized_ &&
286 parabolic_system_->magnetic_drift_limit())
287 enforce_magnetic_drift_velocity(state_vector);
288 potential_initialized_ = true;
289 }
290 }
291
292
293 template <typename Description, int dim, typename Number>
294 template <int stages>
296 const StateVector &old_state_vector,
297 const Number old_t,
298 std::array<std::reference_wrapper<const StateVector>,
299 stages> /*stage_state_vectors*/,
300 const std::array<Number, stages> /*stage_weights*/,
301 StateVector &new_state_vector,
302 Number tau) const
303 {
304 step(old_state_vector,
305 old_t,
306 new_state_vector,
307 tau,
308 /*crank_nicolson_extrapolation = */ false);
309 }
310
311
312 template <typename Description, int dim, typename Number>
314 const StateVector &old_state_vector,
315 const Number old_t,
316 StateVector &new_state_vector,
317 Number tau) const
318 {
319 try {
320 /* Backward Euler step to half time step, and extrapolate: */
321
322 step(old_state_vector,
323 old_t,
324 new_state_vector,
325 tau / Number(2.),
326 /*crank_nicolson_extrapolation = */ true);
327
328 } catch (Correction) {
329
330 /*
331 * Under very rare circumstances we might fail to perform a Crank
332 * Nicolson step because the extrapolation step produced
333 * inadmissible states. We could correct the update now by
334 * performing a limiting step (either convex limiting, or flux
335 * corrected transport)... but *meh*, just perform a backward Euler
336 * step:
337 */
338 step(old_state_vector,
339 old_t,
340 new_state_vector,
341 tau,
342 /*crank_nicolson_extrapolation = */ false);
343 }
344 }
345
346
347 template <typename Description, int dim, typename Number>
349 std::ostream &output) const
350 {
351 output << " [ " << std::setprecision(2) << std::fixed //
352 << n_iterations_gauss_ << " GMG gauss -- " //
353 << n_iterations_step_ << " GMG step ]" << std::endl;
354 }
355
356
357 template <typename Description, int dim, typename Number>
359 {
360#ifdef DEBUG_OUTPUT
361 std::cout << "ParabolicModule<dim, Number>::create_constraints()"
362 << std::endl;
363#endif
364
365 const auto &discretization = offline_data_->discretization();
366 const auto &dof_handler = offline_data_->dof_handler_cg();
367
368 affine_constraints_potential_.clear();
369
370 const auto locally_relevant =
371 DoFTools::extract_locally_relevant_dofs(dof_handler);
372
373 const IndexSet &locally_owned = dof_handler.locally_owned_dofs();
374 affine_constraints_potential_.reinit(locally_owned, locally_relevant);
375
376 DoFTools::make_hanging_node_constraints(offline_data_->dof_handler_cg(),
377 affine_constraints_potential_);
378
379 /*
380 * Enforce periodic boundary conditions. We assume that the mesh is in
381 * "normal configuration."
382 */
383
384 const auto &periodic_faces =
385 discretization.triangulation().get_periodic_face_map();
386
387 for (const auto &[left, value] : periodic_faces) {
388 const auto &[right, orientation] = value;
389
390 typename DoFHandler<dim>::cell_iterator dof_cell_left(
391 &left.first->get_triangulation(),
392 left.first->level(),
393 left.first->index(),
394 &dof_handler);
395
396 typename DoFHandler<dim>::cell_iterator dof_cell_right(
397 &right.first->get_triangulation(),
398 right.first->level(),
399 right.first->index(),
400 &dof_handler);
401
402 if constexpr (std::is_same_v<Number, double>) {
403 DoFTools::make_periodicity_constraints(
404 dof_cell_left->face(left.second),
405 dof_cell_right->face(right.second),
406 affine_constraints_potential_,
407 ComponentMask(),
408 orientation);
409 } else {
410 AssertThrow(false, dealii::ExcNotImplemented());
411 __builtin_trap();
412 }
413 }
414
415 for (const auto &it :
416 selected_electrostatic_configuration_->dirichlet_boundaries())
417 DoFTools::make_zero_boundary_constraints(
418 offline_data_->dof_handler_cg(), it, affine_constraints_potential_);
419
420 affine_constraints_potential_.close();
421 }
422
423
424 template <typename Description, int dim, typename Number>
425 void ParabolicModule<Description, dim, Number>::update_background_density(
426 const Number t) const
427 {
428#ifdef DEBUG_OUTPUT
429 std::cout << "ParabolicModule<dim, Number>::update_background_density()"
430 << std::endl;
431#endif
432
433 /*
434 * Skip updating the background density if t > 0 and if the fields
435 * are time independent:
436 */
437 if (!selected_electrostatic_configuration_->is_time_dependent() &&
438 (t > Number(0.)))
439 return;
440
441 /*
442 * Skip updating if we have already populated the background density
443 * for the chosen time t.
444 */
445 if (std::abs(t_background_density_ - t) < 1.e-12)
446 return;
447
448#ifdef DEBUG_OUTPUT
449 std::cout << " updating to t = " << t << std::endl;
450#endif
451
452 ComputingTimer::Scope scope("time step [X] - interpolate data vectors");
453
454 const auto &discretization = offline_data_->discretization();
455 background_density_.zero_out_ghost_values();
456 dealii::VectorTools::interpolate(
457 discretization.mapping(),
458 offline_data_->dof_handler(),
459 dealii::ScalarFunctionFromFunctionObject<dim, Number>(
460 [&](const dealii::Point<dim> &p) {
461 return selected_electrostatic_configuration_
462 ->background_density(p, t);
463 }),
464 background_density_);
465 background_density_.update_ghost_values();
466
467 t_background_density_ = t;
468 }
469
470
471 template <typename Description, int dim, typename Number>
472 void ParabolicModule<Description, dim, Number>::update_magnetic_field(
473 const Number t) const
474 {
475#ifdef DEBUG_OUTPUT
476 std::cout << "ParabolicModule<dim, Number>::update_magnetic_field()"
477 << std::endl;
478#endif
479
480 /*
481 * Skip updating the background density if t > 0 and if the fields
482 * are time independent:
483 */
484 if (!selected_electrostatic_configuration_->is_time_dependent() &&
485 (t > Number(0.)))
486 return;
487
488 /*
489 * Skip updating if we have already populated the background density
490 * for the chosen time t.
491 */
492 if (std::abs(t_magnetic_field_ - t) < 1.e-12)
493 return;
494
495#ifdef DEBUG_OUTPUT
496 std::cout << " updating to t = " << t << std::endl;
497#endif
498
499 ComputingTimer::Scope scope("time step [X] - interpolate data vectors");
500
501 const auto &discretization = offline_data_->discretization();
502 for (unsigned int k = 0; k < (dim == 2 ? 1 : dim); ++k) {
503 magnetic_field_.block(k).zero_out_ghost_values();
504 dealii::VectorTools::interpolate(
505 discretization.mapping(),
506 offline_data_->dof_handler(),
507 to_function<dim, Number>(
508 [&](const dealii::Point<dim> &p) {
509 return selected_electrostatic_configuration_->magnetic_field(
510 p, t);
511 },
512 k),
513 magnetic_field_.block(k));
514 }
515 magnetic_field_.update_ghost_values();
516
517 t_magnetic_field_ = t;
518 }
519
520
521 template <typename Description, int dim, typename Number>
522 void ParabolicModule<Description, dim, Number>::compute_potential(
523 const Number t, StateVector &state_vector) const
524 {
525#ifdef DEBUG_OUTPUT
526 std::cout << "ParabolicModule<dim, Number>::compute_potential()"
527 << std::endl;
528#endif
529 const auto U_view = std::get<0>(state_vector).view();
530 auto &V = std::get<2>(state_vector);
531 auto &potential = V[0].deal_ii_vector();
532
533 const unsigned int n_owned = offline_data_->n_locally_owned();
534
535 constexpr unsigned int order_fe = 1;
536 constexpr unsigned int order_quad = 2;
537
538 /*
539 * -----------------------------------------------------------------------
540 * Step 1a: build right hand side for Gauss law
541 * -----------------------------------------------------------------------
542 */
543
544 ComputingTimer::Scope scope("time step [P] 1 - enforce Gauss law");
545
546 update_background_density(t);
547
548 const auto body_copy = [&](auto sentinel, unsigned int i) {
549 using T = decltype(sentinel);
550 const auto view = hyperbolic_system_->template view<dim, T>();
551 const auto U_i = U_view.template read_tensor<T>(i);
552 const auto rho_i = view.density(U_i);
553 write_entry<T>(density_, rho_i, i);
554 };
555
556 cpu_simd_loop<Number>(
557 "time_step_parabolic_1a", body_copy, 0, n_owned, n_owned);
558
559 density_.update_ghost_values();
560
561 const auto body_matrix_free = [this](const auto &data,
562 auto &dst,
563 const auto &src,
564 const auto range) {
565 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
566 fee_potential(data, /*CG*/ 0, /*lumped quadrature*/ 1);
567 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
568 fee_density(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
569 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
570 fee_background(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
571
572
573 const Number alpha = parabolic_system_->alpha();
574
575 for (unsigned int cell = range.first; cell < range.second; ++cell) {
576 fee_potential.reinit(cell);
577 fee_density.reinit(cell);
578 fee_background.reinit(cell);
579
580 fee_density.gather_evaluate(src, dealii::EvaluationFlags::values);
581 fee_background.gather_evaluate(background_density_,
582 dealii::EvaluationFlags::values);
583
584 for (unsigned int q = 0; q < fee_potential.n_q_points; ++q) {
585 const auto density_q = fee_density.get_value(q);
586 const auto background_q = fee_background.get_value(q);
587
588 const auto value = alpha * (density_q + background_q);
589 fee_potential.submit_value(value, q);
590 }
591 fee_potential.integrate_scatter(dealii::EvaluationFlags::values, dst);
592 }
593 };
594
595 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
596 body_matrix_free,
597 potential_rhs_,
598 density_,
599 /*zero destination*/ true);
600
601 /*
602 * -----------------------------------------------------------------------
603 * Step 1b: solve Poisson problem
604 * -----------------------------------------------------------------------
605 */
606
607 matrix_free_.get_affine_constraints(0).distribute(potential);
608 matrix_free_.get_affine_constraints(0).set_zero(potential_rhs_);
609
610 const auto tolerance =
611 (tolerance_linfty_norm_ ? potential_rhs_.linfty_norm()
612 : potential_rhs_.l2_norm()) *
613 tolerance_;
614
615 typename dealii::SolverCG<ScalarHostVector>::AdditionalData solver_data;
616
617 try {
618 SolverControl solver_control(gmg_max_iter_, tolerance);
619 dealii::SolverCG<ScalarHostVector> solver(solver_control, solver_data);
620 solver.solve(laplace_operator_,
621 potential,
622 potential_rhs_,
623 multigrid_preconditioner_);
624
625
626 if (potential_initialized_) {
627 /* update exponential moving average */
628 n_iterations_gauss_ =
629 0.9 * n_iterations_gauss_ + 0.1 * solver_control.last_step();
630 } else {
631 n_iterations_gauss_ = solver_control.last_step();
632 }
633
634 } catch (SolverControl::NoConvergence &) {
635 SolverControl solver_control(1000, tolerance);
636 dealii::SolverCG<ScalarHostVector> solver(solver_control, solver_data);
637
638 solver.solve(laplace_operator_,
639 potential,
640 potential_rhs_,
641 diagonal_preconditioner_);
642
643 if (potential_initialized_) {
644 /* update exponential moving average */
645 n_iterations_gauss_ *= 0.9;
646 n_iterations_gauss_ +=
647 0.1 * gmg_max_iter_ + 0.1 * solver_control.last_step();
648 } else {
649 n_iterations_gauss_ = gmg_max_iter_ + solver_control.last_step();
650 }
651
652 /* update exponential moving average, counting also GMG iterations */
653 }
654
655 matrix_free_.get_affine_constraints(0).distribute(potential);
656 }
657
658
659 template <typename Description, int dim, typename Number>
660 void
661 ParabolicModule<Description, dim, Number>::enforce_magnetic_drift_velocity(
662 StateVector &state_vector) const
663 {
664#ifdef DEBUG_OUTPUT
665 std::cout
666 << "ParabolicModule<dim, Number>::enforce_magnetic_drift_velocity()"
667 << std::endl;
668#endif
669
670 const auto U_view = std::get<0>(state_vector).view();
671 auto &V = std::get<2>(state_vector);
672 auto &potential = V[0].deal_ii_vector();
673
674 const unsigned int n_owned = offline_data_->n_locally_owned();
675
676 const auto lumped_mass_matrix_inverse_view =
677 offline_data_->lumped_mass_matrix_inverse().view();
678
679 constexpr unsigned int order_fe = 1;
680 constexpr unsigned int order_quad = 2;
681
682 /*
683 * -----------------------------------------------------------------------
684 * Step 1c: enforce magnetic drift velocity
685 * -----------------------------------------------------------------------
686 */
687
688 update_magnetic_field(Number(0.));
689
690 /* Project gradient of potential into velocity space: */
691
692 const auto body_velocity =
693 [](const auto &data, auto &dst, const auto &src, const auto range) {
694 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
695 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
696 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
697 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
698
699 for (unsigned int cell = range.first; cell < range.second; ++cell) {
700 fee_pot.reinit(cell);
701 fee_vel.reinit(cell);
702
703 fee_pot.gather_evaluate(src, dealii::EvaluationFlags::gradients);
704 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
705 fee_vel.submit_value(fee_pot.get_gradient(q), q);
706 }
707 fee_vel.integrate_scatter(dealii::EvaluationFlags::values, dst);
708 }
709 };
710
711 matrix_free_.template cell_loop<BlockHostVector, ScalarHostVector>(
712 body_velocity,
713 velocity_rhs_,
714 potential,
715 /*zero destination*/ true);
716
717 const auto body = [&](auto sentinel, unsigned int i) {
718 using T = decltype(sentinel);
719 const auto view = hyperbolic_system_->template view<dim, T>();
720
721 const auto m_i_inv =
722 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
723
724 auto U_i = U_view.template read_tensor<T>(i);
725 const auto rho_i = view.density(U_i);
726 const auto m_i = view.momentum(U_i);
727 const auto v_i = m_i / rho_i;
728
729 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
730 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
731 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
732
733 dealii::Tensor<1, dim, T> grad_phi;
734 for (unsigned int d = 0; d < dim; ++d)
735 grad_phi[d] = m_i_inv * read_entry<T>(velocity_rhs_.block(d), i);
736
737 auto new_v_i = v_i;
738
739 if constexpr (dim == 2) {
740 new_v_i = -magnetic_field[0] * cross_product_2d(grad_phi) /
741 magnetic_field.norm_square();
742
743 } else if constexpr (dim == 3) {
744 new_v_i = -cross_product_3d(grad_phi, magnetic_field) /
745 magnetic_field.norm_square();
746 }
747
748 for (unsigned int d = 0; d < dim; ++d)
749 U_i[1 + d] = rho_i * new_v_i[d];
750
751 /* Update the total energy accordingly: */
752 if constexpr (view.have_energy_equation)
753 U_i[1 + dim] +=
754 Number(0.5) * rho_i * (new_v_i.norm_square() - v_i.norm_square());
755
756 U_view.template write_tensor<T>(U_i, i);
757 };
758
759 cpu_simd_loop<Number>(
760 "time_step_parabolic_1c", body, 0, n_owned, n_owned);
761 }
762
763
764 template <typename Description, int dim, typename Number>
765 void ParabolicModule<Description, dim, Number>::step(
766 const StateVector &old_state_vector,
767 const Number t,
768 StateVector &new_state_vector,
769 Number tau [[maybe_unused]],
770 const bool crank_nicolson_extrapolation [[maybe_unused]]) const
771 {
772#ifdef DEBUG_OUTPUT
773 std::cout << "ParabolicModule<dim, Number>::step()" << std::endl;
774 std::cout << " perform time-step with tau = " << tau << std::endl;
775 if (crank_nicolson_extrapolation)
776 std::cout << " and extrapolate to t + 2 * tau" << std::endl;
777#endif
778
779 const Number alpha = parabolic_system_->alpha();
780
781 const auto &old_U = std::get<0>(old_state_vector);
782 const auto old_U_view = old_U.view();
783 const auto &old_V = std::get<2>(old_state_vector);
784 const auto &old_potential = old_V[0].deal_ii_vector();
785
786 auto &new_U = std::get<0>(new_state_vector);
787 auto &new_V = std::get<2>(new_state_vector);
788 auto &new_potential = new_V[0].deal_ii_vector();
789
790 const unsigned int n_owned = offline_data_->n_locally_owned();
791
792 const auto lumped_mass_matrix_inverse_view =
793 offline_data_->lumped_mass_matrix_inverse().view();
794
795 constexpr unsigned int order_fe = 1;
796 constexpr unsigned int order_quad = 2;
797
798 /*
799 * Initialize the new potential with the old one:
800 */
801
802 new_potential = old_potential;
803
804 /*
805 * If the Gauss law restart strategy is "static full restart" or
806 * "static no restart", we skip updating the potential.
807 */
808 if ((gauss_law_restart_strategy_ !=
810 (gauss_law_restart_strategy_ !=
812
813 /*
814 * ---------------------------------------------------------------------
815 * Step 2a: build right hand side for potential update
816 *
817 * The right-hand side reads:
818 * (\nabla \varphi^n, \nabla \chi) +
819 * \tau \alpha \langle \rho^n B^{-1} v^n, \nabla \chi \rangle
820 *
821 * In case of a time-dependent background density, we add a term
822 * \theta \alpha \langle \rho_b^{n+1} - \rho_b^n, \chi \rangle to
823 * account for the time dependence. Here, t_{n+1} is the final time
824 * t_n + tau, or t_n + 2 * tau (in case of Crank Nicolson). This
825 * ensures that we are consistent with the Gauß law involution
826 * "-\Delta \varphi^{n+1} = \alpha \rho^{n+1}."
827 * ---------------------------------------------------------------------
828 */
829
830 ComputingTimer::Scope scope("time step [P] 2 - update potential");
831
832 /* Query the magnetic field at the time t + tau: */
833 update_magnetic_field(t + tau);
834
835 /*
836 * Write out density and assemble velocity part. We need density_
837 * to be set to the correct density for UpdateOperator::vmult()
838 */
839
840 const auto body_copy = [&](auto sentinel, unsigned int i) {
841 using T = decltype(sentinel);
842 const auto view = hyperbolic_system_->template view<dim, T>();
843
844 const auto U_i = old_U_view.template read_tensor<T>(i);
845 const auto rho_i = view.density(U_i);
846 const auto m_i = view.momentum(U_i);
847
848 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
849 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
850 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
851
852 const auto velocity_rhs =
853 tau * alpha * rho_i *
854 apply_B_n_inverse(magnetic_field, tau, m_i / rho_i);
855
856 write_entry<T>(density_, rho_i, i);
857 for (unsigned int d = 0; d < dim; ++d)
858 write_entry<T>(velocity_rhs_.block(d), velocity_rhs[d], i);
859 };
860
861 cpu_simd_loop<Number>(
862 "time_step_parabolic_2a", body_copy, 0, n_owned, n_owned);
863
864 density_.update_ghost_values();
865
866 /* Apply Laplace operator to right hand side: */
867
868 const auto body_laplace = [](const auto &data,
869 auto &dst,
870 const auto &src,
871 const auto range) {
872 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number> fee(
873 data, /*CG*/ 0, /*full quadrature*/ 0);
874
875 for (unsigned int cell = range.first; cell < range.second; ++cell) {
876 fee.reinit(cell);
877 fee.gather_evaluate(src, dealii::EvaluationFlags::gradients);
878
879 for (unsigned int q = 0; q < fee.n_q_points; ++q) {
880 const auto grad_potential = fee.get_gradient(q);
881 fee.submit_gradient(grad_potential, q);
882 }
883 fee.integrate_scatter(dealii::EvaluationFlags::gradients, dst);
884 }
885 };
886
887 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
888 body_laplace,
889 potential_rhs_,
890 old_potential,
891 /*zero destination*/ true);
892
893 /* Apply Velocity contribution to right hand side: */
894
895 const auto body_velocity = [](const auto &data,
896 auto &dst,
897 const auto &src,
898 const auto range) {
899 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
900 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
901 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
902 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
903
904 for (unsigned int cell = range.first; cell < range.second; ++cell) {
905 fee_pot.reinit(cell);
906 fee_vel.reinit(cell);
907
908 fee_vel.gather_evaluate(src, dealii::EvaluationFlags::values);
909
910 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
911 if constexpr (dim == 1) {
912 decltype(fee_pot.get_gradient(q)) velocity_rhs;
913 velocity_rhs[0] = fee_vel.get_value(q);
914 fee_pot.submit_gradient(velocity_rhs, q);
915 } else {
916 fee_pot.submit_gradient(fee_vel.get_value(q), q);
917 }
918 }
919 fee_pot.integrate_scatter(dealii::EvaluationFlags::gradients, dst);
920 }
921 };
922
923 matrix_free_.template cell_loop<ScalarHostVector, BlockHostVector>(
924 body_velocity,
925 potential_rhs_,
926 velocity_rhs_,
927 /*zero destination*/ false);
928
929 /* Time-dependent background density: */
930
931 if (selected_electrostatic_configuration_->is_time_dependent()) {
932
933 /*
934 * Subtract background density at time t_n:
935 */
936
937 update_background_density(t);
938
939 Number factor = (crank_nicolson_extrapolation ? -0.5 : -1.0) * alpha;
940
941 const auto body = [&factor](const auto &data,
942 auto &dst,
943 const auto &src,
944 const auto range) {
945 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
946 fee_potential(data, /*CG*/ 0, /*lumped quadrature*/ 1);
947 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
948 fee_background(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
949
950 for (unsigned int cell = range.first; cell < range.second; ++cell) {
951 fee_potential.reinit(cell);
952 fee_background.reinit(cell);
953 fee_background.gather_evaluate(src, EvaluationFlags::values);
954
955 for (unsigned int q = 0; q < fee_potential.n_q_points; ++q) {
956 const auto background_q = fee_background.get_value(q);
957 fee_potential.submit_value(factor * background_q, q);
958 }
959 fee_potential.integrate_scatter(EvaluationFlags::values, dst);
960 }
961 };
962
963 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
964 body,
965 potential_rhs_,
966 background_density_,
967 /*zero destination*/ false);
968
969 /*
970 * Add background density at time t_{n+1}:
971 */
972
973 update_background_density(
974 t + (crank_nicolson_extrapolation ? 2. : 1.) * tau);
975
976 factor *= -1.;
977
978 matrix_free_.template cell_loop<ScalarHostVector, ScalarHostVector>(
979 body,
980 potential_rhs_,
981 background_density_,
982 /*zero destination*/ false);
983 }
984
985 /*
986 * ---------------------------------------------------------------------
987 * Step 2b: solve modified poisson problem
988 * ---------------------------------------------------------------------
989 */
990
991 update_operator_.set_alpha(alpha);
992 update_operator_.set_theta_tau(tau);
993
994 matrix_free_.get_affine_constraints(0).distribute(new_potential);
995 matrix_free_.get_affine_constraints(0).set_zero(potential_rhs_);
996
997 const auto tolerance =
998 (tolerance_linfty_norm_ ? potential_rhs_.linfty_norm()
999 : potential_rhs_.l2_norm()) *
1000 tolerance_;
1001
1002 typename dealii::SolverCG<ScalarHostVector>::AdditionalData solver_data;
1003
1004 try {
1005 SolverControl solver_control(gmg_max_iter_, tolerance);
1006 dealii::SolverCG<ScalarHostVector> solver(solver_control,
1007 solver_data);
1008 solver.solve(update_operator_,
1009 new_potential,
1010 potential_rhs_,
1011 multigrid_preconditioner_);
1012
1013 /* update exponential moving average */
1014 n_iterations_step_ =
1015 0.9 * n_iterations_step_ + 0.1 * solver_control.last_step();
1016
1017 } catch (SolverControl::NoConvergence &) {
1018 SolverControl solver_control(1000, tolerance);
1019 dealii::SolverCG<ScalarHostVector> solver(solver_control,
1020 solver_data);
1021
1022 solver.solve(update_operator_,
1023 new_potential,
1024 potential_rhs_,
1025 diagonal_preconditioner_);
1026
1027 /* update exponential moving average, counting also GMG iterations */
1028 n_iterations_step_ *= 0.9;
1029 n_iterations_step_ +=
1030 0.1 * gmg_max_iter_ + 0.1 * solver_control.last_step();
1031 }
1032
1033 matrix_free_.get_affine_constraints(0).distribute(new_potential);
1034 }
1035
1036 /*
1037 * ---------------------------------------------------------------------
1038 * Step 2c: update velocity vector field; Crank-Nicolson extrapolation
1039 * ---------------------------------------------------------------------
1040 */
1041
1042 /* Project gradient of potential into velocity space: */
1043
1044 const auto body_velocity =
1045 [](const auto &data, auto &dst, const auto &src, const auto range) {
1046 FEEvaluation<dim, order_fe, order_quad, /*components*/ 1, Number>
1047 fee_pot(data, /*CG*/ 0, /*lumped quadrature*/ 1);
1048 FEEvaluation<dim, order_fe, order_quad, /*components*/ dim, Number>
1049 fee_vel(data, /*hyperbolic*/ 1, /*lumped quadrature*/ 1);
1050
1051 for (unsigned int cell = range.first; cell < range.second; ++cell) {
1052 fee_pot.reinit(cell);
1053 fee_vel.reinit(cell);
1054
1055 fee_pot.gather_evaluate(src, dealii::EvaluationFlags::gradients);
1056 for (unsigned int q = 0; q < fee_pot.n_q_points; ++q) {
1057 fee_vel.submit_value(fee_pot.get_gradient(q), q);
1058 }
1059 fee_vel.integrate_scatter(dealii::EvaluationFlags::values, dst);
1060 }
1061 };
1062
1063 matrix_free_.template cell_loop<BlockHostVector, ScalarHostVector>(
1064 body_velocity,
1065 velocity_rhs_,
1066 new_potential,
1067 /*zero destination*/ true);
1068
1069 /*
1070 * Now that we have written out the gradients, copy over the old
1071 * state vector and perform the Crank-Nicolson extrapolation step on
1072 * the potential:
1073 */
1074
1075 new_U = old_U;
1076 const auto new_U_view = new_U.view();
1077
1078 if (crank_nicolson_extrapolation) {
1079 new_potential *= Number(2.);
1080 new_potential -= old_potential;
1081 }
1082
1083 /*
1084 * Update the momentum and total energy:
1085 */
1086
1087 const auto body = [&](auto sentinel, unsigned int i) {
1088 using T = decltype(sentinel);
1089 const auto view = hyperbolic_system_->template view<dim, T>();
1090
1091 const auto m_i_inv =
1092 lumped_mass_matrix_inverse_view.template read_entry<T>(i);
1093
1094 const auto old_U_i = old_U_view.template read_tensor<T>(i);
1095 const auto rho_i = view.density(old_U_i);
1096 const auto old_m_i = view.momentum(old_U_i);
1097 const auto old_v_i = old_m_i / rho_i;
1098
1099 dealii::Tensor<1, (dim == 2 ? 1 : dim), T> magnetic_field;
1100 for (unsigned int d = 0; d < (dim == 2 ? 1 : dim); ++d)
1101 magnetic_field[d] = read_entry<T>(magnetic_field_.block(d), i);
1102
1103 dealii::Tensor<1, dim, T> grad_phi;
1104 for (unsigned int d = 0; d < dim; ++d)
1105 grad_phi[d] = m_i_inv * read_entry<T>(velocity_rhs_.block(d), i);
1106
1107 auto new_v_i =
1108 apply_B_n_inverse(magnetic_field, tau, old_v_i - tau * grad_phi);
1109
1110 /* Perform an extrapolation step: */
1111 if (crank_nicolson_extrapolation)
1112 new_v_i = Number(2.) * new_v_i - old_v_i;
1113
1114 auto new_U_i = old_U_i;
1115 for (unsigned int d = 0; d < dim; ++d)
1116 new_U_i[1 + d] = rho_i * new_v_i[d];
1117
1118 /* Update the total energy accordingly: */
1119 if constexpr (view.have_energy_equation)
1120 new_U_i[1 + dim] += Number(0.5) * rho_i *
1121 (new_v_i.norm_square() - old_v_i.norm_square());
1122
1123 new_U_view.template write_tensor<T>(new_U_i, i);
1124 };
1125
1126 cpu_simd_loop<Number>(
1127 "time_step_parabolic_2c", body, 0, n_owned, n_owned);
1128 }
1129
1130 } // namespace EulerPoisson
1131} /* 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)