ryujin 2.1.1 revision 3bc2805b8013f3db96af2324837f4d07c0f9fb89
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 - 2024 by the ryujin authors
4//
5
6#pragma once
7
8#include "openmp.h"
9#include "quantities.h"
10
11#include <deal.II/base/function_parser.h>
12#include <deal.II/base/mpi.templates.h>
13#include <deal.II/base/work_stream.h>
14#include <deal.II/dofs/dof_tools.h>
15
16#include <fstream>
17
18DEAL_II_NAMESPACE_OPEN
19template <int rank, int dim, typename Number>
20bool operator<(const Tensor<rank, dim, Number> &left,
21 const Tensor<rank, dim, Number> &right)
22{
23 return std::lexicographical_compare(
24 left.begin_raw(), left.end_raw(), right.begin_raw(), right.end_raw());
25}
26DEAL_II_NAMESPACE_CLOSE
27
28namespace ryujin
29{
30 using namespace dealii;
31
32 namespace
33 {
34 template <typename T>
35 const std::string &get_options_from_name(const T &manifolds,
36 const std::string &name)
37 {
38 const auto it =
39 std::find_if(manifolds.begin(),
40 manifolds.end(),
41 [&, name = std::cref(name)](const auto &element) {
42 return std::get<0>(element) == name.get();
43 });
44 Assert(it != manifolds.end(), dealii::ExcInternalError());
45 return std::get<2>(*it);
46 }
47 } // namespace
48
49
50 template <typename Description, int dim, typename Number>
52 const MPI_Comm &mpi_communicator,
53 const HyperbolicSystem &hyperbolic_system,
54 const OfflineData<dim, Number> &offline_data,
55 const std::string &subsection /*= "Quantities"*/)
56 : ParameterAcceptor(subsection)
57 , mpi_communicator_(mpi_communicator)
58 , hyperbolic_system_(&hyperbolic_system)
59 , offline_data_(&offline_data)
60 , base_name_("")
61 , time_series_cycle_(1)
62 , first_cycle_(true)
63 {
64
65 add_parameter("interior manifolds",
66 interior_manifolds_,
67 "List of level set functions describing interior manifolds. "
68 "The description is used to only output point values for "
69 "vertices belonging to a certain level set. "
70 "Format: '<name> : <level set formula> : <options> , [...] "
71 "(options: time_averaged, space_averaged, instantaneous)");
72
73 add_parameter("boundary manifolds",
74 boundary_manifolds_,
75 "List of level set functions describing boundary. The "
76 "description is used to only output point values for "
77 "boundary vertices belonging to a certain level set. "
78 "Format: '<name> : <level set formula> : <options> , [...] "
79 "(options: time_averaged, space_averaged, instantaneous)");
80
81 clear_temporal_statistics_on_writeout_ = true;
82 add_parameter("clear statistics on writeout",
83 clear_temporal_statistics_on_writeout_,
84 "If set to true then all temporal statistics (for "
85 "\"time_averaged\" quantities) accumulated so far are reset "
86 "each time a writeout of quantities is performed");
87 }
88
89
90 template <typename Description, int dim, typename Number>
92 unsigned int cycle)
93 {
94#ifdef DEBUG_OUTPUT
95 std::cout << "Quantities<dim, Number>::prepare()" << std::endl;
96#endif
97
98 base_name_ = name;
99 time_series_cycle_ = cycle;
100 first_cycle_ = true;
101
102 const unsigned int n_owned = offline_data_->n_locally_owned();
103 const auto &sparsity_simd = offline_data_->sparsity_pattern_simd();
104
105 /*
106 * Create interior maps and allocate statistics.
107 *
108 * We have to loop over the cells and populate the std::map interior_maps_.
109 */
110
111 interior_maps_.clear();
112 std::transform(
113 interior_manifolds_.begin(),
114 interior_manifolds_.end(),
115 std::inserter(interior_maps_, interior_maps_.end()),
116 [this, n_owned, &sparsity_simd](auto it) {
117 const auto &[name, expression, option] = it;
118 FunctionParser<dim> level_set_function(expression);
119
120 std::vector<interior_point> map;
121 std::map<int, interior_point> preliminary_map;
122
123 const auto &discretization = offline_data_->discretization();
124 const auto &dof_handler = offline_data_->dof_handler();
125
126 const unsigned int dofs_per_cell = dof_handler.get_fe().dofs_per_cell;
127
128 const auto support_points =
129 dof_handler.get_fe().get_unit_support_points();
130
131 std::vector<dealii::types::global_dof_index> local_dof_indices(
132 dofs_per_cell);
133
134 /* Loop over cells */
135 for (auto cell : dof_handler.active_cell_iterators()) {
136
137 /* skip if not locally owned */
138 if (!cell->is_locally_owned())
139 continue;
140
141 cell->get_active_or_mg_dof_indices(local_dof_indices);
142
143 for (unsigned int j = 0; j < dofs_per_cell; ++j) {
144
145 Point<dim> position =
146 discretization.mapping().transform_unit_to_real_cell(
147 cell, support_points[j]);
148
149 /*
150 * Insert index, interior mass value and position into
151 * a preliminary map if we satisfy level set condition.
152 */
153
154 if (std::abs(level_set_function.value(position)) > 1.e-12)
155 continue;
156
157 const auto global_index = local_dof_indices[j];
158 const auto index =
159 offline_data_->scalar_partitioner()->global_to_local(
160 global_index);
161
162 /* Skip constrained degrees of freedom: */
163 const unsigned int row_length = sparsity_simd.row_length(index);
164 if (row_length == 1)
165 continue;
166
167 if (index >= n_owned)
168 continue;
169
170 const Number interior_mass =
171 offline_data_->lumped_mass_matrix().local_element(index);
172 // FIXME: change to std::set
173 preliminary_map[index] = {index, interior_mass, position};
174 }
175 }
176
177 /*
178 * Now we populate the std::vector(interior_point) object called map.
179 */
180 // FIXME: use std::copy
181 for (const auto &[index, tuple] : preliminary_map) {
182 map.push_back(tuple);
183 }
184
185 return std::make_pair(name, map);
186 });
187
188 /* Output interior maps: */
189
190 for (const auto &[name, interior_map] : interior_maps_) {
191 /* Skip outputting the boundary map for spatial averages. */
192 const auto &options = get_options_from_name(interior_manifolds_, name);
193 if (options.find("instantaneous") == std::string::npos &&
194 options.find("time_averaged") == std::string::npos)
195 continue;
196
197 /*
198 * FIXME: This currently distributes boundary maps to all MPI ranks.
199 * This is unnecessarily wasteful. Ideally, we should do MPI IO with
200 * only MPI ranks participating who actually have boundary values.
201 */
202
203 const auto received =
204 Utilities::MPI::gather(mpi_communicator_, interior_map);
205
206 if (Utilities::MPI::this_mpi_process(mpi_communicator_) == 0) {
207
208 std::ofstream output(base_name_ + "-" + name + "-R" +
209 Utilities::to_string(cycle, 4) + "-points.dat");
210
211 output << std::scientific << std::setprecision(14);
212
213 output << "#\n# position\tinterior mass\n";
214
215 unsigned int rank = 0;
216 for (const auto &entries : received) {
217 output << "# rank " << rank++ << "\n";
218 for (const auto &entry : entries) {
219 const auto &[index, mass_i, x_i] = entry;
220 output << x_i << "\t" << mass_i << "\n";
221 } /*entry*/
222 } /*entries*/
223
224 output << std::flush;
225 }
226 }
227
228 /*
229 * Create boundary maps and allocate statistics vector:
230 *
231 * We want to loop over the boundary_map() once and populate the map
232 * object boundary_maps_. We have to create a vector of
233 * boundary_manifolds.size() that holds a std::vector<boundary_point>
234 * for each map entry.
235 */
236
237 boundary_maps_.clear();
238 std::transform(
239 boundary_manifolds_.begin(),
240 boundary_manifolds_.end(),
241 std::inserter(boundary_maps_, boundary_maps_.end()),
242 [this, n_owned](auto it) {
243 const auto &[name, expression, option] = it;
244 FunctionParser<dim> level_set_function(expression);
245
246 std::vector<boundary_point> map;
247
248 for (const auto &entry : offline_data_->boundary_map()) {
249 /* skip nonlocal */
250 if (entry.first >= n_owned)
251 continue;
252
253 /* skip constrained */
254 if (offline_data_->affine_constraints().is_constrained(
255 offline_data_->scalar_partitioner()->local_to_global(
256 entry.first)))
257 continue;
258
259 const auto &[normal, normal_mass, boundary_mass, id, position] =
260 entry.second;
261 if (std::abs(level_set_function.value(position)) < 1.e-12)
262 map.push_back({entry.first,
263 normal,
264 normal_mass,
265 boundary_mass,
266 id,
267 position});
268 }
269 return std::make_pair(name, map);
270 });
271
272 /*
273 * Output boundary maps:
274 */
275
276 for (const auto &[name, boundary_map] : boundary_maps_) {
277 /* Skip outputting the boundary map for spatial averages. */
278 const auto &options = get_options_from_name(boundary_manifolds_, name);
279 if (options.find("instantaneous") == std::string::npos &&
280 options.find("time_averaged") == std::string::npos)
281 continue;
282
283 /*
284 * FIXME: This currently distributes boundary maps to all MPI ranks.
285 * This is unnecessarily wasteful. Ideally, we should do MPI IO with
286 * only MPI ranks participating who actually have boundary values.
287 */
288
289 const auto received =
290 Utilities::MPI::gather(mpi_communicator_, boundary_map);
291
292 if (Utilities::MPI::this_mpi_process(mpi_communicator_) == 0) {
293
294 std::ofstream output(base_name_ + "-" + name + "-R" +
295 Utilities::to_string(cycle, 4) + "-points.dat");
296
297 output << std::scientific << std::setprecision(14);
298
299 output << "#\n# position\tnormal\tnormal mass\tboundary mass\n";
300
301 unsigned int rank = 0;
302 for (const auto &entries : received) {
303 output << "# rank " << rank++ << "\n";
304 for (const auto &entry : entries) {
305 const auto &[index, n_i, nm_i, bm_i, id, x_i] = entry;
306 output << x_i << "\t" << n_i << "\t" << nm_i << "\t" << bm_i
307 << "\n";
308 } /*entry*/
309 } /*entries*/
310
311 output << std::flush;
312 }
313 }
314
315 /* Clear statistics: */
316 clear_statistics();
317
318 /* Prepare header string: */
319 const auto &names = View::primitive_component_names;
320 header_ = std::accumulate(
321 std::begin(names),
322 std::end(names),
323 std::string(),
324 [](const std::string &description, const std::string &name) {
325 return description.empty()
326 ? (std::string("primitive state (") + name)
327 : (description + ", " + name);
328 }) +
329 ")\t and 2nd moments\n";
330 }
331
332
333 template <typename Description, int dim, typename Number>
334 void Quantities<Description, dim, Number>::clear_statistics()
335 {
336 const auto reset = [](const auto &manifold_map, auto &statistics_map) {
337 for (const auto &[name, data_map] : manifold_map) {
338 const auto n_entries = data_map.size();
339 auto &[val_old, val_new, val_sum, t_old, t_new, t_sum] =
340 statistics_map[name];
341 val_old.resize(n_entries);
342 val_new.resize(n_entries);
343 val_sum.resize(n_entries);
344 t_old = t_new = t_sum = 0.;
345 }
346 };
347
348 /* Clear statistics and time series: */
349
350 interior_statistics_.clear();
351 reset(interior_maps_, interior_statistics_);
352 interior_time_series_.clear();
353
354 boundary_statistics_.clear();
355 reset(boundary_maps_, boundary_statistics_);
356 boundary_time_series_.clear();
357 }
358
359
360 template <typename Description, int dim, typename Number>
361 template <typename point_type, typename value_type>
362 value_type Quantities<Description, dim, Number>::internal_accumulate(
363 const StateVector &state_vector,
364 const std::vector<point_type> &points_vector,
365 std::vector<value_type> &val_new)
366 {
367 const auto &U = std::get<0>(state_vector);
368
369 value_type spatial_average;
370 Number mass_sum = Number(0.);
371
372 std::transform(
373 points_vector.begin(),
374 points_vector.end(),
375 val_new.begin(),
376 [&](auto point) -> value_type {
377 const auto i = std::get<0>(point);
378 /*
379 * Small trick to get the correct index for retrieving the
380 * boundary mass.
381 */
382 constexpr auto index =
383 std::is_same<point_type, interior_point>::value ? 1 : 3;
384 const auto mass_i = std::get<index>(point);
385
386 const auto U_i = U.get_tensor(i);
387 const auto view = hyperbolic_system_->template view<dim, Number>();
388 const auto primitive_state = view.to_primitive_state(U_i);
389
390 value_type result;
391 std::get<0>(result) = primitive_state;
392 /* Compute second moments of the primitive state: */
393 std::get<1>(result) = schur_product(primitive_state, primitive_state);
394
395 mass_sum += mass_i;
396 std::get<0>(spatial_average) += mass_i * std::get<0>(result);
397 std::get<1>(spatial_average) += mass_i * std::get<1>(result);
398
399 return result;
400 });
401
402 /* synchronize MPI ranks (MPI Barrier): */
403
404 mass_sum = Utilities::MPI::sum(mass_sum, mpi_communicator_);
405
406 std::get<0>(spatial_average) =
407 Utilities::MPI::sum(std::get<0>(spatial_average), mpi_communicator_);
408 std::get<1>(spatial_average) =
409 Utilities::MPI::sum(std::get<1>(spatial_average), mpi_communicator_);
410
411 /* take average: */
412
413 std::get<0>(spatial_average) /= mass_sum;
414 std::get<1>(spatial_average) /= mass_sum;
415
416 return spatial_average;
417 }
418
419
420 template <typename Description, int dim, typename Number>
421 template <typename value_type>
422 void Quantities<Description, dim, Number>::internal_write_out(
423 std::ostream &output,
424 const std::vector<value_type> &values,
425 const Number scale)
426 {
427 /*
428 * FIXME: This currently distributes interior maps to all MPI ranks.
429 * This is unnecessarily wasteful. Ideally, we should do MPI IO with
430 * only MPI ranks participating who actually have interior values.
431 */
432
433 const auto received = Utilities::MPI::gather(mpi_communicator_, values);
434
435 if (Utilities::MPI::this_mpi_process(mpi_communicator_) == 0) {
436
437 output << "# " << header_;
438
439 unsigned int rank = 0;
440 for (const auto &entries : received) {
441 output << "# rank " << rank++ << "\n";
442 for (const auto &entry : entries) {
443 const auto &[state, state_square] = entry;
444 output << scale * state << "\t" << scale * state_square << "\n";
445 } /*entry*/
446 } /*entries*/
447
448 output << std::flush;
449 }
450 }
451
452
453 template <typename Description, int dim, typename Number>
454 template <typename value_type>
455 void Quantities<Description, dim, Number>::internal_write_out_time_series(
456 std::ostream &output,
457 const std::vector<std::tuple<Number, value_type>> &values,
458 bool append)
459 {
460 if (Utilities::MPI::this_mpi_process(mpi_communicator_) == 0) {
461
462 if (!append)
463 output << "# time t\t" << header_;
464
465 for (const auto &entry : values) {
466 const auto t = std::get<0>(entry);
467 const auto &[state, state_square] = std::get<1>(entry);
468
469 output << t << "\t" << state << "\t" << state_square << "\n";
470 }
471
472 output << std::flush;
473 }
474 }
475
476
477 template <typename Description, int dim, typename Number>
479 const StateVector &state_vector, const Number t)
480 {
481#ifdef DEBUG_OUTPUT
482 std::cout << "Quantities<dim, Number>::accumulate()" << std::endl;
483#endif
484
485 const auto accumulate = [&](const auto &point_maps,
486 const auto &manifolds,
487 auto &statistics,
488 auto &time_series) {
489 for (const auto &[name, point_map] : point_maps) {
490
491 /* Find the correct option string in manifolds */
492 const auto &options = get_options_from_name(manifolds, name);
493
494 /* skip if we don't average in space or time: */
495 if (options.find("time_averaged") == std::string::npos &&
496 options.find("space_averaged") == std::string::npos)
497 continue;
498
499 auto &[val_old, val_new, val_sum, t_old, t_new, t_sum] =
500 statistics[name];
501
502 std::swap(t_old, t_new);
503 std::swap(val_old, val_new);
504
505 /* accumulate new values */
506
507 const auto spatial_average =
508 internal_accumulate(state_vector, point_map, val_new);
509
510 /* Average in time with trapezoidal rule: */
511
512 if (RYUJIN_UNLIKELY(t_old == Number(0.) && t_new == Number(0.))) {
513 /* We have not accumulated any statistics yet: */
514 t_old = t - 1.;
515 t_new = t;
516
517 } else {
518
519 t_new = t;
520 const Number tau = t_new - t_old;
521
522 for (std::size_t i = 0; i < val_sum.size(); ++i) {
523 std::get<0>(val_sum[i]) += 0.5 * tau * std::get<0>(val_old[i]);
524 std::get<0>(val_sum[i]) += 0.5 * tau * std::get<0>(val_new[i]);
525 std::get<1>(val_sum[i]) += 0.5 * tau * std::get<1>(val_old[i]);
526 std::get<1>(val_sum[i]) += 0.5 * tau * std::get<1>(val_new[i]);
527 }
528 t_sum += tau;
529 }
530
531 /* Record average in space: */
532 time_series[name].push_back({t, spatial_average});
533 }
534 };
535
536 accumulate(interior_maps_,
537 interior_manifolds_,
538 interior_statistics_,
539 interior_time_series_);
540
541 accumulate(boundary_maps_,
542 boundary_manifolds_,
543 boundary_statistics_,
544 boundary_time_series_);
545 }
546
547
548 template <typename Description, int dim, typename Number>
550 const StateVector &state_vector, const Number t, unsigned int cycle)
551 {
552#ifdef DEBUG_OUTPUT
553 std::cout << "Quantities<dim, Number>::write_out()" << std::endl;
554#endif
555
556 const auto write_out = [&](const auto &point_maps,
557 const auto &manifolds,
558 auto &statistics,
559 auto &time_series) {
560 for (const auto &[name, point_map] : point_maps) {
561
562 /* Find the correct option string in manifolds */
563 const auto &options = get_options_from_name(manifolds, name);
564
565 const auto prefix =
566 base_name_ + "-" + name + "-R" + Utilities::to_string(cycle, 4);
567
568 /*
569 * Compute and output instantaneous field:
570 */
571
572 if (options.find("instantaneous") != std::string::npos) {
573
574 auto &[val_old, val_new, val_sum, t_old, t_new, t_sum] =
575 statistics[name];
576
577 /* We have not computed any updated statistics yet: */
578
579 if (options.find("time_averaged") == std::string::npos &&
580 options.find("space_averaged") == std::string::npos)
581 internal_accumulate(state_vector, point_map, val_new);
582 else
583 AssertThrow(t_new == t, dealii::ExcInternalError());
584
585 std::ofstream output(prefix + "-instantaneous.dat");
586
587 output << std::scientific << std::setprecision(14);
588 output << "# at t = " << t << std::endl;
589
590 internal_write_out(output, val_new, Number(1.));
591 }
592
593 /*
594 * Output time averaged field:
595 */
596
597 if (options.find("time_averaged") != std::string::npos) {
598
599 std::ofstream output(prefix + "-time_averaged.dat");
600
601 auto &[val_old, val_new, val_sum, t_old, t_new, t_sum] =
602 statistics[name];
603
604 output << std::scientific << std::setprecision(14);
605 output << "# averaged from t = " << t_new - t_sum
606 << " to t = " << t_new << std::endl;
607
608 internal_write_out(output, val_sum, Number(1.) / t_sum);
609 }
610
611 /*
612 * Output space averaged field:
613 */
614
615 if (options.find("space_averaged") != std::string::npos) {
616
617 auto &series = time_series[name];
618
619 std::ofstream output;
620 const auto file_name = base_name_ + "-" + name + "-R" +
621 Utilities::to_string(time_series_cycle_, 4) +
622 "-space_averaged_time_series.dat";
623
624 output << std::scientific << std::setprecision(14);
625
626 if (first_cycle_) {
627 first_cycle_ = false;
628 output.open(file_name, std::ofstream::out | std::ofstream::trunc);
629 internal_write_out_time_series(output, series, /*append*/ false);
630
631 } else {
632
633 output.open(file_name, std::ofstream::out | std::ofstream::app);
634 internal_write_out_time_series(output, series, /*append*/ true);
635 }
636
637 series.clear();
638 }
639 }
640 };
641
642 write_out(interior_maps_,
643 interior_manifolds_,
644 interior_statistics_,
645 interior_time_series_);
646
647 write_out(boundary_maps_,
648 boundary_manifolds_,
649 boundary_statistics_,
650 boundary_time_series_);
651
652 if (clear_temporal_statistics_on_writeout_)
653 clear_statistics();
654 }
655
656} /* namespace ryujin */
typename Description::HyperbolicSystem HyperbolicSystem
Definition: quantities.h:34
typename View::StateVector StateVector
Definition: quantities.h:41
Quantities(const MPI_Comm &mpi_communicator, const HyperbolicSystem &hyperbolic_system, const OfflineData< dim, Number > &offline_data, const std::string &subsection="/Quantities")
void prepare(const std::string &name, unsigned int cycle)
#define RYUJIN_UNLIKELY(x)
Definition: openmp.h:132
std::tuple< MultiComponentVector< Number, problem_dim >, MultiComponentVector< Number, prec_dim >, BlockVector< Number > > StateVector
Definition: state_vector.h:45
DEAL_II_NAMESPACE_OPEN bool operator<(const Tensor< rank, dim, Number > &left, const Tensor< rank, dim, Number > &right)