nxpp
Header-only graph utilities on top of Boost Graph Library
Loading...
Searching...
No Matches
centrality.hpp
Go to the documentation of this file.
1#pragma once
2
11#include "graph.hpp"
12
13#include <algorithm>
14#include <cmath>
15#include <queue>
16
17namespace nxpp {
18
19template <typename NodeID, typename EdgeWeight, bool Directed, bool Multi, bool Weighted, typename OutEdgeSelector, typename VertexSelector>
21 const auto node_count = boost::num_vertices(g);
22 std::vector<double> centrality_by_index(static_cast<std::size_t>(node_count), 0.0);
23 if (node_count == 0) {
25 }
26 for (auto [v, vend] = boost::vertices(g); v != vend; ++v) {
27 centrality_by_index[get_vertex_index(*v)] = 0.0;
28 }
29 if (node_count <= 1) {
30 return build_node_indexed_result<double>([&](VertexDesc v) {
31 return centrality_by_index[get_vertex_index(v)];
32 });
33 }
34 const double scale = 1.0 / static_cast<double>(node_count - 1);
35 for (auto [v, vend] = boost::vertices(g); v != vend; ++v) {
36 double degree = 0.0;
37 if constexpr (Directed) {
38 degree = static_cast<double>(boost::in_degree(*v, g) + boost::out_degree(*v, g));
39 } else {
40 degree = static_cast<double>(boost::degree(*v, g));
41 }
42 centrality_by_index[get_vertex_index(*v)] = degree * scale;
43 }
44 return build_node_indexed_result<double>([&](VertexDesc v) {
45 return centrality_by_index[get_vertex_index(v)];
46 });
47}
48
49template <typename NodeID, typename EdgeWeight, bool Directed, bool Multi, bool Weighted, typename OutEdgeSelector, typename VertexSelector>
51 double tolerance,
52 std::size_t max_iterations
53) const {
54 const auto node_count = boost::num_vertices(g);
55 if (node_count == 0) {
57 }
58
59 const double n = static_cast<double>(node_count);
60 const double damping = 0.85;
61 std::vector<double> rank(static_cast<std::size_t>(node_count), 1.0 / n);
62 std::vector<double> next(static_cast<std::size_t>(node_count), 0.0);
63
64 for (std::size_t iteration = 0; iteration < max_iterations; ++iteration) {
65 const double base = (1.0 - damping) / n;
66 std::fill(next.begin(), next.end(), base);
67
68 double dangling_sum = 0.0;
69 for (auto [u, uend] = boost::vertices(g); u != uend; ++u) {
70 const auto out = boost::out_degree(*u, g);
71 const auto index = get_vertex_index(*u);
72 if (out == 0) {
73 dangling_sum += rank[index];
74 continue;
75 }
76
77 const double share = damping * rank[index] / static_cast<double>(out);
78 for (auto [adj, adj_end] = boost::adjacent_vertices(*u, g); adj != adj_end; ++adj) {
79 next[get_vertex_index(*adj)] += share;
80 }
81 }
82
83 if (dangling_sum > 0.0) {
84 const double dangling_share = damping * dangling_sum / n;
85 for (double& value : next) {
86 value += dangling_share;
87 }
88 }
89
90 double delta = 0.0;
91 for (std::size_t i = 0; i < rank.size(); ++i) {
92 delta += std::abs(next[i] - rank[i]);
93 }
94
95 rank.swap(next);
96 if (delta < tolerance) {
97 break;
98 }
99 }
100
101 return build_node_indexed_result<double>([&](VertexDesc v) {
102 return rank[get_vertex_index(v)];
103 });
104}
105
106template <typename GraphWrapper>
114[[deprecated("Use G.pagerank() instead.")]]
115auto pagerank(const GraphWrapper& G) {
116 return G.pagerank();
117}
118
119template <typename GraphWrapper>
127[[deprecated("Use G.degree_centrality() instead.")]]
128auto degree_centrality(const GraphWrapper& G) {
129 return G.degree_centrality();
130}
131
132template <typename NodeID, typename EdgeWeight, bool Directed, bool Multi, bool Weighted, typename OutEdgeSelector, typename VertexSelector>
134 const auto node_count = boost::num_vertices(g);
135 if (node_count == 0) {
137 }
138
139 const std::size_t n = static_cast<std::size_t>(node_count);
140 std::vector<double> bc(n, 0.0);
141
142 // Brandes algorithm: for each source, run BFS to count shortest paths,
143 // then back-propagate dependency scores to accumulate betweenness.
144 for (auto [s_it, s_end] = boost::vertices(g); s_it != s_end; ++s_it) {
145 const VertexDesc s = *s_it;
146 const std::size_t s_idx = get_vertex_index(s);
147
148 // Vertices in order of non-increasing distance from s (for back-propagation).
149 std::vector<std::size_t> stack;
150 stack.reserve(n);
151
152 // Predecessors on shortest paths from s, indexed by vertex index.
153 std::vector<std::vector<std::size_t>> pred(n);
154
155 // Number of shortest paths from s to each vertex.
156 std::vector<double> sigma(n, 0.0);
157 sigma[s_idx] = 1.0;
158
159 // BFS distance from s (-1 means unvisited).
160 std::vector<int> dist(n, -1);
161 dist[s_idx] = 0;
162
163 std::queue<VertexDesc> bfs_queue;
164 bfs_queue.push(s);
165
166 while (!bfs_queue.empty()) {
167 const VertexDesc v = bfs_queue.front();
168 bfs_queue.pop();
169 const std::size_t v_idx = get_vertex_index(v);
170 stack.push_back(v_idx);
171
172 for (auto [w_it, w_end] = boost::adjacent_vertices(v, g); w_it != w_end; ++w_it) {
173 const std::size_t w_idx = get_vertex_index(*w_it);
174
175 if (dist[w_idx] < 0) {
176 bfs_queue.push(*w_it);
177 dist[w_idx] = dist[v_idx] + 1;
178 }
179 if (dist[w_idx] == dist[v_idx] + 1) {
180 sigma[w_idx] += sigma[v_idx];
181 pred[w_idx].push_back(v_idx);
182 }
183 }
184 }
185
186 // Back-propagate dependency in reverse BFS order.
187 std::vector<double> delta(n, 0.0);
188 while (!stack.empty()) {
189 const std::size_t w_idx = stack.back();
190 stack.pop_back();
191 for (const std::size_t v_idx : pred[w_idx]) {
192 if (sigma[w_idx] > 0.0) {
193 delta[v_idx] += (sigma[v_idx] / sigma[w_idx]) * (1.0 + delta[w_idx]);
194 }
195 }
196 if (w_idx != s_idx) {
197 bc[w_idx] += delta[w_idx];
198 }
199 }
200 }
201
202 // Normalize by the number of ordered (directed) or unordered (undirected) pairs,
203 // matching NetworkX betweenness_centrality(G, normalized=True) semantics.
204 if (node_count > 2) {
205 const double denom = static_cast<double>(node_count - 1) * static_cast<double>(node_count - 2);
206 const double scale = Directed ? 1.0 / denom : 2.0 / denom;
207 for (double& v : bc) v *= scale;
208 }
209
210 return build_node_indexed_result<double>([&](VertexDesc v) {
211 return bc[get_vertex_index(v)];
212 });
213}
214
215template <typename GraphWrapper>
223[[deprecated("Use G.betweenness_centrality() instead.")]]
224auto betweenness_centrality(const GraphWrapper& G) {
225 return G.betweenness_centrality();
226}
227
228} // namespace nxpp
auto degree_centrality(const GraphWrapper &G)
Deprecated free-function alias for G.degree_centrality().
Definition centrality.hpp:128
auto betweenness_centrality(const GraphWrapper &G)
Deprecated free-function alias for G.betweenness_centrality().
Definition centrality.hpp:224
auto pagerank(const GraphWrapper &G)
Deprecated free-function alias for G.pagerank().
Definition centrality.hpp:115
auto pagerank(double tolerance=1e-6, std::size_t max_iterations=100) const
Computes PageRank scores for every node.
Definition centrality.hpp:50
auto betweenness_centrality() const
Computes normalized betweenness centrality for every node.
Definition centrality.hpp:133
auto degree_centrality() const
Computes normalized degree centrality for every node.
Definition centrality.hpp:20
Definition graph.hpp:158
Core graph wrapper, proxy surface, and public alias presets.