#include "evaluation.h" #include #include #include #include #include #include #include #ifdef _OPENMP #include #endif #ifdef EASYGRAPH_ENABLE_GPU #include #endif #include "../../classes/graph.h" #include "../../classes/directed_graph.h" #include "../../common/utils.h" struct pair_hash { template std::size_t operator()(const std::pair& p) const { auto h1 = std::hash()(p.first); auto h2 = std::hash()(p.second); return h1 ^ h2; } }; typedef std::unordered_map, weight_t, pair_hash> rec_type; enum norm_t { sum, max }; void preprocess_graph_for_constraint( Graph& G, std::string weight_key, std::unordered_map>& weighted_adj, std::unordered_map& strength ) { for (auto& u_entry : G.adj) { node_t u = u_entry.first; for (auto& v_entry : u_entry.second) { node_t v = v_entry.first; double w = 1.0; if (!weight_key.empty() && v_entry.second.count(weight_key)) { w = v_entry.second[weight_key]; } weighted_adj[u][v] += w; strength[u] += w; weighted_adj[v][u] += w; strength[v] += w; } } } py::object invoke_cpp_constraint(py::object G, py::object nodes, py::object weight) { std::string weight_key = weight_to_string(weight); if (nodes.is_none()) { nodes = G.attr("nodes"); } py::list nodes_list = py::list(nodes); int nodes_list_len = py::len(nodes_list); Graph& G_ref = G.cast(); std::vector node_ids(nodes_list_len); for (int i = 0; i < nodes_list_len; i++) { node_ids[i] = G_ref.node_to_id[nodes_list[i]].cast(); } std::unordered_map> weighted_adj; std::unordered_map strength; preprocess_graph_for_constraint(G_ref, weight_key, weighted_adj, strength); std::vector constraint_results(nodes_list_len, 0.0); { py::gil_scoped_release release; #pragma omp parallel for schedule(dynamic) for (int i = 0; i < nodes_list_len; i++) { node_t u = node_ids[i]; auto str_it = strength.find(u); if (str_it == strength.end() || str_it->second == 0.0) { constraint_results[i] = Py_NAN; continue; } double u_strength = str_it->second; auto& neighbors_u = weighted_adj[u]; if (neighbors_u.empty()) { constraint_results[i] = Py_NAN; continue; } std::unordered_map contrib; for (auto& neighbor : neighbors_u) { node_t j = neighbor.first; double w_uj = neighbor.second; double p_uj = w_uj / u_strength; contrib[j] += p_uj; } for (auto& neighbor_j : neighbors_u) { node_t j = neighbor_j.first; double w_uj = neighbor_j.second; double p_uj = w_uj / u_strength; auto q_it = weighted_adj.find(j); if (q_it != weighted_adj.end()) { double j_strength = strength[j]; for (auto& neighbor_q : q_it->second) { node_t q = neighbor_q.first; if (q == u) continue; double w_jq = neighbor_q.second; double p_jq = w_jq / j_strength; contrib[q] += p_uj * p_jq; } } } double c_u = 0.0; for (auto& neighbor : neighbors_u) { node_t j = neighbor.first; if (contrib.count(j)) { c_u += pow(contrib[j], 2); } } constraint_results[i] = c_u; } } py::array::ShapeContainer ret_shape{nodes_list_len}; py::array_t ret(ret_shape, constraint_results.data()); return ret; } #ifdef EASYGRAPH_ENABLE_GPU static py::object invoke_gpu_constraint(py::object G, py::object nodes, py::object weight) { 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; auto coo_graph = G_.transfer_csr_to_coo(csr_graph); std::vector& V = csr_graph->V; std::vector& E = csr_graph->E; std::vector& row = coo_graph->row; std::vector& col = coo_graph->col; std::vector *W_p = weight.is_none() ? &(coo_graph->unweighted_W) : coo_graph->W_map.find(weight_to_string(weight))->second.get(); std::unordered_map& node2idx = coo_graph->node2idx; int num_nodes = coo_graph->node2idx.size(); bool is_directed = G.attr("is_directed")().cast(); std::vector constraint_results(num_nodes, 0.0); std::vector node_mask(num_nodes, 0); py::list nodes_list; if (!nodes.is_none()) { nodes_list = py::list(nodes); for (auto node : nodes_list) { int node_id = node2idx[G_.node_to_id[node].cast()]; node_mask[node_id] = 1; } } else { nodes_list = py::list(G.attr("nodes")); std::fill(node_mask.begin(), node_mask.end(), 1); } int gpu_r = gpu_easygraph::constraint(V, E, row, col, num_nodes, *W_p, is_directed, node_mask, constraint_results); if (gpu_r != gpu_easygraph::EG_GPU_SUCC) { py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r)); } py::array::ShapeContainer ret_shape{(int)constraint_results.size()}; py::array_t ret(ret_shape, constraint_results.data()); return ret; } #endif py::object constraint(py::object G, py::object nodes, py::object weight, py::object n_workers) { #ifdef EASYGRAPH_ENABLE_GPU return invoke_gpu_constraint(G, nodes, weight); #else return invoke_cpp_constraint(G, nodes, weight); #endif } template inline weight_t get_edge_weight(const MapType& attrs, const std::string& weight_key) { if (weight_key.empty()) return 1.0; auto it = attrs.find(weight_key); return it != attrs.end() ? it->second : 1.0; } inline weight_t compute_mutual_weight(const Graph& G, node_t u, node_t v, const std::string& weight_key) { weight_t w = 0; if (G.adj.count(u)) { const auto& adj_u = G.adj.at(u); auto it = adj_u.find(v); if (it != adj_u.end()) w += get_edge_weight(it->second, weight_key); } if (G.adj.count(v)) { const auto& adj_v = G.adj.at(v); auto it = adj_v.find(u); if (it != adj_v.end()) w += get_edge_weight(it->second, weight_key); } return w; } inline weight_t compute_directed_mutual_weight(const DiGraph& G, node_t u, node_t v, const std::string& weight_key) { weight_t w = 0; if (G.adj.count(u)) { const auto& adj_u = G.adj.at(u); auto it = adj_u.find(v); if (it != adj_u.end()) w += get_edge_weight(it->second, weight_key); } if (G.adj.count(v)) { const auto& adj_v = G.adj.at(v); auto it = adj_v.find(u); if (it != adj_v.end()) w += get_edge_weight(it->second, weight_key); } return w; } std::vector compute_redundancy_core(py::object G_obj, const std::vector& target_nodes, const std::string& weight_key, bool is_directed) { // Cast to C++ objects once to avoid Python API overhead const Graph* G_ptr = nullptr; const DiGraph* DiG_ptr = nullptr; if (is_directed) { DiG_ptr = &G_obj.cast(); } else { G_ptr = &G_obj.cast(); } // Pre-compute max ID and node list node_t max_graph_id = 0; std::vector all_nodes_vec; if (is_directed) { for (const auto& kv : DiG_ptr->adj) if (kv.first > max_graph_id) max_graph_id = kv.first; for (const auto& kv : DiG_ptr->pred) if (kv.first > max_graph_id) max_graph_id = kv.first; all_nodes_vec.reserve(DiG_ptr->adj.size() + DiG_ptr->pred.size()); for(const auto& kv : DiG_ptr->adj) all_nodes_vec.push_back(kv.first); for(const auto& kv : DiG_ptr->pred) all_nodes_vec.push_back(kv.first); } else { for (const auto& kv : G_ptr->adj) if (kv.first > max_graph_id) max_graph_id = kv.first; all_nodes_vec.reserve(G_ptr->adj.size()); for(const auto& kv : G_ptr->adj) all_nodes_vec.push_back(kv.first); } // Deduplicate nodes std::sort(all_nodes_vec.begin(), all_nodes_vec.end()); all_nodes_vec.erase(std::unique(all_nodes_vec.begin(), all_nodes_vec.end()), all_nodes_vec.end()); // Ensure vector size covers target nodes if (!target_nodes.empty()) { node_t max_target = *std::max_element(target_nodes.begin(), target_nodes.end()); max_graph_id = std::max(max_graph_id, max_target); } // Pre-compute Scale std::vector scale_sum_vec(max_graph_id + 1, 0.0); std::vector scale_max_vec(max_graph_id + 1, 0.0); #pragma omp parallel for schedule(dynamic) for(int i = 0; i < (int)all_nodes_vec.size(); ++i) { node_t u = all_nodes_vec[i]; double s_sum = 0; double s_max = 0; if (is_directed) { if (DiG_ptr->adj.count(u)) { for(const auto& p : DiG_ptr->adj.at(u)) { weight_t tw = compute_directed_mutual_weight(*DiG_ptr, u, p.first, weight_key); s_sum += tw; s_max = std::max(s_max, (double)tw); } } if (DiG_ptr->pred.count(u)) { for(const auto& p : DiG_ptr->pred.at(u)) { weight_t tw = compute_directed_mutual_weight(*DiG_ptr, u, p.first, weight_key); s_sum += tw; s_max = std::max(s_max, (double)tw); } } } else { if (G_ptr->adj.count(u)) { for(const auto& p : G_ptr->adj.at(u)) { weight_t tw = compute_mutual_weight(*G_ptr, u, p.first, weight_key); s_sum += tw; s_max = std::max(s_max, (double)tw); } } } if (u < scale_sum_vec.size()) { scale_sum_vec[u] = s_sum; scale_max_vec[u] = s_max; } } // Compute Redundancy std::vector results(target_nodes.size()); if (!is_directed) { // Undirected #pragma omp parallel for schedule(dynamic) for (int i = 0; i < (int)target_nodes.size(); i++) { node_t v_id = target_nodes[i]; if (G_ptr->adj.find(v_id) == G_ptr->adj.end() || G_ptr->adj.at(v_id).empty()) { results[i] = NAN; continue; } const auto& v_neighbors = G_ptr->adj.at(v_id); double redundancy_sum = 0; double scale_v_sum = (v_id < scale_sum_vec.size()) ? scale_sum_vec[v_id] : 0; // Direct iteration avoids malloc locks for (const auto& neighbor_info : v_neighbors) { node_t u_id = neighbor_info.first; double scale_u_max = (u_id < scale_max_vec.size()) ? scale_max_vec[u_id] : 0; double r_vu = 0; for (const auto& w_pair : v_neighbors) { node_t w_id = w_pair.first; if (u_id == w_id) continue; weight_t mw_uw = compute_mutual_weight(*G_ptr, u_id, w_id, weight_key); if (mw_uw == 0) continue; weight_t mw_vw = compute_mutual_weight(*G_ptr, v_id, w_id, weight_key); double p_iq = (scale_v_sum > 0) ? (mw_vw / scale_v_sum) : 0; double m_jq = (scale_u_max > 0) ? (mw_uw / scale_u_max) : 0; r_vu += p_iq * m_jq; } redundancy_sum += (1.0 - r_vu); } results[i] = redundancy_sum; } } else { //Directed #pragma omp parallel for schedule(dynamic) for (int i = 0; i < target_nodes.size(); i++) { node_t v_id = target_nodes[i]; bool has_neighbors = (DiG_ptr->adj.count(v_id) && !DiG_ptr->adj.at(v_id).empty()) || (DiG_ptr->pred.count(v_id) && !DiG_ptr->pred.at(v_id).empty()); if (!has_neighbors) { results[i] = NAN; continue; } double redundancy_sum = 0; double scale_v_sum = (v_id < scale_sum_vec.size()) ? scale_sum_vec[v_id] : 0; // Prepare common candidates std::vector common_candidates; if (DiG_ptr->adj.count(v_id)) { for(auto& p : DiG_ptr->adj.at(v_id)) common_candidates.push_back(p.first); } if (DiG_ptr->pred.count(v_id)) { for(auto& p : DiG_ptr->pred.at(v_id)) common_candidates.push_back(p.first); } std::sort(common_candidates.begin(), common_candidates.end()); common_candidates.erase(std::unique(common_candidates.begin(), common_candidates.end()), common_candidates.end()); // Loop A: Out-neighbors if (DiG_ptr->adj.count(v_id)) { for (const auto& neighbor_info : DiG_ptr->adj.at(v_id)) { node_t u_id = neighbor_info.first; double scale_u_max = (u_id < scale_max_vec.size()) ? scale_max_vec[u_id] : 0; double r_vu = 0; for (const auto& w_id : common_candidates) { if (u_id == w_id) continue; weight_t mw_uw = compute_directed_mutual_weight(*DiG_ptr, u_id, w_id, weight_key); if (mw_uw == 0) continue; weight_t mw_vw = compute_directed_mutual_weight(*DiG_ptr, v_id, w_id, weight_key); double p_iq = (scale_v_sum > 0) ? (mw_vw / scale_v_sum) : 0; double m_jq = (scale_u_max > 0) ? (mw_uw / scale_u_max) : 0; r_vu += p_iq * m_jq; } redundancy_sum += (1.0 - r_vu); } } // Loop B: In-neighbors if (DiG_ptr->pred.count(v_id)) { for (const auto& neighbor_info : DiG_ptr->pred.at(v_id)) { node_t u_id = neighbor_info.first; double scale_u_max = (u_id < scale_max_vec.size()) ? scale_max_vec[u_id] : 0; double r_vu = 0; for (const auto& w_id : common_candidates) { if (u_id == w_id) continue; weight_t mw_uw = compute_directed_mutual_weight(*DiG_ptr, u_id, w_id, weight_key); if (mw_uw == 0) continue; weight_t mw_vw = compute_directed_mutual_weight(*DiG_ptr, v_id, w_id, weight_key); double p_iq = (scale_v_sum > 0) ? (mw_vw / scale_v_sum) : 0; double m_jq = (scale_u_max > 0) ? (mw_uw / scale_u_max) : 0; r_vu += p_iq * m_jq; } redundancy_sum += (1.0 - r_vu); } } results[i] = redundancy_sum; } } return results; } py::object invoke_cpp_effective_size(py::object G, py::object nodes, py::object weight) { std::string weight_key = weight.is_none() ? "" : weight.cast(); bool is_directed = G.attr("is_directed")().cast(); if (nodes.is_none()) nodes = G.attr("nodes"); py::list nodes_list = py::list(nodes); int len = py::len(nodes_list); std::vector target_ids(len); if (py::hasattr(G, "node_to_id")) { py::object node_to_id = G.attr("node_to_id"); for (int i = 0; i < len; i++) { target_ids[i] = node_to_id[nodes_list[i]].cast(); } } else { for (int i = 0; i < len; i++) { target_ids[i] = nodes_list[i].cast(); } } std::vector results = compute_redundancy_core(G, target_ids, weight_key, is_directed); py::array::ShapeContainer ret_shape{ (long)results.size() }; return py::array_t(ret_shape, results.data()); } #ifdef EASYGRAPH_ENABLE_GPU static py::object invoke_gpu_effective_size(py::object G, py::object nodes, py::object weight) { 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; auto coo_graph = G_.transfer_csr_to_coo(csr_graph); std::vector& V = csr_graph->V; std::vector& E = csr_graph->E; std::vector& row = coo_graph->row; std::vector& col = coo_graph->col; std::vector* W_p = weight.is_none() ? &(coo_graph->unweighted_W) : coo_graph->W_map.find(weight_to_string(weight))->second.get(); std::unordered_map& node2idx = coo_graph->node2idx; int num_nodes = coo_graph->node2idx.size(); std::vector effective_size_results(num_nodes); bool is_directed = G.attr("is_directed")().cast(); std::vector node_mask(num_nodes, 0); py::list nodes_list; if (!nodes.is_none()) { nodes_list = py::list(nodes); for (auto node : nodes_list) { int node_id = node2idx[G_.node_to_id[node].cast()]; node_mask[node_id] = 1; } } else { nodes_list = py::list(G.attr("nodes")); std::fill(node_mask.begin(), node_mask.end(), 1); } int gpu_r = gpu_easygraph::effective_size(V, E, row, col, num_nodes, *W_p, is_directed, node_mask, effective_size_results); if (gpu_r != gpu_easygraph::EG_GPU_SUCC) { py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r)); } py::array::ShapeContainer ret_shape{(int)effective_size_results.size()}; py::array_t ret(ret_shape, effective_size_results.data()); return ret; } #endif py::object effective_size(py::object G, py::object nodes, py::object weight, py::object n_workers) { #ifdef EASYGRAPH_ENABLE_GPU return invoke_gpu_effective_size(G, nodes, weight); #else return invoke_cpp_effective_size(G, nodes, weight); #endif } #ifdef EASYGRAPH_ENABLE_GPU static py::object invoke_gpu_efficiency(py::object G, py::object nodes, py::object weight) { Graph& G_ = G.cast(); py::dict effective_size = py::dict(); if (weight.is_none()) { G_.gen_CSR(); } else { G_.gen_CSR(weight_to_string(weight)); } auto csr_graph = G_.csr_graph; auto coo_graph = G_.transfer_csr_to_coo(csr_graph); std::vector& V = csr_graph->V; std::vector& E = csr_graph->E; std::vector& row = coo_graph->row; std::vector& col = coo_graph->col; std::vector* W_p = weight.is_none() ? &(coo_graph->unweighted_W) : coo_graph->W_map.find(weight_to_string(weight))->second.get(); std::unordered_map& node2idx = coo_graph->node2idx; int num_nodes = coo_graph->node2idx.size(); std::vector effective_size_results(num_nodes); bool is_directed = G.attr("is_directed")().cast(); std::vector node_mask(num_nodes, 0); py::list nodes_list; if (!nodes.is_none()) { nodes_list = py::list(nodes); for (auto node : nodes_list) { int node_id = node2idx[G_.node_to_id[node].cast()]; node_mask[node_id] = 1; } } else { nodes_list = py::list(G.attr("nodes")); std::fill(node_mask.begin(), node_mask.end(), 1); } int gpu_r = gpu_easygraph::effective_size(V, E, row, col, num_nodes, *W_p, is_directed, node_mask, effective_size_results); if (gpu_r != gpu_easygraph::EG_GPU_SUCC) { py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r)); } py::dict effective_size_dict; for (auto node : nodes_list) { int node_id = G_.node_to_id[node].cast(); int idx = node2idx[node_id]; py::object node_name = G_.id_to_node.attr("get")(py::cast(node_id)); effective_size_dict[node_name] = py::cast(effective_size_results[idx]); } py::dict degree; if (weight.is_none()) { degree = G.attr("degree")(py::none()).cast(); } else { degree = G.attr("degree")(weight).cast(); } py::dict efficiency_dict; for (auto item : effective_size_dict) { int node = py::reinterpret_borrow(item.first).cast(); double eff_size = py::reinterpret_borrow(item.second).cast(); if (!degree.contains(py::cast(node))) { continue; } double node_degree = py::reinterpret_borrow(degree[py::cast(node)]).cast(); if (node_degree == 0.0) { efficiency_dict[py::cast(node)] = py::cast(Py_NAN); } else { double efficiency_value = eff_size / node_degree; efficiency_dict[py::cast(node)] = py::cast(efficiency_value); } } return efficiency_dict; } #endif py::object invoke_cpp_efficiency(py::object G, py::object nodes, py::object weight, py::object n_workers) { std::string weight_key = weight.is_none() ? "" : weight.cast(); bool is_directed = G.attr("is_directed")().cast(); // Parsing Nodes if (nodes.is_none()) nodes = G.attr("nodes"); py::list nodes_list = py::list(nodes); int len = py::len(nodes_list); std::vector target_ids(len); if (py::hasattr(G, "node_to_id")) { py::object node_to_id = G.attr("node_to_id"); for (int i = 0; i < len; i++) { target_ids[i] = node_to_id[nodes_list[i]].cast(); } } else { for (int i = 0; i < len; i++) { target_ids[i] = nodes_list[i].cast(); } } // Compute Efficiency = Effective Size / Degree std::vector eff_sizes = compute_redundancy_core(G, target_ids, weight_key, is_directed); // Cast Graph pointers for fast degree access const Graph* G_ptr = nullptr; const DiGraph* DiG_ptr = nullptr; if (is_directed) DiG_ptr = &G.cast(); else G_ptr = &G.cast(); std::vector efficiency_results(len); #pragma omp parallel for schedule(static) for (int i = 0; i < len; ++i) { double es = eff_sizes[i]; // Propagate NAN from core if (std::isnan(es)) { efficiency_results[i] = NAN; continue; } node_t v = target_ids[i]; double degree = 0; if (is_directed) { if (DiG_ptr->adj.count(v)) degree += DiG_ptr->adj.at(v).size(); if (DiG_ptr->pred.count(v)) degree += DiG_ptr->pred.at(v).size(); } else { if (G_ptr->adj.count(v)) degree += G_ptr->adj.at(v).size(); } if (degree > 0) { efficiency_results[i] = es / degree; } else { efficiency_results[i] = NAN; } } py::array::ShapeContainer ret_shape{ (long)len }; return py::array_t(ret_shape, efficiency_results.data()); } py::object efficiency(py::object G, py::object nodes, py::object weight, py::object n_workers) { #ifdef EASYGRAPH_ENABLE_GPU return invoke_gpu_efficiency(G, nodes, weight); #else return invoke_cpp_efficiency(G, nodes, weight, n_workers); #endif } py::object invoke_cpp_hierarchy(py::object G, py::object nodes, py::object weight, py::object n_workers) { std::string weight_key = weight_to_string(weight); if (nodes.is_none()) { nodes = G.attr("nodes"); } py::list nodes_list = py::list(nodes); int nodes_list_len = py::len(nodes_list); Graph& G_ref = G.cast(); std::vector node_ids(nodes_list_len); for (int i = 0; i < nodes_list_len; i++) { node_ids[i] = G_ref.node_to_id[nodes_list[i]].cast(); } std::unordered_map> weighted_adj; std::unordered_map strength; preprocess_graph_for_constraint(G_ref, weight_key, weighted_adj, strength); std::vector hierarchy_results(nodes_list_len, 0.0); // Release GIL for parallel computation { py::gil_scoped_release release; #pragma omp parallel for schedule(dynamic) for (int i = 0; i < nodes_list_len; i++) { node_t u = node_ids[i]; // Validate node strength auto str_it = strength.find(u); if (str_it == strength.end() || str_it->second == 0.0) continue; double u_strength = str_it->second; auto& neighbors_u = weighted_adj[u]; int N = neighbors_u.size(); if (N <= 1) continue; // Calculate dyadic constraint components std::unordered_map contrib; // Direct for (auto& neighbor : neighbors_u) { node_t j = neighbor.first; double p_uj = neighbor.second / u_strength; contrib[j] += p_uj; } // Indirect for (auto& neighbor_j : neighbors_u) { node_t q = neighbor_j.first; double p_uq = neighbor_j.second / u_strength; auto q_it = weighted_adj.find(q); if (q_it != weighted_adj.end()) { double q_strength = strength[q]; for (auto& neighbor_k : q_it->second) { node_t j = neighbor_k.first; if (j == u) continue; // Check if closed triad exists if (weighted_adj[u].count(j)) { double p_qj = neighbor_k.second / q_strength; contrib[j] += p_uq * p_qj; } } } } // Compute Hierarchy score double C_total = 0.0; std::unordered_map C_j; for (auto& neighbor : neighbors_u) { node_t j = neighbor.first; if (contrib.count(j)) { double val = std::pow(contrib[j], 2); C_j[j] = val; C_total += val; } } if (C_total > 0) { double hierarchy_sum = 0.0; double denominator = N * std::log(N); for (auto& item : C_j) { double c_val = item.second; if (c_val > 0) { double p_i = c_val / C_total; double term = p_i * N; if (term > 0) { hierarchy_sum += term * std::log(term); } } } hierarchy_results[i] = hierarchy_sum / denominator; } } } py::array::ShapeContainer ret_shape{nodes_list_len}; py::array_t ret(ret_shape, hierarchy_results.data()); return ret; } #ifdef EASYGRAPH_ENABLE_GPU static py::object invoke_gpu_hierarchy(py::object G, py::object nodes, py::object weight) { 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; auto coo_graph = G_.transfer_csr_to_coo(csr_graph); std::vector& V = csr_graph->V; std::vector& E = csr_graph->E; std::vector& row = coo_graph->row; std::vector& col = coo_graph->col; std::vector *W_p = weight.is_none() ? &(coo_graph->unweighted_W) : coo_graph->W_map.find(weight_to_string(weight))->second.get(); std::unordered_map& node2idx = coo_graph->node2idx; int num_nodes = coo_graph->node2idx.size(); bool is_directed = G.attr("is_directed")().cast(); std::vector hierarchy_results; std::vector node_mask(num_nodes, 0); py::list nodes_list; if (!nodes.is_none()) { nodes_list = py::list(nodes); for (auto node : nodes_list) { int node_id = node2idx[G_.node_to_id[node].cast()]; node_mask[node_id] = 1; } } else { nodes_list = py::list(G.attr("nodes")); std::fill(node_mask.begin(), node_mask.end(), 1); } int gpu_r = gpu_easygraph::hierarchy(V, E, row, col, num_nodes, *W_p, is_directed, node_mask, hierarchy_results); if (gpu_r != gpu_easygraph::EG_GPU_SUCC) { py::pybind11_fail(gpu_easygraph::err_code_detail(gpu_r)); } py::dict hierarchy_dict; for (auto node : nodes_list) { int node_id = G_.node_to_id[node].cast(); int idx = node2idx[node_id]; py::object node_name = G_.id_to_node.attr("get")(py::cast(node_id)); hierarchy_dict[node_name] = py::cast(hierarchy_results[idx]); } return hierarchy_dict; } #endif py::object hierarchy(py::object G, py::object nodes, py::object weight, py::object n_workers) { #ifdef EASYGRAPH_ENABLE_GPU return invoke_gpu_hierarchy(G, nodes, weight); #else return invoke_cpp_hierarchy(G, nodes, weight, n_workers); #endif }