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