Skip to content

Commit

Permalink
Node Order Barycenter Heuristic (#777)
Browse files Browse the repository at this point in the history
  • Loading branch information
soerendomroes committed Mar 21, 2022
1 parent 2026c8f commit 68de8ea
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 22 deletions.
Expand Up @@ -131,6 +131,7 @@ algorithm layered(LayeredLayoutProvider) {
supports org.eclipse.elk.alg.layered.layering.layerConstraint
supports org.eclipse.elk.alg.layered.cycleBreaking.strategy
supports org.eclipse.elk.alg.layered.crossingMinimization.strategy
supports org.eclipse.elk.alg.layered.crossingMinimization.forceNodeModelOrder
supports org.eclipse.elk.alg.layered.crossingMinimization.greedySwitch.activationThreshold
supports org.eclipse.elk.alg.layered.crossingMinimization.greedySwitch.type
supports org.eclipse.elk.alg.layered.crossingMinimization.greedySwitchHierarchical.type
Expand Down Expand Up @@ -332,6 +333,17 @@ group crossingMinimization {
default = CrossingMinimizationStrategy.LAYER_SWEEP
targets parents
}

option forceNodeModelOrder: boolean {
label "Force Node Model Order"
description
"The node order given by the model does not change to produce a better layout. E.g. if node A
is before node B in the model this is not changed during crossing minimization. This assumes that the
node model order is already respected before crossing minimization. This can be achieved by setting
considerModelOrder.strategy to NODES_AND_EDGES."
default = false
targets parents
}

advanced option hierarchicalSweepiness: double {
label "Hierarchical Sweepiness"
Expand Down
Expand Up @@ -142,7 +142,8 @@ public List<LGraph> split(final LGraph graph) {
}
// If model order should be preserved the connected components should be ordered by their elements.
// The component with the node with the smallest order should be first.
if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE) {
if (graph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| graph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
Collections.sort(result, (g1, g2) -> {
int g1Order = Integer.MAX_VALUE;
for (LNode node : g1.getLayerlessNodes()) {
Expand Down
Expand Up @@ -71,7 +71,8 @@ public void combine(final List<LGraph> components, final LGraph target) {
public int compare(final LGraph graph1, final LGraph graph2) {
int prio = graph2.id - graph1.id;
if (prio == 0) {
if (graph1.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) == OrderingStrategy.NONE) {
if (graph1.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) == OrderingStrategy.NONE
&& !graph1.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
double size1 = graph1.getSize().x * graph1.getSize().y;
double size2 = graph2.getSize().x * graph2.getSize().y;
return Double.compare(size1, size2);
Expand Down
Expand Up @@ -227,7 +227,8 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
if (!child.getProperty(LayeredOptions.NO_LAYOUT)) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER) {
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
child.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
}
Expand All @@ -241,7 +242,8 @@ private void importFlatGraph(final ElkNode elkgraph, final LGraph lgraph) {
for (ElkEdge elkedge : elkgraph.getContainedEdges()) {
if (elkgraph.getProperty(LayeredOptions.CONSIDER_MODEL_ORDER_STRATEGY) != OrderingStrategy.NONE
|| elkgraph.getProperty(LayeredOptions.CYCLE_BREAKING_STRATEGY)
== CycleBreakingStrategy.MODEL_ORDER) {
== CycleBreakingStrategy.MODEL_ORDER
|| elkgraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
elkedge.setProperty(InternalProperties.MODEL_ORDER, index);
index++;
}
Expand Down
Expand Up @@ -17,9 +17,10 @@

import org.eclipse.elk.alg.layered.graph.LNode;
import org.eclipse.elk.alg.layered.graph.LNode.NodeType;
import org.eclipse.elk.alg.layered.graph.LPort;
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.alg.layered.graph.LPort;

import com.google.common.collect.Lists;

Expand All @@ -32,18 +33,18 @@
* @author cds
* @author ima
*/
public final class BarycenterHeuristic implements ICrossingMinimizationHeuristic {
public class BarycenterHeuristic implements ICrossingMinimizationHeuristic {

/** the array of port ranks. */
private float[] portRanks;
protected float[] portRanks;
/** the random number generator. */
private final Random random;
protected final Random random;
/** the constraint resolver for ordering constraints. */
private ForsterConstraintResolver constraintResolver;
protected ForsterConstraintResolver constraintResolver;
/** the barycenter values of every node in the graph, indexed by layer.id and node.id. */
private BarycenterState[][] barycenterState;
protected BarycenterState[][] barycenterState;
/** The Barycenter PortDistributor is used to ask for the port ranks.*/
private final AbstractBarycenterPortDistributor portDistributor;
protected final AbstractBarycenterPortDistributor portDistributor;

/**
* Constructs a Barycenter heuristic for crossing minimization.
Expand Down Expand Up @@ -83,7 +84,12 @@ public void minimizeCrossings(final List<LNode> layer, final boolean preOrdered,

if (layer.size() > 1) {
// Sort the vertices according to their barycenters
Collections.sort(layer, barycenterStateComparator);
if (layer.get(0).getGraph().getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
ModelOrderBarycenterHeuristic.insertionSort(layer, barycenterStateComparator,
(ModelOrderBarycenterHeuristic) this);
} else {
Collections.sort(layer, barycenterStateComparator);
}

// Resolve ordering constraints
constraintResolver.processConstraints(layer);
Expand All @@ -96,7 +102,7 @@ public void minimizeCrossings(final List<LNode> layer, final boolean preOrdered,
* @param nodes
* a layer
*/
private void randomizeBarycenters(final List<LNode> nodes) {
protected void randomizeBarycenters(final List<LNode> nodes) {
for (LNode node : nodes) {
// Set barycenters only for nodeGroups containing a single node.
stateOf(node).barycenter = random.nextDouble();
Expand All @@ -113,7 +119,7 @@ private void randomizeBarycenters(final List<LNode> nodes) {
* @param preOrdered
* whether the nodeGroups have been ordered in a previous run.
*/
private void fillInUnknownBarycenters(final List<LNode> nodes, final boolean preOrdered) {
protected void fillInUnknownBarycenters(final List<LNode> nodes, final boolean preOrdered) {
// Determine placements for nodes with undefined barycenter value
if (preOrdered) {
double lastValue = -1;
Expand Down Expand Up @@ -175,7 +181,7 @@ private void fillInUnknownBarycenters(final List<LNode> nodes, final boolean pre
* @param forward
* {@code true} if the current sweep moves forward
*/
private void calculateBarycenters(final List<LNode> nodes, final boolean forward) {
protected void calculateBarycenters(final List<LNode> nodes, final boolean forward) {
// Set all visited flags to false
for (LNode node : nodes) {
stateOf(node).visited = false;
Expand Down Expand Up @@ -266,7 +272,7 @@ private void calculateBarycenter(final LNode node, final boolean forward) {
}
}

private BarycenterState stateOf(final LNode node) {
protected BarycenterState stateOf(final LNode node) {
return barycenterState[node.getLayer().id][node.id];
}

Expand Down Expand Up @@ -307,7 +313,7 @@ public String toString() {
/**
* Compares two {@link LNode}s based on their barycenter values.
*/
private final Comparator<LNode> barycenterStateComparator =
protected Comparator<LNode> barycenterStateComparator =
(n1, n2) -> {
BarycenterState s1 = stateOf(n1);
BarycenterState s2 = stateOf(n2);
Expand Down
Expand Up @@ -97,6 +97,11 @@ public GraphInfoHolder(final LGraph graph, final CrossMinType crossMinType, fina
initializables.add(constraintResolver);
crossMinimizer = new BarycenterHeuristic(constraintResolver, random,
(AbstractBarycenterPortDistributor) portDistributor, currentNodeOrder);
} else if (crossMinType == CrossMinType.MODEL_ORDER) {
ForsterConstraintResolver constraintResolver = new ForsterConstraintResolver(currentNodeOrder);
initializables.add(constraintResolver);
crossMinimizer = new ModelOrderBarycenterHeuristic(constraintResolver, random,
(AbstractBarycenterPortDistributor) portDistributor, currentNodeOrder);
} else {
crossMinimizer = new GreedySwitchHeuristic(crossMinType, this);
}
Expand Down
Expand Up @@ -87,7 +87,7 @@ public class LayerSweepCrossingMinimizer
private Set<GraphInfoHolder> graphsWhoseNodeOrderChanged;
private Random random;
private long randomSeed;
private final CrossMinType crossMinType;
private CrossMinType crossMinType;

/**
* Creates LayerSweepHierarchicalCrossingMinimizer using given minimizer type.
Expand Down Expand Up @@ -295,7 +295,7 @@ private double minimizeCrossingsNodePortOrderWithCounter(final GraphInfoHolder g

return oldNumberOfCrossings;
}

/**
* Compares all nodes in a each layer and counts how often they are not in model order.
* This requires that the {@code SortByInputModelProcessor} ran previously.
Expand Down Expand Up @@ -566,6 +566,9 @@ private boolean isOnEndOfSweepSide(final LPort port, final boolean isForwardSwee
* Traverses inclusion breadth-first and initializes each Graph.
*/
private List<GraphInfoHolder> initialize(final LGraph rootGraph) {
if (rootGraph.getProperty(LayeredOptions.CROSSING_MINIMIZATION_FORCE_NODE_MODEL_ORDER)) {
crossMinType = CrossMinType.MODEL_ORDER;
}
graphInfoHolders = Lists.newArrayList();
random = rootGraph.getProperty(InternalProperties.RANDOM);
randomSeed = random.nextLong();
Expand Down Expand Up @@ -620,6 +623,8 @@ public List<GraphInfoHolder> getGraphData() {
public enum CrossMinType {
/** Use BarycenterHeuristic. */
BARYCENTER,
/** Use ModelOrderBarycenterHeuristic. */
MODEL_ORDER,
/** Use one-sided GreedySwitchHeuristic. */
ONE_SIDED_GREEDY_SWITCH,
/** Use two-sided GreedySwitchHeuristic. */
Expand Down

0 comments on commit 68de8ea

Please sign in to comment.