Skip to content

Commit 594dc8d

Browse files
Bridges report size of components left
Co-authored-by: Veselin Nikolov <veselin.nikolov@neotechnology.com>
1 parent b984867 commit 594dc8d

File tree

18 files changed

+190
-60
lines changed

18 files changed

+190
-60
lines changed

algo/src/main/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinition.java

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

22-
import org.neo4j.gds.bridges.Bridges;
2322
import org.neo4j.gds.collections.ha.HugeLongArray;
2423
import org.neo4j.gds.collections.ha.HugeObjectArray;
2524
import org.neo4j.gds.mem.Estimate;
@@ -32,7 +31,7 @@ public class ArticulationPointsMemoryEstimateDefinition implements MemoryEstimat
3231
@Override
3332
public MemoryEstimation memoryEstimation() {
3433

35-
var builder = MemoryEstimations.builder(Bridges.class);
34+
var builder = MemoryEstimations.builder(ArticulationPoints.class);
3635
builder
3736
.perNode("tin", HugeLongArray::memoryEstimation)
3837
.perNode("low", HugeLongArray::memoryEstimation)
@@ -46,7 +45,6 @@ public MemoryEstimation memoryEstimation() {
4645
HugeObjectArray.memoryEstimation(relationshipCount, Estimate.sizeOfInstance(ArticulationPoints.StackEvent.class))
4746
);
4847

49-
5048
}));
5149

5250
return builder.build();

algo/src/main/java/org/neo4j/gds/bridges/Bridge.java

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

22-
public record Bridge(long from, long to) {
22+
public record Bridge(long from, long to,long[] remainingSizes) {
2323

24-
static Bridge create(long from, long to){
25-
return new Bridge(Math.min(from,to), Math.max(from,to));
24+
static Bridge create(long from, long to, long[] remainingSizes){
25+
return new Bridge(Math.min(from,to), Math.max(from,to), remainingSizes);
2626
}
2727
}

algo/src/main/java/org/neo4j/gds/bridges/Bridges.java

Lines changed: 37 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828

2929
import java.util.ArrayList;
3030
import java.util.List;
31+
import java.util.Optional;
3132
import java.util.concurrent.atomic.AtomicBoolean;
33+
import java.util.function.BiConsumer;
3234

3335
public class Bridges extends Algorithm<BridgeResult> {
3436

@@ -39,14 +41,26 @@ public class Bridges extends Algorithm<BridgeResult> {
3941
private long timer;
4042
private long stackIndex = -1;
4143
private List<Bridge> result = new ArrayList<>();
44+
private final Optional<TreeSizeTracker> treeSizeTracker;
4245

43-
public Bridges(Graph graph, ProgressTracker progressTracker){
46+
public static Bridges create(Graph graph, ProgressTracker progressTracker, boolean shouldComputeComponents){
47+
48+
if (shouldComputeComponents) {
49+
var treeSizeTracker = new TreeSizeTracker(graph.nodeCount());
50+
return new Bridges(graph, progressTracker, Optional.of(treeSizeTracker));
51+
}else{
52+
return new Bridges(graph,progressTracker,Optional.empty());
53+
}
54+
}
55+
56+
private Bridges(Graph graph, ProgressTracker progressTracker, Optional<TreeSizeTracker> treeSizeTracker){
4457
super(progressTracker);
4558

4659
this.graph = graph;
4760
this.visited = new BitSet(graph.nodeCount());
4861
this.tin = HugeLongArray.newArray(graph.nodeCount());
4962
this.low = HugeLongArray.newArray(graph.nodeCount());
63+
this.treeSizeTracker = treeSizeTracker;
5064
}
5165

5266
@Override
@@ -59,37 +73,50 @@ public BridgeResult compute() {
5973
//each edge may have at most one event to the stack at the same time
6074
var stack = HugeObjectArray.newArray(StackEvent.class, graph.relationshipCount());
6175

76+
BiConsumer<Long,Long> onLastChildVisit = (treeSizeTracker.isPresent()) ? treeSizeTracker.get()::recordTreeChild : (a,b)->{};
77+
int listIndex=0;
6278
var n = graph.nodeCount();
63-
for (int i = 0; i < n; ++i) {
64-
if (!visited.get(i))
65-
dfs(i, stack);
79+
for (long i = 0; i < n; ++i) {
80+
if (!visited.get(i)) {
81+
dfs(i, stack,onLastChildVisit);
82+
83+
if (treeSizeTracker.isPresent()) {
84+
var tracker =treeSizeTracker.get();
85+
var listEndIndex = result.size();
86+
for (var j = listIndex; j < listEndIndex; ++j) {
87+
var currentBridge = result.get(j);
88+
var remainingSizes = tracker.recordBridge(currentBridge.to(),i);
89+
result.set(j,new Bridge(currentBridge.from(), currentBridge.to(),remainingSizes));
90+
}
91+
listIndex = listEndIndex;
92+
}
93+
}
6694
}
6795
progressTracker.endSubTask("Bridges");
6896
return new BridgeResult(result);
6997

7098
}
7199

72-
73-
74-
private void dfs(long node, HugeObjectArray<StackEvent> stack) {
100+
private void dfs(long node, HugeObjectArray<StackEvent> stack, BiConsumer<Long,Long> onLastChildVisit) {
75101
stack.set(++stackIndex, StackEvent.upcomingVisit(node,-1));
76102
while (stackIndex >= 0) {
77103
var stackEvent = stack.get(stackIndex--);
78-
visitEvent(stackEvent, stack);
104+
visitEvent(stackEvent, stack,onLastChildVisit);
79105
}
80106
progressTracker.logProgress();
81107
}
82108

83-
private void visitEvent(StackEvent event, HugeObjectArray<StackEvent> stack) {
109+
private void visitEvent(StackEvent event, HugeObjectArray<StackEvent> stack, BiConsumer<Long,Long> onLastChildVisit) {
84110
if (event.lastVisit()) {
85111
var to = event.eventNode();
86112
var v = event.triggerNode();
87113
var lowV = low.get(v);
88114
var lowTo = low.get(to);
89115
low.set(v, Math.min(lowV, lowTo));
90116
var tinV = tin.get(v);
117+
onLastChildVisit.accept(v,to);
91118
if (lowTo > tinV) {
92-
result.add(new Bridge(v, to));
119+
result.add(new Bridge(v, to, null));
93120
}
94121
progressTracker.logProgress();
95122
return;

algo/src/main/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinition.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@
2828
import org.neo4j.gds.mem.MemoryRange;
2929

3030
public class BridgesMemoryEstimateDefinition implements MemoryEstimateDefinition {
31+
32+
private final boolean shouldComputeComponents;
33+
34+
public BridgesMemoryEstimateDefinition(boolean shouldComputeComponents) {
35+
this.shouldComputeComponents = shouldComputeComponents;
36+
}
37+
3138
@Override
3239
public MemoryEstimation memoryEstimation() {
3340

@@ -38,6 +45,13 @@ public MemoryEstimation memoryEstimation() {
3845
.perNode("visited", Estimate::sizeOfBitset)
3946
.perNode("bridge", (v)-> v * Estimate.sizeOfInstance(Bridge.class));
4047

48+
if (shouldComputeComponents){
49+
builder.add(
50+
"component split",
51+
MemoryEstimations.builder(TreeSizeTracker.class)
52+
.perNode("treeSize", HugeLongArray::memoryEstimation)
53+
.build());
54+
}
4155
builder.rangePerGraphDimension("stack", ((graphDimensions, concurrency) -> {
4256
long relationshipCount = graphDimensions.relCountUpperBound();
4357
return MemoryRange.of(
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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.bridges;
21+
22+
import org.neo4j.gds.collections.ha.HugeLongArray;
23+
24+
class TreeSizeTracker {
25+
26+
private final HugeLongArray subTreeSize;
27+
28+
TreeSizeTracker(long nodeCount){
29+
subTreeSize = HugeLongArray.newArray(nodeCount);
30+
subTreeSize.setAll( v -> 1);
31+
}
32+
void recordTreeChild(long parent, long child) {
33+
subTreeSize.addTo(parent,subTreeSize.get(child));
34+
}
35+
36+
public long[] recordBridge( long child, long root) {
37+
var childSubTree = subTreeSize.get(child);
38+
var rootSubTree= subTreeSize.get(root) - childSubTree;
39+
return new long[]{rootSubTree, childSubTree};
40+
}
41+
42+
}

algo/src/test/java/org/neo4j/gds/articulationpoints/ArticulationPointsMemoryEstimateDefinitionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,6 @@ void shouldEstimateMemoryAccurately() {
3030

3131
MemoryEstimationAssert.assertThat(memoryEstimation)
3232
.memoryRange(100, 6000, new Concurrency(1))
33-
.hasSameMinAndMaxEqualTo(218752);
33+
.hasSameMinAndMaxEqualTo(218760);
3434
}
3535
}

algo/src/test/java/org/neo4j/gds/bridges/BridgesLargestTest.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,31 +74,32 @@ class BridgesLargestTest {
7474
@Inject
7575
private TestGraph graph;
7676

77-
private Bridge bridge(String from, String to) {
78-
return new Bridge(graph.toOriginalNodeId(from), graph.toOriginalNodeId(to));
77+
private Bridge bridge(String from, String to, long[] remainingSizes) {
78+
return new Bridge(graph.toOriginalNodeId(from), graph.toOriginalNodeId(to), remainingSizes);
7979
}
8080

8181

8282
@Test
8383
void shouldFindAllBridges() {
84-
var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER);
84+
var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,true);
8585

8686
var result = bridges.compute().bridges().stream()
8787
.map(b -> new Bridge(
8888
graph.toOriginalNodeId(b.from()),
89-
graph.toOriginalNodeId(b.to())
89+
graph.toOriginalNodeId(b.to()),
90+
b.remainingSizes()
9091
)).toList();
9192

92-
9393
assertThat(result)
9494
.isNotNull()
95+
.usingRecursiveFieldByFieldElementComparator()
9596
.containsExactlyInAnyOrder(
96-
bridge("a1", "a2"),
97-
bridge("a3", "a4"),
98-
bridge("a3", "a7"),
99-
bridge("a7", "a8"),
100-
bridge("a10", "a11"),
101-
bridge("a14", "a13")
97+
bridge("a1", "a2", new long[]{1,1}),
98+
bridge("a3", "a4", new long[]{3,1}),
99+
bridge("a3", "a7", new long[]{2,2}),
100+
bridge("a7", "a8", new long[]{3,1}),
101+
bridge("a10", "a11", new long[]{5,4}),
102+
bridge("a14", "a13", new long[]{8,1})
102103
);
103104
}
104105
}

algo/src/test/java/org/neo4j/gds/bridges/BridgesMemoryEstimateDefinitionTest.java

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,31 @@
1919
*/
2020
package org.neo4j.gds.bridges;
2121

22-
import org.junit.jupiter.api.Test;
22+
import org.junit.jupiter.params.ParameterizedTest;
23+
import org.junit.jupiter.params.provider.Arguments;
24+
import org.junit.jupiter.params.provider.MethodSource;
2325
import org.neo4j.gds.assertions.MemoryEstimationAssert;
2426
import org.neo4j.gds.core.concurrency.Concurrency;
2527

28+
import java.util.stream.Stream;
29+
2630
class BridgesMemoryEstimateDefinitionTest {
2731

28-
@Test
29-
void shouldEstimateMemoryAccurately() {
30-
var memoryEstimation = new BridgesMemoryEstimateDefinition().memoryEstimation();
32+
static Stream<Arguments> memoryEstimationTuples() {
33+
return Stream.of(
34+
Arguments.of(false, 221064L),
35+
Arguments.of(true, 221920L)
36+
);
37+
}
38+
39+
@ParameterizedTest
40+
@MethodSource("memoryEstimationTuples")
41+
void shouldEstimateMemoryAccurately(boolean should, long expectedMemory) {
42+
var memoryEstimation = new BridgesMemoryEstimateDefinition(should).memoryEstimation();
3143

3244
MemoryEstimationAssert.assertThat(memoryEstimation)
3345
.memoryRange(100, 6000, new Concurrency(1))
34-
.hasSameMinAndMaxEqualTo(221056L);
46+
.hasSameMinAndMaxEqualTo(expectedMemory);
3547
}
48+
3649
}

algo/src/test/java/org/neo4j/gds/bridges/SmallBridgesTest.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,15 +65,32 @@ class GraphWithBridges {
6565

6666

6767
@Test
68-
void shouldFindBridges() {
69-
var bridges = new Bridges(graph, ProgressTracker.NULL_TRACKER);
68+
void shouldFindJustBridges() {
69+
var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,false);
70+
var result = bridges.compute().bridges();
71+
72+
assertThat(result)
73+
.isNotNull()
74+
.usingRecursiveFieldByFieldElementComparator()
75+
.containsExactlyInAnyOrder(
76+
Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d"),null),
77+
Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"),null)
78+
);
79+
}
80+
81+
82+
83+
@Test
84+
void shouldFindBridgesAndComponentSizes() {
85+
var bridges = Bridges.create(graph, ProgressTracker.NULL_TRACKER,true);
7086
var result = bridges.compute().bridges();
7187

7288
assertThat(result)
7389
.isNotNull()
90+
.usingRecursiveFieldByFieldElementComparator()
7491
.containsExactlyInAnyOrder(
75-
Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d")),
76-
Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"))
92+
Bridge.create(graph.toMappedNodeId("a"), graph.toMappedNodeId("d"),new long[]{3,2}),
93+
Bridge.create(graph.toMappedNodeId("d"), graph.toMappedNodeId("e"),new long[]{4,1})
7794
);
7895
}
7996

@@ -84,7 +101,7 @@ void shouldLogProgress(){
84101
var log = new GdsTestLog();
85102
var progressTracker = new TaskProgressTracker(progressTask, log, new Concurrency(1), EmptyTaskRegistryFactory.INSTANCE);
86103

87-
var bridges = new Bridges(graph, progressTracker);
104+
var bridges = Bridges.create(graph, progressTracker,false);
88105
bridges.compute();
89106

90107
Assertions.assertThat(log.getMessages(TestLog.INFO))
@@ -130,7 +147,7 @@ class GraphWithoutBridges {
130147

131148
@Test
132149
void shouldFindBridges() {
133-
var bridges = new Bridges(graph,ProgressTracker.NULL_TRACKER);
150+
var bridges = Bridges.create(graph,ProgressTracker.NULL_TRACKER,false);
134151
var result = bridges.compute().bridges();
135152

136153
assertThat(result)

applications/algorithms/centrality/src/main/java/org/neo4j/gds/applications/algorithms/centrality/CentralityAlgorithms.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,12 @@ public BetwennessCentralityResult betweennessCentrality(
181181
);
182182
}
183183

184-
BridgeResult bridges(Graph graph, AlgoBaseConfig configuration) {
184+
BridgeResult bridges(Graph graph, AlgoBaseConfig configuration, boolean shouldComputeComponents) {
185185

186186
var task = BridgeProgressTaskCreator.progressTask(graph.nodeCount());
187187
var progressTracker = progressTrackerCreator.createProgressTracker(configuration, task);
188188

189-
var algorithm = new Bridges(graph, progressTracker);
189+
var algorithm = Bridges.create(graph, progressTracker,shouldComputeComponents);
190190

191191
return algorithmMachinery.runAlgorithmsAndManageProgressTracker(
192192
algorithm,

0 commit comments

Comments
 (0)