Working on minimizing crossings
This commit is contained in:
@ -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)
|
||||||
|
@ -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 ->
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user