Utopia  2
Framework for studying models of complex & adaptive systems.
data_manager.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_DATAIO_DATA_MANAGER_HH
2 #define UTOPIA_DATAIO_DATA_MANAGER_HH
3 
4 // stl includes for having shared_ptr, hashmap, vector, swap
5 #include <algorithm>
6 #include <memory>
7 #include <stdexcept>
8 #include <type_traits>
9 #include <unordered_map>
10 #include <vector>
11 
12 // utopia includes
13 #include "../../core/logging.hh"
14 #include "../../core/type_traits.hh"
15 #include "../cfg_utils.hh"
16 #include "defaults.hh"
17 #include "utils.hh"
18 
19 namespace Utopia
20 {
21 namespace DataIO
22 {
23 
24 using namespace Utopia::Utils;
25 
26 // Doxygen group for DataManager ++++++++++++++++++++++++++++++++++++++++++++++
68 template < class TaskType,
69  class DeciderType,
70  class TriggerType,
71  class ExecutionProcessType >
73 {
74  using Task = TaskType;
75  using Decider = DeciderType;
76  using Trigger = TriggerType;
77  using ExecutionProcess = ExecutionProcessType;
78 };
79 
129 template < class Traits >
131 {
132  public:
135 
137  using Task = typename Traits::Task;
138 
140  using Decider = typename Traits::Decider;
141 
143  using Trigger = typename Traits::Trigger;
144 
146  using ExecutionProcess = typename Traits::ExecutionProcess;
147 
149  using TaskMap = std::unordered_map< std::string, std::shared_ptr< Task > >;
150 
152  using OrderedTaskMap = std::map< std::string, std::shared_ptr< Task > >;
153 
155  using DeciderMap =
156  std::unordered_map< std::string, std::shared_ptr< Decider > >;
157 
160  std::map< std::string, std::shared_ptr< Decider > >;
161 
163  using TriggerMap =
164  std::unordered_map< std::string, std::shared_ptr< Trigger > >;
165 
168  std::map< std::string, std::shared_ptr< Trigger > >;
169 
171  using AssocsMap =
172  std::unordered_map< std::string, std::vector< std::string > >;
173 
174  protected:
178  std::shared_ptr< spdlog::logger > _log;
179 
184 
189 
194 
200 
206 
212 
213  // .. Construction Helpers ................................................
214 
215  template < typename ObjMap, typename KnownObjectsMap >
216  ObjMap
217  _setup_from_config(const Config& cfg, KnownObjectsMap&& known_objects)
218  {
219  _log->debug("Setting up name -> object map from config node ...");
220 
221  // Check whether the given configuration is valid
222  if (not cfg)
223  {
224  throw std::invalid_argument("Received a zombie node for the setup "
225  "of DataManager objects!");
226  }
227  else if (not cfg.IsMap())
228  {
229  throw std::invalid_argument("Expected a mapping for DataManager "
230  "object setup, got:\n" +
231  to_string(cfg));
232  }
233 
234  // The name -> object map that is to be populated
235  ObjMap map;
236  _log->debug("Configuring DataManager objects ... (container size: {})",
237  known_objects.size());
238 
239  // Go over the known objects and decide whether to retain them
240  // as they are or whether new objects need to be constructed from the
241  // known ones
242  // Depending on the name of the object, the name given in the
243  // configuration, and the configuration itself, decide on
244  // whether a new object needs to be constructed or can be
245  // retained from the known objects.
246  // If the configuration specifies a type, use that information
247  // to either construct a new object from given arguments or
248  // copy-construct one from an existing object
249  for (const auto& node_pair : cfg)
250  {
251  // Unpack the (key node, value node) pair, i.e. the name of the
252  // object that is to be configured and the corresponding
253  // configuration node
254  const auto cfg_name = node_pair.first.as< std::string >();
255  const auto& obj_cfg = node_pair.second;
256 
257  const auto type_name = get_as< std::string >("type", obj_cfg);
258 
259  _log->debug("Attempting to build {} of type {} from config",
260  cfg_name,
261  type_name);
262 
263  if (known_objects.find(type_name) == known_objects.end())
264  {
265  throw std::invalid_argument("Error for node " + cfg_name +
266  ": No 'type' node given");
267  }
268  else
269  {
270  if (obj_cfg["args"])
271  {
272  _log->debug(" ... using given arguments from config ...");
273  map[cfg_name] =
274  known_objects[type_name](); // default construct
275  map[cfg_name]->set_from_cfg(obj_cfg["args"]);
276  }
277  else
278  {
279 
280  // if no args is given, do a default build because this
281  // not all of the deciders/triggers need an args node
282  _log->debug("... constructing {} of type {} without "
283  "config args because no node 'args' is "
284  "given for it in the config.",
285  cfg_name,
286  type_name);
287 
288  map[cfg_name] = known_objects[type_name]();
289  }
290  }
291  }
292 
293  return map;
294  }
295 
304  TaskMap
305  _filter_tasks_from_config(const Config& task_cfg, TaskMap& tasks)
306  {
307  // map to use in the end
308  TaskMap map;
309 
310  if (not task_cfg)
311  {
312  throw std::invalid_argument(
313  "Error: data_manager config node needs to contain a node "
314  "'tasks' which it apparently is missing ");
315  }
316  if (not task_cfg.IsMap())
317  {
318  throw std::invalid_argument("Expected a mapping for DataManager "
319  "task filtering, got:\n" +
320  to_string(task_cfg));
321  }
322 
323  for (const auto& node_pair : task_cfg)
324  {
325  // Unpack the (key node, value node) pair, i.e. the name of the
326  // object that is to be configured and the corresponding
327  // configuration node
328  const auto cfg_name = node_pair.first.as< std::string >();
329  const auto& obj_cfg = node_pair.second;
330 
331  _log->debug("Investigating task {} and checking if it is active ",
332  cfg_name);
333 
334  if (get_as< bool >("active", obj_cfg))
335  {
336 
337  _log->debug("Task '{}' was marked as active; will be kept.",
338  cfg_name);
339 
340  if (tasks.find(cfg_name) == tasks.end())
341  {
342  throw std::invalid_argument(
343  "Error, no task supplied to the datamanager is named " +
344  cfg_name);
345  }
346  else
347  {
348  map[cfg_name] = tasks[cfg_name];
349  }
350  }
351  else
352  {
353  // skip inactive tasks
354  _log->debug("Task '{}' was marked as not active; skipping.",
355  cfg_name);
356  continue;
357  }
358  }
359 
360  return map;
361  }
362 
378  template < typename DTMap >
379  AssocsMap
381  DTMap&& dt_map,
382  std::string lookup_key)
383  {
384  AssocsMap map;
385 
386  // error checking regarding the task_cfg node is done in the
387  // _filter_tasks function, and hence does not need to be repeated here
388 
389  _log->debug("Building task to {} associations from given config ...",
390  lookup_key);
391 
392  // Iterate over the given configuration node, pull out the name of the
393  // task and the name of the associated decider/trigger, and put the
394  // AssocsMap together from this
395  for (const auto& node_pair : task_cfg)
396  {
397  // Unpack the (key node, value node) pair
398  const auto task_name = node_pair.first.as< std::string >();
399  const auto& task_cfg = node_pair.second;
400 
401  // Find out if active; true by default
402  const auto active = get_as< bool >("active", task_cfg, true);
403 
404  // Associate only if active
405  if (not active)
406  {
407  _log->debug("Task '{}' was marked as not active; skipping.",
408  task_name);
409  continue;
410  }
411 
412  // Get the name of the trigger or decider to associate to
413 
414  const auto dt_to_associate_to =
415  get_as< std::string >(lookup_key, task_cfg);
416 
417  // Find erroneous config namings for deciders/triggers
418  if (dt_map.find(dt_to_associate_to) == dt_map.end())
419  {
420  this->_log->info(" Error for decider/trigger: {}",
421  dt_to_associate_to);
422  throw std::invalid_argument(
423  "Error when trying to associate tasks to deciders or "
424  "triggers: "
425  "Name in config does not match the name of a "
426  "decider/trigger known to the datamanager");
427  }
428  else
429  {
430  // dt_to_associate_to exists in the dt_map, we're good
431  map[dt_to_associate_to].push_back(task_name);
432 
433  _log->debug("Associating task '{}' to {} '{}'.",
434  task_name,
435  lookup_key,
436  dt_to_associate_to);
437  }
438  }
439 
440  // Use the helper function to build the actual association map
441  return map;
442  }
443 
444  public:
445  // -- Public interface ----------------------------------------------------
446 
455  template < class Model, typename... Args >
456  void
457  operator()(Model&& model, Args&&... args)
458  {
459  _execution_process(
460  *this, std::forward< Model >(model), std::forward< Args >(args)...);
461  }
462 
463  // .. Getters .............................................................
464 
470  DeciderMap&
472  {
473  return _deciders;
474  }
475 
481  TaskMap&
483  {
484  return _tasks;
485  }
486 
492  TriggerMap&
494  {
495  return _triggers;
496  }
497 
503  AssocsMap&
505  {
506  return _decider_task_map;
507  }
508 
514  AssocsMap&
516  {
517  return _trigger_task_map;
518  }
519 
525  const std::shared_ptr< spdlog::logger >&
526  get_logger() const
527  {
528  return _log;
529  }
530 
531  // .. Rule of Five ........................................................
532 
537  DataManager() = default;
538 
544  DataManager(const DataManager& other) = default;
545 
551  DataManager(DataManager&& other) = default;
552 
559  DataManager&
560  operator=(const DataManager& other) = default;
561 
568  DataManager&
569  operator=(DataManager&& other) = default;
570 
575  virtual ~DataManager() = default;
576 
577  // .. Constructors ........................................................
578 
601  const Config& cfg,
602  TaskMap tasks,
603  std::unordered_map< std::string,
604  std::function< std::shared_ptr< Decider >() > >
605  deciders,
606  std::unordered_map< std::string,
607  std::function< std::shared_ptr< Trigger >() > >
608  triggers,
609  ExecutionProcess execproc) :
610  // Get the global data manager logger
611  _log(spdlog::get("data_mngr")),
612  _tasks(_filter_tasks_from_config(cfg["tasks"], tasks)),
613  _deciders(_setup_from_config< DeciderMap >(cfg["deciders"], deciders)),
614  _triggers(_setup_from_config< TriggerMap >(cfg["triggers"], triggers)),
615  // Create maps: decider/trigger -> vector of task names
616  _decider_task_map(
617  _associate_from_config(cfg["tasks"], _deciders, "decider")),
618  _trigger_task_map(
619  _associate_from_config(cfg["tasks"], _triggers, "trigger")),
620  _execution_process(execproc)
621  {
622  // nothing remains to be done here
623  }
624 
644  OrderedDeciderMap deciders,
645  OrderedTriggerMap triggers,
646  ExecutionProcess execproc,
647  std::map< std::string, std::string > decider_task_assocs = {},
648  std::map< std::string, std::string > trigger_task_assocs = {}) :
649  // Get the global data manager logger
650  _log(spdlog::get("data_mngr")),
651  _tasks(TaskMap(tasks.begin(), tasks.end())),
652  _deciders(DeciderMap(deciders.begin(), deciders.end())),
653  _triggers(TriggerMap(triggers.begin(), triggers.end())),
654  // Create maps: decider/trigger -> vector of task names
655  _decider_task_map(_DMUtils::build_task_association_map< AssocsMap >(
656  tasks, deciders, decider_task_assocs)),
657  _trigger_task_map(_DMUtils::build_task_association_map< AssocsMap >(
658  tasks, triggers, trigger_task_assocs)),
659  _execution_process(execproc)
660  {
661  _log->info("DataManager setup with {} task(s), {} decider(s), and "
662  "{} trigger(s).",
663  _tasks.size(),
664  _decider_task_map.size(),
665  _trigger_task_map.size());
666  }
667 
668  // .. Helper Methods ......................................................
669 
675  void
677  {
678  if (this == &other)
679  {
680  return;
681  }
682 
683  using std::swap;
684  swap(_log, other._log);
685  swap(_tasks, other._tasks);
686  swap(_deciders, other._deciders);
687  swap(_triggers, other._triggers);
688  swap(_decider_task_map, other._decider_task_map);
689  swap(_trigger_task_map, other._trigger_task_map);
690  }
691 };
692 
697 // ++ Default DataManager +++++++++++++++++++++++++++++++++++++++++++++++++++++
698 
705 namespace Default
706 {
715 template < typename Model >
721 } // namespace Default
722 
727 } // namespace DataIO
728 } // namespace Utopia
729 
730 #endif // UTOPIA_DATAIO_DATAMANAGER_HH
Manage different tasks of writing out data from a source in a uniform yet flexible way....
Definition: data_manager.hh:131
ObjMap _setup_from_config(const Config &cfg, KnownObjectsMap &&known_objects)
Definition: data_manager.hh:217
std::unordered_map< std::string, std::shared_ptr< Decider > > DeciderMap
Map of decider names to decider functions.
Definition: data_manager.hh:156
typename Traits::Task Task
Task type, as defined in the traits.
Definition: data_manager.hh:137
DataManager()=default
Construct a new Data Manager object.
DeciderMap & get_deciders()
Get the container of decider objects.
Definition: data_manager.hh:471
virtual ~DataManager()=default
Destroy the Data Manager object.
DataManager & operator=(const DataManager &other)=default
Copy assignment.
DataManager(DataManager &&other)=default
Construct a new Data Manager object.
TaskMap _filter_tasks_from_config(const Config &task_cfg, TaskMap &tasks)
Check which tasks supplied to the datamanager are active and shall be retained, using the config node...
Definition: data_manager.hh:305
typename Traits::Trigger Trigger
Trigger type, as defined in the traits.
Definition: data_manager.hh:143
void operator()(Model &&model, Args &&... args)
Invoke the execution process.
Definition: data_manager.hh:457
void swap(DataManager &other)
Exchange the state of the caller with the argument 'other'.
Definition: data_manager.hh:676
std::map< std::string, std::shared_ptr< Task > > OrderedTaskMap
Same as TaskMap, but using std::map such that ordering is preserved.
Definition: data_manager.hh:152
ExecutionProcess _execution_process
Callable which tells how to utilize triggers, deciders, tasks to write data.
Definition: data_manager.hh:211
AssocsMap & get_decider_task_map()
Get the decider task map object.
Definition: data_manager.hh:504
AssocsMap _trigger_task_map
Mapping from trigger names to containers of names of tasks that use those triggers.
Definition: data_manager.hh:205
std::map< std::string, std::shared_ptr< Decider > > OrderedDeciderMap
Same as DeciderMap, but using std::map such that ordering is preserved.
Definition: data_manager.hh:160
DataManager(const DataManager &other)=default
Construct a new Data Manager object.
DataManager & operator=(DataManager &&other)=default
Move assignment.
TriggerMap & get_triggers()
Get the container of trigger objects.
Definition: data_manager.hh:493
DataManager(const Config &cfg, TaskMap tasks, std::unordered_map< std::string, std::function< std::shared_ptr< Decider >() > > deciders, std::unordered_map< std::string, std::function< std::shared_ptr< Trigger >() > > triggers, ExecutionProcess execproc)
Construct DataManager using a config node.
Definition: data_manager.hh:600
TaskMap & get_tasks()
Get the container of task objects.
Definition: data_manager.hh:482
typename Traits::ExecutionProcess ExecutionProcess
Execution process type, as defined in the traits.
Definition: data_manager.hh:146
TaskMap _tasks
Stores (name, task) pairs in an unordered map.
Definition: data_manager.hh:183
std::shared_ptr< spdlog::logger > _log
Used to inform about progress of DataManager operations.
Definition: data_manager.hh:178
std::unordered_map< std::string, std::vector< std::string > > AssocsMap
Map of decider/task names to a collection of task names.
Definition: data_manager.hh:172
AssocsMap _associate_from_config(const Config &task_cfg, DTMap &&dt_map, std::string lookup_key)
Given a configuration, builds an association map.
Definition: data_manager.hh:380
std::unordered_map< std::string, std::shared_ptr< Task > > TaskMap
Map of task names to shared pointers of Tasks; supporting polymorphism.
Definition: data_manager.hh:149
const std::shared_ptr< spdlog::logger > & get_logger() const
Get the logger used in this DataManager.
Definition: data_manager.hh:526
AssocsMap _decider_task_map
Mapping from deciders names to containers of names of tasks that use those deciders.
Definition: data_manager.hh:199
typename Traits::Decider Decider
Decider type, as defined in the traits.
Definition: data_manager.hh:140
DataManager(OrderedTaskMap tasks, OrderedDeciderMap deciders, OrderedTriggerMap triggers, ExecutionProcess execproc, std::map< std::string, std::string > decider_task_assocs={}, std::map< std::string, std::string > trigger_task_assocs={})
Construct DataManager without config node from passed mappings only. If the last two arguments are no...
Definition: data_manager.hh:643
AssocsMap & get_trigger_task_map()
Get the trigger task map object.
Definition: data_manager.hh:515
TriggerMap _triggers
Stores (name, triggerfunction) pairs in an unordered map.
Definition: data_manager.hh:193
DeciderMap _deciders
Stores (name, deciderfunction) pairs in an unordered map.
Definition: data_manager.hh:188
std::unordered_map< std::string, std::shared_ptr< Trigger > > TriggerMap
Map of trigger names to trigger functions.
Definition: data_manager.hh:164
std::map< std::string, std::shared_ptr< Trigger > > OrderedTriggerMap
Same as TriggerMap, but using std::map such that ordering is preserved.
Definition: data_manager.hh:168
Base class interface for Models using the CRT Pattern.
Definition: model.hh:112
YAML::Node Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:71
std::string to_string(const Config &node)
Given a config node, returns a string representation of it.
Definition: cfg_utils.hh:110
Decider< Model > Trigger
Definition: defaults.hh:439
void swap(WriteTask< BGB, DW, DB, AWG, AWD > &lhs, WriteTask< BGB, DW, DB, AWG, AWD > &rhs)
Swaps the state of lhs and rhs.
Definition: write_task.hh:240
Definition: metaprogramming.hh:43
Definition: agent.hh:11
Type traits for the DataManager This allows to specify custom types for the DataManager....
Definition: data_manager.hh:73
ExecutionProcessType ExecutionProcess
Definition: data_manager.hh:77
TaskType Task
Definition: data_manager.hh:74
DeciderType Decider
Definition: data_manager.hh:75
TriggerType Trigger
Definition: data_manager.hh:76
Common interface for all deciders (and triggers, for that matter). Every decider/Trigger must inherit...
Definition: defaults.hh:166
Functor representing what is considered the most widely used execution process for writing data.
Definition: defaults.hh:84