SpECTRE Documentation Coverage Report
Current view: top level - Parallel - Main.hpp Hit Total Coverage
Commit: 1f2210958b4f38fdc0400907ee7c6d5af5111418 Lines: 10 42 23.8 %
Date: 2025-12-05 05:03:31
Legend: Lines: hit not hit

          Line data    Source code
       1           1 : // Distributed under the MIT License.
       2             : // See LICENSE.txt for details.
       3             : 
       4             : /// \file
       5             : /// Defines the Charm++ mainchare.
       6             : 
       7             : #pragma once
       8             : 
       9             : #include <array>
      10             : #include <boost/program_options.hpp>
      11             : #include <charm++.h>
      12             : #include <cstddef>
      13             : #include <initializer_list>
      14             : #include <pup.h>
      15             : #include <string>
      16             : #include <tuple>
      17             : #include <type_traits>
      18             : 
      19             : #include "Informer/InfoFromBuild.hpp"
      20             : #include "Informer/Informer.hpp"
      21             : #include "Options/ParseOptions.hpp"
      22             : #include "Options/Tags.hpp"
      23             : #include "Parallel/AlgorithmMetafunctions.hpp"
      24             : #include "Parallel/CharmRegistration.hpp"
      25             : #include "Parallel/CreateFromOptions.hpp"
      26             : #include "Parallel/ExitCode.hpp"
      27             : #include "Parallel/GlobalCache.hpp"
      28             : #include "Parallel/Local.hpp"
      29             : #include "Parallel/ParallelComponentHelpers.hpp"
      30             : #include "Parallel/Phase.hpp"
      31             : #include "Parallel/PhaseControl/ExecutePhaseChange.hpp"
      32             : #include "Parallel/PhaseControl/InitializePhaseChangeDecisionData.hpp"
      33             : #include "Parallel/PhaseControl/PhaseControlTags.hpp"
      34             : #include "Parallel/PhaseControlReductionHelpers.hpp"
      35             : #include "Parallel/Printf/Printf.hpp"
      36             : #include "Parallel/Reduction.hpp"
      37             : #include "Parallel/ResourceInfo.hpp"
      38             : #include "Parallel/Tags/ResourceInfo.hpp"
      39             : #include "Parallel/TypeTraits.hpp"
      40             : #include "Utilities/Algorithm.hpp"
      41             : #include "Utilities/ErrorHandling/Error.hpp"
      42             : #include "Utilities/FileSystem.hpp"
      43             : #include "Utilities/Formaline.hpp"
      44             : #include "Utilities/Kokkos/KokkosCore.hpp"
      45             : #include "Utilities/MakeString.hpp"
      46             : #include "Utilities/Overloader.hpp"
      47             : #include "Utilities/StdHelpers.hpp"
      48             : #include "Utilities/System/Exit.hpp"
      49             : #include "Utilities/System/ParallelInfo.hpp"
      50             : #include "Utilities/TMPL.hpp"
      51             : #include "Utilities/TaggedTuple.hpp"
      52             : #include "Utilities/TypeTraits/CreateGetTypeAliasOrDefault.hpp"
      53             : #include "Utilities/TypeTraits/CreateIsCallable.hpp"
      54             : 
      55             : #include "Parallel/Main.decl.h"
      56             : 
      57             : namespace Parallel {
      58             : namespace detail {
      59             : CREATE_IS_CALLABLE(run_deadlock_analysis_simple_actions)
      60             : CREATE_IS_CALLABLE_V(run_deadlock_analysis_simple_actions)
      61             : 
      62             : // Return the dir name for the Charm++ checkpoints as well as the prefix for
      63             : // checkpoint names and their padding. This is a "detail" function so that
      64             : // these pieces can be defined in one place only.
      65             : std::tuple<std::string, std::string, size_t> checkpoints_dir_prefix_pad();
      66             : 
      67             : // Return the dir name for the next Charm++ checkpoint; check and error if
      68             : // this name already exists and writing the checkpoint would be unsafe.
      69             : std::string next_checkpoint_dir(size_t checkpoint_dir_counter);
      70             : 
      71             : // Check if future checkpoint dirs are available; error if any already exist.
      72             : void check_future_checkpoint_dirs_available(size_t checkpoint_dir_counter);
      73             : }  // namespace detail
      74             : 
      75             : /// \ingroup ParallelGroup
      76             : /// The main function of a Charm++ executable.
      77             : /// See [the Parallelization documentation](group__ParallelGroup.html#details)
      78             : /// for an overview of Metavariables, Phases, and parallel components.
      79             : template <typename Metavariables>
      80           1 : class Main : public CBase_Main<Metavariables> {
      81             :  public:
      82           0 :   using component_list = typename Metavariables::component_list;
      83           0 :   using const_global_cache_tags = get_const_global_cache_tags<Metavariables>;
      84           0 :   using mutable_global_cache_tags =
      85             :       get_mutable_global_cache_tags<Metavariables>;
      86             : 
      87           0 :   using phase_change_tags_and_combines_list =
      88             :       PhaseControl::get_phase_change_tags<Metavariables>;
      89             :   /// \cond HIDDEN_SYMBOLS
      90             :   /// The constructor used to register the class
      91             :   explicit Main(const Parallel::charmxx::
      92             :                     MainChareRegistrationConstructor& /*used_for_reg*/) {}
      93             :   ~Main() override {
      94             :     (void)Parallel::charmxx::RegisterChare<
      95             :         Main<Metavariables>, CkIndex_Main<Metavariables>>::registrar;
      96             :   }
      97             :   Main(const Main&) = default;
      98             :   Main& operator=(const Main&) = default;
      99             :   Main(Main&&) = default;
     100             :   Main& operator=(Main&&) = default;
     101             :   /// \endcond
     102             : 
     103           0 :   explicit Main(CkArgMsg* msg);
     104           0 :   explicit Main(CkMigrateMessage* msg);
     105             : 
     106             :   // NOLINTNEXTLINE(google-runtime-references)
     107           0 :   void pup(PUP::er& p) override;
     108             : 
     109             :   /// Allocate singleton components and the initial elements of array
     110             :   /// components, then execute the initialization phase on each component
     111           1 :   void allocate_remaining_components_and_execute_initialization_phase();
     112             : 
     113             :   /// Determine the next phase of the simulation and execute it.
     114           1 :   void execute_next_phase();
     115             : 
     116             :   /// Place the Charm++ call that starts load balancing
     117             :   ///
     118             :   /// \details This call is wrapped within an entry method so that it may be
     119             :   /// used as the callback after a quiescence detection.
     120           1 :   void start_load_balance();
     121             : 
     122             :   /// Place the Charm++ call that starts writing a checkpoint
     123             :   /// Reset the checkpoint counter to zero if the checkpoints directory does not
     124             :   /// exist (this happens when the simulation continues in a new segment).
     125             :   ///
     126             :   /// \details This call is wrapped within an entry method so that it may be
     127             :   /// used as the callback after a quiescence detection.
     128           1 :   void start_write_checkpoint();
     129             : 
     130             :   /// Reduction target for data used in phase change decisions.
     131             :   ///
     132             :   /// It is required that the `Parallel::ReductionData` holds a single
     133             :   /// `tuples::TaggedTuple`.
     134             :   template <typename InvokeCombine, typename... Tags>
     135           1 :   void phase_change_reduction(
     136             :       ReductionData<ReductionDatum<tuples::TaggedTuple<Tags...>, InvokeCombine,
     137             :                                    funcl::Identity, std::index_sequence<>>>
     138             :           reduction_data);
     139             : 
     140             :   /// Add an exception to the list of exceptions. Upon a phase change we print
     141             :   /// all the received exceptions and exit.
     142             :   ///
     143             :   /// Upon receiving an exception all algorithms are terminated to guarantee
     144             :   /// quiescence occurs soon after the exception is reported.
     145           1 :   void add_exception_message(std::string exception_message);
     146             : 
     147             :   /// A reduction target used to determine if all the elements of the array,
     148             :   /// group, nodegroup, or singleton parallel components terminated
     149             :   /// successfully.
     150             :   ///
     151             :   /// This allows detecting deadlocks in iterable actions, but not simple or
     152             :   /// reduction action.
     153           1 :   void did_all_elements_terminate(bool all_elements_terminated);
     154             : 
     155             :   /// Prints exit info and stops the executable with failure if a deadlock was
     156             :   /// detected.
     157           1 :   void post_deadlock_analysis_termination();
     158             : 
     159             :  private:
     160             :   // Starts a reduction on the component specified by
     161             :   // the current_termination_check_index_ member variable, then increment
     162             :   // current_termination_check_index_
     163           0 :   void check_if_component_terminated_correctly();
     164             : 
     165             :   // After a restart, update the const global cache with new values constructed
     166             :   // from parsing an overlay input file.
     167           0 :   void update_const_global_cache_from_input_file();
     168             : 
     169             :   // Lists of all parallel component types
     170           0 :   using group_component_list =
     171             :       tmpl::filter<component_list, tmpl::or_<Parallel::is_group<tmpl::_1>,
     172             :                                              Parallel::is_nodegroup<tmpl::_1>>>;
     173           0 :   using all_array_component_list =
     174             :       tmpl::filter<component_list, Parallel::is_array<tmpl::_1>>;
     175           0 :   using non_bound_array_component_list =
     176             :       tmpl::filter<component_list,
     177             :                    tmpl::and_<Parallel::is_array<tmpl::_1>,
     178             :                               tmpl::not_<Parallel::is_bound_array<tmpl::_1>>>>;
     179           0 :   using bound_array_component_list =
     180             :       tmpl::filter<component_list,
     181             :                    tmpl::and_<Parallel::is_array<tmpl::_1>,
     182             :                               Parallel::is_bound_array<tmpl::_1>>>;
     183           0 :   using singleton_component_list =
     184             :       tmpl::filter<component_list, Parallel::is_singleton<tmpl::_1>>;
     185             : 
     186             :   template <typename ParallelComponent>
     187           0 :   using parallel_component_options = Parallel::get_option_tags<
     188             :       typename ParallelComponent::simple_tags_from_options, Metavariables>;
     189             :   template <typename ArrayComponent>
     190           0 :   using array_component_allocation_options =
     191             :       Parallel::get_option_tags<typename ArrayComponent::array_allocation_tags,
     192             :                                 Metavariables>;
     193           0 :   using option_list = tmpl::remove_duplicates<tmpl::flatten<tmpl::list<
     194             :       Parallel::OptionTags::ResourceInfo<Metavariables>,
     195             :       Parallel::get_option_tags<const_global_cache_tags, Metavariables>,
     196             :       Parallel::get_option_tags<mutable_global_cache_tags, Metavariables>,
     197             :       tmpl::transform<component_list,
     198             :                       tmpl::bind<parallel_component_options, tmpl::_1>>,
     199             :       tmpl::transform<
     200             :           all_array_component_list,
     201             :           tmpl::bind<array_component_allocation_options, tmpl::_1>>>>>;
     202           0 :   using overlayable_option_list =
     203             :       Parallel::get_overlayable_option_list<Metavariables>;
     204             : 
     205           0 :   Parallel::Phase current_phase_{Parallel::Phase::Initialization};
     206           0 :   CProxy_GlobalCache<Metavariables> global_cache_proxy_;
     207           0 :   detail::CProxy_AtSyncIndicator<Metavariables> at_sync_indicator_proxy_;
     208           0 :   std::string input_file_{};
     209             :   Options::Parser<tmpl::remove<option_list, Options::Tags::InputSource>>
     210           0 :       parser_{Metavariables::help};
     211             :   // This is only used during startup, and will be cleared after all
     212             :   // the chares are created.  It is a member variable because passing
     213             :   // local state through charm callbacks is painful.
     214           0 :   tuples::tagged_tuple_from_typelist<option_list> options_{};
     215             :   // type to be determined by the collection of available phase changers in the
     216             :   // Metavariables
     217             :   tuples::tagged_tuple_from_typelist<phase_change_tags_and_combines_list>
     218           0 :       phase_change_decision_data_;
     219           0 :   size_t checkpoint_dir_counter_ = 0_st;
     220           0 :   Parallel::ResourceInfo<Metavariables> resource_info_{};
     221             :   // All exception errors we've received so far.
     222           0 :   std::vector<std::string> exception_messages_{};
     223             :   // Used to keep track of which parallel component we are checking has
     224             :   // successfully terminated.
     225           0 :   size_t current_termination_check_index_{0};
     226           0 :   std::vector<std::string> components_that_did_not_terminate_{};
     227           0 :   bool just_restored_from_checkpoint_ = false;
     228             : };
     229             : 
     230             : namespace detail {
     231             : 
     232             : // Charm++ AtSync effectively requires an additional global sync to the
     233             : // quiescence detection we do for switching phases. However, AtSync only needs
     234             : // to be called for one array to trigger the sync-based load balancing, so the
     235             : // AtSyncIndicator is a silly hack to have a centralized indication to start
     236             : // load-balancing. It participates in the `AtSync` barrier, but is not
     237             : // migratable, and should only be constructed by the `Main` chare on the same
     238             : // processor as the `Main` chare. When load-balancing occurs, main invokes the
     239             : // member function `IndicateAtSync()`, and when `ResumeFromSync()` is called
     240             : // from charm, `AtSyncIndicator` simply passes control back to the Main chare
     241             : // via `execute_next_phase()`.
     242             : template <typename Metavariables>
     243             : class AtSyncIndicator : public CBase_AtSyncIndicator<Metavariables> {
     244             :  public:
     245             :   AtSyncIndicator(CProxy_Main<Metavariables> main_proxy);
     246             :   AtSyncIndicator(const AtSyncIndicator&) = default;
     247             :   AtSyncIndicator& operator=(const AtSyncIndicator&) = default;
     248             :   AtSyncIndicator(AtSyncIndicator&&) = default;
     249             :   AtSyncIndicator& operator=(AtSyncIndicator&&) = default;
     250             :   ~AtSyncIndicator() override {
     251             :     (void)Parallel::charmxx::RegisterChare<
     252             :         AtSyncIndicator<Metavariables>,
     253             :         CkIndex_AtSyncIndicator<Metavariables>>::registrar;
     254             :   }
     255             : 
     256             :   void IndicateAtSync();
     257             : 
     258             :   void ResumeFromSync() override;
     259             : 
     260             :   explicit AtSyncIndicator(CkMigrateMessage* msg)
     261             :       : CBase_AtSyncIndicator<Metavariables>(msg) {}
     262             : 
     263             :   void pup(PUP::er& p) override { p | main_proxy_; }
     264             : 
     265             :  private:
     266             :   CProxy_Main<Metavariables> main_proxy_;
     267             : };
     268             : 
     269             : template <typename Metavariables>
     270             : AtSyncIndicator<Metavariables>::AtSyncIndicator(
     271             :     CProxy_Main<Metavariables> main_proxy)
     272             :     : main_proxy_{main_proxy} {
     273             :   this->usesAtSync = true;
     274             :   this->setMigratable(false);
     275             : }
     276             : 
     277             : template <typename Metavariables>
     278             : void AtSyncIndicator<Metavariables>::IndicateAtSync() {
     279             :   this->AtSync();
     280             : }
     281             : 
     282             : template <typename Metavariables>
     283             : void AtSyncIndicator<Metavariables>::ResumeFromSync() {
     284             :   main_proxy_.execute_next_phase();
     285             : }
     286             : }  // namespace detail
     287             : 
     288             : // ================================================================
     289             : 
     290             : template <typename Metavariables>
     291             : Main<Metavariables>::Main(CkArgMsg* msg) {
     292             : #ifdef SPECTRE_KOKKOS
     293             :   Kokkos::initialize(msg->argc, msg->argv);
     294             : #endif  // SPECTRE_KOKKOS
     295             : 
     296             :   Informer::print_startup_info(msg);
     297             : 
     298             :   /// \todo detail::register_events_to_trace();
     299             : 
     300             :   namespace bpo = boost::program_options;
     301             :   try {
     302             :     bpo::options_description command_line_options;
     303             :     // disable clang-format because it combines the repeated call operator
     304             :     // invocations making the code more difficult to parse.
     305             :     // clang-format off
     306             :     command_line_options.add_options()
     307             :         ("help,h", "Describe program options")
     308             :         ("check-options", "Check input file options")
     309             :         ("dump-source-tree-as", bpo::value<std::string>(),
     310             :          "If specified, then a gzip archive of the source tree is dumped "
     311             :          "with the specified name. The archive can be expanded using "
     312             :          "'tar -xzf ARCHIVE.tar.gz'")
     313             :         ("dump-paths",
     314             :          "Dump the PATH, CPATH, LD_LIBRARY_PATH, LIBRARY_PATH, and "
     315             :          "CMAKE_PREFIX_PATH at compile time.")
     316             :         ("dump-environment",
     317             :          "Dump the result of printenv at compile time.")
     318             :         ("dump-build-info",
     319             :          "Dump the contents of SpECTRE's BuildInfo.txt")
     320             :         ("dump-only",
     321             :          "Exit after dumping requested information.")
     322             :         ("copyright-and-licenses",
     323             :          "Returns all of the copyright and license info for SpECTRE and "
     324             :          "its dependencies.")
     325             :         ;
     326             :     // clang-format on
     327             : 
     328             :     // False if there are no other options besides the explicitly added
     329             :     // Parallel::OptionTags::ResourceInfo<Metavariables>,
     330             :     constexpr bool has_options = tmpl::size<option_list>::value > 1;
     331             :     // Add input-file option if it makes sense
     332             :     Overloader{
     333             :         [&command_line_options](std::true_type /*meta*/, auto mv,
     334             :                                 int /*gcc_bug*/)
     335             :             -> std::void_t<
     336             :                 decltype(tmpl::type_from<decltype(mv)>::input_file)> {
     337             :           // Metavariables has options and default input file name
     338             :           command_line_options.add_options()(
     339             :               "input-file",
     340             :               bpo::value<std::string>()->default_value(
     341             :                   tmpl::type_from<decltype(mv)>::input_file),
     342             :               "Input file name");
     343             :         },
     344             :         [&command_line_options](std::true_type /*meta*/, auto /*mv*/,
     345             :                                 auto... /*unused*/) {
     346             :           // Metavariables has options and no default input file name
     347             :           command_line_options.add_options()(
     348             :               "input-file", bpo::value<std::string>(), "Input file name");
     349             :         },
     350             :         [](std::false_type /*meta*/, auto mv, int /*gcc_bug*/)
     351             :             -> std::void_t<
     352             :                 decltype(tmpl::type_from<decltype(mv)>::input_file)> {
     353             :           // Metavariables has no options and default input file name
     354             : 
     355             :           // always false, but must depend on mv
     356             :           static_assert(std::is_same_v<decltype(mv), void>,
     357             :                         "Metavariables supplies input file name, "
     358             :                         "but there are no options");
     359             :           ERROR("This should have failed at compile time");
     360             :         },
     361             :         [](std::false_type /*meta*/, auto... /*unused*/) {
     362             :           // Metavariables has no options and no default input file name
     363             :         }}(std::bool_constant<has_options>{}, tmpl::type_<Metavariables>{}, 0);
     364             : 
     365             :     bpo::command_line_parser command_line_parser(msg->argc, msg->argv);
     366             :     command_line_parser.options(command_line_options);
     367             : 
     368             :     const bool ignore_unrecognized_command_line_options = Overloader{
     369             :         [](auto mv, int /*gcc_bug*/)
     370             :             -> decltype(tmpl::type_from<decltype(mv)>::
     371             :                             ignore_unrecognized_command_line_options) {
     372             :           return tmpl::type_from<
     373             :               decltype(mv)>::ignore_unrecognized_command_line_options;
     374             :         },
     375             :         [](auto /*mv*/, auto... /*meta*/) { return false; }}(
     376             :         tmpl::type_<Metavariables>{}, 0);
     377             :     if (ignore_unrecognized_command_line_options) {
     378             :       // Allow unknown --options
     379             :       command_line_parser.allow_unregistered();
     380             :     } else {
     381             :       // Forbid positional parameters
     382             :       command_line_parser.positional({});
     383             :     }
     384             : 
     385             :     bpo::variables_map parsed_command_line_options;
     386             :     bpo::store(command_line_parser.run(), parsed_command_line_options);
     387             :     bpo::notify(parsed_command_line_options);
     388             : 
     389             :     if (parsed_command_line_options.count("help") != 0) {
     390             :       Parallel::printf("%s\n%s", command_line_options, parser_.help());
     391             :       sys::exit();
     392             :     }
     393             : 
     394             :     if (parsed_command_line_options.count("dump-source-tree-as") != 0) {
     395             :       formaline::write_to_file(
     396             :           parsed_command_line_options["dump-source-tree-as"].as<std::string>());
     397             :       Parallel::printf("Dumping archive of source tree at link time.\n");
     398             :     }
     399             :     if (parsed_command_line_options.count("dump-paths") != 0) {
     400             :       Parallel::printf("Paths at link time were:\n%s\n",
     401             :                        formaline::get_paths());
     402             :     }
     403             :     if (parsed_command_line_options.count("dump-environment") != 0) {
     404             :       Parallel::printf("Environment variables at link time were:\n%s\n",
     405             :                        formaline::get_environment_variables());
     406             :     }
     407             :     if (parsed_command_line_options.count("dump-build-info") != 0) {
     408             :       Parallel::printf("BuildInfo.txt at link time was:\n%s\n",
     409             :                        formaline::get_build_info());
     410             :     }
     411             :     if (parsed_command_line_options.count("copyright-and-licenses") != 0) {
     412             :       Parallel::printf("%s\n", copyright_and_license_info());
     413             :     }
     414             :     if (parsed_command_line_options.count("dump-only") != 0) {
     415             :       sys::exit();
     416             :     }
     417             : 
     418             :     if (has_options) {
     419             :       if (parsed_command_line_options.count("input-file") == 0) {
     420             :         ERROR_NO_TRACE("No default input file name.  Pass --input-file.");
     421             :       }
     422             :       input_file_ = parsed_command_line_options["input-file"].as<std::string>();
     423             :       parser_.parse_file(input_file_);
     424             :     } else {
     425             :       if constexpr (tmpl::size<singleton_component_list>::value > 0) {
     426             :         parser_.parse(
     427             :             "ResourceInfo:\n"
     428             :             "  AvoidGlobalProc0: false\n"
     429             :             "  Singletons: Auto\n");
     430             :       } else {
     431             :         parser_.parse(
     432             :             "ResourceInfo:\n"
     433             :             "  AvoidGlobalProc0: false\n");
     434             :       }
     435             :     }
     436             : 
     437             :     if (parsed_command_line_options.count("check-options") != 0) {
     438             :       // Force all the options to be created.
     439             :       parser_.template apply<option_list, Metavariables>([](auto... args) {
     440             :         (void)std::initializer_list<char>{((void)args, '0')...};
     441             :       });
     442             :       if (has_options) {
     443             :         Parallel::printf("\n%s parsed successfully!\n", input_file_);
     444             :       } else {
     445             :         // This is still considered successful, since it means the
     446             :         // program would have started.
     447             :         Parallel::printf("\nNo options to check!\n");
     448             :       }
     449             : 
     450             :       // Include a check that the checkpoint dirs are available for writing as
     451             :       // part of checking the option parsing. Doing these checks together helps
     452             :       // catch more user errors before running the executable 'for real'.
     453             :       //
     454             :       // Note we don't do this check at the beginning of the Main chare
     455             :       // constructor because we don't _always_ want to error if checkpoint dirs
     456             :       // already exist. For example, running the executable with flags like
     457             :       // `--help` or `--dump-source-tree-as` should succeed even if checkpoints
     458             :       // were previously written.
     459             :       detail::check_future_checkpoint_dirs_available(checkpoint_dir_counter_);
     460             : 
     461             :       sys::exit();
     462             :     }
     463             : 
     464             :     options_ =
     465             :         parser_.template apply<option_list, Metavariables>([](auto... args) {
     466             :           return tuples::tagged_tuple_from_typelist<option_list>(
     467             :               std::move(args)...);
     468             :         });
     469             : 
     470             :     resource_info_ =
     471             :         tuples::get<Parallel::OptionTags::ResourceInfo<Metavariables>>(
     472             :             options_);
     473             : 
     474             :     Parallel::printf("\nOption parsing completed.\n");
     475             :   } catch (const bpo::error& e) {
     476             :     ERROR(e.what());
     477             :   }
     478             : 
     479             :   detail::check_future_checkpoint_dirs_available(checkpoint_dir_counter_);
     480             : 
     481             :   global_cache_proxy_ = CProxy_GlobalCache<Metavariables>::ckNew(
     482             :       Parallel::create_from_options<Metavariables>(options_,
     483             :                                                    const_global_cache_tags{}),
     484             :       Parallel::create_from_options<Metavariables>(options_,
     485             :                                                    mutable_global_cache_tags{}),
     486             :       this->thisProxy);
     487             : 
     488             :   // Now that the GlobalCache has been built, create the singleton map which
     489             :   // will be used to allocate all the singletons. We need to be careful here
     490             :   // because the parallel components have not been set at this point, so if we
     491             :   // try to Parallel::get_parallel_component here, an error will occur. This
     492             :   // call is OK though because build_singleton_map() only uses the parallel info
     493             :   // functions from the GlobalCache (like cache.number_of_procs()).
     494             :   resource_info_.build_singleton_map(
     495             :       *Parallel::local_branch(global_cache_proxy_));
     496             : 
     497             :   // Now that the singleton map has been built, set the resource info in the
     498             :   // GlobalCache (if the tags exist). Since this info will be constant
     499             :   // throughout a simulation, we opt for directly editing a const tag in the
     500             :   // GlobalCache before we pass it to any other parallel component rather than
     501             :   // having a mutable tag and using a mutate call to set it.
     502             :   global_cache_proxy_.set_resource_info(resource_info_);
     503             : 
     504             :   // Now that the singleton map has been built, we have to replace the
     505             :   // ResourceInfo that was created from options with the one that has all the
     506             :   // correct singleton assignments so simple tags can be created from options
     507             :   // with a valid ResourceInfo.
     508             :   get<Parallel::OptionTags::ResourceInfo<Metavariables>>(options_) =
     509             :       resource_info_;
     510             : 
     511             :   at_sync_indicator_proxy_ =
     512             :       detail::CProxy_AtSyncIndicator<Metavariables>::ckNew();
     513             :   at_sync_indicator_proxy_[0].insert(this->thisProxy, sys::my_proc());
     514             :   at_sync_indicator_proxy_.doneInserting();
     515             : 
     516             :   using parallel_component_tag_list = tmpl::transform<
     517             :       component_list,
     518             :       tmpl::bind<
     519             :           tmpl::type_,
     520             :           tmpl::bind<Parallel::proxy_from_parallel_component, tmpl::_1>>>;
     521             :   tuples::tagged_tuple_from_typelist<parallel_component_tag_list>
     522             :       the_parallel_components;
     523             : 
     524             :   // Print info on DataBox variants
     525             : #ifdef SPECTRE_DEBUG
     526             :   Parallel::printf("\nParallel components:\n");
     527             :   tmpl::for_each<component_list>([](auto parallel_component_v) {
     528             :     using parallel_component = tmpl::type_from<decltype(parallel_component_v)>;
     529             :     using chare_type = typename parallel_component::chare_type;
     530             :     using charm_type = Parallel::charm_types_with_parameters<
     531             :         parallel_component, typename Parallel::get_array_index<
     532             :                                 chare_type>::template f<parallel_component>>;
     533             :     Parallel::printf(
     534             :         "  %s (%s) has a DataBox with %u items.\n",
     535             :         pretty_type::name<parallel_component>(),
     536             :         pretty_type::name<chare_type>(),
     537             :         tmpl::size<
     538             :             typename charm_type::algorithm::databox_type::tags_list>::value);
     539             :   });
     540             :   Parallel::printf("\n");
     541             : #endif  // SPECTRE_DEBUG
     542             : 
     543             :   // Construct the group proxies with a dependency on the GlobalCache proxy
     544             :   CkEntryOptions global_cache_dependency;
     545             :   global_cache_dependency.setGroupDepID(global_cache_proxy_.ckGetGroupID());
     546             : 
     547             :   tmpl::for_each<group_component_list>([this, &the_parallel_components,
     548             :                                         &global_cache_dependency](
     549             :                                            auto parallel_component_v) {
     550             :     using parallel_component = tmpl::type_from<decltype(parallel_component_v)>;
     551             :     using ParallelComponentProxy =
     552             :         Parallel::proxy_from_parallel_component<parallel_component>;
     553             :     tuples::get<tmpl::type_<ParallelComponentProxy>>(the_parallel_components) =
     554             :         ParallelComponentProxy::ckNew(
     555             :             global_cache_proxy_,
     556             :             Parallel::create_from_options<Metavariables>(
     557             :                 options_,
     558             :                 typename parallel_component::simple_tags_from_options{}),
     559             :             &global_cache_dependency);
     560             :   });
     561             : 
     562             :   // Create proxies for empty array chares (whose elements will be created by
     563             :   // the allocate functions of the array components during
     564             :   // execute_initialization_phase)
     565             :   tmpl::for_each<non_bound_array_component_list>([&the_parallel_components](
     566             :                                                      auto parallel_component) {
     567             :     using ParallelComponentProxy = Parallel::proxy_from_parallel_component<
     568             :         tmpl::type_from<decltype(parallel_component)>>;
     569             :     tuples::get<tmpl::type_<ParallelComponentProxy>>(the_parallel_components) =
     570             :         ParallelComponentProxy::ckNew();
     571             :   });
     572             : 
     573             :   // Create proxies for empty bound array chares
     574             :   tmpl::for_each<bound_array_component_list>([&the_parallel_components](
     575             :                                                  auto parallel_component) {
     576             :     using ParallelComponentProxy = Parallel::proxy_from_parallel_component<
     577             :         tmpl::type_from<decltype(parallel_component)>>;
     578             :     CkArrayOptions opts;
     579             :     opts.bindTo(
     580             :         tuples::get<tmpl::type_<Parallel::proxy_from_parallel_component<
     581             :             typename tmpl::type_from<decltype(parallel_component)>::bind_to>>>(
     582             :             the_parallel_components));
     583             :     tuples::get<tmpl::type_<ParallelComponentProxy>>(the_parallel_components) =
     584             :         ParallelComponentProxy::ckNew(opts);
     585             :   });
     586             : 
     587             :   // Create proxies for singletons (which are single-element charm++ arrays)
     588             :   tmpl::for_each<singleton_component_list>([&the_parallel_components](
     589             :                                                auto parallel_component) {
     590             :     using ParallelComponentProxy = Parallel::proxy_from_parallel_component<
     591             :         tmpl::type_from<decltype(parallel_component)>>;
     592             :     tuples::get<tmpl::type_<ParallelComponentProxy>>(the_parallel_components) =
     593             :         ParallelComponentProxy::ckNew();
     594             :   });
     595             : 
     596             :   // Send the complete list of parallel_components to the GlobalCache on
     597             :   // each Charm++ node.  After all nodes have finished, the callback is
     598             :   // executed.
     599             :   CkCallback callback(
     600             :       CkIndex_Main<Metavariables>::
     601             :           allocate_remaining_components_and_execute_initialization_phase(),
     602             :       this->thisProxy);
     603             :   global_cache_proxy_.set_parallel_components(the_parallel_components,
     604             :                                               callback);
     605             : 
     606             :   get<Tags::ExitCode>(phase_change_decision_data_) =
     607             :       Parallel::ExitCode::Complete;
     608             :   PhaseControl::initialize_phase_change_decision_data(
     609             :       make_not_null(&phase_change_decision_data_),
     610             :       *Parallel::local_branch(global_cache_proxy_));
     611             : 
     612             :   printer_chare = CProxy_PrinterChare::ckNew(1);
     613             :   printer_chare_is_set = true;
     614             : }
     615             : 
     616             : template <typename Metavariables>
     617             : Main<Metavariables>::Main(CkMigrateMessage* msg)
     618             :     : CBase_Main<Metavariables>(msg) {}
     619             : 
     620             : template <typename Metavariables>
     621             : void Main<Metavariables>::pup(PUP::er& p) {  // NOLINT
     622             :   p | current_phase_;
     623             :   p | global_cache_proxy_;
     624             :   p | at_sync_indicator_proxy_;
     625             :   p | input_file_;
     626             :   p | parser_;
     627             :   // Note: we don't serialize options_, because it's used as a temporary in
     628             :   // startup only (see comment in class definition).
     629             :   p | phase_change_decision_data_;
     630             : 
     631             :   p | checkpoint_dir_counter_;
     632             :   p | resource_info_;
     633             :   p | exception_messages_;
     634             :   p | current_termination_check_index_;
     635             :   p | components_that_did_not_terminate_;
     636             :   if (p.isUnpacking()) {
     637             :     detail::check_future_checkpoint_dirs_available(checkpoint_dir_counter_);
     638             :     // Main doesn't migrate unless checkpointing or restarting, so we can
     639             :     // indicate here that we've just restored from checkpoint.
     640             :     just_restored_from_checkpoint_ = true;
     641             :     // Initialize Kokkos on restart
     642             : #ifdef SPECTRE_KOKKOS
     643             :   Kokkos::initialize();
     644             : #endif  // SPECTRE_KOKKOS
     645             :   }
     646             : 
     647             :   // For now we only support restarts on the same hardware configuration (same
     648             :   // number of nodes and same procs per node) used when writing the checkpoint.
     649             :   // We check this by adding counters to the pup stream.
     650             :   if (p.isUnpacking()) {
     651             :     int previous_nodes = 0;
     652             :     int previous_procs = 0;
     653             :     p | previous_nodes;
     654             :     p | previous_procs;
     655             :     if (previous_nodes != sys::number_of_nodes() or
     656             :         previous_procs != sys::number_of_procs()) {
     657             :       ERROR(
     658             :           "Must restart on the same hardware configuration used when writing "
     659             :           "the checkpoint.\n"
     660             :           "Checkpoint written with "
     661             :           << previous_nodes << " nodes, " << previous_procs
     662             :           << " procs.\n"
     663             :              "Restarted with "
     664             :           << sys::number_of_nodes() << " nodes, " << sys::number_of_procs()
     665             :           << " procs.");
     666             :     }
     667             :   } else {
     668             :     int current_nodes = sys::number_of_nodes();
     669             :     int current_procs = sys::number_of_procs();
     670             :     p | current_nodes;
     671             :     p | current_procs;
     672             :   }
     673             : }
     674             : 
     675             : template <typename Metavariables>
     676             : void Main<Metavariables>::
     677             :     allocate_remaining_components_and_execute_initialization_phase() {
     678             :   if (current_phase_ != Parallel::Phase::Initialization) {
     679             :     ERROR("Must be in the Initialization phase.");
     680             :   }
     681             :   // Since singletons are actually single-element Charm++ arrays, we have to
     682             :   // allocate them here along with the other Charm++ arrays.
     683             :   tmpl::for_each<singleton_component_list>([this](auto singleton_component_v) {
     684             :     using singleton_component =
     685             :         tmpl::type_from<decltype(singleton_component_v)>;
     686             :     auto& local_cache = *Parallel::local_branch(global_cache_proxy_);
     687             :     auto& singleton_proxy =
     688             :         Parallel::get_parallel_component<singleton_component>(local_cache);
     689             :     auto options = Parallel::create_from_options<Metavariables>(
     690             :         options_, typename singleton_component::simple_tags_from_options{});
     691             : 
     692             :     const size_t proc = resource_info_.template proc_for<singleton_component>();
     693             :     singleton_proxy[0].insert(global_cache_proxy_, std::move(options), proc);
     694             :     singleton_proxy.doneInserting();
     695             :   });
     696             : 
     697             :   // These are Spectre array components built on Charm++ array chares. Each
     698             :   // component is in charge of allocating and distributing its elements over the
     699             :   // computing system.
     700             :   tmpl::for_each<all_array_component_list>([this](auto parallel_component_v) {
     701             :     using parallel_component = tmpl::type_from<decltype(parallel_component_v)>;
     702             :     parallel_component::allocate_array(
     703             :         global_cache_proxy_,
     704             :         Parallel::create_from_options<Metavariables>(
     705             :             options_, typename parallel_component::simple_tags_from_options{}),
     706             :         Parallel::create_from_options<Metavariables>(
     707             :             options_, typename parallel_component::array_allocation_tags{}),
     708             :         resource_info_.procs_to_ignore());
     709             :   });
     710             : 
     711             :   // Free any resources from the initial option parsing.
     712             :   options_ = decltype(options_){};
     713             : 
     714             :   tmpl::for_each<component_list>([this](auto parallel_component_v) {
     715             :     using parallel_component = tmpl::type_from<decltype(parallel_component_v)>;
     716             :     Parallel::get_parallel_component<parallel_component>(
     717             :         *Parallel::local_branch(global_cache_proxy_))
     718             :         .start_phase(current_phase_);
     719             :   });
     720             :   CkStartQD(CkCallback(CkIndex_Main<Metavariables>::execute_next_phase(),
     721             :                        this->thisProxy));
     722             : }
     723             : 
     724             : template <typename Metavariables>
     725             : void Main<Metavariables>::execute_next_phase() {
     726             :   if (not exception_messages_.empty()) {
     727             :     // Print exceptions whether we errored during execution or cleanup
     728             :     Parallel::printf(
     729             :         "\n\n###############################\n"
     730             :         "The following exceptions were reported during the phase: %s\n",
     731             :         current_phase_);
     732             :     for (const std::string& exception_message : exception_messages_) {
     733             :       Parallel::printf("%s\n\n", exception_message);
     734             :     }
     735             :     exception_messages_.clear();
     736             :     Parallel::printf(
     737             :         "To determine where an exception is thrown, run gdb and do\n"
     738             :         "catch throw EXCEPTION_TYPE\n"
     739             :         "run\n"
     740             :         "where EXCEPTION_TYPE is the Type of the exception above.\n"
     741             :         "You may have to type `continue` to skip some option parser\n"
     742             :         "exceptions until you get to the one you care about\n"
     743             :         "You may also have to type `up` or `down` to go up and down\n"
     744             :         "the function calls in order to find a useful line number.\n\n");
     745             : 
     746             :     // Errored during cleanup. Can't have this, so just abort
     747             :     if (current_phase_ == Parallel::Phase::PostFailureCleanup) {
     748             :       Parallel::printf(
     749             :           "Received termination while cleaning up a previous termination. This "
     750             :           "is cyclic behavior and cannot be supported. Cleanup must exit "
     751             :           "cleanly without errors.");
     752             :       sys::abort("");
     753             :     }
     754             : 
     755             :     // Errored during execution. Go to cleanup
     756             :     current_phase_ = Parallel::Phase::PostFailureCleanup;
     757             :     Parallel::printf("Entering phase: %s at time %s\n", current_phase_,
     758             :                      sys::pretty_wall_time());
     759             :   } else {
     760             :     if (Parallel::Phase::Exit == current_phase_) {
     761             :       ERROR("Current phase is Exit, but program did not exit!");
     762             :     }
     763             : 
     764             :     if (current_phase_ == Parallel::Phase::PostFailureCleanup) {
     765             :       Parallel::printf("PostFailureCleanup phase complete. Aborting.\n");
     766             :       Informer::print_exit_info();
     767             :       sys::abort("");
     768             :     }
     769             : 
     770             :     const auto next_phase = PhaseControl::arbitrate_phase_change(
     771             :         make_not_null(&phase_change_decision_data_), current_phase_,
     772             :         *Parallel::local_branch(global_cache_proxy_));
     773             :     if (next_phase.has_value()) {
     774             :       // Only print info if there was an actual phase change.
     775             :       if (current_phase_ != next_phase.value()) {
     776             :         Parallel::printf("Entering phase from phase control: %s at time %s\n",
     777             :                          next_phase.value(), sys::pretty_wall_time());
     778             :         current_phase_ = next_phase.value();
     779             :       }
     780             :     } else {
     781             :       if (current_phase_ ==
     782             :           Parallel::Phase::UpdateOptionsAtRestartFromCheckpoint) {
     783             :         ERROR(
     784             :             "Failed to find the next phase after overlaying options during a "
     785             :             "restart. This is caused by removing the phase change that caused "
     786             :             "the restart.");
     787             :       }
     788             :       const auto& default_order = Metavariables::default_phase_order;
     789             :       auto it = alg::find(default_order, current_phase_);
     790             :       using ::operator<<;
     791             :       if (it == std::end(default_order)) {
     792             :         ERROR("Cannot determine next phase as '"
     793             :               << current_phase_
     794             :               << "' is not in Metavariables::default_phase_order "
     795             :               << default_order << "\n");
     796             :       }
     797             :       if (std::next(it) == std::end(default_order)) {
     798             :         ERROR("Cannot determine next phase as '"
     799             :               << current_phase_
     800             :               << "' is last in Metavariables::default_phase_order "
     801             :               << default_order << "\n");
     802             :       }
     803             :       current_phase_ = *std::next(it);
     804             : 
     805             :       Parallel::printf("Entering phase: %s at time %s\n", current_phase_,
     806             :                        sys::pretty_wall_time());
     807             :     }
     808             :   }
     809             : 
     810             :   if (Parallel::Phase::Exit == current_phase_) {
     811             :     check_if_component_terminated_correctly();
     812             : #ifdef SPECTRE_KOKKOS
     813             :     Kokkos::finalize();
     814             : #endif  // SPECTRE_KOKKOS
     815             :     return;
     816             :   }
     817             :   tmpl::for_each<component_list>([this](auto parallel_component) {
     818             :     tmpl::type_from<decltype(parallel_component)>::execute_next_phase(
     819             :         current_phase_, global_cache_proxy_);
     820             :   });
     821             : 
     822             :   // Here we handle phases with direct Charm++ calls. By handling these phases
     823             :   // after calling each component's execute_next_phase entry method, we ensure
     824             :   // that each component knows what phase it is in. This is useful for pup
     825             :   // functions that need special handling that depends on the phase.
     826             :   //
     827             :   // Note that in future versions of Charm++ it may become possible for pup
     828             :   // functions to have knowledge of the migration type. At that point, it
     829             :   // should no longer be necessary to wait until after
     830             :   // component::execute_next_phase to make the direct charm calls. Instead, the
     831             :   // load balance or checkpoint work could be initiated *before* the call to
     832             :   // component::execute_next_phase and *without* the need for a quiescence
     833             :   // detection. This may be a slight optimization.
     834             :   if (current_phase_ == Parallel::Phase::LoadBalancing) {
     835             :     CkStartQD(CkCallback(CkIndex_Main<Metavariables>::start_load_balance(),
     836             :                          this->thisProxy));
     837             :     return;
     838             :   }
     839             :   if (current_phase_ == Parallel::Phase::WriteCheckpoint) {
     840             :     CkStartQD(CkCallback(CkIndex_Main<Metavariables>::start_write_checkpoint(),
     841             :                          this->thisProxy));
     842             :     return;
     843             :   }
     844             : 
     845             :   // We skip the reparsing and overlaying if there are no eligible tags
     846             :   if constexpr (tmpl::size<overlayable_option_list>::value > 0) {
     847             :     // Make sure we only update the options the first time we run the phase
     848             :     // UpdateOptionsAtRestartFromCheckpoint after restoring from checkpoint.
     849             :     // Else, the code might try to update options each time the phase was
     850             :     // encountered, even if there was no checkpoint/restart...
     851             :     if (just_restored_from_checkpoint_ and
     852             :         current_phase_ ==
     853             :             Parallel::Phase::UpdateOptionsAtRestartFromCheckpoint) {
     854             :       update_const_global_cache_from_input_file();
     855             :       just_restored_from_checkpoint_ = false;
     856             :     }
     857             :   }
     858             : 
     859             :   // The general case simply returns to execute_next_phase
     860             :   CkStartQD(CkCallback(CkIndex_Main<Metavariables>::execute_next_phase(),
     861             :                        this->thisProxy));
     862             : }
     863             : 
     864             : template <typename Metavariables>
     865             : void Main<Metavariables>::start_load_balance() {
     866             :   at_sync_indicator_proxy_.IndicateAtSync();
     867             :   // No need for a callback to return to execute_next_phase: this is done by
     868             :   // ResumeFromSync instead.
     869             : }
     870             : 
     871             : template <typename Metavariables>
     872             : void Main<Metavariables>::start_write_checkpoint() {
     873             :   // Reset the counter if the checkpoints directory does not exist.
     874             :   // This happens when the simulation continues in a new segment.
     875             :   const auto [checkpoints_dir, prefix, pad] =
     876             :       detail::checkpoints_dir_prefix_pad();
     877             :   if (not file_system::check_if_dir_exists(checkpoints_dir)) {
     878             :     checkpoint_dir_counter_ = 0;
     879             :   }
     880             :   const std::string dir = detail::next_checkpoint_dir(checkpoint_dir_counter_);
     881             :   checkpoint_dir_counter_++;
     882             :   file_system::create_directory(dir);
     883             :   CkStartCheckpoint(
     884             :       dir.c_str(), CkCallback(CkIndex_Main<Metavariables>::execute_next_phase(),
     885             :                               this->thisProxy));
     886             : }
     887             : 
     888             : template <typename Metavariables>
     889             : template <typename InvokeCombine, typename... Tags>
     890             : void Main<Metavariables>::phase_change_reduction(
     891             :     ReductionData<ReductionDatum<tuples::TaggedTuple<Tags...>, InvokeCombine,
     892             :                                  funcl::Identity, std::index_sequence<>>>
     893             :         reduction_data) {
     894             :   using tagged_tuple_type = std::decay_t<
     895             :       std::tuple_element_t<0, std::decay_t<decltype(reduction_data.data())>>>;
     896             :   (void)Parallel::charmxx::RegisterPhaseChangeReduction<
     897             :       Metavariables, InvokeCombine, Tags...>::registrar;
     898             :   static_assert(tt::is_a_v<tuples::TaggedTuple, tagged_tuple_type>,
     899             :                 "The main chare expects a tagged tuple in the phase change "
     900             :                 "reduction target.");
     901             :   reduction_data.finalize();
     902             :   PhaseControl::TaggedTupleMainCombine::apply(
     903             :       make_not_null(&phase_change_decision_data_),
     904             :       get<0>(reduction_data.data()));
     905             : }
     906             : 
     907             : template <typename Metavariables>
     908             : void Main<Metavariables>::add_exception_message(std::string exception_message) {
     909             :   exception_messages_.push_back(std::move(exception_message));
     910             :   auto* global_cache = Parallel::local_branch(global_cache_proxy_);
     911             :   ASSERT(global_cache != nullptr, "Could not retrieve the local global cache.");
     912             :   // Set terminate_=true on all components to cause them to stop the current
     913             :   // phase.
     914             :   tmpl::for_each<component_list>([global_cache](auto component_tag_v) {
     915             :     using component_tag = tmpl::type_from<decltype(component_tag_v)>;
     916             :     Parallel::get_parallel_component<component_tag>(*global_cache)
     917             :         .set_terminate(true);
     918             :   });
     919             : }
     920             : 
     921             : template <typename Metavariables>
     922             : void Main<Metavariables>::did_all_elements_terminate(
     923             :     const bool all_elements_terminated) {
     924             :   if (not all_elements_terminated) {
     925             :     tmpl::for_each<component_list>([this](auto component_tag_v) {
     926             :       using component_tag = tmpl::type_from<decltype(component_tag_v)>;
     927             :       if (tmpl::index_of<component_list, component_tag>::value ==
     928             :           current_termination_check_index_ - 1) {
     929             :         components_that_did_not_terminate_.push_back(
     930             :             pretty_type::name<component_tag>());
     931             :       }
     932             :     });
     933             :   }
     934             :   if (current_termination_check_index_ == tmpl::size<component_list>::value) {
     935             :     if (not components_that_did_not_terminate_.empty()) {
     936             :       using ::operator<<;
     937             :       // Need the MakeString to avoid GCC compilation failure that it can't
     938             :       // print out the vector...
     939             :       Parallel::printf(
     940             :           "\n############ ERROR ############\n"
     941             :           "The following components did not terminate cleanly:\n"
     942             :           "%s\n\n"
     943             :           "This means the executable stopped because of a hang/deadlock.\n"
     944             :           "############ ERROR ############\n\n",
     945             :           std::string{MakeString{} << components_that_did_not_terminate_});
     946             :       if constexpr (detail::is_run_deadlock_analysis_simple_actions_callable_v<
     947             :                         Metavariables, Parallel::GlobalCache<Metavariables>&,
     948             :                         const std::vector<std::string>&>) {
     949             :         Parallel::printf("Starting deadlock analysis.\n");
     950             :         Metavariables::run_deadlock_analysis_simple_actions(
     951             :             *Parallel::local_branch(global_cache_proxy_),
     952             :             components_that_did_not_terminate_);
     953             :         CkStartQD(CkCallback(
     954             :             CkIndex_Main<Metavariables>::post_deadlock_analysis_termination(),
     955             :             this->thisProxy));
     956             :         return;
     957             :       } else {
     958             :         Parallel::printf(
     959             :             "No deadlock analysis function found in metavariables. To enable "
     960             :             "deadlock analysis via simple actions add a function:\n"
     961             :             "  static void run_deadlock_analysis_simple_actions(\n"
     962             :             "        Parallel::GlobalCache<metavariables>& cache,\n"
     963             :             "        const std::vector<std::string>& deadlocked_components);\n"
     964             :             "to your metavariables.\n");
     965             :       }
     966             :     }
     967             :     post_deadlock_analysis_termination();
     968             :   }
     969             : 
     970             :   check_if_component_terminated_correctly();
     971             : }
     972             : 
     973             : template <typename Metavariables>
     974             : void Main<Metavariables>::check_if_component_terminated_correctly() {
     975             :   if constexpr (tmpl::size<component_list>::value == 0) {
     976             :     post_deadlock_analysis_termination();
     977             :   }
     978             :   auto* global_cache = Parallel::local_branch(global_cache_proxy_);
     979             :   ASSERT(global_cache != nullptr, "Could not retrieve the local global cache.");
     980             : 
     981             :   tmpl::for_each<component_list>([global_cache, this](auto component_tag_v) {
     982             :     using component_tag = tmpl::type_from<decltype(component_tag_v)>;
     983             :     if (tmpl::index_of<component_list, component_tag>::value ==
     984             :         current_termination_check_index_) {
     985             :       Parallel::get_parallel_component<component_tag>(*global_cache)
     986             :           .contribute_termination_status_to_main();
     987             :     }
     988             :   });
     989             :   current_termination_check_index_++;
     990             : }
     991             : 
     992             : namespace detail {
     993             : CREATE_IS_CALLABLE(execute_next_phase)
     994             : CREATE_IS_CALLABLE_V(execute_next_phase)
     995             : }  // namespace detail
     996             : 
     997             : template <typename Metavariables>
     998             : void Main<Metavariables>::post_deadlock_analysis_termination() {
     999             :   // If no components deadlocked, then we just exit.
    1000             :   if (components_that_did_not_terminate_.empty()) {
    1001             :     Informer::print_exit_info();
    1002             :     const Parallel::ExitCode exit_code =
    1003             :         get<Tags::ExitCode>(phase_change_decision_data_);
    1004             :     sys::exit(static_cast<int>(exit_code));
    1005             :   } else {
    1006             :     // Even though we didn't raise an exception, a deadlock is still a
    1007             :     // failure and some components may want to run things during cleanup so
    1008             :     // we switch phases here and start the PostFailureCleanup phase on each
    1009             :     // component.
    1010             :     current_phase_ = Parallel::Phase::PostFailureCleanup;
    1011             :     Parallel::printf("Entering phase: %s at time %s\n", current_phase_,
    1012             :                      sys::pretty_wall_time());
    1013             :     tmpl::for_each<component_list>([this](auto component_tag_v) {
    1014             :       using component_tag = tmpl::type_from<decltype(component_tag_v)>;
    1015             :       // If a component deadlocked, then the terminate_ flag in the
    1016             :       // DistributedObject isn't correct and we can't start a new phase.
    1017             :       // Therefore, we have to manually set it so that it thinks it terminated
    1018             :       // cleanly.
    1019             :       if (alg::found(components_that_did_not_terminate_,
    1020             :                      pretty_type::name<component_tag>())) {
    1021             :         Parallel::printf("Setting terminate to true on %s\n",
    1022             :                          pretty_type::name<component_tag>());
    1023             :       }
    1024             :       if constexpr (detail::is_execute_next_phase_callable_v<
    1025             :                         component_tag, Parallel::Phase,
    1026             :                         CProxy_GlobalCache<Metavariables>, bool>) {
    1027             :         component_tag::execute_next_phase(current_phase_, global_cache_proxy_,
    1028             :                                           true);
    1029             :       } else {
    1030             :         component_tag::execute_next_phase(current_phase_, global_cache_proxy_);
    1031             :       }
    1032             :     });
    1033             :     // As a callback, we set execute_next_phase() because it will just
    1034             :     // immediately exit after the PostFailureCleanup phase is complete which is
    1035             :     // what we want
    1036             :     CkStartQD(CkCallback(CkIndex_Main<Metavariables>::execute_next_phase(),
    1037             :                          this->thisProxy));
    1038             :   }
    1039             : }
    1040             : 
    1041             : template <typename Metavariables>
    1042             : void Main<Metavariables>::update_const_global_cache_from_input_file() {
    1043             :   if (checkpoint_dir_counter_ < 1) {
    1044             :     ERROR("Executable is unaware of previous checkpoints, so can't reparse.");
    1045             :   }
    1046             : 
    1047             :   // Get the padded counter (e.g., 000XYZ) of the checkpoint we restarted from
    1048             :   const size_t restart_checkpoint = checkpoint_dir_counter_ - 1;
    1049             :   const std::string counter = std::to_string(restart_checkpoint);
    1050             :   const auto [checkpoints_dir, prefix, pad] =
    1051             :       detail::checkpoints_dir_prefix_pad();
    1052             :   const std::string padded_counter =
    1053             :       std::string(pad - counter.size(), '0').append(counter);
    1054             :   (void)checkpoints_dir;
    1055             :   (void)prefix;
    1056             : 
    1057             :   // Given input file "Input.yaml", overlay file is "Input.overlay_WXYZ.yaml"
    1058             :   const std::string dot_yaml = ".yaml";
    1059             :   const std::string overlay_counter = ".overlay_" + padded_counter;
    1060             :   const auto found = input_file_.find(dot_yaml);
    1061             :   std::string input_file_for_reparse = input_file_;
    1062             :   if (found != std::string::npos) {
    1063             :     input_file_for_reparse.insert(found, overlay_counter);
    1064             :   } else {
    1065             :     ERROR("Overlaying assumes the input file has a .yaml extension");
    1066             :   }
    1067             : 
    1068             :   Parallel::printf("Attempting to overlay input file: %s\n",
    1069             :                    input_file_for_reparse);
    1070             :   if (file_system::check_if_file_exists(input_file_for_reparse)) {
    1071             :     parser_.template overlay_file<overlayable_option_list>(
    1072             :         input_file_for_reparse);
    1073             :     const auto data_to_overlay =
    1074             :         parser_.template apply<overlayable_option_list,
    1075             :                                Metavariables>([](auto... args) {
    1076             :           return tuples::tagged_tuple_from_typelist<overlayable_option_list>(
    1077             :               std::move(args)...);
    1078             :         });
    1079             :     global_cache_proxy_.overlay_cache_data(data_to_overlay);
    1080             :     Parallel::printf("... success!\n");
    1081             :   } else {
    1082             :     Parallel::printf(
    1083             :         "... file not found. Continuing with values from previous run.\n");
    1084             :   }
    1085             : }
    1086             : 
    1087             : }  // namespace Parallel
    1088             : 
    1089           0 : #define CK_TEMPLATES_ONLY
    1090             : #include "Parallel/Main.def.h"
    1091             : #undef CK_TEMPLATES_ONLY

Generated by: LCOV version 1.14