net.oneandone.maven.plugins.cycles.graph.FeedbackArcSet.java Source code

Java tutorial

Introduction

Here is the source code for net.oneandone.maven.plugins.cycles.graph.FeedbackArcSet.java

Source

/**
 * Copyright 1&1 Internet AG, https://github.com/1and1/
 *
 * 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 net.oneandone.maven.plugins.cycles.graph;

import java.util.Collection;
import java.util.List;

import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;

import edu.uci.ics.jung.algorithms.filters.FilterUtils;
import edu.uci.ics.jung.graph.DirectedGraph;

/**
 * Greedily computes an approximation of a "feedback arc set" (FAS), i.e., a
 * minimal set of edges that breaks a strongly connected component if removed.
 * 
 * Algorithm taken from: Peter Eades, Xuemin Lin, and W. F. Smyth.
 * "A Fast and Effective Heuristic for the Feedback Arc Set Problem.". Inf.
 * Process. Lett., Vol. 47, Nr. 6 (1993), p. 319-323.
 * 
 * @author chschmitz
 */
public final class FeedbackArcSet {

    /**
     * @param graph a directed graph
     * @param <V> vertex type
     * @param <E> edge type
     * @return approximation of a feedback arc set, i.e., a set of edges that, if removed,
     * breaks a strong component 
     */
    public static <V, E> Collection<E> feedbackArcs(DirectedGraph<V, E> graph) {
        return feedbackArcs(graph, new DefaultVertexEvaluator<V, DirectedGraph<V, E>>());
    }

    /**
     * @param graph a directed graph
     * @param evaluator a vertex evaluator
     * @param <V> vertex type
     * @param <E> edge type
     * @return approximation of a feedback arc set, i.e., a set of edges that, if removed,
     * breaks a strong component; the algorithm will try to pick minimum weight edges 
     */
    public static <V, E> Collection<E> feedbackArcs(DirectedGraph<V, E> graph,
            VertexEvaluator<V, DirectedGraph<V, E>> evaluator) {
        return ImmutableList.copyOf(
                Collections2.filter(graph.getEdges(), isBackwardEdge(vertexOrdering(graph, evaluator), graph)));
    }

    private static <V, E> Predicate<E> isBackwardEdge(final Ordering<V> ordering, final DirectedGraph<V, E> graph) {
        return new Predicate<E>() {
            public boolean apply(E edge) {
                V from = graph.getSource(edge);
                V to = graph.getDest(edge);
                return ordering.compare(from, to) > 0;
            }
        };
    }

    private static <V, E> Ordering<V> vertexOrdering(DirectedGraph<V, E> graph,
            VertexEvaluator<V, DirectedGraph<V, E>> evaluator) {
        List<V> front = Lists.newLinkedList();
        List<V> back = Lists.newLinkedList();

        // FIXME: there must be an easier way to clone a graph
        DirectedGraph<V, E> g = FilterUtils.createInducedSubgraph(graph.getVertices(), graph);

        while (!g.getVertices().isEmpty()) {
            collectSinks(g, back);
            collectSources(g, front);
            collectMaxDelta(g, front, evaluator);
        }

        return Ordering.explicit(Lists.newArrayList(Iterables.concat(front, back)));
    }

    private static <V, E> void collectMaxDelta(DirectedGraph<V, E> g, List<V> front,
            VertexEvaluator<V, DirectedGraph<V, E>> evaluator) {
        V maxVertex = findMaxWeightVertex(g, evaluator);
        if (maxVertex != null) {
            front.add(maxVertex);
            g.removeVertex(maxVertex);
        }
    }

    private static <V, E> V findMaxWeightVertex(DirectedGraph<V, E> g,
            VertexEvaluator<V, DirectedGraph<V, E>> evaluator) {
        V maxWeightVertex = null;
        double maxWeight = 0;
        for (V v : g.getVertices()) {
            double degreeDelta = evaluator.weight(v, g);
            if (maxWeightVertex == null || degreeDelta > maxWeight) {
                maxWeightVertex = v;
                maxWeight = degreeDelta;
            }
        }
        return maxWeightVertex;
    }

    private static <V, E> void collectSources(DirectedGraph<V, E> g, List<V> front) {
        V source;
        while ((source = findSource(g)) != null) {
            g.removeVertex(source);
            front.add(source);
        }
    }

    private static <V, E> void collectSinks(DirectedGraph<V, E> g, List<V> back) {
        V sink;
        while ((sink = findSink(g)) != null) {
            g.removeVertex(sink);
            back.add(0, sink);
        }
    }

    private static <V, E> V findSink(DirectedGraph<V, E> g) {
        for (V v : g.getVertices()) {
            if (g.outDegree(v) == 0) {
                return v;
            }
        }
        return null;
    }

    private static <V, E> V findSource(DirectedGraph<V, E> g) {
        for (V v : g.getVertices()) {
            if (g.inDegree(v) == 0) {
                return v;
            }
        }
        return null;
    }

    private FeedbackArcSet() {
    }
}