Line data Source code
1 0 : // Distributed under the MIT License.
2 : // See LICENSE.txt for details.
3 :
4 : #pragma once
5 :
6 : #include <array>
7 : #include <cstddef>
8 : #include <memory>
9 : #include <optional>
10 : #include <string>
11 : #include <vector>
12 :
13 : #include "ControlSystem/Component.hpp"
14 : #include "DataStructures/DataVector.hpp"
15 : #include "Domain/FunctionsOfTime/FunctionOfTime.hpp"
16 : #include "Domain/FunctionsOfTime/QuaternionFunctionOfTime.hpp"
17 : #include "IO/Observer/ReductionActions.hpp"
18 : #include "Parallel/GlobalCache.hpp"
19 : #include "Parallel/Info.hpp"
20 : #include "Parallel/Invoke.hpp"
21 :
22 : namespace control_system {
23 : /*!
24 : * \ingroup ControlSystemGroup
25 : * \brief Writes all components of a function of time to disk at a specific time
26 : * from a control system after it updates the functions of time.
27 : *
28 : * \details The columns of data written are:
29 : * - %Time
30 : * - FunctionOfTime
31 : * - dtFunctionOfTime
32 : * - d2tFunctionOfTime
33 : * - ControlError
34 : * - dtControlError
35 : * - DampingTimescale
36 : *
37 : * Data will be stored in the reduction file. All components are sent to the
38 : * `observers::ObserverWriter` in a single
39 : * `observers::ThreadedActions::WriteMultipleReductionDataRows` call so the
40 : * reduction file is opened only once per control system and measurement. All
41 : * subfiles for the control system within the H5 file will be under the group
42 : * "/ControlSystems".
43 : * Within this group, there will be one group for each control system. The name
44 : * of each group will be the result of the `name()` function from each control
45 : * system. An example would look like
46 : *
47 : * - /ControlSystems/SystemA
48 : * - /ControlSystems/SystemB
49 : * - /ControlSystems/SystemC
50 : *
51 : * Then, within each system group, there will be one subfile for each component
52 : * of the function of time that is being controlled. The name of this subfile is
53 : * the name of the component. The name of each component will be the result of
54 : * the `component_name(i)` function from the control system, where `i` is the
55 : * index of the component. For example, if "SystemA" has 3 components with names
56 : * "X", "Y", and "Z", then the subfiles would look like
57 : *
58 : * - /ControlSystems/SystemA/X.dat
59 : * - /ControlSystems/SystemA/Y.dat
60 : * - /ControlSystems/SystemA/Z.dat
61 : */
62 : template <typename ControlSystem, typename Metavariables>
63 1 : void write_components_to_disk(
64 : const double time, Parallel::GlobalCache<Metavariables>& cache,
65 : const std::unique_ptr<domain::FunctionsOfTime::FunctionOfTime>&
66 : function_of_time,
67 : const std::array<DataVector, 2>& q_and_dtq, const DataVector& timescales) {
68 : auto& observer_writer_proxy = Parallel::get_parallel_component<
69 : observers::ObserverWriter<Metavariables>>(cache);
70 :
71 : constexpr size_t deriv_order = ControlSystem::deriv_order;
72 : std::array<DataVector, 3> function_at_current_time{};
73 : const auto* const quat_func_of_time = dynamic_cast<
74 : const domain::FunctionsOfTime::QuaternionFunctionOfTime<deriv_order>*>(
75 : function_of_time.get());
76 : if (quat_func_of_time == nullptr) {
77 : // Just call the usual `func_and_2_derivs` member.
78 : function_at_current_time = function_of_time->func_and_2_derivs(time);
79 : } else {
80 : // If we are working with a QuaternionFunctionOfTime, we aren't actually
81 : // controlling a quaternion. We are controlling a small change in angle
82 : // associated with the angular velocity in each direction. Because of this,
83 : // we want to write the component data of the thing we are actually
84 : // controlling. This is accessed by the `angle_func_and_2_derivs` member of
85 : // a QuaternionFunctionOfTime. Since `angle_func_and_2_derivs` is not a
86 : // virtual function of the FunctionOfTime base class, we need to down cast
87 : // the original function to a QuaternionFunctionOfTime.
88 : function_at_current_time = quat_func_of_time->angle_func_and_2_derivs(time);
89 : }
90 :
91 : // Each control system is its own group under the overarching `ControlSystems`
92 : // group. There is a different subfile for each component, so loop over them.
93 : // Eg. translation files will look like
94 : //
95 : // ControlSystems/Translation/X.dat
96 : // ControlSystems/Translation/Y.dat
97 : // ControlSystems/Translation/Z.dat
98 : //
99 : // All components are collected and written with a single call so the
100 : // reduction file is opened only once rather than once per component.
101 : const std::vector<std::string> legend{
102 : "Time", "FunctionOfTime", "dtFunctionOfTime", "d2tFunctionOfTime",
103 : "ControlError", "dtControlError", "DampingTimescale"};
104 : const size_t num_components = function_at_current_time[0].size();
105 : std::vector<observers::DatRows> rows_to_write{};
106 : rows_to_write.reserve(num_components);
107 : for (size_t i = 0; i < num_components; ++i) {
108 : const std::optional<std::string> component_name_opt =
109 : ControlSystem::component_name(i, num_components);
110 : if (not component_name_opt) {
111 : continue;
112 : }
113 : // Currently all reduction data is written to the reduction file so preface
114 : // everything with ControlSystems/
115 : rows_to_write.push_back(observers::DatRows{
116 : "/ControlSystems/" + ControlSystem::name() + "/" + *component_name_opt,
117 : legend,
118 : {{
119 : // clang-format off
120 : time,
121 : function_at_current_time[0][i],
122 : function_at_current_time[1][i],
123 : function_at_current_time[2][i],
124 : q_and_dtq[0][i],
125 : q_and_dtq[1][i],
126 : timescales[i]
127 : // clang-format on
128 : }}});
129 : }
130 : if (rows_to_write.empty()) {
131 : return;
132 : }
133 :
134 : Parallel::threaded_action<
135 : observers::ThreadedActions::WriteMultipleReductionDataRows>(
136 : // Node 0 is always the writer
137 : observer_writer_proxy[0], std::move(rows_to_write));
138 : }
139 : } // namespace control_system
|