blob: c07b00a3725e1381f0b344df0a092e688632cedd [file] [log] [blame]
/*
* Copyright 2015-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.graph;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.slf4j.LoggerFactory.getLogger;
/**
* Runs K shortest paths algorithm on a provided directed graph. Returns results in the form of an
* InnerOrderedResult so iteration through the returned paths will return paths in ascending order according to the
* provided EdgeWeight.
*/
public class KShortestPathsSearch<V extends Vertex, E extends Edge<V>> extends AbstractGraphPathSearch<V, E> {
private final Logger log = getLogger(getClass());
@Override
public Result<V, E> search(Graph<V, E> graph, V src, V dst, EdgeWeight<V, E> weight, int maxPaths) {
checkNotNull(src);
checkNotNull(dst);
//The modified edge weight removes any need to modify the original graph
InnerEdgeWeighter modifiedWeighter = new InnerEdgeWeighter(checkNotNull(weight));
checkArgument(maxPaths != ALL_PATHS, "KShortestPath search cannot" +
"be used with ALL_PATHS.");
checkArgument(maxPaths > 0, "The max number of paths must be greater" +
" than 0");
Graph<V, E> originalGraph = checkNotNull(graph);
//the result contains the set of eventual results
InnerOrderedResult result = new InnerOrderedResult(src, dst, maxPaths);
ArrayList<Path<V, E>> resultPaths = new ArrayList<>(maxPaths);
ArrayList<Path<V, E>> potentialPaths = Lists.newArrayList();
DijkstraGraphSearch<V, E> dijkstraSearch = new DijkstraGraphSearch<>();
Set<Path<V, E>> dijkstraResults = dijkstraSearch.search(originalGraph, src, dst, modifiedWeighter, 1).paths();
//Checks if the dst was reachable
if (dijkstraResults.isEmpty()) {
log.warn("No path was found.");
return result;
}
//If it was reachable adds the first shortest path to the set of results
resultPaths.add(dijkstraResults.iterator().next());
for (int k = 1; k < maxPaths; k++) {
for (int i = 0; i < (resultPaths.get(k - 1).edges().size() - 1); i++) {
V spurNode = resultPaths.get(k - 1).edges().get(i).src();
List<E> rootPathEdgeList = resultPaths.get(k - 1).edges().subList(0, i);
for (Path<V, E> path : resultPaths) {
if (path.edges().size() >= i && edgeListsAreEqual(rootPathEdgeList, path.edges().subList(0, i))) {
modifiedWeighter.removedEdges.add(path.edges().get(i));
}
}
//Effectively remove all nodes from the source path
for (E edge : rootPathEdgeList) {
originalGraph.getEdgesFrom(edge.src()).forEach(e -> modifiedWeighter.removedEdges.add(e));
originalGraph.getEdgesTo(edge.src()).forEach(e -> modifiedWeighter.removedEdges.add(e));
}
dijkstraResults = dijkstraSearch.search(originalGraph, spurNode, dst, modifiedWeighter, 1).paths();
if (!dijkstraResults.isEmpty()) {
Path<V, E> spurPath = dijkstraResults.iterator().next();
List<E> totalPath = new ArrayList<>(rootPathEdgeList);
spurPath.edges().forEach(e -> totalPath.add(e));
//The following line must use the original weighter not the modified weighter because the modified
//weighter will count -1 values used for modifying the graph and return an inaccurate cost.
potentialPaths.add(new DefaultPath<V, E>(totalPath,
calculatePathCost(weight, totalPath)));
}
//Restore all removed paths and nodes
modifiedWeighter.removedEdges.clear();
}
if (potentialPaths.isEmpty()) {
break;
}
potentialPaths.sort(new InnerPathComparator());
resultPaths.add(potentialPaths.get(0));
potentialPaths.remove(0);
}
result.pathSet.addAll(resultPaths);
return result;
}
//Edge list equality is judges by shared endpoints, and shared endpoints should be the same
private boolean edgeListsAreEqual(List<E> edgeListOne, List<E> edgeListTwo) {
if (edgeListOne.size() != edgeListTwo.size()) {
return false;
}
E edgeOne;
E edgeTwo;
for (int i = 0; i < edgeListOne.size(); i++) {
edgeOne = edgeListOne.get(i);
edgeTwo = edgeListTwo.get(i);
if (!edgeOne.equals(edgeTwo)) {
return false;
}
}
return true;
}
private Double calculatePathCost(EdgeWeight<V, E> weighter, List<E> edges) {
Double totalCost = 0.0;
for (E edge : edges) {
totalCost += weighter.weight(edge);
}
return totalCost;
}
/**
* Weights edges to make them inaccessible if set, otherwise returns the result of the original EdgeWeight.
*/
private class InnerEdgeWeighter implements EdgeWeight<V, E> {
private Set<E> removedEdges = Sets.newConcurrentHashSet();
private EdgeWeight<V, E> innerEdgeWeight;
public InnerEdgeWeighter(EdgeWeight<V, E> weight) {
this.innerEdgeWeight = weight;
}
@Override
public double weight(E edge) {
if (removedEdges.contains(edge)) {
//THIS RELIES ON THE LOCAL DIJKSTRA ALGORITHM AVOIDING NEGATIVES
return -1;
} else {
return innerEdgeWeight.weight(edge);
}
}
}
/**
* A result modified to return paths ordered according to the provided comparator.
*/
protected class InnerOrderedResult extends DefaultResult {
private TreeSet<Path<V, E>> pathSet = new TreeSet<>(new InnerPathComparator());
public InnerOrderedResult(V src, V dst) {
super(src, dst);
}
public InnerOrderedResult(V src, V dst, int maxPaths) {
super(src, dst, maxPaths);
}
@Override
public Set<Path<V, E>> paths() {
return ImmutableSet.copyOf(pathSet);
}
}
/**
* Provides a comparator to order the set of paths.
*/
private class InnerPathComparator implements Comparator<Path<V, E>> {
@Override
public int compare(Path<V, E> pathOne, Path<V, E> pathTwo) {
int comparisonValue = Double.compare(pathOne.cost(), pathTwo.cost());
if (comparisonValue != 0) {
return comparisonValue;
} else if (edgeListsAreEqual(pathOne.edges(), pathTwo.edges())) {
return 0;
} else {
return 1;
}
}
}
}