SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem/Actions - LimitTimeStep.hpp Hit Total Coverage
Commit: a9ee6aa7c65ba703dbdc6dfb79436bb5378d4465 Lines: 1 6 16.7 %
Date: 2024-05-05 02:31:09
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 <optional>
       9             : 
      10             : #include "ControlSystem/CombinedName.hpp"
      11             : #include "ControlSystem/Metafunctions.hpp"
      12             : #include "DataStructures/DataBox/DataBox.hpp"
      13             : #include "Parallel/AlgorithmExecution.hpp"
      14             : #include "Parallel/ArrayComponentId.hpp"
      15             : #include "Parallel/Callback.hpp"
      16             : #include "Parallel/GlobalCache.hpp"
      17             : #include "Time/ChangeSlabSize/ChangeSlabSize.hpp"
      18             : #include "Time/Tags/HistoryEvolvedVariables.hpp"
      19             : #include "Utilities/ErrorHandling/Assert.hpp"
      20             : #include "Utilities/ErrorHandling/Error.hpp"
      21             : #include "Utilities/Gsl.hpp"
      22             : #include "Utilities/TMPL.hpp"
      23             : 
      24             : /// \cond
      25             : namespace Tags {
      26             : struct TimeStep;
      27             : struct TimeStepId;
      28             : template <typename StepperInterface>
      29             : struct TimeStepper;
      30             : }  // namespace Tags
      31             : class TimeStepper;
      32             : namespace control_system::Tags {
      33             : template <typename ControlSystems>
      34             : struct FutureMeasurements;
      35             : struct MeasurementTimescales;
      36             : }  // namespace control_system::Tags
      37             : namespace domain::Tags {
      38             : struct FunctionsOfTime;
      39             : }  // namespace domain::Tags
      40             : namespace tuples {
      41             : template <typename... Tags>
      42             : class TaggedTuple;
      43             : }  // namespace tuples
      44             : /// \endcond
      45             : 
      46             : namespace control_system::Actions {
      47             : /// \ingroup ControlSystemsGroup
      48             : /// \brief Limit the step size in a GTS evolution to prevent deadlocks from
      49             : /// control system measurements.
      50             : ///
      51             : /// \details Most time steppers require evaluations of the coordinates
      52             : /// at several times during the step before they can produce dense
      53             : /// output.  If any of those evaluations require a function-of-time
      54             : /// update depending on a measurement within the step, the evolution
      55             : /// will deadlock.  This action reduces the step size if necessary to
      56             : /// prevent that from happening.
      57             : ///
      58             : /// Specifically:
      59             : /// 1. The chosen step will never be longer than the unmodified step,
      60             : ///    and will be short enough to avoid relevant function-of-time
      61             : ///    expirations.
      62             : /// 2. Given the previous, the step will cover as many control-system
      63             : ///    updates as possible.
      64             : /// 3. If the next step is likely to be limited by this action, adjust
      65             : ///    the length of the current step so that this step and the next
      66             : ///    step will be as close as possible to the same size.
      67             : template <typename ControlSystems>
      68           1 : struct LimitTimeStep {
      69             :  private:
      70           0 :   using control_system_groups =
      71             :       tmpl::transform<metafunctions::measurements_t<ControlSystems>,
      72             :                       metafunctions::control_systems_with_measurement<
      73             :                           tmpl::pin<ControlSystems>, tmpl::_1>>;
      74             : 
      75             :   template <typename Group>
      76           0 :   struct GroupExpiration {
      77           0 :     using type = double;
      78             :   };
      79             : 
      80             :  public:
      81             :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
      82             :             typename ArrayIndex, typename ActionList,
      83             :             typename ParallelComponent>
      84           0 :   static Parallel::iterable_action_return_t apply(
      85             :       db::DataBox<DbTagsList>& box,
      86             :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
      87             :       Parallel::GlobalCache<Metavariables>& cache,
      88             :       const ArrayIndex& array_index, ActionList /*meta*/,
      89             :       const ParallelComponent* const /*meta*/) {
      90             :     static_assert(not Metavariables::local_time_stepping,
      91             :                   "The control system LimitTimeStep action is only for global "
      92             :                   "time stepping.");
      93             :     const auto& time_step_id = db::get<::Tags::TimeStepId>(box);
      94             :     if (time_step_id.substep() != 0) {
      95             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
      96             :     }
      97             : 
      98             :     const auto& time_stepper = db::get<::Tags::TimeStepper<TimeStepper>>(box);
      99             :     if (time_stepper.monotonic()) {
     100             :       // Monotonic steppers order operations in the same manner at the
     101             :       // control system, so they cannot introduce deadlocks.
     102             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     103             :     }
     104             : 
     105             :     const auto& this_proxy =
     106             :         ::Parallel::get_parallel_component<ParallelComponent>(
     107             :             cache)[array_index];
     108             : 
     109             :     // Minimum expiration time for any FoT in the measurement group.
     110             :     tmpl::wrap<tmpl::transform<control_system_groups,
     111             :                                tmpl::bind<GroupExpiration, tmpl::_1>>,
     112             :                tuples::TaggedTuple>
     113             :         group_expiration_times{};
     114             : 
     115             :     bool ready = true;
     116             :     // Calculate group_expiration_times
     117             :     tmpl::for_each<control_system_groups>([&](auto group_v) {
     118             :       if (not ready) {
     119             :         return;
     120             :       }
     121             :       using group = tmpl::type_from<decltype(group_v)>;
     122             : 
     123             :       auto& future_measurements =
     124             :           db::get_mutable_reference<Tags::FutureMeasurements<group>>(
     125             :               make_not_null(&box));
     126             : 
     127             :       std::optional<double> group_update = future_measurements.next_update();
     128             :       if (not group_update.has_value()) {
     129             :         Parallel::mutable_cache_item_is_ready<
     130             :             control_system::Tags::MeasurementTimescales>(
     131             :             cache,
     132             :             Parallel::make_array_component_id<ParallelComponent>(array_index),
     133             :             [&](const auto& measurement_timescales) {
     134             :               const auto& group_timescale =
     135             :                   *measurement_timescales.at(combined_name<group>());
     136             :               future_measurements.update(group_timescale);
     137             :               group_update = future_measurements.next_update();
     138             :               ready = group_update.has_value();
     139             :               return ready ? std::unique_ptr<Parallel::Callback>{}
     140             :                            : std::unique_ptr<Parallel::Callback>(
     141             :                                  new Parallel::PerformAlgorithmCallback(
     142             :                                      this_proxy));
     143             :             });
     144             :         if (not ready) {
     145             :           return;
     146             :         }
     147             :       }
     148             : 
     149             :       auto& group_expiration =
     150             :           get<GroupExpiration<group>>(group_expiration_times);
     151             :       group_expiration = std::numeric_limits<double>::infinity();
     152             : 
     153             :       if (*group_update == std::numeric_limits<double>::infinity()) {
     154             :         // Control measurement is not active.
     155             :         return;
     156             :       }
     157             : 
     158             :       // Calculate group_expiration
     159             :       Parallel::mutable_cache_item_is_ready<domain::Tags::FunctionsOfTime>(
     160             :           cache,
     161             :           Parallel::make_array_component_id<ParallelComponent>(array_index),
     162             :           [&](const auto& functions_of_time) {
     163             :             tmpl::for_each<group>([&](auto system) {
     164             :               using System = tmpl::type_from<decltype(system)>;
     165             :               if (not ready) {
     166             :                 return;
     167             :               }
     168             :               const auto& fot = *functions_of_time.at(System::name());
     169             :               ready = fot.time_bounds()[1] > *group_update;
     170             :               if (ready) {
     171             :                 group_expiration = std::min(
     172             :                     group_expiration, fot.expiration_after(*group_update));
     173             :               }
     174             :             });
     175             :             return ready ? std::unique_ptr<Parallel::Callback>{}
     176             :                          : std::unique_ptr<Parallel::Callback>(
     177             :                                new Parallel::PerformAlgorithmCallback(
     178             :                                    this_proxy));
     179             :           });
     180             :     });
     181             :     if (not ready) {
     182             :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     183             :     }
     184             : 
     185             :     const double orig_step_start = time_step_id.step_time().value();
     186             :     const double orig_step_end =
     187             :         (time_step_id.step_time() + db::get<::Tags::TimeStep>(box)).value();
     188             : 
     189             :     // Smallest of the current step end and the FoT expirations.  We
     190             :     // can't step any farther than this.
     191             :     const double latest_valid_step =
     192             :         tmpl::as_pack<control_system_groups>([&](auto... groups) {
     193             :           return std::min(
     194             :               {orig_step_end,
     195             :                get<GroupExpiration<tmpl::type_from<decltype(groups)>>>(
     196             :                    group_expiration_times)...});
     197             :         });
     198             : 
     199             :     if (not time_stepper.can_change_step_size(
     200             :             time_step_id, db::get<::Tags::HistoryEvolvedVariables<>>(box))) {
     201             :       if (orig_step_end > latest_valid_step) {
     202             :         ERROR(
     203             :             "Step must be decreased to avoid control-system deadlock, but "
     204             :             "time-stepper requires a fixed step size.");
     205             :       }
     206             :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     207             :     }
     208             :     ASSERT(db::get<::Tags::TimeStep>(box).fraction() == 1,
     209             :            "Trying to change GTS step, but it isn't a full slab.  Non-slab "
     210             :            "steps should only happen during self-start, but the preceding "
     211             :            "check should have ended the action if this is self-start.");
     212             : 
     213             :     // The last update that we can perform on the next step.  Don't
     214             :     // shrink the step past this time since that will force another
     215             :     // step to take the measurement.
     216             :     double last_update_time = orig_step_start;
     217             :     // Step time that produces a balanced step with the following
     218             :     // step, ignoring the restrictions on the current step.
     219             :     double preferred_step_time = orig_step_end;
     220             : 
     221             :     tmpl::for_each<control_system_groups>([&](auto group_v) {
     222             :       using group = tmpl::type_from<decltype(group_v)>;
     223             : 
     224             :       // This was used above, so it is not nullopt.
     225             :       const double group_update =
     226             :           db::get<Tags::FutureMeasurements<group>>(box).next_update().value();
     227             :       if (group_update <= latest_valid_step) {
     228             :         // We've satisfied this measurement.
     229             :         last_update_time = std::max(last_update_time, group_update);
     230             :       } else {
     231             :         // We can't make it far enough to do the final measurement.
     232             :         // Try to avoid a small step by choosing two equal-sized steps
     233             :         // to the expiration time.
     234             :         const double equal_step_time =
     235             :             0.5 * (orig_step_start +
     236             :                    get<GroupExpiration<group>>(group_expiration_times));
     237             :         preferred_step_time = std::min(preferred_step_time, equal_step_time);
     238             :       }
     239             :     });
     240             : 
     241             :     const double new_step_end =
     242             :         std::clamp(preferred_step_time, last_update_time, latest_valid_step);
     243             : 
     244             :     change_slab_size(make_not_null(&box), new_step_end);
     245             : 
     246             :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     247             :   }
     248             : };
     249             : }  // namespace control_system::Actions

Generated by: LCOV version 1.14