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

Generated by: LCOV version 1.14