Skip to content

Commit

Permalink
layered: Added model order cycle breaker. (#759)
Browse files Browse the repository at this point in the history
* layered: Added model order cycle breaker.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: Model order cycle breaker: Set cyclic property if needed. Throw
correct exception, fixed copy-paste bug.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: ModelOrderCyclebreaker: Handle layer constraints.

Also removes author tag and check for self loops.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered.test: Add model order cycle breaker to test cases.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: Correctly not use port order if port constraints are set.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: Corrected model order offset between constraint classes.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered.test: Added model order and model order cycle breaker tests.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: Make sure that model order ordering for dummy nodes is always
transitive.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>

* layered: Improved calculation of transitive comparator dependencies.

Signed-off-by: Soeren Domroes <sdo@informatik.uni-kiel.de>
  • Loading branch information
soerendomroes committed Sep 15, 2021
1 parent ea98387 commit 5f4ffc1
Show file tree
Hide file tree
Showing 8 changed files with 367 additions and 15 deletions.
Expand Up @@ -25,6 +25,7 @@
import org.eclipse.elk.alg.layered.graph.LPadding;
import org.eclipse.elk.alg.layered.graph.LPort;
import org.eclipse.elk.alg.layered.options.CrossingMinimizationStrategy;
import org.eclipse.elk.alg.layered.options.CycleBreakingStrategy;
import org.eclipse.elk.alg.layered.options.GraphProperties;
import org.eclipse.elk.alg.layered.options.InternalProperties;
import org.eclipse.elk.alg.layered.options.LayeredOptions;
Expand Down Expand Up @@ -224,7 +225,9 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
int index = 0;
for (ElkNode child : elkgraph.getChildren()) {
if (!child.getProperty(LayeredOptions.NO_LAYOUT)) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER) {
child.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
}
Expand All @@ -236,7 +239,9 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
// (this is not part of the previous loop since all children must have already been transformed)
index = 0;
for (ElkEdge elkedge : elkgraph.getContainedEdges()) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER) {
elkedge.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
}
Expand Down
Expand Up @@ -57,7 +57,7 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
Layer previousLayer = graph.getLayers().get(previousLayerIndex);
for (LNode node : layer.getNodes()) {
if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER
|| node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_POS) {
&& node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_POS) {
// Special case:
// If two edges (of the same node) have the same target node they should be next to each other.
// Therefore all ports that connect to the same node should have the same
Expand All @@ -81,7 +81,6 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
node.setProperty(InternalProperties.TARGET_NODE_MODEL_ORDER, targetNodeModelOrder);
Collections.sort(node.getPorts(),
new ModelOrderPortComparator(previousLayer, targetNodeModelOrder));
node.cachePortSides();
}
}
// Sort nodes.
Expand Down
Expand Up @@ -10,6 +10,8 @@
package org.eclipse.elk.alg.layered.intermediate.preserveorder;

import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;

import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LNode;
Expand All @@ -35,6 +37,15 @@ public class ModelOrderNodeComparator implements Comparator<LNode> {
*/
private final OrderingStrategy orderingStrategy;

/**
* Each node has an entry of nodes for which it is bigger.
*/
private HashMap<LNode, HashSet<LNode>> biggerThan = new HashMap<>();
/**
* Each node has an entry of nodes for which it is smaller.
*/
private HashMap<LNode, HashSet<LNode>> smallerThan = new HashMap<>();

/**
* Dummy node sorting strategy when compared to nodes with no connection to the previous layer.
*/
Expand Down Expand Up @@ -75,6 +86,26 @@ private ModelOrderNodeComparator(final OrderingStrategy orderingStrategy,

@Override
public int compare(final LNode n1, final LNode n2) {
if (!biggerThan.containsKey(n1)) {
biggerThan.put(n1, new HashSet<>());
} else if (biggerThan.get(n1).contains(n2)) {
return 1;
}
if (!biggerThan.containsKey(n2)) {
biggerThan.put(n2, new HashSet<>());
} else if (biggerThan.get(n2).contains(n1)) {
return -1;
}
if (!smallerThan.containsKey(n1)) {
smallerThan.put(n1, new HashSet<>());
} else if (smallerThan.get(n1).contains(n2)) {
return -1;
}
if (!smallerThan.containsKey(n2)) {
smallerThan.put(n2, new HashSet<>());
} else if (biggerThan.get(n2).contains(n1)) {
return 1;
}
// If no model order is set, the one node is a dummy node and the nodes should be ordered
// by the connected edges.
// This kind of ordering should be preferred, if the order of the edges has priority.
Expand All @@ -98,8 +129,12 @@ public int compare(final LNode n1, final LNode n2) {
// port ordering.
for (LPort port : p1Node.getPorts()) {
if (port.equals(p1SourcePort)) {
// Case the port is the one connecting to n1, therefore, n1 has a smaller model order
updateBiggerAndSmallerAssociations(n2, n1);
return -1;
} else if (port.equals(p2SourcePort)) {
// Case the port is the one connecting to n2, therefore, n1 has a bigger model order
updateBiggerAndSmallerAssociations(n1, n2);
return 1;
}
}
Expand All @@ -115,27 +150,43 @@ public int compare(final LNode n1, final LNode n2) {
// since the ordering in the previous layer does already reflect it.
for (LNode previousNode : previousLayer) {
if (previousNode.equals(p1Node)) {
updateBiggerAndSmallerAssociations(n2, n1);
return -1;
} else if (previousNode.equals(p2Node)) {
updateBiggerAndSmallerAssociations(n1, n2);
return 1;
}
}
}

// One node has no source port
if (!n1.hasProperty(InternalProperties.MODEL_ORDER) || !n2.hasProperty(InternalProperties.MODEL_ORDER)) {
int n1ModelOrder = getModelOrderFromConnectedEdges(n1);
int n2ModelOrder = getModelOrderFromConnectedEdges(n2);
if (n1ModelOrder > n2ModelOrder) {
updateBiggerAndSmallerAssociations(n1, n2);
} else {
updateBiggerAndSmallerAssociations(n2, n1);
}
return Integer.compare(
getModelOrderFromConnectedEdges(n1),
getModelOrderFromConnectedEdges(n2));
n1ModelOrder,
n2ModelOrder);
}
// Fall through case.
// Both nodes are not connected to the previous layer. Therefore, they must be normal nodes.
// The model order shall be used to order them.
}
// Order nodes by their order in the model.
int n1ModelOrder = n1.getProperty(InternalProperties.MODEL_ORDER);
int n2ModelOrder = n2.getProperty(InternalProperties.MODEL_ORDER);
if (n1ModelOrder > n2ModelOrder) {
updateBiggerAndSmallerAssociations(n1, n2);
} else {
updateBiggerAndSmallerAssociations(n2, n1);
}
return Integer.compare(
n1.getProperty(InternalProperties.MODEL_ORDER),
n2.getProperty(InternalProperties.MODEL_ORDER));
n1ModelOrder,
n2ModelOrder);
}

/**
Expand All @@ -160,4 +211,25 @@ private int getModelOrderFromConnectedEdges(final LNode n) {
// that do not have a connection to the previous layer.
return longEdgeNodeOrder.returnValue();
}

private void updateBiggerAndSmallerAssociations(final LNode bigger, final LNode smaller) {
HashSet<LNode> biggerNodeBiggerThan = biggerThan.get(bigger);
HashSet<LNode> smallerNodeBiggerThan = biggerThan.get(smaller);
HashSet<LNode> biggerNodeSmallerThan = smallerThan.get(bigger);
HashSet<LNode> smallerNodeSmallerThan = smallerThan.get(smaller);
biggerNodeBiggerThan.add(smaller);
smallerNodeSmallerThan.add(bigger);
for (LNode verySmall : smallerNodeBiggerThan) {
biggerNodeBiggerThan.add(verySmall);
smallerThan.get(verySmall).add(bigger);
smallerThan.get(verySmall).addAll(biggerNodeSmallerThan);
}


for (LNode veryBig : biggerNodeSmallerThan) {
smallerNodeSmallerThan.add(veryBig);
biggerThan.get(veryBig).add(smaller);
biggerThan.get(veryBig).addAll(smallerNodeBiggerThan);
}
}
}
Expand Up @@ -91,12 +91,13 @@ public int compare(final LPort p1, final LPort p2) {
if (p1TargetNode != null && p1TargetNode.equals(p2TargetNode)) {
return Integer.compare(p1Order, p2Order);
}

if (targetNodeModelOrder.containsKey(p1TargetNode)) {
p1Order = targetNodeModelOrder.get(p1TargetNode);
}
if (targetNodeModelOrder.containsKey(p2TargetNode)) {
p2Order = targetNodeModelOrder.get(p2TargetNode);
if (targetNodeModelOrder != null) {
if (targetNodeModelOrder.containsKey(p1TargetNode)) {
p1Order = targetNodeModelOrder.get(p1TargetNode);
}
if (targetNodeModelOrder.containsKey(p2TargetNode)) {
p2Order = targetNodeModelOrder.get(p2TargetNode);
}
}
return Integer.compare(p1Order, p2Order);

Expand Down
Expand Up @@ -14,6 +14,7 @@
import org.eclipse.elk.alg.layered.p1cycles.DepthFirstCycleBreaker;
import org.eclipse.elk.alg.layered.p1cycles.GreedyCycleBreaker;
import org.eclipse.elk.alg.layered.p1cycles.InteractiveCycleBreaker;
import org.eclipse.elk.alg.layered.p1cycles.ModelOrderCycleBreaker;
import org.eclipse.elk.core.alg.ILayoutPhase;
import org.eclipse.elk.core.alg.ILayoutPhaseFactory;
import org.eclipse.elk.graph.properties.AdvancedPropertyValue;
Expand All @@ -40,7 +41,13 @@ public enum CycleBreakingStrategy implements ILayoutPhaseFactory<LayeredPhases,
* a node, that movement is reflected in the decision which edges to reverse.
*/
@AdvancedPropertyValue
INTERACTIVE;
INTERACTIVE,

/**
* Reacts to the input model by respecting the initial ordering in the model file.
* This ordering is used to identify backwards edges.
*/
MODEL_ORDER;


@Override
Expand All @@ -55,6 +62,9 @@ public ILayoutPhase<LayeredPhases, LGraph> create() {
case INTERACTIVE:
return new InteractiveCycleBreaker();

case MODEL_ORDER:
return new ModelOrderCycleBreaker();

default:
throw new IllegalArgumentException(
"No implementation is available for the cycle breaker " + this.toString());
Expand Down
@@ -0,0 +1,135 @@
/*******************************************************************************
* Copyright (c) 2021 Kiel University and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.elk.alg.layered.p1cycles;

import java.util.List;

import org.eclipse.elk.alg.layered.LayeredPhases;
import org.eclipse.elk.alg.layered.graph.LEdge;
import org.eclipse.elk.alg.layered.graph.LGraph;
import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LPort;
import org.eclipse.elk.alg.layered.intermediate.IntermediateProcessorStrategy;
import org.eclipse.elk.alg.layered.options.InternalProperties;
import org.eclipse.elk.alg.layered.options.LayeredOptions;
import org.eclipse.elk.alg.layered.options.PortType;
import org.eclipse.elk.core.alg.ILayoutPhase;
import org.eclipse.elk.core.alg.LayoutProcessorConfiguration;
import org.eclipse.elk.core.util.IElkProgressMonitor;

import com.google.common.collect.Lists;

/**
* A cycle breaker that reverses all edges that go against the model order,
* i.e. edges from high model order to low model order.
*
* <dl>
* <dt>Precondition:</dt>
* <dd>no self loops</dd>
* <dt>Postcondition:</dt>
* <dd>the graph has no cycles</dd>
* </dl>
*/
public final class ModelOrderCycleBreaker implements ILayoutPhase<LayeredPhases, LGraph> {

private int firstSeparateModelOrder;
private int lastSeparateModelOrder;

/** intermediate processing configuration. */
private static final LayoutProcessorConfiguration<LayeredPhases, LGraph> INTERMEDIATE_PROCESSING_CONFIGURATION =
LayoutProcessorConfiguration.<LayeredPhases, LGraph>create()
.addAfter(LayeredPhases.P5_EDGE_ROUTING, IntermediateProcessorStrategy.REVERSED_EDGE_RESTORER);

@Override
public LayoutProcessorConfiguration<LayeredPhases, LGraph> getLayoutProcessorConfiguration(final LGraph graph) {
return INTERMEDIATE_PROCESSING_CONFIGURATION;
}

@Override
public void process(final LGraph layeredGraph, final IElkProgressMonitor monitor) {
monitor.begin("Model order cycle breaking", 1);

// Reset FIRST_SEPARATE and LAST_SEPARATE counters.
firstSeparateModelOrder = 0;
lastSeparateModelOrder = 0;

// gather edges that point to the wrong direction
List<LEdge> revEdges = Lists.newArrayList();

// One needs an offset to make sure that the model order of nodes with port constraints is
// always lower/higher than that of other nodes.
// E.g. A node with the LAST constraint needs to have a model order m = modelOrder + offset
// such that m > m(n) with m(n) being the model order of a normal node n (without constraints).
// Such that the highest model order has to be used as an offset
int offset = layeredGraph.getLayerlessNodes().size();
for (LNode node : layeredGraph.getLayerlessNodes()) {
if (node.hasProperty(InternalProperties.MODEL_ORDER)) {
offset = Math.max(offset, node.getProperty(InternalProperties.MODEL_ORDER) + 1);
}
}

for (LNode source : layeredGraph.getLayerlessNodes()) {
int modelOrderSource = computeConstraintModelOrder(source, offset);

for (LPort port : source.getPorts(PortType.OUTPUT)) {
for (LEdge edge : port.getOutgoingEdges()) {
LNode target = edge.getTarget().getNode();
int modelOrderTarget = computeConstraintModelOrder(target, offset);
if (modelOrderTarget < modelOrderSource) {
revEdges.add(edge);
}
}
}
}

// reverse the gathered edges
for (LEdge edge : revEdges) {
edge.reverse(layeredGraph, true);
layeredGraph.setProperty(InternalProperties.CYCLIC, true);
}
revEdges.clear();
monitor.done();
}

/**
* Set model order to a value such that the constraint is respected and the ordering between nodes with
* the same constraint is preserved.
* The order should be FIRST_SEPARATE < FIRST < NORMAL < LAST < LAST_SEPARATE. The offset is used to make sure the
* all nodes have unique model orders.
* @param node The LNode
* @param offset The offset between FIRST, FIRST_SEPARATE, NORMAL, LAST_SEPARATE, and LAST nodes for unique order
* @return A unique model order
*/
private int computeConstraintModelOrder(final LNode node, final int offset) {
int modelOrder = 0;
switch (node.getProperty(LayeredOptions.LAYERING_LAYER_CONSTRAINT)) {
case FIRST_SEPARATE:
modelOrder = 2 * -offset + firstSeparateModelOrder;
firstSeparateModelOrder++;
break;
case FIRST:
modelOrder = -offset;
break;
case LAST:
modelOrder = offset;
break;
case LAST_SEPARATE:
modelOrder = 2 * offset + lastSeparateModelOrder;
lastSeparateModelOrder++;
break;
default:
break;
}
if (node.hasProperty(InternalProperties.MODEL_ORDER)) {
modelOrder += node.getProperty(InternalProperties.MODEL_ORDER);
}
return modelOrder;
}
}

0 comments on commit 5f4ffc1

Please sign in to comment.