Skip to content

Commit

Permalink
Docs, Queues: binary heap (#2182)
Browse files Browse the repository at this point in the history
More progress towards #2067!

This PR takes a stab at tweaking `docs/frontends/queues.md` to
incorporate additions from PRs #2164 and #2174.

---------

Co-authored-by: Anshuman Mohan <[email protected]>
  • Loading branch information
polybeandip and anshumanmohan authored Jul 1, 2024
1 parent 071f9af commit b5aafd5
Showing 1 changed file with 36 additions and 5 deletions.
41 changes: 36 additions & 5 deletions docs/frontends/queues.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ Because of the total order, some element of the queue is the _most favorably ran
We can read this element using the `peek` operation.
We can also remove this element from the queue using the `dequeue` operation, which is also known as `pop` or `remove` in some contexts.

We provision three types of queues in Calyx, which follow the same interface for ease of use.
We provision four types of queues in Calyx. The first three follow the same shared interface, while the fourth follows a slightly extended interface.
The frontend is implemented using the [Calyx builder library][builder], and the source code is heavily commented.

We first describe the shared interface and then detail the three types of queues.
We first describe the shared interface and their associated testing harness and then detail the four types of queues.

## Shared Interface

Expand All @@ -19,7 +19,7 @@ Selects the operation to perform:
- `0`: `pop`.
- `1`: `peek`.
- `2`: `push`.
- Input port `value`, a 32-bit integer
- Input port `value`, a 32-bit integer.
The value to push.
- Register `ans`, a 32-bit integer that is passed to the queue by reference.
If `peek` or `pop` is selected, the queue writes the result to this register.
Expand All @@ -40,8 +40,8 @@ The Python code is in [`queue_data_gen.py`][queue_data_gen.py].

#### Oracles
Next, we have a Python module _for each kind of queue_ that reads the `.data` file, simulates the queue in Python, and dumps the expected result out in a Calyx `.expect` file.
This Python code is aware of our [shared iterface](#shared-interface).
The oracles are in [`fifo_oracle.py`][fifo_oracle.py], [`pifo_oracle.py`][pifo_oracle.py], and [`pifo_tree_oracle.py`][pifo_tree_oracle.py].
This Python code is aware of our [shared interface](#shared-interface).
The oracles are in [`fifo_oracle.py`][fifo_oracle.py], [`pifo_oracle.py`][pifo_oracle.py], [`pifo_tree_oracle.py`][pifo_tree_oracle.py], and [`binheap_oracle.py`][binheap_oracle.py].
They all appeal to pure-Python implementations of the queues, which are found in [`queues.py`][queues.py].
Each oracle also requires, as command line arguments, the number of operations being simulated and the queue's length.

Expand Down Expand Up @@ -123,10 +123,40 @@ Internally, our PIFO tree is implemented by leveraging the PIFO frontend.
The PIFO frontend seeks to orchestrate two queues, which in the simple case will just be two FIFOs.
However, it is easy to generalize those two queues: instead of being FIFOs, they can be PIFOs or even PIFO trees.

## Minimum Binary Heap

A minimum binary heap is another tree-shaped data structure where each node has at most two children.
However, unlike the queues discussed above, a heap exposes an extended interface:
in addition to the input ports and reference registers discussed above, a heap has an additional input `rank`.
The `push` operation now accepts both a `value` and the `rank` that the user wishes to associate with that value.
Consequently, a heap _orders_ its elements by `rank`, with the `pop` (resp. `peek`) operation set to remove (resp. read) the element with minimal rank.

To maintain this ordering efficiently, a heap stores `(rank, value)` pairs in each node and takes special care to maintain the following invariant:
> **Min-Heap Property**: for any given node `C`, if `P` is a parent of `C`, then the rank of `P` is less than or equal to the rank of `C`.
To `push` or `pop` an element is easy at the top level: write to or read from the correct node, and then massage the tree to restore the Min-Heap Property.
The `peek` operation is constant-time and `push` and `pop` are logarithmic in the size of the heap.

Our frontend allows for the creation of minimum binary heaps in Calyx; the source code is available in [`binheap.py`][binheap.py].

One quirk of any minimum binary heap is its ambiguous behavior in the case of rank ties.
More specifically, if the value `a` is pushed with some rank, and then later value `b` is pushed with the same rank, it's unclear which will be popped first.
Often, it's desirable to break such ties in FIFO order: that is we'd like a guarantee that `a` will be popped first.
A binary heap that provides this guarantee is called a _stable binary heap_, and our frontend provides a thin layer over our heap that enforces this property.

Our `stable_binheap` is a heap accepting 32-bit ranks and values.
It uses a counter `i` and instantiates, in turn, a binary heap that accepts 64-bit ranks and 32-bit values.
- To push a pair `(r, v)` into `stable_binheap`, we craft a new 64-bit rank that incorporates the counter `i` (specifically, we compute `r << 32 + i`), and we push `v` into our underlying binary heap with this new 64-bit rank. We also increment the counter `i`.
- To pop `stable_binheap`, we pop the underlying binary heap.
- To peek `stable_binheap`, we peek the underlying binary heap.

The source code is available in [`stable_binheap.py`][stable_binheap.py].

[builder]: ../builder/calyx-py.md
[fifo.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/test/correctness/queues/fifo.py
[pifo.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/test/correctness/queues/pifo.py
[binheap.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/test/correctness/queues/binheap/binheap.py
[stable_binheap.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/test/correctness/queues/binheap/stable_binheap.py
[pifo_tree.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/test/correctness/queues/pifo_tree.py
[sivaraman16]: https://dl.acm.org/doi/10.1145/2934872.2934899
[mohan23]: https://dl.acm.org/doi/10.1145/3622845
Expand All @@ -135,6 +165,7 @@ However, it is easy to generalize those two queues: instead of being FIFOs, they
[fifo_oracle.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/fifo_oracle.py
[pifo_oracle.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/pifo_oracle.py
[pifo_tree_oracle.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/pifo_tree_oracle.py
[binheap_oracle.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/binheap_oracle.py
[gen_queue_data_expect.sh]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/gen_queue_data_expect.sh
[queue_call.py]: https://github.com/calyxir/calyx/blob/main/calyx-py/calyx/queue_call.py
[runt-queues]: https://github.com/calyxir/calyx/blob/a4c2442675d3419be6d2f5cf912aa3f804b3c4ab/runt.toml#L131-L144

0 comments on commit b5aafd5

Please sign in to comment.