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 <limits>
9 :
10 : #include "DataStructures/DataVector.hpp"
11 : #include "Options/String.hpp"
12 :
13 : /// \cond
14 : namespace PUP {
15 : class er;
16 : } // namespace PUP
17 : /// \endcond
18 :
19 : /// \ingroup ControlSystemGroup
20 : /// A PND (proportional to Q and N derivatives of Q) controller that computes
21 : /// the control signal:
22 : /// \f[ U(t) = \sum_{k=0}^{N} a_{k} \frac{d^kQ}{dt^k} \f]
23 : /// where N is specified by the template parameter `DerivOrder`.
24 : ///
25 : /// If an averager is used for `q_and_derivs` (as we typically do), there is an
26 : /// induced time offset, \f$\Delta t\f$, due to the time-weighted averaging.
27 : /// Therefore, the `q_and_derivs` that we have in hand are at some time
28 : /// \f$t_{0}\f$. However, we desire `q_and_derivs` at the current time
29 : /// \f$t = t_{0} + \Delta t\f$ to determine the appropriate control
30 : /// signal. We accomplish this by Taylor expanding
31 : /// \f$Q(t_{0} + \Delta t)\f$. The averager allows for averaging of
32 : /// \f$Q\f$ and its derivatives OR to not average \f$Q\f$ while still averaging
33 : /// the derivatives (the derivatives are always averaged in order to reduce
34 : /// noise due to numerical differentiation). When they are both averaged, the
35 : /// time offset will be identical for \f$Q\f$ and the derivatives,
36 : /// i.e. `q_time_offset` = `deriv_time_offset`. If an unaveraged \f$Q\f$ is
37 : /// used, then the time offset associated with \f$Q\f$ is zero,
38 : /// i.e. `q_time_offset`=0. and the derivative time offset, `deriv_time_offset`,
39 : /// remains non-zero.
40 : template <size_t DerivOrder>
41 1 : class Controller {
42 : public:
43 0 : struct UpdateFraction {
44 0 : using type = double;
45 0 : static constexpr Options::String help = {
46 : "Fraction of damping timescale used to determine how often to update "
47 : "functions of time."};
48 : };
49 :
50 0 : using options = tmpl::list<UpdateFraction>;
51 0 : static constexpr Options::String help{
52 : "Computes control signal used to reset highest derivative of a function "
53 : "of time. Also determines when a function of time needs to be updated "
54 : "next."};
55 :
56 0 : Controller(const double update_fraction)
57 : : update_fraction_(update_fraction) {}
58 :
59 0 : Controller() = default;
60 0 : Controller(Controller&&) = default;
61 0 : Controller& operator=(Controller&&) = default;
62 0 : Controller(const Controller&) = default;
63 0 : Controller& operator=(const Controller&) = default;
64 0 : ~Controller() = default;
65 :
66 0 : DataVector operator()(const double time, const DataVector& timescales,
67 : const std::array<DataVector, DerivOrder>& q_and_derivs,
68 : double q_time_offset, double deriv_time_offset);
69 :
70 : /// Takes the current minimum of all timescales for a specific control system
71 : /// and uses that to set the time between updates
72 1 : void assign_time_between_updates(const double current_min_timescale) {
73 : time_between_updates_ = update_fraction_ * current_min_timescale;
74 : }
75 :
76 : /// Returns `true` if the controller is ready to calculate the control signal
77 1 : bool is_ready(const double time) {
78 : return time >= last_update_time_ + time_between_updates_;
79 : }
80 :
81 : /// Calculates the new expiration time as the old expiration time plus the
82 : /// time between updates
83 1 : double next_expiration_time(const double current_expiration_time) {
84 : return current_expiration_time + time_between_updates_;
85 : }
86 :
87 : /// Sets the last time updated to the initial time
88 1 : void set_initial_update_time(const double initial_time) {
89 : last_update_time_ = initial_time;
90 : }
91 :
92 : /// Return the update fraction
93 1 : double get_update_fraction() const { return update_fraction_; }
94 :
95 : // NOLINTNEXTLINE(google-runtime-references)
96 0 : void pup(PUP::er& p) {
97 : p | update_fraction_;
98 : p | time_between_updates_;
99 : p | last_update_time_;
100 : }
101 :
102 : template <size_t LocalDerivOrder>
103 : // NOLINTNEXTLINE(readability-redundant-declaration) false positive
104 0 : friend bool operator==(const Controller<LocalDerivOrder>& lhs,
105 : const Controller<LocalDerivOrder>& rhs);
106 :
107 : private:
108 : // If update_fraction_ isn't set we need to error
109 0 : double update_fraction_{std::numeric_limits<double>::signaling_NaN()};
110 : // If this time_between_updates_ isn't set, the default should just be that
111 : // the functions of time are never updated (i.e. infinity)
112 0 : double time_between_updates_{std::numeric_limits<double>::infinity()};
113 : // Set to a large negative number to signify that we haven't updated yet and
114 : // should as early as possible
115 0 : double last_update_time_{-std::numeric_limits<double>::max()};
116 : };
117 :
118 : template <size_t DerivOrder>
119 0 : bool operator!=(const Controller<DerivOrder>& lhs,
120 : const Controller<DerivOrder>& rhs);
|