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 :
11 : #include "DataStructures/DataBox/DataBox.hpp"
12 : #include "DataStructures/DataBox/ObservationBox.hpp"
13 : #include "DataStructures/DataBox/TagName.hpp"
14 : #include "DataStructures/DataVector.hpp"
15 : #include "DataStructures/Tensor/Tensor.hpp"
16 : #include "Domain/Structure/ElementId.hpp"
17 : #include "Domain/Tags.hpp"
18 : #include "IO/Observer/GetSectionObservationKey.hpp"
19 : #include "IO/Observer/Helpers.hpp"
20 : #include "IO/Observer/ObservationId.hpp"
21 : #include "IO/Observer/ObserverComponent.hpp"
22 : #include "IO/Observer/ReductionActions.hpp"
23 : #include "IO/Observer/TypeOfObservation.hpp"
24 : #include "NumericalAlgorithms/Spectral/Mesh.hpp"
25 : #include "Options/String.hpp"
26 : #include "Parallel/ArrayComponentId.hpp"
27 : #include "Parallel/ArrayIndex.hpp"
28 : #include "Parallel/GlobalCache.hpp"
29 : #include "Parallel/Invoke.hpp"
30 : #include "Parallel/Local.hpp"
31 : #include "Parallel/Reduction.hpp"
32 : #include "ParallelAlgorithms/Events/Tags.hpp"
33 : #include "ParallelAlgorithms/EventsAndTriggers/Event.hpp"
34 : #include "Utilities/ErrorHandling/Assert.hpp"
35 : #include "Utilities/ErrorHandling/Error.hpp"
36 : #include "Utilities/Functional.hpp"
37 : #include "Utilities/Numeric.hpp"
38 : #include "Utilities/OptionalHelpers.hpp"
39 : #include "Utilities/Serialization/CharmPupable.hpp"
40 : #include "Utilities/TMPL.hpp"
41 :
42 : namespace Events {
43 : /// @{
44 : /*!
45 : * \brief Find the extremum of a Scalar<DataVector> over
46 : * all elements, as well as the value of other functions
47 : * at the location of that extremum.
48 : *
49 : *
50 : * Here is an example of an input file:
51 : *
52 : * \snippet Test_ObserveAtExtremum.cpp input_file_examples
53 : *
54 : * \par Array sections
55 : * This event supports sections (see `Parallel::Section`). Set the
56 : * `ArraySectionIdTag` template parameter to split up observations into subsets
57 : * of elements. The `observers::Tags::ObservationKey<ArraySectionIdTag>` must be
58 : * available in the DataBox. It identifies the section and is used as a suffix
59 : * for the path in the output file.
60 : */
61 : template <typename ObservableTensorTagsList,
62 : typename NonTensorComputeTagsList = tmpl::list<>,
63 : typename ArraySectionIdTag = void>
64 1 : class ObserveAtExtremum;
65 :
66 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
67 : typename ArraySectionIdTag>
68 0 : class ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
69 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>
70 : : public Event {
71 : private:
72 : /// Reduction data will contain the time, and either the maximum
73 : /// or the minimum of a function
74 : template <typename MinMaxFunctional>
75 1 : using ReductionData = Parallel::ReductionData<
76 : // Observation value
77 : Parallel::ReductionDatum<double, funcl::AssertEqual<>>,
78 : // Maximum of first component of a vector
79 : Parallel::ReductionDatum<std::vector<double>, MinMaxFunctional>>;
80 :
81 : /// Information about the scalar whose extremum we search for,
82 : /// the type of extremum, and the other tensors to observer at
83 : /// that extremum
84 1 : struct ObserveTensors {
85 0 : static constexpr Options::String help = {
86 : "The tensor to extremize, and other tensors to observe."};
87 :
88 0 : struct Name {
89 0 : using type = std::string;
90 0 : static constexpr Options::String help = {
91 : "The name of the scalar to extremize."};
92 : };
93 :
94 0 : struct ExtremumType {
95 0 : using type = std::string;
96 0 : static constexpr Options::String help = {
97 : "The type of extremum -- either Min or Max."};
98 : };
99 :
100 0 : struct AdditionalData {
101 0 : using type = std::vector<std::string>;
102 0 : static constexpr Options::String help = {
103 : "List of other tensors to observe at the extremum"};
104 : };
105 :
106 0 : using options = tmpl::list<Name, ExtremumType, AdditionalData>;
107 :
108 0 : ObserveTensors() = default;
109 :
110 0 : ObserveTensors(std::string in_scalar, std::string in_extremum_type,
111 : std::vector<std::string> in_additional_data,
112 : const Options::Context& context = {});
113 :
114 0 : std::string scalar_name;
115 0 : std::string extremum_type;
116 0 : std::vector<std::string> additional_data{};
117 : };
118 :
119 : public:
120 : /// The name of the subfile inside the HDF5 file
121 1 : struct SubfileName {
122 0 : using type = std::string;
123 0 : static constexpr Options::String help = {
124 : "The name of the subfile inside the HDF5 file without an extension and "
125 : "without a preceding '/'."};
126 : };
127 : /// The scalar to extremize, and other tensors to observe at extremum
128 1 : struct TensorsToObserve {
129 0 : using type = ObserveTensors;
130 0 : static constexpr Options::String help = {
131 : "Struct specifying the scalar to extremize, the type of extremum "
132 : "and other tensors to observe at that extremum."};
133 : };
134 :
135 0 : explicit ObserveAtExtremum(CkMigrateMessage* msg);
136 : using PUP::able::register_constructor;
137 0 : WRAPPED_PUPable_decl_template(ObserveAtExtremum); // NOLINT
138 :
139 0 : using options = tmpl::list<SubfileName, TensorsToObserve>;
140 :
141 0 : static constexpr Options::String help =
142 : "Observe extremum of a scalar in the DataBox.\n"
143 : "\n"
144 : "Writes reduction quantities:\n"
145 : " * Observation value (e.g. Time or IterationId)\n"
146 : " * Extremum value of the desired scalar\n"
147 : " * Additional data at extremum\n";
148 :
149 0 : ObserveAtExtremum() = default;
150 :
151 0 : ObserveAtExtremum(std::string subfile_name,
152 : ObserveTensors observe_tensors);
153 :
154 0 : using observed_reduction_data_tags = observers::make_reduction_data_tags<
155 : tmpl::list<ReductionData<funcl::Max<>>, ReductionData<funcl::Min<>>>>;
156 :
157 0 : using compute_tags_for_observation_box =
158 : tmpl::list<ObservableTensorTags..., NonTensorComputeTags...>;
159 :
160 0 : using return_tags = tmpl::list<>;
161 0 : using argument_tags = tmpl::list<::Tags::ObservationBox>;
162 :
163 : template <typename ComputeTagsList, typename DataBoxType,
164 : typename Metavariables, size_t VolumeDim,
165 : typename ParallelComponent>
166 0 : void operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
167 : Parallel::GlobalCache<Metavariables>& cache,
168 : const ElementId<VolumeDim>& array_index,
169 : const ParallelComponent* const /*meta*/,
170 : const ObservationValue& observation_value) const;
171 :
172 0 : using observation_registration_tags = tmpl::list<::Tags::DataBox>;
173 :
174 : template <typename DbTagsList>
175 : std::optional<
176 : std::pair<observers::TypeOfObservation, observers::ObservationKey>>
177 0 : get_observation_type_and_key_for_registration(
178 : const db::DataBox<DbTagsList>& box) const {
179 : const std::optional<std::string> section_observation_key =
180 : observers::get_section_observation_key<ArraySectionIdTag>(box);
181 : if (not section_observation_key.has_value()) {
182 : return std::nullopt;
183 : }
184 : return {{observers::TypeOfObservation::Reduction,
185 : observers::ObservationKey(
186 : subfile_path_ + section_observation_key.value() + ".dat")}};
187 : }
188 :
189 0 : using is_ready_argument_tags = tmpl::list<>;
190 :
191 : template <typename Metavariables, typename ArrayIndex, typename Component>
192 0 : bool is_ready(Parallel::GlobalCache<Metavariables>& /*cache*/,
193 : const ArrayIndex& /*array_index*/,
194 : const Component* const /*meta*/) const {
195 : return true;
196 : }
197 :
198 1 : bool needs_evolved_variables() const override { return true; }
199 :
200 : // NOLINTNEXTLINE(google-runtime-references)
201 0 : void pup(PUP::er& p) override;
202 :
203 : private:
204 0 : std::string subfile_path_;
205 0 : std::string scalar_name_;
206 0 : std::string extremum_type_;
207 0 : std::vector<std::string> additional_tensor_names_{};
208 : };
209 : /// @}
210 :
211 : /// \cond
212 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
213 : typename ArraySectionIdTag>
214 : ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
215 : tmpl::list<NonTensorComputeTags...>,
216 : ArraySectionIdTag>::ObserveAtExtremum(CkMigrateMessage* msg)
217 : : Event(msg) {}
218 :
219 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
220 : typename ArraySectionIdTag>
221 : ObserveAtExtremum<
222 : tmpl::list<ObservableTensorTags...>, tmpl::list<NonTensorComputeTags...>,
223 : ArraySectionIdTag>::ObserveAtExtremum(std::string subfile_name,
224 : ObserveTensors observe_tensors)
225 : : subfile_path_("/" + subfile_name),
226 : scalar_name_(std::move(observe_tensors.scalar_name)),
227 : extremum_type_(std::move(observe_tensors.extremum_type)),
228 : additional_tensor_names_(std::move(observe_tensors.additional_data)) {}
229 :
230 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
231 : typename ArraySectionIdTag>
232 : ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
233 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>::
234 : ObserveTensors::ObserveTensors(std::string in_scalar,
235 : std::string in_extremum_type,
236 : std::vector<std::string> in_additional_data,
237 : const Options::Context& context)
238 : : scalar_name(std::move(in_scalar)),
239 : extremum_type(std::move(in_extremum_type)),
240 : additional_data(std::move(in_additional_data)) {
241 : if (((scalar_name != db::tag_name<ObservableTensorTags>()) and ...)) {
242 : PARSE_ERROR(
243 : context, "Tensor '"
244 : << scalar_name << "' is not known. Known tensors are: "
245 : << ((db::tag_name<ObservableTensorTags>() + ",") + ...));
246 : }
247 :
248 : tmpl::for_each<tmpl::list<ObservableTensorTags...>>(
249 : [this, &context](auto tag_v) {
250 : using tag = tmpl::type_from<decltype(tag_v)>;
251 : const std::string tensor_name = db::tag_name<tag>();
252 : if (tensor_name == scalar_name) {
253 : if constexpr (tt::is_a_v<std::optional, typename tag::type>) {
254 : if (tag::type::value_type::rank() != 0) {
255 : PARSE_ERROR(context,
256 : "ObserveAtExtremum can only observe scalars!");
257 : }
258 : } else if (tag::type::rank() != 0) {
259 : PARSE_ERROR(context, "ObserveAtExtremum can only observe scalars!");
260 : }
261 : }
262 : });
263 :
264 : if (extremum_type != "Max" and extremum_type != "Min") {
265 : PARSE_ERROR(context, "Extremum type " << extremum_type
266 : << " not recognized; use Max or Min");
267 : }
268 : for (const auto& tensor : additional_data) {
269 : if (((tensor != db::tag_name<ObservableTensorTags>()) and ...)) {
270 : PARSE_ERROR(context,
271 : "Tensor '"
272 : << tensor << "' is not known. Known tensors are: "
273 : << ((db::tag_name<ObservableTensorTags>() + ",") + ...));
274 : }
275 : }
276 : }
277 :
278 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
279 : typename ArraySectionIdTag>
280 : template <typename ComputeTagsList, typename DataBoxType,
281 : typename Metavariables, size_t VolumeDim, typename ParallelComponent>
282 : void ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
283 : tmpl::list<NonTensorComputeTags...>, ArraySectionIdTag>::
284 : operator()(const ObservationBox<ComputeTagsList, DataBoxType>& box,
285 : Parallel::GlobalCache<Metavariables>& cache,
286 : const ElementId<VolumeDim>& array_index,
287 : const ParallelComponent* const /*meta*/,
288 : const ObservationValue& observation_value) const {
289 : // Skip observation on elements that are not part of a section
290 : const std::optional<std::string> section_observation_key =
291 : observers::get_section_observation_key<ArraySectionIdTag>(box);
292 : if (not section_observation_key.has_value()) {
293 : return;
294 : }
295 :
296 : using tensor_tags = tmpl::list<ObservableTensorTags...>;
297 :
298 : // Vector that will contain the local extremum, and the value
299 : // of other tensors at that extremum
300 : std::vector<double> data_to_reduce{};
301 : // Vector containing a description of the data to be reduced.
302 : std::vector<std::string> legend{observation_value.name};
303 : // Location of the local extremum
304 : size_t index_of_extremum = 0;
305 : // First, look for local extremum of desired scalar
306 : tmpl::for_each<tensor_tags>([this, &box, &data_to_reduce, &legend,
307 : &index_of_extremum](auto tag_v) {
308 : using tag = tmpl::type_from<decltype(tag_v)>;
309 : const std::string tensor_name = db::tag_name<tag>();
310 : if (tensor_name == scalar_name_) {
311 : if (UNLIKELY(not has_value(get<tag>(box)))) {
312 : ERROR("Cannot observe a norm of '"
313 : << tensor_name
314 : << "' because it is a std::optional and wasn't able to be "
315 : "computed. This can happen when you try to observe errors "
316 : "without an analytic solution.");
317 : }
318 : const auto& scalar = value(get<tag>(box));
319 : const auto components = get<1>(scalar.get_vector_of_data());
320 : if (components.size() > 1) {
321 : ERROR("Extremum should be taken on a scalar, yet we have "
322 : << components.size() << " components in tensor " << tensor_name);
323 : }
324 : for (size_t i = 1; i < components[0].size(); i++) {
325 : if ((extremum_type_ == "Max" and
326 : (components[0][i] > components[0][index_of_extremum])) or
327 : (extremum_type_ == "Min" and
328 : (components[0][i] < components[0][index_of_extremum]))) {
329 : index_of_extremum = i;
330 : }
331 : }
332 : data_to_reduce.push_back(components[0][index_of_extremum]);
333 : if (extremum_type_ == "Max") {
334 : legend.push_back("Max(" + scalar_name_ + ")");
335 : } else {
336 : legend.push_back("Min(" + scalar_name_ + ")");
337 : }
338 : }
339 : });
340 : // Now get value of additional tensors at extremum
341 : tmpl::for_each<tensor_tags>([this, &box, &data_to_reduce, &legend,
342 : &index_of_extremum](auto tag_v) {
343 : using tag = tmpl::type_from<decltype(tag_v)>;
344 : const std::string tensor_name = db::tag_name<tag>();
345 : for (size_t i = 0; i < additional_tensor_names_.size(); ++i)
346 : if (tensor_name == additional_tensor_names_[i]) {
347 : if (UNLIKELY(not has_value(get<tag>(box)))) {
348 : ERROR("Cannot observe a norm of '"
349 : << tensor_name
350 : << "' because it is a std::optional and wasn't able to be "
351 : "computed. This can happen when you try to observe errors "
352 : "without an analytic solution.");
353 : }
354 : const auto& tensor = value(get<tag>(box));
355 : const auto [component_names, components] = tensor.get_vector_of_data();
356 : for (size_t j = 0; j < components.size(); j++) {
357 : data_to_reduce.push_back(components[j][index_of_extremum]);
358 : if (components.size() > 1) {
359 : legend.push_back("At" + scalar_name_ + extremum_type_ + "(" +
360 : tensor_name + "_" + component_names[j] + ")");
361 : } else {
362 : legend.push_back("At" + scalar_name_ + extremum_type_ + "(" +
363 : tensor_name + ")");
364 : }
365 : }
366 : }
367 : });
368 :
369 : // Send data to reduction observer
370 : auto& local_observer = *Parallel::local_branch(
371 : Parallel::get_parallel_component<observers::Observer<Metavariables>>(
372 : cache));
373 : const std::string subfile_path_with_suffix =
374 : subfile_path_ + section_observation_key.value();
375 :
376 : if (extremum_type_ == "Max") {
377 : Parallel::simple_action<observers::Actions::ContributeReductionData>(
378 : local_observer,
379 : observers::ObservationId(observation_value.value,
380 : subfile_path_with_suffix + ".dat"),
381 : Parallel::make_array_component_id<ParallelComponent>(array_index),
382 : subfile_path_with_suffix, std::move(legend),
383 : ReductionData<funcl::Max<>>{observation_value.value,
384 : std::move(data_to_reduce)});
385 : } else {
386 : Parallel::simple_action<observers::Actions::ContributeReductionData>(
387 : local_observer,
388 : observers::ObservationId(observation_value.value,
389 : subfile_path_with_suffix + ".dat"),
390 : Parallel::make_array_component_id<ParallelComponent>(array_index),
391 : subfile_path_with_suffix, std::move(legend),
392 : ReductionData<funcl::Min<>>{observation_value.value,
393 : std::move(data_to_reduce)});
394 : }
395 : }
396 :
397 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
398 : typename ArraySectionIdTag>
399 : void ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
400 : tmpl::list<NonTensorComputeTags...>,
401 : ArraySectionIdTag>::pup(PUP::er& p) {
402 : Event::pup(p);
403 : p | subfile_path_;
404 : p | scalar_name_;
405 : p | extremum_type_;
406 : p | additional_tensor_names_;
407 : }
408 :
409 : template <typename... ObservableTensorTags, typename... NonTensorComputeTags,
410 : typename ArraySectionIdTag>
411 : PUP::able::PUP_ID ObserveAtExtremum<tmpl::list<ObservableTensorTags...>,
412 : tmpl::list<NonTensorComputeTags...>,
413 : ArraySectionIdTag>::my_PUP_ID =
414 : 0; // NOLINT
415 : /// \endcond
416 : } // namespace Events
|