Skip to content

Commit 99b19c2

Browse files
Correctly assign community volumes for weighted graphs
Co-authored-by: Veselin Nikolov <veselin.nikolov@neotechnology.com>
1 parent c449600 commit 99b19c2

File tree

4 files changed

+102
-15
lines changed

4 files changed

+102
-15
lines changed

algo/src/main/java/org/neo4j/gds/leiden/Leiden.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,9 @@ private void updateModularity(
277277
boolean localPhaseConverged,
278278
int iteration
279279
) {
280-
//will calculate modularity only if:
281-
// - the local phase has not converged (i.e., no swaps done)
282-
//- or we terminate in the first iteration (i.e., given seeding is optimal, graph is empty etc)
280+
// Will calculate modularity only if:
281+
// - the local phase has not converged (i.e., no swaps done)
282+
// - or we terminate in the first iteration (i.e., given seeding is optimal, graph is empty, etc)
283283
boolean shouldCalculateModularity = !localPhaseConverged || iteration == 0;
284284

285285
if (shouldCalculateModularity) {
@@ -329,7 +329,7 @@ private double initVolumes(
329329
rootGraph.forEachNode(nodeId -> {
330330
long communityId = initialCommunities.get(nodeId);
331331
progressTracker.logProgress();
332-
communityVolumes.addTo(communityId, rootGraph.degree(nodeId));
332+
communityVolumes.addTo(communityId, nodeVolumes.get(nodeId));
333333
return true;
334334
});
335335
progressTracker.endSubTask("Initialization");

algo/src/main/java/org/neo4j/gds/leiden/LocalMovePhase.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919
*/
2020
package org.neo4j.gds.leiden;
2121

22-
import org.apache.commons.lang3.mutable.MutableDouble;
2322
import org.neo4j.gds.api.Graph;
2423
import org.neo4j.gds.core.concurrency.RunWithConcurrency;
2524
import org.neo4j.gds.core.utils.mem.MemoryEstimation;
@@ -103,10 +102,9 @@ private LocalMovePhase(
103102
}
104103

105104
/**
106-
*
107105
* @return The new community count.
108106
*/
109-
public long run() {
107+
public void run() {
110108
HugeAtomicDoubleArray atomicCommunityVolumes = HugeAtomicDoubleArray.newArray(graph.nodeCount());
111109
graph.forEachNode(v -> {
112110
atomicCommunityVolumes.set(v, communityVolumes.get(v));
@@ -147,15 +145,11 @@ public long run() {
147145
swaps += task.swaps;
148146
}
149147

150-
MutableDouble aliveCommunities = new MutableDouble(graph.nodeCount());
151148
graph.forEachNode(v -> {
152149
communityVolumes.set(v, atomicCommunityVolumes.get(v));
153-
if (Double.compare(communityVolumes.get(v), 0.0) == 0) {
154-
aliveCommunities.decrement();
155-
}
156150
return true;
157151
});
158-
return aliveCommunities.longValue();
152+
159153
}
160-
154+
161155
}

algo/src/main/java/org/neo4j/gds/leiden/LocalMoveTask.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,11 @@ private void moveNodeToNewCommunity(
129129
long oldCommunityId = currentCommunities.get(nodeId);
130130
currentCommunities.set(nodeId, newCommunityId);
131131
communityVolumes.getAndAdd(newCommunityId, currentNodeVolume);
132-
communityVolumes.getAndAdd(oldCommunityId, -currentNodeVolume);
133-
132+
//do a atomic update to never go into negatives etc
133+
communityVolumes.update(oldCommunityId, (oldValue) -> {
134+
var diff = oldValue - currentNodeVolume;
135+
return Math.max(diff, 0.0);
136+
});
134137
swaps++;
135138

136139
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/*
2+
* Copyright (c) "Neo4j"
3+
* Neo4j Sweden AB [http://neo4j.com]
4+
*
5+
* This file is part of Neo4j.
6+
*
7+
* Neo4j is free software: you can redistribute it and/or modify
8+
* it under the terms of the GNU General Public License as published by
9+
* the Free Software Foundation, either version 3 of the License, or
10+
* (at your option) any later version.
11+
*
12+
* This program is distributed in the hope that it will be useful,
13+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15+
* GNU General Public License for more details.
16+
*
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
*/
20+
package org.neo4j.gds.leiden;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.neo4j.gds.Orientation;
24+
import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
25+
import org.neo4j.gds.extension.GdlExtension;
26+
import org.neo4j.gds.extension.GdlGraph;
27+
import org.neo4j.gds.extension.IdFunction;
28+
import org.neo4j.gds.extension.Inject;
29+
import org.neo4j.gds.extension.TestGraph;
30+
31+
import java.util.Arrays;
32+
33+
import static org.assertj.core.api.Assertions.assertThat;
34+
import static org.neo4j.gds.core.ProcedureConstants.TOLERANCE_DEFAULT;
35+
36+
@GdlExtension
37+
class LeidenWeightedCliqueTest {
38+
39+
@GdlGraph(orientation = Orientation.UNDIRECTED)
40+
private static final String DB_CYPHER =
41+
"CREATE" +
42+
"(a : Node), "+
43+
"(b : Node), "+
44+
"(c : Node), "+
45+
"(d : Node), "+
46+
"(e : Node), "+
47+
48+
"(a)-[:R {w: 0.1}]->(b),"+
49+
"(a)-[:R {w: 0.1}]->(c),"+
50+
"(a)-[:R {w: 0.1}]->(d),"+
51+
"(a)-[:R {w: 0.1}]->(e),"+
52+
53+
"(b)-[:R {w: 0.1}]->(c),"+
54+
"(b)-[:R {w: 0.1}]->(d),"+
55+
"(b)-[:R {w: 0.1}]->(e),"+
56+
57+
"(c)-[:R {w: 0.1}]->(d),"+
58+
"(c)-[:R {w: 0.1}]->(e),"+
59+
60+
"(d)-[:R {w: 0.1}]->(e)";
61+
62+
@Inject
63+
private TestGraph graph;
64+
65+
@Inject
66+
private IdFunction idFunction;
67+
68+
@Test
69+
void weightedLeiden() {
70+
var maxLevels = 10;
71+
72+
Leiden leiden = new Leiden(
73+
graph,
74+
maxLevels,
75+
1.0,
76+
0.01,
77+
true,
78+
19L,
79+
null,
80+
TOLERANCE_DEFAULT,
81+
4,
82+
ProgressTracker.NULL_TRACKER
83+
84+
);
85+
var leidenResult = leiden.compute();
86+
assertThat(Arrays.stream(leidenResult.communities().toArray()).distinct().count()).isEqualTo(1);
87+
88+
}
89+
90+
}

0 commit comments

Comments
 (0)