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