Utopia  2
Framework for studying models of complex & adaptive systems.
CopyMeGraph.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_MODELS_COPYMEGRAPH_HH
2 #define UTOPIA_MODELS_COPYMEGRAPH_HH
3 // TODO Adjust above include guard (and at bottom of file)
4 
5 // standard library includes
6 #include <random>
7 #include <iterator>
8 
9 // third-party library includes
10 #include <boost/graph/adjacency_list.hpp>
11 #include <boost/range.hpp>
12 
13 // Utopia-related includes
14 #include <utopia/core/model.hh>
15 #include <utopia/core/graph.hh>
17 
19 {
20 // ++ Type definitions ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
21 
22 // ----------------------------------------------------------------------------
23 // -- General Information on choosing the adequate graph for your model -------
24 // (Delete after reading, thinking and making choices ... )
25 //
26 // GRAPH CHOICE: You should think about what kind of graph you want to have.
27 // Often, it is a good choise to have the graph represented as an
28 // adjacency list (
29 // https://www.boost.org/doc/libs/1_60_0/libs/graph/doc/adjacency_list.html
30 // ). However, there are also other viable options such an
31 // adjacency_matrix. If you are unsure, just have a look in the
32 // boost::graph documentation (quick googling helps you ;) )
33 //
34 // CONTAINER CHOICE: Depending on what you want to do in your model, you should
35 // think about the adequate choice of container to store the vertices
36 // and edges in. You find again helpful information in the boost graph
37 // documentation (e.g.
38 // https://www.boost.org/doc/libs/master/libs/graph/doc/using_adjacency_list.html
39 // ). As a simple and basic rule of thumb you should choose a vector
40 // (boost::vecS) if you have a lot of random accesses to you vertices
41 // or edges and a list (boost::listS) if you manipulate your entities
42 // often e.g. if you want to add and remove a lot of vertices/edges.
43 // NOTE that if you select a VertexContainer that does not provide a fixed
44 // internal ordering, thus, no numbered vertex (or edge)
45 // descriptors such as for example boost::listS your model will not
46 // compile. To make it compile you will need to replace all
47 // boost::get(boost::vertex_index_t(), g, v) functions with
48 // g[v].id(); this will use the Utopia::GraphEntity id of the vertex
49 // to identify the vertex instead of the in this case
50 // not-really-accessible vertex_descriptor identification.
51 //
52 // DATA WRITING CHOICE: You should also think about how the data will be
53 // written. If the number of vertices and edges changes over time, it is
54 // difficult to initialize appropriate datasets as the size is not known.
55 // Luckily, there are solutions for both the static case (graph topology
56 // is constant over time) and the dynamic case (changing topology). Below,
57 // examples for both cases are shown, denoted by '*_static' and
58 // '*_dynamic', respectively. Don't forget to delete the parts irrelevant
59 // for your model. Also note that the 'dynamic' case refers to the case
60 // of changing vertex number AND edge number. Possibly, a mixed solution
61 // might work best in your case. Since the data writing is slower in the
62 // dynamic case, it is recommended to stick with the static case wherever
63 // possible.
64 // ----------------------------------------------------------------------------
65 
66 // -- Vertex ------------------------------------------------------------------
68 
73 {
75  double some_state;
76 
79 
82 
83  // Add your vertex parameters here.
84  // ...
85 };
86 
89 
92 
94 
97 using VertexContainer = boost::vecS;
98 
99 // -- Edge --------------------------------------------------------------------
101 
105 struct EdgeState
106 {
108  double weight;
109 
110  // Add your edge parameters here.
111  // ...
112 };
113 
116 
119 
121 
124 using EdgeContainer = boost::listS;
125 
126 // -- Graph -------------------------------------------------------------------
128 
141 using GraphType =
142  boost::adjacency_list<EdgeContainer,
144  boost::bidirectionalS, // -> undirected graph
145  Vertex,
146  Edge>;
147 
150 
151 // ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
152 
154 
159 class CopyMeGraph : public Model<CopyMeGraph, ModelTypes>
160 {
161  public:
164 
166  using DataGroup = typename Base::DataGroup;
167 
169  using DataSet = typename Base::DataSet;
170 
171  // .. Graph-related types and rule types ..................................
173  using VertexDesc =
174  typename boost::graph_traits<GraphType>::vertex_descriptor;
175 
177  using EdgeDesc = typename boost::graph_traits<GraphType>::edge_descriptor;
178 
180  using VertexVoidRule = typename std::function<void(VertexDesc, GraphType&)>;
181 
184  typename std::function<VertexState(VertexDesc, GraphType&)>;
185 
187  using EdgeVoidRule = typename std::function<void(EdgeDesc, GraphType&)>;
188 
191  typename std::function<EdgeState(EdgeDesc, GraphType&)>;
192 
193  private:
194  // Base members: _time, _name, _cfg, _hdfgrp, _rng, _monitor, _log, _space
195  // ... but you should definitely check out the documentation ;)
196 
197  // -- Members -------------------------------------------------------------
199  std::uniform_real_distribution<double> _prob_distr;
200 
203 
206 
207  // More parameters here ...
208 
209  // .. Temporary objects ...................................................
210 
211  // .. Datagroups ..........................................................
212  // Use datagroups to structure and organize your data. In this case, it
213  // makes sense creating one datagroup for the graph and all data which is
214  // directly related to it. If you have multiple graphs, create one group
215  // for each graph.
216  //
217  // NOTE They should be named '_dgrp_<groupname>', where <groupname> is the
218  // datagroup's actual name as set in its constructor.
220  std::shared_ptr<DataGroup> _dgrp_g_static;
221 
223  std::shared_ptr<DataGroup> _dgrp_g_dynamic;
224 
225  // .. Datasets ............................................................
226  // NOTE They should be named '_dset_<name>', where <name> is the
227  // dataset's actual name as set in its constructor.
229  std::shared_ptr<DataSet> _dset_some_state;
230 
232  std::shared_ptr<DataSet> _dset_some_trait;
233 
234  public:
235  // -- Model Setup ---------------------------------------------------------
236 
238 
246  template<class ParentModel>
248  const std::string& name,
249  ParentModel& parent_model,
250  const DataIO::Config& custom_cfg = {}
251  )
252  :
253  // Initialize first via base model
254  Base(name, parent_model, custom_cfg),
255 
256  // Initialize the uniform real distribution to range [0., 1.]
257  _prob_distr(0., 1.),
258 
259  // Now initialize the graph
260  _g {initialize_graph()},
261 
262  // Initialize model parameters
263  _some_parameter(get_as<double>("some_parameter", this->_cfg)),
264  // ...
265 
266  // Datagroups
267  // For saving graph-related data, it is recommended to use a so-called
268  // graph group. This is a normal datagroup (opened with the open_group
269  // method) but with additional attributes such as information about the
270  // graph itself. This allows for later loading the data from the graph
271  // group directly into a NetworkX graph object in Python.
272  _dgrp_g_static(create_graph_group(_g, this->_hdfgrp, "g_static")),
273  _dgrp_g_dynamic(create_graph_group(_g, this->_hdfgrp, "g_dynamic")),
274 
275  // Datasets
276  // For setting up datasets that store Graph data, you can use the
277  // Model::create_dset helper method which already takes care of using
278  // the correct length into the time dimension (depending on the
279  // num_steps and write_every parameters). Additionally, the parent
280  // datagroup can be specified. The syntax is:
281  //
282  // this->create_dset("a_number", dgrp, {}) // 1D {#writes}
283  // this->create_dset("a_vec", dgrp, {num_cols}) // 2D {#writes, #cols}
284  //
285  // This is also done here for storing two vertex properties in the
286  // static graph group.
287  _dset_some_state(this->create_dset("some_state",
288  _dgrp_g_static,
289  {boost::num_vertices(_g)})),
290  _dset_some_trait(this->create_dset("some_trait",
291  _dgrp_g_static,
292  {boost::num_vertices(_g)}))
293  {
294  // Can do remaining initialization steps here ...
295 
296  // Set the second dimension name to 'vertex_idx'. The first dimension's
297  // name is 'time'. Also, specify the coordinates for the vertex_idx
298  // dimension, which are just the trivial coordinates 0, ..., N-1,
299  // where N is the number of vertices.
300  // NOTE The IDs of the vertices do not necessarily line up with the
301  // indices of the vertices when iterating over the graph. That's
302  // why the dimension is called vertex_idx, not vertex_id.
303  _dset_some_state->add_attribute("dim_name__1", "vertex_idx");
304  _dset_some_state->add_attribute("coords_mode__vertex_idx", "trivial");
305 
306  _dset_some_trait->add_attribute("dim_name__1", "vertex_idx");
307  _dset_some_state->add_attribute("coords_mode__vertex_idx", "trivial");
308 
309  // NOTE The initial state need and should NOT be written here. The
310  // write_data method is invoked first at time `write_start`.
311  // However, this is a good place to store data that is constant
312  // during the run and needs to be written at some point.
313 
314  // In the case of a static graph, the topology can be saved once using
315  // the DataIO::save_graph function. It saves the graph structure to the
316  // specified datagroup by writing the vertex id's in a dataset named
317  // `_vertices` and the edges in a dataset named `_edges`.
319 
320  // Initialization should be finished here.
321  this->_log->debug("{} model fully set up.", this->_name);
322  }
323 
324  private:
325  // .. Setup functions .....................................................
326 
329  {
330  this->_log->debug("Create and initialize the graph ...");
331 
332  auto g = Graph::create_graph<GraphType>(this->_cfg["create_graph"],
333  *this->_rng);
334 
335  this->initialize_vertices(g);
336  this->initialize_edges(g);
337 
338  return g;
339  }
340 
342  {
343  // Define a rule that acts on a vertex
344  auto initialize_vertex = [this](const auto v, auto& g) {
345  g[v].state.some_state =
346  get_as<double>("init_some_state", this->_cfg);
347  g[v].state.some_trait = get_as<int>("init_some_trait", this->_cfg);
348 
349  // Every 13th vertex (on average) is a VIP vertex
350  if (this->_prob_distr(*this->_rng) < (1. / 13.)) {
351  g[v].state.is_a_vip_vertex = true;
352  }
353  else {
354  g[v].state.is_a_vip_vertex = false;
355  }
356  };
357 
358  // Apply the rule to all vertices
359  apply_rule<IterateOver::vertices, Update::async, Shuffle::off>(
360  initialize_vertex,
361  g);
362  }
363 
365  {
366  // Define a rule that acts on an edge
367  auto initialize_edge = [this](const auto e, auto& g) {
368  // Get the initial weight from the configuration
369  g[e].state.weight = get_as<double>("init_weight", this->_cfg);
370 
371  // If set in the configuration randomize the weight by
372  // multiplying a random number between drawn uniformaly from [0,1].
373  if (get_as<bool>("init_random_weight", this->_cfg)) {
374  // Here you see, how to generate a random number using the
375  // random number generated from the parent model.
376  // Remember that the member variable is a shared pointer,
377  // so you need to dereference it by writing '*' in front of it.
378  g[e].state.weight *= _prob_distr(*this->_rng);
379  }
380  };
381 
382  // Apply the single edge initialization rule to all edges
383  // NOTE that you should distinguish between in_edges and out_edges
384  // if your graph is directed.
385  apply_rule<IterateOver::edges, Update::async, Shuffle::off>(
386  initialize_edge,
387  g);
388  }
389 
390  // Can add additional setup functions here ...
391 
392  // .. Helper functions ....................................................
393 
395  double calc_some_state_mean() const
396  {
397  auto sum = 0.;
398  for (const auto v : range<IterateOver::vertices>(_g)) {
399  sum += _g[v].state.some_state;
400  }
401  return sum / boost::num_vertices(_g);
402  }
403 
404  // .. Rule functions ......................................................
405  // Rule functions that can be applied to the graph's vertices or edges
406  // NOTE The below are examples; delete and/or adjust them to your needs!
407  // Ideally, only define those rule functions as members that are used
408  // more than once.
409 
412  // Increase some_state by one
413  g[v].state.some_state += 1;
414 
415  // Iterate over all neighbors of the current vertex
416  for (const auto nb : range<IterateOver::neighbors>(v, g)) {
417  // Obvious thing to do is to increase some_trait by the sum of
418  // some_traits's of the neighbor. Sure thing.
419  g[v].state.some_trait += g[nb].state.some_trait;
420 
421  // Let's add a random number in range [-1, +1] as well
422  g[v].state.some_trait += (this->_prob_distr(*this->_rng) * 2. - 1.);
423  }
424 
425  // Ahhh and obviously you need to divide some float by
426  // _some_parameter because that makes totally sense
427  g[v].state.some_trait /= this->_some_parameter;
428  };
429 
431 
437  // COPY the state -- important for synchronous update
438  auto state = g[v].state;
439 
440  // With a probability of 0.3 set the vertex's some_state to 0
441  if (this->_prob_distr(*this->_rng) < 0.3) {
442  state.some_state = 0;
443  }
444 
445  return state;
446  };
447 
448  public:
449  // -- Public Interface ----------------------------------------------------
450  // .. Simulation Control ..................................................
451 
453 
457  {
458  // Apply the rule 'some_interaction' to all vertices sequentially
459  apply_rule<IterateOver::vertices, Update::async, Shuffle::on>(
461  _g,
462  *this->_rng);
463 
464  // Apply 'some_other_rule' synchronously to all vertices
465  apply_rule<IterateOver::vertices, Update::sync>(some_other_rule, _g);
466  }
467 
469 
477  void monitor()
478  {
479  this->_monitor.set_entry("some_value", 42);
480  this->_monitor.set_entry("state_mean", calc_some_state_mean());
481  }
482 
484 
488  void write_data()
489  {
490  // .. Writing to the static graph group ...............................
491  // To save data to the already created datasets, the write method can
492  // be called directly using a lambda function to extract the data from
493  // the graph.
494  //
495  // Get the iterator pair of the vertices
496  auto [v, v_end] = boost::vertices(_g);
497 
498  // Write out the some_state of all vertices
499  _dset_some_state->write(v, v_end, [this](const auto v) {
500  return this->_g[v].state.some_state;
501  });
502 
503  // Write out the some_trait of all vertices
504  _dset_some_trait->write(v, v_end, [this](const auto v) {
505  return this->_g[v].state.some_trait;
506  });
507 
508  // .. Writing to the dynamic graph group ..............................
509  // To save data for dynamic graphs, it is recommended to use the
510  // save_<vertex/edge>_properties functions. Since it writes all data in
511  // a single iteration over the graph entities, it ensures that all data
512  // written within one time step is aligned.
513  //
514  // First, two 'adaptor tuples' are defined - one that accesses the data
515  // via a vertex-descriptor and one that does it via an edge-descriptor.
516  // NOTE In order to ensure correct ordering of the data, there must not
517  // be a second adaptor tuple using the same type.
518  // The adaptor tuple for the vertex data contains 1D adaptors which are
519  // tuples of the form: (adaptor_name, adaptor_lambda)
520  const auto get_vertex_data = std::make_tuple(
521  std::make_tuple(
522  "_vertices",
523  [](auto vd, auto& g) {
524  return boost::get(boost::vertex_index_t(), g, vd);
525  }),
526  std::make_tuple("some_state",
527  [](auto vd, auto& g) {
528  return g[vd].state.some_state;
529  }),
530  std::make_tuple("some_trait", [](auto vd, auto& g) {
531  return g[vd].state.some_trait;
532  }));
533 
534  // N-dimensional data can also be written. Here, 2D adaptors are used
535  // for the edges and a 1D adaptor for the weights. For more details,
536  // checkout the documentation of the save_<vertex/edge>_properties
537  // functions.
538  const auto get_edge_data = std::make_tuple(
539  std::make_tuple(
540  "_edges",
541  "label",
542  std::make_tuple("source",
543  [](auto ed, auto& g) {
544  return boost::get(boost::vertex_index_t(),
545  g,
546  boost::source(ed, g));
547  }),
548  std::make_tuple("target",
549  [](auto ed, auto& g) {
550  return boost::get(boost::vertex_index_t(),
551  g,
552  boost::target(ed, g));
553  })),
554  std::make_tuple("weights", [](auto ed, auto& g) {
555  return g[ed].state.weight;
556  }));
557  // NOTE When using an adaptor tuple for saving dynamic edges (vertices),
558  // all edge (vertex) properties should also be saved via the
559  // adaptor tuple to ensure correct ordering.
560 
561  // Then, the save_<vertex/edge>_properties functions can be called.
562  // NOTE Using the current time as the name of the dataset(s) indexes the
563  // data and allows to later load the data directly into a NetworkX
564  // graph.
568  get_vertex_data);
572  get_edge_data);
573 
574  // When writing in every time step, this will result in the following
575  // data structure:
576  // ...
577  // └┬ graph_group
578  // └┬ _vertices
579  // └┬ 0 < ... shape(num_vertices,)
580  // ├ 1 < ... shape(num_vertices,)
581  // ├ ...
582  // ├ _edges
583  // └┬ 0 < ... shape(num_edges, 2)
584  // ├ 1 < ... shape(num_edges, 2)
585  // ├ ...
586  // ├ ...
587  //
588  // By default, the coordinates 'vertex_idx' and 'edge_idx' are added.
589  }
590 
591  // .. Getters and setters .................................................
592  // Add public getters and setters here to interface with other models
593 };
594 
595 } // namespace Utopia::Models::CopyMeGraph
596 
597 #endif // UTOPIA_MODELS_COPYMEGRAPH_HH
A graph entity is a slightly specialized state container.
Definition: entity.hh:42
Base class interface for Models using the CRT Pattern.
Definition: model.hh:112
const std::shared_ptr< DataGroup > _hdfgrp
The HDF group this model instance should write its data to.
Definition: model.hh:176
Monitor _monitor
The monitor.
Definition: model.hh:188
typename ModelTypes::DataSet DataSet
Data type that is used for storing data.
Definition: model.hh:125
const Config _cfg
Config node belonging to this model instance.
Definition: model.hh:158
const std::string _name
Name of the model instance.
Definition: model.hh:149
std::shared_ptr< DataSet > create_dset(const std::string name, const std::shared_ptr< DataGroup > &hdfgrp, std::vector< hsize_t > add_write_shape, const std::size_t compression_level=1, const std::vector< hsize_t > chunksize={})
Create a new dataset within the given group.
Definition: model.hh:752
Time get_time() const
Return the current time of this model.
Definition: model.hh:393
typename ModelTypes::DataGroup DataGroup
Data type that is used for storing datasets.
Definition: model.hh:122
const std::shared_ptr< spdlog::logger > _log
The (model) logger.
Definition: model.hh:164
const std::shared_ptr< RNG > _rng
The RNG shared between models.
Definition: model.hh:161
The CopyMeGraph Model; a good start for a graph-based model.
Definition: CopyMeGraph.hh:160
void monitor()
Monitor model information.
Definition: CopyMeGraph.hh:477
double calc_some_state_mean() const
Calculate the mean of all vertices' some_state.
Definition: CopyMeGraph.hh:395
typename Base::DataSet DataSet
Data type for a dataset.
Definition: CopyMeGraph.hh:169
double _some_parameter
Some parameter.
Definition: CopyMeGraph.hh:205
CopyMeGraph(const std::string &name, ParentModel &parent_model, const DataIO::Config &custom_cfg={})
Construct the CopyMeGraph model.
Definition: CopyMeGraph.hh:247
typename std::function< EdgeState(EdgeDesc, GraphType &)> EdgeStateRule
Data type for a rule function operating on edges returning a state.
Definition: CopyMeGraph.hh:191
std::shared_ptr< DataGroup > _dgrp_g_dynamic
A datagroup for a dynamic graph.
Definition: CopyMeGraph.hh:223
void perform_step()
Iterate a single step.
Definition: CopyMeGraph.hh:456
void initialize_vertices(GraphType &g)
Definition: CopyMeGraph.hh:341
std::shared_ptr< DataSet > _dset_some_trait
A dataset for storing all vertices' some_trait.
Definition: CopyMeGraph.hh:232
GraphType initialize_graph()
Initialize the graph.
Definition: CopyMeGraph.hh:328
typename boost::graph_traits< GraphType >::vertex_descriptor VertexDesc
Data type for a vertex descriptor.
Definition: CopyMeGraph.hh:174
void initialize_edges(GraphType &g)
Definition: CopyMeGraph.hh:364
std::shared_ptr< DataGroup > _dgrp_g_static
A datagroup for a static graph.
Definition: CopyMeGraph.hh:220
typename std::function< void(EdgeDesc, GraphType &)> EdgeVoidRule
Data type for a rule function operating on edges returning void.
Definition: CopyMeGraph.hh:187
void write_data()
Write data.
Definition: CopyMeGraph.hh:488
GraphType _g
The graph.
Definition: CopyMeGraph.hh:202
Model< CopyMeGraph, ModelTypes > Base
The type of the Model base class of this derived class.
Definition: CopyMeGraph.hh:163
typename std::function< VertexState(VertexDesc, GraphType &)> VertexStateRule
Data type for a rule function operating on vertices returning a state.
Definition: CopyMeGraph.hh:184
typename std::function< void(VertexDesc, GraphType &)> VertexVoidRule
Data type for a rule function operating on vertices returning void.
Definition: CopyMeGraph.hh:180
std::shared_ptr< DataSet > _dset_some_state
A dataset for storing all vertices' some_state.
Definition: CopyMeGraph.hh:229
typename boost::graph_traits< GraphType >::edge_descriptor EdgeDesc
Data type for an edge descriptor.
Definition: CopyMeGraph.hh:177
VertexVoidRule some_interaction
An interaction function of a single vertex with its neighbors.
Definition: CopyMeGraph.hh:411
std::uniform_real_distribution< double > _prob_distr
A re-usable uniform real distribution to evaluate probabilities.
Definition: CopyMeGraph.hh:199
typename Base::DataGroup DataGroup
Data type of the group to write model data to, holding datasets.
Definition: CopyMeGraph.hh:166
VertexStateRule some_other_rule
Some other rule function.
Definition: CopyMeGraph.hh:436
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
void save_edge_properties(Graph &&g, const std::shared_ptr< HDFGroup > &nw_grp, const std::string &label, const std::tuple< Adaptors... > &adaptor_tuple)
Definition: graph_utils.hh:573
std::shared_ptr< HDFGroup > create_graph_group(const Graph &g, const std::shared_ptr< HDFGroup > &parent_grp, const std::string &name)
Definition: graph_utils.hh:291
void save_graph(const Graph &g, const std::shared_ptr< HDFGroup > &grp)
Write function for a boost::Graph.
Definition: graph_utils.hh:332
void save_vertex_properties(Graph &&g, const std::shared_ptr< HDFGroup > &nw_grp, const std::string &label, const std::tuple< Adaptors... > &adaptor_tuple)
Definition: graph_utils.hh:542
Definition: CopyMeGraph.hh:19
boost::vecS VertexContainer
The vertex container type.
Definition: CopyMeGraph.hh:97
boost::listS EdgeContainer
The edge container type.
Definition: CopyMeGraph.hh:124
GraphEntity< VertexTraits > Vertex
A vertex is a graph entity with vertex traits.
Definition: CopyMeGraph.hh:91
boost::adjacency_list< EdgeContainer, VertexContainer, boost::bidirectionalS, Vertex, Edge > GraphType
The type of the graph.
Definition: CopyMeGraph.hh:146
The entity traits struct gathers types to be used for specializing an entity.
Definition: entity.hh:49
Wrapper struct for defining model class data types.
Definition: model.hh:92
The edge state.
Definition: CopyMeGraph.hh:106
double weight
Every parameter should be usefully documented :)
Definition: CopyMeGraph.hh:108
The vertex state.
Definition: CopyMeGraph.hh:73
int some_trait
Another useful documentation string, yeah.
Definition: CopyMeGraph.hh:78
bool is_a_vip_vertex
Whether this vertex is very important.
Definition: CopyMeGraph.hh:81
double some_state
A useful documentation string.
Definition: CopyMeGraph.hh:75