feat: Add implement_ops and encode pass to the toy_k2 architecture - #407
PabloAndresCQ wants to merge 1 commit into
Conversation
| # Another alternative is to define the addressable gates in | ||
| # `guppyft.code.toy_k2.logical` acting on the dynamic_qubit type and | ||
| # adding HUGR extension ops to inspect these types, checking if two are | ||
| # in the same block (which is implemented by comparing ints during | ||
| # implement_ops), etc. In that case, we would just replace a | ||
| # computational CX with this logical CX via the compose_op replacement, | ||
| # and *not* have the dyn ops in the extension. |
There was a problem hiding this comment.
This is my current preference. Happy to explain it in more detail.
There was a problem hiding this comment.
If I understand correctly, the proposal would be to add a cx gate to logicals.py that would look like
def cx(control: Qubit, target: Qubit) -> None:
if same_block(control, target):
cx_intra(...)
else:
cx_inter(...)where cx_inter/intra are primitives that act on Block types with int arguments to determine the location of the qubit to be addressed.
In that case, we would also need some operation that consumes Qubit types and returns the Block and int address to be passed to the cx_inter/intra primitives.
hsemenenko
left a comment
There was a problem hiding this comment.
Adding a few comments.
| @guppy | ||
| @no_type_check | ||
| def is_borrowed(self) -> bool: | ||
| """Check if any address in the block is borrowed.""" | ||
| return self.borrowed_addr[0] or self.borrowed_addr[1] |
There was a problem hiding this comment.
Can't we use the existing Guppy .is_borrowed method on the array?
| # Remove the remaining addresses of this block from the stack | ||
| # NOTE: Ideally, `avail_dyn_addrs` would be a data | ||
| # structure with fast deletion (e.g. a set). | ||
| # Generally, this stack will be small, though, so it | ||
| # shouldn't be a performance bottleneck. | ||
| aux_stack: Stack[int, comptime(n_blocks)] = empty_stack() | ||
| for _ in range(len(self.avail_dyn_addrs)): | ||
| addr = self.avail_dyn_addrs.pop() | ||
| if addr[0] != blk_id: | ||
| aux_stack.push(addr) | ||
| for addr in aux_stack: | ||
| self.avail_dyn_addrs.push(addr) |
There was a problem hiding this comment.
Could we instead have a counter on each block to indicate how many "live" qubits there are? Once it reaches 0, all addresses are pushed back to the Stack.
|
|
||
| @guppy | ||
| @no_type_check | ||
| def allocate_block(self, logical_block: LogicalBlock[4] @ owned) -> int: |
There was a problem hiding this comment.
This function doesn't allocation the block, so perhaps allocate_block_addr is more appropriate.
| @guppy | ||
| @no_type_check | ||
| @link_name("guppyft.toy_k2._h_dynq") | ||
| def _h_dynq(addr: tuple[int, int]) -> tuple[int, int]: |
There was a problem hiding this comment.
If h_dynq needs an ancilla block, shouldn't we instead treat it like t-state injection where the op accepts a logical block?
| @guppy | ||
| @no_type_check | ||
| @link_name("guppyft.toy_k2._alloc_dynq") | ||
| def _alloc_dynq() -> tuple[int, int]: |
There was a problem hiding this comment.
This is required to match Guppy and HUGR types I think.
| def _alloc_dynq() -> tuple[int, int]: | |
| def _alloc_dynq() -> tuple[tuple[int, int]]: |
| state.qed_policy( | ||
| array(blk_id), comptime(qed_policy.costs.measure_z_all) | ||
| ) |
There was a problem hiding this comment.
This isn't needed if the block has been released/consumed.
Closes #243
DISCLAIMER
I'm putting this up so that we can get the discussion started. This is very much still WIP. Consider what I've put up a fleshed out "design intent". There's plenty of work left before this can be merged: