MockDistributedObject.hpp
1 // Distributed under the MIT License.
2 // See LICENSE.txt for details.
3 
4 #pragma once
5 
6 #include <algorithm>
7 #include <array>
8 #include <boost/preprocessor/control/if.hpp>
9 #include <boost/preprocessor/logical/compl.hpp>
10 #include <boost/preprocessor/logical/not.hpp>
11 #include <boost/preprocessor/punctuation/comma_if.hpp>
12 #include <boost/preprocessor/repetition/repeat.hpp>
13 #include <converse.h>
14 #include <cstddef>
15 #include <deque>
16 #include <exception>
17 #include <memory>
18 #include <tuple>
19 #include <utility>
20 
21 #include "Parallel/AlgorithmMetafunctions.hpp"
22 #include "Parallel/GlobalCache.hpp"
23 #include "Parallel/NodeLock.hpp"
24 #include "Parallel/ParallelComponentHelpers.hpp"
25 #include "Parallel/PhaseDependentActionList.hpp"
26 #include "Parallel/SimpleActionVisitation.hpp"
29 #include "Utilities/Gsl.hpp"
30 #include "Utilities/NoSuchType.hpp"
31 #include "Utilities/Overloader.hpp"
32 #include "Utilities/PrettyType.hpp"
33 #include "Utilities/StdHelpers.hpp"
34 #include "Utilities/TMPL.hpp"
35 #include "Utilities/TypeTraits.hpp"
36 
37 namespace ActionTesting {
38 
39 namespace detail {
40 
41 #define ACTION_TESTING_CHECK_MOCK_ACTION_LIST(NAME) \
42  template <typename Component, typename = std::void_t<>> \
43  struct get_##NAME##_mocking_list { \
44  using replace_these_##NAME = tmpl::list<>; \
45  using with_these_##NAME = tmpl::list<>; \
46  }; \
47  template <typename Component> \
48  struct get_##NAME##_mocking_list< \
49  Component, std::void_t<typename Component::replace_these_##NAME, \
50  typename Component::with_these_##NAME>> { \
51  using replace_these_##NAME = typename Component::replace_these_##NAME; \
52  using with_these_##NAME = typename Component::with_these_##NAME; \
53  }; \
54  template <typename Component> \
55  using replace_these_##NAME##_t = \
56  typename get_##NAME##_mocking_list<Component>::replace_these_##NAME; \
57  template <typename Component> \
58  using with_these_##NAME##_t = \
59  typename get_##NAME##_mocking_list<Component>::with_these_##NAME
60 
61 ACTION_TESTING_CHECK_MOCK_ACTION_LIST(simple_actions);
62 ACTION_TESTING_CHECK_MOCK_ACTION_LIST(threaded_actions);
63 #undef ACTION_TESTING_CHECK_MOCK_ACTION_LIST
64 
65 template <typename Component, typename = std::void_t<>>
66 struct get_initialization_tags_from_component {
67  using type = tmpl::list<>;
68 };
69 
70 template <typename Component>
71 struct get_initialization_tags_from_component<
72  Component, std::void_t<typename Component::initialization_tags>> {
73  using type = typename Component::initialization_tags;
74 };
75 
76 // Given the tags `SimpleTags`, forwards them into the `DataBox`.
77 template <typename SimpleTagsList>
78 struct ForwardAllOptionsToDataBox;
79 
80 template <typename... SimpleTags>
81 struct ForwardAllOptionsToDataBox<tmpl::list<SimpleTags...>> {
82  using simple_tags = tmpl::list<SimpleTags...>;
83 
84  template <typename DbTagsList, typename... Args>
85  static auto apply(db::DataBox<DbTagsList>&& box, Args&&... args) noexcept {
86  static_assert(
87  sizeof...(SimpleTags) == sizeof...(Args),
88  "The number of arguments passed to ForwardAllOptionsToDataBox must "
89  "match the number of SimpleTags passed.");
90  return db::create_from<db::RemoveTags<>, simple_tags>(
91  std::move(box), std::forward<Args>(args)...);
92  }
93 };
94 
95 // Returns the type of `Tag` (including const and reference-ness as would be
96 // returned by `db::get<Tag>`) if the tag is in the `DataBox` of type
97 // `DataBoxType`, otherwise returns `NoSuchType`.
98 template <typename Tag, typename DataBoxType,
99  bool = db::tag_is_retrievable_v<Tag, DataBoxType>>
100 struct item_type_if_contained;
101 
102 template <typename Tag, typename DataBoxType>
103 struct item_type_if_contained<Tag, DataBoxType, true> {
104  using type = decltype(db::get<Tag>(DataBoxType{}));
105 };
106 
107 template <typename Tag, typename DataBoxType>
108 struct item_type_if_contained<Tag, DataBoxType, false> {
109  using type = NoSuchType;
110 };
111 
112 template <typename Tag, typename DataBoxType>
113 using item_type_if_contained_t =
114  typename item_type_if_contained<Tag, DataBoxType>::type;
115 } // namespace detail
116 
117 /// MockDistributedObject mocks the AlgorithmImpl class. It should not be
118 /// considered as part of the user interface.
119 ///
120 /// `MockDistributedObject` represents an object on a supercomputer
121 /// that can have methods invoked on it (possibly) remotely; this is
122 /// standard nomenclature in the HPC community, based on the idea that
123 /// such objects get distributed among the cores/nodes on an HPC (even
124 /// though each object typically lives on only one core). For
125 /// example, an element of an array chare in charm++ is a mock
126 /// distributed object, whereas the entire array chare is a collection
127 /// of mock distributed objects, each with its own array
128 /// index.
129 /// `MockDistributedObject` is a modified implementation of
130 /// `AlgorithmImpl` and so some of the code is shared between the
131 /// two. The main difference is that `MockDistributedObject` has
132 /// support for introspection. For example, it is possible to check
133 /// how many simple actions are queued, to look at the inboxes,
134 /// etc. Another key difference is that `MockDistributedObject` runs
135 /// only one action in the action list at a time. This is done in
136 /// order to provide opportunity for introspection and checking
137 /// statements before and after actions are invoked.
138 template <typename Component>
140  private:
141  class InvokeActionBase {
142  public:
143  InvokeActionBase() = default;
144  InvokeActionBase(const InvokeActionBase&) = default;
145  InvokeActionBase& operator=(const InvokeActionBase&) = default;
146  InvokeActionBase(InvokeActionBase&&) = default;
147  InvokeActionBase& operator=(InvokeActionBase&&) = default;
148  virtual ~InvokeActionBase() = default;
149  virtual void invoke_action() noexcept = 0;
150  };
151 
152  // Holds the arguments to be passed to the simple action once it is invoked.
153  // We delay simple action calls that are made from within an action for
154  // several reasons:
155  // - This is consistent with what actually happens in the parallel code
156  // - This prevents possible stack overflows
157  // - Allows better introspection and control over the Actions' behavior
158  template <typename Action, typename... Args>
159  class InvokeSimpleAction : public InvokeActionBase {
160  public:
161  InvokeSimpleAction(MockDistributedObject* mock_distributed_object,
162  std::tuple<Args...> args)
163  : mock_distributed_object_(mock_distributed_object),
164  args_(std::move(args)) {}
165 
166  explicit InvokeSimpleAction(MockDistributedObject* mock_distributed_object)
167  : mock_distributed_object_(mock_distributed_object) {}
168 
169  void invoke_action() noexcept override {
170  if (not valid_) {
171  ERROR(
172  "Cannot invoke the exact same simple action twice. This is an "
173  "internal bug in the action testing framework. Please file an "
174  "issue.");
175  }
176  valid_ = false;
177  invoke_action_impl(std::move(args_));
178  }
179 
180  private:
181  template <typename Arg0, typename... Rest>
182  void invoke_action_impl(std::tuple<Arg0, Rest...> args) noexcept {
183  mock_distributed_object_->simple_action<Action>(std::move(args), true);
184  }
185 
186  template <typename... LocalArgs,
187  Requires<sizeof...(LocalArgs) == 0> = nullptr>
188  void invoke_action_impl(std::tuple<LocalArgs...> /*args*/) noexcept {
189  mock_distributed_object_->simple_action<Action>(true);
190  }
191 
192  MockDistributedObject* mock_distributed_object_;
193  std::tuple<Args...> args_{};
194  bool valid_{true};
195  };
196 
197  // Holds the arguments passed to threaded actions. `InvokeThreadedAction` is
198  // analogous to `InvokeSimpleAction`.
199  template <typename Action, typename... Args>
200  class InvokeThreadedAction : public InvokeActionBase {
201  public:
202  InvokeThreadedAction(MockDistributedObject* mock_distributed_object,
203  std::tuple<Args...> args)
204  : mock_distributed_object_(mock_distributed_object),
205  args_(std::move(args)) {}
206 
207  explicit InvokeThreadedAction(
208  MockDistributedObject* mock_distributed_object)
209  : mock_distributed_object_(mock_distributed_object) {}
210 
211  void invoke_action() noexcept override {
212  if (not valid_) {
213  ERROR(
214  "Cannot invoke the exact same threaded action twice. This is an "
215  "internal bug in the action testing framework. Please file an "
216  "issue.");
217  }
218  valid_ = false;
219  invoke_action_impl(std::move(args_));
220  }
221 
222  private:
223  template <typename Arg0, typename... Rest>
224  void invoke_action_impl(std::tuple<Arg0, Rest...> args) noexcept {
225  mock_distributed_object_->threaded_action<Action>(std::move(args), true);
226  }
227 
228  template <typename... LocalArgs,
229  Requires<sizeof...(LocalArgs) == 0> = nullptr>
230  void invoke_action_impl(std::tuple<LocalArgs...> /*args*/) noexcept {
231  mock_distributed_object_->threaded_action<Action>(true);
232  }
233 
234  MockDistributedObject* mock_distributed_object_;
235  std::tuple<Args...> args_{};
236  bool valid_{true};
237  };
238 
239  public:
240  using phase_dependent_action_lists =
241  typename Component::phase_dependent_action_list;
242  static_assert(tmpl::size<phase_dependent_action_lists>::value > 0,
243  "Must have at least one phase dependent action list "
244  "(PhaseActions) in a parallel component.");
245 
246  using all_actions_list = tmpl::flatten<tmpl::transform<
247  phase_dependent_action_lists,
249 
250  using metavariables = typename Component::metavariables;
251 
252  using inbox_tags_list =
254 
255  using array_index = typename Parallel::get_array_index<
256  typename Component::chare_type>::template f<Component>;
257 
258  using parallel_component = Component;
259 
260  using PhaseType =
261  typename tmpl::front<phase_dependent_action_lists>::phase_type;
262 
264  using initialization_tags =
265  typename detail::get_initialization_tags_from_component<Component>::type;
266  using initial_tags = tmpl::flatten<tmpl::list<
269  using initial_databox = db::compute_databox_type<initial_tags>;
270 
271  // The types held by the boost::variant, box_
272  using databox_phase_types =
273  typename Parallel::Algorithm_detail::build_databox_types<
274  tmpl::list<>, phase_dependent_action_lists, initial_databox,
275  inbox_tags_list, metavariables, typename Component::array_index,
276  Component>::type;
277  template <typename T>
279  using type = typename T::databox_types;
280  };
281 
282  using databox_types = tmpl::flatten<
283  tmpl::transform<databox_phase_types, get_databox_types<tmpl::_1>>>;
284  using variant_boxes = tmpl::remove_duplicates<
285  tmpl::push_front<databox_types, db::DataBox<tmpl::list<>>>>;
286 
287  MockDistributedObject() = default;
288 
289  template <typename... Options>
291  const array_index& index,
293  tuples::tagged_tuple_from_typelist<inbox_tags_list>* inboxes,
294  Options&&... opts)
295  : array_index_(index), global_cache_(cache), inboxes_(inboxes) {
296  box_ = detail::ForwardAllOptionsToDataBox<initialization_tags>::apply(
297  db::create<
300  all_cache_tags>>>(
301  global_cache_),
302  std::forward<Options>(opts)...);
303  }
304 
305  void set_phase(PhaseType phase) noexcept {
306  phase_ = phase;
307  algorithm_step_ = 0;
308  terminate_ = number_of_actions_in_phase(phase) == 0;
309  }
310  PhaseType get_phase() const noexcept { return phase_; }
311 
312  void set_terminate(bool t) noexcept { terminate_ = t; }
313  bool get_terminate() const noexcept { return terminate_; }
314 
315  // Actions may call this, but since tests step through actions manually it has
316  // no effect.
317  void perform_algorithm() noexcept {}
318 
319  size_t number_of_actions_in_phase(const PhaseType phase) const noexcept {
320  size_t number_of_actions = 0;
321  tmpl::for_each<phase_dependent_action_lists>(
322  [&number_of_actions, phase](auto pdal_v) {
323  const auto pdal = tmpl::type_from<decltype(pdal_v)>{};
324  if (pdal.phase == phase) {
325  number_of_actions = pdal.number_of_actions;
326  }
327  });
328  return number_of_actions;
329  }
330 
331  // @{
332  /// Returns the DataBox with the tags set from the GlobalCache and the
333  /// tags in `AdditionalTagsList`. If the DataBox type is incorrect
334  /// `std::terminate` is called.
335  template <typename AdditionalTagsList>
336  auto& get_databox() noexcept {
337  using box_type = db::compute_databox_type<
338  tmpl::flatten<tmpl::list<initial_tags, AdditionalTagsList>>>;
339  return boost::get<box_type>(box_);
340  }
341 
342  template <typename AdditionalTagsList>
343  const auto& get_databox() const noexcept {
344  using box_type = db::compute_databox_type<
345  tmpl::flatten<tmpl::list<initial_tags, AdditionalTagsList>>>;
346  return boost::get<box_type>(box_);
347  }
348  // @}
349 
350  /// Walks through the variant of DataBoxes and retrieves the tag from the
351  /// current one, if the current DataBox has the tag. If the current DataBox
352  /// does not have the requested tag it is an error.
353  template <typename Tag>
354  const auto& get_databox_tag() const noexcept {
355  return get_databox_tag_visitation<Tag>(box_);
356  }
357 
358  template <typename Tag>
359  bool box_contains() const noexcept {
360  return box_contains_visitation<Tag>(box_);
361  }
362 
363  template <typename Tag>
364  bool tag_is_retrievable() const noexcept {
365  return tag_is_retrievable_visitation<Tag>(box_);
366  }
367 
368  // @{
369  /// Returns the `boost::variant` of DataBoxes.
370  auto& get_variant_box() noexcept { return box_; }
371 
372  const auto& get_variant_box() const noexcept { return box_; }
373  // @}
374 
375  /// Force the next action invoked to be the `next_action_id`th action in the
376  /// current phase.
377  void force_next_action_to_be(const size_t next_action_id) noexcept {
378  algorithm_step_ = next_action_id;
379  }
380 
381  /// Returns which action (by integer) will be invoked next in the current
382  /// phase.
383  size_t get_next_action_index() const noexcept { return algorithm_step_; }
384 
385  /// Invoke the next action in the action list for the current phase.
386  void next_action() noexcept;
387 
388  /// Evaluates the `is_ready` method on the next action and returns the result.
389  bool is_ready() noexcept;
390 
391  /// Defines the methods used for invoking threaded and simple actions. Since
392  /// the two cases are so similar we use a macro to reduce the amount of
393  /// redundant code.
394 #define SIMPLE_AND_THREADED_ACTIONS(USE_SIMPLE_ACTION, NAME) \
395  template <typename Action, typename... Args, \
396  Requires<not tmpl::list_contains_v< \
397  detail::replace_these_##NAME##s_t<Component>, Action>> = \
398  nullptr> \
399  void NAME(std::tuple<Args...> args, \
400  const bool direct_from_action_runner = false) noexcept { \
401  if (direct_from_action_runner) { \
402  performing_action_ = true; \
403  forward_tuple_to_##NAME<Action>( \
404  std::move(args), std::make_index_sequence<sizeof...(Args)>{}); \
405  performing_action_ = false; \
406  } else { \
407  NAME##_queue_.push_back( \
408  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
409  InvokeThreadedAction) < Action, \
410  Args...> > (this, std::move(args))); \
411  } \
412  } \
413  template <typename Action, typename... Args, \
414  Requires<tmpl::list_contains_v< \
415  detail::replace_these_##NAME##s_t<Component>, Action>> = \
416  nullptr> \
417  void NAME(std::tuple<Args...> args, \
418  const bool direct_from_action_runner = false) noexcept { \
419  using index_of_action = \
420  tmpl::index_of<detail::replace_these_##NAME##s_t<Component>, Action>; \
421  using new_action = tmpl::at_c<detail::with_these_##NAME##s_t<Component>, \
422  index_of_action::value>; \
423  if (direct_from_action_runner) { \
424  performing_action_ = true; \
425  forward_tuple_to_##NAME<new_action>( \
426  std::move(args), std::make_index_sequence<sizeof...(Args)>{}); \
427  performing_action_ = false; \
428  } else { \
429  NAME##_queue_.push_back( \
430  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
431  InvokeThreadedAction) < new_action, \
432  Args...> > (this, std::move(args))); \
433  } \
434  } \
435  template <typename Action, \
436  Requires<not tmpl::list_contains_v< \
437  detail::replace_these_##NAME##s_t<Component>, Action>> = \
438  nullptr> \
439  void NAME(const bool direct_from_action_runner = false) noexcept { \
440  if (direct_from_action_runner) { \
441  performing_action_ = true; \
442  Parallel::Algorithm_detail::simple_action_visitor<Action, Component>( \
443  box_, *global_cache_, \
444  std::as_const(array_index_) \
445  BOOST_PP_COMMA_IF(BOOST_PP_NOT(USE_SIMPLE_ACTION)) BOOST_PP_IF( \
446  USE_SIMPLE_ACTION, , make_not_null(&node_lock_))); \
447  performing_action_ = false; \
448  } else { \
449  NAME##_queue_.push_back( \
450  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
451  InvokeThreadedAction) < Action> > \
452  (this)); \
453  } \
454  } \
455  template <typename Action, \
456  Requires<tmpl::list_contains_v< \
457  detail::replace_these_##NAME##s_t<Component>, Action>> = \
458  nullptr> \
459  void NAME(const bool direct_from_action_runner = false) noexcept { \
460  using index_of_action = \
461  tmpl::index_of<detail::replace_these_##NAME##s_t<Component>, Action>; \
462  using new_action = tmpl::at_c<detail::with_these_##NAME##s_t<Component>, \
463  index_of_action::value>; \
464  if (direct_from_action_runner) { \
465  performing_action_ = true; \
466  Parallel::Algorithm_detail::simple_action_visitor<new_action, \
467  Component>( \
468  box_, *global_cache_, \
469  std::as_const(array_index_) \
470  BOOST_PP_COMMA_IF(BOOST_PP_NOT(USE_SIMPLE_ACTION)) BOOST_PP_IF( \
471  USE_SIMPLE_ACTION, , make_not_null(&node_lock_))); \
472  performing_action_ = false; \
473  } else { \
474  simple_action_queue_.push_back( \
475  std::make_unique<BOOST_PP_IF(USE_SIMPLE_ACTION, InvokeSimpleAction, \
476  InvokeThreadedAction) < new_action> > \
477  (this)); \
478  } \
479  }
480 
481  SIMPLE_AND_THREADED_ACTIONS(1, simple_action)
482  SIMPLE_AND_THREADED_ACTIONS(0, threaded_action)
483 #undef SIMPLE_AND_THREADED_ACTIONS
484 
485  bool is_simple_action_queue_empty() const noexcept {
486  return simple_action_queue_.empty();
487  }
488 
489  void invoke_queued_simple_action() noexcept {
490  if (simple_action_queue_.empty()) {
491  ERROR(
492  "There are no queued simple actions to invoke. Are you sure a "
493  "previous action invoked a simple action on this component?");
494  }
495  simple_action_queue_.front()->invoke_action();
496  simple_action_queue_.pop_front();
497  }
498 
499  bool is_threaded_action_queue_empty() const noexcept {
500  return threaded_action_queue_.empty();
501  }
502 
503  void invoke_queued_threaded_action() noexcept {
504  if (threaded_action_queue_.empty()) {
505  ERROR(
506  "There are no queued threaded actions to invoke. Are you sure a "
507  "previous action invoked a threaded action on this component?");
508  }
509  threaded_action_queue_.front()->invoke_action();
510  threaded_action_queue_.pop_front();
511  }
512 
513  template <typename InboxTag, typename Data>
514  void receive_data(const typename InboxTag::temporal_id& id, Data&& data,
515  const bool enable_if_disabled = false) {
516  // The variable `enable_if_disabled` might be useful in the future but is
517  // not needed now. However, it is required by the interface to be compliant
518  // with the Algorithm invocations.
519  (void)enable_if_disabled;
520  InboxTag::insert_into_inbox(
521  make_not_null(&tuples::get<InboxTag>(*inboxes_)), id,
522  std::forward<Data>(data));
523  }
524 
525  private:
526  template <typename Action, typename... Args, size_t... Is>
527  void forward_tuple_to_simple_action(
528  std::tuple<Args...>&& args,
529  std::index_sequence<Is...> /*meta*/) noexcept {
530  Parallel::Algorithm_detail::simple_action_visitor<Action, Component>(
531  box_, *global_cache_, std::as_const(array_index_),
532  std::forward<Args>(std::get<Is>(args))...);
533  }
534 
535  template <typename Action, typename... Args, size_t... Is>
536  void forward_tuple_to_threaded_action(
537  std::tuple<Args...>&& args,
538  std::index_sequence<Is...> /*meta*/) noexcept {
539  Parallel::Algorithm_detail::simple_action_visitor<Action, Component>(
540  box_, *global_cache_, std::as_const(array_index_),
541  make_not_null(&node_lock_), std::forward<Args>(std::get<Is>(args))...);
542  }
543 
544  template <typename PhaseDepActions, size_t... Is>
545  void next_action_impl(std::index_sequence<Is...> /*meta*/) noexcept;
546 
547  template <typename PhaseDepActions, size_t... Is>
548  bool is_ready_impl(std::index_sequence<Is...> /*meta*/) noexcept;
549 
550  template <typename Tag, typename ThisVariantBox, typename Type,
551  typename... Variants,
552  Requires<tmpl::size<tmpl::filter<
553  typename ThisVariantBox::tags_list,
554  std::is_base_of<tmpl::pin<Tag>, tmpl::_1>>>::value !=
555  0> = nullptr>
556  void get_databox_tag_visitation_impl(
557  const Type** result, const gsl::not_null<int*> iter,
558  const gsl::not_null<bool*> already_visited,
559  const boost::variant<Variants...>& box) const noexcept {
560  if (box.which() == *iter and not *already_visited) {
561  *result = &db::get<Tag>(boost::get<ThisVariantBox>(box));
562  (void)result;
563  *already_visited = true;
564  }
565  (*iter)++;
566  }
567  template <typename Tag, typename ThisVariantBox, typename Type,
568  typename... Variants,
569  Requires<tmpl::size<tmpl::filter<
570  typename ThisVariantBox::tags_list,
571  std::is_base_of<tmpl::pin<Tag>, tmpl::_1>>>::value ==
572  0> = nullptr>
573  void get_databox_tag_visitation_impl(
574  const Type** /*result*/, const gsl::not_null<int*> iter,
575  const gsl::not_null<bool*> already_visited,
576  const boost::variant<Variants...>& box) const noexcept {
577  if (box.which() == *iter and not *already_visited) {
578  ERROR("Cannot retrieve tag: "
579  << db::tag_name<Tag>()
580  << " from the current DataBox because it is not in it.");
581  }
582  (*iter)++;
583  }
584 
585  template <typename Tag, typename... Variants>
586  const auto& get_databox_tag_visitation(
587  const boost::variant<Variants...>& box) const noexcept {
588  using item_types = tmpl::remove_duplicates<tmpl::remove_if<
589  tmpl::list<cpp20::remove_cvref_t<
590  detail::item_type_if_contained_t<Tag, Variants>>...>,
592  static_assert(tmpl::size<item_types>::value != 0,
593  "Could not find the tag or the tag as a base tag in any "
594  "DataBox in the get_databox_tag function.");
595  static_assert(
596  tmpl::size<item_types>::value < 2,
597  "Found the tag in or the tag as a base tag in more than one DataBox in "
598  "the get_databox_tag function. This means you need to explicitly "
599  "retrieve the DataBox type to retrieve the tag or file an issue "
600  "requesting a get_databox_tag function that can also take a type "
601  "explicitly. We have not yet encountered a need for this functionality "
602  "but it could be added.");
603  const tmpl::front<item_types>* result = nullptr;
604  int iter = 0;
605  bool already_visited = false;
606  EXPAND_PACK_LEFT_TO_RIGHT(get_databox_tag_visitation_impl<Tag, Variants>(
607  &result, &iter, &already_visited, box));
608  if (result == nullptr) {
609  ERROR("The result pointer is nullptr, which it should never be.\n");
610  }
611  return *result;
612  }
613 
614  template <typename Tag, typename ThisVariantBox, typename... Variants,
615  Requires<tmpl::list_contains_v<typename ThisVariantBox::tags_list,
616  Tag>> = nullptr>
617  void box_contains_visitation_impl(
618  bool* const contains_tag, const gsl::not_null<int*> iter,
619  const boost::variant<Variants...>& box) const noexcept {
620  if (box.which() == *iter) {
621  *contains_tag =
622  tmpl::list_contains_v<typename ThisVariantBox::tags_list, Tag>;
623  }
624  (*iter)++;
625  }
626  template <typename Tag, typename ThisVariantBox, typename... Variants,
627  Requires<not tmpl::list_contains_v<
628  typename ThisVariantBox::tags_list, Tag>> = nullptr>
629  void box_contains_visitation_impl(
630  bool* const /*contains_tag*/, const gsl::not_null<int*> iter,
631  const boost::variant<Variants...>& /*box*/) const noexcept {
632  (*iter)++;
633  }
634 
635  template <typename Tag, typename... Variants>
636  bool box_contains_visitation(
637  const boost::variant<Variants...>& box) const noexcept {
638  bool contains_tag = false;
639  int iter = 0;
641  box_contains_visitation_impl<Tag, Variants>(&contains_tag, &iter, box));
642  return contains_tag;
643  }
644 
645  template <typename Tag, typename... Variants>
646  bool tag_is_retrievable_visitation(
647  const boost::variant<Variants...>& box) const noexcept {
648  bool is_retrievable = false;
649  const auto helper = [&box, &is_retrievable](auto box_type) noexcept {
650  using DataBoxType = typename decltype(box_type)::type;
651  if (static_cast<int>(
652  tmpl::index_of<tmpl::list<Variants...>, DataBoxType>::value) ==
653  box.which()) {
654  is_retrievable = db::tag_is_retrievable_v<Tag, DataBoxType>;
655  }
656  };
657  EXPAND_PACK_LEFT_TO_RIGHT(helper(tmpl::type_<Variants>{}));
658  return is_retrievable;
659  }
660 
661  bool terminate_{false};
662  make_boost_variant_over<variant_boxes> box_ = db::DataBox<tmpl::list<>>{};
663  // The next action we should execute.
664  size_t algorithm_step_ = 0;
665  bool performing_action_ = false;
666  PhaseType phase_{};
667 
668  typename Component::array_index array_index_{};
670  nullptr};
671  tuples::tagged_tuple_from_typelist<inbox_tags_list>* inboxes_{nullptr};
672  std::deque<std::unique_ptr<InvokeActionBase>> simple_action_queue_;
673  std::deque<std::unique_ptr<InvokeActionBase>> threaded_action_queue_;
674  Parallel::NodeLock node_lock_;
675 };
676 
677 template <typename Component>
679  bool found_matching_phase = false;
680  const auto invoke_for_phase =
681  [this, &found_matching_phase](auto phase_dep_v) noexcept {
682  using PhaseDep = typename decltype(phase_dep_v)::type;
683  constexpr PhaseType phase = PhaseDep::phase;
684  using actions_list = typename PhaseDep::action_list;
685  if (phase_ == phase) {
686  found_matching_phase = true;
687  this->template next_action_impl<PhaseDep>(
688  std::make_index_sequence<tmpl::size<actions_list>::value>{});
689  }
690  };
691  tmpl::for_each<phase_dependent_action_lists>(invoke_for_phase);
692  if (not found_matching_phase) {
693  ERROR("Could not find any actions in the current phase for the component '"
694  << pretty_type::short_name<Component>() << "'.");
695  }
696 }
697 
698 template <typename Component>
699 template <typename PhaseDepActions, size_t... Is>
701  std::index_sequence<Is...> /*meta*/) noexcept {
702  if (UNLIKELY(performing_action_)) {
703  ERROR(
704  "Cannot call an Action while already calling an Action on the same "
705  "MockDistributedObject (an element of a parallel component array, or a "
706  "parallel component singleton).");
707  }
708  // Keep track of if we already evaluated an action since we want `next_action`
709  // to only evaluate one per call.
710  bool already_did_an_action = false;
711  const auto helper = [this, &already_did_an_action](auto iteration) noexcept {
712  constexpr size_t iter = decltype(iteration)::value;
713  if (already_did_an_action or algorithm_step_ != iter) {
714  return;
715  }
716 
717  using actions_list = typename PhaseDepActions::action_list;
718  using this_action = tmpl::at_c<actions_list, iter>;
719  // Invoke the action's static `apply` method. The overloads are for handling
720  // the cases where the `apply` method returns:
721  // 1. only a DataBox
722  // 2. a DataBox and a bool determining whether or not to terminate
723  // 3. a DataBox, a bool, and an integer corresponding to which action in the
724  // current phase's algorithm to execute next.
725  //
726  // The first argument to the invokable is the DataBox to be passed into the
727  // action's `apply` method, while the second is:
728  // ```
729  // typename std::tuple_size<decltype(this_action::apply(
730  // box, inboxes_, *global_cache_,
731  // std::as_const(array_index_), actions_list{},
732  // std::add_pointer_t<ParallelComponent>{}))>::type{}
733  // ```
734  const auto invoke_this_action = make_overloader(
735  [this](auto& my_box,
736  std::integral_constant<size_t, 1> /*meta*/) noexcept {
737  std::tie(box_) = this_action::apply(
738  my_box, *inboxes_, *global_cache_, std::as_const(array_index_),
739  actions_list{}, std::add_pointer_t<Component>{});
740  },
741  [this](auto& my_box,
742  std::integral_constant<size_t, 2> /*meta*/) noexcept {
743  std::tie(box_, terminate_) = this_action::apply(
744  my_box, *inboxes_, *global_cache_, std::as_const(array_index_),
745  actions_list{}, std::add_pointer_t<Component>{});
746  },
747  [this](auto& my_box,
748  std::integral_constant<size_t, 3> /*meta*/) noexcept {
749  std::tie(box_, terminate_, algorithm_step_) = this_action::apply(
750  my_box, *inboxes_, *global_cache_, std::as_const(array_index_),
751  actions_list{}, std::add_pointer_t<Component>{});
752  });
753 
754  // `check_if_ready` calls the `is_ready` static method on the action
755  // `action` if it has one, otherwise returns true. The first argument is the
756  // ```
757  // Algorithm_detail::is_is_ready_callable_t<action, databox,
758  // tuples::tagged_tuple_from_typelist<inbox_tags_list>,
759  // Parallel::GlobalCache<metavariables>, array_index>{}
760  // ```
761  const auto check_if_ready = make_overloader(
762  [this](std::true_type /*has_is_ready*/, auto action,
763  const auto& check_local_box) noexcept {
764  return decltype(action)::is_ready(
765  check_local_box, std::as_const(*inboxes_), *global_cache_,
766  std::as_const(array_index_));
767  },
768  [](std::false_type /*has_is_ready*/, auto /*action*/,
769  const auto& /*box*/) noexcept { return true; });
770 
771  constexpr size_t phase_index =
772  tmpl::index_of<phase_dependent_action_lists, PhaseDepActions>::value;
773  using databox_phase_type = tmpl::at_c<databox_phase_types, phase_index>;
774  using databox_types_this_phase = typename databox_phase_type::databox_types;
775 
776  const auto display_databox_error = [this]() noexcept {
777  ERROR(
778  "The DataBox type being retrieved at algorithm step: "
779  << algorithm_step_ << " in phase " << phase_index
780  << " corresponding to action " << pretty_type::get_name<this_action>()
781  << " is not the correct type but is of variant index " << box_.which()
782  << ". The type of the current box is: " << type_of_current_state(box_)
783  << "\nIf you are using Goto and Label actions then you are using "
784  "them incorrectly.");
785  };
786 
787  // The overload separately handles the first action in the phase from the
788  // remaining actions. The reason for this is that the first action can have
789  // as its input DataBox either the output of the last action in the phase or
790  // the output of the last action in the *previous* phase. This is handled by
791  // checking which DataBox is currently in the `boost::variant` (using the
792  // call `box_.which()`).
794  // clang-format off
795  [ this, &check_if_ready, &invoke_this_action, &
796  display_databox_error ](auto current_iter) noexcept
798  decltype(current_iter)>::value> {
799  // clang-format on
800  // When `algorithm_step_ == 0` we could be the first DataBox or
801  // the last Databox.
802  using first_databox = tmpl::at_c<databox_types_this_phase, 0>;
803  using last_databox =
804  tmpl::at_c<databox_types_this_phase,
805  tmpl::size<databox_types_this_phase>::value - 1>;
806  using local_this_action =
807  tmpl::at_c<actions_list, decltype(current_iter)::value>;
808  if (box_.which() ==
809  static_cast<int>(
810  tmpl::index_of<variant_boxes, first_databox>::value)) {
811  using this_databox = first_databox;
812  auto& box = boost::get<this_databox>(box_);
813  if (not check_if_ready(
814  Parallel::Algorithm_detail::is_is_ready_callable_t<
815  local_this_action, this_databox,
816  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
817  Parallel::GlobalCache<metavariables>, array_index>{},
818  local_this_action{}, box)) {
819  ERROR("Tried to invoke the action '"
820  << pretty_type::get_name<local_this_action>()
821  << "' but have not received all the "
822  "necessary data.");
823  }
824  performing_action_ = true;
825  algorithm_step_++;
826  invoke_this_action(
827  box,
828  typename std::tuple_size<decltype(local_this_action::apply(
829  box, *inboxes_, *global_cache_, std::as_const(array_index_),
830  actions_list{}, std::add_pointer_t<Component>{}))>::type{});
831  } else if (box_.which() ==
832  static_cast<int>(
833  tmpl::index_of<variant_boxes, last_databox>::value)) {
834  using this_databox = last_databox;
835  auto& box = boost::get<this_databox>(box_);
836  if (not check_if_ready(
837  Parallel::Algorithm_detail::is_is_ready_callable_t<
838  local_this_action, this_databox,
839  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
840  Parallel::GlobalCache<metavariables>, array_index>{},
841  local_this_action{}, box)) {
842  ERROR("Tried to invoke the action '"
843  << pretty_type::get_name<local_this_action>()
844  << "' but have not received all the "
845  "necessary data.");
846  }
847  performing_action_ = true;
848  algorithm_step_++;
849  invoke_this_action(
850  box,
851  typename std::tuple_size<decltype(local_this_action::apply(
852  box, *inboxes_, *global_cache_, std::as_const(array_index_),
853  actions_list{}, std::add_pointer_t<Component>{}))>::type{});
854  } else {
855  display_databox_error();
856  }
857  return nullptr;
858  },
859  // clang-format off
860  [ this, &check_if_ready, &invoke_this_action, &
861  display_databox_error ](auto current_iter) noexcept
863  decltype(current_iter)>::value> {
864  // clang-format on
865  // When `algorithm_step_ != 0` we must be the DataBox of before us
866  using this_databox = tmpl::at_c<databox_types_this_phase,
867  decltype(current_iter)::value>;
868  using local_this_action =
869  tmpl::at_c<actions_list, decltype(current_iter)::value>;
870  if (box_.which() ==
871  static_cast<int>(
872  tmpl::index_of<variant_boxes, this_databox>::value)) {
873  auto& box = boost::get<this_databox>(box_);
874  if (not check_if_ready(
875  Parallel::Algorithm_detail::is_is_ready_callable_t<
876  local_this_action, this_databox,
877  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
878  Parallel::GlobalCache<metavariables>, array_index>{},
879  local_this_action{}, box)) {
880  ERROR("Tried to invoke the action '"
881  << pretty_type::get_name<local_this_action>()
882  << "' but have not received all the "
883  "necessary data.");
884  }
885  performing_action_ = true;
886  algorithm_step_++;
887  invoke_this_action(
888  box,
889  typename std::tuple_size<decltype(local_this_action::apply(
890  box, *inboxes_, *global_cache_, std::as_const(array_index_),
891  actions_list{}, std::add_pointer_t<Component>{}))>::type{});
892  } else {
893  display_databox_error();
894  }
895  return nullptr;
897 
898  performing_action_ = false;
899  already_did_an_action = true;
900  // Wrap counter if necessary
901  if (algorithm_step_ >= tmpl::size<actions_list>::value) {
902  algorithm_step_ = 0;
903  }
904  };
905  // Silence compiler warning when there are no Actions.
906  (void)helper;
908 }
909 
910 template <typename Component>
912  bool action_is_ready = false;
913  bool found_matching_phase = false;
914  const auto invoke_for_phase = [this, &action_is_ready, &found_matching_phase](
915  auto phase_dep_v) noexcept {
916  using PhaseDep = typename decltype(phase_dep_v)::type;
917  constexpr PhaseType phase = PhaseDep::phase;
918  using actions_list = typename PhaseDep::action_list;
919  if (phase_ == phase) {
920  found_matching_phase = true;
921  action_is_ready = this->template is_ready_impl<PhaseDep>(
922  std::make_index_sequence<tmpl::size<actions_list>::value>{});
923  }
924  };
925  tmpl::for_each<phase_dependent_action_lists>(invoke_for_phase);
926  if (not found_matching_phase) {
927  ERROR("Could not find any actions in the current phase for the component '"
928  << pretty_type::short_name<Component>() << "'.");
929  }
930  return action_is_ready;
931 }
932 
933 template <typename Component>
934 template <typename PhaseDepActions, size_t... Is>
936  std::index_sequence<Is...> /*meta*/) noexcept {
937  bool next_action_is_ready = false;
938  const auto helper = [this, &array_index = array_index_, &inboxes = *inboxes_,
939  &global_cache = global_cache_,
940  &next_action_is_ready](auto iteration) noexcept {
941  constexpr size_t iter = decltype(iteration)::value;
942  using actions_list = typename PhaseDepActions::action_list;
943  using this_action = tmpl::at_c<actions_list, iter>;
944 
945  constexpr size_t phase_index =
946  tmpl::index_of<phase_dependent_action_lists, PhaseDepActions>::value;
947  using databox_phase_type = tmpl::at_c<databox_phase_types, phase_index>;
948  using databox_types_this_phase = typename databox_phase_type::databox_types;
949  using this_databox =
950  tmpl::at_c<databox_types_this_phase,
951  iter == 0 ? tmpl::size<databox_types_this_phase>::value - 1
952  : iter>;
953  if (iter != algorithm_step_) {
954  return;
955  }
956 
957  this_databox* box_ptr{};
958  try {
959  box_ptr = &boost::get<this_databox>(box_);
960  } catch (std::exception& e) {
961  ERROR(
962  "\nFailed to retrieve Databox in take_next_action:\nCaught "
963  "exception: '"
964  << e.what() << "'\nDataBox type: '"
965  << pretty_type::get_name<this_databox>() << "'\nIteration: " << iter
966  << "\nAction: '" << pretty_type::get_name<this_action>()
967  << "'\nBoost::Variant id: " << box_.which()
968  << "\nBoost::Variant type is: '" << type_of_current_state(box_)
969  << "'\n\n");
970  }
971  this_databox& box = *box_ptr;
972 
973  // `check_if_ready` calls the `is_ready` static method on the action
974  // `action` if it has one, otherwise returns true. The first argument is the
975  // ```
976  // Algorithm_detail::is_is_ready_callable_t<action, databox,
977  // tuples::tagged_tuple_from_typelist<inbox_tags_list>,
978  // Parallel::GlobalCache<metavariables>, array_index>{}
979  // ```
980  const auto check_if_ready = make_overloader(
981  [&box, &array_index, &global_cache, &inboxes](
982  std::true_type /*has_is_ready*/, auto t) {
983  return decltype(t)::is_ready(std::as_const(box),
984  std::as_const(inboxes), *global_cache,
985  std::as_const(array_index));
986  },
987  [](std::false_type /*has_is_ready*/, auto /*meta*/) { return true; });
988 
989  next_action_is_ready =
990  check_if_ready(Parallel::Algorithm_detail::is_is_ready_callable_t<
991  this_action, this_databox,
992  tuples::tagged_tuple_from_typelist<inbox_tags_list>,
994  typename Component::array_index>{},
995  this_action{});
996  };
997  // Silence compiler warning when there are no Actions.
998  (void)helper;
1000  return next_action_is_ready;
1001 }
1002 } // namespace ActionTesting
NoSuchType
Used to mark "no type" or "bad state" for metaprogramming.
Definition: NoSuchType.hpp:10
std::apply
T apply(T... args)
std::is_same
std::integral_constant
EXPAND_PACK_LEFT_TO_RIGHT
#define EXPAND_PACK_LEFT_TO_RIGHT(...)
Expand a parameter pack evaluating the terms from left to right.
Definition: TMPL.hpp:563
utility
exception
make_overloader
Overloader< Fs... > make_overloader(Fs... fs)
Create Overloader<Fs...>, see Overloader for details.
Definition: Overloader.hpp:109
UNLIKELY
#define UNLIKELY(x)
Definition: Gsl.hpp:73
Parallel::GlobalCache< typename Component::metavariables >
ActionTesting
Structures used for mocking the parallel components framework in order to test actions.
Definition: ActionTesting.hpp:308
db::compute_databox_type
typename detail::compute_dbox_type< get_items< TagList >, get_compute_items< TagList > >::type compute_databox_type
Returns the type of the DataBox that would be constructed from the TagList of tags.
Definition: DataBox.hpp:1044
GlobalCache.hpp
std::index_sequence
Error.hpp
ActionTesting::MockDistributedObject::get_databox_types
Definition: MockDistributedObject.hpp:278
PrettyType.hpp
Parallel::Tags::FromGlobalCache
Definition: GlobalCache.hpp:662
ActionTesting::threaded_action
void threaded_action(const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index, Args &&... args) noexcept
Runs the simple action Action on the array_indexth element of the parallel component Component.
Definition: MockRuntimeSystemFreeFunctions.hpp:176
db::AddComputeTags
tmpl::flatten< tmpl::list< Tags... > > AddComputeTags
List of Compute Item Tags to add to the DataBox.
Definition: DataBox.hpp:1006
BoostHelpers.hpp
tuple
std::as_const
T as_const(T... args)
algorithm
ActionTesting::MockDistributedObject::next_action
void next_action() noexcept
Invoke the next action in the action list for the current phase.
Definition: MockDistributedObject.hpp:678
Parallel::get_inbox_tags
tmpl::remove_duplicates< tmpl::join< tmpl::transform< ActionsList, detail::get_inbox_tags_from_action< tmpl::_1 > >> > get_inbox_tags
Given a list of Actions, get a list of the unique inbox tags.
Definition: ParallelComponentHelpers.hpp:31
ActionTesting::MockDistributedObject::get_databox_tag
const auto & get_databox_tag() const noexcept
Walks through the variant of DataBoxes and retrieves the tag from the current one,...
Definition: MockDistributedObject.hpp:354
Parallel::NodeLock
A typesafe wrapper for a lock for synchronization of shared resources on a given node,...
Definition: NodeLock.hpp:25
ERROR
#define ERROR(m)
prints an error message to the standard error stream and aborts the program.
Definition: Error.hpp:36
db::create
constexpr auto create(Args &&... args)
Create a new DataBox.
Definition: DataBox.hpp:1068
std::add_pointer_t
Options
Utilities for parsing input files.
Definition: MinmodType.hpp:8
ActionTesting::is_ready
bool is_ready(MockRuntimeSystem< Metavariables > &runner, const typename Component::array_index &array_index) noexcept
Runs the is_ready function and returns the result for the next action in the current phase on the arr...
Definition: MockRuntimeSystemFreeFunctions.hpp:155
cstddef
array
make_boost_variant_over
typename detail::make_boost_variant_over_impl< tmpl::remove_duplicates< Sequence > >::type make_boost_variant_over
Create a boost::variant with all all the types inside the typelist Sequence.
Definition: BoostHelpers.hpp:43
ActionTesting::MockDistributedObject::get_databox
auto & get_databox() noexcept
Returns the DataBox with the tags set from the GlobalCache and the tags in AdditionalTagsList....
Definition: MockDistributedObject.hpp:336
ActionTesting::MockDistributedObject
MockDistributedObject mocks the AlgorithmImpl class. It should not be considered as part of the user ...
Definition: MockDistributedObject.hpp:139
deque
Parallel::Tags::GlobalCacheImpl
Definition: GlobalCache.hpp:652
Parallel::get_action_list_from_phase_dep_action_list
(Lazy) metafunction to get the action list from a PhaseActions
Definition: PhaseDependentActionList.hpp:29
memory
ActionTesting::MockDistributedObject::force_next_action_to_be
void force_next_action_to_be(const size_t next_action_id) noexcept
Force the next action invoked to be the next_action_idth action in the current phase.
Definition: MockDistributedObject.hpp:377
Parallel::get_const_global_cache_tags
tmpl::remove_duplicates< tmpl::flatten< tmpl::list< typename detail::get_const_global_cache_tags_from_parallel_struct< Metavariables >::type, tmpl::transform< typename Metavariables::component_list, detail::get_const_global_cache_tags_from_parallel_struct< tmpl::_1 > >, tmpl::transform< typename Metavariables::component_list, detail::get_const_global_cache_tags_from_pdal< tmpl::_1 > >> >> get_const_global_cache_tags
Given the metavariables, get a list of the unique tags that will specify the items in the GlobalCache...
Definition: ParallelComponentHelpers.hpp:116
db::AddSimpleTags
tmpl::flatten< tmpl::list< Tags... > > AddSimpleTags
List of Tags to add to the DataBox.
Definition: DataBox.hpp:985
ActionTesting::simple_action
void simple_action(const gsl::not_null< MockRuntimeSystem< Metavariables > * > runner, const typename Component::array_index &array_index, Args &&... args) noexcept
Runs the simple action Action on the array_indexth element of the parallel component Component.
Definition: MockRuntimeSystemFreeFunctions.hpp:164
Gsl.hpp
db::wrap_tags_in
tmpl::transform< TagList, tmpl::bind< Wrapper, tmpl::_1, tmpl::pin< Args >... > > wrap_tags_in
Create a new tmpl::list of tags by wrapping each tag in TagList in Wrapper<_, Args....
Definition: PrefixHelpers.hpp:30
ActionTesting::MockDistributedObject::get_next_action_index
size_t get_next_action_index() const noexcept
Returns which action (by integer) will be invoked next in the current phase.
Definition: MockDistributedObject.hpp:383
StdHelpers.hpp
make_not_null
gsl::not_null< T * > make_not_null(T *ptr) noexcept
Construct a not_null from a pointer. Often this will be done as an implicit conversion,...
Definition: Gsl.hpp:880
Requires
typename Requires_detail::requires_impl< B >::template_error_type_failed_to_meet_requirements_on_template_parameters Requires
Express requirements on the template parameters of a function or class, replaces std::enable_if_t
Definition: Requires.hpp:67
ActionTesting::MockDistributedObject::is_ready
bool is_ready() noexcept
Evaluates the is_ready method on the next action and returns the result.
Definition: MockDistributedObject.hpp:911
type_of_current_state
std::string type_of_current_state(const boost::variant< Ts... > &variant) noexcept
Get the type name of the current state of the boost::variant.
Definition: BoostHelpers.hpp:85
TMPL.hpp
ActionTesting::MockDistributedObject::get_variant_box
auto & get_variant_box() noexcept
Returns the boost::variant of DataBoxes.
Definition: MockDistributedObject.hpp:370
std::is_base_of
gsl::not_null
Require a pointer to not be a nullptr
Definition: ReadSpecThirdOrderPiecewisePolynomial.hpp:13