blob: 95f99dc8136acccc3eb85006e6e635ef4e6fdb66 [file] [log] [blame]
/*
* Copyright 2014-present Open Networking Laboratory
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onlab.graph;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Basis for various graph path search algorithm implementations.
*
* @param <V> vertex type
* @param <E> edge type
*/
public abstract class AbstractGraphPathSearch<V extends Vertex, E extends Edge<V>>
implements GraphPathSearch<V, E> {
/**
* Default path search result that uses the DefaultPath to convey paths
* in a graph.
*/
protected class DefaultResult implements Result<V, E> {
private final V src;
private final V dst;
protected final Set<Path<V, E>> paths = new HashSet<>();
protected final Map<V, Weight> costs = new HashMap<>();
protected final Map<V, Set<E>> parents = new HashMap<>();
protected final int maxPaths;
/**
* Creates the result of a single-path search.
*
* @param src path source
* @param dst optional path destination
*/
public DefaultResult(V src, V dst) {
this(src, dst, 1);
}
/**
* Creates the result of path search.
*
* @param src path source
* @param dst optional path destination
* @param maxPaths optional limit of number of paths;
* {@link GraphPathSearch#ALL_PATHS} if no limit
*/
public DefaultResult(V src, V dst, int maxPaths) {
checkNotNull(src, "Source cannot be null");
this.src = src;
this.dst = dst;
this.maxPaths = maxPaths;
}
@Override
public V src() {
return src;
}
@Override
public V dst() {
return dst;
}
@Override
public Set<Path<V, E>> paths() {
return paths;
}
@Override
public Map<V, Weight> costs() {
return costs;
}
@Override
public Map<V, Set<E>> parents() {
return parents;
}
/**
* Indicates whether or not the given vertex has a cost yet.
*
* @param v vertex to test
* @return true if the vertex has cost already
*/
boolean hasCost(V v) {
return costs.containsKey(v);
}
/**
* Returns the current cost to reach the specified vertex.
* If the vertex has not been accessed yet, it has no cost
* associated and null will be returned.
*
* @param v vertex to reach
* @return weight cost to reach the vertex if already accessed;
* null otherwise
*/
Weight cost(V v) {
return costs.get(v);
}
/**
* Updates the cost of the vertex using its existing cost plus the
* cost to traverse the specified edge. If the search is in single
* path mode, only one path will be accrued.
*
* @param vertex vertex to update
* @param edge edge through which vertex is reached
* @param cost current cost to reach the vertex from the source
* @param replace true to indicate that any accrued edges are to be
* cleared; false to indicate that the edge should be
* added to the previously accrued edges as they yield
* the same cost
*/
void updateVertex(V vertex, E edge, Weight cost, boolean replace) {
costs.put(vertex, cost);
if (edge != null) {
Set<E> edges = parents.get(vertex);
if (edges == null) {
edges = new HashSet<>();
parents.put(vertex, edges);
}
if (replace) {
edges.clear();
}
if (maxPaths == ALL_PATHS || edges.size() < maxPaths) {
edges.add(edge);
}
}
}
/**
* Removes the set of parent edges for the specified vertex.
*
* @param v vertex
*/
void removeVertex(V v) {
parents.remove(v);
}
/**
* If possible, relax the specified edge using the supplied base cost
* and edge-weight function.
*
* @param edge edge to be relaxed
* @param cost base cost to reach the edge destination vertex
* @param ew optional edge weight function
* @param forbidNegatives if true negative values will forbid the link
* @return true if the edge was relaxed; false otherwise
*/
boolean relaxEdge(E edge, Weight cost, EdgeWeigher<V, E> ew,
boolean... forbidNegatives) {
V v = edge.dst();
Weight hopCost = ew.weight(edge);
if ((!hopCost.isViable()) ||
(hopCost.isNegative() && forbidNegatives.length == 1 && forbidNegatives[0])) {
return false;
}
Weight newCost = cost.merge(hopCost);
int compareResult = -1;
if (hasCost(v)) {
Weight oldCost = cost(v);
compareResult = newCost.compareTo(oldCost);
}
if (compareResult <= 0) {
updateVertex(v, edge, newCost, compareResult < 0);
}
return compareResult < 0;
}
/**
* Builds a set of paths for the specified src/dst vertex pair.
*/
protected void buildPaths() {
Set<V> destinations = new HashSet<>();
if (dst == null) {
destinations.addAll(costs.keySet());
} else {
destinations.add(dst);
}
// Build all paths between the source and all requested destinations.
for (V v : destinations) {
// Ignore the source, if it is among the destinations.
if (!v.equals(src)) {
buildAllPaths(this, src, v, maxPaths);
}
}
}
}
/**
* Builds a set of all paths between the source and destination using the
* graph search result by applying breadth-first search through the parent
* edges and vertex costs.
*
* @param result graph search result
* @param src source vertex
* @param dst destination vertex
* @param maxPaths limit on the number of paths built;
* {@link GraphPathSearch#ALL_PATHS} if no limit
*/
private void buildAllPaths(DefaultResult result, V src, V dst, int maxPaths) {
DefaultMutablePath<V, E> basePath = new DefaultMutablePath<>();
basePath.setCost(result.cost(dst));
Set<DefaultMutablePath<V, E>> pendingPaths = new HashSet<>();
pendingPaths.add(basePath);
while (!pendingPaths.isEmpty() &&
(maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
Set<DefaultMutablePath<V, E>> frontier = new HashSet<>();
for (DefaultMutablePath<V, E> path : pendingPaths) {
// For each pending path, locate its first vertex since we
// will be moving backwards from it.
V firstVertex = firstVertex(path, dst);
// If the first vertex is our expected source, we have reached
// the beginning, so add the this path to the result paths.
if (firstVertex.equals(src)) {
path.setCost(result.cost(dst));
result.paths.add(new DefaultPath<>(path.edges(), path.cost()));
} else {
// If we have not reached the beginning, i.e. the source,
// fetch the set of edges leading to the first vertex of
// this pending path; if there are none, abandon processing
// this path for good.
Set<E> firstVertexParents = result.parents.get(firstVertex);
if (firstVertexParents == null || firstVertexParents.isEmpty()) {
break;
}
// Now iterate over all the edges and for each of them
// cloning the current path and then insert that edge to
// the path and then add that path to the pending ones.
// When processing the last edge, modify the current
// pending path rather than cloning a new one.
Iterator<E> edges = firstVertexParents.iterator();
while (edges.hasNext()) {
E edge = edges.next();
boolean isLast = !edges.hasNext();
// Exclude any looping paths
if (!isInPath(edge, path)) {
DefaultMutablePath<V, E> pendingPath = isLast ? path : new DefaultMutablePath<>(path);
pendingPath.insertEdge(edge);
frontier.add(pendingPath);
}
}
}
}
// All pending paths have been scanned so promote the next frontier
pendingPaths = frontier;
}
}
/**
* Indicates whether or not the specified edge source is already visited
* in the specified path.
*
* @param edge edge to test
* @param path path to test
* @return true if the edge.src() is a vertex in the path already
*/
private boolean isInPath(E edge, DefaultMutablePath<V, E> path) {
return path.edges().stream().anyMatch(e -> edge.src().equals(e.dst()));
}
// Returns the first vertex of the specified path. This is either the source
// of the first edge or, if there are no edges yet, the given destination.
private V firstVertex(Path<V, E> path, V dst) {
return path.edges().isEmpty() ? dst : path.edges().get(0).src();
}
/**
* Checks the specified path search arguments for validity.
*
* @param graph graph; must not be null
* @param src source vertex; must not be null and belong to graph
* @param dst optional target vertex; must belong to graph
*/
protected void checkArguments(Graph<V, E> graph, V src, V dst) {
checkNotNull(graph, "Graph cannot be null");
checkNotNull(src, "Source cannot be null");
Set<V> vertices = graph.getVertexes();
checkArgument(vertices.contains(src), "Source not in the graph");
checkArgument(dst == null || vertices.contains(dst),
"Destination not in graph");
}
@Override
public Result<V, E> search(Graph<V, E> graph, V src, V dst,
EdgeWeigher<V, E> weigher, int maxPaths) {
checkArguments(graph, src, dst);
return internalSearch(graph, src, dst,
weigher != null ? weigher : new DefaultEdgeWeigher<>(),
maxPaths);
}
protected abstract Result<V, E> internalSearch(Graph<V, E> graph, V src, V dst,
EdgeWeigher<V, E> weigher, int maxPaths);
}