ryujin 2.1.1 revision 71cdc42292164f8095c0bd62c4ea75ebcfac58fa
Loading...
Searching...
No Matches
quantities.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 "loop.h"
9#include "quantities.h"
10
11#include <deal.II/base/function_parser.h>
12#include <deal.II/base/mpi.templates.h>
13
14#include <array>
15#include <fstream>
16#include <iomanip>
17#include <map>
18#include <sstream>
19
20namespace ryujin
21{
22 using namespace dealii;
23
24 namespace
25 {
31 template <typename Number>
32 void to_central_moments(Number *moments,
33 const unsigned int n_moments,
34 const unsigned int n_selected)
35 {
36 for (unsigned int c = 0; c < n_selected; ++c) {
37 const auto m = [&](const unsigned int k) -> Number & {
38 return moments[(k - 1) * n_selected + c];
39 };
40 const Number m1 = m(1);
41
42 /* Higher central moments are computed from raw moments first: */
43 if (n_moments >= 4)
44 m(4) +=
45 -4. * m1 * m(3) + 6. * m1 * m1 * m(2) - 3. * m1 * m1 * m1 * m1;
46 if (n_moments >= 3)
47 m(3) += -3. * m1 * m(2) + 2. * m1 * m1 * m1;
48 if (n_moments >= 2)
49 m(2) -= m1 * m1;
50 }
51 }
52 } // namespace
53
54
55 template <typename Description, int dim, typename Number>
57 const MPIEnsemble &mpi_ensemble,
58 const OfflineData<dim, Number> &offline_data,
59 const HyperbolicSystem &hyperbolic_system,
60 const ParabolicSystem &parabolic_system,
61 const InitialPrecomputedVector &initial_precomputed,
62 const std::string &subsection /*= "Quantities"*/)
63 : ParameterAcceptor(subsection)
64 , mpi_ensemble_(mpi_ensemble)
65 , offline_data_(&offline_data)
66 , extractor_(offline_data,
67 hyperbolic_system,
68 parabolic_system,
69 initial_precomputed)
70 , base_name_("")
71 , mesh_files_have_been_written_(false)
72 {
73 std::copy(std::begin(View::primitive_component_names),
74 std::end(View::primitive_component_names),
75 std::back_inserter(quantities_));
76
77 add_parameter("quantities",
78 quantities_,
79 "List of conserved, primitive, precomputed, initial, or "
80 "parabolic quantities for which statistics are accumulated "
81 "on all manifolds.");
82
83 n_moments_ = 2;
84 add_parameter("number of moments",
85 n_moments_,
86 "Number of moments computed for every selected quantity in "
87 "the time averaged and space averaged statistics: 1 (mean), "
88 "2 (mean and variance), 3 (additionally the third central "
89 "moment), 4 (additionally the fourth central moment).");
91 add_parameter("interior manifolds",
92 interior_manifolds_,
93 "List of level set functions describing interior manifolds. "
94 "The description is used to only output point values for "
95 "vertices belonging to a certain level set. "
96 "Format: '<name> : <level set formula> : <options> , [...] "
97 "(options: time_averaged, space_averaged, instantaneous)");
98
99 add_parameter("boundary manifolds",
100 boundary_manifolds_,
101 "List of level set functions describing boundary. The "
102 "description is used to only output point values for "
103 "boundary vertices belonging to a certain level set. "
104 "Format: '<name> : <level set formula> : <options> , [...] "
105 "(options: time_averaged, space_averaged, instantaneous)");
106
107 clear_temporal_statistics_on_writeout_ = true;
108 add_parameter("clear statistics on writeout",
109 clear_temporal_statistics_on_writeout_,
110 "If set to true then all temporal statistics (for "
111 "\"time_averaged\" quantities) accumulated so far are reset "
112 "each time a writeout of quantities is performed");
113 }
114
115
116 template <typename Description, int dim, typename Number>
118 {
119#ifdef DEBUG_OUTPUT
120 std::cout << "Quantities<dim, Number>::prepare()" << std::endl;
121#endif
122
123 base_name_ = name;
124
125 extractor_.prepare(quantities_);
126
127 AssertThrow(1 <= n_moments_ && n_moments_ <= 4,
128 dealii::ExcMessage("Invalid number of moments: \"" +
129 std::to_string(n_moments_) +
130 "\" is not in the range 1 to 4."));
131
132 const unsigned int n_owned = offline_data_->n_locally_owned();
133 const auto sparsity_simd_view =
134 offline_data_->sparsity_pattern_simd().view();
135 const auto lumped_mass_matrix_view =
136 offline_data_->lumped_mass_matrix().view();
137
138 /*
139 * Create a manifold record with the name and parsed options for every
140 * entry of the parameter lists:
141 */
142
143 const auto create_manifold = [](const auto &entry, const bool boundary) {
144 const auto &[name, expression, options] = entry;
145
146 Manifold manifold;
147 manifold.name = name;
148 manifold.boundary = boundary;
149 manifold.instantaneous =
150 options.find("instantaneous") != std::string::npos;
151 manifold.time_averaged =
152 options.find("time_averaged") != std::string::npos;
153 manifold.space_averaged =
154 options.find("space_averaged") != std::string::npos;
155
156 AssertThrow(manifold.instantaneous || manifold.time_averaged ||
157 manifold.space_averaged,
158 dealii::ExcMessage(
159 "Invalid options \"" + options + "\" for manifold \"" +
160 name +
161 "\": at least one of instantaneous, time_averaged, or "
162 "space_averaged has to be selected."));
163
164 return manifold;
165 };
166
167 /*
168 * Sort the points of a manifold lexicographically by position (and
169 * normal) so that the output does not depend on the local numbering
170 * of degrees of freedom:
171 */
172
173 const auto sort_points = [](std::vector<ManifoldPoint> &points) {
174 const auto less = [](const auto &left, const auto &right) {
175 for (unsigned int d = 0; d < dim; ++d)
176 if (left[d] != right[d])
177 return left[d] < right[d];
178 return false;
179 };
180 std::sort(
181 points.begin(), points.end(), [&](const auto &a, const auto &b) {
182 const auto &[i_a, n_a, nm_a, m_a, id_a, x_a] = a;
183 const auto &[i_b, n_b, nm_b, m_b, id_b, x_b] = b;
184 return less(x_a, x_b) || (!less(x_b, x_a) && less(n_a, n_b));
185 });
186 };
187
188 /*
189 * Sort the points and populate the mirrored index and mass arrays
190 * used in the compute loops:
191 */
192
193 const auto finalize = [&](Manifold &manifold) {
194 sort_points(manifold.points);
195
196 const auto n_points = manifold.points.size();
197 manifold.indices.reinit(n_points, TransferPolicy::implicit_transfers);
198 manifold.masses.reinit(n_points, TransferPolicy::implicit_transfers);
199
200 auto *indices = manifold.indices.view();
201 auto *masses = manifold.masses.view();
202 Number mass_sum = Number(0.);
203 for (std::size_t p = 0; p < n_points; ++p) {
204 indices[p] = std::get<0>(manifold.points[p]);
205 masses[p] = std::get<3>(manifold.points[p]);
206 mass_sum += masses[p];
207 }
208
209 manifold.mass_sum =
210 Utilities::MPI::sum(mass_sum, mpi_ensemble_.ensemble_communicator());
211
212 manifolds_.push_back(std::move(manifold));
213 };
214
215 manifolds_.clear();
216
217 /*
218 * Create interior manifolds: We have to loop over all cells and
219 * collect all degrees of freedom satisfying the level set condition.
220 */
221
222 for (const auto &entry : interior_manifolds_) {
223 auto manifold = create_manifold(entry, /*boundary*/ false);
224 FunctionParser<dim> level_set_function(std::get<1>(entry));
225
226 const auto &discretization = offline_data_->discretization();
227 const auto &dof_handler = offline_data_->dof_handler();
228
229 const auto support_points =
230 dof_handler.get_fe().get_unit_support_points();
231
232 std::vector<dealii::types::global_dof_index> local_dof_indices;
233
234 /* We use a map to deduplicate the collected points: */
235 std::map<unsigned int, ManifoldPoint> preliminary_map;
236
237 for (auto cell : dof_handler.active_cell_iterators()) {
238 if (!cell->is_locally_owned())
239 continue;
240
241 const unsigned int dofs_per_cell = cell->get_fe().n_dofs_per_cell();
242 local_dof_indices.resize(dofs_per_cell);
243 cell->get_active_or_mg_dof_indices(local_dof_indices);
244
245 const auto &mapping = discretization.mapping()[cell->active_fe_index()];
246
247 for (unsigned int j = 0; j < dofs_per_cell; ++j) {
248 const Point<dim> position =
249 mapping.transform_unit_to_real_cell(cell, support_points[j]);
250
251 if (std::abs(level_set_function.value(position)) > 1.e-12)
252 continue;
253
254 const auto global_index = local_dof_indices[j];
255 const auto index =
256 offline_data_->scalar_partitioner()->global_to_local(
257 global_index);
258
259 /* Skip constrained degrees of freedom: */
260 if (sparsity_simd_view.row_length(index) == 1)
261 continue;
262
263 if (index >= n_owned)
264 continue;
265
266 const Number mass = lumped_mass_matrix_view.read_entry(index);
267 preliminary_map[index] = {index,
268 dealii::Tensor<1, dim, Number>(),
269 Number(0.),
270 mass,
271 dealii::numbers::internal_face_boundary_id,
272 position};
273 }
274 }
275
276 for (const auto &[index, point] : preliminary_map)
277 manifold.points.push_back(point);
278
279 finalize(manifold);
280 }
281
282 /*
283 * Create boundary manifolds: We loop over the boundary map and collect
284 * all degrees of freedom satisfying the level set condition.
285 */
286
287 for (const auto &entry : boundary_manifolds_) {
288 auto manifold = create_manifold(entry, /*boundary*/ true);
289 FunctionParser<dim> level_set_function(std::get<1>(entry));
290
291 for (const auto &point : offline_data_->boundary_map()) {
292 const auto &i = std::get<0>(point);
293
294 /* skip nonlocal */
295 if (i >= n_owned)
296 continue;
297
298 /* skip constrained */
299 if (offline_data_->affine_constraints().is_constrained(
300 offline_data_->scalar_partitioner()->local_to_global(i)))
301 continue;
302
303 const auto &position = std::get<5>(point);
304 if (std::abs(level_set_function.value(position)) < 1.e-12)
305 manifold.points.push_back(point);
306 }
307
308 finalize(manifold);
309 }
310
311 /* Clear statistics: */
312 clear_statistics();
313
314 /* Make sure we output new mesh files: */
315 mesh_files_have_been_written_ = false;
316 }
317
318
319 template <typename Description, int dim, typename Number>
321 {
322 return n_moments_ * extractor_.n_selected();
323 }
324
325
326 template <typename Description, int dim, typename Number>
327 std::string
328 Quantities<Description, dim, Number>::header(const bool averaged) const
329 {
330 static const std::array<std::string, 4> labels{
331 "mean", "var", "mu_3", "mu_4"};
332
333 std::string result;
334 for (unsigned int k = 0; k < (averaged ? n_moments_ : 1); ++k)
335 for (const auto &name : quantities_)
336 result += (result.empty() ? "" : "\t") +
337 (averaged ? labels[k] + "(" + name + ")" : name);
338 return result + "\n";
339 }
340
341
342 template <typename Description, int dim, typename Number>
344 const StateVector &state_vector, const Number t)
345 {
346#ifdef DEBUG_OUTPUT
347 std::cout << "Quantities<dim, Number>::accumulate()" << std::endl;
348#endif
349
350 using MemorySpace = selected_memory_space_t;
351
352 extractor_.template prepare_extraction<MemorySpace>(state_vector);
353
354 for (auto &manifold : manifolds_) {
355 /* skip if we don't average in space or time: */
356 if (!manifold.time_averaged && !manifold.space_averaged)
357 continue;
358
359 std::swap(manifold.t_old, manifold.t_new);
360 std::swap(manifold.old, manifold.current);
361
362 /* accumulate new values */
363
364 auto spatial_average = internal_accumulate(manifold);
365
366 /* Average in time with trapezoidal rule: */
367
368 if (RYUJIN_UNLIKELY(manifold.t_old == Number(0.) &&
369 manifold.t_new == Number(0.))) {
370 /* We have not accumulated any statistics yet: */
371 manifold.t_old = t - 1.;
372 manifold.t_new = t;
373
374 } else {
375
376 manifold.t_new = t;
377 const Number tau = manifold.t_new - manifold.t_old;
378 const Number weight = 0.5 * tau;
379
380 const auto *old =
381 std::as_const(manifold.old).template view<MemorySpace>();
382 const auto *current =
383 std::as_const(manifold.current).template view<MemorySpace>();
384 auto *sum = manifold.sum.template view<MemorySpace>();
385
386 const auto body = [=](auto sentinel, const unsigned int j) {
387 using T = decltype(sentinel);
388 if constexpr (std::is_same_v<T, dealii::VectorizedArray<Number>>) {
389 T s, o, c;
390 s.load(sum + j);
391 o.load(old + j);
392 c.load(current + j);
393 s += weight * (o + c);
394 s.store(sum + j);
395 } else {
396 sum[j] += weight * (old[j] + current[j]);
397 }
398 };
399
400 const auto n_entries = static_cast<unsigned int>(manifold.sum.size());
401 loop<MemorySpace, Number>(
402 "quantities_trapezoidal_rule", body, 0, n_entries, n_entries);
403
404 manifold.t_sum += tau;
405 }
406
407 /* Record average in space: */
408 to_central_moments(
409 spatial_average.data(), n_moments_, extractor_.n_selected());
410 manifold.time_series.emplace_back(t, std::move(spatial_average));
411 }
412 }
413
414
415 template <typename Description, int dim, typename Number>
417 const StateVector &state_vector, const Number t, unsigned int cycle)
418 {
419#ifdef DEBUG_OUTPUT
420 std::cout << "Quantities<dim, Number>::write_out()" << std::endl;
421#endif
422
423 /*
424 * First, write out mesh files if this hasn't happened yet.
425 */
426 if (!mesh_files_have_been_written_) {
427 write_mesh_files(cycle);
428 mesh_files_have_been_written_ = true;
429 }
430
431 /*
432 * Manifolds that are only output instantaneously have not been
433 * evaluated in accumulate(). Prepare the extractor if we have to
434 * evaluate any of them:
435 */
436
437 if (std::any_of(manifolds_.begin(), manifolds_.end(), [](const auto &m) {
438 return m.instantaneous && !m.time_averaged && !m.space_averaged;
439 }))
440 extractor_.template prepare_extraction<selected_memory_space_t>(
441 state_vector);
442
443 /*
444 * Next write out instantaneous and time_averaged maps, and flush the
445 * space_averaged values to the corresponding log files:
446 */
447
448 for (auto &manifold : manifolds_) {
449 const auto prefix = base_name_ + "-" + manifold.name + "-R" +
450 Utilities::to_string(cycle, 4);
451
452 /*
453 * Compute and output instantaneous field:
454 */
455
456 if (manifold.instantaneous) {
457 const std::string file_name = prefix + "-instantaneous.dat";
458
459 std::stringstream time_stamp;
460 time_stamp << std::scientific << std::setprecision(14);
461 time_stamp << "# at t = " << t << std::endl;
462
463 /* We have not computed any updated statistics yet: */
464
465 if (!manifold.time_averaged && !manifold.space_averaged)
466 internal_accumulate(manifold);
467 else
468 AssertThrow(manifold.t_new == t, dealii::ExcInternalError());
469
470 internal_write_out(file_name,
471 time_stamp.str(),
472 manifold.current,
473 Number(1.),
474 /*averaged*/ false);
475 }
476
477 /*
478 * Output time averaged field:
479 */
480
481 if (manifold.time_averaged) {
482 const std::string file_name = prefix + "-time_averaged.dat";
483
484 /* Check whether we have accumulated any statistics yet: */
485 if (manifold.t_sum != Number(0.)) {
486 std::stringstream time_stamp;
487 time_stamp << std::scientific << std::setprecision(14);
488 time_stamp << "# averaged from t = "
489 << manifold.t_new - manifold.t_sum
490 << " to t = " << manifold.t_new << std::endl;
491
492 internal_write_out(file_name,
493 time_stamp.str(),
494 manifold.sum,
495 Number(1.) / manifold.t_sum,
496 /*averaged*/ true);
497 }
498 }
499
500 /*
501 * Output space averaged field:
502 */
503
504 if (manifold.space_averaged) {
505 /* Write to a new time series file after every call to prepare(): */
506 bool append = true;
507 if (!manifold.time_series_cycle.has_value()) {
508 manifold.time_series_cycle = cycle;
509 append = false;
510 }
511
512 const auto file_name =
513 base_name_ + "-" + manifold.name + "-R" +
514 Utilities::to_string(manifold.time_series_cycle.value(), 4) +
515 "-space_averaged_time_series.dat";
516
517 internal_write_out_time_series(
518 file_name, manifold.time_series, /*append*/ append);
519 manifold.time_series.clear();
520 }
521 }
522
523 if (clear_temporal_statistics_on_writeout_)
524 clear_statistics();
525 }
526
527
528 template <typename Description, int dim, typename Number>
529 void
531 {
532 for (const auto &manifold : manifolds_) {
533 /* Skip outputting the point map for spatial averages. */
534 if (!manifold.instantaneous && !manifold.time_averaged)
535 continue;
536
537 /*
538 * Gather the point maps of all MPI ranks on the root rank, which
539 * then writes out the file.
540 *
541 * FIXME: This serializes the output on a single rank. Ideally, we
542 * should do MPI IO with all ranks participating.
543 */
544
545 const auto received = Utilities::MPI::gather(
546 mpi_ensemble_.ensemble_communicator(), manifold.points);
547
548 if (Utilities::MPI::this_mpi_process(
549 mpi_ensemble_.ensemble_communicator()) != 0)
550 continue;
551
552 std::ofstream output(base_name_ + "-" + manifold.name + "-R" +
553 Utilities::to_string(cycle, 4) + "-points.dat");
554
555 output << std::scientific << std::setprecision(14);
556
557 if (manifold.boundary)
558 output << "#\n# position\tnormal\tnormal mass\tboundary mass\n";
559 else
560 output << "#\n# position\tinterior mass\n";
561
562 unsigned int rank = 0;
563 for (const auto &entries : received) {
564 output << "# rank " << rank++ << "\n";
565 for (const auto &entry : entries) {
566 const auto &[index, n_i, nm_i, m_i, id, x_i] = entry;
567 if (manifold.boundary)
568 output << x_i << "\t" << n_i << "\t" << nm_i << "\t" << m_i << "\n";
569 else
570 output << x_i << "\t" << m_i << "\n";
571 } /*entry*/
572 } /*entries*/
573
574 output << std::flush;
575 }
576 }
577
578
579 template <typename Description, int dim, typename Number>
580 void Quantities<Description, dim, Number>::clear_statistics()
581 {
582 using MemorySpace = selected_memory_space_t;
583
584 for (auto &manifold : manifolds_) {
585 /* reinit() zero initializes the arrays on the selected memory space: */
586 const auto n_entries = manifold.points.size() * stride();
587 for (auto *values : {&manifold.old, &manifold.current, &manifold.sum})
588 values->reinit(
589 n_entries, TransferPolicy::implicit_transfers, MemorySpace{});
590
591 manifold.t_old = manifold.t_new = manifold.t_sum = 0.;
592 manifold.time_series.clear();
593 }
594 }
595
596
597 template <typename Description, int dim, typename Number>
598 std::vector<Number>
599 Quantities<Description, dim, Number>::internal_accumulate(Manifold &manifold)
600 {
601 using MemorySpace = selected_memory_space_t;
602
603 const auto extractor_view = extractor_.template view<MemorySpace>();
604 const unsigned int n_selected = extractor_.n_selected();
605 const unsigned int n_moments = n_moments_;
606 const unsigned int stride = this->stride();
607 const auto n_points = static_cast<unsigned int>(manifold.points.size());
608
609 const auto *indices =
610 std::as_const(manifold.indices).template view<MemorySpace>();
611 const auto *masses =
612 std::as_const(manifold.masses).template view<MemorySpace>();
613 auto *current = manifold.current.template view<MemorySpace>();
614
615 /*
616 * Extract the values of all selected quantities of a point directly
617 * into the first moment block, compute all higher raw moments (i.e.,
618 * powers of the values), and return the mass weighted values as
619 * contribution to the spatial sums:
620 */
621
622 const auto body = [=](auto /*sentinel*/, const unsigned int p) {
623 auto *values = current + p * stride;
624 extractor_view.extract_element(values, indices[p]);
625
626 for (unsigned int k = 1; k < n_moments; ++k)
627 for (unsigned int c = 0; c < n_selected; ++c)
628 values[k * n_selected + c] =
629 values[(k - 1) * n_selected + c] * values[c];
630
631 const auto mass = masses[p];
632 return [=](const unsigned int j) { return mass * values[j]; };
633 };
634
635 std::vector<Number> spatial_average(stride, Number(0.));
636 reduction_loop<MemorySpace>(
637 "quantities_accumulate",
638 body,
639 ArrayReducer<Kokkos::Sum<Number>>(spatial_average),
640 0,
641 n_points);
642
643 /* Sum over all MPI ranks and take the average: */
644
645 Utilities::MPI::sum(spatial_average,
646 mpi_ensemble_.ensemble_communicator(),
647 spatial_average);
648
649 for (auto &it : spatial_average)
650 it /= manifold.mass_sum;
651
652 return spatial_average;
653 }
654
655
656 template <typename Description, int dim, typename Number>
657 void Quantities<Description, dim, Number>::internal_write_out(
658 const std::string &file_name,
659 const std::string &time_stamp,
660 const Mirrored<Number *> &values,
661 const Number scale,
662 const bool averaged)
663 {
664 /*
665 * Gather the values of all MPI ranks on the root rank, which then
666 * writes out the file. The (read only) host view triggers a transfer
667 * from the device if necessary.
668 *
669 * FIXME: This serializes the output on a single rank. Ideally, we
670 * should do MPI IO with all ranks participating.
671 */
672
673 const auto *data = values.view();
674 auto received =
675 Utilities::MPI::gather(mpi_ensemble_.ensemble_communicator(),
676 std::vector<Number>(data, data + values.size()));
677
678 if (Utilities::MPI::this_mpi_process(
679 mpi_ensemble_.ensemble_communicator()) != 0)
680 return;
681
682 const unsigned int n_selected = extractor_.n_selected();
683 const unsigned int stride = this->stride();
684
685 /* For instantaneous values we only output the first moment: */
686 const unsigned int n_columns = averaged ? stride : n_selected;
687
688 std::ofstream output(file_name);
689 output << std::scientific << std::setprecision(14);
690 output << time_stamp << "# " << header(averaged);
691
692 unsigned int rank = 0;
693 for (auto &entries : received) {
694 output << "# rank " << rank++ << "\n";
695 for (std::size_t j = 0; j < entries.size(); j += stride) {
696 auto *point = entries.data() + j;
697
698 if (averaged) {
699 for (unsigned int m = 0; m < stride; ++m)
700 point[m] *= scale;
701 to_central_moments(point, n_moments_, n_selected);
702 }
703
704 for (unsigned int m = 0; m < n_columns; ++m)
705 output << (m == 0 ? "" : "\t") << point[m];
706 output << "\n";
707 }
708 }
709
710 output << std::flush;
711 }
712
713
714 template <typename Description, int dim, typename Number>
715 void Quantities<Description, dim, Number>::internal_write_out_time_series(
716 const std::string &file_name,
717 const std::vector<std::pair<Number, std::vector<Number>>> &values,
718 bool append)
719 {
720 if (Utilities::MPI::this_mpi_process(
721 mpi_ensemble_.ensemble_communicator()) != 0)
722 return;
723
724 std::ofstream output;
725 output << std::scientific << std::setprecision(14);
726
727 if (append) {
728 output.open(file_name, std::ofstream::out | std::ofstream::app);
729 } else {
730 output.open(file_name, std::ofstream::out | std::ofstream::trunc);
731 output << "# time t\t" << header(/*averaged*/ true);
732 }
733
734 for (const auto &[t, entry] : values) {
735 output << t;
736 for (const auto &value : entry)
737 output << "\t" << value;
738 output << "\n";
739 }
740
741 output << std::flush;
742 }
743
744} /* namespace ryujin */
typename Description::HyperbolicSystem HyperbolicSystem
Definition quantities.h:55
typename View::InitialPrecomputedVector InitialPrecomputedVector
Definition quantities.h:61
typename Description::ParabolicSystem ParabolicSystem
Definition quantities.h:56
void write_out(const StateVector &state_vector, const Number t, unsigned int cycle)
typename View::StateVector StateVector
Definition quantities.h:60
Quantities(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system, const InitialPrecomputedVector &initial_precomputed, const std::string &subsection="/Quantities")
void accumulate(const StateVector &state_vector, const Number t)
void prepare(const std::string &name)
std::conditional_t< have_separate_memory_spaces, dealii::MemorySpace::Default, dealii::MemorySpace::Host > selected_memory_space_t
Definition gpu.h:65
#define RYUJIN_UNLIKELY(x)