Skip to content

Commit 806f4cb

Browse files
Merge pull request #1147 from soerendomroes/sdo/groupModelOrder
Added Group Model Order thesis strategies
2 parents 54c88aa + 44d2d8a commit 806f4cb

21 files changed

+1468
-114
lines changed

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/Layered.melk

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,23 @@ algorithm layered(LayeredLayoutProvider) {
201201
supports org.eclipse.elk.interactiveLayout
202202
supports layering.layerId
203203
supports crossingMinimization.positionId
204+
204205
supports considerModelOrder.strategy
205206
supports considerModelOrder.longEdgeStrategy
206207
supports considerModelOrder.crossingCounterNodeInfluence
207208
supports considerModelOrder.crossingCounterPortInfluence
208209
supports considerModelOrder.noModelOrder
209210
supports considerModelOrder.components
210211
supports considerModelOrder.portModelOrder
212+
supports considerModelOrder.groupModelOrder.cycleBreakingId
213+
supports considerModelOrder.groupModelOrder.crossingMinimizationId
214+
supports considerModelOrder.groupModelOrder.componentGroupId
215+
supports considerModelOrder.groupModelOrder.cbGroupOrderStrategy
216+
supports considerModelOrder.groupModelOrder.cmGroupOrderStrategy
217+
supports considerModelOrder.groupModelOrder.cmEnforcedGroupOrders
218+
supports considerModelOrder.groupModelOrder.cbPreferredSourceId
219+
supports considerModelOrder.groupModelOrder.cbPreferredTargetId
220+
211221
supports generatePositionAndLayerIds
212222
}
213223

@@ -1078,6 +1088,73 @@ group considerModelOrder {
10781088
targets parents
10791089
requires org.eclipse.elk.alg.layered.considerModelOrder.strategy
10801090
}
1091+
1092+
group groupModelOrder {
1093+
option cycleBreakingId: int {
1094+
label "Group ID of the Node Type"
1095+
description
1096+
"Used to define partial ordering groups during cycle breaking. A lower group id means that the group is
1097+
sorted before other groups. A group model order of 0 is the default group."
1098+
default = 0
1099+
targets nodes
1100+
requires noModelOrder == false
1101+
}
1102+
option crossingMinimizationId: int {
1103+
label "Group ID of the Node Type"
1104+
description
1105+
"Used to define partial ordering groups during crossing minimization. A lower group id means that the group is
1106+
sorted before other groups. A group model order of 0 is the default group."
1107+
default = 0
1108+
targets nodes, edges, ports
1109+
requires noModelOrder == false
1110+
}
1111+
option componentGroupId: int {
1112+
label "Group ID of the Node Type"
1113+
description
1114+
"Used to define partial ordering groups during component packing. A lower group id means that the group is
1115+
sorted before other groups. A group model order of 0 is the default group."
1116+
default = 0
1117+
targets nodes, edges, ports
1118+
requires noModelOrder == false
1119+
}
1120+
option cbGroupOrderStrategy: GroupOrderStrategy {
1121+
label "Cycle Breaking Group Ordering Strategy"
1122+
description "Determines how to count ordering violations during cycle breaking. NONE: They do not
1123+
count. ENFORCED: A group with a higher model order is before a node with a smaller.
1124+
MODEL_ORDER: The model order counts instead of the model order group id ordering."
1125+
default = GroupOrderStrategy.ONLY_WITHIN_GROUP
1126+
targets parents
1127+
}
1128+
option cbPreferredSourceId: int {
1129+
label "Cycle Breaking Preferred Source Id"
1130+
description "The model order group id for which should be preferred as a source if possible."
1131+
targets parents
1132+
requires cycleBreaking.strategy == CycleBreakingStrategy.SCC_NODE_TYPE
1133+
}
1134+
option cbPreferredTargetId: int {
1135+
label "Cycle Breaking Preferred Target Id"
1136+
description "The model order group id for which should be preferred as a target if possible."
1137+
targets parents
1138+
requires cycleBreaking.strategy == CycleBreakingStrategy.SCC_NODE_TYPE
1139+
}
1140+
option cmGroupOrderStrategy: GroupOrderStrategy {
1141+
label "Crossing Minimization Group Ordering Strategy"
1142+
description "Determines how to count ordering violations during crossing minimization. NONE: They do not
1143+
count. ENFORCED: A group with a lower id is before a group with a higher id.
1144+
MODEL_ORDER: The model order counts instead of the model order group id ordering."
1145+
default = GroupOrderStrategy.ONLY_WITHIN_GROUP
1146+
targets parents
1147+
}
1148+
option cmEnforcedGroupOrders: List<Integer> {
1149+
label "Crossing Minimization Enforced Group Orders"
1150+
description "Holds all group ids which are enforcing their order during crossing minimization strategies.
1151+
E.g. if only groups 2 and -1 (default) enforce their ordering. Other groups e.g. the group of
1152+
timer nodes can be ordered arbitrarily if it helps and the mentioned groups may not change
1153+
their order."
1154+
targets parents
1155+
default = #[1, 2, 6, 7, 10, 11]
1156+
}
1157+
}
10811158
}
10821159

10831160
advanced option directionCongruency: DirectionCongruency {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Kiel University and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*******************************************************************************/
10+
package org.eclipse.elk.alg.layered.graph;
11+
12+
import java.util.HashMap;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.Set;
16+
import java.util.Stack;
17+
18+
import org.eclipse.elk.alg.layered.options.InternalProperties;
19+
20+
/**
21+
* Tarjan implementation to be used during layered layout.
22+
*/
23+
24+
25+
public class Tarjan {
26+
27+
public Tarjan (List<LEdge> edgesToBeReversed, List<Set<LNode>> stronglyConnectedComponents,
28+
HashMap <LNode,Integer> nodeToSCCID) {
29+
this.edgesToBeReversed = edgesToBeReversed;
30+
this.stronglyConnectedComponents = stronglyConnectedComponents;
31+
this.nodeToSCCID = nodeToSCCID;
32+
}
33+
34+
private List<LEdge> edgesToBeReversed;
35+
private int index = 0;
36+
protected List<Set<LNode>> stronglyConnectedComponents; // FIXME Why no ordered set here? this is bad
37+
private Stack<LNode> stack = new Stack<LNode>();
38+
private HashMap <LNode,Integer> nodeToSCCID = new HashMap<>();
39+
40+
public void tarjan(final LGraph graph) {
41+
index = 0;
42+
stack = new Stack<LNode>();
43+
for (LNode node : graph.getLayerlessNodes()) {
44+
if (node.getProperty(InternalProperties.TARJAN_ID) == -1) {
45+
stronglyConnected(node);
46+
stack.clear();
47+
}
48+
}
49+
}
50+
51+
public void stronglyConnected(final LNode v) {
52+
v.setProperty(InternalProperties.TARJAN_ID, index);
53+
v.setProperty(InternalProperties.TARJAN_LOWLINK, index);
54+
index++;
55+
stack.push(v);
56+
v.setProperty(InternalProperties.TARJAN_ON_STACK, true);
57+
for (LEdge edge : v.getConnectedEdges()) {
58+
if (edge.getSource().getNode() != v && !edgesToBeReversed.contains(edge)) {
59+
continue;
60+
}
61+
if (edge.getSource().getNode() == v && edgesToBeReversed.contains(edge)) {
62+
continue;
63+
}
64+
LNode target = null;
65+
if (edge.getTarget().getNode() == v) {
66+
target = edge.getSource().getNode();
67+
} else {
68+
target = edge.getTarget().getNode();
69+
}
70+
if (target.getProperty(InternalProperties.TARJAN_ID) == -1) {
71+
stronglyConnected(target);
72+
v.setProperty(InternalProperties.TARJAN_LOWLINK,
73+
Math.min(v.getProperty(InternalProperties.TARJAN_LOWLINK),
74+
target.getProperty(InternalProperties.TARJAN_LOWLINK)));
75+
} else if (target.getProperty(InternalProperties.TARJAN_ON_STACK)) {
76+
v.setProperty(InternalProperties.TARJAN_LOWLINK,
77+
Math.min(v.getProperty(InternalProperties.TARJAN_LOWLINK),
78+
target.getProperty(InternalProperties.TARJAN_ID)));
79+
}
80+
}
81+
if (v.getProperty(InternalProperties.TARJAN_LOWLINK) == v.getProperty(InternalProperties.TARJAN_ID)) {
82+
Set<LNode> sCC = new HashSet<LNode>();
83+
LNode n = null;
84+
do {
85+
n = stack.pop();
86+
n.setProperty(InternalProperties.TARJAN_ON_STACK, false);
87+
sCC.add(n);
88+
} while (v != n);
89+
if (sCC.size() >1) {
90+
int index = stronglyConnectedComponents.size();
91+
stronglyConnectedComponents.add(sCC);
92+
for (LNode node : sCC) {
93+
nodeToSCCID.put(node, index);
94+
}
95+
}
96+
}
97+
}
98+
99+
public void resetTarjan(final LGraph graph) {
100+
for (LNode n : graph.getLayerlessNodes()) {
101+
n.setProperty(InternalProperties.TARJAN_ON_STACK, false);
102+
n.setProperty(InternalProperties.TARJAN_LOWLINK, -1);
103+
n.setProperty(InternalProperties.TARJAN_ID, -1);
104+
stack.clear();
105+
for (LEdge e : n.getConnectedEdges()) {
106+
e.setProperty(InternalProperties.IS_PART_OF_CYCLE, false);
107+
}
108+
}
109+
}
110+
}

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/graph/transform/ElkGraphImporter.java

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
package org.eclipse.elk.alg.layered.graph.transform;
1111

1212
import java.util.EnumSet;
13+
import java.util.HashSet;
1314
import java.util.Iterator;
1415
import java.util.List;
1516
import java.util.Map;
@@ -228,15 +229,24 @@ private void calculateMinimumGraphSize(final ElkNode elkgraph, final LGraph lgra
228229
private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
229230
// Transform the node's children, unless we're told not to
230231
int index = 0;
232+
HashSet<Integer> cbGroupModelOrders = new HashSet<>();
231233
for (ElkNode child : elkgraph.getChildren()) {
232234
if (!child.getProperty(LayeredOptions.NO_LAYOUT)) {
233235
if (needsModelOrder(child)) {
234236
child.setProperty(InternalProperties.MODEL_ORDER, index);
235237
index++;
238+
if (child.hasProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID)) {
239+
cbGroupModelOrders.add(child.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CYCLE_BREAKING_ID));
240+
}
236241
}
237242
transformNode(child, lgraph);
238243
}
239244
}
245+
// Save the maximum node model order.
246+
// This is relevant to create graph partitions based on model order and constraints.
247+
lgraph.setProperty(InternalProperties.MAX_MODEL_ORDER_NODES, index);
248+
// Save the number of model order groups.
249+
lgraph.setProperty(InternalProperties.CB_NUM_MODEL_ORDER_GROUPS, cbGroupModelOrders.size());
240250

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

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

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

362381
// Model order index for edges.
363382
index = 0;
@@ -458,22 +477,29 @@ private boolean needsModelOrder(final ElkNode child) {
458477
* @return True, if model order should be set.
459478
*/
460479
private boolean needsModelOrderBasedOnParent(final ElkNode elkgraph) {
461-
return (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
462-
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.MODEL_ORDER
463-
|| elkgraph
464-
.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.GREEDY_MODEL_ORDER
480+
481+
boolean modelOrderCycleBreaking = elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.MODEL_ORDER
482+
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.BFS_NODE_ORDER
483+
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.DFS_NODE_ORDER
484+
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.GREEDY_MODEL_ORDER
485+
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.SCC_CONNECTIVITY
486+
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY) == CycleBreakingStrategy.SCC_NODE_TYPE;
487+
boolean modelOrderLayering = elkgraph.getProperty(LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.BF_MODEL_ORDER
488+
|| elkgraph.getProperty(LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.DF_MODEL_ORDER
489+
|| elkgraph.getProperty(LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_LEFT_TO_RIGHT
490+
|| elkgraph.getProperty(LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_RIGHT_TO_LEFT;
491+
boolean modelOrderCrossingMinimization =
492+
// Maybe add the explicit strategies here
493+
elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
465494
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)
466-
|| elkgraph
467-
.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE)
468-
|| elkgraph.getProperty(
469-
LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_LEFT_TO_RIGHT
470-
|| elkgraph.getProperty(
471-
LayeredOptions.LAYERING_NODE_PROMOTION_STRATEGY) == NodePromotionStrategy.MODEL_ORDER_RIGHT_TO_LEFT
472-
|| elkgraph.getProperty(
473-
LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.BF_MODEL_ORDER
474-
|| elkgraph.getProperty(
475-
LayeredOptions.LAYERING_STRATEGY) == LayeringStrategy.DF_MODEL_ORDER;
476-
}
495+
// Maybe add the explicit strategies here
496+
|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_COMPONENTS) != ComponentOrderingStrategy.NONE
497+
498+
|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_NODE_INFLUENCE) != 0
499+
|| elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_CROSSING_COUNTER_PORT_INFLUENCE) != 0;
500+
return modelOrderCycleBreaking || modelOrderLayering || modelOrderCrossingMinimization;
501+
}
502+
477503

478504
/**
479505
* Checks if the given node has any inside self loops.

plugins/org.eclipse.elk.alg.layered/src/org/eclipse/elk/alg/layered/intermediate/SortByInputModelProcessor.java

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,10 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
6262
final int previousLayerIndex = layerIndex == 0 ? 0 : layerIndex - 1;
6363
Layer previousLayer = graph.getLayers().get(previousLayerIndex);
6464
// Sort nodes before port sorting to have sorted nodes for in-layer feedback edge dummies.
65-
ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(previousLayer,
65+
ModelOrderNodeComparator comparator = new ModelOrderNodeComparator(graph, previousLayer,
6666
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
67-
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), true);
67+
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY),
68+
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CM_GROUP_ORDER_STRATEGY), true);
6869
SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator);
6970
for (LNode node : layer.getNodes()) {
7071
if (node.getProperty(LayeredOptions.PORT_CONSTRAINTS) != PortConstraints.FIXED_ORDER
@@ -75,17 +76,18 @@ public void process(final LGraph graph, final IElkProgressMonitor progressMonito
7576
// (their minimal) model order.
7677
// Get minimal model order for target node
7778
Collections.sort(node.getPorts(),
78-
new ModelOrderPortComparator(previousLayer,
79+
new ModelOrderPortComparator(graph, previousLayer,
7980
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
8081
longEdgeTargetNodePreprocessing(node),
8182
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_PORT_MODEL_ORDER)));
8283
progressMonitor.log("Node " + node + " ports: " + node.getPorts());
8384
}
8485
}
8586
// Sort nodes after port sorting to also sort dummy feedback nodes from the current layer correctly.
86-
comparator = new ModelOrderNodeComparator(previousLayer,
87+
comparator = new ModelOrderNodeComparator(graph, previousLayer,
8788
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY),
88-
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY), false);
89+
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_LONG_EDGE_STRATEGY),
90+
graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_GROUP_MODEL_ORDER_CM_GROUP_ORDER_STRATEGY), false);
8991
SortByInputModelProcessor.insertionSort(layer.getNodes(), comparator);
9092

9193
progressMonitor.log("Layer " + layerIndex + ": " + layer);

0 commit comments

Comments
 (0)