Working on minimizing crossings

This commit is contained in:
2025-09-05 10:32:23 +02:00
parent 2d36d60020
commit 9f78c3e44a
3 changed files with 109 additions and 13 deletions

View File

@ -13,8 +13,9 @@ import javax.swing.SwingUtilities
data class Edge(val id: Int, val isWrite: Boolean) data class Edge(val id: Int, val isWrite: Boolean)
fun getRegGraph(): IGraph<String, Edge> { fun getRegGraph(): IGraph<String, Edge> {
val graph = BaseGraph<String, Edge>() val graph = BaseGraph<String, Edge>()
val roots = listOf(647, 645, 643, 530, 519, 512, 513, 522, 523, 631, 632, 633, 655).sorted() val allowed = setOf(633, 631, 632, 634, 638, 635, 636)
val others = listOf(533, 550, 547, 552, 637, 638, 635, 634, 548, 659, 657, 636, 639, 557, 642, 640, 644, 646, 648, 650, 652, 653, 654, 656, 658).sorted() val roots = listOf(647, 645, 643, 530, 519, 512, 513, 522, 523, 631, 632, 633, 655).sorted().filter { it in allowed }
val others = listOf(533, 550, 547, 552, 637, 638, 635, 634, 548, 659, 657, 636, 639, 557, 642, 640, 644, 646, 648, 650, 652, 653, 654, 656, 658).sorted().filter { it in allowed }
val regs = roots.associateWith { graph.addVertex("%reg$it", true) } + others.associateWith { graph.addVertex("%reg$it", false) } val regs = roots.associateWith { graph.addVertex("%reg$it", true) } + others.associateWith { graph.addVertex("%reg$it", false) }
@ -24,10 +25,10 @@ fun getRegGraph(): IGraph<String, Edge> {
632 to 634, 633 to 634, 633 to 636, 547 to 548, 637 to 659, 637 to 557, 637 to 639, 637 to 657, 635 to 636, 639 to 642, 639 to 640, 639 to 644, 646 to 648, 632 to 634, 633 to 634, 633 to 636, 547 to 548, 637 to 659, 637 to 557, 637 to 639, 637 to 657, 635 to 636, 639 to 642, 639 to 640, 639 to 644, 646 to 648,
648 to 650, 639 to 648, 639 to 650, 639 to 653, 650 to 652, 650 to 652, 650 to 656, 650 to 654, 652 to 653, 654 to 656, 655 to 656, 650 to 658, 557 to 642, 648 to 650, 639 to 648, 639 to 650, 639 to 653, 650 to 652, 650 to 652, 650 to 656, 650 to 654, 652 to 653, 654 to 656, 655 to 656, 650 to 658, 557 to 642,
557 to 654, 644 to 646, 657 to 658 557 to 654, 644 to 646, 657 to 658
).map { it to false } ).filter{ (f,s) -> f in allowed && s in allowed }.map { it to false }
val writes = listOf( val writes = listOf(
656 to 643, 550 to 519, 548 to 513, 552 to 522, 658 to 522, 637 to 522, 636 to 631, 634 to 631 656 to 643, 550 to 519, 548 to 513, 552 to 522, 658 to 522, 637 to 522, 636 to 631, 634 to 631
).map { it to true } ).filter{ (f,s) -> f in allowed && s in allowed }.map { it to true }
(normal + writes).forEach { (p, isWrite) -> (normal + writes).forEach { (p, isWrite) ->
val (from, to) = p val (from, to) = p
graph.connect(regs[from]!!, regs[to]!!, Edge(count++, isWrite)) graph.connect(regs[from]!!, regs[to]!!, Edge(count++, isWrite))
@ -37,11 +38,12 @@ fun getRegGraph(): IGraph<String, Edge> {
} }
fun getRegPane(graph: IGraph<String, Edge>): GraphPane<String, Edge> = GraphPane(graph) { pane, graph -> fun getRegPane(graph: IGraph<String, Edge>): GraphPane<String, Edge> = GraphPane(graph) { pane, graph ->
PseudoForestLayout(graph, 10.0f, 20.0f, 10.0f, { it.isWrite }) { v -> PseudoForestLayout(graph, 10.0f, 20.0f, 10.0f, { it, p -> it.isWrite && p == PseudoForestLayout.LayoutPhase.LAYERING }) { v ->
(pane.getComponentFor(v) as DefaultVertexComponent).vertexSize() (pane.getComponentFor(v) as DefaultVertexComponent).vertexSize()
} }
}.also { pane -> }.also { pane ->
val edge = QuadraticEdge<Edge>( val edge = QuadraticEdge<Edge>(
delta = 0.0f,
color = { if(it.isWrite) Color.ORANGE.darker() else Color.BLACK } color = { if(it.isWrite) Color.ORANGE.darker() else Color.BLACK }
) )
pane.setEdgeRenderer(edge) pane.setEdgeRenderer(edge)

View File

@ -43,10 +43,22 @@ class PseudoForestLayout<V, E>(
var horizontalMargin: Float, var horizontalMargin: Float,
var disjoinXMargin: Float, var disjoinXMargin: Float,
var interLayer: Float, var interLayer: Float,
val ignoreInLayout: (E) -> Boolean = { false }, val ignoreInLayout: (E, LayoutPhase) -> Boolean = { _, _ -> false },
vertexSize: (V) -> VertexSize vertexSize: (V) -> VertexSize
) : ILayout<V, E> ) : ILayout<V, E>
{ {
/**
* An enum representing the different phases of the layout process where edges can be ignored.
* - [LAYERING]: during the layering phase, where back-edges are ignored to determine layers.
* - [DISJOINTS]: during the disjoint graph computation phase, where edges connecting disjoint subgraphs can be ignored.
* - [SLOT_ASSIGNMENT]: during the slot assignment phase, where certain edges may be ignored to optimize layout.
*/
enum class LayoutPhase {
LAYERING,
DISJOINTS,
SLOT_ASSIGNMENT
}
/** /**
* A class representing data on the size of a vertex, including its label offset and size. * A class representing data on the size of a vertex, including its label offset and size.
* *
@ -124,6 +136,7 @@ class PseudoForestLayout<V, E>(
override fun equals(other: Any?): Boolean = other is Conn<*> && other._id == _id override fun equals(other: Any?): Boolean = other is Conn<*> && other._id == _id
override fun hashCode(): Int = _id.hashCode() override fun hashCode(): Int = _id.hashCode()
override fun toString(): String = "C[${from?.x?.fold({ it.toString() }) { "C" } ?: "null"}->${to?.x?.fold({ it.toString() }) { "C" } ?: "null"}@$_id]"
companion object { companion object {
private var _nextId = 0 private var _nextId = 0
@ -133,6 +146,8 @@ class PseudoForestLayout<V, E>(
constructor(x: V): this(x.sum1()) constructor(x: V): this(x.sum1())
constructor(x: Conn<V>): this(x.sum2()) constructor(x: Conn<V>): this(x.sum2())
override fun toString(): String = x.fold({ it.toString() }) { it.toString() }
fun same(other: Vert<V>) = x.fold({ it1 -> fun same(other: Vert<V>) = x.fold({ it1 ->
other.x.fold({ it2 -> it1 == it2 }) { false } other.x.fold({ it2 -> it1 == it2 }) { false }
}) { it1 -> }) { it1 ->
@ -141,7 +156,8 @@ class PseudoForestLayout<V, E>(
fun directParentOf(other: Vert<V>, graph: IGraph<V, *>): Boolean = x.fold({ it1 -> fun directParentOf(other: Vert<V>, graph: IGraph<V, *>): Boolean = x.fold({ it1 ->
other.x.fold({ it2 -> graph.xToY(it1, it2) != null }) { it2 -> other.x.fold({ it2 -> graph.xToY(it1, it2) != null }) { it2 ->
it2.from == this println(" - Checking direct parenthood between $it1 and dummy $it2 (${it2.from}): ${it2.from?.x == it1}")
it2.from?.x == it1
} }
}) { it1 -> }) { it1 ->
it1.to!!.same(other) it1.to!!.same(other)
@ -161,9 +177,60 @@ class PseudoForestLayout<V, E>(
else (v.x.asT2).to = chain[i + 1] else (v.x.asT2).to = chain[i + 1]
} }
println(" - Breaking edge $from -> $to with chain: $chain")
return chain return chain
} }
private fun reachableFrom(start: V, phase: LayoutPhase): Set<V> {
val seen = mutableSetOf<V>()
val queue = ArrayDeque<V>()
queue.add(start)
while(!queue.isEmpty()) {
val v = queue.removeFirst()
if(seen.add(v)) {
_graph.successors(v).forEach { (succ, edge) ->
if(ignoreInLayout(edge, phase)) return@forEach
if(succ !in seen) queue.addLast(succ)
}
}
}
return seen
}
private fun computeCrossings(lTop: List<Vert<V>>, lBot: List<Vert<V>>, edges: List<Pair<Vert<V>, Vert<V>>>): Int {
var count = 0
println(" - Computing crossings between layers:")
println(" - Top: $lTop")
println(" - Bot: $lBot")
println(" - Edges: $edges")
val conns = edges.map { (top, bot) -> lTop.indexOf(top) to lBot.indexOf(bot) }.sortedWith { (t1, b1), (t2, b2) ->
if (t1 != t2) t1 - t2
else b1 - b2
}
println(" - Connections (by index): $conns")
conns.forEachIndexed { i, conn ->
for(j in i + 1 until conns.size) {
val other = conns[j]
if(conn.first == other.first || conn.second == other.second) continue // shared vertex -> cannot cross
val topFirst = conn.first < other.first
val botFirst = conn.second < other.second
if(topFirst != botFirst) {
println(" - Crossing between ${lTop[conn.first]}->${lBot[conn.second]} and ${lTop[other.first]}->${lBot[other.second]}")
count++
}
}
}
return count
}
override fun compute() { override fun compute() {
println("Acquiring lock") println("Acquiring lock")
locked { locked {
@ -188,7 +255,7 @@ class PseudoForestLayout<V, E>(
val succLayer = layer + 1 val succLayer = layer + 1
_graph.successors(vertex).forEach { (succ, edge) -> _graph.successors(vertex).forEach { (succ, edge) ->
if(ignoreInLayout(edge)) { if(ignoreInLayout(edge, LayoutPhase.LAYERING)) {
println(" - Ignoring edge $edge for layout") println(" - Ignoring edge $edge for layout")
return@forEach return@forEach
} }
@ -258,9 +325,7 @@ class PseudoForestLayout<V, E>(
// Compute disjoint graphs // Compute disjoint graphs
val disjoint = roots.fold(listOf<Set<V>>()) { acc, root -> val disjoint = roots.fold(listOf<Set<V>>()) { acc, root ->
val reachable = layers[root]?.second?.toMutableSet() ?: return@fold acc val reachable = reachableFrom(root, LayoutPhase.DISJOINTS).toMutableSet()
reachable += root
val dedup = acc.mapNotNull { other -> val dedup = acc.mapNotNull { other ->
val inter = reachable intersect other val inter = reachable intersect other
if(inter.isEmpty()) other // fully disjoint -> keep if(inter.isEmpty()) other // fully disjoint -> keep
@ -300,6 +365,12 @@ class PseudoForestLayout<V, E>(
layered[idx + offset + 1] += dummy layered[idx + offset + 1] += dummy
} }
} }
else if(otherLayer < idx - 1) {
val chain = buildChain(other, node, otherLayer, idx)
chain.forEachIndexed { offset, dummy ->
layered[otherLayer + offset + 1] += dummy
}
}
} }
}) {} // do nothing on dummy nodes }) {} // do nothing on dummy nodes
} }
@ -336,6 +407,29 @@ class PseudoForestLayout<V, E>(
val maxWidth = layerWidths.max() val maxWidth = layerWidths.max()
// TODO: do some reorderings to minimize #crossings? // TODO: do some reorderings to minimize #crossings?
println(" - Optimizing slot assignments")
layered.forEachIndexed { idx, list ->
println(" - Layer $idx: $list")
}
val edges = mutableListOf<MutableList<Pair<Vert<V>, Vert<V>>>>()
val crossings = mutableListOf<Int>()
for(i in 1 until layered.size) {
edges.add(mutableListOf())
val current = edges[i - 1]
layered[i].forEach { vBot ->
val forVBot = layered[i - 1].filter {
println(" - Checking edge between $it and $vBot: ${it.directParentOf(vBot, _graph)} || ${vBot.directParentOf(it, _graph)}")
it.directParentOf(vBot, _graph) || vBot.directParentOf(it, _graph)
}.map { it to vBot }
current.addAll(forVBot)
}
val c = computeCrossings(layered[i - 1], layered[i], current)
crossings.add(c)
println(" - Connections between layer ${i - 1} and $i have $c crossings")
}
// Assign x positions // Assign x positions
layered.forEachIndexed { idx, layer -> layered.forEachIndexed { idx, layer ->

View File

@ -22,7 +22,7 @@ sealed class SumType<out T1, out T2> {
*/ */
class SumT1<out T1>(val value: T1) : SumType<T1, Nothing>() { class SumT1<out T1>(val value: T1) : SumType<T1, Nothing>() {
override fun equals(other: Any?): Boolean = override fun equals(other: Any?): Boolean =
(other is SumT1<*> && value == other.value) //|| value == other (other is SumT1<*> && value == other.value) || value == other
override fun hashCode(): Int = value.hashCode() override fun hashCode(): Int = value.hashCode()
} }
@ -39,7 +39,7 @@ sealed class SumType<out T1, out T2> {
*/ */
class SumT2<out T2>(val value: T2) : SumType<Nothing, T2>() { class SumT2<out T2>(val value: T2) : SumType<Nothing, T2>() {
override fun equals(other: Any?): Boolean = override fun equals(other: Any?): Boolean =
(other is SumT2<*> && value == other.value) //|| value == other (other is SumT2<*> && value == other.value) || value == other
override fun hashCode(): Int = value.hashCode() override fun hashCode(): Int = value.hashCode()
} }