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

Generated by: LCOV version 1.14