11611346bb08abe2038ae613887bdc8333072283
[ccsdk/sli.git] /
1 package org.onap.ccsdk.sli.core.slipluginutils.slitopologyutils.graph;
2
3 import java.util.*;
4
5 import static com.google.common.base.Preconditions.checkArgument;
6 import static com.google.common.base.Preconditions.checkNotNull;
7
8 /**
9  * Dijkstra shortest-path graph search algorithm capable of finding not just
10  * one, but all shortest paths between the source and destinations.
11  */
12 public class DijkstraGraphSearch<V extends Vertex, E extends Edge<V>> {
13
14     static int ALL_PATHS = -1;
15     /**
16      * Default path search result that uses the DefaultPath to convey paths
17      * in a graph.
18      */
19     public class Result {
20
21         private final V src;
22         private final V dst;
23         protected final Set<Path<V, E>> paths = new HashSet<>();
24         protected final Map<V, Weight> costs = new HashMap<>();
25         protected final Map<V, Set<E>> parents = new HashMap<>();
26         protected final int maxPaths;
27
28         /**
29          * Creates the result of a single-path search.
30          *
31          * @param src path source
32          * @param dst optional path destination
33          */
34         public Result(V src, V dst) {
35             this(src, dst, 1);
36         }
37
38         /**
39          * Creates the result of path search.
40          *
41          * @param src      path source
42          * @param dst      optional path destination
43          * @param maxPaths optional limit of number of paths;
44          *                 {@link #ALL_PATHS} if no limit
45          */
46         public Result(V src, V dst, int maxPaths) {
47             checkNotNull(src, "Source cannot be null");
48             this.src = src;
49             this.dst = dst;
50             this.maxPaths = maxPaths;
51         }
52
53
54         public V src() {
55             return src;
56         }
57
58
59         public V dst() {
60             return dst;
61         }
62
63
64         public Set<Path<V, E>> paths() {
65             return paths;
66         }
67
68         public Map<V, Weight> costs() {
69             return costs;
70         }
71
72         public Map<V, Set<E>> parents() {
73             return parents;
74         }
75
76         /**
77          * Indicates whether or not the given vertex has a cost yet.
78          *
79          * @param v vertex to test
80          * @return true if the vertex has cost already
81          */
82         public boolean hasCost(V v) {
83             return costs.containsKey(v);
84         }
85
86         /**
87          * Returns the current cost to reach the specified vertex.
88          * If the vertex has not been accessed yet, it has no cost
89          * associated and null will be returned.
90          *
91          * @param v vertex to reach
92          * @return weight cost to reach the vertex if already accessed;
93          *         null otherwise
94          */
95         public Weight cost(V v) {
96             return costs.get(v);
97         }
98
99         /**
100          * Updates the cost of the vertex using its existing cost plus the
101          * cost to traverse the specified edge. If the search is in single
102          * path mode, only one path will be accrued.
103          *
104          * @param vertex  vertex to update
105          * @param edge    edge through which vertex is reached
106          * @param cost    current cost to reach the vertex from the source
107          * @param replace true to indicate that any accrued edges are to be
108          *                cleared; false to indicate that the edge should be
109          *                added to the previously accrued edges as they yield
110          *                the same cost
111          */
112         public void updateVertex(V vertex, E edge, Weight cost, boolean replace) {
113             costs.put(vertex, cost);
114             if (edge != null) {
115                 Set<E> edges = parents.get(vertex);
116                 if (edges == null) {
117                     edges = new HashSet<>();
118                     parents.put(vertex, edges);
119                 }
120                 if (replace) {
121                     edges.clear();
122                 }
123                 if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
124                     edges.add(edge);
125                 }
126             }
127         }
128
129         /**
130          * Removes the set of parent edges for the specified vertex.
131          *
132          * @param v vertex
133          */
134         void removeVertex(V v) {
135             parents.remove(v);
136         }
137
138         /**
139          * If possible, relax the specified edge using the supplied base cost
140          * and edge-weight function.
141          *
142          * @param edge            edge to be relaxed
143          * @param cost            base cost to reach the edge destination vertex
144          * @param ew              optional edge weight function
145          * @param forbidNegatives if true negative values will forbid the link
146          * @return true if the edge was relaxed; false otherwise
147          */
148         public boolean relaxEdge(E edge, Weight cost, EdgeWeigher<V, E> ew,
149                                  boolean... forbidNegatives) {
150             V v = edge.dst();
151
152             Weight hopCost = ew.weight(edge);
153             if ((!hopCost.isViable()) ||
154                     (hopCost.isNegative() && forbidNegatives.length == 1 && forbidNegatives[0])) {
155                 return false;
156             }
157             Weight newCost = cost.merge(hopCost);
158
159             int compareResult = -1;
160             if (hasCost(v)) {
161                 Weight oldCost = cost(v);
162                 compareResult = newCost.compareTo(oldCost);
163             }
164
165             if (compareResult <= 0) {
166                 updateVertex(v, edge, newCost, compareResult < 0);
167             }
168             return compareResult < 0;
169         }
170
171         /**
172          * Builds a set of paths for the specified src/dst vertex pair.
173          */
174         public void buildPaths() {
175             Set<V> destinations = new HashSet<>();
176             if (dst == null) {
177                 destinations.addAll(costs.keySet());
178             } else {
179                 destinations.add(dst);
180             }
181
182             // Build all paths between the source and all requested destinations.
183             for (V v : destinations) {
184                 // Ignore the source, if it is among the destinations.
185                 if (!v.equals(src)) {
186                     buildAllPaths(this, src, v, maxPaths);
187                 }
188             }
189         }
190
191     }
192     /**
193      * Builds a set of all paths between the source and destination using the
194      * graph search result by applying breadth-first search through the parent
195      * edges and vertex costs.
196      *
197      * @param result   graph search result
198      * @param src      source vertex
199      * @param dst      destination vertex
200      * @param maxPaths limit on the number of paths built;
201      *                 {@link #ALL_PATHS} if no limit
202      */
203     private void buildAllPaths(Result result, V src, V dst, int maxPaths) {
204         DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
205         basePath.setCost(result.cost(dst));
206
207         Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
208         pendingPaths.add(basePath);
209
210         while (!pendingPaths.isEmpty() &&
211                 (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
212             Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
213
214             for (DefaultMutablePath<V, E> path : pendingPaths) {
215                 // For each pending path, locate its first vertex since we
216                 // will be moving backwards from it.
217                 V firstVertex = firstVertex(path, dst);
218
219                 // If the first vertex is our expected source, we have reached
220                 // the beginning, so add the this path to the result paths.
221                 if (firstVertex.equals(src)) {
222                     path.setCost(result.cost(dst));
223                     result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
224
225                 } else {
226                     // If we have not reached the beginning, i.e. the source,
227                     // fetch the set of edges leading to the first vertex of
228                     // this pending path; if there are none, abandon processing
229                     // this path for good.
230                     Set<E> firstVertexParents = result.parents.get(firstVertex);
231                     if (firstVertexParents == null || firstVertexParents.isEmpty()) {
232                         break;
233                     }
234
235                     // Now iterate over all the edges and for each of them
236                     // cloning the current path and then insert that edge to
237                     // the path and then add that path to the pending ones.
238                     // When processing the last edge, modify the current
239                     // pending path rather than cloning a new one.
240                     Iterator<E> edges = firstVertexParents.iterator();
241                     while (edges.hasNext()) {
242                         E edge = edges.next();
243                         boolean isLast = !edges.hasNext();
244                         // Exclude any looping paths
245                         if (!isInPath(edge, path)) {
246                             DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
247                             pendingPath.insertEdge(edge);
248                             frontier.add(pendingPath);
249                         }
250                     }
251                 }
252             }
253
254             // All pending paths have been scanned so promote the next frontier
255             pendingPaths = frontier;
256         }
257     }
258
259     /**
260      * Indicates whether or not the specified edge source is already visited
261      * in the specified path.
262      *
263      * @param edge edge to test
264      * @param path path to test
265      * @return true if the edge.src() is a vertex in the path already
266      */
267     private boolean isInPath(E edge, DefaultMutablePath<V, E> path) {
268         return path.edges().stream().anyMatch(e -> edge.src().equals(e.dst()));
269     }
270
271     // Returns the first vertex of the specified path. This is either the source
272     // of the first edge or, if there are no edges yet, the given destination.
273     private V firstVertex(Path<V, E> path, V dst) {
274         return path.edges().isEmpty() ? dst : path.edges().get(0).src();
275     }
276
277     /**
278      * Checks the specified path search arguments for validity.
279      *
280      * @param graph graph; must not be null
281      * @param src   source vertex; must not be null and belong to graph
282      * @param dst   optional target vertex; must belong to graph
283      */
284     protected void checkArguments(Graph<V, E> graph, V src, V dst) {
285         checkNotNull(graph, "Graph cannot be null");
286         checkNotNull(src, "Source cannot be null");
287         Set<V> vertices = graph.getVertexes();
288         checkArgument(vertices.contains(src), "Source not in the graph");
289         checkArgument(dst == null || vertices.contains(dst),
290                 "Destination not in graph");
291     }
292
293     public Result search(Graph<V, E> graph, V src, V dst,
294                          EdgeWeigher<V, E> weigher, int maxPaths) {
295         checkArguments(graph, src, dst);
296
297         return internalSearch(graph, src, dst,
298                 weigher != null ? weigher : new DefaultEdgeWeigher<>(),
299                 maxPaths);
300     }
301
302     protected Result internalSearch(Graph<V, E> graph, V src, V dst,
303                                     EdgeWeigher<V, E> weigher, int maxPaths) {
304
305         // Use the default result to remember cumulative costs and parent
306         // edges to each each respective vertex.
307         Result result = new Result(src, dst, maxPaths);
308
309         // Cost to reach the source vertex is 0 of course.
310         result.updateVertex(src, null, weigher.getInitialWeight(), false);
311
312         if (graph.getEdges().isEmpty()) {
313             result.buildPaths();
314             return result;
315         }
316
317         // Use the min priority queue to progressively find each nearest
318         // vertex until we reach the desired destination, if one was given,
319         // or until we reach all possible destinations.
320         Heap<V> minQueue = createMinQueue(graph.getVertexes(),
321                 new PathCostComparator(result));
322         while (!minQueue.isEmpty()) {
323             // Get the nearest vertex
324             V nearest = minQueue.extractExtreme();
325             if (nearest.equals(dst)) {
326                 break;
327             }
328
329             // Find its cost and use it to determine if the vertex is reachable.
330             if (result.hasCost(nearest)) {
331                 Weight cost = result.cost(nearest);
332
333                 // If the vertex is reachable, relax all its egress edges.
334                 for (E e : graph.getEdgesFrom(nearest)) {
335                     result.relaxEdge(e, cost, weigher, true);
336                 }
337             }
338
339             // Re-prioritize the min queue.
340             minQueue.heapify();
341         }
342
343         // Now construct a set of paths from the results.
344         result.buildPaths();
345         return result;
346     }
347
348     // Compares path weights using their accrued costs; used for sorting the
349     // min priority queue.
350     private final class PathCostComparator implements Comparator<V> {
351         private final Result result;
352
353         private PathCostComparator(Result result) {
354             this.result = result;
355         }
356
357         @Override
358         public int compare(V v1, V v2) {
359             //not accessed vertices should be pushed to the back of the queue
360             if (!result.hasCost(v1) && !result.hasCost(v2)) {
361                 return 0;
362             } else if (!result.hasCost(v1)) {
363                 return -1;
364             } else if (!result.hasCost(v2)) {
365                 return 1;
366             }
367
368             return result.cost(v2).compareTo(result.cost(v1));
369         }
370     }
371
372     // Creates a min priority queue from the specified vertexes and comparator.
373     private Heap<V> createMinQueue(Set<V> vertexes, Comparator<V> comparator) {
374         return new Heap<>(new ArrayList<>(vertexes), comparator);
375     }
376
377 }