Skip to content

feat(clustering): Louvain community detection - #7

Closed
seilat wants to merge 2 commits into
masterfrom
feat/louvain-clustering
Closed

feat(clustering): Louvain community detection#7
seilat wants to merge 2 commits into
masterfrom
feat/louvain-clustering

Conversation

@seilat

@seilat seilat commented May 30, 2026

Copy link
Copy Markdown
Owner

Self-review PR (fork only — not for upstream; Louvain holds for a jgrapht-dev signal first per project cadence).

What

LouvainClustering<V,E> in org.jgrapht.alg.clustering, implementing ClusteringAlgorithm<V>.
Alternating local-moving + aggregation phases greedily maximising modularity; weighted/self-loop
aware; modularity conventions match UndirectedModularityMeasurer (reused for getModularity()).
Constructors mirror LabelPropagationClustering: (graph), (graph, rng), (graph, rng, tolerance).

Tests — 15/15 green

cliques→2, ring→3, complete→1 (Q=0), weighted, seed determinism, single/empty/isolated,
self-loops, random partition-validity ×20, getModularity vs measurer, beats all-singletons.

Benchmark (perf/clustering, planted partitions, in-process forks=0)

nodes LabelProp Louvain GreedyModularity
125 0.21 ms 0.26 ms 1.33 ms
500 1.85 ms 2.93 ms 29.5 ms

Louvain ≈ LabelPropagation cost while directly optimising modularity; 5–10× faster than
GreedyModularity and scales far better. pom.xml exports the new perf.clustering package(s)
in the surefire argLine (matches the documented per-package pattern).

Gates

checkstyle clean (etc/jgrapht_checks.xml), javadoc clean. Companion /clustering/
visualization added in jgrapht-algo-visualization (separate repo). Dev-list draft prepared.

Review focus

  • modularity-gain formula + 2m/self-loop conventions vs UndirectedModularityMeasurer
  • aggregation degree-conservation (intra-community edges → super-vertex self-loops)
  • determinism via seeded Random

🤖 Generated with Claude Code

seilat and others added 2 commits May 30, 2026 11:57
Add LouvainClustering implementing ClusteringAlgorithm<V> in
org.jgrapht.alg.clustering: alternating local-moving and aggregation
phases that greedily maximise modularity, with weighted/self-loop
handling and modularity conventions matching UndirectedModularityMeasurer
(reused for getModularity()). Constructors mirror LabelPropagationClustering
((graph), (graph, rng), (graph, rng, tolerance)).

- 15 JUnit5 tests: cliques->2, ring->3, complete->1 (Q=0), weighted,
  seed determinism, single/empty/isolated, self-loops, random
  partition-validity, getModularity vs measurer, beats all-singletons.
- JMH benchmark (perf/clustering) vs LabelPropagation and GreedyModularity
  on planted-partition graphs; export the new perf package(s) in the
  surefire argLine, matching the existing per-package pattern.

checkstyle clean (etc/jgrapht_checks.xml); javadoc clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…weights

Adversarial review follow-ups on the Louvain PR:

- Zero-weight edges (2m == 0) made getModularity() return NaN because the
  modularity guard tested edgeSet().isEmpty() rather than the actual total
  weight. Guard on totalWeight > 0 instead; return 0 otherwise.
- Reject negative edge weights with IllegalArgumentException at compute time
  (modularity is undefined for negative weights), documented on the class.
- Replace the circular getModularityMatchesMeasurer test (getModularity
  delegates to the measurer) with a pinned exact value Q = 11/26 for the
  two-K4-plus-bridge graph; add regressions for zero-weight (no NaN) and
  negative-weight (throws).

17/17 tests pass; checkstyle clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@seilat

seilat commented May 30, 2026

Copy link
Copy Markdown
Owner Author

Superseded by #9 — Louvain + Leiden combined into a single clustering PR.

@seilat seilat closed this May 30, 2026
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.

1 participant