SpECTRE Documentation Coverage Report
Current view: top level - ControlSystem/Actions - LimitTimeStep.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 22.2 % 9 2
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 <optional>
       9              : 
      10              : #include "ControlSystem/CombinedName.hpp"
      11              : #include "ControlSystem/Metafunctions.hpp"
      12              : #include "ControlSystem/Tags/IsActiveMap.hpp"
      13              : #include "DataStructures/DataBox/DataBox.hpp"
      14              : #include "Parallel/AlgorithmExecution.hpp"
      15              : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      16              : #include "Parallel/ArrayCollection/PerformAlgorithmOnElement.hpp"
      17              : #include "Parallel/ArrayCollection/Tags/ElementLocations.hpp"
      18              : #include "Parallel/ArrayComponentId.hpp"
      19              : #include "Parallel/Callback.hpp"
      20              : #include "Parallel/GlobalCache.hpp"
      21              : #include "ParallelAlgorithms/Actions/GetItemFromDistributedObject.hpp"
      22              : #include "Time/ChangeSlabSize/ChangeSlabSize.hpp"
      23              : #include "Time/Tags/HistoryEvolvedVariables.hpp"
      24              : #include "Time/Tags/MinimumTimeStep.hpp"
      25              : #include "Utilities/ErrorHandling/Assert.hpp"
      26              : #include "Utilities/ErrorHandling/Error.hpp"
      27              : #include "Utilities/Gsl.hpp"
      28              : #include "Utilities/TMPL.hpp"
      29              : 
      30              : /// \cond
      31              : namespace Tags {
      32              : struct TimeStep;
      33              : struct TimeStepId;
      34              : template <typename StepperInterface>
      35              : struct TimeStepper;
      36              : }  // namespace Tags
      37              : class TimeStepper;
      38              : namespace control_system::Tags {
      39              : template <typename ControlSystems>
      40              : struct FutureMeasurements;
      41              : struct MeasurementTimescales;
      42              : }  // namespace control_system::Tags
      43              : namespace domain::Tags {
      44              : struct FunctionsOfTime;
      45              : }  // namespace domain::Tags
      46              : namespace tuples {
      47              : template <typename... Tags>
      48              : class TaggedTuple;
      49              : }  // namespace tuples
      50              : /// \endcond
      51              : 
      52              : namespace control_system::Actions {
      53              : /// \ingroup ControlSystemGroup
      54              : /// \brief Limit the step size in a GTS evolution to prevent deadlocks from
      55              : /// control system measurements.
      56              : ///
      57              : /// \details Most time steppers require evaluations of the coordinates
      58              : /// at several times during the step before they can produce dense
      59              : /// output.  If any of those evaluations require a function-of-time
      60              : /// update depending on a measurement within the step, the evolution
      61              : /// will deadlock.  This action reduces the step size if necessary to
      62              : /// prevent that from happening.
      63              : ///
      64              : /// Specifically:
      65              : /// 1. The chosen step will never be longer than the unmodified step,
      66              : ///    and will be short enough to avoid relevant function-of-time
      67              : ///    expirations.
      68              : /// 2. Given the previous, the step will cover as many control-system
      69              : ///    updates as possible.
      70              : /// 3. If the next step is likely to be limited by this action, adjust
      71              : ///    the length of the current step so that this step and the next
      72              : ///    step will be as close as possible to the same size.
      73              : template <typename ControlSystems>
      74            1 : struct LimitTimeStep {
      75              :  private:
      76            0 :   using control_system_groups =
      77              :       tmpl::transform<metafunctions::measurements_t<ControlSystems>,
      78              :                       metafunctions::control_systems_with_measurement<
      79              :                           tmpl::pin<ControlSystems>, tmpl::_1>>;
      80              : 
      81              :   template <typename Group>
      82            0 :   struct GroupExpiration {
      83            0 :     using type = double;
      84              :   };
      85              : 
      86              :  public:
      87            0 :   using const_global_cache_tags = tmpl::list<::Tags::MinimumTimeStep>;
      88              : 
      89              :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
      90              :             size_t Dim, typename ActionList, typename ParallelComponent>
      91            0 :   static Parallel::iterable_action_return_t apply(
      92              :       db::DataBox<DbTagsList>& box,
      93              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
      94              :       Parallel::GlobalCache<Metavariables>& cache,
      95              :       const ElementId<Dim>& array_index, ActionList /*meta*/,
      96              :       const ParallelComponent* const /*meta*/) {
      97              :     const auto& time_step_id = db::get<::Tags::TimeStepId>(box);
      98              :     if (time_step_id.substep() != 0) {
      99              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     100              :     }
     101              : 
     102              :     const auto& time_stepper = db::get<::Tags::TimeStepper<TimeStepper>>(box);
     103              :     if (time_stepper.monotonic()) {
     104              :       // Monotonic steppers order operations in the same manner at the
     105              :       // control system, so they cannot introduce deadlocks.
     106              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     107              :     }
     108              : 
     109              :     auto& proxy = ::Parallel::get_parallel_component<ParallelComponent>(cache);
     110              : 
     111              :     // Minimum expiration time for any FoT in the measurement group.
     112              :     tmpl::wrap<tmpl::transform<control_system_groups,
     113              :                                tmpl::bind<GroupExpiration, tmpl::_1>>,
     114              :                tuples::TaggedTuple>
     115              :         group_expiration_times{};
     116              : 
     117              :     bool ready = true;
     118              :     // Calculate group_expiration_times
     119              :     tmpl::for_each<control_system_groups>([&](auto group_v) {
     120              :       if (not ready) {
     121              :         return;
     122              :       }
     123              :       using group = tmpl::type_from<decltype(group_v)>;
     124              : 
     125              :       auto& future_measurements =
     126              :           db::get_mutable_reference<Tags::FutureMeasurements<group>>(
     127              :               make_not_null(&box));
     128              : 
     129              :       std::optional<double> group_update = future_measurements.next_update();
     130              :       if (not group_update.has_value()) {
     131              :         Parallel::mutable_cache_item_is_ready<
     132              :             control_system::Tags::MeasurementTimescales>(
     133              :             cache,
     134              :             Parallel::make_array_component_id<ParallelComponent>(array_index),
     135              :             [&](const auto& measurement_timescales) {
     136              :               const auto& group_timescale =
     137              :                   *measurement_timescales.at(combined_name<group>());
     138              :               future_measurements.update(group_timescale);
     139              :               group_update = future_measurements.next_update();
     140              :               ready = group_update.has_value();
     141              :               if constexpr (Parallel::is_dg_element_collection_v<
     142              :                                 ParallelComponent>) {
     143              :                 // Note: The ArrayComponentId is still created with the
     144              :                 // array_index (ElementId) because we only support 1 callback
     145              :                 // per ArrayComponentId. This would mean for nodegroups we would
     146              :                 // discard a lot of callbacks that we need. Alternatively, the
     147              :                 // callback could do a broadcast to all elements on the
     148              :                 // nodegroup.
     149              :                 const auto element_location = static_cast<int>(
     150              :                     Parallel::local_synchronous_action<
     151              :                         ::Parallel::Actions::GetItemFromDistributedOject<
     152              :                             Parallel::Tags::ElementLocations<Dim>>>(proxy)
     153              :                         ->at(array_index));
     154              :                 return ready ? std::unique_ptr<Parallel::Callback>{}
     155              :                              : std::unique_ptr<Parallel::Callback>(
     156              :                                    new Parallel::ThreadedActionCallback<
     157              :                                        Parallel::Actions::
     158              :                                            PerformAlgorithmOnElement<false>,
     159              :                                        decltype(proxy[element_location]),
     160              :                                        std::decay_t<decltype(array_index)>>{
     161              :                                        proxy[element_location], array_index});
     162              :               } else {
     163              :                 return ready ? std::unique_ptr<Parallel::Callback>{}
     164              :                              : std::unique_ptr<Parallel::Callback>(
     165              :                                    new Parallel::PerformAlgorithmCallback(
     166              :                                        proxy[array_index]));
     167              :               }
     168              :             });
     169              :         if (not ready) {
     170              :           return;
     171              :         }
     172              :       }
     173              : 
     174              :       auto& group_expiration =
     175              :           get<GroupExpiration<group>>(group_expiration_times);
     176              :       group_expiration = std::numeric_limits<double>::infinity();
     177              : 
     178              :       if (*group_update == std::numeric_limits<double>::infinity()) {
     179              :         // Control measurement is not active.
     180              :         return;
     181              :       }
     182              : 
     183              :       const auto& is_active_map =
     184              :           Parallel::get<control_system::Tags::IsActiveMap>(cache);
     185              : 
     186              :       // Calculate group_expiration
     187              :       Parallel::mutable_cache_item_is_ready<domain::Tags::FunctionsOfTime>(
     188              :           cache,
     189              :           Parallel::make_array_component_id<ParallelComponent>(array_index),
     190              :           [&](const auto& functions_of_time) {
     191              :             // Reset default readiness state to true because
     192              :             // mutable_cache_item_is_ready() calls this lambda twice. The first
     193              :             // call may have set ready = false, but FoT could have been updated
     194              :             // before the second call, so we default to true and then do a fresh
     195              :             // readiness check from the fresh FoT below.
     196              :             ready = true;
     197              :             tmpl::for_each<group>([&](auto system) {
     198              :               using System = tmpl::type_from<decltype(system)>;
     199              :               if (not ready or not is_active_map.at(System::name())) {
     200              :                 return;
     201              :               }
     202              :               const auto& fot = *functions_of_time.at(System::name());
     203              :               ready = fot.time_bounds()[1] > *group_update;
     204              :               if (ready) {
     205              :                 group_expiration = std::min(
     206              :                     group_expiration, fot.expiration_after(*group_update));
     207              :               }
     208              :             });
     209              :             if constexpr (Parallel::is_dg_element_collection_v<
     210              :                               ParallelComponent>) {
     211              :               // Note: The ArrayComponentId is still created with the
     212              :               // array_index (ElementId) because we only support 1 callback
     213              :               // per ArrayComponentId. This would mean for nodegroups we would
     214              :               // discard a lot of callbacks that we need. Alternatively, the
     215              :               // callback could do a broadcast to all elements on the
     216              :               // nodegroup.
     217              :               const auto element_location = static_cast<int>(
     218              :                   Parallel::local_synchronous_action<
     219              :                       ::Parallel::Actions::GetItemFromDistributedOject<
     220              :                           Parallel::Tags::ElementLocations<Dim>>>(proxy)
     221              :                       ->at(array_index));
     222              :               return ready
     223              :                          ? std::unique_ptr<Parallel::Callback>{}
     224              :                          : std::unique_ptr<Parallel::Callback>(
     225              :                                new Parallel::ThreadedActionCallback<
     226              :                                    Parallel::Actions::PerformAlgorithmOnElement<
     227              :                                        false>,
     228              :                                    decltype(proxy[element_location]),
     229              :                                    std::decay_t<decltype(array_index)>>{
     230              :                                    proxy[element_location], array_index});
     231              :             } else {
     232              :               return ready ? std::unique_ptr<Parallel::Callback>{}
     233              :                            : std::unique_ptr<Parallel::Callback>(
     234              :                                  new Parallel::PerformAlgorithmCallback(
     235              :                                      proxy[array_index]));
     236              :             }
     237              :           });
     238              :     });
     239              :     if (not ready) {
     240              :       return {Parallel::AlgorithmExecution::Retry, std::nullopt};
     241              :     }
     242              : 
     243              :     const double orig_step_start = time_step_id.step_time().value();
     244              :     const double orig_step_end =
     245              :         (time_step_id.step_time() + db::get<::Tags::TimeStep>(box)).value();
     246              : 
     247              :     // Smallest of the current step end and the FoT expirations.  We
     248              :     // can't step any farther than this.
     249              :     const double latest_valid_step =
     250              :         tmpl::as_pack<control_system_groups>([&](auto... groups) {
     251              :           return std::min(
     252              :               {orig_step_end,
     253              :                get<GroupExpiration<tmpl::type_from<decltype(groups)>>>(
     254              :                    group_expiration_times)...});
     255              :         });
     256              : 
     257              :     if (not tmpl::as_pack<::Tags::get_all_history_tags<DbTagsList>>(
     258              :             [&]<typename... HistoryTags>(tmpl::type_<HistoryTags>... /*meta*/) {
     259              :               return (... and time_stepper.can_change_step_size(
     260              :                                   time_step_id, db::get<HistoryTags>(box)));
     261              :             })) {
     262              :       if (orig_step_end > latest_valid_step) {
     263              :         ERROR(
     264              :             "Step must be decreased to avoid control-system deadlock, but "
     265              :             "time-stepper requires a fixed step size.");
     266              :       }
     267              :       return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     268              :     }
     269              :     ASSERT(db::get<::Tags::TimeStep>(box).fraction() == 1,
     270              :            "Trying to change GTS step, but it isn't a full slab.  Non-slab "
     271              :            "steps should only happen during self-start, but the preceding "
     272              :            "check should have ended the action if this is self-start.");
     273              : 
     274              :     // The last update that we can perform on the next step.  Don't
     275              :     // shrink the step past this time since that will force another
     276              :     // step to take the measurement.
     277              :     double last_update_time = orig_step_start;
     278              :     // Step time that produces a balanced step with the following
     279              :     // step, ignoring the restrictions on the current step.
     280              :     double preferred_step_time = orig_step_end;
     281              : 
     282              :     tmpl::for_each<control_system_groups>([&](auto group_v) {
     283              :       using group = tmpl::type_from<decltype(group_v)>;
     284              : 
     285              :       // This was used above, so it is not nullopt.
     286              :       const double group_update =
     287              :           db::get<Tags::FutureMeasurements<group>>(box).next_update().value();
     288              :       if (group_update <= latest_valid_step) {
     289              :         // We've satisfied this measurement.
     290              :         last_update_time = std::max(last_update_time, group_update);
     291              :       } else {
     292              :         // We can't make it far enough to do the final measurement.
     293              :         // Try to avoid a small step by choosing two equal-sized steps
     294              :         // to the expiration time.
     295              :         const double equal_step_time =
     296              :             0.5 * (orig_step_start +
     297              :                    get<GroupExpiration<group>>(group_expiration_times));
     298              :         preferred_step_time = std::min(preferred_step_time, equal_step_time);
     299              :       }
     300              :     });
     301              : 
     302              :     const double new_step_end =
     303              :         std::clamp(preferred_step_time, last_update_time, latest_valid_step);
     304              : 
     305              :     change_slab_size(make_not_null(&box), new_step_end);
     306              : 
     307              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     308              :   }
     309              : };
     310              : 
     311              : /// \ingroup ControlSystemGroup
     312              : /// \brief No-control-system specialization that does nothing.
     313              : ///
     314              : /// Exists just to avoid having to put conditionals in metavariables
     315              : /// that may or may not have control systems.
     316              : template <>
     317            1 : struct LimitTimeStep<tmpl::list<>> {
     318              :  public:
     319              :   template <typename DbTagsList, typename... InboxTags, typename Metavariables,
     320              :             size_t Dim, typename ActionList, typename ParallelComponent>
     321            0 :   static Parallel::iterable_action_return_t apply(
     322              :       db::DataBox<DbTagsList>& /*box*/,
     323              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     324              :       Parallel::GlobalCache<Metavariables>& /*cache*/,
     325              :       const ElementId<Dim>& /*array_index*/, ActionList /*meta*/,
     326              :       const ParallelComponent* const /*meta*/) {
     327              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     328              :   }
     329              : };
     330              : }  // namespace control_system::Actions
        

Generated by: LCOV version 2.0-1