#ifdef _OPENMP #include #endif #include #include #include #include #include #include #include "centrality.h" #ifdef EASYGRAPH_ENABLE_GPU #include #endif #include "../../classes/graph.h" #include "../../common/utils.h" #include "../../classes/linkgraph.h" namespace py = pybind11; void betweenness_bfs_worker( const Graph_L& G_l, const int& S, std::vector& bc, int cutoff, int endpoints_, std::vector& q, std::vector& dis, std::vector& head_path, std::vector& St, std::vector& count_path, std::vector& delta, std::vector& E_path, std::vector& stamp, int& cur_stamp ) { int N = G_l.n; int edge_number_path = 0; int cnt_St = 0; ++cur_stamp; if ((int)q.size() < N + 1) q.resize(N + 1); int front = 0, back = 0; int cutoff_int = (cutoff < 0) ? -1 : cutoff; stamp[S] = cur_stamp; dis[S] = 0; count_path[S] = 1; delta[S] = 0.0; head_path[S] = 0; q[back++] = S; const std::vector& head = G_l.head; const std::vector& E = G_l.edges; while (front < back) { int u = q[front++]; int du = dis[u]; if (cutoff_int >= 0 && du > cutoff_int) break; St[cnt_St++] = u; for (int p = head[u]; p != -1; p = E[p].next) { int v = E[p].to; int new_dis = du + 1; if (cutoff_int >= 0 && new_dis > cutoff_int) continue; if (stamp[v] != cur_stamp) { stamp[v] = cur_stamp; dis[v] = new_dis; count_path[v] = count_path[u]; delta[v] = 0.0; head_path[v] = 0; q[back++] = v; E_path[++edge_number_path].next = head_path[v]; E_path[edge_number_path].to = u; head_path[v] = edge_number_path; } else if (dis[v] == new_dis) { count_path[v] += count_path[u]; E_path[++edge_number_path].next = head_path[v]; E_path[edge_number_path].to = u; head_path[v] = edge_number_path; } } } if (endpoints_) bc[S] += cnt_St - 1; while (cnt_St > 0) { int u = St[--cnt_St]; double cu = count_path[u]; if (cu != 0) { double coeff = (1.0 + delta[u]) / cu; for (int p = head_path[u]; p; p = E_path[p].next) { int w = E_path[p].to; delta[w] += count_path[w] * coeff; } } if (u != S) bc[u] += delta[u] + endpoints_; } } void betweenness_dijkstra_worker( const Graph_L& G_l, const int& S, std::vector& bc, double cutoff, std::vector& dis, std::vector& head_path, std::vector& St, std::vector& count_path, std::vector& delta, std::vector& E_path, int endpoints_, std::vector& stamp, int& cur_stamp ) { const int dis_inf = 0x3f3f3f3f; int N = G_l.n; int edge_number_path = 0; int cnt_St = 0; ++cur_stamp; stamp[S] = cur_stamp; dis[S] = 0; count_path[S] = 1; delta[S] = 0.0; head_path[S] = 0; std::priority_queue, std::vector>, std::greater>> pq; pq.push({0, S}); const std::vector& head = G_l.head; const std::vector& E = G_l.edges; while (!pq.empty()) { std::pair top = pq.top(); pq.pop(); int d = top.first; int u = top.second; if (d > dis[u]) continue; if (cutoff >= 0 && d > cutoff) continue; St[cnt_St++] = u; for (int p = head[u]; p != -1; p = E[p].next) { int v = E[p].to; int w = E[p].w; int nd = dis[u] + w; if (cutoff >= 0 && nd > cutoff) continue; bool first_visit = (stamp[v] != cur_stamp); if (first_visit || dis[v] > nd) { if (first_visit) { stamp[v] = cur_stamp; delta[v] = 0.0; } dis[v] = nd; count_path[v] = count_path[u]; head_path[v] = 0; E_path[++edge_number_path].next = head_path[v]; E_path[edge_number_path].to = u; head_path[v] = edge_number_path; pq.push({nd, v}); } else if (dis[v] == nd) { count_path[v] += count_path[u]; E_path[++edge_number_path].next = head_path[v]; E_path[edge_number_path].to = u; head_path[v] = edge_number_path; } } } if (endpoints_) bc[S] += cnt_St - 1; while (cnt_St > 0) { int u = St[--cnt_St]; double cu = count_path[u]; if (cu != 0) { double coeff = (1.0 + delta[u]) / cu; for (int p = head_path[u]; p; p = E_path[p].next) { int w = E_path[p].to; delta[w] += count_path[w] * coeff; } } if (u != S) bc[u] += delta[u] + endpoints_; } } static double calc_scale(int len_V, int is_directed, int normalized, int endpoints) { double scale = 1.0; if (normalized) { if (endpoints) { if (len_V < 2) { scale = 1.0; } else { scale = 1.0 / (double(len_V) * (len_V - 1)); } } else { if (len_V <= 2) { scale = 1.0; } else { scale = 1.0 / ((double(len_V) - 1) * (len_V - 2)); } } } else { if (!is_directed) { scale = 0.5; } else { scale = 1.0; } } return scale; } static py::object invoke_cpp_betweenness_centrality( py::object G, py::object weight, py::object cutoff, py::object sources, py::object normalized, py::object endpoints ) { Graph& G_ = G.cast(); int cutoff_ = -1; if (!cutoff.is_none()) { cutoff_ = cutoff.cast(); } int N = G_.node.size(); bool is_directed = G.attr("is_directed")().cast(); int normalized_ = normalized.cast(); int endpoints_ = endpoints.cast(); double scale = calc_scale(N, is_directed, normalized_, endpoints_); bool use_weights = !weight.is_none(); std::string weight_key = ""; if (use_weights) { weight_key = weight_to_string(weight); } Graph_L G_l; if (G_.linkgraph_dirty) { G_l = graph_to_linkgraph(G_, is_directed, weight_key, false, false); G_.linkgraph_structure = G_l; } else { G_l = G_.linkgraph_structure; } int edges_num = G_l.edges.size(); std::vector bc(N + 1, 0.0); std::vector BC; int num_threads = 1; #ifdef _OPENMP num_threads = omp_get_max_threads(); #endif std::vector> dis_all(num_threads, std::vector(N + 1)); std::vector> head_path_all(num_threads, std::vector(N + 1)); std::vector> St_all(num_threads, std::vector(N + 1)); std::vector> count_path_all(num_threads, std::vector(N + 1)); std::vector> delta_all(num_threads, std::vector(N + 1)); std::vector> E_path_all(num_threads, std::vector(edges_num + 1)); std::vector> queue_all(num_threads, std::vector(N + 1)); std::vector> stamp_all(num_threads, std::vector(N + 1, 0)); std::vector cur_stamp_all(num_threads, 0); std::vector> bc_local_all(num_threads, std::vector(N + 1, 0.0)); if (!sources.is_none()) { py::list sources_list = py::list(sources); int sources_list_len = py::len(sources_list); std::vector sources_vec; sources_vec.reserve(sources_list_len); for (int i = 0; i < sources_list_len; i++) { if (G_.node_to_id.attr("get")(sources_list[i], py::none()).is_none()) { printf("The node should exist in the graph!"); return py::none(); } sources_vec.push_back(G_.node_to_id.attr("get")(sources_list[i]).cast()); } #ifdef _OPENMP #pragma omp parallel for schedule(dynamic) #endif for (int i = 0; i < sources_list_len; i++) { node_t source_id = sources_vec[i]; #ifdef _OPENMP int tid = omp_get_thread_num(); #else int tid = 0; #endif auto& bc_local = bc_local_all[tid]; auto& dis = dis_all[tid]; auto& head_path = head_path_all[tid]; auto& St = St_all[tid]; auto& count_path = count_path_all[tid]; auto& delta = delta_all[tid]; auto& E_path = E_path_all[tid]; auto& q = queue_all[tid]; auto& stamp = stamp_all[tid]; int& cur_stamp = cur_stamp_all[tid]; if (use_weights) { betweenness_dijkstra_worker( G_l, source_id, bc_local, cutoff_, dis, head_path, St, count_path, delta, E_path, endpoints_, stamp, cur_stamp ); } else { betweenness_bfs_worker( G_l, source_id, bc_local, cutoff_, endpoints_, q, dis, head_path, St, count_path, delta, E_path, stamp, cur_stamp ); } } } else { #ifdef _OPENMP #pragma omp parallel for schedule(dynamic) #endif for (int i = 1; i <= N; ++i) { #ifdef _OPENMP int tid = omp_get_thread_num(); #else int tid = 0; #endif auto& bc_local = bc_local_all[tid]; auto& dis = dis_all[tid]; auto& head_path = head_path_all[tid]; auto& St = St_all[tid]; auto& count_path = count_path_all[tid]; auto& delta = delta_all[tid]; auto& E_path = E_path_all[tid]; auto& q = queue_all[tid]; auto& stamp = stamp_all[tid]; int& cur_stamp = cur_stamp_all[tid]; if (use_weights) { betweenness_dijkstra_worker( G_l, i, bc_local, cutoff_, dis, head_path, St, count_path, delta, E_path, endpoints_, stamp, cur_stamp ); } else { betweenness_bfs_worker( G_l, i, bc_local, cutoff_, endpoints_, q, dis, head_path, St, count_path, delta, E_path, stamp, cur_stamp ); } } } #ifdef _OPENMP #pragma omp parallel for schedule(static) for (int j = 1; j <= N; ++j) { double s = 0.0; for (int tid = 0; tid < num_threads; ++tid) s += bc_local_all[tid][j]; bc[j] += s; } #else for (int j = 1; j <= N; ++j) { bc[j] += bc_local_all[0][j]; } #endif BC.reserve(N); for (int i = 1; i <= N; i++) { BC.push_back(scale * bc[i]); } py::array::ShapeContainer ret_shape{(int)BC.size()}; py::array_t ret(ret_shape, BC.data()); return ret; } #ifdef EASYGRAPH_ENABLE_GPU static py::object invoke_gpu_betweenness_centrality(py::object G, py::object weight, py::object py_sources, py::object normalized, py::object endpoints) { Graph& G_ = G.cast(); if (weight.is_none()) { G_.gen_CSR(); } else { G_.gen_CSR(weight_to_string(weight)); } auto csr_graph = G_.csr_graph; std::vector& E = csr_graph->E; std::vector& V = csr_graph->V; std::vector *W_p = weight.is_none() ? &(csr_graph->unweighted_W) : csr_graph->W_map.find(weight_to_string(weight))->second.get(); auto sources = G_.gen_CSR_sources(py_sources); std::vector BC; bool is_directed = G.attr("is_directed")().cast(); int gpu_r = gpu_easygraph::betweenness_centrality(V, E, *W_p, *sources, is_directed, normalized.cast(), endpoints.cast(), BC); if (gpu_r != gpu_easygraph::EG_GPU_SUCC) { // the code below will throw an exception py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r)); } py::array::ShapeContainer ret_shape{(int)BC.size()}; py::array_t ret(ret_shape, BC.data()); return ret; } #endif py::object betweenness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources, py::object normalized, py::object endpoints) { #ifdef EASYGRAPH_ENABLE_GPU return invoke_gpu_betweenness_centrality(G, weight, sources, normalized, endpoints); #else return invoke_cpp_betweenness_centrality(G, weight, cutoff, sources, normalized, endpoints); #endif } // void betweenness_dijkstra(const Graph_L& G_l, const int &S, std::vector& bc, double cutoff) { // int N = G_l.n; // int edge_number_path = 0; // __gnu_pbds::priority_queue q; // std::vector dis(N+1, INFINITY); // std::vector vis(N+1, false); // std::vector head_path(N+1, 0); // const std::vector& head = G_l.head; // const std::vector& E = G_l.edges; // int edges_num = E.size(); // std::vector St(N+1, 0); // std::vector count_path(N+1, 0); // std::vector delta(N+1, 0); // std::vector E_path(edges_num+1); // head_path[S] = 0; // dis[S] = 0; // count_path[S] = 1; // dis[S] = 0; // count_path[S] = 1; // q.push(compare_node(S, 0)); // int cnt_St = 0; // while(!q.empty()) { // int u = q.top().x; // q.pop(); // if (vis[u]){ // continue; // } // if (cutoff >= 0 && dis[u] > cutoff){ // continue; // } // St[cnt_St++] = u; // vis[u] = true; // for(int p = head[u]; p != -1; p = E[p].next) { // int v = E[p].to; // if(cutoff >= 0 && (dis[u] + E[p].w) > cutoff){ // continue; // } // if (dis[v] > dis[u] + E[p].w) { // dis[v] = dis[u] + E[p].w; // q.push(compare_node(v, dis[v])); // count_path[v] = count_path[u]; // head_path[v] = 0; // E_path[++edge_number_path].next = head_path[v]; // E_path[edge_number_path].to = u; // head_path[v] = edge_number_path; // } // else if (dis[v] == dis[u] + E[p].w) { // count_path[v] += count_path[u]; // E_path[++edge_number_path].next = head_path[v]; // E_path[edge_number_path].to = u; // head_path[v] = edge_number_path; // } // } // } // while (cnt_St > 0) { // int u = St[--cnt_St]; // float coeff = (1.0 + delta[u]) / count_path[u]; // for(int p = head_path[u]; p; p = E_path[p].next){ // delta[E_path[p].to] += count_path[E_path[p].to] * coeff; // } // if (u != S) // bc[u] += delta[u]; // } // }