Skip to content
This repository was archived by the owner on Apr 22, 2020. It is now read-only.

Commit 0fede3f

Browse files
authored
Make the shortest path algorithms have a consistent feel (#675)
* let user choose direction * making shortest path algos consistent * updating tests * outgoing here too * test reverse * adding more tests * make GraphView handle our new style undirected loading
1 parent aa77ac2 commit 0fede3f

17 files changed

+341
-58
lines changed

algo/src/main/java/org/neo4j/graphalgo/AllShortestPathsProc.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,17 +66,27 @@ public Stream<AllShortestPaths.Result> allShortestPathsStream(
6666
Map<String, Object> config) {
6767

6868
ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
69+
70+
Direction direction = configuration.getDirection(Direction.BOTH);
71+
6972
AllocationTracker tracker = AllocationTracker.create();
70-
Graph graph = new GraphLoader(api, Pools.DEFAULT)
73+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
7174
.withOptionalLabel(configuration.getNodeLabelOrQuery())
7275
.withOptionalRelationshipType(configuration.getRelationshipOrQuery())
7376
.withOptionalRelationshipWeightsFromProperty(
7477
propertyName,
7578
configuration.getWeightPropertyDefaultValue(1.0))
76-
.withDirection(Direction.OUTGOING)
7779
.withConcurrency(configuration.getConcurrency())
78-
.withAllocationTracker(tracker)
79-
.load(configuration.getGraphImpl());
80+
.withAllocationTracker(tracker);
81+
82+
if(direction == Direction.BOTH) {
83+
direction = Direction.OUTGOING;
84+
graphLoader.asUndirected(true).withDirection(direction);
85+
} else {
86+
graphLoader.withDirection(direction);
87+
}
88+
89+
Graph graph = graphLoader.load(configuration.getGraphImpl());
8090

8191
if (graph.nodeCount() == 0) {
8292
graph.release();
@@ -93,19 +103,21 @@ public Stream<AllShortestPaths.Result> allShortestPathsStream(
93103
hugeGraph,
94104
tracker,
95105
configuration.getConcurrency(),
96-
Pools.DEFAULT);
106+
Pools.DEFAULT,
107+
direction);
97108
} else {
98109
algo = new MSBFSAllShortestPaths(
99110
graph,
100111
configuration.getConcurrency(),
101-
Pools.DEFAULT);
112+
Pools.DEFAULT,
113+
direction);
102114
}
103115
algo.withProgressLogger(ProgressLogger.wrap(
104116
log,
105117
"AllShortestPaths(MultiSource)"));
106118
} else {
107119
// weighted ASP otherwise
108-
algo = new AllShortestPaths(graph, Pools.DEFAULT, configuration.getConcurrency())
120+
algo = new AllShortestPaths(graph, Pools.DEFAULT, configuration.getConcurrency(), direction)
109121
.withProgressLogger(ProgressLogger.wrap(log, "AllShortestPaths)"));
110122
}
111123

algo/src/main/java/org/neo4j/graphalgo/ShortestPathDeltaSteppingProc.java

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -81,21 +81,29 @@ public Stream<ShortestPathDeltaStepping.DeltaSteppingResult> deltaSteppingStream
8181
Map<String, Object> config) {
8282

8383
ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
84+
Direction direction = configuration.getDirection(Direction.BOTH);
8485

85-
final Graph graph = new GraphLoader(api, Pools.DEFAULT)
86+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
8687
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
8788
.withRelationshipWeightsFromProperty(
8889
propertyName,
89-
configuration.getWeightPropertyDefaultValue(Double.MAX_VALUE))
90-
.withDirection(Direction.OUTGOING)
91-
.load(configuration.getGraphImpl());
90+
configuration.getWeightPropertyDefaultValue(Double.MAX_VALUE));
91+
92+
if(direction == Direction.BOTH) {
93+
direction = Direction.OUTGOING;
94+
graphLoader.asUndirected(true).withDirection(direction);
95+
} else {
96+
graphLoader.withDirection(direction);
97+
}
98+
99+
final Graph graph = graphLoader.load(configuration.getGraphImpl());
92100

93101
if (graph.nodeCount() == 0 || startNode == null) {
94102
graph.release();
95103
return Stream.empty();
96104
}
97105

98-
final ShortestPathDeltaStepping algo = new ShortestPathDeltaStepping(graph, delta)
106+
final ShortestPathDeltaStepping algo = new ShortestPathDeltaStepping(graph, delta, direction)
99107
.withProgressLogger(ProgressLogger.wrap(log, "ShortestPaths(DeltaStepping)"))
100108
.withTerminationFlag(TerminationFlag.wrap(transaction))
101109
.withExecutorService(Executors.newFixedThreadPool(
@@ -118,16 +126,26 @@ public Stream<DeltaSteppingProcResult> deltaStepping(
118126
Map<String, Object> config) {
119127

120128
final ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
129+
Direction direction = configuration.getDirection(Direction.BOTH);
130+
121131
final DeltaSteppingProcResult.Builder builder = DeltaSteppingProcResult.builder();
122132

123133
final Graph graph;
124134
try (ProgressTimer timer = builder.timeLoad()) {
125-
graph = new GraphLoader(api, Pools.DEFAULT)
135+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
126136
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
127137
.withRelationshipWeightsFromProperty(
128138
propertyName,
129-
configuration.getWeightPropertyDefaultValue(Double.MAX_VALUE))
130-
.withDirection(Direction.OUTGOING)
139+
configuration.getWeightPropertyDefaultValue(Double.MAX_VALUE));
140+
141+
if(direction == Direction.BOTH) {
142+
direction = Direction.OUTGOING;
143+
graphLoader.asUndirected(true).withDirection(direction);
144+
} else {
145+
graphLoader.withDirection(direction);
146+
}
147+
148+
graph = graphLoader
131149
.load(configuration.getGraphImpl());
132150
}
133151

@@ -137,7 +155,7 @@ public Stream<DeltaSteppingProcResult> deltaStepping(
137155
}
138156

139157
final TerminationFlag terminationFlag = TerminationFlag.wrap(transaction);
140-
final ShortestPathDeltaStepping algorithm = new ShortestPathDeltaStepping(graph, delta)
158+
final ShortestPathDeltaStepping algorithm = new ShortestPathDeltaStepping(graph, delta, direction)
141159
.withProgressLogger(ProgressLogger.wrap(log, "ShortestPaths(DeltaStepping)"))
142160
.withTerminationFlag(terminationFlag)
143161
.withExecutorService(Pools.DEFAULT);

algo/src/main/java/org/neo4j/graphalgo/ShortestPathProc.java

Lines changed: 44 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,23 @@ public Stream<ShortestPathDijkstra.Result> dijkstraStream(
8484

8585
ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
8686

87-
final Direction direction = configuration.getDirection(Direction.BOTH);
87+
Direction direction = configuration.getDirection(Direction.BOTH);
8888

89-
final Graph graph = new GraphLoader(api, Pools.DEFAULT)
89+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
9090
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
9191
.withOptionalRelationshipWeightsFromProperty(
9292
propertyName,
93-
configuration.getWeightPropertyDefaultValue(1.0))
94-
.withDirection(direction)
95-
.load(configuration.getGraphImpl());
93+
configuration.getWeightPropertyDefaultValue(1.0));
94+
95+
if(direction == Direction.BOTH) {
96+
direction = Direction.OUTGOING;
97+
graphLoader.asUndirected(true).withDirection(direction);
98+
} else {
99+
graphLoader.withDirection(direction);
100+
}
101+
102+
103+
final Graph graph = graphLoader.load(configuration.getGraphImpl());
96104

97105
if (graph.nodeCount() == 0 || startNode == null || endNode == null) {
98106
graph.release();
@@ -124,15 +132,24 @@ public Stream<DijkstraResult> dijkstra(
124132
final Graph graph;
125133
final ShortestPathDijkstra dijkstra;
126134

127-
final Direction direction = configuration.getDirection(Direction.BOTH);
135+
Direction direction = configuration.getDirection(Direction.BOTH);
128136
try (ProgressTimer timer = builder.timeLoad()) {
129-
graph = new GraphLoader(api, Pools.DEFAULT)
137+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
130138
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
131139
.withOptionalRelationshipWeightsFromProperty(
132140
propertyName,
133141
configuration.getWeightPropertyDefaultValue(1.0))
134-
.withDirection(direction)
135-
.load(configuration.getGraphImpl());
142+
.withDirection(direction);
143+
144+
if(direction == Direction.BOTH) {
145+
direction = Direction.OUTGOING;
146+
graphLoader.asUndirected(true).withDirection(direction);
147+
} else {
148+
graphLoader.withDirection(direction);
149+
}
150+
151+
152+
graph = graphLoader.load(configuration.getGraphImpl());
136153
}
137154

138155
if (graph.nodeCount() == 0 || startNode == null || endNode == null) {
@@ -180,16 +197,24 @@ public Stream<ShortestPathAStar.Result> astarStream(
180197
@Name(value = "propertyKeyLat", defaultValue = "latitude") String propertyKeyLat,
181198
@Name(value = "propertyKeyLon", defaultValue = "longitude") String propertyKeyLon,
182199
@Name(value = "config", defaultValue = "{}") Map<String, Object> config) {
183-
184-
ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
185-
186-
final Direction direction = configuration.getDirection(Direction.BOTH);
187-
188-
final Graph graph = new GraphLoader(api, Pools.DEFAULT)
189-
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
190-
.withOptionalRelationshipWeightsFromProperty(propertyName, configuration.getWeightPropertyDefaultValue(1.0))
191-
.withDirection(direction)
192-
.load(configuration.getGraphImpl());
200+
201+
ProcedureConfiguration configuration = ProcedureConfiguration.create(config);
202+
Direction direction = configuration.getDirection(Direction.BOTH);
203+
204+
GraphLoader graphLoader = new GraphLoader(api, Pools.DEFAULT)
205+
.init(log, configuration.getNodeLabelOrQuery(), configuration.getRelationshipOrQuery(), configuration)
206+
.withOptionalRelationshipWeightsFromProperty(propertyName, configuration.getWeightPropertyDefaultValue(1.0))
207+
.withDirection(direction);
208+
209+
210+
if(direction == Direction.BOTH) {
211+
direction = Direction.OUTGOING;
212+
graphLoader.asUndirected(true).withDirection(direction);
213+
} else {
214+
graphLoader.withDirection(direction);
215+
}
216+
217+
final Graph graph = graphLoader.load(configuration.getGraphImpl());
193218

194219
if (graph.nodeCount() == 0 || startNode == null || endNode == null) {
195220
graph.release();

algo/src/main/java/org/neo4j/graphalgo/impl/AllShortestPaths.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,16 @@ public class AllShortestPaths extends MSBFSASPAlgorithm<AllShortestPaths> {
6363
*/
6464
private AtomicInteger counter;
6565
private ExecutorService executorService;
66+
private final Direction direction;
6667
private BlockingQueue<Result> resultQueue;
6768

6869
private volatile boolean outputStreamOpen;
6970

70-
public AllShortestPaths(Graph graph, ExecutorService executorService, int concurrency) {
71+
public AllShortestPaths(Graph graph, ExecutorService executorService, int concurrency, Direction direction) {
7172
this.graph = graph;
7273
this.nodeCount = Math.toIntExact(graph.nodeCount());
7374
this.executorService = executorService;
75+
this.direction = direction;
7476
if (concurrency < 1) {
7577
throw new IllegalArgumentException("concurrency must be >0");
7678
}
@@ -168,7 +170,7 @@ public void compute(int startNode) {
168170
// scan relationships
169171
graph.forEachRelationship(
170172
node,
171-
Direction.OUTGOING,
173+
direction,
172174
(source, target, relId, weight) -> {
173175
// relax
174176
final double targetDistance = weight + sourceDistance;

algo/src/main/java/org/neo4j/graphalgo/impl/HugeMSBFSAllShortestPaths.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,21 @@ public class HugeMSBFSAllShortestPaths extends MSBFSASPAlgorithm<HugeMSBFSAllSho
5050
private final AllocationTracker tracker;
5151
private final int concurrency;
5252
private final ExecutorService executorService;
53+
private final Direction direction;
5354
private final long nodeCount;
5455

5556
public HugeMSBFSAllShortestPaths(
5657
HugeGraph graph,
5758
AllocationTracker tracker,
5859
int concurrency,
59-
ExecutorService executorService) {
60+
ExecutorService executorService,
61+
Direction direction) {
6062
this.graph = graph;
6163
nodeCount = graph.nodeCount();
6264
this.tracker = tracker;
6365
this.concurrency = concurrency;
6466
this.executorService = executorService;
67+
this.direction = direction;
6568
this.resultQueue = new LinkedBlockingQueue<>(); // TODO limit size?
6669
}
6770

@@ -130,7 +133,7 @@ public void run() {
130133
new HugeMultiSourceBFS(
131134
graph,
132135
graph,
133-
Direction.OUTGOING,
136+
direction,
134137
(target, distance, sources) -> {
135138
while (sources.hasNext()) {
136139
long source = sources.next();

algo/src/main/java/org/neo4j/graphalgo/impl/MSBFSAllShortestPaths.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,15 @@ public class MSBFSAllShortestPaths extends MSBFSASPAlgorithm<MSBFSAllShortestPat
4848
private BlockingQueue<Result> resultQueue;
4949
private final int concurrency;
5050
private final ExecutorService executorService;
51+
private final Direction direction;
5152
private final int nodeCount;
5253

53-
public MSBFSAllShortestPaths(Graph graph, int concurrency, ExecutorService executorService) {
54+
public MSBFSAllShortestPaths(Graph graph, int concurrency, ExecutorService executorService, Direction direction) {
5455
this.graph = graph;
5556
nodeCount = Math.toIntExact(graph.nodeCount());
5657
this.concurrency = concurrency;
5758
this.executorService = executorService;
59+
this.direction = direction;
5860
this.resultQueue = new LinkedBlockingQueue<>(); // TODO limit size?
5961
}
6062

@@ -123,7 +125,7 @@ public void run() {
123125
new MultiSourceBFS(
124126
graph,
125127
graph,
126-
Direction.OUTGOING,
128+
direction,
127129
(target, distance, sources) -> {
128130
while (sources.hasNext()) {
129131
int source = sources.next();

algo/src/main/java/org/neo4j/graphalgo/impl/ShortestPathDeltaStepping.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,14 @@ public class ShortestPathDeltaStepping extends Algorithm<ShortestPathDeltaSteppi
6969

7070
// multiplier used to scale an double to int
7171
private double multiplier = 100_000d; // double type is intended
72+
private Direction direction;
7273

73-
public ShortestPathDeltaStepping(Graph graph, double delta) {
74+
public ShortestPathDeltaStepping(Graph graph, double delta, Direction direction) {
7475
this.graph = graph;
7576
this.delta = delta;
7677
this.iDelta = (int) (multiplier * delta);
7778
nodeCount = Math.toIntExact(graph.nodeCount());
79+
this.direction = direction;
7880
distance = new AtomicIntegerArray(nodeCount);
7981
buckets = new Buckets(nodeCount);
8082
heavy = new ArrayDeque<>(1024);
@@ -137,7 +139,7 @@ public ShortestPathDeltaStepping compute(long startNode) {
137139
// for each node in bucket
138140
buckets.forEachInBucket(phase, node -> {
139141
// relax each outgoing light edge
140-
graph.forEachRelationship(node, Direction.OUTGOING, (sourceNodeId, targetNodeId, relationId, cost) -> {
142+
graph.forEachRelationship(node, direction, (sourceNodeId, targetNodeId, relationId, cost) -> {
141143
final int iCost = (int) (cost * multiplier + distance.get(sourceNodeId));
142144
if (cost <= delta) { // determine if light or heavy edge
143145
light.add(() -> relax(targetNodeId, iCost));

core/src/main/java/org/neo4j/graphalgo/core/neo4jview/GraphView.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,18 @@ public class GraphView implements Graph {
6363
private final GraphDimensions dimensions;
6464
private final double propertyDefaultWeight;
6565
private final IdMap idMapping;
66+
private boolean loadAsUndirected;
6667

6768
GraphView(
6869
GraphDatabaseAPI db,
6970
GraphDimensions dimensions,
7071
IdMap idMapping,
71-
double propertyDefaultWeight) {
72+
double propertyDefaultWeight, boolean loadAsUndirected) {
7273
this.tx = new TransactionWrapper(db);
7374
this.dimensions = dimensions;
7475
this.propertyDefaultWeight = propertyDefaultWeight;
7576
this.idMapping = idMapping;
77+
this.loadAsUndirected = loadAsUndirected;
7678
}
7779

7880
@Override
@@ -130,7 +132,7 @@ private void forAllRelationships(
130132
};
131133

132134
LoadRelationships loader = rels(transaction);
133-
if (direction == Direction.BOTH) {
135+
if (direction == Direction.BOTH || (direction == Direction.OUTGOING && loadAsUndirected) ) {
134136
// can't use relationshipsBoth here, b/c we want to be consistent with the other graph impls
135137
// that are iteration first over outgoing, then over incoming relationships
136138
RelationshipSelectionCursor cursor = loader.relationshipsOut(nc);

core/src/main/java/org/neo4j/graphalgo/core/neo4jview/GraphViewFactory.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,6 @@ public Graph build() {
4545
dimensions.nodeCount(),
4646
dimensions.labelId()
4747
).call();
48-
return new GraphView(api, dimensions, idMap, setup.relationDefaultWeight);
48+
return new GraphView(api, dimensions, idMap, setup.relationDefaultWeight, setup.loadAsUndirected);
4949
}
5050
}

0 commit comments

Comments
 (0)