ryujin 2.1.1 revision ee5cbcbf2346c1299c942d0e1f13b46449973c18
Loading...
Searching...
No Matches
postprocessor.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 "loop.h"
10#include "postprocessor.h"
11#include "simd.h"
12
13#include <deal.II/base/function_parser.h>
14#include <deal.II/numerics/data_out.h>
15#include <deal.II/numerics/vector_tools.h>
16
17namespace ryujin
18{
19 template <typename Description, int dim, typename Number>
21 const MPIEnsemble &mpi_ensemble,
22 const OfflineData<dim, Number> &offline_data,
23 const HyperbolicSystem &hyperbolic_system,
24 const ParabolicSystem &parabolic_system,
25 const std::string &subsection /*= "Postprocessor"*/)
26 : ParameterAcceptor(subsection)
27 , mpi_ensemble_(mpi_ensemble)
28 , offline_data_(&offline_data)
29 , hyperbolic_system_(&hyperbolic_system)
30 , parabolic_system_(&parabolic_system)
31 {
32 beta_ = 10.;
33 add_parameter("schlieren beta",
34 beta_,
35 "Beta factor used in the exponential scale for the schlieren "
36 "and vorticity plots");
37
38 recompute_bounds_ = true;
39 add_parameter(
40 "schlieren recompute bounds",
41 recompute_bounds_,
42 "Recompute bounds for every output cycle. If set to false, bounds once "
43 "at the beginning and reused thereafter.");
44
45 static_assert(View::component_names.size() > 0,
46 "Need at least one scalar quantitity");
47 schlieren_quantities_.push_back(View::component_names[0]);
48
49 add_parameter(
50 "schlieren quantities",
51 schlieren_quantities_,
52 "List of conserved quantities used for the schlieren postprocessor.");
53
54 if constexpr (dim > 1) {
55 add_parameter(
56 "vorticity quantities",
57 vorticity_quantities_,
58 "List of conserved quantities used for the vorticity postprocessor.");
59 }
60 }
61
62
63 template <typename Description, int dim, typename Number>
65 {
66#ifdef DEBUG_OUTPUT
67 std::cout << "Postprocessor<dim, Number>::prepare()" << std::endl;
68#endif
69
70 bounds_.clear();
71 component_names_.clear();
72 schlieren_indices_.clear();
73 vorticity_indices_.clear();
74
75 const auto populate = [&](const auto &strings,
76 auto &indices,
77 const auto &pre) {
78 const auto &cons = View::component_names;
79 const auto &prim = View::primitive_component_names;
80 for (const auto &entry : strings) {
81 bool found = false;
82 for (const auto &[is_primitive, names] :
83 {std::make_pair(false, cons), std::make_pair(true, prim)}) {
84 const auto pos = std::find(std::begin(names), std::end(names), entry);
85 if (!found && pos != std::end(names)) {
86 const auto index = std::distance(std::begin(names), pos);
87 indices.push_back(std::make_pair(is_primitive, index));
88 component_names_.push_back(pre + entry);
89 found = true;
90 }
91 }
92 AssertThrow(
93 found,
94 dealii::ExcMessage("Invalid component name »" + entry + "«"));
95 }
96 };
97 populate(schlieren_quantities_, schlieren_indices_, "schlieren_");
98 populate(vorticity_quantities_, vorticity_indices_, "vorticity_");
99
100 const auto &partitioner = offline_data_->scalar_partitioner();
101
102 quantities_.resize(component_names_.size());
103 for (auto &it : quantities_)
104 it.reinit(partitioner);
105 }
106
107
108 template <typename Description, int dim, typename Number>
110 const StateVector &state_vector) const
111 {
112#ifdef DEBUG_OUTPUT
113 std::cout << "Postprocessor<dim, Number>::compute()" << std::endl;
114#endif
115
116 /* Ensure that the state vector is resident on the host memory space. */
117 if constexpr (have_separate_memory_spaces) {
118 ComputingTimer::Scope scope("time step [X] _ - memory space transfers");
119 const auto &[U, precomputed, parabolic] = state_vector;
120 U.template copy_to_memory_space<dealii::MemorySpace::Host>();
121 }
122
123 const auto U_view = std::get<0>(state_vector).view();
124
125 using VA = dealii::VectorizedArray<Number>;
126
127 const auto &affine_constraints = offline_data_->affine_constraints();
128
129 const auto sparsity_simd_view =
130 offline_data_->sparsity_pattern_simd().view();
131 const auto lumped_mass_matrix_view =
132 offline_data_->lumped_mass_matrix().view();
133 const auto cij_matrix_view = offline_data_->cij_matrix().view();
134
135 const unsigned int n_internal = offline_data_->n_locally_internal();
136 const unsigned int n_owned = offline_data_->n_locally_owned();
137
138 const unsigned int n_schlieren = schlieren_indices_.size();
139 Assert(n_schlieren == schlieren_quantities_.size(),
140 dealii::ExcInternalError());
141 const unsigned int n_vorticities = vorticity_indices_.size();
142 Assert(n_vorticities == vorticity_quantities_.size(),
143 dealii::ExcInternalError());
144 const unsigned int n_quantities = n_schlieren + n_vorticities;
145 Assert(n_quantities == quantities_.size(), dealii::ExcInternalError());
146 Assert(n_quantities == component_names_.size(), dealii::ExcInternalError());
147
148 /*
149 * Step 1: Compute quantities:
150 */
151
152 const auto body = [&](auto sentinel, unsigned int i) {
153 using T = decltype(sentinel);
154 constexpr unsigned int stride_size = get_stride_size<T>;
155
156 /* Skip constrained degrees of freedom: */
157 const unsigned int row_length = sparsity_simd_view.row_length(i);
158 if (row_length == 1)
159 return;
160
161 std::vector<grad_type<T>> local_schlieren_values(n_schlieren);
162 std::vector<curl_type<T>> local_vorticity_values(n_vorticities);
163
164 for (auto &it : local_schlieren_values)
165 it = grad_type<T>();
166 for (auto &it : local_vorticity_values)
167 it = curl_type<T>();
168
169 const unsigned int *js = sparsity_simd_view.columns(i);
170 for (unsigned int col_idx = 0; col_idx < row_length;
171 ++col_idx, js += stride_size) {
172
173 const auto U_j = U_view.template read_tensor<T>(js);
174 const auto view = hyperbolic_system_->template view<dim, T>();
175 const auto prim_j = view.to_primitive_state(U_j);
176
177 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
178
179 unsigned int k = 0;
180 for (const auto &[is_primitive, index] : schlieren_indices_) {
181 local_schlieren_values[k++] -=
182 c_ij * (is_primitive ? prim_j[index] : U_j[index]);
183 }
184
185 k = 0;
186 for (const auto &[is_primitive, index] : vorticity_indices_) {
187 grad_type<T> q_j;
188 for (unsigned int d = 0; d < dim; ++d)
189 q_j[d] = (is_primitive ? prim_j[index + d] : U_j[index + d]);
190
191 if constexpr (dim == 2) {
192 local_vorticity_values[k++][0] -= cross_product_2d(c_ij) * q_j;
193 } else if constexpr (dim == 3) {
194 local_vorticity_values[k++] -= cross_product_3d(c_ij, q_j);
195 }
196 }
197 }
198
199 /* Populate quantities: */
200 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
201
202 unsigned int k = 0;
203 for (const auto &schlieren : local_schlieren_values) {
204 const auto value_i = schlieren.norm() / m_i;
205 write_entry<T>(quantities_[k++], value_i, i);
206 }
207 for (const auto &vorticity : local_vorticity_values) {
208 auto value_i = (dim == 2 ? vorticity[0] / m_i : vorticity.norm() / m_i);
209 write_entry<T>(quantities_[k++], value_i, i);
210 }
211 };
212
213 cpu_simd_loop<Number>("", body, 0, n_internal, n_owned);
214
215 /*
216 * Step 2: Compute bounds and synchronize over MPI ranks:
217 */
218
219 /* Force recomputation of bounds: */
220 if (recompute_bounds_)
221 bounds_.clear();
222
223 if (bounds_.size() != n_quantities) {
224 bounds_.clear();
225 bounds_.resize(
226 n_quantities,
227 std::make_pair(Number(0.), std::numeric_limits<Number>::max()));
228
229 for (unsigned int d = 0; d < n_quantities; ++d) {
230 auto &[q_max, q_min] = bounds_[d];
231 for (unsigned int i = 0; i < n_owned; ++i) {
232 const auto q = quantities_[d].local_element(i);
233 q_max = std::max(q_max, std::abs(q));
234 q_min = std::min(q_min, std::abs(q));
235 }
236 q_max = dealii::Utilities::MPI::max(
237 q_max, mpi_ensemble_.ensemble_communicator());
238 q_min = dealii::Utilities::MPI::min(
239 q_min, mpi_ensemble_.ensemble_communicator());
240 Assert(q_max >= q_min, dealii::ExcInternalError());
241 }
242 }
243
244 /*
245 * Step 3: Normalize quantities on exponential scale:
246 */
247
248 {
249 constexpr Number eps = std::numeric_limits<Number>::epsilon();
250 constexpr Number floor = std::max(Number(1.0e-10), eps);
251
252 for (unsigned int d = 0; d < n_quantities; ++d) {
253 auto &[q_max, q_min] = bounds_[d];
254 for (unsigned int i = 0; i < n_owned; ++i) {
255 auto &q = quantities_[d].local_element(i);
256 /* clip off everything that is below the noise "floor": */
257 const auto ratio = std::max(Number(0.), std::abs(q) - q_min - floor) /
258 std::max(q_max - q_min, eps);
259
260 const auto magnitude = Number(1.) - std::exp(-beta_ * ratio);
261 q = std::copysign(magnitude, q);
262 }
263 }
264 }
265
266 /*
267 * Step 4: Fix up constraints and distribute:
268 */
269
270 for (auto &it : quantities_) {
271 affine_constraints.distribute(it);
272 it.update_ghost_values();
273 }
274 }
275
276} // namespace ryujin
typename Description::HyperbolicSystem HyperbolicSystem
Postprocessor(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const ParabolicSystem &parabolic_system, const std::string &subsection="/Postprocessor")
typename Description::ParabolicSystem ParabolicSystem
dealii::Tensor< 1, dim==2 ? 1 :dim, T > curl_type
dealii::Tensor< 1, dim, T > grad_type
void compute(const StateVector &state_vector) const
typename View::StateVector StateVector
constexpr bool have_separate_memory_spaces
Definition gpu.h:29