From 6b4abd77fb889dbc01d2951a0a43fdef212e68d5 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Tue, 13 Aug 2024 10:12:47 -0700 Subject: [PATCH 1/2] Update musical score diagram for adjoint and add more replacements for qpic (#1279) --- qualtran/_infra/adjoint.py | 8 +++----- qualtran/drawing/musical_score.py | 11 +++++++++++ qualtran/drawing/qpic_diagram.py | 3 +++ 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/qualtran/_infra/adjoint.py b/qualtran/_infra/adjoint.py index 8966e0630..f8142c771 100644 --- a/qualtran/_infra/adjoint.py +++ b/qualtran/_infra/adjoint.py @@ -13,7 +13,7 @@ # limitations under the License. from functools import cached_property -from typing import cast, Dict, List, Optional, Set, Tuple, TYPE_CHECKING +from typing import Dict, List, Optional, Set, Tuple, TYPE_CHECKING import cirq from attrs import frozen @@ -24,7 +24,7 @@ from .registers import Signature if TYPE_CHECKING: - from qualtran import Bloq, CompositeBloq, Register, Signature, Soquet, SoquetT + from qualtran import Bloq, CompositeBloq, Register, Signature, SoquetT from qualtran.drawing import WireSymbol from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -176,10 +176,8 @@ def wire_symbol( # Note: since we pass are passed a soquet which has the 'new' side, we flip it before # delegating and then flip back. Subbloqs only have to answer this protocol # if the provided soquet is facing the correct direction. - from qualtran.drawing import Text - if reg is None: - return Text(cast(Text, self.subbloq.wire_symbol(reg=None)).text + '†') + return self.subbloq.wire_symbol(reg=None).adjoint() return self.subbloq.wire_symbol(reg=reg.adjoint(), idx=idx).adjoint() diff --git a/qualtran/drawing/musical_score.py b/qualtran/drawing/musical_score.py index 4a7c747f9..5aecc0230 100644 --- a/qualtran/drawing/musical_score.py +++ b/qualtran/drawing/musical_score.py @@ -371,6 +371,11 @@ def json_dict(self) -> Dict[str, Any]: return {'symb_cls': self.__class__.__name__, 'symb_attributes': attrs.asdict(self)} +def _text_adjoint(text: str) -> str: + """Add / Remove a dagger from the end of the text.""" + return text.strip('†') if text.endswith('†') else text + '†' + + @frozen class TextBox(WireSymbol): text: str @@ -387,6 +392,9 @@ def draw(self, ax, x, y): bbox={'boxstyle': 'round', 'fc': 'white'}, ) + def adjoint(self) -> 'TextBox': + return TextBox(_text_adjoint(self.text)) + @frozen class Text(WireSymbol): @@ -405,6 +413,9 @@ def draw(self, ax, x, y): bbox={'lw': 0, 'fc': 'white'}, ) + def adjoint(self) -> 'Text': + return Text(_text_adjoint(self.text), self.fontsize) + @frozen class RarrowTextBox(WireSymbol): diff --git a/qualtran/drawing/qpic_diagram.py b/qualtran/drawing/qpic_diagram.py index e4936b366..e8dae8f97 100644 --- a/qualtran/drawing/qpic_diagram.py +++ b/qualtran/drawing/qpic_diagram.py @@ -55,8 +55,11 @@ def _format_label_text(label: str, scale: float = 0.8) -> str: '<': r'$<$', '>': r'$>$', '⨁': r'$\oplus$', + '⊕': r'$\oplus$', + '⇋': r'$\rightleftharpoons$', '⨂': r'$\otimes$', '∧': r'$\land$', + '†': r'$^\dagger$', } for key, val in replacements.items(): label = label.replace(key, val) From 4159901068695fd319b812ac72f90d1fa97fd285 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Tue, 13 Aug 2024 11:36:58 -0700 Subject: [PATCH 2/2] Rewrite `SelectSwapQROM` using `build_composite_bloq` (#1276) * Rewrite SelectSwapQROM using build_composite_bloq * Fix failing tests * Regenerate notebook * Remove stale code * Fix typo * Address nits and do some more refactoring for future upgrades --- qualtran/_infra/composite_bloq.py | 12 +- qualtran/bloqs/bookkeeping/allocate.py | 12 +- qualtran/bloqs/bookkeeping/bookkeeping.ipynb | 6 +- qualtran/bloqs/bookkeeping/free.py | 10 +- qualtran/bloqs/data_loading/qrom.py | 3 + .../bloqs/data_loading/select_swap_qrom.py | 243 +++++++++++++----- .../data_loading/select_swap_qrom_test.py | 2 +- qualtran/cirq_interop/_bloq_to_cirq_test.py | 14 +- 8 files changed, 214 insertions(+), 88 deletions(-) diff --git a/qualtran/_infra/composite_bloq.py b/qualtran/_infra/composite_bloq.py index 625c4de96..ee9448600 100644 --- a/qualtran/_infra/composite_bloq.py +++ b/qualtran/_infra/composite_bloq.py @@ -1200,20 +1200,22 @@ def _fin(idxed_soq: Soquet, reg: Register, idx: Tuple[int, ...]): connections=self._cxns, signature=signature, bloq_instances=self._binsts ) - def allocate(self, n: Union[int, sympy.Expr] = 1, dtype: Optional[QDType] = None) -> Soquet: + def allocate( + self, n: Union[int, sympy.Expr] = 1, dtype: Optional[QDType] = None, dirty: bool = False + ) -> Soquet: from qualtran.bloqs.bookkeeping import Allocate if dtype is not None: - return self.add(Allocate(dtype=dtype)) - return self.add(Allocate(dtype=(QAny(n)))) + return self.add(Allocate(dtype=dtype, dirty=dirty)) + return self.add(Allocate(dtype=(QAny(n)), dirty=dirty)) - def free(self, soq: Soquet) -> None: + def free(self, soq: Soquet, dirty: bool = False) -> None: from qualtran.bloqs.bookkeeping import Free if not isinstance(soq, Soquet): raise ValueError("`free` expects a single Soquet to free.") - self.add(Free(dtype=soq.reg.dtype), reg=soq) + self.add(Free(dtype=soq.reg.dtype, dirty=dirty), reg=soq) def split(self, soq: Soquet) -> NDArray[Soquet]: # type: ignore[type-var] """Add a Split bloq to split up a register.""" diff --git a/qualtran/bloqs/bookkeeping/allocate.py b/qualtran/bloqs/bookkeeping/allocate.py index 7ab3fb383..7f566a714 100644 --- a/qualtran/bloqs/bookkeeping/allocate.py +++ b/qualtran/bloqs/bookkeeping/allocate.py @@ -47,12 +47,14 @@ class Allocate(_BookkeepingBloq): Args: dtype: the quantum data type of the allocated register. + dirty: If true, represents a borrowing operation where allocated qubits can be dirty. Registers: reg [right]: The allocated register. """ dtype: QDType + dirty: bool = False @cached_property def signature(self) -> Signature: @@ -64,7 +66,7 @@ def decompose_bloq(self) -> 'CompositeBloq': def adjoint(self) -> 'Bloq': from qualtran.bloqs.bookkeeping.free import Free - return Free(self.dtype) + return Free(self.dtype, self.dirty) def on_classical_vals(self) -> Dict[str, int]: return {'reg': 0} @@ -91,10 +93,12 @@ def as_cirq_op( self, qubit_manager: 'cirq.QubitManager' ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: shape = (*self.signature[0].shape, self.signature[0].bitsize) - return ( - None, - {'reg': np.array(qubit_manager.qalloc(self.signature.n_qubits())).reshape(shape)}, + qubits = ( + qubit_manager.qborrow(self.signature.n_qubits()) + if self.dirty + else qubit_manager.qalloc(self.signature.n_qubits()) ) + return (None, {'reg': np.array(qubits).reshape(shape)}) @bloq_example diff --git a/qualtran/bloqs/bookkeeping/bookkeeping.ipynb b/qualtran/bloqs/bookkeeping/bookkeeping.ipynb index 4304177d7..a439c2b61 100644 --- a/qualtran/bloqs/bookkeeping/bookkeeping.ipynb +++ b/qualtran/bloqs/bookkeeping/bookkeeping.ipynb @@ -41,7 +41,8 @@ "Allocate an `n` bit register.\n", "\n", "#### Parameters\n", - " - `dtype`: the quantum data type of the allocated register. \n", + " - `dtype`: the quantum data type of the allocated register.\n", + " - `dirty`: If true, represents a borrowing operation where allocated qubits can be dirty. \n", "\n", "#### Registers\n", " - `reg [right]`: The allocated register.\n" @@ -146,7 +147,8 @@ "vector after freeing qubits and make sure it is normalized.\n", "\n", "#### Parameters\n", - " - `dtype`: The quantum data type of the register to be freed. \n", + " - `dtype`: The quantum data type of the register to be freed.\n", + " - `dirty`: If true, represents adjoint of a borrowing operation where deallocated qubits were borrowed dirty from another part of the algorithm and must be free'd in the same dirty state. \n", "\n", "#### Registers\n", " - `reg [left]`: The register to free.\n" diff --git a/qualtran/bloqs/bookkeeping/free.py b/qualtran/bloqs/bookkeeping/free.py index f8a2a0700..0efb8933b 100644 --- a/qualtran/bloqs/bookkeeping/free.py +++ b/qualtran/bloqs/bookkeeping/free.py @@ -52,12 +52,16 @@ class Free(_BookkeepingBloq): Args: dtype: The quantum data type of the register to be freed. + dirty: If true, represents adjoint of a borrowing operation where deallocated qubits + were borrowed dirty from another part of the algorithm and must be free'd in the same + dirty state. Registers: reg [left]: The register to free. """ dtype: QDType + dirty: bool = False @cached_property def signature(self) -> Signature: @@ -69,11 +73,11 @@ def decompose_bloq(self) -> 'CompositeBloq': def adjoint(self) -> 'Bloq': from qualtran.bloqs.bookkeeping.allocate import Allocate - return Allocate(self.dtype) + return Allocate(self.dtype, self.dirty) def on_classical_vals(self, reg: int) -> Dict[str, 'ClassicalValT']: - if reg != 0: - raise ValueError(f"Tried to free a non-zero register: {reg}.") + if reg != 0 and not self.dirty: + raise ValueError(f"Tried to free a non-zero register: {reg} with {self.dirty=}") return {} def my_tensors( diff --git a/qualtran/bloqs/data_loading/qrom.py b/qualtran/bloqs/data_loading/qrom.py index db2607a57..6491caad7 100644 --- a/qualtran/bloqs/data_loading/qrom.py +++ b/qualtran/bloqs/data_loading/qrom.py @@ -223,6 +223,9 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: n_cnot = prod(self.target_bitsizes) * prod(self.data_shape) return {(And(), n_and), (And().adjoint(), n_and), (CNOT(), n_cnot)} + def adjoint(self) -> 'QROM': + return self + @bloq_example def _qrom_small() -> QROM: diff --git a/qualtran/bloqs/data_loading/select_swap_qrom.py b/qualtran/bloqs/data_loading/select_swap_qrom.py index 7da76b9ae..4a4016519 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom.py @@ -20,11 +20,11 @@ import cirq import numpy as np import sympy -from numpy.typing import ArrayLike, NDArray +from numpy.typing import ArrayLike -from qualtran import bloq_example, BloqDocSpec, GateWithRegisters, Register, Signature -from qualtran._infra.gate_with_registers import total_bits -from qualtran.bloqs.basic_gates import CNOT +from qualtran import bloq_example, BloqDocSpec, BoundedQUInt, GateWithRegisters, Register, Signature +from qualtran.bloqs.arithmetic.bitwise import Xor +from qualtran.bloqs.bookkeeping import Partition from qualtran.bloqs.data_loading.qrom import QROM from qualtran.bloqs.data_loading.qrom_base import QROMBase from qualtran.bloqs.swap_network import SwapWithZero @@ -32,7 +32,7 @@ from qualtran.symbolics import ceil, is_symbolic, log2, prod, SymbolicFloat, SymbolicInt if TYPE_CHECKING: - from qualtran import Bloq + from qualtran import Bloq, BloqBuilder, QDType, SoquetT from qualtran.resource_counting import BloqCountT, SympySymbolAllocator @@ -60,6 +60,17 @@ def value(kk: List[int]): return int(k_int[np.argmin(value(k_int))]) # obtain optimal k +def _alloc_anc_for_reg( + bb: 'BloqBuilder', dtype: 'QDType', shape: Tuple[int, ...], dirty: bool +) -> 'SoquetT': + if not shape: + return bb.allocate(dtype=dtype, dirty=dirty) + soqs = np.empty(shape, dtype=object) + for idx in np.ndindex(shape): + soqs[idx] = bb.allocate(dtype=dtype, dirty=dirty) + return soqs + + @attrs.frozen class SelectSwapQROM(QROMBase, GateWithRegisters): # type: ignore[misc] """Gate to load data[l] in the target register when the selection register stores integer l. @@ -110,6 +121,13 @@ class SelectSwapQROM(QROMBase, GateWithRegisters): # type: ignore[misc] ) use_dirty_ancilla: bool = True + def __attrs_post_init__(self): + super().__attrs_post_init__() + if self.target_shapes != self._default_target_shapes(): + raise ValueError( + f"{type(self)} currently only supports target registers of shape (). Found {self.target_shapes}" + ) + @cached_property def signature(self) -> Signature: return Signature( @@ -239,81 +257,164 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: toggle_overhead = 2 if self.use_dirty_ancilla else 1 ret[self.qrom_bloq] += 1 ret[self.qrom_bloq.adjoint()] += 1 - ret[CNOT()] += toggle_overhead * total_bits(self.target_registers) + for reg in self.target_registers: + ret[Xor(reg.dtype)] += toggle_overhead * np.prod(reg.shape, dtype=int) for swz in self.swap_with_zero_bloqs: if any(is_symbolic(s) or s > 0 for s in swz.selection_bitsizes): ret[swz] += toggle_overhead ret[swz.adjoint()] += toggle_overhead return set(ret.items()) - def _alloc_qrom_target_qubits( - self, reg: Register, qm: cirq.QubitManager - ) -> NDArray[cirq.Qid]: # type:ignore[type-var] - qubits = ( - qm.qborrow(total_bits([reg])) - if self.use_dirty_ancilla - else qm.qalloc(total_bits([reg])) - ) - return np.array(qubits).reshape(reg.shape + (reg.bitsize,)) - - def decompose_from_registers( # type: ignore[return] + def _add_qrom_bloq( self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - # 1. Construct QROM to load the batched data. - qrom = self.qrom_bloq - qrom_ctrls = {reg.name: quregs[reg.name] for reg in qrom.control_registers} - qrom_selection = { - qrom_reg.name: quregs[sel_reg.name][: qrom_reg.bitsize] - for sel_reg, qrom_reg in zip(self.selection_registers, qrom.selection_registers) - } - qrom_targets = { - reg.name: self._alloc_qrom_target_qubits(reg, context.qubit_manager) - for reg in qrom.target_registers - } - qrom_op = qrom.on_registers(**qrom_ctrls, **qrom_selection, **qrom_targets) - # 2. Construct SwapWithZero. - swz_ops = [] - assert len(qrom_targets) == len(self.swap_with_zero_bloqs) - for targets, swz in zip(qrom_targets.values(), self.swap_with_zero_bloqs): - if len(targets) <= 1: - continue - swz_selection = { - swz_reg.name: quregs[sel_reg.name][-swz_reg.bitsize :] - for sel_reg, swz_reg in zip(self.selection_registers, swz.selection_registers) - } - swz_ops.append(swz.on_registers(**swz_selection, targets=targets)) - # 3. Construct CNOTs from 0th borrowed register to clean target registers. - cnot_ops = [] - for qrom_reg, target_reg in zip(qrom.target_registers, self.target_registers): - qrom_batched_target = qrom_targets[qrom_reg.name] - idx = (0,) * len(qrom_reg.shape) - cnot_ops += [ - [cirq.CNOT(a, b) for a, b in zip(qrom_batched_target[idx], quregs[target_reg.name])] - ] - - # Yield the operations in correct order. - if any(b > 0 for b in self.block_sizes): - yield qrom_op - yield swz_ops - yield cnot_ops - yield cirq.inverse(swz_ops) - yield cirq.inverse(qrom_op) - if self.use_dirty_ancilla: - yield swz_ops - yield cnot_ops - yield cirq.inverse(swz_ops) - else: - yield qrom_op - yield cnot_ops - yield cirq.inverse(qrom_op) - yield cnot_ops + bb: 'BloqBuilder', + ctrls: List['SoquetT'], + sel_l: List['SoquetT'], + targets: List['SoquetT'], + uncompute: bool = False, + ) -> Tuple[List['SoquetT'], List['SoquetT'], List['SoquetT']]: + in_soqs = {reg.name: soq for reg, soq in zip(self.qrom_bloq.control_registers, ctrls)} + in_soqs |= {reg.name: soq for reg, soq in zip(self.qrom_bloq.selection_registers, sel_l)} + in_soqs |= {reg.name: soq for reg, soq in zip(self.qrom_bloq.target_registers, targets)} + out_soqs = bb.add_d(self.qrom_bloq.adjoint() if uncompute else self.qrom_bloq, **in_soqs) + ctrls = [out_soqs[reg.name] for reg in self.qrom_bloq.control_registers] + sel_l = [out_soqs[reg.name] for reg in self.qrom_bloq.selection_registers] + targets = [out_soqs[reg.name] for reg in self.qrom_bloq.target_registers] + return ctrls, sel_l, targets + + def _add_swap_with_zero_bloq( + self, + bb: 'BloqBuilder', + selection: List['SoquetT'], + targets: List['SoquetT'], + uncompute: bool = False, + ) -> Tuple[List['SoquetT'], List['SoquetT']]: + # Get soquets for SwapWithZero + assert len(targets) == len(self.swap_with_zero_bloqs) + sel_names = [reg.name for reg in self.swap_with_zero_bloqs[0].selection_registers] + soqs = {sel_name: soq for sel_name, soq in zip(sel_names, selection)} + out_targets: List['SoquetT'] = [] + for target, swz in zip(targets, self.swap_with_zero_bloqs): + soqs['targets'] = target + soqs = bb.add_d(swz.adjoint() if uncompute else swz, **soqs) + out_targets.append(soqs['targets']) + return [soqs[reg_name] for reg_name in sel_names], out_targets + + def _add_cnot( + self, bb: 'BloqBuilder', qrom_targets: List['SoquetT'], target: List['SoquetT'] + ) -> Tuple[List['SoquetT'], List['SoquetT']]: + for i, qrom_reg in enumerate(qrom_targets): + assert isinstance(qrom_reg, np.ndarray) # Make mypy happy. + idx = np.unravel_index(0, qrom_reg.shape) + qrom_reg[idx], target[i] = bb.add( + Xor(self.target_registers[i].dtype), x=qrom_reg[idx], y=target[i] + ) + return qrom_targets, target - context.qubit_manager.qfree( - [q for targets in qrom_targets.values() for q in targets.flatten()] + @cached_property + def _partition_selection_reg_bloqs(self) -> List[Partition]: + partition_bloqs = [] + for reg, k in zip(self.selection_registers, self.log_block_sizes): + preg = ( + Register('l', BoundedQUInt(reg.bitsize - k, 2 ** (reg.bitsize - k))), + Register('k', BoundedQUInt(k, 2**k)), + ) + partition_bloqs.append(Partition(reg.bitsize, preg)) + return partition_bloqs + + def _partition_sel_register( + self, bb: 'BloqBuilder', selection: List['SoquetT'] + ) -> Tuple[List['SoquetT'], List['SoquetT']]: + sel_l, sel_k = [], [] + for sel, pbloq in zip(selection, self._partition_selection_reg_bloqs): + sl, sk = bb.add(pbloq, x=sel) + sel_l.append(sl) + sel_k.append(sk) + return sel_l, sel_k + + def _unpartition_sel_register( + self, bb: 'BloqBuilder', sel_l: List['SoquetT'], sel_k: List['SoquetT'] + ) -> List['SoquetT']: + selection = [] + for l, k, pbloq in zip(sel_l, sel_k, self._partition_selection_reg_bloqs): + selection.append(bb.add(pbloq.adjoint(), l=l, k=k)) + return selection + + def _build_composite_bloq_with_swz( + self, bb: 'BloqBuilder', ctrl, selection, target, qrom_targets + ): + sel_l, sel_k = self._partition_sel_register(bb, selection) + # Partition selection registers into l & k + ctrl, sel_l, qrom_targets = self._add_qrom_bloq(bb, ctrl, sel_l, qrom_targets) + sel_k, qrom_targets = self._add_swap_with_zero_bloq(bb, sel_k, qrom_targets) + qrom_targets, target = self._add_cnot(bb, qrom_targets, target) + sel_k, qrom_targets = self._add_swap_with_zero_bloq(bb, sel_k, qrom_targets, uncompute=True) + ctrl, sel_l, qrom_targets = self._add_qrom_bloq( + bb, ctrl, sel_l, qrom_targets, uncompute=True ) + if self.use_dirty_ancilla: + sel_k, qrom_targets = self._add_swap_with_zero_bloq(bb, sel_k, qrom_targets) + qrom_targets, target = self._add_cnot(bb, qrom_targets, target) + sel_k, qrom_targets = self._add_swap_with_zero_bloq( + bb, sel_k, qrom_targets, uncompute=True + ) + # UnPartition sel_l, sel_k into selection + selection = self._unpartition_sel_register(bb, sel_l, sel_k) + return ctrl, selection, target, qrom_targets + + def _build_composite_bloq_without_swz( + self, bb: 'BloqBuilder', ctrl, selection, target, qrom_targets + ): + ctrl, selection, qrom_targets = self._add_qrom_bloq(bb, ctrl, selection, qrom_targets) + qrom_targets, target = self._add_cnot(bb, qrom_targets, target) + ctrl, selection, qrom_targets = self._add_qrom_bloq( + bb, ctrl, selection, qrom_targets, uncompute=True + ) + if self.use_dirty_ancilla: + qrom_targets, target = self._add_cnot(bb, qrom_targets, target) + return ctrl, selection, target, qrom_targets + + def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']: + # Get the ctrl and target register for the SelectSwapQROM. + ctrl = [soqs.pop(reg.name) for reg in self.control_registers] + selection = [soqs.pop(reg.name) for reg in self.selection_registers] + target = [soqs.pop(reg.name) for reg in self.target_registers] + # Allocate intermediate clean/dirty ancilla for the underlying QROM call. + if is_symbolic(*self.block_sizes): + raise ValueError( + f"Cannot decompose SelectSwapQROM bloq with symbolic block sizes. Found {self.block_sizes=}" + ) + block_sizes = cast(Tuple[int, ...], self.block_sizes) + qrom_targets = [ + _alloc_anc_for_reg(bb, reg.dtype, block_sizes, self.use_dirty_ancilla) + for reg in self.target_registers + ] + # Verify some of the assumptions are correct. + assert not soqs, f"All registers must have been used by now. Found: {soqs}" + assert len(self.qrom_bloq.target_shapes) == len(self.target_registers) + for qrom_target, my_target in zip(self.qrom_bloq.target_registers, self.target_registers): + assert qrom_target.dtype == my_target.dtype + assert qrom_target.shape == block_sizes + # Add the bloq decomposition + if any(b > 1 for b in block_sizes): + ctrl, selection, target, qrom_targets = self._build_composite_bloq_with_swz( + bb, ctrl, selection, target, qrom_targets + ) + else: + ctrl, selection, target, qrom_targets = self._build_composite_bloq_without_swz( + bb, ctrl, selection, target, qrom_targets + ) + # Free the allocated register. + for reg in qrom_targets: + assert isinstance(reg, np.ndarray) + for soq in reg.flat: + bb.free(soq) + # Add back target and control registers. + soqs |= {reg.name: soq for reg, soq in zip(self.control_registers, ctrl)} + soqs |= {reg.name: soq for reg, soq in zip(self.selection_registers, selection)} + soqs |= {reg.name: soq for reg, soq in zip(self.target_registers, target)} + # Return dictionary of final soquets. + return soqs def _circuit_diagram_info_(self, args) -> cirq.CircuitDiagramInfo: from qualtran.cirq_interop._bloq_to_cirq import _wire_symbol_to_cirq_diagram_info diff --git a/qualtran/bloqs/data_loading/select_swap_qrom_test.py b/qualtran/bloqs/data_loading/select_swap_qrom_test.py index f09280370..de9b10f59 100644 --- a/qualtran/bloqs/data_loading/select_swap_qrom_test.py +++ b/qualtran/bloqs/data_loading/select_swap_qrom_test.py @@ -61,10 +61,10 @@ def test_select_swap_qrom(data, block_size): qrom_circuit = cirq.Circuit( cirq.decompose_once(qrom.on_registers(**qubit_regs), context=context) ) - dirty_target_ancilla = sorted( qrom_circuit.all_qubits() - set(q for qs in qubit_regs.values() for q in qs.flatten()) ) + assert greedy_mm._size == len(dirty_target_ancilla) == len(greedy_mm._free_qubits) circuit = cirq.Circuit( # Prepare dirty ancillas in an arbitrary state. diff --git a/qualtran/cirq_interop/_bloq_to_cirq_test.py b/qualtran/cirq_interop/_bloq_to_cirq_test.py index bee180dad..8ecc6a59b 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq_test.py +++ b/qualtran/cirq_interop/_bloq_to_cirq_test.py @@ -20,7 +20,7 @@ from qualtran import Bloq, BloqBuilder, ConnectionT, Signature, Soquet, SoquetT from qualtran._infra.gate_with_registers import get_named_qubits -from qualtran.bloqs.basic_gates import Toffoli, XGate +from qualtran.bloqs.basic_gates import Toffoli, XGate, YGate from qualtran.bloqs.factoring import ModExp from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd from qualtran.bloqs.state_preparation import PrepareUniformSuperposition @@ -206,9 +206,19 @@ def test_bloq_as_cirq_gate_left_register(): q = bb.allocate(1) q = bb.add(XGate(), q=q) bb.free(q) + q = bb.allocate(1, dirty=True) + q = bb.add(YGate(), q=q) + bb.free(q, dirty=True) cbloq = bb.finalize() circuit = cbloq.to_cirq_circuit() - cirq.testing.assert_has_diagram(circuit, """_c(0): ───X───""") + cirq.testing.assert_has_diagram( + circuit, + """ +_b(0): ───Y─── + +_c(0): ───X─── +""", + ) def test_bloq_as_cirq_gate_for_mod_exp():