Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <cstddef>
7 : #include <optional>
8 : #include <pup.h>
9 : #include <string>
10 : #include <unordered_map>
11 : #include <utility>
12 : #include <vector>
13 :
14 : #include "DataStructures/DataBox/DataBox.hpp"
15 : #include "DataStructures/DataBox/ObservationBox.hpp"
16 : #include "DataStructures/DataBox/TagName.hpp"
17 : #include "DataStructures/DataVector.hpp"
18 : #include "DataStructures/Tensor/Tensor.hpp"
19 : #include "Domain/Structure/ElementId.hpp"
20 : #include "Domain/Tags.hpp"
21 : #include "IO/Observer/GetSectionObservationKey.hpp"
22 : #include "IO/Observer/Helpers.hpp"
23 : #include "IO/Observer/ObservationId.hpp"
24 : #include "IO/Observer/ObserverComponent.hpp"
25 : #include "IO/Observer/ReductionActions.hpp"
26 : #include "IO/Observer/TypeOfObservation.hpp"
27 : #include "NumericalAlgorithms/LinearOperators/DefiniteIntegral.hpp"
28 : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
29 : #include "NumericalAlgorithms/Spectral/Quadrature.hpp"
30 : #include "Options/String.hpp"
31 : #include "Parallel/ArrayIndex.hpp"
32 : #include "Parallel/GlobalCache.hpp"
33 : #include "Parallel/Invoke.hpp"
34 : #include "Parallel/Local.hpp"
35 : #include "Parallel/Reduction.hpp"
36 : #include "Parallel/TypeTraits.hpp"
37 : #include "ParallelAlgorithms/Events/Tags.hpp"
38 : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
39 : #include "Utilities/ErrorHandling/Assert.hpp"
40 : #include "Utilities/ErrorHandling/Error.hpp"
41 : #include "Utilities/Functional.hpp"
42 : #include "Utilities/OptionalHelpers.hpp"
43 : #include "Utilities/PrettyType.hpp"
44 : #include "Utilities/Serialization/CharmPupable.hpp"
45 : #include "Utilities/TMPL.hpp"
46 :
47 : namespace Events {
48 : /// @{
49 : /*!
50 : * \brief Compute norms of tensors in the DataBox and write them to disk.
51 : *
52 : * The L2 norm is computed as the RMS, so
53 : *
54 : * \f{align*}{
55 : * L_2(u)=\sqrt{\frac{1}{N}\sum_{i=0}^{N} u_i^2}
56 : * \f}
57 : *
58 : * where \f$N\f$ is the number of grid points.
59 : *
60 : * The norm can be taken for each individual component, or summed over
61 : * components. For the max/min it is then the max/min over all components, while
62 : * for the L2 norm we have (for a 3d vector, 2d and 1d are similar)
63 : *
64 : * \f{align*}{
65 : * L_2(v^k)=\sqrt{\frac{1}{N}\sum_{i=0}^{N} \left[(v^x_i)^2 + (v^y_i)^2
66 : * + (v^z_i)^2\right]}
67 : * \f}
68 : *
69 : * The L2 integral norm is:
70 : *
71 : * \begin{equation}
72 : * L_{2,\mathrm{int}}(v^k) = \sqrt{\frac{1}{V}\int_\Omega \left[
73 : * (v^x_i)^2 + (v^y_i)^2 + (v^z_i)^2\right] \mathrm{d}V}
74 : * \end{equation}
75 : *
76 : * where $V=\int_\Omega$ is the volume of the entire domain in inertial
77 : * coordinates.
78 : *
79 : * VolumeIntegral only computes the volume integral without any normalization.
80 : *
81 : * Here is an example of an input file:
82 : *
83 : * \snippet Test_ObserveNorms.cpp input_file_examples
84 : *
85 : * \note The `NonTensorComputeTags` are intended to be used for `Variables`
86 : * compute tags like `Tags::DerivCompute`
87 : *
88 : * \par Array sections
89 : * This event supports sections (see `Parallel::Section`). Set the
90 : * `ArraySectionIdTag` template parameter to split up observations into subsets
91 : * of elements. The `observers::Tags::ObservationKey<ArraySectionIdTag>` must be
92 : * available in the DataBox. It identifies the section and is used as a suffix
93 : * for the path in the output file.
94 : *
95 : * \par Option name
96 : * The `OptionName` template parameter is used to give the event a name in the
97 : * input file. If it is not specified, the name defaults to "ObserveNorms". If
98 : * you have multiple `ObserveNorms` events in the input file, you must specify a
99 : * unique name for each one. This can happen, for example, if you want to
100 : * observe norms the full domain and also over a section of the domain.
101 : */
102 : template <typename ObservableTensorTagsList,
103 : typename NonTensorComputeTagsList = tmpl::list<>,
104 : typename ArraySectionIdTag = void, typename OptionName = void>
105 1 : class ObserveNorms;
106 :
107 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
108 : typename ArraySectionIdTag, typename OptionName>
109 0 : class ObserveNorms<tmpl::list<ObservableTensorTags...>,
110 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
111 : OptionName> : public Event {
112 : private:
113 0 : struct ObserveTensor {
114 0 : static constexpr Options::String help = {
115 : "The tensor to reduce, and how to reduce it."};
116 :
117 0 : struct Name {
118 0 : using type = std::string;
119 0 : static constexpr Options::String help = {
120 : "The name of the tensor to observe."};
121 : };
122 0 : struct NormType {
123 0 : using type = std::string;
124 0 : static constexpr Options::String help = {
125 : "The type of norm to use. Must be one of Max, Min, L2Norm, "
126 : "L2IntegralNorm, or VolumeIntegral."};
127 : };
128 0 : struct Components {
129 0 : using type = std::string;
130 0 : static constexpr Options::String help = {
131 : "How to handle tensor components. Must be Individual or Sum."};
132 : };
133 :
134 0 : using options = tmpl::list<Name, NormType, Components>;
135 :
136 0 : ObserveTensor() = default;
137 :
138 0 : ObserveTensor(std::string in_tensor, std::string in_norm_type,
139 : std::string in_components,
140 : const Options::Context& context = {});
141 :
142 0 : std::string tensor{};
143 0 : std::string norm_type{};
144 0 : std::string components{};
145 : };
146 :
147 0 : using ReductionData = Parallel::ReductionData<
148 : // Observation value
149 : Parallel::ReductionDatum<double, funcl::AssertEqual<>>,
150 : // Number of grid points
151 : Parallel::ReductionDatum<size_t, funcl::Plus<>>,
152 : // Total volume
153 : Parallel::ReductionDatum<double, funcl::Plus<>>,
154 : // Max
155 : Parallel::ReductionDatum<std::vector<double>,
156 : funcl::ElementWise<funcl::Max<>>>,
157 : // Min
158 : Parallel::ReductionDatum<std::vector<double>,
159 : funcl::ElementWise<funcl::Min<>>>,
160 : // L2Norm
161 : Parallel::ReductionDatum<
162 : std::vector<double>, funcl::ElementWise<funcl::Plus<>>,
163 : funcl::ElementWise<funcl::Sqrt<funcl::Divides<>>>,
164 : std::index_sequence<1>>,
165 : // L2IntegralNorm
166 : Parallel::ReductionDatum<
167 : std::vector<double>, funcl::ElementWise<funcl::Plus<>>,
168 : funcl::ElementWise<funcl::Sqrt<funcl::Divides<>>>,
169 : std::index_sequence<2>>,
170 : // VolumeIntegral
171 : Parallel::ReductionDatum<std::vector<double>,
172 : funcl::ElementWise<funcl::Plus<>>>>;
173 :
174 : public:
175 0 : static std::string name() {
176 : if constexpr (std::is_same_v<OptionName, void>) {
177 : return "ObserveNorms";
178 : } else {
179 : return pretty_type::name<OptionName>();
180 : }
181 : }
182 :
183 : /// The name of the subfile inside the HDF5 file
184 1 : struct SubfileName {
185 0 : using type = std::string;
186 0 : static constexpr Options::String help = {
187 : "The name of the subfile inside the HDF5 file without an extension and "
188 : "without a preceding '/'."};
189 : };
190 : /// The tensor to observe and how to do the reduction
191 1 : struct TensorsToObserve {
192 0 : using type = std::vector<ObserveTensor>;
193 0 : static constexpr Options::String help = {
194 : "List specifying each tensor to observe and how it is reduced."};
195 : };
196 :
197 0 : explicit ObserveNorms(CkMigrateMessage* msg);
198 : using PUP::able::register_constructor;
199 0 : WRAPPED_PUPable_decl_template(ObserveNorms); // NOLINT
200 :
201 0 : using options = tmpl::list<SubfileName, TensorsToObserve>;
202 :
203 0 : static constexpr Options::String help =
204 : "Observe norms of tensors in the DataBox.\n"
205 : "\n"
206 : "You can choose the norm type for each observation. Note that the\n"
207 : "'L2Norm' (root mean square) emphasizes regions of the domain with many\n"
208 : "grid points, whereas the 'L2IntegralNorm' emphasizes regions of the\n"
209 : "domain with large volume. Choose wisely! When in doubt, try the\n"
210 : "'L2Norm' first.\n"
211 : "\n"
212 : "Writes reduction quantities:\n"
213 : " * Observation value (e.g. Time or IterationId)\n"
214 : " * NumberOfPoints = total number of points in the domain\n"
215 : " * Volume = total volume of the domain in inertial coordinates\n"
216 : " * Max values\n"
217 : " * Min values\n"
218 : " * L2-norm values\n"
219 : " * L2 integral norm values\n"
220 : " * Volume integral values\n";
221 :
222 0 : ObserveNorms() = default;
223 :
224 0 : ObserveNorms(const std::string& subfile_name,
225 : const std::vector<ObserveTensor>& observe_tensors);
226 :
227 0 : using observed_reduction_data_tags =
228 : observers::make_reduction_data_tags<tmpl::list<ReductionData>>;
229 :
230 0 : using compute_tags_for_observation_box =
231 : tmpl::list<ObservableTensorTags..., NonTensorComputeTags...>;
232 :
233 0 : using return_tags = tmpl::list<>;
234 0 : using argument_tags = tmpl::list<::Tags::ObservationBox>;
235 :
236 : template <typename TensorToObserveTag, typename ComputeTagsList,
237 : typename DataBoxType, size_t Dim>
238 0 : void observe_norms_impl(
239 : gsl::not_null<
240 : std::unordered_map<std::string, std::pair<std::vector<double>,
241 : std::vector<std::string>>>*>
242 : norm_values_and_names,
243 : const ObservationBox<ComputeTagsList, DataBoxType>& box,
244 : const Mesh<Dim>& mesh, const DataVector& det_jacobian,
245 : size_t number_of_points) const;
246 :
247 : template <typename ComputeTagsList, typename DataBoxType,
248 : typename Metavariables, size_t VolumeDim,
249 : typename ParallelComponent>
250 0 : void operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
251 : Parallel::GlobalCache<Metavariables>& cache,
252 : const ElementId<VolumeDim>& array_index,
253 : const ParallelComponent* const /*meta*/,
254 : const ObservationValue& observation_value) const;
255 :
256 0 : using observation_registration_tags = tmpl::list<::Tags::DataBox>;
257 :
258 : template <typename DbTagsList>
259 : std::optional<
260 : std::pair<observers::TypeOfObservation, observers::ObservationKey>>
261 0 : get_observation_type_and_key_for_registration(
262 : const db::DataBox<DbTagsList>& box) const {
263 : const std::optional<std::string> section_observation_key =
264 : observers::get_section_observation_key<ArraySectionIdTag>(box);
265 : if (not section_observation_key.has_value()) {
266 : return std::nullopt;
267 : }
268 : return {{observers::TypeOfObservation::Reduction,
269 : observers::ObservationKey(
270 : subfile_path_ + section_observation_key.value() + ".dat")}};
271 : }
272 :
273 0 : using is_ready_argument_tags = tmpl::list<>;
274 :
275 : template <typename Metavariables, typename ArrayIndex, typename Component>
276 0 : bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
277 : const ArrayIndex& /*array_index*/,
278 : const Component* const /*meta*/) const {
279 : return true;
280 : }
281 :
282 1 : bool needs_evolved_variables() const override { return true; }
283 :
284 : // NOLINTNEXTLINE(google-runtime-references)
285 0 : void pup(PUP::er& p) override;
286 :
287 : private:
288 0 : std::string subfile_path_;
289 0 : std::vector<std::string> tensor_names_{};
290 0 : std::vector<std::string> tensor_norm_types_{};
291 0 : std::vector<std::string> tensor_components_{};
292 : };
293 : /// @}
294 :
295 : /// \cond
296 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
297 : typename ArraySectionIdTag, typename OptionName>
298 : ObserveNorms<tmpl::list<ObservableTensorTags...>,
299 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
300 : OptionName>::ObserveNorms(CkMigrateMessage* msg)
301 : : Event(msg) {}
302 :
303 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
304 : typename ArraySectionIdTag, typename OptionName>
305 : ObserveNorms<tmpl::list<ObservableTensorTags...>,
306 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
307 : OptionName>::ObserveNorms(const std::string& subfile_name,
308 : const std::vector<ObserveTensor>&
309 : observe_tensors)
310 : : subfile_path_("/" + subfile_name) {
311 : tensor_names_.reserve(observe_tensors.size());
312 : tensor_norm_types_.reserve(observe_tensors.size());
313 : tensor_components_.reserve(observe_tensors.size());
314 : for (const auto& observe_tensor : observe_tensors) {
315 : tensor_names_.push_back(observe_tensor.tensor);
316 : tensor_norm_types_.push_back(observe_tensor.norm_type);
317 : tensor_components_.push_back(observe_tensor.components);
318 : }
319 : }
320 :
321 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
322 : typename ArraySectionIdTag, typename OptionName>
323 : ObserveNorms<
324 : tmpl::list<ObservableTensorTags...>, tmpl::list<NonTensorComputeTags...>,
325 : ArraySectionIdTag,
326 : OptionName>::ObserveTensor::ObserveTensor(std::string in_tensor,
327 : std::string in_norm_type,
328 : std::string in_components,
329 : const Options::Context& context)
330 : : tensor(std::move(in_tensor)),
331 : norm_type(std::move(in_norm_type)),
332 : components(std::move(in_components)) {
333 : if (((tensor != db::tag_name<ObservableTensorTags>()) and ...)) {
334 : PARSE_ERROR(
335 : context, "Tensor '"
336 : << tensor << "' is not known. Known tensors are: "
337 : << ((db::tag_name<ObservableTensorTags>() + ",") + ...));
338 : }
339 : if (norm_type != "Max" and norm_type != "Min" and norm_type != "L2Norm" and
340 : norm_type != "L2IntegralNorm" and norm_type != "VolumeIntegral") {
341 : PARSE_ERROR(
342 : context,
343 : "NormType must be one of Max, Min, L2Norm, L2IntegralNorm, or "
344 : "VolumeIntegral not " << norm_type);
345 : }
346 : if (components != "Individual" and components != "Sum") {
347 : PARSE_ERROR(context,
348 : "Components must be Individual or Sum, not " << components);
349 : }
350 : }
351 :
352 : // implementation of ObserveNorms::operator() factored out to save on compile
353 : // time and compile memory
354 : namespace ObserveNorms_impl {
355 : void check_norm_is_observable(const std::string& tensor_name,
356 : bool tag_has_value);
357 :
358 : template <size_t Dim>
359 : void fill_norm_values_and_names(
360 : gsl::not_null<std::unordered_map<
361 : std::string, std::pair<std::vector<double>, std::vector<std::string>>>*>
362 : norm_values_and_names,
363 : const std::pair<std::vector<std::string>, std::vector<DataVector>>&
364 : names_and_components,
365 : const Mesh<Dim>& mesh, const DataVector& det_jacobian,
366 : const std::string& tensor_name, const std::string& tensor_norm_type,
367 : const std::string& tensor_component, size_t number_of_points);
368 :
369 : // Expand complex data into real and imaginary parts, or just forward real data
370 : std::pair<std::vector<std::string>, std::vector<DataVector>>
371 : split_complex_vector_of_data(
372 : std::pair<std::vector<std::string>, std::vector<DataVector>>&&
373 : names_and_components);
374 : std::pair<std::vector<std::string>, std::vector<DataVector>>
375 : split_complex_vector_of_data(
376 : const std::pair<std::vector<std::string>, std::vector<ComplexDataVector>>&
377 : names_and_components);
378 : } // namespace ObserveNorms_impl
379 :
380 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
381 : typename ArraySectionIdTag, typename OptionName>
382 : template <typename TensorToObserveTag, typename ComputeTagsList,
383 : typename DataBoxType, size_t Dim>
384 : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
385 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
386 : OptionName>::
387 : observe_norms_impl(
388 : const gsl::not_null<std::unordered_map<
389 : std::string,
390 : std::pair<std::vector<double>, std::vector<std::string>>>*>
391 : norm_values_and_names,
392 : const ObservationBox<ComputeTagsList, DataBoxType>& box,
393 : const Mesh<Dim>& mesh, const DataVector& det_jacobian,
394 : const size_t number_of_points) const {
395 : const std::string tensor_name = db::tag_name<TensorToObserveTag>();
396 : for (size_t i = 0; i < tensor_names_.size(); ++i) {
397 : if (tensor_name == tensor_names_[i]) {
398 : ObserveNorms_impl::check_norm_is_observable(
399 : tensor_name, has_value(get<TensorToObserveTag>(box)));
400 : ObserveNorms_impl::fill_norm_values_and_names(
401 : norm_values_and_names,
402 : ObserveNorms_impl::split_complex_vector_of_data(
403 : value(get<TensorToObserveTag>(box)).get_vector_of_data()),
404 : mesh, det_jacobian, tensor_name, tensor_norm_types_[i],
405 : tensor_components_[i], number_of_points);
406 : }
407 : }
408 : }
409 :
410 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
411 : typename ArraySectionIdTag, typename OptionName>
412 : template <typename ComputeTagsList, typename DataBoxType,
413 : typename Metavariables, size_t VolumeDim, typename ParallelComponent>
414 : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
415 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
416 : OptionName>::
417 : operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
418 : Parallel::GlobalCache<Metavariables>& cache,
419 : const ElementId<VolumeDim>& array_index,
420 : const ParallelComponent* const /*meta*/,
421 : const ObservationValue& observation_value) const {
422 : // Skip observation on elements that are not part of a section
423 : const std::optional<std::string> section_observation_key =
424 : observers::get_section_observation_key<ArraySectionIdTag>(box);
425 : if (not section_observation_key.has_value()) {
426 : return;
427 : }
428 :
429 : const auto& mesh = get<::Events::Tags::ObserverMesh<VolumeDim>>(box);
430 : const auto det_jacobian = [&box, &mesh]() -> DataVector {
431 : if constexpr (VolumeDim == 3 and
432 : db::tag_is_retrievable_v<
433 : domain::Tags::Coordinates<VolumeDim, Frame::Inertial>,
434 : std::decay_t<decltype(box)>>) {
435 : if (mesh.basis(2) == Spectral::Basis::Cartoon) {
436 : if (mesh.quadrature(2) == Spectral::Quadrature::SphericalSymmetry) {
437 : // Spherical Symmetry, needs x^2 cartesian to spherical jacobian
438 : return square(get<0>(
439 : get<domain::Tags::Coordinates<VolumeDim, Frame::Inertial>>(
440 : box))) /
441 : get(get<::Events::Tags::ObserverDetInvJacobian<
442 : Frame::ElementLogical, Frame::Inertial>>(box));
443 : } else {
444 : // Axial Symmetry, needs x cartesian to cylindrical jacobian
445 : ASSERT(mesh.quadrature(2) == Spectral::Quadrature::AxialSymmetry,
446 : "Unexpected quadrature " << mesh.quadrature(2)
447 : << " (expected AxialSymmetry)");
448 : return get<0>(
449 : get<domain::Tags::Coordinates<VolumeDim, Frame::Inertial>>(
450 : box)) /
451 : get(get<::Events::Tags::ObserverDetInvJacobian<
452 : Frame::ElementLogical, Frame::Inertial>>(box));
453 : }
454 : } else {
455 : return 1. / get(get<::Events::Tags::ObserverDetInvJacobian<
456 : Frame::ElementLogical, Frame::Inertial>>(box));
457 : }
458 : } else {
459 : (void)mesh;
460 : return 1. / get(get<::Events::Tags::ObserverDetInvJacobian<
461 : Frame::ElementLogical, Frame::Inertial>>(box));
462 : }
463 : }();
464 : const size_t number_of_points = mesh.number_of_grid_points();
465 : const double local_volume = definite_integral(det_jacobian, mesh);
466 :
467 : std::unordered_map<std::string,
468 : std::pair<std::vector<double>, std::vector<std::string>>>
469 : norm_values_and_names{};
470 : // Loop over ObservableTensorTags and see if it was requested to be observed.
471 : // This approach allows us to delay evaluating any compute tags until they're
472 : // actually needed for observing.
473 : (observe_norms_impl<ObservableTensorTags>(
474 : make_not_null(&norm_values_and_names), box, mesh, det_jacobian,
475 : number_of_points),
476 : ...);
477 :
478 : // Concatenate the legend info together.
479 : std::vector<std::string> legend{observation_value.name, "NumberOfPoints",
480 : "Volume"};
481 : legend.insert(legend.end(), norm_values_and_names["Max"].second.begin(),
482 : norm_values_and_names["Max"].second.end());
483 : legend.insert(legend.end(), norm_values_and_names["Min"].second.begin(),
484 : norm_values_and_names["Min"].second.end());
485 : legend.insert(legend.end(), norm_values_and_names["L2Norm"].second.begin(),
486 : norm_values_and_names["L2Norm"].second.end());
487 : legend.insert(legend.end(),
488 : norm_values_and_names["L2IntegralNorm"].second.begin(),
489 : norm_values_and_names["L2IntegralNorm"].second.end());
490 : legend.insert(legend.end(),
491 : norm_values_and_names["VolumeIntegral"].second.begin(),
492 : norm_values_and_names["VolumeIntegral"].second.end());
493 :
494 : const std::string subfile_path_with_suffix =
495 : subfile_path_ + section_observation_key.value();
496 : // Send data to reduction observer
497 : auto& local_observer = *Parallel::local_branch(
498 : Parallel::get_parallel_component<
499 : tmpl::conditional_t<Parallel::is_nodegroup_v<ParallelComponent>,
500 : observers::ObserverWriter<Metavariables>,
501 : observers::Observer<Metavariables>>>(cache));
502 : observers::ObservationId observation_id{observation_value.value,
503 : subfile_path_with_suffix + ".dat"};
504 : Parallel::ArrayComponentId array_component_id{
505 : std::add_pointer_t<ParallelComponent>{nullptr},
506 : Parallel::ArrayIndex<ElementId<VolumeDim>>(array_index)};
507 : ReductionData reduction_data{
508 : observation_value.value,
509 : number_of_points,
510 : local_volume,
511 : std::move(norm_values_and_names["Max"].first),
512 : std::move(norm_values_and_names["Min"].first),
513 : std::move(norm_values_and_names["L2Norm"].first),
514 : std::move(norm_values_and_names["L2IntegralNorm"].first),
515 : std::move(norm_values_and_names["VolumeIntegral"].first)};
516 :
517 : if constexpr (Parallel::is_nodegroup_v<ParallelComponent>) {
518 : Parallel::threaded_action<
519 : observers::ThreadedActions::CollectReductionDataOnNode>(
520 : local_observer, std::move(observation_id),
521 : std::move(array_component_id), subfile_path_with_suffix,
522 : std::move(legend), std::move(reduction_data));
523 : } else {
524 : Parallel::simple_action<observers::Actions::ContributeReductionData>(
525 : local_observer, std::move(observation_id),
526 : std::move(array_component_id), subfile_path_with_suffix,
527 : std::move(legend), std::move(reduction_data));
528 : }
529 : }
530 :
531 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
532 : typename ArraySectionIdTag, typename OptionName>
533 : void ObserveNorms<tmpl::list<ObservableTensorTags...>,
534 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag,
535 : OptionName>::pup(PUP::er& p) {
536 : Event::pup(p);
537 : p | subfile_path_;
538 : p | tensor_names_;
539 : p | tensor_norm_types_;
540 : p | tensor_components_;
541 : }
542 :
543 : // NOLINTBEGIN(cppcoreguidelines-avoid-non-const-global-variables)
544 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
545 : typename ArraySectionIdTag, typename OptionName>
546 : PUP::able::PUP_ID ObserveNorms<tmpl::list<ObservableTensorTags...>,
547 : tmpl::list<NonTensorComputeTags...>,
548 : ArraySectionIdTag, OptionName>::my_PUP_ID = 0;
549 : // NOLINTEND(cppcoreguidelines-avoid-non-const-global-variables)
550 : /// \endcond
551 : } // namespace Events
|