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