blob: 5039c51b2688ade3fe515cc78c9ef55593fc9a95 [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;
30import java.util.stream.Stream;
31import java.util.stream.StreamSupport;
32
33import com.google.common.collect.ComparisonChain;
34import com.google.common.collect.ImmutableList;
35import com.google.common.collect.Sets;
36
37/**
38 * Lazily runs K shortest paths algorithm on a provided directed graph.
39 */
40public class LazyKShortestPathsSearch<V extends Vertex, E extends Edge<V>> {
41
42 private final Comparator<Path<V, E>> pathComparator = new PathComparator();
43
44 private final GraphPathSearch<V, E> shortest = new DijkstraGraphSearch<>();
45
46 /**
47 * Searches the specified graph for paths between vertices.
48 *
49 * @param graph graph to be searched
50 * @param src source vertex
51 * @param dst destination vertex
52 * @param weigher edge-weigher
53 * @return Stream of shortest paths
54 */
55 public Stream<Path<V, E>> lazyPathSearch(Graph<V, E> graph,
56 V src, V dst,
57 EdgeWeigher<V, E> weigher) {
58
59 Iterator<Path<V, E>> it = new ShortestPathIterator(graph, src, dst, weigher);
60
61 return StreamSupport.stream(spliteratorUnknownSize(it,
62 Spliterator.ORDERED |
63 Spliterator.DISTINCT |
64 Spliterator.NONNULL |
65 Spliterator.IMMUTABLE),
66 false);
67 }
68
69 /**
70 * Iterator returning shortest paths, searched incrementally on each next() call.
71 */
72 private final class ShortestPathIterator implements Iterator<Path<V, E>> {
73
74 final Graph<V, E> graph;
75 final V src;
76 final V dst;
77 final EdgeWeigher<V, E> weigher;
78
79 final InnerEdgeWeigher maskingWeigher;
80
81 final List<Path<V, E>> resultPaths = new ArrayList<>(); // A
82 final Queue<Path<V, E>> potentialPaths = new PriorityQueue<>(pathComparator); // B
83
84 Path<V, E> next;
85
86 ShortestPathIterator(Graph<V, E> graph,
87 V src, V dst,
88 EdgeWeigher<V, E> weigher) {
89 this.graph = checkNotNull(graph);
90 this.src = checkNotNull(src);
91 this.dst = checkNotNull(dst);
92 this.weigher = checkNotNull(weigher);
93
94 maskingWeigher = new InnerEdgeWeigher(weigher);
95 next = shortest.search(graph, src, dst, weigher, 1)
96 .paths().stream().findFirst().orElse(null);
97 }
98
99 @Override
100 public boolean hasNext() {
101 return next != null;
102 }
103
104 @Override
105 public Path<V, E> next() {
106 if (next == null) {
107 throw new NoSuchElementException("No more path between " + src + "-" + dst);
108 }
109
110 // lastPath: the path to return at the end of this call
111 Path<V, E> lastPath = next;
112 resultPaths.add(lastPath);
113
114 /// following is basically Yen's k-shortest path algorithm
115
116 // start searching for next path
117 for (int i = 0; i < lastPath.edges().size(); i++) {
118 V spurNode = lastPath.edges().get(i).src();
119 List<E> rootPathEdgeList = lastPath.edges().subList(0, i);
120
121 for (Path<V, E> path : resultPaths) {
122 if (path.edges().size() >= i &&
123 rootPathEdgeList.equals(path.edges().subList(0, i))) {
124 maskingWeigher.excluded.add(path.edges().get(i));
125 }
126 }
127
128 // Effectively remove all root path nodes other than spurNode
129 rootPathEdgeList.forEach(edge -> {
130 maskingWeigher.excluded.addAll(graph.getEdgesFrom(edge.src()));
131 maskingWeigher.excluded.addAll(graph.getEdgesTo(edge.src()));
132 });
133
134 shortest.search(graph, spurNode, dst, maskingWeigher, 1)
135 .paths().stream().findAny().ifPresent(spurPath -> {
136 List<E> totalPath = ImmutableList.<E>builder()
137 .addAll(rootPathEdgeList)
138 .addAll(spurPath.edges())
139 .build();
140 potentialPaths.add(path(totalPath));
141 });
142
143 // Restore all removed paths and nodes
144 maskingWeigher.excluded.clear();
145 }
146
147 if (potentialPaths.isEmpty()) {
148 next = null;
149 } else {
150 next = potentialPaths.poll();
151 }
152
153 return lastPath;
154 }
155
156 private Path<V, E> path(List<E> edges) {
157 //The following line must use the original weigher not the modified weigher because the modified
158 //weigher will count -1 values used for modifying the graph and return an inaccurate cost.
159 return new DefaultPath<>(edges, calculatePathCost(weigher, edges));
160 }
161
162 private Weight calculatePathCost(EdgeWeigher<V, E> weighter, List<E> edges) {
163 Weight totalCost = weighter.getInitialWeight();
164 for (E edge : edges) {
165 totalCost = totalCost.merge(weighter.weight(edge));
166 }
167 return totalCost;
168 }
169 }
170
171 /**
172 * EdgeWeigher which excludes specified edges from path computation.
173 */
174 private final class InnerEdgeWeigher implements EdgeWeigher<V, E> {
175
176 private final Set<E> excluded = Sets.newConcurrentHashSet();
177 private final EdgeWeigher<V, E> weigher;
178
179 private InnerEdgeWeigher(EdgeWeigher<V, E> weigher) {
180 this.weigher = weigher;
181 }
182
183 @Override
184 public Weight weight(E edge) {
185 if (excluded.contains(edge)) {
186 return weigher.getNonViableWeight();
187 }
188 return weigher.weight(edge);
189 }
190
191 @Override
192 public Weight getInitialWeight() {
193 return weigher.getInitialWeight();
194 }
195
196 @Override
197 public Weight getNonViableWeight() {
198 return weigher.getNonViableWeight();
199 }
200 }
201
202 /**
203 * Provides a comparator to order the set of paths.
204 * Compare by cost, then by hop count.
205 */
206 private final class PathComparator implements Comparator<Path<V, E>> {
207
208 @Override
209 public int compare(Path<V, E> pathOne, Path<V, E> pathTwo) {
210 return ComparisonChain.start()
211 .compare(pathOne.cost(), pathTwo.cost())
212 .compare(pathOne.edges().size(), pathTwo.edges().size())
213 .result();
214 }
215 }
216
217}