SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem - Averager.hpp Hit Total Coverage
Commit: d0fc80462417e83e5cddfa1b9901bb4a9b6af4d6 Lines: 12 39 30.8 %
Date: 2024-03-29 00:33:31
Legend: Lines: hit not hit

          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 <deque>
       9             : #include <optional>
      10             : #include <pup.h>
      11             : #include <pup_stl.h>
      12             : 
      13             : #include "DataStructures/DataVector.hpp"
      14             : #include "Options/String.hpp"
      15             : #include "Utilities/Gsl.hpp"
      16             : #include "Utilities/Serialization/PupStlCpp17.hpp"
      17             : 
      18             : /// \ingroup ControlSystemGroup
      19             : /// A weighted exponential averager of \f$Q\f$ and its derivatives
      20             : /// implementing Appendix A in \cite Hemberger2012jz
      21             : ///
      22             : /// The purpose of the averager is to provide \f$Q\f$ and 'smoothed' numerical
      23             : /// derivatives of \f$Q\f$ up to the `DerivOrder`'th derivative. The 0th
      24             : /// derivative of \f$Q\f$ is not typically averaged, since no differencing is
      25             : /// necessary and no noise is introduced. The `average_0th_deriv_of_q` option
      26             : /// allows for specifying that the 0th derivative should be averaged in addition
      27             : /// to the derivatives. This may be desirable for control systems where
      28             : /// \f$Q\f$ contains some 'noise', e.g. size control typically uses an averaged
      29             : /// \f$Q\f$ since the raw computed \f$Q\f$ is a function of the minimum on a
      30             : /// surface and may jump around discontinuously.
      31             : ///
      32             : /// The averager is designed to support DerivOrders 1, 2, and 3. If an
      33             : /// additional DerivOrder is needed, finite differencing needs to be implemented
      34             : /// to specifically handle that order (as it seems like overkill to generalize
      35             : /// the differencing stencil at this time).
      36             : template <size_t DerivOrder>
      37           1 : class Averager {
      38             :   static_assert(DerivOrder != 0, "Cannot average just a function value.");
      39             :   static_assert(
      40             :       DerivOrder <= 3,
      41             :       "Averager only instantiated for deriv order less than or equal to 3. If "
      42             :       "you want a higher deriv order, you'll need to add it yourself.");
      43             : 
      44             :  public:
      45           0 :   struct AverageTimescaleFraction {
      46           0 :     using type = double;
      47           0 :     static constexpr Options::String help = {
      48             :         "Time scale of exponential averaging"};
      49             :   };
      50             : 
      51           0 :   struct Average0thDeriv {
      52           0 :     using type = bool;
      53           0 :     static constexpr Options::String help = {
      54             :         "Whether to average the 0th derivative"};
      55             :   };
      56             : 
      57           0 :   using options = tmpl::list<AverageTimescaleFraction, Average0thDeriv>;
      58           0 :   static constexpr Options::String help{
      59             :       "Averager: Performs exponential averaging of the control signal at "
      60             :       "multiple times in order to provide smoother derivatives of the control "
      61             :       "signal."};
      62             : 
      63             :   /// `avg_timescale_frac` determines the exponential averaging timescale
      64             :   /// through \f$\tau_\mathrm{avg} = \f$`avg_timescale_frac`\f$\times \tau\f$,
      65             :   /// where \f$\tau\f$ is the damping time.
      66             :   /// `avg_timescale_frac` must be positive.
      67             :   /// `average_0th_deriv_of_q` determines whether the call operator returns an
      68             :   /// averaged or unaveraged quantity for the 0th derivative of \f$Q\f$.
      69             :   /// `true` returns an averaged 0th derivative of \f$Q\f$.
      70             :   /// `false` returns the raw 0th derivative of \f$Q\f$.
      71             :   /// The derivatives are always averaged (to reduce noise due to numerical
      72             :   /// differentiation), so the `average_0th_deriv_of_q` option only specifies
      73             :   /// whether to return an averaged value for the 0th derivative piece of the
      74             :   /// function.
      75           1 :   Averager(double avg_timescale_frac, bool average_0th_deriv_of_q);
      76             : 
      77           0 :   Averager() = default;
      78             :   // Explicitly defined move constructor due to the fact that the std::deque
      79             :   // move constructor is not marked
      80           0 :   Averager(Averager&& rhs);
      81           0 :   Averager& operator=(Averager&& rhs);
      82           0 :   Averager(const Averager&) = default;
      83           0 :   Averager& operator=(const Averager&) = default;
      84           0 :   ~Averager() = default;
      85             : 
      86             :   template <size_t DDerivOrder>                          // NOLINT
      87           0 :   friend bool operator==(const Averager<DDerivOrder>&,   // NOLINT
      88             :                          const Averager<DDerivOrder>&);  // NOLINT
      89             : 
      90             :   /// Returns \f$Q\f$ and its derivatives at \f$t=\f$`time`, provided there is
      91             :   /// sufficient data. The averager is limited by the need for at least
      92             :   /// (`DerivOrder` + 1) data points in order to provide the `DerivOrder`'th
      93             :   /// derivative. If sufficient data is available, it returns \f$Q\f$ and
      94             :   /// averaged derivatives of \f$Q\f$ up to the `DerivOrder`'th derivative, at
      95             :   /// \f$t=\f$`time`. If `using_average_0th_deriv_of_q()` is `true`, then the
      96             :   /// returned 0th derivative of \f$Q\f$ is also averaged. In the case that
      97             :   /// there is insufficient data, the operator returns an invalid
      98             :   /// `std::optional`.
      99           1 :   const std::optional<std::array<DataVector, DerivOrder + 1>>& operator()(
     100             :       double time) const;
     101             :   /// A function that allows for resetting the averager.
     102           1 :   void clear();
     103             :   /// The function responsible for updating the averaged values
     104             :   /// at \f$t=\f$`time`. Requires `raw_q` (the raw components of \f$Q(t)\f$)
     105             :   /// and `timescales` (the associated damping times for each component).
     106           1 :   void update(double time, const DataVector& raw_q,
     107             :               const DataVector& timescales);
     108             :   /// Returns the latest time at which the averager has sufficient data to
     109             :   /// return \f$Q\f$ and its derivatives.
     110           1 :   double last_time_updated() const;
     111             :   /// Returns the exponentially averaged time at \f$t=\f$`time`. The time is
     112             :   /// averaged along side \f$Q\f$ to determine the effective time at which
     113             :   /// the average value is computed. The effective time is retarded, due to the
     114             :   /// weighting of past times.
     115           1 :   double average_time(double time) const;
     116             :   /// Returns a bool corresponding to whether `average_0th_deriv_of_q`
     117             :   /// is `true`/`false`.
     118           1 :   bool using_average_0th_deriv_of_q() const { return average_0th_deriv_of_q_; };
     119             :   /// Returns the averaging timescale fraction
     120           1 :   double avg_timescale_frac() const { return avg_tscale_frac_; }
     121             : 
     122             :   /// Assign the minimum of the measurement timescales related to a specific
     123             :   /// control system to time_between_measurements_
     124           1 :   void assign_time_between_measurements(
     125             :       const double current_time_between_measurements) {
     126             :     time_between_measurements_ = current_time_between_measurements;
     127             :   }
     128             : 
     129             :   /// Returns `true` if the averager is ready to receive a measurement
     130           1 :   bool is_ready(const double time) const {
     131             :     if (UNLIKELY(times_.empty())) {
     132             :       return true;
     133             :     }
     134             :     return time >= times_[0] + time_between_measurements_;
     135             :   }
     136             : 
     137           0 :   void pup(PUP::er& p);
     138             : 
     139             :  private:
     140             :   /// Returns the function and numerical derivatives up to the
     141             :   /// `DerivOrder`'th derivative using backward finite differencing that
     142             :   /// supports non-uniform time spacing. Since we use a stencil size of
     143             :   /// (`DerivOrder` + 1), the order of the finite difference varies for
     144             :   /// different derivatives. Assuming \f$M\lte\f$`DerivOrder`, the \f$M\f$'th
     145             :   /// derivative is of order (`DerivOrder` + 1 - \f$M\f$).
     146           1 :   std::array<DataVector, DerivOrder + 1> get_derivs() const;
     147             : 
     148           0 :   double avg_tscale_frac_;
     149           0 :   bool average_0th_deriv_of_q_;
     150           0 :   std::optional<std::array<DataVector, DerivOrder + 1>> averaged_values_{};
     151           0 :   std::optional<std::array<DataVector, DerivOrder + 1>> nullopt_ = std::nullopt;
     152           0 :   std::deque<double> times_;
     153           0 :   std::deque<DataVector> raw_qs_;
     154           0 :   double weight_k_ = 0.0;
     155           0 :   double tau_k_ = 0.0;
     156             :   // If this time_between_measurements_ isn't set, the default should just be
     157             :   // that no measurements happen (i.e. infinity)
     158           0 :   double time_between_measurements_{std::numeric_limits<double>::infinity()};
     159             : };
     160             : 
     161             : template <size_t DDerivOrder>
     162           0 : bool operator!=(const Averager<DDerivOrder>&, const Averager<DDerivOrder>&);

Generated by: LCOV version 1.14