diff --git a/qualtran/__init__.py b/qualtran/__init__.py
index 98ee00bd7..14a904a4e 100644
--- a/qualtran/__init__.py
+++ b/qualtran/__init__.py
@@ -58,4 +58,6 @@
Soquet,
)
+from ._infra.gate_with_registers import GateWithRegisters
+
# --------------------------------------------------------------------------------------------------
diff --git a/qualtran/_infra/gate_with_registers.ipynb b/qualtran/_infra/gate_with_registers.ipynb
new file mode 100644
index 000000000..2f371e41b
--- /dev/null
+++ b/qualtran/_infra/gate_with_registers.ipynb
@@ -0,0 +1,244 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3b990f88",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Copyright 2023 The Cirq Developers\n",
+ "#\n",
+ "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "bf9c80ce",
+ "metadata": {},
+ "source": [
+ "# Gate with Registers\n",
+ "\n",
+ "This package includes a subclass of `cirq.Gate` called `GateWithRegisters`. Instead of operating on a flat list of `cirq.Qid`, this lets the developer define gates in terms of named registers of given widths."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c0833444",
+ "metadata": {},
+ "source": [
+ "## `Signature`\n",
+ "\n",
+ "`Register` objects have a name, a bitsize and a shape. `Signature` is an ordered collection of `Register` with some helpful methods."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c75414f2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qualtran import Register, Signature\n",
+ "\n",
+ "control_reg = Register(name='control', bitsize=2)\n",
+ "target_reg = Register(name='target', bitsize=3)\n",
+ "control_reg, target_reg"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b38d210c",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "r = Signature([control_reg, target_reg])\n",
+ "r"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2b32274b",
+ "metadata": {},
+ "source": [
+ "You can also use the `build` factory method to quickly define a set of registers"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "4f10e66e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "r == Signature.build(\n",
+ " control=2,\n",
+ " target=3,\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a5955208",
+ "metadata": {},
+ "source": [
+ "### `GateWithRegisters`"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "b3957db4",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import cirq\n",
+ "from qualtran import GateWithRegisters\n",
+ "\n",
+ "class MyGate(GateWithRegisters):\n",
+ " \n",
+ " @property\n",
+ " def signature(self):\n",
+ " return Signature.build(\n",
+ " control=2,\n",
+ " target=3,\n",
+ " )\n",
+ " \n",
+ " def decompose_from_registers(self, context, control, target):\n",
+ " assert len(control) == 2\n",
+ " assert len(target) == 3\n",
+ " \n",
+ " for c in control:\n",
+ " for t in target:\n",
+ " yield cirq.CNOT(c, t)\n",
+ " "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "2de931eb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gate = MyGate()\n",
+ "gate"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "ef98f3a2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Number of qubits is derived from registers\n",
+ "cirq.num_qubits(gate)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "2d725646",
+ "metadata": {},
+ "source": [
+ "The `Signature` object can allocate a dictionary of `cirq.NamedQubit` that we can use to turn our `Gate` into an `Operation`. `GateWithRegisters` exposes an `on_registers` method to compliment Cirq's `on` method where we can use names to make sure each qubit is used appropriately."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "057148da",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from qualtran._infra.gate_with_registers import get_named_qubits\n",
+ "\n",
+ "r = gate.signature\n",
+ "quregs = get_named_qubits(r)\n",
+ "quregs"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "0257d8f1",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "operation = gate.on_registers(**quregs)\n",
+ "operation"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "541f2e91",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from cirq.contrib.svg import SVGCircuit\n",
+ "SVGCircuit(cirq.Circuit(operation))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6686f7f8",
+ "metadata": {},
+ "source": [
+ "## `GateHelper`\n",
+ "\n",
+ "Since `GateWithRegisters` contains enough metadata to derive qubits, an operation, and a circuit we provide a helper class to provide easy access to these quantities."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "93a6c8f2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import qualtran.cirq_interop.testing as cq_testing\n",
+ "\n",
+ "g = cq_testing.GateHelper(gate)\n",
+ "\n",
+ "print('r:', g.r)\n",
+ "print('quregs:', g.quregs)\n",
+ "print('operation:', g.operation)\n",
+ "print('\\ncircuit:\\n', g.circuit)\n",
+ "print('\\n\\ndecomposed circuit:\\n', cirq.Circuit(cirq.decompose_once(g.operation)))"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.16"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/qualtran/_infra/gate_with_registers.py b/qualtran/_infra/gate_with_registers.py
new file mode 100644
index 000000000..5946ca7dd
--- /dev/null
+++ b/qualtran/_infra/gate_with_registers.py
@@ -0,0 +1,246 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import abc
+import itertools
+from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
+
+import attr
+import cirq
+import numpy as np
+import quimb.tensor as qtn
+from numpy.typing import NDArray
+
+from qualtran._infra.bloq import Bloq
+from qualtran._infra.composite_bloq import CompositeBloq, SoquetT
+from qualtran._infra.quantum_graph import Soquet
+from qualtran._infra.registers import Register, Side, Signature
+
+if TYPE_CHECKING:
+ from qualtran.cirq_interop import CirqQuregT
+
+
+def total_bits(registers: Iterable[Register]) -> int:
+ """Sum of `reg.total_bits()` for each register `reg` in input `signature`."""
+ return sum(reg.total_bits() for reg in registers)
+
+
+def split_qubits(
+ registers: Iterable[Register], qubits: Sequence[cirq.Qid]
+) -> Dict[str, NDArray[cirq.Qid]]: # type: ignore[type-var]
+ """Splits the flat list of qubits into a dictionary of appropriately shaped qubit arrays."""
+
+ qubit_regs = {}
+ base = 0
+ for reg in registers:
+ qubit_regs[reg.name] = np.array(qubits[base : base + reg.total_bits()]).reshape(
+ reg.shape + (reg.bitsize,)
+ )
+ base += reg.total_bits()
+ return qubit_regs
+
+
+def merge_qubits(
+ registers: Iterable[Register],
+ **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]],
+) -> List[cirq.Qid]:
+ """Merges the dictionary of appropriately shaped qubit arrays into a flat list of qubits."""
+
+ ret: List[cirq.Qid] = []
+ for reg in registers:
+ if reg.name not in qubit_regs:
+ raise ValueError(f"All qubit registers must be present. {reg.name} not in qubit_regs")
+ qubits = qubit_regs[reg.name]
+ qubits = np.array([qubits] if isinstance(qubits, cirq.Qid) else qubits)
+ full_shape = reg.shape + (reg.bitsize,)
+ if qubits.shape != full_shape:
+ raise ValueError(
+ f'{reg.name} register must of shape {full_shape} but is of shape {qubits.shape}'
+ )
+ ret += qubits.flatten().tolist()
+ return ret
+
+
+def get_named_qubits(registers: Iterable[Register]) -> Dict[str, NDArray[cirq.Qid]]:
+ """Returns a dictionary of appropriately shaped named qubit signature for input `signature`."""
+
+ def _qubit_array(reg: Register):
+ qubits = np.empty(reg.shape + (reg.bitsize,), dtype=object)
+ for ii in reg.all_idxs():
+ for j in range(reg.bitsize):
+ prefix = "" if not ii else f'[{", ".join(str(i) for i in ii)}]'
+ suffix = "" if reg.bitsize == 1 else f"[{j}]"
+ qubits[ii + (j,)] = cirq.NamedQubit(reg.name + prefix + suffix)
+ return qubits
+
+ def _qubits_for_reg(reg: Register):
+ if len(reg.shape) > 0:
+ return _qubit_array(reg)
+
+ return np.array(
+ [cirq.NamedQubit(f"{reg.name}")]
+ if reg.total_bits() == 1
+ else cirq.NamedQubit.range(reg.total_bits(), prefix=reg.name),
+ dtype=object,
+ )
+
+ return {reg.name: _qubits_for_reg(reg) for reg in registers}
+
+
+class GateWithRegisters(Bloq, cirq.Gate, metaclass=abc.ABCMeta):
+ """`cirq.Gate`s extension with support for composite gates acting on multiple qubit registers.
+
+ Though Cirq was nominally designed for circuit construction for near-term devices the core
+ concept of the `cirq.Gate`, a programmatic representation of an operation on a state without
+ a complete qubit address specification, can be leveraged to describe more abstract algorithmic
+ primitives. To define composite gates, users derive from `cirq.Gate` and implement the
+ `_decompose_` method that yields the sub-operations provided a flat list of qubits.
+
+ This API quickly becomes inconvenient when defining operations that act on multiple qubit
+ registers of variable sizes. Cirq-FT extends the `cirq.Gate` idea by introducing a new abstract
+ base class `cirq_ft.GateWithRegisters` containing abstract methods `registers` and optional
+ method `decompose_from_registers` that provides an overlay to the Cirq flat address API.
+
+ As an example, in the following code snippet we use the `cirq_ft.GateWithRegisters` to
+ construct a multi-target controlled swap operation:
+
+ >>> import attr
+ >>> import cirq
+ >>> import qualtran
+ >>>
+ >>> @attr.frozen
+ ... class MultiTargetCSwap(qualtran.GateWithRegisters):
+ ... bitsize: int
+ ...
+ ... @property
+ ... def signature(self) -> qualtran.Signature:
+ ... return qualtran.Signature.build(ctrl=1, x=self.bitsize, y=self.bitsize)
+ ...
+ ... def decompose_from_registers(self, context, ctrl, x, y) -> cirq.OP_TREE:
+ ... yield [cirq.CSWAP(*ctrl, qx, qy) for qx, qy in zip(x, y)]
+ ...
+ >>> op = MultiTargetCSwap(2).on_registers(
+ ... ctrl=[cirq.q('ctrl')],
+ ... x=cirq.NamedQubit.range(2, prefix='x'),
+ ... y=cirq.NamedQubit.range(2, prefix='y'),
+ ... )
+ >>> print(cirq.Circuit(op))
+ ctrl: ───MultiTargetCSwap───
+ │
+ x0: ─────x──────────────────
+ │
+ x1: ─────x──────────────────
+ │
+ y0: ─────y──────────────────
+ │
+ y1: ─────y──────────────────"""
+
+ # Part-1: Bloq interface is automatically available for users, via default convertors.
+
+ def decompose_bloq(self) -> 'CompositeBloq':
+ """Decompose this Bloq into its constituent parts contained in a CompositeBloq.
+
+ Bloq users can call this function to delve into the definition of a Bloq. If you're
+ trying to define a bloq's decomposition, consider overriding `build_composite_bloq`
+ which provides helpful arguments for implementers.
+
+ Returns:
+ A CompositeBloq containing the decomposition of this Bloq.
+
+ Raises:
+ NotImplementedError: If there is no decomposition defined; namely: if
+ `build_composite_bloq` returns `NotImplemented`.
+ """
+ from qualtran.cirq_interop._cirq_to_bloq import decompose_from_cirq_op
+
+ return decompose_from_cirq_op(self, decompose_once=True)
+
+ def add_my_tensors(
+ self,
+ tn: qtn.TensorNetwork,
+ tag: Any,
+ *,
+ incoming: Dict[str, 'SoquetT'],
+ outgoing: Dict[str, 'SoquetT'],
+ ):
+ from qualtran.cirq_interop._cirq_to_bloq import _add_my_tensors_from_gate
+
+ _add_my_tensors_from_gate(
+ self,
+ self.signature,
+ self.short_name(),
+ tn=tn,
+ tag=tag,
+ incoming=incoming,
+ outgoing=outgoing,
+ )
+
+ def as_cirq_op(
+ self, qubit_manager: 'cirq.QubitManager', **cirq_quregs: 'CirqQuregT'
+ ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]:
+ from qualtran.cirq_interop._bloq_to_cirq import _construct_op_from_gate
+
+ return _construct_op_from_gate(
+ self,
+ in_quregs={k: np.array(v) for k, v in cirq_quregs.items()},
+ qubit_manager=qubit_manager,
+ )
+
+ def t_complexity(self) -> 'TComplexity':
+ from qualtran.cirq_interop.t_complexity_protocol import t_complexity
+
+ return t_complexity(self)
+
+ def wire_symbol(self, soq: 'Soquet') -> 'WireSymbol':
+ from qualtran.cirq_interop._cirq_to_bloq import _wire_symbol_from_gate
+
+ return _wire_symbol_from_gate(self, self.signature, soq)
+
+ # Part-2: Cirq-FT style interface can be used to implemented algorithms by Bloq authors.
+
+ def _num_qubits_(self) -> int:
+ return total_bits(self.signature)
+
+ def decompose_from_registers(
+ self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid]
+ ) -> cirq.OP_TREE:
+ return NotImplemented
+
+ def _decompose_with_context_(
+ self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None
+ ) -> cirq.OP_TREE:
+ qubit_regs = split_qubits(self.signature, qubits)
+ if context is None:
+ context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
+ return self.decompose_from_registers(context=context, **qubit_regs)
+
+ def _decompose_(self, qubits: Sequence[cirq.Qid]) -> cirq.OP_TREE:
+ return self._decompose_with_context_(qubits)
+
+ def on_registers(
+ self, **qubit_regs: Union[cirq.Qid, Sequence[cirq.Qid], NDArray[cirq.Qid]]
+ ) -> cirq.Operation:
+ return self.on(*merge_qubits(self.signature, **qubit_regs))
+
+ def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo:
+ """Default diagram info that uses register names to name the boxes in multi-qubit gates.
+
+ Descendants can override this method with more meaningful circuit diagram information.
+ """
+ wire_symbols = []
+ for reg in self.signature:
+ wire_symbols += [reg.name] * reg.total_bits()
+
+ wire_symbols[0] = self.__class__.__name__
+ return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols)
diff --git a/qualtran/_infra/gate_with_registers_test.py b/qualtran/_infra/gate_with_registers_test.py
new file mode 100644
index 000000000..b25432d90
--- /dev/null
+++ b/qualtran/_infra/gate_with_registers_test.py
@@ -0,0 +1,49 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cirq
+
+from qualtran import GateWithRegisters, Register, Signature
+from qualtran.testing import execute_notebook
+
+
+class _TestGate(GateWithRegisters):
+ @property
+ def signature(self) -> Signature:
+ r1 = Register("r1", 5)
+ r2 = Register("r2", 2)
+ r3 = Register("r3", 1)
+ regs = Signature([r1, r2, r3])
+ return regs
+
+ def decompose_from_registers(self, *, context, **quregs) -> cirq.OP_TREE:
+ yield cirq.H.on_each(quregs['r1'])
+ yield cirq.X.on_each(quregs['r2'])
+ yield cirq.X.on_each(quregs['r3'])
+
+
+def test_gate_with_registers():
+ tg = _TestGate()
+ assert tg._num_qubits_() == 8
+ qubits = cirq.LineQubit.range(8)
+ circ = cirq.Circuit(tg._decompose_(qubits))
+ assert circ.operation_at(cirq.LineQubit(3), 0).gate == cirq.H
+
+ op1 = tg.on_registers(r1=qubits[:5], r2=qubits[6:], r3=qubits[5])
+ op2 = tg.on(*qubits[:5], *qubits[6:], qubits[5])
+ assert op1 == op2
+
+
+def test_notebook():
+ execute_notebook('gate_with_registers')
diff --git a/qualtran/_infra/registers.py b/qualtran/_infra/registers.py
index d37aee001..7b9f5f569 100644
--- a/qualtran/_infra/registers.py
+++ b/qualtran/_infra/registers.py
@@ -19,7 +19,7 @@
from typing import Dict, Iterable, Iterator, List, overload, Tuple, TYPE_CHECKING
import numpy as np
-from attr import frozen
+from attr import field, frozen
from numpy.typing import NDArray
if TYPE_CHECKING:
@@ -61,7 +61,9 @@ class Register:
name: str
bitsize: int
- shape: Tuple[int, ...] = tuple()
+ shape: Tuple[int, ...] = field(
+ default=tuple(), converter=lambda v: (v,) if isinstance(v, int) else tuple(v)
+ )
side: Side = Side.THRU
def all_idxs(self) -> Iterable[Tuple[int, ...]]:
diff --git a/qualtran/bloqs/and_bloq.ipynb b/qualtran/bloqs/and_bloq.ipynb
index ac94ec430..3ce8835bf 100644
--- a/qualtran/bloqs/and_bloq.ipynb
+++ b/qualtran/bloqs/and_bloq.ipynb
@@ -12,12 +12,20 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 1,
"id": "47e0d2c9",
"metadata": {
"cq.autogen": "top_imports"
},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "OMP: Info #270: omp_set_nested routine deprecated, please use omp_set_max_active_levels instead.\n"
+ ]
+ }
+ ],
"source": [
"from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n",
"from qualtran.drawing import show_bloq\n",
@@ -49,12 +57,103 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "4c4ec376",
"metadata": {
"cq.autogen": "_make_and.py"
},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"from qualtran.bloqs.and_bloq import And\n",
"\n",
@@ -64,10 +163,64 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 3,
"id": "310e538e",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"from qualtran.resource_counting import get_bloq_counts_graph, GraphvizCounts, SympySymbolAllocator\n",
"import attrs\n",
@@ -86,10 +239,123 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 4,
"id": "3750053b",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"bb = BloqBuilder()\n",
"q0 = bb.add_register('q0', 1)\n",
@@ -103,10 +369,24 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 5,
"id": "a65ce1f3",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "array([[1.+0.j, 0.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 1.+0.j, 0.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 1.+0.j, 0.+0.j],\n",
+ " [0.+0.j, 0.+0.j, 0.+0.j, 1.+0.j]])"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
"source": [
"mat = cbloq.tensor_contract()\n",
"np.testing.assert_allclose(np.eye(4), mat)\n",
@@ -134,12 +414,185 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 6,
"id": "51233de9",
"metadata": {
"cq.autogen": "_make_multi_and.py"
},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"from qualtran.bloqs.and_bloq import MultiAnd\n",
"\n",
@@ -149,10 +602,183 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 7,
"id": "dac3eaba",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "data": {
+ "image/svg+xml": [
+ ""
+ ],
+ "text/plain": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
"source": [
"bloq = MultiAnd(cvs=(1, 1, 1, 1))\n",
"show_bloq(bloq.decompose_bloq())"
@@ -160,10 +786,50 @@
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 8,
"id": "a61eb113",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "ename": "RecursionError",
+ "evalue": "maximum recursion depth exceeded while calling a Python object",
+ "output_type": "error",
+ "traceback": [
+ "\u001B[0;31m---------------------------------------------------------------------------\u001B[0m",
+ "\u001B[0;31mRecursionError\u001B[0m Traceback (most recent call last)",
+ "Cell \u001B[0;32mIn[8], line 1\u001B[0m\n\u001B[0;32m----> 1\u001B[0m graph, sigma \u001B[38;5;241m=\u001B[39m \u001B[43mget_bloq_counts_graph\u001B[49m\u001B[43m(\u001B[49m\u001B[43mbloq\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 2\u001B[0m GraphvizCounts(graph)\u001B[38;5;241m.\u001B[39mget_svg()\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/resource_counting/bloq_counts.py:176\u001B[0m, in \u001B[0;36mget_bloq_counts_graph\u001B[0;34m(bloq, generalizer, ssa, keep)\u001B[0m\n\u001B[1;32m 174\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m bloq \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m 175\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mYou can\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mt generalize away the root bloq.\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m--> 176\u001B[0m sigma \u001B[38;5;241m=\u001B[39m \u001B[43m_descend_counts\u001B[49m\u001B[43m(\u001B[49m\u001B[43mbloq\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mssa\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mgeneralizer\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkeep\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 177\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m g, sigma\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/resource_counting/bloq_counts.py:130\u001B[0m, in \u001B[0;36m_descend_counts\u001B[0;34m(parent, g, ssa, generalizer, keep)\u001B[0m\n\u001B[1;32m 127\u001B[0m g\u001B[38;5;241m.\u001B[39madd_edge(parent, child, n\u001B[38;5;241m=\u001B[39mn)\n\u001B[1;32m 129\u001B[0m \u001B[38;5;66;03m# Do the recursive step, which will continue to mutate `g`\u001B[39;00m\n\u001B[0;32m--> 130\u001B[0m child_counts \u001B[38;5;241m=\u001B[39m \u001B[43m_descend_counts\u001B[49m\u001B[43m(\u001B[49m\u001B[43mchild\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mssa\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mgeneralizer\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkeep\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 132\u001B[0m \u001B[38;5;66;03m# Update `sigma` with the recursion results.\u001B[39;00m\n\u001B[1;32m 133\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m k \u001B[38;5;129;01min\u001B[39;00m child_counts\u001B[38;5;241m.\u001B[39mkeys():\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/resource_counting/bloq_counts.py:130\u001B[0m, in \u001B[0;36m_descend_counts\u001B[0;34m(parent, g, ssa, generalizer, keep)\u001B[0m\n\u001B[1;32m 127\u001B[0m g\u001B[38;5;241m.\u001B[39madd_edge(parent, child, n\u001B[38;5;241m=\u001B[39mn)\n\u001B[1;32m 129\u001B[0m \u001B[38;5;66;03m# Do the recursive step, which will continue to mutate `g`\u001B[39;00m\n\u001B[0;32m--> 130\u001B[0m child_counts \u001B[38;5;241m=\u001B[39m \u001B[43m_descend_counts\u001B[49m\u001B[43m(\u001B[49m\u001B[43mchild\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mssa\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mgeneralizer\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkeep\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 132\u001B[0m \u001B[38;5;66;03m# Update `sigma` with the recursion results.\u001B[39;00m\n\u001B[1;32m 133\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m k \u001B[38;5;129;01min\u001B[39;00m child_counts\u001B[38;5;241m.\u001B[39mkeys():\n",
+ " \u001B[0;31m[... skipping similar frames: _descend_counts at line 130 (2944 times)]\u001B[0m\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/resource_counting/bloq_counts.py:130\u001B[0m, in \u001B[0;36m_descend_counts\u001B[0;34m(parent, g, ssa, generalizer, keep)\u001B[0m\n\u001B[1;32m 127\u001B[0m g\u001B[38;5;241m.\u001B[39madd_edge(parent, child, n\u001B[38;5;241m=\u001B[39mn)\n\u001B[1;32m 129\u001B[0m \u001B[38;5;66;03m# Do the recursive step, which will continue to mutate `g`\u001B[39;00m\n\u001B[0;32m--> 130\u001B[0m child_counts \u001B[38;5;241m=\u001B[39m \u001B[43m_descend_counts\u001B[49m\u001B[43m(\u001B[49m\u001B[43mchild\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mg\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mssa\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mgeneralizer\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mkeep\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 132\u001B[0m \u001B[38;5;66;03m# Update `sigma` with the recursion results.\u001B[39;00m\n\u001B[1;32m 133\u001B[0m \u001B[38;5;28;01mfor\u001B[39;00m k \u001B[38;5;129;01min\u001B[39;00m child_counts\u001B[38;5;241m.\u001B[39mkeys():\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/resource_counting/bloq_counts.py:111\u001B[0m, in \u001B[0;36m_descend_counts\u001B[0;34m(parent, g, ssa, generalizer, keep)\u001B[0m\n\u001B[1;32m 109\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m {parent: \u001B[38;5;241m1\u001B[39m}\n\u001B[1;32m 110\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n\u001B[0;32m--> 111\u001B[0m count_decomp \u001B[38;5;241m=\u001B[39m \u001B[43mparent\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mbloq_counts\u001B[49m\u001B[43m(\u001B[49m\u001B[43mssa\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 112\u001B[0m \u001B[38;5;28;01mexcept\u001B[39;00m \u001B[38;5;167;01mNotImplementedError\u001B[39;00m:\n\u001B[1;32m 113\u001B[0m \u001B[38;5;66;03m# Base case 2: Decomposition (or `bloq_counts`) is not implemented. This is left as a\u001B[39;00m\n\u001B[1;32m 114\u001B[0m \u001B[38;5;66;03m# leaf node.\u001B[39;00m\n\u001B[1;32m 115\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m {parent: \u001B[38;5;241m1\u001B[39m}\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/bloq.py:243\u001B[0m, in \u001B[0;36mBloq.bloq_counts\u001B[0;34m(self, ssa)\u001B[0m\n\u001B[1;32m 234\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mbloq_counts\u001B[39m(\u001B[38;5;28mself\u001B[39m, ssa: Optional[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mSympySymbolAllocator\u001B[39m\u001B[38;5;124m'\u001B[39m] \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Set[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mBloqCountT\u001B[39m\u001B[38;5;124m'\u001B[39m]:\n\u001B[1;32m 235\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Return a set of `(n, bloq)` tuples where bloq is used `n` times in the decomposition.\u001B[39;00m\n\u001B[1;32m 236\u001B[0m \n\u001B[1;32m 237\u001B[0m \u001B[38;5;124;03m By default, this method will use `self.decompose_bloq()` to count up bloqs.\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 241\u001B[0m \u001B[38;5;124;03m sympy symbols (perhaps with the aid of the provided `SympySymbolAllocator`).\u001B[39;00m\n\u001B[1;32m 242\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 243\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mdecompose_bloq\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[38;5;241m.\u001B[39mbloq_counts(ssa)\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:166\u001B[0m, in \u001B[0;36mGateWithRegisters.decompose_bloq\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 151\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Decompose this Bloq into its constituent parts contained in a CompositeBloq.\u001B[39;00m\n\u001B[1;32m 152\u001B[0m \n\u001B[1;32m 153\u001B[0m \u001B[38;5;124;03mBloq users can call this function to delve into the definition of a Bloq. If you're\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 162\u001B[0m \u001B[38;5;124;03m `build_composite_bloq` returns `NotImplemented`.\u001B[39;00m\n\u001B[1;32m 163\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 164\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mqualtran\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mcirq_interop\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01m_cirq_to_bloq\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m decompose_from_cirq_op\n\u001B[0;32m--> 166\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mdecompose_from_cirq_op\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/cirq_interop/_cirq_to_bloq.py:391\u001B[0m, in \u001B[0;36mdecompose_from_cirq_op\u001B[0;34m(bloq)\u001B[0m\n\u001B[1;32m 389\u001B[0m qubit_manager \u001B[38;5;241m=\u001B[39m InteropQubitManager()\n\u001B[1;32m 390\u001B[0m in_quregs \u001B[38;5;241m=\u001B[39m get_cirq_quregs(bloq\u001B[38;5;241m.\u001B[39msignature, qubit_manager)\n\u001B[0;32m--> 391\u001B[0m cirq_op, out_quregs \u001B[38;5;241m=\u001B[39m \u001B[43mbloq\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mas_cirq_op\u001B[49m\u001B[43m(\u001B[49m\u001B[43mqubit_manager\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43min_quregs\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 392\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mqualtran\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mcirq_interop\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01m_bloq_to_cirq\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m BloqAsCirqGate\n\u001B[1;32m 394\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m cirq_op \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;129;01mor\u001B[39;00m (\n\u001B[1;32m 395\u001B[0m \u001B[38;5;28misinstance\u001B[39m(cirq_op, cirq\u001B[38;5;241m.\u001B[39mOperation) \u001B[38;5;129;01mand\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(cirq_op\u001B[38;5;241m.\u001B[39mgate, BloqAsCirqGate)\n\u001B[1;32m 396\u001B[0m ):\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:193\u001B[0m, in \u001B[0;36mGateWithRegisters.as_cirq_op\u001B[0;34m(self, qubit_manager, **cirq_quregs)\u001B[0m\n\u001B[1;32m 188\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mas_cirq_op\u001B[39m(\n\u001B[1;32m 189\u001B[0m \u001B[38;5;28mself\u001B[39m, qubit_manager: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcirq.QubitManager\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mcirq_quregs: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mCirqQuregT\u001B[39m\u001B[38;5;124m'\u001B[39m\n\u001B[1;32m 190\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Tuple[Union[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcirq.Operation\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;28;01mNone\u001B[39;00m], Dict[\u001B[38;5;28mstr\u001B[39m, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mCirqQuregT\u001B[39m\u001B[38;5;124m'\u001B[39m]]:\n\u001B[1;32m 191\u001B[0m \u001B[38;5;28;01mfrom\u001B[39;00m \u001B[38;5;21;01mqualtran\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01mcirq_interop\u001B[39;00m\u001B[38;5;21;01m.\u001B[39;00m\u001B[38;5;21;01m_bloq_to_cirq\u001B[39;00m \u001B[38;5;28;01mimport\u001B[39;00m _construct_op_from_gate\n\u001B[0;32m--> 193\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_construct_op_from_gate\u001B[49m\u001B[43m(\u001B[49m\n\u001B[1;32m 194\u001B[0m \u001B[43m \u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\n\u001B[1;32m 195\u001B[0m \u001B[43m \u001B[49m\u001B[43min_quregs\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43m{\u001B[49m\u001B[43mk\u001B[49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[43mnp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43marray\u001B[49m\u001B[43m(\u001B[49m\u001B[43mv\u001B[49m\u001B[43m)\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43;01mfor\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mk\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mv\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;129;43;01min\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mcirq_quregs\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mitems\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\u001B[43m}\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 196\u001B[0m \u001B[43m \u001B[49m\u001B[43mqubit_manager\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mqubit_manager\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 197\u001B[0m \u001B[43m \u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/cirq_interop/_bloq_to_cirq.py:295\u001B[0m, in \u001B[0;36m_construct_op_from_gate\u001B[0;34m(gate, in_quregs, qubit_manager)\u001B[0m\n\u001B[1;32m 292\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m reg\u001B[38;5;241m.\u001B[39mside \u001B[38;5;241m&\u001B[39m Side\u001B[38;5;241m.\u001B[39mRIGHT:\n\u001B[1;32m 293\u001B[0m \u001B[38;5;66;03m# Right registers should be part of the output.\u001B[39;00m\n\u001B[1;32m 294\u001B[0m out_quregs[reg\u001B[38;5;241m.\u001B[39mname] \u001B[38;5;241m=\u001B[39m all_quregs[reg\u001B[38;5;241m.\u001B[39mname]\n\u001B[0;32m--> 295\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mgate\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mon_registers\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mall_quregs\u001B[49m\u001B[43m)\u001B[49m, out_quregs\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:233\u001B[0m, in \u001B[0;36mGateWithRegisters.on_registers\u001B[0;34m(self, **qubit_regs)\u001B[0m\n\u001B[1;32m 230\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mon_registers\u001B[39m(\n\u001B[1;32m 231\u001B[0m \u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mqubit_regs: Union[cirq\u001B[38;5;241m.\u001B[39mQid, Sequence[cirq\u001B[38;5;241m.\u001B[39mQid], NDArray[cirq\u001B[38;5;241m.\u001B[39mQid]]\n\u001B[1;32m 232\u001B[0m ) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m cirq\u001B[38;5;241m.\u001B[39mOperation:\n\u001B[0;32m--> 233\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mon\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mmerge_qubits\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msignature\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mqubit_regs\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/ops/raw_types.py:233\u001B[0m, in \u001B[0;36mGate.on\u001B[0;34m(self, *qubits)\u001B[0m\n\u001B[1;32m 224\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mon\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39mqubits: Qid) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mOperation\u001B[39m\u001B[38;5;124m'\u001B[39m:\n\u001B[1;32m 225\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Returns an application of this gate to the given qubits.\u001B[39;00m\n\u001B[1;32m 226\u001B[0m \n\u001B[1;32m 227\u001B[0m \u001B[38;5;124;03m Args:\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 231\u001B[0m \u001B[38;5;124;03m qubits.\u001B[39;00m\n\u001B[1;32m 232\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m--> 233\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mops\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mgate_operation\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mGateOperation\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mlist\u001B[39;49m\u001B[43m(\u001B[49m\u001B[43mqubits\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/ops/gate_operation.py:60\u001B[0m, in \u001B[0;36mGateOperation.__init__\u001B[0;34m(self, gate, qubits)\u001B[0m\n\u001B[1;32m 53\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m__init__\u001B[39m(\u001B[38;5;28mself\u001B[39m, gate: \u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcirq.Gate\u001B[39m\u001B[38;5;124m'\u001B[39m, qubits: Sequence[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcirq.Qid\u001B[39m\u001B[38;5;124m'\u001B[39m]) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m 54\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Inits GateOperation.\u001B[39;00m\n\u001B[1;32m 55\u001B[0m \n\u001B[1;32m 56\u001B[0m \u001B[38;5;124;03m Args:\u001B[39;00m\n\u001B[1;32m 57\u001B[0m \u001B[38;5;124;03m gate: The gate to apply.\u001B[39;00m\n\u001B[1;32m 58\u001B[0m \u001B[38;5;124;03m qubits: The qubits to operate on.\u001B[39;00m\n\u001B[1;32m 59\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m---> 60\u001B[0m \u001B[43mgate\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mvalidate_args\u001B[49m\u001B[43m(\u001B[49m\u001B[43mqubits\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 61\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_gate \u001B[38;5;241m=\u001B[39m gate\n\u001B[1;32m 62\u001B[0m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_qubits \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mtuple\u001B[39m(qubits)\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/ops/raw_types.py:222\u001B[0m, in \u001B[0;36mGate.validate_args\u001B[0;34m(self, qubits)\u001B[0m\n\u001B[1;32m 205\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Checks if this gate can be applied to the given qubits.\u001B[39;00m\n\u001B[1;32m 206\u001B[0m \n\u001B[1;32m 207\u001B[0m \u001B[38;5;124;03mBy default checks that:\u001B[39;00m\n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 219\u001B[0m \u001B[38;5;124;03m ValueError: The gate can't be applied to the qubits.\u001B[39;00m\n\u001B[1;32m 220\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 221\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m __cirq_debug__\u001B[38;5;241m.\u001B[39mget():\n\u001B[0;32m--> 222\u001B[0m \u001B[43m_validate_qid_shape\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mqubits\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/ops/raw_types.py:1042\u001B[0m, in \u001B[0;36m_validate_qid_shape\u001B[0;34m(val, qubits)\u001B[0m\n\u001B[1;32m 1036\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_validate_qid_shape\u001B[39m(val: Any, qubits: Sequence[\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mcirq.Qid\u001B[39m\u001B[38;5;124m'\u001B[39m]) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28;01mNone\u001B[39;00m:\n\u001B[1;32m 1037\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Helper function to validate qubits for gates and operations.\u001B[39;00m\n\u001B[1;32m 1038\u001B[0m \n\u001B[1;32m 1039\u001B[0m \u001B[38;5;124;03m Raises:\u001B[39;00m\n\u001B[1;32m 1040\u001B[0m \u001B[38;5;124;03m ValueError: The operation had qids that don't match it's qid shape.\u001B[39;00m\n\u001B[1;32m 1041\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m-> 1042\u001B[0m qid_shape \u001B[38;5;241m=\u001B[39m \u001B[43mprotocols\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mqid_shape\u001B[49m\u001B[43m(\u001B[49m\u001B[43mval\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 1043\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(qubits) \u001B[38;5;241m!=\u001B[39m \u001B[38;5;28mlen\u001B[39m(qid_shape):\n\u001B[1;32m 1044\u001B[0m \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\n\u001B[1;32m 1045\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mWrong number of qubits for <\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mval\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m>. \u001B[39m\u001B[38;5;124m'\u001B[39m\n\u001B[1;32m 1046\u001B[0m \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m'\u001B[39m\u001B[38;5;124mExpected \u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mlen\u001B[39m(qid_shape)\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m qubits but got <\u001B[39m\u001B[38;5;132;01m{\u001B[39;00mqubits\u001B[38;5;132;01m!r}\u001B[39;00m\u001B[38;5;124m>.\u001B[39m\u001B[38;5;124m'\u001B[39m\n\u001B[1;32m 1047\u001B[0m )\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/protocols/qid_shape_protocol.py:107\u001B[0m, in \u001B[0;36mqid_shape\u001B[0;34m(val, default)\u001B[0m\n\u001B[1;32m 84\u001B[0m \u001B[38;5;250m\u001B[39m\u001B[38;5;124;03m\"\"\"Returns a tuple describing the number of quantum levels of each\u001B[39;00m\n\u001B[1;32m 85\u001B[0m \u001B[38;5;124;03mqubit/qudit/qid `val` operates on.\u001B[39;00m\n\u001B[1;32m 86\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 104\u001B[0m \u001B[38;5;124;03m was specified.\u001B[39;00m\n\u001B[1;32m 105\u001B[0m \u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 106\u001B[0m getter \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mgetattr\u001B[39m(val, \u001B[38;5;124m'\u001B[39m\u001B[38;5;124m_qid_shape_\u001B[39m\u001B[38;5;124m'\u001B[39m, \u001B[38;5;28;01mNone\u001B[39;00m)\n\u001B[0;32m--> 107\u001B[0m result \u001B[38;5;241m=\u001B[39m \u001B[38;5;28mNotImplemented\u001B[39m \u001B[38;5;28;01mif\u001B[39;00m getter \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28;01mNone\u001B[39;00m \u001B[38;5;28;01melse\u001B[39;00m \u001B[43mgetter\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 108\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m result \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m \u001B[38;5;28mNotImplemented\u001B[39m:\n\u001B[1;32m 109\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m result\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/value/abc_alt.py:137\u001B[0m, in \u001B[0;36mABCMetaImplementAnyOneOf.__new__..wrap_scope..impl_of_abstract\u001B[0;34m(*args, **kwargs)\u001B[0m\n\u001B[1;32m 136\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mimpl_of_abstract\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m--> 137\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mimpl\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Cirq/cirq-core/cirq/ops/raw_types.py:445\u001B[0m, in \u001B[0;36mGate._default_shape_from_num_qubits\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 444\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_default_shape_from_num_qubits\u001B[39m(\u001B[38;5;28mself\u001B[39m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m Tuple[\u001B[38;5;28mint\u001B[39m, \u001B[38;5;241m.\u001B[39m\u001B[38;5;241m.\u001B[39m\u001B[38;5;241m.\u001B[39m]:\n\u001B[0;32m--> 445\u001B[0m num_qubits \u001B[38;5;241m=\u001B[39m \u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43m_num_qubits_\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m\n\u001B[1;32m 446\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m num_qubits \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;28mNotImplemented\u001B[39m:\n\u001B[1;32m 447\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mNotImplemented\u001B[39m\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:212\u001B[0m, in \u001B[0;36mGateWithRegisters._num_qubits_\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 211\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_num_qubits_\u001B[39m(\u001B[38;5;28mself\u001B[39m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[0;32m--> 212\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mtotal_bits\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43msignature\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:35\u001B[0m, in \u001B[0;36mtotal_bits\u001B[0;34m(registers)\u001B[0m\n\u001B[1;32m 33\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mtotal_bits\u001B[39m(registers: Iterable[Register]) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[1;32m 34\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Sum of `reg.total_bits()` for each register `reg` in input `signature`.\"\"\"\u001B[39;00m\n\u001B[0;32m---> 35\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28msum\u001B[39m(reg\u001B[38;5;241m.\u001B[39mtotal_bits() \u001B[38;5;28;01mfor\u001B[39;00m reg \u001B[38;5;129;01min\u001B[39;00m registers)\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/gate_with_registers.py:35\u001B[0m, in \u001B[0;36m\u001B[0;34m(.0)\u001B[0m\n\u001B[1;32m 33\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mtotal_bits\u001B[39m(registers: Iterable[Register]) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[1;32m 34\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"Sum of `reg.total_bits()` for each register `reg` in input `signature`.\"\"\"\u001B[39;00m\n\u001B[0;32m---> 35\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28msum\u001B[39m(\u001B[43mreg\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mtotal_bits\u001B[49m\u001B[43m(\u001B[49m\u001B[43m)\u001B[49m \u001B[38;5;28;01mfor\u001B[39;00m reg \u001B[38;5;129;01min\u001B[39;00m registers)\n",
+ "File \u001B[0;32m~/quantum/Qualtran/qualtran/_infra/registers.py:76\u001B[0m, in \u001B[0;36mRegister.total_bits\u001B[0;34m(self)\u001B[0m\n\u001B[1;32m 71\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mtotal_bits\u001B[39m(\u001B[38;5;28mself\u001B[39m) \u001B[38;5;241m-\u001B[39m\u001B[38;5;241m>\u001B[39m \u001B[38;5;28mint\u001B[39m:\n\u001B[1;32m 72\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"The total number of bits in this register.\u001B[39;00m\n\u001B[1;32m 73\u001B[0m \n\u001B[1;32m 74\u001B[0m \u001B[38;5;124;03m This is the product of bitsize and each of the dimensions in `shape`.\u001B[39;00m\n\u001B[1;32m 75\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m---> 76\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39mbitsize \u001B[38;5;241m*\u001B[39m \u001B[38;5;28mint\u001B[39m(\u001B[43mnp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mproduct\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mshape\u001B[49m\u001B[43m)\u001B[49m)\n",
+ "File \u001B[0;32m<__array_function__ internals>:200\u001B[0m, in \u001B[0;36mproduct\u001B[0;34m(*args, **kwargs)\u001B[0m\n",
+ "File \u001B[0;32m~/opt/anaconda3/envs/qualtran/lib/python3.11/site-packages/numpy/core/fromnumeric.py:3775\u001B[0m, in \u001B[0;36mproduct\u001B[0;34m(*args, **kwargs)\u001B[0m\n\u001B[1;32m 3766\u001B[0m \u001B[38;5;129m@array_function_dispatch\u001B[39m(_prod_dispatcher, verify\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mFalse\u001B[39;00m)\n\u001B[1;32m 3767\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mproduct\u001B[39m(\u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[1;32m 3768\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 3769\u001B[0m \u001B[38;5;124;03m Return the product of array elements over a given axis.\u001B[39;00m\n\u001B[1;32m 3770\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 3773\u001B[0m \u001B[38;5;124;03m prod : equivalent function; see for details.\u001B[39;00m\n\u001B[1;32m 3774\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m-> 3775\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mprod\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m<__array_function__ internals>:200\u001B[0m, in \u001B[0;36mprod\u001B[0;34m(*args, **kwargs)\u001B[0m\n",
+ "File \u001B[0;32m~/opt/anaconda3/envs/qualtran/lib/python3.11/site-packages/numpy/core/fromnumeric.py:3076\u001B[0m, in \u001B[0;36mprod\u001B[0;34m(a, axis, dtype, out, keepdims, initial, where)\u001B[0m\n\u001B[1;32m 2955\u001B[0m \u001B[38;5;129m@array_function_dispatch\u001B[39m(_prod_dispatcher)\n\u001B[1;32m 2956\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mprod\u001B[39m(a, axis\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, dtype\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, out\u001B[38;5;241m=\u001B[39m\u001B[38;5;28;01mNone\u001B[39;00m, keepdims\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue,\n\u001B[1;32m 2957\u001B[0m initial\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue, where\u001B[38;5;241m=\u001B[39mnp\u001B[38;5;241m.\u001B[39m_NoValue):\n\u001B[1;32m 2958\u001B[0m \u001B[38;5;250m \u001B[39m\u001B[38;5;124;03m\"\"\"\u001B[39;00m\n\u001B[1;32m 2959\u001B[0m \u001B[38;5;124;03m Return the product of array elements over a given axis.\u001B[39;00m\n\u001B[1;32m 2960\u001B[0m \n\u001B[0;32m (...)\u001B[0m\n\u001B[1;32m 3074\u001B[0m \u001B[38;5;124;03m 10\u001B[39;00m\n\u001B[1;32m 3075\u001B[0m \u001B[38;5;124;03m \"\"\"\u001B[39;00m\n\u001B[0;32m-> 3076\u001B[0m \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43m_wrapreduction\u001B[49m\u001B[43m(\u001B[49m\u001B[43ma\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mnp\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mmultiply\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[38;5;124;43mprod\u001B[39;49m\u001B[38;5;124;43m'\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43maxis\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mdtype\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mout\u001B[49m\u001B[43m,\u001B[49m\n\u001B[1;32m 3077\u001B[0m \u001B[43m \u001B[49m\u001B[43mkeepdims\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mkeepdims\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43minitial\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43minitial\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mwhere\u001B[49m\u001B[38;5;241;43m=\u001B[39;49m\u001B[43mwhere\u001B[49m\u001B[43m)\u001B[49m\n",
+ "File \u001B[0;32m~/opt/anaconda3/envs/qualtran/lib/python3.11/site-packages/numpy/core/fromnumeric.py:70\u001B[0m, in \u001B[0;36m_wrapreduction\u001B[0;34m(obj, ufunc, method, axis, dtype, out, **kwargs)\u001B[0m\n\u001B[1;32m 69\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21m_wrapreduction\u001B[39m(obj, ufunc, method, axis, dtype, out, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[0;32m---> 70\u001B[0m passkwargs \u001B[38;5;241m=\u001B[39m {k: v \u001B[38;5;28;01mfor\u001B[39;00m k, v \u001B[38;5;129;01min\u001B[39;00m kwargs\u001B[38;5;241m.\u001B[39mitems()\n\u001B[1;32m 71\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m v \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m np\u001B[38;5;241m.\u001B[39m_NoValue}\n\u001B[1;32m 73\u001B[0m \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mtype\u001B[39m(obj) \u001B[38;5;129;01mis\u001B[39;00m \u001B[38;5;129;01mnot\u001B[39;00m mu\u001B[38;5;241m.\u001B[39mndarray:\n\u001B[1;32m 74\u001B[0m \u001B[38;5;28;01mtry\u001B[39;00m:\n",
+ "\u001B[0;31mRecursionError\u001B[0m: maximum recursion depth exceeded while calling a Python object"
+ ]
+ }
+ ],
"source": [
"graph, sigma = get_bloq_counts_graph(bloq)\n",
"GraphvizCounts(graph).get_svg()"
@@ -279,9 +945,7 @@
"cell_type": "code",
"execution_count": null,
"id": "f93ce8ed",
- "metadata": {
- "scrolled": false
- },
+ "metadata": {},
"outputs": [],
"source": [
"from qualtran.bloqs.and_bloq import MultiAnd\n",
@@ -354,9 +1018,9 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.10.9"
+ "version": "3.11.4"
}
},
"nbformat": 4,
"nbformat_minor": 5
-}
+}
\ No newline at end of file
diff --git a/qualtran/bloqs/and_bloq.py b/qualtran/bloqs/and_bloq.py
index b0ab95f9c..d0c2216a3 100644
--- a/qualtran/bloqs/and_bloq.py
+++ b/qualtran/bloqs/and_bloq.py
@@ -16,22 +16,24 @@
from functools import cached_property
from typing import Any, Dict, Optional, Set, Tuple
+import cirq
import numpy as np
import quimb.tensor as qtn
import sympy
from attrs import field, frozen
from numpy.typing import NDArray
-from qualtran import Bloq, Register, Side, Signature, Soquet, SoquetT
+from qualtran import Bloq, GateWithRegisters, Register, Side, Signature, Soquet, SoquetT
from qualtran.bloqs.basic_gates import TGate
from qualtran.bloqs.util_bloqs import ArbitraryClifford
+from qualtran.cirq_interop.t_complexity_protocol import TComplexity
from qualtran.drawing import Circle, directional_text_box, WireSymbol
from qualtran.resource_counting import big_O, SympySymbolAllocator
@frozen
-class And(Bloq):
- """A two-bit 'and' operation.
+class And(GateWithRegisters):
+ """A two-bit 'and' operation optimized for T-count.
Args:
cv1: Whether the first bit is a positive control.
@@ -90,7 +92,6 @@ def add_my_tensors(
incoming: Dict[str, SoquetT],
outgoing: Dict[str, SoquetT],
):
-
# Fill in our tensor using "and" logic.
data = np.zeros((2, 2, 2, 2, 2), dtype=np.complex128)
for c1, c2 in itertools.product((0, 1), repeat=2):
@@ -127,9 +128,62 @@ def wire_symbol(self, soq: 'Soquet') -> 'WireSymbol':
filled = bool(self.cv1 if c_idx == 0 else self.cv2)
return Circle(filled)
+ def decompose_from_registers(
+ self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid]
+ ) -> cirq.OP_TREE:
+ """Decomposes a single `And` gate on 2 controls and 1 target in terms of Clifford+T gates.
+
+ * And(cv).on(c1, c2, target) uses 4 T-gates and assumes target is in |0> state.
+ * And(cv, adjoint=True).on(c1, c2, target) uses measurement based un-computation
+ (0 T-gates) and will always leave the target in |0> state.
+ """
+ (c1, c2), (target,) = (quregs['ctrl'].flatten(), quregs['target'].flatten())
+ pre_post_ops = [cirq.X(q) for (q, v) in zip([c1, c2], [self.cv1, self.cv2]) if v == 0]
+ yield pre_post_ops
+ if self.adjoint:
+ yield cirq.H(target)
+ yield cirq.measure(target, key=f"{target}")
+ yield cirq.CZ(c1, c2).with_classical_controls(f"{target}")
+ yield cirq.reset(target)
+ else:
+ yield [cirq.H(target), cirq.T(target)]
+ yield [cirq.CNOT(c1, target), cirq.CNOT(c2, target)]
+ yield [cirq.CNOT(target, c1), cirq.CNOT(target, c2)]
+ yield [cirq.T(c1) ** -1, cirq.T(c2) ** -1, cirq.T(target)]
+ yield [cirq.CNOT(target, c1), cirq.CNOT(target, c2)]
+ yield [cirq.H(target), cirq.S(target)]
+ yield pre_post_ops
+
+ def __pow__(self, power: int) -> "And":
+ if power == 1:
+ return self
+ if power == -1:
+ return And(self.cv1, self.cv2, adjoint=self.adjoint ^ True)
+ return NotImplemented # pragma: no cover
+
+ def __str__(self) -> str:
+ suffix = "" if self.cv1 == self.cv2 == 1 else str((self.cv1, self.cv2))
+ return f"And†{suffix}" if self.adjoint else f"And{suffix}"
+
+ def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo:
+ controls = ["(0)", "@"]
+ target = "And†" if self.adjoint else "And"
+ wire_symbols = [controls[self.cv1], controls[self.cv2], target]
+ return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols)
+
+ def _has_unitary_(self) -> bool:
+ return not self.adjoint
+
+ def _t_complexity_(self) -> TComplexity:
+ pre_post_cliffords = 2 - self.cv1 - self.cv2 # number of zeros in self.cv
+ if self.adjoint:
+ return TComplexity(clifford=4 + 2 * pre_post_cliffords)
+ else:
+ return TComplexity(t=4 * 1, clifford=9 + 2 * pre_post_cliffords)
+
@frozen
-class MultiAnd(Bloq):
+class MultiAnd(GateWithRegisters):
"""A many-bit (multi-control) 'and' operation.
Args:
@@ -160,11 +214,11 @@ def pretty_name(self) -> str:
dag = '†' if self.adjoint else ''
return f'And{dag}'
- def decompose_bloq(self) -> 'CompositeBloq':
- cbloq = super().decompose_bloq()
- if self.adjoint:
- raise NotImplementedError("Come back soon.")
- return cbloq
+ # def decompose_bloq(self) -> 'CompositeBloq':
+ # cbloq = Bloq.decompose_bloq(self)
+ # if self.adjoint:
+ # raise NotImplementedError("Come back soon.")
+ # return cbloq
def build_composite_bloq(
self, bb: 'BloqBuilder', *, ctrl: NDArray[Soquet]
@@ -205,3 +259,71 @@ def on_classical_vals(self, ctrl: NDArray[np.uint8]) -> Dict[str, NDArray[np.uin
accumulate_and = np.bitwise_and.accumulate(np.equal(ctrl, self.cvs).astype(np.uint8))
junk, target = accumulate_and[1:-1], accumulate_and[-1]
return {'ctrl': ctrl, 'junk': junk, 'target': target}
+
+ def __pow__(self, power: int) -> "And":
+ if power == 1:
+ return self
+ if power == -1:
+ return And(self.cvs, adjoint=self.adjoint ^ True)
+ return NotImplemented # pragma: no cover
+
+ def __str__(self) -> str:
+ suffix = "" if self.cvs == (1,) * len(self.cvs) else str(self.cvs)
+ return f"And†{suffix}" if self.adjoint else f"And{suffix}"
+
+ def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo:
+ controls = ["(0)", "@"]
+ target = "And†" if self.adjoint else "And"
+ wire_symbols = [controls[c] for c in self.cvs]
+ wire_symbols += ["Anc"] * (len(self.cvs) - 2)
+ wire_symbols += [target]
+ return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols)
+
+ def _has_unitary_(self) -> bool:
+ return not self.adjoint
+
+ def _decompose_via_tree(
+ self,
+ controls: NDArray[cirq.Qid],
+ control_values: Tuple[int, ...],
+ ancillas: NDArray[cirq.Qid],
+ target: cirq.Qid,
+ ) -> cirq.ops.op_tree.OpTree:
+ """Decomposes multi-controlled `And` in-terms of an `And` ladder of size #controls- 2."""
+
+ if len(controls) == 2:
+ yield And(*control_values, adjoint=self.adjoint).on(*controls, target)
+ return
+ new_controls = np.concatenate([ancillas[0:1], controls[2:]])
+ new_control_values = (1, *control_values[2:])
+ and_op = And(*control_values[:2], adjoint=self.adjoint).on(*controls[:2], ancillas[0])
+ if self.adjoint:
+ yield from self._decompose_via_tree(
+ new_controls, new_control_values, ancillas[1:], target
+ )
+ yield and_op
+ else:
+ yield and_op
+ yield from self._decompose_via_tree(
+ new_controls, new_control_values, ancillas[1:], target
+ )
+
+ def decompose_from_registers(
+ self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid]
+ ) -> cirq.OP_TREE:
+ control, ancilla, target = (
+ quregs['ctrl'].flatten(),
+ quregs.get('junk', np.array([])).flatten(),
+ quregs['target'].flatten(),
+ )
+ yield self._decompose_via_tree(control, self.cvs, ancilla, *target)
+
+ def _t_complexity_(self) -> TComplexity:
+ pre_post_cliffords = len(self.cvs) - sum(self.cvs) # number of zeros in self.cv
+ num_single_and = len(self.cv) - 1
+ if self.adjoint:
+ return TComplexity(clifford=4 * num_single_and + 2 * pre_post_cliffords)
+ else:
+ return TComplexity(
+ t=4 * num_single_and, clifford=9 * num_single_and + 2 * pre_post_cliffords
+ )
diff --git a/qualtran/bloqs/and_bloq_test.py b/qualtran/bloqs/and_bloq_test.py
index 334eb59ee..8d44c3177 100644
--- a/qualtran/bloqs/and_bloq_test.py
+++ b/qualtran/bloqs/and_bloq_test.py
@@ -144,7 +144,7 @@ def test_multi_truth_table():
# Tensor simulation
vec = cbloq.tensor_contract()
should_be = np.all(ctrl_string == cvs)
- *junk_is, res_i = np.where(vec.reshape((2,) * (n - 1)))
+ *junk_is, res_i = np.where(abs(vec.reshape((2,) * (n - 1))) > 1e-10)
assert res_i == should_be, ctrl_string
# Classical simulation
diff --git a/qualtran/cirq_interop/_bloq_to_cirq.py b/qualtran/cirq_interop/_bloq_to_cirq.py
index dd545901e..bc9c0f154 100644
--- a/qualtran/cirq_interop/_bloq_to_cirq.py
+++ b/qualtran/cirq_interop/_bloq_to_cirq.py
@@ -18,16 +18,25 @@
from typing import Callable, Dict, Iterable, List, Optional, Tuple
import cirq
-import cirq_ft
import networkx as nx
import numpy as np
-from qualtran import Bloq, Connection, LeftDangle, Register, RightDangle, Side, Signature, Soquet
+from qualtran import (
+ Bloq,
+ Connection,
+ GateWithRegisters,
+ LeftDangle,
+ Register,
+ RightDangle,
+ Side,
+ Signature,
+ Soquet,
+)
from qualtran._infra.composite_bloq import _binst_to_cxns
from qualtran.cirq_interop._cirq_to_bloq import _QReg, CirqQuregInT, CirqQuregT
-class BloqAsCirqGate(cirq_ft.GateWithRegisters):
+class BloqAsCirqGate(GateWithRegisters):
"""A shim for using bloqs in a Cirq circuit.
Args:
@@ -52,19 +61,9 @@ def bloq(self) -> Bloq:
return self._bloq
@cached_property
- def signature(self) -> cirq_ft.Signature:
+ def signature(self) -> Signature:
"""`cirq_ft.GateWithRegisters` registers."""
- legacy_regs: List[cirq_ft.Register] = []
- for reg in self.bloq.signature:
- legacy_regs.append(
- cirq_ft.Register(
- name=reg.name,
- shape=reg.shape,
- bitsize=reg.bitsize,
- side=cirq_ft.infra.Side(reg.side.value),
- )
- )
- return cirq_ft.Signature(legacy_regs)
+ return self.bloq.signature
@classmethod
def bloq_on(
@@ -254,14 +253,12 @@ def _f_quregs(reg: Register) -> CirqQuregT:
def _construct_op_from_gate(
- gate: cirq_ft.GateWithRegisters,
- in_quregs: Dict[str, 'CirqQuregT'],
- qubit_manager: cirq.QubitManager,
+ gate: GateWithRegisters, in_quregs: Dict[str, 'CirqQuregT'], qubit_manager: cirq.QubitManager
) -> Tuple[cirq.Operation, Dict[str, 'CirqQuregT']]:
"""Allocates / Deallocates qubits for RIGHT / LEFT only registers to construct a Cirq operation
Args:
- gate: A `cirq_ft.GateWithRegisters` which specifies a signature.
+ gate: A `GateWithRegisters` which specifies a signature.
in_quregs: Mapping from LEFT register names of `gate` and corresponding cirq qubits.
qubit_manager: For allocating / deallocating qubits for RIGHT / LEFT only registers.
@@ -271,25 +268,26 @@ def _construct_op_from_gate(
"""
all_quregs: Dict[str, 'CirqQuregT'] = {}
out_quregs: Dict[str, 'CirqQuregT'] = {}
+ # def _cmp()
for reg in gate.signature:
full_shape = reg.shape + (reg.bitsize,)
- if reg.side & cirq_ft.infra.Side.LEFT:
+ if Side(reg.side.value) & Side.LEFT:
if reg.name not in in_quregs or in_quregs[reg.name].shape != full_shape:
# Left registers should exist as input to `as_cirq_op`.
raise ValueError(f'Compatible {reg=} must exist in {in_quregs=}')
all_quregs[reg.name] = in_quregs[reg.name]
- if reg.side == cirq_ft.infra.Side.RIGHT:
+ if Side(reg.side.value) == Side.RIGHT:
# Right only registers will get allocated as part of `as_cirq_op`.
if reg.name in in_quregs:
raise ValueError(f"RIGHT register {reg=} shouldn't exist in {in_quregs=}.")
all_quregs[reg.name] = np.array(qubit_manager.qalloc(reg.total_bits())).reshape(
full_shape
)
- if reg.side == cirq_ft.infra.Side.LEFT:
+ if Side(reg.side.value) == Side.LEFT:
# LEFT only registers should be de-allocated and not be part of output.
qubit_manager.qfree(in_quregs[reg.name].flatten())
- if reg.side & cirq_ft.infra.Side.RIGHT:
+ if Side(reg.side.value) & Side.RIGHT:
# Right registers should be part of the output.
out_quregs[reg.name] = all_quregs[reg.name]
return gate.on_registers(**all_quregs), out_quregs
diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py
index 79c6cd417..b71c03aaa 100644
--- a/qualtran/cirq_interop/_cirq_to_bloq.py
+++ b/qualtran/cirq_interop/_cirq_to_bloq.py
@@ -19,14 +19,15 @@
from typing import Any, Dict, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union
import cirq
-import cirq_ft
import numpy as np
import quimb.tensor as qtn
from attrs import field, frozen
from numpy.typing import NDArray
from qualtran import Bloq, BloqBuilder, CompositeBloq, Register, Side, Signature, Soquet, SoquetT
+from qualtran._infra.gate_with_registers import split_qubits
from qualtran.cirq_interop._interop_qubit_manager import InteropQubitManager
+from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity
if TYPE_CHECKING:
from qualtran.drawing import WireSymbol
@@ -56,33 +57,21 @@ def short_name(self) -> str:
@cached_property
def signature(self) -> 'Signature':
- return Signature(
- [
- Register(reg.name, bitsize=reg.bitsize, shape=reg.shape, side=Side(reg.side.value))
- for reg in self.cirq_registers
- ]
- )
+ if isinstance(self.gate, Bloq):
+ return self.gate.signature
+ import cirq_ft
- @cached_property
- def cirq_registers(self) -> cirq_ft.Signature:
if isinstance(self.gate, cirq_ft.GateWithRegisters):
- return self.gate.signature
- else:
- return cirq_ft.Signature(
- [cirq_ft.Register('qubits', shape=(cirq.num_qubits(self.gate),), bitsize=1)]
+ return Signature(
+ [
+ Register(reg.name, reg.bitsize, reg.shape, Side(reg.side.value))
+ for reg in self.gate.signature
+ ]
)
+ return Signature([Register('qubits', shape=cirq.num_qubits(self.gate), bitsize=1)])
def decompose_bloq(self) -> 'CompositeBloq':
- qubit_manager = InteropQubitManager()
- in_quregs = get_cirq_quregs(self.signature, qubit_manager)
- cirq_op, out_quregs = self.as_cirq_op(qubit_manager, **in_quregs)
- context = cirq.DecompositionContext(qubit_manager=qubit_manager)
- decomposed_optree = cirq.decompose_once(cirq_op, context=context, default=None)
- if decomposed_optree is None:
- raise NotImplementedError(f"{self} does not support decomposition.")
- return cirq_optree_to_cbloq(
- decomposed_optree, signature=self.signature, in_quregs=in_quregs, out_quregs=out_quregs
- )
+ return decompose_from_cirq_op(self, decompose_once=True)
def add_my_tensors(
self,
@@ -92,65 +81,25 @@ def add_my_tensors(
incoming: Dict[str, 'SoquetT'],
outgoing: Dict[str, 'SoquetT'],
):
- if not cirq.has_unitary(self.gate):
- raise NotImplementedError(
- f"CirqGateAsBloq.add_my_tensors is currently supported only for unitary gates. "
- f"Found {self.gate}."
- )
- unitary_shape = []
- reg_to_idx = defaultdict(list)
- for reg in self.cirq_registers:
- start = len(unitary_shape)
- for i in range(int(np.prod(reg.shape))):
- reg_to_idx[reg.name].append(start + i)
- unitary_shape.append(2**reg.bitsize)
-
- unitary_shape = (*unitary_shape, *unitary_shape)
- unitary = cirq.unitary(self.gate).reshape(unitary_shape)
- idx: List[Union[int, slice]] = [slice(x) for x in unitary_shape]
- n = len(unitary_shape) // 2
- for reg in self.signature:
- if reg.side == Side.LEFT:
- for i in reg_to_idx[reg.name]:
- # LEFT register ends, extract right subspace that's equivalent to 0.
- idx[i] = 0
- if reg.side == Side.RIGHT:
- for i in reg_to_idx[reg.name]:
- # Right register begins, extract the left subspace that's equivalent to 0.
- idx[i + n] = 0
- unitary = unitary[tuple(idx)]
- new_shape = tuple(
- [
- *itertools.chain.from_iterable(
- (2**reg.bitsize,) * int(np.prod(reg.shape))
- for reg in [*self.signature.rights(), *self.signature.lefts()]
- )
- ]
- )
- assert unitary.shape == new_shape
- incoming_list = [
- *itertools.chain.from_iterable(
- [np.array(incoming[reg.name]).flatten() for reg in self.signature.lefts()]
- )
- ]
- outgoing_list = [
- *itertools.chain.from_iterable(
- [np.array(outgoing[reg.name]).flatten() for reg in self.signature.rights()]
- )
- ]
-
- tn.add(
- qtn.Tensor(
- data=unitary, inds=outgoing_list + incoming_list, tags=[self.short_name(), tag]
- )
+ _add_my_tensors_from_gate(
+ self.gate,
+ self.signature,
+ self.short_name(),
+ tn=tn,
+ tag=tag,
+ incoming=incoming,
+ outgoing=outgoing,
)
def as_cirq_op(
self, qubit_manager: 'cirq.QubitManager', **cirq_quregs: 'CirqQuregT'
) -> Tuple['cirq.Operation', Dict[str, 'CirqQuregT']]:
+ import cirq_ft
+
+ from qualtran import GateWithRegisters
from qualtran.cirq_interop._bloq_to_cirq import _construct_op_from_gate
- if not isinstance(self.gate, cirq_ft.GateWithRegisters):
+ if not isinstance(self.gate, (cirq_ft.GateWithRegisters, GateWithRegisters)):
return self.gate.on(*cirq_quregs['qubits'].flatten()), cirq_quregs
return _construct_op_from_gate(
self.gate,
@@ -158,21 +107,84 @@ def as_cirq_op(
qubit_manager=qubit_manager,
)
- def t_complexity(self) -> 'cirq_ft.TComplexity':
- return cirq_ft.t_complexity(self.gate)
+ def t_complexity(self) -> 'TComplexity':
+ return t_complexity(self.gate)
def wire_symbol(self, soq: 'Soquet') -> 'WireSymbol':
- from qualtran.drawing import directional_text_box
+ return _wire_symbol_from_gate(self.gate, self.signature, soq)
+
+
+def _wire_symbol_from_gate(gate: cirq.Gate, signature: Signature, soq: 'Soquet') -> 'WireSymbol':
+ from qualtran.drawing import directional_text_box
- wire_symbols = cirq.circuit_diagram_info(self.gate).wire_symbols
- begin = 0
- symbol: str = soq.pretty()
- for reg in self.signature:
- finish = begin + int(np.prod(reg.shape))
- if reg == soq.reg:
- symbol = np.array(wire_symbols[begin:finish]).reshape(reg.shape)[soq.idx]
- begin = finish
- return directional_text_box(text=symbol, side=soq.reg.side)
+ wire_symbols = cirq.circuit_diagram_info(gate).wire_symbols
+ begin = 0
+ symbol: str = soq.pretty()
+ for reg in signature:
+ finish = begin + int(np.prod(reg.shape))
+ if reg == soq.reg:
+ symbol = np.array(wire_symbols[begin:finish]).reshape(reg.shape)[soq.idx]
+ begin = finish
+ return directional_text_box(text=symbol, side=soq.reg.side)
+
+
+def _add_my_tensors_from_gate(
+ gate: cirq.Gate,
+ signature: Signature,
+ short_name: str,
+ tn: qtn.TensorNetwork,
+ tag: Any,
+ *,
+ incoming: Dict[str, 'SoquetT'],
+ outgoing: Dict[str, 'SoquetT'],
+):
+ if not cirq.has_unitary(gate):
+ raise NotImplementedError(
+ f"CirqGateAsBloq.add_my_tensors is currently supported only for unitary gates. "
+ f"Found {gate}."
+ )
+ unitary_shape = []
+ reg_to_idx = defaultdict(list)
+ for reg in signature:
+ start = len(unitary_shape)
+ for i in range(int(np.prod(reg.shape))):
+ reg_to_idx[reg.name].append(start + i)
+ unitary_shape.append(2**reg.bitsize)
+
+ unitary_shape = (*unitary_shape, *unitary_shape)
+ unitary = cirq.unitary(gate).reshape(unitary_shape)
+ idx: List[Union[int, slice]] = [slice(x) for x in unitary_shape]
+ n = len(unitary_shape) // 2
+ for reg in signature:
+ if reg.side == Side.LEFT:
+ for i in reg_to_idx[reg.name]:
+ # LEFT register ends, extract right subspace that's equivalent to 0.
+ idx[i] = 0
+ if reg.side == Side.RIGHT:
+ for i in reg_to_idx[reg.name]:
+ # Right register begins, extract the left subspace that's equivalent to 0.
+ idx[i + n] = 0
+ unitary = unitary[tuple(idx)]
+ new_shape = tuple(
+ [
+ *itertools.chain.from_iterable(
+ (2**reg.bitsize,) * int(np.prod(reg.shape))
+ for reg in [*signature.rights(), *signature.lefts()]
+ )
+ ]
+ )
+ assert unitary.shape == new_shape
+ incoming_list = [
+ *itertools.chain.from_iterable(
+ [np.array(incoming[reg.name]).flatten() for reg in signature.lefts()]
+ )
+ ]
+ outgoing_list = [
+ *itertools.chain.from_iterable(
+ [np.array(outgoing[reg.name]).flatten() for reg in signature.rights()]
+ )
+ ]
+ tn.add(qtn.Tensor(data=unitary, inds=outgoing_list + incoming_list, tags=[short_name, tag]))
@frozen
@@ -309,11 +321,11 @@ def cirq_optree_to_cbloq(
if op.gate is None:
raise ValueError(f"Only gate operations are supported, not {op}.")
- bloq = CirqGateAsBloq(op.gate)
+ bloq = op.gate if isinstance(op.gate, Bloq) else CirqGateAsBloq(op.gate)
# 3.1 Find input / output registers.
all_op_quregs: Dict[str, NDArray[_QReg]] = {
k: np.apply_along_axis(_QReg, -1, v)
- for k, v in cirq_ft.infra.split_qubits(bloq.cirq_registers, op.qubits).items()
+ for k, v in split_qubits(bloq.signature, op.qubits).items()
}
in_op_quregs: Dict[str, NDArray[_QReg]] = {
reg.name: all_op_quregs[reg.name] for reg in bloq.signature.lefts()
@@ -350,7 +362,7 @@ def cirq_optree_to_cbloq(
return bb.finalize(**final_soqs_dict)
-def decompose_from_cirq_op(bloq: 'Bloq') -> 'CompositeBloq':
+def decompose_from_cirq_op(bloq: 'Bloq', *, decompose_once: bool = False) -> 'CompositeBloq':
"""Returns a CompositeBloq constructed using Cirq operations obtained via `bloq.as_cirq_op`.
This method first checks whether `bloq.signature` is parameterized. If yes, it raises a
@@ -368,13 +380,14 @@ def decompose_from_cirq_op(bloq: 'Bloq') -> 'CompositeBloq':
qubit_manager = InteropQubitManager()
in_quregs = get_cirq_quregs(bloq.signature, qubit_manager)
cirq_op, out_quregs = bloq.as_cirq_op(qubit_manager, **in_quregs)
- from qualtran.cirq_interop._bloq_to_cirq import BloqAsCirqGate
+ context = cirq.DecompositionContext(qubit_manager=qubit_manager)
+ decomposed_optree = (
+ cirq.decompose_once(cirq_op, context=context, default=None) if decompose_once else cirq_op
+ )
- if cirq_op is None or (
- isinstance(cirq_op, cirq.Operation) and isinstance(cirq_op.gate, BloqAsCirqGate)
- ):
+ if decomposed_optree is None:
raise NotImplementedError(f"{bloq} does not support decomposition.")
return cirq_optree_to_cbloq(
- cirq_op, signature=bloq.signature, in_quregs=in_quregs, out_quregs=out_quregs
+ decomposed_optree, signature=bloq.signature, in_quregs=in_quregs, out_quregs=out_quregs
)
diff --git a/qualtran/cirq_interop/cirq_interop.ipynb b/qualtran/cirq_interop/cirq_interop.ipynb
index dff67c4aa..56039948c 100644
--- a/qualtran/cirq_interop/cirq_interop.ipynb
+++ b/qualtran/cirq_interop/cirq_interop.ipynb
@@ -203,7 +203,7 @@
"bloq = CirqGateAsBloq(select.gate)\n",
"fig, ax = draw_musical_score(get_musical_score_data(bloq.decompose_bloq()))\n",
"fig.set_size_inches(30, 12)\n",
- "assert bloq.t_complexity() == cirq_ft.t_complexity(select.gate)"
+ "assert bloq.t_complexity().t == cirq_ft.t_complexity(select.gate).t"
]
},
{
@@ -217,7 +217,7 @@
"bloq = CirqGateAsBloq(prepare.gate)\n",
"fig, ax = draw_musical_score(get_musical_score_data(bloq.decompose_bloq()))\n",
"fig.set_size_inches(30, 12)\n",
- "assert bloq.t_complexity() == cirq_ft.t_complexity(prepare.gate)"
+ "assert bloq.t_complexity().t == cirq_ft.t_complexity(prepare.gate).t"
]
},
{
@@ -637,4 +637,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
-}
+}
\ No newline at end of file
diff --git a/qualtran/cirq_interop/decompose_protocol.py b/qualtran/cirq_interop/decompose_protocol.py
new file mode 100644
index 000000000..9f0acd23f
--- /dev/null
+++ b/qualtran/cirq_interop/decompose_protocol.py
@@ -0,0 +1,98 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Any, FrozenSet, Sequence
+
+import cirq
+from cirq.protocols.decompose_protocol import DecomposeResult
+
+_FREDKIN_GATESET = cirq.Gateset(cirq.FREDKIN, unroll_circuit_op=False)
+
+
+def _fredkin(qubits: Sequence[cirq.Qid], context: cirq.DecompositionContext) -> cirq.OP_TREE:
+ """Decomposition with 7 T and 10 clifford operations from https://arxiv.org/abs/1308.4134"""
+ c, t1, t2 = qubits
+ yield [cirq.CNOT(t2, t1)]
+ yield [cirq.CNOT(c, t1), cirq.H(t2)]
+ yield [cirq.T(c), cirq.T(t1) ** -1, cirq.T(t2)]
+ yield [cirq.CNOT(t2, t1)]
+ yield [cirq.CNOT(c, t2), cirq.T(t1)]
+ yield [cirq.CNOT(c, t1), cirq.T(t2) ** -1]
+ yield [cirq.T(t1) ** -1, cirq.CNOT(c, t2)]
+ yield [cirq.CNOT(t2, t1)]
+ yield [cirq.T(t1), cirq.H(t2)]
+ yield [cirq.CNOT(t2, t1)]
+
+
+def _try_decompose_from_known_decompositions(
+ val: Any, context: cirq.DecompositionContext
+) -> DecomposeResult:
+ """Returns a flattened decomposition of the object into operations, if possible.
+
+ Args:
+ val: The object to decompose.
+ context: Decomposition context storing common configurable options for `cirq.decompose`.
+
+ Returns:
+ A flattened decomposition of `val` if it's a gate or operation with a known decomposition.
+ """
+ if not isinstance(val, (cirq.Gate, cirq.Operation)):
+ return None
+ qubits = cirq.LineQid.for_gate(val) if isinstance(val, cirq.Gate) else val.qubits
+ known_decompositions = [(_FREDKIN_GATESET, _fredkin)]
+
+ classical_controls: FrozenSet[cirq.Condition] = frozenset()
+ if isinstance(val, cirq.ClassicallyControlledOperation):
+ classical_controls = val.classical_controls
+ val = val.without_classical_controls()
+
+ decomposition = None
+ for gateset, decomposer in known_decompositions:
+ if val in gateset:
+ decomposition = cirq.flatten_to_ops(decomposer(qubits, context))
+ break
+ return (
+ tuple(op.with_classical_controls(*classical_controls) for op in decomposition)
+ if decomposition
+ else None
+ )
+
+
+def _decompose_once_considering_known_decomposition(val: Any) -> DecomposeResult:
+ """Decomposes a value into operations, if possible.
+
+ Args:
+ val: The value to decompose into operations.
+
+ Returns:
+ A tuple of operations if decomposition succeeds.
+ """
+ import uuid
+
+ context = cirq.DecompositionContext(
+ qubit_manager=cirq.GreedyQubitManager(prefix=f'_{uuid.uuid4()}', maximize_reuse=True)
+ )
+
+ decomposed = _try_decompose_from_known_decompositions(val, context)
+ if decomposed is not None:
+ return decomposed
+
+ if isinstance(val, cirq.Gate):
+ decomposed = cirq.decompose_once_with_qubits(
+ val, cirq.LineQid.for_gate(val), context=context, flatten=False, default=None
+ )
+ else:
+ decomposed = cirq.decompose_once(val, context=context, flatten=False, default=None)
+
+ return [*cirq.flatten_to_ops(decomposed)] if decomposed is not None else None
diff --git a/qualtran/cirq_interop/decompose_protocol_test.py b/qualtran/cirq_interop/decompose_protocol_test.py
new file mode 100644
index 000000000..43770ab7a
--- /dev/null
+++ b/qualtran/cirq_interop/decompose_protocol_test.py
@@ -0,0 +1,58 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cirq
+import numpy as np
+import pytest
+from cirq_ft.infra.decompose_protocol import (
+ _decompose_once_considering_known_decomposition,
+ _fredkin,
+ _try_decompose_from_known_decompositions,
+)
+
+
+def test_fredkin_unitary():
+ c, t1, t2 = cirq.LineQid.for_gate(cirq.FREDKIN)
+ context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
+ np.testing.assert_allclose(
+ cirq.Circuit(_fredkin((c, t1, t2), context)).unitary(),
+ cirq.unitary(cirq.FREDKIN(c, t1, t2)),
+ atol=1e-8,
+ )
+
+
+@pytest.mark.parametrize('gate', [cirq.FREDKIN, cirq.FREDKIN**-1])
+def test_decompose_fredkin(gate):
+ c, t1, t2 = cirq.LineQid.for_gate(cirq.FREDKIN)
+ op = cirq.FREDKIN(c, t1, t2)
+ context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
+ want = tuple(cirq.flatten_op_tree(_fredkin((c, t1, t2), context)))
+ assert want == _try_decompose_from_known_decompositions(op, context)
+
+ op = cirq.FREDKIN(c, t1, t2).with_classical_controls('key')
+ classical_controls = op.classical_controls
+ want = tuple(
+ o.with_classical_controls(*classical_controls)
+ for o in cirq.flatten_op_tree(_fredkin((c, t1, t2), context))
+ )
+ assert want == _try_decompose_from_known_decompositions(op, context)
+
+
+def test_known_decomposition_empty_unitary():
+ class DecomposeEmptyList(cirq.testing.SingleQubitGate):
+ def _decompose_(self, _):
+ return []
+
+ gate = DecomposeEmptyList()
+ assert _decompose_once_considering_known_decomposition(gate) == []
diff --git a/qualtran/cirq_interop/t_complexity.ipynb b/qualtran/cirq_interop/t_complexity.ipynb
new file mode 100644
index 000000000..3697188b9
--- /dev/null
+++ b/qualtran/cirq_interop/t_complexity.ipynb
@@ -0,0 +1,174 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "51db731e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Copyright 2023 The Cirq Developers\n",
+ "#\n",
+ "# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
+ "# you may not use this file except in compliance with the License.\n",
+ "# You may obtain a copy of the License at\n",
+ "#\n",
+ "# https://www.apache.org/licenses/LICENSE-2.0\n",
+ "#\n",
+ "# Unless required by applicable law or agreed to in writing, software\n",
+ "# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
+ "# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
+ "# See the License for the specific language governing permissions and\n",
+ "# limitations under the License."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "33438ed0",
+ "metadata": {},
+ "source": [
+ "# T Complexity"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "abed0743",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import matplotlib.pyplot as plt\n",
+ "import numpy as np\n",
+ "\n",
+ "from cirq_ft import And, t_complexity, infra"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "29f4c77d",
+ "metadata": {},
+ "source": [
+ "## Two Qubit And Gate"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "635a411e",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# And of two qubits\n",
+ "gate = And() # create an And gate\n",
+ "# create an operation\n",
+ "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n",
+ "# this operation doesn't directly support TComplexity but it's decomposable and its components are simple.\n",
+ "print(t_complexity(operation))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "88cfc5f4",
+ "metadata": {},
+ "source": [
+ "## Adjoint of two qubit And gate"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "3c0301d9",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "gate = And() ** -1 # adjoint of And\n",
+ "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n",
+ "# the deomposition is H, measure, CZ, and Reset\n",
+ "print(t_complexity(operation))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "8e585436",
+ "metadata": {},
+ "source": [
+ "## And gate on n qubits"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "a207ea1a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n = 5\n",
+ "gate = And((1, )*n)\n",
+ "operation = gate.on_registers(**infra.get_named_qubits(gate.signature))\n",
+ "print(t_complexity(operation))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "394032a5",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def Generate(n_max: int = 10):\n",
+ " \"\"\"Returns the #T when the number of qubits is between 2 and n_max inclusive\"\"\"\n",
+ " n_controls = []\n",
+ " t_count = []\n",
+ " for n in range(2, n_max + 2):\n",
+ " n_controls.append(n)\n",
+ " gate = And(cv=(1, )*n)\n",
+ " op = gate.on_registers(**infra.get_named_qubits(gate.signature))\n",
+ " c = t_complexity(op)\n",
+ " t_count.append(c.t)\n",
+ " return n_controls, t_count"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "919ca418",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "n_controls, t_count = Generate()\n",
+ "plt.plot(n_controls, t_count, label='T')\n",
+ "plt.ylabel('count')\n",
+ "plt.xlabel('number of qubits')\n",
+ "plt.title('And gate')\n",
+ "plt.legend()\n",
+ "plt.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.8.16"
+ },
+ "vscode": {
+ "interpreter": {
+ "hash": "1882f3b63550a2f9350e6532bf63174910df57e92f62a2be07440f9e606398c8"
+ }
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
\ No newline at end of file
diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py
new file mode 100644
index 000000000..77a262f1a
--- /dev/null
+++ b/qualtran/cirq_interop/t_complexity_protocol.py
@@ -0,0 +1,195 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from typing import Any, Callable, Hashable, Iterable, Optional, overload, Union
+
+import attr
+import cachetools
+import cirq
+from typing_extensions import Literal, Protocol
+
+from qualtran.cirq_interop.decompose_protocol import _decompose_once_considering_known_decomposition
+
+_T_GATESET = cirq.Gateset(cirq.T, cirq.T**-1, unroll_circuit_op=False)
+
+
+@attr.frozen
+class TComplexity:
+ """Dataclass storing counts of logical T-gates, Clifford gates and single qubit rotations."""
+
+ t: int = 0
+ clifford: int = 0
+ rotations: int = 0
+
+ def __add__(self, other: 'TComplexity') -> 'TComplexity':
+ return TComplexity(
+ self.t + other.t, self.clifford + other.clifford, self.rotations + other.rotations
+ )
+
+ def __mul__(self, other: int) -> 'TComplexity':
+ return TComplexity(self.t * other, self.clifford * other, self.rotations * other)
+
+ def __rmul__(self, other: int) -> 'TComplexity':
+ return self.__mul__(other)
+
+ def __str__(self) -> str:
+ return (
+ f'T-count: {self.t:g}\n'
+ f'Rotations: {self.rotations:g}\n'
+ f'Cliffords: {self.clifford:g}\n'
+ )
+
+
+class SupportsTComplexity(Protocol):
+ """An object whose TComplexity can be computed.
+
+ An object whose TComplexity can be computed either implements the `_t_complexity_` function
+ or is of a type that SupportsDecompose.
+ """
+
+ def _t_complexity_(self) -> TComplexity:
+ """Returns the TComplexity."""
+
+
+def _has_t_complexity(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
+ """Returns TComplexity of stc by calling `stc._t_complexity_()` method, if it exists."""
+ estimator = getattr(stc, '_t_complexity_', None)
+ if estimator is not None:
+ result = estimator()
+ if result is not NotImplemented:
+ return result
+ if isinstance(stc, cirq.Operation) and stc.gate is not None:
+ return _has_t_complexity(stc.gate, fail_quietly)
+ return None
+
+
+def _is_clifford_or_t(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
+ """Attempts to infer the type of a gate/operation as one of clifford, T or Rotation."""
+ if not isinstance(stc, (cirq.Gate, cirq.Operation)):
+ return None
+
+ if isinstance(stc, cirq.ClassicallyControlledOperation):
+ stc = stc.without_classical_controls()
+
+ if cirq.has_stabilizer_effect(stc):
+ # Clifford operation.
+ return TComplexity(clifford=1)
+
+ if stc in _T_GATESET:
+ # T-gate.
+ return TComplexity(t=1) # T gate
+
+ if cirq.num_qubits(stc) == 1 and cirq.has_unitary(stc):
+ # Single qubit rotation operation.
+ return TComplexity(rotations=1)
+ return None
+
+
+def _is_iterable(it: Any, fail_quietly: bool) -> Optional[TComplexity]:
+ if not isinstance(it, Iterable):
+ return None
+ t = TComplexity()
+ for v in it:
+ r = t_complexity(v, fail_quietly=fail_quietly)
+ if r is None:
+ return None
+ t = t + r
+ return t
+
+
+def _from_decomposition(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
+ # Decompose the object and recursively compute the complexity.
+ decomposition = _decompose_once_considering_known_decomposition(stc)
+ if decomposition is None:
+ return None
+ return _is_iterable(decomposition, fail_quietly=fail_quietly)
+
+
+def _get_hash(val: Any, fail_quietly: bool = False):
+ """Returns hash keys for caching a cirq.Operation and cirq.Gate.
+
+ The hash of a cirq.Operation changes depending on its qubits, tags,
+ classical controls, and other properties it has, none of these properties
+ affect the TComplexity.
+ For gates and gate backed operations we intend to compute the hash of the
+ gate which is a property of the Gate.
+
+ Args:
+ val: object to compute its hash.
+
+ Returns:
+ - `val.gate` if `val` is a `cirq.Operation` which has an underlying `val.gate`.
+ - `val` otherwise
+ """
+ if isinstance(val, cirq.Operation) and val.gate is not None:
+ val = val.gate
+ return val
+
+
+def _t_complexity_from_strategies(
+ stc: Any, fail_quietly: bool, strategies: Iterable[Callable[[Any, bool], Optional[TComplexity]]]
+):
+ ret = None
+ for strategy in strategies:
+ ret = strategy(stc, fail_quietly)
+ if ret is not None:
+ break
+ return ret
+
+
+@cachetools.cached(cachetools.LRUCache(128), key=_get_hash, info=True)
+def _t_complexity_for_gate_or_op(
+ gate_or_op: Union[cirq.Gate, cirq.Operation], fail_quietly: bool
+) -> Optional[TComplexity]:
+ strategies = [_has_t_complexity, _is_clifford_or_t, _from_decomposition]
+ return _t_complexity_from_strategies(gate_or_op, fail_quietly, strategies)
+
+
+@overload
+def t_complexity(stc: Any, fail_quietly: Literal[False] = False) -> TComplexity:
+ ...
+
+
+@overload
+def t_complexity(stc: Any, fail_quietly: bool) -> Optional[TComplexity]:
+ ...
+
+
+def t_complexity(stc: Any, fail_quietly: bool = False) -> Optional[TComplexity]:
+ """Returns the TComplexity.
+
+ Args:
+ stc: an object to compute its TComplexity.
+ fail_quietly: bool whether to return None on failure or raise an error.
+
+ Returns:
+ The TComplexity of the given object or None on failure (and fail_quietly=True).
+
+ Raises:
+ TypeError: if fail_quietly=False and the methods fails to compute TComplexity.
+ """
+ if isinstance(stc, (cirq.Gate, cirq.Operation)) and isinstance(stc, Hashable):
+ ret = _t_complexity_for_gate_or_op(stc, fail_quietly)
+ else:
+ strategies = [_has_t_complexity, _is_clifford_or_t, _from_decomposition, _is_iterable]
+ ret = _t_complexity_from_strategies(stc, fail_quietly, strategies)
+
+ if ret is None and not fail_quietly:
+ raise TypeError("couldn't compute TComplexity of:\n" f"type: {type(stc)}\n" f"value: {stc}")
+ return ret
+
+
+t_complexity.cache_clear = _t_complexity_for_gate_or_op.cache_clear # type: ignore[attr-defined]
+t_complexity.cache_info = _t_complexity_for_gate_or_op.cache_info # type: ignore[attr-defined]
+t_complexity.cache = _t_complexity_for_gate_or_op.cache # type: ignore[attr-defined]
diff --git a/qualtran/cirq_interop/t_complexity_protocol_test.py b/qualtran/cirq_interop/t_complexity_protocol_test.py
new file mode 100644
index 000000000..3bfee03fc
--- /dev/null
+++ b/qualtran/cirq_interop/t_complexity_protocol_test.py
@@ -0,0 +1,205 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import cirq
+import pytest
+
+from qualtran import GateWithRegisters, Signature
+from qualtran._infra.gate_with_registers import get_named_qubits
+from qualtran.bloqs.and_bloq import And
+from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity
+from qualtran.cirq_interop.testing import GateHelper
+from qualtran.testing import execute_notebook
+
+
+class SupportTComplexity:
+ def _t_complexity_(self) -> TComplexity:
+ return TComplexity(t=1)
+
+
+class DoesNotSupportTComplexity:
+ ...
+
+
+class SupportsTComplexityGateWithRegisters(GateWithRegisters):
+ @property
+ def signature(self) -> Signature:
+ return Signature.build(s=1, t=2)
+
+ def _t_complexity_(self) -> TComplexity:
+ return TComplexity(t=1, clifford=2)
+
+
+class SupportTComplexityGate(cirq.Gate):
+ def _num_qubits_(self) -> int:
+ return 1
+
+ def _t_complexity_(self) -> TComplexity:
+ return TComplexity(t=1)
+
+
+class DoesNotSupportTComplexityGate(cirq.Gate):
+ def _num_qubits_(self):
+ return 1
+
+
+def test_t_complexity():
+ with pytest.raises(TypeError):
+ _ = t_complexity(DoesNotSupportTComplexity())
+
+ with pytest.raises(TypeError):
+ _ = t_complexity(DoesNotSupportTComplexityGate())
+
+ assert t_complexity(DoesNotSupportTComplexity(), fail_quietly=True) is None
+ assert t_complexity([DoesNotSupportTComplexity()], fail_quietly=True) is None
+ assert t_complexity(DoesNotSupportTComplexityGate(), fail_quietly=True) is None
+
+ assert t_complexity(SupportTComplexity()) == TComplexity(t=1)
+ assert t_complexity(SupportTComplexityGate().on(cirq.q('t'))) == TComplexity(t=1)
+
+ g = GateHelper(SupportsTComplexityGateWithRegisters())
+ assert g.gate._decompose_with_context_(g.operation.qubits) is NotImplemented
+ assert t_complexity(g.gate) == TComplexity(t=1, clifford=2)
+ assert t_complexity(g.operation) == TComplexity(t=1, clifford=2)
+
+ assert t_complexity([cirq.T, cirq.X]) == TComplexity(t=1, clifford=1)
+
+ q = cirq.NamedQubit('q')
+ assert t_complexity([cirq.T(q), cirq.X(q)]) == TComplexity(t=1, clifford=1)
+
+
+def test_gates():
+ # T gate and its adjoint
+ assert t_complexity(cirq.T) == TComplexity(t=1)
+ assert t_complexity(cirq.T**-1) == TComplexity(t=1)
+
+ assert t_complexity(cirq.H) == TComplexity(clifford=1) # Hadamard
+ assert t_complexity(cirq.CNOT) == TComplexity(clifford=1) # CNOT
+ assert t_complexity(cirq.S) == TComplexity(clifford=1) # S
+ assert t_complexity(cirq.S**-1) == TComplexity(clifford=1) # S†
+
+ # Pauli operators are clifford
+ assert t_complexity(cirq.X) == TComplexity(clifford=1)
+ assert t_complexity(cirq.Y) == TComplexity(clifford=1)
+ assert t_complexity(cirq.Z) == TComplexity(clifford=1)
+
+ # Rotation about X, Y, and Z axes
+ assert t_complexity(cirq.Rx(rads=2)) == TComplexity(rotations=1)
+ assert t_complexity(cirq.Ry(rads=2)) == TComplexity(rotations=1)
+ assert t_complexity(cirq.Rz(rads=2)) == TComplexity(rotations=1)
+
+ # clifford+T
+ assert t_complexity(And()) == TComplexity(t=4, clifford=9)
+ assert t_complexity(And() ** -1) == TComplexity(clifford=4)
+
+ assert t_complexity(cirq.FREDKIN) == TComplexity(t=7, clifford=10)
+
+
+def test_operations():
+ q = cirq.NamedQubit('q')
+ assert t_complexity(cirq.T(q)) == TComplexity(t=1)
+
+ gate = And()
+ op = gate.on_registers(**get_named_qubits(gate.signature))
+ assert t_complexity(op) == TComplexity(t=4, clifford=9)
+
+ gate = And() ** -1
+ op = gate.on_registers(**get_named_qubits(gate.signature))
+ assert t_complexity(op) == TComplexity(clifford=4)
+
+
+def test_circuits():
+ q = cirq.NamedQubit('q')
+ circuit = cirq.Circuit(
+ cirq.Rz(rads=0.6)(q),
+ cirq.T(q),
+ cirq.X(q) ** 0.5,
+ cirq.Rx(rads=0.1)(q),
+ cirq.Ry(rads=0.6)(q),
+ cirq.measure(q, key='m'),
+ )
+ assert t_complexity(circuit) == TComplexity(clifford=2, rotations=3, t=1)
+
+ circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m'))
+ assert t_complexity(circuit) == TComplexity(clifford=1, rotations=1, t=1)
+
+
+def test_circuit_operations():
+ q = cirq.NamedQubit('q')
+ circuit = cirq.FrozenCircuit(
+ cirq.T(q), cirq.X(q) ** 0.5, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m')
+ )
+ assert t_complexity(cirq.CircuitOperation(circuit)) == TComplexity(clifford=2, rotations=1, t=1)
+ assert t_complexity(cirq.CircuitOperation(circuit, repetitions=10)) == TComplexity(
+ clifford=20, rotations=10, t=10
+ )
+
+ circuit = cirq.FrozenCircuit(cirq.T(q) ** -1, cirq.Rx(rads=0.1)(q), cirq.measure(q, key='m'))
+ assert t_complexity(cirq.CircuitOperation(circuit)) == TComplexity(clifford=1, rotations=1, t=1)
+ assert t_complexity(cirq.CircuitOperation(circuit, repetitions=3)) == TComplexity(
+ clifford=3, rotations=3, t=3
+ )
+
+
+def test_classically_controlled_operations():
+ q = cirq.NamedQubit('q')
+ assert t_complexity(cirq.X(q).with_classical_controls('c')) == TComplexity(clifford=1)
+ assert t_complexity(cirq.Rx(rads=0.1)(q).with_classical_controls('c')) == TComplexity(
+ rotations=1
+ )
+ assert t_complexity(cirq.T(q).with_classical_controls('c')) == TComplexity(t=1)
+
+
+def test_tagged_operations():
+ q = cirq.NamedQubit('q')
+ assert t_complexity(cirq.X(q).with_tags('tag1')) == TComplexity(clifford=1)
+ assert t_complexity(cirq.T(q).with_tags('tage1')) == TComplexity(t=1)
+ assert t_complexity(cirq.Ry(rads=0.1)(q).with_tags('tag1', 'tag2')) == TComplexity(rotations=1)
+
+
+def test_cache_clear():
+ class IsCachable(cirq.Operation):
+ def __init__(self) -> None:
+ super().__init__()
+ self.num_calls = 0
+ self._gate = cirq.X
+
+ def _t_complexity_(self) -> TComplexity:
+ self.num_calls += 1
+ return TComplexity()
+
+ @property
+ def qubits(self):
+ return [cirq.LineQubit(3)] # pragma: no cover
+
+ def with_qubits(self, _):
+ ...
+
+ @property
+ def gate(self):
+ return self._gate
+
+ assert t_complexity(cirq.X) == TComplexity(clifford=1)
+ # Using a global cache will result in a failure of this test since `cirq.X` has
+ # `T-complexity(clifford=1)` but we explicitly return `TComplexity()` for IsCachable
+ # operation; for which the hash would be equivalent to the hash of it's subgate i.e. `cirq.X`.
+ t_complexity.cache_clear()
+ op = IsCachable()
+ assert t_complexity([op, op]) == TComplexity()
+ assert op.num_calls == 1
+ t_complexity.cache_clear()
+
+
+def test_notebook():
+ execute_notebook('t_complexity')
diff --git a/qualtran/cirq_interop/testing.py b/qualtran/cirq_interop/testing.py
new file mode 100644
index 000000000..caa5d738c
--- /dev/null
+++ b/qualtran/cirq_interop/testing.py
@@ -0,0 +1,137 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from dataclasses import dataclass
+from typing import Any, Dict, List, Sequence, Tuple
+
+import cirq
+import numpy as np
+from cirq._compat import cached_property
+from numpy.typing import NDArray
+
+from qualtran import Signature
+from qualtran._infra.gate_with_registers import GateWithRegisters, get_named_qubits, merge_qubits
+from qualtran.cirq_interop.decompose_protocol import _decompose_once_considering_known_decomposition
+from qualtran.cirq_interop.t_complexity_protocol import t_complexity
+
+
+@dataclass(frozen=True)
+class GateHelper:
+ """A collection of related objects derivable from a `GateWithRegisters`.
+
+ These are likely useful to have at one's fingertips while writing tests or
+ demo notebooks.
+
+ Attributes:
+ gate: The gate from which all other objects are derived.
+ """
+
+ gate: GateWithRegisters
+ context: cirq.DecompositionContext = cirq.DecompositionContext(cirq.ops.SimpleQubitManager())
+
+ @cached_property
+ def r(self) -> Signature:
+ """The Signature system for the gate."""
+ return self.gate.signature
+
+ @cached_property
+ def quregs(self) -> Dict[str, NDArray[cirq.Qid]]: # type: ignore[type-var]
+ """A dictionary of named qubits appropriate for the signature for the gate."""
+ return get_named_qubits(self.r)
+
+ @cached_property
+ def all_qubits(self) -> List[cirq.Qid]:
+ """All qubits in Register order."""
+ merged_qubits = merge_qubits(self.r, **self.quregs)
+ decomposed_qubits = self.decomposed_circuit.all_qubits()
+ return merged_qubits + sorted(decomposed_qubits - frozenset(merged_qubits))
+
+ @cached_property
+ def operation(self) -> cirq.Operation:
+ """The `gate` applied to example qubits."""
+ return self.gate.on_registers(**self.quregs)
+
+ @cached_property
+ def circuit(self) -> cirq.Circuit:
+ """The `gate` applied to example qubits wrapped in a `cirq.Circuit`."""
+ return cirq.Circuit(self.operation)
+
+ @cached_property
+ def decomposed_circuit(self) -> cirq.Circuit:
+ """The `gate` applied to example qubits, decomposed and wrapped in a `cirq.Circuit`."""
+ return cirq.Circuit(cirq.decompose(self.operation, context=self.context))
+
+
+def assert_circuit_inp_out_cirqsim(
+ circuit: cirq.AbstractCircuit,
+ qubit_order: Sequence[cirq.Qid],
+ inputs: Sequence[int],
+ outputs: Sequence[int],
+ decimals: int = 2,
+):
+ """Use a Cirq simulator to test that `circuit` behaves correctly on an input.
+
+ Args:
+ circuit: The circuit representing the reversible classical operation.
+ qubit_order: The qubit order to pass to the cirq simulator.
+ inputs: The input state bit values.
+ outputs: The (correct) output state bit values.
+ decimals: The number of decimals of precision to use when comparing
+ amplitudes. Reversible classical operations should produce amplitudes
+ that are 0 or 1.
+ """
+ actual, should_be = get_circuit_inp_out_cirqsim(circuit, qubit_order, inputs, outputs, decimals)
+ assert actual == should_be
+
+
+def get_circuit_inp_out_cirqsim(
+ circuit: cirq.AbstractCircuit,
+ qubit_order: Sequence[cirq.Qid],
+ inputs: Sequence[int],
+ outputs: Sequence[int],
+ decimals: int = 2,
+) -> Tuple[str, str]:
+ """Use a Cirq simulator to get a outputs of a `circuit`.
+
+ Args:
+ circuit: The circuit representing the reversible classical operation.
+ qubit_order: The qubit order to pass to the cirq simulator.
+ inputs: The input state bit values.
+ outputs: The (correct) output state bit values.
+ decimals: The number of decimals of precision to use when comparing
+ amplitudes. Reversible classical operations should produce amplitudes
+ that are 0 or 1.
+
+ Returns:
+ actual: The simulated output state as a string bitstring.
+ should_be: The outputs argument formatted as a string bitstring for ease of comparison.
+ """
+ result = cirq.Simulator(dtype=np.complex128).simulate(
+ circuit, initial_state=inputs, qubit_order=qubit_order
+ )
+ actual = result.dirac_notation(decimals=decimals)[1:-1]
+ should_be = "".join(str(x) for x in outputs)
+ return actual, should_be
+
+
+def assert_decompose_is_consistent_with_t_complexity(val: Any):
+ t_complexity_method = getattr(val, '_t_complexity_', None)
+ expected = NotImplemented if t_complexity_method is None else t_complexity_method()
+ if expected is NotImplemented or expected is None:
+ return
+ decomposition = _decompose_once_considering_known_decomposition(val)
+ if decomposition is None:
+ return
+ from_decomposition = t_complexity(decomposition, fail_quietly=False)
+ assert expected == from_decomposition, f'{expected} != {from_decomposition}'
diff --git a/qualtran/cirq_interop/testing_test.py b/qualtran/cirq_interop/testing_test.py
new file mode 100644
index 000000000..400389137
--- /dev/null
+++ b/qualtran/cirq_interop/testing_test.py
@@ -0,0 +1,92 @@
+# Copyright 2023 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import cirq
+import numpy as np
+import pytest
+
+from qualtran import Register, Side, Signature
+from qualtran.bloqs.and_bloq import And, MultiAnd
+from qualtran.cirq_interop import testing
+from qualtran.cirq_interop.t_complexity_protocol import TComplexity
+
+
+def test_assert_circuit_inp_out_cirqsim():
+ qubits = cirq.LineQubit.range(4)
+ initial_state = [0, 1, 0, 0]
+ circuit = cirq.Circuit(cirq.X(qubits[3]))
+ final_state = [0, 1, 0, 1]
+
+ testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state)
+
+ final_state = [0, 0, 0, 1]
+ with pytest.raises(AssertionError):
+ testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state)
+
+
+def test_gate_helper():
+ g = testing.GateHelper(MultiAnd(cvs=(1, 0, 1, 0)))
+ assert g.gate == MultiAnd(cvs=(1, 0, 1, 0))
+ assert g.r == Signature(
+ [
+ Register('ctrl', bitsize=1, shape=4),
+ Register('junk', bitsize=1, shape=2, side=Side.RIGHT),
+ Register('target', bitsize=1, side=Side.RIGHT),
+ ]
+ )
+ expected_quregs = {
+ 'ctrl': np.array([[cirq.q(f'ctrl[{i}]')] for i in range(4)]),
+ 'junk': np.array([[cirq.q(f'junk[{i}]')] for i in range(2)]),
+ 'target': [cirq.NamedQubit('target')],
+ }
+ for key in expected_quregs:
+ assert np.array_equal(g.quregs[key], expected_quregs[key])
+ assert g.operation.qubits == tuple(g.all_qubits)
+ assert len(g.circuit) == 1
+
+
+class DoesNotDecompose(cirq.Operation):
+ def _t_complexity_(self) -> TComplexity:
+ return TComplexity(t=1, clifford=2, rotations=3)
+
+ @property
+ def qubits(self):
+ return []
+
+ def with_qubits(self, _):
+ pass
+
+
+class InconsistentDecompostion(cirq.Operation):
+ def _t_complexity_(self) -> TComplexity:
+ return TComplexity(rotations=1)
+
+ def _decompose_(self) -> cirq.OP_TREE:
+ yield cirq.X(self.qubits[0])
+
+ @property
+ def qubits(self):
+ return tuple(cirq.LineQubit(3).range(3))
+
+ def with_qubits(self, _):
+ pass
+
+
+@pytest.mark.parametrize("val", [cirq.T, DoesNotDecompose(), And()])
+def test_assert_decompose_is_consistent_with_t_complexity(val):
+ testing.assert_decompose_is_consistent_with_t_complexity(val)
+
+
+def test_assert_decompose_is_consistent_with_t_complexity_raises():
+ with pytest.raises(AssertionError):
+ testing.assert_decompose_is_consistent_with_t_complexity(InconsistentDecompostion())