blob: 95f99dc8136acccc3eb85006e6e635ef4e6fdb66 [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
tome3489412014-08-29 02:30:38 -070036 /**
37 * Default path search result that uses the DefaultPath to convey paths
38 * in a graph.
39 */
40 protected class DefaultResult implements Result<V, E> {
41
42 private final V src;
43 private final V dst;
44 protected final Set<Path<V, E>> paths = new HashSet<>();
Andrey Komarov2398d962016-09-26 15:11:23 +030045 protected final Map<V, Weight> costs = new HashMap<>();
tome3489412014-08-29 02:30:38 -070046 protected final Map<V, Set<E>> parents = new HashMap<>();
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080047 protected final int maxPaths;
tome3489412014-08-29 02:30:38 -070048
49 /**
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080050 * Creates the result of a single-path search.
tome3489412014-08-29 02:30:38 -070051 *
52 * @param src path source
53 * @param dst optional path destination
54 */
55 public DefaultResult(V src, V dst) {
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080056 this(src, dst, 1);
57 }
58
59 /**
60 * Creates the result of path search.
61 *
62 * @param src path source
63 * @param dst optional path destination
64 * @param maxPaths optional limit of number of paths;
65 * {@link GraphPathSearch#ALL_PATHS} if no limit
66 */
67 public DefaultResult(V src, V dst, int maxPaths) {
tome3489412014-08-29 02:30:38 -070068 checkNotNull(src, "Source cannot be null");
69 this.src = src;
70 this.dst = dst;
Thomas Vachuskac31d9f12015-01-22 12:33:27 -080071 this.maxPaths = maxPaths;
tome3489412014-08-29 02:30:38 -070072 }
73
74 @Override
75 public V src() {
76 return src;
77 }
78
79 @Override
80 public V dst() {
81 return dst;
82 }
83
84 @Override
85 public Set<Path<V, E>> paths() {
86 return paths;
87 }
88
89 @Override
Andrey Komarov2398d962016-09-26 15:11:23 +030090 public Map<V, Weight> costs() {
tome3489412014-08-29 02:30:38 -070091 return costs;
92 }
93
94 @Override
95 public Map<V, Set<E>> parents() {
96 return parents;
97 }
98
99 /**
100 * Indicates whether or not the given vertex has a cost yet.
101 *
102 * @param v vertex to test
103 * @return true if the vertex has cost already
104 */
105 boolean hasCost(V v) {
Andrey Komarov2398d962016-09-26 15:11:23 +0300106 return costs.containsKey(v);
tome3489412014-08-29 02:30:38 -0700107 }
108
109 /**
110 * Returns the current cost to reach the specified vertex.
Andrey Komarov2398d962016-09-26 15:11:23 +0300111 * If the vertex has not been accessed yet, it has no cost
112 * associated and null will be returned.
tome3489412014-08-29 02:30:38 -0700113 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700114 * @param v vertex to reach
Andrey Komarov2398d962016-09-26 15:11:23 +0300115 * @return weight cost to reach the vertex if already accessed;
116 * null otherwise
tome3489412014-08-29 02:30:38 -0700117 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300118 Weight cost(V v) {
119 return costs.get(v);
tome3489412014-08-29 02:30:38 -0700120 }
121
122 /**
123 * Updates the cost of the vertex using its existing cost plus the
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800124 * cost to traverse the specified edge. If the search is in single
125 * path mode, only one path will be accrued.
tome3489412014-08-29 02:30:38 -0700126 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700127 * @param vertex vertex to update
tome3489412014-08-29 02:30:38 -0700128 * @param edge edge through which vertex is reached
129 * @param cost current cost to reach the vertex from the source
130 * @param replace true to indicate that any accrued edges are to be
131 * cleared; false to indicate that the edge should be
132 * added to the previously accrued edges as they yield
133 * the same cost
134 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300135 void updateVertex(V vertex, E edge, Weight cost, boolean replace) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700136 costs.put(vertex, cost);
tome3489412014-08-29 02:30:38 -0700137 if (edge != null) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700138 Set<E> edges = parents.get(vertex);
tome3489412014-08-29 02:30:38 -0700139 if (edges == null) {
140 edges = new HashSet<>();
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700141 parents.put(vertex, edges);
tome3489412014-08-29 02:30:38 -0700142 }
143 if (replace) {
144 edges.clear();
145 }
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800146 if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
147 edges.add(edge);
148 }
tome3489412014-08-29 02:30:38 -0700149 }
150 }
151
152 /**
153 * Removes the set of parent edges for the specified vertex.
154 *
155 * @param v vertex
156 */
157 void removeVertex(V v) {
158 parents.remove(v);
159 }
160
161 /**
162 * If possible, relax the specified edge using the supplied base cost
163 * and edge-weight function.
164 *
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700165 * @param edge edge to be relaxed
Thomas Vachuska4d690872014-10-27 08:57:08 -0700166 * @param cost base cost to reach the edge destination vertex
167 * @param ew optional edge weight function
168 * @param forbidNegatives if true negative values will forbid the link
tome3489412014-08-29 02:30:38 -0700169 * @return true if the edge was relaxed; false otherwise
170 */
Andrey Komarov2398d962016-09-26 15:11:23 +0300171 boolean relaxEdge(E edge, Weight cost, EdgeWeigher<V, E> ew,
Thomas Vachuska4d690872014-10-27 08:57:08 -0700172 boolean... forbidNegatives) {
Thomas Vachuska7b652ad2014-10-30 14:10:51 -0700173 V v = edge.dst();
Andrey Komarov2398d962016-09-26 15:11:23 +0300174
175 Weight hopCost = ew.weight(edge);
176 if ((!hopCost.isViable()) ||
177 (hopCost.isNegative() && forbidNegatives.length == 1 && forbidNegatives[0])) {
Thomas Vachuska4d690872014-10-27 08:57:08 -0700178 return false;
179 }
Andrey Komarov2398d962016-09-26 15:11:23 +0300180 Weight newCost = cost.merge(hopCost);
Thomas Vachuska4d690872014-10-27 08:57:08 -0700181
Andrey Komarov2398d962016-09-26 15:11:23 +0300182 int compareResult = -1;
183 if (hasCost(v)) {
184 Weight oldCost = cost(v);
185 compareResult = newCost.compareTo(oldCost);
tome3489412014-08-29 02:30:38 -0700186 }
Andrey Komarov2398d962016-09-26 15:11:23 +0300187
188 if (compareResult <= 0) {
189 updateVertex(v, edge, newCost, compareResult < 0);
190 }
191 return compareResult < 0;
tome3489412014-08-29 02:30:38 -0700192 }
193
194 /**
195 * Builds a set of paths for the specified src/dst vertex pair.
196 */
197 protected void buildPaths() {
198 Set<V> destinations = new HashSet<>();
199 if (dst == null) {
200 destinations.addAll(costs.keySet());
201 } else {
202 destinations.add(dst);
203 }
204
205 // Build all paths between the source and all requested destinations.
206 for (V v : destinations) {
207 // Ignore the source, if it is among the destinations.
208 if (!v.equals(src)) {
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800209 buildAllPaths(this, src, v, maxPaths);
tome3489412014-08-29 02:30:38 -0700210 }
211 }
212 }
213
214 }
215
216 /**
217 * Builds a set of all paths between the source and destination using the
218 * graph search result by applying breadth-first search through the parent
219 * edges and vertex costs.
220 *
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800221 * @param result graph search result
222 * @param src source vertex
223 * @param dst destination vertex
224 * @param maxPaths limit on the number of paths built;
225 * {@link GraphPathSearch#ALL_PATHS} if no limit
tome3489412014-08-29 02:30:38 -0700226 */
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800227 private void buildAllPaths(DefaultResult result, V src, V dst, int maxPaths) {
tome3489412014-08-29 02:30:38 -0700228 DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
229 basePath.setCost(result.cost(dst));
230
231 Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
232 pendingPaths.add(basePath);
233
Thomas Vachuskac31d9f12015-01-22 12:33:27 -0800234 while (!pendingPaths.isEmpty() &&
235 (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
tome3489412014-08-29 02:30:38 -0700236 Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
237
238 for (DefaultMutablePath<V, E> path : pendingPaths) {
239 // For each pending path, locate its first vertex since we
240 // will be moving backwards from it.
241 V firstVertex = firstVertex(path, dst);
242
243 // If the first vertex is our expected source, we have reached
244 // the beginning, so add the this path to the result paths.
245 if (firstVertex.equals(src)) {
246 path.setCost(result.cost(dst));
247 result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
248
249 } else {
250 // If we have not reached the beginning, i.e. the source,
251 // fetch the set of edges leading to the first vertex of
252 // this pending path; if there are none, abandon processing
253 // this path for good.
254 Set<E> firstVertexParents = result.parents.get(firstVertex);
255 if (firstVertexParents == null || firstVertexParents.isEmpty()) {
256 break;
257 }
258
259 // Now iterate over all the edges and for each of them
260 // cloning the current path and then insert that edge to
261 // the path and then add that path to the pending ones.
262 // When processing the last edge, modify the current
263 // pending path rather than cloning a new one.
264 Iterator<E> edges = firstVertexParents.iterator();
265 while (edges.hasNext()) {
266 E edge = edges.next();
267 boolean isLast = !edges.hasNext();
Thomas Vachuskab1995cb2016-08-23 09:36:08 -0700268 // Exclude any looping paths
269 if (!isInPath(edge, path)) {
270 DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
271 pendingPath.insertEdge(edge);
272 frontier.add(pendingPath);
273 }
tome3489412014-08-29 02:30:38 -0700274 }
275 }
276 }
277
278 // All pending paths have been scanned so promote the next frontier
279 pendingPaths = frontier;
280 }
281 }
282
Thomas Vachuskab1995cb2016-08-23 09:36:08 -0700283 /**
284 * Indicates whether or not the specified edge source is already visited
285 * in the specified path.
286 *
287 * @param edge edge to test
288 * @param path path to test
289 * @return true if the edge.src() is a vertex in the path already
290 */
291 private boolean isInPath(E edge, DefaultMutablePath<V, E> path) {
292 return path.edges().stream().anyMatch(e -> edge.src().equals(e.dst()));
293 }
294
tome3489412014-08-29 02:30:38 -0700295 // Returns the first vertex of the specified path. This is either the source
296 // of the first edge or, if there are no edges yet, the given destination.
297 private V firstVertex(Path<V, E> path, V dst) {
298 return path.edges().isEmpty() ? dst : path.edges().get(0).src();
299 }
300
301 /**
302 * Checks the specified path search arguments for validity.
303 *
304 * @param graph graph; must not be null
305 * @param src source vertex; must not be null and belong to graph
306 * @param dst optional target vertex; must belong to graph
307 */
308 protected void checkArguments(Graph<V, E> graph, V src, V dst) {
309 checkNotNull(graph, "Graph cannot be null");
310 checkNotNull(src, "Source cannot be null");
311 Set<V> vertices = graph.getVertexes();
312 checkArgument(vertices.contains(src), "Source not in the graph");
313 checkArgument(dst == null || vertices.contains(dst),
314 "Destination not in graph");
315 }
316
Andrey Komarov2398d962016-09-26 15:11:23 +0300317 @Override
318 public Result<V, E> search(Graph<V, E> graph, V src, V dst,
319 EdgeWeigher<V, E> weigher, int maxPaths) {
320 checkArguments(graph, src, dst);
321
322 return internalSearch(graph, src, dst,
323 weigher != null ? weigher : new DefaultEdgeWeigher<>(),
324 maxPaths);
325 }
326
327 protected abstract Result<V, E> internalSearch(Graph<V, E> graph, V src, V dst,
328 EdgeWeigher<V, E> weigher, int maxPaths);
tome3489412014-08-29 02:30:38 -0700329}