
org.opentripplanner.astar.AStar Maven / Gradle / Ivy
The newest version!
package org.opentripplanner.astar;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.opentripplanner.astar.model.BinHeap;
import org.opentripplanner.astar.model.GraphPath;
import org.opentripplanner.astar.model.ShortestPathTree;
import org.opentripplanner.astar.spi.AStarEdge;
import org.opentripplanner.astar.spi.AStarState;
import org.opentripplanner.astar.spi.AStarVertex;
import org.opentripplanner.astar.spi.DominanceFunction;
import org.opentripplanner.astar.spi.RemainingWeightHeuristic;
import org.opentripplanner.astar.spi.SearchTerminationStrategy;
import org.opentripplanner.astar.spi.SkipEdgeStrategy;
import org.opentripplanner.astar.spi.TraverseVisitor;
import org.opentripplanner.framework.application.OTPRequestTimeoutException;
import org.opentripplanner.utils.time.DateUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Find the shortest path between graph vertices using A*. A basic Dijkstra search is a special case
* of AStar where the heuristic is always zero.
*/
public class AStar<
State extends AStarState,
Edge extends AStarEdge,
Vertex extends AStarVertex
> {
private static final Logger LOG = LoggerFactory.getLogger(AStar.class);
private static final boolean verbose = LOG.isDebugEnabled();
private final boolean arriveBy;
private final Set fromVertices;
private final Set toVertices;
private final RemainingWeightHeuristic heuristic;
private final SkipEdgeStrategy skipEdgeStrategy;
private final SearchTerminationStrategy terminationStrategy;
private final TraverseVisitor traverseVisitor;
private final Duration timeout;
private final ShortestPathTree spt;
private final BinHeap pq;
private final List targetAcceptedStates;
private State u;
private int nVisited;
AStar(
RemainingWeightHeuristic heuristic,
SkipEdgeStrategy skipEdgeStrategy,
TraverseVisitor traverseVisitor,
boolean arriveBy,
Set fromVertices,
Set toVertices,
SearchTerminationStrategy terminationStrategy,
DominanceFunction dominanceFunction,
Duration timeout,
Collection initialStates
) {
this.heuristic = heuristic;
this.skipEdgeStrategy = skipEdgeStrategy;
this.traverseVisitor = traverseVisitor;
this.fromVertices = fromVertices;
this.toVertices = toVertices;
this.arriveBy = arriveBy;
this.terminationStrategy = terminationStrategy;
this.timeout = Objects.requireNonNull(timeout);
this.spt = new ShortestPathTree<>(dominanceFunction);
// Initialized with a reasonable size, see #4445
this.pq = new BinHeap<>(1000);
this.nVisited = 0;
this.targetAcceptedStates = new ArrayList<>();
for (State initialState : initialStates) {
spt.add(initialState);
pq.insert(initialState, initialState.getWeight());
}
}
ShortestPathTree getShortestPathTree() {
runSearch();
return spt;
}
List> getPathsToTarget() {
runSearch();
return targetAcceptedStates
.stream()
.filter(State::isFinal)
.map(GraphPath::new)
.collect(Collectors.toList());
}
private boolean iterate() {
// print debug info
if (verbose) {
double w = pq.peek_min_key();
LOG.debug("pq min key = {}", w);
}
// get the lowest-weight state in the queue
u = pq.extract_min();
// check that this state has not been dominated
// and mark vertex as visited
if (!spt.visit(u)) {
// state has been dominated since it was added to the priority queue, so it is
// not in any optimal path. drop it on the floor and try the next one.
return false;
}
if (traverseVisitor != null) {
traverseVisitor.visitVertex(u);
}
nVisited += 1;
Vertex u_vertex = u.getVertex();
if (verbose) {
LOG.debug(" vertex {}", u_vertex);
}
Collection edges = arriveBy ? u_vertex.getIncoming() : u_vertex.getOutgoing();
for (Edge edge : edges) {
if (skipEdgeStrategy != null && skipEdgeStrategy.shouldSkipEdge(u, edge)) {
continue;
}
// Iterate over traversal results. When an edge leads nowhere (as indicated by
// returning an empty array), the iteration is over.
var states = edge.traverse(u);
for (var v : states) {
// Could be: for (State v : traverseEdge...)
if (traverseVisitor != null) {
traverseVisitor.visitEdge(edge);
}
double remaining_w = heuristic.estimateRemainingWeight(v);
if (remaining_w < 0 || Double.isInfinite(remaining_w)) {
continue;
}
double estimate = v.getWeight() + remaining_w;
if (verbose) {
LOG.debug(" edge {}", edge);
LOG.debug(
" {} -> {}(w) + {}(heur) = {} vert = {}",
u.getWeight(),
v.getWeight(),
remaining_w,
estimate,
v.getVertex()
);
}
// spt.add returns true if the state is hopeful; enqueue state if it's hopeful
if (spt.add(v)) {
// report to the visitor if there is one
if (traverseVisitor != null) {
traverseVisitor.visitEnqueue();
}
pq.insert(v, estimate);
}
}
}
return true;
}
private void runSearch() {
OTPRequestTimeoutException.checkForTimeout();
long abortTime = DateUtils.absoluteTimeout(timeout);
/* the core of the A* algorithm */
while (!pq.empty()) { // Until the priority queue is empty:
/*
* Terminate based on timeout. We don't check the termination on every round, as it is
* expensive to fetch the current time, compared to just running one more round.
*/
if (timeout != null && nVisited % 100 == 0 && System.currentTimeMillis() > abortTime) {
LOG.warn("Search timeout. origin={} target={}", fromVertices, toVertices);
// Rather than returning null to indicate that the search was aborted/timed out, we instead
// set a flag in the SPT and return it anyway. This allows returning a partial list results
// even when a timeout occurs.
spt.setAborted();
break;
}
/*
* Get next best state and, if it hasn't already been dominated, add adjacent states to queue.
* If it has been dominated, the iteration is over; don't bother checking for termination condition.
*
* Note that termination is checked after adjacent states are added. This presents the negligible inefficiency
* that adjacent states are generated for a state which could be the last one you need to check. The advantage
* of this is that the algorithm is always left in a restartable state, which is useful for debugging or
* potential future variations.
*/
if (!iterate()) {
continue;
}
if (terminationStrategy != null) {
if (terminationStrategy.shouldSearchTerminate(u)) {
break;
}
}
if (toVertices != null && toVertices.contains(u.getVertex()) && u.isFinal()) {
targetAcceptedStates.add(u);
// Break out of the search if we've found the requested number of paths.
// Currently, we can only find one path per search.
LOG.debug("total vertices visited {}", nVisited);
break;
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy