
org.opentripplanner.routing.algorithm.astar.AStar Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of otp Show documentation
Show all versions of otp Show documentation
The OpenTripPlanner multimodal journey planning system
package org.opentripplanner.routing.algorithm.astar;
import com.beust.jcommander.internal.Lists;
import org.opentripplanner.common.pqueue.BinHeap;
import org.opentripplanner.routing.algorithm.astar.strategies.RemainingWeightHeuristic;
import org.opentripplanner.routing.algorithm.astar.strategies.SearchTerminationStrategy;
import org.opentripplanner.routing.algorithm.astar.strategies.SkipEdgeStrategy;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.spt.GraphPath;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.util.DateUtils;
import org.opentripplanner.util.monitoring.MonitoringStore;
import org.opentripplanner.util.monitoring.MonitoringStoreFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* 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.
*
* NOTE this is now per-request scoped, which has caused some threading problems in the past.
* Always make one new instance of this class per request, it contains a lot of state fields.
*/
public class AStar {
private static final Logger LOG = LoggerFactory.getLogger(AStar.class);
// FIXME this is not really a factory, it's a way to fake a global variable. This should be stored at the OTPServer level.
private static final MonitoringStore store = MonitoringStoreFactory.getStore();
private static final double OVERSEARCH_MULTIPLIER = 4.0;
private boolean verbose = false;
private TraverseVisitor traverseVisitor;
enum RunStatus {
RUNNING, STOPPED
}
private SkipEdgeStrategy skipEdgeStrategy;
/* TODO instead of having a separate class for search state, we should just make one GenericAStar per request. */
class RunState {
public State u;
public ShortestPathTree spt;
BinHeap pq;
RemainingWeightHeuristic heuristic;
public RoutingContext rctx;
public int nVisited;
public List targetAcceptedStates;
public RunStatus status;
private RoutingRequest options;
private SearchTerminationStrategy terminationStrategy;
public Vertex u_vertex;
Double foundPathWeight = null;
public RunState(RoutingRequest options, SearchTerminationStrategy terminationStrategy) {
this.options = options;
this.terminationStrategy = terminationStrategy;
}
}
private RunState runState;
/**
* Compute SPT using default timeout and termination strategy.
*/
public ShortestPathTree getShortestPathTree(RoutingRequest req) {
return getShortestPathTree(req, -1, null); // negative timeout means no timeout
}
/**
* Compute SPT using default termination strategy.
*/
public ShortestPathTree getShortestPathTree(RoutingRequest req, double relTimeoutSeconds) {
return this.getShortestPathTree(req, relTimeoutSeconds, null);
}
/** set up a single-origin search */
public void startSearch(RoutingRequest options,
SearchTerminationStrategy terminationStrategy, long abortTime) {
startSearch(options, terminationStrategy, abortTime, true);
}
/** set up the search, optionally not adding the initial state to the queue (for multi-state Dijkstra) */
private void startSearch(RoutingRequest options,
SearchTerminationStrategy terminationStrategy, long abortTime, boolean addToQueue) {
runState = new RunState( options, terminationStrategy );
runState.rctx = options.getRoutingContext();
runState.spt = options.getNewShortestPathTree();
// We want to reuse the heuristic instance in a series of requests for the same target to avoid repeated work.
runState.heuristic = runState.rctx.remainingWeightHeuristic;
// Since initial states can be multiple, heuristic cannot depend on the initial state.
// Initializing the bidirectional heuristic is a pretty complicated operation that involves searching through
// the streets around the origin and destination.
runState.heuristic.initialize(runState.options, abortTime);
if (abortTime < Long.MAX_VALUE && System.currentTimeMillis() > abortTime) {
LOG.warn("Timeout during initialization of goal direction heuristic.");
runState = null; // Search timed out
return;
}
// Priority Queue.
// The queue is self-resizing, so we initialize it to have size = O(sqrt(|V|)) << |V|.
// For reference, a random, undirected search on a uniform 2d grid will examine roughly sqrt(|V|) vertices
// before reaching its target.
int initialSize = runState.rctx.graph.getVertices().size();
initialSize = (int) Math.ceil(2 * (Math.sqrt((double) initialSize + 1)));
runState.pq = new BinHeap<>(initialSize);
runState.nVisited = 0;
runState.targetAcceptedStates = Lists.newArrayList();
if (addToQueue) {
for (State initialState : State.getStates(options)) {
runState.spt.add(initialState);
runState.pq.insert(initialState, 0);
}
}
}
boolean iterate(){
// print debug info
if (verbose) {
double w = runState.pq.peek_min_key();
System.out.println("pq min key = " + w);
}
// interleave some heuristic-improving work (single threaded)
runState.heuristic.doSomeWork();
// get the lowest-weight state in the queue
runState.u = runState.pq.extract_min();
// check that this state has not been dominated
// and mark vertex as visited
if (!runState.spt.visit(runState.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(runState.u);
}
runState.u_vertex = runState.u.getVertex();
if (verbose)
System.out.println(" vertex " + runState.u_vertex);
runState.nVisited += 1;
Collection edges = runState.options.arriveBy ? runState.u_vertex.getIncoming() : runState.u_vertex.getOutgoing();
for (Edge edge : edges) {
if (skipEdgeStrategy != null &&
skipEdgeStrategy.shouldSkipEdge(null,null,null, edge,null,null)) {
continue;
}
// Iterate over traversal results. When an edge leads nowhere (as indicated by
// returning NULL), the iteration is over.
for (State v = edge.traverse(runState.u); v != null; v = v.getNextResult()) {
// Could be: for (State v : traverseEdge...)
if (traverseVisitor != null) {
traverseVisitor.visitEdge(edge, v);
}
double remaining_w = runState.heuristic.estimateRemainingWeight(v);
// LOG.info("{} {}", v, remaining_w);
if (remaining_w < 0 || Double.isInfinite(remaining_w) ) {
continue;
}
double estimate = v.getWeight() + remaining_w;
if (verbose) {
System.out.println(" edge " + edge);
System.out.println(" " + runState.u.getWeight() + " -> " + v.getWeight()
+ "(w) + " + remaining_w + "(heur) = " + estimate + " vert = "
+ v.getVertex());
}
// avoid enqueuing useless branches
if (estimate > runState.options.maxWeight) {
// too expensive to get here
if (verbose)
System.out.println(" too expensive to reach, not enqueued. estimated weight = " + estimate);
continue;
}
if (isWorstTimeExceeded(v, runState.options)) {
// too much time to get here
if (verbose)
System.out.println(" too much time to reach, not enqueued. time = " + v.getTimeSeconds());
continue;
}
// spt.add returns true if the state is hopeful; enqueue state if it's hopeful
if (runState.spt.add(v)) {
// report to the visitor if there is one
if (traverseVisitor != null)
traverseVisitor.visitEnqueue(v);
//LOG.info("u.w={} v.w={} h={}", runState.u.weight, v.weight, remaining_w);
runState.pq.insert(v, estimate);
}
}
}
return true;
}
void runSearch(long abortTime){
/* the core of the A* algorithm */
while (!runState.pq.empty()) { // Until the priority queue is empty:
/*
* Terminate based on timeout?
*/
if (abortTime < Long.MAX_VALUE && System.currentTimeMillis() > abortTime) {
LOG.warn("Search timeout. origin={} target={}", runState.rctx.fromVertices, runState.rctx.toVertices);
// Rather than returning null to indicate that the search was aborted/timed out,
// we instead set a flag in the routing context and return the SPT anyway. This
// allows returning a partial list results even when a timeout occurs.
runState.options.rctx.aborted = true; // signal search cancellation up to higher stack frames
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;
}
/*
* Should we terminate the search?
*/
// Don't search too far past the most recently found accepted path/state
if (runState.foundPathWeight != null &&
runState.u.getWeight() > runState.foundPathWeight * OVERSEARCH_MULTIPLIER ) {
break;
}
if (runState.terminationStrategy != null) {
if (runState.terminationStrategy.shouldSearchTerminate (
runState.rctx.fromVertices, runState.rctx.toVertices, runState.u, runState.spt, runState.options)) {
break;
}
} else if (runState.rctx.toVertices != null
&& runState.rctx.toVertices.contains(runState.u_vertex)
&& runState.u.isFinal()) {
runState.targetAcceptedStates.add(runState.u);
runState.foundPathWeight = runState.u.getWeight();
// new GraphPath(runState.u, false).dump();
/* Break out of the search if we've found the requested number of paths. */
if (runState.targetAcceptedStates.size() >= runState.options.getNumItineraries()) {
LOG.debug("total vertices visited {}", runState.nVisited);
break;
}
}
}
}
/** @return the shortest path, or null if none is found */
public ShortestPathTree getShortestPathTree(RoutingRequest options, double relTimeoutSeconds,
SearchTerminationStrategy terminationStrategy) {
ShortestPathTree spt = null;
long abortTime = DateUtils.absoluteTimeout(relTimeoutSeconds);
startSearch (options, terminationStrategy, abortTime);
if (runState != null) {
runSearch(abortTime);
spt = runState.spt;
}
storeMemory();
return spt;
}
/** Get an SPT, starting from a collection of states */
public ShortestPathTree getShortestPathTree(RoutingRequest options, double relTimeoutSeconds,
SearchTerminationStrategy terminationStrategy, Collection initialStates) {
ShortestPathTree spt = null;
long abortTime = DateUtils.absoluteTimeout(relTimeoutSeconds);
startSearch (options, terminationStrategy, abortTime, false);
if (runState != null) {
for (State state : initialStates) {
runState.spt.add(state);
// TODO: hardwired for earliest arrival
// TODO: weights are seconds, no?
runState.pq.insert(state, state.getElapsedTimeSeconds());
}
runSearch(abortTime);
spt = runState.spt;
}
return spt;
}
private void storeMemory() {
if (store.isMonitoring("memoryUsed")) {
System.gc();
long memoryUsed = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
store.setLongMax("memoryUsed", memoryUsed);
}
}
private boolean isWorstTimeExceeded(State v, RoutingRequest opt) {
if (opt.arriveBy)
return v.getTimeSeconds() < opt.worstTime;
else
return v.getTimeSeconds() > opt.worstTime;
}
public void setTraverseVisitor(TraverseVisitor traverseVisitor) {
this.traverseVisitor = traverseVisitor;
}
public List getPathsToTarget() {
if (runState == null) {
return Collections.emptyList();
}
List ret = new LinkedList<>();
for (State s : runState.targetAcceptedStates) {
if (s.isFinal()) {
ret.add(new GraphPath(s, true));
}
}
return ret;
}
public void setSkipEdgeStrategy(SkipEdgeStrategy skipEdgeStrategy) {
this.skipEdgeStrategy = skipEdgeStrategy;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy