SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem - ExpirationTimes.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 83.3 % 6 5
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 <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 2.0-1