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