blob: 01f17efa26685fb7747166ea0743a534ceb3fbc2 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2014-present Open Networking Laboratory
Thomas Vachuska24c849c2014-10-27 09:53:05 -07003 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07004 * 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
Thomas Vachuska24c849c2014-10-27 09:53:05 -07007 *
Thomas Vachuska4f1a60c2014-10-28 13:39:07 -07008 * 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.
Thomas Vachuska24c849c2014-10-27 09:53:05 -070015 */
tome3489412014-08-29 02:30:38 -070016package org.onlab.graph;
17
18import java.util.HashMap;
19import java.util.HashSet;
20import java.util.Iterator;
21import java.util.Map;
22import java.util.Set;
23
24import static com.google.common.base.Preconditions.checkArgument;
25import static com.google.common.base.Preconditions.checkNotNull;
26
27/**
28 * Basis for various graph path search algorithm implementations.
29 *
30 * @param <V> vertex type
31 * @param <E> edge type
32 */
tom144de692014-08-29 11:38:44 -070033public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V>>
tome3489412014-08-29 02:30:38 -070034 implements GraphPathSearch<V, E> {
35
Thomas Vachuska4353a5a2014-10-27 15:18:10 -070036 private double samenessThreshold = Double.MIN_VALUE;
tome3489412014-08-29 02:30:38 -070037
38 /**
39 * Sets a new sameness threshold for comparing cost values; default is
Thomas Vachuskad916bcd2015-01-16 13:03:27 -080040 * is {@link Double#MIN_VALUE}.
tome3489412014-08-29 02:30:38 -070041 *
42 * @param threshold fractional double value
43 */
44 public void setSamenessThreshold(double threshold) {
45 samenessThreshold = threshold;
46 }
47
48 /**
49 * Returns the current sameness threshold for comparing cost values.
50 *
51 * @return current threshold
52 */
53 public double samenessThreshold() {
54 return samenessThreshold;
55 }
56
57 /**
58 * Default path search result that uses the DefaultPath to convey paths
59 * in a graph.
60 */
61 protected class DefaultResult implements Result<V, E> {
62
63 private final V src;
64 private final V dst;
65 protected final Set<Path<V, E>> paths = new HashSet<>();
66 protected final Map<V, Double> costs = new HashMap<>();
67 protected final Map<V, Set<E>> parents = new HashMap<>();
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080068 protected final int maxPaths;
tome3489412014-08-29 02:30:38 -070069
70 /**
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080071 * Creates the result of a single-path search.
tome3489412014-08-29 02:30:38 -070072 *
73 * @param src path source
74 * @param dst optional path destination
75 */
76 public DefaultResult(V src, V dst) {
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080077 this(src, dst, 1);
78 }
79
80 /**
81 * Creates the result of path search.
82 *
83 * @param src path source
84 * @param dst optional path destination
85 * @param maxPaths optional limit of number of paths;
86 * {@link GraphPathSearch#ALL_PATHS} if no limit
87 */
88 public DefaultResult(V src, V dst, int maxPaths) {
tome3489412014-08-29 02:30:38 -070089 checkNotNull(src, "Source cannot be null");
90 this.src = src;
91 this.dst = dst;
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080092 this.maxPaths = maxPaths;
tome3489412014-08-29 02:30:38 -070093 }
94
95 @Override
96 public V src() {
97 return src;
98 }
99
100 @Override
101 public V dst() {
102 return dst;
103 }
104
105 @Override
106 public Set<Path<V, E>> paths() {
107 return paths;
108 }
109
110 @Override
111 public Map<V, Double> costs() {
112 return costs;
113 }
114
115 @Override
116 public Map<V, Set<E>> parents() {
117 return parents;
118 }
119
120 /**
121 * Indicates whether or not the given vertex has a cost yet.
122 *
123 * @param v vertex to test
124 * @return true if the vertex has cost already
125 */
126 boolean hasCost(V v) {
127 return costs.get(v) != null;
128 }
129
130 /**
131 * Returns the current cost to reach the specified vertex.
132 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700133 * @param v vertex to reach
tome3489412014-08-29 02:30:38 -0700134 * @return cost to reach the vertex
135 */
136 double cost(V v) {
137 Double c = costs.get(v);
138 return c == null ? Double.MAX_VALUE : c;
139 }
140
141 /**
142 * Updates the cost of the vertex using its existing cost plus the
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800143 * cost to traverse the specified edge. If the search is in single
144 * path mode, only one path will be accrued.
tome3489412014-08-29 02:30:38 -0700145 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700146 * @param vertex vertex to update
tome3489412014-08-29 02:30:38 -0700147 * @param edge edge through which vertex is reached
148 * @param cost current cost to reach the vertex from the source
149 * @param replace true to indicate that any accrued edges are to be
150 * cleared; false to indicate that the edge should be
151 * added to the previously accrued edges as they yield
152 * the same cost
153 */
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700154 void updateVertex(V vertex, E edge, double cost, boolean replace) {
155 costs.put(vertex, cost);
tome3489412014-08-29 02:30:38 -0700156 if (edge != null) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700157 Set<E> edges = parents.get(vertex);
tome3489412014-08-29 02:30:38 -0700158 if (edges == null) {
159 edges = new HashSet<>();
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700160 parents.put(vertex, edges);
tome3489412014-08-29 02:30:38 -0700161 }
162 if (replace) {
163 edges.clear();
164 }
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800165 if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
166 edges.add(edge);
167 }
tome3489412014-08-29 02:30:38 -0700168 }
169 }
170
171 /**
172 * Removes the set of parent edges for the specified vertex.
173 *
174 * @param v vertex
175 */
176 void removeVertex(V v) {
177 parents.remove(v);
178 }
179
180 /**
181 * If possible, relax the specified edge using the supplied base cost
182 * and edge-weight function.
183 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700184 * @param edge edge to be relaxed
Thomas Vachuska4d690872014-10-27 08:57:08 -0700185 * @param cost base cost to reach the edge destination vertex
186 * @param ew optional edge weight function
187 * @param forbidNegatives if true negative values will forbid the link
tome3489412014-08-29 02:30:38 -0700188 * @return true if the edge was relaxed; false otherwise
189 */
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700190 boolean relaxEdge(E edge, double cost, EdgeWeight<V, E> ew,
Thomas Vachuska4d690872014-10-27 08:57:08 -0700191 boolean... forbidNegatives) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700192 V v = edge.dst();
tome3489412014-08-29 02:30:38 -0700193 double oldCost = cost(v);
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700194 double hopCost = ew == null ? 1.0 : ew.weight(edge);
Thomas Vachuska4d690872014-10-27 08:57:08 -0700195 if (hopCost < 0 && forbidNegatives.length == 1 && forbidNegatives[0]) {
196 return false;
197 }
198
199 double newCost = cost + hopCost;
tome3489412014-08-29 02:30:38 -0700200 boolean relaxed = newCost < oldCost;
Thomas Vachuska4353a5a2014-10-27 15:18:10 -0700201 boolean same = Math.abs(newCost - oldCost) <= samenessThreshold;
tome3489412014-08-29 02:30:38 -0700202 if (same || relaxed) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700203 updateVertex(v, edge, newCost, !same);
tome3489412014-08-29 02:30:38 -0700204 }
205 return relaxed;
206 }
207
208 /**
209 * Builds a set of paths for the specified src/dst vertex pair.
210 */
211 protected void buildPaths() {
212 Set<V> destinations = new HashSet<>();
213 if (dst == null) {
214 destinations.addAll(costs.keySet());
215 } else {
216 destinations.add(dst);
217 }
218
219 // Build all paths between the source and all requested destinations.
220 for (V v : destinations) {
221 // Ignore the source, if it is among the destinations.
222 if (!v.equals(src)) {
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800223 buildAllPaths(this, src, v, maxPaths);
tome3489412014-08-29 02:30:38 -0700224 }
225 }
226 }
227
228 }
229
230 /**
231 * Builds a set of all paths between the source and destination using the
232 * graph search result by applying breadth-first search through the parent
233 * edges and vertex costs.
234 *
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800235 * @param result graph search result
236 * @param src source vertex
237 * @param dst destination vertex
238 * @param maxPaths limit on the number of paths built;
239 * {@link GraphPathSearch#ALL_PATHS} if no limit
tome3489412014-08-29 02:30:38 -0700240 */
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800241 private void buildAllPaths(DefaultResult result, V src, V dst, int maxPaths) {
tome3489412014-08-29 02:30:38 -0700242 DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
243 basePath.setCost(result.cost(dst));
244
245 Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
246 pendingPaths.add(basePath);
247
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800248 while (!pendingPaths.isEmpty() &&
249 (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
tome3489412014-08-29 02:30:38 -0700250 Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
251
252 for (DefaultMutablePath<V, E> path : pendingPaths) {
253 // For each pending path, locate its first vertex since we
254 // will be moving backwards from it.
255 V firstVertex = firstVertex(path, dst);
256
257 // If the first vertex is our expected source, we have reached
258 // the beginning, so add the this path to the result paths.
259 if (firstVertex.equals(src)) {
260 path.setCost(result.cost(dst));
261 result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
262
263 } else {
264 // If we have not reached the beginning, i.e. the source,
265 // fetch the set of edges leading to the first vertex of
266 // this pending path; if there are none, abandon processing
267 // this path for good.
268 Set<E> firstVertexParents = result.parents.get(firstVertex);
269 if (firstVertexParents == null || firstVertexParents.isEmpty()) {
270 break;
271 }
272
273 // Now iterate over all the edges and for each of them
274 // cloning the current path and then insert that edge to
275 // the path and then add that path to the pending ones.
276 // When processing the last edge, modify the current
277 // pending path rather than cloning a new one.
278 Iterator<E> edges = firstVertexParents.iterator();
279 while (edges.hasNext()) {
280 E edge = edges.next();
281 boolean isLast = !edges.hasNext();
Thomas Vachuskab1995cb2016-08-23 09:36:08 -0700282 // Exclude any looping paths
283 if (!isInPath(edge, path)) {
284 DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
285 pendingPath.insertEdge(edge);
286 frontier.add(pendingPath);
287 }
tome3489412014-08-29 02:30:38 -0700288 }
289 }
290 }
291
292 // All pending paths have been scanned so promote the next frontier
293 pendingPaths = frontier;
294 }
295 }
296
Thomas Vachuskab1995cb2016-08-23 09:36:08 -0700297 /**
298 * Indicates whether or not the specified edge source is already visited
299 * in the specified path.
300 *
301 * @param edge edge to test
302 * @param path path to test
303 * @return true if the edge.src() is a vertex in the path already
304 */
305 private boolean isInPath(E edge, DefaultMutablePath<V, E> path) {
306 return path.edges().stream().anyMatch(e -> edge.src().equals(e.dst()));
307 }
308
tome3489412014-08-29 02:30:38 -0700309 // Returns the first vertex of the specified path. This is either the source
310 // of the first edge or, if there are no edges yet, the given destination.
311 private V firstVertex(Path<V, E> path, V dst) {
312 return path.edges().isEmpty() ? dst : path.edges().get(0).src();
313 }
314
315 /**
316 * Checks the specified path search arguments for validity.
317 *
318 * @param graph graph; must not be null
319 * @param src source vertex; must not be null and belong to graph
320 * @param dst optional target vertex; must belong to graph
321 */
322 protected void checkArguments(Graph<V, E> graph, V src, V dst) {
323 checkNotNull(graph, "Graph cannot be null");
324 checkNotNull(src, "Source cannot be null");
325 Set<V> vertices = graph.getVertexes();
326 checkArgument(vertices.contains(src), "Source not in the graph");
327 checkArgument(dst == null || vertices.contains(dst),
328 "Destination not in graph");
329 }
330
331}