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

org.jgrapht.alg.shortestpath.BidirectionalAStarShortestPath 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.interfaces.*;
import org.jgrapht.graph.*;
import org.jheaps.*;
import org.jheaps.tree.*;

import java.util.*;
import java.util.function.*;

/**
 * A bidirectional version of A* algorithm.
 *
 * 

* See the Wikipedia article for details and references about * bidirectional search. This * technique does not change the worst-case behavior of the algorithm but reduces, in some cases, * the number of visited vertices in practice. *

* The algorithm was first introduced in Ira Sheldon Pohl. 1969. Bi-Directional and Heuristic Search * in Path Problems. Ph.D. Dissertation. Stanford University, Stanford, CA, USA. AAI7001588. *

* The implementation uses two termination criteria depending on if the provided heuristic is * consistent or not. Both criteria are based on the shortest path distance $\mu$ seen thus far in * the search. Initially, in both cases the algorithm sets $\mu=\infty$. Whenever the search updates * the information about the vertex $v$, it sets $\mu = min\{\mu; g_f(v) + g_b(v)\}$, where $g_f(v)$ * is the current best-known path cost from $source$ to $sink$ and $g_b(v)$ is the current * best-known path cost from $sink$ to $source$. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @author Dimitrios Michail * @author Joris Kinable * @author Jon Robison * @author Thomas Breitbart * @see AStarShortestPath */ public class BidirectionalAStarShortestPath extends BaseBidirectionalShortestPathAlgorithm { /** * Heuristic used for forward search. */ private AStarAdmissibleHeuristic forwardHeuristic; /** * Heuristic used for backward search. In general $d(u,v)\neq d(v,u)$, e.g. in the directed * graphs. */ private AStarAdmissibleHeuristic backwardHeuristic; private final Supplier> heapSupplier; /** * Constructs a new instance of the algorithm for a given graph and heuristic. * * @param graph the graph * @param heuristic heuristic that estimates distances between nodes */ public BidirectionalAStarShortestPath(Graph graph, AStarAdmissibleHeuristic heuristic) { this(graph, heuristic, PairingHeap::new); } /** * Constructs a new instance of the algorithm for a given graph, heuristic and heap supplier. * * @param graph the graph * @param heuristic heuristic that estimates distances between nodes * @param heapSupplier supplier of the preferable heap implementation */ public BidirectionalAStarShortestPath( Graph graph, AStarAdmissibleHeuristic heuristic, Supplier> heapSupplier) { super(graph); this.forwardHeuristic = Objects.requireNonNull(heuristic, "Heuristic function cannot be null!"); if (graph.getType().isDirected()) { backwardHeuristic = new ReversedGraphHeuristic( Objects.requireNonNull(heuristic, "Heuristic function cannot be null!")); } else { this.backwardHeuristic = Objects.requireNonNull(heuristic, "Heuristic function cannot be null!"); } this.heapSupplier = Objects.requireNonNull(heapSupplier, "Heap supplier cannot be null!"); } /** * {@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); } // create frontiers AStarSearchFrontier forwardFrontier = new AStarSearchFrontier(graph, sink, forwardHeuristic); AStarSearchFrontier backwardFrontier; if (graph.getType().isDirected()) { backwardFrontier = new AStarSearchFrontier(new EdgeReversedGraph<>(graph), source, backwardHeuristic); } else { backwardFrontier = new AStarSearchFrontier(graph, source, backwardHeuristic); } forwardFrontier.updateDistance(source, null, 0.0, 0.0); backwardFrontier.updateDistance(sink, null, 0.0, 0.0); // initialize best path double bestPath = Double.POSITIVE_INFINITY; V bestPathCommonVertex = null; AStarSearchFrontier frontier = forwardFrontier; AStarSearchFrontier otherFrontier = backwardFrontier; TerminationCriterion condition; if (forwardHeuristic.isConsistent(graph)) { double sourceTargetEstimate = forwardFrontier.heuristic.getCostEstimate(source, sink); condition = new ConsistentTerminationCriterion( forwardFrontier, backwardFrontier, sourceTargetEstimate); } else { condition = new InconsistentTerminationCriterion(forwardFrontier, backwardFrontier); } while (true) { // stopping condition if (condition.stop(bestPath)) { break; } // frontier scan AddressableHeap.Handle node = frontier.openList.deleteMin(); V v = node.getValue(); for (E edge : frontier.graph.outgoingEdgesOf(v)) { V successor = Graphs.getOppositeVertex(frontier.graph, edge, v); if (successor.equals(v)) { // Ignore self-loop continue; } double edgeWeight = frontier.graph.getEdgeWeight(edge); double gScore = frontier.getDistance(v); double tentativeGScore = gScore + edgeWeight; double fScore = tentativeGScore + frontier.heuristic.getCostEstimate(successor, frontier.endVertex); frontier.updateDistance(successor, edge, tentativeGScore, fScore); // check if best path can be updated double pathDistance = gScore + edgeWeight + otherFrontier.getDistance(successor); if (pathDistance < bestPath) { bestPath = pathDistance; bestPathCommonVertex = successor; } } // close current vertex frontier.closedList.add(v); // swap frontiers if (frontier.openList.size() > otherFrontier.openList.size()) { AStarSearchFrontier tmpFrontier = frontier; frontier = otherFrontier; otherFrontier = tmpFrontier; } } // create path if found if (Double.isFinite(bestPath)) { return createPath( forwardFrontier, backwardFrontier, bestPath, source, bestPathCommonVertex, sink); } else { return createEmptyPath(source, sink); } } /** * Maintains search frontier during shortest path computation. */ class AStarSearchFrontier extends BaseSearchFrontier { /** * End vertex of the frontier. */ final V endVertex; /** * Heuristic used in this frontier. */ final AStarAdmissibleHeuristic heuristic; /** * Open nodes of the frontier. */ final AddressableHeap openList; final Map> vertexToHeapNodeMap; /** * Closed nodes of the frontier. */ final Set closedList; /** * Tentative distance to the vertices in tha graph computed so far. */ final Map gScoreMap; /** * Predecessor map. */ final Map cameFrom; AStarSearchFrontier(Graph graph, V endVertex, AStarAdmissibleHeuristic heuristic) { super(graph); this.endVertex = endVertex; this.heuristic = heuristic; openList = heapSupplier.get(); vertexToHeapNodeMap = new HashMap<>(); closedList = new HashSet<>(); gScoreMap = new HashMap<>(); cameFrom = new HashMap<>(); } void updateDistance(V v, E e, double tentativeGScore, double fScore) { AddressableHeap.Handle node = vertexToHeapNodeMap.get(v); if (vertexToHeapNodeMap.containsKey(v)) { // We re-encountered a vertex. It's // either in the open or closed list. if (tentativeGScore >= gScoreMap.get(v)) {// Ignore path since it is non-improving return; } cameFrom.put(v, e); gScoreMap.put(v, tentativeGScore); if (closedList.contains(v)) { // it's in the closed list. Move node back to // open list, since we discovered a shorter path to this node closedList.remove(v); openList.insert(fScore, v); } else { // It's in the open list node.decreaseKey(fScore); } } else { // We've encountered a new vertex. cameFrom.put(v, e); gScoreMap.put(v, tentativeGScore); node = openList.insert(fScore, v); vertexToHeapNodeMap.put(v, node); } } @Override double getDistance(V v) { Double distance = gScoreMap.get(v); if (distance == null) { return Double.POSITIVE_INFINITY; } else { return distance; } } @Override E getTreeEdge(V v) { return cameFrom.get(v); } } /** * Helper class for backward search, since it should operate on the reversed graph. */ class ReversedGraphHeuristic implements AStarAdmissibleHeuristic { private final AStarAdmissibleHeuristic heuristic; ReversedGraphHeuristic(AStarAdmissibleHeuristic heuristic) { this.heuristic = heuristic; } @Override public double getCostEstimate(V sourceVertex, V targetVertex) { return heuristic.getCostEstimate(targetVertex, sourceVertex); } } /** * Termination criterion for the heuristic search. */ abstract class TerminationCriterion { final AStarSearchFrontier forward; final AStarSearchFrontier backward; TerminationCriterion(AStarSearchFrontier forward, AStarSearchFrontier backward) { this.forward = forward; this.backward = backward; } /** * Determines if the search should be terminated. * * @param bestPath length of the shortest path seen so far * @return true iff the search should be terminated */ abstract boolean stop(double bestPath); } /** * Termination criterion for the consistent heuristics. */ class ConsistentTerminationCriterion extends TerminationCriterion { final double sourceTargetEstimate; ConsistentTerminationCriterion( AStarSearchFrontier forward, AStarSearchFrontier backward, double sourceTargetEstimate) { super(forward, backward); this.sourceTargetEstimate = sourceTargetEstimate; } @Override boolean stop(double bestPath) { return forward.openList.isEmpty() || backward.openList.isEmpty() || forward.openList.findMin().getKey() + backward.openList.findMin().getKey() >= bestPath + sourceTargetEstimate; } } /** * Termination criterion for the inconsistent heuristics. */ class InconsistentTerminationCriterion extends TerminationCriterion { InconsistentTerminationCriterion(AStarSearchFrontier forward, AStarSearchFrontier backward) { super(forward, backward); } @Override boolean stop(double bestPath) { return forward.openList.isEmpty() || backward.openList.isEmpty() || Math.max( forward.openList.findMin().getKey(), backward.openList.findMin().getKey()) >= bestPath; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy