SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem - ExpirationTimes.hpp Hit Total Coverage
Commit: 4051845fe0624911980dfa1768f07c3db4541b67 Lines: 3 4 75.0 %
Date: 2024-12-10 15:09:17
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 <algorithm>
       7             : #include <memory>
       8             : #include <string>
       9             : #include <type_traits>
      10             : #include <unordered_map>
      11             : #include <unordered_set>
      12             : 
      13             : #include "ControlSystem/CalculateMeasurementTimescales.hpp"
      14             : #include "ControlSystem/CombinedName.hpp"
      15             : #include "ControlSystem/Metafunctions.hpp"
      16             : #include "ControlSystem/Tags/SystemTags.hpp"
      17             : #include "DataStructures/DataVector.hpp"
      18             : #include "Domain/Creators/DomainCreator.hpp"
      19             : #include "Utilities/TMPL.hpp"
      20             : 
      21             : namespace control_system {
      22             : /*!
      23             :  * \ingroup ControlSystemGroup
      24             :  * \brief Calculate the next expiration time for the FunctionsOfTime.
      25             :  *
      26             :  * \f{align}
      27             :  * T_\mathrm{expr}^\mathrm{FoT} &= t + \tau_\mathrm{m}^\mathrm{old}
      28             :  *      + N * \tau_\mathrm{m}^\mathrm{new} \\
      29             :  * \f}
      30             :  *
      31             :  * where \f$T_\mathrm{expr}^\mathrm{FoT}\f$ is the expiration time for the
      32             :  * FunctionsOfTime, \f$t\f$ is the update time,
      33             :  * \f$\tau_\mathrm{m}^\mathrm{old/new}\f$ is the measurement timescale, and
      34             :  * \f$N\f$ is the number of measurements per update.
      35             :  *
      36             :  * The expiration is calculated this way because we update the functions of time
      37             :  * one (old) measurement before they actually expire.
      38             :  *
      39             :  * The choice of having the functions of time expire exactly one old measurement
      40             :  * after they are updated is arbitrary. They could expire any time between the
      41             :  * update time and one old measurement after the update. This decision was made
      42             :  * to minimize time spent waiting for the functions of time to be valid.
      43             :  *
      44             :  * Since functions of time are valid at their expiration time, we are actually
      45             :  * able to do the next measurement if the expiration time is at that
      46             :  * measurement. Thus we delay any potential waiting that may happen until the
      47             :  * subsequent measurement (and by that time, most, if not all, functions of time
      48             :  * should have been updated because an entire horizon find happened in the
      49             :  * meantime). If the expiration time was earlier than the next measurement, we'd
      50             :  * have to pause the Algorithm on the DG elements and wait until all the
      51             :  * functions of time have been updated.
      52             :  */
      53           1 : double function_of_time_expiration_time(
      54             :     const double time, const DataVector& old_measurement_timescales,
      55             :     const DataVector& new_measurement_timescales,
      56             :     const int measurements_per_update);
      57             : 
      58             : /*!
      59             :  * \ingroup ControlSystemGroup
      60             :  * \brief Calculate the next expiration time for the MeasurementTimescales.
      61             :  *
      62             :  * \f{align}
      63             :  * T_\mathrm{expr}^\mathrm{m} &= T_\mathrm{expr}^\mathrm{FoT} - \frac{1}{2}
      64             :  *   \tau_\mathrm{m}^\mathrm{new} \\
      65             :  * \f}
      66             :  *
      67             :  * where \f$T_\mathrm{expr}^\mathrm{m}\f$ is the measurement expiration time,
      68             :  * \f$T_\mathrm{expr}^\mathrm{FoT}\f$ is the function of time expiration time as
      69             :  * calculate from `function_of_time_expiration_time()`, and
      70             :  * \f$\tau_\mathrm{m}^\mathrm{new}\f$ is the new measurement timescale. The
      71             :  * reason for the factor of a half is as follows:
      72             :  *
      73             :  * We update the functions of time one (old) measurement before the expiration
      74             :  * time. Based on how dense triggers are set up, which control_system::Trigger
      75             :  * is a dense trigger, you calculate the next trigger (measurement) time at the
      76             :  * current measurement time. However, at the function of time expiration time we
      77             :  * need updated damping timescales from all control systems in order to
      78             :  * calculate when the next measurement is going to be (and in turn, the next
      79             :  * measurement expiration time). Thus, at the measurement that occurs at the
      80             :  * function of time expiration time, our measurement timescales can't be valid
      81             :  * and we must wait for updated ones. To achieve this, we set the measurement
      82             :  * expiration time *before* the function of time expiration time, but *after*
      83             :  * the previous measurement (the update measurement). The factor of one half is
      84             :  * just to guarantee we are more than epsilon before the function of time
      85             :  * expiration time and more than epsilon after the update measurement.
      86             :  */
      87           1 : double measurement_expiration_time(const double time,
      88             :                                    const DataVector& old_measurement_timescales,
      89             :                                    const DataVector& new_measurement_timescales,
      90             :                                    const int measurements_per_update);
      91             : 
      92             : /*!
      93             :  * \ingroup ControlSystemGroup
      94             :  * \brief Construct the initial expiration times for functions of time that are
      95             :  * controlled by a control system
      96             :  *
      97             :  * The expiration times are constructed using inputs from control system
      98             :  * OptionHolders as an unordered map from the name of the function of time being
      99             :  * controlled to the expiration time.
     100             :  *
     101             :  * The expiration time for each individual function of time is computed as
     102             :  * $\tau_\mathrm{exp} = \alpha_\mathrm{update} \tau_\mathrm{damp}$ where
     103             :  * $\alpha_\mathrm{update}$ is the update fraction supplied as input to the
     104             :  * Controller and $\tau_\mathrm{damp}$ is/are the damping timescales
     105             :  * supplied from the TimescaleTuner ($\tau_\mathrm{damp}$ is a DataVector
     106             :  * with as many components as the corresponding function of time, thus
     107             :  * $\tau_\mathrm{exp}$ will also be a DataVector of the same length).
     108             :  *
     109             :  * However, this expiration time calculated above is not necessarily the
     110             :  * expiration that is returned by this function. We group functions of time by
     111             :  * the `control_system::protocols::Measurement` that their corresponding control
     112             :  * systems use. This is because one measurement may be used to update many
     113             :  * functions of time. So the actual expiration time that is used for all the
     114             :  * functions of time in this group is the *minimum* of the $\tau_\mathrm{exp}$
     115             :  * of each function of time in the group.
     116             :  *
     117             :  * These groups are calculated using `control_system::system_to_combined_names`
     118             :  * where the list of control systems comes from the passed in `OptionHolder`s.
     119             :  *
     120             :  * If the control system isn't active then expiration time is
     121             :  * `std::numeric_limits<double>::infinity()`, regardless of what the groups'
     122             :  * expiration time is.
     123             :  */
     124             : template <size_t Dim, typename... OptionHolders>
     125           1 : std::unordered_map<std::string, double> initial_expiration_times(
     126             :     const double initial_time, const int measurements_per_update,
     127             :     const std::unique_ptr<::DomainCreator<Dim>>& domain_creator,
     128             :     const OptionHolders&... option_holders) {
     129             :   std::unordered_map<std::string, double> initial_expiration_times{};
     130             : 
     131             :   using control_systems = tmpl::list<typename OptionHolders::control_system...>;
     132             : 
     133             :   // First string is name of each control system. Second string is combination
     134             :   // of control systems with same measurement
     135             :   std::unordered_map<std::string, std::string> map_of_names =
     136             :       system_to_combined_names<control_systems>();
     137             : 
     138             :   std::unordered_map<std::string, double> combined_expiration_times{};
     139             :   std::unordered_set<std::string> infinite_expiration_times{};
     140             :   for (const auto& [control_system_name, combined_name] : map_of_names) {
     141             :     (void)control_system_name;
     142             :     if (combined_expiration_times.count(combined_name) != 1) {
     143             :       combined_expiration_times[combined_name] =
     144             :           std::numeric_limits<double>::infinity();
     145             :     }
     146             :   }
     147             : 
     148             :   [[maybe_unused]] const auto combine_expiration_times =
     149             :       [&initial_time, &measurements_per_update, &domain_creator, &map_of_names,
     150             :        &combined_expiration_times,
     151             :        &infinite_expiration_times](const auto& option_holder) {
     152             :         const std::string& control_system_name =
     153             :             std::decay_t<decltype(option_holder)>::control_system::name();
     154             :         const std::string& combined_name = map_of_names[control_system_name];
     155             : 
     156             :         auto tuner = option_holder.tuner;
     157             :         Tags::detail::initialize_tuner(make_not_null(&tuner), domain_creator,
     158             :                                        initial_time, control_system_name);
     159             : 
     160             :         const auto& controller = option_holder.controller;
     161             :         const DataVector measurement_timescales =
     162             :             calculate_measurement_timescales(controller, tuner,
     163             :                                              measurements_per_update);
     164             :         const double min_measurement_timescale = min(measurement_timescales);
     165             : 
     166             :         double initial_expiration_time = function_of_time_expiration_time(
     167             :             initial_time, DataVector{1, 0.0},
     168             :             DataVector{1, min_measurement_timescale}, measurements_per_update);
     169             :         initial_expiration_time = option_holder.is_active
     170             :                                       ? initial_expiration_time
     171             :                                       : std::numeric_limits<double>::infinity();
     172             : 
     173             :         if (initial_expiration_time ==
     174             :             std::numeric_limits<double>::infinity()) {
     175             :           infinite_expiration_times.insert(control_system_name);
     176             :         }
     177             : 
     178             :         combined_expiration_times[combined_name] = std::min(
     179             :             combined_expiration_times[combined_name], initial_expiration_time);
     180             :       };
     181             : 
     182             :   EXPAND_PACK_LEFT_TO_RIGHT(combine_expiration_times(option_holders));
     183             : 
     184             :   // Set all functions of time for a given measurement to the same expiration
     185             :   // time
     186             :   for (const auto& [control_system_name, combined_name] : map_of_names) {
     187             :     initial_expiration_times[control_system_name] =
     188             :         infinite_expiration_times.count(control_system_name) == 1
     189             :             ? std::numeric_limits<double>::infinity()
     190             :             : combined_expiration_times[combined_name];
     191             :   }
     192             : 
     193             :   return initial_expiration_times;
     194             : }
     195             : }  // namespace control_system

Generated by: LCOV version 1.14