All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jgrapht.alg.shortestpath.ContractionHierarchyBidirectionalDijkstra Maven / Gradle / Ivy

The newest version!
/*
 * (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); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy