-
Notifications
You must be signed in to change notification settings - Fork 229
Add Louvain community detection algorithm #453
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Becheler
wants to merge
29
commits into
boostorg:develop
Choose a base branch
from
Becheler:feature/louvain-algorithm
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
29 commits
Select commit
Hold shift + click to select a range
374db7b
Add Louvain clustering algorithm
Becheler 6ef3267
adding louvain tests to jamfile
Becheler 76efd88
add some comments
Becheler de9b6a8
Delete scratch/benchmark/run_benchmark.sh
Becheler 385e8c8
Delete scratch/benchmark/bgl_louvain.cpp
Becheler 78d9225
PR review: fixed copyright, local optimization visibility, assertions…
Becheler 422d376
fix: URGB made generic
Becheler 6b278b8
adding LouvainQualityFunctionConcept
Becheler 28721e1
incremental versus non-incremental concepts
Becheler c5c9ac4
fix wrong namespace
Becheler 24002db
fix unused variables in concepts
Becheler a02bc0f
incremental and non incremental metrics can lead to different optimiz…
Becheler 0034d3f
Trigger CI
Becheler e8760cf
incremental and non incremental metrics can lead to different optimiz…
Becheler 7180cd6
fix: no hierarchy_t, free unfold function
Becheler fb61051
docs
Becheler af23880
index-based interals and contguous outputs labels
Becheler 967f47b
index-based interals and contguous outputs labels
Becheler bdedea7
fix dosctrsing
Becheler 4b8a584
specializing std::hash forbidden here
Becheler f04a9cc
quality functions passed as objects with named methods
Becheler 39c4863
default value for policy
Becheler a4e2ada
typo
Becheler ca528f5
updated documentation
Becheler 85f0174
fix: assertion on edge added
Becheler a846cde
fix: cleanup dead internal_weights
Becheler ef7933e
test: TRUST_AGGREGATED_Q path
Becheler 79523b6
fix: disentangle trust Q and track peak
Becheler b9ad3a8
benchmarks informed choice to trust aggregation as modularity preserv…
Becheler File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> | ||
| <!-- | ||
| Copyright (c) 2026 Arnaud Becheler | ||
|
|
||
| Distributed under the Boost Software License, Version 1.0. | ||
| (See accompanying file LICENSE_1_0.txt or copy at | ||
| http://www.boost.org/LICENSE_1_0.txt) | ||
| --> | ||
| <HTML> | ||
| <Head> | ||
| <Title>Boost Graph Library: Louvain Clustering</Title> | ||
| </Head> | ||
| <BODY BGCOLOR="#ffffff" LINK="#0000ee" TEXT="#000000" VLINK="#551a8b" | ||
| ALINK="#ff0000"> | ||
| <IMG SRC="../../../boost.png" | ||
| ALT="C++ Boost" width="277" height="86"> | ||
|
|
||
| <BR Clear> | ||
|
|
||
| <H1><A NAME="sec:louvain-clustering"></A> | ||
| <TT>louvain_clustering</TT> | ||
| </H1> | ||
|
|
||
| <PRE> | ||
| template <typename QualityFunction = newman_and_girvan, | ||
| typename Graph, typename ComponentMap, | ||
| typename WeightMap, typename URBG> | ||
| typename property_traits<WeightMap>::value_type | ||
| louvain_clustering(const Graph& g, | ||
| ComponentMap components, | ||
| const WeightMap& w, | ||
| URBG&& gen, | ||
| QualityFunction f = QualityFunction{}, | ||
| typename property_traits<WeightMap>::value_type min_improvement_inner = 0, | ||
| typename property_traits<WeightMap>::value_type min_improvement_outer = 0); | ||
| </PRE> | ||
|
|
||
| <P> | ||
| This algorithm implements the Louvain method for community detection | ||
| [<a href="#references">1</a>]. It finds a partition of the vertices into communities | ||
| that approximately maximizes a quality function (by default, | ||
| <a href="louvain_quality_functions.html#newman_and_girvan">Newman–Girvan | ||
| modularity</a>). | ||
|
|
||
| <P>The algorithm alternates two phases: | ||
| <OL> | ||
| <LI><B>Local optimization.</B> Each vertex is moved to the neighboring | ||
| community that yields the largest improvement in the quality function. | ||
| Vertices are visited in random order and the process repeats until no | ||
| single-vertex move improves the quality by more than | ||
| <TT>min_improvement_inner</TT>. | ||
|
|
||
| <LI><B>Aggregation.</B> The graph is contracted by collapsing each | ||
| community into a single super-vertex. Edge weights between | ||
| super-vertices are the sums of the original inter-community edge | ||
| weights and self-loops carry the total intra-community weight. | ||
| </OL> | ||
|
|
||
| <P> These two phases are applied repeatedly on the coarsened graph, | ||
| discovering communities of communities, until | ||
| the quality improvement between successive levels falls below | ||
| <TT>min_improvement_outer</TT>, or the graph can no longer be | ||
| coarsened. | ||
|
|
||
| <P> Once every level has converged, the algorithm iterates | ||
| from the coarsest aggregated graph down to the original graph to | ||
| trace assignment of vertices to communities to produce the final | ||
| community label written into <TT>components</TT>. | ||
|
|
||
| <P> The speed of the local optimization phase depends on the quality | ||
| function's interface. A quality function that only models | ||
| <a href="louvain_quality_functions.html#base_concept"> | ||
| <TT>GraphPartitionQualityFunctionConcept</TT></a> requires a full | ||
| O(V+E) recomputation of the quality for every candidate vertex move. | ||
| A quality function that also models | ||
| <a href="louvain_quality_functions.html#incremental_concept"> | ||
| <TT>GraphPartitionQualityFunctionIncrementalConcept</TT></a> | ||
| evaluates each candidate move in O(1) using incremental | ||
| bookkeeping, making the total cost per vertex O(degree). | ||
| The algorithm detects which interface is available at | ||
| compile time and selects the appropriate code path automatically. | ||
|
|
||
| <H3>Where Defined</H3> | ||
|
|
||
| <P> | ||
| <a href="../../../boost/graph/louvain_clustering.hpp"><TT>boost/graph/louvain_clustering.hpp</TT></a> | ||
|
|
||
| <H3>Parameters</H3> | ||
|
|
||
| IN: <tt>const Graph& g</tt> | ||
| <blockquote> | ||
| An undirected graph. Must model | ||
| <a href="VertexListGraph.html">Vertex List Graph</a> and | ||
| <a href="IncidenceGraph.html">Incidence Graph</a>. | ||
| The graph is not modified by the algorithm. | ||
| Passing a directed graph produces a compile-time error. | ||
| </blockquote> | ||
|
|
||
| OUT: <tt>ComponentMap components</tt> | ||
| <blockquote> | ||
| Records the community each vertex belongs to. After the call, | ||
| <tt>get(components, v)</tt> returns a contiguous integer label | ||
| in the range [0, <i>k</i>) where <i>k</i> is the number of | ||
| communities found. Two vertices with the same label are in the | ||
| same community. This matches the convention used by | ||
| <a href="connected_components.html"><tt>connected_components</tt></a>.<br> | ||
| Must model | ||
| <a href="../../property_map/doc/ReadWritePropertyMap.html">Read/Write | ||
| Property Map</a> with the graph's vertex descriptor as key type | ||
| and an integer type (e.g. <tt>std::size_t</tt>) as value type. | ||
| </blockquote> | ||
|
|
||
| IN: <tt>const WeightMap& w</tt> | ||
| <blockquote> | ||
| Edge weights. Must model | ||
| <a href="../../property_map/doc/ReadablePropertyMap.html">Readable | ||
| Property Map</a> with the graph's edge descriptor as key type. | ||
| Weights must be non-negative. | ||
| </blockquote> | ||
|
|
||
| IN: <tt>URBG&& gen</tt> | ||
jeremy-murphy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <blockquote> | ||
| A random number generator used to shuffle the vertex processing | ||
| order at each pass. Any type meeting the C++ | ||
| <i>UniformRandomBitGenerator</i> requirements works | ||
| (e.g. <tt>std::mt19937</tt>). | ||
| </blockquote> | ||
|
|
||
| IN: <tt>QualityFunction f</tt> | ||
| <blockquote> | ||
| An instance of the quality function to use for evaluating and | ||
| incrementally updating partition quality.<br> | ||
| <b>Default:</b> <tt>QualityFunction{}</tt> | ||
| </blockquote> | ||
|
|
||
| IN: <tt>weight_type min_improvement_inner</tt> | ||
jeremy-murphy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| <blockquote> | ||
| The inner loop (local optimization) stops when a full pass over | ||
| all vertices improves quality by less than this value.<br> | ||
| <b>Default:</b> <tt>0</tt> | ||
| </blockquote> | ||
|
|
||
| IN: <tt>weight_type min_improvement_outer</tt> | ||
| <blockquote> | ||
| The outer loop (aggregation) stops when quality improves by less | ||
| than this value between successive levels.<br> | ||
| <b>Default:</b> <tt>0</tt> | ||
| </blockquote> | ||
|
|
||
| <H3>Template Parameters</H3> | ||
|
|
||
| <tt>QualityFunction</tt> | ||
| <blockquote> | ||
| The partition quality metric to maximize. Must model | ||
| <a href="louvain_quality_functions.html#base_concept"> | ||
| <tt>GraphPartitionQualityFunctionConcept</tt></a>. If it also models | ||
| <a href="louvain_quality_functions.html#incremental_concept"> | ||
| <tt>GraphPartitionQualityFunctionIncrementalConcept</tt></a>, the | ||
| faster incremental code path is selected automatically.<br> | ||
| <b>Default:</b> | ||
| <tt><a href="louvain_quality_functions.html#newman_and_girvan">newman_and_girvan</a></tt> | ||
| </blockquote> | ||
|
|
||
| <H3>Return Value</H3> | ||
| <P>The quality (e.g. modularity) of the best partition found. | ||
| For Newman–Girvan modularity this is a value in | ||
| [−0.5, 1). | ||
|
|
||
| <H3>Complexity</H3> | ||
| <P>With the incremental quality function (the default), each local | ||
| optimization pass costs O(E) since every vertex is visited once and | ||
| each visit scans its neighbors. With a non-incremental quality function, | ||
| each candidate move requires a full O(V+E) traversal, making each pass | ||
| O(E · (V+E)). The number of passes per level and the | ||
| number of aggregation levels are both small in practice, so the | ||
| incremental path typically runs in O(E log V) overall on | ||
| sparse graphs. | ||
|
|
||
| <H3>Preconditions</H3> | ||
| <UL> | ||
| <LI>The graph must be undirected (enforced at compile time). | ||
| <LI>Edge weights must be non-negative. | ||
| <LI>The graph must have a <TT>vertex_index</TT> property mapping | ||
| vertices to contiguous integers in | ||
| [0, <TT>num_vertices(g)</TT>). | ||
| </UL> | ||
|
|
||
| <H3>Example</H3> | ||
| <PRE> | ||
| #include <boost/graph/adjacency_list.hpp> | ||
| #include <boost/graph/louvain_clustering.hpp> | ||
| #include <random> | ||
| #include <iostream> | ||
|
|
||
| int main() | ||
| { | ||
| using Graph = boost::adjacency_list< | ||
| boost::vecS, boost::vecS, boost::undirectedS, | ||
| boost::no_property, | ||
| boost::property<boost::edge_weight_t, double>>; | ||
|
|
||
| // Two triangles connected by a weak bridge | ||
| Graph g(6); | ||
| boost::add_edge(0, 1, 1.0, g); | ||
| boost::add_edge(1, 2, 1.0, g); | ||
| boost::add_edge(0, 2, 1.0, g); | ||
| boost::add_edge(3, 4, 1.0, g); | ||
| boost::add_edge(4, 5, 1.0, g); | ||
| boost::add_edge(3, 5, 1.0, g); | ||
| boost::add_edge(2, 3, 0.1, g); | ||
|
|
||
| std::vector<std::size_t> communities(boost::num_vertices(g)); | ||
| auto cmap = boost::make_iterator_property_map( | ||
| communities.begin(), boost::get(boost::vertex_index, g)); | ||
|
|
||
| std::mt19937 rng(42); | ||
| double Q = boost::louvain_clustering( | ||
| g, cmap, boost::get(boost::edge_weight, g), rng); | ||
|
|
||
| std::cout << "Modularity: " << Q << "\n"; | ||
| for (auto v : boost::make_iterator_range(boost::vertices(g))) | ||
| std::cout << " vertex " << v | ||
| << " -> community " << boost::get(cmap, v) << "\n"; | ||
| } | ||
| </PRE> | ||
|
|
||
| <H3>See Also</H3> | ||
| <P> | ||
| <a href="louvain_quality_functions.html">Louvain Quality Function Concepts</a>, | ||
| <a href="bc_clustering.html"><TT>betweenness_centrality_clustering</TT></a> | ||
|
|
||
| <H3>References</H3> | ||
| <a name="references"></a> | ||
| <P>[1] V. D. Blondel, J.‑L. Guillaume, | ||
| R. Lambiotte, and E. Lefebvre, | ||
| “Fast unfolding of communities in large networks,” | ||
| <i>Journal of Statistical Mechanics: Theory and Experiment</i>, | ||
| vol. 2008, no. 10, P10008, 2008. | ||
| <a href="https://doi.org/10.1088/1742-5468/2008/10/P10008">doi:10.1088/1742-5468/2008/10/P10008</a> | ||
|
|
||
| <P>[2] V. A. Traag, L. Waltman, and | ||
| N. J. van Eck, | ||
| “From Louvain to Leiden: guaranteeing well-connected communities,” | ||
| <i>Scientific Reports</i>, vol. 9, 5233, 2019. | ||
| <a href="https://doi.org/10.1038/s41598-019-41695-z">doi:10.1038/s41598-019-41695-z</a> | ||
|
|
||
| <BR> | ||
| <HR> | ||
| <TABLE> | ||
| <TR valign=top> | ||
| <TD nowrap>Copyright © 2026</TD><TD> | ||
| Arnaud Becheler | ||
| </TD></TR></TABLE> | ||
|
|
||
| </BODY> | ||
| </HTML> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The algorithm has the additional requirement that vertices are copyable, hashable etc., as they're internally stored in unordered_sets.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I have been changing the vertices handling in this aspect because it was not friendly with some types of graphs. The interface now takes a VertexIndexMap but I still have to commit those changes, sorry 😓
I will update the documentation in that sense once I merged the new stuff
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still an open issue or resolved?