ryujin 2.1.1 revision b04fb4aba7bd97ddfa5994a6b4b6e8586eb15db2
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 HyperbolicDescription, int dim, typename Number>
21 const MPIEnsemble &mpi_ensemble,
22 const OfflineData<dim, Number> &offline_data,
23 const HyperbolicSystem &hyperbolic_system,
24 const Interface::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 Assert(hyperbolic_system.state_names(dim).size() > 0,
46 dealii::ExcMessage("Need at least one scalar quantitity"));
47 schlieren_quantities_.push_back(hyperbolic_system.state_names(dim)[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 HyperbolicDescription, 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();
75 const auto populate = [&](const auto &strings,
76 auto &indices,
77 const auto &pre) {
78 const auto &cons = hyperbolic_system_->state_names(dim);
79 const auto &prim = hyperbolic_system_->primitive_names(dim);
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
108 template <typename HyperbolicDescription, int dim, typename Number>
110 const StateVector<Number> &state_vector) const
111 {
112#ifdef DEBUG_OUTPUT
113 std::cout << "Postprocessor<dim, Number>::compute()" << std::endl;
114#endif
115
116 copy_to_memory_space<dealii::MemorySpace::Host>(
117 state_vector, StateVectorPart::hyperbolic);
118
119 const auto U_view = state_vector.U.template view<problem_dimension>();
120
121 using VA = dealii::VectorizedArray<Number>;
122
123 const auto &affine_constraints = offline_data_->affine_constraints();
124
125 const auto sparsity_simd_view =
126 offline_data_->sparsity_pattern_simd().view();
127 const auto lumped_mass_matrix_view =
128 offline_data_->lumped_mass_matrix().template view<1>();
129 const auto cij_matrix_view = offline_data_->cij_matrix().view();
130
131 const unsigned int n_internal = offline_data_->n_locally_internal();
132 const unsigned int n_owned = offline_data_->n_locally_owned();
133
134 const unsigned int n_schlieren = schlieren_indices_.size();
135 Assert(n_schlieren == schlieren_quantities_.size(),
136 dealii::ExcInternalError());
137 const unsigned int n_vorticities = vorticity_indices_.size();
138 Assert(n_vorticities == vorticity_quantities_.size(),
139 dealii::ExcInternalError());
140 const unsigned int n_quantities = n_schlieren + n_vorticities;
141 Assert(n_quantities == quantities_.size(), dealii::ExcInternalError());
142 Assert(n_quantities == component_names_.size(), dealii::ExcInternalError());
143
144 /*
145 * Step 1: Compute quantities:
146 */
147
148 const auto body = [&](auto sentinel, unsigned int i) {
149 using T = decltype(sentinel);
150 const unsigned int stride_size = sparsity_simd_view.stride_of_row(i);
151
152 /* Skip constrained degrees of freedom: */
153 const unsigned int row_length = sparsity_simd_view.row_length(i);
154 if (row_length == 1)
155 return;
156
157 std::vector<grad_type<T>> local_schlieren_values(n_schlieren);
158 std::vector<curl_type<T>> local_vorticity_values(n_vorticities);
159
160 for (auto &it : local_schlieren_values)
161 it = grad_type<T>();
162 for (auto &it : local_vorticity_values)
163 it = curl_type<T>();
164
165 const unsigned int *js = sparsity_simd_view.columns(i);
166 for (unsigned int col_idx = 0; col_idx < row_length;
167 ++col_idx, js += stride_size) {
168
169 const auto U_j = U_view.template read_tensor<T>(js);
170 const auto view = hyperbolic_system_->template view<dim, T>();
171 const auto prim_j = view.to_primitive_state(U_j);
172
173 const auto c_ij = cij_matrix_view.template read_tensor<T>(i, col_idx);
174
175 unsigned int k = 0;
176 for (const auto &[is_primitive, index] : schlieren_indices_) {
177 local_schlieren_values[k++] -=
178 c_ij * (is_primitive ? prim_j[index] : U_j[index]);
179 }
180
181 k = 0;
182 for (const auto &[is_primitive, index] : vorticity_indices_) {
183 grad_type<T> q_j;
184 for (unsigned int d = 0; d < dim; ++d)
185 q_j[d] = (is_primitive ? prim_j[index + d] : U_j[index + d]);
186
187 if constexpr (dim == 2) {
188 local_vorticity_values[k++][0] -= cross_product_2d(c_ij) * q_j;
189 } else if constexpr (dim == 3) {
190 local_vorticity_values[k++] -= cross_product_3d(c_ij, q_j);
191 }
192 }
193 }
194
195 /* Populate quantities: */
196 const auto m_i = lumped_mass_matrix_view.template read_entry<T>(i);
197
198 unsigned int k = 0;
199 for (const auto &schlieren : local_schlieren_values) {
200 const auto value_i = schlieren.norm() / m_i;
201 write_entry<T>(quantities_[k++], value_i, i);
202 }
203 for (const auto &vorticity : local_vorticity_values) {
204 auto value_i = (dim == 2 ? vorticity[0] / m_i : vorticity.norm() / m_i);
205 write_entry<T>(quantities_[k++], value_i, i);
206 }
207 };
208
209 cpu_simd_loop<Number>("", body, 0, n_internal, n_owned);
210
211 /*
212 * Step 2: Compute bounds and synchronize over MPI ranks:
213 */
214
215 /* Force recomputation of bounds: */
216 if (recompute_bounds_)
217 bounds_.clear();
218
219 if (bounds_.size() != n_quantities) {
220 bounds_.clear();
221 bounds_.resize(
222 n_quantities,
223 std::make_pair(Number(0.), std::numeric_limits<Number>::max()));
224
225 for (unsigned int d = 0; d < n_quantities; ++d) {
226 auto &[q_max, q_min] = bounds_[d];
227 for (unsigned int i = 0; i < n_owned; ++i) {
228 const auto q = quantities_[d].local_element(i);
229 q_max = std::max(q_max, std::abs(q));
230 q_min = std::min(q_min, std::abs(q));
231 }
232 q_max = dealii::Utilities::MPI::max(
233 q_max, mpi_ensemble_.ensemble_communicator());
234 q_min = dealii::Utilities::MPI::min(
235 q_min, mpi_ensemble_.ensemble_communicator());
236 Assert(q_max >= q_min, dealii::ExcInternalError());
237 }
238 }
239
240 /*
241 * Step 3: Normalize quantities on exponential scale:
242 */
243
244 {
245 constexpr Number eps = std::numeric_limits<Number>::epsilon();
246 constexpr Number floor = std::max(Number(1.0e-10), eps);
247
248 for (unsigned int d = 0; d < n_quantities; ++d) {
249 auto &[q_max, q_min] = bounds_[d];
250 for (unsigned int i = 0; i < n_owned; ++i) {
251 auto &q = quantities_[d].local_element(i);
252 /* clip off everything that is below the noise "floor": */
253 const auto ratio = std::max(Number(0.), std::abs(q) - q_min - floor) /
254 std::max(q_max - q_min, eps);
255
256 const auto magnitude = Number(1.) - std::exp(-beta_ * ratio);
257 q = std::copysign(magnitude, q);
258 }
259 }
260 }
261
262 /*
263 * Step 4: Fix up constraints and distribute:
264 */
265
266 for (auto &it : quantities_) {
267 affine_constraints.distribute(it);
268 it.update_ghost_values();
269 }
270 }
271
272} // namespace ryujin
typename HyperbolicDescription::HyperbolicSystem HyperbolicSystem
Postprocessor(const MPIEnsemble &mpi_ensemble, const OfflineData< dim, Number > &offline_data, const HyperbolicSystem &hyperbolic_system, const Interface::ParabolicSystem &parabolic_system, const std::string &subsection="/Postprocessor")
dealii::Tensor< 1, dim, T > grad_type
dealii::Tensor< 1, dim==2 ? 1 :dim, T > curl_type
void compute(const StateVector< Number > &state_vector) const
MultiComponentVector< Number > U