Skip to content

Commit 3d9b8e4

Browse files
committed
Handle empty graphs in path finding compute facade
1 parent c84c97f commit 3d9b8e4

File tree

9 files changed

+489
-14
lines changed

9 files changed

+489
-14
lines changed

algo/src/main/java/org/neo4j/gds/dag/topologicalsort/TopologicalSortResult.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@
1919
*/
2020
package org.neo4j.gds.dag.topologicalsort;
2121

22-
import org.neo4j.gds.collections.haa.HugeAtomicDoubleArray;
2322
import org.neo4j.gds.collections.ha.HugeLongArray;
23+
import org.neo4j.gds.collections.haa.HugeAtomicDoubleArray;
2424

2525
import java.util.Optional;
2626
import java.util.concurrent.atomic.AtomicLong;
@@ -29,6 +29,9 @@
2929
* An implementation of the result with message queues instead of synchronization.
3030
*/
3131
public class TopologicalSortResult {
32+
33+
public static final TopologicalSortResult EMPTY = new TopologicalSortResult(0, Optional.empty());
34+
3235
private final HugeLongArray sortedNodes;
3336
private final AtomicLong addIndex = new AtomicLong(0);
3437
private final Optional<HugeAtomicDoubleArray> maxSourceDistances;

algo/src/main/java/org/neo4j/gds/paths/bellmanford/BellmanFordResult.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,11 @@ public record BellmanFordResult(
2525
PathFindingResult shortestPaths,
2626
PathFindingResult negativeCycles,
2727
boolean containsNegativeCycle
28-
) {}
28+
) {
29+
public static final BellmanFordResult EMPTY = new BellmanFordResult(
30+
PathFindingResult.EMPTY,
31+
PathFindingResult.EMPTY,
32+
false
33+
);
34+
35+
}

algo/src/main/java/org/neo4j/gds/paths/dijkstra/PathFindingResult.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import java.util.stream.Stream;
3131

3232
public class PathFindingResult {
33+
34+
public static final PathFindingResult EMPTY = new PathFindingResult(Stream.empty());
35+
3336
private final Stream<PathResult> paths;
3437

3538
private final Runnable closeStreamAction;

algo/src/main/java/org/neo4j/gds/pricesteiner/PrizeSteinerTreeResult.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ public record PrizeSteinerTreeResult(
3232
public static final long PRUNED=-2;
3333
public static final long ROOT=-1;
3434

35-
35+
public static final PrizeSteinerTreeResult EMPTY = new PrizeSteinerTreeResult(
36+
HugeLongArray.newArray(0),
37+
HugeDoubleArray.newArray(0),
38+
0,
39+
0.0,
40+
0.0
41+
);
3642

3743
}

algo/src/main/java/org/neo4j/gds/spanningtree/SpanningTree.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,15 @@
3131
*/
3232
public class SpanningTree {
3333

34+
public static final SpanningTree EMPTY = new SpanningTree(
35+
-1L,
36+
0,
37+
0,
38+
HugeLongArray.newArray(0),
39+
n -> 0.0,
40+
0.0
41+
);
42+
3443
final long head;
3544
final long nodeCount;
3645
final long effectiveNodeCount;
@@ -54,7 +63,6 @@ public SpanningTree(
5463
this.totalWeight = totalWeight;
5564
}
5665

57-
5866
public long effectiveNodeCount() {
5967
return effectiveNodeCount;
6068
}

algo/src/main/java/org/neo4j/gds/steiner/SteinerTreeResult.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,13 @@ public record SteinerTreeResult(
2828
double totalCost,
2929
long effectiveNodeCount,
3030
long effectiveTargetNodesCount
31-
) {}
31+
) {
32+
33+
public static final SteinerTreeResult EMPTY = new SteinerTreeResult(
34+
HugeLongArray.newArray(0),
35+
HugeDoubleArray.newArray(0),
36+
0.0,
37+
0,
38+
0
39+
);
40+
}

algorithms-compute-facade/src/main/java/org/neo4j/gds/pathfinding/PathFindingComputeFacade.java

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.neo4j.gds.async.AsyncAlgorithmCaller;
2929
import org.neo4j.gds.collections.ha.HugeLongArray;
3030
import org.neo4j.gds.collections.haa.HugeAtomicLongArray;
31+
import org.neo4j.gds.core.utils.paged.ParalleLongPageCreator;
3132
import org.neo4j.gds.core.utils.progress.JobId;
3233
import org.neo4j.gds.dag.longestPath.DagLongestPath;
3334
import org.neo4j.gds.dag.longestPath.DagLongestPathParameters;
@@ -115,6 +116,10 @@ public CompletableFuture<Stream<AllShortestPathsStreamResult>> allShortestPaths(
115116
AllShortestPathsParameters parameters,
116117
JobId jobId
117118
) {
119+
if (graph.isEmpty()) {
120+
return CompletableFuture.completedFuture(Stream.empty());
121+
}
122+
118123
// Create ProgressTracker
119124
// `allShortestPaths` doesn't use progress tracker (yet 🤔)
120125
var progressTracker = progressTrackerFactory.nullTracker();
@@ -142,6 +147,11 @@ public CompletableFuture<BellmanFordResult> bellmanFord(
142147
JobId jobId,
143148
boolean logProgress
144149
) {
150+
// If the input graph is empty return a completed future with empty result
151+
if (graph.isEmpty()) {
152+
return CompletableFuture.completedFuture(BellmanFordResult.EMPTY);
153+
}
154+
145155
// Create ProgressTracker
146156
var progressTracker = progressTrackerFactory.create(
147157
BellmanFordProgressTask.create(),
@@ -174,6 +184,11 @@ public CompletableFuture<HugeLongArray> breadthFirstSearch(
174184
JobId jobId,
175185
boolean logProgress
176186
) {
187+
// If the input graph is empty return a completed future with empty result
188+
if (graph.isEmpty()) {
189+
return CompletableFuture.completedFuture(HugeLongArray.newArray(0L));
190+
}
191+
177192
// Create ProgressTracker
178193
var progressTracker = progressTrackerFactory.create(
179194
BFSProgressTask.create(),
@@ -211,6 +226,11 @@ public CompletableFuture<PathFindingResult> deltaStepping(
211226
JobId jobId,
212227
boolean logProgress
213228
) {
229+
// If the input graph is empty return a completed future with empty result
230+
if (graph.isEmpty()) {
231+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
232+
}
233+
214234
// Create ProgressTracker
215235
var progressTracker = progressTrackerFactory.create(
216236
DeltaSteppingProgressTask.create(),
@@ -235,8 +255,12 @@ public CompletableFuture<HugeLongArray> depthFirstSearch(
235255
TraversalParameters parameters,
236256
JobId jobId,
237257
boolean logProgress
238-
239258
) {
259+
// If the input graph is empty return a completed future with empty result
260+
if (graph.isEmpty()) {
261+
return CompletableFuture.completedFuture(HugeLongArray.newArray(0L));
262+
}
263+
240264
// Create ProgressTracker
241265
var progressTracker = progressTrackerFactory.create(
242266
DFSProgressTask.create(),
@@ -272,6 +296,11 @@ public CompletableFuture<SpanningTree> kSpanningTree(
272296
JobId jobId,
273297
boolean logProgress
274298
) {
299+
// If the input graph is empty return a completed future with empty result
300+
if (graph.isEmpty()) {
301+
return CompletableFuture.completedFuture(SpanningTree.EMPTY);
302+
}
303+
275304
// Create ProgressTracker
276305
var progressTracker = progressTrackerFactory.create(
277306
KSpanningTreeTask.create(graph.relationshipCount()),
@@ -303,6 +332,11 @@ public CompletableFuture<PathFindingResult> longestPath(
303332
JobId jobId,
304333
boolean logProgress
305334
) {
335+
// If the input graph is empty return a completed future with empty result
336+
if (graph.isEmpty()) {
337+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
338+
}
339+
306340
// Create ProgressTracker
307341
var progressTracker = progressTrackerFactory.create(
308342
LongestPathTask.create(graph.nodeCount()),
@@ -332,6 +366,11 @@ public CompletableFuture<Stream<long[]>> randomWalk(
332366
JobId jobId,
333367
boolean logProgress
334368
) {
369+
// If the input graph is empty return a completed future with empty result
370+
if (graph.isEmpty()) {
371+
return CompletableFuture.completedFuture(Stream.empty());
372+
}
373+
335374
// Create ProgressTracker
336375
var progressTracker = progressTrackerFactory.create(
337376
RandomWalkProgressTask.create(graph),
@@ -361,6 +400,13 @@ public CompletableFuture<HugeAtomicLongArray> randomWalkCountingNodeVisits(
361400
JobId jobId,
362401
boolean logProgress
363402
) {
403+
// If the input graph is empty return a completed future with empty result
404+
if (graph.isEmpty()) {
405+
return CompletableFuture.completedFuture(HugeAtomicLongArray.of(
406+
0,
407+
ParalleLongPageCreator.passThrough(parameters.concurrency())
408+
));
409+
}
364410

365411
// Create ProgressTracker
366412
var progressTracker = progressTrackerFactory.create(
@@ -392,6 +438,11 @@ public CompletableFuture<PrizeSteinerTreeResult> pcst(
392438
JobId jobId,
393439
boolean logProgress
394440
) {
441+
// If the input graph is empty return a completed future with empty result
442+
if (graph.isEmpty()) {
443+
return CompletableFuture.completedFuture(PrizeSteinerTreeResult.EMPTY);
444+
}
445+
395446
// Create ProgressTracker
396447
var progressTracker = progressTrackerFactory.create(
397448
PCSTProgressTrackerTaskCreator.progressTask(graph.nodeCount(), graph.relationshipCount()),
@@ -421,6 +472,11 @@ public CompletableFuture<PathFindingResult> singlePairShortestPathAStar(
421472
JobId jobId,
422473
boolean logProgress
423474
) {
475+
// If the input graph is empty return a completed future with empty result
476+
if (graph.isEmpty()) {
477+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
478+
}
479+
424480
// Create ProgressTracker
425481
var progressTracker = progressTrackerFactory.create(
426482
RelationshipCountProgressTaskFactory.create(AlgorithmLabel.AStar, graph.relationshipCount()),
@@ -450,6 +506,11 @@ public CompletableFuture<PathFindingResult> singlePairShortestPathDijkstra(
450506
JobId jobId,
451507
boolean logProgress
452508
) {
509+
// If the input graph is empty return a completed future with empty result
510+
if (graph.isEmpty()) {
511+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
512+
}
513+
453514
// Create ProgressTracker
454515
var progressTracker = progressTrackerFactory.create(
455516
RelationshipCountProgressTaskFactory.create(AlgorithmLabel.Dijkstra, graph.relationshipCount()),
@@ -482,6 +543,11 @@ public CompletableFuture<PathFindingResult> singlePairShortestPathYens(
482543
JobId jobId,
483544
boolean logProgress
484545
) {
546+
// If the input graph is empty return a completed future with empty result
547+
if (graph.isEmpty()) {
548+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
549+
}
550+
485551
// Create ProgressTracker
486552
var progressTracker = progressTrackerFactory.create(
487553
YensProgressTask.create(
@@ -514,6 +580,11 @@ public CompletableFuture<PathFindingResult> singleSourceShortestPathDijkstra(
514580
JobId jobId,
515581
boolean logProgress
516582
) {
583+
// If the input graph is empty return a completed future with empty result
584+
if (graph.isEmpty()) {
585+
return CompletableFuture.completedFuture(PathFindingResult.EMPTY);
586+
}
587+
517588
// Create ProgressTracker
518589
var progressTracker = progressTrackerFactory.create(
519590
RelationshipCountProgressTaskFactory.create(AlgorithmLabel.SingleSourceDijkstra, graph.relationshipCount()),
@@ -545,6 +616,11 @@ public CompletableFuture<SpanningTree> spanningTree(
545616
JobId jobId,
546617
boolean logProgress
547618
) {
619+
// If the input graph is empty return a completed future with empty result
620+
if (graph.isEmpty()) {
621+
return CompletableFuture.completedFuture(SpanningTree.EMPTY);
622+
}
623+
548624
// Create ProgressTracker
549625
var progressTracker = progressTrackerFactory.create(
550626
RelationshipCountProgressTaskFactory.create(AlgorithmLabel.SpanningTree, graph.relationshipCount()),
@@ -575,6 +651,10 @@ public CompletableFuture<SteinerTreeResult> steinerTree(
575651
JobId jobId,
576652
boolean logProgress
577653
) {
654+
// If the input graph is empty return a completed future with empty result
655+
if (graph.isEmpty()) {
656+
return CompletableFuture.completedFuture(SteinerTreeResult.EMPTY);
657+
}
578658

579659
// Create ProgressTracker
580660
var progressTracker = progressTrackerFactory.create(
@@ -616,6 +696,11 @@ public CompletableFuture<TopologicalSortResult> topologicalSort(
616696
JobId jobId,
617697
boolean logProgress
618698
) {
699+
// If the input graph is empty return a completed future with empty result
700+
if (graph.isEmpty()) {
701+
return CompletableFuture.completedFuture(TopologicalSortResult.EMPTY);
702+
}
703+
619704
// Create ProgressTracker
620705
var progressTracker = progressTrackerFactory.create(
621706
TopSortTask.create(graph),

0 commit comments

Comments
 (0)