SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem - Averager.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 30.8 % 39 12
Test Date: 2026-10-22 17:29:42
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 2.0-1