blob: 106508d13cb81655849f5a610f328871c6a30b1a [file] [log] [blame]
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -07001/*
2 * Copyright 2017-present Open Networking Laboratory
3 *
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 static com.google.common.base.Preconditions.checkNotNull;
19import static java.util.Spliterators.spliteratorUnknownSize;
20
21import java.util.ArrayList;
22import java.util.Comparator;
23import java.util.Iterator;
24import java.util.List;
25import java.util.NoSuchElementException;
26import java.util.PriorityQueue;
27import java.util.Queue;
28import java.util.Set;
29import java.util.Spliterator;
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -070030import java.util.function.Supplier;
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -070031import java.util.stream.Stream;
32import java.util.stream.StreamSupport;
33
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -070034import com.google.common.base.Suppliers;
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -070035import com.google.common.collect.ComparisonChain;
36import com.google.common.collect.ImmutableList;
37import com.google.common.collect.Sets;
38
39/**
40 * Lazily runs K shortest paths algorithm on a provided directed graph.
41 */
42public class LazyKShortestPathsSearch<V extends Vertex, E extends Edge<V>> {
43
44 private final Comparator<Path<V, E>> pathComparator = new PathComparator();
45
46 private final GraphPathSearch<V, E> shortest = new DijkstraGraphSearch<>();
47
48 /**
49 * Searches the specified graph for paths between vertices.
50 *
51 * @param graph graph to be searched
52 * @param src source vertex
53 * @param dst destination vertex
54 * @param weigher edge-weigher
55 * @return Stream of shortest paths
56 */
57 public Stream<Path<V, E>> lazyPathSearch(Graph<V, E> graph,
58 V src, V dst,
59 EdgeWeigher<V, E> weigher) {
60
61 Iterator<Path<V, E>> it = new ShortestPathIterator(graph, src, dst, weigher);
62
63 return StreamSupport.stream(spliteratorUnknownSize(it,
64 Spliterator.ORDERED |
65 Spliterator.DISTINCT |
66 Spliterator.NONNULL |
67 Spliterator.IMMUTABLE),
68 false);
69 }
70
71 /**
72 * Iterator returning shortest paths, searched incrementally on each next() call.
73 */
74 private final class ShortestPathIterator implements Iterator<Path<V, E>> {
75
76 final Graph<V, E> graph;
77 final V src;
78 final V dst;
79 final EdgeWeigher<V, E> weigher;
80
81 final InnerEdgeWeigher maskingWeigher;
82
83 final List<Path<V, E>> resultPaths = new ArrayList<>(); // A
84 final Queue<Path<V, E>> potentialPaths = new PriorityQueue<>(pathComparator); // B
85
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -070086 Supplier<Path<V, E>> next;
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -070087
88 ShortestPathIterator(Graph<V, E> graph,
89 V src, V dst,
90 EdgeWeigher<V, E> weigher) {
91 this.graph = checkNotNull(graph);
92 this.src = checkNotNull(src);
93 this.dst = checkNotNull(dst);
94 this.weigher = checkNotNull(weigher);
95
96 maskingWeigher = new InnerEdgeWeigher(weigher);
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -070097 next = Suppliers.ofInstance(
98 shortest.search(graph, src, dst, weigher, 1)
99 .paths().stream().findFirst().orElse(null));
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700100 }
101
102 @Override
103 public boolean hasNext() {
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700104 return next.get() != null;
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700105 }
106
107 @Override
108 public Path<V, E> next() {
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700109 if (next.get() == null) {
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700110 throw new NoSuchElementException("No more path between " + src + "-" + dst);
111 }
112
113 // lastPath: the path to return at the end of this call
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700114 Path<V, E> lastPath = next.get();
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700115 resultPaths.add(lastPath);
116
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700117 next = Suppliers.memoize(() -> computeNext(lastPath));
118
119 return lastPath;
120 }
121
122 private Path<V, E> computeNext(Path<V, E> lastPath) {
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700123 /// following is basically Yen's k-shortest path algorithm
124
125 // start searching for next path
126 for (int i = 0; i < lastPath.edges().size(); i++) {
127 V spurNode = lastPath.edges().get(i).src();
128 List<E> rootPathEdgeList = lastPath.edges().subList(0, i);
129
130 for (Path<V, E> path : resultPaths) {
131 if (path.edges().size() >= i &&
132 rootPathEdgeList.equals(path.edges().subList(0, i))) {
133 maskingWeigher.excluded.add(path.edges().get(i));
134 }
135 }
136
137 // Effectively remove all root path nodes other than spurNode
138 rootPathEdgeList.forEach(edge -> {
139 maskingWeigher.excluded.addAll(graph.getEdgesFrom(edge.src()));
140 maskingWeigher.excluded.addAll(graph.getEdgesTo(edge.src()));
141 });
142
143 shortest.search(graph, spurNode, dst, maskingWeigher, 1)
144 .paths().stream().findAny().ifPresent(spurPath -> {
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700145
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700146 List<E> totalPath = ImmutableList.<E>builder()
147 .addAll(rootPathEdgeList)
148 .addAll(spurPath.edges())
149 .build();
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700150 potentialPaths.add(path(totalPath));
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700151 });
152
153 // Restore all removed paths and nodes
154 maskingWeigher.excluded.clear();
155 }
156
157 if (potentialPaths.isEmpty()) {
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700158 return null;
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700159 } else {
Yuta HIGUCHI2c2feff2017-05-23 10:50:11 -0700160 return potentialPaths.poll();
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700161 }
Yuta HIGUCHIe3ebe692017-04-17 10:00:42 -0700162 }
163
164 private Path<V, E> path(List<E> edges) {
165 //The following line must use the original weigher not the modified weigher because the modified
166 //weigher will count -1 values used for modifying the graph and return an inaccurate cost.
167 return new DefaultPath<>(edges, calculatePathCost(weigher, edges));
168 }
169
170 private Weight calculatePathCost(EdgeWeigher<V, E> weighter, List<E> edges) {
171 Weight totalCost = weighter.getInitialWeight();
172 for (E edge : edges) {
173 totalCost = totalCost.merge(weighter.weight(edge));
174 }
175 return totalCost;
176 }
177 }
178
179 /**
180 * EdgeWeigher which excludes specified edges from path computation.
181 */
182 private final class InnerEdgeWeigher implements EdgeWeigher<V, E> {
183
184 private final Set<E> excluded = Sets.newConcurrentHashSet();
185 private final EdgeWeigher<V, E> weigher;
186
187 private InnerEdgeWeigher(EdgeWeigher<V, E> weigher) {
188 this.weigher = weigher;
189 }
190
191 @Override
192 public Weight weight(E edge) {
193 if (excluded.contains(edge)) {
194 return weigher.getNonViableWeight();
195 }
196 return weigher.weight(edge);
197 }
198
199 @Override
200 public Weight getInitialWeight() {
201 return weigher.getInitialWeight();
202 }
203
204 @Override
205 public Weight getNonViableWeight() {
206 return weigher.getNonViableWeight();
207 }
208 }
209
210 /**
211 * Provides a comparator to order the set of paths.
212 * Compare by cost, then by hop count.
213 */
214 private final class PathComparator implements Comparator<Path<V, E>> {
215
216 @Override
217 public int compare(Path<V, E> pathOne, Path<V, E> pathTwo) {
218 return ComparisonChain.start()
219 .compare(pathOne.cost(), pathTwo.cost())
220 .compare(pathOne.edges().size(), pathTwo.edges().size())
221 .result();
222 }
223 }
224
225}