ryujin 2.1.1 revision ee5cbcbf2346c1299c942d0e1f13b46449973c18
Loading...
Searching...
No Matches
time_loop.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2020 - 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include "computing_timer.h"
9#include "gpu.h"
11#include "state_vector.h"
12#include "time_loop.h"
13#include "version_info.h"
14
15#include <deal.II/base/logstream.h>
16#include <deal.II/base/work_stream.h>
17#include <deal.II/numerics/vector_tools.h>
18#include <deal.II/numerics/vector_tools.templates.h>
19
20#include <cstdlib>
21#include <filesystem>
22#include <fstream>
23#include <iomanip>
24#include <set>
25
26#ifdef WITH_OPENMP
27#include "omp.h"
28#endif
29
30using namespace dealii;
31
32namespace ryujin
33{
34 template <typename Description, int dim, typename Number>
36 : ParameterAcceptor("/A - TimeLoop")
37 , mpi_ensemble_(mpi_comm,
38 [] {
39 if constexpr (has_n_mpi_ensembles_v<Description>)
40 return Description::n_mpi_ensembles();
41 else
42 return 1;
43 }())
44 , hyperbolic_system_(mpi_ensemble_, "/B - Equation")
45 , parabolic_system_(mpi_ensemble_, "/B - Equation")
46 , discretization_(mpi_ensemble_, "/C - Discretization")
47 , offline_data_(mpi_ensemble_, discretization_, "/D - OfflineData")
48 , initial_values_(mpi_ensemble_,
49 "/E - InitialValues",
50 mpi_ensemble_,
51 offline_data_,
52 hyperbolic_system_,
53 parabolic_system_)
54 , hyperbolic_module_(mpi_ensemble_,
55 offline_data_,
56 hyperbolic_system_,
57 initial_values_,
58 "/F - HyperbolicModule")
59 , parabolic_module_(mpi_ensemble_,
60 offline_data_,
61 hyperbolic_system_,
62 parabolic_system_,
63 initial_values_,
64 "/G - ParabolicModule")
65 , time_integrator_(mpi_ensemble_,
66 offline_data_,
67 hyperbolic_module_,
68 parabolic_module_,
69 "/H - TimeIntegrator")
70 , mesh_adaptor_(mpi_ensemble_,
71 offline_data_,
72 hyperbolic_system_,
73 parabolic_system_,
74 hyperbolic_module_.initial_precomputed(),
75 hyperbolic_module_.alpha(),
76 "/I - MeshAdaptor")
77 , solution_transfer_(mpi_ensemble_,
78 offline_data_,
79 hyperbolic_system_,
80 parabolic_system_,
81 "/I - MeshAdaptor")
82 , postprocessor_(mpi_ensemble_,
83 offline_data_,
84 hyperbolic_system_,
85 parabolic_system_,
86 "/J - VTUOutput")
87 , vtu_output_(mpi_ensemble_,
88 offline_data_,
89 hyperbolic_system_,
90 parabolic_system_,
91 postprocessor_,
92 hyperbolic_module_.initial_precomputed(),
93 hyperbolic_module_.alpha(),
94 mesh_adaptor_.smoothness_indicators(),
95 "/J - VTUOutput")
96 , quantities_(mpi_ensemble_,
97 offline_data_,
98 hyperbolic_system_,
99 parabolic_system_,
100 "/K - Quantities")
101 , n_global_dofs_(0)
102 , n_devices_(0)
103 {
104 base_name_ = "test";
105 add_parameter("basename", base_name_, "Base name for all output files");
106
107 t_final_ = Number(5.);
108 add_parameter("final time", t_final_, "Final time");
109
110 enforce_t_final_ = false;
111 add_parameter("enforce final time",
112 enforce_t_final_,
113 "Boolean indicating whether the final time should be "
114 "enforced strictly. If set to true the last time step is "
115 "shortened so that the simulation ends precisely at t_final");
116
117 timer_granularity_ = Number(0.01);
118 add_parameter("timer granularity",
119 timer_granularity_,
120 "The timer granularity specifies the time interval after "
121 "which compute, output, postprocessing, and mesh adaptation "
122 "routines are run. This \"baseline tick\" is further "
123 "modified by the corresponding \"*_multiplier\" options");
124
125 enable_output_full_ = false;
126 add_parameter("enable output full",
127 enable_output_full_,
128 "Write out full pvtu records. The frequency is determined by "
129 "\"timer granularity\" and \"timer output full multiplier\"");
130
131 enable_output_levelsets_ = false;
132 add_parameter(
133 "enable output levelsets",
134 enable_output_levelsets_,
135 "Write out levelsets pvtu records. The frequency is determined by "
136 "\"timer granularity\" and \"timer output levelsets multiplier\"");
137
138 enable_compute_error_ = false;
139 add_parameter("enable compute error",
140 enable_compute_error_,
141 "Flag to control whether we compute the Linfty Linf_norm of "
142 "the difference to an analytic solution. Implemented only "
143 "for certain initial state configurations.");
144
145 enable_compute_quantities_ = false;
146 add_parameter(
147 "enable compute quantities",
148 enable_compute_quantities_,
149 "Flag to control whether we compute quantities of interest. The "
150 "frequency how often quantities are logged is determined by \"timer "
151 "granularity\" and \"timer compute quantities multiplier\"");
152
153 enable_mesh_adaptivity_ = false;
154 add_parameter(
155 "enable mesh adaptivity",
156 enable_mesh_adaptivity_,
157 "Flag to control whether we use an adaptive mesh refinement strategy. "
158 "The frequency how often we query MeshAdaptor::analyze() for deciding "
159 "on adapting the mesh is determined by \"timer granularity\" and "
160 "\"timer mesh refinement multiplier\"");
161
162 timer_output_full_multiplier_ = 1;
163 add_parameter("timer output full multiplier",
164 timer_output_full_multiplier_,
165 "Multiplicative modifier applied to \"timer granularity\" "
166 "that determines the full pvtu writeout granularity");
167
168 timer_output_levelsets_multiplier_ = 1;
169 add_parameter("timer output levelsets multiplier",
170 timer_output_levelsets_multiplier_,
171 "Multiplicative modifier applied to \"timer granularity\" "
172 "that determines the levelsets pvtu writeout granularity");
173
174 timer_compute_quantities_multiplier_ = 1;
175 add_parameter(
176 "timer compute quantities multiplier",
177 timer_compute_quantities_multiplier_,
178 "Multiplicative modifier applied to \"timer granularity\" that "
179 "determines the writeout granularity for quantities of interest");
180
181 std::copy(std::begin(View::component_names),
182 std::end(View::component_names),
183 std::back_inserter(error_quantities_));
184
185 add_parameter("error quantities",
186 error_quantities_,
187 "List of conserved quantities used in the computation of the "
188 "error norms.");
189
190 error_normalize_ = true;
191 add_parameter("error normalize",
192 error_normalize_,
193 "Flag to control whether the error should be normalized by "
194 "the corresponding norm of the analytic solution.");
195
196 resume_ = false;
197 add_parameter("resume", resume_, "Resume an interrupted computation");
198
199 resume_at_time_zero_ = false;
200 add_parameter("resume at time zero",
201 resume_at_time_zero_,
202 "Resume from the latest checkpoint but set the time to t=0.");
203
204 terminal_update_interval_ = 5;
205 add_parameter("terminal update interval",
206 terminal_update_interval_,
207 "Number of seconds after which output statistics are "
208 "recomputed and printed on the terminal. Setting the "
209 "interval to zero disables terminal output.");
210
211 terminal_correct_for_hypertreadhing_ = true;
212 add_parameter(
213 "terminal correct for hyperthreading",
214 terminal_correct_for_hypertreadhing_,
215 "If set to true, the CPU throughput is corrected by dividing the total "
216 "consumed CPU time by a factor of 2. This correction is only active if "
217 "the number of threads (per MPI rank) is 2.");
218
219 checkpoint_update_interval_ = 0;
220 add_parameter(
221 "checkpoint update interval",
222 checkpoint_update_interval_,
223 "Number of seconds after which a new checkpoint is written out to "
224 "disk. Setting the interval to zero disables checkpointing.");
225
226 debug_command_ = "";
227 add_parameter("debug command",
228 debug_command_,
229 "If set to a nonempty string then the host environment's "
230 "command processor is invoked via std::system() with the "
231 "specified string as command parameter.");
232
233 debug_filename_ = "";
234 add_parameter("debug filename",
235 debug_filename_,
236 "If set to a nonempty string then we output the contents of "
237 "this file at the end. This is mainly useful in the "
238 "testsuite to output files we wish to compare");
239 }
240
241
242 /*
243 * ---------------------------------------------------------------------------
244 * Setup and main loop:
245 * ---------------------------------------------------------------------------
246 */
247
248
249 template <typename Description, int dim, typename Number>
251 {
252#ifdef DEBUG_OUTPUT
253 std::cout << "TimeLoop<dim, Number>::run()" << std::endl;
254#endif
255
258
259 {
260 base_name_ensemble_ = base_name_;
261 if (mpi_ensemble_.n_ensembles() > 1) {
262 print_info("setting up MPI ensemble");
263 unsigned int digits =
264 dealii::Utilities::needed_digits(mpi_ensemble_.n_ensembles() - 1);
265 base_name_ensemble_ +=
266 "-ensemble_" +
267 dealii::Utilities::int_to_string(mpi_ensemble_.ensemble(), digits);
268 }
269 }
270
271 /* Attach log file and record runtime parameters: */
272
273 if (mpi_ensemble_.world_rank() == 0)
274 logfile_.open(base_name_ + ".log");
275
276 print_parameters(logfile_);
277
278 /*
279 * Prepare data structures:
280 */
281
282 Number t = 0.;
283 unsigned int timer_cycle = 0;
284 StateVector state_vector;
285
286 /* Create a small lambda for preparing compute kernels: */
287 const auto prepare_compute_kernels = [&]() {
288 print_info("preparing compute kernels");
289
290 offline_data_.prepare(problem_dimension, n_precomputed_values);
291
292 hyperbolic_module_.prepare();
293 parabolic_module_.prepare();
294 time_integrator_.prepare();
295 mesh_adaptor_.prepare(/*needs current timepoint*/ t);
296 postprocessor_.prepare();
297 vtu_output_.prepare();
298 quantities_.prepare(base_name_ensemble_);
299 print_mpi_partition(logfile_);
300
301 if (mpi_ensemble_.ensemble_rank() == 0)
302 n_global_dofs_ = dealii::Utilities::MPI::sum(
303 offline_data_.dof_handler().n_dofs(),
304 mpi_ensemble_.ensemble_leader_communicator());
305 };
306
307 {
308 ComputingTimer::Scope scope("(re)initialize data structures");
309 print_info("initializing data structures");
310
311 if (resume_) {
312 print_info("resume: reading mesh and loading state vector");
313
314 read_checkpoint(state_vector,
315 base_name_ensemble_,
316 t,
317 timer_cycle,
318 prepare_compute_kernels);
319
320 if (resume_at_time_zero_) {
321 /* Reset the current time t and the output cycle count to zero: */
322 t = 0.;
323 timer_cycle = 0;
324 }
325
326 } else {
327 print_info("creating mesh and interpolating initial values");
328
329 discretization_.prepare(base_name_ensemble_);
330
331 prepare_compute_kernels();
332
333 hyperbolic_module_.reinit_state_vector(state_vector);
334 parabolic_module_.reinit_state_vector(state_vector);
335 {
337 "time step [X] - interpolate data vectors");
338 std::get<0>(state_vector) =
339 initial_values_.get().interpolate_hyperbolic_vector();
340 }
341 Vectors::debug_poison_invalid_values(state_vector, offline_data_);
342 }
343 }
344
345 print_device_information(logfile_);
346
347 /* Prepare the state vector for time stepping. */
348 time_integrator_.prepare_state_vector(state_vector, t);
349
350 /*
351 * The honorable main loop:
352 */
353
354 Number last_terminal_output = terminal_update_interval_ == Number(0.)
355 ? std::numeric_limits<Number>::max()
356 : Number(0.);
357 Number last_checkpoint = checkpoint_update_interval_ == Number(0.)
358 ? std::numeric_limits<Number>::max()
359 : Number(0.);
360
361 print_info("entering main loop");
362 ComputingTimer::timer("time loop").start();
363
364 constexpr Number relax =
365 Number(1.) - Number(10.) * std::numeric_limits<Number>::epsilon();
366
367 unsigned int cycle = 1;
368 for (;; ++cycle) {
369
370#ifdef DEBUG_OUTPUT
371 std::cout << "\n\n### cycle = " << cycle << " ###\n\n" << std::endl;
372#endif
373
374 /* Accumulate quantities of interest: */
375
376 if (enable_compute_quantities_) {
377 ComputingTimer::Scope scope("time step [X] - accumulate quantities");
378 quantities_.accumulate(state_vector, t);
379 }
380
381 /* Perform output tasks whenever we reach a timer tick: */
382
383 if (t >= relax * timer_cycle * timer_granularity_) {
384 if (enable_compute_error_) {
385 /*
386 * FIXME: We interpolate the analytic solution at every timer
387 * tick. If we happen to actually not output anything then this
388 * is terribly inefficient...
389 */
390
391 StateVector analytic;
392 {
394 "time step [X] - interpolate data vectors");
395 hyperbolic_module_.reinit_state_vector(analytic);
396 parabolic_module_.reinit_state_vector(analytic);
397 std::get<0>(analytic) =
398 initial_values_.get().interpolate_hyperbolic_vector(t);
399 }
400
401 time_integrator_.prepare_state_vector(analytic, t);
402
403 output(analytic,
404 base_name_ensemble_ + "-analytic_solution",
405 t,
406 timer_cycle);
407 }
408
409 output(state_vector, base_name_ensemble_ + "-solution", t, timer_cycle);
410
411 if (enable_compute_quantities_ &&
412 (timer_cycle % timer_compute_quantities_multiplier_ == 0)) {
413 ComputingTimer::Scope scope("time step [X] - write out quantities");
414 quantities_.write_out(state_vector, t, timer_cycle);
415 }
416
417 ++timer_cycle;
418 }
419
420 /* Break if we have reached the final time. */
421
422 if (t >= relax * t_final_)
423 break;
424
425 /* Peform a mesh adaptation cycle: */
426
427 if (enable_mesh_adaptivity_) {
428 {
430 "time step [X] - analyze for mesh adaptation");
431
432 mesh_adaptor_.analyze(state_vector, t, cycle);
433 }
434
435 if (mesh_adaptor_.need_mesh_adaptation()) {
436 ComputingTimer::Scope scope_1("(re)initialize data structures");
437 ComputingTimer::Scope scope_2(
438 "time step [X] - perform mesh adaptation");
439 print_info("performing mesh adaptation");
440
441 adapt_mesh_and_transfer_state_vector(state_vector,
442 prepare_compute_kernels);
443
444 /* Prepare the state vector for time stepping. */
445 time_integrator_.prepare_state_vector(state_vector, t);
446 }
447 }
448
449 /* Perform a time step: */
450
451 const auto tau = time_integrator_.step(
452 state_vector,
453 t,
454 enforce_t_final_
455 ? std::min(t_final_, timer_cycle * timer_granularity_)
456 : std::numeric_limits<Number>::max());
457
458 t += tau;
459
460 time_integrator_.prepare_state_vector(state_vector, t);
461
462 /* Synchronize wall time: */
463
464 auto wall_time = ComputingTimer::timer("time loop").wall_time();
465 {
467 "time step [X] _ - synchronization barriers");
468 wall_time =
469 Utilities::MPI::max(wall_time, mpi_ensemble_.world_communicator());
470 }
471
472 /* Print and record cycle statistics: */
473
474 const bool write_to_log_file =
475 (terminal_update_interval_ != Number(0.)) && /* suppress output */
476 (t >= relax * timer_cycle * timer_granularity_);
477
478 const bool update_terminal =
479 (wall_time >= last_terminal_output + terminal_update_interval_);
480
481 if (write_to_log_file || update_terminal) {
483 "time step [X] _ - synchronization barriers");
484 print_cycle_statistics(cycle,
485 t,
486 timer_cycle,
487 last_checkpoint,
488 /*logfile*/ write_to_log_file);
489 last_terminal_output = wall_time;
490 }
491
492 const bool update_checkpoint =
493 (wall_time >= last_checkpoint + checkpoint_update_interval_);
494
495 if (update_checkpoint) {
496 ComputingTimer::Scope scop("time step [X] - perform checkpointing");
497
498 print_info("scheduling checkpointing");
499 write_checkpoint(state_vector, base_name_ensemble_, t, timer_cycle);
500 last_checkpoint = wall_time;
501 }
502 } /* end of loop */
503
504 /* We have actually performed one cycle less. */
505 --cycle;
506
507 if (checkpoint_update_interval_ != Number(0.)) {
508 ComputingTimer::Scope scope("time step [X] - perform checkpointing");
509
510 print_info("scheduling checkpointing");
511 write_checkpoint(state_vector, base_name_ensemble_, t, timer_cycle);
512 }
513
514 ComputingTimer::timer("time loop").stop();
515
516 if (terminal_update_interval_ != Number(0.)) {
517 /* Write final timing statistics to screen and logfile: */
518 print_cycle_statistics(cycle,
519 t,
520 timer_cycle,
521 last_checkpoint,
522 /*logfile*/ true,
523 /*final*/ true);
524 }
525
526 if (enable_compute_error_) {
527 /* Output final error: */
528 compute_error(state_vector, t);
529 }
530
531 /*
532 *
533 */
534
535 if (mpi_ensemble_.world_rank() == 0) {
536 if (debug_command_ != "") {
537 auto result [[maybe_unused]] = std::system(debug_command_.c_str());
538 }
539
540 if (debug_filename_ != "") {
541 std::ifstream f(debug_filename_);
542 if (f.is_open())
543 std::cout << f.rdbuf();
544 }
545 }
546 }
547
548
549 /*
550 * ---------------------------------------------------------------------------
551 * Checkpointing, VTK output, and compute error:
552 * ---------------------------------------------------------------------------
553 */
554
555
556 template <typename Description, int dim, typename Number>
557 template <typename Callable>
559 StateVector &state_vector,
560 const std::string &base_name,
561 Number &t,
562 unsigned int &timer_cycle,
563 const Callable &prepare_compute_kernels)
564 {
565#ifdef DEBUG_OUTPUT
566 std::cout << "TimeLoop<dim, Number>::read_checkpoint()" << std::endl;
567#endif
568
569 /*
570 * Initialize discretization, read in the mesh, and initialize everything:
571 */
572
573 discretization_.refinement() = 0; /* do not refine */
574 discretization_.prepare(base_name);
575 discretization_.triangulation().load(base_name + "-checkpoint.mesh");
576
577 prepare_compute_kernels();
578
579 /*
580 * Read in and broadcast metadata:
581 */
582
583 std::string name = base_name + "-checkpoint";
584
585 unsigned int transfer_handle;
586 if (mpi_ensemble_.ensemble_rank() == 0) {
587 std::string meta = name + ".metadata";
588
589 std::ifstream file(meta, std::ios::binary);
590 boost::archive::binary_iarchive ia(file);
591 ia >> t >> timer_cycle >> transfer_handle;
592 }
593
594 int ierr;
595 if constexpr (std::is_same_v<Number, double>)
596 ierr = MPI_Bcast(
597 &t, 1, MPI_DOUBLE, 0, mpi_ensemble_.ensemble_communicator());
598 else
599 ierr =
600 MPI_Bcast(&t, 1, MPI_FLOAT, 0, mpi_ensemble_.ensemble_communicator());
601 AssertThrowMPI(ierr);
602
603 ierr = MPI_Bcast(&timer_cycle,
604 1,
605 MPI_UNSIGNED,
606 0,
607 mpi_ensemble_.ensemble_communicator());
608 AssertThrowMPI(ierr);
609
610 ierr = MPI_Bcast(&transfer_handle,
611 1,
612 MPI_UNSIGNED,
613 0,
614 mpi_ensemble_.ensemble_communicator());
615 AssertThrowMPI(ierr);
616
617 /* Now read in the state vector: */
618
619 hyperbolic_module_.reinit_state_vector(state_vector);
620 parabolic_module_.reinit_state_vector(state_vector);
621
622 solution_transfer_.set_handle(transfer_handle);
623 solution_transfer_.project(state_vector);
624 solution_transfer_.reset_handle();
625 Vectors::debug_poison_invalid_values(state_vector, offline_data_);
626
627 time_integrator_.prepare_state_vector(state_vector, t);
628 }
629
630
631 template <typename Description, int dim, typename Number>
632 void TimeLoop<Description, dim, Number>::write_checkpoint(
633 const StateVector &state_vector,
634 const std::string &base_name,
635 const Number &t,
636 const unsigned int &timer_cycle)
637 {
638#ifdef DEBUG_OUTPUT
639 std::cout << "TimeLoop<dim, Number>::write_checkpoint()" << std::endl;
640#endif
641
642 solution_transfer_.prepare_projection(state_vector);
643 const auto transfer_handle = solution_transfer_.get_handle();
644 solution_transfer_.reset_handle();
645
646 std::string name = base_name + "-checkpoint";
647
648 if (mpi_ensemble_.ensemble_rank() == 0) {
649 for (const std::string suffix :
650 {".mesh", ".mesh_fixed.data", ".mesh.info", ".metadata"})
651 if (std::filesystem::exists(name + suffix))
652 std::filesystem::rename(name + suffix, name + suffix + "~");
653 }
654
655 const auto &triangulation = discretization_.triangulation();
656 triangulation.save(name + ".mesh");
657
658 /*
659 * Now, write out metadata on rank 0:
660 */
661
662 if (mpi_ensemble_.ensemble_rank() == 0) {
663 std::string meta = name + ".metadata";
664 std::ofstream file(meta, std::ios::binary | std::ios::trunc);
665 boost::archive::binary_oarchive oa(file);
666 oa << t << timer_cycle << transfer_handle;
667 }
668
669 const int ierr = MPI_Barrier(mpi_ensemble_.ensemble_communicator());
670 AssertThrowMPI(ierr);
671 }
672
673
674 template <typename Description, int dim, typename Number>
675 template <typename Callable>
676 void TimeLoop<Description, dim, Number>::adapt_mesh_and_transfer_state_vector(
677 StateVector &state_vector, const Callable &prepare_compute_kernels)
678 {
679#ifdef DEBUG_OUTPUT
680 std::cout << "TimeLoop<dim, Number>::adapt_mesh_and_transfer_state_vector()"
681 << std::endl;
682#endif
683
684 AssertThrow(mpi_ensemble_.n_ensembles() == 1, dealii::ExcNotImplemented());
685
686 /*
687 * Mark cells for coarsening and refinement and set up triangulation:
688 */
689
690 auto &triangulation = discretization_.triangulation();
691 mesh_adaptor_.mark_cells_for_coarsening_and_refinement(triangulation);
692
693 triangulation.prepare_coarsening_and_refinement();
694
695 solution_transfer_.prepare_projection(state_vector);
696
697 /* Execute mesh adaptation and project old state to new state vector: */
698
699 triangulation.execute_coarsening_and_refinement();
700 prepare_compute_kernels();
701
702 hyperbolic_module_.reinit_state_vector(state_vector);
703 parabolic_module_.reinit_state_vector(state_vector);
704
705 solution_transfer_.project(state_vector);
706 solution_transfer_.reset_handle();
707 Vectors::debug_poison_invalid_values(state_vector, offline_data_);
708 }
709
710
711 template <typename Description, int dim, typename Number>
712 void TimeLoop<Description, dim, Number>::compute_error(
713 const StateVector &state_vector, const Number t)
714 {
715#ifdef DEBUG_OUTPUT
716 std::cout << "TimeLoop<dim, Number>::compute_error()" << std::endl;
717#endif
718
719 /* Ensure that the state vector is resident on the host memory space. */
720 if constexpr (have_separate_memory_spaces) {
721 ComputingTimer::Scope scope("time step [X] _ - memory space transfers");
722 const auto &[U, precomputed, parabolic] = state_vector;
723 U.template copy_to_memory_space<dealii::MemorySpace::Host>();
724 }
725
726 Vector<Number> difference_per_cell(
727 discretization_.triangulation().n_active_cells());
728
729 Number linf_norm = 0.;
730 Number l1_norm = 0;
731 Number l2_norm = 0;
732
733 const auto analytic_U =
734 initial_values_.get().interpolate_hyperbolic_vector(t);
735 const auto &U = std::get<0>(state_vector);
736
737 using ScalarHostVector = Vectors::ScalarHostVector<Number>;
738 ScalarHostVector analytic_component;
739 ScalarHostVector error_component;
740 analytic_component.reinit(offline_data_.scalar_partitioner());
741 error_component.reinit(offline_data_.scalar_partitioner());
742
743 /* Loop over all selected components: */
744 for (const auto &entry : error_quantities_) {
745 const auto &names = View::component_names;
746 const auto pos = std::find(std::begin(names), std::end(names), entry);
747 if (pos == std::end(names)) {
748 AssertThrow(
749 false,
750 dealii::ExcMessage("Unknown component name »" + entry + "«"));
751 __builtin_trap();
752 }
753
754 const auto index = std::distance(std::begin(names), pos);
755
756 analytic_U.view().extract_component(analytic_component, index);
757
758 /* Compute norms of analytic solution: */
759
760 Number linf_norm_analytic = 0.;
761 Number l1_norm_analytic = 0.;
762 Number l2_norm_analytic = 0.;
763
764 if (error_normalize_) {
765 linf_norm_analytic = analytic_component.linfty_norm();
766
767 VectorTools::integrate_difference(
768 discretization_.mapping(),
769 offline_data_.dof_handler(),
770 analytic_component,
771 Functions::ZeroFunction<dim, Number>(),
772 difference_per_cell,
773 discretization_.quadrature_high_order(),
774 VectorTools::L1_norm);
775
776 l1_norm_analytic =
777 Utilities::MPI::sum(difference_per_cell.l1_norm(),
778 mpi_ensemble_.ensemble_communicator());
779
780 VectorTools::integrate_difference(
781 discretization_.mapping(),
782 offline_data_.dof_handler(),
783 analytic_component,
784 Functions::ZeroFunction<dim, Number>(),
785 difference_per_cell,
786 discretization_.quadrature_high_order(),
787 VectorTools::L2_norm);
788
789 l2_norm_analytic = Number(std::sqrt(
790 Utilities::MPI::sum(std::pow(difference_per_cell.l2_norm(), 2),
791 mpi_ensemble_.ensemble_communicator())));
792 }
793
794 /* Compute norms of error: */
795
796 U.view().extract_component(error_component, index);
797 /* Populate constrained dofs due to periodicity: */
798 offline_data_.affine_constraints().distribute(error_component);
799 error_component.update_ghost_values();
800 error_component -= analytic_component;
801
802 const Number linf_norm_error = error_component.linfty_norm();
803
804 VectorTools::integrate_difference(discretization_.mapping(),
805 offline_data_.dof_handler(),
806 error_component,
807 Functions::ZeroFunction<dim, Number>(),
808 difference_per_cell,
809 discretization_.quadrature_high_order(),
810 VectorTools::L1_norm);
811
812 const Number l1_norm_error = Utilities::MPI::sum(
813 difference_per_cell.l1_norm(), mpi_ensemble_.ensemble_communicator());
814
815 VectorTools::integrate_difference(discretization_.mapping(),
816 offline_data_.dof_handler(),
817 error_component,
818 Functions::ZeroFunction<dim, Number>(),
819 difference_per_cell,
820 discretization_.quadrature_high_order(),
821 VectorTools::L2_norm);
822
823 const Number l2_norm_error = Number(std::sqrt(
824 Utilities::MPI::sum(std::pow(difference_per_cell.l2_norm(), 2),
825 mpi_ensemble_.ensemble_communicator())));
826
827 if (error_normalize_) {
828 linf_norm += linf_norm_error / linf_norm_analytic;
829 l1_norm += l1_norm_error / l1_norm_analytic;
830 l2_norm += l2_norm_error / l2_norm_analytic;
831 } else {
832 linf_norm += linf_norm_error;
833 l1_norm += l1_norm_error;
834 l2_norm += l2_norm_error;
835 }
836 }
837
838 if (mpi_ensemble_.ensemble_rank() != 0)
839 return;
840
841 /*
842 * Sum up over all participating MPI ranks. Note: we only perform this
843 * operation on "peer" ranks zero:
844 */
845
846 if (mpi_ensemble_.n_ensembles() > 1) {
847 linf_norm = Utilities::MPI::sum(
848 linf_norm, mpi_ensemble_.ensemble_leader_communicator());
849 l1_norm = Utilities::MPI::sum(
850 l1_norm, mpi_ensemble_.ensemble_leader_communicator());
851 l2_norm = Utilities::MPI::sum(
852 l2_norm, mpi_ensemble_.ensemble_leader_communicator());
853 }
854
855 if (mpi_ensemble_.world_rank() != 0)
856 return;
857
858 logfile_ << std::endl << "Computed errors:" << std::endl << std::endl;
859 logfile_ << std::setprecision(16);
860
861 std::string description =
862 error_normalize_ ? "Normalized consolidated" : "Consolidated";
863
864 logfile_ << description + " Linf, L1, and L2 errors at final time \n";
865 logfile_ << std::setprecision(16);
866 logfile_ << "#dofs = " << n_global_dofs_ << std::endl;
867 logfile_ << "t = " << t << std::endl;
868 logfile_ << "Linf = " << linf_norm << std::endl;
869 logfile_ << "L1 = " << l1_norm << std::endl;
870 logfile_ << "L2 = " << l2_norm << std::endl;
871
872 std::cout << description + " Linf, L1, and L2 errors at final time \n";
873 std::cout << std::setprecision(16);
874 std::cout << "#dofs = " << n_global_dofs_ << std::endl;
875 std::cout << "t = " << t << std::endl;
876 std::cout << "Linf = " << linf_norm << std::endl;
877 std::cout << "L1 = " << l1_norm << std::endl;
878 std::cout << "L2 = " << l2_norm << std::endl;
879 }
880
881
882 template <typename Description, int dim, typename Number>
883 void
884 TimeLoop<Description, dim, Number>::output(const StateVector &state_vector,
885 const std::string &name,
886 const Number t,
887 const unsigned int cycle)
888 {
889#ifdef DEBUG_OUTPUT
890 std::cout << "TimeLoop<dim, Number>::output(t = " << t << ")" << std::endl;
891#endif
892
893 const bool do_full_output =
894 (cycle % timer_output_full_multiplier_ == 0) && enable_output_full_;
895 const bool do_levelsets =
896 (cycle % timer_output_levelsets_multiplier_ == 0) &&
897 enable_output_levelsets_;
898
899 /* There is nothing to do: */
900 if (!(do_full_output || do_levelsets))
901 return;
902
903 /* Data output: */
904
905 ComputingTimer::Scope scope("time step [X] - perform vtu output");
906 print_info("scheduling output");
907
908 postprocessor_.compute(state_vector);
909 /*
910 * Workaround: Manually reset bounds during the first output cycle
911 * (which is often just a uniform flow field) to obtain a better
912 * normailization:
913 */
914 if (cycle == 0)
915 postprocessor_.reset_bounds();
916
917 /* Make sure we have a valid vector of smoothness indicators. */
918 mesh_adaptor_.compute_smoothness_indicators(state_vector);
919
920 vtu_output_.schedule_output(
921 state_vector, name, t, cycle, do_full_output, do_levelsets);
922 }
923
924
925 /*
926 * ---------------------------------------------------------------------------
927 * Output and logging related functions:
928 * ---------------------------------------------------------------------------
929 */
930
931
932 template <typename Description, int dim, typename Number>
933 void
934 TimeLoop<Description, dim, Number>::print_parameters(std::ostream &stream)
935 {
936 if (mpi_ensemble_.world_rank() != 0)
937 return;
938
939 /* Output commit and library information: */
940
942
943 /* Print run time parameters: */
944
945 stream << std::endl << "Run time parameters:" << std::endl << std::endl;
946 ParameterAcceptor::prm.print_parameters(
947 stream, ParameterHandler::OutputStyle::ShortPRM);
948 stream << std::endl;
949
950 /* Also print out parameters to a prm file: */
951
952 std::ofstream output(base_name_ + "-parameters.prm");
953 ParameterAcceptor::prm.print_parameters(output, ParameterHandler::ShortPRM);
954 }
955
956
957 template <typename Description, int dim, typename Number>
958 void
959 TimeLoop<Description, dim, Number>::print_mpi_partition(std::ostream &stream)
960 {
961 /*
962 * Fixme: this conversion to double is really not elegant. We should
963 * improve the Utilities::MPI::min_max_avg function in deal.II to
964 * handle different data types
965 */
966
967 // NOLINTBEGIN
968 std::vector<double> values = {
969 (double)offline_data_.n_export_indices(),
970 (double)offline_data_.n_locally_internal(),
971 (double)offline_data_.n_locally_owned(),
972 (double)offline_data_.n_locally_relevant(),
973 (double)offline_data_.n_export_indices() /
974 (double)offline_data_.n_locally_relevant(),
975 (double)offline_data_.n_locally_internal() /
976 (double)offline_data_.n_locally_relevant(),
977 (double)offline_data_.n_locally_owned() /
978 (double)offline_data_.n_locally_relevant()};
979 // NOLINTEND
980
981 const auto data =
982 Utilities::MPI::min_max_avg(values, mpi_ensemble_.world_communicator());
983
984 if (mpi_ensemble_.world_rank() != 0)
985 return;
986
987 std::ostringstream output;
988
989 unsigned int n =
990 dealii::Utilities::needed_digits(mpi_ensemble_.n_world_ranks());
991
992 const auto print_snippet = [&output, n](const std::string &name,
993 const auto &values) {
994 output << name << ": ";
995 // NOLINTBEGIN
996 output << std::setw(9) << (unsigned int)values.min //
997 << " [p" << std::setw(n) << values.min_index << "] " //
998 << std::setw(9) << (unsigned int)values.avg << " " //
999 << std::setw(9) << (unsigned int)values.max //
1000 << " [p" << std::setw(n) << values.max_index << "]"; //
1001 // NOLINTEND
1002 };
1003
1004 const auto print_percentages = [&output, n](const auto &percentages) {
1005 output << std::endl << " ";
1006 output << " (" << std::setw(3) << std::setprecision(2)
1007 << percentages.min * 100 << "% )"
1008 << " [p" << std::setw(n) << percentages.min_index << "] "
1009 << " (" << std::setw(3) << std::setprecision(2)
1010 << percentages.avg * 100 << "% )"
1011 << " "
1012 << " (" << std::setw(3) << std::setprecision(2)
1013 << percentages.max * 100 << "% )"
1014 << " [p" << std::setw(n) << percentages.max_index << "]";
1015 };
1016
1017 output << std::endl << std::endl << "Partition: ";
1018 print_snippet("exp", data[0]);
1019 print_percentages(data[4]);
1020
1021 output << std::endl << " ";
1022 print_snippet("int", data[1]);
1023 print_percentages(data[5]);
1024
1025 output << std::endl << " ";
1026 print_snippet("own", data[2]);
1027 print_percentages(data[6]);
1028
1029 output << std::endl << " ";
1030 print_snippet("rel", data[3]);
1031
1032 stream << output.str() << std::endl;
1033 }
1034
1035
1036 template <typename Description, int dim, typename Number>
1037 void TimeLoop<Description, dim, Number>::print_device_information(
1038 std::ostream &stream)
1039 {
1040 /* Device information is only meaningful for a device build: */
1041 if constexpr (!have_separate_memory_spaces)
1042 return;
1043
1044#if defined(KOKKOS_ENABLE_CUDA) || defined(KOKKOS_ENABLE_HIP)
1045 /*
1046 * Determine how many distinct devices we are running on: Multiple
1047 * ranks might be bound to the same device, so counting ranks would
1048 * overreport. We thus count distinct (hostname, device id) pairs.
1049 */
1050 {
1051 using Device = std::pair<std::string, int>;
1052
1053 const Device local{dealii::Utilities::System::get_hostname(),
1054 Kokkos::device_id()};
1055 const auto all = dealii::Utilities::MPI::all_gather(
1056 mpi_ensemble_.world_communicator(), local);
1057 n_devices_ = std::set<Device>(all.begin(), all.end()).size();
1058 }
1059#endif
1060
1061 if (mpi_ensemble_.world_rank() != 0)
1062 return;
1063
1064 std::ostringstream output;
1065
1066 const auto entry [[maybe_unused]] =
1067 [&output](const std::string &label) -> std::ostream & {
1068 output << std::endl
1069 << " " << std::left << std::setw(22) << label;
1070 return output;
1071 };
1072
1073 output << std::endl
1074 << std::endl
1075 << "Device: " << std::left << std::setw(22) << "backend"
1076 << Kokkos::DefaultExecutionSpace::name();
1077
1078 /*
1079 * Kokkos does not provide a portable device property class. We thus
1080 * have to query the individual backends by hand:
1081 */
1082
1083#if defined(KOKKOS_ENABLE_CUDA)
1084 const auto &prop = Kokkos::Cuda{}.cuda_device_prop();
1085
1086 entry("device") << prop.name;
1087 entry("compute capability") << prop.major << "." << prop.minor;
1088 entry("device id") << Kokkos::device_id() << " of "
1089 << Kokkos::num_devices();
1090 entry("devices in use")
1091 << n_devices_ << " on " << mpi_ensemble_.n_world_ranks() << " ranks";
1092 entry("multiprocessors") << prop.multiProcessorCount;
1093 entry("warp size") << prop.warpSize << " (hardware), " << warp_size
1094 << " (ryujin::warp_size)";
1095 entry("threads per SM")
1096 << prop.maxThreadsPerMultiProcessor << " ("
1097 << prop.maxThreadsPerMultiProcessor / prop.warpSize << " warps)";
1098 entry("threads per block") << prop.maxThreadsPerBlock;
1099 entry("concurrency") << Kokkos::DefaultExecutionSpace{}.concurrency();
1100 entry("registers per SM") << prop.regsPerBlock;
1101 entry("shared memory per SM")
1102 << prop.sharedMemPerMultiprocessor / 1024 << " KiB";
1103 entry("L2 cache") << prop.l2CacheSize / 1024 / 1024 << " MiB";
1104 entry("global memory") << prop.totalGlobalMem / 1024 / 1024 << " MiB";
1105
1106#elif defined(KOKKOS_ENABLE_HIP)
1107 const auto &prop = Kokkos::HIP::hip_device_prop();
1108
1109 entry("device") << prop.name;
1110 entry("architecture") << prop.gcnArchName;
1111 entry("device id") << Kokkos::device_id() << " of "
1112 << Kokkos::num_devices();
1113 entry("devices in use")
1114 << n_devices_ << " on " << mpi_ensemble_.n_world_ranks() << " ranks";
1115 entry("compute units") << prop.multiProcessorCount;
1116 entry("warp size") << prop.warpSize << " (hardware), " << warp_size
1117 << " (ryujin::warp_size)";
1118 entry("threads per CU")
1119 << prop.maxThreadsPerMultiProcessor << " ("
1120 << prop.maxThreadsPerMultiProcessor / prop.warpSize << " warps)";
1121 entry("threads per block") << prop.maxThreadsPerBlock;
1122 entry("concurrency") << Kokkos::DefaultExecutionSpace{}.concurrency();
1123 entry("registers per block") << prop.regsPerBlock;
1124 entry("shared memory per block") << prop.sharedMemPerBlock / 1024 << " KiB";
1125 entry("L2 cache") << prop.l2CacheSize / 1024 / 1024 << " MiB";
1126 entry("global memory") << prop.totalGlobalMem / 1024 / 1024 << " MiB";
1127#endif
1128
1129 stream << output.str() << std::endl;
1130 }
1131
1132
1133 template <typename Description, int dim, typename Number>
1134 void TimeLoop<Description, dim, Number>::print_info(const std::string &header)
1135 {
1136 if (mpi_ensemble_.world_rank() != 0)
1137 return;
1138
1139 std::cout << "[INFO] " << header << std::endl;
1140 }
1141
1142
1143 template <typename Description, int dim, typename Number>
1144 void
1145 TimeLoop<Description, dim, Number>::print_head(const std::string &header,
1146 const std::string &secondary,
1147 std::ostream &stream)
1148 {
1149 if (mpi_ensemble_.world_rank() != 0)
1150 return;
1151
1152 const int header_size = header.size();
1153 const auto padded_header =
1154 std::string(std::max(0, 34 - header_size) / 2, ' ') + header +
1155 std::string(std::max(0, 35 - header_size) / 2, ' ');
1156
1157 const int secondary_size = secondary.size();
1158 const auto padded_secondary =
1159 std::string(std::max(0, 34 - secondary_size) / 2, ' ') + secondary +
1160 std::string(std::max(0, 35 - secondary_size) / 2, ' ');
1161
1162 /* clang-format off */
1163 stream << "\n";
1164 stream << " ####################################################\n";
1165 stream << " #########" << padded_header << "#########\n";
1166 stream << " #########" << padded_secondary << "#########\n";
1167 stream << " ####################################################\n";
1168 stream << std::endl;
1169 /* clang-format on */
1170 }
1171
1172
1173 template <typename Description, int dim, typename Number>
1174 void TimeLoop<Description, dim, Number>::print_information(
1175 unsigned int timer_cycle,
1176 Number last_checkpoint,
1177 std::ostream &stream,
1178 bool final_time)
1179 {
1180 static const std::string backend_name = [] {
1181 const auto precision = [] {
1182 if constexpr (std::is_same_v<Number, double>)
1183 return std::string("FP64");
1184 else if constexpr (std::is_same_v<Number, float>)
1185 return std::string("FP32");
1186 else
1187 __builtin_trap();
1188 }();
1189
1190 constexpr auto simd_size = VectorizedArray<Number>::size();
1191
1192 if constexpr (have_separate_memory_spaces)
1193 return "GPU, " + precision + ", warp size " + std::to_string(warp_size);
1194 else if constexpr (simd_size == 1)
1195 return "scalar, " + precision;
1196 else
1197 return "SIMD, " + precision + ", width " + std::to_string(simd_size);
1198 }();
1199
1200 stream << "Information: (HYP) " << hyperbolic_system_.get().problem_name;
1201 if constexpr (!ParabolicSystem::is_identity) {
1202 stream << "\n (PAR) " << parabolic_system_.get().problem_name;
1203 }
1204 stream << "\n [" << base_name_ << "] ";
1205 if (mpi_ensemble_.n_ensembles() > 1) {
1206 stream << mpi_ensemble_.n_ensembles() << " ensembles ";
1207 }
1208 stream << "with " << n_global_dofs_ << " Qdofs on "
1209 << mpi_ensemble_.n_world_ranks() << " ranks "
1210#if defined(WITH_OPENMP)
1211 << "/ " << omp_get_max_threads() << " threads "
1212#ifndef WITH_DEAL_II_THREADS
1213 << "[serial dealii] "
1214#endif
1215#elif defined(WITH_DEAL_II_THREADS)
1216 << "/ " << MultithreadInfo::n_threads() << " threads "
1217#endif
1218 << "<" << backend_name << ">\n";
1219
1220 stream << " Last output cycle " //
1221 << timer_cycle - 1 //
1222 << " at t = " << timer_granularity_ * (timer_cycle - 1) //
1223 << " [ log ";
1224
1225 if (enable_output_full_)
1226 stream << "full ";
1227 if (enable_output_levelsets_)
1228 stream << "levelsets ";
1229 if (enable_compute_quantities_)
1230 stream << "quantities ";
1231
1232 stream << "]\n";
1233
1234 if (checkpoint_update_interval_ != Number(0.)) {
1235 const auto wall_time = Utilities::MPI::min_max_avg(
1236 ComputingTimer::timer("time loop").wall_time(),
1237 mpi_ensemble_.world_communicator());
1238
1239 if (final_time) {
1240 stream << " Last checkpoint at FINAL TIME\n";
1241 } else {
1242 stream << " Last checkpoint at wall time " //
1243 << std::setprecision(2) << std::fixed << last_checkpoint //
1244 << "s (" << std::setprecision(0)
1245 << std::max(0., wall_time.max - last_checkpoint)
1246 << "s ago, interval " << checkpoint_update_interval_ << "s)\n";
1247 }
1248 }
1249 }
1250
1251
1252 template <typename Description, int dim, typename Number>
1253 void TimeLoop<Description, dim, Number>::print_memory_statistics(
1254 std::ostream &stream)
1255 {
1256 Utilities::System::MemoryStats stats;
1257 Utilities::System::get_memory_stats(stats);
1258
1259 Utilities::MPI::MinMaxAvg data = Utilities::MPI::min_max_avg(
1260 stats.VmRSS / 1024., mpi_ensemble_.world_communicator());
1261
1262 if (mpi_ensemble_.world_rank() != 0)
1263 return;
1264
1265 std::ostringstream output;
1266
1267 unsigned int n =
1268 dealii::Utilities::needed_digits(mpi_ensemble_.n_world_ranks());
1269
1270 output << "\nMemory: [MiB]" //
1271 << std::setw(8) << data.min //
1272 << " [p" << std::setw(n) << data.min_index << "] " //
1273 << std::setw(8) << data.avg << " " //
1274 << std::setw(8) << data.max //
1275 << " [p" << std::setw(n) << data.max_index << "]"; //
1276
1277 stream << output.str() << std::endl;
1278 }
1279
1280
1281 template <typename Description, int dim, typename Number>
1282 void TimeLoop<Description, dim, Number>::print_timers(std::ostream &stream)
1283 {
1284 std::vector<std::ostringstream> output(ComputingTimer::timers().size());
1285
1286 const auto equalize = [&]() {
1287 const auto ptr =
1288 std::max_element(output.begin(),
1289 output.end(),
1290 [](const auto &left, const auto &right) {
1291 return left.str().length() < right.str().length();
1292 });
1293 const auto length = ptr->str().length();
1294 for (auto &it : output)
1295 it << std::string(length - it.str().length() + 1, ' ');
1296 };
1297
1298 const auto print_wall_time = [&](auto &timer, auto &stream) {
1299 const auto wall_time = Utilities::MPI::min_max_avg(
1300 timer.wall_time(), mpi_ensemble_.world_communicator());
1301
1302 constexpr auto eps = std::numeric_limits<double>::epsilon();
1303 /*
1304 * Cut off at 99.9% to avoid silly percentages cluttering up the
1305 * output.
1306 */
1307 const auto skew_negative = std::max(
1308 100. * (wall_time.min - wall_time.avg) / wall_time.avg - eps, -99.9);
1309 const auto skew_positive = std::min(
1310 100. * (wall_time.max - wall_time.avg) / wall_time.avg + eps, 99.9);
1311
1312 stream << std::setprecision(2) << std::fixed << std::setw(9)
1313 << wall_time.avg << "s [sk: " << std::setprecision(1)
1314 << std::setw(5) << std::fixed << skew_negative << "%/"
1315 << std::setw(4) << std::fixed << skew_positive << "%]";
1316 unsigned int n =
1317 dealii::Utilities::needed_digits(mpi_ensemble_.n_world_ranks());
1318 stream << " [p" << std::setw(n) << wall_time.min_index << "/"
1319 << wall_time.max_index << "]";
1320 };
1321
1322 const auto cpu_time_statistics = Utilities::MPI::min_max_avg(
1323 ComputingTimer::timer("time loop").cpu_time(),
1324 mpi_ensemble_.world_communicator());
1325 const double total_cpu_time = cpu_time_statistics.sum;
1326
1327 const auto print_cpu_time =
1328 [&](auto &timer, auto &stream, bool percentage) {
1329 const auto cpu_time = Utilities::MPI::min_max_avg(
1330 timer.cpu_time(), mpi_ensemble_.world_communicator());
1331
1332 stream << std::setprecision(2) << std::fixed << std::setw(12)
1333 << cpu_time.sum << "s ";
1334
1335 if (percentage)
1336 stream << "(" << std::setprecision(1) << std::setw(4)
1337 << 100. * cpu_time.sum / total_cpu_time << "%)";
1338 };
1339
1340 auto jt = output.begin();
1341 for (auto &it : ComputingTimer::timers())
1342 *jt++ << " " << it.first;
1343 equalize();
1344
1345 jt = output.begin();
1346 for (auto &it : ComputingTimer::timers())
1347 print_wall_time(it.second, *jt++);
1348 equalize();
1349
1350 jt = output.begin();
1351 bool compute_percentages = false;
1352 for (auto &it : ComputingTimer::timers()) {
1353 print_cpu_time(it.second, *jt++, compute_percentages);
1354 if (it.first.starts_with("time loop"))
1355 compute_percentages = true;
1356 }
1357 equalize();
1358
1359 if (mpi_ensemble_.world_rank() != 0)
1360 return;
1361
1362 stream << std::endl << "Timer statistics:\n";
1363 for (auto &it : output)
1364 stream << it.str() << std::endl;
1365 }
1366
1367
1368 template <typename Description, int dim, typename Number>
1369 void TimeLoop<Description, dim, Number>::print_throughput(
1370 unsigned int cycle, Number t, std::ostream &stream, bool final_time)
1371 {
1372 /*
1373 * Fixme: The global state kept in this function should be refactored
1374 * into its own class object.
1375 */
1376 static struct Data {
1377 unsigned int cycle = 0;
1378 double t = 0.;
1379 double cpu_time_sum = 0.;
1380 double cpu_time_avg = 0.;
1381 double cpu_time_min = 0.;
1382 double cpu_time_max = 0.;
1383 double wall_time = 0.;
1384 double device_time_sum = 0.;
1385 } previous, current;
1386
1387 static double time_per_second_exp = 0.;
1388
1389 /* Update statistics: */
1390
1391 {
1392 previous = current;
1393
1394 current.cycle = cycle;
1395 current.t = t;
1396
1397 const auto wall_time_statistics = Utilities::MPI::min_max_avg(
1398 ComputingTimer::timer("time loop").wall_time(),
1399 mpi_ensemble_.world_communicator());
1400 current.wall_time = wall_time_statistics.max;
1401
1402 const auto cpu_time_statistics = Utilities::MPI::min_max_avg(
1403 ComputingTimer::timer("time loop").cpu_time(),
1404 mpi_ensemble_.world_communicator());
1405 current.cpu_time_sum = cpu_time_statistics.sum;
1406 current.cpu_time_avg = cpu_time_statistics.avg;
1407 current.cpu_time_min = cpu_time_statistics.min;
1408 current.cpu_time_max = cpu_time_statistics.max;
1409
1410 if constexpr (have_separate_memory_spaces) {
1411 const auto device_time_statistics = Utilities::MPI::min_max_avg(
1412 DeviceTimer::seconds(), mpi_ensemble_.world_communicator());
1413 current.device_time_sum = device_time_statistics.sum;
1414 }
1415 }
1416
1417 if (final_time)
1418 previous = Data();
1419
1420 /* Take averages: */
1421
1422 double delta_cycles = current.cycle - previous.cycle;
1423 const double cycles_per_second =
1424 delta_cycles / (current.wall_time - previous.wall_time);
1425
1426 const auto efficiency = time_integrator_.efficiency();
1427 const auto n_dofs = static_cast<double>(n_global_dofs_);
1428
1429 double wall_m_dofs_per_sec = delta_cycles * n_dofs * efficiency / 1.e6 /
1430 (current.wall_time - previous.wall_time);
1431
1432 const double delta_time =
1433 (current.t - previous.t) / (current.cycle - previous.cycle);
1434 const double time_per_second =
1435 (current.t - previous.t) / (current.wall_time - previous.wall_time);
1436
1437 /* Print Jean-Luc and Martin metrics: */
1438
1439 std::ostringstream output;
1440
1441 /* clang-format off */
1442 output << std::endl;
1443
1444 output << "Throughput:" << std::endl;
1445
1446 if constexpr (have_separate_memory_spaces) {
1447 /* Write out some statistics about GPU utilization: */
1448
1449 const double delta_wall_time = current.wall_time - previous.wall_time;
1450 const double delta_device_time =
1451 current.device_time_sum - previous.device_time_sum;
1452
1453 const double utilization =
1454 delta_device_time / (n_devices_ * delta_wall_time);
1455
1456 const double device_m_dofs_per_sec =
1457 delta_cycles * n_dofs * efficiency / 1.e6 / delta_device_time;
1458 output << " GPU : "
1459 << std::setprecision(4) << std::fixed << device_m_dofs_per_sec
1460 << " MQ/s in compute kernels (on "
1461 << n_devices_ << (n_devices_ == 1 ? " device)" : " devices)")
1462 << std::endl;
1463
1464 output << " ["
1465 << std::setprecision(2) << std::fixed << delta_device_time
1466 << "s device / "
1467 << std::setprecision(2) << std::fixed << delta_wall_time
1468 << "s wall = "
1469 << std::setprecision(1) << std::fixed << 100. * utilization
1470 << "% utilization ]" << std::endl;
1471
1472 } else {
1473 /* Write out some statistics about CPU utilization: */
1474
1475 double cpu_m_dofs_per_sec = delta_cycles * n_dofs * efficiency / 1.e6 /
1476 (current.cpu_time_sum - previous.cpu_time_sum);
1477
1478 /* Determine whether we fudge the CPU timings: */
1479 const bool fudge_cpu_timings = terminal_correct_for_hypertreadhing_ &&
1480#if defined(WITH_OPENMP)
1481 (omp_get_max_threads() == 2);
1482#elif defined(WITH_DEAL_II_THREADS)
1483 (MultithreadInfo::n_threads() == 2);
1484#else
1485 false;
1486#endif
1487
1488 if (fudge_cpu_timings)
1489 cpu_m_dofs_per_sec *= 2.;
1490
1491 double cpu_time_skew = (current.cpu_time_max - current.cpu_time_min - //
1492 previous.cpu_time_max + previous.cpu_time_min) /
1493 delta_cycles;
1494 /* avoid printing small negative numbers: */
1495 cpu_time_skew = std::max(0., cpu_time_skew);
1496
1497 const double cpu_time_skew_percentage =
1498 cpu_time_skew * delta_cycles /
1499 (current.cpu_time_avg - previous.cpu_time_avg);
1500
1501 output << " "
1502 << (fudge_cpu_timings ? "CPU*: " : "CPU : ")
1503 << std::setprecision(4) << std::fixed << cpu_m_dofs_per_sec
1504 << " MQ/s ("
1505 << std::scientific << 1. / cpu_m_dofs_per_sec * 1.e-6
1506 << " s/Qdof/substep)" << std::endl;
1507
1508 output << " [cpu time skew: "
1509 << std::setprecision(2) << std::scientific << cpu_time_skew
1510 << "s/cycle ("
1511 << std::setprecision(1) << std::setw(4) << std::setfill(' ') << std::fixed
1512 << 100. * cpu_time_skew_percentage
1513 << "%)]" << std::endl;
1514 }
1515
1516 output << " WALL: "
1517 << std::setprecision(4) << std::fixed << wall_m_dofs_per_sec
1518 << " MQ/s ("
1519 << std::scientific << 1. / wall_m_dofs_per_sec * 1.e-6
1520 << " s/Qdof/substep) ("
1521 << std::setprecision(2) << std::fixed << cycles_per_second
1522 << " cycles/s)" << std::endl;
1523
1524 const auto &scheme = time_integrator_.time_stepping_scheme();
1525 output << " [ "
1526 << Patterns::Tools::Convert<TimeSteppingScheme>::to_string(scheme)
1527 << " with CFL = "
1528 << std::setprecision(2) << std::fixed << hyperbolic_module_.cfl()
1529 << " ("
1530 << std::setprecision(0) << std::fixed << hyperbolic_module_.n_restarts()
1531 << "/"
1532 << std::setprecision(0) << std::fixed << parabolic_module_.n_restarts()
1533 << " rsts) ("
1534 << std::setprecision(0) << std::fixed << hyperbolic_module_.n_warnings()
1535 << "/"
1536 << std::setprecision(0) << std::fixed << parabolic_module_.n_warnings()
1537 << " warn) ("
1538 << std::setprecision(0) << std::fixed << hyperbolic_module_.n_corrections()
1539 << "/"
1540 << std::setprecision(0) << std::fixed << parabolic_module_.n_corrections()
1541 << " corr) ]" << std::endl;
1542
1543 if constexpr (!ParabolicSystem::is_identity)
1544 parabolic_module_.print_solver_statistics(output);
1545
1546 output << " [ dt = "
1547 << std::scientific << std::setprecision(2) << delta_time
1548 << " ( "
1549 << time_per_second
1550 << " dt/s) ]" << std::endl;
1551 /* clang-format on */
1552
1553 /* And print an ETA: */
1554
1555 time_per_second_exp = 0.8 * time_per_second_exp + 0.2 * time_per_second;
1556 auto eta = static_cast<unsigned int>(std::max(t_final_ - t, Number(0.)) /
1557 time_per_second_exp);
1558
1559 output << "\n ETA : ";
1560
1561 const unsigned int days = eta / (24 * 3600);
1562 if (days > 0) {
1563 output << days << " d ";
1564 eta %= 24 * 3600;
1565 }
1566
1567 const unsigned int hours = eta / 3600;
1568 if (hours > 0) {
1569 output << hours << " h ";
1570 eta %= 3600;
1571 }
1572
1573 const unsigned int minutes = eta / 60;
1574 output << minutes << " min";
1575
1576 output << " (terminal update every " //
1577 << std::setprecision(2) << std::fixed << terminal_update_interval_
1578 << "s)";
1579
1580 if (mpi_ensemble_.world_rank() != 0)
1581 return;
1582
1583 stream << output.str() << std::endl;
1584 }
1585
1586
1587 template <typename Description, int dim, typename Number>
1588 void TimeLoop<Description, dim, Number>::print_cycle_statistics(
1589 unsigned int cycle,
1590 Number t,
1591 unsigned int timer_cycle,
1592 Number last_checkpoint,
1593 bool write_to_logfile,
1594 bool final_time)
1595 {
1596 std::ostringstream output;
1597
1598 /* Print header: */
1599
1600 std::ostringstream primary;
1601 if (final_time) {
1602 primary << "FINAL (cycle " << Utilities::int_to_string(cycle, 6) << ")";
1603 } else {
1604 primary << "Cycle " << Utilities::int_to_string(cycle, 6) //
1605 << " (" << std::fixed << std::setprecision(1) //
1606 << t / t_final_ * 100 << "%)";
1607 }
1608
1609 std::ostringstream secondary;
1610 secondary << "at time t = " << std::setprecision(8) << std::fixed << t;
1611
1612 print_head(primary.str(), secondary.str(), output);
1613
1614 /* Print information and statistics: */
1615
1616 print_information(timer_cycle, last_checkpoint, output, final_time);
1617 print_memory_statistics(output);
1618 print_timers(output);
1619 print_throughput(cycle, t, output, final_time);
1620
1621 /* Only output on rank 0: */
1622 if (mpi_ensemble_.world_rank() != 0)
1623 return;
1624
1625#ifndef DEBUG_OUTPUT
1626 std::cout << "\033[2J\033[H";
1627#endif
1628 std::cout << output.str() << std::flush;
1629
1630 if (write_to_logfile) {
1631 logfile_ << "\n" << output.str() << std::flush;
1632 }
1633 }
1634} // namespace ryujin
static dealii::Timer & timer(const std::string &section)
static const std::map< std::string, dealii::Timer > & timers()
static double seconds()
TimeLoop(const MPI_Comm &mpi_comm)
typename View::StateVector StateVector
Definition time_loop.h:55
constexpr unsigned int warp_size
Definition gpu.h:46
constexpr bool have_separate_memory_spaces
Definition gpu.h:29
void debug_poison_invalid_values(StateVector< Number, prob_dim, prec_dim > &state_vector, const OfflineData &offline_data)
dealii::LinearAlgebra::distributed::Vector< Number > ScalarHostVector
void print_revision_and_version(std::ostream &stream)