SpECTRE Documentation Coverage Report
Current view: top level - Parallel - DistributedObject.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 56.3 % 71 40
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 <charm++.h>
       7              : #include <converse.h>
       8              : #include <cstddef>
       9              : #include <exception>
      10              : #include <initializer_list>
      11              : #include <limits>
      12              : #include <mutex>
      13              : #include <optional>
      14              : #include <ostream>
      15              : #include <pup.h>
      16              : #include <sstream>
      17              : #include <string>
      18              : #include <tuple>
      19              : #include <unordered_map>
      20              : #include <unordered_set>
      21              : #include <utility>
      22              : 
      23              : #include "DataStructures/DataBox/DataBox.hpp"
      24              : #include "DataStructures/DataBox/MetavariablesTag.hpp"
      25              : #include "DataStructures/DataBox/PrefixHelpers.hpp"
      26              : #include "DataStructures/TaggedTuple.hpp"
      27              : #include "Parallel/AlgorithmExecution.hpp"
      28              : #include "Parallel/AlgorithmMetafunctions.hpp"
      29              : #include "Parallel/Algorithms/AlgorithmArrayDeclarations.hpp"
      30              : #include "Parallel/Algorithms/AlgorithmGroupDeclarations.hpp"
      31              : #include "Parallel/Algorithms/AlgorithmNodegroupDeclarations.hpp"
      32              : #include "Parallel/Algorithms/AlgorithmSingletonDeclarations.hpp"
      33              : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      34              : #include "Parallel/Callback.hpp"
      35              : #include "Parallel/CharmRegistration.hpp"
      36              : #include "Parallel/ElementRegistration.hpp"
      37              : #include "Parallel/GlobalCache.hpp"
      38              : #include "Parallel/Info.hpp"
      39              : #include "Parallel/Local.hpp"
      40              : #include "Parallel/NodeLock.hpp"
      41              : #include "Parallel/ParallelComponentHelpers.hpp"
      42              : #include "Parallel/Phase.hpp"
      43              : #include "Parallel/PhaseDependentActionList.hpp"
      44              : #include "Parallel/Printf/Printf.hpp"
      45              : #include "Parallel/Tags/ArrayIndex.hpp"
      46              : #include "Parallel/Tags/DistributedObjectTags.hpp"
      47              : #include "Parallel/TypeTraits.hpp"
      48              : #include "ParallelAlgorithms/Initialization/MutateAssign.hpp"
      49              : #include "Utilities/Algorithm.hpp"
      50              : #include "Utilities/ErrorHandling/Assert.hpp"
      51              : #include "Utilities/ErrorHandling/Error.hpp"
      52              : #include "Utilities/ForceInline.hpp"
      53              : #include "Utilities/Gsl.hpp"
      54              : #include "Utilities/MakeString.hpp"
      55              : #include "Utilities/NoSuchType.hpp"
      56              : #include "Utilities/Overloader.hpp"
      57              : #include "Utilities/PrettyType.hpp"
      58              : #include "Utilities/Requires.hpp"
      59              : #include "Utilities/Serialization/PupStlCpp11.hpp"
      60              : #include "Utilities/Serialization/PupStlCpp17.hpp"
      61              : #include "Utilities/System/ParallelInfo.hpp"
      62              : #include "Utilities/TMPL.hpp"
      63              : #include "Utilities/TypeTraits.hpp"
      64              : 
      65              : /// \cond
      66              : template <size_t Dim>
      67              : class ElementId;
      68              : 
      69              : template <size_t Dim>
      70              : bool is_zeroth_element(const ElementId<Dim>&, const std::optional<size_t>&);
      71              : /// \endcond
      72              : 
      73              : namespace Parallel {
      74              : /// \cond
      75              : template <typename ParallelComponent, typename PhaseDepActionList>
      76              : class DistributedObject;
      77              : /// \endcond
      78              : 
      79              : /*!
      80              :  * \ingroup ParallelGroup
      81              :  * \brief A distributed object (Charm++ Chare) that executes a series of Actions
      82              :  * and is capable of sending and receiving data. Acts as an interface to
      83              :  * Charm++.
      84              :  *
      85              :  * ### Different Types of Algorithms
      86              :  * Charm++ chares can be one of four types, which is specified by the type alias
      87              :  * `chare_type` inside the `ParallelComponent`. The four available types of
      88              :  * Algorithms are:
      89              :  * 1. A Parallel::Algorithms::Singleton where there is only one
      90              :  * in the entire execution of the program.
      91              :  * 2. A Parallel::Algorithms::Array which holds zero or more
      92              :  * elements each of which is a distributed object on some core. An array can
      93              :  * grow and shrink in size dynamically if need be and can also be bound to
      94              :  * another array. That is, the bound array has the same number of elements as
      95              :  * the array it is bound to, and elements with the same ID are on the same core.
      96              :  * 3. A Parallel::Algorithms::Group, which is an array but there is
      97              :  * one element per core and they are not able to be moved around between cores.
      98              :  * These are typically useful for gathering data from array elements on their
      99              :  * core, and then processing or reducing it.
     100              :  * 4. A Parallel::Algorithms::Nodegroup, which is similar to a
     101              :  * group except that there is one element per node. For Charm++ SMP (shared
     102              :  * memory parallelism) builds a node corresponds to the usual definition of a
     103              :  * node on a supercomputer. However, for non-SMP builds nodes and cores are
     104              :  * equivalent. An important difference between groups and nodegroups is that
     105              :  * entry methods (remote calls to functions) are not threadsafe on nodegroups.
     106              :  * It is up to the person writing the Actions that will be executed on the
     107              :  * Nodegroup Algorithm to ensure they are threadsafe.
     108              :  *
     109              :  * ### What is an Algorithm?
     110              :  * An Algorithm is a distributed object, a Charm++ chare, that repeatedly
     111              :  * executes a series of Actions. An Action is a struct that has a `static` apply
     112              :  * function with signature:
     113              :  *
     114              :  * \code
     115              :  * template <typename... DbTags, typename... InboxTags, typename Metavariables,
     116              :  * typename ArrayIndex,  typename ActionList>
     117              :  * static auto  apply(db::DataBox<tmpl::list<DbTags...>>& box,
     118              :  *                    tuples::TaggedTuple<InboxTags...>& inboxes,
     119              :  *                    const GlobalCache<Metavariables>& cache,
     120              :  *                    const ArrayIndex& array_index,
     121              :  *                    const TemporalId& temporal_id, const ActionList meta);
     122              :  * \endcode
     123              :  *
     124              :  * Note that any of the arguments can be const or non-const references except
     125              :  * `array_index`, which must be a `const&`.
     126              :  *
     127              :  * ### Explicit instantiations of entry methods
     128              :  * The code in src/Parallel/CharmMain.tpp registers all entry methods, and if
     129              :  * one is not properly registered then a static_assert explains how to have it
     130              :  * be registered. If there is a bug in the implementation and an entry method
     131              :  * isn't being registered or hitting a static_assert then Charm++ will give an
     132              :  * error of the following form:
     133              :  *
     134              :  * \verbatim
     135              :  * registration happened after init Entry point: simple_action(), addr:
     136              :  * 0x555a3d0e2090
     137              :  * ------------- Processor 0 Exiting: Called CmiAbort ------------
     138              :  * Reason: Did you forget to instantiate a templated entry method in a .ci file?
     139              :  * \endverbatim
     140              :  *
     141              :  * If you encounter this issue please file a bug report supplying everything
     142              :  * necessary to reproduce the issue.
     143              :  */
     144              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     145            1 : class DistributedObject<ParallelComponent,
     146              :                         tmpl::list<PhaseDepActionListsPack...>>
     147              :     : public ParallelComponent::chare_type::template cbase<
     148              :           ParallelComponent,
     149              :           typename get_array_index<typename ParallelComponent::chare_type>::
     150              :               template f<ParallelComponent>> {
     151              :   static_assert(
     152              :       sizeof...(PhaseDepActionListsPack) > 0,
     153              :       "Must have at least one phase dependent action list "
     154              :       "(PhaseActions) in a parallel component. See the first template "
     155              :       "parameter of 'DistributedObject' in the error message to see which "
     156              :       "component doesn't have any phase dependent action lists.");
     157              : 
     158              :  public:
     159              :   /// List of Actions in the order that generates the DataBox types
     160            1 :   using all_actions_list = tmpl::flatten<
     161              :       tmpl::list<typename PhaseDepActionListsPack::action_list...>>;
     162              :   /// The metavariables class passed to the Algorithm
     163            1 :   using metavariables = typename ParallelComponent::metavariables;
     164              :   /// List off all the Tags that can be received into the Inbox
     165            1 :   using inbox_tags_list = Parallel::get_inbox_tags<all_actions_list>;
     166              :   /// The type of the object used to uniquely identify the element of the array,
     167              :   /// group, or nodegroup. The default depends on the component, see
     168              :   /// ParallelComponentHelpers.
     169            1 :   using array_index = typename get_array_index<
     170              :       typename ParallelComponent::chare_type>::template f<ParallelComponent>;
     171              : 
     172            0 :   using parallel_component = ParallelComponent;
     173              :   /// The type of the Chare
     174            1 :   using chare_type = typename parallel_component::chare_type;
     175              :   /// The Charm++ proxy object type
     176            1 :   using cproxy_type =
     177              :       typename chare_type::template cproxy<parallel_component, array_index>;
     178              :   /// The Charm++ base object type
     179            1 :   using cbase_type =
     180              :       typename chare_type::template cbase<parallel_component, array_index>;
     181              : 
     182            0 :   using phase_dependent_action_lists = tmpl::list<PhaseDepActionListsPack...>;
     183              : 
     184            0 :   using inbox_type = tuples::tagged_tuple_from_typelist<inbox_tags_list>;
     185            0 :   using all_cache_tags = get_const_global_cache_tags<metavariables>;
     186            0 :   using distributed_object_tags =
     187              :       typename Tags::distributed_object_tags<metavariables, array_index>;
     188            0 :   using databox_type = db::compute_databox_type<tmpl::flatten<tmpl::list<
     189              :       distributed_object_tags,
     190              :       typename parallel_component::simple_tags_from_options,
     191              :       Tags::GlobalCacheCompute<metavariables>,
     192              :       Tags::ResourceInfoReference<metavariables>,
     193              :       db::wrap_tags_in<Tags::FromGlobalCache, all_cache_tags, metavariables>,
     194              :       Algorithm_detail::action_list_simple_tags<parallel_component>,
     195              :       Algorithm_detail::action_list_compute_tags<parallel_component>>>>;
     196              : 
     197              :   /// \cond
     198              :   // Needed for serialization
     199              :   DistributedObject();
     200              :   /// \endcond
     201              : 
     202              :   /// Constructor used by Main to initialize the algorithm
     203              :   template <class... InitializationTags>
     204            1 :   DistributedObject(
     205              :       const Parallel::CProxy_GlobalCache<metavariables>& global_cache_proxy,
     206              :       tuples::TaggedTuple<InitializationTags...> initialization_items);
     207              : 
     208              :   /// Constructor used to dynamically add a new element of an array
     209              :   /// The `callback` is executed after the element is created.
     210            1 :   DistributedObject(
     211              :       const Parallel::CProxy_GlobalCache<metavariables>& global_cache_proxy,
     212              :       Parallel::Phase current_phase,
     213              :       std::unordered_map<Parallel::Phase, size_t> phase_bookmarks,
     214              :       const std::unique_ptr<Parallel::Callback>& callback);
     215              : 
     216              :   /// Charm++ migration constructor, used after a chare is migrated
     217            1 :   explicit DistributedObject(CkMigrateMessage* /*msg*/);
     218              : 
     219              :   /// \cond
     220              :   ~DistributedObject() override;
     221              : 
     222              :   DistributedObject(const DistributedObject& /*unused*/) = delete;
     223              :   DistributedObject& operator=(const DistributedObject& /*unused*/) = delete;
     224              :   DistributedObject(DistributedObject&& /*unused*/) = delete;
     225              :   DistributedObject& operator=(DistributedObject&& /*unused*/) = delete;
     226              :   /// \endcond
     227              : 
     228              :   /// Print the expanded type aliases
     229            1 :   std::string print_types() const;
     230              : 
     231              :   /// Print the current state of the algorithm
     232            1 :   std::string print_state() const;
     233              : 
     234              :   /// Print the current contents of the inboxes
     235            1 :   std::string print_inbox() const;
     236              : 
     237              :   /// Print the current contents of the DataBox
     238            1 :   std::string print_databox() const;
     239              : 
     240              :   /// Get read access to all the inboxes
     241            1 :   const auto& get_inboxes() const { return inboxes_; }
     242              : 
     243            0 :   auto& get_node_lock() { return node_lock_; }
     244              : 
     245            0 :   void pup(PUP::er& p) override;  // NOLINT
     246              : 
     247              :   /*!
     248              :    * \brief Calls the `apply` function `Action` after a reduction has been
     249              :    * completed.
     250              :    *
     251              :    * The `apply` function must take `arg` as its last argument.
     252              :    */
     253              :   template <typename Action, typename Arg>
     254            1 :   void reduction_action(Arg arg);
     255              : 
     256              :   /// \brief Explicitly call the action `Action`.
     257              :   template <typename Action, typename... Args>
     258            1 :   void simple_action(std::tuple<Args...> args);
     259              : 
     260              :   template <typename Action>
     261            0 :   void simple_action();
     262              : 
     263              :   /// \brief Call the `Action` sychronously, returning a result without any
     264              :   /// parallelization. The action is called immediately and control flow returns
     265              :   /// to the caller immediately upon completion.
     266              :   ///
     267              :   /// \note `Action` must have a type alias `return_type` specifying its return
     268              :   /// type. This constraint is to simplify the variant visitation logic for the
     269              :   /// \ref DataBoxGroup "DataBox".
     270              :   template <typename Action, typename... Args>
     271            1 :   typename Action::return_type local_synchronous_action(Args&&... args);
     272              : 
     273              :   /// @{
     274              :   /// Call an Action on a local nodegroup requiring the Action to handle thread
     275              :   /// safety.
     276              :   ///
     277              :   /// The `Parallel::NodeLock` of the nodegroup is passed to the Action instead
     278              :   /// of the `action_list` as a `const gsl::not_null<Parallel::NodeLock*>&`. The
     279              :   /// node lock can be locked with the `Parallel::NodeLock::lock()` function,
     280              :   /// and unlocked with `Parallel::unlock()`. `Parallel::NodeLock::try_lock()`
     281              :   /// is also provided in case something useful can be done if the lock couldn't
     282              :   /// be acquired.
     283              :   template <
     284              :       typename Action, typename... Args,
     285              :       Requires<((void)sizeof...(Args),
     286              :                 std::is_same_v<Parallel::Algorithms::Nodegroup, chare_type>)> =
     287              :           nullptr>
     288            1 :   void threaded_action(std::tuple<Args...> args) {
     289              :     // Note: this method is defined inline because GCC fails to compile when the
     290              :     // definition is out of line.
     291              :     (void)Parallel::charmxx::RegisterThreadedAction<ParallelComponent, Action,
     292              :                                                     Args...>::registrar;
     293              :     forward_tuple_to_threaded_action<Action>(
     294              :         std::move(args), std::make_index_sequence<sizeof...(Args)>{});
     295              :   }
     296              : 
     297              :   template <typename Action>
     298            1 :   void threaded_action();
     299              :   /// @}
     300              : 
     301              :   /// \brief Receive data and store it in the Inbox, and try to continue
     302              :   /// executing the algorithm
     303              :   ///
     304              :   /// When an algorithm has terminated it can be restarted by passing
     305              :   /// `enable_if_disabled = true`. This allows long-term disabling and
     306              :   /// re-enabling of algorithms
     307              :   template <typename ReceiveTag, typename ReceiveDataType>
     308            1 :   void receive_data(typename ReceiveTag::temporal_id instance,
     309              :                     ReceiveDataType&& t, bool enable_if_disabled = false);
     310              : 
     311              :   template <typename ReceiveTag, typename MessageType>
     312            0 :   void receive_data(MessageType* message);
     313              : 
     314              :   /// @{
     315              :   /// Start evaluating the algorithm until it is stopped by an action.
     316            1 :   void perform_algorithm();
     317              : 
     318            1 :   void perform_algorithm(const bool restart_if_terminated);
     319              :   /// @}
     320              : 
     321              :   /// Start execution of the phase-dependent action list in `next_phase`. If
     322              :   /// `next_phase` has already been visited, execution will resume at the point
     323              :   /// where the previous execution of the same phase left off.
     324              :   ///
     325              :   /// If \p force is true, then regardless of how this component terminated
     326              :   /// (error or deadlock), it will resume.
     327              :   ///
     328              :   /// \warning Don't set \p force to true unless you are absolutely sure you
     329              :   /// want to. This can have very unintended consequences if used incorrectly.
     330            1 :   void start_phase(Parallel::Phase next_phase, bool force = false);
     331              : 
     332              :   /// Get the current phase
     333            1 :   Phase phase() const { return phase_; }
     334              : 
     335              :   /// \brief Get the phase bookmarks
     336              :   ///
     337              :   /// \details These are used to allow a phase to be resumed at a specific
     338              :   /// step in its iterable action list after PhaseControl is used to temporarily
     339              :   /// switch to other phases.
     340            1 :   const std::unordered_map<Parallel::Phase, size_t>& phase_bookmarks() const {
     341              :     return phase_bookmarks_;
     342              :   }
     343              : 
     344              :   /// Tell the Algorithm it should no longer execute the algorithm. This does
     345              :   /// not mean that the execution of the program is terminated, but only that
     346              :   /// the algorithm has terminated. An algorithm can be restarted by passing
     347              :   /// `true` as the second argument to the `receive_data` method or by calling
     348              :   /// perform_algorithm(true).
     349            1 :   constexpr void set_terminate(const bool t) { terminate_ = t; }
     350              : 
     351              :   /// Check if an algorithm should continue being evaluated
     352            1 :   constexpr bool get_terminate() const { return terminate_; }
     353              : 
     354              :   /// @{
     355              :   /// Wrappers for charm++ informational functions.
     356              : 
     357              :   /// Number of processing elements
     358            1 :   inline int number_of_procs() const { return sys::number_of_procs(); }
     359              : 
     360              :   /// %Index of my processing element.
     361            1 :   inline int my_proc() const { return sys::my_proc(); }
     362              : 
     363              :   /// Number of nodes.
     364            1 :   inline int number_of_nodes() const { return sys::number_of_nodes(); }
     365              : 
     366              :   /// %Index of my node.
     367            1 :   inline int my_node() const { return sys::my_node(); }
     368              : 
     369              :   /// Number of processing elements on the given node.
     370            1 :   inline int procs_on_node(const int node_index) const {
     371              :     return sys::procs_on_node(node_index);
     372              :   }
     373              : 
     374              :   /// The local index of my processing element on my node.
     375              :   /// This is in the interval 0, ..., procs_on_node(my_node()) - 1.
     376            1 :   inline int my_local_rank() const { return sys::my_local_rank(); }
     377              : 
     378              :   /// %Index of first processing element on the given node.
     379            1 :   inline int first_proc_on_node(const int node_index) const {
     380              :     return sys::first_proc_on_node(node_index);
     381              :   }
     382              : 
     383              :   /// %Index of the node for the given processing element.
     384            1 :   inline int node_of(const int proc_index) const {
     385              :     return sys::node_of(proc_index);
     386              :   }
     387              : 
     388              :   /// The local index for the given processing element on its node.
     389            1 :   inline int local_rank_of(const int proc_index) const {
     390              :     return sys::local_rank_of(proc_index);
     391              :   }
     392              :   /// @}
     393              : 
     394              :   // Invoke the static `apply` method of `ThisAction`. The if constexprs are for
     395              :   // handling the cases where the `apply` method returns a tuple of one, two,
     396              :   // or three elements, in order:
     397              :   // 1. A DataBox
     398              :   // 2. Either:
     399              :   //    2a. A bool determining whether or not to terminate (and potentially move
     400              :   //        to the next phase), or
     401              :   //    2b. An `AlgorithmExecution` object describing whether to continue,
     402              :   //        pause, or halt.
     403              :   // 3. An unsigned integer corresponding to which action in the current phase's
     404              :   //    algorithm to execute next.
     405              :   //
     406              :   // Returns whether the action ran successfully, i.e., did not return
     407              :   // AlgorithmExecution::Retry.
     408              :   //
     409              :   // Needs to be public for local entry methods to work.
     410              :   //
     411              :   // Note: The template parameters PhaseIndex and DataBoxIndex are used to
     412              :   // shorten the function name to make profiling easier.
     413              :   template <typename ThisAction, typename PhaseIndex, typename DataBoxIndex>
     414            0 :   bool invoke_iterable_action();
     415              : 
     416              :   /// Does a reduction over the component of the reduction status sending the
     417              :   /// result to Main's did_all_elements_terminate member function.
     418            1 :   void contribute_termination_status_to_main();
     419              : 
     420              :   /// Returns the name of the last "next iterable action" to be run before a
     421              :   /// deadlock occurred.
     422            1 :   const std::string& deadlock_analysis_next_iterable_action() const {
     423              :     return deadlock_analysis_next_iterable_action_;
     424              :   }
     425              : 
     426              :  private:
     427            0 :   void set_array_index();
     428              : 
     429              :   template <typename PhaseDepActions, size_t... Is>
     430            0 :   constexpr bool iterate_over_actions(std::index_sequence<Is...> /*meta*/);
     431              : 
     432              :   template <typename Action, typename... Args, size_t... Is>
     433            0 :   void forward_tuple_to_action(std::tuple<Args...>&& args,
     434              :                                std::index_sequence<Is...> /*meta*/);
     435              : 
     436              :   template <typename Action, typename... Args, size_t... Is>
     437            0 :   void forward_tuple_to_threaded_action(
     438              :       std::tuple<Args...>&& args, std::index_sequence<Is...> /*meta*/);
     439              : 
     440            0 :   size_t number_of_actions_in_phase(const Parallel::Phase phase) const;
     441              : 
     442              :   // After catching an exception, shutdown the simulation
     443            0 :   void initiate_shutdown(const std::exception& exception);
     444              : 
     445              :   // Member variables
     446              : #ifdef SPECTRE_CHARM_PROJECTIONS
     447              :   double non_action_time_start_;
     448              : #endif
     449              : 
     450            0 :   Parallel::CProxy_GlobalCache<metavariables> global_cache_proxy_;
     451            0 :   bool performing_action_ = false;
     452            0 :   Parallel::Phase phase_{Parallel::Phase::Initialization};
     453            0 :   std::unordered_map<Parallel::Phase, size_t> phase_bookmarks_{};
     454            0 :   std::size_t algorithm_step_ = 0;
     455              :   tmpl::conditional_t<Parallel::is_node_group_proxy<cproxy_type>::value,
     456              :                       Parallel::NodeLock, NoSuchType>
     457            0 :       node_lock_;
     458              : 
     459            0 :   bool terminate_{true};
     460            0 :   bool halt_algorithm_until_next_phase_{false};
     461              : 
     462              :   // Records the name of the next action to be called so that during deadlock
     463              :   // analysis we can print this out.
     464            0 :   std::string deadlock_analysis_next_iterable_action_{};
     465              : 
     466            0 :   databox_type box_;
     467            0 :   inbox_type inboxes_{};
     468            0 :   array_index array_index_;
     469              : };
     470              : 
     471              : ////////////////////////////////////////////////////////////////
     472              : // Definitions
     473              : ////////////////////////////////////////////////////////////////
     474              : 
     475              : /// \cond
     476              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     477              : DistributedObject<ParallelComponent,
     478              :                   tmpl::list<PhaseDepActionListsPack...>>::DistributedObject() {
     479              :   set_array_index();
     480              : }
     481              : 
     482              : namespace detail {
     483              : inline bool is_zeroth_element(const int array_index) {
     484              :   return 0 == array_index;
     485              : }
     486              : 
     487              : template <size_t Dim>
     488              : bool is_zeroth_element(const ElementId<Dim>& array_index) {
     489              :   return ::is_zeroth_element(array_index, std::nullopt);
     490              : }
     491              : }  // namespace detail
     492              : 
     493              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     494              : template <class... InitializationTags>
     495              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
     496              :     DistributedObject(
     497              :         const Parallel::CProxy_GlobalCache<metavariables>& global_cache_proxy,
     498              :         tuples::TaggedTuple<InitializationTags...> initialization_items)
     499              :     : DistributedObject() {
     500              :   try {
     501              :     if (detail::is_zeroth_element(array_index_)) {
     502              :       const auto check_for_phase = [](auto phase_dep_v) {
     503              :         using PhaseDep = decltype(phase_dep_v);
     504              :         constexpr Parallel::Phase phase = PhaseDep::phase;
     505              :         // PostFailureCleanup is never in the default phase order, but is
     506              :         // controlled by Main rather than PhaseControl
     507              :         if (alg::count(metavariables::default_phase_order, phase) == 0 and
     508              :             phase != Parallel::Phase::PostFailureCleanup) {
     509              :           Parallel::printf(
     510              :               "NOTE: Phase::%s is in the phase dependent action list of\n"
     511              :               "component %s,\nbut not in the default_phase_order specified by "
     512              :               "the metavariables.\nThis means that phase will not be executed "
     513              :               "unless chosen by PhaseControl.\n\n",
     514              :               phase, pretty_type::name<parallel_component>());
     515              :         }
     516              :       };
     517              :       EXPAND_PACK_LEFT_TO_RIGHT(check_for_phase(PhaseDepActionListsPack{}));
     518              :     }
     519              :     (void)initialization_items;  // avoid potential compiler warnings if unused
     520              :     // When we are using the LoadBalancing phase, we want the Main component to
     521              :     // handle the synchronization, so the components do not participate in the
     522              :     // charm++ `AtSync` barrier.
     523              :     // The array parallel components are migratable so they get balanced
     524              :     // appropriately when load balancing is triggered by the LoadBalancing phase
     525              :     // in Main
     526              :     if constexpr (std::is_same_v<typename ParallelComponent::chare_type,
     527              :                                  Parallel::Algorithms::Array>) {
     528              :       this->usesAtSync = false;
     529              :       this->setMigratable(true);
     530              :     }
     531              :     global_cache_proxy_ = global_cache_proxy;
     532              :     ::Initialization::mutate_assign<
     533              :         tmpl::push_back<distributed_object_tags, InitializationTags...>>(
     534              :         make_not_null(&box_), metavariables{}, array_index_,
     535              :         global_cache_proxy_,
     536              :         std::move(get<InitializationTags>(initialization_items))...);
     537              :   } catch (const std::exception& exception) {
     538              :     initiate_shutdown(exception);
     539              :   }
     540              : }
     541              : 
     542              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     543              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
     544              :     DistributedObject(
     545              :         const Parallel::CProxy_GlobalCache<metavariables>& global_cache_proxy,
     546              :         Parallel::Phase current_phase,
     547              :         std::unordered_map<Parallel::Phase, size_t> phase_bookmarks,
     548              :         const std::unique_ptr<Parallel::Callback>& callback)
     549              :     : DistributedObject() {
     550              :   static_assert(Parallel::is_array_proxy<cproxy_type>::value,
     551              :                 "Can only dynamically add elements to an array component");
     552              :   try {
     553              :     // When we are using the LoadBalancing phase, we want the Main component to
     554              :     // handle the synchronization, so the components do not participate in the
     555              :     // charm++ `AtSync` barrier.
     556              :     // The array parallel components are migratable so they get balanced
     557              :     // appropriately when load balancing is triggered by the LoadBalancing phase
     558              :     // in Main
     559              :     this->usesAtSync = false;
     560              :     this->setMigratable(true);
     561              :     global_cache_proxy_ = global_cache_proxy;
     562              :     phase_ = current_phase;
     563              :     phase_bookmarks_ = std::move(phase_bookmarks);
     564              :     ::Initialization::mutate_assign<distributed_object_tags>(
     565              :         make_not_null(&box_), metavariables{}, array_index_,
     566              :         global_cache_proxy_);
     567              :     callback->invoke();
     568              :   } catch (const std::exception& exception) {
     569              :     initiate_shutdown(exception);
     570              :   }
     571              : }
     572              : 
     573              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     574              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
     575              :     DistributedObject(CkMigrateMessage* msg)
     576              :     : cbase_type(msg) {}
     577              : 
     578              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     579              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
     580              :     ~DistributedObject() {
     581              :   // We place the registrar in the destructor since every DistributedObject will
     582              :   // have a destructor, but we have different constructors so it's not clear
     583              :   // which will be instantiated.
     584              :   (void)Parallel::charmxx::RegisterParallelComponent<
     585              :       ParallelComponent>::registrar;
     586              : }
     587              : 
     588              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     589              : std::string
     590              : DistributedObject<ParallelComponent,
     591              :                   tmpl::list<PhaseDepActionListsPack...>>::print_types() const {
     592              :   std::ostringstream os;
     593              :   os << "Algorithm type aliases:\n";
     594              :   os << "using all_actions_list = " << pretty_type::get_name<all_actions_list>()
     595              :      << ";\n";
     596              : 
     597              :   os << "using metavariables = " << pretty_type::get_name<metavariables>()
     598              :      << ";\n";
     599              :   os << "using inbox_tags_list = " << pretty_type::get_name<inbox_tags_list>()
     600              :      << ";\n";
     601              :   os << "using array_index = " << pretty_type::get_name<array_index>() << ";\n";
     602              :   os << "using parallel_component = "
     603              :      << pretty_type::get_name<parallel_component>() << ";\n";
     604              :   os << "using chare_type = " << pretty_type::get_name<chare_type>() << ";\n";
     605              :   os << "using cproxy_type = " << pretty_type::get_name<cproxy_type>() << ";\n";
     606              :   os << "using cbase_type = " << pretty_type::get_name<cbase_type>() << ";\n";
     607              :   os << "using phase_dependent_action_lists = "
     608              :      << pretty_type::get_name<phase_dependent_action_lists>() << ";\n";
     609              :   os << "using all_cache_tags = " << pretty_type::get_name<all_cache_tags>()
     610              :      << ";\n";
     611              :   os << "using databox_type = " << pretty_type::get_name<databox_type>()
     612              :      << ";\n";
     613              :   return os.str();
     614              : }
     615              : 
     616              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     617              : std::string
     618              : DistributedObject<ParallelComponent,
     619              :                   tmpl::list<PhaseDepActionListsPack...>>::print_state() const {
     620              :   using ::operator<<;
     621              :   std::ostringstream os;
     622              :   os << "State:\n";
     623              :   os << "performing_action_ = " << std::boolalpha << performing_action_
     624              :      << ";\n";
     625              :   os << "phase_ = " << phase_ << ";\n";
     626              :   os << "phase_bookmarks_ = " << phase_bookmarks_ << ";\n";
     627              :   os << "algorithm_step_ = " << algorithm_step_ << ";\n";
     628              :   os << "terminate_ = " << terminate_ << ";\n";
     629              :   os << "halt_algorithm_until_next_phase_ = "
     630              :      << halt_algorithm_until_next_phase_ << ";\n";
     631              :   os << "array_index_ = " << array_index_ << ";\n";
     632              :   return os.str();
     633              : }
     634              : 
     635              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     636              : std::string
     637              : DistributedObject<ParallelComponent,
     638              :                   tmpl::list<PhaseDepActionListsPack...>>::print_inbox() const {
     639              :   std::ostringstream os;
     640              :   os << "inboxes_ = " << inboxes_ << ";\n";
     641              :   return os.str();
     642              : }
     643              : 
     644              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     645              : std::string DistributedObject<
     646              :     ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::print_databox()
     647              :     const {
     648              :   std::ostringstream os;
     649              :   os << "box_:\n" << box_;
     650              :   return os.str();
     651              : }
     652              : 
     653              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     654              : void DistributedObject<
     655              :     ParallelComponent,
     656              :     tmpl::list<PhaseDepActionListsPack...>>::pup(PUP::er& p) {  // NOLINT
     657              : #ifdef SPECTRE_CHARM_PROJECTIONS
     658              :   p | non_action_time_start_;
     659              : #endif
     660              :   if (performing_action_ and not p.isSizing()) {
     661              :     ERROR("cannot serialize while performing action!");
     662              :   }
     663              :   p | performing_action_;
     664              :   p | phase_;
     665              :   p | phase_bookmarks_;
     666              :   p | algorithm_step_;
     667              :   if constexpr (Parallel::is_node_group_proxy<cproxy_type>::value) {
     668              :     p | node_lock_;
     669              :   }
     670              :   p | terminate_;
     671              :   p | halt_algorithm_until_next_phase_;
     672              :   p | array_index_;
     673              :   p | global_cache_proxy_;
     674              : 
     675              :   // We have no way to check that everything in the DataBox is
     676              :   // temporary, but anything with non-trivial initialization isn't.
     677              :   static_assert(
     678              :       ParallelComponent::checkpoint_data or
     679              :       std::is_same_v<typename ParallelComponent::simple_tags_from_options,
     680              :                      tmpl::list<>>);
     681              :   if constexpr (ParallelComponent::checkpoint_data) {
     682              :     p | box_;
     683              :     // After unpacking the DataBox, we "touch" the GlobalCache proxy inside.
     684              :     // This forces the DataBox to recompute the GlobalCache* the next time it
     685              :     // is needed, but delays this process until after the pupper is called.
     686              :     // (This delay is important: updating the pointer requires calling
     687              :     // ckLocalBranch() on the Charm++ proxy, and in a restart from checkpoint
     688              :     // this call may not be well-defined until after components are finished
     689              :     // unpacking.)
     690              :     if (p.isUnpacking()) {
     691              :       db::mutate<Tags::GlobalCacheProxy<metavariables>>(
     692              :           [](const gsl::not_null<CProxy_GlobalCache<metavariables>*> proxy) {
     693              :             (void)proxy;
     694              :           },
     695              :           make_not_null(&box_));
     696              :     }
     697              :     p | inboxes_;
     698              :     if constexpr (Parallel::is_dg_element_collection_v<ParallelComponent>) {
     699              :       if (phase_ == Parallel::Phase::LoadBalancing) {
     700              :         ERROR(
     701              :             "Can't do load balacing phase with DG element collection right "
     702              :             "now.");
     703              :       }
     704              :     } else {
     705              :       // Note that `perform_registration_or_deregistration` passes the `box_`
     706              :       // by const reference. If mutable access is required to the box, this
     707              :       // function call needs to be carefully considered with respect to the
     708              :       // `p | box_` call in both packing and unpacking scenarios.
     709              :       //
     710              :       // Note also that we don't perform (de)registrations when pup'ing for a
     711              :       // checkpoint/restart. This enables a simpler first-pass implementation
     712              :       // of checkpointing, though it means the restart must occur on the same
     713              :       // hardware configuration (same number of nodes and same procs per node)
     714              :       // used when writing the checkpoint.
     715              :       if (phase_ == Parallel::Phase::LoadBalancing) {
     716              :         // The deregistration and registration below does not actually insert
     717              :         // anything into the PUP::er stream, so nothing is done on a sizing pup.
     718              :         if (p.isPacking()) {
     719              :           deregister_element<ParallelComponent>(
     720              :               box_, *Parallel::local_branch(global_cache_proxy_), array_index_);
     721              :         }
     722              :         if (p.isUnpacking()) {
     723              :           register_element<ParallelComponent>(
     724              :               box_, *Parallel::local_branch(global_cache_proxy_), array_index_);
     725              :         }
     726              :       }
     727              :     }
     728              :   } else if (p.isUnpacking()) {
     729              :     box_ = decltype(box_){};
     730              :     inboxes_ = decltype(inboxes_){};
     731              :     db::mutate<Tags::GlobalCacheProxy<metavariables>>(
     732              :         [&](const gsl::not_null<CProxy_GlobalCache<metavariables>*> proxy) {
     733              :           *proxy = global_cache_proxy_;
     734              :         },
     735              :         make_not_null(&box_));
     736              :   }
     737              : }
     738              : 
     739              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     740              : template <typename Action, typename Arg>
     741              : void DistributedObject<
     742              :     ParallelComponent,
     743              :     tmpl::list<PhaseDepActionListsPack...>>::reduction_action(Arg arg) {
     744              :   try {
     745              :     (void)Parallel::charmxx::RegisterReductionAction<
     746              :         ParallelComponent, Action, std::decay_t<Arg>>::registrar;
     747              :     {
     748              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     749              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     750              :         hold_lock.emplace(node_lock_);
     751              :       }
     752              :       if (performing_action_) {
     753              :         ERROR(
     754              :             "Already performing an Action and cannot execute additional "
     755              :             "Actions from inside of an Action. This is only possible if the "
     756              :             "reduction_action function is not invoked via a proxy, which makes "
     757              :             "no sense for a reduction.");
     758              :       }
     759              :       performing_action_ = true;
     760              :       arg.finalize();
     761              :       forward_tuple_to_action<Action>(
     762              :           std::move(arg.data()), std::make_index_sequence<Arg::pack_size()>{});
     763              :       performing_action_ = false;
     764              :     }
     765              :     perform_algorithm();
     766              :   } catch (const std::exception& exception) {
     767              :     initiate_shutdown(exception);
     768              :   }
     769              : }
     770              : 
     771              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     772              : template <typename Action, typename... Args>
     773              : void DistributedObject<ParallelComponent,
     774              :                        tmpl::list<PhaseDepActionListsPack...>>::
     775              :     simple_action(std::tuple<Args...> args) {
     776              :   try {
     777              :     (void)Parallel::charmxx::RegisterSimpleAction<ParallelComponent, Action,
     778              :                                                   Args...>::registrar;
     779              :     {
     780              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     781              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     782              :         hold_lock.emplace(node_lock_);
     783              :       }
     784              :       if (performing_action_) {
     785              :         ERROR(
     786              :             "Already performing an Action and cannot execute additional "
     787              :             "Actions from inside of an Action. This is only possible if the "
     788              :             "simple_action function is not invoked via a proxy, which "
     789              :             "we do not allow.");
     790              :       }
     791              :       performing_action_ = true;
     792              :       forward_tuple_to_action<Action>(
     793              :           std::move(args), std::make_index_sequence<sizeof...(Args)>{});
     794              :       performing_action_ = false;
     795              :     }
     796              :     perform_algorithm();
     797              :   } catch (const std::exception& exception) {
     798              :     initiate_shutdown(exception);
     799              :   }
     800              : }
     801              : 
     802              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     803              : template <typename Action>
     804              : void DistributedObject<
     805              :     ParallelComponent,
     806              :     tmpl::list<PhaseDepActionListsPack...>>::simple_action() {
     807              :   try {
     808              :     (void)Parallel::charmxx::RegisterSimpleAction<ParallelComponent,
     809              :                                                   Action>::registrar;
     810              :     {
     811              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     812              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     813              :         hold_lock.emplace(node_lock_);
     814              :       }
     815              :       if (performing_action_) {
     816              :         ERROR(
     817              :             "Already performing an Action and cannot execute additional "
     818              :             "Actions from inside of an Action. This is only possible if the "
     819              :             "simple_action function is not invoked via a proxy, which "
     820              :             "we do not allow.");
     821              :       }
     822              :       performing_action_ = true;
     823              :       Action::template apply<ParallelComponent>(
     824              :           box_, *Parallel::local_branch(global_cache_proxy_),
     825              :           static_cast<const array_index&>(array_index_));
     826              :       performing_action_ = false;
     827              :     }
     828              :     perform_algorithm();
     829              :   } catch (const std::exception& exception) {
     830              :     initiate_shutdown(exception);
     831              :   }
     832              : }
     833              : 
     834              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     835              : template <typename Action, typename... Args>
     836              : typename Action::return_type
     837              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
     838              :     local_synchronous_action(Args&&... args) {
     839              :   static_assert(Parallel::is_node_group_proxy<cproxy_type>::value,
     840              :                 "Cannot call a (blocking) local synchronous action on a "
     841              :                 "chare that is not a NodeGroup");
     842              :   return Action::template apply<ParallelComponent>(
     843              :       box_, make_not_null(&node_lock_), std::forward<Args>(args)...);
     844              : }
     845              : 
     846              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     847              : template <typename Action>
     848              : void DistributedObject<
     849              :     ParallelComponent,
     850              :     tmpl::list<PhaseDepActionListsPack...>>::threaded_action() {
     851              :   try {
     852              :     // NOLINTNEXTLINE(modernize-redundant-void-arg)
     853              :     (void)Parallel::charmxx::RegisterThreadedAction<ParallelComponent,
     854              :                                                     Action>::registrar;
     855              :     if constexpr (Parallel::is_dg_element_collection_v<parallel_component>) {
     856              :       Action::template apply<ParallelComponent>(
     857              :           box_, *Parallel::local_branch(global_cache_proxy_),
     858              :           static_cast<const array_index&>(array_index_),
     859              :           make_not_null(&node_lock_), this);
     860              :     } else {
     861              :       Action::template apply<ParallelComponent>(
     862              :           box_, *Parallel::local_branch(global_cache_proxy_),
     863              :           static_cast<const array_index&>(array_index_),
     864              :           make_not_null(&node_lock_));
     865              :     }
     866              :   } catch (const std::exception& exception) {
     867              :     initiate_shutdown(exception);
     868              :   }
     869              : }
     870              : 
     871              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     872              : template <typename ReceiveTag, typename ReceiveDataType>
     873              : void DistributedObject<ParallelComponent,
     874              :                        tmpl::list<PhaseDepActionListsPack...>>::
     875              :     receive_data(typename ReceiveTag::temporal_id instance, ReceiveDataType&& t,
     876              :                  const bool enable_if_disabled) {
     877              :   try {
     878              :     (void)Parallel::charmxx::RegisterReceiveData<ParallelComponent, ReceiveTag,
     879              :                                                  false>::registrar;
     880              :     bool do_perform_algorithm = false;
     881              :     {
     882              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     883              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     884              :         hold_lock.emplace(node_lock_);
     885              :       }
     886              :       if (enable_if_disabled) {
     887              :         set_terminate(false);
     888              :       }
     889              :       do_perform_algorithm = ReceiveTag::insert_into_inbox(
     890              :           make_not_null(&tuples::get<ReceiveTag>(inboxes_)), instance,
     891              :           std::forward<ReceiveDataType>(t));
     892              :     }
     893              :     if (do_perform_algorithm) {
     894              :       perform_algorithm();
     895              :     }
     896              :   } catch (const std::exception& exception) {
     897              :     initiate_shutdown(exception);
     898              :   }
     899              : }
     900              : 
     901              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     902              : template <typename ReceiveTag, typename MessageType>
     903              : void DistributedObject<ParallelComponent,
     904              :                        tmpl::list<PhaseDepActionListsPack...>>::
     905              :     receive_data(MessageType* message) {
     906              :   try {
     907              :     (void)Parallel::charmxx::RegisterReceiveData<ParallelComponent, ReceiveTag,
     908              :                                                  true>::registrar;
     909              :     bool do_perform_algorithm = false;
     910              :     {
     911              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     912              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     913              :         hold_lock.emplace(node_lock_);
     914              :       }
     915              :       if (message->enable_if_disabled) {
     916              :         set_terminate(false);
     917              :       }
     918              :       do_perform_algorithm = ReceiveTag::insert_into_inbox(
     919              :           make_not_null(&tuples::get<ReceiveTag>(inboxes_)), message);
     920              :       // Cannot use message after this call because a std::unique_ptr now owns
     921              :       // it. Doing so would result in undefined behavior
     922              :     }
     923              :     if (do_perform_algorithm) {
     924              :       perform_algorithm();
     925              :     }
     926              :   } catch (const std::exception& exception) {
     927              :     initiate_shutdown(exception);
     928              :   }
     929              : }
     930              : 
     931              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     932              : void DistributedObject<
     933              :     ParallelComponent,
     934              :     tmpl::list<PhaseDepActionListsPack...>>::perform_algorithm() {
     935              :   try {
     936              :     if (performing_action_ or get_terminate() or
     937              :         halt_algorithm_until_next_phase_) {
     938              :       return;
     939              :     }
     940              : #ifdef SPECTRE_CHARM_PROJECTIONS
     941              :     non_action_time_start_ = sys::wall_time();
     942              : #endif
     943              :     {
     944              :       std::optional<std::lock_guard<Parallel::NodeLock>> hold_lock{};
     945              :       if constexpr (std::is_same_v<Parallel::NodeLock, decltype(node_lock_)>) {
     946              :         hold_lock.emplace(node_lock_);
     947              :       }
     948              :       const auto invoke_for_phase = [this](auto phase_dep_v) {
     949              :         using PhaseDep = decltype(phase_dep_v);
     950              :         constexpr Parallel::Phase phase = PhaseDep::phase;
     951              :         using actions_list = typename PhaseDep::action_list;
     952              :         if (phase_ == phase) {
     953              :           while (tmpl::size<actions_list>::value > 0 and not get_terminate() and
     954              :                  not halt_algorithm_until_next_phase_ and
     955              :                  iterate_over_actions<PhaseDep>(
     956              :                      std::make_index_sequence<
     957              :                          tmpl::size<actions_list>::value>{})) {
     958              :           }
     959              :           tmpl::for_each<actions_list>([this](auto action_v) {
     960              :             using action = tmpl::type_from<decltype(action_v)>;
     961              :             if (algorithm_step_ ==
     962              :                 tmpl::index_of<actions_list, action>::value) {
     963              :               deadlock_analysis_next_iterable_action_ =
     964              :                   pretty_type::name<action>();
     965              :             }
     966              :           });
     967              :         }
     968              :       };
     969              :       // Loop over all phases, once the current phase is found we perform the
     970              :       // algorithm in that phase until we are no longer able to because we are
     971              :       // waiting on data to be sent or because the algorithm has been marked as
     972              :       // terminated.
     973              :       EXPAND_PACK_LEFT_TO_RIGHT(invoke_for_phase(PhaseDepActionListsPack{}));
     974              :     }
     975              : #ifdef SPECTRE_CHARM_PROJECTIONS
     976              :     traceUserBracketEvent(SPECTRE_CHARM_NON_ACTION_WALLTIME_EVENT_ID,
     977              :                           non_action_time_start_, sys::wall_time());
     978              : #endif
     979              :   } catch (const std::exception& exception) {
     980              :     initiate_shutdown(exception);
     981              :   }
     982              : }
     983              : 
     984              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     985              : void DistributedObject<ParallelComponent,
     986              :                        tmpl::list<PhaseDepActionListsPack...>>::
     987              :     perform_algorithm(const bool restart_if_terminated) {
     988              :   try {
     989              :     if (restart_if_terminated) {
     990              :       set_terminate(false);
     991              :     }
     992              :     perform_algorithm();
     993              :   } catch (const std::exception& exception) {
     994              :     initiate_shutdown(exception);
     995              :   }
     996              : }
     997              : 
     998              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
     999              : void DistributedObject<ParallelComponent,
    1000              :                        tmpl::list<PhaseDepActionListsPack...>>::
    1001              :     start_phase(const Parallel::Phase next_phase, const bool force) {
    1002              :   try {
    1003              :     // An algorithm must always be set to terminate at the beginning of a phase,
    1004              :     // otherwise it's an error (see below). Therefore if we want to force a
    1005              :     // phase start, we must set terminate to true regardless of what it was
    1006              :     // before.
    1007              :     if (force) {
    1008              :       set_terminate(true);
    1009              :     }
    1010              :     // terminate should be true since we exited a phase previously.
    1011              :     if (not get_terminate() and not halt_algorithm_until_next_phase_) {
    1012              :       ERROR(
    1013              :           "An algorithm must always be set to terminate at the beginning of a "
    1014              :           "phase. Since this is not the case the previous phase did not end "
    1015              :           "correctly. The previous phase is: "
    1016              :           << phase_ << " and the next phase is: " << next_phase
    1017              :           << ", The termination flag is: " << get_terminate()
    1018              :           << ", and the halt flag is: " << halt_algorithm_until_next_phase_);
    1019              :     }
    1020              :     // set terminate to true if there are no actions in this PDAL
    1021              :     set_terminate(number_of_actions_in_phase(next_phase) == 0);
    1022              : 
    1023              :     // Ideally, we'd set the bookmarks as we are leaving a phase, but there is
    1024              :     // no 'clean-up' code that we run when departing a phase, so instead we set
    1025              :     // the bookmark for the previous phase (still stored in `phase_` at this
    1026              :     // point), before we update the member variable `phase_`.
    1027              :     // Then, after updating `phase_`, we check if we've ever stored a bookmark
    1028              :     // for the new phase previously. If so, we start from where we left off,
    1029              :     // otherwise, start from the beginning of the action list.
    1030              :     phase_bookmarks_[phase_] = algorithm_step_;
    1031              :     phase_ = next_phase;
    1032              :     if (phase_bookmarks_.count(phase_) != 0) {
    1033              :       algorithm_step_ = phase_bookmarks_.at(phase_);
    1034              :     } else {
    1035              :       algorithm_step_ = 0;
    1036              :     }
    1037              :     halt_algorithm_until_next_phase_ = false;
    1038              :     perform_algorithm();
    1039              :   } catch (const std::exception& exception) {
    1040              :     initiate_shutdown(exception);
    1041              :   }
    1042              : }
    1043              : 
    1044              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1045              : void DistributedObject<
    1046              :     ParallelComponent,
    1047              :     tmpl::list<PhaseDepActionListsPack...>>::set_array_index() {
    1048              :   // down cast to the algorithm_type, so that the `thisIndex` method can be
    1049              :   // called, which is defined in the CBase class
    1050              :   array_index_ = static_cast<typename chare_type::template algorithm_type<
    1051              :       ParallelComponent, array_index>&>(*this)
    1052              :                      .thisIndex;
    1053              : }
    1054              : 
    1055              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1056              : template <typename PhaseDepActions, size_t... Is>
    1057              : constexpr bool
    1058              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
    1059              :     iterate_over_actions(const std::index_sequence<Is...> /*meta*/) {
    1060              :   bool take_next_action = true;
    1061              :   const auto helper = [this, &take_next_action](auto iteration) {
    1062              :     constexpr size_t iter = decltype(iteration)::value;
    1063              :     if (not(take_next_action and not terminate_ and
    1064              :             not halt_algorithm_until_next_phase_ and algorithm_step_ == iter)) {
    1065              :       return;
    1066              :     }
    1067              :     using actions_list = typename PhaseDepActions::action_list;
    1068              :     using this_action = tmpl::at_c<actions_list, iter>;
    1069              : 
    1070              :     constexpr size_t phase_index =
    1071              :         tmpl::index_of<phase_dependent_action_lists, PhaseDepActions>::value;
    1072              :     performing_action_ = true;
    1073              :     ++algorithm_step_;
    1074              :     // While the overhead from using the local entry method to enable
    1075              :     // profiling is fairly small (<2%), we still avoid it when we aren't
    1076              :     // tracing.
    1077              : #ifdef SPECTRE_CHARM_PROJECTIONS
    1078              :     if constexpr (Parallel::is_array<parallel_component>::value) {
    1079              :       if (not this->thisProxy[array_index_]
    1080              :                   .template invoke_iterable_action<
    1081              :                       this_action, std::integral_constant<size_t, phase_index>,
    1082              :                       std::integral_constant<size_t, iter>>()) {
    1083              :         take_next_action = false;
    1084              :         --algorithm_step_;
    1085              :       }
    1086              :     } else {
    1087              : #endif  // SPECTRE_CHARM_PROJECTIONS
    1088              :       if (not invoke_iterable_action<
    1089              :               this_action, std::integral_constant<size_t, phase_index>,
    1090              :               std::integral_constant<size_t, iter>>()) {
    1091              :         take_next_action = false;
    1092              :         --algorithm_step_;
    1093              :       }
    1094              : #ifdef SPECTRE_CHARM_PROJECTIONS
    1095              :     }
    1096              : #endif  // SPECTRE_CHARM_PROJECTIONS
    1097              :     performing_action_ = false;
    1098              :     // Wrap counter if necessary
    1099              :     if (algorithm_step_ >= tmpl::size<actions_list>::value) {
    1100              :       algorithm_step_ = 0;
    1101              :     }
    1102              :   };
    1103              :   // In case of no Actions avoid compiler warning.
    1104              :   (void)helper;
    1105              :   // This is a template for loop for Is
    1106              :   EXPAND_PACK_LEFT_TO_RIGHT(helper(std::integral_constant<size_t, Is>{}));
    1107              :   return take_next_action;
    1108              : }
    1109              : 
    1110              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1111              : template <typename Action, typename... Args, size_t... Is>
    1112              : void DistributedObject<ParallelComponent,
    1113              :                        tmpl::list<PhaseDepActionListsPack...>>::
    1114              :     forward_tuple_to_action(std::tuple<Args...>&& args,
    1115              :                             std::index_sequence<Is...> /*meta*/) {
    1116              :   Action::template apply<ParallelComponent>(
    1117              :       box_, *Parallel::local_branch(global_cache_proxy_),
    1118              :       static_cast<const array_index&>(array_index_),
    1119              :       std::forward<Args>(std::get<Is>(args))...);
    1120              : }
    1121              : 
    1122              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1123              : template <typename Action, typename... Args, size_t... Is>
    1124              : void DistributedObject<ParallelComponent,
    1125              :                        tmpl::list<PhaseDepActionListsPack...>>::
    1126              :     forward_tuple_to_threaded_action(std::tuple<Args...>&& args,
    1127              :                                      std::index_sequence<Is...> /*meta*/) {
    1128              :   const gsl::not_null<Parallel::NodeLock*> node_lock{&node_lock_};
    1129              :   if constexpr (Parallel::is_dg_element_collection_v<parallel_component>) {
    1130              :     Action::template apply<ParallelComponent>(
    1131              :         box_, *Parallel::local_branch(global_cache_proxy_),
    1132              :         static_cast<const array_index&>(array_index_), node_lock, this,
    1133              :         std::forward<Args>(std::get<Is>(args))...);
    1134              :   } else {
    1135              :     Action::template apply<ParallelComponent>(
    1136              :         box_, *Parallel::local_branch(global_cache_proxy_),
    1137              :         static_cast<const array_index&>(array_index_), node_lock,
    1138              :         std::forward<Args>(std::get<Is>(args))...);
    1139              :   }
    1140              : }
    1141              : 
    1142              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1143              : size_t
    1144              : DistributedObject<ParallelComponent, tmpl::list<PhaseDepActionListsPack...>>::
    1145              :     number_of_actions_in_phase(const Parallel::Phase phase) const {
    1146              :   size_t number_of_actions = 0;
    1147              :   const auto helper = [&number_of_actions, phase](auto pdal_v) {
    1148              :     if (pdal_v.phase == phase) {
    1149              :       number_of_actions = pdal_v.number_of_actions;
    1150              :     }
    1151              :   };
    1152              :   EXPAND_PACK_LEFT_TO_RIGHT(helper(PhaseDepActionListsPack{}));
    1153              :   return number_of_actions;
    1154              : }
    1155              : 
    1156              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1157              : template <typename ThisAction, typename PhaseIndex, typename DataBoxIndex>
    1158              : bool DistributedObject<
    1159              :     ParallelComponent,
    1160              :     tmpl::list<PhaseDepActionListsPack...>>::invoke_iterable_action() {
    1161              :   using phase_dep_action =
    1162              :       tmpl::at_c<phase_dependent_action_lists, PhaseIndex::value>;
    1163              :   using actions_list = typename phase_dep_action::action_list;
    1164              : 
    1165              : #ifdef SPECTRE_CHARM_PROJECTIONS
    1166              :   if constexpr (Parallel::is_array<parallel_component>::value) {
    1167              :     (void)Parallel::charmxx::RegisterInvokeIterableAction<
    1168              :         ParallelComponent, ThisAction, PhaseIndex, DataBoxIndex>::registrar;
    1169              :   }
    1170              : #endif // SPECTRE_CHARM_PROJECTIONS
    1171              : 
    1172              :   AlgorithmExecution requested_execution{};
    1173              :   std::optional<std::size_t> next_action_step{};
    1174              :   std::tie(requested_execution, next_action_step) = ThisAction::apply(
    1175              :       box_, inboxes_, *Parallel::local_branch(global_cache_proxy_),
    1176              :       std::as_const(array_index_), actions_list{},
    1177              :       std::add_pointer_t<ParallelComponent>{});
    1178              : 
    1179              :   if (next_action_step.has_value()) {
    1180              :     ASSERT(
    1181              :         AlgorithmExecution::Retry != requested_execution,
    1182              :         "Switching actions on Retry doesn't make sense. Specify std::nullopt "
    1183              :         "as the second argument of the iterable action return type");
    1184              :     algorithm_step_ = next_action_step.value();
    1185              :   }
    1186              : 
    1187              :   switch (requested_execution) {
    1188              :     case AlgorithmExecution::Continue:
    1189              :       return true;
    1190              :     case AlgorithmExecution::Retry:
    1191              :       return false;
    1192              :     case AlgorithmExecution::Pause:
    1193              :       terminate_ = true;
    1194              :       return true;
    1195              :     case AlgorithmExecution::Halt:
    1196              :       halt_algorithm_until_next_phase_ = true;
    1197              :       terminate_ = true;
    1198              :       return true;
    1199              :     default:  // LCOV_EXCL_LINE
    1200              :       // LCOV_EXCL_START
    1201              :       ERROR("No case for a Parallel::AlgorithmExecution with integral value "
    1202              :             << static_cast<std::underlying_type_t<AlgorithmExecution>>(
    1203              :                    requested_execution)
    1204              :             << "\n");
    1205              :       // LCOV_EXCL_STOP
    1206              :   }
    1207              : }
    1208              : 
    1209              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1210              : void DistributedObject<ParallelComponent,
    1211              :                        tmpl::list<PhaseDepActionListsPack...>>::
    1212              :     contribute_termination_status_to_main() {
    1213              :   auto* global_cache = Parallel::local_branch(global_cache_proxy_);
    1214              :   if (UNLIKELY(global_cache == nullptr)) {
    1215              :     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
    1216              :     CkError(
    1217              :         "Global cache pointer is null. This is an internal inconsistency "
    1218              :         "error. Please file an issue.");
    1219              :     sys::abort("");
    1220              :   }
    1221              :   auto main_proxy = global_cache->get_main_proxy();
    1222              :   if (UNLIKELY(not main_proxy.has_value())) {
    1223              :     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
    1224              :     CkError(
    1225              :         "The main proxy has not been set in the global cache when "
    1226              :         "checking that all components have terminated. This is an internal "
    1227              :         "inconsistency error. Please file an issue.");
    1228              :     sys::abort("");
    1229              :   }
    1230              :   CkCallback cb(
    1231              :       CkReductionTarget(Main<metavariables>, did_all_elements_terminate),
    1232              :       main_proxy.value());
    1233              :   this->contribute(sizeof(bool), &terminate_, CkReduction::logical_and_bool,
    1234              :                    cb);
    1235              : }
    1236              : 
    1237              : template <typename ParallelComponent, typename... PhaseDepActionListsPack>
    1238              : void DistributedObject<ParallelComponent,
    1239              :                        tmpl::list<PhaseDepActionListsPack...>>::
    1240              :     initiate_shutdown(const std::exception& exception) {
    1241              :   // In order to make it so that we can later run other actions for cleanup
    1242              :   // (e.g. dumping data) we need to make sure that we enable running actions
    1243              :   // again
    1244              :   performing_action_ = false;
    1245              :   // Send message to `Main` that we received an exception and set termination.
    1246              :   auto* global_cache = Parallel::local_branch(global_cache_proxy_);
    1247              :   if (UNLIKELY(global_cache == nullptr)) {
    1248              :     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
    1249              :     CkError(
    1250              :         "Global cache pointer is null. This is an internal inconsistency "
    1251              :         "error. Please file an issue.");
    1252              :     sys::abort("");
    1253              :   }
    1254              :   auto main_proxy = global_cache->get_main_proxy();
    1255              :   if (UNLIKELY(not main_proxy.has_value())) {
    1256              :     // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg)
    1257              :     CkError(
    1258              :         "The main proxy has not been set in the global cache when terminating "
    1259              :         "the component. This is an internal inconsistency error. Please file "
    1260              :         "an issue.");
    1261              :     sys::abort("");
    1262              :   }
    1263              :   const std::string message =
    1264              :       MakeString{} << "Component: " << pretty_type::name<parallel_component>()
    1265              :                    << "\nArray Index: " << array_index_ << "\n"
    1266              :                    << "Phase: " << phase_ << "\n"
    1267              :                    << "Algorithm Step: " << algorithm_step_ << "\n"
    1268              :                    << "Message: " << exception.what() << "\nType: "
    1269              :                    << pretty_type::get_runtime_type_name(exception);
    1270              :   main_proxy.value().add_exception_message(message);
    1271              :   set_terminate(true);
    1272              : }
    1273              : /// \endcond
    1274              : 
    1275              : template <typename ParallelComponent, typename PhaseDepActionLists>
    1276            0 : std::ostream& operator<<(
    1277              :     std::ostream& os,
    1278              :     const DistributedObject<ParallelComponent, PhaseDepActionLists>&
    1279              :         algorithm_impl) {
    1280              :   os << algorithm_impl.print_types() << "\n";
    1281              :   os << algorithm_impl.print_state() << "\n";
    1282              :   os << algorithm_impl.print_inbox() << "\n";
    1283              :   os << algorithm_impl.print_databox() << "\n";
    1284              :   return os;
    1285              : }
    1286              : }  // namespace Parallel
        

Generated by: LCOV version 2.0-1