All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.onlab.graph.AbstractGraphPathSearch Maven / Gradle / Ivy

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright 2014-present Open Networking Foundation
 *
 * 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  vertex type
 * @param  edge type
 */
public abstract class AbstractGraphPathSearch>
        implements GraphPathSearch {

    /**
     * Default path search result that uses the DefaultPath to convey paths
     * in a graph.
     */
    protected class DefaultResult implements Result {

        private final V src;
        private final V dst;
        protected final Set> paths = new HashSet<>();
        protected final Map costs = new HashMap<>();
        protected final Map> 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> paths() {
            return paths;
        }

        @Override
        public Map costs() {
            return costs;
        }

        @Override
        public Map> 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 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 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 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 basePath = new DefaultMutablePath<>();
        basePath.setCost(result.cost(dst));

        Set> pendingPaths = new HashSet<>();
        pendingPaths.add(basePath);

        while (!pendingPaths.isEmpty() &&
                (maxPaths == ALL_PATHS || result.paths.size() < maxPaths)) {
            Set> frontier = new HashSet<>();

            for (DefaultMutablePath 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 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 edges = firstVertexParents.iterator();
                    while (edges.hasNext()) {
                        E edge = edges.next();
                        boolean isLast = !edges.hasNext();
                        // Exclude any looping paths
                        if (!isInPath(edge, path)) {
                            DefaultMutablePath 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 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 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 graph, V src, V dst) {
        checkNotNull(graph, "Graph cannot be null");
        checkNotNull(src, "Source cannot be null");
        Set 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 search(Graph graph, V src, V dst,
                               EdgeWeigher weigher, int maxPaths) {
        checkArguments(graph, src, dst);

        return internalSearch(graph, src, dst,
                weigher != null ? weigher : new DefaultEdgeWeigher<>(),
                maxPaths);
    }

    protected abstract Result internalSearch(Graph graph, V src, V dst,
                                          EdgeWeigher weigher, int maxPaths);
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy