chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:36:30 +08:00
commit 55ab4e4a73
473 changed files with 72932 additions and 0 deletions
+83
View File
@@ -0,0 +1,83 @@
cmake_minimum_required(VERSION 3.23)
project(cpp_easygraph)
set(CMAKE_CXX_STANDARD 11)
file(GLOB SOURCES
classes/*.cpp
common/*.cpp
functions/*/*.cpp
cpp_easygraph.cpp
)
add_subdirectory(pybind11)
option(EASYGRAPH_ENABLE_OPENMP "Enable OpenMP acceleration (auto-detect)" ON)
option(EASYGRAPH_ENABLE_GPU "EASYGRAPH_ENABLE_GPU" OFF)
if (EASYGRAPH_ENABLE_GPU)
pybind11_add_module(cpp_easygraph
${SOURCES}
$<TARGET_OBJECTS:gpu_easygraph>
)
set_property(TARGET cpp_easygraph PROPERTY CUDA_ARCHITECTURES all)
target_compile_definitions(cpp_easygraph
PRIVATE EASYGRAPH_ENABLE_GPU
)
target_link_libraries(cpp_easygraph
PRIVATE cudart_static
)
else()
pybind11_add_module(cpp_easygraph
${SOURCES}
)
endif()
if (EASYGRAPH_ENABLE_OPENMP)
if (APPLE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
find_path(EG_LIBOMP_INCLUDE_DIR
NAMES omp.h
PATHS
/opt/homebrew/opt/libomp/include
/usr/local/opt/libomp/include
)
find_library(EG_LIBOMP_LIBRARY
NAMES omp libomp
PATHS
/opt/homebrew/opt/libomp/lib
/usr/local/opt/libomp/lib
)
if (EG_LIBOMP_INCLUDE_DIR AND EG_LIBOMP_LIBRARY)
message(STATUS "libomp found: ${EG_LIBOMP_LIBRARY}")
set(OpenMP_C_FLAGS "-Xpreprocessor -fopenmp" CACHE STRING "" FORCE)
set(OpenMP_CXX_FLAGS "-Xpreprocessor -fopenmp" CACHE STRING "" FORCE)
set(OpenMP_CXX_INCLUDE_DIR "${EG_LIBOMP_INCLUDE_DIR}" CACHE PATH "" FORCE)
set(OpenMP_omp_LIBRARY "${EG_LIBOMP_LIBRARY}" CACHE FILEPATH "" FORCE)
else()
message(STATUS
"libomp not found on macOS. OpenMP will be disabled.\n"
"To enable OpenMP, run: brew install libomp"
)
endif()
endif()
find_package(OpenMP QUIET)
endif()
if (OpenMP_CXX_FOUND)
message(STATUS "OpenMP found, enabling parallel acceleration.")
target_link_libraries(cpp_easygraph PRIVATE OpenMP::OpenMP_CXX)
target_compile_definitions(cpp_easygraph PRIVATE EASYGRAPH_USE_OPENMP=1)
else()
message(STATUS "OpenMP not found, building in single-thread mode.")
target_compile_definitions(cpp_easygraph PRIVATE EASYGRAPH_USE_OPENMP=0)
endif()
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "graph.h"
#include "directed_graph.h"
#include "operation.h"
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../common/common.h"
struct COOGraph {
std::vector<int> row;
std::vector<int> col;
std::vector<double> unweighted_W;
std::unordered_map<std::string, std::shared_ptr<std::vector<double>>> W_map;
std::vector<node_t> nodes;
std::unordered_map<node_t, int> node2idx;
};
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include "../common/common.h"
struct CSRGraph {
std::vector<int> V;
std::vector<int> E;
std::vector<double> unweighted_W;
std::unordered_map<std::string, std::shared_ptr<std::vector<double>>> W_map;
std::vector<node_t> nodes;
std::unordered_map<node_t, int> node2idx;
};
+613
View File
@@ -0,0 +1,613 @@
#include "directed_graph.h"
#include "../common/utils.h"
DiGraph::DiGraph() : Graph() {
}
py::object DiGraph__init__(py::args args, py::kwargs kwargs) {
py::object self = args[0];
self.attr("__init__")();
DiGraph& self_ = self.cast<DiGraph&>();
py::dict graph_attr = kwargs;
self_.graph.attr("update")(graph_attr);
self_.nodes_cache = py::dict();
self_.adj_cache = py::dict();
return py::none();
}
py::object DiGraph_out_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(u)) {
degree[u] = py::object(degree[u]) + d.attr("get")(weight, 1);
} else {
degree[u] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object DiGraph_in_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(v)) {
degree[v] = py::object(degree[v]) + d.attr("get")(weight, 1);
} else {
degree[v] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object DiGraph_degree(py::object self, py::object weight) {
py::dict degree = py::dict();
py::dict out_degree = self.attr("out_degree")(weight).cast<py::dict>();
py::dict in_degree = self.attr("in_degree")(weight).cast<py::dict>();
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object u = nodes[i];
degree[u] = out_degree[u] + in_degree[u];
}
return degree;
}
py::object DiGraph_size(py::object self, py::object weight) {
py::dict out_degree = self.attr("out_degree")(weight).cast<py::dict>();
py::object s = py_sum(out_degree.attr("values")());
return (weight.is_none()) ? py::int_(s) : s;
}
py::object DiGraph_number_of_edges(py::object self, py::object u, py::object v) {
if (u.is_none()) {
return self.attr("size")();
}
Graph& G = self.cast<Graph&>();
node_t u_id = G.node_to_id[u].cast<node_t>();
node_t v_id = G.node_to_id.attr("get")(v, -1).cast<node_t>();
return py::cast(int(v_id != -1 && G.adj[u_id].count(v_id)));
}
py::object DiGraph_neighbors(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
if (self_.node_to_id.contains(node)) {
return self.attr("adj")[node].attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
py::object DiGraph_predecessors(py::object self, py::object node) {
DiGraph& self_ = self.cast<DiGraph&>();
adj_dict_factory& pred = self_.pred;
node_t node_id = self_.node_to_id[node].cast<node_t>();
if (pred.find(node_id) != pred.end()) {
adj_attr_dict_factory node_pred_dict = pred[node_id];
py::dict node_pred = py::dict();
for (adj_attr_dict_factory::iterator i = node_pred_dict.begin();
i != node_pred_dict.end(); i++) {
edge_attr_dict_factory edge_attr_dict = i->second;
node_pred[self_.id_to_node[py::cast(i->first)]] = attr_to_dict(edge_attr_dict);
}
return node_pred.attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
node_t DiGraph_add_one_node(DiGraph& self, py::object one_node_for_adding,
py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
self.adj[id] = adj_attr_dict_factory();
self.pred[id] = adj_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
py::object DiGraph_add_node(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::object one_node_for_adding = args[1];
py::dict node_attr = kwargs;
DiGraph_add_one_node(self, one_node_for_adding, node_attr);
return py::none();
}
py::object DiGraph_add_nodes(DiGraph& self, py::list nodes_for_adding, py::list nodes_attr) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (py::len(nodes_attr) != 0) {
if (py::len(nodes_for_adding) != py::len(nodes_attr)) {
PyErr_Format(PyExc_AssertionError, "Nodes and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(nodes_for_adding); i++) {
py::object one_node_for_adding = nodes_for_adding[i];
py::dict node_attr;
if (py::len(nodes_attr)) {
node_attr = nodes_attr[i].cast<py::dict>();
} else {
node_attr = py::dict();
}
DiGraph_add_one_node(self, one_node_for_adding, node_attr);
}
return py::none();
}
py::object DiGraph_add_nodes_from(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::list nodes_for_adding = py::list(args[1]);
for (int i = 0; i < py::len(nodes_for_adding); i++) {
bool newnode;
py::dict attr = kwargs;
py::dict newdict, ndict;
py::object n = nodes_for_adding[i];
try {
newnode = !self.node_to_id.contains(n);
newdict = attr;
} catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
py::tuple n_pair = n.cast<py::tuple>();
n = n_pair[0];
ndict = n_pair[1].cast<py::dict>();
newnode = !self.node_to_id.contains(n);
newdict = attr.attr("copy")();
newdict.attr("update")(ndict);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
if (newnode) {
if (n.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
DiGraph_add_one_node(self, n);
}
node_t id = self.node_to_id[n].cast<node_t>();
py::list items = py::list(newdict.attr("items")());
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
}
return py::none();
}
py::object DiGraph_remove_node(DiGraph& self, py::object node_to_remove) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (!self.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
node_t node_to_remove_id = self.node_to_id[node_to_remove].cast<node_t>();
adj_attr_dict_factory succs = self.adj[node_to_remove_id];
adj_attr_dict_factory preds = self.pred[node_to_remove_id];
self.node.erase(node_to_remove_id);
for (adj_attr_dict_factory::iterator i = succs.begin(); i != succs.end(); i++) {
self.pred[i->first].erase(node_to_remove_id);
}
for (adj_attr_dict_factory::iterator i = preds.begin(); i != preds.end(); i++) {
self.adj[i->first].erase(node_to_remove_id);
}
self.adj.erase(node_to_remove_id);
self.pred.erase(node_to_remove_id);
self.node_to_id.attr("pop")(node_to_remove);
self.id_to_node.attr("pop")(node_to_remove_id);
return py::none();
}
py::object DiGraph_remove_nodes(py::object self, py::list nodes_to_remove) {
DiGraph& self_ = self.cast<DiGraph&>();
self_.dirty_nodes = true;
self_.dirty_adj = true;
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
if (!self_.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
}
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
self.attr("remove_node")(node_to_remove);
}
return py::none();
}
void DiGraph_add_one_edge(DiGraph& self, py::object u_of_edge,
py::object v_of_edge, py::object edge_attr) {
node_t u, v;
if (!self.node_to_id.contains(u_of_edge)) {
u = DiGraph_add_one_node(self, u_of_edge);
} else {
u = self.node_to_id[u_of_edge].cast<node_t>();
}
if (!self.node_to_id.contains(v_of_edge)) {
v = DiGraph_add_one_node(self, v_of_edge);
} else {
v = self.node_to_id[v_of_edge].cast<node_t>();
}
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.pred[v][u] = edge_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.pred[v][u].insert(std::make_pair(weight_key, value));
}
}
py::object DiGraph_add_edge(py::args args, py::kwargs kwargs) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::object u_of_edge = args[1], v_of_edge = args[2];
py::dict edge_attr = kwargs;
DiGraph_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object DiGraph_add_edges(DiGraph& self, py::list edges_for_adding, py::list edges_attr) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (py::len(edges_attr) != 0) {
if (py::len(edges_for_adding) != py::len(edges_attr)) {
PyErr_Format(PyExc_AssertionError, "Edges and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(edges_for_adding); i++) {
py::tuple one_edge_for_adding = edges_for_adding[i].cast<py::tuple>();
py::dict edge_attr;
if (py::len(edges_attr)) {
edge_attr = edges_attr[i].cast<py::dict>();
} else {
edge_attr = py::dict();
}
DiGraph_add_one_edge(self, one_edge_for_adding[0], one_edge_for_adding[1], edge_attr);
}
return py::none();
}
py::object DiGraph_add_edges_from(py::args args, py::kwargs attr) {
DiGraph& self = args[0].cast<DiGraph&>();
self.dirty_nodes = true;
self.dirty_adj = true;
py::list ebunch_to_add = py::list(args[1]);
for (int i = 0; i < len(ebunch_to_add); i++) {
py::list e = py::list(ebunch_to_add[i]);
py::object u, v;
py::dict dd;
switch (len(e)) {
case 2: {
u = e[0];
v = e[1];
break;
}
case 3: {
u = e[0];
v = e[1];
dd = e[2].cast<py::dict>();
break;
}
default: {
PyErr_Format(PyExc_ValueError, "Edge tuple %R must be a 2 - tuple or 3 - tuple.", e.ptr());
return py::none();
}
}
node_t u_id, v_id;
if (!self.node_to_id.contains(u)) {
if (u.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
u_id = DiGraph_add_one_node(self, u);
} else {
u_id = self.node_to_id[u].cast<node_t>();
}
if (!self.node_to_id.contains(v)) {
if (v.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
v_id = DiGraph_add_one_node(self, v);
} else {
v_id = self.node_to_id[v].cast<node_t>();
}
auto datadict = self.adj[u_id].count(v_id) ? self.adj[u_id][v_id] : node_attr_dict_factory();
py::list items = py::list(attr.attr("items")());
items.attr("extend")(py::list(dd.attr("items")()));
for (int j = 0; j < py::len(items); j++) {
py::tuple kv = items[j].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
datadict.insert(std::make_pair(weight_key, value));
}
// Warning: in Graph.py the edge attr is directed assigned by the dict extended from the original attr
self.adj[u_id][v_id].insert(datadict.begin(), datadict.end());
}
return py::none();
}
py::object DiGraph_add_edges_from_file(DiGraph& self, py::str file, py::object weighted, py::object is_transform) {
self.dirty_nodes = true;
self.dirty_adj = true;
bool _is_transform = is_transform.cast<bool>();
struct commactype : std::ctype<char> {
commactype() : std::ctype<char>(get_table()) {}
std::ctype_base::mask const* get_table() {
std::ctype_base::mask* rc = 0;
if (rc == 0) {
rc = new std::ctype_base::mask[std::ctype<char>::table_size];
std::fill_n(rc, std::ctype<char>::table_size, std::ctype_base::mask());
rc[','] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc['\t'] = std::ctype_base::space;
rc['\n'] = std::ctype_base::space;
rc['\r'] = std::ctype_base::space;
}
return rc;
}
};
std::ios::sync_with_stdio(0);
std::string file_path = file.cast<std::string>();
std::ifstream in;
in.open(file_path);
if (!in.is_open()) {
PyErr_Format(PyExc_FileNotFoundError, "Please check the file and make sure the path only contains English");
return py::none();
}
in.imbue(std::locale(std::locale(), new commactype));
std::string data, key("weight");
std::string su, sv;
weight_t weight;
while (in >> su >> sv) {
py::str pu(su), pv(sv);
node_t u, v;
if (!self.node_to_id.contains(pu)) {
u = DiGraph_add_one_node(self, pu);
} else {
u = self.node_to_id[pu].cast<node_t>();
}
if (!self.node_to_id.contains(pv)) {
v = DiGraph_add_one_node(self, pv);
} else {
v = self.node_to_id[pv].cast<node_t>();
}
if (weighted.cast<bool>()) {
in >> weight;
self.adj[u][v][key] = weight;
self.pred[v][u][key] = weight;
} else {
if (!self.adj[u].count(v)) {
self.adj[u][v] = node_attr_dict_factory();
self.pred[v][u] = node_attr_dict_factory();
}
}
}
if(_is_transform){
Graph_L g_l = graph_to_linkgraph(self,true, key, true, false);
self.linkgraph_structure = g_l;
self.linkgraph_dirty = false;
}
in.close();
return py::none();
}
py::object DiGraph_add_weighted_edge(DiGraph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight) {
self.dirty_nodes = true;
self.dirty_adj = true;
py::dict edge_attr;
edge_attr["weight"] = weight;
DiGraph_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object DiGraph_remove_edge(DiGraph& self, py::object u, py::object v) {
self.dirty_nodes = true;
self.dirty_adj = true;
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& u_neighbors_info = self.adj[u_id];
if (u_neighbors_info.find(v_id) != u_neighbors_info.end()) {
u_neighbors_info.erase(v_id);
auto& v_predecessors_info = self.pred[v_id];
v_predecessors_info.erase(u_id);
return py::none();
}
}
PyErr_Format(PyExc_KeyError, "No edge %R-%R in graph.", u.ptr(), v.ptr());
return py::none();
}
py::object DiGraph_remove_edges(py::object self, py::list edges_to_remove) {
DiGraph& self_ = self.cast<DiGraph&>();
for (int i = 0; i < py::len(edges_to_remove); i++) {
py::tuple edge = edges_to_remove[i].cast<py::tuple>();
py::object u = edge[0], v = edge[1];
self.attr("remove_edge")(u, v);
}
self_.dirty_nodes = true;
self_.dirty_adj = true;
return py::none();
}
py::object DiGraph_remove_edges_from(py::object self, py::list ebunch) {
DiGraph& self_ = self.cast<DiGraph&>();
for (int i = 0; i < py::len(ebunch); i++) {
py::tuple edge = ebunch[i].cast<py::tuple>();
node_t u_id = edge[0].cast<node_t>();
node_t v_id = edge[1].cast<node_t>();
if (self_.adj[u_id].find(v_id) != self_.adj[u_id].end() && self_.adj[v_id].find(u_id) != self_.adj[v_id].end()) {
self_.adj[u_id].erase(v_id);
self_.pred[v_id].erase(u_id);
}
}
return py::none();
}
py::object DiGraph_nodes_subgraph(py::object self, py::list from_nodes) {
py::object G = self.attr("__class__")();
Graph& self_ = self.cast<Graph&>();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
for (int i = 0; i < py::len(from_nodes); i++) {
py::object node = from_nodes[i];
if (self_.node_to_id.contains(node)) {
py::object node_attr = nodes[node];
DiGraph_add_one_node(G_, node, node_attr);
}
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (from_nodes.contains(v)) {
DiGraph_add_one_edge(G_, node, v, edge_attr);
}
}
}
return G;
}
py::object DiGraph_generate_linkgraph(py::object self, py::object weight){
DiGraph& G_ = self.cast<DiGraph&>();
std::string w = weight_to_string(weight);
Graph_L g_l = graph_to_linkgraph(G_, true, w, true, false);
G_.linkgraph_dirty = false;
G_.linkgraph_structure = g_l;
return py::none();
}
py::object DiGraph_copy(py::object self) {
DiGraph& self_ = self.cast<DiGraph&>();
py::object G = self.attr("__class__")();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
G_.id_to_node.attr("update")(self_.id_to_node);
G_.node_to_id.attr("update")(self_.node_to_id);
G_.node = self_.node;
G_.adj = self_.adj;
G_.pred = self_.pred;
return py::object(G);
}
py::object DiGraph_is_directed(py::object self) {
return py::cast(true);
}
py::object DiGraph_py(py::object self) {
py::object G = py::module_::import("easygraph").attr("DiGraph")();
G.attr("graph").attr("update")(self.attr("graph"));
G.attr("adj").attr("update")(self.attr("adj"));
G.attr("nodes").attr("update")(self.attr("nodes"));
G.attr("pred").attr("update")(self.attr("pred"));
// G.attr("succ").attr("update")(self.attr("succ"));
return G;
}
py::object DiGraph::get_pred() {
adj_dict_factory pred = this->pred;
py::dict predecessors = py::dict();
for (const auto& ego_edges : this->pred) {
node_t start_point = ego_edges.first;
py::dict ego_edges_dict = py::dict();
for (const auto& edge_info : ego_edges.second) {
node_t end_point = edge_info.first;
const auto& edge_attr = edge_info.second;
ego_edges_dict[this->id_to_node[py::cast(end_point)]] = attr_to_dict(edge_attr);
}
predecessors[this->id_to_node[py::cast(start_point)]] = ego_edges_dict;
}
return predecessors;
}
py::object DiGraph::get_edges() {
py::list edges = py::list();
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
edges.append(py::make_tuple(this->id_to_node[py::cast(u)], this->id_to_node[py::cast(v)],
attr_to_dict(edge_attr)));
}
}
}
return edges;
}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include "../common/common.h"
#include "graph.h"
struct DiGraph : public Graph {
DiGraph();
py::object get_edges();
py::object get_pred();
adj_dict_factory pred;
};
py::object DiGraph__init__(py::args args, py::kwargs kwargs);
py::object DiGraph_out_degree(py::object self, py::object weight);
py::object DiGraph_in_degree(py::object self, py::object weight);
py::object DiGraph_degree(py::object self, py::object weight);
py::object DiGraph_size(py::object self, py::object weight);
py::object DiGraph_number_of_edges(py::object self, py::object u, py::object v);
py::object DiGraph_predecessors(py::object self, py::object node);
py::object DiGraph_add_node(py::args args, py::kwargs kwargs);
py::object DiGraph_add_nodes(DiGraph& self, py::list nodes_for_adding, py::list nodes_attr);
py::object DiGraph_add_nodes_from(py::args args, py::kwargs kwargs);
py::object DiGraph_remove_node(DiGraph& self, py::object node_to_remove);
py::object DiGraph_remove_nodes(py::object self, py::list nodes_to_remove);
py::object DiGraph_add_edge(py::args args, py::kwargs kwargs);
py::object DiGraph_add_edges(DiGraph& self, py::list edges_for_adding, py::list edges_attr);
py::object DiGraph_add_edges_from_file(DiGraph& self, py::str file, py::object weighted, py::object is_transform);
py::object DiGraph_add_edges_from(py::args args, py::kwargs attr);
py::object DiGraph_remove_edge(DiGraph& self, py::object u, py::object v);
py::object DiGraph_remove_edges(py::object self, py::list edges_to_remove);
py::object DiGraph_remove_edges_from(py::object self, py::list ebunch);
py::object DiGraph_add_weighted_edge(DiGraph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight);
py::object DiGraph_nodes_subgraph(py::object self, py::list from_nodes);
py::object DiGraph_is_directed(py::object self);
py::object DiGraph_py(py::object self);
py::object DiGraph_generate_linkgraph(py::object self, py::object weight);
+990
View File
@@ -0,0 +1,990 @@
#include "graph.h"
#include "linkgraph.h"
#include "../common/utils.h"
Graph::Graph() {
this->id = 0;
this->dirty_nodes = true;
this->dirty_adj = true;
this->linkgraph_dirty = true;
this->csr_graph = nullptr;
this->node_to_id = py::dict();
this->id_to_node = py::dict();
this->graph = py::dict();
this->nodes_cache = py::dict();
this->adj_cache = py::dict();
this->coo_graph = nullptr;
}
py::object Graph__init__(py::args args, py::kwargs kwargs) {
py::object self = args[0];
self.attr("__init__")();
Graph& self_ = self.cast<Graph&>();
py::dict graph_attr = kwargs;
self_.graph.attr("update")(graph_attr);
self_.nodes_cache = py::dict();
self_.adj_cache = py::dict();
return py::none();
}
py::object Graph__iter__(py::object self) {
return self.attr("nodes").attr("__iter__")();
}
py::object Graph__len__(py::object self) {
Graph& self_ = self.cast<Graph&>();
return py::cast(py::len(self_.node_to_id));
}
py::object Graph__contains__(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
try {
return py::cast(self_.node_to_id.contains(node));
} catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
return py::cast(false);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
}
py::object Graph__getitem__(py::object self, py::object node) {
return self.attr("adj")[node];
}
node_t _add_one_node(Graph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
self.adj[id] = adj_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
py::object Graph_add_node(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::object one_node_for_adding = args[1];
py::dict node_attr = kwargs;
_add_one_node(self, one_node_for_adding, node_attr);
return py::none();
}
py::object Graph_add_nodes(Graph& self, py::list nodes_for_adding, py::list nodes_attr) {
self.drop_cache();
if (py::len(nodes_attr) != 0) {
if (py::len(nodes_for_adding) != py::len(nodes_attr)) {
PyErr_Format(PyExc_AssertionError, "Nodes and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(nodes_for_adding); i++) {
py::object one_node_for_adding = nodes_for_adding[i];
py::dict node_attr;
if (py::len(nodes_attr)) {
node_attr = nodes_attr[i].cast<py::dict>();
} else {
node_attr = py::dict();
}
_add_one_node(self, one_node_for_adding, node_attr);
}
return py::none();
}
py::object Graph_add_nodes_from(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::list nodes_for_adding = py::list(args[1]);
for (int i = 0; i < py::len(nodes_for_adding); i++) {
bool newnode;
py::dict attr = kwargs;
py::dict newdict, ndict;
py::object n = nodes_for_adding[i];
try {
newnode = !self.node_to_id.contains(n);
newdict = attr;
}
catch (const py::error_already_set&) {
PyObject *type, *value, *traceback;
PyErr_Fetch(&type, &value, &traceback);
if (PyErr_GivenExceptionMatches(PyExc_TypeError, type)) {
py::tuple n_pair = n.cast<py::tuple>();
n = n_pair[0];
ndict = n_pair[1].cast<py::dict>();
newnode = !self.node_to_id.contains(n);
newdict = attr.attr("copy")();
newdict.attr("update")(ndict);
} else {
PyErr_Restore(type, value, traceback);
return py::none();
}
}
if (newnode) {
if (n.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
_add_one_node(self, n);
}
node_t id = self.node_to_id[n].cast<node_t>();
for (auto item : newdict) {
std::string weight_key = weight_to_string(item.first.cast<py::object>());
weight_t value = item.second.cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
}
return py::none();
}
py::object Graph_remove_node(Graph& self, py::object node_to_remove) {
self.drop_cache();
if (!self.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
node_t node_id = self.node_to_id[node_to_remove].cast<node_t>();
for (const auto& neighbor_info : self.adj[node_id]) {
node_t neighbor_id = neighbor_info.first;
self.adj[neighbor_id].erase(node_id);
}
self.adj.erase(node_id);
self.node.erase(node_id);
self.node_to_id.attr("pop")(node_to_remove);
self.id_to_node.attr("pop")(node_id);
return py::none();
}
py::object Graph_remove_nodes(py::object self, py::list nodes_to_remove) {
Graph& self_ = self.cast<Graph&>();
self_.drop_cache();
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
if (!self_.node_to_id.contains(node_to_remove)) {
PyErr_Format(PyExc_KeyError, "No node %R in graph.", node_to_remove.ptr());
return py::none();
}
}
for (int i = 0; i < py::len(nodes_to_remove); i++) {
py::object node_to_remove = nodes_to_remove[i];
self.attr("remove_node")(node_to_remove);
}
return py::none();
}
py::object Graph_number_of_nodes(Graph& self) {
return py::cast(int(self.node.size()));
}
py::object Graph_has_node(Graph& self, py::object node) {
return py::cast(self.node_to_id.contains(node));
}
py::object Graph_nbunch_iter(py::object self, py::object nbunch) {
py::object bunch = py::none();
if (nbunch.is_none()) {
bunch = self.attr("adj").attr("__iter__")();
} else if (self.contains(nbunch)) {
py::list nbunch_wrapper = py::list();
nbunch_wrapper.append(nbunch);
bunch = nbunch_wrapper.attr("__iter__")();
} else {
py::list nbunch_list = py::list(nbunch), nodes_list = py::list();
for (int i = 0; i < py::len(nbunch_list); i++) {
py::object n = nbunch_list[i];
if (self.contains(n)) {
nodes_list.append(n);
}
}
bunch = nbunch_list.attr("__iter__")();
}
return bunch;
}
void _add_one_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, py::object edge_attr) {
node_t u, v;
if (!self.node_to_id.contains(u_of_edge)) {
u = _add_one_node(self, u_of_edge);
} else {
u = self.node_to_id[u_of_edge].cast<node_t>();
}
if (!self.node_to_id.contains(v_of_edge)) {
v = _add_one_node(self, v_of_edge);
} else {
v = self.node_to_id[v_of_edge].cast<node_t>();
}
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.adj[v][u] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.adj[v][u].insert(std::make_pair(weight_key, value));
}
}
py::object Graph_add_edge(py::args args, py::kwargs kwargs) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::object u_of_edge = args[1], v_of_edge = args[2];
py::dict edge_attr = kwargs;
_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object Graph_add_edges(Graph& self, py::list edges_for_adding, py::list edges_attr) {
self.drop_cache();
if (py::len(edges_attr) != 0) {
if (py::len(edges_for_adding) != py::len(edges_attr)) {
PyErr_Format(PyExc_AssertionError, "Edges and Attributes lists must have same length.");
return py::none();
}
}
for (int i = 0; i < py::len(edges_for_adding); i++) {
py::tuple one_edge_for_adding = edges_for_adding[i].cast<py::tuple>();
py::dict edge_attr;
if (py::len(edges_attr)) {
edge_attr = edges_attr[i].cast<py::dict>();
} else {
edge_attr = py::dict();
}
_add_one_edge(self, one_edge_for_adding[0], one_edge_for_adding[1], edge_attr);
}
return py::none();
}
py::object Graph_add_edges_from(py::args args, py::kwargs attr) {
Graph& self = args[0].cast<Graph&>();
self.drop_cache();
py::list ebunch_to_add = py::list(args[1]);
for (int i = 0; i < len(ebunch_to_add); i++) {
py::list e = py::list(ebunch_to_add[i]);
py::object u, v;
py::dict dd;
switch (len(e)) {
case 2: {
u = e[0];
v = e[1];
break;
}
case 3: {
u = e[0];
v = e[1];
dd = e[2].cast<py::dict>();
break;
}
default: {
PyErr_Format(PyExc_ValueError, "Edge tuple %R must be a 2 - tuple or 3 - tuple.", e.ptr());
return py::none();
}
}
node_t u_id, v_id;
if (!self.node_to_id.contains(u)) {
if (u.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
u_id = _add_one_node(self, u);
} else {
u_id = self.node_to_id[u].cast<node_t>();
}
if (!self.node_to_id.contains(v)) {
if (v.is_none()) {
PyErr_Format(PyExc_ValueError, "None cannot be a node");
return py::none();
}
v_id = _add_one_node(self, v);
} else {
v_id = (self.node_to_id[v]).cast<node_t>();
}
auto datadict = self.adj[u_id].count(v_id) ? self.adj[u_id][v_id] : node_attr_dict_factory();
py::list items = py::list(attr.attr("items")());
items.attr("extend")(py::list(dd.attr("items")()));
for (int i = 0; i < py::len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
datadict.insert(std::make_pair(weight_key, value));
}
// Warning: in Graph.py the edge attr is directed assigned by the dict extended from the original attr
self.adj[u_id][v_id].insert(datadict.begin(), datadict.end());
self.adj[v_id][u_id].insert(datadict.begin(), datadict.end());
}
return py::none();
}
py::object Graph_add_edges_from_file(Graph& self, py::str file, py::object weighted, py::object is_transform) {
self.drop_cache();
bool _is_transform = is_transform.cast<bool>();
struct commactype : std::ctype<char> {
commactype() : std::ctype<char>(get_table()) {}
std::ctype_base::mask const* get_table() {
std::ctype_base::mask* rc = 0;
if (rc == 0) {
rc = new std::ctype_base::mask[std::ctype<char>::table_size];
std::fill_n(rc, std::ctype<char>::table_size, std::ctype_base::mask());
rc[','] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc[' '] = std::ctype_base::space;
rc['\t'] = std::ctype_base::space;
rc['\n'] = std::ctype_base::space;
rc['\r'] = std::ctype_base::space;
}
return rc;
}
};
std::ios::sync_with_stdio(0);
std::string file_path = file.cast<std::string>();
std::ifstream in;
in.open(file_path);
if (!in.is_open()) {
PyErr_Format(PyExc_FileNotFoundError, "Please check the file and make sure the path only contains English");
return py::none();
}
in.imbue(std::locale(std::locale(), new commactype));
std::string data, key("weight");
std::string su, sv;
weight_t weight;
while (in >> su >> sv) {
py::str pu(su), pv(sv);
node_t u, v;
if (!self.node_to_id.contains(pu)) {
u = _add_one_node(self, pu);
} else {
u = self.node_to_id[pu].cast<node_t>();
}
if (!self.node_to_id.contains(pv)) {
v = _add_one_node(self, pv);
} else {
v = self.node_to_id[pv].cast<node_t>();
}
if (weighted.cast<bool>()) {
in >> weight;
self.adj[u][v][key] = self.adj[v][u][key] = weight;
} else {
if (!self.adj[u].count(v)) {
self.adj[u][v] = node_attr_dict_factory();
}
if (!self.adj[v].count(u)) {
self.adj[v][u] = node_attr_dict_factory();
}
}
}
in.close();
if(_is_transform){
Graph_L g_l = graph_to_linkgraph(self, false, key, true, false);
self.linkgraph_structure = g_l;
self.linkgraph_dirty = false;
}
return py::none();
}
py::object Graph_add_weighted_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight) {
self.drop_cache();
py::dict edge_attr;
edge_attr["weight"] = weight;
_add_one_edge(self, u_of_edge, v_of_edge, edge_attr);
return py::none();
}
py::object Graph_remove_edge(Graph& self, py::object u, py::object v) {
self.drop_cache();
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& v_neighbors_info = self.adj[u_id];
if (v_neighbors_info.find(v_id) != v_neighbors_info.end()) {
v_neighbors_info.erase(v_id);
if (u_id != v_id) {
self.adj[v_id].erase(u_id);
}
return py::none();
}
}
PyErr_Format(PyExc_KeyError, "No edge %R-%R in graph.", u.ptr(), v.ptr());
return py::none();
}
py::object Graph_remove_edges(py::object self, py::list edges_to_remove) {
Graph& self_ = self.cast<Graph&>();
for (int i = 0; i < py::len(edges_to_remove); i++) {
py::tuple edge = edges_to_remove[i].cast<py::tuple>();
py::object u = edge[0], v = edge[1];
self.attr("remove_edge")(u, v);
}
self_.drop_cache();
return py::none();
}
py::object Graph_number_of_edges(py::object self, py::object u, py::object v) {
if (u.is_none()) {
return self.attr("size")();
}
Graph& self_ = self.cast<Graph&>();
node_t u_id = self_.node_to_id.attr("get")(u, -1).cast<node_t>();
node_t v_id = self_.node_to_id.attr("get")(v, -1).cast<node_t>();
return py::cast(int(self_.adj.count(u_id) && self_.adj[u_id].count(v_id)));
}
py::object Graph_has_edge(Graph& self, py::object u, py::object v) {
if (self.node_to_id.contains(u) && self.node_to_id.contains(v)) {
node_t u_id = self.node_to_id[u].cast<node_t>();
node_t v_id = self.node_to_id[v].cast<node_t>();
auto& v_neighbors_info = self.adj[u_id];
if (v_neighbors_info.find(v_id) != v_neighbors_info.end()) {
return py::cast(true);
}
}
return py::cast(false);
}
py::object Graph_copy(py::object self) {
Graph& self_ = self.cast<Graph&>();
py::object G = self.attr("__class__")();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
G_.id_to_node.attr("update")(self_.id_to_node);
G_.node_to_id.attr("update")(self_.node_to_id);
G_.id = self_.id;
G_.node = self_.node;
G_.adj = self_.adj;
return G;
}
py::object Graph_degree(py::object self, py::object weight) {
py::dict degree;
py::list edges = self.attr("edges").cast<py::list>();
py::object u, v;
py::dict d;
for (int i = 0; i < py::len(edges); i++) {
py::tuple edge = edges[i].cast<py::tuple>();
u = edge[0];
v = edge[1];
d = edge[2].cast<py::dict>();
if (degree.contains(u)) {
degree[u] = py::object(degree[u]) + d.attr("get")(weight, 1);
} else {
degree[u] = d.attr("get")(weight, 1);
}
if (degree.contains(v)) {
degree[v] = py::object(degree[v]) + d.attr("get")(weight, 1);
} else {
degree[v] = d.attr("get")(weight, 1);
}
}
py::list nodes = py::list(self.attr("nodes"));
for (int i = 0; i < py::len(nodes); i++) {
py::object node = nodes[i];
if (!degree.contains(node)) {
degree[node] = 0;
}
}
return degree;
}
py::object Graph_neighbors(py::object self, py::object node) {
Graph& self_ = self.cast<Graph&>();
if (self_.node_to_id.contains(node)) {
return self.attr("adj")[node].attr("__iter__")();
} else {
PyErr_Format(PyExc_KeyError, "No node %R", node.ptr());
return py::none();
}
}
py::object Graph_generate_linkgraph(py::object self, py::object weight){
Graph& G_ = self.cast<Graph&>();
std::string w = weight_to_string(weight);
Graph_L g_l = graph_to_linkgraph(G_, false, w, true, false);
G_.linkgraph_dirty = false;
G_.linkgraph_structure = g_l;
return py::none();
}
py::object Graph_nodes_subgraph(py::object self, py::list from_nodes) {
py::object G = self.attr("__class__")();
Graph& self_ = self.cast<Graph&>();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
for (int i = 0; i < py::len(from_nodes); i++) {
py::object node = from_nodes[i];
if (self_.node_to_id.contains(node)) {
py::object node_attr = nodes[node];
_add_one_node(G_, node, node_attr);
}
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (from_nodes.contains(v)) {
_add_one_edge(G_, node, v, edge_attr);
}
}
}
return G;
}
py::object Graph_ego_subgraph(py::object self, py::object center) {
py::list neighbors_of_center = py::list(self.attr("all_neighbors")(center));
neighbors_of_center.append(center);
return self.attr("nodes_subgraph")(neighbors_of_center);
}
py::object Graph_size(py::object self, py::object weight) {
py::dict degree = self.attr("degree")(weight).cast<py::dict>();
weight_t s = 0;
for (auto item : degree) {
s += item.second.cast<weight_t>();
}
return (weight.is_none()) ? py::cast(int(s) / 2) : py::cast(s / 2);
}
py::object Graph_is_directed(py::object self) {
return py::cast(false);
}
py::object Graph_is_multigraph(py::object self) {
return py::cast(false);
}
py::object Graph_to_index_node_graph(py::object self, py::object begin_index) {
py::object G = self.attr("__class__")();
G.attr("graph").attr("update")(self.attr("graph"));
py::dict index_of_node = py::dict(), node_of_index = py::dict();
int begin = begin_index.cast<int>();
int index = 0;
for (auto item : self.attr("nodes").cast<py::dict>()) {
py::object node = item.first.cast<py::object>();
py::dict node_attr = item.second.cast<py::dict>();
G.attr("add_node")(py::cast(index + begin), **node_attr);
index_of_node[node] = index + begin;
node_of_index[py::cast(index + begin)] = node;
index++;
}
for (auto item : self.attr("adj").cast<py::dict>()) {
py::object u = item.first.cast<py::object>();
py::dict nbrs = item.second.cast<py::dict>();
for (auto item_ : nbrs) {
py::object v = item_.first.cast<py::object>();
py::dict edge_data = item_.second.cast<py::dict>();
G.attr("add_edge")(index_of_node[u], index_of_node[v], **edge_data);
}
}
return py::make_tuple(G, index_of_node, node_of_index);
}
py::object Graph_py(py::object self) {
py::object G = py::module_::import("easygraph").attr("Graph")();
G.attr("graph").attr("update")(self.attr("graph"));
G.attr("adj").attr("update")(self.attr("adj"));
G.attr("nodes").attr("update")(self.attr("nodes"));
return G;
}
py::object Graph::get_nodes() {
if (this->dirty_nodes) {
py::dict nodes = py::dict();
for (const auto& node_info : node) {
node_t id = node_info.first;
const auto& node_attr = node_info.second;
nodes[this->id_to_node[py::cast(id)]] = attr_to_dict(node_attr);
}
this->nodes_cache = nodes;
this->dirty_nodes = false;
}
return this->nodes_cache;
}
py::object Graph::get_name() {
return this->graph.attr("get")("name", "");
}
py::object Graph::set_name(py::object name) {
this->graph[py::cast("name")] = name;
return py::none();
}
py::object Graph::get_node_index() {
py::dict node_index = py::dict();
int len = py::len(this->node_to_id);
for(int i = 1; i <= len; i++){
node_index[this->id_to_node[py::cast(i)]] = py::cast(i - 1);
}
return node_index;
}
py::object Graph::get_graph() {
return this->graph;
}
py::object Graph::get_adj() {
if (this->dirty_adj) {
py::dict adj = py::dict();
for (const auto& ego_edges : this->adj) {
node_t start_point = ego_edges.first;
py::dict ego_edges_dict = py::dict();
for (const auto& edge_info : ego_edges.second) {
node_t end_point = edge_info.first;
const auto& edge_attr = edge_info.second;
ego_edges_dict[this->id_to_node[py::cast(end_point)]] = attr_to_dict(edge_attr);
}
adj[this->id_to_node[py::cast(start_point)]] = ego_edges_dict;
}
this->adj_cache = adj;
this->dirty_adj = false;
}
return this->adj_cache;
}
py::object Graph::get_edges() {
py::list edges = py::list();
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
seen.insert(std::make_pair(v, u));
edges.append(py::make_tuple(this->id_to_node[py::cast(u)], this->id_to_node[py::cast(v)], attr_to_dict(edge_attr)));
}
}
}
return edges;
}
Graph_L Graph::_get_linkgraph_structure() {
return this->linkgraph_structure;
}
bool Graph::is_linkgraph_dirty(){
return this->linkgraph_dirty;
}
std::vector<graph_edge> Graph::_get_edges(bool if_directed) {
std::vector<graph_edge> edges;
std::set<std::pair<node_t, node_t> > seen;
for (const auto& ego_edges : this->adj) {
node_t u = ego_edges.first;
for (const auto& edge_info : ego_edges.second) {
node_t v = edge_info.first;
const auto& edge_attr = edge_info.second;
if (seen.find(std::make_pair(u, v)) == seen.end()) {
seen.insert(std::make_pair(u, v));
if(!if_directed){
seen.insert(std::make_pair(v, u));
}
edges.emplace_back(u, v, edge_attr);
}
}
}
return edges;
}
void Graph::drop_cache() {
dirty_nodes = true;
dirty_adj = true;
linkgraph_dirty = true;
csr_graph = nullptr;
}
std::shared_ptr<CSRGraph> Graph::gen_CSR(const std::string& weight) {
if (csr_graph != nullptr) {
if (csr_graph->W_map.find(weight) == csr_graph->W_map.end()) {
auto W = std::make_shared<std::vector<double>>();
// According to C++ Standard, the iteration order of a unordered contrainer will
// not change without rehashing which only happens during a insert.
for (node_t n : csr_graph->nodes) {
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
}
}
csr_graph->W_map[weight] = W;
}
} else {
// the graph has been modified
csr_graph = std::make_shared<CSRGraph>();
std::vector<node_t>& nodes = csr_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = csr_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
auto W = std::make_shared<std::vector<double>>();
for (int idx = 0; idx < nodes.size(); ++idx) {
V.push_back(E.size());
node_t n = nodes[idx];
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
E.push_back(node2idx[adj_it->first]);
}
}
V.push_back(E.size());
csr_graph->W_map[weight] = W;
}
return csr_graph;
}
std::shared_ptr<CSRGraph> Graph::gen_CSR() {
if (csr_graph != nullptr) {
if (csr_graph->unweighted_W.size() != csr_graph->E.size()) {
csr_graph->unweighted_W = std::vector<double>(csr_graph->E.size(), 1.0);
}
} else {
// the graph has been modified
csr_graph = std::make_shared<CSRGraph>();
std::vector<node_t>& nodes = csr_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = csr_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
for (int idx = 0; idx < nodes.size(); ++idx) {
V.push_back(E.size());
node_t n = nodes[idx];
// if n is not in adj, this way can raise an exception
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
E.push_back(node2idx[adj_it->first]);
}
}
V.push_back(E.size());
csr_graph->unweighted_W = std::vector<double>(E.size(), 1.0);
}
return csr_graph;
}
std::shared_ptr<std::vector<int>> Graph::gen_CSR_sources(const py::object& py_sources) {
auto sources = std::make_shared<std::vector<int>>();
if (py_sources.is_none()) {
for (int i = 0; i < csr_graph->V.size() - 1; ++i) {
sources->push_back(i);
}
} else {
for (auto it = py_sources.begin(); it != py_sources.end(); ++it) {
sources->push_back(csr_graph->node2idx[node_to_id[*it].cast<node_t>()]);
}
}
return sources;
}
std::shared_ptr<COOGraph> Graph::gen_COO() {
if (coo_graph != nullptr) {
if (coo_graph->unweighted_W.size() != coo_graph->row.size()) {
coo_graph->unweighted_W = std::vector<double>(coo_graph->row.size(), 1.0);
}
} else {
coo_graph = std::make_shared<COOGraph>();
std::vector<node_t>& nodes = coo_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double>& unweighted_W = coo_graph->unweighted_W;
for (int idx = 0; idx < nodes.size(); ++idx) {
node_t n = nodes[idx];
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
node_t neighbor = adj_it->first;
row.push_back(idx);
col.push_back(node2idx[neighbor]);
unweighted_W.push_back(1.0);
}
}
}
return coo_graph;
}
std::shared_ptr<COOGraph> Graph::gen_COO(const std::string& weight) {
if (coo_graph != nullptr) {
if (coo_graph->W_map.find(weight) == coo_graph->W_map.end()) {
auto W = std::make_shared<std::vector<double>>();
for (node_t n : coo_graph->nodes) {
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
W->push_back(w);
}
}
coo_graph->W_map[weight] = W;
}
} else {
coo_graph = std::make_shared<COOGraph>();
std::vector<node_t>& nodes = coo_graph->nodes;
for (auto it = node.begin(); it != node.end(); ++it) {
nodes.push_back(it->first);
}
std::sort(nodes.begin(), nodes.end());
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
for (int i = 0; i < nodes.size(); ++i) {
node2idx[nodes[i]] = i;
}
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
auto W = std::make_shared<std::vector<double>>();
for (int idx = 0; idx < nodes.size(); ++idx) {
node_t n = nodes[idx];
const auto& n_adjs = adj.find(n)->second;
for (auto adj_it = n_adjs.begin(); adj_it != n_adjs.end(); ++adj_it) {
const edge_attr_dict_factory& edge_attr = adj_it->second;
auto edge_it = edge_attr.find(weight);
weight_t w = edge_it != edge_attr.end() ? edge_it->second : 1.0;
row.push_back(idx);
col.push_back(node2idx[adj_it->first]);
W->push_back(w);
}
}
coo_graph->W_map[weight] = W;
}
return coo_graph;
}
std::shared_ptr<COOGraph> Graph::transfer_csr_to_coo(const std::shared_ptr<CSRGraph>& csr_graph) {
auto coo_graph = std::make_shared<COOGraph>();
coo_graph->nodes = csr_graph->nodes;
coo_graph->node2idx = csr_graph->node2idx;
const std::vector<int>& V = csr_graph->V;
const std::vector<int>& E = csr_graph->E;
int num_edges = E.size();
coo_graph->row.reserve(num_edges);
coo_graph->col.reserve(num_edges);
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
for (int i = 0; i < V.size() - 1; ++i) {
int start_idx = V[i];
int end_idx = V[i + 1];
for (int j = start_idx; j < end_idx; ++j) {
row.push_back(i);
col.push_back(E[j]);
}
}
if (!csr_graph->unweighted_W.empty()) {
coo_graph->unweighted_W = csr_graph->unweighted_W;
} else {
coo_graph->W_map = csr_graph->W_map;
}
return coo_graph;
}
+73
View File
@@ -0,0 +1,73 @@
#pragma once
#include "../common/common.h"
#include "csr_graph.h"
#include "linkgraph.h"
#include "coo_graph.h"
// old version
struct Graph {
node_dict_factory node;
adj_dict_factory adj;
Graph_L linkgraph_structure;
std::shared_ptr<CSRGraph> csr_graph;
py::kwargs node_to_id, id_to_node, graph;
node_t id;
bool dirty_nodes, dirty_adj, linkgraph_dirty;
py::object nodes_cache, adj_cache;
std::shared_ptr<COOGraph> coo_graph;
Graph();
py::object get_nodes();
py::object get_name();
py::object set_name(py::object name);
py::object get_graph();
py::object get_adj();
py::object get_edges();
py::object get_node_index();
std::vector<graph_edge> _get_edges(bool if_directed=true);
bool is_linkgraph_dirty();
Graph_L _get_linkgraph_structure();
void drop_cache();
std::shared_ptr<CSRGraph> gen_CSR(const std::string& weight);
std::shared_ptr<CSRGraph> gen_CSR();
std::shared_ptr<std::vector<int>> gen_CSR_sources(const py::object& py_sources);
std::shared_ptr<COOGraph> gen_COO();
std::shared_ptr<COOGraph> gen_COO(const std::string& weight);
std::shared_ptr<COOGraph> transfer_csr_to_coo(const std::shared_ptr<CSRGraph>& csr_graph);
};
py::object Graph__init__(py::args args, py::kwargs kwargs);
py::object Graph__iter__(py::object self);
py::object Graph__len__(py::object self);
py::object Graph__contains__(py::object self, py::object node);
py::object Graph__getitem__(py::object self, py::object node);
py::object Graph_add_node(py::args args, py::kwargs kwargs);
py::object Graph_add_nodes(Graph& self, py::list nodes_for_adding, py::list nodes_attr);
py::object Graph_add_nodes_from(py::args args, py::kwargs kwargs);
py::object Graph_remove_node(Graph& self, py::object node_to_remove);
py::object Graph_remove_nodes(py::object self, py::list nodes_to_remove);
py::object Graph_number_of_nodes(Graph& self);
py::object Graph_has_node(Graph& self, py::object node);
py::object Graph_nbunch_iter(py::object self, py::object nbunch);
py::object Graph_add_edge(py::args args, py::kwargs kwargs);
py::object Graph_add_edges(Graph& self, py::list edges_for_adding, py::list edges_attr);
py::object Graph_add_edges_from(py::args args, py::kwargs attr);
py::object Graph_add_edges_from_file(Graph& self, py::str file, py::object weighted, py::object is_transform);
py::object Graph_add_weighted_edge(Graph& self, py::object u_of_edge, py::object v_of_edge, weight_t weight);
py::object Graph_remove_edge(Graph& self, py::object u, py::object v);
py::object Graph_remove_edges(py::object self, py::list edges_to_remove);
py::object Graph_number_of_edges(py::object self, py::object u, py::object v);
py::object Graph_has_edge(Graph& self, py::object u, py::object v);
py::object Graph_copy(py::object self);
py::object Graph_degree(py::object self, py::object weight);
py::object Graph_neighbors(py::object self, py::object node);
py::object Graph_nodes_subgraph(py::object self, py::list from_nodes);
py::object Graph_ego_subgraph(py::object self, py::object center);
py::object Graph_size(py::object self, py::object weight);
py::object Graph_is_directed(py::object self);
py::object Graph_is_multigraph(py::object self);
py::object Graph_to_index_node_graph(py::object self, py::object begin_index);
py::object Graph_generate_linkgraph(py::object self, py::object weight);
py::object Graph_py(py::object self);
+91
View File
@@ -0,0 +1,91 @@
#pragma once
#include "../common/common.h"
struct LinkEdge{
// 终点
node_t to;
// 边权重
weight_t w;
// 同起点的上一条边的编号
node_t next;
};
struct Graph_L{
int n;
int e;
bool is_directed;
bool is_deg;
std::vector<int> head;
std::vector<LinkEdge> edges;
std::vector<int> degree;
int max_deg = -1;
Graph_L(int vertex_num = 0, bool directed = true, bool deg = false){
this->n = vertex_num;
this->e = 0;
this->is_deg = deg;
this->is_directed = directed;
LinkEdge le;
le.to = -1;
le.next = -1;
edges.emplace_back(le);
if(n > 0){
head.resize(vertex_num + 1);
if(deg){
degree.resize(vertex_num + 1);
for(int i = 0; i < vertex_num+1; i++){
head[i] = -1;
degree[i] = 0;
}
}
else{
for(int i = 0; i < vertex_num+1; i++){
head[i] = -1;
}
}
}
}
void add_unweighted_edge(const int &u, const int &v) {
LinkEdge le;
le.to = v;
le.next = head[u];
this->edges.emplace_back(le);
this->head[u] = this->e;
this->e += 1;
if(this->is_deg){
this->degree[u]++;
this->max_deg = std::max(this->max_deg, this->degree[u]);
}
}
void add_weighted_edge(const int &u, const int &v, const double &w) {
// printf("u:%d,v:%d w:%.2f\n",u,v,w);
LinkEdge le;
this->e += 1;
le.to = v;
le.w = w;
le.next = head[u];
// printf("next:%d\n",this->head[u]);
this->edges.emplace_back(le);
this->head[u] = this->e;
// printf("head:%d\n",this->head[u]);
if(this->is_deg){
this->degree[u]++;
this->max_deg = std::max(this->max_deg, this->degree[u]);
}
}
};
struct compare_node {
compare_node(){}
compare_node(int _x, int _d) {x = _x; d = _d;}
int x, d;
bool operator < (const compare_node &rhs) const {
return d > rhs.d;
}
};
std::vector<double> _dijkstra(const Graph_L& G_l, int source, int target);
+16
View File
@@ -0,0 +1,16 @@
#include "operation.h"
#include "graph.h"
py::object density(py::object G) {
Graph& G_ = G.cast<Graph&>();
int n = G_.node.size();
int m = G.attr("number_of_edges")().cast<int>();
if (m == 0 || n <= 1) {
return py::cast(0);
}
weight_t d = m * 1.0 / (n * (n - 1));
if(G.attr("is_directed")().equal(py::cast(false))){
d*=2;
}
return py::cast(d);
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "../common/common.h"
py::object density(py::object G);
+42
View File
@@ -0,0 +1,42 @@
#include "../common/common.h"
class Segment_tree_zkw {
private:
int tn;
int size;
public:
std::vector<int> t;
std::vector<int> num;
Segment_tree_zkw(int N){
size = (N+1)<<2;
this->t = std::vector<int>(size+1 , INT_MAX);
this->num = std::vector<int>(size+1 , 0);
}
void init(int N) {
for(int i = 0; i < size; i++){
this->t[i] = INT_MAX;
this->num[i] = 0;
}
tn = 1;
while(tn < N) tn <<= 1;
--tn;
for (int i = 1; i <= N; ++i)
this->num[i + tn] = i;
}
void change(int p, const int &k) {
p += tn; this->t[p] = k; p >>= 1;
while (p) {
if (this->t[p<<1] < this->t[p<<1|1]) {
this->t[p] = this->t[p<<1];
this->num[p] = this->num[p<<1];
}
else {
this->t[p] = this->t[p<<1|1];
this->num[p] = this->num[p<<1|1];
}
p >>= 1;
}
}
};
+3
View File
@@ -0,0 +1,3 @@
#include "common.h"
graph_edge::graph_edge(node_t u_, node_t v_, edge_attr_dict_factory attr_) : u(u_), v(v_), attr(attr_) {}
+37
View File
@@ -0,0 +1,37 @@
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
#include <pybind11/stl.h>
#include <iostream>
#include <stdio.h>
#include <fstream>
#include <map>
#include <memory>
#include <unordered_map>
#include <unordered_set>
#include <exception>
#include <set>
#include <cmath>
#include <random>
#include <algorithm>
#include <queue>
#include <vector>
#include <thread>
#include <inttypes.h>
#include <limits>
namespace py = pybind11;
typedef int node_t;
typedef float weight_t;
typedef std::map<std::string, weight_t> node_attr_dict_factory; //(weight_key, value)
typedef std::map<std::string, weight_t> edge_attr_dict_factory; //(weight_key, value)
typedef std::unordered_map<node_t, node_attr_dict_factory> node_dict_factory; //(node, node_attr)
typedef std::unordered_map<node_t, edge_attr_dict_factory> adj_attr_dict_factory; //(out_node, (weight_key, value))
typedef std::unordered_map<node_t, adj_attr_dict_factory> adj_dict_factory; //(node, edge_attr)
struct graph_edge {
node_t u, v;
edge_attr_dict_factory attr;
graph_edge(node_t, node_t, edge_attr_dict_factory);
};
+48
View File
@@ -0,0 +1,48 @@
#include "utils.h"
#include "../classes/graph.h"
#include "../classes/linkgraph.h"
py::object attr_to_dict(const node_attr_dict_factory& attr) {
py::dict attr_dict = py::dict();
for (const auto& kv : attr) {
attr_dict[py::cast(kv.first)] = kv.second;
}
return attr_dict;
}
std::string weight_to_string(py::object weight) {
py::object warn = py::module_::import("warnings").attr("warn");
if (!py::isinstance<py::str>(weight)) {
if (!weight.is_none()) {
warn(py::str(weight) + py::str(" would be transformed into an instance of str."));
}
weight = py::str(weight);
}
std::string weight_key = weight.cast<std::string>();
return weight_key;
}
py::object py_sum(py::object o) {
py::object sum = py::module_::import("builtins").attr("sum");
return sum(o);
}
Graph_L graph_to_linkgraph(Graph &G, bool if_directed, std::string weight_key, bool is_deg, bool is_reverse){
int node_num = G.node.size();
const std::vector<graph_edge>& edges = G._get_edges(if_directed);
int edges_num = edges.size();
Graph_L G_l(node_num, if_directed, is_deg);
for(int i = 0; i < edges_num; i++){
graph_edge e = edges[i];
edge_attr_dict_factory& edge_attr = e.attr;
weight_t edge_weight = edge_attr.find(weight_key) != edge_attr.end() ? edge_attr[weight_key] : 1;
if(is_reverse){
std::swap(e.u, e.v);
}
G_l.add_weighted_edge(e.u, e.v, edge_weight);
if (!if_directed){
G_l.add_weighted_edge(e.v, e.u, edge_weight);
}
}
return G_l;
}
+9
View File
@@ -0,0 +1,9 @@
#pragma once
#include "common.h"
#include "../classes/linkgraph.h"
#include "../classes/graph.h"
py::object attr_to_dict(const node_attr_dict_factory& attr);
std::string weight_to_string(py::object weight);
py::object py_sum(py::object o);
Graph_L graph_to_linkgraph(Graph &G, bool if_directed=false, std::string weight_key = "weight", bool is_deg = false, bool is_reverse = false);
+120
View File
@@ -0,0 +1,120 @@
#include "classes/__init__.h"
#include "functions/__init__.h"
PYBIND11_MODULE(cpp_easygraph, m) {
py::class_<Graph>(m, "Graph")
.def(py::init<>())
.def("__init__", &Graph__init__)
.def("__iter__", &Graph__iter__)
.def("__len__", &Graph__len__)
.def("__contains__", &Graph__contains__, py::arg("node"))
.def("__getitem__", &Graph__getitem__, py::arg("node"))
.def("add_node", &Graph_add_node)
.def("add_nodes", &Graph_add_nodes, py::arg("nodes_for_adding"), py::arg("nodes_attr") = py::list())
.def("add_nodes_from", &Graph_add_nodes_from)
.def("remove_node", &Graph_remove_node, py::arg("node_to_remove"))
.def("remove_nodes", &Graph_remove_nodes, py::arg("nodes_to_remove"))
.def("number_of_nodes", &Graph_number_of_nodes)
.def("has_node", &Graph_has_node, py::arg("node"))
.def("nbunch_iter", &Graph_nbunch_iter, py::arg("nbunch") = py::none())
.def("add_edge", &Graph_add_edge)
.def("add_edges", &Graph_add_edges, py::arg("edges_for_adding"), py::arg("edges_attr") = py::list())
.def("add_edges_from", &Graph_add_edges_from)
.def("add_edges_from_file", &Graph_add_edges_from_file, py::arg("file"), py::arg("weighted") = false, py::arg("is_transform") = false)
.def("add_weighted_edge", &Graph_add_weighted_edge, py::arg("u_of_edge"), py::arg("v_of_edge"), py::arg("weight"))
.def("remove_edge", &Graph_remove_edge, py::arg("u"), py::arg("v"))
.def("remove_edges", &Graph_remove_edges, py::arg("edges_to_remove"))
.def("number_of_edges", &Graph_number_of_edges, py::arg("u") = py::none(), py::arg("v") = py::none())
.def("has_edge", &Graph_has_edge, py::arg("u"), py::arg("y"))
.def("copy", &Graph_copy)
.def("degree", &Graph_degree, py::arg("weight") = "weight")
.def("neighbors", &Graph_neighbors, py::arg("node"))
.def("all_neighbors", &Graph_neighbors, py::arg("node"))
.def("nodes_subgraph", &Graph_nodes_subgraph, py::arg("from_nodes"))
.def("ego_subgraph", &Graph_ego_subgraph, py::arg("center"))
.def("size", &Graph_size, py::arg("weight") = py::none())
.def("is_directed", &Graph_is_directed)
.def("is_multigraph", &Graph_is_multigraph)
.def("to_index_node_graph", &Graph_to_index_node_graph, py::arg("begin_index") = 0)
.def("py", &Graph_py)
.def_property("graph", &Graph::get_graph, nullptr)
.def_property("nodes", &Graph::get_nodes, nullptr)
.def_property("name", &Graph::get_name, &Graph::set_name)
.def_property("adj", &Graph::get_adj, nullptr)
.def_property("edges", &Graph::get_edges, nullptr)
.def_property("node_index", &Graph::get_node_index, nullptr)
.def("generate_linkgraph", &Graph_generate_linkgraph,py::arg("weight") = "weight");
py::class_<DiGraph, Graph>(m, "DiGraph")
.def(py::init<>())
.def("__init__", &DiGraph__init__)
.def("out_degree", &DiGraph_out_degree, py::arg("weight") = "weight")
.def("in_degree", &DiGraph_in_degree, py::arg("weight") = "weight")
.def("degree", &DiGraph_degree, py::arg("weight") = "weight")
.def("size", &DiGraph_size, py::arg("weight") = py::none())
.def("number_of_edges", &DiGraph_number_of_edges, py::arg("u") = py::none(), py::arg("v") = py::none())
.def("successors", &Graph_neighbors, py::arg("node"))
.def("predecessors", &DiGraph_predecessors, py::arg("node"))
.def("add_node", &DiGraph_add_node)
.def("add_nodes", &DiGraph_add_nodes, py::arg("nodes_for_adding"), py::arg("nodes_attr") = py::list())
.def("add_nodes_from", &DiGraph_add_nodes_from)
.def("remove_node", &DiGraph_remove_node, py::arg("node_to_remove"))
.def("remove_nodes", &DiGraph_remove_nodes, (py::arg("nodes_to_remove")))
.def("add_edge", &DiGraph_add_edge)
.def("add_edges", &DiGraph_add_edges, py::arg("edges_for_adding"), py::arg("edges_attr") = py::list())
.def("add_edges_from", &DiGraph_add_edges_from)
.def("add_edges_from_file", &DiGraph_add_edges_from_file, py::arg("file"), py::arg("weighted") = false, py::arg("is_transform") = false)
.def("add_weighted_edge", &DiGraph_add_weighted_edge, py::arg("u_of_edge"), py::arg("v_of_edge"), py::arg("weight"))
.def("remove_edge", &DiGraph_remove_edge, py::arg("u"), py::arg("v"))
.def("remove_edges", &DiGraph_remove_edges, py::arg("edges_to_remove"))
.def("remove_edges_from", &DiGraph_remove_edges_from, py::arg("ebunch"))
.def("nodes_subgraph", &DiGraph_nodes_subgraph, py::arg("from_nodes"))
.def("is_directed", &DiGraph_is_directed)
.def("py", &DiGraph_py)
.def_property("edges", &DiGraph::get_edges, nullptr)
.def_property("pred", &DiGraph::get_pred,nullptr)
.def("generate_linkgraph", &DiGraph_generate_linkgraph,py::arg("weight") = "weight");
m.def("cpp_degree_centrality", &degree_centrality, py::arg("G"));
m.def("cpp_in_degree_centrality", &in_degree_centrality, py::arg("G"));
m.def("cpp_out_degree_centrality", &out_degree_centrality, py::arg("G"));
m.def("cpp_closeness_centrality", &closeness_centrality, py::arg("G"), py::arg("weight") = "weight", py::arg("cutoff") = py::none(), py::arg("sources") = py::none());
m.def("cpp_betweenness_centrality", &betweenness_centrality, py::arg("G"), py::arg("weight") = "weight", py::arg("cutoff") = py::none(),py::arg("sources") = py::none(), py::arg("normalized") = py::bool_(true), py::arg("endpoints") = py::bool_(false));
m.def("cpp_katz_centrality", &cpp_katz_centrality, py::arg("G"), py::arg("alpha") = 0.1, py::arg("beta") = 1.0, py::arg("max_iter") = 1000, py::arg("tol") = 1e-6, py::arg("normalized") = true);
m.def("cpp_eigenvector_centrality", &cpp_eigenvector_centrality, py::arg("G"), py::arg("max_iter") = 100, py::arg("tol") = 1.0e-6, py::arg("nstart") = py::none(), py::arg("weight") = "weight");
m.def("cpp_k_core", &core_decomposition, py::arg("G"));
m.def("cpp_density", &density, py::arg("G"));
m.def("cpp_constraint", &constraint, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_effective_size", &effective_size, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_efficiency", &efficiency, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_hierarchy", &hierarchy, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none(), py::arg("n_workers") = py::none());
m.def("cpp_pagerank", &_pagerank, py::arg("G"), py::arg("alpha") = 0.85, py::arg("max_iterator") = 500, py::arg("threshold") = 1e-6, py::arg("weight") = "weight");
m.def("cpp_dijkstra_multisource", &_dijkstra_multisource, py::arg("G"), py::arg("sources"), py::arg("weight") = "weight", py::arg("target") = py::none());
m.def("cpp_spfa", &_spfa, py::arg("G"), py::arg("source"), py::arg("weight") = "weight");
m.def("cpp_clustering", &clustering, py::arg("G"), py::arg("nodes") = py::none(), py::arg("weight") = py::none());
m.def("cpp_biconnected_dfs_record_edges", &_biconnected_dfs_record_edges, py::arg("G"), py::arg("need_components") = true);
m.def("cpp_strongly_connected_components",&strongly_connected_components,py::arg("G"));
m.def("cpp_Floyd", &Floyd, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_Prim", &Prim, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_Kruskal", &Kruskal, py::arg("G"), py::arg("weight") = "weight");
m.def("cpp_plain_bfs", &plain_bfs, py::arg("G"), py::arg("source"));
m.def("cpp_kruskal_mst_edges", &kruskal_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_prim_mst_edges", &prim_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_boruvka_mst_edges", &boruvka_mst_edges, py::arg("G"), py::arg("minimum") = true, py::arg("weight") = "weight", py::arg("data") = true, py::arg("ignore_nan") = false);
m.def("cpp_average_shortest_path_length", &average_shortest_path_length, py::arg("G"), py::arg("weight") = py::none(), py::arg("method") = py::none());
m.def("cpp_eccentricity", &eccentricity, py::arg("G"), py::arg("v") = py::none(), py::arg("sp") = py::none());
m.def("cpp_connected_components_undirected", &connected_component_undirected, py::arg("G"));
m.def("cpp_connected_components_directed", &connected_component_directed, py::arg("G"));
// community methods
m.def("cpp_modularity", &cpp_modularity, py::arg("G"), py::arg("communities"), py::arg("weight") = py::str("weight"));
m.def("cpp_greedy_modularity_communities", &cpp_greedy_modularity_communities, py::arg("G"), py::arg("weight") = py::str("weight"));
m.def("cpp_enumerate_subgraph", &cpp_enumerate_subgraph, py::arg("G"), py::arg("k"));
m.def("cpp_random_enumerate_subgraph", &cpp_random_enumerate_subgraph, py::arg("G"), py::arg("k"), py::arg("cut_prob"));
m.def("cpp_louvain_communities", &cpp_louvain_communities, py::arg("G"), py::arg("weight") = py::str("weight"), py::arg("threshold") = py::float_(0.00002), py::arg("resolution") = py::float_(1.0));
m.def("cpp_louvain_communities_serial", &cpp_louvain_communities_serial, py::arg("G"), py::arg("weight") = py::str("weight"), py::arg("threshold") = py::float_(0.00002), py::arg("resolution") = py::float_(1.0));
m.def("cpp_LPA", &cpp_LPA, py::arg("G"));
m.def("cpp_ego_graph", &cpp_ego_graph, py::arg("G"), py::arg("n"), py::arg("radius") = py::int_(1), py::arg("center") = py::bool_(true), py::arg("undirected") = py::bool_(false), py::arg("distance") = py::none());
m.def("cpp_ego_graph_csr", &cpp_ego_graph_csr, py::arg("G"), py::arg("n"), py::arg("radius") = py::int_(1), py::arg("center") = py::bool_(true), py::arg("undirected") = py::bool_(false), py::arg("distance") = py::none());
m.def("cpp_localsearch", &cpp_localsearch, py::arg("G"), py::arg("center_num") = py::none(), py::arg("auto_choose_centers") = py::bool_(false), py::arg("maximum_tree") = py::bool_(true), py::arg("seed") = py::none(), py::arg("self_loop") = py::bool_(false));
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include "components/__init__.h"
#include "basic/__init__.h"
#include "path/__init__.h"
#include "structural_holes/__init__.h"
#include "cores/__init__.h"
#include "centrality/__init__.h"
#include "pagerank/__init__.h"
#include "community/__init__.h"
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include "avg_degree.h"
#include "cluster.h"
@@ -0,0 +1,9 @@
#include "avg_degree.h"
#include "../../classes/graph.h"
py::object average_degree(py::object G) {
Graph& G_ = G.cast<Graph&>();
int n = G_.node.size();
int m = G.attr("number_of_edges")().cast<int>();
return py::cast(2.0 * m / n);
}
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object average_degree(py::object G);
+298
View File
@@ -0,0 +1,298 @@
#include "cluster.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
inline weight_t wt(adj_dict_factory& adj, node_t u, node_t v, std::string weight, weight_t max_weight = 1) {
auto& attr = adj[u][v];
return (attr.count(weight) ? attr[weight] : 1) / max_weight;
}
py::list _weighted_triangles_and_degree(py::object G, py::object nodes, py::object weight) {
std::string weight_key = weight_to_string(weight);
Graph& G_ = G.cast<Graph&>();
auto& adj = G_.adj;
weight_t max_weight = 1;
if (weight.is_none() || G.attr("number_of_edges")().equal(py::cast(0))) {
max_weight = 1;
}
else {
int assigned = 0;
for (auto& u_info : G_.adj) {
for (auto& v_info : u_info.second) {
auto& d = v_info.second;
if (assigned) {
max_weight = std::max(max_weight, d.count(weight_key) ? d[weight_key] : 1);
}
else {
assigned = 1;
max_weight = d.count(weight_key) ? d[weight_key] : 1;
}
}
}
}
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> inbrs, seen;
for (const auto& pair : adj[i_id]) {
inbrs.insert(pair.first);
}
inbrs.erase(i_id);
weight_t weighted_triangles = 0;
for (const auto& j_id : inbrs) {
seen.insert(j_id);
weight_t wij = wt(adj, i_id, j_id, weight_key, max_weight);
for (const auto& k_id : inbrs) {
if (adj[j_id].count(k_id) && !seen.count(k_id)) {
weight_t wjk = wt(adj, j_id, k_id, weight_key, max_weight);
weight_t wki = wt(adj, k_id, i_id, weight_key, max_weight);
weighted_triangles += std::cbrt(wij * wjk * wki);
}
}
}
ret.append(py::make_tuple(G_.id_to_node[py::cast(i_id)], inbrs.size(), 2 * weighted_triangles));
}
return ret;
}
py::list _directed_weighted_triangles_and_degree(py::object G, py::object nodes, py::object weight) {
std::string weight_key = weight_to_string(weight);
DiGraph& G_ = G.cast<DiGraph&>();
auto& adj = G_.adj;
weight_t max_weight = 1;
if (weight.is_none() || G.attr("number_of_edges")().equal(py::cast(0))) {
max_weight = 1;
}
else {
int assigned = 0;
for (auto& u_info : G_.adj) {
for (auto& v_info : u_info.second) {
auto& d = v_info.second;
if (assigned) {
max_weight = std::max(max_weight, d.count(weight_key) ? d[weight_key] : 1);
}
else {
assigned = 1;
max_weight = d.count(weight_key) ? d[weight_key] : 1;
}
}
}
}
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> ipreds, isuccs;
for (const auto& pair : G_.pred[i_id]) {
ipreds.insert(pair.first);
}
ipreds.erase(i_id);
for (const auto& pair : G_.adj[i_id]) {
isuccs.insert(pair.first);
}
isuccs.erase(i_id);
weight_t directed_triangles = 0;
for (const auto& j_id : ipreds) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
if (ipreds.count(k_id)) { // ipreds & jpreds
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jpreds
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
if (ipreds.count(k_id)) { // ipreds & jsuccs
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jsuccs
directed_triangles += std::cbrt(wt(adj, j_id, i_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
}
}
for (const auto& j_id : isuccs) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
if (ipreds.count(k_id)) { // ipreds & jpreds
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jpreds
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, k_id, j_id, weight_key, max_weight));
}
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
if (ipreds.count(k_id)) { // ipreds & jsuccs
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, k_id, i_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
if (isuccs.count(k_id)) { // isuccs & jsuccs
directed_triangles += std::cbrt(wt(adj, i_id, j_id, weight_key, max_weight) * wt(adj, i_id, k_id, weight_key, max_weight) * wt(adj, j_id, k_id, weight_key, max_weight));
}
}
}
int dtotal = ipreds.size() + isuccs.size();
int dbidirectional = 0;
for (const auto& node : ipreds) {
dbidirectional += isuccs.count(node);
}
ret.append(py::make_tuple(nodes_list[i], dtotal, dbidirectional, directed_triangles));
}
return ret;
}
py::list _triangles_and_degree(py::object G, py::object nodes = py::none()) {
Graph& G_ = G.cast<Graph&>();
auto& adj = G_.adj;
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t v = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> vs;
for (const auto& pair : adj[v]) {
vs.insert(pair.first);
}
vs.erase(v);
weight_t ntriangles = 0;
for (const auto& w : vs) {
for (const auto& node : vs) {
ntriangles += node != w && adj[w].count(node);
}
}
ret.append(py::make_tuple(G_.id_to_node[py::cast(v)], vs.size(), ntriangles));
}
return ret;
}
py::list _directed_triangles_and_degree(py::object G, py::object nodes = py::none()) {
DiGraph& G_ = G.cast<DiGraph&>();
auto& adj = G_.adj;
py::list nodes_list = py::list(G.attr("nbunch_iter")(nodes));
py::list ret = py::list();
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
std::unordered_set<node_t> ipreds, isuccs;
for (const auto& pair : G_.pred[i_id]) {
ipreds.insert(pair.first);
}
ipreds.erase(i_id);
for (const auto& pair : G_.adj[i_id]) {
isuccs.insert(pair.first);
}
isuccs.erase(i_id);
weight_t directed_triangles = 0;
for (const auto& j_id : ipreds) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
directed_triangles += ipreds.count(k_id); // ipreds & jpreds
directed_triangles += isuccs.count(k_id); // isuccs & jpreds
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
directed_triangles += ipreds.count(k_id); // ipreds & jsuccs
directed_triangles += isuccs.count(k_id); // isuccs & jsuccs
}
}
for (const auto& j_id : isuccs) {
for (const auto& k_pair : G_.pred[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jpreds
directed_triangles += ipreds.count(k_id); // ipreds & jpreds
directed_triangles += isuccs.count(k_id); // isuccs & jpreds
}
for (const auto& k_pair : G_.adj[j_id]) {
node_t k_id = k_pair.first;
if (k_id == j_id) {
continue;
}// jsuccs
directed_triangles += ipreds.count(k_id); // ipreds & jsuccs
directed_triangles += isuccs.count(k_id); // isuccs & jsuccs
}
}
int dtotal = ipreds.size() + isuccs.size();
int dbidirectional = 0;
for (const auto& node : ipreds) {
dbidirectional += isuccs.count(node);
}
ret.append(py::make_tuple(nodes_list[i], dtotal, dbidirectional, directed_triangles));
}
return ret;
}
py::object clustering(py::object G, py::object nodes, py::object weight) {
py::dict clusterc = py::dict();
if (G.attr("is_directed")().cast<bool>()) {
py::list td_list;
if (!weight.is_none()) {
td_list = _directed_weighted_triangles_and_degree(G, nodes, weight);
}
else {
td_list = _directed_triangles_and_degree(G, nodes);
}
for (int i = 0;i < py::len(td_list);i++) {
py::tuple tuple = td_list[i].cast<py::tuple>();
py::object v = tuple[0];
int dt = tuple[1].cast<int>(), db = tuple[2].cast<int>();
weight_t t = tuple[3].cast<weight_t>();
if (t == 0) {
clusterc[v] = 0;
}
else {
clusterc[v] = t / ((dt * (dt - 1) - 2 * db) * 2);
}
}
}
else {
py::list td_list;
if (!weight.is_none()) {
td_list = _weighted_triangles_and_degree(G, nodes, weight);
}
else {
td_list = _triangles_and_degree(G, nodes);
}
for (int i = 0;i < py::len(td_list);i++) {
py::tuple tuple = td_list[i].cast<py::tuple>();
py::object v = tuple[0];
int d = tuple[1].cast<int>();
weight_t t = tuple[2].cast<weight_t>();
if (t == 0) {
clusterc[v] = 0;
}
else {
clusterc[v] = t / (d * (d - 1));
}
}
}
if (G.contains(nodes)) {
return clusterc[nodes];
}
return clusterc;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object clustering(py::object G, py::object nodes, py::object weight);
@@ -0,0 +1 @@
#include "centrality.h"
@@ -0,0 +1,467 @@
#ifdef _OPENMP
#include <omp.h>
#endif
#include <vector>
#include <queue>
#include <limits.h>
#include <algorithm>
#include <string>
#include <cstdio>
#include "centrality.h"
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#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<double>& bc, int cutoff, int endpoints_,
std::vector<int>& q, std::vector<int>& dis, std::vector<int>& head_path, std::vector<int>& St,
std::vector<long long>& count_path, std::vector<double>& delta, std::vector<LinkEdge>& E_path,
std::vector<int>& 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<int>& head = G_l.head;
const std::vector<LinkEdge>& 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<double>& bc, double cutoff,
std::vector<int>& dis, std::vector<int>& head_path,
std::vector<int>& St, std::vector<long long>& count_path, std::vector<double>& delta,
std::vector<LinkEdge>& E_path, int endpoints_,
std::vector<int>& 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::pair<int, int>, std::vector<std::pair<int, int>>, std::greater<std::pair<int, int>>> pq;
pq.push({0, S});
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!pq.empty()) {
std::pair<int, int> 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<Graph&>();
int cutoff_ = -1;
if (!cutoff.is_none()) {
cutoff_ = cutoff.cast<int>();
}
int N = G_.node.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
int normalized_ = normalized.cast<bool>();
int endpoints_ = endpoints.cast<bool>();
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<double> bc(N + 1, 0.0);
std::vector<double> BC;
int num_threads = 1;
#ifdef _OPENMP
num_threads = omp_get_max_threads();
#endif
std::vector<std::vector<int>> dis_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> head_path_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> St_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<long long>> count_path_all(num_threads, std::vector<long long>(N + 1));
std::vector<std::vector<double>> delta_all(num_threads, std::vector<double>(N + 1));
std::vector<std::vector<LinkEdge>> E_path_all(num_threads, std::vector<LinkEdge>(edges_num + 1));
std::vector<std::vector<int>> queue_all(num_threads, std::vector<int>(N + 1));
std::vector<std::vector<int>> stamp_all(num_threads, std::vector<int>(N + 1, 0));
std::vector<int> cur_stamp_all(num_threads, 0);
std::vector<std::vector<double>> bc_local_all(num_threads, std::vector<double>(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<node_t> 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<node_t>());
}
#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<double> 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<Graph&>();
if (weight.is_none()) {
G_.gen_CSR();
} else {
G_.gen_CSR(weight_to_string(weight));
}
auto csr_graph = G_.csr_graph;
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<double> *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<double> BC;
bool is_directed = G.attr("is_directed")().cast<bool>();
int gpu_r = gpu_easygraph::betweenness_centrality(V, E, *W_p, *sources,
is_directed, normalized.cast<py::bool_>(),
endpoints.cast<py::bool_>(), 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<double> 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<double>& bc, double cutoff) {
// int N = G_l.n;
// int edge_number_path = 0;
// __gnu_pbds::priority_queue<compare_node> q;
// std::vector<double> dis(N+1, INFINITY);
// std::vector<bool> vis(N+1, false);
// std::vector<int> head_path(N+1, 0);
// const std::vector<int>& head = G_l.head;
// const std::vector<LinkEdge>& E = G_l.edges;
// int edges_num = E.size();
// std::vector<int> St(N+1, 0);
// std::vector<long long> count_path(N+1, 0);
// std::vector<double> delta(N+1, 0);
// std::vector<LinkEdge> 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];
// }
// }
@@ -0,0 +1,27 @@
#pragma once
#include "../../common/common.h"
py::object closeness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources);
py::object betweenness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources,
py::object normalized, py::object endpoints);
py::object cpp_katz_centrality(
py::object G,
py::object py_alpha,
py::object py_beta,
py::object py_max_iter,
py::object py_tol,
py::object py_normalized
);
py::object degree_centrality(py::object G);
py::object in_degree_centrality(py::object G);
py::object out_degree_centrality(py::object G);
py::object cpp_eigenvector_centrality(
py::object G,
py::object py_max_iter,
py::object py_tol,
py::object py_nstart,
py::object py_weight
);
@@ -0,0 +1,353 @@
#include "centrality.h"
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include <queue>
#include <vector>
#include <limits>
#include <cmath>
#ifdef _OPENMP
#include <omp.h>
#endif
// Heap node: use negative value + max heap to implement min heap
typedef std::pair<float, int> HeapNode;
// Optimized adjacency list cache
struct FastAdjCache {
std::vector<int*> neighbor_ptrs;
std::vector<int> neighbor_counts;
std::vector<float*> weight_ptrs;
std::vector<std::vector<int>> neighbor_storage;
std::vector<std::vector<float>> weight_storage;
void init(int N) {
neighbor_ptrs.resize(N + 1, nullptr);
neighbor_counts.resize(N + 1, 0);
neighbor_storage.resize(N + 1);
}
void init_with_weights(int N) {
init(N);
weight_ptrs.resize(N + 1, nullptr);
weight_storage.resize(N + 1);
}
inline void build_if_needed(int u, const std::vector<int>& head,
const std::vector<LinkEdge>& edges, bool store_weights) {
if (neighbor_ptrs[u] != nullptr) return;
std::vector<int>& neis = neighbor_storage[u];
for (int p = head[u]; p != -1; p = edges[p].next) {
neis.push_back(edges[p].to);
if (store_weights) {
weight_storage[u].push_back(edges[p].w);
}
}
neighbor_counts[u] = neis.size();
neighbor_ptrs[u] = neis.data();
if (store_weights) {
weight_ptrs[u] = weight_storage[u].data();
}
}
inline int* get_neighbors_ptr(int u) const { return neighbor_ptrs[u]; }
inline int get_neighbor_count(int u) const { return neighbor_counts[u]; }
inline float* get_weights_ptr(int u) const { return weight_ptrs[u]; }
};
// BFS implementation - directly use raw adjacency list
double closeness_bfs_direct(const Graph_L& G_l, const int &S, int cutoff,
std::vector<int>& already_counted,
std::vector<int>& queue_storage,
int timestamp) {
int N = G_l.n;
const std::vector<LinkEdge>& E = G_l.edges;
const std::vector<int>& head = G_l.head;
int nodes_reached = 0;
long long sum_dis = 0;
queue_storage.clear();
int queue_front = 0;
already_counted[S] = timestamp;
queue_storage.push_back(S);
queue_storage.push_back(0);
while (queue_front < static_cast<int>(queue_storage.size())) {
int u = queue_storage[queue_front++];
int actdist = queue_storage[queue_front++];
if (cutoff >= 0 && actdist > cutoff) {
continue;
}
sum_dis += actdist;
nodes_reached++;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
if (already_counted[v] == timestamp) {
continue;
}
already_counted[v] = timestamp;
queue_storage.push_back(v);
queue_storage.push_back(actdist + 1);
}
}
if (nodes_reached == 1)
return 0.0;
else
return 1.0 * (nodes_reached - 1) * (nodes_reached - 1) / ((N - 1) * sum_dis);
}
// Check if the graph is unweighted
inline bool is_unweighted_graph(const Graph_L& G_l) {
const std::vector<LinkEdge>& E = G_l.edges;
for (const auto& edge : E) {
if (std::abs(edge.w - 1.0f) > 1e-9) {
return false;
}
}
return true;
}
// Dijkstra implementation - use on-demand adjacency cache
double closeness_dijkstra_cached(const Graph_L& G_l, const int &S, int cutoff,
std::vector<float>& dist,
std::vector<int>& which,
FastAdjCache& cache,
int timestamp) {
int N = G_l.n;
const std::vector<LinkEdge>& E = G_l.edges;
const std::vector<int>& head = G_l.head;
int nodes_reached = 0;
double sum_dis = 0.0;
std::priority_queue<HeapNode> heap;
dist[S] = 1.0f;
which[S] = timestamp;
heap.push({-1.0f, S});
while (!heap.empty()) {
HeapNode top = heap.top();
heap.pop();
float mindist = -top.first;
int minnei = top.second;
if (mindist > dist[minnei]) {
continue;
}
float actual_dist = mindist - 1.0f;
if (cutoff >= 0 && actual_dist > cutoff) {
continue;
}
sum_dis += actual_dist;
nodes_reached++;
cache.build_if_needed(minnei, head, E, true);
int* neis = cache.get_neighbors_ptr(minnei);
float* ws = cache.get_weights_ptr(minnei);
int nlen = cache.get_neighbor_count(minnei);
for (int j = 0; j < nlen; j++) {
int to = neis[j];
float altdist = mindist + ws[j];
float curdist = dist[to];
if (which[to] != timestamp) {
which[to] = timestamp;
dist[to] = altdist;
heap.push({-altdist, to});
} else if (curdist == 0.0f || altdist < curdist) {
dist[to] = altdist;
heap.push({-altdist, to});
}
}
}
if (nodes_reached == 1)
return 0.0;
else
return 1.0 * (nodes_reached - 1) * (nodes_reached - 1) / ((N - 1) * sum_dis);
}
static py::object invoke_cpp_closeness_centrality(py::object G, py::object weight,
py::object cutoff, py::object sources) {
Graph& G_ = G.cast<Graph&>();
int N = G_.node.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::string weight_key = weight_to_string(weight);
const Graph_L& G_l = graph_to_linkgraph(G_, is_directed, weight_key, false, false);
int cutoff_ = -1;
if (!cutoff.is_none()){
cutoff_ = cutoff.cast<int>();
}
// Auto algorithm selection
bool use_bfs = (weight.is_none() || is_unweighted_graph(G_l));
std::vector<double> CC;
if(!sources.is_none()){
py::list sources_list = py::list(sources);
int sources_list_len = py::len(sources_list);
CC.resize(sources_list_len);
// Collect all source node IDs
std::vector<node_t> source_ids(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();
}
source_ids[i] = G_.node_to_id.attr("get")(sources_list[i]).cast<node_t>();
}
// OpenMP parallel computation
// Only enable parallelism when sources are many to avoid overhead
#pragma omp parallel if(sources_list_len > 100)
{
// Per-thread data structures (avoid race conditions)
std::vector<int> already_counted(N + 1, 0);
std::vector<int> queue_storage;
queue_storage.reserve(N * 2);
std::vector<float> dist(N + 1, 0.0f);
std::vector<int> which(N + 1, 0);
FastAdjCache cache;
if (!use_bfs) {
cache.init_with_weights(N);
}
// Assign unique timestamp start for each thread
#ifdef _OPENMP
int thread_id = omp_get_thread_num();
int num_threads = omp_get_num_threads();
int timestamp = thread_id * sources_list_len;
#else
int timestamp = 0;
#endif
// Parallel loop: each thread handles different source node
#pragma omp for schedule(dynamic, 1)
for(int i = 0; i < sources_list_len; i++){
timestamp++;
double res;
if (use_bfs) {
res = closeness_bfs_direct(G_l, source_ids[i], cutoff_,
already_counted, queue_storage, timestamp);
} else {
res = closeness_dijkstra_cached(G_l, source_ids[i], cutoff_,
dist, which, cache, timestamp);
}
CC[i] = res;
}
}
}
else{
CC.resize(N);
// OpenMP parallel computation for all nodes
// Only enable parallelism when node count is large
#pragma omp parallel if(N > 100)
{
// Per-thread data structures
std::vector<int> already_counted(N + 1, 0);
std::vector<int> queue_storage;
queue_storage.reserve(N * 2);
std::vector<float> dist(N + 1, 0.0f);
std::vector<int> which(N + 1, 0);
FastAdjCache cache;
if (!use_bfs) {
cache.init_with_weights(N);
}
// Assign unique timestamp start for each thread
#ifdef _OPENMP
int thread_id = omp_get_thread_num();
int num_threads = omp_get_num_threads();
int timestamp = thread_id * N;
#else
int timestamp = 0;
#endif
// Parallel loop: dynamic scheduling for load balancing
#pragma omp for schedule(dynamic, 10)
for(int i = 1; i <= N; i++){
timestamp++;
double res;
if (use_bfs) {
res = closeness_bfs_direct(G_l, i, cutoff_,
already_counted, queue_storage, timestamp);
} else {
res = closeness_dijkstra_cached(G_l, i, cutoff_,
dist, which, cache, timestamp);
}
CC[i - 1] = res;
}
}
}
py::array::ShapeContainer ret_shape{(int)CC.size()};
py::array_t<double> ret(ret_shape, CC.data());
return ret;
}
#ifdef EASYGRAPH_ENABLE_GPU
static py::object invoke_gpu_closeness_centrality(py::object G, py::object weight, py::object py_sources) {
Graph& G_ = G.cast<Graph&>();
if (weight.is_none()) {
G_.gen_CSR();
} else {
G_.gen_CSR(weight_to_string(weight));
}
auto csr_graph = G_.csr_graph;
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<double> *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<double> CC;
int gpu_r = gpu_easygraph::closeness_centrality(V, E, *W_p, *sources, CC);
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)CC.size()};
py::array_t<double> ret(ret_shape, CC.data());
return ret;
}
#endif
py::object closeness_centrality(py::object G, py::object weight, py::object cutoff, py::object sources) {
#ifdef EASYGRAPH_ENABLE_GPU
return invoke_gpu_closeness_centrality(G, weight, sources);
#else
return invoke_cpp_closeness_centrality(G, weight, cutoff, sources);
#endif
}
@@ -0,0 +1,96 @@
#include "centrality.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
namespace py = pybind11;
py::object degree_centrality(
py::object G
) {
Graph* graph = G.cast<Graph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
for (const auto& node_handle : nodes) {
centrality_map[node_handle] = 0.0;
}
return centrality_map;
}
double scale = 1.0 / (n - 1);
std::string class_name = G.attr("__class__").attr("__name__").cast<std::string>();
if (class_name == "DiGraphC") {
// 有向图 (DiGraph)
DiGraph* digraph = G.cast<DiGraph*>();
py::object adj = digraph->get_adj();
py::object pred = digraph->get_pred();
for (const auto& node_handle : nodes) {
int out_deg = py::len(adj[node_handle]);
int in_deg = py::len(pred[node_handle]);
centrality_map[node_handle] = (double)(out_deg + in_deg) * scale;
}
} else {
py::object adj = graph->get_adj();
for (const auto& node_handle : nodes) {
int degree = py::len(adj[node_handle]);
centrality_map[node_handle] = (double)degree * scale;
}
}
return centrality_map;
}
py::object in_degree_centrality(
py::object G
) {
DiGraph* graph = G.cast<DiGraph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
return centrality_map;
}
double scale = 1.0 / (n - 1);
py::object pred = graph->get_pred();
for (const auto& node_handle : nodes) {
int in_degree = py::len(pred[node_handle]);
centrality_map[node_handle] = in_degree * scale;
}
return centrality_map;
}
py::object out_degree_centrality(
py::object G
) {
Graph* graph = G.cast<Graph*>();
py::dict centrality_map = py::dict();
py::object nodes = graph->get_nodes();
int n = py::len(nodes);
if (n <= 1) {
return centrality_map;
}
double scale = 1.0 / (n - 1);
py::object adj = graph->get_adj();
for (const auto& node_handle : nodes) {
int out_degree = py::len(adj[node_handle]);
centrality_map[node_handle] = out_degree * scale;
}
return centrality_map;
}
@@ -0,0 +1,198 @@
#include <vector>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#ifdef _OPENMP
#include <omp.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
namespace py = pybind11;
class CSRMatrix {
public:
std::vector<int> indptr;
std::vector<int> indices;
std::vector<double> data; // Empty if unweighted (all 1.0)
int rows, cols;
bool is_weighted;
CSRMatrix(int r, int c) : rows(r), cols(c), is_weighted(false) {
indptr.assign(r + 1, 0);
}
};
// Power iteration with branch optimization for weighted/unweighted paths
std::vector<double> power_iteration_optimized(
const CSRMatrix& A,
int max_iter,
double tol,
std::vector<double>& x
) {
const int n = A.rows;
std::vector<double> x_next(n);
bool use_weight = A.is_weighted && !A.data.empty();
// Initial normalization
double norm = 0.0;
#pragma omp parallel for reduction(+:norm)
for (int i = 0; i < n; ++i) norm += x[i] * x[i];
norm = std::sqrt(norm);
if (norm < 1e-12) {
std::fill(x.begin(), x.end(), 1.0 / std::sqrt(n));
} else {
double inv_norm = 1.0 / norm;
#pragma omp parallel for
for (int i = 0; i < n; ++i) x[i] *= inv_norm;
}
double delta = tol + 1.0;
for (int iter = 0; iter < max_iter && delta >= tol; ++iter) {
double next_norm_sq = 0.0;
#pragma omp parallel for reduction(+:next_norm_sq) schedule(dynamic, 64)
for (int i = 0; i < n; ++i) {
double sum = 0.0;
const int start = A.indptr[i];
const int end = A.indptr[i+1];
if (use_weight) {
for (int j = start; j < end; ++j) {
sum += A.data[j] * x[A.indices[j]];
}
} else {
for (int j = start; j < end; ++j) {
sum += x[A.indices[j]];
}
}
x_next[i] = sum;
next_norm_sq += sum * sum;
}
double next_norm = std::sqrt(next_norm_sq);
if (next_norm < 1e-12) break;
double inv_next_norm = 1.0 / next_norm;
delta = 0.0;
#pragma omp parallel for reduction(+:delta) schedule(static)
for (int i = 0; i < n; ++i) {
double val = x_next[i] * inv_next_norm;
delta += std::abs(val - x[i]);
x_next[i] = val;
}
x.swap(x_next);
}
return x;
}
// Build transpose CSR with fallback logic for missing weight keys
CSRMatrix build_transpose_matrix_smart(Graph& graph, const std::vector<node_t>& nodes, const std::string& weight_key) {
std::shared_ptr<CSRGraph> csr_ptr = weight_key.empty() ? graph.gen_CSR() : graph.gen_CSR(weight_key);
int n = static_cast<int>(nodes.size());
CSRMatrix At(n, n);
if (!csr_ptr) return At;
const auto& src_indptr = csr_ptr->V;
const auto& src_indices = csr_ptr->E;
std::vector<double> src_data;
bool actually_weighted = false;
// Detect if weighted calculation is required
if (!weight_key.empty()) {
auto it = csr_ptr->W_map.find(weight_key);
if (it != csr_ptr->W_map.end() && it->second) {
src_data = *(it->second);
for (double w : src_data) {
if (std::abs(w - 1.0) > 1e-9) {
actually_weighted = true;
break;
}
}
}
}
At.is_weighted = actually_weighted;
// Calculate row counts for transpose
for (int x_idx : src_indices) {
if (x_idx >= 0 && x_idx < n) At.indptr[x_idx + 1]++;
}
for (int i = 0; i < n; ++i) At.indptr[i + 1] += At.indptr[i];
At.indices.resize(src_indices.size());
if (actually_weighted) At.data.resize(src_indices.size());
std::vector<int> cur_pos(At.indptr.begin(), At.indptr.end());
// Populate transpose CSR data
for (int r = 0; r < n; ++r) {
for (int p = src_indptr[r]; p < src_indptr[r+1]; ++p) {
int c = src_indices[p];
if (c < 0 || c >= n) continue;
int dest = cur_pos[c]++;
At.indices[dest] = r;
if (actually_weighted) At.data[dest] = src_data[p];
}
}
return At;
}
py::object cpp_eigenvector_centrality(
py::object G,
py::object py_max_iter,
py::object py_tol,
py::object py_nstart,
py::object py_weight
) {
try {
Graph& graph = G.cast<Graph&>();
int max_iter = py_max_iter.cast<int>();
double tol = py_tol.cast<double>();
std::string weight_key = py_weight.is_none() ? "" : py_weight.cast<std::string>();
if (graph.node.empty()) return py::dict();
std::vector<node_t> nodes;
for (auto& pair : graph.node) nodes.push_back(pair.first);
int n = nodes.size();
CSRMatrix A_transpose = build_transpose_matrix_smart(graph, nodes, weight_key);
// Initialize x vector (prefer degree-based or uniform)
std::vector<double> x(n, 1.0 / n);
if (!py_nstart.is_none()) {
py::dict nstart = py_nstart.cast<py::dict>();
for (int i = 0; i < n; i++) {
py::object node_obj = graph.id_to_node[py::cast(nodes[i])];
if (nstart.contains(node_obj)) x[i] = nstart[node_obj].cast<double>();
}
} else {
for (int i = 0; i < n; i++) {
int degree = A_transpose.indptr[i+1] - A_transpose.indptr[i];
x[i] = (degree > 0) ? (double)degree : 1.0/n;
}
}
std::vector<double> res = power_iteration_optimized(A_transpose, max_iter, tol, x);
py::dict result;
for (int i = 0; i < n; i++) {
py::object node_obj = graph.id_to_node[py::cast(nodes[i])];
result[node_obj] = res[i];
}
return result;
} catch (const std::exception& e) {
throw std::runtime_error(std::string("C++ Eigenvector Error: ") + e.what());
}
}
@@ -0,0 +1,194 @@
#ifdef _OPENMP
#include <omp.h>
#endif
#include <vector>
#include <cmath>
#include <algorithm>
#include <stdexcept>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "centrality.h"
#include "../../classes/graph.h"
namespace py = pybind11;
class CSRMatrix {
public:
std::vector<int> indptr; // size rows+1
std::vector<int> indices; // size nnz
std::vector<double> data; // size nnz
int rows = 0;
int cols = 0;
CSRMatrix() = default;
CSRMatrix(int r, int c) : rows(r), cols(c) {
indptr.assign(r + 1, 0);
}
};
// Build transpose CSR from EasyGraph CSR so that row i contains in-neighbors of i.
static CSRMatrix build_transpose_matrix_from_csr(const std::shared_ptr<CSRGraph>& csr_ptr) {
if (!csr_ptr) return CSRMatrix();
const int n = static_cast<int>(csr_ptr->nodes.size());
if (n == 0) return CSRMatrix(0, 0);
const auto& src_indptr = csr_ptr->V;
const auto& src_indices = csr_ptr->E;
// Unweighted: all ones.
std::vector<double> src_data(src_indices.size(), 1.0);
CSRMatrix At(n, n);
// Count nnz per column in the source (becomes nnz per row in transpose).
for (int c : src_indices) {
if (c >= 0 && c < n) At.indptr[c + 1]++;
}
// Prefix sum.
for (int i = 0; i < n; ++i) {
At.indptr[i + 1] += At.indptr[i];
}
const int nnz = static_cast<int>(src_indices.size());
At.indices.resize(nnz);
At.data.resize(nnz);
std::vector<int> cur_pos(At.indptr.begin(), At.indptr.end());
// Fill transpose.
for (int r = 0; r < n; ++r) {
const int start = src_indptr[r];
const int end = src_indptr[r + 1];
for (int p = start; p < end; ++p) {
const int c = src_indices[p];
if (c < 0 || c >= n) continue;
const int dest = cur_pos[c]++;
At.indices[dest] = r;
At.data[dest] = src_data[p];
}
}
return At;
}
static std::vector<double> katz_centrality_omp(const CSRMatrix& A,
double alpha,
const std::vector<double>& beta,
int max_iters,
double tol,
bool normalize) {
const int n = A.rows;
std::vector<double> x(n, 1.0); // initial guess
std::vector<double> x_next(n, 0.0); // next iterate
if (n == 0) return x;
for (int iter = 0; iter < max_iters; ++iter) {
double err_sq = 0.0;
double norm_sq = 0.0;
// SpMV + Katz update + error and norm in ONE pass
#pragma omp parallel for reduction(+ : err_sq, norm_sq) schedule(static)
for (int i = 0; i < n; ++i) {
double sum = 0.0;
const int row_start = A.indptr[i];
const int row_end = A.indptr[i + 1];
for (int e = row_start; e < row_end; ++e) {
sum += A.data[e] * x[A.indices[e]];
}
const double new_val = alpha * sum + beta[i];
const double diff = new_val - x[i];
x_next[i] = new_val;
err_sq += diff * diff;
norm_sq += new_val * new_val;
}
const double err = std::sqrt(err_sq);
const double norm = std::sqrt(norm_sq);
x.swap(x_next);
if (norm > 0.0 && (err / norm) < tol) {
break;
}
}
if (normalize) {
double norm_sq2 = 0.0;
#pragma omp parallel for reduction(+ : norm_sq2) schedule(static)
for (int i = 0; i < n; ++i) {
norm_sq2 += x[i] * x[i];
}
const double norm = std::sqrt(norm_sq2);
if (norm > 0.0) {
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; ++i) {
x[i] /= norm;
}
}
}
return x;
}
py::object cpp_katz_centrality(py::object G,
py::object py_alpha,
py::object py_beta,
py::object py_max_iter,
py::object py_tol,
py::object py_normalized) {
Graph& graph = G.cast<Graph&>();
const double alpha = py_alpha.cast<double>();
const int max_iter = py_max_iter.cast<int>();
const double tol = py_tol.cast<double>();
const bool normalized = py_normalized.cast<bool>();
std::shared_ptr<CSRGraph> csr_ptr = graph.gen_CSR();
if (!csr_ptr || csr_ptr->nodes.empty()) {
return py::dict();
}
const int n = static_cast<int>(csr_ptr->nodes.size());
// Build transpose CSR so that we accumulate from in-neighbors.
CSRMatrix A = build_transpose_matrix_from_csr(csr_ptr);
// Process beta parameter: scalar or dict(node->beta).
std::vector<double> beta(n, 1.0);
if (py::isinstance<py::float_>(py_beta) || py::isinstance<py::int_>(py_beta)) {
const double beta_val = py_beta.cast<double>();
#pragma omp parallel for schedule(static)
for (int i = 0; i < n; ++i) {
beta[i] = beta_val;
}
} else if (py::isinstance<py::dict>(py_beta)) {
py::dict beta_dict = py_beta.cast<py::dict>();
for (int i = 0; i < n; ++i) {
node_t internal_id = csr_ptr->nodes[i];
py::object node_obj = graph.id_to_node[py::cast(internal_id)];
if (beta_dict.contains(node_obj)) {
beta[i] = beta_dict[node_obj].cast<double>();
}
}
} else {
throw py::type_error("beta must be a float/int or a dict");
}
std::vector<double> scores = katz_centrality_omp(A, alpha, beta, max_iter, tol, normalized);
// Prepare results
py::dict result;
for (int i = 0; i < n; ++i) {
node_t internal_id = csr_ptr->nodes[i];
py::object node_obj = graph.id_to_node[py::cast(internal_id)];
result[node_obj] = scores[i];
}
return result;
}
+146
View File
@@ -0,0 +1,146 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <random>
#include <queue>
#include <cstdint>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../classes/linkgraph.h"
#include "../../common/utils.h"
namespace py = pybind11;
using namespace std;
py::object cpp_LPA(py::object G) {
Graph& G_ = G.cast<Graph&>();
Graph_L linkgraph = G_._get_linkgraph_structure();
int n = linkgraph.n;
py::dict id_to_node = G_.id_to_node;
if (n <= 1) {
py::dict result;
if (n == 1) {
py::list node_list;
node_list.append(id_to_node[py::cast(1)]);
result[py::cast(1)] = node_list;
}
return result;
}
vector<vector<int>> adj(n + 1);
for (int u = 1; u <= n; ++u) {
for (int e = linkgraph.head[u]; e != -1; e = linkgraph.edges[e].next) {
int v = linkgraph.edges[e].to;
if (u != v) {
adj[u].push_back(v);
}
}
}
for (int u = 1; u <= n; ++u) {
if (adj[u].size() > 1) {
sort(adj[u].begin(), adj[u].end());
adj[u].erase(unique(adj[u].begin(), adj[u].end()), adj[u].end());
}
}
vector<int> labels(n);
vector<int> nodes(n);
for (int i = 0; i < n; ++i) {
labels[i] = i;
nodes[i] = i + 1;
}
random_device rd;
mt19937 gen(rd());
const int MAX_ITERATIONS = 1000;
int iteration_count = 0;
vector<int> label_count(n, 0);
vector<int> active_labels;
active_labels.reserve(128);
while (iteration_count < MAX_ITERATIONS) {
iteration_count++;
shuffle(nodes.begin(), nodes.end(), gen);
bool changed = false;
for (int node : nodes) {
int max_count = 0;
for (int neighbor : adj[node]) {
int neighbor_label = labels[neighbor - 1];
if (label_count[neighbor_label] == 0) {
active_labels.push_back(neighbor_label);
}
label_count[neighbor_label]++;
if (label_count[neighbor_label] > max_count) {
max_count = label_count[neighbor_label];
}
}
if (max_count > 0) {
int current_label = labels[node - 1];
vector<int> best_labels;
for (int label : active_labels) {
if (label_count[label] == max_count) {
best_labels.push_back(label);
}
}
bool current_is_best = false;
for (int label : best_labels) {
if (label == current_label) {
current_is_best = true;
break;
}
}
if (!current_is_best) {
changed = true;
if (best_labels.size() == 1) {
labels[node - 1] = best_labels[0];
} else {
uniform_int_distribution<> dis(0, best_labels.size() - 1);
labels[node - 1] = best_labels[dis(gen)];
}
}
}
for (int label : active_labels) {
label_count[label] = 0;
}
active_labels.clear();
}
if (!changed) {
break;
}
}
unordered_map<int, vector<int>> label_to_nodes;
for (int i = 0; i < n; ++i) {
int label = labels[i];
label_to_nodes[label].push_back(i + 1);
}
py::dict result;
int community_id = 1;
for (const auto& pair : label_to_nodes) {
py::list node_list;
for (int internal_id : pair.second) {
node_list.append(id_to_node[py::cast(internal_id)]);
}
result[py::cast(community_id)] = node_list;
community_id++;
}
return result;
}
+7
View File
@@ -0,0 +1,7 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_LPA(py::object G);
@@ -0,0 +1,10 @@
#pragma once
#include "modularity.h"
#include "greedy_modularity.h"
#include "motif.h"
#include "louvain.h"
#include "LPA.h"
#include "ego_graph.h"
#include "graph_coloring.h"
#include "localsearch.h"
@@ -0,0 +1,155 @@
#include "ego_graph.h"
#include "subgraph.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../classes/csr_graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "../path/path.h"
#include <algorithm>
struct _EgoGraphCore {
Graph_L G_l;
std::vector<node_t> node_ids;
std::unordered_map<node_t, int> node_to_idx;
py::dict id_to_node_py;
bool has_error = false;
};
static _EgoGraphCore _cpp_ego_graph_compute_impl(
Graph& G_,
py::object n,
double radius_val,
bool center_val,
bool undirected_val,
bool is_directed,
const std::string& weight_key) {
_EgoGraphCore out;
if (G_.node_to_id.contains(n) == 0) {
PyErr_Format(PyExc_KeyError, "Node %R is not in the graph.", n.ptr());
out.has_error = true;
return out;
}
node_t center_id = G_.node_to_id[n].cast<node_t>();
out.id_to_node_py = G_.id_to_node;
if (G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1) {
if (undirected_val) {
out.G_l = graph_to_linkgraph(G_, false, weight_key, true, false);
} else {
out.G_l = graph_to_linkgraph(G_, is_directed, weight_key, true, false);
}
G_.linkgraph_dirty = false;
G_.linkgraph_structure = out.G_l; // also cache the freshly built linkgraph
} else {
out.G_l = G_.linkgraph_structure;
}
std::vector<double> dist = _dijkstra(out.G_l, center_id, -1);
int N = out.G_l.n;
for (int i = 1; i <= N; i++) {
if (dist[i] > radius_val) continue;
if (!center_val && i == (int)center_id) continue;
out.node_to_idx[i] = (int)out.node_ids.size();
out.node_ids.push_back(i);
}
return out;
}
static _EgoGraphCore _cpp_ego_graph_compute(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
bool is_directed = G.attr("is_directed")().cast<bool>();
bool center_val = center.cast<bool>();
bool undirected_val = undirected.cast<bool>();
double radius_val = radius.cast<double>();
std::string weight_key = weight_to_string(distance);
if (is_directed) {
DiGraph& G_ = G.cast<DiGraph&>();
return _cpp_ego_graph_compute_impl(
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
} else {
Graph& G_ = G.cast<Graph&>();
return _cpp_ego_graph_compute_impl(
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
}
}
py::object cpp_ego_graph(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
if (core.has_error) return py::none();
return nodes_subgraph_cpp(G, core.node_ids);
}
py::object cpp_ego_graph_csr(
py::object G,
py::object n,
py::object radius,
py::object center,
py::object undirected,
py::object distance) {
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
if (core.has_error) return py::none();
int n_nodes = (int)core.node_ids.size();
if (n_nodes == 0) {
return py::dict();
}
std::vector<int> V(n_nodes + 1, 0);
std::vector<int> E;
std::vector<double> W;
for (int new_u = 0; new_u < n_nodes; new_u++) {
node_t u = core.node_ids[new_u];
V[new_u + 1] = V[new_u];
int edge_idx = core.G_l.head[u];
while (edge_idx != -1 && edge_idx < core.G_l.e) {
node_t v = core.G_l.edges[edge_idx].to;
auto it = core.node_to_idx.find(v);
if (it != core.node_to_idx.end()) {
E.push_back(it->second);
W.push_back(core.G_l.edges[edge_idx].w);
V[new_u + 1]++;
}
edge_idx = core.G_l.edges[edge_idx].next;
}
}
py::list original_nodes;
for (node_t internal_id : core.node_ids) {
original_nodes.append(core.id_to_node_py[py::cast(internal_id)]);
}
py::dict result;
result["nodes"] = original_nodes;
result["V"] = py::cast(V);
result["E"] = py::cast(E);
result["W"] = py::cast(W);
return result;
}
@@ -0,0 +1,23 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_ego_graph(
py::object G,
py::object n,
py::object radius = py::int_(1),
py::object center = py::bool_(true),
py::object undirected = py::bool_(false),
py::object distance = py::none()
);
py::object cpp_ego_graph_csr(
py::object G,
py::object n,
py::object radius = py::int_(1),
py::object center = py::bool_(true),
py::object undirected = py::bool_(false),
py::object distance = py::none()
);
@@ -0,0 +1,135 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <iostream>
#include <random>
#include <numeric>
#include <cstdint>
#include <chrono>
#include <queue>
#include <cstring>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: omp_graph_coloring will fall back to single-threaded execution."
#endif
#include "../../classes/linkgraph.h"
using namespace std;
vector<int> greedy_graph_coloring(const Graph_L& G) {
int n = G.n;
if (n == 0) return vector<int>();
vector<int> degree(n + 1, 0);
for (int v = 1; v <= n; ++v) {
for (int e = G.head[v]; e != -1; e = G.edges[e].next) {
degree[v]++;
}
}
vector<int> nodes(n);
iota(nodes.begin(), nodes.end(), 1);
sort(nodes.begin(), nodes.end(), [&degree](int a, int b) {
return degree[a] > degree[b];
});
vector<int> colors(n, -1);
vector<int> used(n + 1, -1);
for (int u : nodes) {
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
if (v == u) continue;
int neighbor_color = colors[v - 1];
if (neighbor_color != -1) {
used[neighbor_color] = u;
}
}
int cr = 0;
while (cr <= n && used[cr] == u) {
cr++;
}
colors[u - 1] = cr;
}
return colors;
}
vector<int> omp_graph_coloring(const Graph_L& G) {
int n = G.n;
if (n == 0) return vector<int>();
vector<int> degree(n + 1, 0);
for (int v = 1; v <= n; ++v) {
for (int e = G.head[v]; e != -1; e = G.edges[e].next) {
degree[v]++;
}
}
vector<int> nodes(n);
iota(nodes.begin(), nodes.end(), 1);
sort(nodes.begin(), nodes.end(), [&degree](int a, int b) {
return degree[a] > degree[b];
});
vector<int> colors(n, -1);
#pragma omp parallel
{
vector<int> local_used(n + 1, -1);
#pragma omp for schedule(guided)
for (int i = 0; i < n; ++i) {
int u = nodes[i];
fill(local_used.begin(), local_used.end(), -1);
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
if (v == u) continue;
int neighbor_color = colors[v - 1];
if (neighbor_color != -1 && neighbor_color <= n) {
local_used[neighbor_color] = u;
}
}
int cr = 0;
while (cr <= n && local_used[cr] == u) {
cr++;
}
colors[u - 1] = cr;
}
}
return colors;
}
bool verify_coloring(const Graph_L& g, const vector<int>& colors) {
int n = g.n;
for (int v = 1; v <= n; v++) {
int v_color = colors[v - 1];
for (int e = g.head[v]; e != -1; e = g.edges[e].next) {
int u = g.edges[e].to;
if (u == v) continue;
int u_color = colors[u - 1];
if (v_color == u_color) {
return false;
}
}
}
return true;
}
int count_colors(const vector<int>& colors) {
int max_color = -1;
for (int c : colors) {
if (c > max_color) {
max_color = c;
}
}
return max_color + 1;
}
@@ -0,0 +1,12 @@
#pragma once
#include <vector>
#include "../../classes/linkgraph.h"
std::vector<int> greedy_graph_coloring(const Graph_L& G);
std::vector<int> omp_graph_coloring(const Graph_L& G);
bool verify_coloring(const Graph_L& g, const std::vector<int>& colors);
int count_colors(const std::vector<int>& colors);
@@ -0,0 +1,289 @@
#include "indexed_heap.h"
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <cmath>
#include <queue>
#include <set>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "greedy_modularity.h"
namespace py = pybind11;
using namespace std;
py::object cpp_greedy_modularity_communities(py::object G, py::object weight) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed) {
throw py::value_error("greedy_modularity_communities currently only supports undirected graphs (Graph class). "
"For directed graphs, please use other community detection algorithms.");
}
string weight_key = weight.is_none() ? "weight" : weight.cast<string>();
Graph_L GL;
bool used_cached_linkgraph = false;
if (G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1) {
GL = graph_to_linkgraph(G_, false, weight_key, true, false);
G_.linkgraph_dirty = false;
} else {
GL = G_.linkgraph_structure;
used_cached_linkgraph = true;
}
int N = GL.n;
if (N == 0) {
return py::list();
}
double m = 0.0;
vector<double> k(N + 1, 0.0);
for (int u = 1; u <= N; ++u) {
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
double w = GL.edges[e].w;
if (v > u) {
m += w;
}
k[u] += w;
}
}
if (m == 0) {
py::list result;
py::dict id_to_node = G_.id_to_node;
for (int i = 1; i <= N; ++i) {
py::set comm;
comm.add(id_to_node[py::cast(i)]);
result.append(comm);
}
return result;
}
double q0 = 1.0 / (2.0 * m);
vector<double> a(N);
for (int i = 0; i < N; ++i) {
a[i] = k[i + 1] * q0;
}
vector<int> parent(N);
vector<vector<int>> nodes(N);
for (int i = 0; i < N; ++i) {
parent[i] = i;
nodes[i].push_back(i + 1);
}
vector<vector<int>> neigh(N);
int max_neigh_size = 0;
for (int i = 0; i < N; ++i) {
int u = i + 1;
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
if (v > 0 && v <= N && v != u) {
neigh[i].push_back(v - 1);
}
}
sort(neigh[i].begin(), neigh[i].end());
neigh[i].erase(unique(neigh[i].begin(), neigh[i].end()), neigh[i].end());
if ((int)neigh[i].size() > max_neigh_size) {
max_neigh_size = neigh[i].size();
}
}
unordered_map<long long, double> edge_weights;
for (int u = 1; u <= N; ++u) {
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
int v = GL.edges[e].to;
double w = GL.edges[e].w;
if (v > u) {
long long key = ((long long)(u - 1) << 32) | (unsigned int)(v - 1);
edge_weights[key] = w;
}
}
}
unordered_map<long long, double> dq;
dq.reserve(N * 4);
for (int i = 0; i < N; ++i) {
int u = i + 1;
for (int j : neigh[i]) {
if (j > i) {
int v = j + 1;
long long edge_key = ((long long)i << 32) | (unsigned int)j;
double w = edge_weights.count(edge_key) ? edge_weights[edge_key] : 0.0;
double dq_val = 2.0 * w * q0 - 2.0 * k[u] * k[v] * q0 * q0;
long long key = ((long long)i << 32) | (unsigned int)j;
dq[key] = dq_val;
}
}
}
IndexedMaxHeap H(N);
for (const auto& kv : dq) {
int i = (int)(kv.first >> 32);
int j = (int)(kv.first & 0xFFFFFFFF);
H.push(kv.second, i, j);
}
vector<char> merged(N, 0);
int merge_count = 0;
vector<int> combined;
combined.reserve(max_neigh_size * 2);
while (!H.empty()) {
auto best = H.pop();
int i = best.i;
int j = best.j;
if (merged[i] || merged[j]) {
continue;
}
if (parent[i] != i || parent[j] != j) {
continue;
}
long long key = ((long long)i << 32) | (unsigned int)j;
auto it = dq.find(key);
if (it == dq.end() || it->second != best.dq) {
continue;
}
if (best.dq <= 0) {
break;
}
nodes[i].insert(nodes[i].end(), nodes[j].begin(), nodes[j].end());
nodes[j].clear();
parent[j] = i;
merged[j] = 1;
merge_count++;
combined.clear();
const vector<int>& list_i = neigh[i];
const vector<int>& list_j = neigh[j];
size_t p1 = 0, p2 = 0;
while (p1 < list_i.size() && p2 < list_j.size()) {
int val1 = list_i[p1];
int val2 = list_j[p2];
if (val1 < val2) {
if (val1 != i && val1 != j && !merged[val1]) {
combined.push_back(val1);
}
p1++;
} else if (val1 > val2) {
if (val2 != i && val2 != j && !merged[val2]) {
combined.push_back(val2);
}
p2++;
} else {
if (val1 != i && val1 != j && !merged[val1]) {
combined.push_back(val1);
}
p1++;
p2++;
}
}
while (p1 < list_i.size()) {
int val = list_i[p1];
if (val != i && val != j && !merged[val]) {
combined.push_back(val);
}
p1++;
}
while (p2 < list_j.size()) {
int val = list_j[p2];
if (val != i && val != j && !merged[val]) {
combined.push_back(val);
}
p2++;
}
neigh[i].swap(combined);
for (int k_node : neigh[i]) {
if (k_node == i) continue;
if (parent[k_node] != k_node) continue;
long long key_ik = ((long long)min(i, k_node) << 32) | (unsigned int)max(i, k_node);
long long key_jk = ((long long)min(j, k_node) << 32) | (unsigned int)max(j, k_node);
bool has_ik = dq.find(key_ik) != dq.end();
bool has_jk = dq.find(key_jk) != dq.end();
double new_dq;
if (has_ik && has_jk) {
new_dq = dq[key_ik] + dq[key_jk];
} else if (has_jk) {
new_dq = dq[key_jk] - 2.0 * a[i] * a[k_node];
} else {
new_dq = dq[key_ik] - 2.0 * a[j] * a[k_node];
}
dq[key_ik] = new_dq;
if (has_jk) {
dq.erase(key_jk);
}
if (H.get_index(i, k_node) >= 0) {
H.update(new_dq, i, k_node);
} else {
H.push(new_dq, i, k_node);
}
}
const vector<int>& old_j_neigh = neigh[j];
for (int k_node : old_j_neigh) {
if (k_node == i || k_node == j) continue;
if (parent[k_node] != k_node) continue;
H.remove(j, k_node);
}
neigh[j].clear();
a[i] += a[j];
a[j] = 0;
}
py::dict id_to_node = G_.id_to_node;
py::list result;
for (int i = 0; i < N; ++i) {
if (parent[i] == i && !nodes[i].empty()) {
py::set comm;
for (int node_id : nodes[i]) {
comm.add(id_to_node[py::cast(node_id)]);
}
result.append(comm);
}
}
py::list sorted_result;
vector<int> sizes;
for (size_t i = 0; i < result.size(); ++i) {
sizes.push_back(py::len(result[i]));
}
vector<int> indices(result.size());
iota(indices.begin(), indices.end(), 0);
sort(indices.begin(), indices.end(), [&](int a_idx, int b_idx) {
return sizes[a_idx] > sizes[b_idx];
});
for (int idx : indices) {
sorted_result.append(result[idx]);
}
return sorted_result;
}
@@ -0,0 +1,11 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_greedy_modularity_communities(
py::object G,
py::object weight = py::str("weight")
);
@@ -0,0 +1,191 @@
#pragma once
#include <vector>
#include <algorithm>
class IndexedMaxHeap {
public:
struct HeapEntry {
double dq;
int i;
int j;
};
private:
std::vector<HeapEntry> heap;
std::vector<std::vector<int>> pair_to_idx;
size_t heap_size;
int capacity_n;
static inline long long make_key(int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
return ((long long)a << 32) | (unsigned int)b;
}
public:
IndexedMaxHeap(size_t capacity = 0) : heap_size(0), capacity_n((int)capacity) {
heap.reserve(capacity);
if (capacity > 0) {
pair_to_idx.assign(capacity, std::vector<int>(capacity, -1));
}
}
bool empty() const { return heap_size == 0; }
size_t size() const { return heap_size; }
int get_index(int i, int j) const {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return -1;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return -1;
if (heap[idx].i == a && heap[idx].j == b) {
return (int)idx;
}
return -1;
}
void push(double dq, int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t existing = (size_t)pair_to_idx[a][b];
if (existing < heap.size() && heap[existing].i == a && heap[existing].j == b) {
update(dq, i, j);
return;
}
HeapEntry entry = {dq, a, b};
heap.push_back(entry);
size_t idx = heap.size() - 1;
pair_to_idx[a][b] = (int)idx;
sift_up(idx);
heap_size++;
}
void update(double dq, int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return;
if (heap[idx].i != a || heap[idx].j != b) return;
if (heap[idx].dq == dq) return;
bool is_increase = dq > heap[idx].dq;
heap[idx].dq = dq;
if (is_increase) {
sift_up(idx);
} else {
sift_down(idx);
}
}
void remove(int i, int j) {
int a = std::min(i, j);
int b = std::max(i, j);
if (a < 0 || b < 0 || a >= capacity_n || b >= capacity_n) return;
size_t idx = (size_t)pair_to_idx[a][b];
if (idx >= heap.size()) return;
if (heap[idx].i != a || heap[idx].j != b) return;
pair_to_idx[a][b] = -1;
if (idx == heap.size() - 1) {
heap.pop_back();
} else {
heap[idx] = heap.back();
int last_a = heap[idx].i;
int last_b = heap[idx].j;
if (last_a >= 0 && last_a < capacity_n && last_b >= 0 && last_b < capacity_n) {
pair_to_idx[last_a][last_b] = (int)idx;
}
heap.pop_back();
sift_up(idx);
sift_down(idx);
}
if (heap_size > 0) heap_size--;
}
HeapEntry pop() {
HeapEntry result = heap[0];
pair_to_idx[result.i][result.j] = -1;
if (heap.size() > 1) {
heap[0] = heap.back();
int new_a = heap[0].i;
int new_b = heap[0].j;
if (new_a >= 0 && new_a < capacity_n && new_b >= 0 && new_b < capacity_n) {
pair_to_idx[new_a][new_b] = 0;
}
heap.pop_back();
sift_down(0);
} else {
heap.pop_back();
}
if (heap_size > 0) heap_size--;
return result;
}
const HeapEntry& top() const { return heap[0]; }
private:
void sift_up(size_t idx) {
while (idx > 0) {
size_t parent = (idx - 1) >> 1;
if (heap[parent].dq >= heap[idx].dq) break;
std::swap(heap[parent], heap[idx]);
int p_i = heap[parent].i, p_j = heap[parent].j;
int c_i = heap[idx].i, c_j = heap[idx].j;
if (p_i >= 0 && p_i < capacity_n && p_j >= 0 && p_j < capacity_n) {
pair_to_idx[p_i][p_j] = (int)parent;
}
if (c_i >= 0 && c_i < capacity_n && c_j >= 0 && c_j < capacity_n) {
pair_to_idx[c_i][c_j] = (int)idx;
}
idx = parent;
}
}
void sift_down(size_t idx) {
size_t n = heap.size();
while (true) {
size_t largest = idx;
size_t left = idx * 2 + 1;
size_t right = idx * 2 + 2;
if (left < n && heap[left].dq > heap[largest].dq) {
largest = left;
}
if (right < n && heap[right].dq > heap[largest].dq) {
largest = right;
}
if (largest == idx) break;
std::swap(heap[largest], heap[idx]);
int l_i = heap[largest].i, l_j = heap[largest].j;
int c_i = heap[idx].i, c_j = heap[idx].j;
if (l_i >= 0 && l_i < capacity_n && l_j >= 0 && l_j < capacity_n) {
pair_to_idx[l_i][l_j] = (int)largest;
}
if (c_i >= 0 && c_i < capacity_n && c_j >= 0 && c_j < capacity_n) {
pair_to_idx[c_i][c_j] = (int)idx;
}
idx = largest;
}
}
};
@@ -0,0 +1,613 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <algorithm>
#include <queue>
#include <map>
#include <random>
#include <cmath>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "localsearch.h"
namespace py = pybind11;
using namespace std;
struct RootDecision {
int superior;
int path_length;
int degree;
};
int choose_center(const vector<pair<int, double>>& sorted_multi) {
if (sorted_multi.size() < 2) {
return 1;
}
vector<double> y;
for (const auto& p : sorted_multi) {
y.push_back(p.second);
}
vector<double> delta;
for (size_t i = 1; i < y.size(); ++i) {
delta.push_back(fabs(y[i] - y[i-1]));
}
if (delta.empty()) {
return 1;
}
vector<double> delta_nozero;
for (double d : delta) {
if (d != 0) {
delta_nozero.push_back(d);
}
}
if (delta_nozero.empty()) {
return 1;
}
double mean = 0;
for (double d : delta_nozero) {
mean += d;
}
mean /= delta_nozero.size();
double variance = 0;
for (double d : delta_nozero) {
variance += (d - mean) * (d - mean);
}
variance /= delta_nozero.size();
double std_dev = sqrt(variance);
double threshold = std_dev + mean;
for (size_t i = 0; i < delta.size(); ++i) {
if (delta[i] > threshold) {
return i + 1;
}
}
return 0;
}
py::object cpp_localsearch(
py::object G,
py::object center_num,
py::object auto_choose_centers,
py::object maximum_tree,
py::object seed,
py::object self_loop
) {
Graph_generate_linkgraph(G, py::str("weight"));
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_._get_linkgraph_structure();
py::dict id_to_node = G_.id_to_node;
int n = GL.n;
if (n == 0) {
return py::make_tuple(py::none(), py::list(), py::list(), py::dict(), py::dict(), py::none());
}
bool has_edges = false;
for (int u = 1; u <= n; ++u) {
if (GL.head[u] != -1) {
has_edges = true;
break;
}
}
if (!has_edges) {
py::dict result_grouped;
py::list result_center_dcd;
py::list result_y_dcd;
py::dict result_y_partition;
for (int u = 1; u <= n; ++u) {
py::object node = id_to_node[py::cast(u)];
py::list members;
members.append(node);
result_grouped[node] = members;
result_center_dcd.append(node);
result_y_dcd.append(node);
result_y_partition[node] = node;
}
return py::make_tuple(py::none(), result_center_dcd, result_y_dcd, result_y_partition, result_grouped, py::none());
}
std::mt19937 rng(42);
if (!seed.is_none()) {
try {
int seed_value = seed.cast<int>();
rng.seed(seed_value);
} catch (...) {
}
}
std::uniform_real_distribution<double> random_dist(0.0, 1.0);
unordered_set<int> selfloop_nodes;
bool has_selfloop = self_loop.cast<bool>();
if (!has_selfloop) {
selfloop_nodes.clear();
}
vector<int> degree(n + 1, 0);
for (int u = 1; u <= n; ++u) {
int deg = 0;
for (int e = GL.head[u]; e != -1; e = GL.edges[e].next) {
deg++;
}
if (selfloop_nodes.count(u)) {
degree[u] = deg + 1;
} else {
degree[u] = deg;
}
}
unordered_map<int, vector<int>> dag_adj;
unordered_map<int, vector<int>> dag_pred;
for (int v = 1; v <= n; ++v) {
if (degree[v] == 0) continue;
int kv = degree[v];
vector<pair<int, int>> neighbors;
for (int e = GL.head[v]; e != -1; e = GL.edges[e].next) {
int nn = GL.edges[e].to;
if (nn == v && selfloop_nodes.count(v)) continue;
int deg_nn = degree[nn];
neighbors.push_back({nn, deg_nn});
}
if (!neighbors.empty()) {
int knnmax = -1;
for (auto& p : neighbors) {
if (p.second > knnmax) knnmax = p.second;
}
if (knnmax >= kv) {
for (auto& p : neighbors) {
if (p.second == knnmax) {
int nn = p.first;
bool already_has = false;
bool has_reverse = false;
for (int existing : dag_adj[v]) {
if (existing == nn) {
already_has = true;
break;
}
}
for (int existing : dag_adj[nn]) {
if (existing == v) {
has_reverse = true;
break;
}
}
if (!already_has && !has_reverse) {
dag_adj[v].push_back(nn);
dag_pred[nn].push_back(v);
}
}
}
}
}
}
vector<int> out_degree_dag(n + 1, 0);
for (int u = 1; u <= n; ++u) {
out_degree_dag[u] = (int)dag_adj[u].size();
}
vector<int> roots;
for (int u = 1; u <= n; ++u) {
if (out_degree_dag[u] == 0 && degree[u] > 0) {
roots.push_back(u);
}
}
if (roots.empty()) {
for (int u = 1; u <= n; ++u) {
if (degree[u] > 0) {
roots.push_back(u);
}
}
}
if (roots.size() > 1) {
bool all_same_degree = true;
int first_degree = -1;
for (int root : roots) {
if (first_degree == -1) {
first_degree = degree[root];
} else if (degree[root] != first_degree) {
all_same_degree = false;
break;
}
}
if (all_same_degree) {
int max_root = -1;
for (int root : roots) {
if (root > max_root) {
max_root = root;
}
}
roots.clear();
roots.push_back(max_root);
}
}
unordered_map<int, int> tree_rootnode;
unordered_map<int, int> tree_parentnode;
unordered_map<int, int> tree_distancetoroot;
for (int i = 1; i <= n; ++i) {
tree_rootnode[i] = -1;
tree_parentnode[i] = -1;
tree_distancetoroot[i] = -1;
}
queue<pair<int, int>> bfs_queue;
for (int root : roots) {
bfs_queue.push({root, 0});
tree_rootnode[root] = root;
tree_parentnode[root] = -1;
tree_distancetoroot[root] = 0;
}
vector<int> visited(n + 1, 0);
for (int root : roots) {
visited[root] = 1;
}
while (!bfs_queue.empty()) {
int parent, dist;
std::tie(parent, dist) = bfs_queue.front();
bfs_queue.pop();
for (int pred : dag_pred[parent]) {
if (tree_distancetoroot[pred] != -1 && tree_distancetoroot[pred] < dist + 1) {
continue;
}
if (tree_distancetoroot[pred] == -1) {
tree_rootnode[pred] = tree_rootnode[parent];
tree_parentnode[pred] = parent;
tree_distancetoroot[pred] = dist + 1;
bfs_queue.push({pred, dist + 1});
} else if (tree_distancetoroot[pred] == dist + 1) {
if (random_dist(rng) < 0.5) {
continue;
}
tree_rootnode[pred] = tree_rootnode[parent];
tree_parentnode[pred] = parent;
}
}
}
unordered_map<int, vector<int>> root_to_node;
for (int node = 1; node <= n; ++node) {
int root = tree_rootnode[node];
if (root != -1) {
root_to_node[root].push_back(node);
}
}
vector<int> valid_roots;
for (auto& kv : root_to_node) {
if ((int)kv.second.size() > 1) {
valid_roots.push_back(kv.first);
}
}
unordered_set<int> root_set(valid_roots.begin(), valid_roots.end());
unordered_map<int, RootDecision> root_decision;
auto BFS_from_s = [&](int s) -> pair<int, int> {
queue<int> search_queue;
unordered_map<int, int> path_dict;
unordered_set<int> seen;
search_queue.push(s);
seen.insert(s);
path_dict[s] = 0;
while (!search_queue.empty()) {
int vertex = search_queue.front();
search_queue.pop();
int current_dist = path_dict[vertex];
vector<pair<int, int>> neighbors;
for (int e = GL.head[vertex]; e != -1; e = GL.edges[e].next) {
int nn = GL.edges[e].to;
int deg_nn = degree[nn];
neighbors.push_back({nn, deg_nn});
}
sort(neighbors.begin(), neighbors.end(),
[](const pair<int, int>& a, const pair<int, int>& b) {
return a.second > b.second;
});
for (auto& p : neighbors) {
int w = p.first;
if (!seen.count(w)) {
path_dict[w] = current_dist + 1;
seen.insert(w);
search_queue.push(w);
}
if (root_set.count(w) && degree[w] > degree[s]) {
return {w, path_dict[w]};
}
}
}
return {s, -1};
};
for (int root : valid_roots) {
auto result = BFS_from_s(root);
root_decision[root] = {result.first, result.second, degree[root]};
}
int max_path = -1;
for (auto& kv : root_decision) {
if (kv.second.path_length > max_path) {
max_path = kv.second.path_length;
}
}
if (max_path < 0) max_path = 2;
for (auto& kv : root_decision) {
if (kv.second.path_length == -1) {
kv.second.path_length = max_path;
}
}
unordered_map<int, RootDecision> node_plot = root_decision;
for (int node = 1; node <= n; ++node) {
if (node_plot.find(node) == node_plot.end() && degree[node] > 0) {
int parent = tree_parentnode[node];
node_plot[node] = {parent, 1, degree[node]};
}
}
vector<int> node_ids;
vector<int> degrees;
vector<int> path_lens;
for (auto& kv : node_plot) {
node_ids.push_back(kv.first);
degrees.push_back(kv.second.degree);
path_lens.push_back(kv.second.path_length);
}
for (size_t i = 0; i < path_lens.size(); ++i) {
if (degrees[i] <= 1) {
path_lens[i] = 1;
}
}
unordered_map<int, int> degree_rank;
vector<pair<int, int>> sorted_by_deg;
for (size_t i = 0; i < node_ids.size(); ++i) {
sorted_by_deg.push_back({degrees[i], node_ids[i]});
}
sort(sorted_by_deg.begin(), sorted_by_deg.end(),
[](const pair<int, int>& a, const pair<int, int>& b) {
return a.first < b.first;
});
int rank = 1;
int last_deg = -1;
for (auto& p : sorted_by_deg) {
if (p.first != last_deg) {
degree_rank[p.second] = rank;
rank++;
last_deg = p.first;
} else {
degree_rank[p.second] = rank - 1;
}
}
int min_rank = 1;
int max_rank = rank - 1;
double rank_range = (max_rank - min_rank);
if (rank_range == 0) rank_range = 1;
vector<double> square_path(node_ids.size());
double max_sq_path = 0;
double min_sq_path = 1e9;
for (size_t i = 0; i < node_ids.size(); ++i) {
square_path[i] = (double)path_lens[i] * (double)path_lens[i];
if (square_path[i] > max_sq_path) max_sq_path = square_path[i];
if (square_path[i] < min_sq_path) min_sq_path = square_path[i];
}
double sq_range = max_sq_path - min_sq_path;
if (sq_range == 0) sq_range = 1;
unordered_map<int, double> multi_dict;
for (size_t i = 0; i < node_ids.size(); ++i) {
int node = node_ids[i];
double norm_deg, norm_sq_path;
if (max_rank == min_rank) {
norm_deg = 1.0 / (double)node_ids.size();
} else {
norm_deg = (double)(degree_rank[node] - min_rank) / rank_range;
}
if (max_sq_path == min_sq_path) {
norm_sq_path = 1.0 / (double)node_ids.size();
} else {
norm_sq_path = (square_path[i] - min_sq_path) / sq_range;
}
multi_dict[node] = norm_deg * norm_sq_path;
}
vector<pair<int, double>> sorted_multi;
for (auto& kv : multi_dict) {
sorted_multi.push_back(kv);
}
sort(sorted_multi.begin(), sorted_multi.end(),
[](const pair<int, double>& a, const pair<int, double>& b) {
if (fabs(a.second - b.second) > 1e-9) return a.second > b.second;
return a.first > b.first;
});
int num_centers = (int)valid_roots.size();
bool auto_choose = auto_choose_centers.cast<bool>();
if (auto_choose && sorted_multi.size() > 0) {
int auto_centernum = choose_center(sorted_multi);
if (!center_num.is_none()) {
int user_center_num = center_num.cast<int>();
num_centers = (auto_centernum < user_center_num) ? auto_centernum : user_center_num;
} else {
num_centers = auto_centernum;
}
} else if (!center_num.is_none()) {
num_centers = center_num.cast<int>();
}
if (num_centers <= 0) {
num_centers = (int)valid_roots.size();
}
vector<int> center_dcd;
int local_cnt = 0;
for (size_t i = 0; i < sorted_multi.size() && local_cnt < num_centers; ++i) {
if (sorted_multi[i].second > 0) {
local_cnt++;
center_dcd.push_back(sorted_multi[i].first);
}
}
if (center_dcd.empty() && !sorted_multi.empty()) {
center_dcd.push_back(sorted_multi[0].first);
}
bool all_same_degree = true;
int first_deg = -1;
for (int i = 1; i <= n && all_same_degree; ++i) {
if (degree[i] > 0) {
if (first_deg == -1) {
first_deg = degree[i];
} else if (degree[i] != first_deg) {
all_same_degree = false;
}
}
}
if (all_same_degree && n > 0) {
center_dcd.clear();
center_dcd.push_back(n);
}
unordered_set<int> center_set(center_dcd.begin(), center_dcd.end());
for (int node : valid_roots) {
int superior = root_decision[node].superior;
tree_parentnode[node] = superior;
tree_rootnode[node] = superior;
}
for (int node = 0; node < n; ++node) {
if (degree[node] > 0 && center_set.count(node)) {
tree_rootnode[node] = node;
}
}
for (int node = 0; node < n; ++node) {
if (degree[node] == 0) continue;
vector<int> recent;
recent.push_back(node);
bool flag = false;
while (center_set.find(tree_rootnode[node]) == center_set.end() && !flag) {
int j = tree_rootnode[node];
if (j == -1 || find(recent.begin(), recent.end(), j) != recent.end()) {
tree_rootnode[node] = -1;
flag = true;
break;
}
recent.push_back(j);
tree_rootnode[node] = tree_rootnode[j];
}
}
unordered_map<int, int> y_partition;
vector<int> y_dcd;
for (int node = 1; node <= n; ++node) {
if (degree[node] == 0) continue;
int root = tree_rootnode[node];
if (root == -1 && !center_dcd.empty()) {
root = center_dcd[0];
tree_rootnode[node] = root;
}
y_partition[node] = root;
if (root == -1) {
y_dcd.push_back(-1);
} else {
y_dcd.push_back(root);
}
}
unordered_map<int, vector<int>> grouped;
for (auto& kv : y_partition) {
int center = kv.second;
if (center != -1) {
grouped[center].push_back(kv.first);
}
}
py::dict result_grouped;
for (auto& kv : grouped) {
py::list members;
for (int node_id : kv.second) {
members.append(id_to_node[py::cast(node_id)]);
}
result_grouped[id_to_node[py::cast(kv.first)]] = members;
}
py::list result_center_dcd;
for (int center : center_dcd) {
result_center_dcd.append(id_to_node[py::cast(center)]);
}
py::list result_y_dcd;
for (int label : y_dcd) {
if (label == -1) {
result_y_dcd.append(py::cast(-1));
} else {
result_y_dcd.append(id_to_node[py::cast(label)]);
}
}
py::dict result_y_partition;
for (auto& kv : y_partition) {
if (kv.second == -1) {
result_y_partition[id_to_node[py::cast(kv.first)]] = py::cast(-1);
} else {
result_y_partition[id_to_node[py::cast(kv.first)]] = id_to_node[py::cast(kv.second)];
}
}
return py::make_tuple(
py::none(),
result_center_dcd,
result_y_dcd,
result_y_partition,
result_grouped,
py::none()
);
}
@@ -0,0 +1,13 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_localsearch(
py::object G,
py::object center_num = py::none(),
py::object auto_choose_centers = py::bool_(false),
py::object maximum_tree = py::bool_(true),
py::object seed = py::none(),
py::object self_loop = py::bool_(false)
);
@@ -0,0 +1,544 @@
#include <vector>
#include <unordered_map>
#include <algorithm>
#include <random>
#include <numeric>
#include <cstdint>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: cpp_louvain_communities will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "../../functions/community/graph_coloring.h"
namespace py = pybind11;
using namespace std;
const double epsilon = 1e-10;
const int MAX_ITERATIONS = 50;
const double CONVERGENCE_THRESHOLD = 1e-7;
struct CommunityState {
int size = 0;
double weight_inside = 0;
double weight_all = 0;
};
double calculate_norm(const Graph_L& G) {
double total_weight = 0.0;
for (int u = 1; u < G.n + 1; ++u) {
for (int i = G.head[u]; i != -1; i = G.edges[i].next) {
total_weight += G.edges[i].w;
}
}
return total_weight;
}
double calculate_actual_modularity(const Graph_L& G, const vector<int>& membership,
double norm, double resolution) {
unordered_map<int, double> comm_weight_inside;
unordered_map<int, double> comm_weight_total;
for (int u = 1; u <= G.n; ++u) {
int comm_u = membership[u - 1];
for (int e_idx = G.head[u]; e_idx != -1; e_idx = G.edges[e_idx].next) {
int v = G.edges[e_idx].to;
double w = G.edges[e_idx].w;
int comm_v = membership[v - 1];
comm_weight_total[comm_u] += w;
if (comm_u == comm_v) {
comm_weight_inside[comm_u] += w;
}
}
}
double modularity = 0.0;
for (const auto& kv : comm_weight_inside) {
int comm = kv.first;
double inside = kv.second;
double total = comm_weight_total[comm];
modularity += (inside / 2.0) - resolution * (total * total) / (4.0 * norm * norm);
}
return modularity;
}
inline double calculate_modularity_gain(double comm_weight_without_u, double norm,
double node_weight_all, double weight_to_comm, double resolution) {
double gain = weight_to_comm - resolution * (comm_weight_without_u * node_weight_all) / norm;
return 2 * gain / norm;
}
double modularity_optimization_serial(const Graph_L& G, vector<int>& membership, double resolution_val, double norm) {
int n = G.n;
double total_modularity_gain = 0.0;
vector<CommunityState> comms(n);
for (int i = 0; i < n; ++i) {
membership[i] = i;
comms[i].size = 1;
comms[i].weight_all = 0.0;
comms[i].weight_inside = 0.0;
for (int j = G.head[i + 1]; j != -1; j = G.edges[j].next) {
int v = G.edges[j].to;
double w = G.edges[j].w;
comms[i].weight_all += w;
if (v == i + 1) comms[i].weight_inside += w;
}
}
vector<double> node_weight(n + 1, 0.0);
vector<double> node_loop(n + 1, 0.0);
for (int i = 0; i < n; ++i) {
node_weight[i + 1] = comms[i].weight_all;
node_loop[i + 1] = comms[i].weight_inside;
}
vector<double> neigh_weight(n, 0.0);
vector<int> timestamp(n, 0);
vector<int> touched;
touched.reserve(512);
int version = 0;
std::random_device rd;
std::mt19937 rng(rd());
vector<int> node_order(n);
for (int i = 0; i < n; ++i) node_order[i] = i + 1;
int num_moved_nodes = 1;
int iteration = 0;
double previous_modularity = -1e100;
do {
num_moved_nodes = 0;
iteration++;
std::shuffle(node_order.begin(), node_order.end(), rng);
for (int idx = 0; idx < n; ++idx) {
int u = node_order[idx];
touched.clear();
version++;
if (version == 0) version = 1;
double weight_all = node_weight[u];
double weight_loop = node_loop[u];
double weight_inside = 0.0;
int my_comm = membership[u - 1];
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
double w = G.edges[e].w;
if (u == v) continue;
int v_comm = membership[v - 1];
if (timestamp[v_comm] != version) {
timestamp[v_comm] = version;
neigh_weight[v_comm] = 0.0;
touched.push_back(v_comm);
}
neigh_weight[v_comm] += w;
if (v_comm == my_comm) {
weight_inside += w;
}
}
if (weight_all < epsilon) continue;
int old_comm = my_comm;
double comm_old_w = comms[old_comm].weight_all - weight_all;
double old_gain = calculate_modularity_gain(comm_old_w, norm, weight_all, weight_inside, resolution_val);
double max_gain = std::max(old_gain, 0.0);
int best_comm = old_comm;
double best_weight_to_comm = weight_inside;
for (int target_comm : touched) {
if (target_comm == old_comm) continue;
double w_to_target = neigh_weight[target_comm];
double comm_target_w = comms[target_comm].weight_all;
double gain = calculate_modularity_gain(comm_target_w, norm, weight_all, w_to_target, resolution_val);
if (gain > max_gain + epsilon) {
max_gain = gain;
best_comm = target_comm;
best_weight_to_comm = w_to_target;
}
}
if (best_comm != old_comm) {
comms[old_comm].size--;
comms[old_comm].weight_all -= weight_all;
comms[old_comm].weight_inside -= (2.0 * weight_inside + weight_loop);
comms[best_comm].size++;
comms[best_comm].weight_all += weight_all;
comms[best_comm].weight_inside += (2.0 * best_weight_to_comm + weight_loop);
membership[u - 1] = best_comm;
num_moved_nodes++;
}
}
if (num_moved_nodes == 0) {
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
if (iteration >= MAX_ITERATIONS) {
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
} while (true);
for (int i = 0; i < n; ++i) {
comms[i].weight_all = node_weight[i + 1];
}
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
double modularity_optimization_parallel_simplified(const Graph_L& G, vector<int>& membership, double resolution_val, double norm) {
int n = G.n;
vector<CommunityState> comms(n);
for (int i = 0; i < n; ++i) {
membership[i] = i;
comms[i].size = 1;
comms[i].weight_all = 0.0;
comms[i].weight_inside = 0.0;
for (int j = G.head[i + 1]; j != -1; j = G.edges[j].next) {
int v = G.edges[j].to;
double w = G.edges[j].w;
comms[i].weight_all += w;
if (v == i + 1) comms[i].weight_inside += w;
}
}
vector<double> node_weight(n + 1, 0.0);
vector<double> node_loop(n + 1, 0.0);
for (int i = 0; i < n; ++i) {
node_weight[i + 1] = comms[i].weight_all;
node_loop[i + 1] = comms[i].weight_inside;
}
std::random_device rd;
std::mt19937 rng(rd());
vector<int> node_order(n);
iota(node_order.begin(), node_order.end(), 0);
int num_moved_nodes = 1;
int iteration = 0;
vector<double> comm_weights(n, 0.0);
for (int i = 0; i < n; ++i) {
comm_weights[i] = comms[i].weight_all;
}
do {
num_moved_nodes = 0;
iteration++;
std::shuffle(node_order.begin(), node_order.end(), rng);
#pragma omp parallel
{
#pragma omp for reduction(+:num_moved_nodes) schedule(guided)
for (int idx = 0; idx < n; ++idx) {
int u = node_order[idx];
double weight_all = node_weight[u];
int my_comm = membership[u - 1];
if (weight_all < epsilon) continue;
unordered_map<int, double> neigh_weight;
neigh_weight.reserve(32);
vector<int> touched;
touched.reserve(32);
double weight_inside = 0.0;
for (int e = G.head[u]; e != -1; e = G.edges[e].next) {
int v = G.edges[e].to;
double w = G.edges[e].w;
if (u == v) continue;
int v_comm = membership[v - 1];
auto it = neigh_weight.find(v_comm);
if (it == neigh_weight.end()) {
touched.push_back(v_comm);
neigh_weight[v_comm] = w;
} else {
it->second += w;
}
if (v_comm == my_comm) {
weight_inside += w;
}
}
int old_comm = my_comm;
double comm_old_w = comm_weights[old_comm] - weight_all;
double old_gain = calculate_modularity_gain(comm_old_w, norm, weight_all, weight_inside, resolution_val);
double max_gain = std::max(old_gain, 0.0);
int best_comm = old_comm;
for (int target_comm : touched) {
if (target_comm == old_comm) continue;
double w_to_target = neigh_weight.at(target_comm);
double comm_target_w = comm_weights[target_comm];
double gain = calculate_modularity_gain(comm_target_w, norm, weight_all, w_to_target, resolution_val);
if (gain > max_gain + epsilon) {
max_gain = gain;
best_comm = target_comm;
}
}
if (best_comm != old_comm) {
#pragma omp atomic
comm_weights[old_comm] -= weight_all;
#pragma omp atomic
comm_weights[best_comm] += weight_all;
membership[u - 1] = best_comm;
num_moved_nodes++;
}
}
}
if (num_moved_nodes == 0) break;
if (iteration >= MAX_ITERATIONS) break;
} while (true);
for (int i = 0; i < n; ++i) {
comms[i].weight_all = comm_weights[i];
}
return calculate_actual_modularity(G, membership, norm, resolution_val);
}
struct SuperEdge {
int u, v;
double w;
};
Graph_L* graph_compress(const Graph_L* G, vector<int>& membership) {
unordered_map<int, int> new_comm_id;
int new_vcount = 0;
for (int i = 0; i < G->n; i++) {
int c = membership[i];
if (new_comm_id.find(c) == new_comm_id.end()) {
new_comm_id[c] = new_vcount++;
}
membership[i] = new_comm_id[c];
}
vector<SuperEdge> edges;
for (int u = 1; u < G->n + 1; ++u) {
int su = membership[u-1] + 1;
for (int e = G->head[u]; e != -1; e = G->edges[e].next) {
int v = G->edges[e].to;
double w = G->edges[e].w;
int sv = membership[v-1] + 1;
edges.push_back({su, sv, w});
}
}
sort(edges.begin(), edges.end(), [](const SuperEdge& a, const SuperEdge& b) {
if (a.u != b.u) return a.u < b.u;
return a.v < b.v;
});
Graph_L* super_g = new Graph_L(new_vcount, G->is_directed, G->is_deg);
if (edges.empty()) return super_g;
int curr_u = edges[0].u;
int curr_v = edges[0].v;
double curr_w = edges[0].w;
for (size_t i = 1; i < edges.size(); ++i) {
if (edges[i].u == curr_u && edges[i].v == curr_v) {
curr_w += edges[i].w;
} else {
super_g->add_weighted_edge(curr_u, curr_v, curr_w);
curr_u = edges[i].u;
curr_v = edges[i].v;
curr_w = edges[i].w;
}
}
super_g->add_weighted_edge(curr_u, curr_v, curr_w);
return super_g;
}
py::object cpp_louvain_communities_serial(py::object G, py::object weight, py::object threshold, py::object resolution) {
Graph& G_ = G.cast<Graph&>();
{
if (G_.is_linkgraph_dirty()) {
G_._get_linkgraph_structure();
}
}
Graph_L original_GL = G_._get_linkgraph_structure();
Graph_L* current_GL = &original_GL;
double threshold_val = threshold.cast<double>();
double resolution_val = resolution.cast<double>();
int vcount = current_GL->n;
if (vcount == 0) return py::list();
double norm = calculate_norm(*current_GL);
vector<int> membership(vcount);
iota(membership.begin(), membership.end(), 0);
vector<Graph_L*> allocated_graphs;
int iteration = 0;
double previous_modularity = -1e100;
if (norm > 0.0) {
while (true) {
iteration++;
int curr_vcount = current_GL->n;
vector<int> curr_membership(curr_vcount);
double current_modularity;
current_modularity = modularity_optimization_serial(*current_GL, curr_membership, resolution_val, norm);
double modularity_gain = current_modularity - previous_modularity;
if (modularity_gain <= threshold_val || iteration > 100) {
break;
}
Graph_L* next_GL = graph_compress(current_GL, curr_membership);
allocated_graphs.push_back(next_GL);
current_GL = next_GL;
for (int i = 0; i < vcount; i++) {
membership[i] = curr_membership[membership[i]];
}
previous_modularity = current_modularity;
}
}
unordered_map<int, vector<node_t>> cpp_groups;
for (int i = 0; i < vcount; ++i) {
int comm_id = membership[i];
node_t node_id = i + 1;
cpp_groups[comm_id].push_back(node_id);
}
py::dict id_to_node = G_.id_to_node;
py::list final_result;
for (auto& kv : cpp_groups) {
py::set py_comm;
for (node_t node_id : kv.second) {
py::object node_obj = id_to_node[py::cast(node_id)];
py_comm.add(node_obj);
}
final_result.append(py_comm);
}
for (auto g : allocated_graphs) {
delete g;
}
return final_result;
}
py::object cpp_louvain_communities(py::object G, py::object weight, py::object threshold, py::object resolution) {
Graph& G_ = G.cast<Graph&>();
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
Graph_L original_GL = G_._get_linkgraph_structure();
Graph_L* current_GL = &original_GL;
double threshold_val = threshold.cast<double>();
double resolution_val = resolution.cast<double>();
int vcount = current_GL->n;
if (vcount == 0) return py::list();
double norm = calculate_norm(*current_GL);
vector<int> membership(vcount);
iota(membership.begin(), membership.end(), 0);
vector<Graph_L*> allocated_graphs;
int iteration = 0;
double previous_modularity = 0.0;
if (norm > 0.0) {
while (true) {
iteration++;
int curr_vcount = current_GL->n;
vector<int> curr_membership(curr_vcount);
double current_modularity;
current_modularity = modularity_optimization_parallel_simplified(*current_GL, curr_membership, resolution_val, norm);
double modularity_gain = current_modularity - previous_modularity;
previous_modularity = current_modularity;
if (modularity_gain <= threshold_val || iteration > 100) {
break;
}
Graph_L* next_GL = graph_compress(current_GL, curr_membership);
allocated_graphs.push_back(next_GL);
current_GL = next_GL;
for (int i = 0; i < vcount; i++) {
membership[i] = curr_membership[membership[i]];
}
}
}
unordered_map<int, vector<node_t>> cpp_groups;
for (int i = 0; i < vcount; ++i) {
int comm_id = membership[i];
node_t node_id = i + 1;
cpp_groups[comm_id].push_back(node_id);
}
py::dict id_to_node = G_.id_to_node;
py::list final_result;
for (auto& kv : cpp_groups) {
py::set py_comm;
for (node_t node_id : kv.second) {
py::object node_obj = id_to_node[py::cast(node_id)];
py_comm.add(node_obj);
}
final_result.append(py_comm);
}
for (auto g : allocated_graphs) {
delete g;
}
return final_result;
}
@@ -0,0 +1,20 @@
#pragma once
#include <pybind11/pybind11.h>
namespace py = pybind11;
py::object cpp_louvain_communities(
py::object G,
py::object weight = py::str("weight"),
py::object threshold = py::float_(0.0),
py::object resolution = py::float_(1.0)
);
py::object cpp_louvain_communities_serial(
py::object G,
py::object weight = py::str("weight"),
py::object threshold = py::float_(0.0),
py::object resolution = py::float_(1.0)
);
@@ -0,0 +1,213 @@
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <string>
#include <cstddef>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: modularity utility functions will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
using namespace std;
void addVectorsInPlace(std::vector<double>& v1, std::vector<double>& v2) {
if (v1.size() != v2.size()) {
throw std::invalid_argument("Vectors must have the same size for element-wise addition.");
}
const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(v1.size());
#pragma omp parallel for
for (std::ptrdiff_t i = 0; i < n; ++i) {
double sum = v1[i] + v2[i];
v1[i] = sum;
v2[i] = sum;
}
}
double dotProduct(const std::vector<double>& v1, const std::vector<double>& v2) {
if (v1.size() != v2.size()) {
throw std::invalid_argument("Vectors must have the same size for dot product.");
}
double result = 0.0;
const std::ptrdiff_t n = static_cast<std::ptrdiff_t>(v1.size());
#pragma omp parallel for reduction(+:result)
for (std::ptrdiff_t i = 0; i < n; ++i) {
result += v1[i] * v2[i];
}
return result;
}
void calculate_degrees_and_edges_adj_parallel(
const adj_dict_factory& adj,
const std::vector<int>& membership,
bool directed,
int num_communities,
double& e,
double& m,
std::vector<double>& k_out,
std::vector<double>& k_in
)
{
const int N = membership.size();
#pragma omp parallel
{
double local_e = 0.0;
double local_m = 0.0;
std::vector<double> local_k_out(num_communities, 0.0);
std::vector<double> local_k_in(num_communities, 0.0);
double directed_factor = directed ? 1.0 : 2.0;
#pragma omp for
for (int i = 0; i < N; i++) {
node_t u = i + 1;
int c1 = membership[i];
auto adj_it = adj.find(u);
if (adj_it == adj.end()) continue;
auto& u_neighbors = adj_it->second;
for (auto& v_pair : u_neighbors) {
node_t v = v_pair.first;
if (!directed && u > v) continue;
int c2 = membership[v - 1];
double w = 1.0;
if (!v_pair.second.empty()) {
auto it = v_pair.second.begin();
w = it->second;
}
if (c1 == c2) {
local_e += directed_factor * w;
}
local_k_out[c1] += w;
local_k_in[c2] += w;
local_m += w;
}
}
#pragma omp critical
{
e += local_e;
m += local_m;
for (int i = 0; i < num_communities; i++) {
k_out[i] += local_k_out[i];
k_in[i] += local_k_in[i];
}
}
}
}
// The input `communities` may be either:
// (a) a membership list: a flat sequence of ints, membership[i] = community id of node (i+1); or
// (b) a community list: a sequence of iterables of node ids
py::object cpp_modularity(py::object G, py::object communities, py::object weight=py::str("weight")) {
Graph& G_ = G.cast<Graph&>();
bool directed = G.attr("is_directed")().cast<bool>();
adj_dict_factory& adj = G_.adj;
const int N = G_.node.size();
{
bool is_empty_seq = false;
try {
py::sequence seq = communities.cast<py::sequence>();
is_empty_seq = (seq.size() == 0);
} catch (const py::cast_error&) {
is_empty_seq = false;
}
if (is_empty_seq) {
py::module warnings = py::module::import("warnings");
warnings.attr("warn")(
"cpp_modularity: received an empty community list; returning Q = 0.0."
);
return py::float_(0.0);
}
}
std::vector<int> membership_vec;
bool is_membership = false;
py::sequence outer_seq;
try {
outer_seq = communities.cast<py::sequence>();
if (outer_seq.size() > 0) {
try {
outer_seq[0].cast<int>();
is_membership = true;
} catch (const py::cast_error&) {
is_membership = false;
}
}
} catch (const py::cast_error&) {
is_membership = true;
}
if (is_membership) {
// Already a membership vector: membership[i] is the community id of the (i+1)-th node.
membership_vec = communities.cast<std::vector<int>>();
} else {
membership_vec = std::vector<int>(N, -1);
int comm_id = 0;
for (auto community_handle : py::iter(communities)) {
for (auto node_handle : py::iter(community_handle)) {
py::object node = py::reinterpret_borrow<py::object>(node_handle.ptr());
if (G_.node_to_id.contains(node)) {
int node_id = G_.node_to_id[node].cast<node_t>() - 1;
if (node_id >= 0 && node_id < N) {
membership_vec[node_id] = comm_id;
}
}
}
comm_id++;
}
}
int num_communities = membership_vec.size();
double e = 0.0;
double m = 0.0;
std::vector<double> k_out(num_communities, 0.0);
std::vector<double> k_in(num_communities, 0.0);
calculate_degrees_and_edges_adj_parallel(adj, membership_vec, directed, num_communities, e, m, k_out, k_in);
if (!directed) addVectorsInPlace(k_out, k_in);
// Handle empty graph / zero total edge weight: m == 0 makes
// `norm = 1.0 / (directed_factor * m)` divide by zero and produces
// inf / nan. Define Q = 0.0 in this degenerate case (no edges -> no
// community structure to measure), with a Python warning.
if (m == 0.0) {
py::module warnings = py::module::import("warnings");
warnings.attr("warn")(
"cpp_modularity: graph has no edges (m == 0); returning Q = 0.0."
);
return py::float_(0.0);
}
double directed_factor = directed ? 1.0 : 2.0;
double norm = 1.0 / (directed_factor * m);
e *= norm;
double sum_products = dotProduct(k_out, k_in);
sum_products *= norm * norm;
double Q = e - sum_products;
return py::float_(Q);
}
@@ -0,0 +1,11 @@
#pragma once
#include <vector>
#include <unordered_set>
#include <string>
#include "../../classes/graph.h"
using namespace std;
py::object cpp_modularity(py::object G, py::object communities, py::object weight = py::str("weight"));
+553
View File
@@ -0,0 +1,553 @@
#include <vector>
#include <unordered_set>
#include <unordered_map>
#include <random>
#include <algorithm>
#include <queue>
#include <bitset>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/numpy.h>
#ifdef _OPENMP
#include <omp.h>
#else
#warning "OpenMP is not available: motif counting functions will fall back to single-threaded execution."
#endif
#include "../../classes/graph.h"
#include "../../classes/linkgraph.h"
#include "motif.h"
namespace py = pybind11;
using namespace std;
const int MAX_NODES = 65536;
struct MotifResult {
vector<int> nodes;
};
class MotifEnumerator {
private:
Graph_L GL;
int k;
int v_start;
vector<int>& id_to_node;
mt19937 rng;
uniform_real_distribution<double> dist;
vector<double> cut_prob;
vector<int> temp_nexcl;
vector<MotifResult> results;
bitset<MAX_NODES> nvp_bitset;
public:
vector<int> node_list;
vector<int> neighbor_offsets;
vector<int> neighbor_data;
int n_edges;
void build_neighbor_structure() {
neighbor_offsets.resize(GL.n + 2, 0);
for (int i = 1; i <= GL.n; ++i) {
int count = 0;
for (int e = GL.head[i]; e != -1; e = GL.edges[e].next) {
count++;
}
neighbor_offsets[i + 1] = neighbor_offsets[i] + count;
}
n_edges = neighbor_offsets[GL.n + 1];
neighbor_data.resize(n_edges);
for (int i = 1; i <= GL.n; ++i) {
int idx = neighbor_offsets[i];
for (int e = GL.head[i]; e != -1; e = GL.edges[e].next) {
neighbor_data[idx++] = GL.edges[e].to;
}
}
node_list.reserve(GL.n);
for (int i = 1; i <= GL.n; ++i) {
if (GL.head[i] != -1) {
node_list.push_back(i);
}
}
}
int get_neighbor_count(int v) const {
return neighbor_offsets[v + 1] - neighbor_offsets[v];
}
const int* get_neighbors_ptr(int v) const {
return neighbor_data.data() + neighbor_offsets[v];
}
void exclusive_neighborhood(int v, const vector<int>& vp, vector<int>& result) {
result.clear();
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
nvp_bitset.reset();
for (int node : vp) {
nvp_bitset.set(node);
const int* node_nbr_ptr = get_neighbors_ptr(node);
int node_nbr_count = get_neighbor_count(node);
for (int i = 0; i < node_nbr_count; ++i) {
nvp_bitset.set(node_nbr_ptr[i]);
}
}
for (int i = 0; i < nbr_count; ++i) {
int neighbor = nbr_ptr[i];
if (!nvp_bitset.test(neighbor)) {
result.push_back(neighbor);
}
}
}
void extend_subgraph_local(const vector<int>& Vsubgraph, const vector<int>& Vextension, int local_v_start,
vector<int>& local_temp_nexcl, bitset<MAX_NODES>& local_nvp_bitset,
vector<MotifResult>& local_results) {
if ((int)Vsubgraph.size() == k) {
local_results.push_back({Vsubgraph});
return;
}
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
if (!cut_prob.empty() && (int)cut_prob.size() > (int)Vsubgraph.size() && dist(rng) > cut_prob[Vsubgraph.size()]) {
continue;
}
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood_local(w, Vsubgraph, local_temp_nexcl, local_nvp_bitset);
vector<int> next_ext;
next_ext.reserve(original_size + local_temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > local_v_start) {
next_ext.push_back(u);
}
}
}
for (int u : local_temp_nexcl) {
if (u > local_v_start) {
next_ext.push_back(u);
}
}
extend_subgraph_local(new_subgraph, next_ext, local_v_start, local_temp_nexcl, local_nvp_bitset, local_results);
}
}
int extend_subgraph_count_local(const vector<int>& Vsubgraph, const vector<int>& Vextension, int local_v_start, vector<int>& local_temp_nexcl, bitset<MAX_NODES>& local_nvp_bitset) {
if ((int)Vsubgraph.size() == k) {
return 1;
}
int count = 0;
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood_local(w, Vsubgraph, local_temp_nexcl, local_nvp_bitset);
vector<int> next_ext;
next_ext.reserve(original_size + local_temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > local_v_start) {
next_ext.push_back(u);
}
}
}
for (int u : local_temp_nexcl) {
if (u > local_v_start) {
next_ext.push_back(u);
}
}
count += extend_subgraph_count_local(new_subgraph, next_ext, local_v_start, local_temp_nexcl, local_nvp_bitset);
}
return count;
}
int extend_subgraph_count(const vector<int>& Vsubgraph, const vector<int>& Vextension) {
return extend_subgraph_count_local(Vsubgraph, Vextension, v_start, temp_nexcl, nvp_bitset);
}
void exclusive_neighborhood_local(int v, const vector<int>& vp, vector<int>& result, bitset<MAX_NODES>& local_nvp_bitset) {
result.clear();
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
local_nvp_bitset.reset();
for (int node : vp) {
local_nvp_bitset.set(node);
const int* node_nbr_ptr = get_neighbors_ptr(node);
int node_nbr_count = get_neighbor_count(node);
for (int i = 0; i < node_nbr_count; ++i) {
local_nvp_bitset.set(node_nbr_ptr[i]);
}
}
for (int i = 0; i < nbr_count; ++i) {
int neighbor = nbr_ptr[i];
if (!local_nvp_bitset.test(neighbor)) {
result.push_back(neighbor);
}
}
}
public:
MotifEnumerator(Graph_L gl, int k, vector<int>& id_to_node)
: GL(gl), k(k), v_start(0), id_to_node(id_to_node), rng(42), dist(0.0, 1.0) {
build_neighbor_structure();
temp_nexcl.reserve(gl.n);
}
MotifEnumerator(Graph_L gl, int k, vector<int>& id_to_node, const vector<double>& cut_prob_vec, int seed)
: GL(gl), k(k), v_start(0), id_to_node(id_to_node), cut_prob(cut_prob_vec), rng(seed), dist(0.0, 1.0) {
build_neighbor_structure();
temp_nexcl.reserve(gl.n);
}
py::array_t<int> enumerate() {
results.clear();
if (k == 2) {
for (int v : node_list) {
if (!cut_prob.empty() && dist(rng) > cut_prob[0]) {
continue;
}
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
results.push_back({vector<int>{v, u}});
}
}
}
return convert_results_to_numpy();
}
vector<vector<MotifResult>> thread_results(8);
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
#pragma omp parallel
{
int thread_id = 0;
#ifdef _OPENMP
thread_id = omp_get_thread_num();
#endif
vector<MotifResult>& local_results = thread_results[thread_id];
local_results.reserve(10000);
#pragma omp for schedule(dynamic)
for (int i = 0; i < (int)node_list.size(); ++i) {
int v = node_list[i];
if (!cut_prob.empty() && dist(rng) > cut_prob[0]) {
continue;
}
int local_v_start = v;
vector<int> Vsubgraph;
Vsubgraph.reserve(k);
Vsubgraph.push_back(v);
vector<int> Vextension;
Vextension.reserve(GL.n);
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
Vextension.push_back(u);
}
}
vector<int> local_temp_nexcl;
local_temp_nexcl.reserve(GL.n);
bitset<MAX_NODES> local_nvp_bitset;
extend_subgraph_local(Vsubgraph, Vextension, local_v_start, local_temp_nexcl, local_nvp_bitset, local_results);
}
}
for (const auto& tr : thread_results) {
results.insert(results.end(), tr.begin(), tr.end());
}
return convert_results_to_numpy();
}
void extend_subgraph(const vector<int>& Vsubgraph, const vector<int>& Vextension) {
if ((int)Vsubgraph.size() == k) {
results.push_back({Vsubgraph});
return;
}
int original_size = Vextension.size();
for (int i = original_size - 1; i >= 0; --i) {
int w = Vextension[i];
if (w <= v_start) {
continue;
}
vector<int> new_subgraph;
new_subgraph.reserve(Vsubgraph.size() + 1);
new_subgraph.assign(Vsubgraph.begin(), Vsubgraph.end());
new_subgraph.push_back(w);
exclusive_neighborhood(w, Vsubgraph, temp_nexcl);
vector<int> next_ext;
next_ext.reserve(original_size + temp_nexcl.size());
for (int j = 0; j < original_size; ++j) {
if (j != i) {
int u = Vextension[j];
if (u > v_start) {
next_ext.push_back(u);
}
}
}
for (int u : temp_nexcl) {
if (u > v_start) {
next_ext.push_back(u);
}
}
extend_subgraph(new_subgraph, next_ext);
}
}
int count_enumerate() {
int count = 0;
if (k == 2) {
for (int v : node_list) {
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
count++;
}
}
}
return count;
}
#ifdef _OPENMP
omp_set_num_threads(8);
#endif
#pragma omp parallel reduction(+:count)
{
#pragma omp for schedule(dynamic)
for (int i = 0; i < (int)node_list.size(); ++i) {
int v = node_list[i];
int local_v_start = v;
vector<int> Vsubgraph;
Vsubgraph.reserve(k);
Vsubgraph.push_back(v);
vector<int> Vextension;
Vextension.reserve(GL.n);
const int* nbr_ptr = get_neighbors_ptr(v);
int nbr_count = get_neighbor_count(v);
for (int j = 0; j < nbr_count; ++j) {
int u = nbr_ptr[j];
if (u > v) {
Vextension.push_back(u);
}
}
vector<int> local_temp_nexcl;
local_temp_nexcl.reserve(GL.n);
bitset<MAX_NODES> local_nvp_bitset;
count += extend_subgraph_count_local(Vsubgraph, Vextension, local_v_start, local_temp_nexcl, local_nvp_bitset);
}
}
return count;
}
py::list convert_results_to_python() {
py::list py_results;
for (const auto& result : results) {
py::set py_set;
for (int node_id : result.nodes) {
if (node_id > 0 && node_id < (int)id_to_node.size()) {
py_set.add(py::cast(id_to_node[node_id]));
}
}
py_results.append(py_set);
}
return py_results;
}
py::array_t<int> convert_results_to_numpy() {
size_t num_results = results.size();
size_t k_size = k;
int* data = new int[num_results * k_size];
size_t idx = 0;
for (const auto& result : results) {
for (size_t i = 0; i < k_size; ++i) {
if (i < result.nodes.size() && result.nodes[i] > 0 && result.nodes[i] < (int)id_to_node.size()) {
data[idx++] = id_to_node[result.nodes[i]];
} else {
data[idx++] = -1;
}
}
}
// Create a capsule to manage the memory
py::capsule capsule(data, [](void* ptr) {
delete[] static_cast<int*>(ptr);
});
// Create numpy array with the capsule
py::array_t<int> result_array = py::array_t<int>(
{num_results, k_size}, // shape
{k_size * sizeof(int), sizeof(int)}, // strides
data,
capsule
);
return result_array;
}
const vector<MotifResult>& get_results() const {
return results;
}
size_t get_k() const {
return k;
}
};
py::list cpp_enumerate_subgraph(py::object G, int k) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
MotifEnumerator enumerator(GL, k, id_to_node_vec);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
py::int_ cpp_count_enumerate_subgraph(py::object G, int k) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
MotifEnumerator enumerator(GL, k, id_to_node_vec);
int count = enumerator.count_enumerate();
return count;
}
py::list cpp_random_enumerate_subgraph(py::object G, int k, py::object cut_prob) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
vector<double> cut_prob_vec;
if (py::isinstance<py::list>(cut_prob)) {
py::list cut_prob_list = cut_prob.cast<py::list>();
cut_prob_vec.reserve(cut_prob_list.size());
for (size_t i = 0; i < cut_prob_list.size(); ++i) {
cut_prob_vec.push_back(cut_prob_list[i].cast<double>());
}
} else {
throw std::runtime_error("cut_prob must be a list");
}
if (cut_prob_vec.size() != k) {
throw py::value_error("length of cut_prob invalid, should equal to k");
}
MotifEnumerator enumerator(GL, k, id_to_node_vec, cut_prob_vec, 42);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
py::list cpp_random_enumerate_subgraph_with_seed(py::object G, int k, py::object cut_prob, int seed) {
Graph& G_ = G.cast<Graph&>();
Graph_L GL = G_.linkgraph_structure;
py::dict id_to_node_py = G_.id_to_node;
vector<int> id_to_node_vec;
id_to_node_vec.reserve(id_to_node_py.size() + 1);
id_to_node_vec.push_back(0);
for (int i = 1; i <= (int)id_to_node_py.size(); ++i) {
py::object node_obj = id_to_node_py[py::cast(i)];
id_to_node_vec.push_back(node_obj.cast<int>());
}
vector<double> cut_prob_vec;
if (py::isinstance<py::list>(cut_prob)) {
py::list cut_prob_list = cut_prob.cast<py::list>();
cut_prob_vec.reserve(cut_prob_list.size());
for (size_t i = 0; i < cut_prob_list.size(); ++i) {
cut_prob_vec.push_back(cut_prob_list[i].cast<double>());
}
} else {
throw std::runtime_error("cut_prob must be a list");
}
if (cut_prob_vec.size() != k) {
throw py::value_error("length of cut_prob invalid, should equal to k");
}
MotifEnumerator enumerator(GL, k, id_to_node_vec, cut_prob_vec, seed);
enumerator.enumerate();
return enumerator.convert_results_to_python();
}
+11
View File
@@ -0,0 +1,11 @@
#pragma once
#include <pybind11/pybind11.h>
#include <pybind11/numpy.h>
namespace py = pybind11;
py::list cpp_enumerate_subgraph(py::object G, int k);
py::int_ cpp_count_enumerate_subgraph(py::object G, int k);
py::list cpp_random_enumerate_subgraph(py::object G, int k, py::object cut_prob);
@@ -0,0 +1,179 @@
#include "subgraph.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
static node_t _add_one_node_for_subgraph(Graph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
static void _add_one_edge_for_subgraph(Graph& self, node_t u, node_t v, py::object edge_attr) {
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
self.adj[v][u] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
self.adj[v][u].insert(std::make_pair(weight_key, value));
}
}
static py::object _nodes_subgraph_cpp_graph(py::object self, std::vector<node_t>& node_ids) {
Graph& self_ = self.cast<Graph&>();
py::object G = self.attr("__class__")();
Graph& G_ = G.cast<Graph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
// 修复:维护原图内部索引到新图内部索引的映射
std::unordered_map<node_t, node_t> old_id_to_new_id;
// 第一遍:添加所有节点并建立映射
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object node_attr = nodes[node];
node_t new_id = _add_one_node_for_subgraph(G_, node, node_attr);
old_id_to_new_id[node_id] = new_id;
}
// 第二遍:添加边(使用映射后的索引)
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
// 获取当前节点的新索引
node_t new_u = old_id_to_new_id[node_id];
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (self_.node_to_id.contains(v)) {
node_t v_id = self_.node_to_id[v].cast<node_t>();
bool v_in_subgraph = std::find(node_ids.begin(), node_ids.end(), v_id) != node_ids.end();
if (v_in_subgraph) {
// 使用映射后的索引
node_t new_v = old_id_to_new_id[v_id];
_add_one_edge_for_subgraph(G_, new_u, new_v, edge_attr);
}
}
}
}
return G;
}
static node_t DiGraph_add_one_node_for_subgraph(DiGraph& self, py::object one_node_for_adding, py::object node_attr = py::dict()) {
node_t id;
if (self.node_to_id.contains(one_node_for_adding)) {
id = self.node_to_id[one_node_for_adding].cast<node_t>();
} else {
id = ++(self.id);
self.id_to_node[py::cast(id)] = one_node_for_adding;
self.node_to_id[one_node_for_adding] = id;
}
py::list items = py::list(node_attr.attr("items")());
self.node[id] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.node[id].insert(std::make_pair(weight_key, value));
}
return id;
}
static void DiGraph_add_one_edge_for_subgraph(DiGraph& self, node_t u, node_t v, py::object edge_attr) {
py::list items = py::list(edge_attr.attr("items")());
self.adj[u][v] = node_attr_dict_factory();
for (int i = 0; i < len(items); i++) {
py::tuple kv = items[i].cast<py::tuple>();
py::object pkey = kv[0];
std::string weight_key = weight_to_string(pkey);
weight_t value = kv[1].cast<weight_t>();
self.adj[u][v].insert(std::make_pair(weight_key, value));
}
}
static py::object _nodes_subgraph_cpp_digraph(py::object self, std::vector<node_t>& node_ids) {
DiGraph& self_ = self.cast<DiGraph&>();
py::object G = self.attr("__class__")();
DiGraph& G_ = G.cast<DiGraph&>();
G_.graph.attr("update")(self_.graph);
py::object nodes = self.attr("nodes");
py::object adj = self.attr("adj");
// 修复:维护原图内部索引到新图内部索引的映射
std::unordered_map<node_t, node_t> old_id_to_new_id;
// 第一遍:添加所有节点并建立映射
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object node_attr = nodes[node];
node_t new_id = DiGraph_add_one_node_for_subgraph(G_, node, node_attr);
old_id_to_new_id[node_id] = new_id;
}
// 第二遍:添加边(使用映射后的索引)
for (node_t node_id : node_ids) {
py::object node = self_.id_to_node[py::cast(node_id)];
py::object out_edges = adj[node];
py::list edge_items = py::list(out_edges.attr("items")());
// 获取当前节点的新索引
node_t new_u = old_id_to_new_id[node_id];
for (int j = 0; j < py::len(edge_items); j++) {
py::tuple item = edge_items[j].cast<py::tuple>();
py::object v = item[0];
py::object edge_attr = item[1];
if (self_.node_to_id.contains(v)) {
node_t v_id = self_.node_to_id[v].cast<node_t>();
bool v_in_subgraph = std::find(node_ids.begin(), node_ids.end(), v_id) != node_ids.end();
if (v_in_subgraph) {
// 使用映射后的索引
node_t new_v = old_id_to_new_id[v_id];
DiGraph_add_one_edge_for_subgraph(G_, new_u, new_v, edge_attr);
}
}
}
}
return G;
}
py::object nodes_subgraph_cpp(py::object self, std::vector<node_t>& node_ids) {
bool is_directed = self.attr("is_directed")().cast<bool>();
if (is_directed) {
return _nodes_subgraph_cpp_digraph(self, node_ids);
} else {
return _nodes_subgraph_cpp_graph(self, node_ids);
}
}
@@ -0,0 +1,10 @@
#pragma once
#include <vector>
#include <memory>
#include "../../common/common.h"
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../classes/csr_graph.h"
py::object nodes_subgraph_cpp(py::object self, std::vector<node_t>& node_ids);
@@ -0,0 +1 @@
# TEST package init
@@ -0,0 +1,139 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLPA(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edges_from([
(0, 1, {"weight": 3}),
(1, 2, {"weight": 2}),
(2, 0, {"weight": 4}),
(3, 4, {"weight": 1}),
])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_lpa_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_simple.cpp())
self.assertIsInstance(result, dict)
all_nodes = set()
for community in result.values():
all_nodes.update(community)
self.assertEqual(all_nodes, set(self.G_simple.nodes))
def test_lpa_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_weighted.cpp())
self.assertIsInstance(result, dict)
all_nodes = set(self.G_weighted.nodes)
result_nodes = set()
for community in result.values():
result_nodes.update(community)
self.assertEqual(all_nodes, result_nodes)
def test_lpa_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_disconnected.cpp())
self.assertIsInstance(result, dict)
all_nodes = set(self.G_disconnected.nodes)
result_nodes = set()
for community in result.values():
result_nodes.update(community)
self.assertEqual(all_nodes, result_nodes)
def test_lpa_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_single.cpp())
self.assertIsInstance(result, dict)
self.assertEqual(len(result), 1)
for community in result.values():
self.assertIn(42, community)
def test_lpa_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result = cpeg.cpp_LPA(self.G_empty.cpp())
self.assertIsInstance(result, dict)
self.assertEqual(result, {})
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
result_cpp = cpeg.cpp_LPA(self.G_simple.cpp())
result_py = eg.functions.community.LPA(self.G_simple)
self.assertEqual(len(result_cpp), len(result_py))
cpp_nodes = set()
for community in result_cpp.values():
cpp_nodes.update(community)
py_nodes = set()
for community in result_py.values():
py_nodes.update(community)
self.assertEqual(cpp_nodes, py_nodes)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,254 @@
import unittest
import easygraph as eg
def _get_cpp_module():
"""获取cpp_easygraph模块"""
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
"""将C++ DiGraph转换为C++ Graph无向图"""
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
def _csr_dict_to_graph(csr_dict):
import cpp_easygraph
if not csr_dict or 'nodes' not in csr_dict:
return cpp_easygraph.Graph()
G = cpp_easygraph.Graph()
nodes = csr_dict['nodes']
for node in nodes:
G.add_node(node)
V = csr_dict['V']
E = csr_dict['E']
W = csr_dict.get('W', [1.0] * len(E))
for u in range(len(V) - 1):
start = V[u]
end = V[u + 1]
for i in range(start, end):
v_idx = E[i]
weight = W[i] if i < len(W) else 1.0
G.add_edge(nodes[u], nodes[v_idx], weight=weight)
return G
class TestEgoGraph(unittest.TestCase):
def setUp(self):
self.simple_graph = eg.Graph()
self.simple_graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
self.directed_graph = eg.DiGraph()
self.directed_graph.add_edges_from([(0, 1), (1, 2), (2, 3)])
self.weighted_graph = eg.Graph()
self.weighted_graph.add_edges_from(
[(0, 1, {"weight": 1}), (1, 2, {"weight": 2}), (2, 3, {"weight": 3})]
)
self.disconnected_graph = eg.Graph()
self.disconnected_graph.add_edges_from([(0, 1), (2, 3)])
self.single_node_graph = eg.Graph()
self.single_node_graph.add_node(42)
def _get_cpp_module(self):
return _get_cpp_module()
def test_ego_graph_simple_radius_1(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(2, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(3, ego.nodes)
def test_ego_graph_simple_radius_2(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=2)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(2, ego.nodes)
self.assertIn(3, ego.nodes)
self.assertIn(4, ego.nodes)
def test_ego_graph_directed(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.directed_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 1, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(1, ego.nodes)
self.assertIn(2, ego.nodes)
def test_ego_graph_weighted_with_distance(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.weighted_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego = cpeg.cpp_ego_graph(G_cpp, 0, radius=2, distance="weight")
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.disconnected_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 0, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.single_node_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 42, radius=1)
self.assertIsInstance(ego, type(G_cpp))
self.assertIn(42, ego.nodes)
def test_ego_graph_center_false(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego = cpeg.cpp_ego_graph(G_cpp, 2, radius=1, center=False)
self.assertIsInstance(ego, type(G_cpp))
self.assertNotIn(2, ego.nodes)
self.assertIn(1, ego.nodes)
self.assertIn(3, ego.nodes)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
ego_py = eg.functions.community.ego_graph(self.simple_graph, 2, radius=1)
ego_cpp = cpeg.cpp_ego_graph(G_cpp, 2, radius=1)
self.assertEqual(set(ego_py.nodes), set(ego_cpp.nodes))
class TestEgoGraphCSR(unittest.TestCase):
def setUp(self):
self.simple_graph = eg.Graph()
self.simple_graph.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 4)])
self.disconnected_graph = eg.Graph()
self.disconnected_graph.add_edges_from([(0, 1), (2, 3)])
self.single_node_graph = eg.Graph()
self.single_node_graph.add_node(42)
def _get_cpp_module(self):
return _get_cpp_module()
def test_ego_graph_csr_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 2, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(2, ego.nodes)
def test_ego_graph_csr_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.disconnected_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 0, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(0, ego.nodes)
self.assertIn(1, ego.nodes)
def test_ego_graph_csr_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.single_node_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 42, radius=1)
ego = _csr_dict_to_graph(ego_csr_dict)
self.assertTrue(hasattr(ego, 'nodes'))
self.assertIn(42, ego.nodes)
def test_csr_vs_original_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.simple_graph.cpp()
G_cpp.generate_linkgraph('weight')
ego_original = cpeg.cpp_ego_graph(G_cpp, 2, radius=1, undirected=False)
ego_csr_dict = cpeg.cpp_ego_graph_csr(G_cpp, 2, radius=1)
ego_csr = _csr_dict_to_graph(ego_csr_dict)
self.assertIn(2, ego_original.nodes)
self.assertIn(2, ego_csr.nodes)
self.assertTrue(set(ego_csr.nodes).issubset(set(ego_original.nodes) | {4}))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,143 @@
import random
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestEnumerateSubgraph(unittest.TestCase):
def setUp(self):
self.G_triangle = eg.Graph()
self.G_triangle.add_edges_from([(1, 2), (2, 3), (3, 1)])
self.G_complex = eg.Graph()
self.G_complex.add_edges_from([
(1, 3), (2, 3), (3, 4), (4, 5), (3, 5)
])
self.G_empty = eg.Graph()
self.G_small = eg.Graph()
self.G_small.add_edges_from([(1, 2)])
def _get_cpp_module(self):
return _get_cpp_module()
def test_enumerate_subgraph_k3(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 3)
self.assertIsInstance(motifs, list)
for m in motifs:
self.assertEqual(len(m), 3)
def test_enumerate_subgraph_k4(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 4)
for m in motifs:
self.assertEqual(len(m), 4)
def test_enumerate_subgraph_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_empty.cpp(), 3)
self.assertEqual(motifs, [])
def test_enumerate_subgraph_k_larger_than_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs = cpeg.cpp_enumerate_subgraph(self.G_small.cpp(), 3)
self.assertEqual(motifs, [])
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
motifs_py = eg.enumerate_subgraph(self.G_complex, 3)
motifs_py = [frozenset(m) for m in motifs_py]
motifs_cpp = cpeg.cpp_enumerate_subgraph(self.G_complex.cpp(), 3)
motifs_cpp = [frozenset(m) for m in motifs_cpp]
self.assertEqual(set(motifs_py), set(motifs_cpp))
class TestRandomEnumerateSubgraph(unittest.TestCase):
def setUp(self):
self.G_complex = eg.Graph()
self.G_complex.add_edges_from([
(1, 3), (2, 3), (3, 4), (4, 5), (3, 5)
])
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_random_enumerate_valid_cut_prob(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [1.0, 1.0, 1.0]
motifs = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertIsInstance(motifs, list)
def test_random_enumerate_zero_cut_prob(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [0.0, 0.0, 0.0]
motifs = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertEqual(motifs, [])
def test_random_enumerate_different_results(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
cut_prob = [1.0, 1.0, 1.0]
motifs1 = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
motifs2 = cpeg.cpp_random_enumerate_subgraph(self.G_complex.cpp(), 3, cut_prob)
self.assertIsInstance(motifs1, list)
self.assertIsInstance(motifs2, list)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,117 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestGreedyModularity(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(2, 0, weight=2)
self.G_weighted.add_edge(3, 4, weight=1)
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_greedy_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_greedy_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_greedy_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_disconnected.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_disconnected.nodes))
def test_greedy_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_single.cpp())
self.assertEqual(len(communities), 1)
self.assertIn(42, communities[0])
def test_greedy_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_greedy_modularity_communities(self.G_empty.cpp())
self.assertEqual(communities, [])
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_cpp = cpeg.cpp_greedy_modularity_communities(self.G_simple.cpp())
communities_py = eg.functions.community.greedy_modularity_communities(self.G_simple)
self.assertEqual(len(communities_cpp), len(communities_py))
flat_cpp = {node for comm in communities_cpp for node in comm}
flat_py = {node for comm in communities_py for node in comm}
self.assertEqual(flat_cpp, flat_py)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,188 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLocalsearch(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 2), (0, 3), (0, 4), (0, 5), (1, 2), (1, 3), (1, 4), (1, 5)])
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_localsearch_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
all_nodes = set(self.G_simple.nodes)
result_nodes = set(y_partition.keys())
self.assertEqual(all_nodes, result_nodes)
def test_localsearch_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_disconnected.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
all_nodes = set(self.G_disconnected.nodes)
result_nodes = set(y_partition.keys())
self.assertEqual(all_nodes, result_nodes)
def test_localsearch_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_single.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
self.assertIn(42, y_partition)
def test_localsearch_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_empty.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_maximum_tree_true(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, maximum_tree=True, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_maximum_tree_false(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, maximum_tree=False, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_auto_choose_centers(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, auto_choose_centers=True, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_center_num(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result = cpeg.cpp_localsearch(G_cpp, center_num=2, seed=163)
self.assertIsInstance(result, tuple)
self.assertEqual(len(result), 6)
y_partition = result[3]
self.assertIsInstance(y_partition, dict)
def test_localsearch_with_seed_reproducibility(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
G_cpp = self.G_simple.cpp()
G_cpp.generate_linkgraph('weight')
result1 = cpeg.cpp_localsearch(G_cpp, seed=42)
result2 = cpeg.cpp_localsearch(G_cpp, seed=42)
self.assertIsInstance(result1, tuple)
self.assertIsInstance(result2, tuple)
y_partition1 = result1[3]
y_partition2 = result2[3]
self.assertEqual(y_partition1.keys(), y_partition2.keys())
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,178 @@
import unittest
import easygraph as eg
def _get_cpp_module():
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
class TestLouvainCommunities(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(3, 4, weight=2)
self.G_disconnected = eg.Graph()
self.G_disconnected.add_edges_from([(0, 1), (2, 3), (4, 5)])
self.G_single = eg.Graph()
self.G_single.add_node(42)
self.G_empty = eg.Graph()
def _get_cpp_module(self):
return _get_cpp_module()
def test_louvain_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_louvain_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_louvain_disconnected(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_disconnected.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_disconnected.nodes))
def test_louvain_single_node(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_single.cpp())
self.assertEqual(len(communities), 1)
self.assertIn(42, communities[0])
def test_louvain_empty_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities(self.G_empty.cpp())
self.assertEqual(communities, [])
def test_louvain_resolution_parameter(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities1 = cpeg.cpp_louvain_communities(self.G_simple.cpp(), resolution=1.0)
communities2 = cpeg.cpp_louvain_communities(self.G_simple.cpp(), resolution=0.5)
self.assertIsInstance(communities1, list)
self.assertIsInstance(communities2, list)
flat1 = {node for comm in communities1 for node in comm}
flat2 = {node for comm in communities2 for node in comm}
self.assertEqual(flat1, flat2)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_cpp = cpeg.cpp_louvain_communities(self.G_simple.cpp())
communities_py = eg.functions.community.louvain_communities(self.G_simple)
self.assertEqual(len(communities_cpp), len(communities_py))
flat_cpp = {node for comm in communities_cpp for node in comm}
flat_py = {node for comm in communities_py for node in comm}
self.assertEqual(flat_cpp, flat_py)
class TestLouvainCommunitiesSerial(unittest.TestCase):
def setUp(self):
self.G_simple = eg.Graph()
self.G_simple.add_edges_from([(0, 1), (1, 2), (3, 4)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=5)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(3, 4, weight=2)
def _get_cpp_module(self):
return _get_cpp_module()
def test_louvain_serial_simple(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities_serial(self.G_simple.cpp())
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_simple.nodes))
def test_louvain_serial_weighted(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = cpeg.cpp_louvain_communities_serial(self.G_weighted.cpp(), weight="weight")
self.assertIsInstance(communities, list)
flat = {node for comm in communities for node in comm}
self.assertSetEqual(flat, set(self.G_weighted.nodes))
def test_parallel_vs_serial_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_parallel = cpeg.cpp_louvain_communities(self.G_simple.cpp())
communities_serial = cpeg.cpp_louvain_communities_serial(self.G_simple.cpp())
flat_parallel = {frozenset(comm) for comm in communities_parallel}
flat_serial = {frozenset(comm) for comm in communities_serial}
self.assertEqual(flat_parallel, flat_serial)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,161 @@
import unittest
import easygraph as eg
def _get_cpp_module():
"""获取cpp_easygraph模块"""
try:
import cpp_easygraph
return cpp_easygraph
except ImportError:
return None
def _to_undirected_cpp(G_cpp_digraph):
"""将C++ DiGraph转换为C++ Graph无向图"""
import cpp_easygraph
G_cpp = cpp_easygraph.Graph()
G_cpp.graph.update(G_cpp_digraph.graph)
for node, node_attr in G_cpp_digraph.nodes.items():
G_cpp.add_node(node, **node_attr)
seen_edges = set()
for u, v, edge_data in G_cpp_digraph.edges:
edge = (min(u, v), max(u, v))
if edge not in seen_edges:
seen_edges.add(edge)
G_cpp.add_edge(u, v, **edge_data)
return G_cpp
def _communities_to_membership(G, communities):
if not isinstance(communities, list):
communities = list(communities)
N = G.number_of_nodes()
membership = [-1] * N
node_index = G.node_index
for comm_id, community in enumerate(communities):
for node in community:
if node in node_index:
node_id = node_index[node]
membership[node_id] = comm_id
return membership
class TestModularity(unittest.TestCase):
def setUp(self):
self.G = eg.Graph()
self.G.add_edges_from([(0, 1), (1, 2), (2, 3), (3, 0)])
self.DG = eg.DiGraph()
self.DG.add_edges_from([(0, 1), (1, 2), (2, 0)])
self.G_weighted = eg.Graph()
self.G_weighted.add_edge(0, 1, weight=2)
self.G_weighted.add_edge(1, 2, weight=3)
self.G_weighted.add_edge(2, 0, weight=1)
self.G_selfloop = eg.Graph()
self.G_selfloop.add_edges_from([(0, 0), (1, 1), (0, 1)])
self.G_empty = eg.Graph()
self.config = type('Config', (), {
'is_directed': False
})()
def _get_cpp_module(self):
return _get_cpp_module()
def test_undirected_modularity(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}, {2, 3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
self.assertGreaterEqual(q, -1.0)
self.assertLessEqual(q, 1.0)
def test_directed_modularity(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1, 2}]
membership = _communities_to_membership(self.DG, communities)
q = cpeg.cpp_modularity(self.DG.cpp(), membership)
self.assertIsInstance(q, float)
def test_weighted_graph(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}, {2}]
membership = _communities_to_membership(self.G_weighted, communities)
q = cpeg.cpp_modularity(self.G_weighted.cpp(), membership, weight="weight")
self.assertIsInstance(q, float)
def test_self_loops(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1}]
membership = _communities_to_membership(self.G_selfloop, communities)
q = cpeg.cpp_modularity(self.G_selfloop.cpp(), membership)
self.assertIsInstance(q, float)
def test_single_community(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0, 1, 2, 3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
def test_each_node_its_own_community(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities = [{0}, {1}, {2}, {3}]
membership = _communities_to_membership(self.G, communities)
q = cpeg.cpp_modularity(self.G.cpp(), membership)
self.assertIsInstance(q, float)
def test_empty_community_list(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
q = cpeg.cpp_modularity(self.G.cpp(), [])
self.assertEqual(q, 0.0)
def test_python_cpp_consistency(self):
cpeg = self._get_cpp_module()
if cpeg is None:
self.skipTest("cpp_easygraph module not available")
communities_py = [{0, 1}, {2, 3}]
q_py = eg.functions.community.modularity(self.G, communities_py)
q_cpp = cpeg.cpp_modularity(self.G.cpp(), communities_py)
self.assertAlmostEqual(q_py, q_cpp, places=5)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,55 @@
import unittest
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from cpp_modularity_test import TestModularity
from cpp_greedy_modularity_test import TestGreedyModularity
from cpp_enumerate_subgraph_test import (
TestEnumerateSubgraph,
TestRandomEnumerateSubgraph
)
from cpp_louvain_test import TestLouvainCommunities, TestLouvainCommunitiesSerial
from cpp_LPA_test import TestLPA
from cpp_ego_graph_test import TestEgoGraph, TestEgoGraphCSR
from cpp_localsearch_test import TestLocalsearch
def create_test_suite():
suite = unittest.TestSuite()
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestModularity))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestGreedyModularity))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEnumerateSubgraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestRandomEnumerateSubgraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLouvainCommunities))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLouvainCommunitiesSerial))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLPA))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEgoGraph))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestEgoGraphCSR))
suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestLocalsearch))
return suite
if __name__ == "__main__":
print("=" * 70)
print("Easy-Graph C++ Module Test Suite")
print("=" * 70)
print()
suite = create_test_suite()
runner = unittest.TextTestRunner(verbosity=2)
result = runner.run(suite)
print()
print("=" * 70)
print("Test Summary")
print("=" * 70)
print(f"Tests run: {result.testsRun}")
print(f"Successes: {result.testsRun - len(result.failures) - len(result.errors)}")
print(f"Failures: {len(result.failures)}")
print(f"Errors: {len(result.errors)}")
print(f"Skipped: {len(result.skipped)}")
print("=" * 70)
sys.exit(0 if result.wasSuccessful() else 1)
@@ -0,0 +1,5 @@
#pragma once
#include "biconnected.h"
#include "connected.h"
#include "strongly_connected.h"
@@ -0,0 +1,109 @@
#include "biconnected.h"
#include "../../classes/graph.h"
#include "../../common/utils.h"
node_t index_edge(std::vector<std::pair<node_t, node_t>>& edges, const std::pair<node_t, node_t>& target) {
for (int i = edges.size() - 1;i >= 0;i--) {
if ((edges[i].first == target.first) && (edges[i].second == target.second)) {
return i;
}
}
return -1;
}
py::object _biconnected_dfs_record_edges(py::object G, py::object need_components) {
py::list ret = py::list();
std::unordered_set<node_t> visited;
Graph& G_ = G.cast<Graph&>();
node_dict_factory &nodes_list = G_.node;
for (node_dict_factory::iterator iter = nodes_list.begin();iter != nodes_list.end();iter++) {
node_t start_id = iter->first;
if (visited.find(start_id) != visited.end()) {
continue;
}
std::unordered_map<node_t, int> discovery;
std::unordered_map<node_t, int> low;
node_t root_children = 0;
discovery.emplace(start_id, 0);
low.emplace(start_id, 0);
visited.emplace(start_id);
std::vector<std::pair<node_t, node_t>> edge_stack;
std::vector<stack_node> stack;
adj_attr_dict_factory& start_adj = G_.adj[start_id];
NeighborIterator neighbors_iter = NeighborIterator(start_adj);
stack_node initial_stack_node(start_id, start_id, neighbors_iter);
stack.emplace_back(initial_stack_node);
while (!stack.empty()) {
stack_node& node_info = stack.back();
node_t node_grandparent_id = node_info.grandparent;
node_t node_parent_id = node_info.parent;
try {
node_t node_child_id = node_info.neighbors_iter.next();
if (node_grandparent_id == node_child_id) {
continue;
}
if (visited.find(node_child_id) != visited.end()) {
if (discovery[node_child_id] <= discovery[node_parent_id]) {
low[node_parent_id] = std::min(low[node_parent_id], discovery[node_child_id]);
if (need_components.cast<bool>()) {
edge_stack.emplace_back(std::make_pair(node_parent_id, node_child_id));
}
}
}
else {
low[node_child_id] = discovery[node_child_id] = discovery.size();
visited.emplace(node_child_id);
adj_attr_dict_factory& node_child_adj = G_.adj[node_child_id];
NeighborIterator child_neighbors_iter = NeighborIterator(G_.adj[node_child_id]);
stack.emplace_back(node_parent_id, node_child_id, child_neighbors_iter);
if (need_components.cast<bool>()) {
edge_stack.emplace_back(std::make_pair(node_parent_id, node_child_id));
}
}
}
catch (int) {
stack.pop_back();
if (stack.size() > 1) {
if (low[node_parent_id] >= discovery[node_grandparent_id]) {
if (need_components.cast<bool>()) {
py::list tmp_ret = py::list();
std::pair<node_t, node_t> iter_edge = std::make_pair(-1, -1);
while ((iter_edge.first != node_grandparent_id || iter_edge.second != node_parent_id)) {
iter_edge = edge_stack.back();
edge_stack.pop_back();
tmp_ret.append(py::make_tuple(G_.id_to_node[py::cast(iter_edge.first)], G_.id_to_node[py::cast(iter_edge.second)]));
}
ret.append(tmp_ret);
}
else {
ret.append(G_.id_to_node[py::cast(node_grandparent_id)]);
}
}
low[node_grandparent_id] = std::min(low[node_grandparent_id], low[node_parent_id]);
}
else if (stack.size() > 0) {
root_children += 1;
if (need_components.cast<bool>()) {
std::pair<node_t, node_t> target = std::make_pair(node_grandparent_id, node_parent_id);
node_t ind = index_edge(edge_stack, target);
if (ind != -1) {
py::list tmp_ret = py::list();
for (node_t z = ind;z < edge_stack.size();z++) {
tmp_ret.append(py::make_tuple(G_.id_to_node[py::cast(edge_stack[z].first)], G_.id_to_node[py::cast(edge_stack[z].second)]));
}
ret.append(tmp_ret);
}
}
}
}
}
if (!need_components.cast<bool>()) {
if (root_children > 1) {
ret.append(G_.id_to_node(start_id));
}
}
}
return ret;
}
@@ -0,0 +1,34 @@
#pragma once
#include "../../common/common.h"
class NeighborIterator {
public:
NeighborIterator() {
}
NeighborIterator(adj_attr_dict_factory& neighbor_map) {
now = neighbor_map.begin();;
end = neighbor_map.end();
}
node_t next() {
if (now == end) {
throw -1;
}
else {
return (now++)->first;
}
}
private:
adj_attr_dict_factory::iterator now, end;
};
typedef struct stackNode {
node_t grandparent, parent;
NeighborIterator neighbors_iter;
stackNode(node_t grandparent, node_t parent, NeighborIterator neighbors_iter) {
this->grandparent = grandparent;
this->parent = parent;
this->neighbors_iter = neighbors_iter;
}
}stack_node;
py::object _biconnected_dfs_record_edges(py::object G, py::object need_components);
@@ -0,0 +1,205 @@
#include "connected.h"
#include <pybind11/stl.h>
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
#include "time.h"
#define gmin(x, y) x = x < y? x: y
py::object plain_bfs(py::object G, py::object source) {
Graph& G_ = G.cast<Graph&>();
node_t source_id = G_.node_to_id.attr("get")(source).cast<node_t>();
adj_dict_factory& G_adj = G_.adj;
std::unordered_set<node_t> seen;
std::unordered_set<node_t> nextlevel;
nextlevel.emplace(source_id);
py::list res = py::list();
while (nextlevel.size()) {
std::unordered_set<node_t> thislevel = nextlevel;
nextlevel = std::unordered_set<node_t>();
for (std::unordered_set<node_t>::iterator i = thislevel.begin(); i != thislevel.end(); i++) {
node_t v_id = *i;
if (seen.find(v_id) == seen.end()) {
seen.emplace(v_id);
adj_attr_dict_factory& v_adj = G_adj[v_id];
for (adj_attr_dict_factory::iterator j = v_adj.begin(); j != v_adj.end(); j++) {
node_t neighbor_id = j->first;
nextlevel.emplace(neighbor_id);
}
}
}
}
for (std::unordered_set<node_t>::iterator i = seen.begin(); i != seen.end(); i++) {
node_t res_id = *(i);
res.append(G_.id_to_node.attr("get")(res_id));
}
return res;
}
py::object connected_component_undirected(py::object G) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == true) {
printf("connected_component_undirected is designed for undirected graphs.\n");
return py::dict();
}
int N = G_.node.size();
int M = G.attr("number_of_edges")().cast<int>();
std::vector<Edge_weighted> E_res(N+5);
for(int i=0; i < N+5; i++) {
E_res[i].toward = 0;
E_res[i].next = 0;
}
int edge_number_res = 0;
std::vector<int> parent(N+5, 0);
std::vector<int> rank_node(N+5, 0);
std::vector<int> color(N+5, 0);
std::vector<int> head_res(N+5, 0);
std::vector<bool> has_edge(N+5, false);
py::list nodes_list = py::list(G.attr("nodes"));
for (int i = 0;i < py::len(nodes_list);i++) {
node_t i_id = (G_.node_to_id[nodes_list[i]]).cast<node_t>();
parent[i_id] = i_id;
}
for (graph_edge& edge : G_._get_edges()) {
node_t u = edge.u, v = edge.v;
has_edge[u] = true; has_edge[v] = true;
_union_node(u, v, parent, rank_node);
}
int Tot = 0;
for(int i = 1; i < N + 1; ++i) {
if (!has_edge[i])
continue;
int fx = _getfa(i, parent);
if(fx == i){
color[++Tot] = fx;
}
}
for (int i = 1; i < N + 1; ++i) {
int fx = _getfa(i, parent);
_add_edge_res(fx, i, E_res, head_res, &edge_number_res);
}
py::dict ret = py::dict();
for (int i = 1; i <= Tot; ++i) {
py::list tmp = py::list();
for(int p = head_res[color[i]]; p; p = E_res[p].next){
tmp.append(py::cast(E_res[p].toward));
}
ret[py::cast(i)] = tmp;
}
return ret;
}
inline void _union_node(const int &u, const int &v, std::vector<int> &parent, std::vector<int> &rank_node) {
int x = _getfa(u, parent), y = _getfa(v, parent); //先找到两个根节点
if (rank_node[x] <= rank_node[y])
parent[x] = y;
else
parent[y] = x;
if (rank_node[x] == rank_node[y] && x != y)
rank_node[y]++; //如果深度相同且根节点不同,则新的根节点的深度+1
}
int _getfa(const int & x, std::vector<int> &parent) {
int r,k,t;
r=x;
while(parent[r]!=r)
r=parent[r];
k=r;
r=x;
while(parent[r]!=k) {
t=parent[r];
parent[r]=k;
r=t;
}
return k;
}
py::object connected_component_directed(py::object G) {
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == false) {
printf("connected_component_directed is designed for directed graphs.\n");
return py::list();
}
DiGraph& G_ = G.cast<DiGraph&>();
int N = G_.node.size();
Graph_L G_l;
if(G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1){
G_l = graph_to_linkgraph(G_, is_directed, "", true, false);
G_.linkgraph_dirty = false;
}
else{
G_l = G_.linkgraph_structure;
}
std::vector<LinkEdge>& E = G_l.edges;
std::vector<int> outDegree = G_l.degree;
std::vector<int> head = G_l.head;
int Time = 0, cnt = 0, Tot = 0, edge_number_res = 0;
std::vector<int> dfn(N+5, 0);
std::vector<int> low(N+5, 0);
std::vector<int> st(N+5, 0);
std::vector<int> color(N+5, 0);
std::vector<int> head_res(N+5, 0);
std::vector<bool> in_stack(N+5, false);
std::vector<bool> has_edge(N+5, false);
std::vector<Edge_weighted> E_res(N+5);
for(int i=0; i < N+5; i++) {
E_res[i].toward = 0;
E_res[i].next = 0;
}
for (graph_edge& edge : G_._get_edges()) {
node_t u = edge.u, v = edge.v;
has_edge[u] = true; has_edge[v] = true;
}
for (int i = 1; i < N + 1; ++i)
if (!dfn[i] && has_edge[i])
_tarjan(i, &Time, &cnt, &Tot, E, head, dfn, low, st, color, in_stack, E_res, head_res, &edge_number_res);
py::list ret = py::list();
for (int i = 1; i <= Tot; ++i) {
py::set tmp;
for(int p = head_res[i]; p; p = E_res[p].next)
tmp.add(G_.id_to_node.attr("get")(E_res[p].toward));
ret.append(tmp);
}
return ret;
}
void _add_edge_res(const int &u, const int &v, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res) {
E_res[++(*edge_number_res)].next = head_res[u];
E_res[*edge_number_res].toward = v;
head_res[u] = *edge_number_res;
}
void _tarjan(const int &u, int *Time, int *cnt, int *Tot, std::vector<LinkEdge>& E, std::vector<int>& head, std::vector<int> &dfn, std::vector<int> &low, std::vector<int> &st, std::vector<int> &color, std::vector<bool> &in_stack, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res) {
dfn[u] = low[u] = ++(*Time); st[++(*cnt)] = u; in_stack[u] = true;
for(int p = head[u]; p != -1; p = E[p].next){
int v = E[p].to;
if (!dfn[v]) _tarjan(v, Time, cnt, Tot, E, head, dfn, low, st, color, in_stack, E_res, head_res, edge_number_res), gmin(low[u], low[v]);
else if (in_stack[v]) gmin(low[u], dfn[v]);
}
if (dfn[u] == low[u]) {
for (++(*Tot); st[*cnt] != u; --(*cnt)) {
_add_edge_res(*Tot, st[*cnt], E_res, head_res, edge_number_res);
in_stack[st[*cnt]] = false, color[st[*cnt]] = *Tot;
}
_add_edge_res(*Tot, st[*cnt], E_res, head_res, edge_number_res);
in_stack[u] = false; color[u] = *Tot; --(*cnt);
}
}
@@ -0,0 +1,16 @@
#pragma once
#include "../../common/common.h"
#include "../../classes/linkgraph.h"
struct Edge_weighted{
int toward, next;
};
py::object plain_bfs(py::object G, py::object source);
py::object connected_component_undirected(py::object G);
inline void _union_node(const int &u, const int &v, std::vector<int>& parent, std::vector<int> &rank_node);
int _getfa(const int & x, std::vector<int> &parent);
py::object connected_component_directed(py::object G);
void _tarjan(const int &u, int *Time, int *cnt, int *Tot, std::vector<LinkEdge>& E, std::vector<int>& head, std::vector<int> &dfn, std::vector<int> &low, std::vector<int> &st, std::vector<int> &color, std::vector<bool> &in_stack, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res);
void _add_edge_res(const int &u, const int &v, std::vector<Edge_weighted> &E_res, std::vector<int> &head_res, int *edge_number_res);
@@ -0,0 +1,90 @@
#include "strongly_connected.h"
#include "connected.h"
#include "../../classes/directed_graph.h"
#define MAX_NODES_NUM4RECURSION_METHOD 100000
py::object strongly_connected_components(py::object G) {
bool is_directed = G.attr("is_directed")().cast<bool>();
if (is_directed == false) {
printf("connected_component_directed is designed for directed graphs.\n");
return py::list();
}
int N = G.attr("number_of_nodes")().cast<int>();
if(N < MAX_NODES_NUM4RECURSION_METHOD){
return connected_component_directed(G);
}
return strongly_connected_components_iteration_impl(G);
}
py::object strongly_connected_components_iteration_impl(py::object G) {
py::list res = py::list();
DiGraph& G_ = py::cast<DiGraph&>(G);
adj_dict_factory& adj = G_.adj;
std::unordered_map<node_t, node_t> preorder;
std::unordered_map<node_t, node_t> lowlink;
std::set<node_t> scc_found;
std::vector<node_t> scc_queue;
int i = 0;
node_dict_factory& nodes_list = G_.node;
for (node_dict_factory::iterator source = nodes_list.begin(); source != nodes_list.end(); source++) {
node_t source_id = source->first;
if (scc_found.find(source_id) == scc_found.end()) {
std::vector<node_t> que;
que.emplace_back(source_id);
while (!que.empty()) {
node_t v_id = que.back();
if (preorder.find(v_id) == preorder.end()) {
i += 1;
preorder[v_id] = i;
}
bool done = true;
adj_attr_dict_factory& v_neighbors = adj[v_id];
for (adj_attr_dict_factory::iterator w = v_neighbors.begin(); w != v_neighbors.end(); w++) {
node_t w_id = w->first;
if (preorder.find(w_id) == preorder.end()) {
que.emplace_back(w_id);
done = false;
break;
}
}
if (done) {
lowlink[v_id] = preorder[v_id];
for (adj_attr_dict_factory::iterator w = v_neighbors.begin(); w != v_neighbors.end(); w++) {
node_t w_id = w->first;
if (scc_found.find(w_id) == scc_found.end()) {
if (preorder[w_id] > preorder[v_id]) {
lowlink[v_id] = std::min(lowlink[v_id], lowlink[w_id]);
} else {
lowlink[v_id] = std::min(lowlink[v_id], preorder[w_id]);
}
}
}
que.pop_back();
if (lowlink[v_id] == preorder[v_id]) {
std::unordered_set<node_t> scc;
scc.emplace(v_id);
while (!scc_queue.empty() && (preorder[scc_queue.back()] > preorder[v_id])) {
node_t k = scc_queue.back();
scc_queue.pop_back();
scc.emplace(k);
}
py::set tmp_res;
for (std::unordered_set<node_t>::iterator z = scc.begin(); z != scc.end(); z++) {
scc_found.emplace(*z);
tmp_res.add(G_.id_to_node.attr("get")(*z));
}
res.append(tmp_res);
} else {
scc_queue.emplace_back(v_id);
}
}
}
}
}
return res;
}
@@ -0,0 +1,6 @@
#pragma once
#include "../../common/common.h"
py::object strongly_connected_components(py::object G);
py::object strongly_connected_components_iteration_impl(py::object G);
+3
View File
@@ -0,0 +1,3 @@
#pragma once
#include "k_cores.h"
+101
View File
@@ -0,0 +1,101 @@
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "k_cores.h"
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include <time.h>
py::object invoke_cpp_core_decomposition(py::object G) {
// reference:https://arxiv.org/pdf/cs/0310049.pdf
Graph& G_ = G.cast<Graph&>();
int N = G_.node.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
Graph_L G_l;
if(G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1){
G_l = graph_to_linkgraph(G_, is_directed, "", true, false);
G_.linkgraph_dirty = false;
}
else{
G_l = G_.linkgraph_structure;
}
std::vector<LinkEdge> edges = G_l.edges;
int edges_num = edges.size();
std::vector<int> deg = G_l.degree;
std::vector<int> head = G_l.head;
int max_deg = G_l.max_deg;
std::vector<int> core(N+1, 0);
std::vector<int> bin(max_deg+1, 0);
std::vector<int> pos(N+1, 0);
std::vector<int> vert(N+1, 0);
for(int i = 1; i <= N; ++i)
++bin[deg[i]];
int start = 1;
for(int i = 0; i <= max_deg; ++i){
int num = bin[i];
bin[i] = start;
start += num;
}
for(int i = 1; i <= N; ++i){
pos[i] = bin[deg[i]];
vert[pos[i]] = i;
++bin[deg[i]];
}
for(int i = max_deg; i >= 1; --i){
bin[i] = bin[i - 1];
}
bin[0] = 1;
for(int i = 1; i <= N; ++i){
int v = vert[i];
core[v] = deg[v];
for(int p = head[v]; p!=-1; p = edges[p].next){
int u = edges[p].to;
if (deg[u] > deg[v]) {
int w = vert[bin[deg[u]]];
if(u != w){
std::swap(vert[pos[u]],vert[pos[w]]);
std::swap(pos[u],pos[w]);
}
++bin[deg[u]];
--deg[u];
}
}
}
py::array::ShapeContainer ret_shape{(int)core.size()};
py::array_t<int> ret(ret_shape, core.data());
return ret;
}
#ifdef EASYGRAPH_ENABLE_GPU
py::object invoke_gpu_core_decomposition(py::object G) {
Graph& G_ = G.cast<Graph&>();
auto csr_graph = G_.gen_CSR();
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<int> KC;
int gpu_r = gpu_easygraph::k_core(V, E, KC);
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)KC.size()};
py::array_t<int> ret(ret_shape, KC.data());
return ret;
}
#endif
py::object core_decomposition(py::object G) {
#ifdef EASYGRAPH_ENABLE_GPU
return invoke_gpu_core_decomposition(G);
#else
return invoke_cpp_core_decomposition(G);
#endif
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object core_decomposition(py::object G);
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object _pagerank(py::object G, double alpha, int max_iterator, double threshold, py::object weight);
@@ -0,0 +1,119 @@
#include <vector>
#include <cmath>
#include <string>
#include <algorithm>
#include <pybind11/pybind11.h>
#ifdef _OPENMP
#include <omp.h>
#endif
#include "pagerank.h"
#include "../../classes/directed_graph.h"
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
namespace py = pybind11;
py::object _pagerank(py::object G, double alpha, int max_iterator, double threshold, py::object weight) {
bool is_directed = G.attr("is_directed")().cast<bool>();
std::string weight_key = weight_to_string(weight);
bool has_weight_key = !weight.is_none() && !weight_key.empty();
Graph_L* G_l_ptr = nullptr;
int N = 0;
if (is_directed) {
DiGraph& G_ = G.cast<DiGraph&>();
N = G_.node.size();
if (G_.linkgraph_dirty) {
G_.linkgraph_structure = graph_to_linkgraph(G_, true, weight_key, true, false);
G_.linkgraph_dirty = false;
}
G_l_ptr = &G_.linkgraph_structure;
} else {
Graph& G_ = G.cast<Graph&>();
N = G_.node.size();
if (G_.linkgraph_dirty) {
G_.linkgraph_structure = graph_to_linkgraph(G_, false, weight_key, true, false);
G_.linkgraph_dirty = false;
}
G_l_ptr = &G_.linkgraph_structure;
}
const std::vector<LinkEdge>& E = G_l_ptr->edges;
const std::vector<int>& outDegree = G_l_ptr->degree;
const std::vector<int>& head = G_l_ptr->head;
bool actually_weighted = false;
std::vector<double> outWeightSum(N + 1, 0.0);
if (has_weight_key) {
#pragma omp parallel for reduction(|:actually_weighted)
for (int i = 1; i <= N; ++i) {
double sum_w = 0.0;
for (int p = head[i]; p != -1; p = E[p].next) {
sum_w += E[p].w;
if (!actually_weighted && std::abs(E[p].w - 1.0) > 1e-9) {
actually_weighted = true;
}
}
outWeightSum[i] = sum_w;
}
}
bool use_weighted_logic = has_weight_key && actually_weighted;
std::vector<double> oldPR(N + 1, 1.0 / N);
std::vector<double> newPR(N + 1, 0.0);
int cnt = 0;
while (cnt < max_iterator) {
double dangling_sum = 0.0;
#pragma omp parallel for reduction(+:dangling_sum)
for (int i = 1; i <= N; ++i) {
bool is_dangling = use_weighted_logic ? (outWeightSum[i] < 1e-15) : (outDegree[i] == 0);
if (is_dangling) dangling_sum += oldPR[i];
}
if (!use_weighted_logic) {
#pragma omp parallel for schedule(dynamic, 128)
for (int i = 1; i <= N; ++i) {
if (outDegree[i] == 0) continue;
double out_val = (oldPR[i] / outDegree[i]) * alpha;
for (int p = head[i]; p != -1; p = E[p].next) {
#pragma omp atomic
newPR[E[p].to] += out_val;
}
}
} else {
#pragma omp parallel for schedule(dynamic, 128)
for (int i = 1; i <= N; ++i) {
if (outWeightSum[i] < 1e-15) continue;
double out_val = (oldPR[i] / outWeightSum[i]) * alpha;
for (int p = head[i]; p != -1; p = E[p].next) {
#pragma omp atomic
newPR[E[p].to] += out_val * E[p].w;
}
}
}
double diff_sum = 0.0;
double jump_val = (1.0 - alpha) / N + (dangling_sum / N) * alpha;
#pragma omp parallel for reduction(+:diff_sum)
for (int i = 1; i <= N; ++i) {
double final_pr = newPR[i] + jump_val;
diff_sum += std::fabs(final_pr - oldPR[i]);
oldPR[i] = final_pr;
newPR[i] = 0.0;
}
if (diff_sum < threshold * N) break;
cnt++;
}
py::list res_lst;
for (int i = 1; i <= N; ++i) res_lst.append(oldPR[i]);
return res_lst;
}
@@ -0,0 +1,5 @@
#pragma once
#include "../../common/common.h"
py::object _pagerank(py::object G, double alpha, int max_iterator, double threshold, py::object weight);
+4
View File
@@ -0,0 +1,4 @@
#pragma once
#include "path.h"
#include "mst.h"
@@ -0,0 +1,148 @@
#include "path.h"
#ifdef _OPENMP
#include <omp.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include <vector>
#include <queue>
#include <limits>
#include <string>
double _bfs_sum(const Graph_L& G_l, int source) {
int N = G_l.n;
std::vector<int> dis(N + 1, -1);
std::queue<int> q;
dis[source] = 0;
q.push(source);
double sum = 0.0;
int visited_count = 0;
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!q.empty()) {
int u = q.front();
q.pop();
sum += dis[u];
visited_count++;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
if (dis[v] == -1) {
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return (visited_count == N) ? sum : -1.0;
}
double _dijkstra_sum(const Graph_L& G_l, int source) {
int N = G_l.n;
const double INF = std::numeric_limits<double>::infinity();
std::vector<double> dis(N + 1, INF);
std::priority_queue<std::pair<double, int>,
std::vector<std::pair<double, int>>,
std::greater<std::pair<double, int>>> pq;
dis[source] = 0.0;
pq.push({0.0, source});
double sum = 0.0;
int visited_count = 0;
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!pq.empty()) {
auto top_node = pq.top();
double d = top_node.first;
int u = top_node.second;
pq.pop();
if (d > dis[u]) continue;
sum += d;
visited_count++;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
double w = static_cast<double>(E[p].w);
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
pq.push({dis[v], v});
}
}
}
return (visited_count == N) ? sum : -1.0;
}
py::object average_shortest_path_length(py::object G, py::object weight, py::object method) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::string weight_key = "";
if (!weight.is_none()) {
weight_key = weight.cast<std::string>();
}
std::string method_str;
if (method.is_none()) {
method_str = weight.is_none() ? "single_source_bfs" : "dijkstra";
} else {
method_str = method.cast<std::string>();
}
if(G_.linkgraph_dirty){
G_.linkgraph_structure = graph_to_linkgraph(G_, is_directed, weight_key, true, false);
G_.linkgraph_dirty = false;
}
const Graph_L& G_l = G_.linkgraph_structure;
int N = G_l.n;
if (N <= 1) return py::float_(0.0);
double total_sum = 0.0;
bool is_connected = true;
{
py::gil_scoped_release release;
#pragma omp parallel for reduction(+:total_sum) schedule(dynamic)
for (int i = 1; i <= N; i++) {
if (!is_connected) continue;
double local_sum = 0.0;
if (method_str == "single_source_bfs" || method_str == "unweighted") {
local_sum = _bfs_sum(G_l, i);
} else {
local_sum = _dijkstra_sum(G_l, i);
}
if (local_sum < 0) {
#pragma omp critical
is_connected = false;
} else {
total_sum += local_sum;
}
}
}
if (!is_connected) {
if (is_directed) {
throw py::value_error("Graph is not strongly connected.");
}
else {
throw py::value_error("Graph is not connected.");
}
}
return py::float_(total_sum / (static_cast<double>(N) * (N - 1)));
}
+207
View File
@@ -0,0 +1,207 @@
#include "path.h"
#ifdef _OPENMP
#include <omp.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include <vector>
#include <queue>
#include <limits>
#include <string>
#include <pybind11/pybind11.h>
namespace py = pybind11;
double _bfs_eccentricity(const Graph_L& G_l, int source) {
int N = G_l.n;
std::vector<int> dis(N + 1, -1);
std::queue<int> q;
dis[source] = 0;
q.push(source);
double max_dist = 0.0;
int visited_count = 0;
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!q.empty()) {
int u = q.front();
q.pop();
visited_count++;
if (dis[u] > max_dist) {
max_dist = dis[u];
}
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
if (dis[v] == -1) {
dis[v] = dis[u] + 1;
q.push(v);
}
}
}
return (visited_count == N) ? max_dist : -1.0;
}
py::object eccentricity(py::object G, py::object v = py::none(), py::object sp = py::none()) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = py::hasattr(G, "is_directed") ? G.attr("is_directed")().cast<bool>() : false;
int order = py::len(G.attr("nodes"));
if (!sp.is_none()) {
py::dict result;
py::dict sp_dict = sp.cast<py::dict>();
py::list nodes_to_check;
bool is_single = false;
if (v.is_none()) {
nodes_to_check = py::list(G.attr("nodes"));
} else if (py::isinstance<py::list>(v) || py::isinstance<py::tuple>(v) || py::isinstance<py::set>(v)) {
nodes_to_check = py::list(v);
} else {
nodes_to_check.append(v);
is_single = true;
}
for (py::handle n : nodes_to_check) {
if (!sp_dict.contains(n)) {
throw py::type_error("Format of 'sp' is invalid.");
}
py::dict length_dict = sp_dict[n].cast<py::dict>();
if (py::len(length_dict) != order) {
std::string msg = is_directed ?
"Found infinite path length because the digraph is not strongly connected" :
"Found infinite path length because the graph is not connected";
throw py::value_error(msg);
}
double max_val = 0.0;
for (auto item : length_dict) {
double val = item.second.cast<double>();
if (val > max_val) max_val = val;
}
result[n] = max_val;
}
if (is_single) return result[v];
return result;
}
if (G_.linkgraph_dirty) {
G_.linkgraph_structure = graph_to_linkgraph(G_, is_directed, "", true, false);
G_.linkgraph_dirty = false;
}
const Graph_L& G_l = G_.linkgraph_structure;
int N = G_l.n;
if (N == 0) return py::dict();
py::dict node_index;
std::vector<py::object> index_to_node_vec;
if (py::hasattr(G, "node_index")) {
node_index = G.attr("node_index").cast<py::dict>();
} else {
int idx = 0;
for (py::handle n : G.attr("nodes")) {
node_index[n] = py::cast(idx++);
}
}
if (py::hasattr(G, "index_node")) {
py::object idx_n = G.attr("index_node");
if (py::isinstance<py::list>(idx_n)) {
py::list l = idx_n.cast<py::list>();
for (auto item : l) index_to_node_vec.push_back(item.cast<py::object>());
} else if (py::isinstance<py::dict>(idx_n)) {
py::dict d = idx_n.cast<py::dict>();
index_to_node_vec.resize(py::len(d));
for (auto item : d) {
index_to_node_vec[item.first.cast<int>()] = item.second.cast<py::object>();
}
}
} else {
index_to_node_vec.resize(py::len(node_index));
for (auto item : node_index) {
index_to_node_vec[item.second.cast<int>()] = item.first.cast<py::object>();
}
}
std::vector<int> target_ids;
bool is_single_node = false;
if (v.is_none()) {
target_ids.resize(N);
for (int i = 0; i < N; ++i) target_ids[i] = i + 1;
} else if (py::isinstance<py::list>(v) || py::isinstance<py::tuple>(v) || py::isinstance<py::set>(v)) {
py::iterable v_iterable = v.cast<py::iterable>();
for (py::handle node : v_iterable) {
if (node_index.contains(node)) {
target_ids.push_back(node_index[node].cast<int>() + 1);
}
}
} else {
if (node_index.contains(v)) {
is_single_node = true;
target_ids.push_back(node_index[v].cast<int>() + 1);
} else {
throw py::value_error("Node not found in graph.");
}
}
int num_targets = target_ids.size();
if (num_targets == 0) return py::dict();
std::vector<double> ecc_values(num_targets, -1.0);
bool is_connected = true;
{
py::gil_scoped_release release;
#pragma omp parallel for schedule(dynamic)
for (int i = 0; i < num_targets; i++) {
if (!is_connected) continue;
int u = target_ids[i];
double ecc_val = _bfs_eccentricity(G_l, u);
if (ecc_val < 0) {
#pragma omp critical
is_connected = false;
} else {
ecc_values[i] = ecc_val;
}
}
}
if (!is_connected) {
std::string msg = is_directed ?
"Found infinite path length because the digraph is not strongly connected" :
"Found infinite path length because the graph is not connected";
throw py::value_error(msg);
}
if (is_single_node) {
return py::cast(ecc_values[0]);
} else {
py::dict result;
for (int i = 0; i < num_targets; i++) {
int internal_id = target_ids[i];
py::object original_node = index_to_node_vec[internal_id - 1];
result[original_node] = ecc_values[i];
}
return result;
}
}
+314
View File
@@ -0,0 +1,314 @@
#include "mst.h"
#ifdef _OPENMP
#include <omp.h>
#endif
#include <cmath>
#include "../../classes/graph.h"
#include "../../common/utils.h"
UnionFind::UnionFind() {}
UnionFind::UnionFind(std::vector<node_t> elements) {
for (node_t x : elements) {
parents[x] = x;
weights[x] = 1;
}
}
node_t UnionFind::operator[](node_t object) {
if (!parents.count(object)) {
parents[object] = object;
weights[object] = 1;
return object;
}
std::vector<node_t> path;
path.push_back(object);
node_t root = parents[object];
while (root != path.back()) {
path.push_back(root);
root = parents[root];
}
for (node_t ancestor : path) {
parents[ancestor] = root;
}
return root;
}
void UnionFind::_union(node_t object1, node_t object2) {
node_t root, r;
object1 = (*this)[object1];
object2 = (*this)[object2];
if (weights[object1] < weights[object2]) {
root = object1, r = object2;
} else {
root = object2, r = object1;
}
weights[root] += weights[r];
parents[r] = root;
}
struct mst_Edge {
double wt;
node_t start_node, end_node;
edge_attr_dict_factory edge_attr;
mst_Edge(double wt, node_t start_node, node_t end_node, edge_attr_dict_factory edge_attr) {
this->wt = wt;
this->start_node = start_node;
this->end_node = end_node;
this->edge_attr = edge_attr;
}
};
py::object kruskal_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan) {
UnionFind subtrees;
Graph G_ = G.cast<Graph&>();
std::string weight_key = weight_to_string(weight);
std::vector<std::pair<weight_t, graph_edge>> edges;
int sign = minimum.cast<py::bool_>().equal(py::cast(true)) ? 1 : -1;
for (graph_edge& edge : G_._get_edges()) {
weight_t wt = (edge.attr.count(weight_key) ? edge.attr[weight_key] : 1) * sign;
if (!ignore_nan.cast<py::bool_>() && isnan(wt)) {
PyErr_Format(PyExc_ValueError, "NaN found as an edge weight. Edge (%R, %R, %R)", G_.id_to_node[py::cast(edge.u)].ptr(), G_.id_to_node[py::cast(edge.v)].ptr(), attr_to_dict(edge.attr).ptr());
return py::none();
}
edges.emplace_back(wt, edge);
}
std::sort(edges.begin(), edges.end(), [](const std::pair<weight_t, graph_edge>& edge1, const std::pair<weight_t, graph_edge>& edge2) -> bool {
return edge1.first < edge2.first;
});
py::list ret;
for (const auto& edge : edges) {
node_t u = edge.second.u, v = edge.second.v;
if (subtrees[u] != subtrees[v]) {
if (data.cast<bool>()) {
ret.append(py::make_tuple(G_.id_to_node[py::cast(u)], G_.id_to_node[py::cast(v)], attr_to_dict(edge.second.attr)));
} else {
ret.append(py::make_tuple(G_.id_to_node[py::cast(u)], G_.id_to_node[py::cast(v)]));
}
subtrees._union(u, v);
}
}
return ret;
};
struct cmp {
bool operator()(const mst_Edge& node1, const mst_Edge& node2) {
return node1.wt > node2.wt;
}
};
py::object prim_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan) {
Graph& G_ = G.cast<Graph&>();
py::list res = py::list();
node_dict_factory nodes_list = G_.node;
std::unordered_set<node_t> nodes;
for (node_dict_factory::iterator iter = nodes_list.begin(); iter != nodes_list.end(); iter++) {
node_t node_id = iter->first;
nodes.emplace(node_id);
}
int sign = 1;
if (!minimum.cast<py::bool_>().equal(py::cast(true))) {
sign = -1;
}
while (!nodes.empty()) {
const node_t u = *(nodes.begin());
nodes.erase(nodes.begin());
std::priority_queue<mst_Edge, std::vector<mst_Edge>, cmp> frontier;
std::unordered_map<node_t, bool> visited;
node_t u_ = u;
visited.emplace(u_, true);
adj_attr_dict_factory u_neighbors = G_.adj[u];
for (adj_attr_dict_factory::iterator i = u_neighbors.begin(); i != u_neighbors.end(); i++) {
node_t v = i->first;
edge_attr_dict_factory d = i->second;
double wt = sign;
if (d.find(py::cast<std::string>(weight)) != d.end()) {
wt = d[py::cast<std::string>(weight)] * sign;
}
if (isnan(wt)) {
if (ignore_nan.cast<bool>()) {
continue;
}
PyErr_Format(PyExc_ValueError, "NaN found as an edge weight. Edge {(%R %R %R)}", (G_.id_to_node.attr("get")(u)).ptr(), G_.id_to_node.attr("get")(v).ptr(), attr_to_dict(d).ptr());
return py::none();
}
frontier.push(mst_Edge(wt, u_, v, d));
}
while (!frontier.empty()) {
mst_Edge node = frontier.top();
frontier.pop();
double W = node.wt;
node_t u_id = node.start_node;
node_t v_id = node.end_node;
edge_attr_dict_factory d = node.edge_attr;
if (visited.find(v_id) != visited.end() || nodes.find(v_id) == nodes.end()) {
continue;
}
if (data.cast<bool>()) {
res.append(py::make_tuple(G_.id_to_node.attr("get")(u_id), G_.id_to_node.attr("get")(v_id), attr_to_dict(d)));
} else {
res.append(py::make_tuple(G_.id_to_node.attr("get")(u_id), G_.id_to_node.attr("get")(v_id)));
}
visited.emplace(v_id, true);
nodes.erase(v_id);
adj_attr_dict_factory v_neighbors = G_.adj[v_id];
for (adj_attr_dict_factory::iterator j = v_neighbors.begin(); j != v_neighbors.end(); j++) {
node_t w = j->first;
edge_attr_dict_factory d2 = j->second;
if (visited.find(w) != visited.end()) {
continue;
}
double new_weight = sign;
if (d2.find(py::cast<std::string>(weight)) != d2.end()) {
new_weight = d2[py::cast<std::string>(weight)] * sign;
}
frontier.push(mst_Edge(new_weight, v_id, w, d2));
}
}
}
return res;
}
struct CompactEdge {
int u, v, id;
double wt;
};
struct AtomicBest {
std::atomic<int> edge_idx;
AtomicBest() : edge_idx(-1) {}
AtomicBest(const AtomicBest& other) : edge_idx(other.edge_idx.load()) {}
};
struct IntUnionFind {
std::vector<int> parent;
std::vector<int> rank;
int component_count;
IntUnionFind(int n) : parent(n), rank(n, 0), component_count(n) {
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int i) {
if (parent[i] == i) return i;
return parent[i] = find(parent[i]);
}
int find_readonly(int i) const {
while (i != parent[i]) {
i = parent[i];
}
return i;
}
bool unite(int i, int j) {
int root_i = find(i);
int root_j = find(j);
if (root_i != root_j) {
if (rank[root_i] < rank[root_j]) parent[root_i] = root_j;
else if (rank[root_i] > rank[root_j]) parent[root_j] = root_i;
else { parent[root_i] = root_j; rank[root_j]++; }
component_count--;
return true;
}
return false;
}
};
py::object boruvka_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan) {
Graph& G_ = G.cast<Graph&>();
std::string weight_key = weight_to_string(weight);
int sign = minimum.cast<bool>() ? 1 : -1;
bool return_data = data.cast<bool>();
bool ignore_n = ignore_nan.cast<bool>();
std::shared_ptr<COOGraph> coo = G_.gen_COO(weight_key);
int num_nodes = coo->nodes.size();
int num_edges = coo->row.size();
if (num_nodes == 0) return py::list();
std::vector<CompactEdge> active_edges;
active_edges.reserve(num_edges);
const auto& W_vec = *(coo->W_map[weight_key]);
for (int i = 0; i < num_edges; ++i) {
double wt = W_vec[i] * sign;
if (std::isnan(wt)) {
if (!ignore_n) {
PyErr_Format(PyExc_ValueError, "NaN found as an edge weight.");
return py::none();
}
continue;
}
active_edges.push_back({coo->row[i], coo->col[i], i, wt});
}
IntUnionFind uf(num_nodes);
std::vector<bool> in_mst(num_edges, false);
{
py::gil_scoped_release release;
while (uf.component_count > 1) {
std::vector<AtomicBest> best_at(num_nodes);
bool changed = false;
#pragma omp parallel for
for (int i = 0; i < (int)active_edges.size(); ++i) {
int root_u = uf.find_readonly(active_edges[i].u);
int root_v = uf.find_readonly(active_edges[i].v);
if (root_u != root_v) {
auto update_best = [&](int root, int edge_idx) {
int current = best_at[root].edge_idx.load(std::memory_order_relaxed);
while (current == -1 || active_edges[edge_idx].wt < active_edges[current].wt) {
if (best_at[root].edge_idx.compare_exchange_weak(current, edge_idx)) break;
}
};
update_best(root_u, i);
update_best(root_v, i);
}
}
for (int i = 0; i < num_nodes; ++i) {
int e_idx = best_at[i].edge_idx.load();
if (e_idx != -1) {
const auto& e = active_edges[e_idx];
if (uf.unite(e.u, e.v)) {
in_mst[e.id] = true;
changed = true;
}
}
}
if (!changed) break;
auto new_end = std::remove_if(active_edges.begin(), active_edges.end(), [&](const CompactEdge& e) {
return uf.find(e.u) == uf.find(e.v);
});
active_edges.erase(new_end, active_edges.end());
}
}
py::list ret;
for (int i = 0; i < num_edges; ++i) {
if (in_mst[i]) {
node_t u = coo->nodes[coo->row[i]];
node_t v = coo->nodes[coo->col[i]];
py::object u_obj = G_.id_to_node[py::cast(u)];
py::object v_obj = G_.id_to_node[py::cast(v)];
if (return_data) {
const auto& edge_attr = G_.adj.at(u).at(v);
ret.append(py::make_tuple(u_obj, v_obj, attr_to_dict(edge_attr)));
} else {
ret.append(py::make_tuple(u_obj, v_obj));
}
}
}
return ret;
}
+19
View File
@@ -0,0 +1,19 @@
#pragma once
#include "../../common/common.h"
class UnionFind {
public:
UnionFind();
UnionFind(std::vector<node_t> elements);
node_t operator[](node_t object);
void _union(node_t object1, node_t object2);
private:
std::unordered_map<node_t, node_t> parents;
std::unordered_map<node_t, unsigned int> weights;
};
py::object kruskal_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan);
py::object prim_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan);
py::object boruvka_mst_edges(py::object G, py::object minimum, py::object weight, py::object data, py::object ignore_nan);
+376
View File
@@ -0,0 +1,376 @@
#include "path.h"
#ifdef _OPENMP
#include <omp.h>
#endif
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "../../classes/graph.h"
#include "../../common/utils.h"
#include "../../classes/linkgraph.h"
#include "../../classes/segment_tree.cpp"
#include <vector>
#include <queue>
#include <limits>
#include <string>
std::vector<double> _dijkstra(const Graph_L& G_l, int source, int target) {
int N = G_l.n;
const double INF = std::numeric_limits<double>::infinity();
std::vector<double> dis(N + 1, INF);
std::priority_queue<std::pair<double, int>,
std::vector<std::pair<double, int>>,
std::greater<std::pair<double, int>>> pq;
dis[source] = 0.0;
pq.push({0.0, source});
const std::vector<int>& head = G_l.head;
const std::vector<LinkEdge>& E = G_l.edges;
while (!pq.empty()) {
std::pair<double, int> top = pq.top();
pq.pop();
double d = top.first;
int u = top.second;
//Lazy deletion
if (d > dis[u]) continue;
// cutoff
if (u == target) break;
for (int p = head[u]; p != -1; p = E[p].next) {
int v = E[p].to;
double w = static_cast<double>(E[p].w);
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
pq.push({dis[v], v});
}
}
}
return dis;
}
py::object _invoke_cpp_dijkstra_multisource(py::object G, py::object sources, py::object weight, py::object target) {
bool is_directed = G.attr("is_directed")().cast<bool>();
Graph& G_ = G.cast<Graph&>();
std::string weight_key = weight_to_string(weight);
Graph_L G_l;
if(G_.linkgraph_dirty){
G_l = graph_to_linkgraph(G_, is_directed, weight_key, true, false);
G_.linkgraph_structure = G_l;
G_.linkgraph_dirty = false;
} else {
G_l = G_.linkgraph_structure;
}
node_t target_id = -1;
if (!target.is_none()) {
target_id = G_.node_to_id.attr("get")(target, -1).cast<node_t>();
}
py::list sources_list = py::list(sources);
int num_sources = py::len(sources_list);
int N = G_l.n;
std::vector<node_t> source_ids(num_sources);
for(int i = 0; i < num_sources; i++){
if(G_.node_to_id.attr("get")(sources_list[i], py::none()).is(py::none())){
printf("The node should exist in the graph!");
return py::none();
}
source_ids[i] = G_.node_to_id.attr("get")(sources_list[i]).cast<node_t>();
}
std::vector<double> results(num_sources * N);
{
py::gil_scoped_release release;
#pragma omp parallel for schedule(dynamic)
for (int i = 0; i < num_sources; i++) {
node_t s = source_ids[i];
std::vector<double> dists = _dijkstra(G_l, s, target_id);
size_t offset = (size_t)i * N;
for (int j = 1; j <= N; j++) {
results[offset + (j - 1)] = dists[j];
}
}
}
py::array::ShapeContainer ret_shape{num_sources, N};
py::array_t<double> ret(ret_shape, results.data());
return ret;
}
#ifdef EASYGRAPH_ENABLE_GPU
py::object _invoke_gpu_dijkstra_multisource(py::object G,py::object py_sources, py::object weight, py::object target) {
Graph& G_ = G.cast<Graph&>();
if (weight.is_none()) {
G_.gen_CSR();
} else {
G_.gen_CSR(weight_to_string(weight));
}
auto csr_graph = G_.csr_graph;
std::vector<int>& E = csr_graph->E;
std::vector<int>& V = csr_graph->V;
std::vector<double> *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<double> sssp;
int gpu_r = gpu_easygraph::sssp_dijkstra(V, E, *W_p, *sources,
target.is_none() ? -1 : (int)py::cast<py::int_>(target),
sssp);
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)sources->size(), (int)V.size() - 1};
py::array_t<double> ret(ret_shape, sssp.data());
return ret;
}
#endif
py::object _dijkstra_multisource(py::object G,py::object sources, py::object weight, py::object target) {
#ifdef EASYGRAPH_ENABLE_GPU
return _invoke_gpu_dijkstra_multisource(G, sources, weight, target);
#else
return _invoke_cpp_dijkstra_multisource(G, sources, weight, target);
#endif
}
py::object _spfa(py::object G, py::object source, py::object weight) {
Graph& G_ = G.cast<Graph&>();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::string weight_key = weight_to_string(weight);
Graph_L G_l = graph_to_linkgraph(G_, is_directed,weight_key, false);
int N = G_.node.size();
std::vector<int> Q(N+10,0);
std::vector<double> dis(N+1,INFINITY);
std::vector<bool> vis(N+1,false);
int l = 0, r = 1;
node_t S = G_.node_to_id[source].cast<node_t>();
Q[0] = S; vis[S] = true; dis[S] = 0;
std::vector<LinkEdge>& E = G_l.edges;
std::vector<int>& head = G_l.head;
while (l != r) {
if (r != 0 && dis[Q[l]] >= dis[Q[r - 1]])
std::swap(Q[l], Q[r - 1]);
int u = Q[l++];
if (l >= N) l -= N;
vis[u] = true;
for(int p = head[u]; p != -1; p = E[p].next) {
int v=E[p].to;
if (dis[v]>dis[u]+E[p].w) {
dis[v]=dis[u]+E[p].w;
if (!vis[v]) {
vis[v]=true;
if (l == 0 || dis[v] >= dis[Q[l]])
Q[r++]=v;
else
Q[--l]=v;
if (r >= N) r -= N;
}
}
}
}
py::list pydist = py::list();
for(int i = 1; i <= N; i++){
pydist.append(py::cast(dis[i]));
}
return pydist;
}
py::object Prim(py::object G, py::object weight) {
std::unordered_map<node_t, std::unordered_map<node_t, weight_t>> res_dict;
py::dict result_dict = py::dict();
Graph& G_ = G.cast<Graph&>();
adj_dict_factory adj = G_.adj;
std::vector<node_t> selected;
std::vector<node_t> candidate;
node_dict_factory& node_list = G_.node;
std::string weight_key = weight_to_string(weight);
for (node_dict_factory::iterator i = node_list.begin(); i != node_list.end(); i++) {
node_t node_id = i->first;
result_dict[G_.id_to_node[py::cast(node_id)]] = py::dict();
if (selected.size() == 0) {
selected.emplace_back(node_id);
} else {
candidate.emplace_back(node_id);
}
}
while (candidate.size() > 0) {
node_t start_id = -1;
node_t end_id = -1;
weight_t min_weight = INFINITY;
int selected_len = selected.size();
int candidate_len = candidate.size();
for (int i = 0; i < selected_len; i++) {
for (int j = 0; j < candidate_len; j++) {
adj_attr_dict_factory node_adj = G_.adj[selected[i]];
edge_attr_dict_factory edge_attr;
weight_t edge_weight = INFINITY;
bool j_exist = false;
if (node_adj.find(candidate[j]) != node_adj.end()) {
edge_attr = node_adj[candidate[j]];
edge_weight = edge_attr.find(weight_key) != edge_attr.end() ? edge_attr[weight_key] : 1;
j_exist = true;
}
if ((node_list.find(selected[i]) != node_list.end()) &&
j_exist &&
(edge_weight < min_weight)) {
start_id = selected[i];
end_id = candidate[j];
min_weight = edge_weight;
}
}
}
if (start_id != -1 && end_id != -1) {
res_dict[start_id][end_id] = min_weight;
selected.emplace_back(end_id);
std::vector<node_t>::iterator temp_iter;
temp_iter = std::find(candidate.begin(), candidate.end(), end_id);
candidate.erase(temp_iter);
} else {
break;
}
}
for (std::unordered_map<node_t, std::unordered_map<node_t, weight_t>>::iterator k = res_dict.begin();
k != res_dict.end(); k++) {
py::object res_node = G_.id_to_node[py::cast(k->first)];
for (std::unordered_map<node_t, weight_t>::iterator z = k->second.begin(); z != k->second.end(); z++) {
py::object res_adj_node = G_.id_to_node[py::cast(z->first)];
result_dict[res_node][res_adj_node] = z->second;
}
}
return result_dict;
}
bool comp(const std::pair<std::pair<node_t, node_t>, weight_t>& a, const std::pair<std::pair<node_t, node_t>, weight_t>& b) {
return a.second < b.second;
}
py::object Kruskal(py::object G, py::object weight) {
std::unordered_map<node_t, std::unordered_map<node_t, weight_t>> res_dict;
py::dict result_dict = py::dict();
std::vector<std::vector<node_t>> group;
Graph& G_ = G.cast<Graph&>();
adj_dict_factory& adj = G_.adj;
node_dict_factory& node_list = G_.node;
std::vector<std::pair<std::pair<node_t, node_t>, weight_t>> edge_list;
std::string weight_key = weight_to_string(weight);
for (node_dict_factory::iterator i = node_list.begin(); i != node_list.end(); i++) {
node_t i_id = i->first;
result_dict[G_.id_to_node[py::cast(i_id)]] = py::dict();
std::vector<node_t> temp_vector;
temp_vector.emplace_back(i_id);
group.emplace_back(temp_vector);
adj_attr_dict_factory i_adj = adj[i_id];
for (adj_attr_dict_factory::iterator j = i_adj.begin(); j != i_adj.end(); j++) {
node_t j_id = j->first;
weight_t edge_weight = adj[i_id][j_id].find(weight_key) != adj[i_id][j_id].end() ? adj[i_id][j_id][weight_key] : 1;
edge_list.emplace_back(std::make_pair(std::make_pair(i_id, j_id), edge_weight));
}
}
std::sort(edge_list.begin(), edge_list.end(), comp);
node_t m, n;
int group_size = group.size();
for (auto edge : edge_list) {
for (int i = 0; i < group_size; i++) {
int group_i_size = group[i].size();
for (int j = 0; j < group_i_size; j++) {
if (group[i][j] == edge.first.first) {
m = i;
break;
}
}
for (int j = 0; j < group_i_size; j++) {
if (group[i][j] == edge.first.second) {
n = i;
break;
}
}
}
if (m != n) {
res_dict[edge.first.first][edge.first.second] = edge.second;
std::vector<node_t> temp_vector;
group[m].insert(group[m].end(), group[n].begin(), group[n].end());
group[n].clear();
}
}
for (std::unordered_map<node_t, std::unordered_map<node_t, weight_t>>::iterator k = res_dict.begin();
k != res_dict.end(); k++) {
py::object res_node = G_.id_to_node[py::cast(k->first)];
for (std::unordered_map<node_t, weight_t>::iterator z = k->second.begin(); z != k->second.end(); z++) {
py::object res_adj_node = G_.id_to_node[py::cast(z->first)];
result_dict[res_node][res_adj_node] = z->second;
}
}
return result_dict;
}
py::object Floyd(py::object G, py::object weight) {
std::unordered_map<node_t, std::unordered_map<node_t, weight_t>> res_dict;
Graph& G_ = G.cast<Graph&>();
adj_dict_factory& adj = G_.adj;
py::dict result_dict = py::dict();
node_dict_factory& node_list = G_.node;
std::string weight_key = weight_to_string(weight);
for (node_dict_factory::iterator i = node_list.begin(); i != node_list.end(); i++) {
result_dict[G_.id_to_node[py::cast(i->first)]] = py::dict();
adj_attr_dict_factory temp_key = adj[i->first];
for (node_dict_factory::iterator j = node_list.begin(); j != node_list.end(); j++) {
if (temp_key.find(j->first) != temp_key.end()) {
if (adj[i->first][j->first].count(weight_key) == 0) {
adj[i->first][j->first][weight_key] = 1;
}
res_dict[i->first][j->first] = adj[i->first][j->first][weight_key];
} else {
res_dict[i->first][j->first] = INFINITY;
}
if (i->first == j->first) {
res_dict[i->first][i->first] = 0;
}
}
}
for (node_dict_factory::iterator k = node_list.begin(); k != node_list.end(); k++) {
for (node_dict_factory::iterator i = node_list.begin(); i != node_list.end(); i++) {
for (node_dict_factory::iterator j = node_list.begin(); j != node_list.end(); j++) {
weight_t temp = res_dict[i->first][k->first] + res_dict[k->first][j->first];
weight_t i_j_weight = res_dict[i->first][j->first];
if (i_j_weight > temp) {
res_dict[i->first][j->first] = temp;
}
}
}
}
for (std::unordered_map<node_t, std::unordered_map<node_t, weight_t>>::iterator k = res_dict.begin();
k != res_dict.end(); k++) {
py::object res_node = G_.id_to_node[py::cast(k->first)];
for (std::unordered_map<node_t, weight_t>::iterator z = k->second.begin(); z != k->second.end(); z++) {
py::object res_adj_node = G_.id_to_node[py::cast(z->first)];
result_dict[res_node][res_adj_node] = z->second;
}
}
return result_dict;
}
+12
View File
@@ -0,0 +1,12 @@
#pragma once
#include "../../common/common.h"
py::object _dijkstra_multisource(py::object G, py::object sources, py::object weight, py::object target);
// py::object _dijkstra(py::object G, py::object sources, py::object weight, py::object target);
py::object _spfa(py::object G, py::object source, py::object weight);
py::object Floyd(py::object G,py::object weight);
py::object Prim(py::object G,py::object weight);
py::object Kruskal(py::object G,py::object weight);
py::object average_shortest_path_length(py::object G, py::object weight, py::object method);
py::object eccentricity(py::object G, py::object v, py::object sp);
@@ -0,0 +1,3 @@
#pragma once
#include "evaluation.h"
@@ -0,0 +1,827 @@
#include "evaluation.h"
#include <iomanip>
#include <vector>
#include <unordered_map>
#include <unordered_set>
#include <cmath>
#include <algorithm>
#include <string>
#ifdef _OPENMP
#include <omp.h>
#endif
#ifdef EASYGRAPH_ENABLE_GPU
#include <gpu_easygraph.h>
#endif
#include "../../classes/graph.h"
#include "../../classes/directed_graph.h"
#include "../../common/utils.h"
struct pair_hash {
template <class T1, class T2>
std::size_t operator()(const std::pair<T1, T2>& p) const {
auto h1 = std::hash<T1>()(p.first);
auto h2 = std::hash<T2>()(p.second);
return h1 ^ h2;
}
};
typedef std::unordered_map<std::pair<node_t, node_t>, 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<node_t, std::unordered_map<node_t, double>>& weighted_adj,
std::unordered_map<node_t, double>& 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<Graph&>();
std::vector<node_t> 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<node_t>();
}
std::unordered_map<node_t, std::unordered_map<node_t, double>> weighted_adj;
std::unordered_map<node_t, double> strength;
preprocess_graph_for_constraint(G_ref, weight_key, weighted_adj, strength);
std::vector<double> 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<node_t, double> 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<double> 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<Graph&>();
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<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double> *W_p = weight.is_none() ? &(coo_graph->unweighted_W)
: coo_graph->W_map.find(weight_to_string(weight))->second.get();
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
int num_nodes = coo_graph->node2idx.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::vector<double> constraint_results(num_nodes, 0.0);
std::vector<int> 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_t>()];
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<double> 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 <typename MapType>
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<double> compute_redundancy_core(py::object G_obj, const std::vector<node_t>& 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<const DiGraph&>();
} else {
G_ptr = &G_obj.cast<const Graph&>();
}
// Pre-compute max ID and node list
node_t max_graph_id = 0;
std::vector<node_t> 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<double> scale_sum_vec(max_graph_id + 1, 0.0);
std::vector<double> 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<double> 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<node_t> 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<std::string>();
bool is_directed = G.attr("is_directed")().cast<bool>();
if (nodes.is_none()) nodes = G.attr("nodes");
py::list nodes_list = py::list(nodes);
int len = py::len(nodes_list);
std::vector<node_t> 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<node_t>();
}
} else {
for (int i = 0; i < len; i++) {
target_ids[i] = nodes_list[i].cast<node_t>();
}
}
std::vector<double> results = compute_redundancy_core(G, target_ids, weight_key, is_directed);
py::array::ShapeContainer ret_shape{ (long)results.size() };
return py::array_t<double>(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<Graph&>();
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<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double>* W_p = weight.is_none() ? &(coo_graph->unweighted_W)
: coo_graph->W_map.find(weight_to_string(weight))->second.get();
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
int num_nodes = coo_graph->node2idx.size();
std::vector<double> effective_size_results(num_nodes);
bool is_directed = G.attr("is_directed")().cast<bool>();
std::vector<int> 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_t>()];
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<double> 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<Graph&>();
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<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double>* W_p = weight.is_none() ? &(coo_graph->unweighted_W)
: coo_graph->W_map.find(weight_to_string(weight))->second.get();
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
int num_nodes = coo_graph->node2idx.size();
std::vector<double> effective_size_results(num_nodes);
bool is_directed = G.attr("is_directed")().cast<bool>();
std::vector<int> 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_t>()];
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<node_t>();
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<py::dict>();
} else {
degree = G.attr("degree")(weight).cast<py::dict>();
}
py::dict efficiency_dict;
for (auto item : effective_size_dict) {
int node = py::reinterpret_borrow<py::int_>(item.first).cast<int>();
double eff_size = py::reinterpret_borrow<py::float_>(item.second).cast<double>();
if (!degree.contains(py::cast(node))) {
continue;
}
double node_degree = py::reinterpret_borrow<py::float_>(degree[py::cast(node)]).cast<double>();
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<std::string>();
bool is_directed = G.attr("is_directed")().cast<bool>();
// 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<node_t> 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<node_t>();
}
} else {
for (int i = 0; i < len; i++) {
target_ids[i] = nodes_list[i].cast<node_t>();
}
}
// Compute Efficiency = Effective Size / Degree
std::vector<double> 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<const DiGraph&>();
else G_ptr = &G.cast<const Graph&>();
std::vector<double> 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<double>(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<Graph&>();
std::vector<node_t> 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<node_t>();
}
std::unordered_map<node_t, std::unordered_map<node_t, double>> weighted_adj;
std::unordered_map<node_t, double> strength;
preprocess_graph_for_constraint(G_ref, weight_key, weighted_adj, strength);
std::vector<double> 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<node_t, double> 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<node_t, double> 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<double> 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<Graph&>();
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<int>& V = csr_graph->V;
std::vector<int>& E = csr_graph->E;
std::vector<int>& row = coo_graph->row;
std::vector<int>& col = coo_graph->col;
std::vector<double> *W_p = weight.is_none() ? &(coo_graph->unweighted_W)
: coo_graph->W_map.find(weight_to_string(weight))->second.get();
std::unordered_map<node_t, int>& node2idx = coo_graph->node2idx;
int num_nodes = coo_graph->node2idx.size();
bool is_directed = G.attr("is_directed")().cast<bool>();
std::vector<double> hierarchy_results;
std::vector<int> 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_t>()];
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<node_t>();
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
}
@@ -0,0 +1,8 @@
#pragma once
#include "../../common/common.h"
py::object constraint(py::object G, py::object nodes, py::object weight, py::object n_workers);
py::object effective_size(py::object G, py::object nodes, py::object weight, py::object n_workers);
py::object efficiency(py::object G, py::object nodes, py::object weight, py::object n_workers);
py::object hierarchy(py::object G, py::object nodes, py::object weight, py::object n_workers);