Skip to content

Commit 1485e48

Browse files
Merge pull request #9803 from IoannisPanagiotas/sllp-becomes-explicit
Sllp becomes explicit
2 parents 90941d4 + 4344f82 commit 1485e48

File tree

50 files changed

+2427
-318
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+2427
-318
lines changed
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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.sllpa;
21+
22+
import org.neo4j.gds.Algorithm;
23+
import org.neo4j.gds.api.Graph;
24+
import org.neo4j.gds.beta.pregel.ImmutablePregelResult;
25+
import org.neo4j.gds.beta.pregel.Pregel;
26+
import org.neo4j.gds.beta.pregel.PregelResult;
27+
import org.neo4j.gds.core.utils.progress.tasks.ProgressTracker;
28+
29+
import java.util.Optional;
30+
import java.util.concurrent.ExecutorService;
31+
32+
33+
public class SpeakerListenerLPA extends Algorithm<PregelResult> {
34+
public static final String LABELS_PROPERTY = "communityIds";
35+
36+
private final Graph graph;
37+
private final SpeakerListenerLPAConfig config;
38+
private final ExecutorService executorService;
39+
private final Optional<Long> seed;
40+
41+
public SpeakerListenerLPA(
42+
Graph graph,
43+
SpeakerListenerLPAConfig config,
44+
ExecutorService executorService,
45+
ProgressTracker progressTracker,
46+
Optional<Long> seed
47+
) {
48+
super(progressTracker);
49+
this.graph = graph;
50+
this.config = config;
51+
this.executorService = executorService;
52+
this.seed = seed;
53+
54+
}
55+
56+
@Override
57+
public PregelResult compute() {
58+
59+
var computation = new SpeakerListenerLPAComputation(seed);
60+
61+
var pregelResult = Pregel.create(
62+
graph,
63+
config,
64+
computation,
65+
executorService,
66+
progressTracker,
67+
terminationFlag
68+
).run();
69+
70+
this.progressTracker.endSubTask();
71+
72+
return ImmutablePregelResult.builder()
73+
.nodeValues(pregelResult.nodeValues())
74+
.didConverge(pregelResult.didConverge())
75+
.ranIterations(pregelResult.ranIterations())
76+
.build();
77+
}
78+
}

alpha/alpha-proc/src/main/java/org/neo4j/gds/pregel/SpeakerListenerLPA.java renamed to algo/src/main/java/org/neo4j/gds/sllpa/SpeakerListenerLPAComputation.java

Lines changed: 33 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -17,64 +17,51 @@
1717
* You should have received a copy of the GNU General Public License
1818
* along with this program. If not, see <http://www.gnu.org/licenses/>.
1919
*/
20-
package org.neo4j.gds.pregel;
20+
package org.neo4j.gds.sllpa;
2121

2222
import com.carrotsearch.hppc.LongArrayList;
2323
import com.carrotsearch.hppc.LongIntScatterMap;
2424
import com.carrotsearch.hppc.cursors.LongIntCursor;
25-
import org.neo4j.gds.mem.MemoryEstimateDefinition;
26-
import org.neo4j.gds.annotation.Configuration;
2725
import org.neo4j.gds.api.nodeproperties.ValueType;
2826
import org.neo4j.gds.beta.pregel.Messages;
2927
import org.neo4j.gds.beta.pregel.Pregel;
3028
import org.neo4j.gds.beta.pregel.PregelComputation;
31-
import org.neo4j.gds.beta.pregel.PregelProcedureConfig;
3229
import org.neo4j.gds.beta.pregel.PregelSchema;
33-
import org.neo4j.gds.beta.pregel.annotation.PregelProcedure;
3430
import org.neo4j.gds.beta.pregel.context.ComputeContext;
3531
import org.neo4j.gds.beta.pregel.context.InitContext;
36-
import org.neo4j.gds.core.CypherMapWrapper;
32+
import org.neo4j.gds.mem.MemoryEstimateDefinition;
3733
import org.neo4j.gds.utils.CloseableThreadLocal;
3834

3935
import java.util.Arrays;
4036
import java.util.Map;
37+
import java.util.Optional;
4138
import java.util.SplittableRandom;
4239

43-
@PregelProcedure(
44-
name = "gds.sllpa",
45-
description = "The Speaker Listener Label Propagation algorithm is a fast algorithm for finding overlapping communities in a graph."
46-
)
47-
public class SpeakerListenerLPA implements PregelComputation<SpeakerListenerLPA.SpeakerListenerLPAConfig> {
40+
import static org.neo4j.gds.sllpa.SpeakerListenerLPA.LABELS_PROPERTY;
4841

49-
public static final String LABELS_PROPERTY = "communityIds";
42+
class SpeakerListenerLPAComputation implements PregelComputation<SpeakerListenerLPAConfig> {
5043

5144
private final CloseableThreadLocal<SplittableRandom> random;
5245

53-
@SuppressWarnings("unused") // Needed for the @PregelProcedure annotation
54-
public SpeakerListenerLPA() {
55-
this(System.currentTimeMillis());
56-
}
57-
58-
public SpeakerListenerLPA(long seed) {
59-
var splittableRandom = new SplittableRandom(seed);
46+
SpeakerListenerLPAComputation(Optional<Long> seed) {
47+
var splittableRandom = new SplittableRandom(seed.orElse(System.currentTimeMillis()));
6048
random = CloseableThreadLocal.withInitial(splittableRandom::split);
6149
}
6250

6351
@Override
64-
public PregelSchema schema(SpeakerListenerLPAConfig config) {
65-
return new PregelSchema.Builder()
66-
.add(LABELS_PROPERTY, ValueType.LONG_ARRAY)
67-
.build();
68-
}
69-
52+
public void compute(ComputeContext<SpeakerListenerLPAConfig> context, Messages messages) {
53+
var labels = context.longArrayNodeValue(LABELS_PROPERTY);
7054

71-
@Override
72-
public MemoryEstimateDefinition estimateDefinition(boolean isAsynchronous) {
73-
return () -> Pregel.memoryEstimation(
74-
Map.of(LABELS_PROPERTY, ValueType.LONG_ARRAY),
75-
false,
76-
false
77-
);
55+
if (context.isInitialSuperstep()) {
56+
labels[0] = context.nodeId();
57+
context.sendToNeighbors(context.nodeId());
58+
} else if (context.superstep() < context.config().propagationSteps()) {
59+
listen(context, messages, labels);
60+
speak(context, labels);
61+
} else {
62+
listen(context, messages, labels);
63+
prune(context, labels);
64+
}
7865
}
7966

8067
@Override
@@ -85,25 +72,21 @@ public void init(InitContext<SpeakerListenerLPAConfig> context) {
8572
context.setNodeValue(LABELS_PROPERTY, initialLabels);
8673
}
8774

75+
8876
@Override
89-
public void close() {
90-
random.close();
77+
public PregelSchema schema(SpeakerListenerLPAConfig config) {
78+
return new PregelSchema.Builder()
79+
.add(LABELS_PROPERTY, ValueType.LONG_ARRAY)
80+
.build();
9181
}
9282

9383
@Override
94-
public void compute(ComputeContext<SpeakerListenerLPAConfig> context, Messages messages) {
95-
var labels = context.longArrayNodeValue(LABELS_PROPERTY);
96-
97-
if (context.isInitialSuperstep()) {
98-
labels[0] = context.nodeId();
99-
context.sendToNeighbors(context.nodeId());
100-
} else if (context.superstep() < context.config().propagationSteps()) {
101-
listen(context, messages, labels);
102-
speak(context, labels);
103-
} else {
104-
listen(context, messages, labels);
105-
prune(context, labels);
106-
}
84+
public MemoryEstimateDefinition estimateDefinition(boolean isAsynchronous) {
85+
return () -> Pregel.memoryEstimation(
86+
Map.of(LABELS_PROPERTY, ValueType.LONG_ARRAY),
87+
false,
88+
false
89+
);
10790
}
10891

10992
private void listen(
@@ -161,28 +144,8 @@ private void prune(ComputeContext<SpeakerListenerLPAConfig> context, long[] labe
161144
);
162145
}
163146

164-
165-
@Configuration
166-
public interface SpeakerListenerLPAConfig extends PregelProcedureConfig {
167-
168-
default double minAssociationStrength() {
169-
return 0.2;
170-
}
171-
172-
static SpeakerListenerLPAConfig of(CypherMapWrapper userConfig) {
173-
return new SpeakerListenerLPAConfigImpl(userConfig);
174-
}
175-
176-
@Override
177-
@Configuration.Ignore
178-
default boolean isAsynchronous() {
179-
return true;
180-
}
181-
182-
183-
@Configuration.Ignore
184-
default int propagationSteps() {
185-
return maxIterations() - 1;
186-
}
147+
@Override
148+
public void close() {
149+
random.close();
187150
}
188151
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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.sllpa;
21+
22+
import org.neo4j.gds.api.nodeproperties.ValueType;
23+
import org.neo4j.gds.beta.pregel.Pregel;
24+
import org.neo4j.gds.mem.MemoryEstimateDefinition;
25+
import org.neo4j.gds.mem.MemoryEstimation;
26+
import org.neo4j.gds.mem.MemoryEstimations;
27+
28+
import java.util.Map;
29+
30+
import static org.neo4j.gds.sllpa.SpeakerListenerLPA.LABELS_PROPERTY;
31+
32+
33+
public class SpeakerListenerLPAMemoryEstimateDefinition implements MemoryEstimateDefinition {
34+
35+
@Override
36+
public MemoryEstimation memoryEstimation() {
37+
return MemoryEstimations.builder()
38+
.add(Pregel.memoryEstimation(
39+
Map.of(LABELS_PROPERTY, ValueType.LONG_ARRAY),
40+
false,
41+
false
42+
)).build();
43+
}
44+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
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.sllpa;
21+
22+
import org.neo4j.gds.core.utils.progress.tasks.Task;
23+
import org.neo4j.gds.core.utils.progress.tasks.Tasks;
24+
25+
import java.util.List;
26+
27+
public class SpeakerListenerLPAProgressTrackerCreator {
28+
29+
public static Task progressTask(long nodeCount, int maxIterations,String taskName) {
30+
return Tasks.iterativeDynamic(
31+
taskName,
32+
() -> List.of(
33+
Tasks.leaf("Compute iteration", nodeCount),
34+
Tasks.leaf("Master compute iteration", nodeCount)
35+
),
36+
maxIterations
37+
);
38+
}
39+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
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.sllpa;
21+
22+
import org.junit.jupiter.api.Test;
23+
import org.neo4j.gds.utils.CloseableThreadLocal;
24+
25+
import java.lang.invoke.MethodHandles;
26+
import java.util.Optional;
27+
import java.util.SplittableRandom;
28+
29+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
30+
import static org.assertj.core.api.Assertions.fail;
31+
32+
class SpeakerListenerLPAComputationTest {
33+
34+
@Test
35+
void closesThreadLocal() {
36+
37+
var computation = new SpeakerListenerLPAComputation(Optional.empty());
38+
computation.close();
39+
40+
CloseableThreadLocal<SplittableRandom> threadLocal = null;
41+
try {
42+
//noinspection unchecked
43+
threadLocal = (CloseableThreadLocal<SplittableRandom>) MethodHandles
44+
.privateLookupIn(SpeakerListenerLPAComputation.class, MethodHandles.lookup())
45+
.findGetter(SpeakerListenerLPAComputation.class, "random", CloseableThreadLocal.class)
46+
.invoke(computation);
47+
} catch (Throwable e) {
48+
fail("couldn't inspect the field", e);
49+
}
50+
assertThatThrownBy(threadLocal::get).isInstanceOf(NullPointerException.class);
51+
}
52+
53+
}

0 commit comments

Comments
 (0)