Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
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
12namespace Utopia {
13
14namespace impl {
16template<class Container>
17using entity_t = typename Container::value_type::element_type;
18}
19
20
27enum class Shuffle {
29 on,
31 off
32};
33
35
46template<typename State, typename Rule, typename... Args>
48{
49private:
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
57public:
59 using type = std::invoke_result_t<
60 Rule,
61 typename std::remove_reference_t<Args>::reference...>;
62
63private:
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
74template<typename State, typename Rule, typename... Args>
76 typename rule_invoke_result<State, Rule, Args...>::type;
77
79
81template<typename State, typename Rule, typename... Args>
82constexpr bool is_void_rule ()
83{
84 return std::is_same_v<void, rule_invoke_result_t<State, Rule, Args...>>;
85}
86
88
93template<class Tuple, std::size_t... I>
94constexpr decltype(auto)
95make_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
112template<class Tuple>
113constexpr 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
125template<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>
140
142
156template<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>
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
182 using std::begin, std::end;
183
184 // Apply the rule
185 // NOTE: Capture by reference is fine because rule is a temporary
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
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
206template<Update mode,
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>
216void
224
226
243template<Update mode,
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>
253void
255 Rule&& rule,
256 const ContTarget& cont_target,
257 ContArgs&&... cont_args)
258{
259 // Create the input range zip iterators
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
282template<Update mode,
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>
293void
295 const ContTarget& cont_target,
296 RNG&& rng,
297 ContArgs&&... cont_args)
298{
301}
302
304
333template<Update mode,
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>
344void
346 Rule&& rule,
347 const ContTarget& cont_target,
348 RNG&& rng,
349 ContArgs&&... cont_args)
350{
351 // Create the input range zip iterators
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
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 {
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.
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
425template<
426 class Rule,
427 class Container,
428 bool sync=impl::entity_t<Container>::is_sync()>
429std::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
463template<
464 bool shuffle=true,
465 class Rule,
466 class Container,
467 bool sync=impl::entity_t<Container>::is_sync()>
468std::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
494template<
495 bool shuffle=true,
496 class Rule,
497 class Container,
498 class RNG,
499 bool sync=impl::entity_t<Container>::is_sync()>
500std::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
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
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()
@ sync
Synchronous update.
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