Skip to content

Commit

Permalink
Merge pull request #8682 from IoannisPanagiotas/dijkstra-with-mutiple…
Browse files Browse the repository at this point in the history
…-targets

One-to-many dijkstra
  • Loading branch information
IoannisPanagiotas authored Feb 12, 2024
2 parents 60fd53d + fa9f586 commit 9ee71bb
Show file tree
Hide file tree
Showing 24 changed files with 582 additions and 56 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.paths;

import org.immutables.value.Value;
import org.neo4j.gds.annotation.Configuration;
import org.neo4j.gds.config.OptionalTargetNodeConfig;
import org.neo4j.gds.config.TargetNodesConfig;

import java.util.List;

public interface SourceTargetsShortestPathBaseConfig extends
OptionalTargetNodeConfig,
TargetNodesConfig,
ShortestPathBaseConfig {

@Configuration.Ignore
default List<Long> targetsList() {
var targetNode = targetNode();
var targetNodes = targetNodes();

if (targetNodes.isEmpty() && targetNode.isPresent()) {
return List.of(targetNode.get());
}
return targetNodes;
}

@Value.Check
default void validate() {
if (!targetNodes().isEmpty() && targetNode().isPresent()) {
throw new IllegalArgumentException(
"The `targets` and `target` parameters cannot be both specified at the same time");
}
if (targetsList().isEmpty() && targetNode().isEmpty()) {
throw new IllegalArgumentException(
"One of `targets` or `target` parameters must be specified");
}

}


}
13 changes: 12 additions & 1 deletion algo/src/main/java/org/neo4j/gds/paths/astar/AStar.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import org.neo4j.gds.paths.astar.config.ShortestPathAStarBaseConfig;
import org.neo4j.gds.paths.dijkstra.Dijkstra;
import org.neo4j.gds.paths.dijkstra.PathFindingResult;
import org.neo4j.gds.paths.dijkstra.SingleTarget;
import org.neo4j.gds.termination.TerminationFlag;

import java.util.Optional;
Expand Down Expand Up @@ -79,12 +80,22 @@ public static AStar sourceTarget(

var latitudeProperties = graph.nodeProperties(latitudeProperty);
var longitudeProperties = graph.nodeProperties(longitudeProperty);

var sourceNode = graph.toMappedNodeId(config.sourceNode());
var targetNode = graph.toMappedNodeId(config.targetNode());

var heuristic = new HaversineHeuristic(latitudeProperties, longitudeProperties, targetNode);

// Init dijkstra algorithm for computing shortest paths
var dijkstra = Dijkstra.sourceTarget(graph, config, false, Optional.of(heuristic), progressTracker, terminationFlag);
var dijkstra = new Dijkstra(
graph,
sourceNode,
new SingleTarget(targetNode),
false,
Optional.of(heuristic),
progressTracker,
terminationFlag
);
return new AStar(dijkstra, terminationFlag);
}

Expand Down
28 changes: 28 additions & 0 deletions algo/src/main/java/org/neo4j/gds/paths/dijkstra/AllTargets.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.paths.dijkstra;

class AllTargets implements Targets {

@Override
public TraversalState apply(long nodeId) {
return TraversalState.EMIT_AND_CONTINUE;
}
}
42 changes: 16 additions & 26 deletions algo/src/main/java/org/neo4j/gds/paths/dijkstra/Dijkstra.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,23 +31,24 @@
import org.neo4j.gds.paths.AllShortestPathsBaseConfig;
import org.neo4j.gds.paths.ImmutablePathResult;
import org.neo4j.gds.paths.PathResult;
import org.neo4j.gds.paths.SourceTargetShortestPathBaseConfig;
import org.neo4j.gds.paths.SourceTargetsShortestPathBaseConfig;
import org.neo4j.gds.termination.TerminationFlag;

import java.util.Optional;
import java.util.function.LongToDoubleFunction;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.neo4j.gds.paths.dijkstra.Dijkstra.TraversalState.CONTINUE;
import static org.neo4j.gds.paths.dijkstra.Dijkstra.TraversalState.EMIT_AND_CONTINUE;
import static org.neo4j.gds.paths.dijkstra.Dijkstra.TraversalState.EMIT_AND_STOP;
import static org.neo4j.gds.paths.dijkstra.TraversalState.CONTINUE;
import static org.neo4j.gds.paths.dijkstra.TraversalState.EMIT_AND_CONTINUE;
import static org.neo4j.gds.paths.dijkstra.TraversalState.EMIT_AND_STOP;

public final class Dijkstra extends Algorithm<PathFindingResult> {
private static final long NO_RELATIONSHIP = -1;

private final Graph graph;
// Takes a visited node as input and decides if a path should be emitted.
private final TraversalPredicate traversalPredicate;
private final Targets targets;
// Holds the current state of the traversal.
private TraversalState traversalState;

Expand Down Expand Up @@ -75,7 +76,7 @@ public final class Dijkstra extends Algorithm<PathFindingResult> {
@Deprecated
public static Dijkstra sourceTarget(
Graph graph,
SourceTargetShortestPathBaseConfig config,
SourceTargetsShortestPathBaseConfig config,
boolean trackRelationships,
Optional<HeuristicFunction> heuristicFunction,
ProgressTracker progressTracker
Expand All @@ -95,19 +96,18 @@ public static Dijkstra sourceTarget(
*/
public static Dijkstra sourceTarget(
Graph graph,
SourceTargetShortestPathBaseConfig configuration,
SourceTargetsShortestPathBaseConfig configuration,
boolean trackRelationships,
Optional<HeuristicFunction> heuristicFunction,
ProgressTracker progressTracker,
TerminationFlag terminationFlag
) {
long sourceNode = graph.toMappedNodeId(configuration.sourceNode());
long targetNode = graph.toMappedNodeId(configuration.targetNode());

var targets = configuration.targetsList().stream().map(graph::toMappedNodeId).collect(Collectors.toList());
return new Dijkstra(
graph,
sourceNode,
node -> node == targetNode ? EMIT_AND_STOP : CONTINUE,
Targets.of(targets),
trackRelationships,
heuristicFunction,
progressTracker,
Expand Down Expand Up @@ -149,7 +149,7 @@ public static Dijkstra singleSource(
) {
return new Dijkstra(graph,
graph.toMappedNodeId(config.sourceNode()),
node -> EMIT_AND_CONTINUE,
new AllTargets(),
trackRelationships,
heuristicFunction,
progressTracker,
Expand All @@ -160,15 +160,15 @@ public static Dijkstra singleSource(
public Dijkstra(
Graph graph,
long sourceNode,
TraversalPredicate traversalPredicate,
Targets targets,
boolean trackRelationships,
Optional<HeuristicFunction> heuristicFunction,
ProgressTracker progressTracker,
TerminationFlag terminationFlag) {
super(progressTracker);
this.graph = graph;
this.sourceNode = sourceNode;
this.traversalPredicate = traversalPredicate;
this.targets = targets;
this.traversalState = CONTINUE;
this.trackRelationships = trackRelationships;
this.queue = heuristicFunction
Expand Down Expand Up @@ -218,13 +218,13 @@ public PathFindingResult compute() {
.sourceNode(sourceNode);

var paths = Stream
.generate(() -> next(traversalPredicate, pathResultBuilder))
.generate(() -> next(targets, pathResultBuilder))
.takeWhile(pathResult -> pathResult != PathResult.EMPTY);

return new PathFindingResult(paths, progressTracker::endSubTask);
}

private PathResult next(TraversalPredicate traversalPredicate, ImmutablePathResult.Builder pathResultBuilder) {
private PathResult next(Targets targets, ImmutablePathResult.Builder pathResultBuilder) {
var relationshipId = new MutableInt();

while (!queue.isEmpty() && terminationFlag.running() && traversalState != EMIT_AND_STOP) {
Expand All @@ -249,7 +249,7 @@ private PathResult next(TraversalPredicate traversalPredicate, ImmutablePathResu
);

// Using the current node, decide if we need to emit a path and continue the traversal.
traversalState = traversalPredicate.apply(node);
traversalState = targets.apply(node);

if (traversalState == EMIT_AND_CONTINUE || traversalState == EMIT_AND_STOP) {
return pathResult(node, pathResultBuilder);
Expand Down Expand Up @@ -325,16 +325,6 @@ private PathResult pathResult(long target, ImmutablePathResult.Builder pathResul
.build();
}

enum TraversalState {
EMIT_AND_STOP,
EMIT_AND_CONTINUE,
CONTINUE,
}

@FunctionalInterface
public interface TraversalPredicate {
TraversalState apply(long nodeId);
}

@FunctionalInterface
public interface RelationshipFilter {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
import org.neo4j.gds.core.utils.progress.tasks.Tasks;
import org.neo4j.gds.paths.AllShortestPathsBaseConfig;
import org.neo4j.gds.paths.ShortestPathBaseConfig;
import org.neo4j.gds.paths.SourceTargetShortestPathBaseConfig;
import org.neo4j.gds.paths.SourceTargetsShortestPathBaseConfig;

import java.util.Optional;

Expand All @@ -55,7 +55,7 @@ public static Task dijkstraProgressTask(String taskName, Graph graph) {
return Tasks.leaf(taskName, graph.relationshipCount());
}

public static class SourceTargetDijkstraFactory<T extends SourceTargetShortestPathBaseConfig> extends
public static class SourceTargetDijkstraFactory<T extends SourceTargetsShortestPathBaseConfig> extends
DijkstraFactory<T> {
@Override
public Dijkstra build(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,36 @@
import org.neo4j.gds.core.utils.queue.HugeLongPriorityQueue;
import org.neo4j.gds.mem.MemoryUsage;
import org.neo4j.gds.paths.ShortestPathBaseConfig;
import org.neo4j.gds.paths.SourceTargetsShortestPathBaseConfig;

public class DijkstraMemoryEstimateDefinition implements AlgorithmMemoryEstimateDefinition<ShortestPathBaseConfig> {

@Override
public MemoryEstimation memoryEstimation(ShortestPathBaseConfig configuration) {

return memoryEstimation(false); //could be configration.track potentially
boolean manyTargets = false;
if (configuration instanceof SourceTargetsShortestPathBaseConfig) { //horrible but will have to do for now
manyTargets = ((SourceTargetsShortestPathBaseConfig) configuration).targetsList().size() > 1;
}
return memoryEstimation(false, manyTargets); //could be configration.track potentially
}

public static MemoryEstimation memoryEstimation(boolean trackRelationships){

return memoryEstimation(trackRelationships, false);
}

public static MemoryEstimation memoryEstimation(boolean trackRelationships, boolean manyTargets) {

var builder = MemoryEstimations.builder(Dijkstra.class)
.add("priority queue", HugeLongPriorityQueue.memoryEstimation())
.add("reverse path", HugeLongLongMap.memoryEstimation());
if (trackRelationships) {
builder.add("relationship ids", HugeLongLongMap.memoryEstimation());
}
if (manyTargets) {
builder.perNode("targets bitset", MemoryUsage::sizeOfBitset);
}
return builder
.perNode("visited set", MemoryUsage::sizeOfBitset)
.build();
Expand Down
51 changes: 51 additions & 0 deletions algo/src/main/java/org/neo4j/gds/paths/dijkstra/ManyTargets.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.paths.dijkstra;

import com.carrotsearch.hppc.BitSet;

import java.util.List;

class ManyTargets implements Targets{

private final BitSet bitSet;
private int targetCount;
public ManyTargets(List<Long> targetNodesList){

long maxNodeId = 0;
for (var targetNode : targetNodesList){
maxNodeId = Math.max(targetNode, maxNodeId);
}
bitSet = new BitSet(maxNodeId + 1);

targetNodesList.forEach(bitSet::set);
targetCount = targetNodesList.size();
}
@Override
public TraversalState apply(long nodeId) {
if (bitSet.get(nodeId)){
targetCount--;
return (targetCount==0) ?
TraversalState.EMIT_AND_STOP
: TraversalState.EMIT_AND_CONTINUE;
}
return TraversalState.CONTINUE;
}
}
37 changes: 37 additions & 0 deletions algo/src/main/java/org/neo4j/gds/paths/dijkstra/SingleTarget.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) "Neo4j"
* Neo4j Sweden AB [http://neo4j.com]
*
* This file is part of Neo4j.
*
* Neo4j is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.neo4j.gds.paths.dijkstra;

import static org.neo4j.gds.paths.dijkstra.TraversalState.CONTINUE;
import static org.neo4j.gds.paths.dijkstra.TraversalState.EMIT_AND_STOP;

public class SingleTarget implements Targets {

private final long targetNode;

public SingleTarget(long targetNode) {
this.targetNode = targetNode;
}

@Override
public TraversalState apply(long nodeId) {
return (nodeId == targetNode) ? EMIT_AND_STOP : CONTINUE;
}
}
Loading

0 comments on commit 9ee71bb

Please sign in to comment.