diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 834e6e4b5..2da6d974b 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -148,6 +148,14 @@ module=qualtran.bloqs.basic_gates.s_gate, bloq_specs=[qualtran.bloqs.basic_gates.s_gate._S_GATE_DOC], ), + NotebookSpecV2( + title='Y Gate', + module=qualtran.bloqs.basic_gates.y_gate, + bloq_specs=[ + qualtran.bloqs.basic_gates.y_gate._Y_GATE_DOC, + qualtran.bloqs.basic_gates.y_gate._CY_GATE_DOC, + ], + ), NotebookSpecV2( title='And', module=qualtran.bloqs.mcmt.and_bloq, diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 2a5af961c..e68836c22 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -31,6 +31,7 @@ Bloqs Library basic_gates/hadamard.ipynb basic_gates/cnot.ipynb basic_gates/s_gate.ipynb + basic_gates/y_gate.ipynb mcmt/and_bloq.ipynb basic_gates/states_and_effects.ipynb swap_network/swap_network.ipynb diff --git a/qualtran/bloqs/basic_gates/__init__.py b/qualtran/bloqs/basic_gates/__init__.py index 0f7b9e6fe..ab6a40cf4 100644 --- a/qualtran/bloqs/basic_gates/__init__.py +++ b/qualtran/bloqs/basic_gates/__init__.py @@ -34,5 +34,5 @@ from .t_gate import TGate from .toffoli import Toffoli from .x_basis import MinusEffect, MinusState, PlusEffect, PlusState, XGate -from .y_gate import YGate +from .y_gate import CYGate, YGate from .z_basis import IntEffect, IntState, OneEffect, OneState, ZeroEffect, ZeroState, ZGate diff --git a/qualtran/bloqs/basic_gates/y_gate.ipynb b/qualtran/bloqs/basic_gates/y_gate.ipynb new file mode 100644 index 000000000..0a63ac37c --- /dev/null +++ b/qualtran/bloqs/basic_gates/y_gate.ipynb @@ -0,0 +1,221 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "1f6baa88", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Y Gate" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a4dd57f4", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "39518f37", + "metadata": { + "cq.autogen": "YGate.bloq_doc.md" + }, + "source": [ + "## `YGate`\n", + "The Pauli Y gate.\n", + "\n", + "This causes a bit flip (with a complex phase): Y|0> = 1j|1>, Y|1> = -1j|0>\n", + "\n", + "#### Registers\n", + " - `q`: The qubit.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4f9aa8e7", + "metadata": { + "cq.autogen": "YGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import YGate" + ] + }, + { + "cell_type": "markdown", + "id": "f1dae24e", + "metadata": { + "cq.autogen": "YGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9c3a5a18", + "metadata": { + "cq.autogen": "YGate.y_gate" + }, + "outputs": [], + "source": [ + "y_gate = YGate()" + ] + }, + { + "cell_type": "markdown", + "id": "0bcd3fda", + "metadata": { + "cq.autogen": "YGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c5d7345", + "metadata": { + "cq.autogen": "YGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([y_gate],\n", + " ['`y_gate`'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7a1ba7bf-de51-496d-b637-9db51ed40cce", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(y_gate, 'musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "5f68305f", + "metadata": { + "cq.autogen": "CYGate.bloq_doc.md" + }, + "source": [ + "## `CYGate`\n", + "A controlled Y gate.\n", + "\n", + "#### Registers\n", + " - `ctrl`: The control qubit.\n", + " - `q`: The target qubit.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8fa440cd", + "metadata": { + "cq.autogen": "CYGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import CYGate" + ] + }, + { + "cell_type": "markdown", + "id": "6cd4b475", + "metadata": { + "cq.autogen": "CYGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fb197f4d", + "metadata": { + "cq.autogen": "CYGate.cy_gate" + }, + "outputs": [], + "source": [ + "cy_gate = YGate().controlled()\n", + "assert isinstance(cy_gate, CYGate)" + ] + }, + { + "cell_type": "markdown", + "id": "61fdd296", + "metadata": { + "cq.autogen": "CYGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad2d7347", + "metadata": { + "cq.autogen": "CYGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([cy_gate],\n", + " ['`cy_gate`'])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4242fd0b-17a0-48ad-bae9-ec146c65d810", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(cy_gate, 'musical_score')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.11.8" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/basic_gates/y_gate.py b/qualtran/bloqs/basic_gates/y_gate.py index b3ee7255e..1430e1811 100644 --- a/qualtran/bloqs/basic_gates/y_gate.py +++ b/qualtran/bloqs/basic_gates/y_gate.py @@ -13,13 +13,27 @@ # limitations under the License. from functools import cached_property -from typing import Dict, List, Tuple, TYPE_CHECKING +from typing import Dict, Iterable, List, Optional, Sequence, Tuple, TYPE_CHECKING, Union import numpy as np from attrs import frozen -from qualtran import Bloq, ConnectionT, Signature +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CompositeBloq, + ConnectionT, + CtrlSpec, + DecomposeTypeError, + Register, + Signature, + SoquetT, +) from qualtran.cirq_interop.t_complexity_protocol import TComplexity +from qualtran.drawing import Circle, Text, TextBox, WireSymbol if TYPE_CHECKING: import cirq @@ -35,12 +49,18 @@ class YGate(Bloq): """The Pauli Y gate. This causes a bit flip (with a complex phase): Y|0> = 1j|1>, Y|1> = -1j|0> + + Registers: + q: The qubit. """ @cached_property def signature(self) -> 'Signature': return Signature.build(q=1) + def decompose_bloq(self) -> 'CompositeBloq': + raise DecomposeTypeError(f"{self} is atomic.") + def adjoint(self) -> 'Bloq': return self @@ -55,6 +75,23 @@ def my_tensors( ) ] + def get_ctrl_system( + self, ctrl_spec: Optional['CtrlSpec'] = None + ) -> Tuple['Bloq', 'AddControlledT']: + if not (ctrl_spec is None or ctrl_spec == CtrlSpec()): + return super().get_ctrl_system(ctrl_spec=ctrl_spec) + + bloq = CYGate() + + def _add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl,) = ctrl_soqs + ctrl, q = bb.add(bloq, ctrl=ctrl, target=in_soqs['q']) + return ((ctrl,), (q,)) + + return bloq, _add_ctrled + def as_cirq_op( self, qubit_manager: 'cirq.QubitManager', q: 'CirqQuregT' ) -> Tuple['cirq.Operation', Dict[str, 'CirqQuregT']]: @@ -65,3 +102,89 @@ def as_cirq_op( def _t_complexity_(self) -> 'TComplexity': return TComplexity(clifford=1) + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + return TextBox('Y') + + +@bloq_example +def _y_gate() -> YGate: + y_gate = YGate() + return y_gate + + +_Y_GATE_DOC = BloqDocSpec(bloq_cls=YGate, examples=[_y_gate], call_graph_example=None) + + +@frozen +class CYGate(Bloq): + """A controlled Y gate. + + Registers: + ctrl: The control qubit. + target: The target qubit. + """ + + @cached_property + def signature(self) -> 'Signature': + return Signature.build(ctrl=1, target=1) + + def decompose_bloq(self) -> 'CompositeBloq': + raise DecomposeTypeError(f"{self} is atomic.") + + def adjoint(self) -> 'Bloq': + return self + + def my_tensors( + self, incoming: Dict[str, 'ConnectionT'], outgoing: Dict[str, 'ConnectionT'] + ) -> List['qtn.Tensor']: + import quimb.tensor as qtn + + unitary = np.eye(4, dtype=np.complex128).reshape((2, 2, 2, 2)) + # Use these inds orderings to set the block where ctrl=1 to the desired gate. + inds = [ + (outgoing['ctrl'], 0), + (outgoing['target'], 0), + (incoming['ctrl'], 0), + (incoming['target'], 0), + ] + unitary[1, :, 1, :] = _PAULIY + + return [qtn.Tensor(data=unitary, inds=inds, tags=[str(self)])] + + def as_cirq_op( + self, qubit_manager: 'cirq.QubitManager', ctrl: 'CirqQuregT', target: 'CirqQuregT' + ) -> Tuple[Union['cirq.Operation', None], Dict[str, 'CirqQuregT']]: + import cirq + + (ctrl,) = ctrl + (target,) = target + return cirq.Y.on(target).controlled_by(ctrl), { + 'ctrl': np.array([ctrl]), + 'target': np.array([target]), + } + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text('') + if reg.name == 'ctrl': + return Circle() + if reg.name == 'target': + return TextBox('Y') + raise ValueError(f"Unknown register {reg}.") + + +@bloq_example +def _cy_gate() -> CYGate: + cy_gate = YGate().controlled() + assert isinstance(cy_gate, CYGate) + return cy_gate + + +_CY_GATE_DOC = BloqDocSpec(bloq_cls=CYGate, examples=[_cy_gate], call_graph_example=None) diff --git a/qualtran/bloqs/basic_gates/y_gate_test.py b/qualtran/bloqs/basic_gates/y_gate_test.py index 874248c50..e5f6e9def 100644 --- a/qualtran/bloqs/basic_gates/y_gate_test.py +++ b/qualtran/bloqs/basic_gates/y_gate_test.py @@ -15,7 +15,18 @@ import numpy as np from qualtran import BloqBuilder -from qualtran.bloqs.basic_gates import MinusState, PlusState, YGate +from qualtran.bloqs.basic_gates import MinusState, OneEffect, OneState, PlusState, YGate +from qualtran.bloqs.basic_gates.y_gate import _cy_gate, _y_gate, CYGate +from qualtran.cirq_interop import cirq_gate_to_bloq +from qualtran.cirq_interop.t_complexity_protocol import t_complexity, TComplexity + + +def test_y_gate(bloq_autotester): + bloq_autotester(_y_gate) + + +def test_cy_gate(bloq_autotester): + bloq_autotester(_cy_gate) def test_to_cirq(): @@ -38,3 +49,49 @@ def test_to_cirq(): vec1 = cbloq.tensor_contract() vec2 = cirq.final_state_vector(circuit) np.testing.assert_allclose(vec1, vec2) + + +def test_cy_vs_cirq(): + bloq = YGate().controlled() + assert bloq == CYGate() + + gate = cirq.Y.controlled() + np.testing.assert_allclose(cirq.unitary(gate), bloq.tensor_contract()) + + +def test_cirq_interop(): + circuit = CYGate().as_composite_bloq().to_cirq_circuit() + should_be = cirq.Circuit( + [cirq.Moment(cirq.Y(cirq.NamedQubit('target')).controlled_by(cirq.NamedQubit('ctrl')))] + ) + assert circuit == should_be + + (op,) = list(should_be.all_operations()) + assert op.gate is not None + assert cirq_gate_to_bloq(op.gate) == CYGate() + + +def test_active_cy_is_y(): + bb = BloqBuilder() + q = bb.add_register('q', 1) + ctrl_on = bb.add(OneState()) + ctrl_on, q = bb.add(CYGate(), ctrl=ctrl_on, target=q) + bb.add(OneEffect(), q=ctrl_on) + cbloq = bb.finalize(q=q) + + np.testing.assert_allclose(YGate().tensor_contract(), cbloq.tensor_contract()) + + +def test_cy_adjoint(): + bb = BloqBuilder() + ctrl = bb.add_register('ctrl', 1) + q = bb.add_register('q', 1) + ctrl, q = bb.add(CYGate(), ctrl=ctrl, target=q) + ctrl, q = bb.add(CYGate().adjoint(), ctrl=ctrl, target=q) + cbloq = bb.finalize(ctrl=ctrl, q=q) + + np.testing.assert_allclose(np.eye(4), cbloq.tensor_contract(), atol=1e-12) + + +def test_cy_t_complexity(): + assert t_complexity(CYGate()) == TComplexity(clifford=1) diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index 46ee27f62..2ce5e2a9a 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -330,6 +330,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: CHadamard, CNOT, CSwap, + CYGate, CZPowGate, GlobalPhase, Hadamard, @@ -373,6 +374,7 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: cirq.TOFFOLI: Toffoli(), cirq.X: XGate(), cirq.Y: YGate(), + cirq.ControlledGate(cirq.Y): CYGate(), cirq.Z: ZGate(), cirq.SWAP: TwoBitSwap(), cirq.CSWAP: CSwap(1), diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index 0b8a51187..4e5107eb1 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -108,7 +108,16 @@ def classify_t_count_by_bloq_type( def bloq_is_clifford(b: Bloq): - from qualtran.bloqs.basic_gates import CNOT, Hadamard, SGate, TwoBitSwap, XGate, ZGate + from qualtran.bloqs.basic_gates import ( + CNOT, + CYGate, + Hadamard, + SGate, + TwoBitSwap, + XGate, + YGate, + ZGate, + ) from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiTargetCNOT @@ -116,7 +125,19 @@ def bloq_is_clifford(b: Bloq): b = b.subbloq if isinstance( - b, (TwoBitSwap, Hadamard, XGate, ZGate, ArbitraryClifford, CNOT, MultiTargetCNOT, SGate) + b, + ( + TwoBitSwap, + Hadamard, + XGate, + ZGate, + YGate, + ArbitraryClifford, + CNOT, + MultiTargetCNOT, + CYGate, + SGate, + ), ): return True diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index cb1fa3214..a4123d35d 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -207,6 +207,7 @@ "qualtran.bloqs.basic_gates.x_basis.PlusState": qualtran.bloqs.basic_gates.x_basis.PlusState, "qualtran.bloqs.basic_gates.x_basis.XGate": qualtran.bloqs.basic_gates.x_basis.XGate, "qualtran.bloqs.basic_gates.y_gate.YGate": qualtran.bloqs.basic_gates.y_gate.YGate, + "qualtran.bloqs.basic_gates.y_gate.CYGate": qualtran.bloqs.basic_gates.y_gate.CYGate, "qualtran.bloqs.basic_gates.z_basis.IntEffect": qualtran.bloqs.basic_gates.z_basis.IntEffect, "qualtran.bloqs.basic_gates.z_basis.IntState": qualtran.bloqs.basic_gates.z_basis.IntState, "qualtran.bloqs.basic_gates.z_basis.OneEffect": qualtran.bloqs.basic_gates.z_basis.OneEffect,