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

org.opentripplanner.routing.spt.ShortestPathTree Maven / Gradle / Ivy

There is a newer version: 2.6.0
Show newest version
package org.opentripplanner.routing.spt;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class keeps track which graph vertices have been visited and their associated states, so
 * that decisions can be made about whether new states should be enqueued for later exploration. It
 * also allows states to be retrieved for a given target vertex.
 * 

* We no longer have different implementations of ShortestPathTree because the label-setting * (multi-state) approach used in turn restrictions, bike rental, etc. is a generalization of the * basic Dijkstra (single-state) approach. It is much more straightforward to use the more general * SPT implementation in all cases. *

* TODO: Is this still accurate? * Note that turn restrictions make all searches multi-state; however turn restrictions do not apply * when walking. The turn restriction handling is done in the base dominance function * implementation, and applies to all subclasses. It essentially splits each vertex into N vertices * depending on the incoming edge being taken. */ public class ShortestPathTree { private static final Logger LOG = LoggerFactory.getLogger(ShortestPathTree.class); public final DominanceFunction dominanceFunction; private final Map> stateSets; /** Indicates that the search timed out or was otherwise aborted. */ private boolean aborted = false; public ShortestPathTree(DominanceFunction dominanceFunction) { this.dominanceFunction = dominanceFunction; // Initialized with a reasonable size, see #4445 stateSets = new IdentityHashMap<>(10_000); } /** @return a list of GraphPaths, sometimes empty but never null. */ public List getPaths(Vertex dest) { List stateList = getStates(dest); if (stateList == null) { return Collections.emptyList(); } List ret = new LinkedList<>(); for (State s : stateList) { if (s.isFinal()) { ret.add(new GraphPath(s)); } } return ret; } /** @return a single optimal, optionally back-optimized path to the given vertex. */ public GraphPath getPath(Vertex dest) { State s = getState(dest); if (s == null) { return null; } else { return new GraphPath(s); } } /** Print out a summary of the number of states and vertices. */ public void dump() { Multiset histogram = HashMultiset.create(); int statesCount = 0; int maxSize = 0; for (Map.Entry> kv : stateSets.entrySet()) { List states = kv.getValue(); int size = states.size(); histogram.add(size); statesCount += size; if (size > maxSize) { maxSize = size; } } LOG.info( "SPT: vertices: " + stateSets.size() + " states: total: " + statesCount + " per vertex max: " + maxSize + " avg: " + (statesCount * 1.0 / stateSets.size()) ); List nStates = new ArrayList<>(histogram.elementSet()); Collections.sort(nStates); for (Integer nState : nStates) { LOG.info("{} states: {} vertices.", nState, histogram.count(nState)); } } public Set getVertices() { return stateSets.keySet(); } /** * The add method checks a new State to see if it is non-dominated and thus worth visiting later. * If so, the method returns 'true' indicating that the state is deemed useful and should be * enqueued for later exploration. The method will also perform implementation-specific actions * that track dominant or optimal states. * * @param newState the State to add to the SPT, if it is deemed non-dominated * @return a boolean value indicating whether the state was added to the tree and should therefore * be enqueued */ public boolean add(State newState) { Vertex vertex = newState.getVertex(); List states = stateSets.get(vertex); // if the vertex has no states, add one and return if (states == null) { states = new ArrayList<>(); stateSets.put(vertex, states); states.add(newState); return true; } // if the vertex has any states that dominate the new state, don't add the state // if the new state dominates any old states, remove them Iterator it = states.iterator(); while (it.hasNext()) { State oldState = it.next(); // order is important, because in the case of a tie // we want to reject the new state if (dominanceFunction.betterOrEqualAndComparable(oldState, newState)) { return false; } if (dominanceFunction.betterOrEqualAndComparable(newState, oldState)) { it.remove(); } } // any states remaining are co-dominant with the new state states.add(newState); return true; } /** * Returns the 'best' state for the given Vertex, where 'best' depends on the implementation. * * @param dest the vertex of interest * @return a 'best' state at that vertex */ public State getState(Vertex dest) { Collection states = stateSets.get(dest); if (states == null) { return null; } State ret = null; // TODO are we only checking path parser acceptance when we fetch states via this specific method? for (State s : states) { if ((ret == null || s.weight < ret.weight) && s.isFinal()) { ret = s; } } return ret; } /** * Returns a collection of 'interesting' states for the given Vertex. Depending on the * implementation, this could contain a single optimal state, a set of Pareto-optimal states, or * even states that are not known to be optimal but are judged interesting by some other * criteria. * * @param dest the vertex of interest * @return a collection of 'interesting' states at that vertex */ public List getStates(Vertex dest) { return stateSets.get(dest); } /** @return number of vertices referenced in this SPT */ public int getVertexCount() { return stateSets.keySet().size(); } /** * The visit method should be called upon extracting a State from a priority queue. It checks * whether the State is still worth visiting (i.e. whether it has been dominated since it was * enqueued) and informs the ShortestPathTree that this State's outgoing edges have been relaxed. * A state may remain in the priority queue after being dominated, and such sub-optimal states * must be caught as they come out of the queue to avoid unnecessary branching. *

* So this function checks that a state coming out of the queue is still in the Pareto-optimal set * for this vertex, which indicates that it has not been ruled out as a state on an optimal path. * Many shortest path algorithms will decrease the key of a vertex in the priority queue when it * is updated, but we store states in the queue rather than vertices, and states do not get * updated or change their weight. * TODO consider just removing states from the priority queue. *

* When the Fibonacci heap was replaced with a binary heap, the decrease-key operation was * removed for the same reason: both improve theoretical run time complexity, at the cost of * high constant factors and more complex code. *

* So there can be dominated (useless) states in the queue. When they come out we want to * ignore them rather than spend time branching out from them. * * @param state - the state about to be visited * @return - whether this state is still considered worth visiting. */ public boolean visit(State state) { boolean ret = false; for (State s : stateSets.get(state.getVertex())) { if (s == state) { ret = true; break; } } return ret; } /** @return every state in this tree */ public Collection getAllStates() { ArrayList allStates = new ArrayList<>(); for (List stateSet : stateSets.values()) { allStates.addAll(stateSet); } return allStates; } public void setAborted() { aborted = true; } public boolean isAborted() { return aborted; } public String toString() { return "ShortestPathTree(" + this.stateSets.size() + " vertices)"; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy