blob: 744e54536421d6d53a8c920059a3e1da08e1c3e8 [file] [log] [blame]
Thomas Vachuska24c849c2014-10-27 09:53:05 -07001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
tome3489412014-08-29 02:30:38 -070019package org.onlab.graph;
20
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.Iterator;
24import java.util.Map;
25import java.util.Set;
26
27import static com.google.common.base.Preconditions.checkArgument;
28import static com.google.common.base.Preconditions.checkNotNull;
29
30/**
31 * Basis for various graph path search algorithm implementations.
32 *
33 * @param <V> vertex type
34 * @param <E> edge type
35 */
tom144de692014-08-29 11:38:44 -070036public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V>>
tome3489412014-08-29 02:30:38 -070037 implements GraphPathSearch<V, E> {
38
39 private double samenessThreshold = 0.000000001;
40
41 /**
42 * Sets a new sameness threshold for comparing cost values; default is
43 * is {@code 0.000000001}.
44 *
45 * @param threshold fractional double value
46 */
47 public void setSamenessThreshold(double threshold) {
48 samenessThreshold = threshold;
49 }
50
51 /**
52 * Returns the current sameness threshold for comparing cost values.
53 *
54 * @return current threshold
55 */
56 public double samenessThreshold() {
57 return samenessThreshold;
58 }
59
60 /**
61 * Default path search result that uses the DefaultPath to convey paths
62 * in a graph.
63 */
64 protected class DefaultResult implements Result<V, E> {
65
66 private final V src;
67 private final V dst;
68 protected final Set<Path<V, E>> paths = new HashSet<>();
69 protected final Map<V, Double> costs = new HashMap<>();
70 protected final Map<V, Set<E>> parents = new HashMap<>();
71
72 /**
73 * Creates the result of path search.
74 *
75 * @param src path source
76 * @param dst optional path destination
77 */
78 public DefaultResult(V src, V dst) {
79 checkNotNull(src, "Source cannot be null");
80 this.src = src;
81 this.dst = dst;
82 }
83
84 @Override
85 public V src() {
86 return src;
87 }
88
89 @Override
90 public V dst() {
91 return dst;
92 }
93
94 @Override
95 public Set<Path<V, E>> paths() {
96 return paths;
97 }
98
99 @Override
100 public Map<V, Double> costs() {
101 return costs;
102 }
103
104 @Override
105 public Map<V, Set<E>> parents() {
106 return parents;
107 }
108
109 /**
110 * Indicates whether or not the given vertex has a cost yet.
111 *
112 * @param v vertex to test
113 * @return true if the vertex has cost already
114 */
115 boolean hasCost(V v) {
116 return costs.get(v) != null;
117 }
118
119 /**
120 * Returns the current cost to reach the specified vertex.
121 *
122 * @return cost to reach the vertex
123 */
124 double cost(V v) {
125 Double c = costs.get(v);
126 return c == null ? Double.MAX_VALUE : c;
127 }
128
129 /**
130 * Updates the cost of the vertex using its existing cost plus the
131 * cost to traverse the specified edge.
132 *
133 * @param v vertex
134 * @param edge edge through which vertex is reached
135 * @param cost current cost to reach the vertex from the source
136 * @param replace true to indicate that any accrued edges are to be
137 * cleared; false to indicate that the edge should be
138 * added to the previously accrued edges as they yield
139 * the same cost
140 */
141 void updateVertex(V v, E edge, double cost, boolean replace) {
142 costs.put(v, cost);
143 if (edge != null) {
144 Set<E> edges = parents.get(v);
145 if (edges == null) {
146 edges = new HashSet<>();
147 parents.put(v, edges);
148 }
149 if (replace) {
150 edges.clear();
151 }
152 edges.add(edge);
153 }
154 }
155
156 /**
157 * Removes the set of parent edges for the specified vertex.
158 *
159 * @param v vertex
160 */
161 void removeVertex(V v) {
162 parents.remove(v);
163 }
164
165 /**
166 * If possible, relax the specified edge using the supplied base cost
167 * and edge-weight function.
168 *
Thomas Vachuska4d690872014-10-27 08:57:08 -0700169 * @param e edge to be relaxed
170 * @param cost base cost to reach the edge destination vertex
171 * @param ew optional edge weight function
172 * @param forbidNegatives if true negative values will forbid the link
tome3489412014-08-29 02:30:38 -0700173 * @return true if the edge was relaxed; false otherwise
174 */
Thomas Vachuska4d690872014-10-27 08:57:08 -0700175 boolean relaxEdge(E e, double cost, EdgeWeight<V, E> ew,
176 boolean... forbidNegatives) {
tome3489412014-08-29 02:30:38 -0700177 V v = e.dst();
178 double oldCost = cost(v);
Thomas Vachuska4d690872014-10-27 08:57:08 -0700179 double hopCost = ew == null ? 1.0 : ew.weight(e);
180 if (hopCost < 0 && forbidNegatives.length == 1 && forbidNegatives[0]) {
181 return false;
182 }
183
184 double newCost = cost + hopCost;
tome3489412014-08-29 02:30:38 -0700185 boolean relaxed = newCost < oldCost;
186 boolean same = Math.abs(newCost - oldCost) < samenessThreshold;
187 if (same || relaxed) {
188 updateVertex(v, e, newCost, !same);
189 }
190 return relaxed;
191 }
192
193 /**
194 * Builds a set of paths for the specified src/dst vertex pair.
195 */
196 protected void buildPaths() {
197 Set<V> destinations = new HashSet<>();
198 if (dst == null) {
199 destinations.addAll(costs.keySet());
200 } else {
201 destinations.add(dst);
202 }
203
204 // Build all paths between the source and all requested destinations.
205 for (V v : destinations) {
206 // Ignore the source, if it is among the destinations.
207 if (!v.equals(src)) {
208 buildAllPaths(this, src, v);
209 }
210 }
211 }
212
213 }
214
215 /**
216 * Builds a set of all paths between the source and destination using the
217 * graph search result by applying breadth-first search through the parent
218 * edges and vertex costs.
219 *
220 * @param result graph search result
221 * @param src source vertex
222 * @param dst destination vertex
223 */
224 private void buildAllPaths(DefaultResult result, V src, V dst) {
225 DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
226 basePath.setCost(result.cost(dst));
227
228 Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
229 pendingPaths.add(basePath);
230
231 while (!pendingPaths.isEmpty()) {
232 Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
233
234 for (DefaultMutablePath<V, E> path : pendingPaths) {
235 // For each pending path, locate its first vertex since we
236 // will be moving backwards from it.
237 V firstVertex = firstVertex(path, dst);
238
239 // If the first vertex is our expected source, we have reached
240 // the beginning, so add the this path to the result paths.
241 if (firstVertex.equals(src)) {
242 path.setCost(result.cost(dst));
243 result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
244
245 } else {
246 // If we have not reached the beginning, i.e. the source,
247 // fetch the set of edges leading to the first vertex of
248 // this pending path; if there are none, abandon processing
249 // this path for good.
250 Set<E> firstVertexParents = result.parents.get(firstVertex);
251 if (firstVertexParents == null || firstVertexParents.isEmpty()) {
252 break;
253 }
254
255 // Now iterate over all the edges and for each of them
256 // cloning the current path and then insert that edge to
257 // the path and then add that path to the pending ones.
258 // When processing the last edge, modify the current
259 // pending path rather than cloning a new one.
260 Iterator<E> edges = firstVertexParents.iterator();
261 while (edges.hasNext()) {
262 E edge = edges.next();
263 boolean isLast = !edges.hasNext();
264 DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
265 pendingPath.insertEdge(edge);
266 frontier.add(pendingPath);
267 }
268 }
269 }
270
271 // All pending paths have been scanned so promote the next frontier
272 pendingPaths = frontier;
273 }
274 }
275
276 // Returns the first vertex of the specified path. This is either the source
277 // of the first edge or, if there are no edges yet, the given destination.
278 private V firstVertex(Path<V, E> path, V dst) {
279 return path.edges().isEmpty() ? dst : path.edges().get(0).src();
280 }
281
282 /**
283 * Checks the specified path search arguments for validity.
284 *
285 * @param graph graph; must not be null
286 * @param src source vertex; must not be null and belong to graph
287 * @param dst optional target vertex; must belong to graph
288 */
289 protected void checkArguments(Graph<V, E> graph, V src, V dst) {
290 checkNotNull(graph, "Graph cannot be null");
291 checkNotNull(src, "Source cannot be null");
292 Set<V> vertices = graph.getVertexes();
293 checkArgument(vertices.contains(src), "Source not in the graph");
294 checkArgument(dst == null || vertices.contains(dst),
295 "Destination not in graph");
296 }
297
298}