/* 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 (props, 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.strategies; import java.util.Arrays; import java.util.BitSet; import java.util.List; import org.opentripplanner.common.pqueue.BinHeap; import org.opentripplanner.common.pqueue.OTPPriorityQueue; import org.opentripplanner.routing.core.RoutingRequest; import org.opentripplanner.routing.core.State; import org.opentripplanner.routing.edgetype.StreetTransitLink; import org.opentripplanner.routing.graph.AbstractVertex; import org.opentripplanner.routing.graph.Edge; import org.opentripplanner.routing.graph.Graph; import org.opentripplanner.routing.graph.Vertex; import org.opentripplanner.routing.location.StreetLocation; import org.opentripplanner.routing.services.RemainingWeightHeuristicFactory; import org.opentripplanner.routing.spt.BasicShortestPathTree; import org.opentripplanner.routing.spt.ShortestPathTree; import org.opentripplanner.routing.vertextype.StreetVertex; import org.opentripplanner.routing.vertextype.TransitStop; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; public class ThreadedBidirectionalHeuristic implements RemainingWeightHeuristic { private static final long serialVersionUID = 20111002L; private static Logger LOG = LoggerFactory.getLogger(LBGRemainingWeightHeuristic.class); private boolean aborted = false; Vertex target; // it is important that whenever a thread sees a higher maxFound, the preceding writes to the // node table are also visible. Since Java 1.5 (JSR133) the memory model specifies that // accessing a volatile will flush caches. Word tearing is also avoided by volatile. volatile double maxFound = 0; double[] weights; BitSet closed; Graph g; public ThreadedBidirectionalHeuristic(Graph graph) { this.g = graph; } @Override public void initialize(State s, Vertex target) { if (target == this.target) { LOG.debug("reusing existing heuristic"); } else { this.target = target; new Thread(new Worker(s)).start(); //singlethreaded debug //new Worker(s).run(); } } @Override public double computeForwardWeight(State s, Vertex target) { return computeReverseWeight(s, target); } /** * We must return an underestimate of the cost to reach the destination no matter how much * progress has been made on the heuristic search. * * All on-street vertices must be explored by the heuristic before the main search starts. * This allows us to completely skip walking outside a certain radius of the origin/destination. */ @Override public double computeReverseWeight(State s, Vertex target) { final Vertex v = s.getVertex(); // temp locations might not be found in walk search if (v instanceof StreetLocation) return 0; // if (s.getWeight() < 10 * 60) // return 0; int index = v.getIndex(); if (index < weights.length) { double h = weights[index]; if (v instanceof StreetVertex) return h; // all valid street vertices should be explored before the main search starts // but many transit vertices may not yet be explored when the search starts if (closed.get(index)) { return h; } else { return maxFound; } } else // this vertex was created after this heuristic was calculated return 0; } @Override public void reset() { } private /* inner */ class Worker implements Runnable { Vertex origin; RoutingRequest options; BinHeap<Vertex> q; // constructor runs in main thread so sequential semantics are guaranteed Worker (State s) { this.options = s.getOptions(); this.origin = s.getVertex(); // make sure distance table is initialized before starting thread LOG.debug("initializing heuristic computation thread"); int nVertices = AbstractVertex.getMaxIndex(); weights = new double[nVertices]; Arrays.fill(weights, Double.POSITIVE_INFINITY); // closed nodes must be tracked separately from (possibly tentative) weight values closed = new BitSet(nVertices); // make sure street distances are known before starting thread LOG.debug("street searches"); // forward street search first, sets values around origin to 0 streetSearch(options, false); // create a new priority queue q = new BinHeap<Vertex>(); // enqueue states for each stop within walking distance of the destination for (State stopState : streetSearch(options, true)) { // backward street search q.insert(stopState.getVertex(), stopState.getWeight()); } // once street searches are done, raise the walk limit to max // because hard walk limiting is incorrect and is observed to cause problems // for trips near the cutoff options.setMaxWalkDistance(Double.MAX_VALUE); } @Override public void run() { LOG.debug("start SSSP"); long t0 = System.currentTimeMillis(); while (!q.empty()) { if (aborted) { LOG.debug("aborted threaded heuristic calculation."); break; } double uw = q.peek_min_key(); Vertex u = q.extract_min(); int ui = u.getIndex(); closed.set(ui); // ignore vertices that could have been rekeyed but are not in this implementation if (uw > weights[ui]) continue; maxFound = uw; // OUTgoing for heuristic search when main search is arriveBy for (Edge e : options.isArriveBy() ? u.getOutgoing() : u.getIncoming()) { if (e instanceof StreetTransitLink) // no streets in this phase continue; Vertex v = options.isArriveBy() ? e.getToVertex() : e.getFromVertex(); int vi = v.getIndex(); // check for case where the edge's to vertex has been created after this worker if (vi < weights.length) { double ew = e.weightLowerBound(options); // if (ew < 0) { // LOG.error("negative edge weight {} qt {}", ew, e); // continue; // } double vw = uw + ew; if (weights[vi] > vw) { weights[vi] = vw; // selectively rekeying did not seem to offer any speed advantage q.insert(v, vw); // System.out.println("Insert " + v + " weight " + vw); } } // if the vertex is beyond the index... it is not enqueued. // BTw... the index is constantly increasing... } } LOG.info("End SSSP ({} msec)", System.currentTimeMillis() - t0); } } public synchronized void abort () { this.aborted = true; } /* Main search always proceeds from the origin to the target (arriveBy or departAfter) heuristic search always proceeds outward from the target (arriveBy or departAfter) when main search is departAfter: it gets outgoing edges and traverses them with arriveBy=false heuristic search gets incoming edges and traverses them with arriveBy=true heuristic destination street search also gets incoming edges and traverses them with arriveBy=true heuristic origin street search gets outgoing edges and traverses them with arriveBy=false when main search is arriveBy: it gets incoming edges and traverses them with arriveBy=true heuristic search gets outgoing edges and traverses them with arriveBy=false heuristic destination street search also gets outgoing edges and traverses them with arriveBy=false heuristic origin street search gets incoming edges and traverses them with arriveBy=true We traverse using the real traverse method rather than the lower bound traverse method because this allows us to keep track of the distance walked. We are not boarding transit in the street search. Though walking is only allowed at the very beginning and end of the trip, we have to worry about storing overestimated weights because places around the origin may be walked through pre-transit. Perhaps rather than tracking walk distance, we should just check the straight-line radius and only walk within that distance. This would avoid needing to call the main traversal functions. Really, as soon as you hit any transit stop during the destination search, you can't set any weights higher than that amount unless you flood-fill with 0s from the origin. The other ultra-simple option is to just set the heuristic value to 0 for all on-street locations within walking distance of the origin and destination. Another way of achieving this is to search from the origin first, saving 0s, then search from the destination without overwriting any 0s. */ private List<State> streetSearch (RoutingRequest rr, boolean fromTarget) { // final double RADIUS = 5000; rr = rr.clone(); if (fromTarget) rr.setArriveBy( ! rr.isArriveBy()); List<State> stopStates = Lists.newArrayList(); ShortestPathTree spt = new BasicShortestPathTree(rr); OTPPriorityQueue<State> pq = new BinHeap<State>(); Vertex initVertex = fromTarget ? rr.rctx.target : rr.rctx.origin; State initState = new State(initVertex, rr); pq.insert(initState, 0); while ( ! pq.empty()) { State s = pq.extract_min(); double w = s.getWeight(); Vertex v = s.getVertex(); int vi = v.getIndex(); if (v instanceof TransitStop) { stopStates.add(s); // do not save weights at transit stops since they may be reached by simpletransfer // their weights will be recorded during the main heuristic search continue; } // at this point the vertex is closed (pulled off heap). // on reverse search save measured weights. // on forward search set heuristic to 0 -- we have no idea how far to the destination, // the optimal path may use transit etc. if (!fromTarget) w = 0; if (vi < weights.length) if (weights[vi] > w) weights[vi] = w; //LOG.debug("{} at v={}", w, v); // if (vi < weights.length) // weights[vi] = 0; // here, arriveBy has been set to match actual directional behavior in this subsearch for (Edge e : rr.arriveBy ? v.getIncoming() : v.getOutgoing()) { State s1 = e.traverse(s); if (s1 == null) continue; if (spt.add(s1)) { pq.insert(s1, s1.getWeight()); } } } // return a list of all stops hit LOG.debug("hit stops: {}", stopStates); return stopStates; } public static class Factory implements RemainingWeightHeuristicFactory { @Override public RemainingWeightHeuristic getInstanceForSearch(RoutingRequest opt) { if (opt.getModes().isTransit()) { LOG.debug("Transit itinerary requested."); return new ThreadedBidirectionalHeuristic (opt.rctx.graph); } else { LOG.debug("Non-transit itinerary requested."); return new DefaultRemainingWeightHeuristic(); } } } }