org.jgrapht.alg.shortestpath.TransitNodeRoutingPrecomputation Maven / Gradle / Ivy
/*
* (C) Copyright 2020-2023, by Semen Chudakov and Contributors.
*
* JGraphT : a free Java graph-theory library
*
* See the CONTRIBUTORS.md file distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the
* GNU Lesser General Public License v2.1 or later
* which is available at
* http://www.gnu.org/licenses/old-licenses/lgpl-2.1-standalone.html.
*
* SPDX-License-Identifier: EPL-2.0 OR LGPL-2.1-or-later
*/
package org.jgrapht.alg.shortestpath;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.Graphs;
import org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm;
import org.jgrapht.alg.util.Pair;
import org.jgrapht.graph.EdgeReversedGraph;
import org.jgrapht.graph.MaskSubgraph;
import org.jgrapht.util.CollectionUtil;
import org.jgrapht.util.ConcurrencyUtil;
import org.jheaps.AddressableHeap;
import org.jheaps.tree.PairingHeap;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import static org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths;
import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionEdge;
import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionHierarchy;
import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.ContractionVertex;
import static org.jgrapht.alg.shortestpath.DefaultManyToManyShortestPaths.DefaultManyToManyShortestPathsImpl;
/**
* Parallel implementation of the
* transit node routing route planning precomputation technique.
*
*
* This implementation is based on the {@link ContractionHierarchyPrecomputation}.
*
*
* The precomputation algorithm is described in the article: Arz, Julian & Luxen, Dennis &
* Sanders, Peter. (2013). Transit Node Routing Reconsidered. 7933. 10.1007/978-3-642-38527-8_7.
*
*
* As mentioned is the original paper, TNR in itself is not a complete algorithm, but a framework
* which is used to speed up shortest paths computations. Formally the framework consists of the
* following parts:
*
*
* - set $T ⊆ V$ of transit vertices;
* - distance table $D_{T} : T × T → {\rm I\!R}^{+}_{0}$ of shortest path distances between the
* transit vertices;
* - forward (backward) access vertex mapping $A^{↑} : V → 2^{T}$ ($A^{↓} : V → 2^{T}$). For any
* shortest $s$–$t$-path $P$ containing transit vertices, $A^{↑}(s)$ ($A^{↓}(t)$) must contain the
* first (last) transit vertex on $P$;
* - locality filter $L : V × V → \{true, false\}$. $L(s, t)$ must be $true$ when no shortest path
* between $s$ and $t$ contains a transit vertex. False positives are allowed, i.e., $L(s, t)$ may
* sometimes be $true$ even when a shortest path contains a transit vertex.
*
*
*
* To implement the TNR framework means to define how to select transit vertices and how to compute
* distance table $D_{T}$, access vertices and locality filter. This implementation selects transit
* vertices to be to $k$ vertices form the contraction hierarchy. For the details of how other parts
* of this TNR work please refer to the original paper.
*
*
* For parallelization, this implementation relies on the {@link ThreadPoolExecutor} which is
* supplied to this algorithm from outside.
*
* @param graph vertex type
* @param graph edge type
* @author Semen Chudakov
* @see ContractionHierarchyPrecomputation
*/
class TransitNodeRoutingPrecomputation
{
/**
* Special Voronoi diagram cell id to indicate, that a vertex does not belong to any cells. For
* usual Voronoi cell the ids of contracted vertices are used. Once those ids are non-negative,
* this value is guaranteed to be unique.
*/
private static final int NO_VORONOI_CELL = -1;
/**
* Contraction hierarchy which is used to compute transit node routing.
*/
private ContractionHierarchy contractionHierarchy;
/**
* Contracted graph.
*/
private Graph, ContractionEdge> contractionGraph;
/**
* Mapping of vertices in the initial graph to contracted vertices.
*/
private Map> contractionMapping;
/**
* Number of transit vertices in the graph.
*/
private int numberOfTransitVertices;
/**
* Maximum number of threads used in the computations.
*/
private int parallelism;
/**
* Supplier for the preferable heap implementation. Provided heap is used to build Voronoi
* diagram.
*/
private Supplier>> heapSupplier;
/**
* List of contracted vertices. It is used to evenly distribute work between threads in the
* parallel computations.
*/
private List> contractionVertices;
/**
* Algorithm which is used to compute many-to-many shortest paths between transit vertices.
*/
private ManyToManyShortestPathsAlgorithm manyToManyShortestPathsAlgorithm;
/**
* Set of contracted transit vertices.
*/
private Set> contractedTransitVerticesSet;
/**
* Set of transit vertices.
*/
private Set transitVerticesSet;
/**
* List of transit vertices.
*/
private List transitVerticesList;
/**
* Voronoi diagram for the contraction graph. Here the transit vertices are used as cells
* centers.
*/
private VoronoiDiagram voronoiDiagram;
/**
* Many-to-many shortest paths between transit vertices.
*/
private ManyToManyShortestPaths transitVerticesPaths;
/**
* Executor to which parallel computation tasks are submitted.
*/
private ExecutorService executor;
/**
* Decorator for {@code executor} that allows to keep track of when all submitted tasks are
* finished.
*/
private ExecutorCompletionService completionService;
/**
* Constructs an instance of the algorithm for a given {@code graph} and {@code executor}. It is
* up to a user of this algorithm to handle the creation and termination of the provided
* {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see
* {@link org.jgrapht.util.ConcurrencyUtil}.
*
* @param graph graph
* @param executor executor which will be used for parallelization
*/
public TransitNodeRoutingPrecomputation(Graph graph, ThreadPoolExecutor executor)
{
this(
new ContractionHierarchyPrecomputation<>(graph, executor).computeContractionHierarchy(),
executor);
}
/**
* Constructs an instance of the algorithm for the given {@code contractionHierarchy} and
* {@code executor}. It is up to a user of this algorithm to handle the creation and termination
* of the provided {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see
* {@link org.jgrapht.util.ConcurrencyUtil}.
*
* @param hierarchy contraction hierarchy
* @param executor executor which will be used for parallelization
*/
public TransitNodeRoutingPrecomputation(
ContractionHierarchy hierarchy, ThreadPoolExecutor executor)
{
this(hierarchy, (int) Math.sqrt(hierarchy.getGraph().vertexSet().size()), executor);
}
/**
* Constructs an instance of the algorithm for a given {@code contractionHierarchy},
* {@code numberOfTransitVertices} and {@code executor}. It is up to a user of this algorithm to
* handle the creation and termination of the provided {@code executor}. Utility methods to
* manage a {@code ThreadPoolExecutor} see {@link org.jgrapht.util.ConcurrencyUtil}.
*
* @param hierarchy contraction hierarchy
* @param numberOfTransitVertices number of transit vertices
* @param executor executor which will be used for parallelization
*/
public TransitNodeRoutingPrecomputation(
ContractionHierarchy hierarchy, int numberOfTransitVertices,
ThreadPoolExecutor executor)
{
this(hierarchy, numberOfTransitVertices, PairingHeap::new, executor);
}
/**
* Constructs an instance of the algorithm for a given {@code contractionHierarchy},
* {@code parallelism}, {@code numberOfTransitVertices}, {@code heapSupplier} and
* {@code executor}. Heap provided by the {@code heapSupplier} is used which computing the
* Voronoi diagram. It is up to a user of this algorithm to handle the creation and termination
* of the provided {@code executor}. Utility methods to manage a {@code ThreadPoolExecutor} see
* {@link org.jgrapht.util.ConcurrencyUtil}.
*
* @param hierarchy contraction hierarchy
* @param numberOfTransitVertices number of transit vertices
* @param heapSupplier supplier for preferable heap implementation
* @param executor executor which will be used for parallelization
*/
public TransitNodeRoutingPrecomputation(
ContractionHierarchy hierarchy, int numberOfTransitVertices,
Supplier>> heapSupplier,
ThreadPoolExecutor executor)
{
if (numberOfTransitVertices > hierarchy.getGraph().vertexSet().size()) {
throw new IllegalArgumentException(
"number of transit vertices is larger than the number of vertices in the graph");
}
this.contractionHierarchy = hierarchy;
this.contractionGraph = hierarchy.getContractionGraph();
this.contractionMapping = hierarchy.getContractionMapping();
this.numberOfTransitVertices = numberOfTransitVertices;
this.parallelism = executor.getMaximumPoolSize();
this.heapSupplier = heapSupplier;
this.contractionVertices =
new ArrayList<>(Collections.nCopies(contractionGraph.vertexSet().size(), null));
this.manyToManyShortestPathsAlgorithm = new CHManyToManyShortestPaths<>(hierarchy);
this.executor = executor;
this.completionService = new ExecutorCompletionService<>(this.executor);
}
/**
* Computes transit node routing based on {@code contractionHierarchy}.
*
* @return transit node routing
*/
public TransitNodeRouting computeTransitNodeRouting()
{
fillContractionVerticesList();
contractedTransitVerticesSet = selectTopKTransitVertices(numberOfTransitVertices);
transitVerticesSet = contractedTransitVerticesSet
.stream().map(v -> v.vertex).collect(Collectors.toCollection(HashSet::new));
transitVerticesList = new ArrayList<>(transitVerticesSet);
VoronoiDiagramComputation voronoiDiagramComputation = new VoronoiDiagramComputation();
voronoiDiagram = voronoiDiagramComputation.computeVoronoiDiagram();
ManyToManyShortestPaths contractedPaths = manyToManyShortestPathsAlgorithm
.getManyToManyPaths(transitVerticesSet, transitVerticesSet);
transitVerticesPaths = unpackPaths(contractedPaths);
Pair, LocalityFilter> avAndLf = computeAVAndLF();
return new TransitNodeRouting<>(
contractionHierarchy, contractedTransitVerticesSet, transitVerticesPaths,
voronoiDiagram, avAndLf.getFirst(), avAndLf.getSecond());
}
/**
* Fills {@code contractionVertices} with vertices from {@code contractionGraph}. For each
* vertex its position in the list is equal to its {@code id}.
*/
private void fillContractionVerticesList()
{
for (ContractionVertex v : contractionGraph.vertexSet()) {
contractionVertices.set(v.vertexId, v);
}
}
/**
* Unpacks in parallel contracted paths stored in {@code shortestPaths}. Unpacked path are
* returned as {@link DefaultManyToManyShortestPathsImpl}.
*
* @param shortestPaths contracted many-to-many shortest paths
* @return unpacked paths
*/
private ManyToManyShortestPaths unpackPaths(ManyToManyShortestPaths shortestPaths)
{
Map>> pathsMap =
CollectionUtil.newHashMapWithExpectedSize(numberOfTransitVertices);
for (V v : transitVerticesList) {
pathsMap.put(v, CollectionUtil.newHashMapWithExpectedSize(numberOfTransitVertices));
}
for (int taskId = 0; taskId < parallelism; ++taskId) {
PathsUnpackingTask task =
new PathsUnpackingTask(taskId, transitVerticesList, pathsMap, shortestPaths);
completionService.submit(task, null);
}
waitForTasksCompletion(parallelism);
return new DefaultManyToManyShortestPathsImpl<>(
transitVerticesSet, transitVerticesSet, pathsMap);
}
/**
* Selects top {@code numberOfTransitVertices} vertices in the contraction hierarchy as transit
* vertices.
*
* @param numberOfTransitVertices number of transit vertices to select
* @return set of transit vertices
*/
private Set> selectTopKTransitVertices(int numberOfTransitVertices)
{
int numberOfVertices = contractionGraph.vertexSet().size();
Set> result =
CollectionUtil.newHashSetWithExpectedSize(numberOfTransitVertices);
for (ContractionVertex vertex : contractionGraph.vertexSet()) {
if (vertex.contractionLevel >= numberOfVertices - numberOfTransitVertices) {
result.add(vertex);
}
}
return result;
}
/**
* Computes in parallel access vertices and locality filter for the transit node routing.
*
* @return pair of access vertices and locality filter.
*/
private Pair, LocalityFilter> computeAVAndLF()
{
LocalityFilterBuilder localityFilterBuilder =
new LocalityFilterBuilder(contractionGraph.vertexSet().size());
AccessVerticesBuilder accessVerticesBuilder =
new AccessVerticesBuilder(contractionGraph.vertexSet().size());
ContractionHierarchyBFS forwardBFS = new ContractionHierarchyBFS(
new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward));
ContractionHierarchyBFS backwardBFS = new ContractionHierarchyBFS(
new MaskSubgraph<>(
new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward));
for (int taskId = 0; taskId < parallelism; ++taskId) {
AVAndLFConstructionTask task = new AVAndLFConstructionTask(
taskId, localityFilterBuilder, accessVerticesBuilder, forwardBFS, backwardBFS);
completionService.submit(task, null);
}
waitForTasksCompletion(parallelism);
return Pair
.of(accessVerticesBuilder.buildVertices(), localityFilterBuilder.buildLocalityFilter());
}
/**
* Takes {@code numberOfTasks} tasks from the {@code completionService}.
*
* @param numberOfTasks number of tasks
*/
private void waitForTasksCompletion(int numberOfTasks)
{
for (int i = 0; i < numberOfTasks; ++i) {
try {
completionService.take().get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}
/**
* Algorithm which computes Voronoi diagram for the {@code contractionGraph}. It uses
* {@code transitVertices} as Voronoi cells centers. To build the diagram runs a Dijkstra`s
* algorithm with multiple sources on a reversed {@code graph}. Uses Voronoi cells centers as
* initial sources. During the computations for each vertex maintains distance to the closest
* cell center as well as the id if this cell center.
*/
private class VoronoiDiagramComputation
{
/**
* Priority queue which stores vertices ordered by theirs distances to the corresponding
* Voronoi cell center.
*/
private AddressableHeap> heap;
/**
* For every vertex added to the {@code heap} stores a corresponding handle.
*/
private Map,
AddressableHeap.Handle>> seen;
/**
* For every vertex stores an id of a corresponding Voronoi cell center.
*/
private int[] voronoiCells;
/**
* For every vertex stores distance to the closest Voronoi cell center.
*/
private double[] distanceToCenter;
/**
* Constructs a new instance of the algorithm.
*/
VoronoiDiagramComputation()
{
this.heap = heapSupplier.get();
this.seen = new HashMap<>();
}
/**
* Computes Voronoi diagram for {@code graph}.
*
* @return Voronoi diagram
*/
VoronoiDiagram computeVoronoiDiagram()
{
int numberOfVertices = contractionGraph.vertexSet().size();
voronoiCells = new int[numberOfVertices];
distanceToCenter = new double[numberOfVertices];
Arrays.fill(voronoiCells, NO_VORONOI_CELL);
Arrays.fill(distanceToCenter, Double.POSITIVE_INFINITY);
// mask all shortcuts in the contraction graph
Graph, ContractionEdge> searchGraph = new EdgeReversedGraph<>(
new MaskSubgraph<>(contractionGraph, v -> false, e -> e.edge == null));
for (ContractionVertex transitVertex : contractedTransitVerticesSet) {
updateDistance(transitVertex, transitVertex, 0.0);
}
while (!heap.isEmpty()) {
AddressableHeap.Handle> entry = heap.deleteMin();
double distance = entry.getKey();
ContractionVertex v = entry.getValue();
for (ContractionEdge edge : searchGraph.outgoingEdgesOf(v)) {
ContractionVertex successor = Graphs.getOppositeVertex(searchGraph, edge, v);
double updatedDistance = distance + searchGraph.getEdgeWeight(edge);
if (updatedDistance < distanceToCenter[successor.vertexId]) {
updateDistance(successor, v, updatedDistance);
}
}
}
return new VoronoiDiagram<>(voronoiCells);
}
/**
* If necessary updates distance of the {@code vertex} in the {@code heap}.
*
* @param vertex vertex
* @param predecessor predecessor of {@code vertex} in the shortest paths tree
* @param distance distance to vertex
*/
private void updateDistance(
ContractionVertex vertex, ContractionVertex predecessor, double distance)
{
AddressableHeap.Handle> handle = seen.get(vertex);
if (handle == null) {
handle = heap.insert(distance, vertex);
seen.put(vertex, handle);
visitVertex(vertex, predecessor, distance);
} else if (distance < handle.getKey()) {
handle.decreaseKey(distance);
handle.setValue(handle.getValue());
visitVertex(vertex, predecessor, distance);
}
}
/**
* If necessary updates Voronoi cell id and distance in {@code voronoiCells} and
* {@code distanceToCenter} for vertex.
*
* @param vertex vertex
* @param predecessor predecessor of {@code vertex} in the shortest paths tree
* @param distance distance to vertex
*/
private void visitVertex(
ContractionVertex vertex, ContractionVertex predecessor, double distance)
{
int updatedVoronoiCell;
if (vertex.vertexId == predecessor.vertexId) {
updatedVoronoiCell = vertex.vertexId;
} else {
updatedVoronoiCell = this.voronoiCells[predecessor.vertexId];
}
this.voronoiCells[vertex.vertexId] = updatedVoronoiCell;
this.distanceToCenter[vertex.vertexId] = distance;
}
}
/**
* BFS algorithm which is used to compute access vertices and locality filter. Runs a CH BFS
* query over contractionGraph. Does not traverse edges leaving transit vertices. Reports all
* traversed transit vertices as access vertices. For every traversed non-transit vertex reports
* a corresponding Voronoi cell id. Those ids are then used to construct locality filter.
*/
private class ContractionHierarchyBFS
{
/**
* Search graph.
*/
private Graph, ContractionEdge> contractionGraph;
/**
* Constructs a new instance of the algorithm for the given {@code graph}.
*
* @param contractionGraph contraction graph
*/
public ContractionHierarchyBFS(
Graph, ContractionEdge> contractionGraph)
{
this.contractionGraph = contractionGraph;
}
/**
* Runs a forward CH BFS query to calculate access vertices and ids of visited Voronoi
* cells.
*
* @param vertex search starting vertex
* @return access vertices and visited Voronoi cells ids
*/
public Pair, Set> runSearch(ContractionVertex vertex)
{
Set accessVertices = new HashSet<>();
Set visitedVoronoiCells = new HashSet<>();
Set visitedVerticesIds = new HashSet<>();
Queue> queue = new LinkedList<>();
queue.add(vertex);
while (!queue.isEmpty()) {
ContractionVertex v = queue.remove();
visitedVerticesIds.add(v.vertexId);
if (contractedTransitVerticesSet.contains(v)) {
accessVertices.add(v.vertex);
} else {
visitedVoronoiCells.add(voronoiDiagram.getVoronoiCellId(v));
for (ContractionEdge e : contractionGraph.outgoingEdgesOf(v)) {
ContractionVertex successor =
Graphs.getOppositeVertex(contractionGraph, e, v);
if (!visitedVerticesIds.contains(successor.vertexId)) {
queue.add(successor);
}
}
}
}
return Pair.of(accessVertices, visitedVoronoiCells);
}
}
/**
* Provides API to build an {@code AccessVertices} object.
*/
private class AccessVerticesBuilder
{
/**
* For every vertex in {@code contractionGraph} stores a list of forward access vertices. Id
* of a contracted vertex is equal to the index in this list, at which corresponding access
* vertices are stored.
*/
private List>> forwardAccessVertices;
/**
* For every vertex in {@code contractionGraph} stores a list of backward access vertices.
* Id of a contracted vertex is equal to the index in this list, at which corresponding
* access vertices are stored.
*/
private List>> backwardAccessVertices;
/**
* Constructs an instance for the given {@code numberOfVertices}.
*
* @param numberOfVertices number of vertices in a m graph
*/
public AccessVerticesBuilder(int numberOfVertices)
{
this.forwardAccessVertices = new ArrayList<>(numberOfVertices);
this.backwardAccessVertices = new ArrayList<>(numberOfVertices);
for (int i = 0; i < numberOfVertices; ++i) {
forwardAccessVertices.add(new ArrayList<>());
backwardAccessVertices.add(new ArrayList<>());
}
}
/**
* Builds a new instance of {@code AccessVertices} using {@code forwardAccessVertices} and
* {@code backwardAccessVertices}.
*
* @return access vertices
*/
public AccessVertices buildVertices()
{
return new AccessVertices<>(forwardAccessVertices, backwardAccessVertices);
}
/**
* Computes a list of forward access vertices for {@code v} using {@code vertices} and adds
* them to the {@code forwardAccessVertices}.
*
* @param v vertex
* @param vertices transit vertices
*/
public void addForwardAccessVertices(ContractionVertex v, Set vertices)
{
ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths manyToManyShortestPaths =
manyToManyShortestPathsAlgorithm
.getManyToManyPaths(Collections.singleton(v.vertex), vertices);
Set prunedVertices =
getPrunedAccessVertices(v.vertex, vertices, manyToManyShortestPaths, true);
List> accessVerticesList = forwardAccessVertices.get(v.vertexId);
for (V unpackedVertex : vertices) {
if (!prunedVertices.contains(unpackedVertex)) {
accessVerticesList.add(
new AccessVertex<>(
unpackedVertex,
manyToManyShortestPaths.getPath(v.vertex, unpackedVertex)));
}
}
}
/**
* Computes a list of backward access vertices for {@code v} using {@code vertices} and adds
* them to the {@code backwardAccessVertices}.
*
* @param v vertex
* @param vertices transit vertices
*/
public void addBackwardAccessVertices(ContractionVertex v, Set vertices)
{
ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths manyToManyShortestPaths =
manyToManyShortestPathsAlgorithm
.getManyToManyPaths(vertices, Collections.singleton(v.vertex));
Set prunedVertices =
getPrunedAccessVertices(v.vertex, vertices, manyToManyShortestPaths, false);
List> accessVerticesList = backwardAccessVertices.get(v.vertexId);
for (V unpackedVertex : vertices) {
if (!prunedVertices.contains(unpackedVertex)) {
accessVerticesList.add(
new AccessVertex<>(
unpackedVertex,
manyToManyShortestPaths.getPath(unpackedVertex, v.vertex)));
}
}
}
/**
* Selects redundant access vertices from {@code vertices}.
*
* @param v vertex
* @param vertices transit vertices
* @param manyToManyShortestPaths transit vertices paths
* @param forwardAccessVertices if {@code vertices} are forward access vertices for not wrt
* {@code v}
* @return redundant access vertices
*/
private Set getPrunedAccessVertices(
V v, Set vertices, ManyToManyShortestPaths manyToManyShortestPaths,
boolean forwardAccessVertices)
{
Set result = new HashSet<>();
for (V v1 : vertices) {
if (!result.contains(v1)) {
for (V v2 : vertices) {
if (!v1.equals(v2) && !result.contains(v2)) {
if (forwardAccessVertices) {
if (manyToManyShortestPaths.getWeight(v, v1) + transitVerticesPaths
.getWeight(v1, v2) <= manyToManyShortestPaths
.getWeight(v, v2))
{
result.add(v2);
}
} else {
if (transitVerticesPaths.getWeight(v2, v1) + manyToManyShortestPaths
.getWeight(v1, v) <= manyToManyShortestPaths.getWeight(v2, v))
{
result.add(v2);
}
}
}
}
}
}
return result;
}
}
/**
* Provides API to build a {@code LocalityFilter} object.
*/
private class LocalityFilterBuilder
{
/**
* Visited Voronoi cells by a forward {@code ContractionHierarchyBFS} search.
*/
private List> visitedForwardVoronoiCells;
/**
* Visited Voronoi cells by a backward {@code ContractionHierarchyBFS} search.
*/
private List> visitedBackwardVoronoiCells;
/**
* Constructs an instance for the given {@code numberOfVertices}.
*
* @param numberOfVertices number of vertices in graph
*/
public LocalityFilterBuilder(int numberOfVertices)
{
this.visitedForwardVoronoiCells = new ArrayList<>(numberOfVertices);
this.visitedBackwardVoronoiCells = new ArrayList<>(numberOfVertices);
for (int i = 0; i < numberOfVertices; ++i) {
visitedForwardVoronoiCells.add(null);
visitedBackwardVoronoiCells.add(null);
}
}
/**
* Adds {@code visitedVoronoiCells} to this builder in the forward direction for
* {@code vertex}.
*
* @param vertex vertex
* @param visitedVoronoiCells visited Voronoi cells
*/
public void addForwardVisitedVoronoiCells(
ContractionVertex vertex, Set visitedVoronoiCells)
{
this.visitedForwardVoronoiCells.set(vertex.vertexId, visitedVoronoiCells);
}
/**
* Adds {@code visitedVoronoiCells} to this builder in the backward direction for
* {@code vertex}.
*
* @param vertex vertex
* @param visitedVoronoiCells visited Voronoi cells
*/
public void addBackwardVisitedVoronoiCells(
ContractionVertex vertex, Set visitedVoronoiCells)
{
this.visitedBackwardVoronoiCells.set(vertex.vertexId, visitedVoronoiCells);
}
/**
* Builds an instance of {@code LocalityFilter} using {@code visitedForwardVoronoiCells} and
* {@code visitedBackwardVoronoiCells}.
*
* @return locality filter
*/
public LocalityFilter buildLocalityFilter()
{
return new LocalityFilter<>(
contractionMapping, visitedForwardVoronoiCells, visitedBackwardVoronoiCells);
}
}
/**
* This class represents return type of this algorithm and contains all data computed during the
* execution of the algorithm. Formally it consists of:
*
*
* - {@link ContractionHierarchy} which was used to compute this transit node routing;
* - set of selected transit vertices;
* - {@link ManyToManyShortestPaths} between transit vertices;
* - {@link VoronoiDiagram} computed using transit vertices a cell centers;
* - {@link AccessVertices};
* - {@link LocalityFilter}.
*
*
* @param graph vertex type
* @param graph edge type
*/
static class TransitNodeRouting
{
/**
* Contraction hierarchy based on which this transit node routing was computed.
*/
private ContractionHierarchy contractionHierarchy;
/**
* Selected transit vertices.
*/
private Set> transitVertices;
/**
* Paths between every pair of transit vertices.
*/
private ManyToManyShortestPaths transitVerticesPaths;
/**
* Voronoi diagram of the graph using {@code transitVertices} as cells centers.
*/
private VoronoiDiagram voronoiDiagram;
/**
* Forward and backward access vertices for every vertex in the contraction graph.
*/
private AccessVertices accessVertices;
/**
* Locality filter of this transit node routing.
*/
private LocalityFilter localityFilter;
/**
* Returns contraction hierarchy of this transit node routing.
*
* @return contraction hierarchy of this transit node routing
*/
public ContractionHierarchy getContractionHierarchy()
{
return contractionHierarchy;
}
/**
* Returns transit vertices of this transit node routing.
*
* @return transit vertices of this transit node routing
*/
public Set> getTransitVertices()
{
return transitVertices;
}
/**
* Returns paths between every pair of {@code transitVertices}.
*
* @return paths between every pair of {@code transitVertices}
*/
public ManyToManyShortestPaths getTransitVerticesPaths()
{
return transitVerticesPaths;
}
/**
* Returns Voronoi diagram of this transit node routing.
*
* @return Voronoi diagram of this transit node routing
*/
public VoronoiDiagram getVoronoiDiagram()
{
return voronoiDiagram;
}
/**
* Returns access vertices of this transit node routing.
*
* @return access vertices of this transit node routing
*/
public AccessVertices getAccessVertices()
{
return accessVertices;
}
/**
* Returns locality filter of this transit node routing.
*
* @return locality filter of this transit node routing
*/
public LocalityFilter getLocalityFilter()
{
return localityFilter;
}
/**
* Constructs a new instance for the given {@code contractionHierarchy},
* {@code transitVertices}, {@code transitVerticesPaths}, {@code voronoiDiagram},
* {@code accessVertices} and {@code localityFilter}.
*
* @param contractionHierarchy contraction hierarchy
* @param transitVertices transit vertices
* @param transitVerticesPaths paths between transit vertices
* @param voronoiDiagram Voronoi diagram
* @param accessVertices access vertices
* @param localityFilter locality filter
*/
public TransitNodeRouting(
ContractionHierarchy contractionHierarchy,
Set> transitVertices,
ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths transitVerticesPaths,
VoronoiDiagram voronoiDiagram, AccessVertices accessVertices,
LocalityFilter localityFilter)
{
this.contractionHierarchy = contractionHierarchy;
this.transitVertices = transitVertices;
this.transitVerticesPaths = transitVerticesPaths;
this.voronoiDiagram = voronoiDiagram;
this.localityFilter = localityFilter;
this.accessVertices = accessVertices;
}
}
/**
* Voronoi diagram for a graph. Formally each cell in the diagram is defined as $Vor(v) = \{u ∈
* V : ∀w ∈ T$ \ $ \{v\} : \mu(u, v) ≤ \mu(u, w)\}$, where $V$ is the vertex set, $T$ is a set
* of vertaccess verticesices representing Voronoi cells centers and $\mu(u,v)$ denotes distance
* between vertices $u$ and $v$.
*
* @param graph vertex type
*/
public static class VoronoiDiagram
{
/**
* For each vertex in {@code contractionGraph} contains id of its Voronoi cell, or
* {@code NO_VORONOI_CELL} if it does not belong to any cell.
*/
private int[] voronoiCells;
/**
* Constructs a new instance for the given {@code voronoiCells}.
*
* @param voronoiCells Voronoi cells ids
*/
public VoronoiDiagram(int[] voronoiCells)
{
this.voronoiCells = voronoiCells;
}
/**
* Returns Voronoi cell id which corresponds to {@code vertex}.
*
* @param vertex vertex
* @return Voronoi cell id
*/
public int getVoronoiCellId(ContractionVertex vertex)
{
return voronoiCells[vertex.vertexId];
}
}
/**
* Search space based locality filter.
*
*
* Formally a locality filter is defined as $L : V × V → \{true, false\}$. $L(s, t)$ must be
* $true$ when no shortest path between $s$ and $t$ contains a transit vertex.
*
*
* For every vertex in the {@code contractionGraph} stores two sets of visited Voronoi cells by
* forward and backward {@code ContractionHierarchyBFS}.
*
* @param graph vertex type
*/
public static class LocalityFilter
{
/**
* Mapping of vertices in the initial graph to the vertices in the contraction graph.
*/
private Map> contractionMapping;
/**
* For every vertex in the contraction graph stores visited Voronoi cells ids by a forward
* {@code ContractionHierarchyBFS}.
*/
private List> visitedForwardVoronoiCells;
/**
* For every vertex in the contraction graph stores visited Voronoi cells ids by a backward
* {@code ContractionHierarchyBFS}.
*/
private List> visitedBackwardVoronoiCells;
/**
* Constructs a new instance for the given {@code contractionMapping},
* {@code visitedForwardVoronoiCells} and {@code visitedBackwardVoronoiCells}.
*
* @param contractionMapping contraction mapping
* @param visitedForwardVoronoiCells visited Voronoi cells ids by a forward search
* @param visitedBackwardVoronoiCells visited Voronoi cells ids by a backward search
*/
public LocalityFilter(
Map> contractionMapping,
List> visitedForwardVoronoiCells,
List> visitedBackwardVoronoiCells)
{
this.contractionMapping = contractionMapping;
this.visitedForwardVoronoiCells = visitedForwardVoronoiCells;
this.visitedBackwardVoronoiCells = visitedBackwardVoronoiCells;
}
/**
* Returns $true$ when no shortest paths between {@code source} and {@code sink} contains a
* transit vertex.
*
* @param source source vertex
* @param sink sink vertex
* @return $true$ iff no shortest paths between {@code source} and {@code sink} contains a
* transit vertex
*/
public boolean isLocal(V source, V sink)
{
ContractionVertex contractedSource = contractionMapping.get(source);
ContractionVertex contractedSink = contractionMapping.get(sink);
Set sourceVisitedVoronoiCells =
visitedForwardVoronoiCells.get(contractedSource.vertexId);
Set sinkVisitedVoronoiCells =
visitedBackwardVoronoiCells.get(contractedSink.vertexId);
if (sourceVisitedVoronoiCells.contains(NO_VORONOI_CELL)
|| sinkVisitedVoronoiCells.contains(NO_VORONOI_CELL))
{
return true;
}
Set smallerSet;
Set largerSet;
if (sourceVisitedVoronoiCells.size() <= sinkVisitedVoronoiCells.size()) {
smallerSet = sourceVisitedVoronoiCells;
largerSet = sinkVisitedVoronoiCells;
} else {
smallerSet = sinkVisitedVoronoiCells;
largerSet = sourceVisitedVoronoiCells;
}
for (Integer visitedVoronoiCell : smallerSet) {
if (largerSet.contains(visitedVoronoiCell)) {
return true;
}
}
return false;
}
}
/**
* Stores forward and backward access vertices computed for the transit node routing.
*
* @param graph vertex type
* @param graph edge type
*/
public static class AccessVertices
{
/**
* For each vertex in {@code contractionGraph} stores corresponding forward access vertices.
*/
private List>> forwardAccessVertices;
/**
* For each vertex in {@code contractionGraph} stores corresponding backward access
* vertices.
*/
private List>> backwardAccessVertices;
/**
* Constructs a new instance for the given {@code forwardAccessVertices} and
* {@code backwardAccessVertices}.
*
* @param forwardAccessVertices forward access vertices
* @param backwardAccessVertices backward access vertices
*/
public AccessVertices(
List>> forwardAccessVertices,
List>> backwardAccessVertices)
{
this.forwardAccessVertices = forwardAccessVertices;
this.backwardAccessVertices = backwardAccessVertices;
}
/**
* Given a contraction vertex {@code vertex} returns its forward access vertices
*
* @param vertex vertex
* @return list of forward access vertices
*/
public List> getForwardAccessVertices(ContractionVertex vertex)
{
return forwardAccessVertices.get(vertex.vertexId);
}
/**
* Given a contraction vertex {@code vertex} returns its backward access vertices
*
* @param vertex vertex
* @return list of backward access vertices
*/
public List> getBackwardAccessVertices(ContractionVertex vertex)
{
return backwardAccessVertices.get(vertex.vertexId);
}
}
/**
* Forward or backward access vertex computed for a certain vertex $v$ in the graph.
*
* In the transit node routing if $u$ is a forward access vertex for $v$, it means that if you
* want go far away from $v$, it is highly likely that you would need to pass through $u$.
* Correspondingly, if $u$ is a backward access vertex for $v$, it means that if you want to go
* to $v$ from far away, you would highly likely go through $u$.
*
*
* Stores transit vertex and the shortest path between $v$ and {@code vertex}. If this is a
* forward access vertex, then {@code vertex} is the ending vertex in the {@code path},
* Otherwise it is a starting vertex of the {@code path}.
*
* @param graph vertex type
* @param graph edge type
*/
public static class AccessVertex
{
/**
* Transit vertex.
*/
private V vertex;
/**
* Path between a vertex $v$ this access vertex is computed for and {@code vertex}.
*/
private GraphPath path;
/**
* Returns a transit vertex of this access vertex.
*
* @return transit vertex of this access vertex
*/
public V getVertex()
{
return vertex;
}
/**
* Returns path between a vertex in the graph and {@code vertex}.
*
* @return path between a vertex in the graph and {@code vertex}.
*/
public GraphPath getPath()
{
return path;
}
/**
* Constructs a new instance for the given {@code vertex} and {@code path}.
*
* @param vertex a transit vertex
* @param path path between a vertex in the graph and {@code vertex}
*/
public AccessVertex(V vertex, GraphPath path)
{
this.vertex = vertex;
this.path = path;
}
}
/**
* Task which is used to perform {@code ContractionHierarchyBFS} in parallel.
*/
private class AVAndLFConstructionTask
implements Runnable
{
/**
* Id of this task.
*/
private int taskId;
/**
* Builder object for a {@code LocalityFilter} instance.
*/
private LocalityFilterBuilder localityFilterBuilder;
/**
* Builder object for a {@code AccessVertices} instance.
*/
private AccessVerticesBuilder accessVerticesBuilder;
/**
* Is used to run forward CH BFS query over the {@code contractionGraph}.
*/
private ContractionHierarchyBFS forwardBFS;
/**
* Is used to run backward CH BFS query over the {@code contractionGraph}.
*/
private ContractionHierarchyBFS backwardBFS;
/**
* Constructs a new instance for the give {@code taskId}, {@code localityFilterBuilder},
* {@code accessVerticesBuilder}, {@code forwardBFS} and {@code backwardBFS}.
*
* @param taskId id of this task
* @param localityFilterBuilder builder object for {@code LocalityFilter}
* @param accessVerticesBuilder builder object for {@code AccessVertices}
* @param forwardBFS forward {@code ContractionHierarchyBFS}
* @param backwardBFS backward {@code ContractionHierarchyBFS}
*/
public AVAndLFConstructionTask(
int taskId, LocalityFilterBuilder localityFilterBuilder,
AccessVerticesBuilder accessVerticesBuilder, ContractionHierarchyBFS forwardBFS,
ContractionHierarchyBFS backwardBFS)
{
this.taskId = taskId;
this.localityFilterBuilder = localityFilterBuilder;
this.accessVerticesBuilder = accessVerticesBuilder;
this.forwardBFS = forwardBFS;
this.backwardBFS = backwardBFS;
}
@Override
public void run()
{
int start = workerSegmentStart(0, contractionVertices.size(), taskId);
int end = workerSegmentEnd(0, contractionVertices.size(), taskId);
for (int i = start; i < end; ++i) {
ContractionVertex v = contractionVertices.get(i);
Pair, Set> forwardData = forwardBFS.runSearch(v);
Pair, Set> backwardData = backwardBFS.runSearch(v);
accessVerticesBuilder.addForwardAccessVertices(v, forwardData.getFirst());
accessVerticesBuilder.addBackwardAccessVertices(v, backwardData.getFirst());
localityFilterBuilder.addForwardVisitedVoronoiCells(v, forwardData.getSecond());
localityFilterBuilder.addBackwardVisitedVoronoiCells(v, backwardData.getSecond());
}
}
}
/**
* Task which is used to unpack contracted many-to-many shortest paths between transit vertices.
*/
private class PathsUnpackingTask
implements Runnable
{
/**
* Id of this task.
*/
private int taskId;
/**
* Selected transit vertices.
*/
private List transitVertices;
/**
* Map where the unpacked paths will be stored.
*/
private Map>> pathsMap;
/**
* Many-to-many shortest paths to be unpacked.
*/
private ManyToManyShortestPaths shortestPaths;
/**
* Constructs a new instance for the given {@code taskId}, {@code transitVertices},
* {@code pathsMap} and {@code shortestPaths}.
*
* @param taskId id of this task
* @param transitVertices transit vertices
* @param pathsMap map for unpacked paths
* @param shortestPaths paths to be unpacked
*/
public PathsUnpackingTask(
int taskId, List transitVertices, Map>> pathsMap,
ManyToManyShortestPaths shortestPaths)
{
this.taskId = taskId;
this.transitVertices = transitVertices;
this.pathsMap = pathsMap;
this.shortestPaths = shortestPaths;
}
@Override
public void run()
{
int start = workerSegmentStart(0, transitVertices.size(), taskId);
int end = workerSegmentEnd(0, transitVertices.size(), taskId);
for (int i = start; i < end; ++i) {
V v1 = transitVertices.get(i);
Map> targetToPathsMap = pathsMap.get(v1);
for (V v2 : transitVertices) {
targetToPathsMap.put(v2, shortestPaths.getPath(v1, v2));
}
}
}
}
/**
* Computes start of the working chunk for this task.
*
* @param segmentStart working segment start
* @param segmentEnd working segment end
* @return working chunk start
*/
private int workerSegmentStart(int segmentStart, int segmentEnd, int taskId)
{
return segmentStart + ((segmentEnd - segmentStart) * taskId) / parallelism;
}
/**
* Computes end of the working chunk for this task.
*
* @param segmentStart working segment start
* @param segmentEnd working segment end
* @return working chunk end
*/
private int workerSegmentEnd(int segmentStart, int segmentEnd, int taskId)
{
return segmentStart + ((segmentEnd - segmentStart) * (taskId + 1)) / parallelism;
}
}