SpECTRE Documentation Coverage Report
Current view: top level - Time/Actions - SelfStartActions.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 39.1 % 23 9
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 <cstddef>
       7              : #include <optional>
       8              : #include <string>
       9              : #include <tuple>
      10              : #include <type_traits>
      11              : 
      12              : #include "DataStructures/DataBox/DataBox.hpp"
      13              : #include "DataStructures/DataBox/PrefixHelpers.hpp"
      14              : #include "DataStructures/DataBox/Prefixes.hpp"
      15              : #include "DataStructures/DataBox/Tag.hpp"
      16              : #include "DataStructures/DataBox/TagName.hpp"
      17              : #include "DataStructures/TaggedTuple.hpp"
      18              : #include "Parallel/AlgorithmExecution.hpp"
      19              : #include "ParallelAlgorithms/Actions/Goto.hpp"
      20              : #include "ParallelAlgorithms/Actions/MutateApply.hpp"
      21              : #include "ParallelAlgorithms/Actions/TerminatePhase.hpp"
      22              : #include "ParallelAlgorithms/Initialization/MutateAssign.hpp"
      23              : #include "Time/AdvanceTime.hpp"
      24              : #include "Time/Slab.hpp"
      25              : #include "Time/Tags/HistoryEvolvedVariables.hpp"
      26              : #include "Time/Tags/TimeStep.hpp"
      27              : #include "Time/Time.hpp"
      28              : #include "Time/TimeStepId.hpp"
      29              : #include "Utilities/Gsl.hpp"
      30              : #include "Utilities/TMPL.hpp"
      31              : #include "Utilities/TypeTraits/IsA.hpp"
      32              : 
      33              : /// \cond
      34              : namespace Parallel {
      35              : template <typename Metavariables>
      36              : class GlobalCache;
      37              : }  // namespace Parallel
      38              : namespace Tags {
      39              : struct TimeStepId;
      40              : template <typename StepperInterface>
      41              : struct TimeStepper;
      42              : }  // namespace Tags
      43              : class TimeStepper;
      44              : namespace TimeSteppers::Tags {
      45              : struct FixedOrder;
      46              : }  // namespace TimeSteppers::Tags
      47              : /// \endcond
      48              : 
      49              : /// \ingroup TimeGroup
      50              : /// Definition of the integrator self-starting procedure.
      51              : ///
      52              : /// The self-start procedure generates \f$N\f$ function values of
      53              : /// accuracy \f$O\left((\Delta t)^N\right)\f$, where \f$N\f$ is the
      54              : /// negative of the initial slab number.  To generate values, it
      55              : /// requires the time stepper to be a multistep integrator that will
      56              : /// produce an order-\f$k\f$-accurate result given \f$k-1\f$ history
      57              : /// values.
      58              : ///
      59              : /// If the integrator is started from analytic history data or
      60              : /// requires no history (such as for a substep integrator), then the
      61              : /// initial slab number can be set to zero and no self-start steps
      62              : /// will be taken.
      63              : ///
      64              : /// \details
      65              : /// To self-start a multistep integrator, the function is integrated
      66              : /// repeatedly with increasing accuracy.  A first order integrator
      67              : /// (Euler's method) requires no history values, so it can be used,
      68              : /// starting from the initial point, to generate a
      69              : /// first-order-accurate value at a later time.  We then reset to the
      70              : /// start conditions and use the new "history" value (at a discarded
      71              : /// point later in time) to take two steps with a second-order method.
      72              : /// These values are second-order-accurate despite the history only
      73              : /// being first-order because the calculation of the change in the
      74              : /// value multiplies the previous derivatives by a factor of
      75              : /// \f$\Delta t\f$.  The time and value are then again reset to their
      76              : /// starting values and we start again at third order, and so on.
      77              : ///
      78              : /// The choice of performing the low-order integrations in the same
      79              : /// direction as the main integration makes this a _forward_
      80              : /// self-start procedure, as opposed to a _backward_ procedure that
      81              : /// produces values for times before the start time.  The primary
      82              : /// advantage of the forward version is that the solution is
      83              : /// guaranteed to exist after the start time, but not before.  It also
      84              : /// makes bookkeeping easier, as the reset after each order increase
      85              : /// is to the initial state, rather than to a time one step further
      86              : /// back for each order.  It does have the disadvantage, however, of
      87              : /// leaving a non-monotonic history at the end of the procedure, which
      88              : /// the main evolution loop must be able to handle.
      89              : ///
      90              : /// Each time the state is reset the slab number is increased by one.
      91              : /// This ensures that the evaluations are considered to be ordered in
      92              : /// their evaluation order, even though they are not monotonic in
      93              : /// time.  When the slab number reaches zero the initialization
      94              : /// procedure is complete and history appropriate for use for an
      95              : /// integrator of order \f$N+1\f$ has been generated.
      96              : ///
      97              : /// The self-start procedure performs all its evaluations before the
      98              : /// end of the first time step of the main evolution.  It is important
      99              : /// that none of the early steps fall at the same time as the
     100              : /// self-start history values, so the main evolution should not
     101              : /// decrease its step size on the first step after the procedure.
     102              : /// Additionally, the history times will not be monotonicly increasing
     103              : /// until \f$N\f$ steps have been taken.  The local-time-stepping
     104              : /// calculations require monotonic time, so local time-stepping should
     105              : /// not be initiated until the self-start values have expired from the
     106              : /// history.  These restrictions on step-size changing are checked in
     107              : /// the TimeStepper::can_change_step_size method.
     108            1 : namespace SelfStart {
     109              : /// Self-start tags
     110            1 : namespace Tags {
     111              : /// \ingroup TimeGroup
     112              : /// The initial value of a quantity.  The contents are stored in a
     113              : /// tuple to avoid putting duplicate tensors into the DataBox.
     114              : template <typename Tag>
     115            1 : struct InitialValue : db::PrefixTag, db::SimpleTag {
     116            0 :   using tag = Tag;
     117            0 :   using type = std::tuple<typename Tag::type>;
     118              : };
     119              : }  // namespace Tags
     120              : 
     121              : /// Self-start actions
     122            1 : namespace Actions {
     123              : namespace detail {
     124              : template <typename System, bool HasPrimitives>
     125              : struct vars_to_save_impl {
     126              :   using type = tmpl::flatten<tmpl::list<typename System::variables_tag>>;
     127              : };
     128              : 
     129              : template <typename System>
     130              : struct vars_to_save_impl<System, true> {
     131              :   using type =
     132              :       tmpl::flatten<tmpl::list<typename System::variables_tag,
     133              :                                typename System::primitive_variables_tag>>;
     134              : };
     135              : 
     136              : template <typename System>
     137              : using vars_to_save = typename vars_to_save_impl<
     138              :     System, System::has_primitive_and_conservative_vars>::type;
     139              : }  // namespace detail
     140              : 
     141              : /// \ingroup ActionsGroup
     142              : /// \ingroup TimeGroup
     143              : /// Prepares the evolution for time-stepper self-starting.
     144              : ///
     145              : /// Stores the initial values of the variables and time step and sets
     146              : /// an appropriate step for self-starting.
     147              : ///
     148              : /// \details The self-start procedure must take place within one slab,
     149              : /// and we want to avoid the end of the slab so that we don't have to
     150              : /// deal with another action advancing the slab on us.  There will be
     151              : /// problems if the main evolution tries to evaluate at a time that
     152              : /// the self-start procedure used before the self-start version falls
     153              : /// out of the history, so we have to make sure that does not happen.
     154              : /// We can't do that by making sure our steps are large enough to keep
     155              : /// ahead because of the slab restriction, so instead we have to make
     156              : /// the self-start step smaller to ensure no collisions.  The easiest
     157              : /// way to do that is to fit the entire procedure before the first
     158              : /// real step, so we pick an initialization time step of
     159              : /// \f$\Delta t/(N+1)\f$, for \f$\Delta t\f$ the initial evolution
     160              : /// time step and \f$N\f$ the number of history points to be
     161              : /// generated.
     162              : ///
     163              : /// The original `Tags::TimeStep` is temporarily stored separately
     164              : /// during the self-start procedure, and restored to its original
     165              : /// value at the conclusion of the self-start procedure in preparation
     166              : /// for the main evolution, so that the initial time steps during the
     167              : /// evolution are appropriately set according to `Initialization`
     168              : /// phase values.
     169              : ///
     170              : /// Uses:
     171              : /// - GlobalCache: nothing
     172              : /// - DataBox:
     173              : ///   - Tags::TimeStepId
     174              : ///   - Tags::TimeStep
     175              : ///   - variables_tag
     176              : ///   - primitive_variables_tag if the system has primitives
     177              : ///
     178              : /// DataBox changes:
     179              : /// - Adds:
     180              : ///   - SelfStart::Tags::InitialValue<Tags::TimeStep>
     181              : ///   - SelfStart::Tags::InitialValue<variables_tag>
     182              : ///   - SelfStart::Tags::InitialValue<primitive_variables_tag> if the system
     183              : ///     has primitives
     184              : /// - Removes: nothing
     185              : /// - Modifies: Tags::TimeStep
     186              : template <typename System,
     187              :           template <typename> typename CacheTagPrefix = std::type_identity_t>
     188            1 : struct Initialize {
     189              :  private:
     190              :   template <typename TagsToSave>
     191            0 :   struct StoreInitialValues;
     192              : 
     193              :   template <typename... TagsToSave>
     194            0 :   struct StoreInitialValues<tmpl::list<TagsToSave...>> {
     195            0 :     using simple_tags = tmpl::list<Tags::InitialValue<TagsToSave>...>;
     196              : 
     197              :     template <typename Box>
     198            0 :     static void apply(Box& box) {
     199              :       ::Initialization::mutate_assign<simple_tags>(
     200              :           make_not_null(&box), std::make_tuple(db::get<TagsToSave>(box))...);
     201              :     }
     202              :   };
     203              : 
     204              :  public:
     205            0 :   using simple_tags = typename StoreInitialValues<tmpl::push_back<
     206              :       detail::vars_to_save<System>, ::Tags::TimeStep>>::simple_tags;
     207              : 
     208              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     209              :             typename ArrayIndex, typename ActionList,
     210              :             typename ParallelComponent>
     211            0 :   static Parallel::iterable_action_return_t apply(
     212              :       db::DataBox<DbTags>& box,
     213              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     214              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     215              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     216              :       const ParallelComponent* const /*meta*/) {
     217              :     const TimeDelta initial_step = db::get<::Tags::TimeStep>(box);
     218              :     // The slab number increments each time a new point is generated
     219              :     // until it reaches zero.  The multistep integrators all need
     220              :     // `order` control points at distinct times, even if some of those
     221              :     // are not counted in the reported required past steps.
     222              :     // (Predictor stages can't line up with history points.)  This
     223              :     // doesn't count the value as the initial time, hence the "- 1".
     224              :     const auto values_needed =
     225              :         db::get<::Tags::Next<::Tags::TimeStepId>>(box).slab_number() == 0
     226              :             ? 0
     227              :             : get<TimeSteppers::Tags::FixedOrder>(
     228              :                   db::get<CacheTagPrefix<::Tags::TimeStepper<TimeStepper>>>(box)
     229              :                       .order()) -
     230              :                   1;
     231              : 
     232              :     // Decrease the step so that the generated history will be
     233              :     // entirely before the first step.  This ensures we will not
     234              :     // generate any duplicate times in the history as we start the
     235              :     // real evolution and that the starting procedure does not require
     236              :     // any more information (such as function-of-time values) than the
     237              :     // first real step.
     238              :     const TimeDelta self_start_step = initial_step / (values_needed + 1);
     239              : 
     240              :     StoreInitialValues<tmpl::push_back<detail::vars_to_save<System>,
     241              :                                        ::Tags::TimeStep>>::apply(box);
     242              :     db::mutate<::Tags::TimeStep>(
     243              :         [&self_start_step](const gsl::not_null<::TimeDelta*> time_step) {
     244              :           *time_step = self_start_step;
     245              :         },
     246              :         make_not_null(&box));
     247              : 
     248              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     249              :   }
     250              : };
     251              : 
     252              : /// \ingroup ActionsGroup
     253              : /// \ingroup TimeGroup
     254              : /// Resets the state for the next iteration if the current order is
     255              : /// complete, and exits the self-start loop if the required order has
     256              : /// been reached.
     257              : ///
     258              : /// Uses:
     259              : /// - GlobalCache: nothing
     260              : /// - DataBox: Tags::Next<Tags::TimeStepId>
     261              : ///
     262              : /// DataBox changes:
     263              : /// - Adds: nothing
     264              : /// - Removes: nothing
     265              : /// - Modifies: nothing
     266              : template <typename ExitTag, typename System>
     267            1 : struct CheckForCompletion {
     268              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     269              :             typename ArrayIndex, typename ActionList,
     270              :             typename ParallelComponent>
     271            0 :   static Parallel::iterable_action_return_t apply(
     272              :       db::DataBox<DbTags>& box,
     273              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     274              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     275              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     276              :       const ParallelComponent* const /*meta*/) {
     277              :     const bool done_with_order =
     278              :         db::get<::Tags::Next<::Tags::TimeStepId>>(box).is_at_slab_boundary();
     279              : 
     280              :     if (done_with_order) {
     281              :       tmpl::for_each<detail::vars_to_save<System>>([&box](auto tag) {
     282              :         using Tag = tmpl::type_from<decltype(tag)>;
     283              :         db::mutate<Tag>(
     284              :             [](const gsl::not_null<typename Tag::type*> value,
     285              :                const std::tuple<typename Tag::type>& initial_value) {
     286              :               *value = get<0>(initial_value);
     287              :             },
     288              :             make_not_null(&box), db::get<Tags::InitialValue<Tag>>(box));
     289              :       });
     290              :     }
     291              : 
     292              :     // The self start procedure begins with slab number
     293              :     // -number_of_past_steps and counts up.  When we reach 0 we should
     294              :     // start the evolution proper.  The first thing the evolution loop
     295              :     // will do is update the time, so here we need to check if the
     296              :     // next time should be the first real step.
     297              :     if (db::get<::Tags::Next<::Tags::TimeStepId>>(box).slab_number() == 0) {
     298              :       return {Parallel::AlgorithmExecution::Continue,
     299              :               tmpl::index_of<ActionList, ::Actions::Label<ExitTag>>::value};
     300              :     }
     301              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     302              :   }
     303              : };
     304              : 
     305              : /// \ingroup ActionsGroup
     306              : /// \ingroup TimeGroup
     307              : /// If we have taken enough steps for this order, set the next time to
     308              : /// the start time and increment the slab number
     309              : ///
     310              : /// Uses:
     311              : /// - GlobalCache: nothing
     312              : /// - DataBox:
     313              : ///   - Tags::HistoryEvolvedVariables
     314              : ///   - Tags::TimeStepId
     315              : ///   - Tags::TimeStep
     316              : ///
     317              : /// DataBox changes:
     318              : /// - Adds: nothing
     319              : /// - Removes: nothing
     320              : /// - Modifies: Tags::Next<Tags::TimeStepId> if there is an order increase
     321            1 : struct CheckForOrderIncrease {
     322              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     323              :             typename ArrayIndex, typename ActionList,
     324              :             typename ParallelComponent>
     325            0 :   static Parallel::iterable_action_return_t apply(
     326              :       db::DataBox<DbTags>& box,
     327              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     328              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     329              :       const ArrayIndex& /*array_index*/, ActionList /*meta*/,
     330              :       const ParallelComponent* const /*meta*/) {  // NOLINT const
     331              :     const auto& time_step_id = db::get<::Tags::TimeStepId>(box);
     332              :     const auto& time = time_step_id.step_time();
     333              :     const auto& time_step = db::get<::Tags::TimeStep>(box);
     334              :     using history_tags = ::Tags::get_all_history_tags<DbTags>;
     335              :     const size_t history_integration_order =
     336              :         db::get<tmpl::front<history_tags>>(box).integration_order();
     337              : 
     338              :     // This is correct for both AdamsBashforth and AdamsMoultonPc.
     339              :     // The latter doesn't need as many history values, but it can't
     340              :     // use the first step of the previous order because it would line
     341              :     // up with the predictor stage of the first step for the current
     342              :     // order.
     343              :     const Time required_time =
     344              :         (time_step.is_positive() ? time.slab().start() : time.slab().end()) +
     345              :         history_integration_order * time_step;
     346              :     const bool done_with_order =
     347              :         time_step_id.substep() == 0 and time == required_time;
     348              : 
     349              :     if (done_with_order) {
     350              :       db::mutate<::Tags::Next<::Tags::TimeStepId>>(
     351              :           [](const gsl::not_null<::TimeStepId*> next_time_id,
     352              :              const ::TimeStepId& current_time_id) {
     353              :             const Slab slab = current_time_id.step_time().slab();
     354              :             *next_time_id =
     355              :                 TimeStepId(current_time_id.time_runs_forward(),
     356              :                            current_time_id.slab_number() + 1,
     357              :                            current_time_id.time_runs_forward() ? slab.start()
     358              :                                                                : slab.end());
     359              :           },
     360              :           make_not_null(&box), db::get<::Tags::TimeStepId>(box));
     361              :       tmpl::for_each<history_tags>([&box,
     362              :                                     &history_integration_order](auto tag_v) {
     363              :         using history_tag = typename decltype(tag_v)::type;
     364              :         db::mutate<history_tag>(
     365              :             [&history_integration_order](
     366              :                 const gsl::not_null<typename history_tag::type*>
     367              :                     mutable_history) {
     368              :               ASSERT(mutable_history->integration_order() ==
     369              :                          history_integration_order,
     370              :                      "Using multiple histories of different integration "
     371              :                      "orders. When using multiple histories sharing the same "
     372              :                      "time stepper, all histories must maintain identical "
     373              :                      "integration order.");
     374              :               mutable_history->integration_order(
     375              :                   mutable_history->integration_order() + 1);
     376              :             },
     377              :             make_not_null(&box));
     378              :       });
     379              :     }
     380              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     381              :   }
     382              : };
     383              : 
     384              : /// \ingroup ActionsGroup
     385              : /// \ingroup TimeGroup
     386              : /// Cleans up after the self-start procedure
     387              : ///
     388              : /// Resets the time step to that requested for the evolution and
     389              : /// removes temporary self-start data.
     390              : ///
     391              : /// Uses:
     392              : /// - GlobalCache: nothing
     393              : /// - DataBox: SelfStart::Tags::InitialValue<Tags::TimeStep>
     394              : ///
     395              : /// DataBox changes:
     396              : /// - Adds: nothing
     397              : /// - Removes:
     398              : ///   - All SelfStart::Tags::InitialValue tags
     399              : /// - Modifies: Tags::TimeStep
     400            1 : struct Cleanup {
     401              :  private:
     402              :   template <typename T>
     403            0 :   struct is_a_initial_value : tt::is_a<Tags::InitialValue, T> {};
     404              : 
     405            0 :   using initial_step_tag = Tags::InitialValue<::Tags::TimeStep>;
     406              : 
     407              :  public:
     408              :   template <typename DbTags, typename... InboxTags, typename Metavariables,
     409              :             typename ArrayIndex, typename ActionList,
     410              :             typename ParallelComponent>
     411            0 :   static Parallel::iterable_action_return_t apply(
     412              :       db::DataBox<DbTags>& box,
     413              :       const tuples::TaggedTuple<InboxTags...>& /*inboxes*/,
     414              :       const Parallel::GlobalCache<Metavariables>& /*cache*/,
     415              :       const ArrayIndex& /*array_index*/, const ActionList /*meta*/,
     416              :       const ParallelComponent* const /*meta*/) {
     417              :     // Reset the time step to the value requested by the user.  The
     418              :     // variables were reset in CheckForCompletion.
     419              :     db::mutate<::Tags::TimeStep>(
     420              :         [](const gsl::not_null<::TimeDelta*> time_step,
     421              :            const std::tuple<::TimeDelta>& initial_step) {
     422              :           *time_step = get<0>(initial_step);
     423              :         },
     424              :         make_not_null(&box), db::get<initial_step_tag>(box));
     425              :     using remove_tags = tmpl::filter<DbTags, is_a_initial_value<tmpl::_1>>;
     426              :     // reset each tag to default constructed values to reduce memory usage (Data
     427              :     // structures like `DataVector`s and `Tensor`s have negligible memory usage
     428              :     // when default-constructed). This tends to be better for build time than
     429              :     // constructing more DataBox types with and without this handful of tags.
     430              :     tmpl::for_each<remove_tags>([&box](auto tag_v) {
     431              :       using tag = typename decltype(tag_v)::type;
     432              :       db::mutate<tag>(
     433              :           [](auto tag_value) {
     434              :             *tag_value = std::decay_t<decltype(*tag_value)>{};
     435              :           },
     436              :           make_not_null(&box));
     437              :     });
     438              :     return {Parallel::AlgorithmExecution::Continue, std::nullopt};
     439              :   }
     440              : };
     441              : }  // namespace Actions
     442              : 
     443              : namespace detail {
     444              : struct PhaseStart;
     445              : struct PhaseEnd;
     446              : }  // namespace detail
     447              : 
     448              : /// \ingroup TimeGroup
     449              : /// The list of actions required to self-start an integrator.
     450              : ///
     451              : /// \tparam StepActions List of actions computing and recording the
     452              : /// system derivative and updating the evolved variables (but not the
     453              : /// time).
     454              : ///
     455              : /// \see SelfStart
     456              : template <typename StepActions, typename System,
     457              :           template <typename> typename CacheTagPrefix = std::type_identity_t>
     458            1 : using self_start_procedure = tmpl::flatten<tmpl::list<
     459              : // clang-format off
     460              :     SelfStart::Actions::Initialize<System, CacheTagPrefix>,
     461              :     ::Actions::Label<detail::PhaseStart>,
     462              :     SelfStart::Actions::CheckForCompletion<detail::PhaseEnd, System>,
     463              :     ::Actions::MutateApply<AdvanceTime<CacheTagPrefix>>,
     464              :     SelfStart::Actions::CheckForOrderIncrease,
     465              :     StepActions,
     466              :     ::Actions::Goto<detail::PhaseStart>,
     467              :     ::Actions::Label<detail::PhaseEnd>,
     468              :     SelfStart::Actions::Cleanup,
     469              :     ::Actions::MutateApply<AdvanceTime<CacheTagPrefix>>,
     470              :     Parallel::Actions::TerminatePhase>>;
     471              : // clang-format on
     472              : }  // namespace SelfStart
        

Generated by: LCOV version 2.0-1