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

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

package org.opentripplanner.routing.spt;

import com.google.common.collect.HashMultiset;
import com.google.common.collect.Multiset;
import org.opentripplanner.routing.api.request.RoutingRequest;
import org.opentripplanner.routing.core.State;
import org.opentripplanner.routing.graph.Vertex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 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 public transit routing, 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.
 *
 * 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 RoutingRequest options;

    public final DominanceFunction dominanceFunction;

    private Map> stateSets;

    public ShortestPathTree (RoutingRequest options, DominanceFunction dominanceFunction) {
        this.options = options;
        this.dominanceFunction = dominanceFunction;
        stateSets = new IdentityHashMap>();
    }

    /** @return a list of GraphPaths, sometimes empty but never null. */
    public List getPaths(Vertex dest, boolean optimize) {
        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, optimize));
            }
        }
        return ret;
    }

    /** @return a default set of back-optimized paths to the target vertex. */
    public List getPaths() {
        List graphPaths = new ArrayList<>();
        for (Vertex vertex : options.getRoutingContext().toVertices) {
            graphPaths.addAll(getPaths(vertex, true));
        }
        return graphPaths;
    }

    /** @return a single optimal, optionally back-optimized path to the given vertex. */
    public GraphPath getPath(Vertex dest, boolean optimize) {
        State s = getState(dest);
        if (s == null) {
            return null;
        } else {
            return new GraphPath(s, optimize);
        }
    }

    /** @return the routing context for the search that produced this tree */
    public RoutingRequest getOptions() {
        return options;
    }
    
    /** 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(nState + " states: " + histogram.count(nState) + " vertices.");
        }
    }

    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 String toString() {
        return "ShortestPathTree(" + this.stateSets.size() + " vertices)";
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy