diff --git a/src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt b/src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt index e3af410..c80f0f9 100644 --- a/src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt +++ b/src/main/kotlin/com/jaytux/altgraph/examples/SimpleRegDep.kt @@ -13,8 +13,9 @@ import javax.swing.SwingUtilities data class Edge(val id: Int, val isWrite: Boolean) fun getRegGraph(): IGraph { val graph = BaseGraph() - val roots = listOf(647, 645, 643, 530, 519, 512, 513, 522, 523, 631, 632, 633, 655).sorted() - 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 allowed = setOf(633, 631, 632, 634, 638, 635, 636) + 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) } @@ -24,10 +25,10 @@ fun getRegGraph(): IGraph { 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, 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( 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) -> val (from, to) = p graph.connect(regs[from]!!, regs[to]!!, Edge(count++, isWrite)) @@ -37,11 +38,12 @@ fun getRegGraph(): IGraph { } fun getRegPane(graph: IGraph): GraphPane = 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() } }.also { pane -> val edge = QuadraticEdge( + delta = 0.0f, color = { if(it.isWrite) Color.ORANGE.darker() else Color.BLACK } ) pane.setEdgeRenderer(edge) diff --git a/src/main/kotlin/com/jaytux/altgraph/layout/PseudoForestLayout.kt b/src/main/kotlin/com/jaytux/altgraph/layout/PseudoForestLayout.kt index e268627..97a75e2 100644 --- a/src/main/kotlin/com/jaytux/altgraph/layout/PseudoForestLayout.kt +++ b/src/main/kotlin/com/jaytux/altgraph/layout/PseudoForestLayout.kt @@ -43,10 +43,22 @@ class PseudoForestLayout( var horizontalMargin: Float, var disjoinXMargin: Float, var interLayer: Float, - val ignoreInLayout: (E) -> Boolean = { false }, + val ignoreInLayout: (E, LayoutPhase) -> Boolean = { _, _ -> false }, vertexSize: (V) -> VertexSize ) : ILayout { + /** + * 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. * @@ -124,6 +136,7 @@ class PseudoForestLayout( override fun equals(other: Any?): Boolean = other is Conn<*> && other._id == _id 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 { private var _nextId = 0 @@ -133,6 +146,8 @@ class PseudoForestLayout( constructor(x: V): this(x.sum1()) constructor(x: Conn): this(x.sum2()) + override fun toString(): String = x.fold({ it.toString() }) { it.toString() } + fun same(other: Vert) = x.fold({ it1 -> other.x.fold({ it2 -> it1 == it2 }) { false } }) { it1 -> @@ -141,7 +156,8 @@ class PseudoForestLayout( fun directParentOf(other: Vert, graph: IGraph): Boolean = x.fold({ it1 -> 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.to!!.same(other) @@ -161,9 +177,60 @@ class PseudoForestLayout( else (v.x.asT2).to = chain[i + 1] } + println(" - Breaking edge $from -> $to with chain: $chain") return chain } + private fun reachableFrom(start: V, phase: LayoutPhase): Set { + val seen = mutableSetOf() + val queue = ArrayDeque() + 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>, lBot: List>, edges: List, Vert>>): 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() { println("Acquiring lock") locked { @@ -188,7 +255,7 @@ class PseudoForestLayout( val succLayer = layer + 1 _graph.successors(vertex).forEach { (succ, edge) -> - if(ignoreInLayout(edge)) { + if(ignoreInLayout(edge, LayoutPhase.LAYERING)) { println(" - Ignoring edge $edge for layout") return@forEach } @@ -258,9 +325,7 @@ class PseudoForestLayout( // Compute disjoint graphs val disjoint = roots.fold(listOf>()) { acc, root -> - val reachable = layers[root]?.second?.toMutableSet() ?: return@fold acc - reachable += root - + val reachable = reachableFrom(root, LayoutPhase.DISJOINTS).toMutableSet() val dedup = acc.mapNotNull { other -> val inter = reachable intersect other if(inter.isEmpty()) other // fully disjoint -> keep @@ -300,6 +365,12 @@ class PseudoForestLayout( 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 } @@ -336,6 +407,29 @@ class PseudoForestLayout( val maxWidth = layerWidths.max() // TODO: do some reorderings to minimize #crossings? + println(" - Optimizing slot assignments") + layered.forEachIndexed { idx, list -> + println(" - Layer $idx: $list") + } + + val edges = mutableListOf, Vert>>>() + val crossings = mutableListOf() + 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 layered.forEachIndexed { idx, layer -> diff --git a/src/main/kotlin/com/jaytux/altgraph/layout/SumType.kt b/src/main/kotlin/com/jaytux/altgraph/layout/SumType.kt index 33407ed..a7b1800 100644 --- a/src/main/kotlin/com/jaytux/altgraph/layout/SumType.kt +++ b/src/main/kotlin/com/jaytux/altgraph/layout/SumType.kt @@ -22,7 +22,7 @@ sealed class SumType { */ class SumT1(val value: T1) : SumType() { 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() } @@ -39,7 +39,7 @@ sealed class SumType { */ class SumT2(val value: T2) : SumType() { 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() }