Commit e601eb8d authored by Andi's avatar Andi Committed by Peter

Implements a simple version of stall-on-demand for CH queries. (#1179)

* Implements a simple version of stall-on-demand for CH queries.

* Makes stall on demand the default for CH. Moves different CH routing algorithms to routing package.

* Renames CH algorithms.

* Adds measurements for CH without stall on demand.

* minor cleanup

* Adds tests revealing bug in stall on demand implementation.

Stall on demand can prevent finding a shortest path if the weight of an
edge depends on the driving direction as with Motorcycle or Bike2 weightings.

* Fixes bug in stall on demand implementation.

When checking the stall on demand condition so far the wrong edge
direction was used to calculate the weight of the incoming edge.

* Adjusts prevOrNextEdge parameter in stall on demand check.

* Adds author tag and removes stall on demand public constant.

* Adds author tag and removes stall on demand public constant.

* Fixes compile error.
parent 9d2fa14c
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.routing;
import com.graphhopper.routing.ch.Path4CH;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
public class AStarBidirectionCH extends AStarBidirection {
public AStarBidirectionCH(Graph graph, Weighting weighting, TraversalMode traversalMode) {
super(graph, weighting, traversalMode);
}
@Override
protected void initCollections(int size) {
super.initCollections(Math.min(size, 2000));
}
@Override
protected boolean finished() {
// we need to finish BOTH searches for CH!
if (finishedFrom && finishedTo)
return true;
// changed finish condition for CH
return currFrom.weight >= bestPath.getWeight() && currTo.weight >= bestPath.getWeight();
}
@Override
protected Path createAndInitPath() {
bestPath = new Path4CH(graph, graph.getBaseGraph(), weighting);
return bestPath;
}
@Override
public String getName() {
return "astarbi|ch";
}
@Override
public String toString() {
return getName() + "|" + weighting;
}
}
......@@ -17,7 +17,6 @@
*/
package com.graphhopper.routing;
import com.graphhopper.routing.util.FlagEncoder;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
......
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.routing;
import com.carrotsearch.hppc.IntObjectMap;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
import com.graphhopper.storage.SPTEntry;
import com.graphhopper.util.EdgeExplorer;
import com.graphhopper.util.EdgeIterator;
/**
* Uses a very simple version of stall-on-demand (SOD) for CH queries to prevent exploring nodes that can not be part
* of a shortest path. When a node that is about to be settled is stallable it is not expanded, but no further search
* for neighboring stallable nodes is performed.
*
* @author ammagamma
*/
public class DijkstraBidirectionCH extends DijkstraBidirectionCHNoSOD {
public DijkstraBidirectionCH(Graph graph, Weighting weighting, TraversalMode traversalMode) {
super(graph, weighting, traversalMode);
}
@Override
public boolean fillEdgesFrom() {
if (pqOpenSetFrom.isEmpty()) {
return false;
}
currFrom = pqOpenSetFrom.poll();
visitedCountFrom++;
if (entryIsStallable(currFrom, bestWeightMapFrom, inEdgeExplorer, false)) {
return true;
}
bestWeightMapOther = bestWeightMapTo;
fillEdges(currFrom, pqOpenSetFrom, bestWeightMapFrom, outEdgeExplorer, false);
return true;
}
@Override
public boolean fillEdgesTo() {
if (pqOpenSetTo.isEmpty()) {
return false;
}
currTo = pqOpenSetTo.poll();
visitedCountTo++;
if (entryIsStallable(currTo, bestWeightMapTo, outEdgeExplorer, true)) {
return true;
}
bestWeightMapOther = bestWeightMapFrom;
fillEdges(currTo, pqOpenSetTo, bestWeightMapTo, inEdgeExplorer, true);
return true;
}
@Override
public String getName() {
return "dijkstrabi|ch";
}
@Override
public String toString() {
return getName() + "|" + weighting;
}
private boolean entryIsStallable(SPTEntry entry, IntObjectMap<SPTEntry> bestWeightMap, EdgeExplorer edgeExplorer,
boolean reverse) {
// We check for all 'incoming' edges if we can prove that the current node (that is about to be settled) is
// reached via a suboptimal path. We do this regardless of the CH level of the adjacent nodes.
EdgeIterator iter = edgeExplorer.setBaseNode(entry.adjNode);
while (iter.next()) {
int traversalId = traversalMode.createTraversalId(iter, reverse);
SPTEntry adjNode = bestWeightMap.get(traversalId);
if (adjNode != null &&
adjNode.weight + weighting.calcWeight(iter, !reverse, entry.edge) < entry.weight) {
return true;
}
}
return false;
}
}
/*
* Licensed to GraphHopper GmbH under one or more contributor
* license agreements. See the NOTICE file distributed with this work for
* additional information regarding copyright ownership.
*
* GraphHopper GmbH licenses this file to you under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.routing;
import com.graphhopper.routing.ch.Path4CH;
import com.graphhopper.routing.util.TraversalMode;
import com.graphhopper.routing.weighting.Weighting;
import com.graphhopper.storage.Graph;
public class DijkstraBidirectionCHNoSOD extends DijkstraBidirectionRef {
public DijkstraBidirectionCHNoSOD(Graph graph, Weighting weighting, TraversalMode traversalMode) {
super(graph, weighting, traversalMode);
}
@Override
protected void initCollections(int size) {
super.initCollections(Math.min(size, 2000));
}
@Override
public boolean finished() {
// we need to finish BOTH searches for CH!
if (finishedFrom && finishedTo)
return true;
// changed also the final finish condition for CH
return currFrom.weight >= bestPath.getWeight() && currTo.weight >= bestPath.getWeight();
}
@Override
protected Path createAndInitPath() {
bestPath = new Path4CH(graph, graph.getBaseGraph(), weighting);
return bestPath;
}
@Override
public String getName() {
return "dijkstrabi|ch|no_sod";
}
@Override
public String toString() {
return getName() + "|" + weighting;
}
}
......@@ -42,8 +42,8 @@ public class DijkstraBidirectionRef extends AbstractBidirAlgo {
protected SPTEntry currFrom;
protected SPTEntry currTo;
protected PathBidirRef bestPath;
private PriorityQueue<SPTEntry> pqOpenSetFrom;
private PriorityQueue<SPTEntry> pqOpenSetTo;
PriorityQueue<SPTEntry> pqOpenSetFrom;
PriorityQueue<SPTEntry> pqOpenSetTo;
private boolean updateBestPath = true;
public DijkstraBidirectionRef(Graph graph, Weighting weighting, TraversalMode tMode) {
......
......@@ -647,9 +647,12 @@ public class PrepareContractionHierarchies extends AbstractAlgoPreparation imple
AStarBidirection tmpAlgo = new AStarBidirectionCH(graph, prepareWeighting, traversalMode);
tmpAlgo.setApproximation(RoutingAlgorithmFactorySimple.getApproximation(ASTAR_BI, opts, graph.getNodeAccess()));
algo = tmpAlgo;
} else if (DIJKSTRA_BI.equals(opts.getAlgorithm())) {
algo = new DijkstraBidirectionCH(graph, prepareWeighting, traversalMode);
if (opts.getHints().getBool("stall_on_demand", true)) {
algo = new DijkstraBidirectionCH(graph, prepareWeighting, traversalMode);
} else {
algo = new DijkstraBidirectionCHNoSOD(graph, prepareWeighting, traversalMode);
}
} else {
throw new IllegalArgumentException("Algorithm " + opts.getAlgorithm() + " not supported for Contraction Hierarchies. Try with ch.disable=true");
}
......@@ -659,80 +662,6 @@ public class PrepareContractionHierarchies extends AbstractAlgoPreparation imple
return algo;
}
public static class AStarBidirectionCH extends AStarBidirection {
public AStarBidirectionCH(Graph graph, Weighting weighting, TraversalMode traversalMode) {
super(graph, weighting, traversalMode);
}
@Override
protected void initCollections(int size) {
super.initCollections(Math.min(size, 2000));
}
@Override
protected boolean finished() {
// we need to finish BOTH searches for CH!
if (finishedFrom && finishedTo)
return true;
// changed finish condition for CH
return currFrom.weight >= bestPath.getWeight() && currTo.weight >= bestPath.getWeight();
}
@Override
protected Path createAndInitPath() {
bestPath = new Path4CH(graph, graph.getBaseGraph(), weighting);
return bestPath;
}
@Override
public String getName() {
return "astarbi|ch";
}
@Override
public String toString() {
return getName() + "|" + weighting;
}
}
public static class DijkstraBidirectionCH extends DijkstraBidirectionRef {
public DijkstraBidirectionCH(Graph graph, Weighting weighting, TraversalMode traversalMode) {
super(graph, weighting, traversalMode);
}
@Override
protected void initCollections(int size) {
super.initCollections(Math.min(size, 2000));
}
@Override
public boolean finished() {
// we need to finish BOTH searches for CH!
if (finishedFrom && finishedTo)
return true;
// changed also the final finish condition for CH
return currFrom.weight >= bestPath.getWeight() && currTo.weight >= bestPath.getWeight();
}
@Override
protected Path createAndInitPath() {
bestPath = new Path4CH(graph, graph.getBaseGraph(), weighting);
return bestPath;
}
@Override
public String getName() {
return "dijkstrabi|ch";
}
@Override
public String toString() {
return getName() + "|" + weighting;
}
}
@Override
public String toString() {
return "prepare|dijkstrabi|ch";
......
......@@ -15,9 +15,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.graphhopper.routing.ch;
package com.graphhopper.routing;
import com.graphhopper.routing.*;
import com.carrotsearch.hppc.IntArrayList;
import com.graphhopper.routing.ch.PrepareContractionHierarchies;
import com.graphhopper.routing.util.*;
import com.graphhopper.routing.weighting.FastestWeighting;
import com.graphhopper.routing.weighting.ShortestWeighting;
......@@ -166,4 +167,106 @@ public class DijkstraBidirectionCHTest extends AbstractRoutingAlgorithmTester {
assertEquals(p3.toString(), 12240 * 1000, p3.getTime());
assertEquals(Helper.createTList(0, 4, 5, 7), p3.calcNodes());
}
// 7------8------.---9----0
// | | \ | |
// 6------ | | |
// | | 1 | |
// 5------ | | /
// | _,--| 2 | /
// |/ | |/
// 4----------3--/
@Test
public void testStallingNodesReducesNumberOfVisitedNodes() {
GraphHopperStorage graph = createGHStorage(false);
graph.edge(8, 9, 100, false);
graph.edge(8, 3, 2, false);
graph.edge(8, 5, 1, false);
graph.edge(8, 6, 1, false);
graph.edge(8, 7, 1, false);
graph.edge(1, 2, 2, false);
graph.edge(1, 8, 1, false);
graph.edge(2, 3, 3, false);
for (int i = 3; i < 7; ++i) {
graph.edge(i, i + 1, 1, false);
}
graph.edge(9, 0, 1, false);
graph.edge(3, 9, 200, false);
CHGraph chGraph = graph.getGraph(CHGraph.class);
// explicitly set the node levels equal to the node ids
// the graph contraction with this ordering yields no shortcuts
for (int i = 0; i < 10; ++i) {
chGraph.setLevel(i, i);
}
graph.freeze();
RoutingAlgorithm algo = createCHAlgo(graph, chGraph, true, defaultOpts);
Path p = algo.calcPath(1, 0);
// node 3 will be stalled and nodes 4-7 won't be explored --> we visit 7 nodes
// note that node 9 will be visited by both forward and backward searches
assertEquals(7, algo.getVisitedNodes());
assertEquals(102, p.getDistance(), 1.e-3);
assertEquals(p.toString(), Helper.createTList(1, 8, 9, 0), p.calcNodes());
// without stalling we visit 11 nodes
RoutingAlgorithm algoNoSod = createCHAlgo(graph, chGraph, false, defaultOpts);
Path pNoSod = algoNoSod.calcPath(1, 0);
assertEquals(11, algoNoSod.getVisitedNodes());
assertEquals(102, pNoSod.getDistance(), 1.e-3);
assertEquals(pNoSod.toString(), Helper.createTList(1, 8, 9, 0), pNoSod.calcNodes());
}
// t(0)--slow->1--s(2)
// \ |
// fast |
// \--<---|
@Test
public void testDirectionDependentSpeedFwdSearch() {
runTestWithDirectionDependentEdgeSpeed(10, 20, 0, 2, Helper.createTList(0, 1, 2), new MotorcycleFlagEncoder());
runTestWithDirectionDependentEdgeSpeed(10, 20, 0, 2, Helper.createTList(0, 1, 2), new Bike2WeightFlagEncoder());
}
// s(0)--fast->1--t(2)
// \ |
// slow |
// \--<---|
@Test
public void testDirectionDependentSpeedBwdSearch() {
runTestWithDirectionDependentEdgeSpeed(20, 10, 2, 0, Helper.createTList(2, 1, 0), new MotorcycleFlagEncoder());
runTestWithDirectionDependentEdgeSpeed(20, 10, 2, 0, Helper.createTList(2, 1, 0), new Bike2WeightFlagEncoder());
}
private void runTestWithDirectionDependentEdgeSpeed(
int speed, int revSpeed, int from, int to, IntArrayList expectedPath, FlagEncoder encoder) {
EncodingManager encodingManager = new EncodingManager(encoder);
FastestWeighting weighting = new FastestWeighting(encoder);
AlgorithmOptions algoOpts = AlgorithmOptions.start().weighting(weighting).build();
GraphHopperStorage graph = createGHStorage(encodingManager, Arrays.asList(weighting), false);
EdgeIteratorState edge = graph.edge(0, 1, 2, true);
long flags = edge.getFlags();
flags = encoder.setSpeed(flags, speed);
flags = encoder.setReverseSpeed(flags, revSpeed);
edge.setFlags(flags);
graph.edge(1, 2, 1, true);
CHGraph chGraph = graph.getGraph(CHGraph.class);
for (int i = 0; i < 3; ++i) {
chGraph.setLevel(i, i);
}
graph.freeze();
RoutingAlgorithm algo = createCHAlgo(graph, chGraph, true, algoOpts);
Path p = algo.calcPath(from, to);
assertEquals(3, p.getDistance(), 1.e-3);
assertEquals(p.toString(), expectedPath, p.calcNodes());
}
private RoutingAlgorithm createCHAlgo(GraphHopperStorage graph, CHGraph chGraph, boolean withSOD, AlgorithmOptions algorithmOptions) {
PrepareContractionHierarchies ch = new PrepareContractionHierarchies(new GHDirectory("", DAType.RAM_INT),
graph, chGraph, algorithmOptions.getWeighting(), TraversalMode.NODE_BASED);
if (!withSOD) {
algorithmOptions.getHints().put("stall_on_demand", false);
}
return ch.createAlgo(chGraph, algorithmOptions);
}
}
......@@ -48,6 +48,8 @@ import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import static com.graphhopper.util.Parameters.Algorithms.DIJKSTRA_BI;
/**
* @author Peter Karich
*/
......@@ -115,14 +117,14 @@ public class Measurement {
GHBitSet allowedEdges = printGraphDetails(g, vehicleStr);
printMiscUnitPerfTests(g, isCH, encoder, count * 100, allowedEdges);
printLocationIndexQuery(g, hopper.getLocationIndex(), count);
printTimeOfRouteQuery(hopper, isCH, isLM, count / 20, "routing", vehicleStr, true, -1);
printTimeOfRouteQuery(hopper, isCH, isLM, count / 20, "routing", vehicleStr, true, -1, true);
if (hopper.getLMFactoryDecorator().isEnabled()) {
System.gc();
isLM = true;
int activeLMCount = 12;
for (; activeLMCount > 3; activeLMCount -= 4) {
printTimeOfRouteQuery(hopper, isCH, isLM, count / 4, "routingLM" + activeLMCount, vehicleStr, true, activeLMCount);
printTimeOfRouteQuery(hopper, isCH, isLM, count / 4, "routingLM" + activeLMCount, vehicleStr, true, activeLMCount, true);
}
// compareRouting(hopper, vehicleStr, count / 5);
......@@ -130,13 +132,13 @@ public class Measurement {
if (hopper.getCHFactoryDecorator().isEnabled()) {
isCH = true;
// compareCHWithAndWithoutSOD(hopper, vehicleStr, count/5);
if (hopper.getLMFactoryDecorator().isEnabled()) {
isLM = true;
System.gc();
// try just one constellation, often ~4-6 is best
int lmCount = 5;
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCHLM" + lmCount, vehicleStr, true, lmCount);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCHLM" + lmCount, vehicleStr, true, lmCount, true);
}
isLM = false;
......@@ -145,8 +147,9 @@ public class Measurement {
CHGraph lg = g.getGraph(CHGraph.class, weighting);
fillAllowedEdges(lg.getAllEdges(), allowedEdges);
printMiscUnitPerfTests(lg, isCH, encoder, count * 100, allowedEdges);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH", vehicleStr, true, -1);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_instr", vehicleStr, false, -1);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH", vehicleStr, true, -1, true);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_sod", vehicleStr, true, -1, false);
printTimeOfRouteQuery(hopper, isCH, isLM, count, "routingCH_no_instr", vehicleStr, false, -1, true);
}
logger.info("store into " + propLocation);
} catch (Exception ex) {
......@@ -322,9 +325,59 @@ public class Measurement {
}
}
private void compareCHWithAndWithoutSOD(final GraphHopper hopper, String vehicle, int count) {
logger.info("Comparing " + count + " routes for CH with and without stall on demand." +
" Differences will be printed to stderr.");
final Random rand = new Random(seed);
final Graph g = hopper.getGraphHopperStorage();
final NodeAccess na = g.getNodeAccess();
for (int i = 0; i < count; i++) {
int from = rand.nextInt(maxNode);
int to = rand.nextInt(maxNode);
double fromLat = na.getLatitude(from);
double fromLon = na.getLongitude(from);
double toLat = na.getLatitude(to);
double toLon = na.getLongitude(to);
GHRequest sodReq = new GHRequest(fromLat, fromLon, toLat, toLon).
setWeighting("fastest").
setVehicle(vehicle).
setAlgorithm(DIJKSTRA_BI);
GHRequest noSodReq = new GHRequest(fromLat, fromLon, toLat, toLon).
setWeighting("fastest").
setVehicle(vehicle).
setAlgorithm(DIJKSTRA_BI);
noSodReq.getHints().put("stall_on_demand", false);
GHResponse sodRsp = hopper.route(sodReq);
GHResponse noSodRsp = hopper.route(noSodReq);
String locStr = " iteration " + i + ". " + fromLat + "," + fromLon + " -> " + toLat + "," + toLon;
if (sodRsp.hasErrors()) {
if (noSodRsp.hasErrors()) {
logger.info("Error with and without SOD");
continue;
} else {
logger.error("Error with SOD but not without SOD" + locStr);
continue;
}
}
String infoStr =
" weight:" + noSodRsp.getBest().getRouteWeight() + ", original: " + sodRsp.getBest().getRouteWeight()
+ " distance:" + noSodRsp.getBest().getDistance() + ", original: " + sodRsp.getBest().getDistance()
+ " time:" + Helper.round2(noSodRsp.getBest().getTime() / 1000) + ", original: " + Helper.round2(sodRsp.getBest().getTime() / 1000)
+ " points:" + noSodRsp.getBest().getPoints().size() + ", original: " + sodRsp.getBest().getPoints().size();
if (Math.abs(1 - noSodRsp.getBest().getRouteWeight() / sodRsp.getBest().getRouteWeight()) > 0.000001)
logger.error("Too big weight difference for SOD. " + locStr + infoStr);
}
}
private void printTimeOfRouteQuery(final GraphHopper hopper, final boolean ch, final boolean lm,
int count, String prefix, final String vehicle,
final boolean withInstructions, final int activeLandmarks) {
final boolean withInstructions, final int activeLandmarks, final boolean sod) {
final Graph g = hopper.getGraphHopperStorage();
final AtomicLong maxDistance = new AtomicLong(0);
final AtomicLong minDistance = new AtomicLong(Long.MAX_VALUE);
......@@ -355,6 +408,7 @@ public class Measurement {
setVehicle(vehicle);