ryujin 2.5.0 revision 3a6c8d57210175dea14d8116e1fe878f349f8c1c
Loading...
Searching...
No Matches
postprocessor.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3// Copyright (C) 2026 by the ryujin authors
4//
5
6#pragma once
7
8#include <ryujin/base/compile_time_options.h>
9#include <ryujin/base/gpu.h>
10#include <ryujin/base/loop.h>
12#include <ryujin/base/simd.h>
17
18#include <deal.II/base/exceptions.h>
19#include <deal.II/base/tensor.h>
20
21#include <Kokkos_Pair.hpp>
22
23#include <algorithm>
24#include <cstdint>
25#include <optional>
26#include <string>
27#include <tuple>
28#include <utility>
29#include <vector>
30
31namespace ryujin
32{
50 template <typename Description, int dim, typename Number>
51 class Postprocessor final : public Interface::Postprocessor<dim, Number>
52 {
53 public:
54 using HyperbolicSystem = typename Description::HyperbolicSystem;
55
57
62 const HyperbolicSystem &hyperbolic_system,
63 const Interface::ParabolicSystem &parabolic_system,
64 const MultiComponentVector<Number> &initial_precomputed);
65
66 Selection prepare(const std::vector<std::string> &selected) const override;
67
68 std::vector<ScalarHostVector<Number>>
69 extract(const Selection &selection,
70 const StateVector<Number> &state_vector) const override;
71
72 void extract(const Selection &selection,
73 const StateVector<Number> &state_vector,
74 const unsigned int *indices,
75 const unsigned int n_points,
76 Number *values) const override;
77
78 private:
79 using View = typename HyperbolicSystem::template View<dim, Number>;
80
81 template <int n_comp, typename MemorySpace>
82 using VectorView =
84
88 enum class Section : std::uint8_t {
89 state,
90 primitive,
92 initial_precomputed,
94 };
95
99 enum class Operation : std::uint8_t { value, schlieren, vorticity };
100
110 template <typename MemorySpace>
111 auto reader(const Selection &selection,
112 const StateVector<Number> &state_vector) const;
113
114 dealii::ObserverPointer<const Interface::OfflineData<dim, Number>>
115 offline_data_;
116 dealii::ObserverPointer<const HyperbolicSystem> hyperbolic_system_;
117 dealii::ObserverPointer<const Interface::ParabolicSystem> parabolic_system_;
118 const MultiComponentVector<Number> &initial_precomputed_;
119 };
120
121
122#ifndef DOXYGEN
123 /*
124 * -------------------------------------------------------------------------
125 * Inline function definitions
126 * -------------------------------------------------------------------------
127 */
128
129
130 template <typename Description, int dim, typename Number>
132 const Interface::OfflineData<dim, Number> &offline_data,
133 const HyperbolicSystem &hyperbolic_system,
134 const Interface::ParabolicSystem &parabolic_system,
135 const MultiComponentVector<Number> &initial_precomputed)
136 : offline_data_(&offline_data)
137 , hyperbolic_system_(&hyperbolic_system)
138 , parabolic_system_(&parabolic_system)
139 , initial_precomputed_(initial_precomputed)
140 {
141 }
142
143
144 template <typename Description, int dim, typename Number>
146 const std::vector<std::string> &selected) const -> Selection
147 {
148 const auto &hyperbolic_system = *hyperbolic_system_;
149
150 Assert(hyperbolic_system.state_names(dim).size() ==
151 View::problem_dimension &&
152 hyperbolic_system.primitive_names(dim).size() ==
153 View::problem_dimension &&
154 hyperbolic_system.precomputed_names(dim).size() ==
155 View::n_precomputed_values &&
156 hyperbolic_system.initial_precomputed_names(dim).size() ==
157 View::n_initial_precomputed_values,
158 dealii::ExcMessage("The component names of the hyperbolic system "
159 "do not match the counts of its view."));
160
161 const std::vector<std::pair<Section, const std::vector<std::string> *>>
162 sections{
163 {Section::state, &hyperbolic_system.state_names(dim)},
164 {Section::primitive, &hyperbolic_system.primitive_names(dim)},
165 {Section::precomputed, &hyperbolic_system.precomputed_names(dim)},
166 {Section::initial_precomputed,
167 &hyperbolic_system.initial_precomputed_names(dim)},
168 {Section::dynamic_state, &parabolic_system_->state_names(dim)},
169 };
170
171 /*
172 * Look up a component name and return its section, its index, and
173 * the number of components of the section:
174 */
175 const auto find = [&](const std::string &name)
176 -> std::optional<std::tuple<Section, unsigned int, unsigned int>> {
177 for (const auto &[section, names] : sections) {
178 const auto pos = std::find(names->begin(), names->end(), name);
179 if (pos != names->end())
180 return std::make_tuple(
181 section,
182 static_cast<unsigned int>(pos - names->begin()),
183 static_cast<unsigned int>(names->size()));
184 }
185 return std::nullopt;
186 };
187
188 const std::vector<std::pair<Operation, std::string>> prefixes{
189 {Operation::schlieren, "schlieren_"},
190 {Operation::vorticity, "vorticity_"},
191 };
192
193 Selection selection;
194
195 for (const auto &entry : selected) {
196 auto operation = Operation::value;
197 auto found = find(entry);
198
199 for (const auto &[prefix_operation, prefix] : prefixes) {
200 if (found || entry.rfind(prefix, 0) != 0)
201 continue;
202 operation = prefix_operation;
203 found = find(entry.substr(prefix.size()));
204 }
205
206 AssertThrow(found,
207 dealii::ExcMessage(
208 "Invalid component name: \"" + entry +
209 "\" is not a valid conserved, primitive, precomputed, "
210 "initial, parabolic, or postprocessed "
211 "component name."));
212
213 const auto [section, index, n_names] = *found;
214 const unsigned int n_operands =
215 operation == Operation::vorticity ? dim : 1;
216
217 AssertThrow(operation != Operation::vorticity || dim > 1,
218 dealii::ExcMessage("Invalid component name: \"" + entry +
219 "\": the vorticity is not defined in "
220 "one spatial dimension."));
221 AssertThrow(index + n_operands <= n_names,
222 dealii::ExcMessage("Invalid component name: \"" + entry +
223 "\": the vorticity requires dim "
224 "consecutive components."));
225
226 selection.quantities.push_back(
227 {std::uint8_t(operation),
228 static_cast<unsigned int>(selection.components.size())});
229 for (unsigned int d = 0; d < n_operands; ++d)
230 selection.components.push_back({std::uint8_t(section), index + d});
231 }
232
233 return selection;
234 }
235
236
237 template <typename Description, int dim, typename Number>
238 template <typename MemorySpace>
239 auto Postprocessor<Description, dim, Number>::reader(
240 const Selection &selection, const StateVector<Number> &state_vector) const
241 {
242 const auto &selected = selection.components;
243 const auto selects = [&](const Section section) {
244 return std::any_of(selected.begin(), selected.end(), [&](const auto &it) {
245 return Section(it.first) == section;
246 });
247 };
248
249 const bool read_primitive = selects(Section::primitive);
250 const bool read_state = read_primitive || selects(Section::state);
251 const bool read_precomputed = selects(Section::precomputed);
252 const bool read_initial = selects(Section::initial_precomputed);
253
254 /*
255 * Only set up views for vectors that we are actually reading from,
256 * and only ensure residency of those parts of the state vector:
257 */
258
259 VectorView<View::problem_dimension, MemorySpace> U_view;
260 VectorView<View::n_precomputed_values, MemorySpace> precomputed_view;
261 VectorView<View::n_initial_precomputed_values, MemorySpace> initial_view;
262
263 if (read_state) {
264 copy_to_memory_space<MemorySpace>(state_vector, StateVectorPart::state);
265 U_view = state_vector.state
266 .template view<View::problem_dimension, MemorySpace>();
267 }
268
269 if (read_precomputed) {
270 copy_to_memory_space<MemorySpace>(state_vector,
271 StateVectorPart::precomputed);
272 precomputed_view =
273 state_vector.precomputed
274 .template view<View::n_precomputed_values, MemorySpace>();
275 }
276
277 if (read_initial) {
278 copy_to_memory_space<MemorySpace>(initial_precomputed_);
279 initial_view =
280 initial_precomputed_
281 .template view<View::n_initial_precomputed_values, MemorySpace>();
282 }
283
284 /*
285 * Set up the components, and a view for every parabolic component, on
286 * the host, and copy them into arrays residing on the memory space:
287 */
288
289 const auto n_components = selected.size();
290 Kokkos::View<Kokkos::pair<Section, unsigned int> *, Kokkos::HostSpace>
291 host_components("postprocessor_components", n_components);
292 Kokkos::View<VectorView<1, MemorySpace> *, Kokkos::HostSpace> host_scalars(
293 "postprocessor_scalars", n_components);
294
295 for (unsigned int k = 0; k < n_components; ++k) {
296 const auto section = Section(selected[k].first);
297 const auto index = selected[k].second;
298 host_components[k] = {section, index};
299
300 if (section == Section::dynamic_state) {
301 const auto &vector = state_vector.dynamic_state[index];
302 copy_to_memory_space<MemorySpace>(vector);
303 host_scalars[k] = vector.template view<1, MemorySpace>();
304 }
305 }
306
307 const auto space = typename MemorySpace::kokkos_space{};
308 const auto components =
309 Kokkos::create_mirror_view_and_copy(space, host_components);
310 const auto scalars =
311 Kokkos::create_mirror_view_and_copy(space, host_scalars);
312
313 const SelectView<dim, Number, MemorySpace, HyperbolicSystem> system_views(
314 *hyperbolic_system_);
315
316 return [=](auto sentinel, const unsigned int i, const auto &write) {
317 using T = decltype(sentinel);
318
319 /*
320 * Read all involved tensors once. Reading a single component out of
321 * a MultiComponentVector is not any cheaper than reading the full
322 * tensor...
323 */
324
325 decltype(U_view.template read_tensor<T>(i)) U_i;
326 if (read_state)
327 U_i = U_view.template read_tensor<T>(i);
328
329 auto primitive_i = U_i;
330 if (read_primitive)
331 primitive_i = system_views.template view<T>().to_primitive_state(U_i);
332
333 decltype(precomputed_view.template read_tensor<T>(i)) precomputed_i;
334 if (read_precomputed)
335 precomputed_i = precomputed_view.template read_tensor<T>(i);
336
337 decltype(initial_view.template read_tensor<T>(i)) initial_i;
338 if (read_initial)
339 initial_i = initial_view.template read_tensor<T>(i);
340
341 for (unsigned int k = 0; k < components.extent(0); ++k) {
342 const auto [section, index] = components[k];
343 switch (section) {
344 case Section::state:
345 write(k, U_i[index]);
346 break;
347 case Section::primitive:
348 write(k, primitive_i[index]);
349 break;
350 case Section::precomputed:
351 write(k, precomputed_i[index]);
352 break;
353 case Section::initial_precomputed:
354 write(k, initial_i[index]);
355 break;
356 default:
357 write(k, scalars[k].template read_entry<T>(i));
358 }
359 }
360 };
361 }
362
363
364 template <typename Description, int dim, typename Number>
365 std::vector<ScalarHostVector<Number>>
366 Postprocessor<Description, dim, Number>::extract(
367 const Selection &selection, const StateVector<Number> &state_vector) const
368 {
369 using MemorySpace = dealii::MemorySpace::Host;
370
371 const auto &offline_data = *offline_data_;
372 const unsigned int n_internal = offline_data.n_locally_internal();
373 const unsigned int n_owned = offline_data.n_locally_owned();
374
375 /*
376 * Extract all components in a single pass:
377 */
378
379 std::vector<ScalarHostVector<Number>> components(
380 selection.components.size());
381 std::vector<Number *> destinations;
382 for (auto &it : components) {
383 it.reinit(offline_data.scalar_partitioner());
384 destinations.push_back(it.begin());
385 }
386
387 const auto read = reader<MemorySpace>(selection, state_vector);
388 auto *const *data = destinations.data();
389
390 loop<MemorySpace, Number>(
391 "postprocessor_extract",
392 [=](auto sentinel, const unsigned int i) {
393 using T = decltype(sentinel);
394 read(sentinel, i, [&](const unsigned int k, const T &value) {
395 if constexpr (std::is_same_v<T, dealii::VectorizedArray<Number>>)
396 value.store(data[k] + i);
397 else
398 data[k][i] = value;
399 });
400 },
401 0,
402 n_internal,
403 n_owned);
404
405 for (auto &it : components)
406 it.update_ghost_values();
407
408 /*
409 * Then postprocess extracted components:
410 */
411
412 const auto sparsity_simd_view = offline_data.sparsity_pattern_simd().view();
413 const auto lumped_mass_matrix_view =
414 offline_data.lumped_mass_matrix().template view<1>();
415 const auto cij_matrix_view = offline_data.cij_matrix().view();
416
417 std::vector<ScalarHostVector<Number>> extracted(selection.size());
418 for (unsigned int k = 0; k < selection.size(); ++k) {
419 const auto operation = Operation(selection.quantities[k].first);
420 const auto first = selection.quantities[k].second;
421 auto &result = extracted[k];
422
423 if (operation == Operation::value) {
424 result = std::move(components[first]);
425 continue;
426 }
427
428 result.reinit(offline_data.scalar_partitioner());
429 const auto *operands = &components[first];
430
431 const auto body = [&](auto sentinel, unsigned int i) {
432 using T = decltype(sentinel);
433
434 /* Skip constrained degrees of freedom: */
435 const unsigned int row_length = sparsity_simd_view.row_length(i);
436 if (row_length == 1)
437 return;
438
439 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
440
441 dealii::Tensor<1, dim, T> gradient;
442 dealii::Tensor<1, dim == 2 ? 1 : dim, T> curl;
443
444 const unsigned int *js = sparsity_simd_view.columns(i);
445 for (unsigned int col_idx = 0; col_idx < row_length;
446 ++col_idx, js += stride_size) {
447
448 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
449
450 if (operation == Operation::schlieren) {
451 gradient -= c_ij * read_entry<T>(operands[0], js);
452
453 } else {
454 dealii::Tensor<1, dim, T> u_j;
455 for (unsigned int d = 0; d < dim; ++d)
456 u_j[d] = read_entry<T>(operands[d], js);
457
458 if constexpr (dim == 2) {
459 curl[0] -= cross_product_2d(c_ij) * u_j;
460 } else if constexpr (dim == 3) {
461 curl -= cross_product_3d(c_ij, u_j);
462 }
463 }
464 }
465
466 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
467
468 T value_i;
469 if (operation == Operation::schlieren)
470 value_i = gradient.norm() / m_i;
471 else
472 value_i = (dim == 2 ? curl[0] / m_i : curl.norm() / m_i);
473
474 write_entry<T>(result, value_i, i);
475 };
476
477 cpu_simd_loop<Number>(
478 "postprocessor_postprocess", body, 0, n_internal, n_owned);
479
480 offline_data.affine_constraints().distribute(result);
481 result.update_ghost_values();
482 }
483
484 return extracted;
485 }
486
487
488 template <typename Description, int dim, typename Number>
489 void Postprocessor<Description, dim, Number>::extract(
490 const Selection &selection,
491 const StateVector<Number> &state_vector,
492 const unsigned int *indices,
493 const unsigned int n_points,
494 Number *values) const
495 {
496 AssertThrow(
497 std::all_of(selection.quantities.begin(),
498 selection.quantities.end(),
499 [](const auto &it) {
500 return Operation(it.first) == Operation::value;
501 }),
502 dealii::ExcMessage("Postprocessed quantities cannot be extracted at "
503 "individual points."));
504
505 /* Every selected quantity is a value, thus components == quantities: */
506 const auto read = reader<selected_memory_space_t>(selection, state_vector);
507 const auto n_components =
508 static_cast<unsigned int>(selection.components.size());
509
510 /* The points are scattered: run a scalar loop only. */
511 loop<selected_memory_space_t, Number>(
512 "postprocessor_extract_points",
513 [=](auto /*sentinel*/, const unsigned int p) {
514 auto *values_p = values + p * n_components;
515 read(Number(),
516 indices[p],
517 [=](const unsigned int k, const Number value) {
518 values_p[k] = value;
519 });
520 },
521 0,
522 0,
523 n_points);
524 }
525
526#endif
527} // namespace ryujin
Postprocessor(const Interface::OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const Interface::ParabolicSystem &parabolic_system, const MultiComponentVector< Number > &initial_precomputed)
std::vector< ScalarHostVector< Number > > extract(const Selection &selection, const StateVector< Number > &state_vector) const override
typename Description::HyperbolicSystem HyperbolicSystem
Selection prepare(const std::vector< std::string > &selected) const override
void extract(const Selection &selection, const StateVector< Number > &state_vector, const unsigned int *indices, const unsigned int n_points, Number *values) const override
typename Interface::Postprocessor< dim, Number >::Selection Selection