diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index f0b156b58..2ffe17454 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -593,9 +593,12 @@ title='Basic Rotation Gates', module=qualtran.bloqs.basic_gates.rotation, bloq_specs=[ + qualtran.bloqs.basic_gates.rotation._Z_POW_DOC, + qualtran.bloqs.basic_gates.rotation._CZ_POW_DOC, + qualtran.bloqs.basic_gates.rotation._RZ_DOC, + qualtran.bloqs.basic_gates.rotation._CRZ_DOC, qualtran.bloqs.basic_gates.rotation._X_POW_DOC, qualtran.bloqs.basic_gates.rotation._Y_POW_DOC, - qualtran.bloqs.basic_gates.rotation._Z_POW_DOC, ], ), NotebookSpecV2( diff --git a/qualtran/bloqs/basic_gates/__init__.py b/qualtran/bloqs/basic_gates/__init__.py index 09bf91e34..66910b89f 100644 --- a/qualtran/bloqs/basic_gates/__init__.py +++ b/qualtran/bloqs/basic_gates/__init__.py @@ -27,7 +27,7 @@ from .identity import Identity from .on_each import OnEach from .power import Power -from .rotation import CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate +from .rotation import CRz, CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate from .s_gate import SGate from .su2_rotation import SU2RotationGate from .swap import CSwap, Swap, TwoBitCSwap, TwoBitSwap diff --git a/qualtran/bloqs/basic_gates/global_phase.ipynb b/qualtran/bloqs/basic_gates/global_phase.ipynb index e2fac24ab..949bbece8 100644 --- a/qualtran/bloqs/basic_gates/global_phase.ipynb +++ b/qualtran/bloqs/basic_gates/global_phase.ipynb @@ -38,16 +38,22 @@ "## `GlobalPhase`\n", "Applies a global phase to the circuit as a whole.\n", "\n", - "The unitary effect is to multiply the state vector by the complex scalar\n", - "$e^{i pi t}$ for `exponent` $t$.\n", + "For an exponent $t$, the unitary effect is to multiply the state vector by the complex scalar\n", + "$$\n", + "(-1)^t = e^{i \\pi t}\n", + "$$\n", "\n", "The global phase of a state or circuit does not affect any observable quantity, but\n", - "keeping track of it can be a useful bookkeeping mechanism for testing circuit identities.\n", - "The global phase becomes important if the gate becomes controlled.\n", + "keeping track of it can be a useful bookkeeping mechanism for testing circuit identities, and\n", + "the global phase becomes important when controlling an operation.\n", + "\n", + "This is fundamentally an atomic operation and this bloq has no decomposition in Qualtran.\n", + "\n", + "The single-qubit controlled version of `GlobalPhase` is `ZPowGate`.\n", "\n", "#### Parameters\n", - " - `exponent`: the exponent $t$ of the global phase $e^{i pi t}$ to apply.\n", - " - `eps`: precision\n" + " - `exponent`: the exponent t of the global phase (-1)^t to apply.\n", + " - `eps`: The precision of the rotation. This parameter is for bookkeeping and does not affect e.g. the tensor representation of this gate.\n" ] }, { @@ -108,31 +114,6 @@ " ['`global_phase`'])" ] }, - { - "cell_type": "markdown", - "id": "facc4a57", - "metadata": { - "cq.autogen": "GlobalPhase.call_graph.md" - }, - "source": [ - "### Call Graph" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "94dfefb2", - "metadata": { - "cq.autogen": "GlobalPhase.call_graph.py" - }, - "outputs": [], - "source": [ - "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "global_phase_g, global_phase_sigma = global_phase.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(global_phase_g)\n", - "show_counts_sigma(global_phase_sigma)" - ] - }, { "cell_type": "markdown", "id": "c183275c-cc9c-477d-888c-d8b850f67a2e", @@ -158,7 +139,7 @@ "id": "b737b871-9c61-4d54-860a-d9928f18808b", "metadata": {}, "source": [ - "When a global phase is controlled, it is equivalent to a ZPowGate" + "When a global phase is controlled, it is equivalent to a `ZPowGate`" ] }, { diff --git a/qualtran/bloqs/basic_gates/global_phase.py b/qualtran/bloqs/basic_gates/global_phase.py index cae67a2fa..f1a0c41c9 100644 --- a/qualtran/bloqs/basic_gates/global_phase.py +++ b/qualtran/bloqs/basic_gates/global_phase.py @@ -16,7 +16,7 @@ import attrs import cirq -from attrs import field, frozen +from attrs import frozen from qualtran import ( AddControlledT, @@ -30,7 +30,6 @@ DecomposeTypeError, SoquetT, ) -from qualtran.bloqs.basic_gates.rotation import ZPowGate from qualtran.cirq_interop import CirqGateAsBloqBase from qualtran.symbolics import pi, sarg, sexp, SymbolicComplex, SymbolicFloat @@ -38,23 +37,30 @@ import quimb.tensor as qtn -@frozen +@frozen(kw_only=True) class GlobalPhase(CirqGateAsBloqBase): r"""Applies a global phase to the circuit as a whole. - The unitary effect is to multiply the state vector by the complex scalar - $e^{i pi t}$ for `exponent` $t$. + For an exponent $t$, the unitary effect is to multiply the state vector by the complex scalar + $$ + (-1)^t = e^{i \pi t} + $$ The global phase of a state or circuit does not affect any observable quantity, but - keeping track of it can be a useful bookkeeping mechanism for testing circuit identities. - The global phase becomes important if the gate becomes controlled. + keeping track of it can be a useful bookkeeping mechanism for testing circuit identities, and + the global phase becomes important when controlling an operation. + + This is fundamentally an atomic operation and this bloq has no decomposition in Qualtran. + + The single-qubit controlled version of `GlobalPhase` is `ZPowGate`. Args: - exponent: the exponent $t$ of the global phase $e^{i pi t}$ to apply. - eps: precision + exponent: the exponent t of the global phase (-1)^t to apply. + eps: The precision of the rotation. This parameter is for bookkeeping and does + not affect e.g. the tensor representation of this gate. """ - exponent: SymbolicFloat = field(kw_only=True) + exponent: SymbolicFloat eps: SymbolicFloat = 1e-11 @cached_property @@ -87,14 +93,12 @@ def my_tensors( def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: # Delegate to superclass logic for more than one control. - if not (ctrl_spec == CtrlSpec() or ctrl_spec == CtrlSpec(cvs=0)): + if ctrl_spec != CtrlSpec(): return super().get_ctrl_system(ctrl_spec=ctrl_spec) - # Otherwise, it's a ZPowGate - if ctrl_spec == CtrlSpec(cvs=0): - bloq = ZPowGate(exponent=-self.exponent, global_shift=-1, eps=self.eps) - else: - bloq = ZPowGate(exponent=self.exponent, eps=self.eps) + from qualtran.bloqs.basic_gates import ZPowGate + + bloq = ZPowGate(exponent=self.exponent, eps=self.eps) def _add_ctrled( bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] @@ -115,4 +119,6 @@ def _global_phase() -> GlobalPhase: return global_phase -_GLOBAL_PHASE_DOC = BloqDocSpec(bloq_cls=GlobalPhase, examples=[_global_phase]) +_GLOBAL_PHASE_DOC = BloqDocSpec( + bloq_cls=GlobalPhase, examples=[_global_phase], call_graph_example=None +) diff --git a/qualtran/bloqs/basic_gates/rotation.ipynb b/qualtran/bloqs/basic_gates/rotation.ipynb index e5d4c58d9..74ef85e62 100644 --- a/qualtran/bloqs/basic_gates/rotation.ipynb +++ b/qualtran/bloqs/basic_gates/rotation.ipynb @@ -7,7 +7,37 @@ "cq.autogen": "title_cell" }, "source": [ - "# Basic Rotation Gates" + "# Basic Rotation Gates\n", + "\n", + "Single-qubit rotation gates.\n", + "\n", + "A single qubit's state can be mapped to the Bloch sphere. Rotations around the three\n", + "axes are generated by the pauli matrices:\n", + "$$\n", + "R_a(\\theta) = \\exp(-i \\theta / 2 \\sigma_a)\n", + "$$\n", + "where $\\sigma_a$ is one of the Pauli $X, Y, Z$ operators.\n", + "\n", + "Since global phase is often irrelevant, practitioners can use an alternative phase convention\n", + "and define single qubit gates as the real-valued power of a Pauli operator:\n", + "$$\n", + "(\\sigma_a)^t\n", + "$$\n", + "which can be multiplied by $e^{-i \\pi t / 2}$ to recover the $R_a(t \\pi)$ matrix.\n", + "\n", + "In Qualtran, we provide `ZPowGate` and `Rz` for the two respective phase conventions, as well\n", + "as analogues for the X and Y axes.\n", + "\n", + "Global phase becomes a relevant, relative phase when forming controlled gates. Indeed, the\n", + "`ZPowGate` is a controlled `GlobalPhase` operation. Whereas `ZPowGate` and `Rz` are the\n", + "same up to global phase, their controlled versions `CZPowGate` and `CRz` are different operations\n", + "with different costs.\n", + "\n", + "#### General References\n", + " - [Quantum Computation and Quantum Information](https://doi.org/10.1017/CBO9780511976667).\n", + " Nielsen and Chuang. 2010. Section 4.2\n", + " - [Elementary gates for quantum computation](https://arxiv.org/abs/quant-ph/9503016).\n", + " Barenco et. al. 1995." ] }, { @@ -30,59 +60,300 @@ }, { "cell_type": "markdown", - "id": "520a7128", + "id": "2becabd1", "metadata": { - "cq.autogen": "XPowGate.bloq_doc.md" + "cq.autogen": "ZPowGate.bloq_doc.md" }, "source": [ - "## `XPowGate`\n", - "A gate that rotates around the X axis of the Bloch sphere.\n", + "## `ZPowGate`\n", + "Apply a power of the Pauli Z operator to a single qubit.\n", "\n", - "The unitary matrix of `XPowGate(exponent=t, global_shift=s)` is:\n", + "Given `exponent` $t$, the unitary matrix of this gate is:\n", "$$\n", - "e^{i \\pi t (s + 1/2)}\n", + "Z^t =\n", "\\begin{bmatrix}\n", - " \\cos(\\pi t /2) & -i \\sin(\\pi t /2) \\\\\n", - " -i \\sin(\\pi t /2) & \\cos(\\pi t /2)\n", + " 1 & 0 \\\\\n", + " 0 & e^{i \\pi t}\n", "\\end{bmatrix}\n", "$$\n", "\n", - "Note in particular that this gate has a global phase factor of\n", - "$e^{i \\pi t / 2}$ vs the traditionally defined rotation matrices\n", - "about the Pauli X axis. See `Rx` for rotations without the global\n", - "phase. The global phase factor can be adjusted by using the `global_shift`\n", - "parameter when initializing.\n", + "This is an atomic bloq in Qualtran. For many architectures, you will likely need to\n", + "synthesize an arbitrary-angle rotation from a discrete gateset like Clifford+T. Please\n", + "see the references for more information.\n", + "\n", + "#### Relationships\n", + "This gate differs by a global phase from the $R_Z$ gate. `ZPowGate(t)` equals\n", + "`Rz(angle=t*np.pi)` plus `GlobalPhase(t/2)`.\n", + "\n", + "This gate is the controlled version of a global phase gate. `ZPowGate(t)` equals\n", + "`GlobalPhase(t).controlled()`.\n", + "\n", + "`exponent=1` corresponds to `ZGate`, `exponent=0.5` to `SGate`, and `exponent=0.25` to\n", + "`TGate`.\n", "\n", "#### Parameters\n", - " - `exponent`: The t in gate**t. Determines how much the eigenvalues of the gate are phased by. For example, eigenvectors phased by -1 when `gate**1` is applied will gain a relative phase of e^{i pi exponent} when `gate**exponent` is applied (relative to eigenvectors unaffected by `gate**1`).\n", - " - `global_shift`: Offsets the eigenvalues of the gate at exponent=1. In effect, this controls a global phase factor on the gate's unitary matrix. The factor for global_shift=s is: exp(i * pi * s * t)\n", - " - `eps`: precision for implementation of rotation. \n", + " - `exponent`: The exponent t in Z^t.\n", + " - `eps`: The precision of the rotation. This parameter is for bookkeeping and does not affect e.g. the tensor representation of this gate. When synthesizing a rotation from a discrete gate set, you must fix a precision `eps`. \n", + "\n", + "#### Registers\n", + " - `q`: The qubit. \n", + "\n", + "#### References\n", + " - [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/abs/1403.2975). Ross and Selinger. 2014.\n", + " - [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). Bocharov et. al. 2014. Offers a small improvement in Cliffod+T synthesis.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7e49732e", + "metadata": { + "cq.autogen": "ZPowGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import ZPowGate" + ] + }, + { + "cell_type": "markdown", + "id": "3b1c7641", + "metadata": { + "cq.autogen": "ZPowGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9524ef48", + "metadata": { + "cq.autogen": "ZPowGate.z_pow" + }, + "outputs": [], + "source": [ + "z_pow = ZPowGate(exponent=0.123, eps=1e-8)" + ] + }, + { + "cell_type": "markdown", + "id": "410c9c7a", + "metadata": { + "cq.autogen": "ZPowGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dbb6a2cb", + "metadata": { + "cq.autogen": "ZPowGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([z_pow],\n", + " ['`z_pow`'])" + ] + }, + { + "cell_type": "markdown", + "id": "18e6999b", + "metadata": { + "cq.autogen": "CZPowGate.bloq_doc.md" + }, + "source": [ + "## `CZPowGate`\n", + "The controlled `ZPowGate`\n", + "\n", + "The unitary matrix of `CZPowGate(exponent=t)` is:\n", + "$$\n", + "C[Z^t] =\n", + "\\begin{bmatrix}\n", + " 1 & 0 & 0 & 0 \\\\\n", + " 0 & 1 & 0 & 0 \\\\\n", + " 0 & 0 & 1 & 0 \\\\\n", + " 0 & 0 & 0 & e^{i \\pi t} \\\\\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "#### Relationships\n", + "This gate has the same unitary as `Controlled(ZPowGate)`. CZPowGate(exponent=1) corresponds\n", + "to a `CZ` gate.\n", + "\n", + "#### Parameters\n", + " - `exponent`: The exponent t in Z^t.\n", + " - `eps`: The precision of the controlled rotation. This parameter is for bookkeeping and does not affect e.g. the tensor representation of this gate. When synthesizing a rotation from a discrete gate set, you must fix a precision `eps`. \n", + "\n", + "#### Registers\n", + " - `q`: A shape=(2,) register of two qubits ordered. This is a symmetric gate. \n", + "\n", + "#### References\n", + " - [Simulating chemistry efficiently on fault-tolerant quantum computers](https://arxiv.org/abs/1204.0567). Jones et. al. 2012. Figure 8.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "dd8d319b", + "metadata": { + "cq.autogen": "CZPowGate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import CZPowGate" + ] + }, + { + "cell_type": "markdown", + "id": "48691fd5", + "metadata": { + "cq.autogen": "CZPowGate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99c8add8", + "metadata": { + "cq.autogen": "CZPowGate.cz_pow" + }, + "outputs": [], + "source": [ + "cz_pow = CZPowGate(exponent=0.123)" + ] + }, + { + "cell_type": "markdown", + "id": "da5de6a1", + "metadata": { + "cq.autogen": "CZPowGate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "282ea524", + "metadata": { + "cq.autogen": "CZPowGate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([cz_pow],\n", + " ['`cz_pow`'])" + ] + }, + { + "cell_type": "markdown", + "id": "2ccebaf0", + "metadata": { + "cq.autogen": "CZPowGate.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53546d23", + "metadata": { + "cq.autogen": "CZPowGate.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "cz_pow_g, cz_pow_sigma = cz_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(cz_pow_g)\n", + "show_counts_sigma(cz_pow_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "0abc3463-9e86-4b62-99d9-ef98e85db0d3", + "metadata": {}, + "source": [ + "### Decomposition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4dc5887-2f2f-43b3-931d-31ed2e5f8d56", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(cz_pow.decompose_bloq(), 'musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "e1d92535", + "metadata": { + "cq.autogen": "Rz.bloq_doc.md" + }, + "source": [ + "## `Rz`\n", + "Apply a single-qubit Z rotation.\n", + "\n", + "Given `angle` $\\theta$, the unitary matrix of this gate is:\n", + "$$\n", + "R_Z(\\theta) = \\exp(-i \\frac{\\theta}{2} Z) =\n", + "\\begin{bmatrix}\n", + " e^{-i \\theta/2} & 0 \\\\\n", + " 0 & e^{i \\theta/2}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "This is an atomic bloq in Qualtran. For many architectures, you will likely need to\n", + "synthesize an arbitrary-angle rotation from a discrete gateset like Clifford+T. Please\n", + "see the references for more information.\n", + "\n", + "#### Relationships\n", + "This gate differs by a global phase from the `Z^t` gate. `Rz(a)` equals\n", + "`ZPowGate(exponent=a/np.pi)` plus `GlobalPhase(-a/(2*np.pi))`.\n", + "\n", + "#### Parameters\n", + " - `angle`: The rotation angle in radians.\n", + " - `eps`: The precision of the rotation. This parameter is for bookkeeping and does not affect e.g. the tensor representation of this gate. When synthesizing a rotation from a discrete gate set, you must fix a precision `eps`. \n", "\n", "#### Registers\n", " - `q`: One-bit register. \n", "\n", "#### References\n", - " - [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). Offers a small improvement\n", - " - [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/pdf/1403.2975.pdf). \n" + " - [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/abs/1403.2975). Ross and Selinger. 2014.\n", + " - [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). Bocharov et. al. 2014. Offers a small improvement in Cliffod+T synthesis.\n" ] }, { "cell_type": "code", "execution_count": null, - "id": "87de6a53", + "id": "90df3bde", "metadata": { - "cq.autogen": "XPowGate.bloq_doc.py" + "cq.autogen": "Rz.bloq_doc.py" }, "outputs": [], "source": [ - "from qualtran.bloqs.basic_gates import XPowGate" + "from qualtran.bloqs.basic_gates import Rz" ] }, { "cell_type": "markdown", - "id": "138bee7e", + "id": "0b0988fe", "metadata": { - "cq.autogen": "XPowGate.example_instances.md" + "cq.autogen": "Rz.example_instances.md" }, "source": [ "### Example Instances" @@ -91,20 +362,21 @@ { "cell_type": "code", "execution_count": null, - "id": "530a736e", + "id": "f40878fb", "metadata": { - "cq.autogen": "XPowGate.x_pow" + "cq.autogen": "Rz.rz" }, "outputs": [], "source": [ - "x_pow = XPowGate(exponent=0.123, eps=1e-8)" + "a = sympy.Symbol('a')\n", + "rz = Rz(a)" ] }, { "cell_type": "markdown", - "id": "c641f023", + "id": "40d2b630", "metadata": { - "cq.autogen": "XPowGate.graphical_signature.md" + "cq.autogen": "Rz.graphical_signature.md" }, "source": [ "#### Graphical Signature" @@ -113,22 +385,115 @@ { "cell_type": "code", "execution_count": null, - "id": "0558ecd1", + "id": "5e67131a", "metadata": { - "cq.autogen": "XPowGate.graphical_signature.py" + "cq.autogen": "Rz.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([x_pow],\n", - " ['`x_pow`'])" + "show_bloqs([rz],\n", + " ['`rz`'])" ] }, { "cell_type": "markdown", - "id": "1092a383", + "id": "79f579a1", "metadata": { - "cq.autogen": "XPowGate.call_graph.md" + "cq.autogen": "CRz.bloq_doc.md" + }, + "source": [ + "## `CRz`\n", + "A controlled Rz rotation.\n", + "\n", + "Given `angle` $\\theta$, the unitary matrix of this gate is:\n", + "$$\n", + "C[R_Z(\\theta)] =\n", + "\\begin{bmatrix}\n", + " 1 & & & \\\\\n", + " & 1 & & \\\\\n", + " & & e^{-i \\theta/2} & \\\\\n", + " & & & e^{i \\theta/2}\n", + "\\end{bmatrix}\n", + "$$\n", + "\n", + "#### Parameters\n", + " - `angle`: The rotation angle in radians.\n", + " - `eps`: The precision of the rotation. This parameter is for bookkeeping and does not affect e.g. the tensor representation of this gate. When synthesizing a rotation from a discrete gate set, you must fix a precision `eps`. \n", + "\n", + "#### Registers\n", + " - `ctrl`: Whether the rotation is active.\n", + " - `q`: The qubit on which we optionally perform the rotation. \n", + "\n", + "#### References\n", + " - [Elementary gates for quantum computation](https://arxiv.org/abs/quant-ph/9503016). Barenco et al. 1995. Special case of Lemma 5.4.\n", + " - [Is Controlled(𝑅𝑧(𝜃)) more expensive than Controlled(𝑍𝑡) on the surface code?](https://quantumcomputing.stackexchange.com/a/40012). Adam Zalcman. 2024.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e0fb41e", + "metadata": { + "cq.autogen": "CRz.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.basic_gates import CRz" + ] + }, + { + "cell_type": "markdown", + "id": "442be581", + "metadata": { + "cq.autogen": "CRz.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95820801", + "metadata": { + "cq.autogen": "CRz.crz" + }, + "outputs": [], + "source": [ + "theta = sympy.Symbol(r'\\theta')\n", + "crz = CRz(angle=theta)" + ] + }, + { + "cell_type": "markdown", + "id": "f6d4ea1e", + "metadata": { + "cq.autogen": "CRz.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fea6812c", + "metadata": { + "cq.autogen": "CRz.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([crz],\n", + " ['`crz`'])" + ] + }, + { + "cell_type": "markdown", + "id": "31944637", + "metadata": { + "cq.autogen": "CRz.call_graph.md" }, "source": [ "### Call Graph" @@ -137,44 +502,63 @@ { "cell_type": "code", "execution_count": null, - "id": "f6ae8a85", + "id": "f7d3d4e0", "metadata": { - "cq.autogen": "XPowGate.call_graph.py" + "cq.autogen": "CRz.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "x_pow_g, x_pow_sigma = x_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(x_pow_g)\n", - "show_counts_sigma(x_pow_sigma)" + "crz_g, crz_sigma = crz.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(crz_g)\n", + "show_counts_sigma(crz_sigma)" ] }, { "cell_type": "markdown", - "id": "9f018721", + "id": "194fa525-9745-4c5a-a72c-dbabf4e8e3fc", + "metadata": {}, + "source": [ + "### Decomposition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83bd4c14-ab54-45e0-b3f7-54299acf94a6", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(crz.decompose_bloq(), 'musical_score')" + ] + }, + { + "cell_type": "markdown", + "id": "520a7128", "metadata": { - "cq.autogen": "YPowGate.bloq_doc.md" + "cq.autogen": "XPowGate.bloq_doc.md" }, "source": [ - "## `YPowGate`\n", - "A gate that rotates around the Y axis of the Bloch sphere.\n", + "## `XPowGate`\n", + "A gate that rotates around the X axis of the Bloch sphere.\n", "\n", - "The unitary matrix of `YPowGate(exponent=t)` is:\n", + "The unitary matrix of `XPowGate(exponent=t, global_shift=s)` is:\n", "$$\n", - " \\begin{bmatrix}\n", - " e^{i \\pi t /2} \\cos(\\pi t /2) & - e^{i \\pi t /2} \\sin(\\pi t /2) \\\\\n", - " e^{i \\pi t /2} \\sin(\\pi t /2) & e^{i \\pi t /2} \\cos(\\pi t /2)\n", - " \\end{bmatrix}\n", + "e^{i \\pi t (s + 1/2)}\n", + "\\begin{bmatrix}\n", + " \\cos(\\pi t /2) & -i \\sin(\\pi t /2) \\\\\n", + " -i \\sin(\\pi t /2) & \\cos(\\pi t /2)\n", + "\\end{bmatrix}\n", "$$\n", "\n", "Note in particular that this gate has a global phase factor of\n", "$e^{i \\pi t / 2}$ vs the traditionally defined rotation matrices\n", - "about the Pauli Y axis. See `Ry` for rotations without the global\n", + "about the Pauli X axis. See `Rx` for rotations without the global\n", "phase. The global phase factor can be adjusted by using the `global_shift`\n", "parameter when initializing.\n", "\n", "#### Parameters\n", - " - `exponent`: The t in gate**t. Determines how much the eigenvalues of the gate are phased by. For example, eigenvectors phased by -1 when `gate**1` is applied will gain a relative phase of e^{i pi exponent} when `gate**exponent` is applied (relative to eigenvectors unaffected by `gate**1`). \n", + " - `exponent`: The t in gate**t. Determines how much the eigenvalues of the gate are phased by. For example, eigenvectors phased by -1 when `gate**1` is applied will gain a relative phase of e^{i pi exponent} when `gate**exponent` is applied (relative to eigenvectors unaffected by `gate**1`).\n", " - `global_shift`: Offsets the eigenvalues of the gate at exponent=1. In effect, this controls a global phase factor on the gate's unitary matrix. The factor for global_shift=s is: exp(i * pi * s * t)\n", " - `eps`: precision for implementation of rotation. \n", "\n", @@ -189,20 +573,20 @@ { "cell_type": "code", "execution_count": null, - "id": "77954a77", + "id": "87de6a53", "metadata": { - "cq.autogen": "YPowGate.bloq_doc.py" + "cq.autogen": "XPowGate.bloq_doc.py" }, "outputs": [], "source": [ - "from qualtran.bloqs.basic_gates import YPowGate" + "from qualtran.bloqs.basic_gates import XPowGate" ] }, { "cell_type": "markdown", - "id": "980943e2", + "id": "138bee7e", "metadata": { - "cq.autogen": "YPowGate.example_instances.md" + "cq.autogen": "XPowGate.example_instances.md" }, "source": [ "### Example Instances" @@ -211,20 +595,20 @@ { "cell_type": "code", "execution_count": null, - "id": "b00d653f", + "id": "530a736e", "metadata": { - "cq.autogen": "YPowGate.y_pow" + "cq.autogen": "XPowGate.x_pow" }, "outputs": [], "source": [ - "y_pow = YPowGate(exponent=0.123, eps=1e-8)" + "x_pow = XPowGate(exponent=0.123, eps=1e-8)" ] }, { "cell_type": "markdown", - "id": "6b4f9a33", + "id": "c641f023", "metadata": { - "cq.autogen": "YPowGate.graphical_signature.md" + "cq.autogen": "XPowGate.graphical_signature.md" }, "source": [ "#### Graphical Signature" @@ -233,22 +617,22 @@ { "cell_type": "code", "execution_count": null, - "id": "ca8982ff", + "id": "0558ecd1", "metadata": { - "cq.autogen": "YPowGate.graphical_signature.py" + "cq.autogen": "XPowGate.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([y_pow],\n", - " ['`y_pow`'])" + "show_bloqs([x_pow],\n", + " ['`x_pow`'])" ] }, { "cell_type": "markdown", - "id": "2842d574", + "id": "1092a383", "metadata": { - "cq.autogen": "YPowGate.call_graph.md" + "cq.autogen": "XPowGate.call_graph.md" }, "source": [ "### Call Graph" @@ -257,50 +641,49 @@ { "cell_type": "code", "execution_count": null, - "id": "6069507c", + "id": "f6ae8a85", "metadata": { - "cq.autogen": "YPowGate.call_graph.py" + "cq.autogen": "XPowGate.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "y_pow_g, y_pow_sigma = y_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(y_pow_g)\n", - "show_counts_sigma(y_pow_sigma)" + "x_pow_g, x_pow_sigma = x_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(x_pow_g)\n", + "show_counts_sigma(x_pow_sigma)" ] }, { "cell_type": "markdown", - "id": "2becabd1", + "id": "9f018721", "metadata": { - "cq.autogen": "ZPowGate.bloq_doc.md" + "cq.autogen": "YPowGate.bloq_doc.md" }, "source": [ - "## `ZPowGate`\n", - "A gate that rotates around the Z axis of the Bloch sphere.\n", + "## `YPowGate`\n", + "A gate that rotates around the Y axis of the Bloch sphere.\n", "\n", - "The unitary matrix of `ZPowGate(exponent=t, global_shift=s)` is:\n", + "The unitary matrix of `YPowGate(exponent=t)` is:\n", "$$\n", - " e^{i \\pi s t}\n", " \\begin{bmatrix}\n", - " 1 & 0 \\\\\n", - " 0 & e^{i \\pi t}\n", + " e^{i \\pi t /2} \\cos(\\pi t /2) & - e^{i \\pi t /2} \\sin(\\pi t /2) \\\\\n", + " e^{i \\pi t /2} \\sin(\\pi t /2) & e^{i \\pi t /2} \\cos(\\pi t /2)\n", " \\end{bmatrix}\n", "$$\n", "\n", "Note in particular that this gate has a global phase factor of\n", - "$e^{i\\pi t/2}$ vs the traditionally defined rotation matrices\n", - "about the Pauli Z axis. See `Rz` for rotations without the global\n", + "$e^{i \\pi t / 2}$ vs the traditionally defined rotation matrices\n", + "about the Pauli Y axis. See `Ry` for rotations without the global\n", "phase. The global phase factor can be adjusted by using the `global_shift`\n", "parameter when initializing.\n", "\n", "#### Parameters\n", - " - `exponent`: The t in gate**t. Determines how much the eigenvalues of the gate are phased by. For example, eigenvectors phased by -1 when `gate**1` is applied will gain a relative phase of e^{i pi exponent} when `gate**exponent` is applied (relative to eigenvectors unaffected by `gate**1`).\n", + " - `exponent`: The t in gate**t. Determines how much the eigenvalues of the gate are phased by. For example, eigenvectors phased by -1 when `gate**1` is applied will gain a relative phase of e^{i pi exponent} when `gate**exponent` is applied (relative to eigenvectors unaffected by `gate**1`). \n", " - `global_shift`: Offsets the eigenvalues of the gate at exponent=1. In effect, this controls a global phase factor on the gate's unitary matrix. The factor for global_shift=s is: exp(i * pi * s * t)\n", " - `eps`: precision for implementation of rotation. \n", "\n", "#### Registers\n", - " - `qubits`: One-bit register. \n", + " - `q`: One-bit register. \n", "\n", "#### References\n", " - [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). Offers a small improvement\n", @@ -310,20 +693,20 @@ { "cell_type": "code", "execution_count": null, - "id": "7e49732e", + "id": "77954a77", "metadata": { - "cq.autogen": "ZPowGate.bloq_doc.py" + "cq.autogen": "YPowGate.bloq_doc.py" }, "outputs": [], "source": [ - "from qualtran.bloqs.basic_gates import ZPowGate" + "from qualtran.bloqs.basic_gates import YPowGate" ] }, { "cell_type": "markdown", - "id": "3b1c7641", + "id": "980943e2", "metadata": { - "cq.autogen": "ZPowGate.example_instances.md" + "cq.autogen": "YPowGate.example_instances.md" }, "source": [ "### Example Instances" @@ -332,20 +715,20 @@ { "cell_type": "code", "execution_count": null, - "id": "9524ef48", + "id": "b00d653f", "metadata": { - "cq.autogen": "ZPowGate.z_pow" + "cq.autogen": "YPowGate.y_pow" }, "outputs": [], "source": [ - "z_pow = ZPowGate(exponent=0.123, eps=1e-8)" + "y_pow = YPowGate(exponent=0.123, eps=1e-8)" ] }, { "cell_type": "markdown", - "id": "410c9c7a", + "id": "6b4f9a33", "metadata": { - "cq.autogen": "ZPowGate.graphical_signature.md" + "cq.autogen": "YPowGate.graphical_signature.md" }, "source": [ "#### Graphical Signature" @@ -354,22 +737,22 @@ { "cell_type": "code", "execution_count": null, - "id": "dbb6a2cb", + "id": "ca8982ff", "metadata": { - "cq.autogen": "ZPowGate.graphical_signature.py" + "cq.autogen": "YPowGate.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([z_pow],\n", - " ['`z_pow`'])" + "show_bloqs([y_pow],\n", + " ['`y_pow`'])" ] }, { "cell_type": "markdown", - "id": "0be1c5f9", + "id": "2842d574", "metadata": { - "cq.autogen": "ZPowGate.call_graph.md" + "cq.autogen": "YPowGate.call_graph.md" }, "source": [ "### Call Graph" @@ -378,16 +761,16 @@ { "cell_type": "code", "execution_count": null, - "id": "935667f4", + "id": "6069507c", "metadata": { - "cq.autogen": "ZPowGate.call_graph.py" + "cq.autogen": "YPowGate.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "z_pow_g, z_pow_sigma = z_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(z_pow_g)\n", - "show_counts_sigma(z_pow_sigma)" + "y_pow_g, y_pow_sigma = y_pow.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(y_pow_g)\n", + "show_counts_sigma(y_pow_sigma)" ] } ], diff --git a/qualtran/bloqs/basic_gates/rotation.py b/qualtran/bloqs/basic_gates/rotation.py index 9adb439e1..302addfc0 100644 --- a/qualtran/bloqs/basic_gates/rotation.py +++ b/qualtran/bloqs/basic_gates/rotation.py @@ -11,8 +11,40 @@ # 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. +r"""Single-qubit rotation gates. + +A single qubit's state can be mapped to the Bloch sphere. Rotations around the three +axes are generated by the pauli matrices: +$$ +R_a(\theta) = \exp(-i \theta / 2 \sigma_a) +$$ +where $\sigma_a$ is one of the Pauli $X, Y, Z$ operators. + +Since global phase is often irrelevant, practitioners can use an alternative phase convention +and define single qubit gates as the real-valued power of a Pauli operator: +$$ +(\sigma_a)^t +$$ +which can be multiplied by $e^{-i \pi t / 2}$ to recover the $R_a(t \pi)$ matrix. + +In Qualtran, we provide `ZPowGate` and `Rz` for the two respective phase conventions, as well +as analogues for the X and Y axes. + +Global phase becomes a relevant, relative phase when forming controlled gates. Indeed, the +`ZPowGate` is a controlled `GlobalPhase` operation. Whereas `ZPowGate` and `Rz` are the +same up to global phase, their controlled versions `CZPowGate` and `CRz` are different operations +with different costs. + +#### General References + - [Quantum Computation and Quantum Information](https://doi.org/10.1017/CBO9780511976667). + Nielsen and Chuang. 2010. Section 4.2 + - [Elementary gates for quantum computation](https://arxiv.org/abs/quant-ph/9503016). + Barenco et. al. 1995. +""" + + from functools import cached_property -from typing import Optional, Tuple, Union +from typing import Dict, Iterable, Optional, Sequence, Tuple, Union import attrs import cirq @@ -20,7 +52,21 @@ import sympy from attrs import frozen -from qualtran import bloq_example, BloqDocSpec, CompositeBloq, DecomposeTypeError, Register +from qualtran import ( + AddControlledT, + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + CompositeBloq, + CtrlSpec, + DecomposeTypeError, + QBit, + Register, + Signature, + Soquet, + SoquetT, +) from qualtran.cirq_interop import CirqGateAsBloqBase from qualtran.drawing import Text, TextBox, WireSymbol from qualtran.symbolics import SymbolicFloat @@ -28,62 +74,81 @@ @frozen class ZPowGate(CirqGateAsBloqBase): - r"""A gate that rotates around the Z axis of the Bloch sphere. + r"""Apply a power of the Pauli Z operator to a single qubit. - The unitary matrix of `ZPowGate(exponent=t, global_shift=s)` is: + Given `exponent` $t$, the unitary matrix of this gate is: $$ - e^{i \pi s t} - \begin{bmatrix} - 1 & 0 \\ - 0 & e^{i \pi t} - \end{bmatrix} + Z^t = + \begin{bmatrix} + 1 & 0 \\ + 0 & e^{i \pi t} + \end{bmatrix} $$ - Note in particular that this gate has a global phase factor of - $e^{i\pi t/2}$ vs the traditionally defined rotation matrices - about the Pauli Z axis. See `Rz` for rotations without the global - phase. The global phase factor can be adjusted by using the `global_shift` - parameter when initializing. + This is an atomic bloq in Qualtran. For many architectures, you will likely need to + synthesize an arbitrary-angle rotation from a discrete gateset like Clifford+T. Please + see the references for more information. + + #### Relationships + This gate differs by a global phase from the $R_Z$ gate. `ZPowGate(t)` equals + `Rz(angle=t*np.pi)` plus `GlobalPhase(t/2)`. + + This gate is the controlled version of a global phase gate. `ZPowGate(t)` equals + `GlobalPhase(t).controlled()`. + + `exponent=1` corresponds to `ZGate`, `exponent=0.5` to `SGate`, and `exponent=0.25` to + `TGate`. Args: - exponent: The t in gate**t. Determines how much the eigenvalues of - the gate are phased by. For example, eigenvectors phased by -1 - when `gate**1` is applied will gain a relative phase of - e^{i pi exponent} when `gate**exponent` is applied (relative to - eigenvectors unaffected by `gate**1`). - global_shift: Offsets the eigenvalues of the gate at exponent=1. - In effect, this controls a global phase factor on the gate's - unitary matrix. The factor for global_shift=s is: - exp(i * pi * s * t) - eps: precision for implementation of rotation. + exponent: The exponent t in Z^t. + eps: The precision of the rotation. This parameter is for bookkeeping and does + not affect e.g. the tensor representation of this gate. When synthesizing + a rotation from a discrete gate set, you must fix a precision `eps`. Registers: - qubits: One-bit register. + q: The qubit. References: - [Efficient synthesis of universal Repeat-Until-Success - circuits](https://arxiv.org/abs/1404.5320). Offers a small improvement + [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/abs/1403.2975). + Ross and Selinger. 2014. - [Optimal ancilla-free Clifford+T approximation - of z-rotations](https://arxiv.org/pdf/1403.2975.pdf). + [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). + Bocharov et. al. 2014. + Offers a small improvement in Cliffod+T synthesis. """ exponent: SymbolicFloat = 1.0 - global_shift: SymbolicFloat = 0.0 eps: SymbolicFloat = 1e-11 + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('q', QBit())]) + def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @cached_property def cirq_gate(self) -> cirq.Gate: - if isinstance(self.global_shift, sympy.Expr): - raise TypeError(f"cirq.ZPowGate does not support symbolic {self.global_shift=}") - return cirq.ZPowGate(exponent=self.exponent, global_shift=self.global_shift) + return cirq.ZPowGate(exponent=self.exponent, global_shift=0) + + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + if ctrl_spec != CtrlSpec(): + return super().get_ctrl_system(ctrl_spec) + + ctrl_bloq = CZPowGate(exponent=self.exponent, eps=self.eps) + + def add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + (ctrl_soq,) = ctrl_soqs + ctrl_soq, q = bb.add(ctrl_bloq, q=np.array([ctrl_soq, in_soqs['q']])) + return (ctrl_soq,), (q,) + + return ctrl_bloq, add_ctrled def __pow__(self, power): g = self.cirq_gate**power - return ZPowGate(g.exponent, g.global_shift, self.eps) + return ZPowGate(exponent=g.exponent, eps=self.eps) def adjoint(self) -> 'ZPowGate': return attrs.evolve(self, exponent=-self.exponent) @@ -103,25 +168,60 @@ def _z_pow() -> ZPowGate: return z_pow -_Z_POW_DOC = BloqDocSpec(bloq_cls=ZPowGate, examples=[_z_pow]) +_Z_POW_DOC = BloqDocSpec(bloq_cls=ZPowGate, examples=[_z_pow], call_graph_example=None) @frozen -class CZPowGate(CirqGateAsBloqBase): - exponent: float = 1.0 - global_shift: float = 0.0 - eps: SymbolicFloat = 1e-11 +class CZPowGate(Bloq): + r"""The controlled `ZPowGate` - def decompose_bloq(self) -> 'CompositeBloq': - raise DecomposeTypeError(f"{self} is atomic") + The unitary matrix of `CZPowGate(exponent=t)` is: + $$ + C[Z^t] = + \begin{bmatrix} + 1 & 0 & 0 & 0 \\ + 0 & 1 & 0 & 0 \\ + 0 & 0 & 1 & 0 \\ + 0 & 0 & 0 & e^{i \pi t} \\ + \end{bmatrix} + $$ + + #### Relationships + This gate has the same unitary as `Controlled(ZPowGate)`. CZPowGate(exponent=1) corresponds + to a `CZ` gate. + + Args: + exponent: The exponent t in Z^t. + eps: The precision of the controlled rotation. This parameter is for bookkeeping and does + not affect e.g. the tensor representation of this gate. When synthesizing + a rotation from a discrete gate set, you must fix a precision `eps`. + + Registers: + q: A shape=(2,) register of two qubits ordered. This is a symmetric gate. + + References: + [Simulating chemistry efficiently on fault-tolerant quantum computers](https://arxiv.org/abs/1204.0567). + Jones et. al. 2012. Figure 8. + """ + + exponent: SymbolicFloat = 1.0 + eps: SymbolicFloat = 1e-11 @cached_property - def cirq_gate(self) -> cirq.Gate: - return cirq.CZPowGate(exponent=self.exponent, global_shift=self.global_shift) + def signature(self) -> 'Signature': + return Signature([Register('q', QBit(), shape=(2,))]) + + def build_composite_bloq(self, bb: 'BloqBuilder', q: 'SoquetT') -> Dict[str, 'SoquetT']: + from qualtran.bloqs.mcmt import And + + q1, q2 = q # type: ignore + (q1, q2), anc = bb.add(And(), ctrl=[q1, q2]) + anc = bb.add(ZPowGate(self.exponent, eps=self.eps), q=anc) + (q1, q2) = bb.add(And().adjoint(), ctrl=[q1, q2], target=anc) + return {'q': np.array([q1, q2])} def __pow__(self, power): - g = self.cirq_gate**power - return CZPowGate(g.exponent, g.global_shift, self.eps) + return attrs.evolve(self, exponent=self.exponent * power) def adjoint(self) -> 'CZPowGate': return attrs.evolve(self, exponent=-self.exponent) @@ -130,6 +230,15 @@ def __str__(self): return f'CZ**{self.exponent}' +@bloq_example +def _cz_pow() -> CZPowGate: + cz_pow = CZPowGate(exponent=0.123) + return cz_pow + + +_CZ_POW_DOC = BloqDocSpec(bloq_cls=CZPowGate, examples=[_cz_pow]) + + @frozen class XPowGate(CirqGateAsBloqBase): r"""A gate that rotates around the X axis of the Bloch sphere. @@ -280,25 +389,49 @@ def _y_pow() -> YPowGate: @frozen class Rz(CirqGateAsBloqBase): - """Single-qubit Rz gate. + r"""Apply a single-qubit Z rotation. + + Given `angle` $\theta$, the unitary matrix of this gate is: + $$ + R_Z(\theta) = \exp(-i \frac{\theta}{2} Z) = + \begin{bmatrix} + e^{-i \theta/2} & 0 \\ + 0 & e^{i \theta/2} + \end{bmatrix} + $$ + + This is an atomic bloq in Qualtran. For many architectures, you will likely need to + synthesize an arbitrary-angle rotation from a discrete gateset like Clifford+T. Please + see the references for more information. + + #### Relationships + This gate differs by a global phase from the `Z^t` gate. `Rz(a)` equals + `ZPowGate(exponent=a/np.pi)` plus `GlobalPhase(-a/(2*np.pi))`. Args: - angle: Rotation angle in radians. - eps: precision for implementation of rotation. + angle: The rotation angle in radians. + eps: The precision of the rotation. This parameter is for bookkeeping and does + not affect e.g. the tensor representation of this gate. When synthesizing + a rotation from a discrete gate set, you must fix a precision `eps`. Registers: q: One-bit register. References: - [Efficient synthesis of universal Repeat-Until-Success - circuits](https://arxiv.org/abs/1404.5320). Offers a small improvement + [Optimal ancilla-free Clifford+T approximation of z-rotations](https://arxiv.org/abs/1403.2975). + Ross and Selinger. 2014. - [Optimal ancilla-free Clifford+T approximation - of z-rotations](https://arxiv.org/pdf/1403.2975.pdf). + [Efficient synthesis of universal Repeat-Until-Success circuits](https://arxiv.org/abs/1404.5320). + Bocharov et. al. 2014. + Offers a small improvement in Cliffod+T synthesis. """ - angle: Union[sympy.Expr, float] - eps: Union[sympy.Expr, float] = 1e-11 + angle: SymbolicFloat + eps: SymbolicFloat = 1e-11 + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('q', QBit())]) def decompose_bloq(self) -> 'CompositeBloq': raise DecomposeTypeError(f"{self} is atomic") @@ -307,6 +440,20 @@ def decompose_bloq(self) -> 'CompositeBloq': def cirq_gate(self) -> cirq.Gate: return cirq.rz(self.angle) + def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> Tuple['Bloq', 'AddControlledT']: + if ctrl_spec != CtrlSpec(): + return super().get_ctrl_system(ctrl_spec) + + from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv_from_bloqs + + return get_ctrl_system_1bit_cv_from_bloqs( + bloq=self, + ctrl_spec=ctrl_spec, + current_ctrl_bit=None, + bloq_with_ctrl=CRz(self.angle, eps=self.eps), + ctrl_reg_name='ctrl', + ) + def adjoint(self) -> 'Rz': return attrs.evolve(self, angle=-self.angle) @@ -319,6 +466,83 @@ def __str__(self): return f'Rz({self.angle})' +@bloq_example +def _rz() -> Rz: + a = sympy.Symbol('a') + rz = Rz(a) + return rz + + +_RZ_DOC = BloqDocSpec(bloq_cls=Rz, examples=[_rz], call_graph_example=None) + + +@frozen +class CRz(Bloq): + r"""A controlled Rz rotation. + + Given `angle` $\theta$, the unitary matrix of this gate is: + $$ + C[R_Z(\theta)] = + \begin{bmatrix} + 1 & & & \\ + & 1 & & \\ + & & e^{-i \theta/2} & \\ + & & & e^{i \theta/2} + \end{bmatrix} + $$ + + Args: + angle: The rotation angle in radians. + eps: The precision of the rotation. This parameter is for bookkeeping and does + not affect e.g. the tensor representation of this gate. When synthesizing + a rotation from a discrete gate set, you must fix a precision `eps`. + + Registers: + ctrl: Whether the rotation is active. + q: The qubit on which we optionally perform the rotation. + + References: + [Elementary gates for quantum computation](https://arxiv.org/abs/quant-ph/9503016). + Barenco et al. 1995. Special case of Lemma 5.4. + + [Is Controlled(𝑅𝑧(𝜃)) more expensive than Controlled(𝑍𝑡) on the surface code?](https://quantumcomputing.stackexchange.com/a/40012). + Adam Zalcman. 2024. + """ + + angle: SymbolicFloat + eps: SymbolicFloat = 1e-11 + + @cached_property + def signature(self) -> 'Signature': + return Signature.build(ctrl=1, q=1) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'Soquet', q: 'Soquet' + ) -> Dict[str, 'SoquetT']: + from qualtran.bloqs.basic_gates import CNOT + + t = self.angle / np.pi + q = bb.add(ZPowGate(t / 2, eps=self.eps / 2), q=q) + ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q) + q = bb.add(ZPowGate(-t / 2, eps=self.eps / 2), q=q) + ctrl, q = bb.add(CNOT(), ctrl=ctrl, target=q) + + return {'ctrl': ctrl, 'q': q} + + def __str__(self): + return f'CRz({self.angle})' + + +@bloq_example +def _crz() -> CRz: + theta = sympy.Symbol(r'\theta') + crz = CRz(angle=theta) + return crz + + +_CRZ_DOC = BloqDocSpec(bloq_cls=CRz, examples=[_crz]) + + @frozen class Rx(CirqGateAsBloqBase): angle: Union[sympy.Expr, float] @@ -377,9 +601,3 @@ def _rx() -> Rx: def _ry() -> Ry: ry = Ry(angle=np.pi / 4.0) return ry - - -@bloq_example -def _rz() -> Rz: - rz = Rz(angle=np.pi / 4.0) - return rz diff --git a/qualtran/bloqs/basic_gates/rotation_test.py b/qualtran/bloqs/basic_gates/rotation_test.py index a9e2c87bf..77d291d46 100644 --- a/qualtran/bloqs/basic_gates/rotation_test.py +++ b/qualtran/bloqs/basic_gates/rotation_test.py @@ -17,13 +17,137 @@ import pytest from cirq.ops import SimpleQubitManager +from qualtran import BloqBuilder, Controlled, CtrlSpec from qualtran._infra.gate_with_registers import get_named_qubits -from qualtran.bloqs.basic_gates import CZPowGate, Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate -from qualtran.bloqs.basic_gates.rotation import _rx, _ry, _rz +from qualtran.bloqs.basic_gates import ( + CZ, + CZPowGate, + GlobalPhase, + Rx, + Ry, + Rz, + SGate, + TGate, + XPowGate, + YPowGate, + ZGate, + ZPowGate, +) +from qualtran.bloqs.basic_gates.rotation import _crz, _cz_pow, _rx, _ry, _rz, _z_pow, CRz from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost from qualtran.resource_counting.classify_bloqs import bloq_is_rotation, bloq_is_t_like +def test_zpow_gate(bloq_autotester): + bloq_autotester(_z_pow) + + +def test_zpow_is_controlled_gphase(): + rs = np.random.RandomState(52) + t = rs.uniform(0, 2) + cgphase = GlobalPhase(exponent=t).controlled().tensor_contract() + zpow = ZPowGate(exponent=t).tensor_contract() + np.testing.assert_allclose(zpow, cgphase) + + a = GlobalPhase(exponent=t).tensor_contract() + manual_cgphase = np.diag([1, a]) + np.testing.assert_allclose(zpow, manual_cgphase) + + +def test_zpow_from_rz(): + rs = np.random.RandomState(52) + t = rs.uniform(0, 2) + + bb = BloqBuilder() + q = bb.add_register('q', 1) + q = bb.add(Rz(angle=t * np.pi), q=q) + bb.add(GlobalPhase(exponent=t / 2)) + rz_with_phase = bb.finalize(q=q) + + np.testing.assert_allclose(ZPowGate(t).tensor_contract(), rz_with_phase.tensor_contract()) + + +def test_rz_from_zpow(): + rs = np.random.RandomState(52) + theta = rs.uniform(0, 2 * np.pi) + + bb = BloqBuilder() + q = bb.add_register('q', 1) + q = bb.add(ZPowGate(exponent=theta / np.pi), q=q) + bb.add(GlobalPhase(exponent=-theta / (2 * np.pi))) + zpow_with_phase = bb.finalize(q=q) + + np.testing.assert_allclose(Rz(angle=theta).tensor_contract(), zpow_with_phase.tensor_contract()) + + +def test_zpow_special_exponents(): + zpow_1 = ZPowGate(exponent=1) + np.testing.assert_allclose(ZGate().tensor_contract(), zpow_1.tensor_contract()) + + zpow_half = ZPowGate(exponent=0.5) + np.testing.assert_allclose(SGate().tensor_contract(), zpow_half.tensor_contract()) + + zpow_quarter = ZPowGate(exponent=0.25) + np.testing.assert_allclose(TGate().tensor_contract(), zpow_quarter.tensor_contract()) + + +def test_czpow(bloq_autotester): + bloq_autotester(_cz_pow) + + +def test_czpow_tensor(): + rs = np.random.RandomState(52) + t = rs.uniform(0, 2) + u1 = CZPowGate(exponent=t).tensor_contract() + u2 = cirq.unitary(cirq.ZPowGate(exponent=t).controlled()) + u3 = Controlled(ZPowGate(exponent=t), CtrlSpec()).tensor_contract() + np.testing.assert_allclose(u1, u2, atol=1e-8) + np.testing.assert_allclose(u1, u3, atol=1e-8) + + +def test_czpow_special_exponents(): + czpow_1 = CZPowGate(exponent=1) + np.testing.assert_allclose(CZ().tensor_contract(), czpow_1.tensor_contract()) + + +def test_czpow_from_controlled_z_pow(): + rs = np.random.RandomState(52) + t = rs.uniform(0, 2) + zpow = ZPowGate(exponent=t) + assert zpow.controlled() == CZPowGate(exponent=t) + + cbloq = Controlled(zpow.as_composite_bloq(), CtrlSpec()).decompose_bloq() + (czpow_inst,) = list(cbloq.bloq_instances) + assert czpow_inst.bloq == CZPowGate(exponent=t) + np.testing.assert_allclose(CZPowGate(exponent=t).tensor_contract(), cbloq.tensor_contract()) + + +def test_crz(bloq_autotester): + bloq_autotester(_crz) + + +def test_crz_tensor(): + rs = np.random.RandomState(52) + angle = rs.uniform(0, 2 * np.pi) + u1 = CRz(angle=angle).tensor_contract() + u2 = cirq.unitary(cirq.Rz(rads=angle).controlled()) + u3 = Controlled(Rz(angle=angle), CtrlSpec()).tensor_contract() + np.testing.assert_allclose(u1, u2, atol=1e-8) + np.testing.assert_allclose(u1, u3, atol=1e-8) + + +def test_crz_from_controlled_rz(): + rs = np.random.RandomState() + angle = rs.uniform(0, 2 * np.pi) + rz = Rz(angle=angle) + assert rz.controlled() == CRz(angle=angle) + + cbloq = Controlled(rz.as_composite_bloq(), CtrlSpec()).decompose_bloq() + (crz_inst,) = list(cbloq.bloq_instances) + assert crz_inst.bloq == CRz(angle=angle) + np.testing.assert_allclose(CRz(angle=angle).tensor_contract(), cbloq.tensor_contract()) + + def test_t_like_rotation_gates(): angle = np.pi / 4.0 # In prior versions of the library, only Rz(pi/4) would simplify to a T gate in gate counts. @@ -93,14 +217,12 @@ def test_as_cirq_op(): assert circuit == cirq.Circuit( cirq.YPowGate(exponent=1 / 5, global_shift=-0.5).on(cirq.NamedQubit("q")) ) - bloq = ZPowGate(exponent=1 / 5, global_shift=-0.5) + bloq = ZPowGate(exponent=1 / 5) quregs = get_named_qubits(bloq.signature) op, _ = bloq.as_cirq_op(SimpleQubitManager(), **quregs) assert op is not None circuit = cirq.Circuit(op) - assert circuit == cirq.Circuit( - cirq.ZPowGate(exponent=1 / 5, global_shift=-0.5).on(cirq.NamedQubit("q")) - ) + assert circuit == cirq.Circuit(cirq.ZPowGate(exponent=1 / 5).on(cirq.NamedQubit("q"))) def test_str(): @@ -109,7 +231,7 @@ def test_str(): assert str(YPowGate()) == "Y**1.0" assert str(_ry()) == "Ry(0.7853981633974483)" assert str(_rx()) == "Rx(0.7853981633974483)" - assert str(_rz()) == "Rz(0.7853981633974483)" + assert str(_rz()) == "Rz(a)" assert str(CZPowGate(1.0)) == 'CZ**1.0' assert str(CZPowGate(0.9)) == 'CZ**0.9' diff --git a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py index e05052d4a..41b84e5cb 100644 --- a/qualtran/bloqs/phase_estimation/lp_resource_state_test.py +++ b/qualtran/bloqs/phase_estimation/lp_resource_state_test.py @@ -94,7 +94,7 @@ def test_t_complexity(n): qlt_testing.assert_equivalent_bloq_counts( bloq, [ignore_split_join, ignore_alloc_free, generalize_rotation_angle] ) - lprs_interim_count = 3 * TComplexity(rotations=n + 1, clifford=2 + n) + lprs_interim_count = 3 * TComplexity(rotations=2 * n + 1, clifford=2 + 3 * n) reflection_using_prepare = TComplexity(t=4 * n + 4, clifford=17 * n + 22) misc_count = TComplexity(rotations=3, clifford=5) @@ -103,6 +103,9 @@ def test_t_complexity(n): @pytest.mark.parametrize('bitsize', [*range(1, 14, 2)]) def test_interim_lp2s_interim_prep_t_complexity(bitsize: int): + crz_rots = 2 * bitsize + crz_cliff = 2 * bitsize + h_cliff = bitsize assert t_complexity(LPRSInterimPrep(bitsize)) == TComplexity( - rotations=bitsize + 1, clifford=2 + bitsize + rotations=crz_rots + 1, clifford=2 + crz_cliff + h_cliff ) diff --git a/qualtran/bloqs/qft/qft_text_book_test.py b/qualtran/bloqs/qft/qft_text_book_test.py index 608508d89..0c7e70899 100644 --- a/qualtran/bloqs/qft/qft_text_book_test.py +++ b/qualtran/bloqs/qft/qft_text_book_test.py @@ -16,8 +16,10 @@ import pytest import sympy +import qualtran.testing as qlt_testing from qualtran.bloqs.qft.qft_text_book import _qft_text_book, _symbolic_qft, QFTTextBook -from qualtran.testing import assert_valid_bloq_decomposition +from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.resource_counting.generalizers import ignore_split_join @pytest.mark.parametrize('without_reverse', [True, False]) @@ -26,10 +28,10 @@ def test_qft_text_book_quick(without_reverse: bool): qft_bloq = QFTTextBook(n, not without_reverse) qft_cirq = cirq.QuantumFourierTransformGate(n, without_reverse=without_reverse) - assert np.allclose(cirq.unitary(qft_bloq), cirq.unitary(qft_cirq)) - assert np.allclose(cirq.unitary(qft_bloq**-1), cirq.unitary(qft_cirq**-1)) + assert np.allclose(qft_bloq.tensor_contract(), cirq.unitary(qft_cirq)) + assert np.allclose(qft_bloq.adjoint().tensor_contract(), cirq.unitary(qft_cirq**-1)) - assert_valid_bloq_decomposition(qft_bloq) + qlt_testing.assert_valid_bloq_decomposition(qft_bloq) @pytest.mark.slow @@ -42,23 +44,31 @@ def test_qft_text_book(n: int, without_reverse: bool): assert np.allclose(cirq.unitary(qft_bloq), cirq.unitary(qft_cirq)) assert np.allclose(cirq.unitary(qft_bloq**-1), cirq.unitary(qft_cirq**-1)) - assert_valid_bloq_decomposition(qft_bloq) + qlt_testing.assert_valid_bloq_decomposition(qft_bloq) @pytest.mark.parametrize('n', [10, 123]) def test_qft_text_book_t_complexity(n: int): qft_bloq = QFTTextBook(n) - qft_t_complexity = qft_bloq.t_complexity() - assert qft_t_complexity.rotations == (n * (n - 1)) // 2 - assert qft_t_complexity.t == 0 + qlt_testing.assert_equivalent_bloq_counts(qft_bloq, generalizer=[ignore_split_join]) + gate_counts = get_cost_value(qft_bloq, QECGatesCost()) + # special angle ZPow gets turned into clifford or T + rots = ((n - 3) * (n - 2)) // 2 + if n >= 41: + # TODO(https://github.com/quantumlib/Qualtran/issues/1474) + pytest.xfail("Small angle rotations") + assert gate_counts.t == n - 2 + assert gate_counts.toffoli == 0 + assert gate_counts.rotation == rots + assert gate_counts.and_bloq == (n * (n - 1)) // 2 def test_qft_text_book_t_complexity_symbolic(): n = sympy.symbols('n') qft_bloq = QFTTextBook(bitsize=n) - qft_t_complexity = qft_bloq.t_complexity() - assert qft_t_complexity.rotations == (n - 1) * (n // 2) - assert qft_t_complexity.t == 0 + gate_counts = get_cost_value(qft_bloq, QECGatesCost()) + assert gate_counts.rotation == (n - 1) * (n // 2) + assert gate_counts.and_bloq == (n - 1) * (n // 2) def test_qft_text_book_auto(bloq_autotester): diff --git a/qualtran/bloqs/reflections/reflection_using_prepare.py b/qualtran/bloqs/reflections/reflection_using_prepare.py index a3cd4c2b3..4a8467fde 100644 --- a/qualtran/bloqs/reflections/reflection_using_prepare.py +++ b/qualtran/bloqs/reflections/reflection_using_prepare.py @@ -187,7 +187,9 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': if self.global_phase != 1: phase_op: Bloq = GlobalPhase.from_coefficient(self.global_phase, eps=self.eps) if self.control_val is not None: - phase_op = phase_op.controlled(ctrl_spec=CtrlSpec(cvs=self.control_val)) + phase_op = phase_op.controlled() + if self.control_val == 0: + costs[XGate()] = 2 costs[phase_op] = 1 return costs diff --git a/qualtran/bloqs/reflections/reflection_using_prepare_test.py b/qualtran/bloqs/reflections/reflection_using_prepare_test.py index 502f6e435..2f35a874d 100644 --- a/qualtran/bloqs/reflections/reflection_using_prepare_test.py +++ b/qualtran/bloqs/reflections/reflection_using_prepare_test.py @@ -13,17 +13,16 @@ # limitations under the License. import itertools -from typing import Optional import cirq import numpy as np import pytest from numpy.typing import NDArray -from qualtran import Adjoint, Bloq +import qualtran.testing as qlt_testing +from qualtran import Adjoint from qualtran._infra.gate_with_registers import get_named_qubits from qualtran.bloqs.arithmetic import LessThanConstant, LessThanEqual -from qualtran.bloqs.basic_gates import ZPowGate from qualtran.bloqs.basic_gates.swap import CSwap from qualtran.bloqs.mcmt import MultiControlX, MultiTargetCNOT from qualtran.bloqs.mcmt.and_bloq import And @@ -41,7 +40,6 @@ ignore_cliffords, ignore_split_join, ) -from qualtran.testing import assert_valid_bloq_decomposition, execute_notebook gateset_to_keep = cirq.Gateset( And, @@ -121,7 +119,7 @@ def test_reflection_using_prepare(num_ones, eps, global_phase): prepare_gate = StatePreparationAliasSampling.from_probabilities(data, precision=eps) gate = ReflectionUsingPrepare(prepare_gate, global_phase=global_phase) - assert_valid_bloq_decomposition(gate) + qlt_testing.assert_valid_bloq_decomposition(gate) g, qubit_order, decomposed_circuit = construct_gate_helper_and_qubit_order(gate) @@ -277,30 +275,14 @@ def test_call_graph_matches_decomp(global_phase, control_val): eps = 1e-11 prepare_gate = StatePreparationAliasSampling.from_probabilities(data, precision=0.01) - def catch_zpow_bloq_s_gate_inv(bloq) -> Optional[Bloq]: - # Hack to catch the fact that cirq special cases some ZPowGates - if isinstance(bloq, ZPowGate) and np.any(np.isclose(float(bloq.exponent), [0.5, -0.5])): - # we're already ignoring cliffords - return None - return bloq - gate = ReflectionUsingPrepare( prepare_gate, global_phase=global_phase, eps=eps, control_val=control_val ) - _, cost_decomp = gate.decompose_bloq().call_graph( - generalizer=[ignore_split_join, ignore_alloc_free, ignore_cliffords] - ) - _, cost_call = gate.call_graph( - generalizer=[ - ignore_split_join, - ignore_alloc_free, - ignore_cliffords, - catch_zpow_bloq_s_gate_inv, - ] + qlt_testing.assert_equivalent_bloq_counts( + gate, generalizer=[ignore_split_join, ignore_alloc_free, ignore_cliffords] ) - assert cost_decomp == cost_call @pytest.mark.notebook def test_notebook(): - execute_notebook('reflections') + qlt_testing.execute_notebook('reflections') diff --git a/qualtran/bloqs/rotations/phase_gradient_test.py b/qualtran/bloqs/rotations/phase_gradient_test.py index c633c7c5f..af6677a79 100644 --- a/qualtran/bloqs/rotations/phase_gradient_test.py +++ b/qualtran/bloqs/rotations/phase_gradient_test.py @@ -74,7 +74,7 @@ def test_phase_gradient_gate(n: int, exponent, controlled): cirq_gate: cirq.Gate = cirq.PhaseGradientGate(num_qubits=n, exponent=exponent) if controlled: cirq_gate = cirq_gate.controlled() - assert np.allclose(cirq.unitary(bloq), cirq.unitary(cirq_gate), atol=eps) + assert np.allclose(bloq.tensor_contract(), cirq.unitary(cirq_gate), atol=eps) @pytest.mark.parametrize("controlled_by", [None, 0, 1]) diff --git a/qualtran/cirq_interop/_bloq_to_cirq.py b/qualtran/cirq_interop/_bloq_to_cirq.py index 70f6d122c..99149a3a4 100644 --- a/qualtran/cirq_interop/_bloq_to_cirq.py +++ b/qualtran/cirq_interop/_bloq_to_cirq.py @@ -178,7 +178,7 @@ def __pow__(self, power, modulo=None): bloq = self.bloq if power > 0 else self.bloq.adjoint() try: - return BloqAsCirqGate(Power(bloq, abs(power))) + return Power(bloq, abs(power)) except ValueError as e: raise ValueError(f"Bad power {power}") from e diff --git a/qualtran/cirq_interop/_cirq_to_bloq.py b/qualtran/cirq_interop/_cirq_to_bloq.py index f71810df9..a90d5d8ab 100644 --- a/qualtran/cirq_interop/_cirq_to_bloq.py +++ b/qualtran/cirq_interop/_cirq_to_bloq.py @@ -82,11 +82,12 @@ def signature(self) -> 'Signature': if isinstance(self.cirq_gate, Bloq): return self.cirq_gate.signature nqubits = cirq.num_qubits(self.cirq_gate) - return ( - Signature([Register('q', QBit(), shape=nqubits)]) - if nqubits > 1 - else Signature.build(q=nqubits) - ) + if nqubits == 1: + return Signature([Register('q', QBit())]) + elif nqubits == 0: + return Signature([]) + # else + return Signature([Register('q', QBit(), shape=nqubits)]) def decompose_from_registers( self, *, context: cirq.DecompositionContext, **quregs: CirqQuregT @@ -407,10 +408,11 @@ def cirq_gate_to_bloq(gate: cirq.Gate) -> Bloq: if isinstance(gate, (cirq.Rx, cirq.Ry, cirq.Rz)): return CIRQ_TYPE_TO_BLOQ_MAP[gate.__class__](angle=gate._rads) - if isinstance(gate, (cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZPowGate)): - return CIRQ_TYPE_TO_BLOQ_MAP[gate.__class__]( - exponent=gate.exponent, global_shift=gate.global_shift - ) + if ( + isinstance(gate, (cirq.XPowGate, cirq.YPowGate, cirq.ZPowGate, cirq.CZPowGate)) + and gate.global_shift == 0 + ): + return CIRQ_TYPE_TO_BLOQ_MAP[gate.__class__](exponent=gate.exponent) if isinstance(gate, cirq.GlobalPhaseGate): if isinstance(gate.coefficient, numbers.Complex): diff --git a/qualtran/drawing/flame_graph.py b/qualtran/drawing/flame_graph.py index 0264da559..7546f6991 100644 --- a/qualtran/drawing/flame_graph.py +++ b/qualtran/drawing/flame_graph.py @@ -63,10 +63,10 @@ def _t_counts_for_bloq(bloq: Bloq, graph: nx.DiGraph) -> Union[int, sympy.Expr]: def _keep_if_small(bloq: Bloq) -> bool: - from qualtran.bloqs.basic_gates import Toffoli, TwoBitCSwap + from qualtran.bloqs.basic_gates import CZPowGate, Toffoli, TwoBitCSwap from qualtran.bloqs.mcmt.and_bloq import And - if isinstance(bloq, (And, Toffoli, TwoBitCSwap)): + if isinstance(bloq, (And, Toffoli, TwoBitCSwap, CZPowGate)): return True return False diff --git a/qualtran/drawing/flame_graph_test.py b/qualtran/drawing/flame_graph_test.py index f58b93f8b..b5dde880f 100644 --- a/qualtran/drawing/flame_graph_test.py +++ b/qualtran/drawing/flame_graph_test.py @@ -11,6 +11,8 @@ # 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 pytest + from qualtran.bloqs.basic_gates.swap import CSwap from qualtran.bloqs.mcmt import MultiAnd from qualtran.bloqs.qft.qft_text_book import QFTTextBook @@ -53,6 +55,7 @@ def test_get_flame_graph_data_multi_and(): ] +@pytest.mark.xfail(reason="https://github.com/quantumlib/Qualtran/issues/1474") def test_get_flame_graph_data_qft_textbook(): bloq = QFTTextBook(5) data = get_flame_graph_data(bloq) diff --git a/qualtran/resource_counting/_bloq_counts_test.py b/qualtran/resource_counting/_bloq_counts_test.py index 189f6db05..b8a4ccb00 100644 --- a/qualtran/resource_counting/_bloq_counts_test.py +++ b/qualtran/resource_counting/_bloq_counts_test.py @@ -105,7 +105,7 @@ def test_qec_gates_cost_cbloq(): # And [mcmt.And(), GateCounts(and_bloq=1)], # Rotations - [basic_gates.ZPowGate(exponent=0.1, global_shift=0.0, eps=1e-11), GateCounts(rotation=1)], + [basic_gates.ZPowGate(exponent=0.1, eps=1e-11), GateCounts(rotation=1)], [ rotations.phase_gradient.PhaseGradientUnitary( bitsize=10, exponent=1, is_controlled=False, eps=1e-10 diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index bb2a57323..b6016e8f0 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -218,15 +218,7 @@ def bloq_is_rotation(b: Bloq) -> bool: will be remediated when the Qualtran standard library gains a bespoke bloq for each CRot. """ from qualtran.bloqs.basic_gates import SGate, TGate - from qualtran.bloqs.basic_gates.rotation import ( - CZPowGate, - Rx, - Ry, - Rz, - XPowGate, - YPowGate, - ZPowGate, - ) + from qualtran.bloqs.basic_gates.rotation import Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate if isinstance(b, Controlled): if b.ctrl_spec.num_qubits != 1: @@ -241,9 +233,6 @@ def bloq_is_rotation(b: Bloq) -> bool: # do clifford, T angle simplification. return isinstance(b.subbloq, (Rx, Ry, Rz, XPowGate, YPowGate, ZPowGate)) - if isinstance(b, CZPowGate): - return True - if isinstance(b, (Rz, Rx, Ry)): if is_symbolic(b.angle): return True diff --git a/qualtran/resource_counting/generalizers.py b/qualtran/resource_counting/generalizers.py index 2dfa06501..4948411d9 100644 --- a/qualtran/resource_counting/generalizers.py +++ b/qualtran/resource_counting/generalizers.py @@ -70,7 +70,7 @@ def generalize_rotation_angle(b: Bloq) -> Optional[Bloq]: return attrs.evolve(b, angle=PHI) if isinstance(b, (XPowGate, YPowGate, ZPowGate)): - return attrs.evolve(b, exponent=PHI, global_shift=0) + return attrs.evolve(b, exponent=PHI) if isinstance(b, (TGate, SGate)): # ignore `is_adjoint`. diff --git a/qualtran/resource_counting/t_counts_from_sigma_test.py b/qualtran/resource_counting/t_counts_from_sigma_test.py index 38d64b9c7..d0d6aecc3 100644 --- a/qualtran/resource_counting/t_counts_from_sigma_test.py +++ b/qualtran/resource_counting/t_counts_from_sigma_test.py @@ -14,23 +14,13 @@ import sympy -from qualtran.bloqs.basic_gates import ( - CZPowGate, - Rx, - Ry, - Rz, - TGate, - Toffoli, - XPowGate, - YPowGate, - ZPowGate, -) +from qualtran.bloqs.basic_gates import Rx, Ry, Rz, TGate, Toffoli, XPowGate, YPowGate, ZPowGate from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting.t_counts_from_sigma import t_counts_from_sigma def test_t_counts_from_sigma(): - z_eps1, z_eps2, x_eps, y_eps, cz_eps = sympy.symbols('z_eps1, z_eps2, x_eps, y_eps, cz_eps') + z_eps1, z_eps2, x_eps, y_eps = sympy.symbols('z_eps1, z_eps2, x_eps, y_eps') sigma = { ZPowGate(eps=z_eps1): 1, ZPowGate(eps=z_eps2): 2, @@ -43,8 +33,6 @@ def test_t_counts_from_sigma(): Ry(0.01, eps=y_eps): 6, YPowGate(eps=y_eps): 7, YPowGate(0.01, eps=y_eps): 7, - CZPowGate(eps=cz_eps): 20, - CZPowGate(0.01, eps=cz_eps): 20, TGate(): 100, Toffoli(): 200, } @@ -55,6 +43,5 @@ def test_t_counts_from_sigma(): + 5 * TComplexity.rotation_cost(z_eps2) + 9 * TComplexity.rotation_cost(x_eps) + 13 * TComplexity.rotation_cost(y_eps) - + 20 * TComplexity.rotation_cost(cz_eps) ) assert t_counts_from_sigma(sigma) == expected_t_count diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index d81f9030a..364676f40 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -221,6 +221,7 @@ "qualtran.bloqs.basic_gates.rotation.Rx": qualtran.bloqs.basic_gates.rotation.Rx, "qualtran.bloqs.basic_gates.rotation.Ry": qualtran.bloqs.basic_gates.rotation.Ry, "qualtran.bloqs.basic_gates.rotation.Rz": qualtran.bloqs.basic_gates.rotation.Rz, + "qualtran.bloqs.basic_gates.rotation.CRz": qualtran.bloqs.basic_gates.rotation.CRz, "qualtran.bloqs.basic_gates.rotation.XPowGate": qualtran.bloqs.basic_gates.rotation.XPowGate, "qualtran.bloqs.basic_gates.rotation.YPowGate": qualtran.bloqs.basic_gates.rotation.YPowGate, "qualtran.bloqs.basic_gates.rotation.ZPowGate": qualtran.bloqs.basic_gates.rotation.ZPowGate, diff --git a/qualtran/surface_code/algorithm_summary_test.py b/qualtran/surface_code/algorithm_summary_test.py index 61b475665..673438bf4 100644 --- a/qualtran/surface_code/algorithm_summary_test.py +++ b/qualtran/surface_code/algorithm_summary_test.py @@ -36,7 +36,7 @@ ], [mcmt.And(), AlgorithmSummary(n_algo_qubits=3, n_logical_gates=GateCounts(and_bloq=1))], [ - basic_gates.ZPowGate(exponent=0.1, global_shift=0.0, eps=1e-11), + basic_gates.ZPowGate(exponent=0.1, eps=1e-11), AlgorithmSummary(n_algo_qubits=1, n_logical_gates=GateCounts(rotation=1)), ], [