org.jgrapht.alg.shortestpath.ContractionHierarchyBidirectionalDijkstra Maven / Gradle / Ivy
/*
* (C) Copyright 2019-2023, by Semen Chudakov and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.shortestpath;
import org.jgrapht.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jgrapht.util.ConcurrencyUtil;
import org.jheaps.*;
import org.jheaps.tree.*;
import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.*;
import static org.jgrapht.alg.shortestpath.BidirectionalDijkstraShortestPath.DijkstraSearchFrontier;
import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*;
/**
* Implementation of the hierarchical query algorithm based on the bidirectional Dijkstra search.
* This algorithm is designed to contracted graphs. The best speedup is achieved on sparse graphs
* with low average outdegree.
*
*
* The query algorithm is originally described the article: Robert Geisberger, Peter Sanders,
* Dominik Schultes, and Daniel Delling. 2008. Contraction hierarchies: faster and simpler
* hierarchical routing in road networks. In Proceedings of the 7th international conference on
* Experimental algorithms (WEA'08), Catherine C. McGeoch (Ed.). Springer-Verlag, Berlin,
* Heidelberg, 319-333.
*
*
* During contraction graph is divided into 2 parts which are called upward and downward graphs.
* Both parts have all vertices of the original graph. The upward graph ($G_{\uparrow}$)
* contains only those edges which source has lower level than the target and vice versa for the
* downward graph ($G_{\downarrow}$).
*
*
* For the shortest path query from $s$ to $t$, a modified bidirectional Dijkstra shortest path
* search is performed. The forward search from $s$ operates on $G_{\uparrow}$ and the backward
* search from $t$ - on the $G_{\downarrow}$. In each direction only the edges of the corresponding
* part of the graph are considered. Both searches eventually meet at the vertex $v$, which has the
* highest level in the shortest path from $s$ to $t$. Whenever a search in one direction reaches a
* vertex that has already been processed in other direction, a new candidate for a shortest path is
* found. Search is aborted in one direction if the smallest element in the corresponding priority
* queue is at least as large as the best candidate path found so far.
*
*
* After computing a contracted path, the algorithm unpacks it recursively into the actual shortest
* path using the bypassed edges stored in the contraction hierarchy graph.
*
*
* There is a possibility to provide an already computed contraction for the graph. For now there is
* no means to ensure that the specified contraction is correct, nor to fail-fast. If algorithm uses
* an incorrect contraction, the results of the search are unpredictable.
*
*
* Comparing to usual shortest path algorithm, as {@link DijkstraShortestPath},
* {@link AStarShortestPath}, etc., this algorithm spends time for computing contraction hierarchy
* but offers significant speedup in shortest path query performance. Therefore it is efficient to
* use it in order to compute many shortest path on a single graph. Furthermore, on small graphs
* (i.e with less than 1.000 vertices) the overhead of precomputation is higher than the speed at
* the stage of computing shortest paths. Typically this algorithm is used to gain speedup for
* shortest path queries on graphs of middle and large size (i.e. starting at 1.000 vertices). If a
* further query performance improvement is needed take a look at
* {@link TransitNodeRoutingShortestPath}.
*
* @param the graph vertex type
* @param the graph edge type
* @author Semen Chudakov
* @see ContractionHierarchyPrecomputation
* @since July 2019
*/
public class ContractionHierarchyBidirectionalDijkstra
extends BaseShortestPathAlgorithm
{
/**
* Contraction hierarchy which is used to compute shortest paths.
*/
private ContractionHierarchy contractionHierarchy;
/**
* Contracted graph, which is used during the queries.
*/
private Graph, ContractionEdge> contractionGraph;
/**
* Mapping from original to contracted vertices.
*/
private Map> contractionMapping;
/**
* Supplier for preferable heap implementation.
*/
private Supplier<
AddressableHeap, ContractionEdge>>> heapSupplier;
/**
* Radius of the search.
*/
private double radius;
/**
* Constructs a new instance of the algorithm for a given {@code graph} and {@code executor}. It
* is up to a user of this algorithm to handle the creation and termination of the provided
* {@code executor}. For utility methods to manage a {@code ThreadPoolExecutor} see
* {@link ConcurrencyUtil}.
*
* @param graph the graph
* @param executor executor which is used for computing the {@link ContractionHierarchy}
*/
public ContractionHierarchyBidirectionalDijkstra(Graph graph, ThreadPoolExecutor executor)
{
this(
new ContractionHierarchyPrecomputation<>(graph, executor)
.computeContractionHierarchy());
}
/**
* Constructs a new instance of the algorithm for a given {@code hierarchy}.
*
* @param hierarchy contraction of the {@code graph}
*/
public ContractionHierarchyBidirectionalDijkstra(ContractionHierarchy hierarchy)
{
this(hierarchy, Double.POSITIVE_INFINITY, PairingHeap::new);
}
/**
* Constructs a new instance of the algorithm for the given {@code hierarchy}, {@code radius}
* and {@code heapSupplier}.
*
* @param hierarchy contraction of the {@code graph}
* @param radius search radius
* @param heapSupplier supplier of the preferable heap implementation
*/
public ContractionHierarchyBidirectionalDijkstra(
ContractionHierarchy hierarchy, double radius, Supplier<
AddressableHeap, ContractionEdge>>> heapSupplier)
{
super(hierarchy.getGraph());
this.contractionHierarchy = hierarchy;
this.contractionGraph = hierarchy.getContractionGraph();
this.contractionMapping = hierarchy.getContractionMapping();
this.radius = radius;
this.heapSupplier = heapSupplier;
}
/**
* {@inheritDoc}
*/
@Override
public GraphPath getPath(V source, V sink)
{
if (!graph.containsVertex(source)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SOURCE_VERTEX);
}
if (!graph.containsVertex(sink)) {
throw new IllegalArgumentException(GRAPH_MUST_CONTAIN_THE_SINK_VERTEX);
}
// handle special case if source equals target
if (source.equals(sink)) {
return createEmptyPath(source, sink);
}
ContractionVertex contractedSource = contractionMapping.get(source);
ContractionVertex contractedSink = contractionMapping.get(sink);
// create frontiers
ContractionSearchFrontier, ContractionEdge> forwardFrontier =
new ContractionSearchFrontier<>(
new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward), heapSupplier);
ContractionSearchFrontier,
ContractionEdge> backwardFrontier = new ContractionSearchFrontier<>(
new MaskSubgraph<>(
new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward),
heapSupplier);
// initialize both frontiers
forwardFrontier.updateDistance(contractedSource, null, 0d);
backwardFrontier.updateDistance(contractedSink, null, 0d);
// initialize best path
double bestPath = Double.POSITIVE_INFINITY;
ContractionVertex bestPathCommonVertex = null;
ContractionSearchFrontier, ContractionEdge> frontier =
forwardFrontier;
ContractionSearchFrontier, ContractionEdge> otherFrontier =
backwardFrontier;
while (true) {
if (frontier.heap.isEmpty()) {
frontier.isFinished = true;
}
if (otherFrontier.heap.isEmpty()) {
otherFrontier.isFinished = true;
}
// stopping condition for search
if (frontier.isFinished && otherFrontier.isFinished) {
break;
}
// stopping condition for current frontier
if (frontier.heap.findMin().getKey() >= bestPath) {
frontier.isFinished = true;
} else {
// frontier scan
AddressableHeap.Handle, ContractionEdge>> node =
frontier.heap.deleteMin();
ContractionVertex v = node.getValue().getFirst();
double vDistance = node.getKey();
for (ContractionEdge e : frontier.graph.outgoingEdgesOf(v)) {
ContractionVertex u = frontier.graph.getEdgeTarget(e);
double eWeight = frontier.graph.getEdgeWeight(e);
frontier.updateDistance(u, e, vDistance + eWeight);
// check path with u's distance from the other frontier
double pathDistance = vDistance + eWeight + otherFrontier.getDistance(u);
if (pathDistance < bestPath) {
bestPath = pathDistance;
bestPathCommonVertex = u;
}
}
}
// swap frontiers only if the other frontier is not yet finished
if (!otherFrontier.isFinished) {
ContractionSearchFrontier, ContractionEdge> tmpFrontier =
frontier;
frontier = otherFrontier;
otherFrontier = tmpFrontier;
}
}
// create path if found
if (Double.isFinite(bestPath) && bestPath <= radius) {
return createPath(
forwardFrontier, backwardFrontier, bestPath, contractedSource, bestPathCommonVertex,
contractedSink);
} else {
return createEmptyPath(source, sink);
}
}
/**
* Builds shortest unpacked path between {@code source} and {@code sink} based on the
* information provided by search frontiers and common vertex.
*
* @param forwardFrontier forward direction frontier
* @param backwardFrontier backward direction frontier
* @param weight weight of the shortest path
* @param source path source
* @param commonVertex path common vertex
* @param sink path sink
* @return unpacked shortest path between source and sink
*/
private GraphPath createPath(
ContractionSearchFrontier, ContractionEdge> forwardFrontier,
ContractionSearchFrontier, ContractionEdge> backwardFrontier,
double weight, ContractionVertex source, ContractionVertex commonVertex,
ContractionVertex sink)
{
LinkedList edgeList = new LinkedList<>();
LinkedList vertexList = new LinkedList<>();
// add common vertex
vertexList.add(commonVertex.vertex);
// traverse forward path
ContractionVertex v = commonVertex;
while (true) {
ContractionEdge e = forwardFrontier.getTreeEdge(v);
if (e == null) {
break;
}
contractionHierarchy.unpackBackward(e, vertexList, edgeList);
v = contractionGraph.getEdgeSource(e);
}
// traverse reverse path
v = commonVertex;
while (true) {
ContractionEdge e = backwardFrontier.getTreeEdge(v);
if (e == null) {
break;
}
contractionHierarchy.unpackForward(e, vertexList, edgeList);
v = contractionGraph.getEdgeTarget(e);
}
return new GraphWalk<>(graph, source.vertex, sink.vertex, vertexList, edgeList, weight);
}
/**
* Maintains search frontier during shortest path computation.
*
* @param vertices type
* @param edges type
*/
static class ContractionSearchFrontier
extends DijkstraSearchFrontier
{
boolean isFinished;
/**
* Constructs an instance of a search frontier for the given graph, heap supplier and
* {@code isDownwardEdge} function.
*
* @param graph the graph
* @param heapSupplier supplier for the preferable heap implementation
*/
ContractionSearchFrontier(
Graph graph, Supplier>> heapSupplier)
{
super(graph, heapSupplier);
}
}
}