Skip to content

Commit 57a0ce0

Browse files
terminate earlier inside bucket
1 parent 4be8c05 commit 57a0ce0

File tree

2 files changed

+66
-24
lines changed

2 files changed

+66
-24
lines changed

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

Lines changed: 59 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -173,29 +173,65 @@ private boolean updateSteinerTree(long terminalId,AtomicLong frontierIndex,List<
173173

174174
}
175175

176-
private long tryToUpdateSteinerTree(long oldBin, long currentBin, HugeLongPriorityQueue terminalQueue) {
177-
boolean shouldComputeClosestTerminal = false;
178-
//delta-Stepping differs by Dijkstra in that it processes the nodes not one-by-one but in batches
179-
//whereas in dijkstra once we examine a node, we are certain we have found the shortest path to it,
180-
//in delta-stepping this is not the case
181-
//for example assume a huge delta and assume a bin contains two nodes with distance a (distance=101) and
182-
//b (distance=98) in the same bucket. Assume furthermore, the edge b->a with cost 1 exists.
183-
//Then a is examined, and because of b->a it is re-examined and hten we find a smaller distance from it (99).
184-
185-
//For the moment, we use a simple criteria to discover if there is a terminal for which with full certainty,
186-
//we have found a shortest to it: Whenever we change from one bin to another, we find the terminal of smallest distance
187-
//if it's distance is below the currentBin, the path to it is optimal.
188-
if (currentBin == NO_BIN || oldBin < currentBin) {
189-
shouldComputeClosestTerminal = true;
190-
}
191-
if (shouldComputeClosestTerminal) {
192-
long terminalId = nextTerminal(terminalQueue);
193-
if (terminalId == NO_TERMINAL) return NO_TERMINAL;
194-
if (distances.distance(terminalId) < currentBin * delta) {
195-
return terminalId;
176+
private boolean ensureShortest(
177+
double distance,
178+
long oldBin,
179+
long currentBin,
180+
List<SteinerBasedDeltaTask> tasks
181+
) {
182+
if (oldBin == currentBin) {
183+
//if closest terminal is still far off, unknown if shortest path found
184+
if (distance >= currentBin * delta) {
185+
return false;
196186
}
187+
//find closest node to be processed afterwards
188+
double currentMinDistance = tasks
189+
.stream()
190+
.mapToDouble(SteinerBasedDeltaTask::getSmallest)
191+
.min()
192+
.orElseThrow();
193+
//return true if the closet terminal is at least as close as the closest next node
194+
return distance <= currentMinDistance;
195+
} else {
196+
return (distance < currentBin * delta);
197197
}
198-
return -1;
198+
}
199+
200+
//delta-Stepping differs by Dijkstra in that it processes the nodes not one-by-one but in batches
201+
//whereas in dijkstra once we examine a node, we are certain we have found the shortest path to it,
202+
//in delta-stepping this is not the case
203+
//for example assume a huge delta and assume a bin contains two nodes with distance a (distance=101) and
204+
//b (distance=98) in the same bucket. Assume furthermore, the edge b->a with cost 1 exists.
205+
//Then a is examined, and because of b->a it is re-examined and then we find a smaller distance from it (99).
206+
private long tryToUpdateSteinerTree(
207+
long oldBin,
208+
long currentBin,
209+
HugeLongPriorityQueue terminalQueue,
210+
List<SteinerBasedDeltaTask> tasks
211+
) {
212+
213+
//Use two simple criterias to discover if there is a terminal for which with full certainty,
214+
//we have found a shortest to it:
215+
216+
// Whenever we change from one bin to another, we find the terminal of smallest distance
217+
//if it's distance is below the currentBin, the path to it is optimal.
218+
219+
//Otherwise, if we keep the current bin, ensure all future entries in the current bin
220+
//are worse of than the shortest path to a terminal (hence its shortest distance cannot improve)
221+
222+
long terminalId = nextTerminal(terminalQueue);
223+
if (terminalId == NO_TERMINAL) {
224+
return NO_TERMINAL;
225+
}
226+
227+
boolean shouldReturnTerminal = ensureShortest(
228+
distances.distance(terminalId),
229+
oldBin,
230+
currentBin,
231+
tasks
232+
);
233+
234+
return (shouldReturnTerminal) ? terminalId : NO_TERMINAL;
199235
}
200236

201237
@Override
@@ -241,9 +277,9 @@ public DijkstraResult compute() {
241277
// Find smallest non-empty bin across all tasks
242278
currentBin = tasks.stream().mapToInt(SteinerBasedDeltaTask::minNonEmptyBin).min().orElseThrow();
243279

244-
long terminalId = tryToUpdateSteinerTree(oldCurrentBin, currentBin, terminalQueue);
280+
long terminalId = tryToUpdateSteinerTree(oldCurrentBin, currentBin, terminalQueue, tasks);
245281

246-
if (terminalId != -1) { //if we are certain that we have found a shortest path to one of the remaining terminals
282+
if (terminalId != NO_TERMINAL) { //if we are certain that we have found a shortest path to one of the remaining terminals
247283
//we update the solution and merge its path to the root
248284
terminalQueue.pop();
249285
shouldBreak = updateSteinerTree(terminalId, frontierIndex, paths, pathResultBuilder);

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ class SteinerBasedDeltaTask implements Runnable {
5050
private final HugeLongPriorityQueue terminalQueue;
5151
private final ReentrantLock terminalQueueLock;
5252

53+
private double smallest;
54+
5355
SteinerBasedDeltaTask(
5456
Graph graph,
5557
HugeLongArray frontier,
@@ -77,13 +79,16 @@ class SteinerBasedDeltaTask implements Runnable {
7779
@Override
7880
public void run() {
7981
if (phase == SteinerBasedDeltaStepping.Phase.RELAX) {
82+
smallest = Double.MAX_VALUE;
8083
relaxGlobalBin();
8184
relaxLocalBin();
8285
} else if (phase == SteinerBasedDeltaStepping.Phase.SYNC) {
8386
updateFrontier();
8487
}
8588
}
8689

90+
double getSmallest() {return smallest;}
91+
8792
void setPhase(SteinerBasedDeltaStepping.Phase phase) {
8893
this.phase = phase;
8994
}
@@ -132,7 +137,8 @@ private void relaxLocalBin() {
132137
private void relaxNode(long nodeId) {
133138
graph.forEachRelationship(nodeId, 1.0, (sourceNodeId, targetNodeId, weight) -> {
134139
if (!mergedToSource.get(targetNodeId)) { //ignore merged vertices
135-
tryToUpdate(sourceNodeId, targetNodeId,weight);
140+
tryToUpdate(sourceNodeId, targetNodeId, weight);
141+
smallest = Math.min(weight, smallest);
136142
}
137143
return true;
138144
});

0 commit comments

Comments
 (0)