/* 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.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.opentripplanner.routing.graph.Edge;
import org.opentripplanner.routing.graph.AbstractVertex;
import org.opentripplanner.routing.graph.Graph;
import org.opentripplanner.routing.graph.Vertex;
import org.opentripplanner.routing.location.StreetLocation;
import org.opentripplanner.common.pqueue.BinHeap;
import org.opentripplanner.routing.spt.BasicShortestPathTree;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A directed, non-time-dependent graph
* intended for calculating embeddings and other heuristic-related stuff
* @author andrewbyrd
*/
public class LowerBoundGraph {
private static Logger LOG = LoggerFactory.getLogger(LowerBoundGraph.class);
public static final int OUTGOING = 1;
public static final int UNDIRECTED = 0;
public static final int INCOMING = -1;
Graph originalGraph;
int [][] vertex;
double[][] weight;
int nVertices = 0;
Vertex[] vertexByIndex;
private int heaviest; // the last vertex that was pulled from the queue at the last search
public LowerBoundGraph(Graph original, int kind) {
originalGraph = original;
nVertices = AbstractVertex.getMaxIndex();
LOG.info("Table size is: {}", nVertices);
vertex = new int [nVertices][];
weight = new double[nVertices][];
vertexByIndex = new Vertex[nVertices];
RoutingRequest opt = new RoutingRequest();
if (kind == INCOMING)
opt.setArriveBy(true);
LOG.info("Loading origial graph into compact representation...");
ArrayList<State> svs = new ArrayList<State>();
for (Vertex u : original.getVertices()) {
State su = new State(u, opt);
svs.clear();
Iterable<Edge> edges;
if (kind == INCOMING)
edges = u.getIncoming();
else
edges = u.getOutgoing();
// avoid empty edgelist entries by traversing all edges first
for (Edge e : edges) {
State sv = e.optimisticTraverse(su);
if (sv != null) {
svs.add(sv);
//System.out.println(sv.getWeight() + " " + e);
}
}
int ui = u.getIndex();
int ne = svs.size();
vertex[ui] = new int[ne];
weight[ui] = new double[ne];
int ei = 0;
for (State sv : svs) {
vertex[ui][ei] = sv.getVertex().getIndex();
weight[ui][ei] = sv.getWeight();
ei++;
}
vertexByIndex[ui] = u;
}
if (kind == UNDIRECTED)
this.symmetricize();
}
// turn discrete normed (quasi-metric) space
// into a discrete metric space
// while maintaining lower bound property
private void symmetricize() {
LOG.info("Making distance metric commutative");
for (int ui = 0; ui < nVertices; ui++) {
//System.out.println("A " + ui);
int[] u_vs = vertex[ui];
double[] u_ws = weight[ui];
if (u_vs == null) continue;
int u_ne = u_vs.length;
//System.out.println(Arrays.toString(u_vs));
//System.out.println(Arrays.toString(u_ws));
for (int u_ei = 0; u_ei < u_ne; u_ei++) {
int vi = u_vs[u_ei];
double vw = u_ws[u_ei];
//System.out.println("B pre" + vi);
int[] v_vs = vertex[vi];
double[] v_ws = weight[vi];
//System.out.println(Arrays.toString(v_vs));
//System.out.println(Arrays.toString(v_ws));
int v_ne = v_vs.length;
boolean found = false;
for (int v_ei = 0; v_ei < v_ne; ++v_ei) {
if (v_vs[v_ei] == ui) {
if (v_ws[v_ei] > vw)
v_ws[v_ei] = vw;
else
u_ws[u_ei] = v_ws[v_ei];
found = true;
break;
}
}
if (! found) {
vertex[vi] = Arrays.copyOf(v_vs, v_ne + 1);
weight[vi] = Arrays.copyOf(v_ws, v_ne + 1);
vertex[vi][v_ne] = ui;
weight[vi][v_ne] = vw;
}
//System.out.println("B post" + vi);
//System.out.println(Arrays.toString(vertex[vi]));
//System.out.println(Arrays.toString(weight[vi]));
}
}
}
// single-source shortest path (weight to all reachable destinations)
// single-origin version
public double[] sssp(Vertex origin) {
return sssp(Arrays.asList(origin));
}
// single-source shortest path (weight to all reachable destinations)
// allows several origins
public double[] sssp(Iterable<Vertex> origins) {
double[] result = new double[nVertices];
Arrays.fill(result, Double.POSITIVE_INFINITY);
BinHeap<Integer> q = new BinHeap<Integer>();
for (Vertex origin : origins) {
int originIndex = origin.getIndex();
result[originIndex] = 0;
q.insert(originIndex, 0);
}
long t0 = System.currentTimeMillis();
while ( ! q.empty()) {
double uw = q.peek_min_key();
int ui = q.extract_min();
int[] vs = vertex[ui];
double[] ws = weight[ui];
int ne = vs.length;
if (ne > 0) // track last extracted node with outgoing edges
heaviest = ui;
for (int ei = 0; ei < ne; ei++) {
int vi = vs[ei];
double vw = ws[ei] + uw;
if (result[vi] > vw) {
result[vi] = vw;
q.insert(vi, vw);
}
}
}
LOG.info("End SSSP ({} msec)", System.currentTimeMillis() - t0);
return result;
}
// single-source shortest path (weight to all reachable destinations)
public double[] sssp(StreetLocation origin) {
double[] result = new double[nVertices];
Arrays.fill(result, Double.POSITIVE_INFINITY);
BinHeap<Integer> q = new BinHeap<Integer>();
for (Edge de : origin.getExtra()) {
Vertex toVertex = de.getToVertex();
int toIndex = toVertex.getIndex();
if (toVertex == origin) continue;
if (toIndex >= nVertices) continue;
result[toIndex] = 0;
q.insert(toIndex, 0);
}
LOG.info("Performing SSSP");
while ( ! q.empty()) {
double uw = q.peek_min_key();
int ui = q.extract_min();
int[] vs = vertex[ui];
double[] ws = weight[ui];
if (vs == null) continue;
int ne = vs.length;
//closed[ui] = true;
for (int ei = 0; ei < ne; ei++) {
int vi = vs[ei];
double vw = ws[ei] + uw;
//if (closed[vi])
// continue;
if (result[vi] > vw) {
result[vi] = vw;
q.insert(vi, vw);
}
}
}
LOG.info("End SSSP");
return result;
}
public static void main(String args[]) {
// File f = new File("/home/syncopate/otp_data/pdx/Graph.obj");
// GraphServiceImpl graphService = new GraphServiceImpl();
// graphService.setGraphPath(f);
// graphService.refreshGraph();
// Graph g = graphService.getGraph();
// LowerBoundGraph lbg = new LowerBoundGraph(g);
// for (int i = 1000; i < 9000; i += 500) {
// long t0 = System.currentTimeMillis();
// double[] result1 = lbg.sssp(i);
// long t1 = System.currentTimeMillis();
// LOG.info("search time was {} msec", (t1 - t0));
// int nFound = 0;
// int nFails = 0;
// for (double w : result1)
// if (w == Double.POSITIVE_INFINITY ) nFails++;
// else nFound++;
// LOG.info("number of unreached destinations {}/{}", nFails, result1.length);
//
// // also good for checking that optimisticTraverse is not path-dependent
// ShortestPathTree result2 = lbg.originalSSSP(lbg.vertexByIndex[i]);
// int nMatch = 0;
// int nWrong = 0;
// for (int vi = 0; vi < lbg.nVertices; vi++) {
// double w1 = result1[vi];
// double w2 = Double.POSITIVE_INFINITY;
// State s2 = result2.getState(lbg.vertexByIndex[vi]);
// if (s2 != null)
// w2 = s2.getWeight();
// if (w1 != w2) {
// LOG.trace("Mismatch : {} vs {}", w1, w2);
// nWrong++;
// } else {
// nMatch++;
// }
// }
// LOG.debug("Matches {} mismatches {}", nMatch, nWrong);
// }
//
// // test that lower bound graph really is a lower bound on travel time
//
// // test potential search speed in compact graph
//
// for (int i = 1000; i < lbg.nVertices; i += 500) {
// double[] result1 = lbg.astar(i, lbg.nVertices - i);
// double[] result2 = lbg.astar(i, lbg.nVertices - i - 10000);
// double[] result3 = lbg.astar(i, lbg.nVertices - i - 20000);
// }
}
// testing search function that does an optimistic search in the original graph
private BasicShortestPathTree originalSSSP(Vertex o){
LOG.info("Initializing original SSSP");
BinHeap<State> q = new BinHeap<State>();
RoutingRequest opt = new RoutingRequest();
BasicShortestPathTree spt = new BasicShortestPathTree(opt);
opt.maxWalkDistance = Double.MAX_VALUE;
State initialState = new State(o, opt);
q.insert(initialState, 0);
spt.add(initialState);
LOG.info("Performing original SSSP");
long t0 = System.currentTimeMillis();
while ( ! q.empty()) {
State su = q.extract_min();
if ( ! spt.visit(su)) continue;
for (Edge e : su.getVertex().getOutgoing()) {
State sv = e.optimisticTraverse(su);
if (sv != null && spt.add(sv))
q.insert(sv, sv.getWeight());
}
}
LOG.info("End original SSSP");
long t1 = System.currentTimeMillis();
LOG.info("search time was {} msec", (t1 - t0));
return spt;
}
public Vertex randomVertex() {
return vertexByIndex[(int)(Math.random() * nVertices)];
}
/**
* Return the vertex farthest (in terms of weight) from the given list of origins.
* If the list of origins is empty, return a random vertex that is 'on the edge' of the graph.
*/
public Vertex farthestFrom(List<Vertex> origins) {
if (origins.size() == 0) {
sssp(vertexByIndex[(int)(Math.random() * nVertices)]);
LOG.debug("random farthest vertex is {}", vertexByIndex[heaviest]);
sssp(vertexByIndex[heaviest]);
} else {
sssp(origins);
}
LOG.debug("farthest vertex from {} is {}", origins, vertexByIndex[heaviest]);
return vertexByIndex[heaviest];
}
}