Skip to content

Commit 7f7af74

Browse files
authored
Merge pull request #168 from Musicaldg/feat/community-cpp-binding
Feat/community cpp binding
2 parents 6a42a21 + c3e2210 commit 7f7af74

34 files changed

Lines changed: 4286 additions & 6 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -206,4 +206,7 @@ Temporary Items
206206
.apdisk
207207
tmp_test/allset_test.py
208208
tmp_test/
209-
RandomNetwork.txt
209+
RandomNetwork.txt
210+
TEST/
211+
CON_TEST/
212+
Comm_TEST/

cpp_easygraph/classes/linkgraph.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,6 @@ struct compare_node {
8686
bool operator < (const compare_node &rhs) const {
8787
return d > rhs.d;
8888
}
89-
};
89+
};
90+
91+
std::vector<double> _dijkstra(const Graph_L& G_l, int source, int target);

cpp_easygraph/cpp_easygraph.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,16 @@ PYBIND11_MODULE(cpp_easygraph, m) {
105105
m.def("cpp_eccentricity", &eccentricity, py::arg("G"), py::arg("v") = py::none(), py::arg("sp") = py::none());
106106
m.def("cpp_connected_components_undirected", &connected_component_undirected, py::arg("G"));
107107
m.def("cpp_connected_components_directed", &connected_component_directed, py::arg("G"));
108+
109+
// community methods
110+
m.def("cpp_modularity", &cpp_modularity, py::arg("G"), py::arg("communities"), py::arg("weight") = py::str("weight"));
111+
m.def("cpp_greedy_modularity_communities", &cpp_greedy_modularity_communities, py::arg("G"), py::arg("weight") = py::str("weight"));
112+
m.def("cpp_enumerate_subgraph", &cpp_enumerate_subgraph, py::arg("G"), py::arg("k"));
113+
m.def("cpp_random_enumerate_subgraph", &cpp_random_enumerate_subgraph, py::arg("G"), py::arg("k"), py::arg("cut_prob"));
114+
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));
115+
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));
116+
m.def("cpp_LPA", &cpp_LPA, py::arg("G"));
117+
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());
118+
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());
119+
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));
108120
}

cpp_easygraph/functions/__init__.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@
66
#include "structural_holes/__init__.h"
77
#include "cores/__init__.h"
88
#include "centrality/__init__.h"
9-
#include "pagerank/__init__.h"
9+
#include "pagerank/__init__.h"
10+
#include "community/__init__.h"
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
#include <vector>
2+
#include <unordered_map>
3+
#include <algorithm>
4+
#include <random>
5+
#include <queue>
6+
#include <cstdint>
7+
#include <pybind11/pybind11.h>
8+
#include <pybind11/stl.h>
9+
#include "../../classes/graph.h"
10+
#include "../../classes/linkgraph.h"
11+
#include "../../common/utils.h"
12+
13+
namespace py = pybind11;
14+
using namespace std;
15+
16+
py::object cpp_LPA(py::object G) {
17+
Graph& G_ = G.cast<Graph&>();
18+
Graph_L linkgraph = G_._get_linkgraph_structure();
19+
int n = linkgraph.n;
20+
21+
py::dict id_to_node = G_.id_to_node;
22+
23+
if (n <= 1) {
24+
py::dict result;
25+
if (n == 1) {
26+
py::list node_list;
27+
node_list.append(id_to_node[py::cast(1)]);
28+
result[py::cast(1)] = node_list;
29+
}
30+
return result;
31+
}
32+
33+
vector<vector<int>> adj(n + 1);
34+
for (int u = 1; u <= n; ++u) {
35+
for (int e = linkgraph.head[u]; e != -1; e = linkgraph.edges[e].next) {
36+
int v = linkgraph.edges[e].to;
37+
if (u != v) {
38+
adj[u].push_back(v);
39+
}
40+
}
41+
}
42+
43+
for (int u = 1; u <= n; ++u) {
44+
if (adj[u].size() > 1) {
45+
sort(adj[u].begin(), adj[u].end());
46+
adj[u].erase(unique(adj[u].begin(), adj[u].end()), adj[u].end());
47+
}
48+
}
49+
50+
vector<int> labels(n);
51+
vector<int> nodes(n);
52+
for (int i = 0; i < n; ++i) {
53+
labels[i] = i;
54+
nodes[i] = i + 1;
55+
}
56+
57+
random_device rd;
58+
mt19937 gen(rd());
59+
60+
const int MAX_ITERATIONS = 1000;
61+
int iteration_count = 0;
62+
63+
vector<int> label_count(n, 0);
64+
vector<int> active_labels;
65+
active_labels.reserve(128);
66+
67+
while (iteration_count < MAX_ITERATIONS) {
68+
iteration_count++;
69+
70+
shuffle(nodes.begin(), nodes.end(), gen);
71+
72+
bool changed = false;
73+
for (int node : nodes) {
74+
int max_count = 0;
75+
76+
for (int neighbor : adj[node]) {
77+
int neighbor_label = labels[neighbor - 1];
78+
if (label_count[neighbor_label] == 0) {
79+
active_labels.push_back(neighbor_label);
80+
}
81+
label_count[neighbor_label]++;
82+
if (label_count[neighbor_label] > max_count) {
83+
max_count = label_count[neighbor_label];
84+
}
85+
}
86+
87+
if (max_count > 0) {
88+
int current_label = labels[node - 1];
89+
90+
vector<int> best_labels;
91+
for (int label : active_labels) {
92+
if (label_count[label] == max_count) {
93+
best_labels.push_back(label);
94+
}
95+
}
96+
97+
bool current_is_best = false;
98+
for (int label : best_labels) {
99+
if (label == current_label) {
100+
current_is_best = true;
101+
break;
102+
}
103+
}
104+
105+
if (!current_is_best) {
106+
changed = true;
107+
108+
if (best_labels.size() == 1) {
109+
labels[node - 1] = best_labels[0];
110+
} else {
111+
uniform_int_distribution<> dis(0, best_labels.size() - 1);
112+
labels[node - 1] = best_labels[dis(gen)];
113+
}
114+
}
115+
}
116+
117+
for (int label : active_labels) {
118+
label_count[label] = 0;
119+
}
120+
active_labels.clear();
121+
}
122+
123+
if (!changed) {
124+
break;
125+
}
126+
}
127+
128+
unordered_map<int, vector<int>> label_to_nodes;
129+
for (int i = 0; i < n; ++i) {
130+
int label = labels[i];
131+
label_to_nodes[label].push_back(i + 1);
132+
}
133+
134+
py::dict result;
135+
int community_id = 1;
136+
for (const auto& pair : label_to_nodes) {
137+
py::list node_list;
138+
for (int internal_id : pair.second) {
139+
node_list.append(id_to_node[py::cast(internal_id)]);
140+
}
141+
result[py::cast(community_id)] = node_list;
142+
community_id++;
143+
}
144+
145+
return result;
146+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#pragma once
2+
3+
#include <pybind11/pybind11.h>
4+
5+
namespace py = pybind11;
6+
7+
py::object cpp_LPA(py::object G);
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#pragma once
2+
3+
#include "modularity.h"
4+
#include "greedy_modularity.h"
5+
#include "motif.h"
6+
#include "louvain.h"
7+
#include "LPA.h"
8+
#include "ego_graph.h"
9+
#include "graph_coloring.h"
10+
#include "localsearch.h"
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
#include "ego_graph.h"
2+
#include "subgraph.h"
3+
#include "../../classes/graph.h"
4+
#include "../../classes/directed_graph.h"
5+
#include "../../classes/csr_graph.h"
6+
#include "../../common/utils.h"
7+
#include "../../classes/linkgraph.h"
8+
#include "../path/path.h"
9+
#include <algorithm>
10+
11+
12+
struct _EgoGraphCore {
13+
Graph_L G_l;
14+
std::vector<node_t> node_ids;
15+
std::unordered_map<node_t, int> node_to_idx;
16+
py::dict id_to_node_py;
17+
bool has_error = false;
18+
};
19+
20+
21+
static _EgoGraphCore _cpp_ego_graph_compute_impl(
22+
Graph& G_,
23+
py::object n,
24+
double radius_val,
25+
bool center_val,
26+
bool undirected_val,
27+
bool is_directed,
28+
const std::string& weight_key) {
29+
30+
_EgoGraphCore out;
31+
32+
if (G_.node_to_id.contains(n) == 0) {
33+
PyErr_Format(PyExc_KeyError, "Node %R is not in the graph.", n.ptr());
34+
out.has_error = true;
35+
return out;
36+
}
37+
38+
node_t center_id = G_.node_to_id[n].cast<node_t>();
39+
out.id_to_node_py = G_.id_to_node;
40+
41+
if (G_.linkgraph_dirty || G_.linkgraph_structure.max_deg == -1) {
42+
if (undirected_val) {
43+
out.G_l = graph_to_linkgraph(G_, false, weight_key, true, false);
44+
} else {
45+
out.G_l = graph_to_linkgraph(G_, is_directed, weight_key, true, false);
46+
}
47+
G_.linkgraph_dirty = false;
48+
G_.linkgraph_structure = out.G_l; // also cache the freshly built linkgraph
49+
} else {
50+
out.G_l = G_.linkgraph_structure;
51+
}
52+
53+
std::vector<double> dist = _dijkstra(out.G_l, center_id, -1);
54+
int N = out.G_l.n;
55+
for (int i = 1; i <= N; i++) {
56+
if (dist[i] > radius_val) continue;
57+
if (!center_val && i == (int)center_id) continue;
58+
out.node_to_idx[i] = (int)out.node_ids.size();
59+
out.node_ids.push_back(i);
60+
}
61+
return out;
62+
}
63+
64+
65+
static _EgoGraphCore _cpp_ego_graph_compute(
66+
py::object G,
67+
py::object n,
68+
py::object radius,
69+
py::object center,
70+
py::object undirected,
71+
py::object distance) {
72+
73+
bool is_directed = G.attr("is_directed")().cast<bool>();
74+
bool center_val = center.cast<bool>();
75+
bool undirected_val = undirected.cast<bool>();
76+
double radius_val = radius.cast<double>();
77+
std::string weight_key = weight_to_string(distance);
78+
79+
if (is_directed) {
80+
DiGraph& G_ = G.cast<DiGraph&>();
81+
return _cpp_ego_graph_compute_impl(
82+
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
83+
} else {
84+
Graph& G_ = G.cast<Graph&>();
85+
return _cpp_ego_graph_compute_impl(
86+
G_, n, radius_val, center_val, undirected_val, is_directed, weight_key);
87+
}
88+
}
89+
90+
91+
py::object cpp_ego_graph(
92+
py::object G,
93+
py::object n,
94+
py::object radius,
95+
py::object center,
96+
py::object undirected,
97+
py::object distance) {
98+
99+
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
100+
if (core.has_error) return py::none();
101+
102+
return nodes_subgraph_cpp(G, core.node_ids);
103+
}
104+
105+
106+
py::object cpp_ego_graph_csr(
107+
py::object G,
108+
py::object n,
109+
py::object radius,
110+
py::object center,
111+
py::object undirected,
112+
py::object distance) {
113+
114+
_EgoGraphCore core = _cpp_ego_graph_compute(G, n, radius, center, undirected, distance);
115+
if (core.has_error) return py::none();
116+
117+
int n_nodes = (int)core.node_ids.size();
118+
if (n_nodes == 0) {
119+
return py::dict();
120+
}
121+
122+
std::vector<int> V(n_nodes + 1, 0);
123+
std::vector<int> E;
124+
std::vector<double> W;
125+
126+
for (int new_u = 0; new_u < n_nodes; new_u++) {
127+
node_t u = core.node_ids[new_u];
128+
V[new_u + 1] = V[new_u];
129+
130+
int edge_idx = core.G_l.head[u];
131+
while (edge_idx != -1 && edge_idx < core.G_l.e) {
132+
node_t v = core.G_l.edges[edge_idx].to;
133+
auto it = core.node_to_idx.find(v);
134+
if (it != core.node_to_idx.end()) {
135+
E.push_back(it->second);
136+
W.push_back(core.G_l.edges[edge_idx].w);
137+
V[new_u + 1]++;
138+
}
139+
edge_idx = core.G_l.edges[edge_idx].next;
140+
}
141+
}
142+
143+
py::list original_nodes;
144+
for (node_t internal_id : core.node_ids) {
145+
original_nodes.append(core.id_to_node_py[py::cast(internal_id)]);
146+
}
147+
148+
py::dict result;
149+
result["nodes"] = original_nodes;
150+
result["V"] = py::cast(V);
151+
result["E"] = py::cast(E);
152+
result["W"] = py::cast(W);
153+
154+
return result;
155+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
#include <pybind11/pybind11.h>
4+
5+
namespace py = pybind11;
6+
7+
py::object cpp_ego_graph(
8+
py::object G,
9+
py::object n,
10+
py::object radius = py::int_(1),
11+
py::object center = py::bool_(true),
12+
py::object undirected = py::bool_(false),
13+
py::object distance = py::none()
14+
);
15+
16+
py::object cpp_ego_graph_csr(
17+
py::object G,
18+
py::object n,
19+
py::object radius = py::int_(1),
20+
py::object center = py::bool_(true),
21+
py::object undirected = py::bool_(false),
22+
py::object distance = py::none()
23+
);

0 commit comments

Comments
 (0)