blob: c07b00a3725e1381f0b344df0a092e688632cedd [file] [log] [blame]
Aaron Kruglikov07d80382015-12-07 16:54:37 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Aaron Kruglikov07d80382015-12-07 16:54:37 -08003 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onlab.graph;
17
18import com.google.common.collect.ImmutableSet;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Sets;
21import org.slf4j.Logger;
22
23import java.util.ArrayList;
24import java.util.Comparator;
25import java.util.List;
26import java.util.Set;
27import java.util.TreeSet;
28
29import static com.google.common.base.Preconditions.checkArgument;
30import static com.google.common.base.Preconditions.checkNotNull;
31import static org.slf4j.LoggerFactory.getLogger;
32
33/**
34 * Runs K shortest paths algorithm on a provided directed graph. Returns results in the form of an
35 * InnerOrderedResult so iteration through the returned paths will return paths in ascending order according to the
36 * provided EdgeWeight.
37 */
38public class KShortestPathsSearch<V extends Vertex, E extends Edge<V>> extends AbstractGraphPathSearch<V, E> {
39
40 private final Logger log = getLogger(getClass());
41
42 @Override
43 public Result<V, E> search(Graph<V, E> graph, V src, V dst, EdgeWeight<V, E> weight, int maxPaths) {
44 checkNotNull(src);
45 checkNotNull(dst);
46 //The modified edge weight removes any need to modify the original graph
47 InnerEdgeWeighter modifiedWeighter = new InnerEdgeWeighter(checkNotNull(weight));
Aaron Kruglikovf6aed992016-11-08 15:16:03 -080048 checkArgument(maxPaths != ALL_PATHS, "KShortestPath search cannot" +
49 "be used with ALL_PATHS.");
50 checkArgument(maxPaths > 0, "The max number of paths must be greater" +
51 " than 0");
Aaron Kruglikov07d80382015-12-07 16:54:37 -080052 Graph<V, E> originalGraph = checkNotNull(graph);
53 //the result contains the set of eventual results
54 InnerOrderedResult result = new InnerOrderedResult(src, dst, maxPaths);
55 ArrayList<Path<V, E>> resultPaths = new ArrayList<>(maxPaths);
56 ArrayList<Path<V, E>> potentialPaths = Lists.newArrayList();
57
58 DijkstraGraphSearch<V, E> dijkstraSearch = new DijkstraGraphSearch<>();
59 Set<Path<V, E>> dijkstraResults = dijkstraSearch.search(originalGraph, src, dst, modifiedWeighter, 1).paths();
60 //Checks if the dst was reachable
Jon Hallcbd1b392017-01-18 20:15:44 -080061 if (dijkstraResults.isEmpty()) {
Aaron Kruglikov07d80382015-12-07 16:54:37 -080062 log.warn("No path was found.");
63 return result;
64 }
65 //If it was reachable adds the first shortest path to the set of results
66 resultPaths.add(dijkstraResults.iterator().next());
67
68 for (int k = 1; k < maxPaths; k++) {
69
70 for (int i = 0; i < (resultPaths.get(k - 1).edges().size() - 1); i++) {
71 V spurNode = resultPaths.get(k - 1).edges().get(i).src();
72 List<E> rootPathEdgeList = resultPaths.get(k - 1).edges().subList(0, i);
73
74 for (Path<V, E> path : resultPaths) {
Koosha1525c452016-10-28 18:25:19 +033075 if (path.edges().size() >= i && edgeListsAreEqual(rootPathEdgeList, path.edges().subList(0, i))) {
Aaron Kruglikov07d80382015-12-07 16:54:37 -080076 modifiedWeighter.removedEdges.add(path.edges().get(i));
77 }
78 }
79
80 //Effectively remove all nodes from the source path
81 for (E edge : rootPathEdgeList) {
82 originalGraph.getEdgesFrom(edge.src()).forEach(e -> modifiedWeighter.removedEdges.add(e));
83 originalGraph.getEdgesTo(edge.src()).forEach(e -> modifiedWeighter.removedEdges.add(e));
84 }
85
86 dijkstraResults = dijkstraSearch.search(originalGraph, spurNode, dst, modifiedWeighter, 1).paths();
Jon Hallcbd1b392017-01-18 20:15:44 -080087 if (!dijkstraResults.isEmpty()) {
Aaron Kruglikov07d80382015-12-07 16:54:37 -080088 Path<V, E> spurPath = dijkstraResults.iterator().next();
89 List<E> totalPath = new ArrayList<>(rootPathEdgeList);
90 spurPath.edges().forEach(e -> totalPath.add(e));
91 //The following line must use the original weighter not the modified weighter because the modified
92 //weighter will count -1 values used for modifying the graph and return an inaccurate cost.
93 potentialPaths.add(new DefaultPath<V, E>(totalPath,
94 calculatePathCost(weight, totalPath)));
95 }
96
97 //Restore all removed paths and nodes
98 modifiedWeighter.removedEdges.clear();
99 }
100 if (potentialPaths.isEmpty()) {
101 break;
102 }
103 potentialPaths.sort(new InnerPathComparator());
104 resultPaths.add(potentialPaths.get(0));
105 potentialPaths.remove(0);
106 }
107 result.pathSet.addAll(resultPaths);
108
109 return result;
110 }
111 //Edge list equality is judges by shared endpoints, and shared endpoints should be the same
112 private boolean edgeListsAreEqual(List<E> edgeListOne, List<E> edgeListTwo) {
113 if (edgeListOne.size() != edgeListTwo.size()) {
114 return false;
115 }
116 E edgeOne;
117 E edgeTwo;
118 for (int i = 0; i < edgeListOne.size(); i++) {
119 edgeOne = edgeListOne.get(i);
120 edgeTwo = edgeListTwo.get(i);
121 if (!edgeOne.equals(edgeTwo)) {
122 return false;
123 }
124 }
125 return true;
126 }
127
128 private Double calculatePathCost(EdgeWeight<V, E> weighter, List<E> edges) {
129 Double totalCost = 0.0;
130 for (E edge : edges) {
131 totalCost += weighter.weight(edge);
132 }
133 return totalCost;
134 }
135
136 /**
137 * Weights edges to make them inaccessible if set, otherwise returns the result of the original EdgeWeight.
138 */
139 private class InnerEdgeWeighter implements EdgeWeight<V, E> {
140
141 private Set<E> removedEdges = Sets.newConcurrentHashSet();
142 private EdgeWeight<V, E> innerEdgeWeight;
143
144 public InnerEdgeWeighter(EdgeWeight<V, E> weight) {
145 this.innerEdgeWeight = weight;
146 }
147
148 @Override
149 public double weight(E edge) {
150 if (removedEdges.contains(edge)) {
151 //THIS RELIES ON THE LOCAL DIJKSTRA ALGORITHM AVOIDING NEGATIVES
152 return -1;
153 } else {
154 return innerEdgeWeight.weight(edge);
155 }
156 }
157 }
158
159 /**
160 * A result modified to return paths ordered according to the provided comparator.
161 */
162 protected class InnerOrderedResult extends DefaultResult {
163
164 private TreeSet<Path<V, E>> pathSet = new TreeSet<>(new InnerPathComparator());
165
166 public InnerOrderedResult(V src, V dst) {
167 super(src, dst);
168 }
169
170 public InnerOrderedResult(V src, V dst, int maxPaths) {
171 super(src, dst, maxPaths);
172 }
173
174 @Override
175 public Set<Path<V, E>> paths() {
176 return ImmutableSet.copyOf(pathSet);
177 }
178 }
179
180 /**
181 * Provides a comparator to order the set of paths.
182 */
183 private class InnerPathComparator implements Comparator<Path<V, E>> {
184
185 @Override
186 public int compare(Path<V, E> pathOne, Path<V, E> pathTwo) {
187 int comparisonValue = Double.compare(pathOne.cost(), pathTwo.cost());
188 if (comparisonValue != 0) {
189 return comparisonValue;
190 } else if (edgeListsAreEqual(pathOne.edges(), pathTwo.edges())) {
191 return 0;
192 } else {
193 return 1;
194 }
195 }
196 }
197}