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);