SpECTRE Documentation Coverage Report
Current view: top level - Parallel - DistributedObject.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 40 71 56.3 %
Date: 2025-12-05 05:03:31
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 "Parallel/AlgorithmExecution.hpp"
      27             : #include "Parallel/AlgorithmMetafunctions.hpp"
      28             : #include "Parallel/Algorithms/AlgorithmArrayDeclarations.hpp"
      29             : #include "Parallel/Algorithms/AlgorithmGroupDeclarations.hpp"
      30             : #include "Parallel/Algorithms/AlgorithmNodegroupDeclarations.hpp"
      31             : #include "Parallel/Algorithms/AlgorithmSingletonDeclarations.hpp"
      32             : #include "Parallel/ArrayCollection/IsDgElementCollection.hpp"
      33             : #include "Parallel/Callback.hpp"
      34             : #include "Parallel/CharmRegistration.hpp"
      35             : #include "Parallel/ElementRegistration.hpp"
      36             : #include "Parallel/GlobalCache.hpp"
      37             : #include "Parallel/Info.hpp"
      38             : #include "Parallel/Local.hpp"
      39             : #include "Parallel/NodeLock.hpp"
      40             : #include "Parallel/ParallelComponentHelpers.hpp"
      41             : #include "Parallel/Phase.hpp"
      42             : #include "Parallel/PhaseDependentActionList.hpp"
      43             : #include "Parallel/Printf/Printf.hpp"
      44             : #include "Parallel/Tags/ArrayIndex.hpp"
      45             : #include "Parallel/Tags/DistributedObjectTags.hpp"
      46             : #include "Parallel/TypeTraits.hpp"
      47             : #include "ParallelAlgorithms/Initialization/MutateAssign.hpp"
      48             : #include "Utilities/Algorithm.hpp"
      49             : #include "Utilities/ErrorHandling/Assert.hpp"
      50             : #include "Utilities/ErrorHandling/Error.hpp"
      51             : #include "Utilities/ForceInline.hpp"
      52             : #include "Utilities/Gsl.hpp"
      53             : #include "Utilities/MakeString.hpp"
      54             : #include "Utilities/NoSuchType.hpp"
      55             : #include "Utilities/Overloader.hpp"
      56             : #include "Utilities/PrettyType.hpp"
      57             : #include "Utilities/Requires.hpp"
      58             : #include "Utilities/Serialization/PupStlCpp11.hpp"
      59             : #include "Utilities/Serialization/PupStlCpp17.hpp"
      60             : #include "Utilities/System/ParallelInfo.hpp"
      61             : #include "Utilities/TMPL.hpp"
      62             : #include "Utilities/TaggedTuple.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 1.14