Utopia  2
Framework for studying models of complex & adaptive systems.
factory.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_DATAIO_FACTORY_HH
2 #define UTOPIA_DATAIO_FACTORY_HH
3 
4 // stl includes for having shared_ptr, hashmap, vector, swap
5 #include <algorithm>
6 #include <memory>
7 #include <unordered_map>
8 #include <vector>
9 
10 // boost includes
11 #include <boost/graph/adjacency_list.hpp>
12 #include <boost/graph/adjacency_matrix.hpp>
13 #include <boost/graph/graph_traits.hpp>
14 #include <boost/graph/properties.hpp>
15 
16 #include <boost/hana/ext/std/tuple.hpp>
17 #include <boost/hana/integral_constant.hpp>
18 #include <boost/hana/remove_at.hpp>
19 #include <boost/hana/transform.hpp>
20 
21 // utopia includes
22 #include "../../core/logging.hh"
23 #include "../../core/type_traits.hh"
24 #include "../cfg_utils.hh"
25 #include "data_manager.hh"
26 #include "defaults.hh"
27 #include "utils.hh"
28 
29 namespace Utopia
30 {
31 namespace DataIO
32 {
33 
65 {
66  std::string path;
67  std::vector< hsize_t > dataset_capacity = {};
68  std::vector< hsize_t > dataset_chunksize = {};
70 };
71 
83 enum struct TypeTag
84 {
85  plain,
90 };
91 
92 // Making the factory a functor allows for the separation of template parameters
93 // which are user defined (model, graphtag) and automatically determined ones,
94 // which are given to the call operator.
95 
103 template < class Model, TypeTag typetag = TypeTag::plain >
105 {
106  private:
107  static std::unordered_map<
108  std::string,
109  std::function< std::string(std::string, Model&) > >
111 
129  template < class Func, class AttributeHandle >
130  Func
131  _make_attribute_writer(AttributeHandle&& attr)
132  {
133  using AttrType = std::decay_t< AttributeHandle >;
134  Func writer{};
135 
136  if constexpr (std::is_same_v< AttrType, Nothing >)
137  {
138  // do nothing here, because nothing shall be done ;)
139  }
140  else if constexpr (Utopia::Utils::has_static_size_v< AttrType >)
141  {
142 
143  using std::get;
144  using Nametype =
145  std::decay_t< std::tuple_element_t< 0, AttrType > >;
146 
147  // if the first thing held by the Dataset_attribute tuplelike is
148  // not convertible to stirng, and hence cannot be used to name
149  // the thing, error is thrown.
150  static_assert(
151  Utils::is_string_v< Nametype >,
152  "Error, first entry of Dataset_attribute must be a string "
153  "naming the attribute");
154 
155  // check if the $ indicating string interpolation like behavior
156  // is found. If yes, invoke path modifier, else leave as is
157  std::string name = get< 0 >(attr);
158  auto pos = name.find('$'); // find indicator
159 
160  if (pos != std::string::npos)
161  {
162  auto path_builder = _modifiers[name.substr(pos + 1)];
163  std::string new_path = name.substr(0, pos);
164 
165  writer = [attr, path_builder, new_path](auto&& hdfobject,
166  auto&& m) -> void {
167  hdfobject->add_attribute(path_builder(new_path, m),
168  get< 1 >(attr));
169  };
170  }
171  else
172  {
173  writer = [attr](auto&& hdfobject, auto &&) -> void {
174  hdfobject->add_attribute(get< 0 >(attr),
175  get< 1 >(attr));
176  };
177  }
178  }
179  else
180  {
181  static_assert(
182  Utils::is_callable_v< AttrType >,
183  "Error, if the given attribute argument is not a tuple/pair "
184  "and not 'Nothing', it has to be a function");
185 
186  writer = attr;
187  }
188 
189  return writer;
190  }
191 
202  {
203  Default::DefaultBuilder< Model > dataset_builder;
204 
205  auto pos = dataset_descriptor.path.find('$'); // find indicator
206 
207  // $ indicates that string interpolation shall be used
208  if (pos != std::string::npos)
209  {
210  // get the path builder corresponding to the flag extracted after
211  // indicator
212  auto path_builder =
213  _modifiers[dataset_descriptor.path.substr(pos + 1)];
214 
215  std::string new_path = dataset_descriptor.path.substr(0, pos);
216  // put the latter into the dataset builder
217  dataset_builder =
218  [dataset_descriptor, path_builder, new_path](
219  auto&& group,
220  auto&& model) -> std::shared_ptr< HDFDataset > {
221  return group->open_dataset(
222  path_builder(new_path, model),
223  dataset_descriptor.dataset_capacity,
224  dataset_descriptor.dataset_chunksize,
225  dataset_descriptor.dataset_compression);
226  };
227  }
228  else // no string to interpolate something
229  {
230 
231  dataset_builder =
232  [dataset_descriptor](
233  std::shared_ptr< HDFGroup >& group,
234  Model&) -> std::shared_ptr< HDFDataset > {
235 
236  return group->open_dataset(
237  dataset_descriptor.path,
238  dataset_descriptor.dataset_capacity,
239  dataset_descriptor.dataset_chunksize,
240  dataset_descriptor.dataset_compression);
241  };
242  }
243  return dataset_builder;
244  }
245 
259  template < class SourceGetter, class Getter >
261  _adapt_graph_writer(SourceGetter&& get_source, Getter&& getter)
262  {
264 
265  using GraphType = std::decay_t<
266  std::invoke_result_t< std::decay_t< SourceGetter >, Model& > >;
267 
268  if constexpr (typetag == TypeTag::vertex_property)
269  {
270  writer = [&getter, &get_source](
271  std::shared_ptr< HDFDataset >& dataset,
272  Model& m) -> void {
273  // Collect some information on the graph
274  auto& graph = get_source(m);
275 
276  // Make vertex iterators
277  typename GraphType::vertex_iterator v, v_end;
278  boost::tie(v, v_end) = boost::vertices(graph);
279 
280  dataset->write(v, v_end, [&getter, &graph](auto&& vd) {
281  return getter(graph[vd]);
282  });
283  };
284  }
285  else if constexpr (typetag == TypeTag::edge_property)
286  {
287  writer = [&getter, &get_source](
288  std::shared_ptr< HDFDataset >& dataset,
289  Model& m) -> void {
290  auto& graph = get_source(m);
291 
292  // Make edge iterators
293  typename GraphType::edge_iterator v, v_end;
294  boost::tie(v, v_end) = boost::edges(graph);
295 
296  dataset->write(v, v_end, [&getter, &graph](auto&& vd) {
297  return getter(graph[vd]);
298  });
299  };
300  }
301  else if constexpr (typetag == TypeTag::vertex_descriptor and
302  not std::is_same_v< std::decay_t< Getter >,
303  std::function< void() > >)
304  {
305 
306  writer = [&getter, &get_source](
307  std::shared_ptr< HDFDataset >& dataset,
308  Model& m) -> void {
309  auto& graph = get_source(m);
310 
311  // Make edge iterators
312  typename GraphType::vertex_iterator v, v_end;
313  boost::tie(v, v_end) = boost::vertices(graph);
314  dataset->write(v, v_end, [&getter, &graph](auto&& vd) {
315  return getter(graph, vd);
316  });
317  };
318  }
319  else if constexpr (typetag == TypeTag::edge_descriptor and
320  not std::is_same_v< std::decay_t< Getter >,
321  std::function< void() > >)
322  {
323  writer = [&getter, &get_source](
324  std::shared_ptr< HDFDataset >& dataset,
325  Model& m) -> void {
326  auto& graph = get_source(m);
327 
328  // Make edge iterators
329  typename GraphType::edge_iterator v, v_end;
330  boost::tie(v, v_end) = boost::edges(graph);
331 
332  dataset->write(v, v_end, [&getter, &graph](auto&& vd) {
333  return getter(graph, vd);
334  });
335  };
336  }
337  // for writing pure graphs
338 
339  // vertex
340  else if constexpr (std::is_same_v< std::decay_t< Getter >,
341  std::function< void() > > and
342  typetag == TypeTag::vertex_descriptor)
343  {
344  writer = [&get_source](
345  std::shared_ptr< HDFDataset >& dataset,
346  Model& m) -> void {
347  auto& graph = get_source(m);
348 
349  auto [v, v_end] = boost::vertices(graph);
350  dataset->write(v, v_end, [&](auto&& vd) {
351  return boost::get(boost::vertex_index_t(), graph, vd);
352  });
353  };
354  }
355  // edges
356  else if constexpr (std::is_same_v< std::decay_t< Getter >,
357  std::function< void() > > and
358  typetag == TypeTag::edge_descriptor)
359  {
360  writer =
361  [get_source](std::shared_ptr< HDFDataset >& dataset,
362  Model& m) -> void {
363  auto& graph = get_source(m);
364 
365  auto [e, e_end] = boost::edges(graph);
366 
367  dataset->write(e, e_end, [&](auto&& ed) {
368  return boost::get(boost::vertex_index_t(),
369  graph,
370  boost::source(ed, graph));
371  });
372 
373  dataset->write(e, e_end, [&](auto&& ed) {
374  return boost::get(boost::vertex_index_t(),
375  graph,
376  boost::target(ed, graph));
377  });
378  };
379  }
380  else
381  {
382  // user fucked up
383  throw std::invalid_argument("Unknown ObjectType.");
384  }
385 
386  return writer;
387  }
388 
389  public:
390 
448  template < class SourceGetter,
449  class Getter,
450  class Group_attribute = Nothing,
451  class Dataset_attribute = Nothing >
452  std::pair< std::string,
453  std::shared_ptr< Default::DefaultWriteTask< Model > > >
455  // is in config
456  std::string name,
457  std::string basegroup_path,
458  DatasetDescriptor dataset_descriptor,
459  // has to be given
460  SourceGetter&& get_source,
461  Getter&& getter,
462  Group_attribute&& group_attribute = Nothing{},
463  Dataset_attribute&& dataset_attribute = Nothing{})
464  {
465  // return type of get_source when used with model
466  using Container =
467  std::decay_t< decltype(get_source(std::declval< Model& >())) >;
468 
469  // asserts that the SourceGetter-type is really a container type.
470  static_assert(
471  Utils::is_container_v< std::decay_t< Container > > or
472  Utils::is_graph_v< std::decay_t< Container > >,
473  "Error, the argument 'get_source' must return a container "
474  "type or graph, i.e., "
475  "a type with an iterator");
476 
477  // make basegroup builder and attribute builders
478 
479  Default::DefaultBuilder< Model > dataset_builder =
480  _make_dataset_builder(dataset_descriptor);
481 
482  auto group_attribute_writer = _make_attribute_writer<
483  Default::DefaultAttributeWriterGroup< Model > >(
484  std::forward< Group_attribute >(group_attribute));
485 
486  auto dataset_attribute_writer = _make_attribute_writer<
487  Default::DefaultAttributeWriterDataset< Model > >(
488  std::forward< Dataset_attribute >(dataset_attribute));
489 
490  Default::DefaultDataWriter< Model > datawriter;
491 
492  // assert that the given template argument combination is valid:
493  // A graph cannot be written with TypeTag::plain
494 
495  static_assert(not(Utils::is_graph_v< std::decay_t< Container > > and
496  (typetag == TypeTag::plain)),
497  "Error in WriteTask factory:, a graph cannot be written "
498  "with TypeTag::plain, see documentation of TypeTag enum");
499 
500  if constexpr (Utils::is_graph_v< std::decay_t< Container > > and
501  (typetag != TypeTag::plain))
502  {
503  datawriter =
504  _adapt_graph_writer(std::forward< SourceGetter >(get_source),
505  std::forward< Getter >(getter));
506  }
507  else
508  {
509 
510  datawriter = [getter, get_source](
511  std::shared_ptr< HDFDataset >& dataset,
512  Model& m) -> void {
513  dataset->write(
514  get_source(m).begin(), get_source(m).end(), getter);
515  };
516  }
517 
518  // build a defaultwriteTask
519  return std::make_pair(
520  name,
521  std::make_shared< Default::DefaultWriteTask< Model > >(
522  // builds the basegroup to write datasets in
524  [basegroup_path](std::shared_ptr< HDFGroup > parent) {
525  return parent->open_group(basegroup_path);
526  }),
527  // writes out data
528  datawriter,
529  // builds datasets as needed
530  dataset_builder,
531  // writes attributes to base group
532  group_attribute_writer,
533  // writes attributes to dataset
534  dataset_attribute_writer));
535  }
536 
552  std::pair< std::string,
553  std::shared_ptr< Default::DefaultWriteTask< Model > > >
554  operator()(std::string name,
555  Default::DefaultBaseGroupBuilder group_builder,
557  Default::DefaultBuilder< Model > dataset_builder,
560  {
561  return std::make_pair(
562  name,
563  std::make_shared< Default::DefaultWriteTask< Model > >(
564  group_builder, writer, dataset_builder, group_attr, dset_attr));
565  }
566 };
567 
574 template < typename Model, TypeTag typetag >
575 std::unordered_map< std::string,
576  std::function< std::string(std::string, Model&) > >
578  std::unordered_map< std::string,
579  std::function< std::string(std::string, Model&) > >{
580  { "time",
581  [](std::string path, Model& m) {
582  return path + "_" + std::to_string(m.get_time());
583  } } // for time addition
584  };
585 
597 template < class Model >
599 {
600 
611  template < typename ArgTpl >
612  std::pair< std::string,
613  std::shared_ptr< Default::DefaultWriteTask< Model > > >
614  _call_taskfactory(std::string typetag, ArgTpl&& arg_tpl)
615  {
616 
617  if (typetag == "plain")
618  {
620 
621  return std::apply(factory, std::forward< ArgTpl >(arg_tpl));
622  }
623  else if (typetag == "edge_property")
624  {
626 
627  return std::apply(factory, std::forward< ArgTpl >(arg_tpl));
628  }
629  else if (typetag == "vertex_descriptor")
630  {
632 
633  return std::apply(factory, std::forward< ArgTpl >(arg_tpl));
634  }
635  else if (typetag == "vertex_property")
636  {
638 
639  return std::apply(factory, std::forward< ArgTpl >(arg_tpl));
640  }
641  else
642  {
644 
645  return std::apply(factory, std::forward< ArgTpl >(arg_tpl));
646  }
647  }
648 
649  public:
673  template < typename... Args >
674  auto
675  operator()(const Config& conf,
676  const std::tuple< Args... >& args,
677  const Default::DefaultDecidermap< Model >& deciderfactories =
678  Default::default_deciders<Model>,
679  const Default::DefaultTriggermap< Model >& triggerfactories =
680  Default::default_triggers<Model>)
681  {
682  // Get the global data manager logger
683  const auto _log = spdlog::get("data_mngr");
684 
685  if constexpr (sizeof...(Args) == 0)
686  {
687  _log->info("Empty argument tuple for DataManager factory, "
688  "building default ...");
690  }
691  else
692  {
693  // read the tasks from the config into a map
694  // has the consequence that the ordering need to match
695  std::map< std::string, Config > tasknodes;
696  for (auto&& node : conf["tasks"])
697  {
698  _log->info("Name of current task: {}",
699  node.first.as< std::string >());
700 
701  tasknodes[node.first.as< std::string >()] = node.second;
702  }
703 
704  // this transforms the tuple of argument tuples std::tuple< Args...
705  // >
706  std::unordered_map<
707  std::string,
708  std::shared_ptr< Default::DefaultWriteTask< Model > > >
709  tasks;
710 
712  args,
713  // function gets a tuple representing arguments for the
714  // writetask extracts config-supplied arguments from the
715  // respective config node if the latter is found, puts them in
716  // their correct place, then forwards the rest to the
717  // taksfactory. If no config node for a given taskname is found,
718  // exception is thrown
719  [&](const auto& arg_tpl) {
720  using std::get;
721  std::string name = "";
722  std::string name_in_tpl = get< 0 >(arg_tpl);
723  auto tasknode_iter = tasknodes.find(name_in_tpl);
724 
725  // find the current task in the config, if not found throw
726  if (tasknode_iter != tasknodes.end())
727  {
728  name = tasknode_iter->first;
729  }
730  else
731  {
732  // .. and throw if it is not
733  throw std::invalid_argument(
734  "A task with name '" + name_in_tpl +
735  "' was not found in the config!");
736  }
737 
738  // read out the typetag from the config
739  std::string typetag = "";
740  if (not tasknode_iter->second["typetag"])
741  {
742  typetag = "plain";
743  }
744  else
745  {
746  get_as< std::string >("typetag", tasknode_iter->second);
747  }
748 
749  // make a tuple of types from the argtuple except the first
750  // element which is the sentinel name
751  auto type_tuple = boost::hana::transform(
752  boost::hana::remove_at(arg_tpl,
753  boost::hana::size_t< 0 >{}),
754  [](auto&& t) {
755  return boost::hana::type_c<
756  std::decay_t< decltype(t) > >;
757  });
758 
759  constexpr bool is_all_callable =
760  decltype(boost::hana::unpack(
761  type_tuple,
762  boost::hana::template_<
763  _DMUtils::all_callable >))::type::value;
764 
765  // all arguments are callables
766  if constexpr (is_all_callable)
767  {
768  _log->info("Building write task '{}' via factory ...",
769  name);
770  tasks.emplace(_call_taskfactory(typetag, arg_tpl));
771  }
772  // not all-callable arguments
773  else
774  {
775 
776  // extract arguments from the configfile
777  auto config_args = std::make_tuple(
778  // name
779  name_in_tpl,
780  // basegroup_path
781  get_as< std::string >("basegroup_path",
782  tasknode_iter->second),
783  // dataset_descriptor
785  get_as< std::string >("dataset_path",
786  tasknode_iter->second),
787  (tasknode_iter->second["capacity"]
788  ? get_as< std::vector< hsize_t > >(
789  "capacity", tasknode_iter->second)
790  : std::vector< hsize_t >{}),
791  (tasknode_iter->second["chunksize"]
792  ? get_as< std::vector< hsize_t > >(
793  "chunksize", tasknode_iter->second)
794  : std::vector< hsize_t >{}),
795  (tasknode_iter->second["compression"]
796  ? get_as< int >("compression",
797  tasknode_iter->second)
798  : 0) });
799 
800  // remove the sentinel name and concat the tuples, which
801  // then is forwarded to args
802  auto full_arg_tpl = std::tuple_cat(
803  config_args,
804  boost::hana::remove_at(arg_tpl,
805  boost::hana::size_t< 0 >{}));
806 
807  _log->info("Building write task '{}' via factory ...",
808  name);
809 
810  tasks.emplace(_call_taskfactory(typetag, full_arg_tpl));
811  }
812  });
813 
814  _log->info("Forwarding arguments to DataManager constructor ...");
815  // then produce default datamanager with all default deciders,
816  // triggers etc
817 
819  conf,
820  tasks,
821  deciderfactories,
822  triggerfactories,
824  }
825  }
826 };
829 } // namespace DataIO
830 } // namespace Utopia
831 
832 #endif
Factory function which produces a Datamanager of type Default::DefaultDataManager<Model> from a confi...
Definition: factory.hh:599
std::pair< std::string, std::shared_ptr< Default::DefaultWriteTask< Model > > > _call_taskfactory(std::string typetag, ArgTpl &&arg_tpl)
Function which calls the taskfactory with argument tuples, and takes care of using the correct type-t...
Definition: factory.hh:614
auto operator()(const Config &conf, const std::tuple< Args... > &args, const Default::DefaultDecidermap< Model > &deciderfactories=Default::default_deciders< Model >, const Default::DefaultTriggermap< Model > &triggerfactories=Default::default_triggers< Model >)
Builds a new datamanager from a config file and a tuple of tuples of arguments.
Definition: factory.hh:675
Manage different tasks of writing out data from a source in a uniform yet flexible way....
Definition: data_manager.hh:131
Functor for building a writetask from arguments.
Definition: factory.hh:105
std::pair< std::string, std::shared_ptr< Default::DefaultWriteTask< Model > > > operator()(std::string name, std::string basegroup_path, DatasetDescriptor dataset_descriptor, SourceGetter &&get_source, Getter &&getter, Group_attribute &&group_attribute=Nothing{}, Dataset_attribute &&dataset_attribute=Nothing{})
Basic factory function producing Default::DefaultWriteTask<Model> intstances, for writing out data....
Definition: factory.hh:454
Func _make_attribute_writer(AttributeHandle &&attr)
Function which produces functions for writing attributes to a dataset or group, and which are used by...
Definition: factory.hh:131
std::pair< std::string, std::shared_ptr< Default::DefaultWriteTask< Model > > > operator()(std::string name, Default::DefaultBaseGroupBuilder group_builder, Default::DefaultDataWriter< Model > writer, Default::DefaultBuilder< Model > dataset_builder, Default::DefaultAttributeWriterGroup< Model > group_attr, Default::DefaultAttributeWriterDataset< Model > dset_attr)
Thin wrapper around the writetask constructor which allows to construct a writetask via the factory b...
Definition: factory.hh:554
Default::DefaultBuilder< Model > _make_dataset_builder(DatasetDescriptor dataset_descriptor)
Function producing a dataset builder function of type Default::DefaultBuilder<Model>,...
Definition: factory.hh:201
Default::DefaultDataWriter< Model > _adapt_graph_writer(SourceGetter &&get_source, Getter &&getter)
Function which adapts getter functions for the correct graph accessor type, i.e., vertex_descriptor e...
Definition: factory.hh:261
Base class interface for Models using the CRT Pattern.
Definition: model.hh:112
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
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
std::function< void(std::shared_ptr< HDFDataset > &, Model &) > DefaultAttributeWriterDataset
Type of the default attribute writer for datasets.
Definition: defaults.hh:54
std::function< std::shared_ptr< HDFGroup >(std::shared_ptr< HDFGroup > &&) > DefaultBaseGroupBuilder
Type of the default group builder.
Definition: defaults.hh:34
std::unordered_map< std::string, std::function< std::shared_ptr< Decider< Model > >() > > DefaultDecidermap
Definition: defaults.hh:406
std::function< std::shared_ptr< HDFDataset >(std::shared_ptr< HDFGroup > &, Model &) > DefaultBuilder
Type of the default dataset builder.
Definition: defaults.hh:44
std::function< void(std::shared_ptr< HDFGroup > &, Model &) > DefaultAttributeWriterGroup
Type of the default attribute writer for groups.
Definition: defaults.hh:49
DefaultDecidermap< Model > DefaultTriggermap
Definition: defaults.hh:454
std::function< void(std::shared_ptr< HDFDataset > &, Model &) > DefaultDataWriter
Type of the default data writer.
Definition: defaults.hh:39
static std::unordered_map< std::string, std::function< std::string(std::string, Model &) > > _modifiers
Initialization of the modifier map.
Definition: factory.hh:110
TypeTag
TypeTag enumerates the kind of access which is used to write data. It became necessary after integrat...
Definition: factory.hh:84
boost::adjacency_list< EdgeContainer, VertexContainer, boost::bidirectionalS, Vertex, Edge > GraphType
The type of the graph.
Definition: CopyMeGraph.hh:146
constexpr bool is_graph_v
Shorthand for is_graph<T>::value.
Definition: type_traits.hh:418
constexpr bool is_container_v
Shorthand for 'is_container::value.
Definition: type_traits.hh:181
Definition: agent.hh:11
Metafunction for use with boost hana, check if all T are callable types.
Definition: utils.hh:39
Descriptor for a dataset. Contains: path: string giving the path of the dataset in its group or file ...
Definition: factory.hh:65
std::vector< hsize_t > dataset_chunksize
Definition: factory.hh:68
std::string path
Definition: factory.hh:66
int dataset_compression
Definition: factory.hh:69
std::vector< hsize_t > dataset_capacity
Definition: factory.hh:67
Functor representing what is considered the most widely used execution process for writing data.
Definition: defaults.hh:84
Encapsulate a task for writing data to a destination. Containes a callable 'writer' responisible for ...
Definition: write_task.hh:50
Represent a type that does nothing and represents nothing, hence can be used in metaprogramming whene...
Definition: type_traits.hh:742