Skip to content

ECG Community Detection implementation#502

Open
ryandewolfe33 wants to merge 8 commits intoJuliaGraphs:masterfrom
ryandewolfe33:ECG
Open

ECG Community Detection implementation#502
ryandewolfe33 wants to merge 8 commits intoJuliaGraphs:masterfrom
ryandewolfe33:ECG

Conversation

@ryandewolfe33
Copy link
Copy Markdown
Contributor

Another community detection algorithm, see #231

Ensemble Clustering for Graphs (ECG) uses many runs of Louvain (see #488 ) to improve performance and stability.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 8, 2026

Benchmark Results (Julia v1)

Time benchmarks
master 462b1c3... master / 462b1c3...
centrality/digraphs/betweenness_centrality 16.9 ± 0.36 ms 16.9 ± 0.44 ms 0.997 ± 0.034
centrality/digraphs/closeness_centrality 11.3 ± 0.41 ms 11.5 ± 0.41 ms 0.985 ± 0.05
centrality/digraphs/degree_centrality 2.09 ± 0.15 μs 2.11 ± 0.15 μs 0.991 ± 0.1
centrality/digraphs/katz_centrality 0.846 ± 0.07 ms 0.862 ± 0.069 ms 0.982 ± 0.11
centrality/digraphs/pagerank 0.0374 ± 0.00051 ms 0.038 ± 0.0005 ms 0.985 ± 0.019
centrality/graphs/betweenness_centrality 28.9 ± 1.5 ms 29.7 ± 1.7 ms 0.971 ± 0.074
centrality/graphs/closeness_centrality 20.8 ± 0.33 ms 21.1 ± 0.35 ms 0.982 ± 0.022
centrality/graphs/degree_centrality 1.7 ± 0.22 μs 1.64 ± 0.18 μs 1.04 ± 0.18
centrality/graphs/katz_centrality 1.01 ± 0.075 ms 1.02 ± 0.076 ms 0.99 ± 0.1
connectivity/digraphs/strongly_connected_components 0.0443 ± 0.0015 ms 0.0445 ± 0.0017 ms 0.995 ± 0.05
connectivity/graphs/connected_components 25.6 ± 0.67 μs 25.8 ± 0.66 μs 0.992 ± 0.036
core/edges/digraphs 6.35 ± 0.02 μs 8 ± 0.01 μs 0.793 ± 0.0027
core/edges/graphs 15.7 ± 0.02 μs 14.7 ± 0.18 μs 1.07 ± 0.013
core/has_edge/digraphs 5.75 ± 0.38 μs 5.75 ± 0.39 μs 1 ± 0.095
core/has_edge/graphs 6.14 ± 0.45 μs 6.21 ± 0.42 μs 0.989 ± 0.099
core/nv/digraphs 0.351 ± 0.01 μs 0.361 ± 0.01 μs 0.972 ± 0.039
core/nv/graphs 0.38 ± 0.01 μs 0.371 ± 0.011 μs 1.02 ± 0.041
distance/weighted_diameter/barabasi_albert_naive 31.4 ± 0.69 ms 0.0318 ± 0.00058 s 0.99 ± 0.028
distance/weighted_diameter/barabasi_albert_optimized 2.34 ± 0.048 ms 5.5 ± 0.11 ms 0.425 ± 0.012
distance/weighted_diameter/erdos_renyi_naive 0.0327 ± 0.0012 s 0.0318 ± 0.00066 s 1.03 ± 0.044
distance/weighted_diameter/erdos_renyi_optimized 14.8 ± 0.66 ms 17.3 ± 0.6 ms 0.86 ± 0.049
edges/fille 5.68 ± 0.78 μs 5.69 ± 0.85 μs 0.997 ± 0.2
edges/fillp 4.58 ± 1.8 μs 4.68 ± 1.6 μs 0.979 ± 0.5
edges/tsume 2.18 ± 0.02 μs 2.17 ± 0.011 μs 1 ± 0.011
edges/tsump 2.17 ± 0.011 μs 2.17 ± 0.02 μs 1 ± 0.011
insertions/SG(n,e) Generation 26.2 ± 4.5 ms 26.3 ± 4.5 ms 0.996 ± 0.24
parallel/egonet/twohop 0.301 ± 0.0034 s 0.301 ± 0.0017 s 1 ± 0.012
parallel/egonet/vertexfunction 2.38 ± 0.087 ms 2.39 ± 0.095 ms 0.995 ± 0.054
serial/egonet/twohop 0.317 ± 0.0022 s 0.314 ± 0.0024 s 1.01 ± 0.01
serial/egonet/vertexfunction 2.44 ± 0.089 ms 2.4 ± 0.068 ms 1.02 ± 0.047
traversals/digraphs/bfs_tree 0.0535 ± 0.011 ms 0.0535 ± 0.012 ms 1 ± 0.31
traversals/digraphs/dfs_tree 0.0673 ± 0.012 ms 0.0674 ± 0.012 ms 0.998 ± 0.24
traversals/graphs/bfs_tree 0.0587 ± 0.0044 ms 0.0579 ± 0.0041 ms 1.01 ± 0.11
traversals/graphs/dfs_tree 0.07 ± 0.0054 ms 0.0703 ± 0.005 ms 0.995 ± 0.1
time_to_load 0.52 ± 0.0014 s 0.52 ± 0.0031 s 1 ± 0.0065
Memory benchmarks
master 462b1c3... master / 462b1c3...
centrality/digraphs/betweenness_centrality 0.29 M allocs: 24 MB 0.29 M allocs: 24 MB 1
centrality/digraphs/closeness_centrality 18.6 k allocs: 14.5 MB 18.6 k allocs: 14.5 MB 1
centrality/digraphs/degree_centrality 8 allocs: 5.01 kB 8 allocs: 5.01 kB 1
centrality/digraphs/katz_centrality 2.63 k allocs: 2.83 MB 2.63 k allocs: 2.83 MB 1
centrality/digraphs/pagerank 21 allocs: 14.9 kB 21 allocs: 14.9 kB 1
centrality/graphs/betweenness_centrality 0.545 M allocs: 0.0313 GB 0.545 M allocs: 0.0313 GB 1
centrality/graphs/closeness_centrality 19.3 k allocs: 14 MB 19.3 k allocs: 14 MB 1
centrality/graphs/degree_centrality 10 allocs: 5.43 kB 10 allocs: 5.43 kB 1
centrality/graphs/katz_centrality 2.96 k allocs: 3.1 MB 2.96 k allocs: 3.1 MB 1
connectivity/digraphs/strongly_connected_components 1.05 k allocs: 0.075 MB 1.05 k allocs: 0.075 MB 1
connectivity/graphs/connected_components 0.061 k allocs: 22.5 kB 0.061 k allocs: 22.5 kB 1
core/edges/digraphs 3 allocs: 0.0938 kB 3 allocs: 0.0938 kB 1
core/edges/graphs 3 allocs: 0.0938 kB 3 allocs: 0.0938 kB 1
core/has_edge/digraphs 20 allocs: 12.6 kB 20 allocs: 12.6 kB 1
core/has_edge/graphs 28 allocs: 13.8 kB 28 allocs: 13.8 kB 1
core/nv/digraphs 3 allocs: 0.0938 kB 3 allocs: 0.0938 kB 1
core/nv/graphs 3 allocs: 0.0938 kB 3 allocs: 0.0938 kB 1
distance/weighted_diameter/barabasi_albert_naive 13.5 k allocs: 13.8 MB 13.5 k allocs: 13.8 MB 1
distance/weighted_diameter/barabasi_albert_optimized 1.64 k allocs: 1.08 MB 2.98 k allocs: 2.46 MB 0.439
distance/weighted_diameter/erdos_renyi_naive 13.5 k allocs: 13.8 MB 13.5 k allocs: 13.8 MB 1
distance/weighted_diameter/erdos_renyi_optimized 6.9 k allocs: 6.47 MB 7.89 k allocs: 7.49 MB 0.865
edges/fille 3 allocs: 0.153 MB 3 allocs: 0.153 MB 1
edges/fillp 3 allocs: 0.153 MB 3 allocs: 0.153 MB 1
edges/tsume 0 allocs: 0 B 0 allocs: 0 B
edges/tsump 0 allocs: 0 B 0 allocs: 0 B
insertions/SG(n,e) Generation 0.0465 M allocs: 10.9 MB 0.0465 M allocs: 11 MB 0.998
parallel/egonet/twohop 10 allocs: 0.0768 MB 10 allocs: 0.0768 MB 1
parallel/egonet/vertexfunction 10 allocs: 0.0768 MB 10 allocs: 0.0768 MB 1
serial/egonet/twohop 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
serial/egonet/vertexfunction 3 allocs: 0.0764 MB 3 allocs: 0.0764 MB 1
traversals/digraphs/bfs_tree 2.34 k allocs: 0.113 MB 2.34 k allocs: 0.113 MB 1
traversals/digraphs/dfs_tree 2.44 k allocs: 0.118 MB 2.44 k allocs: 0.118 MB 1
traversals/graphs/bfs_tree 2.52 k allocs: 0.121 MB 2.52 k allocs: 0.121 MB 1
traversals/graphs/dfs_tree 2.63 k allocs: 0.127 MB 2.63 k allocs: 0.127 MB 1
time_to_load 0.145 k allocs: 11 kB 0.145 k allocs: 11 kB 1

@codecov
Copy link
Copy Markdown

codecov bot commented Mar 8, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.46%. Comparing base (bafd56e) to head (462b1c3).

Additional details and impacted files
@@            Coverage Diff             @@
##           master     #502      +/-   ##
==========================================
+ Coverage   97.31%   97.46%   +0.15%     
==========================================
  Files         126      127       +1     
  Lines        7739     7765      +26     
==========================================
+ Hits         7531     7568      +37     
+ Misses        208      197      -11     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Member

@LoveLow-Global LoveLow-Global left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! Adding ECG is a great feature for the community detection.

I've left a few comments. The two main things to check on are ensuring the ensemble weighting only applies to the 2-core of the graph and a small bug with undirected self-loops.

As this is my first review after being a reviewer here, I may have not given the best review possible. I apologize in advance for this issue, please let me know if there is anything to be improved from my side.

move_tol=move_tol,
rng=rng,
)
weights =
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed an algorithmic detail issue from the weight calculation. According to Equation (1) in the Poulin & Théberge paper (which I found on line 23 of this file), the ensemble co-association adjustment must only be applied to edges within the 2-core of the graph. Any edge outside the 2-core should default to $w_*$.

Currently, if I am not mistaken, it applies the interpolation globally to every edge.
I believe you have to conditionally apply this, using core_number(g).

Copy link
Copy Markdown
Contributor Author

@ryandewolfe33 ryandewolfe33 Apr 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. Without this step, the implementation (and intuition) works nicely for weighted (parallel edges considered as weighted) and directed graphs, but the 2-core is not so obvious in these cases. Also the core_number(g) function currently does not support self loops.

My proposed (and now implemented) solution is to have a min_weight_outside_2core parameter that calls core_number(g) and decreases all edge weights with at least of vertex with core number < 2 to the minimum weight. This way it can be bypassed for any of the edge cases above, and will throw an error if the core_number function is not supported for the graph type.

However I'm not super happy with this solution, and would be open to more discussion about the edge cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants