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

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

There is a newer version: 2.7.0
Show newest version
/*
 * Copyright 2017-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 static com.google.common.base.Preconditions.checkNotNull;
import static java.util.Spliterators.spliteratorUnknownSize;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.Supplier;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import com.google.common.base.Suppliers;
import com.google.common.collect.ComparisonChain;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Sets;

/**
 * Lazily runs K shortest paths algorithm on a provided directed graph.
 */
public class LazyKShortestPathsSearch> {

    private final Comparator> pathComparator = new PathComparator();

    private final GraphPathSearch shortest = new DijkstraGraphSearch<>();

    /**
     * Searches the specified graph for paths between vertices.
     *
     * @param graph    graph to be searched
     * @param src      source vertex
     * @param dst      destination vertex
     * @param weigher  edge-weigher
     * @return Stream of shortest paths
     */
    public Stream> lazyPathSearch(Graph graph,
                                              V src, V dst,
                                              EdgeWeigher weigher) {

        Iterator> it = new ShortestPathIterator(graph, src, dst, weigher);

        return StreamSupport.stream(spliteratorUnknownSize(it,
                                                           Spliterator.ORDERED |
                                                           Spliterator.DISTINCT |
                                                           Spliterator.NONNULL |
                                                           Spliterator.IMMUTABLE),
                                    false);
    }

    /**
     * Iterator returning shortest paths, searched incrementally on each next() call.
     */
    private final class ShortestPathIterator implements Iterator> {

        final Graph graph;
        final V src;
        final V dst;
        final EdgeWeigher weigher;

        final InnerEdgeWeigher maskingWeigher;

        final List> resultPaths = new ArrayList<>(); // A
        final Queue> potentialPaths = new PriorityQueue<>(pathComparator); // B

        Supplier> next;

        ShortestPathIterator(Graph graph,
                             V src, V dst,
                             EdgeWeigher weigher) {
            this.graph = checkNotNull(graph);
            this.src = checkNotNull(src);
            this.dst = checkNotNull(dst);
            this.weigher = checkNotNull(weigher);

            maskingWeigher = new InnerEdgeWeigher(weigher);
            next = Suppliers.ofInstance(
                        shortest.search(graph, src, dst, weigher, 1)
                            .paths().stream().findFirst().orElse(null));
        }

        @Override
        public boolean hasNext() {
            return next.get() != null;
        }

        @Override
        public Path next() {
            if (next.get() == null) {
                throw new NoSuchElementException("No more path between " + src + "-" + dst);
            }

            // lastPath: the path to return at the end of this call
            Path lastPath = next.get();
            resultPaths.add(lastPath);

            next = Suppliers.memoize(() -> computeNext(lastPath));

            return lastPath;
        }

        private Path computeNext(Path lastPath) {
            /// following is basically Yen's k-shortest path algorithm

            // start searching for next path
            for (int i = 0; i < lastPath.edges().size(); i++) {
                V spurNode = lastPath.edges().get(i).src();
                List rootPathEdgeList = lastPath.edges().subList(0, i);

                for (Path path : resultPaths) {
                    if (path.edges().size() >= i &&
                        rootPathEdgeList.equals(path.edges().subList(0, i))) {
                        maskingWeigher.excluded.add(path.edges().get(i));
                    }
                }

                // Effectively remove all root path nodes other than spurNode
                rootPathEdgeList.forEach(edge -> {
                    maskingWeigher.excluded.addAll(graph.getEdgesFrom(edge.src()));
                    maskingWeigher.excluded.addAll(graph.getEdgesTo(edge.src()));
                });

                shortest.search(graph, spurNode, dst, maskingWeigher, 1)
                        .paths().stream().findAny().ifPresent(spurPath -> {

                            List totalPath = ImmutableList.builder()
                                    .addAll(rootPathEdgeList)
                                    .addAll(spurPath.edges())
                                    .build();
                            potentialPaths.add(path(totalPath));
                });

                // Restore all removed paths and nodes
                maskingWeigher.excluded.clear();
            }

            if (potentialPaths.isEmpty()) {
                return null;
            } else {
                return potentialPaths.poll();
            }
        }

        private Path path(List edges) {
            //The following line must use the original weigher not the modified weigher because the modified
            //weigher will count -1 values used for modifying the graph and return an inaccurate cost.
            return new DefaultPath<>(edges, calculatePathCost(weigher, edges));
        }

        private Weight calculatePathCost(EdgeWeigher weighter, List edges) {
            Weight totalCost = weighter.getInitialWeight();
            for (E edge : edges) {
                totalCost = totalCost.merge(weighter.weight(edge));
            }
            return totalCost;
        }
    }

    /**
     * EdgeWeigher which excludes specified edges from path computation.
     */
    private final class InnerEdgeWeigher implements EdgeWeigher {

        private final Set excluded = Sets.newConcurrentHashSet();
        private final EdgeWeigher weigher;

        private InnerEdgeWeigher(EdgeWeigher weigher) {
            this.weigher = weigher;
        }

        @Override
        public Weight weight(E edge) {
            if (excluded.contains(edge)) {
                return weigher.getNonViableWeight();
            }
            return weigher.weight(edge);
        }

        @Override
        public Weight getInitialWeight() {
            return weigher.getInitialWeight();
        }

        @Override
        public Weight getNonViableWeight() {
            return weigher.getNonViableWeight();
        }
    }

    /**
     * Provides a comparator to order the set of paths.
     * Compare by cost, then by hop count.
     */
    private final class PathComparator implements Comparator> {

        @Override
        public int compare(Path pathOne, Path pathTwo) {
            return ComparisonChain.start()
                    .compare(pathOne.cost(), pathTwo.cost())
                    .compare(pathOne.edges().size(), pathTwo.edges().size())
                    .result();
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy