Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
30b0435
Added Group Model Order thesis strategies
soerendomroes May 30, 2025
c1f300f
Added FIXMEs to tackle.
soerendomroes May 30, 2025
29d990f
Add BF cycle breaker.
soerendomroes Jun 2, 2025
9b400c4
Corrected BFS cycle breaker and added different group model order modes.
soerendomroes Jun 2, 2025
d489bf6
Added internal maximum model order nodes property.
soerendomroes Jun 2, 2025
6d76925
Layered: Depth first cycle breaker with node visiting order.
soerendomroes Jun 2, 2025
e399e39
Layered: Strongly connected component cycle breaker.
soerendomroes Jun 2, 2025
9319293
Layered: Added group model order support to strict mo cycle breaker.
soerendomroes Jun 2, 2025
9e0d490
Layered: Deleted redundant strict group order cycle breaker.
soerendomroes Jun 2, 2025
4e007b8
Layered added connectivity strongly connected component cycle breaker.
soerendomroes Jun 2, 2025
768d760
Layered: Model order cycle breaking cleanup before tests.
soerendomroes Jun 2, 2025
990c954
Added more comments for cycle breakers.
soerendomroes Jun 2, 2025
e099faf
Added group model order calculator helper class for cycle breaking.
soerendomroes Jun 3, 2025
ae46cb1
Added group model order to tie-breaking strategies.
soerendomroes Jun 3, 2025
68486e5
Added group model order to enforced model order cm.
soerendomroes Jun 3, 2025
355bceb
Actually use cm model order group.
soerendomroes Jun 3, 2025
80b3a2d
Count internal variables for INCLUDE_CHILDREN too.
soerendomroes Jun 4, 2025
c845892
Different ordering strategies for model order barycenter.
soerendomroes Jul 21, 2025
ac714e2
Model order groups partially enforced by CM.
soerendomroes Jul 21, 2025
447f090
Checked fixmes.
soerendomroes Jul 21, 2025
15a52e5
Update plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/la…
soerendomroes Jul 21, 2025
85e6113
Update plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/la…
soerendomroes Jul 21, 2025
54190e4
Update plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/la…
soerendomroes Jul 21, 2025
44d2d8a
Update plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/la…
soerendomroes Jul 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,13 +201,23 @@ algorithm layered(LayeredLayoutProvider) {
supports org.eclipse.elk.interactiveLayout
supports layering.layerId
supports crossingMinimization.positionId

supports considerModelOrder.strategy
supports considerModelOrder.longEdgeStrategy
supports considerModelOrder.crossingCounterNodeInfluence
supports considerModelOrder.crossingCounterPortInfluence
supports considerModelOrder.noModelOrder
supports considerModelOrder.components
supports considerModelOrder.portModelOrder
supports considerModelOrder.groupModelOrder.cycleBreakingId
supports considerModelOrder.groupModelOrder.crossingMinimizationId
supports considerModelOrder.groupModelOrder.componentGroupId
supports considerModelOrder.groupModelOrder.cbGroupOrderStrategy
supports considerModelOrder.groupModelOrder.cmGroupOrderStrategy
supports considerModelOrder.groupModelOrder.cmEnforcedGroupOrders
supports considerModelOrder.groupModelOrder.cbPreferredSourceId
supports considerModelOrder.groupModelOrder.cbPreferredTargetId

supports generatePositionAndLayerIds
}

Expand Down Expand Up @@ -1078,6 +1088,73 @@ group considerModelOrder {
targets parents
requires org.eclipse.elk.alg.layered.considerModelOrder.strategy
}

group groupModelOrder {
option cycleBreakingId: int {
label "Group ID of the Node Type"
description
"Used to define partial ordering groups during cycle breaking. A lower group id means that the group is
sorted before other groups. A group model order of 0 is the default group."
default = 0
targets nodes
requires noModelOrder == false
}
option crossingMinimizationId: int {
label "Group ID of the Node Type"
description
"Used to define partial ordering groups during crossing minimization. A lower group id means that the group is
sorted before other groups. A group model order of 0 is the default group."
default = 0
targets nodes, edges, ports
requires noModelOrder == false
}
option componentGroupId: int {
label "Group ID of the Node Type"
description
"Used to define partial ordering groups during component packing. A lower group id means that the group is
sorted before other groups. A group model order of 0 is the default group."
default = 0
targets nodes, edges, ports
requires noModelOrder == false
}
option cbGroupOrderStrategy: GroupOrderStrategy {
label "Cycle Breaking Group Ordering Strategy"
description "Determines how to count ordering violations during cycle breaking. NONE: They do not
count. ENFORCED: A group with a higher model order is before a node with a smaller.
MODEL_ORDER: The model order counts instead of the model order group id ordering."
default = GroupOrderStrategy.ONLY_WITHIN_GROUP
targets parents
}
option cbPreferredSourceId: int {
label "Cycle Breaking Preferred Source Id"
description "The model order group id for which should be preferred as a source if possible."
targets parents
requires cycleBreaking.strategy == CycleBreakingStrategy.SCC_NODE_TYPE
}
option cbPreferredTargetId: int {
label "Cycle Breaking Preferred Target Id"
description "The model order group id for which should be preferred as a target if possible."
targets parents
requires cycleBreaking.strategy == CycleBreakingStrategy.SCC_NODE_TYPE
}
option cmGroupOrderStrategy: GroupOrderStrategy {
label "Crossing Minimization Group Ordering Strategy"
description "Determines how to count ordering violations during crossing minimization. NONE: They do not
count. ENFORCED: A group with a lower id is before a group with a higher id.
MODEL_ORDER: The model order counts instead of the model order group id ordering."
default = GroupOrderStrategy.ONLY_WITHIN_GROUP
targets parents
}
option cmEnforcedGroupOrders: List<Integer> {
label "Crossing Minimization Enforced Group Orders"
description "Holds all group ids which are enforcing their order during crossing minimization strategies.
E.g. if only groups 2 and -1 (default) enforce their ordering. Other groups e.g. the group of
timer nodes can be ordered arbitrarily if it helps and the mentioned groups may not change
their order."
targets parents
default = #[1, 2, 6, 7, 10, 11]
}
}
}

advanced option directionCongruency: DirectionCongruency {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*******************************************************************************
* Copyright (c) 2025 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.graph;

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.eclipse.elk.alg.layered.options.InternalProperties;

/**
* Tarjan implementation to be used during layered layout.
*/


public class Tarjan {

public Tarjan (List<LEdge> edgesToBeReversed, List<Set<LNode>> stronglyConnectedComponents,
HashMap <LNode,Integer> nodeToSCCID) {
this.edgesToBeReversed = edgesToBeReversed;
this.stronglyConnectedComponents = stronglyConnectedComponents;
this.nodeToSCCID = nodeToSCCID;
}

private List<LEdge> edgesToBeReversed;
private int index = 0;
protected List<Set<LNode>> stronglyConnectedComponents; // FIXME Why no ordered set here? this is bad
private Stack<LNode> stack = new Stack<LNode>();
private HashMap <LNode,Integer> nodeToSCCID = new HashMap<>();

public void tarjan(final LGraph graph) {
index = 0;
stack = new Stack<LNode>();
for (LNode node : graph.getLayerlessNodes()) {
if (node.getProperty(InternalProperties.TARJAN_ID) == -1) {
stronglyConnected(node);
stack.clear();
}
}
}

public void stronglyConnected(final LNode v) {
v.setProperty(InternalProperties.TARJAN_ID, index);
v.setProperty(InternalProperties.TARJAN_LOWLINK, index);
index++;
stack.push(v);
v.setProperty(InternalProperties.TARJAN_ON_STACK, true);
for (LEdge edge : v.getConnectedEdges()) {
if (edge.getSource().getNode() != v && !edgesToBeReversed.contains(edge)) {
continue;
}
if (edge.getSource().getNode() == v && edgesToBeReversed.contains(edge)) {
continue;
}
LNode target = null;
if (edge.getTarget().getNode() == v) {
target = edge.getSource().getNode();
} else {
target = edge.getTarget().getNode();
}
if (target.getProperty(InternalProperties.TARJAN_ID) == -1) {
stronglyConnected(target);
v.setProperty(InternalProperties.TARJAN_LOWLINK,
Math.min(v.getProperty(InternalProperties.TARJAN_LOWLINK),
target.getProperty(InternalProperties.TARJAN_LOWLINK)));
} else if (target.getProperty(InternalProperties.TARJAN_ON_STACK)) {
v.setProperty(InternalProperties.TARJAN_LOWLINK,
Math.min(v.getProperty(InternalProperties.TARJAN_LOWLINK),
target.getProperty(InternalProperties.TARJAN_ID)));
}
}
if (v.getProperty(InternalProperties.TARJAN_LOWLINK) == v.getProperty(InternalProperties.TARJAN_ID)) {
Set<LNode> sCC = new HashSet<LNode>();
LNode n = null;
do {
n = stack.pop();
n.setProperty(InternalProperties.TARJAN_ON_STACK, false);
sCC.add(n);
} while (v != n);
if (sCC.size() >1) {
int index = stronglyConnectedComponents.size();
stronglyConnectedComponents.add(sCC);
for (LNode node : sCC) {
nodeToSCCID.put(node, index);
}
}
}
}

public void resetTarjan(final LGraph graph) {
for (LNode n : graph.getLayerlessNodes()) {
n.setProperty(InternalProperties.TARJAN_ON_STACK, false);
n.setProperty(InternalProperties.TARJAN_LOWLINK, -1);
n.setProperty(InternalProperties.TARJAN_ID, -1);
stack.clear();
for (LEdge e : n.getConnectedEdges()) {
e.setProperty(InternalProperties.IS_PART_OF_CYCLE, false);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
package org.eclipse.elk.alg.layered.graph.transform;

import java.util.EnumSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -228,15 +229,24 @@ private void calculateMinimumGraphSize(final ElkNode elkgraph, final LGraph lgra
private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
// Transform the node's children, unless we're told not to
int index = 0;
HashSet<Integer> cbGroupModelOrders = new HashSet<>();
for (ElkNode child : elkgraph.getChildren()) {
if (!child.getProperty(LayeredOptions.NO_LAYOUT)) {
if (needsModelOrder(child)) {
child.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
if (child.hasProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID)) {
cbGroupModelOrders.add(child.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID));
}
}
transformNode(child, lgraph);
}
}
// Save the maximum node model order.
// This is relevant to create graph partitions based on model order and constraints.
lgraph.setProperty(InternalProperties.MAX_MODEL_ORDER_NODES, index);
// Save the number of model order groups.
lgraph.setProperty(InternalProperties.CB_NUM_MODEL_ORDER_GROUPS, cbGroupModelOrders.size());

// iterate the list of contained edges to preserve the 'input order' of the edges
// (this is not part of the previous loop since all children must have already been transformed)
Expand Down Expand Up @@ -298,6 +308,7 @@ private void importHierarchicalGraph(final ElkNode elkgraph, final LGraph lgraph

// Model order index for nodes
int index = 0;
HashSet<Integer> cbGroupModelOrders = new HashSet<>();
// Transform the node's children
elkGraphQueue.addAll(elkgraph.getChildren());
while (!elkGraphQueue.isEmpty()) {
Expand All @@ -306,6 +317,9 @@ private void importHierarchicalGraph(final ElkNode elkgraph, final LGraph lgraph
if (needsModelOrder(elknode)) {
// Assign a model order to the nodes as they are read
elknode.setProperty(InternalProperties.MODEL_ORDER, index++);
if (elknode.hasProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID)) {
cbGroupModelOrders.add(elknode.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID));
}
}

// Check if the current node is to be laid out in the first place
Expand Down Expand Up @@ -358,6 +372,11 @@ private void importHierarchicalGraph(final ElkNode elkgraph, final LGraph lgraph
}
}
}
// Save the maximum node model order.
// This is relevant to create graph partitions based on model order and constraints.
lgraph.setProperty(InternalProperties.MAX_MODEL_ORDER_NODES, index);
// Save the number of model order groups.
lgraph.setProperty(InternalProperties.CB_NUM_MODEL_ORDER_GROUPS, cbGroupModelOrders.size());

// Model order index for edges.
index = 0;
Expand Down Expand Up @@ -458,22 +477,29 @@ private boolean needsModelOrder(final ElkNode child) {
* @return True, if model order should be set.
*/
private boolean needsModelOrderBasedOnParent(final ElkNode elkgraph) {
return (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.MODEL_ORDER
|| elkgraph
.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.GREEDY_MODEL_ORDER

boolean modelOrderCycleBreaking = elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.BFS_NODE_ORDER
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.DFS_NODE_ORDER
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.GREEDY_MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.SCC_CONNECTIVITY
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.SCC_NODE_TYPE;
boolean modelOrderLayering = elkgraph.getProperty(LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.BF_MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.DF_MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_LEFT_TO_RIGHT
|| elkgraph.getProperty(LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_RIGHT_TO_LEFT;
boolean modelOrderCrossingMinimization =
// Maybe add the explicit strategies here
elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
|| elkgraph
.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE)
|| elkgraph.getProperty(
LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_LEFT_TO_RIGHT
|| elkgraph.getProperty(
LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_RIGHT_TO_LEFT
|| elkgraph.getProperty(
LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.BF_MODEL_ORDER
|| elkgraph.getProperty(
LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.DF_MODEL_ORDER;
}
// Maybe add the explicit strategies here
|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE

|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_NODE_INFLUENCE) != 0
|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_PORT_INFLUENCE) != 0;
return modelOrderCycleBreaking || modelOrderLayering || modelOrderCrossingMinimization;
}


/**
* Checks if the given node has any inside self loops.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,10 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
final int previousLayerIndex = layerIndex == 0 ? 0 : layerIndex - 1;
Layer previousLayer = graph.getLayers().get(previousLayerIndex);
// Sort nodes before port sorting to have sorted nodes for in-layer feedback edge dummies.
ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer,
ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(graph, previousLayer,
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), true);
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY),
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CM_GROUP_ORDER_STRATEGY), true);
SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator);
for (LNode node : layer.getNodes()) {
if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER
Expand All @@ -75,17 +76,18 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
// (their minimal) model order.
// Get minimal model order for target node
Collections.sort(node.getPorts(),
new ModelOrderPortComparator(previousLayer,
new ModelOrderPortComparator(graph, previousLayer,
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
longEdgeTargetNodePreprocessing(node),
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER)));
progressMonitor.log("Node " + node + " ports: " + node.getPorts());
}
}
// Sort nodes after port sorting to also sort dummy feedback nodes from the current layer correctly.
comparator = new ModelOrderNodeComparator(previousLayer,
comparator = new ModelOrderNodeComparator(graph, previousLayer,
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), false);
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY),
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CM_GROUP_ORDER_STRATEGY), false);
SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator);

progressMonitor.log("Layer " + layerIndex + ": " + layer);
Expand Down
Loading