Utopia 2
Framework for studying models of complex & adaptive systems.
Loading...
Searching...
No Matches
Modules | Functions
Graph
Collaboration diagram for Graph:

Modules

 Rules on Entities
 Algorithms for conveniently applying function objects on entities.
 
 GraphEntity
 
 GraphIterators
 

Functions

template<typename Graph >
Graph Utopia::Graph::create_complete_graph (const std::size_t n)
 Create a complete graph.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph >
Graph Utopia::Graph::create_regular_graph (const std::size_t n, const std::size_t k, const bool oriented)
 Create a regular lattice graph.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph , typename RNG >
Graph Utopia::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.
 
template<typename Graph , typename RNG >
Graph Utopia::Graph::create_graph (const Config &cfg, RNG &rng, boost::dynamic_properties pmaps={boost::ignore_other_properties})
 Create a graph from a configuration node.
 

Detailed Description

Function Documentation

◆ BarabasiAlbert_parallel_generator()

template<typename Graph , typename RNG >
Graph Utopia::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.

This is the classic version of the generating model with a completely connected spawning network. This function generates a scale-free graph using the Barabási-Albert model. The algorithm starts with a small spawning network to which new vertices are added one at a time. Each new vertex receives a connection to mean_degree existing vertices with a probability that is proportional to the number of links of the corresponding vertex. In this version, the repeated vertices, that are added during the whole generating process, are stored. With each vertex added a uniform sample from the repeated_vertex is drawn. Each vertex thus has a probability to get selected that is proportional to the number of degrees of that vertex.

Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
num_verticesThe total number of vertices
mean_degreeThe mean degree
rngThe random number generator
Returns
Graph The scale-free graph
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}

◆ create_BarabasiAlbert_graph()

template<typename Graph , typename RNG >
Graph Utopia::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.

This function generates a scale-free graph using the Barabási-Albert model. The algorithm starts with a small spawning network to which new vertices are added one at a time. Each new vertex receives a connection to mean_degree existing vertices with a probability that is proportional to the number of links of the corresponding vertex.

There are two slightly different variants of the algorithm, one that creates a graph with no parallel edges and one that creates a graph with parallel edges.

Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
num_verticesThe total number of vertices
mean_degreeThe mean degree
parallelWhether the graph should have parallel edges or not
rngThe random number generator
Returns
Graph The scale-free graph
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}

◆ create_BollobasRiordan_graph()

template<typename Graph , typename RNG >
Graph Utopia::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.

This function generates a scale-free graph using the model from Bollobás et al. Multi-edges and self-loops are not allowed. The graph is built by continuously adding new edges via preferential attachment. In each step, an edge is added in one of the following three ways:

  • A: add edge from a newly added vertex to an existing one
  • B: add edge between two already existing vertices
  • C: add edge from an existing vertex to a newly added vertex As the graph is directed there can be different attachment probability distributions for in-edges and out-edges. The probability for choosing a vertex as source (target) of the new edge is proportional to its current out-degree (in-degree). Each newly added vertex has a fixed initial probability to be chosen as source (target) which is proportional to del_out (del_in).
Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
nThe total number of vertices
alphaThe probability for option 'A'
betaThe probability for option 'B'
gammaThe probability for option 'C'
del_inThe unnormalized attraction of newly added vertices
del_outThe unnormalized connectivity of newly added vertices
rngThe random number generator
Returns
Graph The scale-free directed graph
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}

◆ create_complete_graph()

template<typename Graph >
Graph Utopia::Graph::create_complete_graph ( const std::size_t  n)

Create a complete graph.

This function creates a complete graph, i.e. one in which every vertex is connected to every other. No parallel edges are created.

Template Parameters
GraphThe graph type
Parameters
nThe total number of vertices
Returns
Graph The graph
43 {
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}

◆ create_ErdosRenyi_graph()

template<typename Graph , typename RNG >
Graph Utopia::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.

This function uses the create_random_graph function from the boost graph library. It uses the Erdös-Rényi algorithm to generate the random graph. Sources and targets of edges are randomly selected with equal probability. Thus, every possible edge has the same probability to be created.

Note
The underlying boost::generate_random_graph function requires the total number of edges as input. In case of an undirected graph it is calculated through: num_edges = num_vertices * mean_degree / 2, in case of a directed graph through: num_edges = num_vertices * mean_degree. If the integer division of the right hand side leaves a rest, the mean degree will be slightly distorted. However, for large numbers of num_vertices this effect is negligible.
Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
num_verticesThe total number of vertices
mean_degreeThe mean degree (= mean in-degree = mean out-degree for directed graphs)
allow_parallelAllow parallel edges within the graph
self_edgesAllows a vertex to be connected to itself
rngThe random number generator
Returns
Graph The random graph
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}

◆ create_graph()

template<typename Graph , typename RNG >
Graph Utopia::Graph::create_graph ( const Config cfg,
RNG &  rng,
boost::dynamic_properties  pmaps = {boost::ignore_other_properties} 
)

Create a graph from a configuration node.

Select a graph creation algorithm and create the graph object a configuration node.

Template Parameters
GraphThe graph type
ConfigThe configuration node type
RNGThe random number generator type
Parameters
cfgThe configuration
rngThe random number generator
pmapsProperty maps that may be used by the graph creation algorithms. At this point, only the load_from_file model will make use of this, allowing to populate a weight property map.
Returns
Graph The graph
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}

◆ create_KlemmEguiluz_graph()

template<typename Graph , typename RNG >
Graph Utopia::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.

This function generates a graph using the Klemm-Eguíluz model (Klemm & Eguíluz 2002). The algorithm starts with a small spawning network to which new vertices are added one at a time. Each new vertex receives a connection to mean_degree existing vertices with a probability that is proportional to the number of links of the corresponding vertex. With probability mu, links are instead rewired to a (possibly non-active) vertex, chosen with a probability that is proportional to its degree. Thus, for mu=1 we obtain the Barabasi-Albert linear preferential attachment model.

Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
num_verticesThe total number of vertices
mean_degreeThe mean degree
muThe probability of rewiring to a random vertex
rngThe random number generator
Returns
Graph The scale-free graph
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}
constexpr bool is_directed()
Check whether the network type allows for directed edges.
Definition utils.hh:35

◆ create_regular_graph()

template<typename Graph >
Graph Utopia::Graph::create_regular_graph ( const std::size_t  n,
const std::size_t  k,
const bool  oriented 
)

Create a regular lattice graph.

This function creates a k-regular graph. The algorithm has been adapted for directed graphs, for which it can be specified whether the underlying lattice graph is oriented or not.

Template Parameters
GraphThe graph type
Parameters
nThe total number of vertices
kThe mean degree (= mean in-degree = mean out-degree for directed graphs)
oriented(For directed graphs) whether the created lattice is oriented (only connecting to forward neighbors) or not
rngThe random number generator
Returns
Graph The regular graph
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}

◆ create_WattsStrogatz_graph()

template<typename Graph , typename RNG >
Graph Utopia::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.

This function creates a small-world graph using the Watts-Strogatz model. It creates a k-regular graph and relocates vertex connections with a given probability. The algorithm has been adapted for directed graphs, for which it can be specified whether the underlying lattice graph is oriented or not.

Template Parameters
GraphThe graph type
RNGThe random number generator type
Parameters
nThe total number of vertices
kThe mean degree (= mean in-degree = mean out-degree for directed graphs)
p_rewireThe rewiring probability
oriented(for directed graphs) Whether the underlying starting graph is oriented (only connecting to forward neighbors) or not
RNGThe random number generator
Returns
Graph The small-world graph
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}