aboutsummaryrefslogtreecommitdiff
path: root/src/main/scala/firrtl/graph/EulerTour.scala
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/scala/firrtl/graph/EulerTour.scala')
-rw-r--r--src/main/scala/firrtl/graph/EulerTour.scala223
1 files changed, 223 insertions, 0 deletions
diff --git a/src/main/scala/firrtl/graph/EulerTour.scala b/src/main/scala/firrtl/graph/EulerTour.scala
new file mode 100644
index 00000000..db25d8d0
--- /dev/null
+++ b/src/main/scala/firrtl/graph/EulerTour.scala
@@ -0,0 +1,223 @@
+// See LICENSE for license details.
+
+package firrtl.graph
+
+import scala.collection.mutable
+
+/** Euler Tour companion object */
+object EulerTour {
+ /** Create an Euler Tour of a `DiGraph[T]` */
+ def apply[T](diGraph: DiGraph[T], start: T): EulerTour[Seq[T]] = {
+ val r = mutable.Map[Seq[T], Int]()
+ val e = mutable.ArrayBuffer[Seq[T]]()
+ val h = mutable.ArrayBuffer[Int]()
+
+ def tour(u: T, parent: Vector[T], height: Int): Unit = {
+ val id = parent :+ u
+ r.getOrElseUpdate(id, e.size)
+ e += id
+ h += height
+ diGraph.getEdges(id.last).foreach { v =>
+ tour(v, id, height + 1)
+ e += id
+ h += height
+ }
+ }
+
+ tour(start, Vector.empty, 0)
+ new EulerTour(r.toMap, e, h)
+ }
+}
+
+/** A class that represents an Euler Tour of a directed graph from a
+ * given root. This requires `O(n)` preprocessing time to generate
+ * the initial Euler Tour.
+ *
+ * @constructor Create a new EulerTour from the specified data
+ * @param r A map of a node to its first index
+ * @param e A representation of the EulerTour as a `Seq[T]`
+ * @param h The depths of the Euler Tour represented as a `Seq[Int]`
+ */
+class EulerTour[T](r: Map[T, Int], e: Seq[T], h: Seq[Int]) {
+ private def lg(x: Double): Double = math.log(x) / math.log(2)
+
+ /** Range Minimum Query of an Euler Tour using a naive algorithm.
+ *
+ * @param x The first query bound
+ * @param y The second query bound
+ * @return The minimum between the first and second query
+ * @note The order of '''x''' and '''y''' does not matter
+ * @note '''Performance''':
+ * - preprocessing: `O(1)`
+ * - query: `O(n)`
+ */
+ def rmqNaive(x: T, y: T): T = {
+ val Seq(i, j) = Seq(r(x), r(y)).sorted
+ e.zip(h).slice(i, j + 1).minBy(_._2)._1
+ }
+
+ // n: the length of the Euler Tour
+ // m: the size of blocks the Euler Tour is split into
+ private val n = h.size
+ private val m = math.ceil(lg(n) / 2).toInt
+
+ /** Split up the tour into blocks of size m, padding the last block to
+ * be a multiple of m. Compute the minimum of each block, a, and
+ * the index of that minimum in each block, b.
+ */
+ private lazy val blocks = (h ++ (1 to (m - n % m))).grouped(m).toArray
+ private lazy val a = blocks map (_.min)
+ private lazy val b = blocks map (b => b.indexOf(b.min))
+
+ /** Construct a Sparse Table (ST) representation for the minimum index
+ * of a sequence of integers. Data in the returned array is indexed
+ * as: [base, power of 2 range]
+ */
+ private def constructSparseTable(x: Seq[Int]): Array[Array[Int]] = {
+ val tmp = Array.ofDim[Int](x.size + 1, math.ceil(lg(x.size)).toInt)
+ for (i <- 0 to x.size - 1; j <- 0 to math.ceil(lg(x.size)).toInt - 1) {
+ tmp(i)(j) = -1
+ }
+
+ def tableRecursive(base: Int, size: Int): Int = {
+ if (size == 0) {
+ tmp(base)(size) = base
+ base
+ } else {
+ val (a, b, c) = (base, base + (1 << (size - 1)), size - 1)
+
+ val l = if (tmp(a)(c) != -1) { tmp(a)(c) }
+ else { tableRecursive(a, c) }
+
+ val r = if (tmp(b)(c) != -1) { tmp(b)(c) }
+ else { tableRecursive(b, c) }
+
+ val min = if (x(l) < x(r)) l else r
+ tmp(base)(size) = min
+ assert(min >= base)
+ min
+ }
+ }
+
+ for (i <- (0 to x.size - 1);
+ j <- (0 to math.ceil(lg(x.size)).toInt - 1);
+ if i + (1 << j) - 1 < x.size) {
+ tableRecursive(i, j)
+ }
+ tmp
+ }
+ private lazy val st = constructSparseTable(a)
+
+ /** Precompute all possible RMQs for an array of size `n where each
+ * entry in the range is different from the last by only +-1
+ */
+ private def constructTableLookups(n: Int): Array[Array[Array[Int]]] = {
+ def sortSeqSeq[T <: Int](x: Seq[T], y: Seq[T]): Boolean = {
+ if (x(0) != y(0)) x(0) < y(0) else sortSeqSeq(x.tail, y.tail)
+ }
+
+ val size = m - 1
+ val out = Seq.fill(size)(Seq(-1, 1))
+ .flatten.combinations(m - 1).flatMap(_.permutations).toList
+ .sortWith(sortSeqSeq)
+ .map(_.foldLeft(Seq(0))((h, pm) => (h.head + pm) +: h).reverse)
+ .map{ a =>
+ var tmp = Array.ofDim[Int](m, m)
+ for (i <- 0 to size; j <- i to size) yield {
+ val window = a.slice(i, j + 1)
+ tmp(i)(j) = window.indexOf(window.min) + i }
+ tmp }.toArray
+ out
+ }
+ private lazy val tables = constructTableLookups(m)
+
+ /** Compute the precomputed table index of a given block */
+ private def mapBlockToTable(block: Seq[Int]): Int = {
+ var index = 0
+ var power = block.size - 2
+ for (Seq(l, r) <- block.sliding(2)) {
+ if (l < r) { index += 1 << power }
+ power -= 1
+ }
+ index
+ }
+
+ /** Precompute a mapping of all blocks to their precomputed RMQ table
+ * indices
+ */
+ private def mapBlocksToTables(blocks: Seq[Seq[Int]]): Array[Int] = {
+ val out = blocks.map(mapBlockToTable(_)).toArray
+ out
+ }
+ private lazy val tableIdx = mapBlocksToTables(blocks)
+
+ /** Range Minimum Query using the Berkman--Vishkin algorithm with the
+ * simplifications of Bender--Farach-Colton.
+ *
+ * @param x The first query bound
+ * @param y The second query bound
+ * @return The minimum between the first and second query
+ * @note The order of '''x''' and '''y''' does not matter
+ * @note '''Performance''':
+ * - preprocessing: `O(n)`
+ * - query: `O(1)`
+ */
+ def rmqBV(x: T, y: T): T = {
+ val Seq(i, j) = Seq(r(x), r(y)).sorted
+
+ // Compute block and word indices
+ val (block_i, block_j) = (i / m, j / m)
+ val (word_i, word_j) = (i % m, j % m)
+
+ /** Up to four possible minimum indices are then computed based on the
+ * following conditions:
+ * 1. `i` and `j` are in the same block:
+ * - one precomputed RMQ from `i` to `j`
+ * 2. `i` and `j` are in adjacent blocks:
+ * - one precomputed RMQ from `i` to the end of its block
+ * - one precomputed RMQ from `j` to the beginning of its block
+ * 3. `i` and `j` have blocks between them:
+ * - one precomputed RMQ from `i` to the end of its block
+ * - one precomputed RMQ from `j` to the beginning of its block
+ * - two sparse table lookups to fully cover all blocks
+ * between `i` and `j`
+ */
+ val minIndices = (block_i, block_j) match {
+ case (bi, bj) if (block_i == block_j) =>
+ val min_i = block_i * m + tables(tableIdx(block_i))(word_i)(word_j)
+ Seq(min_i)
+ case (bi, bj) if (block_i == block_j - 1) =>
+ val min_i = block_i * m + tables(tableIdx(block_i))(word_i)( m - 1)
+ val min_j = block_j * m + tables(tableIdx(block_j))( 0)(word_j)
+ Seq(min_i, min_j)
+ case _ =>
+ val min_i = block_i * m + tables(tableIdx(block_i))(word_i)( m - 1)
+ val min_j = block_j * m + tables(tableIdx(block_j))( 0)(word_j)
+ val (min_between_l, min_between_r) = {
+ val range = math.floor(lg(block_j - block_i - 1)).toInt
+ val base_0 = block_i + 1
+ val base_1 = block_j - (1 << range)
+
+ val (idx_0, idx_1) = (st(base_0)(range), st(base_1)(range))
+ val (min_0, min_1) = (b(idx_0) + idx_0 * m, b(idx_1) + idx_1 * m)
+ (min_0, min_1) }
+ Seq(min_i, min_between_l, min_between_r, min_j)
+ }
+
+ // Return the minimum of all possible minimum indices
+ e(minIndices.minBy(h(_)))
+ }
+
+ /** Range Minimum Query of the Euler Tour.
+ *
+ * Use this for typical queries.
+ *
+ * @param x The first query bound
+ * @param y The second query bound
+ * @return The minimum between the first and second query
+ * @note This currently maps to `rmqBV`, but may choose to map to
+ * either `rmqBV` or `rmqNaive`
+ * @note The order of '''x''' and '''y''' does not matter
+ */
+ def rmq(x: T, y: T): T = rmqBV(x, y)
+}