Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
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
21namespace Utopia {
22namespace Graph {
23
30// -- Graph creation algorithms -----------------------------------------------
31
33
42template <typename Graph>
43Graph 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
103template<typename Graph, typename RNG>
104Graph 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,
127 num_edges,
128 rng,
130 self_edges);
131
132 // Return graph
133 return g;
134}
135
136
138
153template <typename Graph>
154Graph 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) {
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
243template <typename Graph, typename RNG>
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) {
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) {
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) {
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
604template <typename Graph, typename RNG>
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
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),
669 rng);
670
671 ++counter;
672 }
673
674 return g;
675}
676
678
698template <typename Graph, typename RNG>
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) {
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{
728 mean_degree, rng);
729 }
730 }
731}
732
733
735
763template <typename Graph, typename RNG>
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
923template <typename Graph, typename RNG>
924Graph 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) {
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) {
961 }
962
963 // Rewiring function
964 auto add_edges = [&](vertices_size_type v,
965 const std::size_t limit,
968 const bool forward)
969 {
970 for (vertices_size_type i = 1; i <= limit; ++i) {
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
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
1073template<typename Graph, typename RNG>
1074Graph 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 {
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
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
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
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
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
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
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
Container select_entities(const Manager &mngr, const DataIO::Config &sel_cfg)
Select entities according to parameters specified in a configuration.
Definition select.hh:213
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
Definition agent.hh:11
DataIO::Config Config
Type of a variadic dictionary-like data structure used throughout Utopia.
Definition types.hh:80