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 <functional>
8 : #include <initializer_list>
9 : #include <optional>
10 : #include <pup.h>
11 : #include <string>
12 : #include <type_traits>
13 : #include <unordered_map>
14 : #include <utility>
15 : #include <vector>
16 :
17 : #include "DataStructures/DataBox/ObservationBox.hpp"
18 : #include "DataStructures/DataBox/Prefixes.hpp"
19 : #include "DataStructures/DataBox/TagName.hpp"
20 : #include "DataStructures/DataBox/ValidateSelection.hpp"
21 : #include "DataStructures/DataVector.hpp"
22 : #include "DataStructures/FloatingPointType.hpp"
23 : #include "Domain/Structure/BlockGroups.hpp"
24 : #include "Domain/Structure/ElementId.hpp"
25 : #include "Domain/Tags.hpp"
26 : #include "IO/H5/TensorData.hpp"
27 : #include "IO/Observer/GetSectionObservationKey.hpp"
28 : #include "IO/Observer/ObservationId.hpp"
29 : #include "IO/Observer/Tags.hpp"
30 : #include "IO/Observer/VolumeActions.hpp"
31 : #include "NumericalAlgorithms/Interpolation/RegularGridInterpolant.hpp"
32 : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
33 : #include "NumericalAlgorithms/Spectral/Projection.hpp"
34 : #include "Options/Auto.hpp"
35 : #include "Options/String.hpp"
36 : #include "Parallel/ArrayComponentId.hpp"
37 : #include "Parallel/ArrayIndex.hpp"
38 : #include "Parallel/GlobalCache.hpp"
39 : #include "Parallel/Printf/Printf.hpp"
40 : #include "Parallel/TypeTraits.hpp"
41 : #include "ParallelAlgorithms/Events/Tags.hpp"
42 : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
43 : #include "PointwiseFunctions/AnalyticSolutions/Tags.hpp"
44 : #include "Utilities/ErrorHandling/Assert.hpp"
45 : #include "Utilities/ErrorHandling/Error.hpp"
46 : #include "Utilities/Literals.hpp"
47 : #include "Utilities/MakeString.hpp"
48 : #include "Utilities/Numeric.hpp"
49 : #include "Utilities/OptionalHelpers.hpp"
50 : #include "Utilities/Serialization/CharmPupable.hpp"
51 : #include "Utilities/Serialization/PupStlCpp17.hpp"
52 : #include "Utilities/StdHelpers.hpp"
53 : #include "Utilities/TMPL.hpp"
54 : #include "Utilities/TypeTraits/IsA.hpp"
55 :
56 : /// \cond
57 : template <size_t Dim>
58 : class Mesh;
59 : namespace Frame {
60 : struct Inertial;
61 : } // namespace Frame
62 : /// \endcond
63 :
64 : namespace dg {
65 : namespace Events {
66 : /// \cond
67 : template <size_t VolumeDim, typename Tensors,
68 : typename NonTensorComputeTagsList = tmpl::list<>,
69 : typename ArraySectionIdTag = void>
70 : class ObserveFields;
71 : /// \endcond
72 :
73 : /*!
74 : * \ingroup DiscontinuousGalerkinGroup
75 : * \brief %Observe volume tensor fields.
76 : *
77 : * A class that writes volume quantities to an h5 file during the simulation.
78 : * The observed quantitites are specified in the `VariablesToObserve` option.
79 : * Any `Tensor` in the `db::DataBox` can be observed but must be listed in the
80 : * `Tensors` template parameter. Any additional compute tags that hold a
81 : * `Tensor` can also be added to the `Tensors` template parameter. Finally,
82 : * `Variables` and other non-tensor compute tags can be listed in the
83 : * `NonTensorComputeTags` to facilitate observing. Note that the
84 : * `InertialCoordinates` are always observed.
85 : *
86 : * The user may specify an `interpolation_mesh` to which the
87 : * data is interpolated.
88 : *
89 : * \note The `NonTensorComputeTags` are intended to be used for `Variables`
90 : * compute tags like `Tags::DerivCompute`
91 : *
92 : * \par Array sections
93 : * This event supports sections (see `Parallel::Section`). Set the
94 : * `ArraySectionIdTag` template parameter to split up observations into subsets
95 : * of elements. The `observers::Tags::ObservationKey<ArraySectionIdTag>` must be
96 : * available in the DataBox. It identifies the section and is used as a suffix
97 : * for the path in the output file.
98 : */
99 : template <size_t VolumeDim, typename... Tensors,
100 : typename... NonTensorComputeTags, typename ArraySectionIdTag>
101 1 : class ObserveFields<VolumeDim, tmpl::list<Tensors...>,
102 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>
103 : : public Event {
104 : public:
105 : /// The name of the subfile inside the HDF5 file
106 1 : struct SubfileName {
107 0 : using type = std::string;
108 0 : static constexpr Options::String help = {
109 : "The name of the subfile inside the HDF5 file without an extension and "
110 : "without a preceding '/'."};
111 : };
112 :
113 : /// \cond
114 : explicit ObserveFields(CkMigrateMessage* /*unused*/) {}
115 : using PUP::able::register_constructor;
116 : WRAPPED_PUPable_decl_template(ObserveFields); // NOLINT
117 : /// \endcond
118 :
119 0 : struct VariablesToObserve {
120 0 : static constexpr Options::String help = "Subset of variables to observe";
121 0 : using type = std::vector<std::string>;
122 0 : static size_t lower_bound_on_size() { return 1; }
123 : };
124 :
125 0 : struct InterpolateToMesh {
126 0 : using type = Options::Auto<Mesh<VolumeDim>, Options::AutoLabel::None>;
127 0 : static constexpr Options::String help =
128 : "An optional mesh to which the variables are interpolated. This mesh "
129 : "specifies any number of collocation points, basis, and quadrature on "
130 : "which the observed quantities are evaluated. If no mesh is given, the "
131 : "results will be evaluated on the mesh the simulation runs on. The "
132 : "user may add several ObserveField Events e.g. with and without an "
133 : "interpolating mesh to output the data both on the original mesh and "
134 : "on a new mesh.";
135 : };
136 :
137 0 : struct ProjectToMesh {
138 0 : using type = Options::Auto<Mesh<VolumeDim>, Options::AutoLabel::None>;
139 0 : static constexpr Options::String help =
140 : "An optional mesh to which the variables are projected. This mesh "
141 : "must use a Legendre basis (Gauss or Gauss-Lobatto quadrature). This "
142 : "option is mutually exclusive with InterpolateToMesh. Projection "
143 : "truncates the fields in modal space but works only between two "
144 : "Legendre meshes, whereas interpolation works with any mesh but is not "
145 : "a clean truncation and therefore picks up an aliasing error.";
146 : };
147 :
148 : /// The floating point type/precision with which to write the data to disk.
149 : ///
150 : /// Must be specified once for all data or individually for each variable
151 : /// being observed.
152 1 : struct FloatingPointTypes {
153 0 : static constexpr Options::String help =
154 : "The floating point type/precision with which to write the data to "
155 : "disk.\n\n"
156 : "Must be specified once for all data or individually for each "
157 : "variable being observed.";
158 0 : using type = std::vector<FloatingPointType>;
159 0 : static size_t upper_bound_on_size() { return sizeof...(Tensors); }
160 0 : static size_t lower_bound_on_size() { return 1; }
161 : };
162 :
163 : /// The floating point type/precision with which to write the coordinates to
164 : /// disk.
165 1 : struct CoordinatesFloatingPointType {
166 0 : static constexpr Options::String help =
167 : "The floating point type/precision with which to write the coordinates "
168 : "to disk.";
169 0 : using type = FloatingPointType;
170 : };
171 :
172 : /// \brief A list of block or group names on which to observe.
173 : ///
174 : /// Set to `All` to observe everywhere.
175 1 : struct BlocksToObserve {
176 0 : using type =
177 : Options::Auto<std::vector<std::string>, Options::AutoLabel::All>;
178 0 : static constexpr Options::String help = {
179 : "A list of block and group names on which to observe."};
180 : };
181 :
182 0 : using options = tmpl::list<SubfileName, CoordinatesFloatingPointType,
183 : FloatingPointTypes, VariablesToObserve,
184 : BlocksToObserve, InterpolateToMesh, ProjectToMesh>;
185 :
186 0 : static constexpr Options::String help =
187 : "Observe volume tensor fields.\n"
188 : "\n"
189 : "Writes volume quantities:\n"
190 : " * InertialCoordinates\n"
191 : " * Tensors listed in the 'VariablesToObserve' option\n";
192 :
193 0 : ObserveFields() = default;
194 :
195 : // The dependency isn't available through Options
196 0 : ObserveFields(
197 : const std::string& subfile_name,
198 : FloatingPointType coordinates_floating_point_type,
199 : const std::vector<FloatingPointType>& floating_point_types,
200 : const std::vector<std::string>& variables_to_observe,
201 : std::optional<std::vector<std::string>> active_block_or_block_groups = {},
202 : std::optional<Mesh<VolumeDim>> interpolation_mesh = {},
203 : std::optional<Mesh<VolumeDim>> projection_mesh = {},
204 : std::optional<std::string> dependency = {},
205 : const Options::Context& context = {});
206 :
207 0 : ObserveFields(
208 : const std::string& subfile_name,
209 : FloatingPointType coordinates_floating_point_type,
210 : const std::vector<FloatingPointType>& floating_point_types,
211 : const std::vector<std::string>& variables_to_observe,
212 : std::optional<std::vector<std::string>> active_block_or_block_groups = {},
213 : std::optional<Mesh<VolumeDim>> interpolation_mesh = {},
214 : std::optional<Mesh<VolumeDim>> projection_mesh = {},
215 : const Options::Context& context = {})
216 : : ObserveFields(subfile_name, coordinates_floating_point_type,
217 : floating_point_types, variables_to_observe,
218 : std::move(active_block_or_block_groups),
219 : std::move(interpolation_mesh), std::move(projection_mesh),
220 : std::nullopt, context) {}
221 :
222 0 : using compute_tags_for_observation_box =
223 : tmpl::list<Tensors..., NonTensorComputeTags...>;
224 :
225 0 : using return_tags = tmpl::list<>;
226 0 : using argument_tags = tmpl::list<::Tags::ObservationBox,
227 : ::Events::Tags::ObserverMesh<VolumeDim>>;
228 :
229 : template <typename DataBoxType, typename ComputeTagsList,
230 : typename Metavariables, typename ParallelComponent>
231 0 : void operator()(const ObservationBox<DataBoxType, ComputeTagsList>& box,
232 : const Mesh<VolumeDim>& mesh,
233 : Parallel::GlobalCache<Metavariables>& cache,
234 : const ElementId<VolumeDim>& array_index,
235 : const ParallelComponent* const component,
236 : const ObservationValue& observation_value) const {
237 : if (not active_block(get<domain::Tags::Domain<VolumeDim>>(box),
238 : array_index)) {
239 : return;
240 : }
241 : // Skip observation on elements that are not part of a section
242 : const std::optional<std::string> section_observation_key =
243 : observers::get_section_observation_key<ArraySectionIdTag>(box);
244 : if (not section_observation_key.has_value()) {
245 : return;
246 : }
247 : call_operator_impl(subfile_path_ + *section_observation_key,
248 : variables_to_observe_, interpolation_mesh_,
249 : projection_mesh_, mesh, box, cache, array_index,
250 : component, observation_value, dependency_);
251 : }
252 :
253 : // We factor out the work into a static member function so it can be shared
254 : // with other field observing events, like the one that deals with DG-subcell
255 : // where there are two grids. This is to avoid copy-pasting all of the code.
256 : template <typename DataBoxType, typename ComputeTagsList,
257 : typename Metavariables, typename ParallelComponent>
258 0 : static void call_operator_impl(
259 : const std::string& subfile_path,
260 : const std::unordered_map<std::string, FloatingPointType>&
261 : variables_to_observe,
262 : const std::optional<Mesh<VolumeDim>>& interpolation_mesh,
263 : const std::optional<Mesh<VolumeDim>>& projection_mesh,
264 : const Mesh<VolumeDim>& mesh,
265 : const ObservationBox<DataBoxType, ComputeTagsList>& box,
266 : Parallel::GlobalCache<Metavariables>& cache,
267 : const ElementId<VolumeDim>& element_id,
268 : const ParallelComponent* const /*meta*/,
269 : const ObservationValue& observation_value,
270 : const std::optional<std::string>& dependency) {
271 : const bool do_projection = projection_mesh.has_value();
272 : const Mesh<VolumeDim>& target_mesh =
273 : do_projection ? projection_mesh.value()
274 : : interpolation_mesh.value_or(mesh);
275 : if (do_projection) {
276 : for (size_t d = 0; d < VolumeDim; ++d) {
277 : if (mesh.basis(d) != Spectral::Basis::Legendre or
278 : target_mesh.basis(d) != Spectral::Basis::Legendre) {
279 : ERROR("ProjectToMesh requires Legendre basis.");
280 : }
281 : if ((mesh.quadrature(d) != Spectral::Quadrature::Gauss and
282 : mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto) or
283 : (target_mesh.quadrature(d) != Spectral::Quadrature::Gauss and
284 : target_mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto)) {
285 : ERROR("ProjectToMesh requires Gauss or Gauss-Lobatto quadrature.");
286 : }
287 : }
288 : }
289 : // If no interpolation_mesh is provided, the interpolation is essentially
290 : // ignored by the RegularGridInterpolant except for a single copy.
291 : const intrp::RegularGrid interpolant(mesh, target_mesh);
292 :
293 : // Remove tensor types, only storing individual components.
294 : std::vector<TensorComponent> components;
295 : // This is larger than we need if we are only observing some
296 : // tensors, but that's not a big deal and calculating the correct
297 : // size is nontrivial.
298 : components.reserve(alg::accumulate(
299 : std::initializer_list<size_t>{
300 : std::decay_t<decltype(value(typename Tensors::type{}))>::size()...},
301 : 0_st));
302 :
303 : const auto record_tensor_component_impl =
304 : [&components](DataVector&& tensor_component,
305 : const FloatingPointType floating_point_type,
306 : const std::string& component_name) {
307 : if (floating_point_type == FloatingPointType::Float) {
308 : components.emplace_back(component_name,
309 : std::vector<float>{tensor_component.begin(),
310 : tensor_component.end()});
311 : } else {
312 : components.emplace_back(component_name,
313 : std::move(tensor_component));
314 : }
315 : };
316 :
317 : const auto record_tensor_components_impl =
318 : [&record_tensor_component_impl, &interpolant, &mesh, &target_mesh,
319 : do_projection](const auto& tensor,
320 : const FloatingPointType floating_point_type,
321 : const std::string& tag_name) {
322 : using TensorType = std::decay_t<decltype(tensor)>;
323 : using VectorType = typename TensorType::type;
324 : for (size_t i = 0; i < tensor.size(); ++i) {
325 : auto tensor_component =
326 : do_projection
327 : ? Spectral::project(
328 : tensor[i], mesh, target_mesh,
329 : make_array<VolumeDim>(Spectral::SegmentSize::Full),
330 : make_array<VolumeDim>(Spectral::SegmentSize::Full))
331 : : interpolant.interpolate(tensor[i]);
332 : const std::string component_name =
333 : tag_name + tensor.component_suffix(i);
334 : if constexpr (std::is_same_v<VectorType, ComplexDataVector>) {
335 : record_tensor_component_impl(real(tensor_component),
336 : floating_point_type,
337 : "Re(" + component_name + ")");
338 : record_tensor_component_impl(imag(tensor_component),
339 : floating_point_type,
340 : "Im(" + component_name + ")");
341 : } else {
342 : record_tensor_component_impl(std::move(tensor_component),
343 : floating_point_type, component_name);
344 : }
345 : }
346 : };
347 : const auto record_tensor_components =
348 : [&box, &record_tensor_components_impl,
349 : &variables_to_observe](const auto tensor_tag_v) {
350 : using tensor_tag = tmpl::type_from<decltype(tensor_tag_v)>;
351 : const std::string tag_name = db::tag_name<tensor_tag>();
352 : if (const auto var_to_observe = variables_to_observe.find(tag_name);
353 : var_to_observe != variables_to_observe.end()) {
354 : const auto& tensor = get<tensor_tag>(box);
355 : if (not has_value(tensor)) {
356 : // This will only print a warning the first time it's called on a
357 : // node.
358 : [[maybe_unused]] static bool t =
359 : ObserveFields::print_warning_about_optional<tensor_tag>();
360 : return;
361 : }
362 : const auto floating_point_type = var_to_observe->second;
363 : record_tensor_components_impl(value(tensor), floating_point_type,
364 : tag_name);
365 : }
366 : };
367 : EXPAND_PACK_LEFT_TO_RIGHT(record_tensor_components(tmpl::type_<Tensors>{}));
368 :
369 : const Parallel::ArrayComponentId array_component_id{
370 : std::add_pointer_t<ParallelComponent>{nullptr},
371 : Parallel::ArrayIndex<ElementId<VolumeDim>>{element_id}};
372 : ElementVolumeData element_volume_data{element_id, std::move(components),
373 : target_mesh};
374 : observers::ObservationId observation_id{observation_value.value,
375 : subfile_path + ".vol"};
376 :
377 : observers::contribute_volume_data<
378 : not Parallel::is_nodegroup_v<ParallelComponent>>(
379 : cache, std::move(observation_id), subfile_path, array_component_id,
380 : std::move(element_volume_data), dependency);
381 : }
382 :
383 0 : using observation_registration_tags = tmpl::list<::Tags::DataBox>;
384 :
385 : template <typename DbTagsList>
386 : std::optional<
387 : std::pair<observers::TypeOfObservation, observers::ObservationKey>>
388 0 : get_observation_type_and_key_for_registration(
389 : const db::DataBox<DbTagsList>& box) const {
390 : if (not active_block(db::get<domain::Tags::Domain<VolumeDim>>(box),
391 : db::get<domain::Tags::Element<VolumeDim>>(box).id())) {
392 : return std::nullopt;
393 : }
394 : const std::optional<std::string> section_observation_key =
395 : observers::get_section_observation_key<ArraySectionIdTag>(box);
396 : if (not section_observation_key.has_value()) {
397 : return std::nullopt;
398 : }
399 : return {{observers::TypeOfObservation::Volume,
400 : observers::ObservationKey{
401 : subfile_path_ + section_observation_key.value() + ".vol"}}};
402 : }
403 :
404 0 : using is_ready_argument_tags = tmpl::list<>;
405 :
406 : template <typename Metavariables, typename ArrayIndex, typename Component>
407 0 : bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
408 : const ArrayIndex& /*array_index*/,
409 : const Component* const /*meta*/) const {
410 : return true;
411 : }
412 :
413 1 : bool needs_evolved_variables() const override { return true; }
414 :
415 : // NOLINTNEXTLINE(google-runtime-references)
416 0 : void pup(PUP::er& p) override {
417 : Event::pup(p);
418 : p | subfile_path_;
419 : p | variables_to_observe_;
420 : p | active_block_or_block_groups_;
421 : p | interpolation_mesh_;
422 : p | projection_mesh_;
423 : p | dependency_;
424 : }
425 :
426 : private:
427 : template <typename Tag>
428 0 : static bool print_warning_about_optional() {
429 : Parallel::printf(
430 : "Warning: ObserveFields is trying to dump the tag %s "
431 : "but it is stored as a std::optional and has not been "
432 : "evaluated. This most commonly occurs when you are "
433 : "trying to either observe an analytic solution or errors when "
434 : "no analytic solution is available.\n",
435 : db::tag_name<Tag>());
436 : return false;
437 : }
438 :
439 0 : bool active_block(const Domain<VolumeDim>& domain,
440 : const ElementId<VolumeDim>& element_id) const {
441 : if (not active_block_or_block_groups_.has_value()) {
442 : return true;
443 : }
444 : return not domain::block_or_group_names_containing_block(
445 : domain.blocks().at(element_id.block_id()).name(),
446 : active_block_or_block_groups_.value(), domain.block_names(),
447 : domain.block_groups())
448 : .empty();
449 : }
450 :
451 0 : std::string subfile_path_;
452 0 : std::unordered_map<std::string, FloatingPointType> variables_to_observe_{};
453 0 : std::optional<std::vector<std::string>> active_block_or_block_groups_{};
454 0 : std::optional<Mesh<VolumeDim>> interpolation_mesh_{};
455 0 : std::optional<Mesh<VolumeDim>> projection_mesh_{};
456 0 : std::optional<std::string> dependency_;
457 : };
458 :
459 : template <size_t VolumeDim, typename... Tensors,
460 : typename... NonTensorComputeTags, typename ArraySectionIdTag>
461 : ObserveFields<VolumeDim, tmpl::list<Tensors...>,
462 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>::
463 : ObserveFields(
464 : const std::string& subfile_name,
465 : const FloatingPointType coordinates_floating_point_type,
466 : const std::vector<FloatingPointType>& floating_point_types,
467 : const std::vector<std::string>& variables_to_observe,
468 : std::optional<std::vector<std::string>> active_block_or_block_groups,
469 : std::optional<Mesh<VolumeDim>> interpolation_mesh,
470 : std::optional<Mesh<VolumeDim>> projection_mesh,
471 : std::optional<std::string> dependency, const Options::Context& context)
472 : : subfile_path_("/" + subfile_name),
473 : variables_to_observe_([&context, &floating_point_types,
474 : &variables_to_observe]() {
475 : if (floating_point_types.size() != 1 and
476 : floating_point_types.size() != variables_to_observe.size()) {
477 : PARSE_ERROR(context, "The number of floating point types specified ("
478 : << floating_point_types.size()
479 : << ") must be 1 or the number of variables "
480 : "specified for observing ("
481 : << variables_to_observe.size() << ")");
482 : }
483 : std::unordered_map<std::string, FloatingPointType> result{};
484 : for (size_t i = 0; i < variables_to_observe.size(); ++i) {
485 : result[variables_to_observe[i]] = floating_point_types.size() == 1
486 : ? floating_point_types[0]
487 : : floating_point_types[i];
488 : ASSERT(
489 : result.at(variables_to_observe[i]) == FloatingPointType::Float or
490 : result.at(variables_to_observe[i]) ==
491 : FloatingPointType::Double,
492 : "Floating point type for variable '"
493 : << variables_to_observe[i]
494 : << "' must be either Float or Double.");
495 : }
496 : return result;
497 : }()),
498 : active_block_or_block_groups_(std::move(active_block_or_block_groups)),
499 : interpolation_mesh_(interpolation_mesh),
500 : projection_mesh_(projection_mesh),
501 : dependency_(std::move(dependency)) {
502 : if (interpolation_mesh_.has_value() and projection_mesh_.has_value()) {
503 : PARSE_ERROR(context,
504 : "Specify only one of InterpolateToMesh or ProjectToMesh.");
505 : }
506 : if (projection_mesh_.has_value()) {
507 : const auto& proj_mesh = projection_mesh_.value();
508 : for (size_t d = 0; d < VolumeDim; ++d) {
509 : if (proj_mesh.basis(d) != Spectral::Basis::Legendre) {
510 : PARSE_ERROR(context, "ProjectToMesh requires Legendre basis.");
511 : }
512 : if (proj_mesh.quadrature(d) != Spectral::Quadrature::Gauss and
513 : proj_mesh.quadrature(d) != Spectral::Quadrature::GaussLobatto) {
514 : PARSE_ERROR(
515 : context,
516 : "ProjectToMesh requires Gauss or Gauss-Lobatto quadrature.");
517 : }
518 : }
519 : }
520 : ASSERT(
521 : (... or (db::tag_name<Tensors>() == "InertialCoordinates")),
522 : "There is no tag with name 'InertialCoordinates' specified "
523 : "for the observer. Please make sure you specify a tag in the 'Tensors' "
524 : "list that has the 'db::tag_name()' 'InertialCoordinates'.");
525 : db::validate_selection<tmpl::list<Tensors...>>(variables_to_observe, context);
526 : variables_to_observe_["InertialCoordinates"] =
527 : coordinates_floating_point_type;
528 : }
529 :
530 : /// \cond
531 : template <size_t VolumeDim, typename... Tensors,
532 : typename... NonTensorComputeTags, typename ArraySectionIdTag>
533 : PUP::able::PUP_ID ObserveFields<VolumeDim, tmpl::list<Tensors...>,
534 : tmpl::list<NonTensorComputeTags...>,
535 : ArraySectionIdTag>::my_PUP_ID = 0; // NOLINT
536 : /// \endcond
537 : } // namespace Events
538 : } // namespace dg
|