Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
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
97using VertexContainer = boost::vecS;
98
99// -- Edge --------------------------------------------------------------------
101
106{
108 double weight;
109
110 // Add your edge parameters here.
111 // ...
112};
113
116
119
121
124using EdgeContainer = boost::listS;
125
126// -- Graph -------------------------------------------------------------------
128
142 boost::adjacency_list<EdgeContainer,
144 boost::bidirectionalS, // -> undirected graph
145 Vertex,
146 Edge>;
147
150
151// ++ Model definition ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
152
154
159class 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 ..................................
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,
250 const DataIO::Config& custom_cfg = {}
251 )
252 :
253 // Initialize first via base model
255
256 // Initialize the uniform real distribution to range [0., 1.]
257 _prob_distr(0., 1.),
258
259 // Now initialize the 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",
289 {boost::num_vertices(_g)})),
290 _dset_some_trait(this->create_dset("some_trait",
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`.
318 save_graph(_g, _dgrp_g_static);
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
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.
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
461 _g,
462 *this->_rng);
463
464 // Apply 'some_other_rule' synchronously to all vertices
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
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.
565 save_vertex_properties(_g,
567 std::to_string(get_time()),
569 save_edge_properties(_g,
571 std::to_string(get_time()),
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
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
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
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
ReturnType get_as(const std::string &key, const DataIO::Config &node)
This function is a wrapper around the yaml-cpp YAML::Node::as function.
Definition cfg_utils.hh:158
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
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