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

org.jgrapht.alg.shortestpath.CHManyToManyShortestPaths Maven / Gradle / Ivy

The newest version!
/*
 * (C) Copyright 2019-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.*;
import org.jgrapht.alg.util.*;
import org.jgrapht.graph.*;
import org.jgrapht.util.ConcurrencyUtil;

import java.util.*;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.*;

import static org.jgrapht.alg.shortestpath.ContractionHierarchyPrecomputation.*;

/**
 * Efficient algorithm for the many-to-many shortest paths problem based on contraction hierarchy.
 *
 * 

* The algorithm is originally described in the article: Sebastian Knopp, Peter Sanders, Dominik * Schultes, Frank Schulz, and Dorothea Wagner. 2007. Computing many-to-many shortest paths using * highway hierarchies. In Proceedings of the Meeting on Algorithm Engineering & Expermiments. * Society for Industrial and Applied Mathematics, Philadelphia, PA, USA, 36-45. * *

* First contraction hierarchy is constructed. Then for each target vertex a backward single source * shortest paths search is performed on the contracted graph. During the searches a bucket $b(v)$ * is associated with each vertex $v$ in the graph. A bucket stores a set of pairs $(t,d)$, where * $t$ is a target vertex current search is performed from and $d$ is the computed distance from $v$ * to this target. Then a forward single source shortest paths search is performed from every source * vertex. When a search settles a vertex $v$ with distance $d(s,v)$, where $s$ is current source * vertex, its bucket is scanned. For each entry $(t,d)$ if $d(s,t) > d(s,v) + d$ values of paths * weight between $s$ and $t$ and its middle vertex is updated. The middle vertices are then used to * restored actual path from the information in the shortest paths trees. * *

* Additionally if $|S| > |T|$ the algorithm is executed on the reversed graph. This allows to * reduce the number of buckets and optimize memory usage of the algorithm. * *

* The efficiency of this algorithm is derived from the fact that contraction hierarchy produces * fairly small shortest paths trees. This allows to both speedup the computations and decrease * memory usage to store the paths. The bottleneck of the algorithm is the contraction hierarchy * computation, which can lead to significant overhead for dense graphs both in terms of running * time and space complexity. Therefore the ideal use cases for this algorithm are sparse graphs of * any size with low average out-degree of vertices. * * @param the graph vertex type * @param the graph edge type * @author Semen Chudakov * @see DefaultManyToManyShortestPaths * @see DijkstraManyToManyShortestPaths */ public class CHManyToManyShortestPaths extends BaseManyToManyShortestPaths { /** * Contraction hierarchy of {@code graph}. */ private ContractionHierarchy contractionHierarchy; /** * Contracted version of {@code graph}. */ private Graph, ContractionEdge> contractionGraph; /** * Mapping from vertices in the original {@code graph} to vertices in the * {@code contractionGraph}. */ private Map> contractionMapping; /** * 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}. For utility methods to manage a {@code ThreadPoolExecutor} see * {@link ConcurrencyUtil}. * * @param graph a graph * @param executor executor which will be used to compute {@link ContractionHierarchy} */ public CHManyToManyShortestPaths(Graph graph, ThreadPoolExecutor executor) { this( new ContractionHierarchyPrecomputation<>(graph, executor) .computeContractionHierarchy()); } /** * Constructs an instance of the algorithm for a given {@code contractionHierarchy}. * * @param contractionHierarchy contraction of the {@code graph} */ public CHManyToManyShortestPaths(ContractionHierarchy contractionHierarchy) { super(contractionHierarchy.getGraph()); this.contractionHierarchy = contractionHierarchy; this.contractionGraph = contractionHierarchy.getContractionGraph(); this.contractionMapping = contractionHierarchy.getContractionMapping(); } /** * {@inheritDoc} */ @Override public ManyToManyShortestPaths getManyToManyPaths(Set sources, Set targets) { Objects.requireNonNull(sources, "sources cannot be null!"); Objects.requireNonNull(targets, "targets cannot be null!"); Graph, ContractionEdge> searchContractionGraph; boolean reversed; if (sources.size() <= targets.size()) { searchContractionGraph = contractionGraph; reversed = false; } else { searchContractionGraph = new EdgeReversedGraph<>(contractionGraph); reversed = true; Set tmp = targets; targets = sources; sources = tmp; } Map, Map, Pair>>> forwardSearchSpaces = new HashMap<>(); Map, Map, Pair>>> backwardSearchSpaces = new HashMap<>(); Map, ContractionVertex>, Pair>> middleVertices = new HashMap<>(); Set> contractedSources = sources .stream().map(contractionMapping::get).collect(Collectors.toCollection(HashSet::new)); Set> contractedTargets = targets .stream().map(contractionMapping::get).collect(Collectors.toCollection(HashSet::new)); Map, List> bucketsMap = new HashMap<>(); for (ContractionVertex vertex : searchContractionGraph.vertexSet()) { bucketsMap.put(vertex, new ArrayList<>()); } for (ContractionVertex contractedTarget : contractedTargets) { backwardSearch( searchContractionGraph, contractedTarget, contractedSources, bucketsMap, backwardSearchSpaces, reversed); } for (ContractionVertex contractedSource : contractedSources) { forwardSearch( searchContractionGraph, contractedSource, contractedTargets, bucketsMap, forwardSearchSpaces, middleVertices, reversed); } if (reversed) { return new CHManyToManyShortestPathsImpl( graph, contractionHierarchy, targets, sources, backwardSearchSpaces, forwardSearchSpaces, middleVertices); } else { return new CHManyToManyShortestPathsImpl( graph, contractionHierarchy, sources, targets, forwardSearchSpaces, backwardSearchSpaces, middleVertices); } } /** * Performs backward single source shortest paths search in {@code contractionGraph} starting * from {@code target} to {@code sources}. For each vertex $v$ in {@code contractionGraph} a * bucket is created that records entries $(t,d)$, where $t$ is a current {@code target} and $d$ * is a distance computed during current search. A constructed shortest paths tree is then put * in {@code backwardSearchSpaces}. If {@code reversed} flag is set to $true$ the specified * {@code target} belongs to the original source vertices and therefore downward edges should be * masked in the contraction graph instead of upward. * * @param contractionGraph graph to perform search in * @param target search start vertex * @param contractedSources vertices to end search at * @param bucketsMap map from vertices to their buckets * @param backwardSearchSpaces map from vertices to their search spaces * @param reversed indicates if current search is reversed */ private void backwardSearch( Graph, ContractionEdge> contractionGraph, ContractionVertex target, Set> contractedSources, Map, List> bucketsMap, Map, Map, Pair>>> backwardSearchSpaces, boolean reversed) { Graph, ContractionEdge> maskSubgraph; if (reversed) { maskSubgraph = new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> !e.isUpward); } else { maskSubgraph = new MaskSubgraph<>( new EdgeReversedGraph<>(contractionGraph), v -> false, e -> e.isUpward); } Map, Pair>> distanceAndPredecessorMap = getDistanceAndPredecessorMap(maskSubgraph, target, contractedSources); backwardSearchSpaces.put(target, distanceAndPredecessorMap); for (Map.Entry, Pair>> entry : distanceAndPredecessorMap.entrySet()) { bucketsMap .get(entry.getKey()).add(new BucketEntry(target, entry.getValue().getFirst())); } } /** * Performs forward search from the given {@code source} to {@code targets}. A constructed * shortest paths tree is then put in {@code forwardSearchSpaces}. If {@code reversed} flag is * set to $true$ the specified {@code source} belongs to the original target vertices and * therefore upward edges should be masked in the contraction graph instead of the downward. * * @param contractionGraph graph to perform search in * @param source start vertex of the search * @param contractedTargets vertices to end search at * @param bucketsMap map from vertices to their buckets * @param forwardSearchSpaces map from vertices to their search spaces * @param middleVerticesMap map from source-target pairs to theirs distances and middle nodes * @param reversed indicates if current search is reversed */ private void forwardSearch( Graph, ContractionEdge> contractionGraph, ContractionVertex source, Set> contractedTargets, Map, List> bucketsMap, Map, Map, Pair>>> forwardSearchSpaces, Map, ContractionVertex>, Pair>> middleVerticesMap, boolean reversed) { Graph, ContractionEdge> maskSubgraph; if (reversed) { maskSubgraph = new MaskSubgraph<>(contractionGraph, v -> false, e -> e.isUpward); } else { maskSubgraph = new MaskSubgraph<>(contractionGraph, v -> false, e -> !e.isUpward); } Map, Pair>> distanceAndPredecessorMap = getDistanceAndPredecessorMap(maskSubgraph, source, contractedTargets); forwardSearchSpaces.put(source, distanceAndPredecessorMap); for (Map.Entry, Pair>> entry : distanceAndPredecessorMap.entrySet()) { ContractionVertex middleVertex = entry.getKey(); double forwardDistance = entry.getValue().getFirst(); for (BucketEntry bucketEntry : bucketsMap.get(middleVertex)) { double pathDistance = forwardDistance + bucketEntry.distance; Pair, ContractionVertex> pair; if (reversed) { pair = Pair.of(bucketEntry.target, source); } else { pair = Pair.of(source, bucketEntry.target); } middleVerticesMap.compute(pair, (p, distanceAndMiddleNode) -> { if (distanceAndMiddleNode == null || distanceAndMiddleNode.getFirst() > pathDistance) { return Pair.of(pathDistance, middleVertex); } return distanceAndMiddleNode; }); } } } /** * Computes distance and predecessor map for a single source shortest paths search starting at * source and finishing the search as soon as all {@code targets} are reached. * * @param contractionGraph a graph * @param source search start vertex * @param targets search end vertices * @return distance and predecessor map */ private Map, Pair>> getDistanceAndPredecessorMap( Graph, ContractionEdge> contractionGraph, ContractionVertex source, Set> targets) { return ((TreeSingleSourcePathsImpl, ContractionEdge>) getShortestPathsTree(contractionGraph, source, targets)).map; } /** * Stores data computed during the backward searches. */ private class BucketEntry { /** * Start vertex of the backward search during which this entry is created. */ ContractionVertex target; /** * Distance from a vertex this entry is created for to {@code target}. */ double distance; /** * Constrcuts an instance of an entry for the given {@code target} and {@code distance}. * * @param target backward search start vertex * @param distance distance to {@code target} */ public BucketEntry(ContractionVertex target, double distance) { this.target = target; this.distance = distance; } } /** * Implementation of * {@link org.jgrapht.alg.interfaces.ManyToManyShortestPathsAlgorithm.ManyToManyShortestPaths} * for many-to-many shortest paths algorithm based on contraction hierarchy. Paths are stored in * form of bidirectional single source shortest paths trees. When a path weight is queried a * value that is stored in {@code distanceAndMiddleVertexMap} is returned. When an actual paths * is required it is constructed by recursively unpacking edges stored in the shortest paths * trees corresponding to source and target vertices. */ private class CHManyToManyShortestPathsImpl extends BaseManyToManyShortestPathsImpl { /** * The underlying graph. */ private final Graph graph; /** * Contraction hierarchy for {@code graph}. */ private final Graph, ContractionEdge> contractionGraph; /** * Mapping from original to contracted vertices. */ private final Map> contractionMapping; /** * Stores forward search space for each start vertex. */ private Map, Map, Pair>>> forwardSearchSpaces; /** * Stores backward search space for each target vertex. */ private Map, Map, Pair>>> backwardSearchSpaces; /** * Stores pair of path weight and middle vertex for each source-target pair. */ private Map, ContractionVertex>, Pair>> distanceAndMiddleVertexMap; /** * Constructs a new instance for the given {@code graph}, {@code contractionGraph}, * {@code contractionMapping}, {@code forwardSearchSpaces}, {@code backwardSearchSpaces} and * {@code distanceAndMiddleVertexMap}. * * @param graph underlying graph. * @param hierarchy contraction hierarchy * @param forwardSearchSpaces search spaces of source vertices * @param backwardSearchSpaces search spaces of target vertices * @param distanceAndMiddleVertexMap weights and middle vertices of paths */ public CHManyToManyShortestPathsImpl( Graph graph, ContractionHierarchy hierarchy, Set sources, Set targets, Map, Map, Pair>>> forwardSearchSpaces, Map, Map, Pair>>> backwardSearchSpaces, Map, ContractionVertex>, Pair>> distanceAndMiddleVertexMap) { super(sources, targets); this.graph = graph; this.contractionGraph = hierarchy.getContractionGraph(); this.contractionMapping = hierarchy.getContractionMapping(); this.forwardSearchSpaces = forwardSearchSpaces; this.backwardSearchSpaces = backwardSearchSpaces; this.distanceAndMiddleVertexMap = distanceAndMiddleVertexMap; } /** * {@inheritDoc} */ @Override public GraphPath getPath(V source, V target) { assertCorrectSourceAndTarget(source, target); LinkedList edgeList = new LinkedList<>(); LinkedList vertexList = new LinkedList<>(); ContractionVertex contractedSource = contractionMapping.get(source); ContractionVertex contractedTarget = contractionMapping.get(target); Pair, ContractionVertex> contractedVertices = Pair.of(contractedSource, contractedTarget); Map, Pair>> forwardTree = forwardSearchSpaces.get(contractedSource); Map, Pair>> backwardTree = backwardSearchSpaces.get(contractedTarget); Pair> distanceAndCommonVertex = distanceAndMiddleVertexMap.get(contractedVertices); if (distanceAndCommonVertex == null) { return null; } ContractionVertex commonVertex = distanceAndCommonVertex.getSecond(); // add common vertex vertexList.add(commonVertex.vertex); // traverse forward path ContractionVertex v = commonVertex; while (true) { ContractionEdge e = forwardTree.get(v).getSecond(); if (e == null) { break; } contractionHierarchy.unpackBackward(e, vertexList, edgeList); v = contractionGraph.getEdgeSource(e); } // traverse reverse path v = commonVertex; while (true) { ContractionEdge e = backwardTree.get(v).getSecond(); if (e == null) { break; } contractionHierarchy.unpackForward(e, vertexList, edgeList); v = contractionGraph.getEdgeTarget(e); } return new GraphWalk<>( graph, source, target, vertexList, edgeList, distanceAndCommonVertex.getFirst()); } /** * {@inheritDoc} */ @Override public double getWeight(V source, V target) { assertCorrectSourceAndTarget(source, target); Pair, ContractionVertex> contractedVertices = Pair.of(contractionMapping.get(source), contractionMapping.get(target)); if (distanceAndMiddleVertexMap.containsKey(contractedVertices)) { return distanceAndMiddleVertexMap.get(contractedVertices).getFirst(); } else { return Double.POSITIVE_INFINITY; } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy