/* This program is free software: you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public License
as published by the Free Software Foundation, either version 3 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>. */
package org.opentripplanner.routing.algorithm;
import java.util.Collection;
import org.opentripplanner.common.pqueue.BinHeap;
import org.opentripplanner.common.pqueue.OTPPriorityQueue;
import org.opentripplanner.common.pqueue.OTPPriorityQueueFactory;
import org.opentripplanner.routing.algorithm.strategies.RemainingWeightHeuristic;
import org.opentripplanner.routing.algorithm.strategies.SearchTerminationStrategy;
import org.opentripplanner.routing.algorithm.strategies.SkipTraverseResultStrategy;
import org.opentripplanner.routing.algorithm.strategies.TrivialRemainingWeightHeuristic;
import org.opentripplanner.routing.core.RoutingContext;
import org.opentripplanner.routing.core.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.services.SPTService;
import org.opentripplanner.routing.spt.DefaultShortestPathTreeFactory;
import org.opentripplanner.routing.spt.ShortestPathTree;
import org.opentripplanner.routing.spt.ShortestPathTreeFactory;
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;
/**
* Find the shortest path between graph vertices using A*.
*/
public class GenericAStar implements SPTService { // maybe this should be wrapped in a component SPT service
private static final Logger LOG = LoggerFactory.getLogger(GenericAStar.class);
private static final MonitoringStore store = MonitoringStoreFactory.getStore();
private boolean _verbose = false;
private ShortestPathTreeFactory _shortestPathTreeFactory = new DefaultShortestPathTreeFactory();
private SkipTraverseResultStrategy _skipTraversalResultStrategy;
private SearchTerminationStrategy _searchTerminationStrategy;
private TraverseVisitor traverseVisitor;
public void setShortestPathTreeFactory(ShortestPathTreeFactory shortestPathTreeFactory) {
_shortestPathTreeFactory = shortestPathTreeFactory;
}
public void setSkipTraverseResultStrategy(SkipTraverseResultStrategy skipTraversalResultStrategy) {
_skipTraversalResultStrategy = skipTraversalResultStrategy;
}
public void setSearchTerminationStrategy(SearchTerminationStrategy searchTerminationStrategy) {
_searchTerminationStrategy = searchTerminationStrategy;
}
/**
* Compute SPT using default timeout and termination strategy.
*/
@Override
public ShortestPathTree getShortestPathTree(RoutingRequest req) {
return getShortestPathTree(req, -1, _searchTerminationStrategy); // negative timeout means no timeout
}
/**
* Compute SPT using default termination strategy.
*/
@Override
public ShortestPathTree getShortestPathTree(RoutingRequest req, double timeoutSeconds) {
return this.getShortestPathTree(req, timeoutSeconds, _searchTerminationStrategy);
}
/** @return the shortest path, or null if none is found */
public ShortestPathTree getShortestPathTree(RoutingRequest options, double relTimeout,
SearchTerminationStrategy terminationStrategy) {
RoutingContext rctx = options.getRoutingContext();
long abortTime = DateUtils.absoluteTimeout(relTimeout);
// null checks on origin and destination vertices are already performed in setRoutingContext
// options.rctx.check();
ShortestPathTree spt = createShortestPathTree(options);
final RemainingWeightHeuristic heuristic = options.batch ?
new TrivialRemainingWeightHeuristic() : rctx.remainingWeightHeuristic;
// heuristic calc could actually be done when states are constructed, inside state
State initialState = new State(options);
heuristic.initialize(initialState, rctx.target);
spt.add(initialState);
// Priority Queue.
// NOTE(flamholz): 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.
OTPPriorityQueueFactory qFactory = BinHeap.FACTORY;
int initialSize = rctx.graph.getVertices().size();
initialSize = (int) Math.ceil(2 * (Math.sqrt((double) initialSize + 1)));
OTPPriorityQueue<State> pq = qFactory.create(initialSize);
pq.insert(initialState, 0);
// options = options.clone();
// /** max walk distance cannot be less than distances to nearest transit stops */
// double minWalkDistance = origin.getVertex().getDistanceToNearestTransitStop()
// + target.getDistanceToNearestTransitStop();
// options.setMaxWalkDistance(Math.max(options.getMaxWalkDistance(), rctx.getMinWalkDistance()));
int nVisited = 0;
/* the core of the A* algorithm */
while (!pq.empty()) { // Until the priority queue is empty:
if (_verbose) {
double w = pq.peek_min_key();
System.out.println("pq min key = " + w);
}
/**
* Terminate the search prematurely if we've hit our computation wall.
*/
if (abortTime < Long.MAX_VALUE && System.currentTimeMillis() > abortTime) {
LOG.warn("Search timeout. origin={} target={}", rctx.origin, rctx.target);
// Returning null indicates something went wrong and search should be aborted.
// This is distinct from the empty list of paths which implies that a result may still
// be found by retrying with altered options (e.g. max walk distance)
storeMemory();
return null; // throw timeout exception
}
// get the lowest-weight state in the queue
State 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.
continue;
}
if (traverseVisitor != null) {
traverseVisitor.visitVertex(u);
}
Vertex u_vertex = u.getVertex();
// Uncomment the following statement
// to print out a CSV (actually semicolon-separated)
// list of visited nodes for display in a GIS
// System.out.println(u_vertex + ";" + u_vertex.getX() + ";" + u_vertex.getY() + ";" +
// u.getWeight());
if (_verbose)
System.out.println(" vertex " + u_vertex);
/**
* Should we terminate the search?
*/
if (terminationStrategy != null) {
if (!terminationStrategy.shouldSearchContinue(
rctx.origin, rctx.target, u, spt, options))
break;
// TODO AMB: Replace isFinal with bicycle conditions in BasicPathParser
} else if (!options.batch && u_vertex == rctx.target && u.isFinal() && u.allPathParsersAccept()) {
LOG.debug("total vertices visited {}", nVisited);
storeMemory();
return spt;
}
Collection<Edge> edges = options.isArriveBy() ? u_vertex.getIncoming() : u_vertex.getOutgoing();
nVisited += 1;
for (Edge edge : edges) {
// Iterate over traversal results. When an edge leads nowhere (as indicated by
// returning NULL), the iteration is over.
for (State v = edge.traverse(u); v != null; v = v.getNextResult()) {
// Could be: for (State v : traverseEdge...)
if (traverseVisitor != null) {
traverseVisitor.visitEdge(edge, v);
}
// TEST: uncomment to verify that all optimisticTraverse functions are actually
// admissible
// State lbs = edge.optimisticTraverse(u);
// if ( ! (lbs.getWeight() <= v.getWeight())) {
// System.out.printf("inadmissible lower bound %f vs %f on edge %s\n",
// lbs.getWeightDelta(), v.getWeightDelta(), edge);
// }
if (_skipTraversalResultStrategy != null
&& _skipTraversalResultStrategy.shouldSkipTraversalResult(
rctx.origin, rctx.target, u, v, spt, options)) {
continue;
}
double remaining_w = computeRemainingWeight(heuristic, v, rctx.target, options);
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(" " + u.getWeight() + " -> " + v.getWeight()
+ "(w) + " + remaining_w + "(heur) = " + estimate + " vert = "
+ v.getVertex());
}
if (estimate > options.maxWeight) {
// too expensive to get here
if (_verbose)
System.out.println(" too expensive to reach, not enqueued. estimated weight = " + estimate);
} else if (isWorstTimeExceeded(v, options)) {
// too much time to get here
if (_verbose)
System.out.println(" too much time to reach, not enqueued. time = " + v.getTimeSeconds());
} else {
if (spt.add(v)) {
if (traverseVisitor != null)
traverseVisitor.visitEnqueue(v);
pq.insert(v, estimate);
}
}
}
}
}
storeMemory();
return spt;
}
private void storeMemory() {
if (store.isMonitoring("memoryUsed")) {
System.gc();
long memoryUsed = Runtime.getRuntime().totalMemory() -
Runtime.getRuntime().freeMemory();
store.setLongMax("memoryUsed", memoryUsed);
}
}
private double computeRemainingWeight(final RemainingWeightHeuristic heuristic, State v,
Vertex target, RoutingRequest options) {
// actually, the heuristic could figure this out from the TraverseOptions.
// set private member back=options.isArriveBy() on initial weight computation.
if (options.isArriveBy()) {
return heuristic.computeReverseWeight(v, target);
} else {
return heuristic.computeForwardWeight(v, target);
}
}
private boolean isWorstTimeExceeded(State v, RoutingRequest opt) {
if (opt.isArriveBy())
return v.getTimeSeconds() < opt.worstTime;
else
return v.getTimeSeconds() > opt.worstTime;
}
private ShortestPathTree createShortestPathTree(RoutingRequest opts) {
return _shortestPathTreeFactory.create(opts);
}
public void setTraverseVisitor(TraverseVisitor traverseVisitor) {
this.traverseVisitor = traverseVisitor;
}
}