Utopia  2
Framework for studying models of complex & adaptive systems.
creation.hh
Go to the documentation of this file.
1 #ifndef UTOPIA_CORE_GRAPH_CREATION_HH
2 #define UTOPIA_CORE_GRAPH_CREATION_HH
3 
4 #include <algorithm>
5 #include <vector>
6 #include <deque>
7 
8 #include <boost/graph/adjacency_list.hpp>
9 #include <boost/graph/adjacency_matrix.hpp>
10 #include <boost/graph/small_world_generator.hpp>
11 #include <boost/graph/random.hpp>
12 #include <boost/property_map/dynamic_property_map.hpp>
13 
14 #include <spdlog/spdlog.h>
15 
19 #include "utopia/core/types.hh"
20 
21 namespace Utopia {
22 namespace Graph {
23 
30 // -- Graph creation algorithms -----------------------------------------------
31 
33 
42 template <typename Graph>
43 Graph create_complete_graph(const std::size_t n) {
44 
45  using vertices_size_type = typename boost::graph_traits<Graph>::vertices_size_type;
46  using namespace boost;
47 
48  // Create empty graph with n vertices
49  Graph g{n};
50 
51  // Conntect every vertex to every other. For undirected graphs, this means
52  // adding 1/2n(n-1) edges. For directed, add n(n-1) edges.
53  if (is_undirected(g)){
54  for (vertices_size_type v = 0; v < n; ++v){
55  for (vertices_size_type k = v+1; k < n; ++k) {
56  add_edge(vertex(v, g), vertex(k, g), g);
57  }
58  }
59  }
60 
61  else {
62  for (vertices_size_type v = 0; v < n; ++v) {
63  for (vertices_size_type k = 1; k < n; ++k) {
64  add_edge(vertex(v, g), vertex((v+k)%n, g), g);
65  }
66  }
67  }
68 
69  // Return the graph
70  return g;
71 }
72 
73 
75 
103 template<typename Graph, typename RNG>
104 Graph create_ErdosRenyi_graph(const std::size_t num_vertices,
105  const std::size_t mean_degree,
106  bool allow_parallel,
107  bool self_edges,
108  RNG& rng)
109 {
110  // Create an empty graph
111  Graph g;
112 
113  // Calculate the number of edges
114  const std::size_t num_edges = [&](){
115  if (boost::is_directed(g)) {
116  return num_vertices * mean_degree;
117  }
118  else {
119  return num_vertices * mean_degree / 2;
120  }
121  }(); // directly call the lambda function to initialize the variable
122 
123 
124  // Create a random graph using the Erdös-Rényi algorithm
125  boost::generate_random_graph(g,
126  num_vertices,
127  num_edges,
128  rng,
129  allow_parallel,
130  self_edges);
131 
132  // Return graph
133  return g;
134 }
135 
136 
138 
153 template <typename Graph>
154 Graph create_regular_graph(const std::size_t n,
155  const std::size_t k,
156  const bool oriented)
157 {
158  using vertices_size_type = typename boost::graph_traits<Graph>::vertices_size_type;
159  using namespace boost;
160 
161  if (k >= n-1) {
162  return create_complete_graph<Graph>(n);
163  }
164 
165  // Start with empty graph with num_vertices nodes
166  Graph g{n};
167 
168  if (is_undirected(g) && k % 2){
169  throw std::invalid_argument("For undirected regular graphs, the mean "
170  "degree needs to be even!");
171  }
172 
173  else if (is_directed(g) && !oriented && k % 2){
174  throw std::invalid_argument("For directed regular graphs, the mean "
175  "degree can only be uneven if the graph is oriented! Set "
176  "'oriented = true', or choose an even mean degree.");
177  }
178 
179  // Generate a regular network. For undirected graphs, the k-neighborhood are the k/2
180  // vertices to the right, and the k/2 vertices to the left. For directed
181  // graphs, if the lattice is set to 'not oriented', this is also the case.
182  // Alternatively, for oriented = true, the k neighborhood consists of
183  // k neighbors to the right.
184  // No parallel edges or self-loops are created.
185 
186  // Undirected graphs
187  if (is_undirected(g)){
188  for (vertices_size_type v = 0; v < n; ++v) {
189  for (vertices_size_type i = 1; i <= k/2; ++i) {
190  add_edge(vertex(v, g), vertex((v + i) % n, g), g);
191  }
192  }
193  }
194 
195  // Directed graphs
196  else {
197  if (!oriented) {
198  for (vertices_size_type v = 0; v < n; ++v) {
199  for (vertices_size_type i = 1; i <= k/2; ++i) {
200  // Forward direction
201  add_edge(vertex(v, g), vertex((v + i) % n, g), g);
202  // Backward direction
203  add_edge(vertex(v, g), vertex((v - i + n) % n, g), g);
204  }
205  }
206  }
207 
208  else {
209  for (vertices_size_type v = 0; v < n; ++v) {
210  for (vertices_size_type i = 1; i <= k; ++i) {
211  add_edge(vertex(v, g), vertex((v + i) % n, g), g);
212  }
213  }
214  }
215  }
216 
217  // Return the graph
218  return g;
219 }
220 
222 
243 template <typename Graph, typename RNG>
244 Graph create_KlemmEguiluz_graph(const std::size_t num_vertices,
245  const std::size_t mean_degree,
246  const double mu,
247  RNG& rng)
248 {
249  using vertex_size_type = typename boost::graph_traits<Graph>::vertices_size_type;
250  using vertex_desc_type = typename boost::graph_traits<Graph>::vertex_descriptor;
251  using namespace boost;
252 
253  if ( mu > 1. or mu < 0.) {
254  throw std::invalid_argument("The parameter 'mu' must be a probability!");
255  }
256  else if ( mean_degree <= 2 ) {
257  throw std::invalid_argument("This algorithm requires a mean degree of"
258  " 3 or more!");
259  }
260 
261  // Generate complete graphs separately, since they do not allow for rewiring
262  if (mean_degree >= num_vertices-1) {
263  return create_complete_graph<Graph>(num_vertices);
264  }
265 
266  // Uniform probability distribution
267  std::uniform_real_distribution<double> distr(0, 1);
268 
269  // Generate an empty graph with num_vertices vertices. This avoids having to
270  // reallocate when adding vertices.
271  Graph g{num_vertices};
272 
273  // Especially for low vertex counts, the original KE does not produce a
274  // network with exactly the mean_degree specified. This function corrects
275  // for the offset by calcuating an effective size for the spawning network.
276  // This has the added benefit of not neccessetating en even mean degree.
277  const std::size_t m = [&]() -> std::size_t {
278  if (is_undirected(g)) {
279  double a = sqrt(4.0*pow(num_vertices, 2)
280  - 4.0*num_vertices*(mean_degree+1.0) +1.0);
281  a *= -0.5;
282  a += num_vertices - 0.5;
283 
284  return std::round(a);
285  }
286  else {
287  return std::round(
288  num_vertices * static_cast<double>(mean_degree) / (2*(num_vertices-1))
289  );
290  }
291  }();
292 
293  // Get a logger and output an info message saying what the actual mean
294  // degree of the network will be
295  const auto log = spdlog::get("core");
296  if (not log) {
297  throw std::runtime_error(
298  "Logger 'core' was not set up but is needed for "
299  "Utopia::Graph::create_KlemmEguiluz_graph !"
300  );
301  }
302 
303  auto actual_mean_degree = [&](){
304  if (is_undirected(g)) {
305  return (1.0*m*(m-1)+2.0*m*(num_vertices-m))/num_vertices;
306  }
307  else {
308  return (2.0*m*(m-1)+2.0*m*(num_vertices-m))/num_vertices;
309  }
310  };
311  log->info("The desired mean degree of this graph is {}; the actual mean"
312  " degree of this graph will be {}.", mean_degree, actual_mean_degree());
313 
314  // Create a container for the active vertices.
315  // Reserve enough space to avoid reallocating.
316  std::vector<vertex_desc_type> actives;
317  actives.reserve(m);
318 
319  // Create a container listing all degrees in the graph, as well as the
320  // number of vertices of that degree
321  std::vector<std::pair<size_t, size_t>> degrees_and_num;
322 
323  // Create a container of all vertices sorted by their degree. Reserve enough
324  // space to avoid reallocating.
325  const size_t num_deg = is_undirected(g) ? num_vertices : 2*(num_vertices);
326  std::vector<std::deque<vertex_desc_type>> vertices_by_deg(num_deg,
327  std::deque<vertex_desc_type>());
328 
329  // Create a fully-connected initial subnetwork. For all except the pure BA,
330  // set all vertices as active.
331  for (vertex_size_type i = 0; i < m; ++i) {
332  if (is_undirected(g)) {
333  for (vertex_size_type k = i+1; k < m; ++k) {
334  add_edge(vertex(i, g), vertex(k, g), g);
335  }
336  }
337  else {
338  for (vertex_size_type k = 1; k < m; ++k) {
339  add_edge(vertex(i, g), vertex((i+k)%m, g), g);
340  }
341  }
342 
343  // For the pure BA, add all vertices of the spawning network to the
344  // list of vertices
345  if (mu == 1) {
346  if (is_undirected(g)){
347  vertices_by_deg[m-1].emplace_back(vertex(i, g));
348  }
349  else {
350  vertices_by_deg[2*(m-1)].emplace_back(vertex(i, g));
351  }
352  }
353  else {
354  actives.emplace_back(vertex(i, g));
355  }
356  }
357 
358  // For the pure BA, there are now m vertices each with degree m-1
359  // (or 2(m-1) in the directed case). Add them all to the list of degrees.
360  // For quick access, place the frequent case deg = m at the beginning of the
361  // list. That way, every newly added vertex can immediately be placed into the
362  // the list without needing to search for the correct index.
363  if (mu == 1 and is_undirected(g)) {
364  degrees_and_num.emplace_back(std::make_pair(m-1, m));
365  degrees_and_num.emplace_back(std::make_pair(m, 0));
366  }
367  else if (mu == 1 and is_directed(g)){
368  degrees_and_num.emplace_back(std::make_pair(m, 0));
369  degrees_and_num.emplace_back(std::make_pair(2*(m-1), m));
370  }
371 
372  // Select a degree with probability proportional to its prevalence and
373  // value. Returns the degree and the index of that degree in the list.
374  auto get_degree = [&](const size_t norm) -> std::pair<std::size_t, std::size_t>
375  {
376  const double prob = distr(rng) * norm;
377  double cumulative_prob = 0;
378  std::pair<std::size_t, size_t> res = {0, 0};
379  for (std::size_t i = 0; i<degrees_and_num.size(); ++i) {
380  cumulative_prob += degrees_and_num[i].first * degrees_and_num[i].second;
381  if (cumulative_prob >= prob) {
382  res = {degrees_and_num[i].first, i};
383  break;
384  }
385  }
386  return res;
387  };
388 
389  // Pure BA model
390  if (mu == 1) {
391  // Normalisation factor: sum of all vertices x their degree
392  std::size_t norm = is_undirected(g) ? m*(m-1) : m*2*(m-1);
393  for (vertex_size_type n = m; n < num_vertices; ++n) {
394  const auto v = vertex(n, g);
395  for (std::size_t i = 0; i < m; ++i) {
396  // Add an edge to a neighbor that was selected with probability
397  // proportional to its in-degree
398  auto d = get_degree(norm);
399  std::size_t idx = distr(rng) * (vertices_by_deg[d.first].size());
400  auto w = vertices_by_deg[d.first][idx];
401  while (w == v or edge(v, w, g).second){
402  d = get_degree(norm);
403  idx = distr(rng) * (vertices_by_deg[d.first].size());
404  w = vertices_by_deg[d.first][idx];
405  }
406  add_edge(v, w, g);
407  // Move that neighbor to the correct spot in the
408  // list of vertices, and update the counts of the
409  // relevant degree list entries.
410  --degrees_and_num[d.second].second;
411  if (d.second < degrees_and_num.size()-1
412  and degrees_and_num[d.second+1].first == d.first+1)
413  {
414  ++degrees_and_num[d.second+1].second;
415  }
416  else {
417  degrees_and_num.emplace_back(std::make_pair(d.first+1, 1));
418  std::sort(
419  degrees_and_num.begin(), degrees_and_num.end(),
420  [](const auto& a, const auto& b) -> bool {
421  return a.first < b.first;
422  }
423  );
424  }
425 
426  // Finally, increase the norm by 1 (since the degree of one
427  // vertex was increased by 1), and move the neighbor into its
428  // new bin in the vertex list.
429  norm += 1;
430  vertices_by_deg[d.first].erase(vertices_by_deg[d.first].begin()+idx);
431  vertices_by_deg[d.first+1].emplace_back(w);
432  }
433 
434  // After connecting the new vertex to m neighbors, increase the norm
435  // by m, since a vertex of degree m is added. Add the new vertex to
436  // the list of vertices.
437  norm += m;
438  vertices_by_deg[m].emplace_back(v);
439 
440  // Increase the count of the deg = m entry in the degree list.
441  if (is_undirected(g)) {
442  degrees_and_num[1].second++;
443  }
444  else {
445  degrees_and_num[0].second++;
446  }
447  }
448  }
449 
450  else {
451  // Collect the sum of the degree x num vertices of that degree.
452  std::size_t norm = 0;
453 
454  // Add the remaining number of vertices, and add edges to m other vertices.
455  for (vertex_size_type n = m; n < num_vertices; ++n) {
456  const auto v = vertex(n, g);
457 
458  // Treat the special case mu=0 (pure KE) separately
459  // to avoid unnecessarily generating random numbers.
460  if (mu == 0) {
461  for (auto const& a : actives) {
462  add_edge(v, a, g);
463  }
464  }
465 
466  // With probability mu, connect to a non-active node chosen via the linear
467  // attachment model. With probability 1-mu, connect to an active node.
468  else {
469  for (auto const& a : actives) {
470  if (distr(rng) < mu and n != m) {
471  // There may not be enough inactive nodes to rewire to.
472  // Stop the while loop after finite number of attempts
473  // to find a new neighbor. If number of attemps is
474  // surpassed, simply connect to an active node.
475  std::size_t max_attempts = n-m+2;
476 
477  // Add an edge to a neighbor that was selected with
478  // probability proportional to its in-degree
479  auto d = get_degree(norm);
480  std::size_t idx = distr(rng) * (vertices_by_deg[d.first].size());
481  auto w = vertices_by_deg[d.first][idx];
482  while (edge(v, w, g).second and max_attempts > 0){
483  d = get_degree(norm);
484  idx = distr(rng) * (vertices_by_deg[d.first].size());
485  w = vertices_by_deg[d.first][idx];
486  --max_attempts;
487  }
488 
489  // Move that neighbor to the correct spot in the
490  // list of vertices, and update the counts of the
491  // relevant degree list entries.
492  if (max_attempts > 0) {
493  add_edge(v, w, g);
494  --degrees_and_num[d.second].second;
495  if (d.second < degrees_and_num.size()-1
496  and degrees_and_num[d.second+1].first == d.first+1)
497  {
498  ++degrees_and_num[d.second+1].second;
499  }
500  else {
501  degrees_and_num.emplace_back(std::make_pair(d.first+1, 1));
502  std::sort(
503  degrees_and_num.begin(), degrees_and_num.end(),
504  [](const auto& a, const auto& b) -> bool {
505  return a.first < b.first;
506  }
507  );
508  }
509 
510  // Finally, increase the norm by 1 (since the degree
511  // of one vertex was increased by 1), and move the
512  // neighbor into its new bin in the vertex list.
513  norm += 1;
514  vertices_by_deg[d.first].erase(vertices_by_deg[d.first].begin()+idx);
515  vertices_by_deg[d.first+1].emplace_back(w);
516  }
517  else {
518  add_edge(v, a, g);
519  }
520  }
521  else {
522  add_edge(v, a, g);
523  }
524  }
525  }
526 
527  // Calculate the sum of the active nodes in-degrees
528  double gamma = 0;
529  for (const auto& a : actives) {
530  gamma += in_degree(a, g);
531  }
532 
533  // Activate the new node and deactivate one of the old nodes.
534  // Probability for deactivation is proportional to in degree.
535  const double prob_to_drop = distr(rng) * gamma;
536  double sum_of_probs = 0;
537 
538  for (vertex_size_type i = 0; i<actives.size(); ++i) {
539  sum_of_probs += in_degree(actives[i], g);
540 
541  if (sum_of_probs >= prob_to_drop) {
542  bool deg_in_array = false;
543  // Find the correct index for the degree of the active node
544  // selected for deactivation. If no such index exists yet,
545  // create a new entry
546  for (std::size_t k = 0; k<degrees_and_num.size(); ++k) {
547  if (degrees_and_num[k].first == degree(actives[i], g)) {
548  ++degrees_and_num[k].second;
549  deg_in_array = true;
550  break;
551  }
552  }
553  // Index does not exist: create new entry
554  if (not deg_in_array) {
555  degrees_and_num.emplace_back(std::make_pair(degree(actives[i], g), 1));
556  sort(degrees_and_num.begin(), degrees_and_num.end(),
557  [](const auto& a, const auto& b) -> bool {
558  return a.first < b.first;
559  });
560  }
561 
562  // Increase the norm by the degree of the active node.
563  norm += degree(actives[i], g);
564 
565  // Place the active node into the correct entry of the
566  // vertex list
567  vertices_by_deg[degree(actives[i], g)].emplace_back(actives[i]);
568 
569  // Activate the new node
570  actives[i] = v;
571  break;
572  }
573  }
574  }
575  }
576 
577  // Return the graph
578  return g;
579 
580 }
581 
583 
604 template <typename Graph, typename RNG>
605 Graph BarabasiAlbert_parallel_generator(std::size_t num_vertices,
606  std::size_t mean_degree,
607  RNG& rng)
608 {
609  // Type definitions
610  using VertexDesc =
611  typename boost::graph_traits<Graph>::vertex_descriptor;
612 
613  // The number of new edges added per network growing step is
614  // equal to half the mean degree. This is because in calculating
615  // the mean degree of an undirected graph, the edge (i,j) would be
616  // counted twice (also as (j,i))
617  const std::size_t num_new_edges_per_step = mean_degree / 2;
618 
619  // Create an empty graph
620  Graph g{};
621 
622  // Generate the (fully-connected) spawning network
623  for (unsigned v0 = 0; v0 < mean_degree; ++v0){
624  boost::add_vertex(g);
625  for (unsigned v1 = 0; v1 < v0; ++v1){
626  boost::add_edge(boost::vertex(v0, g), boost::vertex(v1, g), g);
627  }
628  }
629 
630  // Create a vector in which to store all target vertices of each step ...
631  std::vector<VertexDesc> target_vertices (boost::vertices(g).first,
632  boost::vertices(g).second);
633 
634  // Create a vector that stores all the repeated vertices
635  std::vector<VertexDesc> repeated_vertices{};
636 
637  // Reserve enough memory for the repeated vertices collection
638  repeated_vertices.reserve(num_vertices * num_new_edges_per_step * 2);
639 
640  // Initialise a counter variable with mean_degree because
641  // that is the number of vertices already added to the graph.
642  std::size_t counter = boost::num_vertices(g);
643 
644  // Add (num_vertices - mean_degree) new vertices and mean_degree new edges
645  while (counter < num_vertices)
646  {
647  const auto new_vertex = boost::add_vertex(g);
648 
649  // Add edges from the new vertex to the target vertices mean_degree
650  // times
651  for (auto target : target_vertices){
652  boost::add_edge(new_vertex, target, g);
653 
654  // Add the target vertices to the repeated vertices container
655  // as well as the new vertex for each time a new connection
656  // is set.
657  repeated_vertices.push_back(target);
658  repeated_vertices.push_back(new_vertex);
659  }
660 
661  // Reset the target vertices for the next iteration step by
662  // randomly selecting mean_degree times uniformly from the
663  // repeated_vertices container
664  target_vertices.clear();
665  std::sample(std::begin(repeated_vertices),
666  std::end(repeated_vertices),
667  std::back_inserter(target_vertices),
668  num_new_edges_per_step,
669  rng);
670 
671  ++counter;
672  }
673 
674  return g;
675 }
676 
678 
698 template <typename Graph, typename RNG>
699 Graph create_BarabasiAlbert_graph(std::size_t num_vertices,
700  std::size_t mean_degree,
701  bool parallel,
702  RNG& rng)
703 {
704  // Generate the parallel version using the Klemm-Eguíluz generator
705  if (not parallel) {
706  return create_KlemmEguiluz_graph<Graph>(num_vertices, mean_degree, 1.0, rng);
707  }
708  else {
709  // Check for cases in which the algorithm does not work.
710  // Unfortunately, it is necessary to construct a graph object to check
711  // whether the graph is directed or not.
712  Graph g{};
713  if (boost::is_directed(g)){
714  throw std::runtime_error("This scale-free generator algorithm "
715  "only works for undirected graphs! "
716  "But the provided graph is directed.");
717 
718  } else if (num_vertices < mean_degree){
719  throw std::invalid_argument("The mean degree has to be smaller than "
720  "the total number of vertices!");
721  }
722  else if (mean_degree % 2){
723  throw std::invalid_argument("The mean degree needs to be even but "
724  "is not an even number!");
725  }
726  else{
727  return BarabasiAlbert_parallel_generator<Graph>(num_vertices,
728  mean_degree, rng);
729  }
730  }
731 }
732 
733 
735 
763 template <typename Graph, typename RNG>
764 Graph create_BollobasRiordan_graph(std::size_t num_vertices,
765  double alpha,
766  double beta,
767  double gamma,
768  double del_in,
769  double del_out,
770  RNG& rng)
771 {
772  // Create empty graph.
773  Graph g;
774 
775  // Check for cases in which the algorithm does not work.
776  if (std::fabs(alpha + beta + gamma - 1.)
777  > std::numeric_limits<double>::epsilon()) {
778  throw std::invalid_argument("The probabilities alpha, beta and gamma "
779  "have to add up to 1!");
780  }
781  if (not boost::is_directed(g)) {
782  throw std::runtime_error("This algorithm only works for directed "
783  "graphs but the graph type specifies an "
784  "undirected graph!");
785  }
786  if (beta == 1.){
787  throw std::invalid_argument("The probability beta must not be 1!");
788  }
789 
790  // Create three-cycle as spawning network.
791  const auto v0 = boost::add_vertex(g);
792  const auto v1 = boost::add_vertex(g);
793  const auto v2 = boost::add_vertex(g);
794  boost::add_edge(v0, v1, g);
795  boost::add_edge(v1, v2, g);
796  boost::add_edge(v2, v0, g);
797 
798  std::uniform_real_distribution<> distr(0, 1);
799 
800  // Define helper variables.
801  auto num_edges = boost::num_edges(g);
802  auto norm_in = 0.;
803  auto norm_out = 0.;
804  bool skip;
805 
806  // In each step, add one edge to the graph. A new vertex may or may not be
807  // added to the graph. In each step, choose option 'A', 'B' or 'C' with the
808  // respective probability fractions 'alpha', 'beta' and 'gamma'.
809  while (boost::num_vertices(g) < num_vertices) {
810  skip = false;
811  auto v = boost::vertex(0, g);
812  auto w = boost::vertex(0, g);
813 
814  // Update the normalization for in-degree and out-degree probabilities.
815  norm_in = num_edges + del_in * boost::num_vertices(g);
816  norm_out = num_edges + del_out * boost::num_vertices(g);
817  const auto rand_num = distr(rng);
818 
819  if (rand_num < alpha) {
820  // option 'A'
821  // Add new vertex v and add edge (v,w) with w drawn from the
822  // discrete in-degree probability distribution of already existing
823  // vertices.
824  auto prob_sum = 0.;
825  const auto r = distr(rng);
826 
827  for (auto [p, p_end] = boost::vertices(g); p!=p_end; ++p) {
828 
829  prob_sum += (boost::in_degree(*p, g) + del_in) / norm_in;
830  if (r < prob_sum) {
831  w = *p;
832  break;
833  }
834  }
835  v = boost::add_vertex(g);
836  }
837 
838  else if (rand_num < alpha + beta) {
839  // option 'B'
840  // Add edge (v,w) with v(w) drawn from the discrete out-degree
841  // (in-degree) probability distribution of already existing
842  // vertices.
843  auto prob_sum_in = 0.;
844  auto prob_sum_out = 0.;
845  const auto r_in = distr(rng);
846  const auto r_out = distr(rng);
847 
848  // Find the source of the new edge.
849  for (auto [p, p_end] = boost::vertices(g); p!=p_end; ++p) {
850 
851  prob_sum_out += (boost::out_degree(*p, g) + del_out)/norm_out;
852  if (r_out < prob_sum_out) {
853  v = *p;
854  break;
855  }
856  }
857  // Find the target of the new edge.
858  for (auto [p, p_end] = boost::vertices(g); p!=p_end; ++p) {
859 
860  prob_sum_in += (boost::in_degree(*p, g) + del_in)/norm_in;
861  if (r_in < prob_sum_in) {
862  if (v!=*p and (not boost::edge(v,*p, g).second)) {
863  w = *p;
864  break;
865  }
866  else {
867  // Do not allow multi-edges or self-loops.
868  skip = true;
869  break;
870  }
871  }
872  }
873  }
874 
875  else {
876  // option 'C'
877  // Add new vertex w and add edge (v,w) with v drawn from the
878  // discrete out-degree probability distribution of already
879  // existing vertices.
880  double prob_sum = 0.;
881  const auto r = distr(rng);
882 
883  for (auto [p, p_end] = boost::vertices(g); p!=p_end; ++p) {
884 
885  prob_sum += (boost::out_degree(*p, g) + del_out)/norm_out;
886  if (r < prob_sum) {
887  v = *p;
888  break;
889  }
890  }
891  w = boost::add_vertex(g);
892  }
893  if (not skip) {
894  ++num_edges;
895  boost::add_edge(v, w, g);
896  }
897  }
898  return g;
899 }
900 
902 
923 template <typename Graph, typename RNG>
924 Graph create_WattsStrogatz_graph(const std::size_t n,
925  const std::size_t k,
926  const double p_rewire,
927  const bool oriented,
928  RNG& rng)
929 {
930  using namespace boost;
931  using vertices_size_type = typename boost::graph_traits<Graph>::vertices_size_type;
932 
933  // Generate complete graphs separately, since they do not allow for rewiring
934  if (k >= n-1) {
935  return create_complete_graph<Graph>(n);
936  }
937 
938  std::uniform_real_distribution<double> distr(0, 1);
939 
940  // Generate random vertex ids
941  std::uniform_int_distribution<vertices_size_type> random_vertex_id(0, n-1);
942 
943  // Start with empty graph with num_vertices nodes
944  Graph g{n};
945 
946  if (is_undirected(g) && k % 2){
947  throw std::invalid_argument("For undirected Watts-Strogatz graphs, the "
948  "mean degree needs to be even!");
949  }
950 
951  else if (is_directed(g) && !oriented && k % 2){
952  throw std::invalid_argument("For directed Watts-Strogatz graphs, the mean "
953  "degree can only be uneven if the graph is oriented! Set "
954  "'oriented = true', or choose an even mean degree.");
955  }
956 
957  // If rewiring is turned off, a regular graph can be returned. This avoids
958  // needlessly generating random numbers
959  else if (p_rewire == 0) {
960  return create_regular_graph<Graph>(n, k, oriented);
961  }
962 
963  // Rewiring function
964  auto add_edges = [&](vertices_size_type v,
965  const std::size_t limit,
966  const vertices_size_type& lower,
967  const vertices_size_type& upper,
968  const bool forward)
969  {
970  for (vertices_size_type i = 1; i <= limit; ++i) {
971  vertices_size_type w;
972  if (distr(rng) <= p_rewire) {
973  w = random_vertex_id(rng);
974  while (
975  ((forward && (w != (v + i) % n ))
976  or (!forward && (w != (v - i + n) % n)))
977  &&
978  ((upper > lower && (w >= lower && w <= upper))
979  or (upper < lower && (w >= lower or w <= upper))
980  or (edge(vertex(v, g), vertex(w, g), g).second)))
981  {
982  w = random_vertex_id(rng);
983  }
984  add_edge(vertex(v, g), vertex(w, g), g);
985  }
986  else {
987  w = forward ? (v + i) % n : (v - i + n) % n;
988  add_edge(vertex(v, g), vertex(w, g), g);
989  }
990  }
991  };
992 
993  // Generate a regular network, but rewiring to a random neighbor with
994  // probability p_rewire. The new neighbor must not fall within the
995  // k-neighborhood. For undirected graphs, the k-neighborhood are the k/2
996  // vertices to the right, and the k/2 vertices to the left. For directed
997  // graphs, if the lattice is set to 'not oriented', this is also the case.
998  // Alternatively, for oriented_lattice = true, the k neighborhood consists of
999  // k neighbors to the right.
1000  // No parallel edges or self-loops are created.
1001 
1002  // Upper and lower bounds of k-neighborhood: no rewiring takes place to
1003  // a vertex within these bounds
1004  vertices_size_type lower;
1005  vertices_size_type upper;
1006 
1007  // Undirected graphs
1008  if (is_undirected(g)){
1009  const std::size_t limit = k/2;
1010  for (vertices_size_type v = 0; v < n; ++v) {
1011  // Forwards direction only
1012  lower = (v - limit + n) % n;
1013  upper = (v + limit) % n;
1014  add_edges(v, limit, lower, upper, true);
1015  }
1016  }
1017 
1018  // Directed graphs
1019  else {
1020  // Unoriented starting graph
1021  if (!oriented) {
1022  const std::size_t limit = k/2;
1023  for (vertices_size_type v = 0; v < n; ++v) {
1024  // Forwards direction
1025  lower = (v + n - limit) % n;
1026  upper = (v + limit) % n;
1027  add_edges(v, limit, lower, upper, true);
1028 
1029  // Backwards direction
1030  lower = (v + n - limit) % n;
1031  upper = v;
1032  add_edges(v, limit, lower, upper, false);
1033  }
1034  }
1035 
1036  // Oriented starting graph
1037  else {
1038  const std::size_t limit = k;
1039  for (vertices_size_type v = 0; v < n; ++v) {
1040  // Forwards direction only, but with neighborhood range k
1041  lower = v;
1042  upper = (v + limit) % n;
1043  add_edges(v, limit, lower, upper, true);
1044  }
1045  }
1046  }
1047 
1048  // Return the graph
1049  return g;
1050 }
1051 
1052 
1053 // .. Convenient graph creation function ......................................
1054 
1056 
1073 template<typename Graph, typename RNG>
1074 Graph create_graph(const Config& cfg,
1075  RNG& rng,
1076  boost::dynamic_properties pmaps
1077  = {boost::ignore_other_properties})
1078 {
1079  // Get the graph generating model
1080  const std::string model = get_as<std::string>("model", cfg);
1081 
1082  // Call the correct graph creation algorithm depending on the configuration
1083  // node.
1084 
1085  if (model == "complete")
1086  {
1087  return create_complete_graph<Graph>(
1088  get_as<std::size_t>("num_vertices", cfg));
1089  }
1090 
1091  else if (model == "regular")
1092  {
1093 
1094  // Get the model-specific configuration options
1095  const auto& cfg_r = get_as<Config>("regular", cfg);
1096 
1097  return create_regular_graph<Graph>(
1098  get_as<std::size_t>("num_vertices", cfg),
1099  get_as<std::size_t>("mean_degree", cfg),
1100  get_as<bool>("oriented", cfg_r));
1101  }
1102  else if (model == "ErdosRenyi")
1103  {
1104  // Get the model-specific configuration options
1105  const auto& cfg_ER = get_as<Config>("ErdosRenyi", cfg);
1106 
1107  return create_ErdosRenyi_graph<Graph>(
1108  get_as<std::size_t>("num_vertices", cfg),
1109  get_as<std::size_t>("mean_degree", cfg),
1110  get_as<bool>("parallel", cfg_ER),
1111  get_as<bool>("self_edges", cfg_ER),
1112  rng);
1113  }
1114  else if (model == "KlemmEguiluz")
1115  {
1116  // Get the model-specific configuration options
1117  const auto& cfg_KE = get_as<Config>("KlemmEguiluz", cfg);
1118 
1119  return create_KlemmEguiluz_graph<Graph>(
1120  get_as<std::size_t>("num_vertices", cfg),
1121  get_as<std::size_t>("mean_degree", cfg),
1122  get_as<double>("mu", cfg_KE),
1123  rng);
1124  }
1125  else if (model == "WattsStrogatz")
1126  {
1127  // Get the model-specific configuration options
1128  const auto& cfg_WS = get_as<Config>("WattsStrogatz", cfg);
1129 
1130  return create_WattsStrogatz_graph<Graph>(
1131  get_as<std::size_t>("num_vertices", cfg),
1132  get_as<std::size_t>("mean_degree", cfg),
1133  get_as<double>("p_rewire", cfg_WS),
1134  get_as<bool>("oriented", cfg_WS),
1135  rng);
1136  }
1137  else if (model == "BarabasiAlbert")
1138  {
1139  // Get the model-specific configuration options
1140  const auto& cfg_BA = get_as<Config>("BarabasiAlbert", cfg);
1141 
1142  return create_BarabasiAlbert_graph<Graph>(
1143  get_as<std::size_t>("num_vertices", cfg),
1144  get_as<std::size_t>("mean_degree", cfg),
1145  get_as<bool>("parallel", cfg_BA),
1146  rng);
1147  }
1148  else if (model == "BollobasRiordan")
1149  {
1150  // Get the model-specific configuration options
1151  const auto& cfg_BR = get_as<Config>("BollobasRiordan", cfg);
1152 
1153  return create_BollobasRiordan_graph<Graph>(
1154  get_as<std::size_t>("num_vertices", cfg),
1155  get_as<double>("alpha", cfg_BR),
1156  get_as<double>("beta", cfg_BR),
1157  get_as<double>("gamma", cfg_BR),
1158  get_as<double>("del_in", cfg_BR),
1159  get_as<double>("del_out", cfg_BR),
1160  rng);
1161  }
1162  else if (model == "load_from_file")
1163  {
1164  // Get the model-specific configuration options
1165  const auto& cfg_lff = get_as<Config>("load_from_file", cfg);
1166 
1167  // Load and return the Graph via DataIO's loader
1168  return Utopia::DataIO::GraphLoad::load_graph<Graph>(cfg_lff, pmaps);
1169 
1170  }
1171  else {
1172  throw std::invalid_argument("The given graph model '" + model +
1173  "'does not exist! Valid options are: "
1174  "'complete', 'regular', 'ErdosRenyi', "
1175  "'WattsStrogatz', 'BarabasiAlbert', "
1176  "'KlemmEguiluz','BollobasRiordan', "
1177  "'load_from_file'.");
1178  }
1179 }
1180 
1186 } // namespace Graph
1187 } // namespace Utopia
1188 
1189 #endif // UTOPIA_CORE_GRAPH_CREATION_HH
Graph create_BarabasiAlbert_graph(std::size_t num_vertices, std::size_t mean_degree, bool parallel, RNG &rng)
Create a Barabási-Albert scale-free graph.
Definition: creation.hh:699
Graph create_complete_graph(const std::size_t n)
Create a complete graph.
Definition: creation.hh:43
Graph create_graph(const Config &cfg, RNG &rng, boost::dynamic_properties pmaps={boost::ignore_other_properties})
Create a graph from a configuration node.
Definition: creation.hh:1074
Graph create_BollobasRiordan_graph(std::size_t num_vertices, double alpha, double beta, double gamma, double del_in, double del_out, RNG &rng)
Create a scale-free directed graph.
Definition: creation.hh:764
Graph create_KlemmEguiluz_graph(const std::size_t num_vertices, const std::size_t mean_degree, const double mu, RNG &rng)
Create a Klemm-Eguíluz scale-free small-world highly-clustered graph.
Definition: creation.hh:244
Graph BarabasiAlbert_parallel_generator(std::size_t num_vertices, std::size_t mean_degree, RNG &rng)
Generate a Barabási-Albert scale-free graph with parallel edges.
Definition: creation.hh:605
Graph create_WattsStrogatz_graph(const std::size_t n, const std::size_t k, const double p_rewire, const bool oriented, RNG &rng)
Create a Watts-Strogatz small-world graph.
Definition: creation.hh:924
Graph create_regular_graph(const std::size_t n, const std::size_t k, const bool oriented)
Create a regular lattice graph.
Definition: creation.hh:154
Graph create_ErdosRenyi_graph(const std::size_t num_vertices, const std::size_t mean_degree, bool allow_parallel, bool self_edges, RNG &rng)
Create a Erdös-Rényi random graph.
Definition: creation.hh:104
auto end(zip< Containers... > &zipper)
end function like std::end
Definition: zip.hh:550
auto begin(zip< Containers... > &zipper)
Begin function like std::begin.
Definition: zip.hh:537
std::mt19937 rng
– Type definitions ----------------------------------------------------—
Definition: test_revision.cc:17
constexpr bool is_directed()
Check whether the network type allows for directed edges.
Definition: utils.hh:35
Definition: agent.hh:11
DataIO::Config Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition: types.hh:80