org.eclipse.elk.alg.layered.networksimplex.NetworkSimplex.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.networksimplex.NetworkSimplex.java

Source

/*******************************************************************************
 * Copyright (c) 2016 Kiel University and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     Kiel University - initial API and implementation
 *******************************************************************************/
package org.eclipse.elk.alg.layered.networksimplex;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.Stack;

import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.Layer;
import org.eclipse.elk.core.util.IElkProgressMonitor;
import org.eclipse.elk.core.util.Pair;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

/**
 * The main class of the network simplex layerer component. It offers an algorithm to determine an
 * optimal layering of all nodes in the graph concerning a minimal length of all edges using the
 * network simplex algorithm described in
 * <ul>
 * <li>Emden R. Gansner, Eleftherios Koutsofios, Stephen C. North, Kiem-Phong Vo, A technique for
 * drawing directed graphs. <i>Software Engineering</i> 19(3), pp. 214-230, 1993.</li>
 * </ul>
 * 
 * <dl>
 * <dt>Precondition:</dt>
 * <dd>the graph has no cycles</dd>
 * <dt>Postcondition:</dt>
 * <dd>all nodes have been assigned a layer such that edges connect only nodes from layers with
 * increasing indices</dd>
 * </dl>
 * 
 * @kieler.design 2012-08-10 chsch grh
 * @kieler.rating proposed yellow by msp
 */
public final class NetworkSimplex {

    // configuration of this network simplex instance

    /** A previous layering to be considered during {@link #normalize()} and {@link #balance(int[])}. */
    private LGraph previousLayering;
    /** Whether to apply {@link #balance(int[])}. */
    private boolean balance = false;
    /** A limit on the number of iterations. */
    private int iterationLimit = Integer.MAX_VALUE;
    /** Empirically determined threshold when removing subtrees pays off. */
    private static final int REMOVE_SUBTREES_THRESH = 40;

    /** Use {@link #forGraph(NGraph)}. */
    private NetworkSimplex() {
    }

    /**
     * @param graph
     *            the graph for which to execute the network simplex
     * @return a new instance of a {@link NetworkSimplex} algorithm.
     */
    public static NetworkSimplex forGraph(final NGraph graph) {
        NetworkSimplex ns = new NetworkSimplex();
        ns.graph = graph;
        return ns;
    }

    /**
     * It balances the layering concerning its width, i.e. the number of nodes in each layer. If the
     * graph allows multiple optimal layerings regarding a minimal edge length, this method moves
     * separate nodes to a layer with a minimal amount of currently contained nodes with respect to
     * the retention of feasibility and optimality of the given layering.
     * 
     * @param doBalance
     *            whether to apply a balancing
     * @return the {@link NetworkSimplex} instance for further configuration or execution.
     */
    public NetworkSimplex withBalancing(final boolean doBalance) {
        this.balance = doBalance;
        return this;
    }

    /**
     * Previously layered nodes may become relevant when moving nodes to layers with fewer nodes
     * during balancing.
     * 
     * @param considerPreviousLayering
     *            whether previously layered nodes should be considered during.
     * @return the {@link NetworkSimplex} instance for further configuration or execution.
     * @see #withBalancing(boolean)
     */
    public NetworkSimplex withPreviousLayering(final LGraph considerPreviousLayering) {
        this.previousLayering = considerPreviousLayering;
        return this;
    }

    /**
     * Since there is a theoretical possibility that the network simplex does not terminate Gansner
     * et al. propose to incorporate an iteration limit. However, in practice this shouldn't happen.
     * 
     * @param limit
     *            the maximum number of iterations of the network simplex algorithm.
     * @return the {@link NetworkSimplex} instance for further configuration or execution.
     */
    public NetworkSimplex withIterationLimit(final int limit) {
        this.iterationLimit = limit;
        return this;
    }

    // ================================== Attributes ==============================================

    /** The graph all methods in this class operate on. */
    private NGraph graph;

    /** An {@code ArrayList} containing all edges in the graph. */
    private List<NEdge> edges;

    /** A {@code HashSet} containing all edges that are part of the spanning tree. */
    private Set<NEdge> treeEdges;

    /**
     * A {@code LinkedList} containing all source nodes of the graph, i.e. all nodes that have no
     * incident incoming edges.
     */
    private List<NNode> sources;

    /**
     * A flag indicating whether a specified edge has been visited during DFS-traversal. This array
     * has to be filled with {@code false} each time, before a DFS-based method is invoked.
     */
    private boolean[] edgeVisited;

    /**
     * The current postorder traversal number used by {@code postorderTraversal()} to assign an
     * unique traversal ID to each node.
     * 
     * @see #postorderTraversal(NNode)
     */
    private int postOrder;

    /**
     * The postorder traversal ID of each node determined by {@code postorderTraversal()}.
     * 
     * @see #postorderTraversal(NNode)
     */
    private int[] poID;

    /**
     * The lowest postorder traversal ID of each nodes reachable through a node lower in the
     * traversal tree determined by {@code postorderTraversal}.
     * 
     * @see #postorderTraversal(NNode)
     */
    private int[] lowestPoID;

    /**
     * The cut value of every edge defined as follows: If the edge is deleted, the spanning tree
     * breaks into two connected components, the head component containing the target node of the
     * edge and the tail component containing the source node of the edge. The cut value is the sum
     * of the weight (here {@code 1}) of all edges going from the tail to the head component,
     * including the tree edge, minus the sum of the weights of all edges from the head to the tail
     * component.
     * 
     * @see #cutvalues()
     */
    private double[] cutvalue;

    /**
     * Nodes that are part of subtrees of the graph. They will be removed prior to the actual
     * execution of the network simplex since positioning them with minimal edge length is trivial.
     * 
     * @see #removeSubtrees()
     * @see #reattachSubtrees()
     */
    private Stack<Pair<NNode, NEdge>> subtreeNodes;

    // =============================== Initialization Methods =====================================

    /**
     * Helper method for the network simplex layerer. It instantiates all necessary attributes for
     * the execution of the network simplex layerer and initializes them with their default values.
     * All edges in the connected component given by the input argument will be determined, as well
     * as the number of incoming and outgoing edges of each node ( {@code inDegree}, respectively
     * {@code outDegree}). All sinks and source nodes in the connected component identified in this
     * step will be added to {@code sinks}, respectively {@code sources}.
     * 
     */
    private void initialize() {
        // initialize node attributes
        int numNodes = graph.nodes.size();
        for (NNode n : graph.nodes) {
            n.treeNode = false;
        }
        poID = new int[numNodes];
        lowestPoID = new int[numNodes];
        sources = Lists.newArrayList();

        // determine edges and re-index nodes
        int index = 0;
        List<NEdge> theEdges = Lists.newArrayList();
        for (NNode node : graph.nodes) {
            node.id = index++;
            // add node to sinks, resp. sources
            if (node.getIncomingEdges().size() == 0) {
                sources.add(node);
            }
            theEdges.addAll(node.getOutgoingEdges());
        }
        // re-index edges
        int counter = 0;
        for (NEdge edge : theEdges) {
            edge.id = counter++;
            edge.treeEdge = false;
        }
        // initialize edge attributes
        int numEdges = theEdges.size();
        if (cutvalue == null || cutvalue.length < numEdges) {
            cutvalue = new double[numEdges];
            edgeVisited = new boolean[numEdges];
        } else {
            Arrays.fill(edgeVisited, false);
        }
        edges = theEdges;
        // we iterate over this set, thus we have to use a linked hash set 
        // to get a deterministic iteration order
        treeEdges = Sets.newLinkedHashSetWithExpectedSize(edges.size());
        postOrder = 1;
    }

    /**
     * Release all created resources so the GC can reap them.
     */
    private void dispose() {
        this.cutvalue = null;
        this.edges = null;
        this.treeEdges = null;
        this.edgeVisited = null;
        this.lowestPoID = null;
        this.poID = null;
        this.sources = null;
        this.subtreeNodes = null;
    }

    // ============================== Network-Simplex Algorithm ===================================

    /**
     * The main method of the network simplex layerer. It determines an optimal layering of all
     * nodes in the graph concerning a minimal length of all edges by using the network simplex
     * algorithm described in {@literal Emden R. Gansner, Eleftherios Koutsofios, Stephen
     * C. North, Kiem-Phong Vo: "A Technique for Drawing Directed Graphs", AT&T Bell Laboratories.
     * Note that the execution time of this implemented algorithm has not been proven quadratic yet.
     * 
     * @param monitor
     *            the progress monitor
     *            
     * @see de.cau.cs.kieler.klay.layered.p2layers.ILayerer ILayerer
     */
    public void execute(final IElkProgressMonitor monitor) {
        monitor.begin("Network simplex", 1);

        if (graph.nodes.size() < 1) {
            monitor.done();
            return;
        }

        // reset any old layering
        for (NNode node : graph.nodes) {
            node.layer = 0;
        }

        // remove leafs
        boolean removeSubtrees = graph.nodes.size() >= REMOVE_SUBTREES_THRESH;
        if (removeSubtrees) {
            removeSubtrees();
        }

        // init all the data structures we use
        initialize();
        // determine an initial feasible layering
        feasibleTree();
        // improve the initial layering until it is optimal
        NEdge e = leaveEdge();
        int iter = 0;
        while (e != null && iter < iterationLimit) {
            // current layering is not optimal
            exchange(e, enterEdge(e));
            e = leaveEdge();
            iter++;
        }

        // re-attach leafs
        if (removeSubtrees) {
            reattachSubtrees();
        }

        // normalize and, if desired, balance
        //   both methods must work on the NNode#layer field
        if (balance) {
            balance(normalize());
        } else {
            normalize();
        }

        // release the created resources
        dispose();
        monitor.done();
    }

    /**
     * Recursively removes subtrees. In other words, removes leafs from the graph until no more
     * leafs are present.
     */
    private void removeSubtrees() {

        subtreeNodes = new Stack<>();

        // find initial leafs
        Queue<NNode> leafs = Lists.newLinkedList();
        for (NNode node : graph.nodes) {
            if (node.getConnectedEdges().size() == 1) {
                leafs.add(node);
            }
        }

        // remove them from the graph like there's no tomorrow
        while (!leafs.isEmpty()) {
            NNode node = leafs.poll();
            // was the edge already removed?
            if (node.getConnectedEdges().size() == 0) {
                continue;
            }
            NEdge edge = node.getConnectedEdges().get(0);
            boolean isOutEdge = node.getOutgoingEdges().size() > 0;

            NNode other = edge.getOther(node);
            if (isOutEdge) {
                other.getIncomingEdges().remove(edge);
            } else {
                other.getOutgoingEdges().remove(edge);
            }

            if (other.getConnectedEdges().size() == 1) {
                leafs.add(other);
            }

            Pair<NNode, NEdge> leafy = Pair.of(node, edge);
            subtreeNodes.push(leafy);
            // remove the node from the graph's nodes
            graph.nodes.remove(node);
        }

    }

    /**
     * Re-attaches the previously removed tree nodes. It is important that 
     * the nodes are re-attached in the opposite order than they were removed.  
     */
    private void reattachSubtrees() {

        while (!subtreeNodes.isEmpty()) {

            Pair<NNode, NEdge> leafy = subtreeNodes.pop();
            NNode node = leafy.getFirst();
            NEdge edge = leafy.getSecond();

            NNode placed = edge.getOther(node);

            if (edge.target == node) {
                placed.getOutgoingEdges().add(edge);
                node.layer = placed.layer + edge.delta;
            } else {
                placed.getIncomingEdges().add(edge);
                node.layer = placed.layer - edge.delta;
            }

            graph.nodes.add(node);
        }

    }

    /**
     * Helper method for the network simplex layerer. It determines an initial feasible spanning
     * tree of the graph. This graph will be tight by construction. For determination, an initial
     * feasible tree is being computed. If all tree edges contained are tight (i.e. their minimal
     * length corresponds with their actual length), a tight tree has already been found. If not,
     * this method iteratively determines a non-tree edge incident to the tree with a minimal amount
     * of slack (i.e. the edge with the lowest difference between its current and minimal length)
     * and shifts all tree edges accordingly to shorten the edge to its minimal size. The edge has
     * become tight and will be added to the spanning tree together with all tight edges leading to
     * non-tree nodes as well. If all nodes of the graph are contained in the spanning tree, a tight
     * tree has been found. A concluding computation of each edge's initial cut value takes place.
     * 
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#tightTreeDFS(NNode)
     *      tightTreeDFS()
     */
    private void feasibleTree() {

        // determine initial layering
        layeringTopologicalNumbering(sources);

        if (edges.size() > 0) {
            Arrays.fill(edgeVisited, false);
            while (tightTreeDFS(graph.nodes.iterator().next()) < graph.nodes.size()) {
                // some nodes are still not part of the tree
                NEdge e = minimalSlack();
                int slack = e.getTarget().layer - e.getSource().layer - e.delta;
                if (e.getTarget().treeNode) {
                    slack = -slack;
                }

                // update tree
                for (NNode node : graph.nodes) {
                    if (node.treeNode) {
                        node.layer += slack;
                    }
                }
                Arrays.fill(edgeVisited, false);
            }
            // update tree-related attributes
            Arrays.fill(edgeVisited, false);
            postorderTraversal(graph.nodes.iterator().next());
            cutvalues();
        }
    }

    /**
     * Helper method for the network simplex layerer. It determines an (initial) feasible layering
     * for the graph by traversing it by a minimal topological numbering. Dependently of
     * the chosen mode indicated by {@code reverse}, this method traverses incoming edges (if
     * {@code reverse = true}), or outgoing edges, if {@code reverse = false}, only. Therefore, this
     * method should only be called with source nodes as argument in the first-mentioned case and
     * only with sink nodes in the latter case.
     * 
     * @param initialRootNodes
     *            the roots of the topological numbering (sources or sinks, depending on the direction)
     * @see #layer
     */
    private void layeringTopologicalNumbering(final List<NNode> initialRootNodes) {

        // initialize the number of incident edges for each node
        int[] incident = new int[graph.nodes.size()];
        for (NNode node : graph.nodes) {
            incident[node.id] += node.getIncomingEdges().size();
        }

        LinkedList<NNode> roots = Lists.newLinkedList(initialRootNodes);
        while (!roots.isEmpty()) {
            NNode node = roots.poll();

            for (NEdge edge : node.getOutgoingEdges()) {
                NNode target = edge.getTarget();
                target.layer = Math.max(target.layer, node.layer + edge.delta);
                incident[target.id]--;
                if (incident[target.id] == 0) {
                    roots.add(target);
                }
            }
        }
    }

    /**
     * Helper method for the network simplex layerer. It determines the length of the currently
     * shortest incoming or outgoing edge of the input node.
     * 
     * @param node
     *            the node to determine the length of its shortest incoming or outgoing edge
     * @return a pair containing the length of the shortest incoming (first element) and outgoing
     *         edge (second element) incident to the input node or {@code -1} as the length, if no
     *         such edge is incident
     * 
     * @see de.cau.cs.kieler.core.util.Pair Pair
     */
    private Pair<Integer, Integer> minimalSpan(final NNode node) {
        int minSpanOut = Integer.MAX_VALUE;
        int minSpanIn = Integer.MAX_VALUE;
        int currentSpan;

        for (NEdge edge : node.getConnectedEdges()) {
            currentSpan = edge.getTarget().layer - edge.getSource().layer;
            if (edge.getTarget() == node && currentSpan < minSpanIn) {
                minSpanIn = currentSpan;
            } else if (currentSpan < minSpanOut) {
                minSpanOut = currentSpan;
            }
        }

        if (minSpanIn == Integer.MAX_VALUE) {
            minSpanIn = -1;
        }
        if (minSpanOut == Integer.MAX_VALUE) {
            minSpanOut = -1;
        }

        return new Pair<Integer, Integer>(minSpanIn, minSpanOut);
    }

    /**
     * Helper method for the network simplex layerer. It determines a DFS-subtree of the graph by
     * traversing tight edges only (i.e. edges whose current length matches their minimal length in
     * the layering) and returns the number of nodes in this. If this number is equal to the total
     * number of nodes in the graph, a tight spanning tree has been determined.
     * 
     * @param node
     *            the root of the DFS-subtree
     * @return the number of nodes in the determined tight DFS-tree
     * 
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#treeEdge treeEdge
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#treeNode treeNode
     */
    private int tightTreeDFS(final NNode node) {
        int nodeCount = 1;
        node.treeNode = true;
        NNode opposite = null;
        for (NEdge edge : node.getConnectedEdges()) {
            if (!edgeVisited[edge.id]) {
                edgeVisited[edge.id] = true;
                opposite = edge.getOther(node);
                if (edge.treeEdge) {
                    // edge is a tree edge already: follow this path
                    nodeCount += tightTreeDFS(opposite);
                } else if (!opposite.treeNode && edge.delta == edge.getTarget().layer - edge.getSource().layer) {
                    // edge is a tight non-tree edge
                    edge.treeEdge = true;
                    treeEdges.add(edge);
                    nodeCount += tightTreeDFS(opposite);
                }
            }
        }
        return nodeCount;
    }

    /**
     * Helper method for the network simplex layerer. It returns the non-tree edge incident on the
     * tree and incident to a non-tree node with a minimal amount of slack (i.e. an edge with the
     * lowest difference between its current and minimal length) or {@code null}, if no such edge
     * exists. Note, that the returned edge's slack is never {@code 0}, since otherwise, the edge
     * would be a tree-edge.
     * 
     * @return a non-tree edge incident on the tree with a minimal amount of slack or {@code null},
     *         if no such edge exists
     */
    private NEdge minimalSlack() {
        int minSlack = Integer.MAX_VALUE;
        NEdge minSlackEdge = null;
        int curSlack;
        for (NEdge edge : edges) {
            if (edge.getSource().treeNode ^ edge.getTarget().treeNode) {
                // edge is non-tree edge and incident on the tree
                curSlack = edge.getTarget().layer - edge.getSource().layer - edge.delta;
                if (curSlack < minSlack) {
                    minSlack = curSlack;
                    minSlackEdge = edge;
                }
            }
        }
        return minSlackEdge;
    }

    /**
     * Helper method for the network simplex layerer. It performs a postorder DFS-traversal of the
     * graph beginning with the input node. Each node will be assigned a unique traversal ID, which
     * will be stored in {@code poID}. Furthermore, the lowest postorder traversal ID of any node in
     * a descending path relative to the input node will be computed and stored in
     * {@code lowestPoID}, which is also the return value of this method.
     * 
     * @param node
     *            the root of the DFS-subtree
     * @return the lowest post-order ID of any descending edge in the depth-first-search
     * 
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#poID poID
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#lowestPoID lowestPoID
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#postOrder postOrder
     */
    private int postorderTraversal(final NNode node) {
        int lowest = Integer.MAX_VALUE;
        for (NEdge edge : node.getConnectedEdges()) {
            if (edge.treeEdge && !edgeVisited[edge.id]) {
                edgeVisited[edge.id] = true;
                lowest = Math.min(lowest, postorderTraversal(edge.getOther(node)));
            }
        }
        poID[node.id] = postOrder;
        lowestPoID[node.id] = Math.min(lowest, postOrder++);
        return lowestPoID[node.id];
    }

    /**
     * Helper method for the the network simplex layerer. It determines, whether an node is part of
     * the head component of the given edge defined as follows: If the input edge is deleted, the
     * spanning tree breaks into to connected components. The head component is that component,
     * which contains the edge's target node, and the tail component is the component, which
     * contains the edge's source node. Note that a node either belongs to the head or tail
     * component. Therefore, if the node is not part of the head component, it must be part of the
     * tail component and vice versa.
     * 
     * @param node
     *            the node to determine, whether it belongs to the edges head (or tail) component
     * @param edge
     *            the edge to determine, whether the node is in the head (or tail) component
     * @return {@code true}, if node is in the head component or {@code false}, if the node is in
     *         the tail component of the edge
     */
    private boolean isInHead(final NNode node, final NEdge edge) {
        NNode source = edge.getSource();
        NNode target = edge.getTarget();

        if (lowestPoID[source.id] <= poID[node.id] && poID[node.id] <= poID[source.id]
                && lowestPoID[target.id] <= poID[node.id] && poID[node.id] <= poID[target.id]) {
            // node is in a descending path in the DFS-Tree
            if (poID[source.id] < poID[target.id]) {
                // root is in the head component
                return false;
            }
            return true;
        }
        if (poID[source.id] < poID[target.id]) {
            // root is in the head component
            return true;
        }
        return false;
    }

    /**
     * Helper method for the network simplex layerer. It determines the cut value of each tree edge,
     * which is defined as follows: If the edge is deleted, the spanning tree breaks into two
     * connected components, the head component containing the target node of the edge and the tail
     * component containing the source node of the edge. The cut value is the sum of the weights of
     * all edges going from the tail to the head component, including the tree edge itself, minus
     * the sum of the weights of all edges from the head to the tail component.
     * 
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#cutvalue cutvalue
     */
    private void cutvalues() {
        // determine incident tree edges for each node
        List<NNode> leafs = Lists.newArrayList();
        int treeEdgeCount;
        for (NNode node : graph.nodes) {
            treeEdgeCount = 0;
            node.unknownCutvalues.clear();
            for (NEdge edge : node.getConnectedEdges()) {
                if (edge.treeEdge) {
                    node.unknownCutvalues.add(edge);
                    treeEdgeCount++;
                }
            }
            if (treeEdgeCount == 1) {
                leafs.add(node);
            }
        }

        // determine cut values
        NEdge toDetermine;
        NNode source, target;
        for (NNode node : leafs) {
            while (node.unknownCutvalues.size() == 1) {
                // one tree edge with undetermined cut value is incident
                toDetermine = node.unknownCutvalues.iterator().next();
                cutvalue[toDetermine.id] = toDetermine.weight;
                source = toDetermine.getSource();
                target = toDetermine.getTarget();
                for (NEdge edge : node.getConnectedEdges()) {
                    if (!edge.equals(toDetermine)) {
                        if (edge.treeEdge) {
                            // edge is tree edge
                            if (source.equals(edge.getSource()) || target.equals(edge.getTarget())) {
                                // edge has not the same direction as toDetermine
                                cutvalue[toDetermine.id] -= cutvalue[edge.id] - edge.weight;
                            } else {
                                cutvalue[toDetermine.id] += cutvalue[edge.id] - edge.weight;
                            }
                        } else {
                            // edge is non-tree edge
                            if (node.equals(source)) {
                                if (edge.getSource().equals(node)) {
                                    cutvalue[toDetermine.id] += edge.weight;
                                } else {
                                    cutvalue[toDetermine.id] -= edge.weight;
                                }
                            } else {
                                if (edge.getSource().equals(node)) {
                                    cutvalue[toDetermine.id] -= edge.weight;
                                } else {
                                    cutvalue[toDetermine.id] += edge.weight;
                                }
                            }
                        }
                    }
                }

                // remove edge from 'unknownCutvalues'
                source.unknownCutvalues.remove(toDetermine);
                target.unknownCutvalues.remove(toDetermine);

                // proceed with next node
                if (source.equals(node)) {
                    node = toDetermine.getTarget();
                } else {
                    node = toDetermine.getSource();
                }
            }
        }
    }

    /**
     * Helper method for the network simplex layerer. It returns a tree edge with a negative cut
     * value or {@code null}, if no such edge exists, meaning that the current layer assignment of
     * all nodes is optimal. Note, that this method returns any edge with a negative cut value. A
     * special preference to an edge with lowest value will not be given.
     * 
     * @return a tree edge with negative cut value or {@code null}, if no such edge exists
     */
    private NEdge leaveEdge() {
        for (NEdge edge : treeEdges) {
            if (edge.treeEdge && cutvalue[edge.id] < 0) {
                return edge;
            }
        }
        return null;
    }

    /**
     * Helper method for the network simplex layerer. It determines an non-tree edge to replace the
     * given tree edge in the spanning tree. All edges going from the head component to the tail
     * component of the edge will be considered. The edge with a minimal amount of slack (i.e. the
     * lowest difference between its current to its minimal length) will be returned.
     * 
     * @param leave
     *            the tree edge to determine a non-tree edge to be replaced with
     * @return a non-tree edge with a minimal amount of slack to replace the given edge
     * @throws IllegalArgumentException
     *             if the input edge is not a tree edge
     */
    private NEdge enterEdge(final NEdge leave) {
        if (!leave.treeEdge) {
            throw new IllegalArgumentException("The input edge is not a tree edge.");
        }

        NEdge replace = null;
        int repSlack = Integer.MAX_VALUE;
        int slack;
        NNode source, target;
        for (NEdge edge : edges) {
            source = edge.getSource();
            target = edge.getTarget();
            if (isInHead(source, leave) && !isInHead(target, leave)) {
                // edge is to consider
                slack = target.layer - source.layer - edge.delta;
                if (slack < repSlack) {
                    repSlack = slack;
                    replace = edge;
                }
            }
        }
        return replace;
    }

    /**
     * Helper method for the network simplex layerer. It exchanges the tree-edge {@code leave} by
     * the non-tree edge {@code enter} and updates all values based on the tree (i.e. performs a new
     * postorder DFS-traversal and updates the cut values).
     * 
     * @param leave
     *            the tree-edge to be replaced
     * @param enter
     *            the non-tree edge to replace the tree edge
     * @throws IllegalArgumentException
     *             if either {@code leave} is no tree edge or {@code enter} is a tree edge already
     * 
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#enterEdge(NEdge)
     *      enterEdge()
     * @see de.cau.cs.kieler.klay.layered.p2layers.NetworkSimplexLayerer#leaveEdge() leaveEdge()
     */
    private void exchange(final NEdge leave, final NEdge enter) {
        if (!leave.treeEdge) {
            throw new IllegalArgumentException("Given leave edge is no tree edge.");
        }
        if (enter.treeEdge) {
            throw new IllegalArgumentException("Given enter edge is a tree edge already.");
        }

        // update tree
        leave.treeEdge = false;
        treeEdges.remove(leave);
        enter.treeEdge = true;
        treeEdges.add(enter);
        int delta = enter.getTarget().layer - enter.getSource().layer - enter.delta;
        if (!isInHead(enter.getTarget(), leave)) {
            delta = -delta;
        }
        for (NNode node : graph.nodes) {
            if (!isInHead(node, leave)) {
                node.layer += delta;
            }
        }

        // TODO it should be possible to do this incrementally right?
        // update tree-based values
        postOrder = 1;
        Arrays.fill(edgeVisited, false);
        postorderTraversal(graph.nodes.iterator().next());
        cutvalues();
    }

    /**
     * Helper method for the network simplex layerer. It normalizes the layering, i.e. determines
     * the lowest layer assigned to a node and shifts all nodes up or down in the layers
     * accordingly. After termination, the lowest layer assigned to a node will be zeroth (and
     * therefore first) layer. This method returns an integer array indicating how many nodes are
     * assigned to which layer. Note that the total number of layers necessary to layer the graph is
     * indicated thereby, which is the size if the array.
     * 
     * @return an integer array indicating how many nodes are assigned to which layer
     */
    private int[] normalize() {
        // determine lowest assigned layer and layer count
        int highest = Integer.MIN_VALUE;
        int lowest = Integer.MAX_VALUE;
        for (NNode node : graph.nodes) {
            lowest = Math.min(lowest, node.layer);
            highest = Math.max(highest, node.layer);
        }
        // normalize and determine layer filling
        int layerID = 0;
        int[] filling = new int[highest - lowest + 1];
        for (NNode node : graph.nodes) {
            node.layer -= lowest;
            filling[node.layer]++;
        }

        // also consider nodes of already layered connected components
        if (previousLayering != null) {
            for (Layer eLayer : previousLayering) {
                filling[layerID++] += eLayer.getNodes().size();
                if (filling.length == layerID) {
                    break;
                }
            }
        }
        return filling;
    }

    /**
     * Helper method for the network simplex layerer. It balances the layering concerning its width,
     * i.e. the number of nodes in each layer. If the graph allows multiple optimal layerings
     * regarding a minimal edge length, this method moves separate nodes to a layer with a minimal
     * amount of currently contained nodes with respect to the retention of feasibility and
     * optimality of the given layering.
     * 
     * @param filling
     *            an integer array indicating how many nodes are currently assigned to each layer
     */
    private void balance(final int[] filling) {
        // determine possible layers
        int newLayer;
        Pair<Integer, Integer> range = null;
        for (NNode node : graph.nodes) {
            if (node.getIncomingEdges().size() == node.getOutgoingEdges().size()) {
                // node might get shifted
                newLayer = node.layer;
                range = minimalSpan(node);
                for (int i = node.layer - range.getFirst() + 1; i < node.layer + range.getSecond(); i++) {
                    if (filling[i] < filling[newLayer]) {
                        newLayer = i;
                    }
                }
                // assign new layer
                if (filling[newLayer] < filling[node.layer]) {
                    filling[node.layer]--;
                    filling[newLayer]++;
                    node.layer = newLayer;
                }
            }
        }
    }

}