chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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(), [°ree](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(), [°ree](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"));
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user