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

Generated by: LCOV version 2.0-1