
com.graphhopper.routing.ch.EdgeBasedNodeContractor Maven / Gradle / Ivy
/*
* 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.ch;
import com.carrotsearch.hppc.*;
import com.carrotsearch.hppc.cursors.IntCursor;
import com.graphhopper.util.BitUtil;
import com.graphhopper.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Locale;
import static com.graphhopper.routing.ch.CHParameters.*;
import static com.graphhopper.util.Helper.nf;
/**
* This class is used to calculate the priority of or contract a given node in edge-based Contraction Hierarchies as it
* is required to support turn-costs. This implementation follows the 'aggressive' variant described in
* 'Efficient Routing in Road Networks with Turn Costs' by R. Geisberger and C. Vetter. Here, we do not store the center
* node for each shortcut, but introduce helper shortcuts when a loop shortcut is encountered.
*
* This class is mostly concerned with triggering the required local searches and introducing the necessary shortcuts
* or calculating the node priority, while the actual searches for witness paths are delegated to
* {@link EdgeBasedWitnessPathSearcher}.
*
* @author easbar
*/
class EdgeBasedNodeContractor implements NodeContractor {
private static final Logger LOGGER = LoggerFactory.getLogger(EdgeBasedNodeContractor.class);
private final CHPreparationGraph prepareGraph;
private PrepareGraphEdgeExplorer inEdgeExplorer;
private PrepareGraphEdgeExplorer outEdgeExplorer;
private PrepareGraphEdgeExplorer existingShortcutExplorer;
private PrepareGraphOrigEdgeExplorer sourceNodeOrigInEdgeExplorer;
private PrepareGraphOrigEdgeExplorer targetNodeOrigOutEdgeExplorer;
private ShortcutHandler shortcutHandler;
private final Params params = new Params();
private final PMap pMap;
private final StopWatch dijkstraSW = new StopWatch();
// temporary data used during node contraction
private final IntSet sourceNodes = new IntHashSet(10);
private final IntSet targetNodes = new IntHashSet(10);
private final LongSet addedShortcuts = new LongHashSet();
private final Stats addingStats = new Stats();
private final Stats countingStats = new Stats();
private Stats activeStats;
private int[] hierarchyDepths;
private EdgeBasedWitnessPathSearcher witnessPathSearcher;
// counts the total number of added shortcuts
private int addedShortcutsCount;
// edge counts used to calculate priority
private int numShortcuts;
private int numPrevEdges;
private int numOrigEdges;
private int numPrevOrigEdges;
private int numAllEdges;
// counters used for performance analysis
private int numPolledEdges;
public EdgeBasedNodeContractor(CHPreparationGraph prepareGraph, ShortcutHandler shortcutHandler, PMap pMap) {
this.prepareGraph = prepareGraph;
this.shortcutHandler = shortcutHandler;
this.pMap = pMap;
extractParams(pMap);
}
private void extractParams(PMap pMap) {
params.edgeQuotientWeight = pMap.getFloat(EDGE_QUOTIENT_WEIGHT, params.edgeQuotientWeight);
params.originalEdgeQuotientWeight = pMap.getFloat(ORIGINAL_EDGE_QUOTIENT_WEIGHT, params.originalEdgeQuotientWeight);
params.hierarchyDepthWeight = pMap.getFloat(HIERARCHY_DEPTH_WEIGHT, params.hierarchyDepthWeight);
}
@Override
public void initFromGraph() {
inEdgeExplorer = prepareGraph.createInEdgeExplorer();
outEdgeExplorer = prepareGraph.createOutEdgeExplorer();
existingShortcutExplorer = prepareGraph.createOutEdgeExplorer();
sourceNodeOrigInEdgeExplorer = prepareGraph.createInOrigEdgeExplorer();
targetNodeOrigOutEdgeExplorer = prepareGraph.createOutOrigEdgeExplorer();
witnessPathSearcher = new EdgeBasedWitnessPathSearcher(prepareGraph, pMap);
hierarchyDepths = new int[prepareGraph.getNodes()];
}
@Override
public void prepareContraction() {
// not needed
}
@Override
public float calculatePriority(int node) {
activeStats = countingStats;
resetEdgeCounters();
countPreviousEdges(node);
if (numAllEdges == 0)
// this node is isolated, maybe it belongs to a removed subnetwork, in any case we can quickly contract it
// no shortcuts will be introduced
return Float.NEGATIVE_INFINITY;
stats().stopWatch.start();
findAndHandlePrepareShortcuts(node, this::countShortcuts);
stats().stopWatch.stop();
// the higher the priority the later (!) this node will be contracted
float edgeQuotient = numShortcuts / (float) numPrevEdges;
float origEdgeQuotient = numOrigEdges / (float) numPrevOrigEdges;
int hierarchyDepth = hierarchyDepths[node];
float priority = params.edgeQuotientWeight * edgeQuotient +
params.originalEdgeQuotientWeight * origEdgeQuotient +
params.hierarchyDepthWeight * hierarchyDepth;
if (LOGGER.isTraceEnabled())
LOGGER.trace("node: {}, eq: {} / {} = {}, oeq: {} / {} = {}, depth: {} --> {}",
node,
numShortcuts, numPrevEdges, edgeQuotient,
numOrigEdges, numPrevOrigEdges, origEdgeQuotient,
hierarchyDepth, priority);
return priority;
}
@Override
public IntContainer contractNode(int node) {
activeStats = addingStats;
stats().stopWatch.start();
findAndHandlePrepareShortcuts(node, this::addShortcutsToPrepareGraph);
insertShortcuts(node);
IntContainer neighbors = prepareGraph.disconnect(node);
updateHierarchyDepthsOfNeighbors(node, neighbors);
stats().stopWatch.stop();
return neighbors;
}
@Override
public void finishContraction() {
shortcutHandler.finishContraction();
}
@Override
public long getAddedShortcutsCount() {
return addedShortcutsCount;
}
@Override
public long getDijkstraCount() {
return witnessPathSearcher.getTotalNumSearches();
}
@Override
public float getDijkstraSeconds() {
return dijkstraSW.getCurrentSeconds();
}
@Override
public String getStatisticsString() {
String result = "sc-handler-count: " + countingStats + ", sc-handler-contract: " + addingStats + ", " +
witnessPathSearcher.getStatisticsString();
witnessPathSearcher.resetStats();
return result;
}
public int getNumPolledEdges() {
return numPolledEdges;
}
/**
* This method performs witness searches between all nodes adjacent to the given node and calls the
* given handler for all required shortcuts.
*/
private void findAndHandlePrepareShortcuts(int node, PrepareShortcutHandler shortcutHandler) {
numPolledEdges = 0;
stats().nodes++;
addedShortcuts.clear();
// first we need to identify the possible source nodes from which we can reach the center node
sourceNodes.clear();
PrepareGraphEdgeIterator incomingEdges = inEdgeExplorer.setBaseNode(node);
while (incomingEdges.next()) {
int sourceNode = incomingEdges.getAdjNode();
if (sourceNode == node) {
continue;
}
boolean isNewSourceNode = sourceNodes.add(sourceNode);
if (!isNewSourceNode) {
continue;
}
// for each source node we need to look at every incoming original edge and find the initial entries
PrepareGraphOrigEdgeIterator origInIter = sourceNodeOrigInEdgeExplorer.setBaseNode(sourceNode);
while (origInIter.next()) {
int numInitialEntries = witnessPathSearcher.initSearch(node, sourceNode, GHUtility.getEdgeFromEdgeKey(origInIter.getOrigEdgeKeyLast()));
if (numInitialEntries < 1) {
continue;
}
// now we need to identify all target nodes that can be reached from the center node
targetNodes.clear();
PrepareGraphEdgeIterator outgoingEdges = outEdgeExplorer.setBaseNode(node);
while (outgoingEdges.next()) {
int targetNode = outgoingEdges.getAdjNode();
if (targetNode == node) {
continue;
}
boolean isNewTargetNode = targetNodes.add(targetNode);
if (!isNewTargetNode) {
continue;
}
// for each target edge outgoing from a target node we need to check if reaching it requires
// a 'bridge-path'
PrepareGraphOrigEdgeIterator targetEdgeIter = targetNodeOrigOutEdgeExplorer.setBaseNode(targetNode);
while (targetEdgeIter.next()) {
dijkstraSW.start();
PrepareCHEntry entry = witnessPathSearcher.runSearch(targetNode, GHUtility.getEdgeFromEdgeKey(targetEdgeIter.getOrigEdgeKeyFirst()));
dijkstraSW.stop();
if (entry == null || Double.isInfinite(entry.weight)) {
continue;
}
PrepareCHEntry root = entry.getParent();
while (EdgeIterator.Edge.isValid(root.parent.prepareEdge)) {
root = root.getParent();
}
// removing this 'optimization' improves contraction time, but introduces more
// shortcuts (makes slower queries). note that we are not detecting 'duplicate' shortcuts at a later
// stage again, especially when we are just running with the counting handler.
long addedShortcutKey = BitUtil.LITTLE.combineIntsToLong(root.getParent().incEdgeKey, entry.incEdgeKey);
if (!addedShortcuts.add(addedShortcutKey))
continue;
// root parent weight was misused to store initial turn cost here
double initialTurnCost = root.getParent().weight;
entry.weight -= initialTurnCost;
LOGGER.trace("Adding shortcuts for target entry {}", entry);
// todo: re-implement loop-avoidance heuristic as it existed in GH 1.0? it did not work the
// way it was implemented so it was removed.
shortcutHandler.handleShortcut(root, entry, incomingEdges.getOrigEdgeCount() + outgoingEdges.getOrigEdgeCount());
}
}
numPolledEdges += witnessPathSearcher.getNumPolledEdges();
}
}
}
/**
* Calls the shortcut handler for all edges and shortcuts adjacent to the given node. After this method is called
* these edges and shortcuts will be removed from the prepare graph, so this method offers the last chance to deal
* with them.
*/
private void insertShortcuts(int node) {
shortcutHandler.startContractingNode();
{
PrepareGraphEdgeIterator iter = outEdgeExplorer.setBaseNode(node);
while (iter.next()) {
if (!iter.isShortcut())
continue;
shortcutHandler.addShortcut(iter.getPrepareEdge(), node, iter.getAdjNode(),
GHUtility.getEdgeFromEdgeKey(iter.getOrigEdgeKeyFirst()), GHUtility.getEdgeFromEdgeKey(iter.getOrigEdgeKeyLast()),
iter.getSkipped1(), iter.getSkipped2(), iter.getWeight(), false);
}
}
{
PrepareGraphEdgeIterator iter = inEdgeExplorer.setBaseNode(node);
while (iter.next()) {
if (!iter.isShortcut())
continue;
// we added loops using the outEdgeExplorer already above
if (iter.getAdjNode() == node)
continue;
shortcutHandler.addShortcut(iter.getPrepareEdge(), node, iter.getAdjNode(),
GHUtility.getEdgeFromEdgeKey(iter.getOrigEdgeKeyFirst()), GHUtility.getEdgeFromEdgeKey(iter.getOrigEdgeKeyLast()),
iter.getSkipped1(), iter.getSkipped2(), iter.getWeight(), true);
}
}
addedShortcutsCount += shortcutHandler.finishContractingNode();
}
private void countPreviousEdges(int node) {
// todo: this edge counting can probably be simplified, but we might need to re-optimize heuristic parameters then
PrepareGraphEdgeIterator outIter = outEdgeExplorer.setBaseNode(node);
while (outIter.next()) {
numAllEdges++;
numPrevEdges++;
numPrevOrigEdges += outIter.getOrigEdgeCount();
}
PrepareGraphEdgeIterator inIter = inEdgeExplorer.setBaseNode(node);
while (inIter.next()) {
numAllEdges++;
// do not consider loop edges a second time
if (inIter.getBaseNode() == inIter.getAdjNode())
continue;
numPrevEdges++;
numPrevOrigEdges += inIter.getOrigEdgeCount();
}
}
private void updateHierarchyDepthsOfNeighbors(int node, IntContainer neighbors) {
int level = hierarchyDepths[node];
for (IntCursor n : neighbors) {
if (n.value == node)
continue;
hierarchyDepths[n.value] = Math.max(hierarchyDepths[n.value], level + 1);
}
}
private PrepareCHEntry addShortcutsToPrepareGraph(PrepareCHEntry edgeFrom, PrepareCHEntry edgeTo, int origEdgeCount) {
if (edgeTo.parent.prepareEdge != edgeFrom.prepareEdge) {
// counting origEdgeCount correctly is tricky with loop shortcuts and the recursion we use here. so we
// simply ignore this, it probably does not matter that much
PrepareCHEntry prev = addShortcutsToPrepareGraph(edgeFrom, edgeTo.getParent(), origEdgeCount);
return doAddShortcut(prev, edgeTo, origEdgeCount);
} else {
return doAddShortcut(edgeFrom, edgeTo, origEdgeCount);
}
}
private PrepareCHEntry doAddShortcut(PrepareCHEntry edgeFrom, PrepareCHEntry edgeTo, int origEdgeCount) {
int from = edgeFrom.parent.adjNode;
int adjNode = edgeTo.adjNode;
final PrepareGraphEdgeIterator iter = existingShortcutExplorer.setBaseNode(from);
while (iter.next()) {
if (!isSameShortcut(iter, adjNode, edgeFrom.getParent().incEdgeKey, edgeTo.incEdgeKey)) {
// this is some other (shortcut) edge -> we do not care
continue;
}
final double existingWeight = iter.getWeight();
if (existingWeight <= edgeTo.weight) {
// our shortcut already exists with lower weight --> do nothing
PrepareCHEntry entry = new PrepareCHEntry(iter.getPrepareEdge(), iter.getOrigEdgeKeyLast(), adjNode, existingWeight);
entry.parent = edgeFrom.parent;
return entry;
} else {
// update weight
iter.setSkippedEdges(edgeFrom.prepareEdge, edgeTo.prepareEdge);
iter.setWeight(edgeTo.weight);
iter.setOrigEdgeCount(origEdgeCount);
PrepareCHEntry entry = new PrepareCHEntry(iter.getPrepareEdge(), iter.getOrigEdgeKeyLast(), adjNode, edgeTo.weight);
entry.parent = edgeFrom.parent;
return entry;
}
}
// our shortcut is new --> add it
// this is a bit of a hack, we misuse incEdgeKey of edgeFrom's parent to store the first orig edge
int origFirstKey = edgeFrom.getParent().incEdgeKey;
LOGGER.trace("Adding shortcut from {} to {}, weight: {}, firstOrigEdgeKey: {}, lastOrigEdgeKey: {}",
from, adjNode, edgeTo.weight, origFirstKey, edgeTo.incEdgeKey);
int prepareEdge = prepareGraph.addShortcut(from, adjNode, origFirstKey, edgeTo.incEdgeKey, edgeFrom.prepareEdge, edgeTo.prepareEdge, edgeTo.weight, origEdgeCount);
// does not matter here
int incEdgeKey = -1;
PrepareCHEntry entry = new PrepareCHEntry(prepareEdge, incEdgeKey, edgeTo.adjNode, edgeTo.weight);
entry.parent = edgeFrom.parent;
return entry;
}
private boolean isSameShortcut(PrepareGraphEdgeIterator iter, int adjNode, int firstOrigEdgeKey, int lastOrigEdgeKey) {
return iter.isShortcut()
&& (iter.getAdjNode() == adjNode)
&& (iter.getOrigEdgeKeyFirst() == firstOrigEdgeKey)
&& (iter.getOrigEdgeKeyLast() == lastOrigEdgeKey);
}
private void resetEdgeCounters() {
numShortcuts = 0;
numPrevEdges = 0;
numOrigEdges = 0;
numPrevOrigEdges = 0;
numAllEdges = 0;
}
@Override
public void close() {
prepareGraph.close();
inEdgeExplorer = null;
outEdgeExplorer = null;
existingShortcutExplorer = null;
sourceNodeOrigInEdgeExplorer = null;
targetNodeOrigOutEdgeExplorer = null;
shortcutHandler = null;
witnessPathSearcher.close();
sourceNodes.release();
targetNodes.release();
addedShortcuts.release();
hierarchyDepths = null;
}
private Stats stats() {
return activeStats;
}
@FunctionalInterface
private interface PrepareShortcutHandler {
void handleShortcut(PrepareCHEntry edgeFrom, PrepareCHEntry edgeTo, int origEdgeCount);
}
private void countShortcuts(PrepareCHEntry edgeFrom, PrepareCHEntry edgeTo, int origEdgeCount) {
int fromNode = edgeFrom.parent.adjNode;
int toNode = edgeTo.adjNode;
int firstOrigEdgeKey = edgeFrom.getParent().incEdgeKey;
int lastOrigEdgeKey = edgeTo.incEdgeKey;
// check if this shortcut already exists
final PrepareGraphEdgeIterator iter = existingShortcutExplorer.setBaseNode(fromNode);
while (iter.next()) {
if (isSameShortcut(iter, toNode, firstOrigEdgeKey, lastOrigEdgeKey)) {
// this shortcut exists already, maybe its weight will be updated but we should not count it as
// a new edge
return;
}
}
// this shortcut is new --> increase counts
numShortcuts++;
numOrigEdges += origEdgeCount;
}
public static class Params {
// todo: optimize
private float edgeQuotientWeight = 1;
private float originalEdgeQuotientWeight = 3;
private float hierarchyDepthWeight = 2;
}
private static class Stats {
int nodes;
StopWatch stopWatch = new StopWatch();
@Override
public String toString() {
return String.format(Locale.ROOT,
"time: %7.2fs, nodes-handled: %10s", stopWatch.getCurrentSeconds(), nf(nodes));
}
}
/**
* This handler is called on every shortcut that this contractor finds necessary to add for the contracted node.
*/
public interface ShortcutHandler {
/**
* Use this hook for any kind of initialization to be done before a node is contracted
*/
void startContractingNode();
/**
* This method is called for every shortcut found by the contractor
*/
void addShortcut(int prepareEdge, int from, int to, int origEdgeFirst, int origEdgeLast, int skipped1, int skipped2, double weight, boolean reverse);
/**
* Use this hook for any kind of post-processing after the node is contracted
*
* @return the actual number of shortcuts that were added to the graph
*/
int finishContractingNode();
/**
* This method is called at the very end of the graph contraction (after the last node was contracted)
*/
void finishContraction();
}
}