org.eclipse.elk.alg.layered.intermediate.compaction.NetworkSimplexCompaction.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.elk.alg.layered.intermediate.compaction.NetworkSimplexCompaction.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.intermediate.compaction;

import java.util.List;
import java.util.Map;

import org.eclipse.elk.alg.layered.compaction.oned.CGroup;
import org.eclipse.elk.alg.layered.compaction.oned.CNode;
import org.eclipse.elk.alg.layered.compaction.oned.OneDimensionalCompactor;
import org.eclipse.elk.alg.layered.compaction.oned.algs.ICompactionAlgorithm;
import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LPort;
import org.eclipse.elk.alg.layered.networksimplex.NEdge;
import org.eclipse.elk.alg.layered.networksimplex.NGraph;
import org.eclipse.elk.alg.layered.networksimplex.NNode;
import org.eclipse.elk.alg.layered.networksimplex.NetworkSimplex;
import org.eclipse.elk.core.options.PortSide;
import org.eclipse.elk.core.util.BasicProgressMonitor;

import com.google.common.collect.HashMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;

/**
 * Compaction strategy for the {@link OneDimensionalCompactor} based on the network simplex
 * algorithm. As a consequence not the width is minimized but the edge length of the unterlying
 * network simplex graph. We model this graph, such that the edge lengths of the original
 * {@link de.cau.cs.kieler.klay.layered.graph.LGraph LGraph} are minimized.
 */
public class NetworkSimplexCompaction implements ICompactionAlgorithm {

    private OneDimensionalCompactor compactor;
    private NGraph networkSimplexGraph;

    private NNode[] nNodes;
    private int index;

    private static final int SEPARATION_WEIGHT = 1;
    private static final int EDGE_WEIGHT = 100;

    /**
     * {@inheritDoc}
     */
    @Override
    public void compact(final OneDimensionalCompactor theCompactor) {

        this.compactor = theCompactor;
        this.networkSimplexGraph = new NGraph();

        this.nNodes = new NNode[compactor.cGraph.cGroups.size()];
        this.index = 0;

        // create a node in the network simplex graph for each group
        for (CGroup cGroup : compactor.cGraph.cGroups) {

            cGroup.id = index;
            NNode nNode = NNode.of().id(index).origin(cGroup).create(networkSimplexGraph);
            nNodes[index] = nNode;

            index++;
        }

        // #2 transform constraint graph edges
        addSeparationConstraints();

        // #3 add the original graph's edges for edge length minimization
        addEdgeConstraints();

        // #4 network simplex does not allow disconnected graphs
        //  so we make it connected!
        addArtificialSourceNode();

        //        if(nNodes.length > 1)
        //        networkSimplexGraph.writeDebugGraph("d:/tmp/cc/foo.kgx");

        // #5 execute network simplex
        NetworkSimplex.forGraph(networkSimplexGraph).execute(new BasicProgressMonitor());

        // #6 apply positions
        for (CNode cNode : compactor.cGraph.cNodes) {
            cNode.startPos = nNodes[cNode.cGroup.id].layer + cNode.cGroupOffset.x;
            cNode.applyPosition();
        }
    }

    /**
     * @return true if both passed nodes originate from the same (set) of {@link LEdge}. 
     */
    private boolean isVerticalSegmentsOfSameEdge(final CNode cNode1, final CNode cNode2) {
        return cNode1.parentNode != null && cNode2.parentNode != null
                && (cNode1 instanceof CLEdge && cNode2 instanceof CLEdge)
                // this might seem quite expensive but in most cases the sets 
                // contain only one element
                && !Sets.intersection(((CLEdge) cNode1).originalLEdges, ((CLEdge) cNode2).originalLEdges).isEmpty();
    }

    /**
     * Add constraints that guarantee proper spacing and order between all groups.
     */
    private void addSeparationConstraints() {

        for (CNode cNode : compactor.cGraph.cNodes) {
            for (CNode incNode : cNode.constraints) {

                // no need to add edges between nodes of the same group
                if (cNode.cGroup == incNode.cGroup) {
                    continue;
                }

                double spacing;
                if (compactor.direction.isHorizontal()) {
                    spacing = compactor.spacingsHandler.getHorizontalSpacing(cNode, incNode);
                } else {
                    spacing = compactor.spacingsHandler.getVerticalSpacing(cNode, incNode);
                }

                // the constraint edges look as follows: 
                //   cNode <-- incNode
                double delta = cNode.cGroupOffset.x + cNode.hitbox.width + spacing - incNode.cGroupOffset.x;

                // network simplex requires an integer for delta
                delta = Math.ceil(delta);

                // negative values can occur with external ports
                //  it should be ok to fix this here
                delta = Math.max(0, delta);

                if (!isVerticalSegmentsOfSameEdge(cNode, incNode)) {

                    // it looks nicer if the initial segment is close to the node
                    double weight = SEPARATION_WEIGHT;
                    if ((cNode instanceof CLEdge && incNode instanceof CLNode)
                            || (incNode instanceof CLEdge && cNode instanceof CLNode)) {
                        weight = 2;
                    }
                    // add a single edge, i.e. constraint to the network simplex graph
                    NEdge.of().delta((int) delta).weight(weight) // small weight here, it only preserves separations
                            .source(nNodes[cNode.cGroup.id]).target(nNodes[incNode.cGroup.id]).create();

                } else {

                    // vertical segments are quite a species ...
                    //  it may be necessary to change the "order" of 
                    //  vertical segments' groups to get a straight 
                    //  edge without violating any other requirements

                    NNode helper = NNode.of().create(networkSimplexGraph);

                    // TODO add an offsetcorrection... just slightly move one of the nodes 
                    int offsetDelta = (int) Math.ceil(incNode.cGroupOffset.x - cNode.cGroupOffset.x);

                    NEdge.of().delta(Math.max(0, offsetDelta)).weight(SEPARATION_WEIGHT) // small weight here, it only preserves separations
                            .source(helper).target(nNodes[cNode.cGroup.id]).create();

                    NEdge.of().delta(Math.max(0, -offsetDelta)).weight(SEPARATION_WEIGHT) // no weight here, it only preserves separations
                            .source(helper).target(nNodes[incNode.cGroup.id]).create();

                }
            }
        }
    }

    /**
     * Add edges to the network simplex graph that represent the original {@link LEdge}s of the
     * graph. Give them a larger weight such that they are kept short.
     * 
     * There are two additional special cases:
     *  - directly connected north + south port
     *    (they are already handled by the {@link #addSeparationConstraints()} method.
     *     Note that this is only possible because the spacings handler that is specified 
     *     in {@link HorizontalGraphCompactor} adds a single pixel to each of the 
     *     vertical segments, thus enforcing a constraint)
     *  - inverted ports
     */
    private void addEdgeConstraints() {

        // collect the original edgess
        Map<LNode, CNode> lNodeMap = Maps.newHashMap();
        Multimap<LEdge, CNode> lEdgeMap = HashMultimap.create();
        for (CNode cNode : compactor.cGraph.cNodes) {
            if (cNode instanceof CLNode) {
                LNode lNode = ((CLNode) cNode).getlNode();
                lNodeMap.put(lNode, cNode);
            } else if (cNode instanceof CLEdge) {
                for (LEdge e : ((CLEdge) cNode).originalLEdges) {
                    lEdgeMap.put(e, cNode);
                }
            }
        }

        // add network simplex edges
        for (CNode cNode : compactor.cGraph.cNodes) {
            if (cNode instanceof CLNode) {
                LNode lNode = ((CLNode) cNode).getlNode();
                for (LEdge lEdge : lNode.getOutgoingEdges()) {

                    // ignore self-loops
                    if (lEdge.isSelfLoop()) {
                        continue;
                    }

                    LPort srcPort = lEdge.getSource();
                    LPort tgtPort = lEdge.getTarget();

                    // they may be connected "directly"
                    // if one of them is north or south however, we still want to pull the node close
                    if (PortSide.SIDES_NORTH_SOUTH.contains(lEdge.getSource().getSide())
                            && PortSide.SIDES_NORTH_SOUTH.contains(lEdge.getTarget().getSide())) {
                        continue;
                    }

                    CNode target = lNodeMap.get(lEdge.getTarget().getNode());

                    NEdge.of().delta(0).weight(EDGE_WEIGHT).source(nNodes[cNode.cGroup.id])
                            .target(nNodes[target.cGroup.id]).create();

                    // keep vertical segments close to the node if they are inverted ports
                    if (srcPort.getSide() == PortSide.WEST && LPort.OUTPUT_PREDICATE.apply(srcPort)) {

                        for (CNode n : lEdgeMap.get(lEdge)) {
                            if (n.hitbox.x < cNode.hitbox.x) {
                                NNode src = nNodes[n.cGroup.id];
                                NNode tgt = nNodes[cNode.cGroup.id];
                                if (src == tgt) {
                                    continue;
                                }
                                NEdge.of().delta(1).weight(EDGE_WEIGHT).source(src).target(tgt).create();
                            }
                        }
                    }

                    if (tgtPort.getSide() == PortSide.EAST && LPort.INPUT_PREDICATE.apply(tgtPort)) {

                        for (CNode n : lEdgeMap.get(lEdge)) {
                            if (n.hitbox.x > cNode.hitbox.x) {
                                NNode src = nNodes[cNode.cGroup.id];
                                NNode tgt = nNodes[n.cGroup.id];
                                if (src == tgt) {
                                    continue;
                                }
                                NEdge.of().delta(1).weight(EDGE_WEIGHT).source(src).target(tgt).create();
                            }
                        }
                    }

                }
            }
        }
    }

    /**
     * Creates an new node in the network simplex graph and adds an edge to every (so far) source
     * node.
     */
    private void addArtificialSourceNode() {
        List<NNode> sources = Lists.newLinkedList();
        for (NNode n : networkSimplexGraph.nodes) {
            if (n.getIncomingEdges().isEmpty()) {
                sources.add(n);
            }
        }

        if (sources.size() > 1) {
            NNode dummySource = NNode.of().id(index++).create(networkSimplexGraph);

            for (NNode src : sources) {
                NEdge.of().delta(1).weight(0).source(dummySource).target(src).create();
            }
        }
    }
}