1 package org.onap.ccsdk.sli.core.slipluginutils.slitopologyutils.graph;
5 import static com.google.common.base.Preconditions.checkArgument;
6 import static com.google.common.base.Preconditions.checkNotNull;
9 * Dijkstra shortest-path graph search algorithm capable of finding not just
10 * one, but all shortest paths between the source and destinations.
12 public class DijkstraGraphSearch<V extends Vertex, E extends Edge<V>> {
14 static int ALL_PATHS = -1;
16 * Default path search result that uses the DefaultPath to convey paths
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;
29 * Creates the result of a single-path search.
31 * @param src path source
32 * @param dst optional path destination
34 public Result(V src, V dst) {
39 * Creates the result of path search.
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
46 public Result(V src, V dst, int maxPaths) {
47 checkNotNull(src, "Source cannot be null");
50 this.maxPaths = maxPaths;
64 public Set<Path<V, E>> paths() {
68 public Map<V, Weight> costs() {
72 public Map<V, Set<E>> parents() {
77 * Indicates whether or not the given vertex has a cost yet.
79 * @param v vertex to test
80 * @return true if the vertex has cost already
82 public boolean hasCost(V v) {
83 return costs.containsKey(v);
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.
91 * @param v vertex to reach
92 * @return weight cost to reach the vertex if already accessed;
95 public Weight cost(V v) {
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.
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
112 public void updateVertex(V vertex, E edge, Weight cost, boolean replace) {
113 costs.put(vertex, cost);
115 Set<E> edges = parents.get(vertex);
117 edges = new HashSet<>();
118 parents.put(vertex, edges);
123 if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
130 * Removes the set of parent edges for the specified vertex.
134 void removeVertex(V v) {
139 * If possible, relax the specified edge using the supplied base cost
140 * and edge-weight function.
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
148 public boolean relaxEdge(E edge, Weight cost, EdgeWeigher<V, E> ew,
149 boolean... forbidNegatives) {
152 Weight hopCost = ew.weight(edge);
153 if ((!hopCost.isViable()) ||
154 (hopCost.isNegative() && forbidNegatives.length == 1 && forbidNegatives[0])) {
157 Weight newCost = cost.merge(hopCost);
159 int compareResult = -1;
161 Weight oldCost = cost(v);
162 compareResult = newCost.compareTo(oldCost);
165 if (compareResult <= 0) {
166 updateVertex(v, edge, newCost, compareResult < 0);
168 return compareResult < 0;
172 * Builds a set of paths for the specified src/dst vertex pair.
174 public void buildPaths() {
175 Set<V> destinations = new HashSet<>();
177 destinations.addAll(costs.keySet());
179 destinations.add(dst);
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);
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.
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
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));
207 Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
208 pendingPaths.add(basePath);
210 while (!pendingPaths.isEmpty() &&
211 (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
212 Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
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);
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()));
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()) {
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);
254 // All pending paths have been scanned so promote the next frontier
255 pendingPaths = frontier;
260 * Indicates whether or not the specified edge source is already visited
261 * in the specified path.
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
267 private boolean isInPath(E edge, DefaultMutablePath<V, E> path) {
268 return path.edges().stream().anyMatch(e -> edge.src().equals(e.dst()));
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();
278 * Checks the specified path search arguments for validity.
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
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");
293 public Result search(Graph<V, E> graph, V src, V dst,
294 EdgeWeigher<V, E> weigher, int maxPaths) {
295 checkArguments(graph, src, dst);
297 return internalSearch(graph, src, dst,
298 weigher != null ? weigher : new DefaultEdgeWeigher<>(),
302 protected Result internalSearch(Graph<V, E> graph, V src, V dst,
303 EdgeWeigher<V, E> weigher, int maxPaths) {
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);
309 // Cost to reach the source vertex is 0 of course.
310 result.updateVertex(src, null, weigher.getInitialWeight(), false);
312 if (graph.getEdges().isEmpty()) {
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)) {
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);
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);
339 // Re-prioritize the min queue.
343 // Now construct a set of paths from the results.
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;
353 private PathCostComparator(Result result) {
354 this.result = result;
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)) {
362 } else if (!result.hasCost(v1)) {
364 } else if (!result.hasCost(v2)) {
368 return result.cost(v2).compareTo(result.cost(v1));
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);