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