Utopia  2
Framework for studying models of complex & adaptive systems.
apply.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_CORE_APPLY_HH
2 #define UTOPIA_CORE_APPLY_HH
3 
4 #include <type_traits>
5 #include <vector>
6 
7 #include "parallel.hh"
8 #include "state.hh"
9 #include "zip.hh"
10 
11 
12 namespace Utopia {
13 
14 namespace impl {
16 template<class Container>
17 using entity_t = typename Container::value_type::element_type;
18 }
19 
20 
27 enum class Shuffle {
29  on,
31  off
32 };
33 
35 
46 template<typename State, typename Rule, typename... Args>
48 {
49 private:
50  // Check if rule can be invoked
51  static_assert(
52  std::is_invocable_v<Rule,
53  typename std::remove_reference_t<Args>::reference...>,
54  "Cannot invoke the Rule with the given container elements as arguments!"
55  " Please check the rule signature!");
56 
57 public:
59  using type = std::invoke_result_t<
60  Rule,
61  typename std::remove_reference_t<Args>::reference...>;
62 
63 private:
64  // Check that rule returns type that can be converted to state
65  static_assert(
66  std::is_same_v<type, void> or std::is_convertible_v<type, State>,
67  "Invoking the rule must return a type that can be converted to the "
68  "Entity state type!");
69 };
70 
72 
74 template<typename State, typename Rule, typename... Args>
76  typename rule_invoke_result<State, Rule, Args...>::type;
77 
79 
81 template<typename State, typename Rule, typename... Args>
82 constexpr bool is_void_rule ()
83 {
84  return std::is_same_v<void, rule_invoke_result_t<State, Rule, Args...>>;
85 }
86 
88 
93 template<class Tuple, std::size_t... I>
94 constexpr decltype(auto)
95 make_tuple_from_tuple_impl(Tuple&& t, std::index_sequence<I...>)
96 {
97  return std::make_tuple(std::get<I>(std::forward<Tuple>(t))...);
98 }
99 
101 
112 template<class Tuple>
113 constexpr decltype(auto)
115 {
117  std::forward<Tuple>(t),
118  std::make_index_sequence<
119  std::tuple_size_v<std::remove_reference_t<Tuple>>>{});
120 }
121 
122 // -- Manually-managed state updates ------------------------------------------
123 
125 template<Update mode,
126  class Rule,
127  class ContTarget,
128  class... ContArgs,
129  typename std::enable_if_t<mode == Update::sync, int> = 0,
130  typename std::enable_if_t<
131  impl::entity_t<ContTarget>::mode
132  == Update::manual, int> = 0>
133 void apply_rule(Rule&& rule,
134  const ContTarget& cont_target,
135  ContArgs&&... cont_args)
136 {
137  apply_rule<mode>(
138  Utopia::ExecPolicy::seq, rule, cont_target, cont_args...);
139 }
140 
142 
156 template<Update mode,
157  class Rule,
158  class ContTarget,
159  class... ContArgs,
160  typename std::enable_if_t<mode == Update::sync, int> = 0,
161  typename std::enable_if_t<
162  impl::entity_t<ContTarget>::mode
163  == Update::manual, int> = 0>
164 void apply_rule(const Utopia::ExecPolicy policy,
165  Rule&& rule,
166  const ContTarget& cont_target,
167  ContArgs&&... cont_args)
168 {
169  using State = typename impl::entity_t<ContTarget>::State;
170 
171  // Make sure to call `invoke_result_type` at least for a conversion check
172  static_assert(not is_void_rule<State, Rule, ContTarget, ContArgs...>(),
173  "Cannot apply void rules in a synchronous update!");
174 
175  // Initialize the state cache
176  // NOTE: Copy one element to avoid requirement of default initialization
177  std::vector<State> state_cache(cont_target.size(),
178  cont_target.front()->state);
179 
180  // create the input zip iterators
181  auto range = Itertools::zip(cont_target, cont_args...);
182  using std::begin, std::end;
183 
184  // Apply the rule
185  // NOTE: Capture by reference is fine because rule is a temporary
186  std::transform(policy,
187  begin(range),
188  end(range),
189  begin(state_cache),
190  [&rule](auto&& args) {
191  return std::apply(rule,
192  std::forward<decltype(args)>(args));
193  });
194 
195  // move the cache
196  auto move_range = Itertools::zip(cont_target, state_cache);
198  begin(move_range),
199  end(move_range),
200  [](auto&& tpl) {
201  std::get<0>(tpl)->state = std::move(std::get<1>(tpl));
202  });
203 }
204 
206 template<Update mode,
207  Shuffle shuffle = Shuffle::on,
208  class Rule,
209  class ContTarget,
210  class... ContArgs,
211  typename std::enable_if_t<mode == Update::async, int> = 0,
212  typename std::enable_if_t<
213  impl::entity_t<ContTarget>::mode
214  == Update::manual, int> = 0,
215  typename std::enable_if_t<shuffle == Shuffle::off, int> = 0>
216 void
217 apply_rule(Rule&& rule,
218  const ContTarget& cont_target,
219  ContArgs&&... cont_args)
220 {
221  apply_rule<mode, shuffle>(
222  Utopia::ExecPolicy::seq, rule, cont_target, cont_args...);
223 }
224 
226 
243 template<Update mode,
244  Shuffle shuffle = Shuffle::on,
245  class Rule,
246  class ContTarget,
247  class... ContArgs,
248  typename std::enable_if_t<mode == Update::async, int> = 0,
249  typename std::enable_if_t<
250  impl::entity_t<ContTarget>::mode
251  == Update::manual, int> = 0,
252  typename std::enable_if_t<shuffle == Shuffle::off, int> = 0>
253 void
255  Rule&& rule,
256  const ContTarget& cont_target,
257  ContArgs&&... cont_args)
258 {
259  // Create the input range zip iterators
260  auto range = Itertools::zip(cont_target, cont_args...);
261  using std::begin, std::end;
262 
263  // Apply the rule, distinguishing by return type of the rule
264  using State = typename impl::entity_t<ContTarget>::State;
265  if constexpr (is_void_rule<State, Rule, ContTarget, ContArgs...>()) {
266  // Is a void-rule; no need to set the return value
267  std::for_each(policy, begin(range), end(range), [&rule](auto&& args) {
268  std::apply(rule,
269  std::forward<decltype(args)>(args));
270  });
271  }
272  else {
273  std::for_each(policy, begin(range), end(range), [&rule](auto&& args) {
274  auto& cell = std::get<0>(args);
275  cell->state = std::apply(rule,
276  std::forward<decltype(args)>(args));
277  });
278  }
279 }
280 
282 template<Update mode,
283  Shuffle shuffle = Shuffle::on,
284  class Rule,
285  class ContTarget,
286  class RNG,
287  class... ContArgs,
288  typename std::enable_if_t<mode == Update::async, int> = 0,
289  typename std::enable_if_t<
290  impl::entity_t<ContTarget>::mode
291  == Update::manual, int> = 0,
292  typename std::enable_if_t<shuffle == Shuffle::on, int> = 0>
293 void
294 apply_rule(Rule&& rule,
295  const ContTarget& cont_target,
296  RNG&& rng,
297  ContArgs&&... cont_args)
298 {
299  apply_rule<mode, shuffle>(
300  Utopia::ExecPolicy::seq, rule, cont_target, rng, cont_args...);
301 }
302 
304 
333 template<Update mode,
334  Shuffle shuffle = Shuffle::on,
335  class Rule,
336  class ContTarget,
337  class RNG,
338  class... ContArgs,
339  typename std::enable_if_t<mode == Update::async, int> = 0,
340  typename std::enable_if_t<
341  impl::entity_t<ContTarget>::mode
342  == Update::manual, int> = 0,
343  typename std::enable_if_t<shuffle == Shuffle::on, int> = 0>
344 void
346  Rule&& rule,
347  const ContTarget& cont_target,
348  RNG&& rng,
349  ContArgs&&... cont_args)
350 {
351  // Create the input range zip iterators
352  auto range = Itertools::zip(cont_target, cont_args...);
353  using std::begin, std::end;
354 
355  // NOTE: std::shuffle requires the container elements to be swappable.
356  // This is not the case for tuples of T& so we need a container of
357  // tuples of std::reference_wrapper<T>. For this to work, we must
358  // propagate the const-ness of the container to the wrapper.
359  // When applying the rule, we must convert the reference wrappers back
360  // to regular references, which can be done with std::make_tuple.
361  using Tuple = std::tuple<
362  // 'ContTarget' is always const
363  std::reference_wrapper<
364  const typename std::remove_reference_t<ContTarget>::value_type>,
365  // Universal references to 'ContArgs' *can* be const
366  std::conditional_t<
367  // Check if container is const
368  std::is_const_v<std::remove_reference_t<ContArgs>>,
369  // If true:
370  std::reference_wrapper<
371  const typename std::remove_reference_t<ContArgs>::value_type>,
372  // If false:
373  std::reference_wrapper<
374  typename std::remove_reference_t<ContArgs>::value_type>>...>;
375 
376  // Create a container of tuples of arguments and shuffle it
377  std::vector<Tuple> args_container(begin(range), end(range));
378  std::shuffle(
379  begin(args_container), end(args_container), std::forward<RNG>(rng));
380 
381  // Apply the rule, distinguishing by return type of the rule
382  using State = typename impl::entity_t<ContTarget>::State;
383  if constexpr(is_void_rule<State, Rule, ContTarget, ContArgs...>())
384  {
385  // Is a void-rule; no need to set the return value
386  std::for_each(policy,
387  begin(args_container),
388  end(args_container),
389  [&rule](auto&& args) {
390  // NOTE: 'args' is tuple of reference_wrappers, and we
391  // want a tuple of regular references.
392  std::apply(rule,
394  std::forward<decltype(args)>(args)));
395  });
396  }
397  else
398  {
399  std::for_each(policy,
400  begin(args_container),
401  end(args_container),
402  [&rule](auto&& args) {
403  // NOTE: 'args' is tuple of reference_wrappers, and we
404  // want a tuple of regular references.
405  auto tpl = make_tuple_from_tuple(
406  std::forward<decltype(args)>(args));
407  auto& entity = std::get<0>(tpl);
408  // NOTE: Do not move 'tpl' as this invalidates ref
409  // 'entity'
410  entity->state = std::apply(rule, tpl);
411  });
412  }
413 }
414 
415 
416 // -- Synchronous state updates -----------------------------------------------
418 
425 template<
426  class Rule,
427  class Container,
428  bool sync=impl::entity_t<Container>::is_sync()>
429 std::enable_if_t<sync, void>
430  apply_rule(const Rule& rule, const Container& container)
431 {
432  // Apply the rule, distinguishing by return type of the rule
433  using ReturnType =
434  std::invoke_result_t<Rule, typename Container::value_type>;
435 
436  if constexpr(std::is_same_v<ReturnType, void>) {
438  std::begin(container), std::end(container),
439  [&rule](const auto& entity){ rule(entity); }
440  );
441  }
442  else {
444  std::begin(container), std::end(container),
445  [&rule](const auto& entity){ entity->state_new() = rule(entity); }
446  );
447  }
448 
449  // Let the entity update its state, moving it from the buffer state to the
450  // actual state and potentially applying other updating operations
452  std::begin(container), std::end(container),
453  [](const auto& entity){ entity->update(); }
454  );
455 }
456 
457 
458 // -- Asynchronous state updates ----------------------------------------------
460 
463 template<
464  bool shuffle=true,
465  class Rule,
466  class Container,
467  bool sync=impl::entity_t<Container>::is_sync()>
468 std::enable_if_t<not sync && not shuffle, void>
469  apply_rule(const Rule& rule, const Container& container)
470 {
471  // Apply the rule, distinguishing by return type of the rule
472  using ReturnType =
473  std::invoke_result_t<Rule, typename Container::value_type>;
474 
475  if constexpr(std::is_same_v<ReturnType, void>) {
477  std::begin(container), std::end(container),
478  [&rule](const auto& entity){ rule(entity); }
479  );
480  }
481  else {
483  std::begin(container), std::end(container),
484  [&rule](const auto& entity){ entity->state() = rule(entity); }
485  );
486  }
487 }
488 
489 
491 
494 template<
495  bool shuffle=true,
496  class Rule,
497  class Container,
498  class RNG,
499  bool sync=impl::entity_t<Container>::is_sync()>
500 std::enable_if_t<not sync && shuffle, void>
501  apply_rule(const Rule& rule, const Container& container, RNG&& rng)
502 {
503  std::remove_const_t<Container> container_shuffled(container);
504  std::shuffle(std::begin(container_shuffled),
505  std::end(container_shuffled),
506  std::forward<RNG>(rng));
507 
508  // Apply the rule, distinguishing by return type of the rule
509  using ReturnType =
510  std::invoke_result_t<Rule, typename Container::value_type>;
511 
512  if constexpr(std::is_same_v<ReturnType, void>)
513  {
515  std::begin(container_shuffled), std::end(container_shuffled),
516  [&rule](const auto& entity){ rule(entity); }
517  );
518  }
519  else
520  {
522  std::begin(container_shuffled), std::end(container_shuffled),
523  [&rule](const auto& entity){ entity->state() = rule(entity); }
524  );
525  }
526 }
527 
532 } // namespace Utopia
533 
534 #endif // UTOPIA_CORE_APPLY_HH
A range defined by instances of ZipIterator.
Definition: zip.hh:410
Helper class for checking rule signatures and return types.
Definition: apply.hh:48
std::invoke_result_t< Rule, typename std::remove_reference_t< Args >::reference... > type
Report rule invoke result.
Definition: apply.hh:61
OutputIt transform(const Utopia::ExecPolicy policy, InputIt first1, InputIt last1, OutputIt d_first, UnaryOperation unary_op)
Apply a unary operator to a range and store the result in a new range.
Definition: parallel.hh:368
void for_each(const Utopia::ExecPolicy policy, InputIt first, InputIt last, UnaryFunction f)
Apply a function to a range.
Definition: parallel.hh:346
decltype(auto) range(const Graph &g)
Get the iterator range over selected graph entities.
Definition: iterator.hh:149
ExecPolicy
Runtime execution policies.
Definition: parallel.hh:60
@ seq
Sequential (i.e., regular) execution.
Definition: parallel.hh:66
@ par_unseq
SIMD execution on multiple threads.
Definition: parallel.hh:69
constexpr decltype(auto) make_tuple_from_tuple(Tuple &&t)
Helper function to create a tuple from a tuple.
Definition: apply.hh:114
constexpr decltype(auto) make_tuple_from_tuple_impl(Tuple &&t, std::index_sequence< I... >)
Helper function to create a tuple from a tuple using an index sequence.
Definition: apply.hh:95
Shuffle
Switch for enabling/disabling shuffling the cells for asynchronous updates.
Definition: apply.hh:27
constexpr bool is_void_rule()
Helper function to check if the rule returns void
Definition: apply.hh:82
Update
Update modes when applying rules.
Definition: state.hh:20
typename rule_invoke_result< State, Rule, Args... >::type rule_invoke_result_t
Helper definition to query the rule result type.
Definition: apply.hh:76
void apply_rule(Rule &&rule, const ContTarget &cont_target, ContArgs &&... cont_args)
Sequential overload.
Definition: apply.hh:133
@ off
Immediately apply the rule sequentially.
@ on
Shuffle the container before applying the rule sequentially.
@ manual
User chooses update type when calling apply_rule()
auto end(zip< Containers... > &zipper)
end function like std::end
Definition: zip.hh:550
auto begin(zip< Containers... > &zipper)
Begin function like std::begin.
Definition: zip.hh:537
std::mt19937 rng
– Type definitions ----------------------------------------------------—
Definition: test_revision.cc:17
typename Container::value_type::element_type entity_t
Return the element type of any container holding pointers to entities.
Definition: apply.hh:17
Definition: agent.hh:11
Definition: parallel.hh:235