diff --git a/cirq-core/cirq/contrib/svg/svg_test.py b/cirq-core/cirq/contrib/svg/svg_test.py index e00b7f25bdd..074651103ee 100644 --- a/cirq-core/cirq/contrib/svg/svg_test.py +++ b/cirq-core/cirq/contrib/svg/svg_test.py @@ -1,5 +1,5 @@ # pylint: disable=wrong-or-nonexistent-copyright-notice -import IPython.display +import IPython.display # type: ignore import numpy as np import pytest diff --git a/cirq-ft/LICENSE b/cirq-ft/LICENSE deleted file mode 100644 index 8dada3edaf5..00000000000 --- a/cirq-ft/LICENSE +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - 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 - - http://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. diff --git a/cirq-ft/README.rst b/cirq-ft/README.rst deleted file mode 100644 index 0c9edcf8be0..00000000000 --- a/cirq-ft/README.rst +++ /dev/null @@ -1,26 +0,0 @@ -.. image:: https://raw.githubusercontent.com/quantumlib/Cirq/main/docs/images/Cirq_logo_color.png - :target: https://github.com/quantumlib/cirq - :alt: cirq-ft - :width: 500px - -Cirq-FT: Cirq for Fault-Tolerant algorithms -------------------------------------------- - -Cirq-FT is a Python library for rapid prototyping and resource estimation of fault tolerant -algorithms that extends the quantum computing SDK **Cirq**. - -Installation ------------- -Cirq-FT is currently in beta mode and only available as a pre-release. -To install the pre-release version of **cirq-ft**, use - -.. code-block:: bash - - pip install cirq-ft~=1.0.dev - - - -Note, that this will install both cirq-ft and cirq-core as well. - -To get all the optional **Cirq** modules installed as well, use `pip install cirq` or -`pip install cirq~=1.0.dev` for the pre-release version. diff --git a/cirq-ft/cirq_ft/__init__.py b/cirq-ft/cirq_ft/__init__.py deleted file mode 100644 index 938720ba693..00000000000 --- a/cirq-ft/cirq_ft/__init__.py +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft._version import __version__ -from cirq_ft.algos import ( - QROM, - AdditionGate, - AddMod, - And, - ApplyGateToLthQubit, - ContiguousRegisterGate, - GenericSelect, - LessThanEqualGate, - LessThanGate, - MultiControlPauli, - MultiTargetCNOT, - MultiTargetCSwap, - MultiTargetCSwapApprox, - PrepareHubbard, - PrepareOracle, - PrepareUniformSuperposition, - ProgrammableRotationGateArray, - ProgrammableRotationGateArrayBase, - QubitizationWalkOperator, - ReflectionUsingPrepare, - SelectedMajoranaFermionGate, - SelectHubbard, - SelectOracle, - SelectSwapQROM, - StatePreparationAliasSampling, - SwapWithZeroGate, - UnaryIterationGate, - unary_iteration, -) -from cirq_ft.infra import ( - GateWithRegisters, - Register, - Signature, - SelectionRegister, - TComplexity, - map_clean_and_borrowable_qubits, - t_complexity, - testing, -) diff --git a/cirq-ft/cirq_ft/_version.py b/cirq-ft/cirq_ft/_version.py deleted file mode 100644 index de17f28f3f7..00000000000 --- a/cirq-ft/cirq_ft/_version.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -"""Define version number here, read it from setup.py automatically""" - -__version__ = "1.4.0.dev" diff --git a/cirq-ft/cirq_ft/_version_test.py b/cirq-ft/cirq_ft/_version_test.py deleted file mode 100644 index f245fae33ee..00000000000 --- a/cirq-ft/cirq_ft/_version_test.py +++ /dev/null @@ -1,19 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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_ft - - -def test_version(): - assert cirq_ft.__version__ == "1.4.0.dev" diff --git a/cirq-ft/cirq_ft/algos/__init__.py b/cirq-ft/cirq_ft/algos/__init__.py deleted file mode 100644 index b0cc284434d..00000000000 --- a/cirq-ft/cirq_ft/algos/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft.algos.and_gate import And -from cirq_ft.algos.apply_gate_to_lth_target import ApplyGateToLthQubit -from cirq_ft.algos.arithmetic_gates import ( - AdditionGate, - AddMod, - ContiguousRegisterGate, - LessThanEqualGate, - LessThanGate, - SingleQubitCompare, - BiQubitsMixer, -) -from cirq_ft.algos.generic_select import GenericSelect -from cirq_ft.algos.hubbard_model import PrepareHubbard, SelectHubbard -from cirq_ft.algos.multi_control_multi_target_pauli import MultiControlPauli, MultiTargetCNOT -from cirq_ft.algos.prepare_uniform_superposition import PrepareUniformSuperposition -from cirq_ft.algos.programmable_rotation_gate_array import ( - ProgrammableRotationGateArray, - ProgrammableRotationGateArrayBase, -) -from cirq_ft.algos.qrom import QROM -from cirq_ft.algos.qubitization_walk_operator import QubitizationWalkOperator -from cirq_ft.algos.reflection_using_prepare import ReflectionUsingPrepare -from cirq_ft.algos.select_and_prepare import PrepareOracle, SelectOracle -from cirq_ft.algos.select_swap_qrom import SelectSwapQROM -from cirq_ft.algos.selected_majorana_fermion import SelectedMajoranaFermionGate -from cirq_ft.algos.state_preparation import StatePreparationAliasSampling -from cirq_ft.algos.swap_network import MultiTargetCSwap, MultiTargetCSwapApprox, SwapWithZeroGate -from cirq_ft.algos.unary_iteration_gate import UnaryIterationGate, unary_iteration diff --git a/cirq-ft/cirq_ft/algos/and_gate.ipynb b/cirq-ft/cirq_ft/algos/and_gate.ipynb deleted file mode 100644 index 1c47eec56ab..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate.ipynb +++ /dev/null @@ -1,226 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "97385158", - "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": "93353f4e", - "metadata": {}, - "source": [ - "# And\n", - "\n", - "To do classical logic with a reversible circuit (a pre-requisite for a quantum circuit), we use a three (qu)bit operation called a Toffoli gate that takes `[a, b, c]` to `[a, b, c ^ (a & b)]`. If we take `c` to be zero, this is an And gate taking `[a, b]` to `[a, b, a & b]`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a95dab52", - "metadata": {}, - "outputs": [], - "source": [ - "import itertools\n", - "for a, b, in itertools.product([0, 1], repeat=2):\n", - " print(a, b, '->', a & b)" - ] - }, - { - "cell_type": "markdown", - "id": "37a44523", - "metadata": {}, - "source": [ - "## Quantum operation\n", - "\n", - "We provide a quantum operation for performing quantum And. Specifically, it assumes the third qubit (i.e. the target) is initialized to the `|0>` state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c456e50", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "from cirq.contrib.svg import SVGCircuit\n", - "from cirq_ft import And, infra\n", - "\n", - "gate = And()\n", - "r = gate.signature\n", - "quregs = infra.get_named_qubits(r)\n", - "operation = gate.on_registers(**quregs)\n", - "circuit = cirq.Circuit(operation)\n", - "SVGCircuit(circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "8d4e16dc", - "metadata": {}, - "source": [ - "## Efficient decomposition\n", - "\n", - "This specialization of the Toffoli gate permits a specialized decomposition that minimizes the `T`-gate count." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "fefe2934", - "metadata": {}, - "outputs": [], - "source": [ - "c2 = cirq.Circuit(cirq.decompose_once(operation))\n", - "SVGCircuit(c2)" - ] - }, - { - "cell_type": "markdown", - "id": "b91cc9d9", - "metadata": {}, - "source": [ - "## Test behavior\n", - "\n", - "We can test the behavior of the And gate on computational basis states using a Quantum simulator." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2ec57c0e", - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "\n", - "input_states = [(a, b, 0) for a, b in itertools.product([0, 1], repeat=2)]\n", - "output_states = [(a, b, a & b) for a, b, _ in input_states]\n", - "\n", - "\n", - "for inp, out in zip(input_states, output_states):\n", - " result = cirq.Simulator(dtype=np.complex128).simulate(c2, initial_state=inp)\n", - " print(inp, '->', result.dirac_notation())\n", - " assert result.dirac_notation()[1:-1] == \"\".join(str(x) for x in out)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7e5832e4", - "metadata": {}, - "outputs": [], - "source": [ - "inds, = np.where(abs(result.final_state_vector) > 1e-8)\n", - "assert len(inds) == 1\n", - "ind, = inds\n", - "f'{ind:3b}'" - ] - }, - { - "cell_type": "markdown", - "id": "fa451755", - "metadata": {}, - "source": [ - "## Uncompute\n", - "\n", - "We can save even more `T` gates when \"uncomputing\" an And operation, i.e. performing the adjoint operation by using classical control." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a2878f83", - "metadata": {}, - "outputs": [], - "source": [ - "inv_operation = operation ** -1\n", - "inv_circuit = cirq.Circuit(inv_operation)\n", - "SVGCircuit(inv_circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "3a2dcf07", - "metadata": {}, - "source": [ - "We reset our target using measurement and fix up phases depending on the result of that measurement:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bd123481", - "metadata": {}, - "outputs": [], - "source": [ - "inv_c2 = cirq.Circuit(cirq.decompose_once(inv_operation))\n", - "inv_c2" - ] - }, - { - "cell_type": "markdown", - "id": "a99e3bf5", - "metadata": {}, - "source": [ - "## Test Adjoint" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3cacf727", - "metadata": {}, - "outputs": [], - "source": [ - "input_states = [(a, b, a & b) for a, b in itertools.product([0, 1], repeat=2)]\n", - "output_states = [(a, b, 0) for a, b, _ in input_states]\n", - "\n", - "for inp, out in zip(input_states, output_states):\n", - " result = cirq.Simulator().simulate(inv_circuit, initial_state=inp)\n", - " print(inp, '->', result.dirac_notation())\n", - " assert result.dirac_notation()[1:-1] == \"\".join(str(x) for x in out)" - ] - } - ], - "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/cirq-ft/cirq_ft/algos/and_gate.py b/cirq-ft/cirq_ft/algos/and_gate.py deleted file mode 100644 index c4210d8ccad..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate.py +++ /dev/null @@ -1,173 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Sequence, Tuple - -import numpy as np -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra - - -@attr.frozen -class And(infra.GateWithRegisters): - r"""And gate optimized for T-count. - - Assumptions: - * And(cv).on(c1, c2, target) assumes that target is initially in the |0> state. - * And(cv, adjoint=True).on(c1, c2, target) will always leave the target in |0> state. - - Multi-controlled AND version decomposes into an AND ladder with `#controls - 2` ancillas. - - Args: - cv: A tuple of integers representing 1 control bit per control qubit. - adjoint: If True, the $And^\dagger$ is implemented using measurement based un-computation. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662) - Babbush et. al. 2018. Section III.A. and Fig. 4. - - [Verifying Measurement Based Uncomputation](https://algassert.com/post/1903). - Gidney, C. 2019. - - Raises: - ValueError: If number of control values (i.e. `len(self.cv)`) is less than 2. - """ - - cv: Tuple[int, ...] = attr.field( - default=(1, 1), converter=lambda v: (v,) if isinstance(v, int) else tuple(v) - ) - adjoint: bool = False - - @cv.validator - def _validate_cv(self, attribute, value): - if len(value) < 2: - raise ValueError(f"And gate needs at-least 2 control values, supplied {value} instead.") - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - n_cv = len(self.cv) - junk_reg = [infra.Register('junk', 1, shape=n_cv - 2, side=one_side)] if n_cv > 2 else [] - return infra.Signature( - [ - infra.Register('ctrl', 1, shape=n_cv), - *junk_reg, - infra.Register('target', 1, side=one_side), - ] - ) - - def __pow__(self, power: int) -> "And": - if power == 1: - return self - if power == -1: - return And(self.cv, adjoint=self.adjoint ^ True) - return NotImplemented # pragma: no cover - - def __str__(self) -> str: - suffix = "" if self.cv == (1,) * len(self.cv) else str(self.cv) - return f"And†{suffix}" if self.adjoint else f"And{suffix}" - - def __repr__(self) -> str: - return f"cirq_ft.And({self.cv}, adjoint={self.adjoint})" - - 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.cv] - wire_symbols += ["Anc"] * (len(self.cv) - 2) - wire_symbols += [target] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _has_unitary_(self) -> bool: - return not self.adjoint - - def _decompose_single_and( - self, cv1: int, cv2: int, c1: cirq.Qid, c2: cirq.Qid, target: cirq.Qid - ) -> cirq.ops.op_tree.OpTree: - """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. - """ - pre_post_ops = [cirq.X(q) for (q, v) in zip([c1, c2], [cv1, 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 _decompose_via_tree( - self, - controls: NDArray[cirq.Qid], # type:ignore[type-var] - control_values: Sequence[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(), - ) - if len(self.cv) == 2: - yield self._decompose_single_and( - self.cv[0], self.cv[1], control[0], control[1], *target - ) - else: - yield self._decompose_via_tree(control, self.cv, ancilla, *target) - - def _t_complexity_(self) -> infra.TComplexity: - pre_post_cliffords = len(self.cv) - sum(self.cv) # number of zeros in self.cv - num_single_and = len(self.cv) - 1 - if self.adjoint: - return infra.TComplexity(clifford=4 * num_single_and + 2 * pre_post_cliffords) - else: - return infra.TComplexity( - t=4 * num_single_and, clifford=9 * num_single_and + 2 * pre_post_cliffords - ) diff --git a/cirq-ft/cirq_ft/algos/and_gate_test.py b/cirq-ft/cirq_ft/algos/and_gate_test.py deleted file mode 100644 index 907e45bb71f..00000000000 --- a/cirq-ft/cirq_ft/algos/and_gate_test.py +++ /dev/null @@ -1,239 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 itertools -import random -from typing import List, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -random.seed(12345) - - -@pytest.mark.parametrize("cv", [(0, 0), (0, 1), (1, 0), (1, 1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate(cv: Tuple[int, int]): - c1, c2, t = cirq.LineQubit.range(3) - input_states = [(0, 0, 0), (0, 1, 0), (1, 0, 0), (1, 1, 0)] - output_states = [inp[:2] + (1 if inp[:2] == cv else 0,) for inp in input_states] - - and_gate = cirq_ft.And(cv) - circuit = cirq.Circuit(and_gate.on(c1, c2, t)) - for inp, out in zip(input_states, output_states): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, [c1, c2, t], inp, out) - - -def random_cv(n: int) -> List[int]: - return [random.randint(0, 1) for _ in range(n)] - - -@pytest.mark.parametrize("cv", [[1] * 3, random_cv(5), random_cv(6), random_cv(7)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_controlled_and_gate(cv: List[int]): - gate = cirq_ft.And(cv) - r = gate.signature - assert r.get_right('junk').total_bits() == r.get_left('ctrl').total_bits() - 2 - quregs = infra.get_named_qubits(r) - and_op = gate.on_registers(**quregs) - circuit = cirq.Circuit(and_op) - - input_controls = [cv] + [random_cv(len(cv)) for _ in range(10)] - qubit_order = infra.merge_qubits(gate.signature, **quregs) - - for input_control in input_controls: - initial_state = input_control + [0] * (r.get_right('junk').total_bits() + 1) - result = cirq.Simulator(dtype=np.complex128).simulate( - circuit, initial_state=initial_state, qubit_order=qubit_order - ) - expected_output = np.asarray([0, 1] if input_control == cv else [1, 0]) - assert cirq.equal_up_to_global_phase( - cirq.sub_state_vector( - result.final_state_vector, keep_indices=[cirq.num_qubits(gate) - 1] - ), - expected_output, - ) - - # Test adjoint. - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit + cirq.Circuit(cirq.inverse(and_op)), - qubit_order=qubit_order, - inputs=initial_state, - outputs=initial_state, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_diagram(): - gate = cirq_ft.And((1, 0, 1, 0, 1, 0)) - qubit_regs = infra.get_named_qubits(gate.signature) - op = gate.on_registers(**qubit_regs) - ctrl, junk, target = ( - qubit_regs["ctrl"].flatten(), - qubit_regs["junk"].flatten(), - qubit_regs['target'].flatten(), - ) - # Qubit order should be alternating (control, ancilla) pairs. - c_and_a = sum(zip(ctrl[1:], junk), ()) + (ctrl[-1],) - qubit_order = np.concatenate([ctrl[0:1], c_and_a, target]) - # Test diagrams. - cirq.testing.assert_has_diagram( - cirq.Circuit(op), - """ -ctrl[0]: ───@───── - │ -ctrl[1]: ───(0)─── - │ -junk[0]: ───Anc─── - │ -ctrl[2]: ───@───── - │ -junk[1]: ───Anc─── - │ -ctrl[3]: ───(0)─── - │ -junk[2]: ───Anc─── - │ -ctrl[4]: ───@───── - │ -junk[3]: ───Anc─── - │ -ctrl[5]: ───(0)─── - │ -target: ────And─── -""", - qubit_order=qubit_order, - ) - cirq.testing.assert_has_diagram( - cirq.Circuit(op**-1), - """ -ctrl[0]: ───@────── - │ -ctrl[1]: ───(0)──── - │ -junk[0]: ───Anc──── - │ -ctrl[2]: ───@────── - │ -junk[1]: ───Anc──── - │ -ctrl[3]: ───(0)──── - │ -junk[2]: ───Anc──── - │ -ctrl[4]: ───@────── - │ -junk[3]: ───Anc──── - │ -ctrl[5]: ───(0)──── - │ -target: ────And†─── -""", - qubit_order=qubit_order, - ) - # Test diagram of decomposed 3-qubit and ladder. - decomposed_circuit = cirq.Circuit(cirq.decompose_once(op)) + cirq.Circuit( - cirq.decompose_once(op**-1) - ) - cirq.testing.assert_has_diagram( - decomposed_circuit, - """ -ctrl[0]: ───@─────────────────────────────────────────────────────────@────── - │ │ -ctrl[1]: ───(0)───────────────────────────────────────────────────────(0)──── - │ │ -junk[0]: ───And───@────────────────────────────────────────────@──────And†─── - │ │ -ctrl[2]: ─────────@────────────────────────────────────────────@───────────── - │ │ -junk[1]: ─────────And───@───────────────────────────────@──────And†────────── - │ │ -ctrl[3]: ───────────────(0)─────────────────────────────(0)────────────────── - │ │ -junk[2]: ───────────────And───@──────────────────@──────And†───────────────── - │ │ -ctrl[4]: ─────────────────────@──────────────────@─────────────────────────── - │ │ -junk[3]: ─────────────────────And───@─────@──────And†──────────────────────── - │ │ -ctrl[5]: ───────────────────────────(0)───(0)──────────────────────────────── - │ │ -target: ────────────────────────────And───And†─────────────────────────────── -""", - qubit_order=qubit_order, - ) - - -@pytest.mark.parametrize( - "cv, adjoint, str_output", - [ - ((1, 1, 1), False, "And"), - ((1, 0, 1), False, "And(1, 0, 1)"), - ((1, 1, 1), True, "And†"), - ((1, 0, 1), True, "And†(1, 0, 1)"), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_str_and_repr(cv, adjoint, str_output): - gate = cirq_ft.And(cv, adjoint=adjoint) - assert str(gate) == str_output - cirq.testing.assert_equivalent_repr(gate, setup_code="import cirq_ft\n") - - -@pytest.mark.parametrize("cv", [(0, 0), (0, 1), (1, 0), (1, 1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_adjoint(cv: Tuple[int, int]): - c1, c2, t = cirq.LineQubit.range(3) - all_cvs = [(0, 0), (0, 1), (1, 0), (1, 1)] - input_states = [inp + (1 if inp == cv else 0,) for inp in all_cvs] - output_states = [inp + (0,) for inp in all_cvs] - - circuit = cirq.Circuit(cirq_ft.And(cv, adjoint=True).on(c1, c2, t)) - for inp, out in zip(input_states, output_states): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, [c1, c2, t], inp, out) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('and_gate') - - -@pytest.mark.parametrize( - "cv", [*itertools.chain(*[itertools.product(range(2), repeat=n) for n in range(2, 7 + 1)])] -) -@pytest.mark.parametrize("adjoint", [*range(2)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(cv, adjoint): - gate = cirq_ft.And(cv=cv, adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(gate) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_raises(): - with pytest.raises(ValueError, match="at-least 2 control values"): - _ = cirq_ft.And(cv=(1,)) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_and_gate_power(): - cv = (1, 0) - and_gate = cirq_ft.And(cv) - assert and_gate**1 is and_gate - assert and_gate**-1 == cirq_ft.And(cv, adjoint=True) - assert (and_gate**-1) ** -1 == cirq_ft.And(cv) diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb deleted file mode 100644 index 236832f691c..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.ipynb +++ /dev/null @@ -1,126 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "60432dd0", - "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": "ac3bfb05", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Apply to L-th Target" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4e214a27", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "249829b0", - "metadata": { - "cq.autogen": "_make_ApplyGateToLthQubit.md" - }, - "source": [ - "## `ApplyGateToLthQubit`\n", - "A controlled SELECT operation for single-qubit gates.\n", - "\n", - "$$\n", - "\\mathrm{SELECT} = \\sum_{l}|l \\rangle \\langle l| \\otimes [G(l)]_l\n", - "$$\n", - "\n", - "Where $G$ is a function that maps an index to a single-qubit gate.\n", - "\n", - "This gate uses the unary iteration scheme to apply `nth_gate(selection)` to the\n", - "`selection`-th qubit of `target` all controlled by the `control` register.\n", - "\n", - "#### Parameters\n", - " - `selection_regs`: Indexing `select` signature of type Tuple[`SelectionRegister`, ...]. It also contains information about the iteration length of each selection register.\n", - " - `nth_gate`: A function mapping the composite selection index to a single-qubit gate.\n", - " - `control_regs`: Control signature for constructing a controlled version of the gate.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b8d2a7bf", - "metadata": { - "cq.autogen": "_make_ApplyGateToLthQubit.py" - }, - "outputs": [], - "source": [ - "def _z_to_odd(n: int):\n", - " if n % 2 == 1:\n", - " return cirq.Z\n", - " return cirq.I\n", - "\n", - "apply_z_to_odd = cirq_ft.ApplyGateToLthQubit(\n", - " cirq_ft.SelectionRegister('selection', 3, 4),\n", - " nth_gate=_z_to_odd,\n", - " control_regs=cirq_ft.Signature.build(control=2),\n", - ")\n", - "\n", - "g = cq_testing.GateHelper(\n", - " apply_z_to_odd\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "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/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py deleted file mode 100644 index 86ce2b3f680..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -import itertools -from typing import Callable, Sequence, Tuple - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import unary_iteration_gate - - -@attr.frozen -class ApplyGateToLthQubit(unary_iteration_gate.UnaryIterationGate): - r"""A controlled SELECT operation for single-qubit gates. - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes [G(l)]_l - $$ - - Where $G$ is a function that maps an index to a single-qubit gate. - - This gate uses the unary iteration scheme to apply `nth_gate(selection)` to the - `selection`-th qubit of `target` all controlled by the `control` register. - - Args: - selection_regs: Indexing `select` signature of type Tuple[`SelectionRegisters`, ...]. - It also contains information about the iteration length of each selection register. - nth_gate: A function mapping the composite selection index to a single-qubit gate. - control_regs: Control signature for constructing a controlled version of the gate. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. and Figure 7. - """ - - selection_regs: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - nth_gate: Callable[..., cirq.Gate] - control_regs: Tuple[infra.Register, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.Register) else tuple(v) - ) - - @control_regs.default - def control_regs_default(self): - return (infra.Register('control', 1),) - - @classmethod - def make_on( - cls, *, nth_gate: Callable[..., cirq.Gate], **quregs: Sequence[cirq.Qid] - ) -> cirq.Operation: - """Helper constructor to automatically deduce bitsize attributes.""" - return ApplyGateToLthQubit( - infra.SelectionRegister('selection', len(quregs['selection']), len(quregs['target'])), - nth_gate=nth_gate, - control_regs=infra.Register('control', len(quregs['control'])), - ).on_registers(**quregs) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.control_regs - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.selection_regs - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - total_iteration_size = np.prod( - tuple(reg.iteration_length for reg in self.selection_registers) - ) - return (infra.Register('target', int(total_iteration_size)),) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - for it in itertools.product(*[range(reg.iteration_length) for reg in self.selection_regs]): - wire_symbols += [str(self.nth_gate(*it))] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - target: Sequence[cirq.Qid], - **selection_indices: int, - ) -> cirq.OP_TREE: - selection_shape = tuple(reg.iteration_length for reg in self.selection_regs) - selection_idx = tuple(selection_indices[reg.name] for reg in self.selection_regs) - target_idx = int(np.ravel_multi_index(selection_idx, selection_shape)) - return self.nth_gate(*selection_idx).on(target[target_idx]).controlled_by(control) diff --git a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py b/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py deleted file mode 100644 index 1bf4cb64157..00000000000 --- a/cirq-ft/cirq_ft/algos/apply_gate_to_lth_target_test.py +++ /dev/null @@ -1,110 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("selection_bitsize,target_bitsize", [[3, 5], [3, 7], [4, 5]]) -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit(selection_bitsize, target_bitsize): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), lambda _: cirq.X - ) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - # Upper bounded because not all ancillas may be used as part of unary iteration. - assert ( - len(g.all_qubits) - <= target_bitsize + 2 * (selection_bitsize + infra.total_bits(gate.control_registers)) - 1 - ) - - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.all_qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.all_qubits] - qubit_vals[g.quregs['target'][n]] = 1 - final_state = [qubit_vals[x] for x in g.all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.decomposed_circuit, g.all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit_diagram(): - # Apply Z gate to all odd targets and Identity to even targets. - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', 3, 5), - lambda n: cirq.Z if n & 1 else cirq.I, - control_regs=cirq_ft.Signature.build(control=2), - ) - circuit = cirq.Circuit(gate.on_registers(**infra.get_named_qubits(gate.signature))) - qubits = list(q for v in infra.get_named_qubits(gate.signature).values() for q in v) - cirq.testing.assert_has_diagram( - circuit, - """ -control0: ─────@──── - │ -control1: ─────@──── - │ -selection0: ───In─── - │ -selection1: ───In─── - │ -selection2: ───In─── - │ -target0: ──────I──── - │ -target1: ──────Z──── - │ -target2: ──────I──── - │ -target3: ──────Z──── - │ -target4: ──────I──── -""", - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_apply_gate_to_lth_qubit_make_on(): - gate = cirq_ft.ApplyGateToLthQubit( - cirq_ft.SelectionRegister('selection', 3, 5), - lambda n: cirq.Z if n & 1 else cirq.I, - control_regs=cirq_ft.Signature.build(control=2), - ) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - op2 = cirq_ft.ApplyGateToLthQubit.make_on( - nth_gate=lambda n: cirq.Z if n & 1 else cirq.I, **infra.get_named_qubits(gate.signature) - ) - # Note: ApplyGateToLthQubit doesn't support value equality. - assert op.qubits == op2.qubits - assert op.gate.selection_regs == op2.gate.selection_regs - assert op.gate.control_regs == op2.gate.control_regs - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('apply_gate_to_lth_target') diff --git a/cirq-ft/cirq_ft/algos/arithmetic_gates.py b/cirq-ft/cirq_ft/algos/arithmetic_gates.py deleted file mode 100644 index 4cc34974f95..00000000000 --- a/cirq-ft/cirq_ft/algos/arithmetic_gates.py +++ /dev/null @@ -1,682 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Iterable, Iterator, List, Optional, Sequence, Tuple, Union - -import attr -import cirq -from numpy.typing import NDArray - -from cirq_ft import infra -from cirq_ft.algos import and_gate -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -@deprecated_cirq_ft_class() -@attr.frozen -class LessThanGate(cirq.ArithmeticGate): - """Applies U_a|x>|z> = |x> |z ^ (x < a)>""" - - bitsize: int - less_than_val: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, self.less_than_val, [2] - - def with_registers(self, *new_registers) -> "LessThanGate": - return LessThanGate(len(new_registers[0]), new_registers[1]) - - def apply(self, *register_vals: int) -> Union[int, Iterable[int]]: - input_val, less_than_val, target_register_val = register_vals - return input_val, less_than_val, target_register_val ^ (input_val < less_than_val) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += [f'+(x < {self.less_than_val})'] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def __repr__(self) -> str: - return f'cirq_ft.LessThanGate({self.bitsize}, {self.less_than_val})' - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - """Decomposes the gate into 4N And and And† operations for a T complexity of 4N. - - The decomposition proceeds from the most significant qubit -bit 0- to the least significant - qubit while maintaining whether the qubit sequence is equal to the current prefix of the - `_val` or not. - - The bare-bone logic is: - 1. if ith bit of `_val` is 1 then: - - qubit sequence < `_val` iff they are equal so far and the current qubit is 0. - 2. update `are_equal`: `are_equal := are_equal and (ith bit == ith qubit).` - - This logic is implemented using $n$ `And` & `And†` operations and n+1 clean ancilla where - - one ancilla `are_equal` contains the equality informaiton - - ancilla[i] contain whether the qubits[:i+1] != (i+1)th prefix of `_val` - """ - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - qubits, target = qubits[:-1], qubits[-1] - # Trivial case, self._val is larger than any value the registers could represent - if self.less_than_val >= 2**self.bitsize: - yield cirq.X(target) - return - adjoint = [] - - (are_equal,) = context.qubit_manager.qalloc(1) - - # Initially our belief is that the numbers are equal. - yield cirq.X(are_equal) - adjoint.append(cirq.X(are_equal)) - - # Scan from left to right. - # `are_equal` contains whether the numbers are equal so far. - ancilla = context.qubit_manager.qalloc(self.bitsize) - for b, q, a in zip( - infra.bit_tools.iter_bits(self.less_than_val, self.bitsize), qubits, ancilla - ): - if b: - yield cirq.X(q) - adjoint.append(cirq.X(q)) - - # ancilla[i] = are_equal so far and (q_i != _val[i]). - # = equivalent to: Is the current prefix of qubits < prefix of `_val`? - yield and_gate.And().on(q, are_equal, a) - adjoint.append(and_gate.And(adjoint=True).on(q, are_equal, a)) - - # target ^= is the current prefix of the qubit sequence < current prefix of `_val` - yield cirq.CNOT(a, target) - - # If `a=1` (i.e. the current prefixes aren't equal) this means that - # `are_equal` is currently = 1 and q[i] != _val[i] so we need to flip `are_equal`. - yield cirq.CNOT(a, are_equal) - adjoint.append(cirq.CNOT(a, are_equal)) - else: - # ancilla[i] = are_equal so far and (q = 1). - yield and_gate.And().on(q, are_equal, a) - adjoint.append(and_gate.And(adjoint=True).on(q, are_equal, a)) - - # if `a=1` then we need to flip `are_equal` since this means that are_equal=1, - # b_i=0, q_i=1 => current prefixes are not equal so we need to flip `are_equal`. - yield cirq.CNOT(a, are_equal) - adjoint.append(cirq.CNOT(a, are_equal)) - - yield from reversed(adjoint) - - def _has_unitary_(self): - return True - - def _t_complexity_(self) -> infra.TComplexity: - n = self.bitsize - if self.less_than_val >= 2**n: - return infra.TComplexity(clifford=1) - return infra.TComplexity( - t=4 * n, clifford=15 * n + 3 * bin(self.less_than_val).count("1") + 2 - ) - - -@attr.frozen -class BiQubitsMixer(infra.GateWithRegisters): - """Implements the COMPARE2 (Fig. 1) https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - - This gates mixes the values in a way that preserves the result of comparison. - The signature being compared are 2-qubit signature where - x = 2*x_msb + x_lsb - y = 2*y_msb + y_lsb - The Gate mixes the 4 qubits so that sign(x - y) = sign(x_lsb' - y_lsb') where x_lsb' and y_lsb' - are the final values of x_lsb' and y_lsb'. - - Note that the ancilla qubits are used to reduce the T-count and the user - should clean the qubits at a later point in time with the adjoint gate. - See: https://github.com/quantumlib/Cirq/pull/6313 and - https://github.com/quantumlib/Qualtran/issues/389 - """ # pylint: disable=line-too-long - - adjoint: bool = False - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - return infra.Signature( - [ - infra.Register('x', 2), - infra.Register('y', 2), - infra.Register('ancilla', 3, side=one_side), - ] - ) - - def __repr__(self) -> str: - return f'cirq_ft.algos.BiQubitsMixer({self.adjoint})' - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - x, y, ancilla = quregs['x'], quregs['y'], quregs['ancilla'] - x_msb, x_lsb = x - y_msb, y_lsb = y - - def _cswap(control: cirq.Qid, a: cirq.Qid, b: cirq.Qid, aux: cirq.Qid) -> cirq.OP_TREE: - """A CSWAP with 4T complexity and whose adjoint has 0T complexity. - - A controlled SWAP that swaps `a` and `b` based on `control`. - It uses an extra qubit `aux` so that its adjoint would have - a T complexity of zero. - """ - yield cirq.CNOT(a, b) - yield and_gate.And(adjoint=self.adjoint).on(control, b, aux) - yield cirq.CNOT(aux, a) - yield cirq.CNOT(a, b) - - def _decomposition(): - # computes the difference of x - y where - # x = 2*x_msb + x_lsb - # y = 2*y_msb + y_lsb - # And stores the result in x_lsb and y_lsb such that - # sign(x - y) = sign(x_lsb - y_lsb) - # This decomposition uses 3 ancilla qubits in order to have a - # T complexity of 8. - yield cirq.X(ancilla[0]) - yield cirq.CNOT(y_msb, x_msb) - yield cirq.CNOT(y_lsb, x_lsb) - yield from _cswap(x_msb, x_lsb, ancilla[0], ancilla[1]) - yield from _cswap(x_msb, y_msb, y_lsb, ancilla[2]) - yield cirq.CNOT(y_lsb, x_lsb) - - if self.adjoint: - yield from reversed(tuple(cirq.flatten_to_ops(_decomposition()))) - else: - yield from _decomposition() - - def __pow__(self, power: int) -> cirq.Gate: - if power == 1: - return self - if power == -1: - return BiQubitsMixer(adjoint=not self.adjoint) - return NotImplemented # pragma: no cover - - def _t_complexity_(self) -> infra.TComplexity: - if self.adjoint: - return infra.TComplexity(clifford=18) - return infra.TComplexity(t=8, clifford=28) - - def _has_unitary_(self): - return not self.adjoint - - -@attr.frozen -class SingleQubitCompare(infra.GateWithRegisters): - """Applies U|a>|b>|0>|0> = |a> |a=b> |(a |(a>b)> - - Source: (FIG. 3) in https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - """ # pylint: disable=line-too-long - - adjoint: bool = False - - @cached_property - def signature(self) -> infra.Signature: - one_side = infra.Side.RIGHT if not self.adjoint else infra.Side.LEFT - return infra.Signature( - [ - infra.Register('a', 1), - infra.Register('b', 1), - infra.Register('less_than', 1, side=one_side), - infra.Register('greater_than', 1, side=one_side), - ] - ) - - def __repr__(self) -> str: - return f'cirq_ft.algos.SingleQubitCompare({self.adjoint})' - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - a = quregs['a'] - b = quregs['b'] - less_than = quregs['less_than'] - greater_than = quregs['greater_than'] - - def _decomposition() -> Iterator[cirq.Operation]: - yield and_gate.And((0, 1), adjoint=self.adjoint).on(*a, *b, *less_than) - yield cirq.CNOT(*less_than, *greater_than) - yield cirq.CNOT(*b, *greater_than) - yield cirq.CNOT(*a, *b) - yield cirq.CNOT(*a, *greater_than) - yield cirq.X(*b) - - if self.adjoint: - yield from reversed(tuple(_decomposition())) - else: - yield from _decomposition() - - def __pow__(self, power: int) -> cirq.Gate: - if not isinstance(power, int): - raise ValueError('SingleQubitCompare is only defined for integer powers.') - if power % 2 == 0: - return cirq.IdentityGate(4) - if power < 0: - return SingleQubitCompare(adjoint=not self.adjoint) - return self - - def _t_complexity_(self) -> infra.TComplexity: - if self.adjoint: - return infra.TComplexity(clifford=11) - return infra.TComplexity(t=4, clifford=16) - - -def _equality_with_zero( - context: cirq.DecompositionContext, qubits: Sequence[cirq.Qid], z: cirq.Qid -) -> cirq.OP_TREE: - if len(qubits) == 1: - (q,) = qubits - yield cirq.X(q) - yield cirq.CNOT(q, z) - return - - ancilla = context.qubit_manager.qalloc(len(qubits) - 2) - yield and_gate.And(cv=[0] * len(qubits)).on(*qubits, *ancilla, z) - - -@deprecated_cirq_ft_class() -@attr.frozen -class LessThanEqualGate(cirq.ArithmeticGate): - """Applies U|x>|y>|z> = |x>|y> |z ^ (x <= y)>""" - - x_bitsize: int - y_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.x_bitsize, [2] * self.y_bitsize, [2] - - def with_registers(self, *new_registers) -> "LessThanEqualGate": - return LessThanEqualGate(len(new_registers[0]), len(new_registers[1])) - - def apply(self, *register_vals: int) -> Union[int, int, Iterable[int]]: - x_val, y_val, target_val = register_vals - return x_val, y_val, target_val ^ (x_val <= y_val) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.x_bitsize - wire_symbols += ["In(y)"] * self.y_bitsize - wire_symbols += ['+(x <= y)'] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def __repr__(self) -> str: - return f'cirq_ft.LessThanEqualGate({self.x_bitsize}, {self.y_bitsize})' - - def _decompose_via_tree( - self, context: cirq.DecompositionContext, X: Sequence[cirq.Qid], Y: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - """Returns comparison oracle from https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - - This decomposition follows the tree structure of (FIG. 2) - """ # pylint: disable=line-too-long - if len(X) == 1: - return - if len(X) == 2: - yield BiQubitsMixer().on_registers(x=X, y=Y, ancilla=context.qubit_manager.qalloc(3)) - return - - m = len(X) // 2 - yield self._decompose_via_tree(context, X[:m], Y[:m]) - yield self._decompose_via_tree(context, X[m:], Y[m:]) - yield BiQubitsMixer().on_registers( - x=(X[m - 1], X[-1]), y=(Y[m - 1], Y[-1]), ancilla=context.qubit_manager.qalloc(3) - ) - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - """Decomposes the gate in a T-complexity optimal way. - - The construction can be broken in 4 parts: - 1. In case of differing bitsizes then a multicontrol And Gate - - Section III.A. https://arxiv.org/abs/1805.03662) is used to check whether - the extra prefix is equal to zero: - - result stored in: `prefix_equality` qubit. - 2. The tree structure (FIG. 2) https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf - followed by a SingleQubitCompare to compute the result of comparison of - the suffixes of equal length: - - result stored in: `less_than` and `greater_than` with equality in qubits[-2] - 3. The results from the previous two steps are combined to update the target qubit. - 4. The adjoint of the previous operations is added to restore the input qubits - to their original state and clean the ancilla qubits. - """ # pylint: disable=line-too-long - - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - lhs, rhs, target = qubits[: self.x_bitsize], qubits[self.x_bitsize : -1], qubits[-1] - - n = min(len(lhs), len(rhs)) - - prefix_equality = None - adjoint: List[cirq.Operation] = [] - - # if one of the registers is longer than the other store equality with |0--0> - # into `prefix_equality` using d = |len(P) - len(Q)| And operations => 4d T. - if len(lhs) != len(rhs): - (prefix_equality,) = context.qubit_manager.qalloc(1) - if len(lhs) > len(rhs): - for op in cirq.flatten_to_ops( - _equality_with_zero(context, lhs[:-n], prefix_equality) - ): - yield op - adjoint.append(cirq.inverse(op)) - else: - for op in cirq.flatten_to_ops( - _equality_with_zero(context, rhs[:-n], prefix_equality) - ): - yield op - adjoint.append(cirq.inverse(op)) - - yield cirq.X(target), cirq.CNOT(prefix_equality, target) - - # compare the remaing suffix of P and Q - lhs = lhs[-n:] - rhs = rhs[-n:] - for op in cirq.flatten_to_ops(self._decompose_via_tree(context, lhs, rhs)): - yield op - adjoint.append(cirq.inverse(op)) - - less_than, greater_than = context.qubit_manager.qalloc(2) - yield SingleQubitCompare().on_registers( - a=lhs[-1], b=rhs[-1], less_than=less_than, greater_than=greater_than - ) - adjoint.append( - SingleQubitCompare(adjoint=True).on_registers( - a=lhs[-1], b=rhs[-1], less_than=less_than, greater_than=greater_than - ) - ) - - if prefix_equality is None: - yield cirq.X(target) - yield cirq.CNOT(greater_than, target) - else: - (less_than_or_equal,) = context.qubit_manager.qalloc(1) - yield and_gate.And([1, 0]).on(prefix_equality, greater_than, less_than_or_equal) - adjoint.append( - and_gate.And([1, 0], adjoint=True).on( - prefix_equality, greater_than, less_than_or_equal - ) - ) - - yield cirq.CNOT(less_than_or_equal, target) - - yield from reversed(adjoint) - - def _t_complexity_(self) -> infra.TComplexity: - n = min(self.x_bitsize, self.y_bitsize) - d = max(self.x_bitsize, self.y_bitsize) - n - is_second_longer = self.y_bitsize > self.x_bitsize - if d == 0: - # When both registers are of the same size the T complexity is - # 8n - 4 same as in https://static-content.springer.com/esm/art%3A10.1038%2Fs41534-018-0071-5/MediaObjects/41534_2018_71_MOESM1_ESM.pdf. pylint: disable=line-too-long - return infra.TComplexity(t=8 * n - 4, clifford=46 * n - 17) - else: - # When the registers differ in size and `n` is the size of the smaller one and - # `d` is the difference in size. The T complexity is the sum of the tree - # decomposition as before giving 8n + O(1) and the T complexity of an `And` gate - # over `d` registers giving 4d + O(1) totaling 8n + 4d + O(1). - # From the decomposition we get that the constant is -4 as well as the clifford counts. - if d == 1: - return infra.TComplexity(t=8 * n, clifford=46 * n + 3 + 2 * is_second_longer) - else: - return infra.TComplexity( - t=8 * n + 4 * d - 4, clifford=46 * n + 17 * d - 14 + 2 * is_second_longer - ) - - def _has_unitary_(self): - return True - - -@deprecated_cirq_ft_class() -@attr.frozen -class ContiguousRegisterGate(cirq.ArithmeticGate): - """Applies U|x>|y>|0> -> |x>|y>|x(x-1)/2 + y> - - This is useful in the case when $|x>$ and $|y>$ represent two selection signature such that - $y < x$. For example, imagine a classical for-loop over two variables $x$ and $y$: - - >>> N = 10 - >>> data = [[1000 * x + 10 * y for y in range(x)] for x in range(N)] - >>> for x in range(N): - ... for y in range(x): - ... # Iterates over a total of (N * (N - 1)) / 2 elements. - ... assert data[x][y] == 1000 * x + 10 * y - - We can rewrite the above using a single for-loop that uses a "contiguous" variable `i` s.t. - - >>> import numpy as np - >>> N = 10 - >>> data = [[1000 * x + 10 * y for y in range(x)] for x in range(N)] - >>> for i in range((N * (N - 1)) // 2): - ... x = int(np.floor((1 + np.sqrt(1 + 8 * i)) / 2)) - ... y = i - (x * (x - 1)) // 2 - ... assert data[x][y] == 1000 * x + 10 * y - - Note that both the for-loops iterate over the same ranges and in the same order. The only - difference is that the second loop is a "flattened" version of the first one. - - Such a flattening of selection signature is useful when we want to load multi dimensional - data to a target register which is indexed on selection signature $x$ and $y$ such that - $0<= y <= x < N$ and we want to use a `SelectSwapQROM` to laod this data; which gives a - sqrt-speedup over a traditional QROM at the cost of using more memory and loading chunks - of size `sqrt(N)` in a single iteration. See the reference for more details. - - References: - [Even More Efficient Quantum Computations of Chemistry Through Tensor Hypercontraction] - (https://arxiv.org/abs/2011.03494) - Lee et. al. (2020). Appendix F, Page 67. - """ - - bitsize: int - target_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, [2] * self.bitsize, [2] * self.target_bitsize - - def with_registers(self, *new_registers) -> 'ContiguousRegisterGate': - x_bitsize, y_bitsize, target_bitsize = [len(reg) for reg in new_registers] - assert ( - x_bitsize == y_bitsize - ), f'x_bitsize={x_bitsize} should be same as y_bitsize={y_bitsize}' - return ContiguousRegisterGate(x_bitsize, target_bitsize) - - def apply(self, *register_vals: int) -> Union[int, Iterable[int]]: - x, y, target = register_vals - return x, y, target ^ ((x * (x - 1)) // 2 + y) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += ["In(y)"] * self.bitsize - wire_symbols += ['+(x(x-1)/2 + y)'] * self.target_bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _t_complexity_(self) -> infra.TComplexity: - # See the linked reference for explanation of the Toffoli complexity. - toffoli_complexity = infra.t_complexity(cirq.CCNOT) - return (self.bitsize**2 + self.bitsize - 1) * toffoli_complexity - - def __repr__(self) -> str: - return f'cirq_ft.ContiguousRegisterGate({self.bitsize}, {self.target_bitsize})' - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - -@deprecated_cirq_ft_class() -@attr.frozen -class AdditionGate(cirq.ArithmeticGate): - """Applies U|p>|q> -> |p>|p+q>. - - Args: - bitsize: The number of bits used to represent each integer p and q. - Note that this adder does not detect overflow if bitsize is not - large enough to hold p + q and simply drops the most significant bits. - - References: - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648) - """ - - bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return [2] * self.bitsize, [2] * self.bitsize - - def with_registers(self, *new_registers) -> 'AdditionGate': - return AdditionGate(len(new_registers[0])) - - def apply(self, *register_values: int) -> Union[int, Iterable[int]]: - p, q = register_values - return p, p + q - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In(x)"] * self.bitsize - wire_symbols += ["In(y)/Out(x+y)"] * self.bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _has_unitary_(self): - return True - - def _left_building_block(self, inp, out, anc, depth): - if depth == self.bitsize - 1: - return - else: - yield cirq.CX(anc[depth - 1], inp[depth]) - yield cirq.CX(anc[depth - 1], out[depth]) - yield and_gate.And().on(inp[depth], out[depth], anc[depth]) - yield cirq.CX(anc[depth - 1], anc[depth]) - yield from self._left_building_block(inp, out, anc, depth + 1) - - def _right_building_block(self, inp, out, anc, depth): - if depth == 0: - return - else: - yield cirq.CX(anc[depth - 1], anc[depth]) - yield and_gate.And(adjoint=True).on(inp[depth], out[depth], anc[depth]) - yield cirq.CX(anc[depth - 1], inp[depth]) - yield cirq.CX(inp[depth], out[depth]) - yield from self._right_building_block(inp, out, anc, depth - 1) - - def _decompose_with_context_( - self, qubits: Sequence[cirq.Qid], context: Optional[cirq.DecompositionContext] = None - ) -> cirq.OP_TREE: - if context is None: - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - input_bits = qubits[: self.bitsize] - output_bits = qubits[self.bitsize :] - ancillas = context.qubit_manager.qalloc(self.bitsize - 1) - # Start off the addition by anding into the ancilla - yield and_gate.And().on(input_bits[0], output_bits[0], ancillas[0]) - # Left part of Fig.2 - yield from self._left_building_block(input_bits, output_bits, ancillas, 1) - yield cirq.CX(ancillas[-1], output_bits[-1]) - yield cirq.CX(input_bits[-1], output_bits[-1]) - # right part of Fig.2 - yield from self._right_building_block(input_bits, output_bits, ancillas, self.bitsize - 2) - yield and_gate.And(adjoint=True).on(input_bits[0], output_bits[0], ancillas[0]) - yield cirq.CX(input_bits[0], output_bits[0]) - context.qubit_manager.qfree(ancillas) - - def _t_complexity_(self) -> infra.TComplexity: - # There are N - 2 building blocks each with one And/And^dag contributing 13 cliffords and - # 6 CXs. In addition there is one additional And/And^dag pair and 3 CXs. - num_clifford = (self.bitsize - 2) * 19 + 16 - return infra.TComplexity(t=4 * self.bitsize - 4, clifford=num_clifford) - - def __repr__(self) -> str: - return f'cirq_ft.AdditionGate({self.bitsize})' - - -@deprecated_cirq_ft_class() -@attr.frozen(auto_attribs=True) -class AddMod(cirq.ArithmeticGate): - """Applies U_{M}_{add}|x> = |(x + add) % M> if x < M else |x>. - - Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t. - 1) If integer `x` < `mod`: output is `|(x + add) % M>` - 2) If integer `x` >= `mod`: output is `|x>`. - - This condition is needed to ensure that the mapping of all input basis states (i.e. input - states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus - the gate is reversible. - - Also supports controlled version of the gate by specifying a per qubit control value as a tuple - of integers passed as `cv`. - """ - - bitsize: int - mod: int = attr.field() - add_val: int = 1 - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - - @mod.validator - def _validate_mod(self, attribute, value): - if not 1 <= value <= 2**self.bitsize: - raise ValueError(f"mod: {value} must be between [1, {2 ** self.bitsize}].") - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - add_reg = (2,) * self.bitsize - control_reg = (2,) * len(self.cv) - return (control_reg, add_reg) if control_reg else (add_reg,) - - def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> "AddMod": - raise NotImplementedError() - - def apply(self, *args) -> Union[int, Iterable[int]]: - target_val = args[-1] - if target_val < self.mod: - new_target_val = (target_val + self.add_val) % self.mod - else: - new_target_val = target_val - if self.cv and args[0] != int(''.join(str(x) for x in self.cv), 2): - new_target_val = target_val - ret = (args[0], new_target_val) if self.cv else (new_target_val,) - return ret - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if b else '@(0)' for b in self.cv] - wire_symbols += [f"Add_{self.add_val}_Mod_{self.mod}"] * self.bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int) -> 'AddMod': - return AddMod(self.bitsize, self.mod, add_val=self.add_val * power, cv=self.cv) - - def __repr__(self) -> str: - return f'cirq_ft.AddMod({self.bitsize}, {self.mod}, {self.add_val}, {self.cv})' - - def _t_complexity_(self) -> infra.TComplexity: - # Rough cost as given in https://arxiv.org/abs/1905.09749 - return 5 * infra.t_complexity(AdditionGate(self.bitsize)) diff --git a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py b/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py deleted file mode 100644 index 57515fe4a87..00000000000 --- a/cirq-ft/cirq_ft/algos/arithmetic_gates_test.py +++ /dev/null @@ -1,441 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 itertools - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft.infra import bit_tools -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def identity_map(n: int): - """Returns a dict of size `2**n` mapping each integer in range [0, 2**n) to itself.""" - return {i: i for i in range(2**n)} - - -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_gate(): - qubits = cirq.LineQubit.range(4) - gate = cirq_ft.LessThanGate(3, 5) - op = gate.on(*qubits) - circuit = cirq.Circuit(op) - basis_map = { - 0b_000_0: 0b_000_1, - 0b_000_1: 0b_000_0, - 0b_001_0: 0b_001_1, - 0b_001_1: 0b_001_0, - 0b_010_0: 0b_010_1, - 0b_010_1: 0b_010_0, - 0b_011_0: 0b_011_1, - 0b_011_1: 0b_011_0, - 0b_100_0: 0b_100_1, - 0b_100_1: 0b_100_0, - 0b_101_0: 0b_101_0, - 0b_101_1: 0b_101_1, - 0b_110_0: 0b_110_0, - 0b_110_1: 0b_110_1, - 0b_111_0: 0b_111_0, - 0b_111_1: 0b_111_1, - } - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(len(qubits)), circuit) - gate2 = cirq_ft.LessThanGate(4, 10) - assert gate.with_registers(*gate2.registers()) == gate2 - assert cirq.circuit_diagram_info(gate).wire_symbols == ("In(x)",) * 3 + ("+(x < 5)",) - assert (gate**1 is gate) and (gate**-1 is gate) - assert gate.__pow__(2) is NotImplemented - - -@pytest.mark.parametrize("bits", [*range(8)]) -@pytest.mark.parametrize("val", [3, 5, 7, 8, 9]) -@allow_deprecated_cirq_ft_use_in_tests -def test_decompose_less_than_gate(bits: int, val: int): - qubit_states = list(bit_tools.iter_bits(bits, 3)) - circuit = cirq.Circuit( - cirq.decompose_once(cirq_ft.LessThanGate(3, val).on(*cirq.LineQubit.range(4))) - ) - if val < 8: - initial_state = [0] * 4 + qubit_states + [0] - output_state = [0] * 4 + qubit_states + [int(bits < val)] - else: - # When val >= 2**number_qubits the decomposition doesn't create any ancilla since the - # answer is always 1. - initial_state = [0] - output_state = [1] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, sorted(circuit.all_qubits()), initial_state, output_state - ) - - -@pytest.mark.parametrize("n", [*range(2, 5)]) -@pytest.mark.parametrize("val", [3, 4, 5, 7, 8, 9]) -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_consistent_protocols(n: int, val: int): - g = cirq_ft.LessThanGate(n, val) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - # Test the unitary is self-inverse - u = cirq.unitary(g) - np.testing.assert_allclose(u @ u, np.eye(2 ** (n + 1))) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_in_less_equal_than_gate(): - qubits = cirq.LineQubit.range(7) - op = cirq_ft.LessThanEqualGate(3, 3).on(*qubits) - circuit = cirq.Circuit(op) - basis_map = {} - for in1, in2 in itertools.product(range(2**3), repeat=2): - for target_reg_val in range(2): - target_bin = bin(target_reg_val)[2:] - in1_bin = format(in1, '03b') - in2_bin = format(in2, '03b') - out_bin = bin(target_reg_val ^ (in1 <= in2))[2:] - true_out_int = target_reg_val ^ (in1 <= in2) - input_int = int(in1_bin + in2_bin + target_bin, 2) - output_int = int(in1_bin + in2_bin + out_bin, 2) - assert true_out_int == int(out_bin, 2) - basis_map[input_int] = output_int - - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(len(qubits)), circuit) - - -@pytest.mark.parametrize("x_bitsize", [*range(1, 5)]) -@pytest.mark.parametrize("y_bitsize", [*range(1, 5)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_less_than_equal_consistent_protocols(x_bitsize: int, y_bitsize: int): - g = cirq_ft.LessThanEqualGate(x_bitsize, y_bitsize) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - # Decomposition works even when context is None. - qubits = cirq.LineQid.range(x_bitsize + y_bitsize + 1, dimension=2) - assert cirq.Circuit(g._decompose_with_context_(qubits=qubits)) == cirq.Circuit( - cirq.decompose_once( - g.on(*qubits), context=cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - ) - ) - - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - # Test the unitary is self-inverse - assert g**-1 is g - u = cirq.unitary(g) - np.testing.assert_allclose(u @ u, np.eye(2 ** (x_bitsize + y_bitsize + 1))) - # Test diagrams - expected_wire_symbols = ("In(x)",) * x_bitsize + ("In(y)",) * y_bitsize + ("+(x <= y)",) - assert cirq.circuit_diagram_info(g).wire_symbols == expected_wire_symbols - # Test with_registers - assert g.with_registers([2] * 4, [2] * 5, [2]) == cirq_ft.LessThanEqualGate(4, 5) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_contiguous_register_gate(): - gate = cirq_ft.ContiguousRegisterGate(3, 6) - circuit = cirq.Circuit(gate.on(*cirq.LineQubit.range(12))) - basis_map = {} - for p in range(2**3): - for q in range(p): - inp = f'0b_{p:03b}_{q:03b}_{0:06b}' - out = f'0b_{p:03b}_{q:03b}_{(p * (p - 1))//2 + q:06b}' - basis_map[int(inp, 2)] = int(out, 2) - - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Test the unitary is self-inverse - gate = cirq_ft.ContiguousRegisterGate(2, 4) - assert gate**-1 is gate - u = cirq.unitary(gate) - np.testing.assert_allclose(u @ u, np.eye(2 ** cirq.num_qubits(gate))) - # Test diagrams - expected_wire_symbols = ("In(x)",) * 2 + ("In(y)",) * 2 + ("+(x(x-1)/2 + y)",) * 4 - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_wire_symbols - # Test with_registers - assert gate.with_registers([2] * 3, [2] * 3, [2] * 6) == cirq_ft.ContiguousRegisterGate(3, 6) - - -@pytest.mark.parametrize('n', [*range(1, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_contiguous_register_gate_t_complexity(n): - gate = cirq_ft.ContiguousRegisterGate(n, 2 * n) - toffoli_complexity = cirq_ft.t_complexity(cirq.CCNOT) - assert cirq_ft.t_complexity(gate) == (n**2 + n - 1) * toffoli_complexity - - -@pytest.mark.parametrize('a,b,num_bits', itertools.product(range(4), range(4), range(3, 5))) -@allow_deprecated_cirq_ft_use_in_tests -def test_add(a: int, b: int, num_bits: int): - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits())[-num_anc:] - initial_state = [0] * (2 * num_bits + num_anc) - initial_state[:num_bits] = list(bit_tools.iter_bits(a, num_bits))[::-1] - initial_state[num_bits : 2 * num_bits] = list(bit_tools.iter_bits(b, num_bits))[::-1] - final_state = [0] * (2 * num_bits + num_bits - 1) - final_state[:num_bits] = list(bit_tools.iter_bits(a, num_bits))[::-1] - final_state[num_bits : 2 * num_bits] = list(bit_tools.iter_bits(a + b, num_bits))[::-1] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, qubits + ancillas, initial_state, final_state - ) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Test diagrams - expected_wire_symbols = ("In(x)",) * num_bits + ("In(y)/Out(x+y)",) * num_bits - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_wire_symbols - # Test with_registers - assert gate.with_registers([2] * 6, [2] * 6) == cirq_ft.AdditionGate(6) - - -@pytest.mark.parametrize('bitsize', [3]) -@pytest.mark.parametrize('mod', [5, 8]) -@pytest.mark.parametrize('add_val', [1, 2]) -@pytest.mark.parametrize('cv', [[], [0, 1], [1, 0], [1, 1]]) -@allow_deprecated_cirq_ft_use_in_tests -def test_add_mod_n(bitsize, mod, add_val, cv): - gate = cirq_ft.AddMod(bitsize, mod, add_val=add_val, cv=cv) - basis_map = {} - num_cvs = len(cv) - for x in range(2**bitsize): - y = (x + add_val) % mod if x < mod else x - if not num_cvs: - basis_map[x] = y - continue - for cb in range(2**num_cvs): - inp = f'0b_{cb:0{num_cvs}b}_{x:0{bitsize}b}' - if tuple(int(x) for x in f'{cb:0{num_cvs}b}') == tuple(cv): - out = f'0b_{cb:0{num_cvs}b}_{y:0{bitsize}b}' - basis_map[int(inp, 2)] = int(out, 2) - else: - basis_map[int(inp, 2)] = int(inp, 2) - - num_qubits = gate.num_qubits() - op = gate.on(*cirq.LineQubit.range(num_qubits)) - circuit = cirq.Circuit(op) - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - circuit += op**-1 - cirq.testing.assert_equivalent_computational_basis_map(identity_map(num_qubits), circuit) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - - -@allow_deprecated_cirq_ft_use_in_tests -def test_add_mod_n_protocols(): - with pytest.raises(ValueError, match="must be between"): - _ = cirq_ft.AddMod(3, 10) - add_one = cirq_ft.AddMod(3, 5, 1) - add_two = cirq_ft.AddMod(3, 5, 2, cv=[1, 0]) - - assert add_one == cirq_ft.AddMod(3, 5, 1) - assert add_one != add_two - assert hash(add_one) != hash(add_two) - assert add_two.cv == (1, 0) - assert cirq.circuit_diagram_info(add_two).wire_symbols == ('@', '@(0)') + ('Add_2_Mod_5',) * 3 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_add_truncated(): - num_bits = 3 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits))) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - # Corresponds to 2^2 + 2^2 (4 + 4 = 8 = 2^3 (needs num_bits = 4 to work properly)) - initial_state = [0, 0, 1, 0, 0, 1, 0, 0] - # Should be 1000 (or 0001 below) but bit falls off the end - final_state = [0, 0, 1, 0, 0, 0, 0, 0] - # increasing number of bits yields correct value - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - num_bits = 4 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - initial_state = [0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0] - final_state = [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0] - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - num_bits = 3 - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits() - frozenset(qubits)) - assert len(ancillas) == num_anc - all_qubits = qubits + ancillas - # Corresponds to 2^2 + (2^2 + 2^1 + 2^0) (4 + 7 = 11 = 1011 (need num_bits=4 to work properly)) - initial_state = [0, 0, 1, 1, 1, 1, 0, 0] - # Should be 1011 (or 1101 below) but last two bits are lost - final_state = [0, 0, 1, 1, 1, 0, 0, 0] - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - -@pytest.mark.parametrize('a,b,num_bits', itertools.product(range(4), range(4), range(3, 5))) -@allow_deprecated_cirq_ft_use_in_tests -def test_subtract(a, b, num_bits): - num_anc = num_bits - 1 - gate = cirq_ft.AdditionGate(num_bits) - qubits = cirq.LineQubit.range(2 * num_bits) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(gate.on(*qubits), context=context)) - ancillas = sorted(circuit.all_qubits())[-num_anc:] - initial_state = [0] * (2 * num_bits + num_anc) - initial_state[:num_bits] = list(bit_tools.iter_bits_twos_complement(a, num_bits))[::-1] - initial_state[num_bits : 2 * num_bits] = list( - bit_tools.iter_bits_twos_complement(-b, num_bits) - )[::-1] - final_state = [0] * (2 * num_bits + num_bits - 1) - final_state[:num_bits] = list(bit_tools.iter_bits_twos_complement(a, num_bits))[::-1] - final_state[num_bits : 2 * num_bits] = list( - bit_tools.iter_bits_twos_complement(a - b, num_bits) - )[::-1] - all_qubits = qubits + ancillas - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, all_qubits, initial_state, final_state) - - -@pytest.mark.parametrize("n", [*range(3, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_addition_gate_t_complexity(n: int): - g = cirq_ft.AdditionGate(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - - -@pytest.mark.parametrize('a,b', itertools.product(range(2**3), repeat=2)) -@allow_deprecated_cirq_ft_use_in_tests -def test_add_no_decompose(a, b): - num_bits = 5 - qubits = cirq.LineQubit.range(2 * num_bits) - op = cirq_ft.AdditionGate(num_bits).on(*qubits) - circuit = cirq.Circuit(op) - basis_map = {} - a_bin = format(a, f'0{num_bits}b') - b_bin = format(b, f'0{num_bits}b') - out_bin = format(a + b, f'0{num_bits}b') - true_out_int = a + b - input_int = int(a_bin + b_bin, 2) - output_int = int(a_bin + out_bin, 2) - assert true_out_int == int(out_bin, 2) - basis_map[input_int] = output_int - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - - -@pytest.mark.parametrize("P,n", [(v, n) for n in range(1, 4) for v in range(1 << n)]) -@pytest.mark.parametrize("Q,m", [(v, n) for n in range(1, 4) for v in range(1 << n)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_decompose_less_than_equal_gate(P: int, n: int, Q: int, m: int): - qubit_states = list(bit_tools.iter_bits(P, n)) + list(bit_tools.iter_bits(Q, m)) - circuit = cirq.Circuit( - cirq.decompose_once( - cirq_ft.LessThanEqualGate(n, m).on(*cirq.LineQubit.range(n + m + 1)), - context=cirq.DecompositionContext(cirq.GreedyQubitManager(prefix='_c')), - ) - ) - qubit_order = tuple(sorted(circuit.all_qubits())) - num_ancillas = len(circuit.all_qubits()) - n - m - 1 - initial_state = qubit_states + [0] + [0] * num_ancillas - output_state = qubit_states + [int(P <= Q)] + [0] * num_ancillas - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, qubit_order, initial_state, output_state - ) - - -@pytest.mark.parametrize("adjoint", [False, True]) -@allow_deprecated_cirq_ft_use_in_tests -def test_single_qubit_compare_protocols(adjoint: bool): - g = cirq_ft.algos.SingleQubitCompare(adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - expected_side = cirq_ft.infra.Side.LEFT if adjoint else cirq_ft.infra.Side.RIGHT - assert g.signature[2] == cirq_ft.Register('less_than', 1, side=expected_side) - assert g.signature[3] == cirq_ft.Register('greater_than', 1, side=expected_side) - - with pytest.raises(ValueError): - _ = g**0.5 # type: ignore - - assert g**2 == cirq.IdentityGate(4) - assert g**1 is g - assert g**-1 == cirq_ft.algos.SingleQubitCompare(adjoint=adjoint ^ True) - - -@pytest.mark.parametrize("v1,v2", [(v1, v2) for v1 in range(2) for v2 in range(2)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_single_qubit_compare(v1: int, v2: int): - g = cirq_ft.algos.SingleQubitCompare() - qubits = cirq.LineQid.range(4, dimension=2) - c = cirq.Circuit(g.on(*qubits)) - initial_state = [v1, v2, 0, 0] - output_state = [v1, int(v1 == v2), int(v1 < v2), int(v1 > v2)] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, output_state - ) - - # Check that g**-1 restores the qubits to their original state - c = cirq.Circuit(g.on(*qubits), (g**-1).on(*qubits)) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, initial_state - ) - - -@pytest.mark.parametrize("adjoint", [False, True]) -@allow_deprecated_cirq_ft_use_in_tests -def test_bi_qubits_mixer_protocols(adjoint: bool): - g = cirq_ft.algos.BiQubitsMixer(adjoint=adjoint) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - cirq.testing.assert_equivalent_repr(g, setup_code='import cirq_ft') - - assert g**1 is g - assert g**-1 == cirq_ft.algos.BiQubitsMixer(adjoint=adjoint ^ True) - - -@pytest.mark.parametrize("x", [*range(4)]) -@pytest.mark.parametrize("y", [*range(4)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_bi_qubits_mixer(x: int, y: int): - g = cirq_ft.algos.BiQubitsMixer() - qubits = cirq.LineQid.range(7, dimension=2) - c = cirq.Circuit(g.on(*qubits)) - x_1, x_0 = (x >> 1) & 1, x & 1 - y_1, y_0 = (y >> 1) & 1, y & 1 - initial_state = [x_1, x_0, y_1, y_0, 0, 0, 0] - result = ( - cirq.Simulator() - .simulate(c, initial_state=initial_state, qubit_order=qubits) - .dirac_notation()[1:-1] - ) - x_0, y_0 = int(result[1]), int(result[3]) - assert np.sign(x - y) == np.sign(x_0 - y_0) - - # Check that g**-1 restores the qubits to their original state - c = cirq.Circuit(g.on(*qubits), (g**-1).on(*qubits)) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - c, sorted(c.all_qubits()), initial_state, initial_state - ) diff --git a/cirq-ft/cirq_ft/algos/generic_select.ipynb b/cirq-ft/cirq_ft/algos/generic_select.ipynb deleted file mode 100644 index c84180565b4..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select.ipynb +++ /dev/null @@ -1,131 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "d852231f", - "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": "ae4c107e", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Generic Select\n", - "\n", - "Gates for applying generic selected unitaries." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c5e21402", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "f217ffaf", - "metadata": { - "cq.autogen": "_make_GenericSelect.md" - }, - "source": [ - "## `GenericSelect`\n", - "A SELECT gate for selecting and applying operators from an array of `PauliString`s.\n", - "\n", - "$$\n", - "\\mathrm{SELECT} = \\sum_{l}|l \\rangle \\langle l| \\otimes U_l\n", - "$$\n", - "\n", - "Where $U_l$ is a member of the Pauli group.\n", - "\n", - "This gate uses the unary iteration scheme to apply `select_unitaries[selection]` to `target`\n", - "controlled on the single-bit `control` register.\n", - "\n", - "#### Parameters\n", - " - `selection_bitsize`: The size of the indexing `select` register. This should be at least `log2(len(select_unitaries))`\n", - " - `target_bitsize`: The size of the `target` register.\n", - " - `select_unitaries`: List of `DensePauliString`s to apply to the `target` register. Each dense pauli string must contain `target_bitsize` terms.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "488c76f9", - "metadata": { - "cq.autogen": "_make_GenericSelect.py" - }, - "outputs": [], - "source": [ - "from cirq_ft import GenericSelect\n", - "\n", - "target_bitsize = 4\n", - "us = ['XIXI', 'YIYI', 'ZZZZ', 'ZXYZ']\n", - "us = [cirq.DensePauliString(u) for u in us]\n", - "selection_bitsize = int(np.ceil(np.log2(len(us))))\n", - "g = cq_testing.GateHelper(\n", - " GenericSelect(selection_bitsize, target_bitsize, select_unitaries=us)\n", - ")\n", - "\n", - "display_gate_and_compilation(g, vertical=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "efb6062a", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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 -} diff --git a/cirq-ft/cirq_ft/algos/generic_select.py b/cirq-ft/cirq_ft/algos/generic_select.py deleted file mode 100644 index 6e86636960d..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select.py +++ /dev/null @@ -1,150 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -"""Gates for applying generic selected unitaries.""" - -from functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import select_and_prepare, unary_iteration_gate - - -def _to_tuple(dps: Sequence[cirq.DensePauliString]) -> Tuple[cirq.DensePauliString, ...]: - return tuple(dps) - - -@attr.frozen -class GenericSelect(select_and_prepare.SelectOracle, unary_iteration_gate.UnaryIterationGate): - r"""A SELECT gate for selecting and applying operators from an array of `PauliString`s. - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes U_l - $$ - - Where $U_l$ is a member of the Pauli group. - - This gate uses the unary iteration scheme to apply `select_unitaries[selection]` to `target` - controlled on the single-bit `control` register. - - Args: - selection_bitsize: The size of the indexing `select` register. This should be at least - `log2(len(select_unitaries))` - target_bitsize: The size of the `target` register. - select_unitaries: List of `DensePauliString`s to apply to the `target` register. Each - dense pauli string must contain `target_bitsize` terms. - control_val: Optional control value. If specified, a singly controlled gate is constructed. - """ - - selection_bitsize: int - target_bitsize: int - select_unitaries: Tuple[cirq.DensePauliString, ...] = attr.field(converter=_to_tuple) - control_val: Optional[int] = None - - def __attrs_post_init__(self): - if any(len(dps) != self.target_bitsize for dps in self.select_unitaries): - raise ValueError( - f"Each dense pauli string in {self.select_unitaries} should contain " - f"{self.target_bitsize} terms." - ) - min_bitsize = (len(self.select_unitaries) - 1).bit_length() - if self.selection_bitsize < min_bitsize: - raise ValueError( - f"selection_bitsize={self.selection_bitsize} should be at-least {min_bitsize}" - ) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister( - 'selection', self.selection_bitsize, len(self.select_unitaries) - ), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('target', self.target_bitsize),) - - def decompose_from_registers( - self, context, **quregs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.OP_TREE: - if self.control_val == 0: - yield cirq.X(*quregs['control']) - yield super(GenericSelect, self).decompose_from_registers(context=context, **quregs) - if self.control_val == 0: - yield cirq.X(*quregs['control']) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - selection: int, - control: cirq.Qid, - target: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - """Applies `self.select_unitaries[selection]`. - - Args: - context: `cirq.DecompositionContext` stores options for decomposing gates (eg: - cirq.QubitManager). - selection: takes on values [0, self.iteration_lengths[0]) - control: Qid that is the control qubit or qubits - target: Target register qubits - """ - ps = self.select_unitaries[selection].on(*target) - return ps.with_coefficient(np.sign(complex(ps.coefficient).real)).controlled_by(control) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'GenericSelect': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return GenericSelect( - self.selection_bitsize, - self.target_bitsize, - self.select_unitaries, - control_val=control_values[0], - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def __repr__(self) -> str: - return ( - f'cirq_ft.GenericSelect(' - f'{self.selection_bitsize},' - f'{self.target_bitsize}, ' - f'{self.select_unitaries}, ' - f'{self.control_val})' - ) diff --git a/cirq-ft/cirq_ft/algos/generic_select_test.py b/cirq-ft/cirq_ft/algos/generic_select_test.py deleted file mode 100644 index 9b086c2cded..00000000000 --- a/cirq-ft/cirq_ft/algos/generic_select_test.py +++ /dev/null @@ -1,288 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 List, Sequence - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def get_1d_Ising_hamiltonian( - qubits: Sequence[cirq.Qid], j_zz_strength: float = 1.0, gamma_x_strength: float = -1 -) -> cirq.PauliSum: - r"""A one dimensional ising model with periodic boundaries. - - $$ - H = -J\sum_{k=0}^{L-1}\sigma_{k}^{Z}\sigma_{(k+1)\%L}^{Z} - \Gamma\sum_{k=0}^{L-1}\sigma_{k}^{X} - $$ - - Args: - qubits: One qubit for each spin site. - j_zz_strength: The two-body ZZ potential strength, $J$. - gamma_x_strength: The one-body X potential strength, $\Gamma$. - - Returns: - cirq.PauliSum representing the Hamiltonian - """ - n_sites = len(qubits) - terms: List[cirq.PauliString] = [] - for k in range(n_sites): - terms.append( - cirq.PauliString( - {qubits[k]: cirq.Z, qubits[(k + 1) % n_sites]: cirq.Z}, coefficient=j_zz_strength - ) - ) - terms.append(cirq.PauliString({qubits[k]: cirq.X}, coefficient=gamma_x_strength)) - return cirq.PauliSum.from_pauli_strings(terms) - - -def get_1d_Ising_lcu_coeffs( - n_spins: int, j_zz_strength: float = np.pi / 3, gamma_x_strength: float = np.pi / 7 -) -> np.ndarray: - """Get LCU coefficients for a 1d ising Hamiltonian. - - The order of the terms is according to `get_1d_Ising_hamiltonian`, namely: ZZ's and X's - interleaved. - """ - spins = cirq.LineQubit.range(n_spins) - ham = get_1d_Ising_hamiltonian(spins, j_zz_strength, gamma_x_strength) - coeffs = np.array([term.coefficient.real for term in ham]) - lcu_coeffs = coeffs / np.sum(coeffs) - return lcu_coeffs - - -@pytest.mark.parametrize('control_val', [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_ising_zero_bitflip_select(control_val): - num_sites = 4 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=control_val, - ).on(control, *selection, *target) - circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = circuit.all_qubits() - - # now we need to have a superposition w.r.t all operators to act on target. - # Normally this would be generated by a PREPARE circuit but we will - # build it directly here. - for selection_integer in range(num_select_unitaries): - # turn on control bit to activate circuit - qubit_vals = {x: int(control_val) if x == control else 0 for x in all_qubits} - # Initialize selection bits appropriately - qubit_vals.update(zip(selection, iter_bits(selection_integer, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in all_qubits] - for i, pauli_val in enumerate(dense_pauli_string_hamiltonian[selection_integer]): - if pauli_val == cirq.X: - # Hamiltonian already defined on correct qubits so just take qid - qubit_vals[target[i]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - - cirq_ft.infra.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_ising_one_bitflip_select(): - num_sites = 4 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=1, - ).on(control, *selection, *target) - circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = sorted(circuit.all_qubits()) - - # now we need to have a superposition w.r.t all operators to act on target. - # Normally this would be generated by a PREPARE circuit, but we will - # build it directly here. - for selection_integer in range(num_select_unitaries): - # turn on control bit to activate circuit - qubit_vals = {x: int(x == control) for x in all_qubits} - # Initialize selection bits appropriately - qubit_vals.update(zip(selection, iter_bits(selection_integer, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in all_qubits] - for i, pauli_val in enumerate(dense_pauli_string_hamiltonian[selection_integer]): - if pauli_val == cirq.X: - # Hamiltonian already defined on correct qubits so just take qid - qubit_vals[target[i]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -def _fake_prepare( - positive_coefficients: np.ndarray, selection_register: List[cirq.Qid] -) -> cirq.OP_TREE: - pos_coeffs = positive_coefficients.flatten() - size_hilbert_of_reg = 2 ** len(selection_register) - assert len(pos_coeffs) <= size_hilbert_of_reg - # pad to 2**(len(selection_bitsize)) size - if len(pos_coeffs) < size_hilbert_of_reg: - pos_coeffs = np.hstack( - (pos_coeffs, np.array([0] * (size_hilbert_of_reg - len(pos_coeffs)))) - ) - - assert np.isclose(pos_coeffs.conj().T @ pos_coeffs, 1.0) - circuit = cirq.Circuit() - circuit.append(cirq.StatePreparationChannel(pos_coeffs).on(*selection_register)) - return circuit - - -@allow_deprecated_cirq_ft_use_in_tests -def test_select_application_to_eigenstates(): - # To validate the unary iteration correctly applies the Hamiltonian to a state we - # compare to directly applying Hamiltonian to the initial state. - # - # The target register starts in an eigenstate so = eig / lambda - sim = cirq.Simulator(dtype=np.complex128) - num_sites = 3 - target_bitsize = num_sites - num_select_unitaries = 2 * num_sites - # PBC Ising in 1-D has num_sites ZZ operations and num_sites X operations. - # Thus 2 * num_sites Pauli ops - selection_bitsize = int(np.ceil(np.log2(num_select_unitaries))) - all_qubits = cirq.LineQubit.range(2 * selection_bitsize + target_bitsize + 1) - control, selection, target = ( - all_qubits[0], - all_qubits[1 : 2 * selection_bitsize : 2], - all_qubits[2 * selection_bitsize + 1 :], - ) - - # Get dense PauliString Hamiltonian terms - # right now we only handle positive interaction term values - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dense_pauli_string_hamiltonian = [tt.dense(target) for tt in ham] - # built select with unary iteration gate - op = cirq_ft.GenericSelect( - selection_bitsize=selection_bitsize, - target_bitsize=target_bitsize, - select_unitaries=dense_pauli_string_hamiltonian, - control_val=1, - ).on(control, *selection, *target) - select_circuit = cirq.Circuit(cirq.decompose(op)) - all_qubits = select_circuit.all_qubits() - - coeffs = get_1d_Ising_lcu_coeffs(num_sites, 1, 1) - prep_circuit = _fake_prepare(np.sqrt(coeffs), selection) - turn_on_control = cirq.Circuit(cirq.X.on(control)) - - ising_eigs, ising_wfns = np.linalg.eigh(ham.matrix()) - qubitization_lambda = sum(xx.coefficient.real for xx in dense_pauli_string_hamiltonian) - for iw_idx, ie in enumerate(ising_eigs): - eigenstate_prep = cirq.Circuit() - eigenstate_prep.append( - cirq.StatePreparationChannel(ising_wfns[:, iw_idx].flatten()).on(*target) - ) - - input_circuit = turn_on_control + prep_circuit + eigenstate_prep - input_vec = sim.simulate(input_circuit, qubit_order=all_qubits).final_state_vector - final_circuit = input_circuit + select_circuit - out_vec = sim.simulate(final_circuit, qubit_order=all_qubits).final_state_vector - - # Overlap of inital_state and SELECT initial_state should be like applying H/lambda - # which should give (E / lambda) * initial_state - np.testing.assert_allclose(np.vdot(input_vec, out_vec), ie / qubitization_lambda, atol=1e-8) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_generic_select_raises(): - with pytest.raises(ValueError, match='should contain 3'): - _ = cirq_ft.GenericSelect(2, 3, [cirq.DensePauliString('Y')]) - - with pytest.raises(ValueError, match='should be at-least 3'): - _ = cirq_ft.GenericSelect(1, 2, [cirq.DensePauliString('XX')] * 5) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_generic_select_consistent_protocols_and_controlled(): - select_bitsize, num_select, num_sites = 3, 6, 3 - # Get Ising Hamiltonian - target = cirq.LineQubit.range(num_sites) - ham = get_1d_Ising_hamiltonian(target, 1, 1) - dps_hamiltonian = [tt.dense(target) for tt in ham] - assert len(dps_hamiltonian) == num_select - - # Build GenericSelect gate. - gate = cirq_ft.GenericSelect(select_bitsize, num_sites, dps_hamiltonian) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq\nimport cirq_ft') - - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('generic_select') diff --git a/cirq-ft/cirq_ft/algos/hubbard_model.ipynb b/cirq-ft/cirq_ft/algos/hubbard_model.ipynb deleted file mode 100644 index 99f17654978..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model.ipynb +++ /dev/null @@ -1,292 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "16d9d3ca", - "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": "1117ec28", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Hubbard Model\n", - "\n", - "Gates for Qubitizing the Hubbard Model.\n", - "\n", - "This follows section V. of the [Linear T Paper](https://arxiv.org/abs/1805.03662).\n", - "\n", - "The Hubbard model is a special case of the electronic structure Hamiltonian\n", - "restricted to spins on a planar grid.\n", - "\n", - "$$\n", - "H = -t \\sum_{\\langle p,q \\rangle, \\sigma} a_{p,\\sigma}^\\dagger a_{q,\\sigma}\n", - " + \\frac{u}{2} \\sum_{p,\\alpha\\ne\\beta} n_{p, \\alpha} n_{p, \\beta}\n", - "$$\n", - "\n", - "Under the Jordan-Wigner transformation this is\n", - "\n", - "$$\n", - "\\def\\Zvec{\\overrightarrow{Z}}\n", - "\\def\\hop#1{#1_{p,\\sigma} \\Zvec #1_{q,\\sigma}}\n", - "H = -\\frac{t}{2} \\sum_{\\langle p,q \\rangle, \\sigma} (\\hop{X} + \\hop{Y})\n", - " + \\frac{u}{8} \\sum_{p,\\alpha\\ne\\beta} Z_{p,\\alpha}Z_{p,\\beta}\n", - " - \\frac{u}{4} \\sum_{p,\\sigma} Z_{p,\\sigma} + \\frac{uN}{4}\\mathbb{1}\n", - "$$\n", - "\n", - "\n", - "This model consists of a PREPARE and SELECT operation where our selection operation has indices\n", - "for $p$, $\\alpha$, $q$, and $\\beta$ as well as two indicator bits $U$ and $V$. There are four cases\n", - "considered in both the PREPARE and SELECT operations corresponding to the terms in the Hamiltonian:\n", - "\n", - " - $U=1$, single-body Z\n", - " - $V=1$, spin-spin ZZ term\n", - " - $pq$, YZY term.\n", - "\n", - "See the documentation for `PrepareHubbard` and `SelectHubbard` for details." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "079e71f9", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "1156cf4f", - "metadata": { - "cq.autogen": "_make_SelectHubbard.md" - }, - "source": [ - "## `SelectHubbard`\n", - "The SELECT operation optimized for the 2D Hubbard model.\n", - "\n", - "In contrast to the arbitrary chemistry Hamiltonian, we:\n", - " - explicitly consider the two dimensions of indices to permit optimization of the circuits.\n", - " - dispense with the `theta` parameter.\n", - "\n", - "If neither $U$ nor $V$ is set we apply the kinetic terms of the Hamiltonian:\n", - "\n", - "$$\n", - "-\\hop{X} \\quad p < q \\\\\n", - "-\\hop{Y} \\quad p > q\n", - "$$\n", - "\n", - "If $U$ is set we know $(p,\\alpha)=(q,\\beta)$ and apply the single-body term: $-Z_{p,\\alpha}$.\n", - "If $V$ is set we know $p=q, \\alpha=0$, and $\\beta=1$ and apply the spin term:\n", - "$Z_{p,\\alpha}Z_{p,\\beta}$\n", - "\n", - "The circuit for implementing $\\textit{C-SELECT}_{Hubbard}$ has a T-cost of $10 * N + log(N)$\n", - "and $0$ rotations.\n", - "\n", - "#### Parameters\n", - "- `x_dim`: the number of sites along the x axis.\n", - "- `y_dim`: the number of sites along the y axis.\n", - "- `control_val`: Optional bit specifying the control value for constructing a controlled\n", - " version of this gate. Defaults to None, which means no control.\n", - "\n", - "\n", - "#### Registers\n", - " - `control`: A control bit for the entire gate.\n", - " - `U`: Whether we're applying the single-site part of the potential.\n", - " - `V`: Whether we're applying the pairwise part of the potential.\n", - " - `p_x`: First set of site indices, x component.\n", - " - `p_y`: First set of site indices, y component.\n", - " - `alpha`: First set of sites' spin indicator.\n", - " - `q_x`: Second set of site indices, x component.\n", - " - `q_y`: Second set of site indices, y component.\n", - " - `beta`: Second set of sites' spin indicator.\n", - " - `target`: The system register to apply the select operation.\n", - "\n", - "#### References\n", - "Section V. and Fig. 19 of https://arxiv.org/abs/1805.03662.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6bfb9288", - "metadata": { - "cq.autogen": "_make_SelectHubbard.py" - }, - "outputs": [], - "source": [ - "from cirq_ft.algos.hubbard_model import *\n", - "\n", - "x_dim, y_dim, t = 2, 2, 5\n", - "mu = 4 * t\n", - "\n", - "select = cq_testing.GateHelper(\n", - " SelectHubbard(x_dim=x_dim, y_dim=y_dim, control_val=1)\n", - ")\n", - "display_gate_and_compilation(select, include_costs=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "09b0da48", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "x, y = [], []\n", - "for dim in range(2, 21):\n", - " select = SelectHubbard(x_dim=dim, y_dim=dim, control_val=1)\n", - " cost = cirq_ft.t_complexity(select)\n", - " N = 2 * dim * dim\n", - " logN = (2 * (dim - 1).bit_length() + 1)\n", - " # 2 * (4 * (N - 1)) : From 2 SelectMajoranaFermion gates.\n", - " # 4 * (N/2) : From 1 mulit-controlled ApplyToLthQubit gate on N / 2 targets.\n", - " # 2 * 7 * logN : From 2 CSWAPS on logN qubits corresponding to (p, q) select signature.\n", - " assert cost.t == 10 * N + 14 * logN - 8\n", - " assert cost.rotations == 0\n", - " x.append(N)\n", - " y.append(cost.t)\n", - " \n", - "plt.xlabel('N')\n", - "plt.ylabel('T-costs')\n", - "plt.title('$\\mathrm{SELECT}_\\mathrm{Hubbard}$')\n", - "plt.plot(x, y, label='$10 * N + 14 * logN - 8$', marker=\"o\")\n", - "plt.legend()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "fa794187", - "metadata": {}, - "source": [ - "## `PrepareHubbard`\n", - "The PREPARE operation optimized for the 2D Hubbard model.\n", - "\n", - "In contrast to the arbitrary chemistry Hamiltonian, we:\n", - " - explicitly consider the two dimensions of indices to permit optimization of the circuits.\n", - " - dispense with the `theta` parameter.\n", - "\n", - "The circuit for implementing $\\textit{PREPARE}_{Hubbard}$ has a T-cost of $O(log(N)$ and uses $O(1)$\n", - "single qubit rotations.\n", - "\n", - "\n", - "#### Parameters\n", - "- `x_dim`: the number of sites along the x axis.\n", - "- `y_dim`: the number of sites along the y axis.\n", - "- `t`: coefficient for hopping terms in the Hubbard model hamiltonian.\n", - "- `mu`: coefficient for single body Z term and two-body ZZ terms in the Hubbard model hamiltonian.\n", - "\n", - "#### Registers\n", - " - `control`: A control bit for the entire gate.\n", - " - `U`: Whether we're applying the single-site part of the potential.\n", - " - `V`: Whether we're applying the pairwise part of the potential.\n", - " - `p_x`: First set of site indices, x component.\n", - " - `p_y`: First set of site indices, y component.\n", - " - `alpha`: First set of sites' spin indicator.\n", - " - `q_x`: Second set of site indices, x component.\n", - " - `q_y`: Second set of site indices, y component.\n", - " - `beta`: Second set of sites' spin indicator.\n", - " - `target`: The system register to apply the select operation.\n", - " - `junk`: Temporary Work space.\n", - "\n", - "#### References\n", - "Section V. and Fig. 20 of https://arxiv.org/abs/1805.03662." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6464ad4f", - "metadata": {}, - "outputs": [], - "source": [ - "prepare = cq_testing.GateHelper(\n", - " PrepareHubbard(x_dim=x_dim, y_dim=x_dim, t=t, mu=mu)\n", - ")\n", - "display_gate_and_compilation(prepare, include_costs=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "54a4259c", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "\n", - "x, y_t, y_max = [], [], []\n", - "for dim in range(2, 21):\n", - " prepare = PrepareHubbard(x_dim=dim, y_dim=dim, t=t, mu=mu)\n", - " cost = cirq_ft.t_complexity(prepare)\n", - " N = 2 * dim * dim\n", - " logN = 2 * (dim - 1).bit_length() + 1\n", - " assert cost.t <= 32 * logN\n", - " assert cost.rotations <= 2 * logN + 9\n", - " x.append(N)\n", - " y_t.append(cost.t)\n", - " y_max.append(32 * logN)\n", - " \n", - "plt.xlabel('N')\n", - "plt.ylabel('T-costs')\n", - "plt.title('$\\mathrm{PREPARE}_\\mathrm{Hubbard}$')\n", - "plt.plot(x, y_t, label='$O(logN)$ T-cost', marker=\"o\")\n", - "plt.plot(x, y_max, label='$32 * logN$')\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" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/cirq-ft/cirq_ft/algos/hubbard_model.py b/cirq-ft/cirq_ft/algos/hubbard_model.py deleted file mode 100644 index 18435bf265d..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model.py +++ /dev/null @@ -1,357 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -r"""Gates for Qubitizing the Hubbard Model. - -This follows section V. of the [Linear T Paper](https://arxiv.org/abs/1805.03662). - -The 2D Hubbard model is a special case of the electronic structure Hamiltonian -restricted to spins on a planar grid. - -$$ -H = -t \sum_{\langle p,q \rangle, \sigma} a_{p,\sigma}^\dagger a_{q,\sigma} - + \frac{u}{2} \sum_{p,\alpha\ne\beta} n_{p, \alpha} n_{p, \beta} -$$ - -Under the Jordan-Wigner transformation, this is - -$$ -\def\Zvec{\overrightarrow{Z}} -\def\hop#1{#1_{p,\sigma} \Zvec #1_{q,\sigma}} -H = -\frac{t}{2} \sum_{\langle p,q \rangle, \sigma} (\hop{X} + \hop{Y}) - + \frac{u}{8} \sum_{p,\alpha\ne\beta} Z_{p,\alpha}Z_{p,\beta} - - \frac{u}{4} \sum_{p,\sigma} Z_{p,\sigma} + \frac{uN}{4}\mathbb{1} -$$ - - -This model consists of a PREPARE and SELECT operation where our selection operation has indices -for $p$, $\alpha$, $q$, and $\beta$ as well as two indicator bits $U$ and $V$. There are four cases -considered in both the PREPARE and SELECT operations corresponding to the terms in the Hamiltonian: - - - $U=1$, single-body Z - - $V=1$, spin-spin ZZ term - - $pq$, YZY term. - -See the documentation for `PrepareHubbard` and `SelectHubbard` for details. -""" -from functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, apply_gate_to_lth_target, arithmetic_gates -from cirq_ft.algos import prepare_uniform_superposition as prep_u -from cirq_ft.algos import ( - qubitization_walk_operator, - select_and_prepare, - selected_majorana_fermion, - swap_network, -) - - -@attr.frozen -class SelectHubbard(select_and_prepare.SelectOracle): - r"""The SELECT operation optimized for the 2D Hubbard model. - - In contrast to the arbitrary chemistry Hamiltonian, we: - - explicitly consider the two dimensions of indices to permit optimization of the circuits. - - dispense with the `theta` parameter. - - If neither $U$ nor $V$ is set we apply the kinetic terms of the Hamiltonian: - - $$ - -\hop{X} \quad p < q \\ - -\hop{Y} \quad p > q - $$ - - If $U$ is set we know $(p,\alpha)=(q,\beta)$ and apply the single-body term: $-Z_{p,\alpha}$. - If $V$ is set we know $p=q, \alpha=0$, and $\beta=1$ and apply the spin term: - $Z_{p,\alpha}Z_{p,\beta}$ - - The circuit for implementing $\textit{C-SELECT}_{Hubbard}$ has a T-cost of $10 * N + log(N)$ - and $0$ rotations. - - - Args: - x_dim: the number of sites along the x axis. - y_dim: the number of sites along the y axis. - control_val: Optional bit specifying the control value for constructing a controlled - version of this gate. Defaults to None, which means no control. - - Signature: - control: A control bit for the entire gate. - U: Whether we're applying the single-site part of the potential. - V: Whether we're applying the pairwise part of the potential. - p_x: First set of site indices, x component. - p_y: First set of site indices, y component. - alpha: First set of sites' spin indicator. - q_x: Second set of site indices, x component. - q_y: Second set of site indices, y component. - beta: Second set of sites' spin indicator. - target: The system register to apply the select operation. - - References: - Section V. and Fig. 19 of https://arxiv.org/abs/1805.03662. - """ - - x_dim: int - y_dim: int - control_val: Optional[int] = None - - def __attrs_post_init__(self): - if self.x_dim != self.y_dim: - raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('U', 1, 2), - infra.SelectionRegister('V', 1, 2), - infra.SelectionRegister('p_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('p_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister('q_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('q_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('beta', 1, 2), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('target', self.x_dim * self.y_dim * 2),) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - p_x, p_y, q_x, q_y = quregs['p_x'], quregs['p_y'], quregs['q_x'], quregs['q_y'] - U, V, alpha, beta = quregs['U'], quregs['V'], quregs['alpha'], quregs['beta'] - control, target = quregs.get('control', ()), quregs['target'] - - yield selected_majorana_fermion.SelectedMajoranaFermionGate( - selection_regs=( - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister( - 'p_y', self.signature.get_left('p_y').total_bits(), self.y_dim - ), - infra.SelectionRegister( - 'p_x', self.signature.get_left('p_x').total_bits(), self.x_dim - ), - ), - control_regs=self.control_registers, - target_gate=cirq.Y, - ).on_registers(control=control, p_x=p_x, p_y=p_y, alpha=alpha, target=target) - - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_x, target_y=q_x) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_y, target_y=q_y) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=alpha, target_y=beta) - - q_selection_regs = ( - infra.SelectionRegister('beta', 1, 2), - infra.SelectionRegister('q_y', self.signature.get_left('q_y').total_bits(), self.y_dim), - infra.SelectionRegister('q_x', self.signature.get_left('q_x').total_bits(), self.x_dim), - ) - yield selected_majorana_fermion.SelectedMajoranaFermionGate( - selection_regs=q_selection_regs, control_regs=self.control_registers, target_gate=cirq.X - ).on_registers(control=control, q_x=q_x, q_y=q_y, beta=beta, target=target) - - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=alpha, target_y=beta) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_y, target_y=q_y) - yield swap_network.MultiTargetCSwap.make_on(control=V, target_x=p_x, target_y=q_x) - - yield ( - cirq.S(*control) ** -1 if control else cirq.global_phase_operation(-1j) - ) # Fix errant i from XY=iZ - yield cirq.Z(*U).controlled_by(*control) # Fix errant -1 from multiple pauli applications - - target_qubits_for_apply_to_lth_gate = [ - target[np.ravel_multi_index((1, qy, qx), (2, self.y_dim, self.x_dim))] - for qx in range(self.x_dim) - for qy in range(self.y_dim) - ] - - yield apply_gate_to_lth_target.ApplyGateToLthQubit( - selection_regs=( - infra.SelectionRegister( - 'q_y', self.signature.get_left('q_y').total_bits(), self.y_dim - ), - infra.SelectionRegister( - 'q_x', self.signature.get_left('q_x').total_bits(), self.x_dim - ), - ), - nth_gate=lambda *_: cirq.Z, - control_regs=infra.Register('control', 1 + infra.total_bits(self.control_registers)), - ).on_registers( - q_x=q_x, q_y=q_y, control=[*V, *control], target=target_qubits_for_apply_to_lth_gate - ) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'SelectHubbard': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return SelectHubbard(self.x_dim, self.y_dim, control_val=control_values[0]) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - info = super(SelectHubbard, self)._circuit_diagram_info_(args) - if self.control_val is None: - return info - ctrl = ('@' if self.control_val else '@(0)',) - return info.with_wire_symbols(ctrl + info.wire_symbols[0:1] + info.wire_symbols[2:]) - - def __repr__(self) -> str: - return f'cirq_ft.SelectHubbard({self.x_dim}, {self.y_dim}, {self.control_val})' - - -@attr.frozen -class PrepareHubbard(select_and_prepare.PrepareOracle): - r"""The PREPARE operation optimized for the 2D Hubbard model. - - In contrast to the arbitrary chemistry Hamiltonian, we: - - explicitly consider the two dimensions of indices to permit optimization of the circuits. - - dispense with the `theta` parameter. - - The circuit for implementing $\textit{PREPARE}_{Hubbard}$ has a T-cost of $O(log(N)$ - and uses $O(1)$ single qubit rotations. - - Args: - x_dim: the number of sites along the x axis. - y_dim: the number of sites along the y axis. - t: coefficient for hopping terms in the Hubbard model hamiltonian. - mu: coefficient for single body Z term and two-body ZZ terms in the Hubbard model - hamiltonian. - - Signature: - control: A control bit for the entire gate. - U: Whether we're applying the single-site part of the potential. - V: Whether we're applying the pairwise part of the potential. - p_x: First set of site indices, x component. - p_y: First set of site indices, y component. - alpha: First set of sites' spin indicator. - q_x: Second set of site indices, x component. - q_y: Second set of site indices, y component. - beta: Second set of sites' spin indicator. - target: The system register to apply the select operation. - junk: Temporary Work space. - - References: - Section V. and Fig. 20 of https://arxiv.org/abs/1805.03662. - """ - - x_dim: int - y_dim: int - t: int - mu: int - - def __attrs_post_init__(self): - if self.x_dim != self.y_dim: - raise NotImplementedError("Currently only supports the case where x_dim=y_dim.") - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('U', 1, 2), - infra.SelectionRegister('V', 1, 2), - infra.SelectionRegister('p_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('p_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('alpha', 1, 2), - infra.SelectionRegister('q_x', (self.x_dim - 1).bit_length(), self.x_dim), - infra.SelectionRegister('q_y', (self.y_dim - 1).bit_length(), self.y_dim), - infra.SelectionRegister('beta', 1, 2), - ) - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('temp', 2),) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.junk_registers]) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - p_x, p_y, q_x, q_y = quregs['p_x'], quregs['p_y'], quregs['q_x'], quregs['q_y'] - U, V, alpha, beta = quregs['U'], quregs['V'], quregs['alpha'], quregs['beta'] - temp = quregs['temp'] - - N = self.x_dim * self.y_dim * 2 - qlambda = 2 * N * self.t + (N * self.mu) // 2 - yield cirq.Ry(rads=2 * np.arccos(np.sqrt(self.t * N / qlambda))).on(*V) - yield cirq.Ry(rads=2 * np.arccos(np.sqrt(1 / 5))).on(*U).controlled_by(*V) - yield prep_u.PrepareUniformSuperposition(self.x_dim).on_registers(controls=[], target=p_x) - yield prep_u.PrepareUniformSuperposition(self.y_dim).on_registers(controls=[], target=p_y) - yield cirq.H.on_each(*temp) - yield cirq.CNOT(*U, *V) - yield cirq.X(*beta) - yield from [cirq.X(*V), cirq.H(*alpha).controlled_by(*V), cirq.CX(*V, *beta), cirq.X(*V)] - yield cirq.Circuit(cirq.CNOT.on_each([*zip([*p_x, *p_y, *alpha], [*q_x, *q_y, *beta])])) - yield swap_network.MultiTargetCSwap.make_on(control=temp[:1], target_x=q_x, target_y=q_y) - yield arithmetic_gates.AddMod(len(q_x), self.x_dim, add_val=1, cv=[0, 0]).on(*U, *V, *q_x) - yield swap_network.MultiTargetCSwap.make_on(control=temp[:1], target_x=q_x, target_y=q_y) - - and_target = context.qubit_manager.qalloc(1) - and_anc = context.qubit_manager.qalloc(1) - yield and_gate.And(cv=(0, 0, 1)).on_registers( - ctrl=np.array([U, V, temp[-1:]]), junk=np.array([and_anc]), target=and_target - ) - yield swap_network.MultiTargetCSwap.make_on( - control=and_target, target_x=[*p_x, *p_y, *alpha], target_y=[*q_x, *q_y, *beta] - ) - yield and_gate.And(cv=(0, 0, 1), adjoint=True).on_registers( - ctrl=np.array([U, V, temp[-1:]]), junk=np.array([and_anc]), target=and_target - ) - context.qubit_manager.qfree([*and_anc, *and_target]) - - def __repr__(self) -> str: - return f'cirq_ft.PrepareHubbard({self.x_dim}, {self.y_dim}, {self.t}, {self.mu})' - - -def get_walk_operator_for_hubbard_model( - x_dim: int, y_dim: int, t: int, mu: int -) -> 'qubitization_walk_operator.QubitizationWalkOperator': - select = SelectHubbard(x_dim, y_dim) - prepare = PrepareHubbard(x_dim, y_dim, t, mu) - return qubitization_walk_operator.QubitizationWalkOperator(select=select, prepare=prepare) diff --git a/cirq-ft/cirq_ft/algos/hubbard_model_test.py b/cirq-ft/cirq_ft/algos/hubbard_model_test.py deleted file mode 100644 index f1545c1b281..00000000000 --- a/cirq-ft/cirq_ft/algos/hubbard_model_test.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize('dim', [*range(2, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_select_t_complexity(dim): - select = cirq_ft.SelectHubbard(x_dim=dim, y_dim=dim, control_val=1) - cost = cirq_ft.t_complexity(select) - N = 2 * dim * dim - logN = 2 * (dim - 1).bit_length() + 1 - assert cost.t == 10 * N + 14 * logN - 8 - assert cost.rotations == 0 - - -@pytest.mark.parametrize('dim', [*range(2, 10)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_t_complexity(dim): - prepare = cirq_ft.PrepareHubbard(x_dim=dim, y_dim=dim, t=2, mu=8) - cost = cirq_ft.t_complexity(prepare) - logN = 2 * (dim - 1).bit_length() + 1 - assert cost.t <= 32 * logN - # TODO(#233): The rotation count should reduce to a constant once cost for Controlled-H - # gates is recognized as $2$ T-gates instead of $2$ rotations. - assert cost.rotations <= 2 * logN + 9 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_hubbard_model_consistent_protocols(): - select_gate = cirq_ft.SelectHubbard(x_dim=2, y_dim=2) - prepare_gate = cirq_ft.PrepareHubbard(x_dim=2, y_dim=2, t=1, mu=2) - - # Test equivalent repr - cirq.testing.assert_equivalent_repr(select_gate, setup_code='import cirq_ft') - cirq.testing.assert_equivalent_repr(prepare_gate, setup_code='import cirq_ft') - - # Build controlled SELECT gate - select_op = select_gate.on_registers(**infra.get_named_qubits(select_gate.signature)) - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - select_gate.controlled(), - select_gate.controlled(num_controls=1), - select_gate.controlled(control_values=(1,)), - select_op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - select_gate.controlled(control_values=(0,)), - select_gate.controlled(num_controls=1, control_values=(0,)), - select_op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = select_gate.controlled(num_controls=2) - - # Test diagrams - expected_symbols = ['U', 'V', 'p_x', 'p_y', 'alpha', 'q_x', 'q_y', 'beta'] - expected_symbols += ['target'] * 8 - expected_symbols[0] = 'SelectHubbard' - assert cirq.circuit_diagram_info(select_gate).wire_symbols == tuple(expected_symbols) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('hubbard_model') diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py b/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py deleted file mode 100644 index 4bf33c45db6..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft.algos.mean_estimation.arctan import ArcTan -from cirq_ft.algos.mean_estimation.complex_phase_oracle import ComplexPhaseOracle -from cirq_ft.algos.mean_estimation.mean_estimation_operator import ( - CodeForRandomVariable, - MeanEstimationOperator, -) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py b/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py deleted file mode 100644 index 08678a1fdd4..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/arctan.py +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 Iterable, Sequence, Union - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -@deprecated_cirq_ft_class() -@attr.frozen -class ArcTan(cirq.ArithmeticGate): - r"""Applies U|x>|0>|0000...0> = |x>|sign>|abs(-2 arctan(x) / pi)>. - - Args: - selection_bitsize: The bitsize of input register |x>. - target_bitsize: The bitsize of output register. The computed quantity, - $\abs(-2 * \arctan(x) / \pi)$ is stored as a fixed-length binary approximation - in the output register of size `target_bitsize`. - """ - - selection_bitsize: int - target_bitsize: int - - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - return (2,) * self.selection_bitsize, (2,), (2,) * self.target_bitsize - - def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> "ArcTan": - raise NotImplementedError() - - def apply(self, *register_values: int) -> Union[int, Iterable[int]]: - input_val, target_sign, target_val = register_values - output_val = -2 * np.arctan(input_val, dtype=np.double) / np.pi - assert -1 <= output_val <= 1 - output_sign, output_bin = infra.bit_tools.float_as_fixed_width_int( - output_val, 1 + self.target_bitsize - ) - return input_val, target_sign ^ output_sign, target_val ^ output_bin - - def _t_complexity_(self) -> infra.TComplexity: - # Approximate T-complexity of O(target_bitsize) - return infra.TComplexity(t=self.target_bitsize) - - def __pow__(self, power) -> 'ArcTan': - if power in [+1, -1]: - return self - raise NotImplementedError("__pow__ is only implemented for +1/-1.") # pragma: no cover diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py deleted file mode 100644 index 3f716a1f206..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/arctan_test.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft.algos.mean_estimation.arctan import ArcTan -from cirq_ft.infra.bit_tools import iter_bits_fixed_point -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize('selection_bitsize', [3, 4]) -@pytest.mark.parametrize('target_bitsize', [3, 5, 6]) -@allow_deprecated_cirq_ft_use_in_tests -def test_arctan(selection_bitsize, target_bitsize): - gate = ArcTan(selection_bitsize, target_bitsize) - maps = {} - for x in range(2**selection_bitsize): - inp = f'0b_{x:0{selection_bitsize}b}_0_{0:0{target_bitsize}b}' - y = -2 * np.arctan(x) / np.pi - bits = [*iter_bits_fixed_point(y, target_bitsize + 1, signed=True)] - sign, y_bin = bits[0], bits[1:] - y_bin_str = ''.join(str(b) for b in y_bin) - out = f'0b_{x:0{selection_bitsize}b}_{sign}_{y_bin_str}' - maps[int(inp, 2)] = int(out, 2) - num_qubits = gate.num_qubits() - op = gate.on(*cirq.LineQubit.range(num_qubits)) - circuit = cirq.Circuit(op) - cirq.testing.assert_equivalent_computational_basis_map(maps, circuit) - circuit += op**-1 - cirq.testing.assert_allclose_up_to_global_phase( - circuit.unitary(), np.diag([1] * 2**num_qubits), atol=1e-8 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_arctan_t_complexity(): - gate = ArcTan(4, 5) - assert cirq_ft.t_complexity(gate) == cirq_ft.TComplexity(t=5) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py b/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py deleted file mode 100644 index 7f482293c95..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import select_and_prepare -from cirq_ft.algos.mean_estimation import arctan - - -@attr.frozen -class ComplexPhaseOracle(infra.GateWithRegisters): - r"""Applies $ROT_{y}|l>|garbage_{l}> = exp(i * -2arctan{y_{l}})|l>|garbage_{l}>$. - - TODO(#6142): This currently assumes that the random variable `y_{l}` only takes integer - values. This constraint can be removed by using a standardized floating point to - binary encoding, like IEEE 754, to encode arbitrary floats in the binary target - register and use them to compute the more accurate $-2arctan{y_{l}}$ for any arbitrary - $y_{l}$. - """ - - encoder: select_and_prepare.SelectOracle - arctan_bitsize: int = 32 - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.encoder.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.encoder.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - qm = context.qubit_manager - target_reg = { - reg.name: qm.qalloc(reg.total_bits()) for reg in self.encoder.target_registers - } - target_qubits = infra.merge_qubits(self.encoder.target_registers, **target_reg) - encoder_op = self.encoder.on_registers(**quregs, **target_reg) - - arctan_sign, arctan_target = qm.qalloc(1), qm.qalloc(self.arctan_bitsize) - arctan_op = arctan.ArcTan(len(target_qubits), self.arctan_bitsize).on( - *target_qubits, *arctan_sign, *arctan_target - ) - - yield encoder_op - yield arctan_op - for i, q in enumerate(arctan_target): - yield (cirq.Z(q) ** (1 / 2 ** (1 + i))).controlled_by(*arctan_sign, control_values=[0]) - yield (cirq.Z(q) ** (-1 / 2 ** (1 + i))).controlled_by(*arctan_sign, control_values=[1]) - - yield cirq.inverse(arctan_op) - yield cirq.inverse(encoder_op) - - qm.qfree([*arctan_sign, *arctan_target, *target_qubits]) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@'] * infra.total_bits(self.control_registers) - wire_symbols += ['ROTy'] * infra.total_bits(self.selection_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py deleted file mode 100644 index 49dd34bbbe6..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/complex_phase_oracle_test.py +++ /dev/null @@ -1,86 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -import math -from typing import Optional, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from attr import frozen -from cirq_ft.algos.mean_estimation.complex_phase_oracle import ComplexPhaseOracle -from cirq_ft.infra import bit_tools -from cirq_ft.infra import testing as cq_testing -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@frozen -class ExampleSelect(cirq_ft.SelectOracle): - bitsize: int - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () if self.control_val is None else (cirq_ft.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.bitsize),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self.bitsize),) - - def decompose_from_registers(self, context, selection, target): - yield [cirq.CNOT(s, t) for s, t in zip(selection, target)] - - -@pytest.mark.parametrize('bitsize', [2, 3, 4, 5]) -@pytest.mark.parametrize('arctan_bitsize', [5, 6, 7]) -@allow_deprecated_cirq_ft_use_in_tests -def test_phase_oracle(bitsize: int, arctan_bitsize: int): - phase_oracle = ComplexPhaseOracle(ExampleSelect(bitsize), arctan_bitsize) - g = cq_testing.GateHelper(phase_oracle) - - # Prepare uniform superposition state on selection register and apply phase oracle. - circuit = cirq.Circuit(cirq.H.on_each(*g.quregs['selection'])) - circuit += cirq.Circuit(cirq.decompose_once(g.operation)) - - # Simulate the circut and test output. - qubit_order = cirq.QubitOrder.explicit(g.quregs['selection'], fallback=cirq.QubitOrder.DEFAULT) - result = cirq.Simulator(dtype=np.complex128).simulate(circuit, qubit_order=qubit_order) - state_vector = result.final_state_vector - state_vector = state_vector.reshape(2**bitsize, len(state_vector) // 2**bitsize) - prepared_state = state_vector.sum(axis=1) - for x in range(2**bitsize): - output_val = -2 * np.arctan(x, dtype=np.double) / np.pi - output_bits = [*bit_tools.iter_bits_fixed_point(np.abs(output_val), arctan_bitsize)] - approx_val = np.sign(output_val) * math.fsum( - [b * (1 / 2 ** (1 + i)) for i, b in enumerate(output_bits)] - ) - - assert math.isclose(output_val, approx_val, abs_tol=1 / 2**bitsize), output_bits - - y = np.exp(1j * approx_val * np.pi) / np.sqrt(2**bitsize) - assert np.isclose(prepared_state[x], y) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_phase_oracle_consistent_protocols(): - bitsize, arctan_bitsize = 3, 5 - gate = ComplexPhaseOracle(ExampleSelect(bitsize, 1), arctan_bitsize) - expected_symbols = ('@',) + ('ROTy',) * bitsize - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_symbols diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py b/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py deleted file mode 100644 index 8d905958e76..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator.py +++ /dev/null @@ -1,177 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import reflection_using_prepare as rup -from cirq_ft.algos import select_and_prepare as sp -from cirq_ft.algos.mean_estimation import complex_phase_oracle - - -@attr.frozen -class CodeForRandomVariable: - r"""A collection of `encoder` and `synthesizer` for a random variable y. - - We say we have "the code" for a random variable $y$ defined on a probability space - $(W, p)$ if we have both, a synthesizer and an encoder defined as follows: - - The synthesizer is responsible to "prepare" the state - $\sum_{w \in W} \sqrt{p(w)} |w> |garbage_{w}>$ on the "selection register" $w$ and potentially - using a "junk register" corresponding to $|garbage_{w}>$. Thus, for convenience, the synthesizer - follows the LCU PREPARE Oracle API. - $$ - synthesizer|0> = \sum_{w \in W} \sqrt{p(w)} |w> |garbage_{w}> - $$ - - - The encoder is responsible to encode the value of random variable $y(w)$ in a "target register" - when the corresponding "selection register" stores integer $w$. Thus, for convenience, the - encoder follows the LCU SELECT Oracle API. - $$ - encoder|w>|0^b> = |w>|y(w)> - $$ - where b is the number of bits required to encode the real range of random variable y. - - References: - https://arxiv.org/abs/2208.07544, Definition 2.2 for synthesizer (P) and - Definition 2.10 for encoder (Y). - """ - - synthesizer: sp.PrepareOracle - encoder: sp.SelectOracle - - def __attrs_post_init__(self): - assert self.synthesizer.selection_registers == self.encoder.selection_registers - - -@attr.frozen -class MeanEstimationOperator(infra.GateWithRegisters): - r"""Mean estimation operator $U=REFL_{p} ROT_{y}$ as per Sec 3.1 of arxiv.org:2208.07544. - - The MeanEstimationOperator (aka KO Operator) expects `CodeForRandomVariable` to specify the - synthesizer and encoder, that follows LCU SELECT/PREPARE API for convenience. It is composed - of two unitaries: - - - REFL_{p}: Reflection around the state prepared by synthesizer $P$. It applies the unitary - $P^{\dagger}(2|0><0| - I)P$. - - ROT_{y}: Applies a complex phase $\exp(i * -2\arctan{y_{w}})$ when the selection register - stores $w$. This is achieved by using the encoder to encode $y(w)$ in a temporary target - register. - - Note that both $REFL_{p}$ and $ROT_{y}$ only act upon a selection register, thus mean estimation - operator expects only a selection register (and a control register, for a controlled version for - phase estimation). - """ - - code: CodeForRandomVariable - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - power: int = 1 - arctan_bitsize: int = 32 - - @cv.validator - def _validate_cv(self, attribute, value): - assert value in [(), (0,), (1,)] - - @cached_property - def reflect(self) -> rup.ReflectionUsingPrepare: - return rup.ReflectionUsingPrepare( - self.code.synthesizer, control_val=None if self.cv == () else self.cv[0] - ) - - @cached_property - def select(self) -> complex_phase_oracle.ComplexPhaseOracle: - return complex_phase_oracle.ComplexPhaseOracle(self.code.encoder, self.arctan_bitsize) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.code.encoder.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.code.encoder.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - select_reg = {reg.name: quregs[reg.name] for reg in self.select.signature} - reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} - select_op = self.select.on_registers(**select_reg) - reflect_op = self.reflect.on_registers(**reflect_reg) - for _ in range(self.power): - yield select_op - # Add a -1 global phase since `ReflectUsingPrepare` applies $R_{s} = I - 2|s> cirq.CircuitDiagramInfo: - wire_symbols = [] if self.cv == () else [["@(0)", "@"][self.cv[0]]] - wire_symbols += ['U_ko'] * ( - infra.total_bits(self.signature) - infra.total_bits(self.control_registers) - ) - if self.power != 1: - wire_symbols[-1] = f'U_ko^{self.power}' - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'MeanEstimationOperator': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and len(control_values) == 1 - and isinstance(control_values[0], int) - and not self.cv - ): - c_select = self.code.encoder.controlled(control_values=control_values) - assert isinstance(c_select, sp.SelectOracle) - return MeanEstimationOperator( - CodeForRandomVariable(encoder=c_select, synthesizer=self.code.synthesizer), - cv=self.cv + (control_values[0],), - power=self.power, - arctan_bitsize=self.arctan_bitsize, - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def with_power(self, new_power: int) -> 'MeanEstimationOperator': - return MeanEstimationOperator( - self.code, cv=self.cv, power=new_power, arctan_bitsize=self.arctan_bitsize - ) - - def __pow__(self, power: int): - return self.with_power(self.power * power) diff --git a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py b/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py deleted file mode 100644 index 19864608bf2..00000000000 --- a/cirq-ft/cirq_ft/algos/mean_estimation/mean_estimation_operator_test.py +++ /dev/null @@ -1,295 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Optional, Sequence, Tuple - -import cirq -import cirq_ft -import numpy as np -import pytest -from attr import frozen -from cirq_ft import infra -from cirq_ft.algos.mean_estimation import CodeForRandomVariable, MeanEstimationOperator -from cirq_ft.infra import bit_tools -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@frozen -class BernoulliSynthesizer(cirq_ft.PrepareOracle): - r"""Synthesizes the state $sqrt(1 - p)|00..00> + sqrt(p)|11..11>$""" - - p: float - nqubits: int - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('q', self.nqubits, 2),) - - def decompose_from_registers( # type:ignore[override] - self, context, q: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - theta = np.arccos(np.sqrt(1 - self.p)) - yield cirq.ry(2 * theta).on(q[0]) - yield [cirq.CNOT(q[0], q[i]) for i in range(1, len(q))] - - -@frozen -class BernoulliEncoder(cirq_ft.SelectOracle): - r"""Encodes Bernoulli random variable y0/y1 as $Enc|ii..i>|0> = |ii..i>|y_{i}>$ where i=0/1.""" - - p: float - y: Tuple[int, int] - selection_bitsize: int - target_bitsize: int - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () if self.control_val is None else (cirq_ft.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('q', self.selection_bitsize, 2),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('t', self.target_bitsize),) - - def decompose_from_registers( # type:ignore[override] - self, context, q: Sequence[cirq.Qid], t: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - y0_bin = bit_tools.iter_bits(self.y[0], self.target_bitsize) - y1_bin = bit_tools.iter_bits(self.y[1], self.target_bitsize) - - for y0, y1, tq in zip(y0_bin, y1_bin, t): - if y0: - yield cirq.X(tq).controlled_by( # pragma: no cover - *q, control_values=[0] * self.selection_bitsize # pragma: no cover - ) # pragma: no cover - if y1: - yield cirq.X(tq).controlled_by(*q, control_values=[1] * self.selection_bitsize) - - def controlled(self, *args, **kwargs): - cv = kwargs['control_values'][0] - return BernoulliEncoder(self.p, self.y, self.selection_bitsize, self.target_bitsize, cv) - - @cached_property - def mu(self) -> float: - return self.p * self.y[1] + (1 - self.p) * self.y[0] - - @cached_property - def s_square(self) -> float: - return self.p * (self.y[1] ** 2) + (1 - self.p) * (self.y[0] ** 2) - - -def overlap(v1: np.ndarray, v2: np.ndarray) -> float: - return np.abs(np.vdot(v1, v2)) ** 2 - - -def satisfies_theorem_321( - synthesizer: cirq_ft.PrepareOracle, - encoder: cirq_ft.SelectOracle, - c: float, - s: float, - mu: float, - arctan_bitsize: int, -): - r"""Verifies Theorem 3.21 of https://arxiv.org/abs/2208.07544 - - Pr[∣sin(θ/2)∣ ∈ ∣µ∣ / √(1 + s ** 2) . [1 / (1 + cs), 1 / (1 - cs)]] >= (1 - 2 / c**2) - """ - code = CodeForRandomVariable(synthesizer=synthesizer, encoder=encoder) - mean_gate = MeanEstimationOperator(code, arctan_bitsize=arctan_bitsize) - - # Compute a reduced unitary for mean_op. - u = cirq.unitary(mean_gate) - assert cirq.is_unitary(u) - - # Compute the final state vector obtained using the synthesizer `Prep |0>` - prep_op = synthesizer.on_registers(**infra.get_named_qubits(synthesizer.signature)) - prep_state = cirq.Circuit(prep_op).final_state_vector() - - expected_hav = abs(mu) * np.sqrt(1 / (1 + s**2)) - expected_hav_low = expected_hav / (1 + c * s) - expected_hav_high = expected_hav / (1 - c * s) - - overlap_sum = 0.0 - eigvals, eigvects = cirq.linalg.unitary_eig(u) - for eig_val, eig_vect in zip(eigvals, eigvects.T): - theta = np.abs(np.angle(eig_val)) - hav_theta = np.sin(theta / 2) - overlap_prob = overlap(prep_state, eig_vect) - if expected_hav_low <= hav_theta <= expected_hav_high: - overlap_sum += overlap_prob - return overlap_sum >= 1 - 2 / (c**2) > 0 - - -@pytest.mark.parametrize('selection_bitsize', [1, 2]) -@pytest.mark.parametrize( - 'p, y_1, target_bitsize, c', - [ - (1 / 100 * 1 / 100, 3, 2, 100 / 7), - (1 / 50 * 1 / 50, 2, 2, 50 / 4), - (1 / 50 * 1 / 50, 1, 1, 50 / 10), - (1 / 4 * 1 / 4, 1, 1, 1.5), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_bernoulli( - p: int, y_1: int, selection_bitsize: int, target_bitsize: int, c: float, arctan_bitsize: int = 5 -): - synthesizer = BernoulliSynthesizer(p, selection_bitsize) - encoder = BernoulliEncoder(p, (0, y_1), selection_bitsize, target_bitsize) - s = np.sqrt(encoder.s_square) - # For hav_theta interval to be reasonably wide, 1/(1-cs) term should be <=2; thus cs <= 0.5. - # The theorem assumes that C >= 1 and s <= 1 / c. - assert c * s <= 0.5 and c >= 1 >= s - - assert satisfies_theorem_321( - synthesizer=synthesizer, - encoder=encoder, - c=c, - s=s, - mu=encoder.mu, - arctan_bitsize=arctan_bitsize, - ) - - -@frozen -class GroverSynthesizer(cirq_ft.PrepareOracle): - r"""Prepare a uniform superposition over the first $2^n$ elements.""" - - n: int - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.n),) - - def decompose_from_registers( # type:ignore[override] - self, *, context, selection: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - yield cirq.H.on_each(*selection) - - def __pow__(self, power): - if power in [+1, -1]: - return self - return NotImplemented # pragma: no cover - - -@frozen -class GroverEncoder(cirq_ft.SelectOracle): - """Enc|marked_item>|0> --> |marked_item>|marked_val>""" - - n: int - marked_item: int - marked_val: int - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return (cirq_ft.SelectionRegister('selection', self.n),) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self.marked_val.bit_length()),) - - def decompose_from_registers( # type:ignore[override] - self, context, *, selection: Sequence[cirq.Qid], target: Sequence[cirq.Qid] - ) -> cirq.OP_TREE: - selection_cv = [ - *bit_tools.iter_bits(self.marked_item, infra.total_bits(self.selection_registers)) - ] - yval_bin = [*bit_tools.iter_bits(self.marked_val, infra.total_bits(self.target_registers))] - - for b, q in zip(yval_bin, target): - if b: - yield cirq.X(q).controlled_by(*selection, control_values=selection_cv) - - @cached_property - def mu(self) -> float: - return self.marked_val / 2**self.n - - @cached_property - def s_square(self) -> float: - return (self.marked_val**2) / 2**self.n - - -@pytest.mark.parametrize('n, marked_val, c', [(5, 1, 4), (4, 1, 2), (2, 1, np.sqrt(2))]) -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_grover( - n: int, marked_val: int, c: float, marked_item: int = 1, arctan_bitsize: int = 5 -): - synthesizer = GroverSynthesizer(n) - encoder = GroverEncoder(n, marked_item=marked_item, marked_val=marked_val) - s = np.sqrt(encoder.s_square) - assert c * s < 1 and c >= 1 >= s - - assert satisfies_theorem_321( - synthesizer=synthesizer, - encoder=encoder, - c=c, - s=s, - mu=encoder.mu, - arctan_bitsize=arctan_bitsize, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_mean_estimation_operator_consistent_protocols(): - p, selection_bitsize, y_1, target_bitsize, arctan_bitsize = 0.1, 2, 1, 1, 4 - synthesizer = BernoulliSynthesizer(p, selection_bitsize) - encoder = BernoulliEncoder(p, (0, y_1), selection_bitsize, target_bitsize) - code = CodeForRandomVariable(synthesizer=synthesizer, encoder=encoder) - mean_gate = MeanEstimationOperator(code, arctan_bitsize=arctan_bitsize) - op = mean_gate.on_registers(**infra.get_named_qubits(mean_gate.signature)) - - # Test controlled gate. - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - mean_gate.controlled(), - mean_gate.controlled(num_controls=1), - mean_gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - mean_gate.controlled(control_values=(0,)), - mean_gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = mean_gate.controlled(num_controls=2) - - # Test with_power - assert mean_gate.with_power(5) ** 2 == MeanEstimationOperator( - code, arctan_bitsize=arctan_bitsize, power=10 - ) - # Test diagrams - expected_symbols = ['U_ko'] * cirq.num_qubits(mean_gate) - assert cirq.circuit_diagram_info(mean_gate).wire_symbols == tuple(expected_symbols) - control_symbols = ['@'] - assert cirq.circuit_diagram_info(mean_gate.controlled()).wire_symbols == tuple( - control_symbols + expected_symbols - ) - control_symbols = ['@(0)'] - assert cirq.circuit_diagram_info( - mean_gate.controlled(control_values=(0,)) - ).wire_symbols == tuple(control_symbols + expected_symbols) - expected_symbols[-1] = 'U_ko^2' - assert cirq.circuit_diagram_info( - mean_gate.with_power(2).controlled(control_values=(0,)) - ).wire_symbols == tuple(control_symbols + expected_symbols) diff --git a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py b/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py deleted file mode 100644 index 7d6142fb175..00000000000 --- a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli.py +++ /dev/null @@ -1,113 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate - - -class MultiTargetCNOT(infra.GateWithRegisters): - """Implements single control, multi-target CNOT_{n} gate in 2*log(n) + 1 CNOT depth. - - Implements CNOT_{n} = |0><0| I + |1><1| X^{n} using a circuit of depth 2*log(n) + 1 - containing only CNOT gates. See Appendix B.1 of https://arxiv.org/abs/1812.00954 for - reference. - """ - - def __init__(self, num_targets: int): - self._num_targets = num_targets - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(control=1, targets=self._num_targets) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ): - control, targets = quregs['control'], quregs['targets'] - - def cnots_for_depth_i(i: int, q: NDArray[cirq.Qid]) -> cirq.OP_TREE: - for c, t in zip(q[: 2**i], q[2**i : min(len(q), 2 ** (i + 1))]): - yield cirq.CNOT(c, t) - - depth = len(targets).bit_length() - for i in range(depth): - yield cirq.Moment(cnots_for_depth_i(depth - i - 1, targets)) - yield cirq.CNOT(*control, targets[0]) - for i in range(depth): - yield cirq.Moment(cnots_for_depth_i(i, targets)) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - return cirq.CircuitDiagramInfo(wire_symbols=["@"] + ["X"] * self._num_targets) - - -@attr.frozen -class MultiControlPauli(infra.GateWithRegisters): - """Implements multi-control, single-target C^{n}P gate. - - Implements $C^{n}P = (1 - |1^{n}><1^{n}|) I + |1^{n}><1^{n}| P^{n}$ using $n-1$ - clean ancillas using a multi-controlled `AND` gate. - - References: - [Constructing Large Controlled Nots] - (https://algassert.com/circuits/2015/06/05/Constructing-Large-Controlled-Nots.html) - """ - - cvs: Tuple[int, ...] = attr.field(converter=lambda v: (v,) if isinstance(v, int) else tuple(v)) - target_gate: cirq.Pauli = cirq.X - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(controls=len(self.cvs), target=1) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray['cirq.Qid'] - ) -> cirq.OP_TREE: - controls, target = quregs['controls'], quregs['target'] - qm = context.qubit_manager - and_ancilla, and_target = np.array(qm.qalloc(len(self.cvs) - 2)), qm.qalloc(1) - yield and_gate.And(self.cvs).on_registers( - ctrl=controls[:, np.newaxis], junk=and_ancilla[:, np.newaxis], target=and_target - ) - yield self.target_gate.on(*target).controlled_by(*and_target) - yield and_gate.And(self.cvs, adjoint=True).on_registers( - ctrl=controls[:, np.newaxis], junk=and_ancilla[:, np.newaxis], target=and_target - ) - qm.qfree([*and_ancilla, *and_target]) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@" if b else "@(0)" for b in self.cvs] - wire_symbols += [str(self.target_gate)] - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _t_complexity_(self) -> infra.TComplexity: - and_cost = infra.t_complexity(and_gate.And(self.cvs)) - controlled_pauli_cost = infra.t_complexity(self.target_gate.controlled(1)) - and_inv_cost = infra.t_complexity(and_gate.And(self.cvs, adjoint=True)) - return and_cost + controlled_pauli_cost + and_inv_cost - - def _apply_unitary_(self, args: 'cirq.ApplyUnitaryArgs') -> np.ndarray: - return cirq.apply_unitary(self.target_gate.controlled(control_values=self.cvs), args) - - def _has_unitary_(self) -> bool: - return True diff --git a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py b/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py deleted file mode 100644 index 158ae31299f..00000000000 --- a/cirq-ft/cirq_ft/algos/multi_control_multi_target_pauli_test.py +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("num_targets", [3, 4, 6, 8, 10]) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cnot(num_targets): - qubits = cirq.LineQubit.range(num_targets + 1) - naive_circuit = cirq.Circuit(cirq.CNOT(qubits[0], q) for q in qubits[1:]) - op = cirq_ft.MultiTargetCNOT(num_targets).on(*qubits) - cirq.testing.assert_circuits_with_terminal_measurements_are_equivalent( - cirq.Circuit(op), naive_circuit, atol=1e-6 - ) - optimal_circuit = cirq.Circuit(cirq.decompose_once(op)) - assert len(optimal_circuit) == 2 * np.ceil(np.log2(num_targets)) + 1 - - -@pytest.mark.parametrize("num_controls", [*range(7, 17)]) -@pytest.mark.parametrize("pauli", [cirq.X, cirq.Y, cirq.Z]) -@pytest.mark.parametrize('cv', [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity_mcp(num_controls: int, pauli: cirq.Pauli, cv: int): - gate = cirq_ft.MultiControlPauli([cv] * num_controls, target_gate=pauli) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(gate) diff --git a/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb b/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb deleted file mode 100644 index 99ca0bed34e..00000000000 --- a/cirq-ft/cirq_ft/algos/phase_estimation_of_quantum_walk.ipynb +++ /dev/null @@ -1,199 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "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", - "metadata": {}, - "source": [ - "## Heisenberg limited phase estimation\n", - "Implements Heisenberg-Limited Phase Estimation of the Qubitized Quantum Walks as described in Section-II B. of [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "\n", - "import cirq_ft\n", - "from cirq_ft import infra\n", - "\n", - "from cirq_ft.algos.qubitization_walk_operator_test import get_walk_operator_for_1d_Ising_model\n", - "from cirq_ft.algos.hubbard_model import get_walk_operator_for_hubbard_model" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def get_resource_state(m: int):\n", - " r\"\"\"Returns a state vector representing the resource state on m qubits from Eq.17 of Ref-1.\n", - " \n", - " Returns a numpy array of size 2^{m} representing the state vector corresponding to the state\n", - " $$\n", - " \\sqrt{\\frac{2}{2^m + 1}} \\sum_{n=0}^{2^{m}-1} \\sin{\\frac{\\pi(n + 1)}{2^{m}+1}}\\ket{n}\n", - " $$\n", - " \n", - " Args:\n", - " m: Number of qubits to prepare the resource state on.\n", - " \n", - " Ref:\n", - " 1) [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]\n", - " (https://arxiv.org/abs/1805.03662)\n", - " Eq. 17\n", - " \"\"\"\n", - " den = 1 + 2 ** m\n", - " norm = np.sqrt(2 / den)\n", - " return norm * np.sin(np.pi * (1 + np.arange(2**m)) / den) \n", - " \n", - "def phase_estimation(walk: cirq_ft.QubitizationWalkOperator, m: int) -> cirq.OP_TREE:\n", - " \"\"\"Heisenberg limited phase estimation circuit for learning eigenphase of `walk`.\n", - " \n", - " The method yields an OPTREE to construct Heisenberg limited phase estimation circuit \n", - " for learning eigenphases of the `walk` operator with `m` bits of accuracy. The \n", - " circuit is implemented as given in Fig.2 of Ref-1.\n", - " \n", - " Args:\n", - " walk: Qubitization walk operator.\n", - " m: Number of bits of accuracy for phase estimation. \n", - " \n", - " Ref:\n", - " 1) [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity]\n", - " (https://arxiv.org/abs/1805.03662)\n", - " Fig. 2\n", - " \"\"\"\n", - " reflect = walk.reflect\n", - " walk_regs = infra.get_named_qubits(walk.signature)\n", - " reflect_regs = {reg.name: walk_regs[reg.name] for reg in reflect.signature}\n", - " \n", - " reflect_controlled = reflect.controlled(control_values=[0])\n", - " walk_controlled = walk.controlled(control_values=[1])\n", - " reflect_op = reflect.on_registers(**reflect_regs)\n", - "\n", - " m_qubits = [cirq.q(f'm_{i}') for i in range(m)]\n", - " state_prep = cirq.StatePreparationChannel(get_resource_state(m), name='chi_m')\n", - "\n", - " yield state_prep.on(*m_qubits)\n", - " yield walk_controlled.on_registers(**walk_regs, control=m_qubits[0])\n", - " for i in range(1, m):\n", - " yield reflect_controlled.on_registers(control=m_qubits[i], **reflect_regs)\n", - " yield walk.on_registers(**walk_regs)\n", - " walk = walk ** 2\n", - " yield reflect_controlled.on_registers(control=m_qubits[i], **reflect_regs)\n", - " \n", - " yield cirq.qft(*m_qubits, inverse=True)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_sites: int = 6\n", - "eps: float = 1e-2\n", - "m_bits: int = 4\n", - "\n", - "circuit = cirq.Circuit(phase_estimation(get_walk_operator_for_1d_Ising_model(num_sites, eps), m=m_bits))\n", - "print(circuit)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resource estimates for 1D Ising model using generic SELECT / PREPARE " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "num_sites: int = 200\n", - "eps: float = 1e-5\n", - "m_bits: int = 14\n", - "\n", - "walk = get_walk_operator_for_1d_Ising_model(num_sites, eps)\n", - "\n", - "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = cirq_ft.t_complexity(circuit[1:-1])\n", - "print(result)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Resource estimates for 2D Hubbard model using specialized SELECT / PREPARE \n", - "Phase estimation of walk operator for 2D Hubbard Model using SELECT and PREPARE circuits from Section V of https://arxiv.org/abs/1805.03662" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "x_dim, y_dim = 20, 20\n", - "t = 20\n", - "mu = 4 * t\n", - "N = x_dim * y_dim * 2\n", - "qlambda = 2 * N * t + (N * mu) // 2\n", - "delta_E = t / 100\n", - "m_bits = int(np.log2(qlambda * np.pi * np.sqrt(2) / delta_E))\n", - "walk = get_walk_operator_for_hubbard_model(x_dim, y_dim, t, mu)\n", - "circuit = cirq.Circuit(phase_estimation(walk, m=m_bits))\n", - "%time result = cirq_ft.t_complexity(circuit[1:-1])\n", - "print(result)" - ] - } - ], - "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": 1 -} \ No newline at end of file diff --git a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py b/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py deleted file mode 100644 index 3564e654037..00000000000 --- a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition.py +++ /dev/null @@ -1,106 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, arithmetic_gates - - -@attr.frozen -class PrepareUniformSuperposition(infra.GateWithRegisters): - r"""Prepares a uniform superposition over first $n$ basis states using $O(log(n))$ T-gates. - - Performs a single round of amplitude amplification and prepares a uniform superposition over - the first $n$ basis states $|0>, |1>, ..., |n - 1>$. The expected T-complexity should be - $10 * log(L) + 2 * K$ T-gates and $2$ single qubit rotation gates, where $n = L * 2^K$. - - However, the current T-complexity is $12 * log(L)$ T-gates and $2 + 2 * (K + log(L))$ rotations - because of two open issues: - - https://github.com/quantumlib/cirq-qubitization/issues/233 and - - https://github.com/quantumlib/cirq-qubitization/issues/235 - - Args: - n: The gate prepares a uniform superposition over first $n$ basis states. - cv: Control values for each control qubit. If specified, a controlled version - of the gate is constructed. - - References: - See Fig 12 of https://arxiv.org/abs/1805.03662 for more details. - """ - - n: int - cv: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(controls=len(self.cv), target=(self.n - 1).bit_length()) - - def __repr__(self) -> str: - return f"cirq_ft.PrepareUniformSuperposition({self.n}, cv={self.cv})" - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - control_symbols = ["@" if cv else "@(0)" for cv in self.cv] - target_symbols = ['target'] * self.signature.get_left('target').total_bits() - target_symbols[0] = f"UNIFORM({self.n})" - return cirq.CircuitDiagramInfo(wire_symbols=control_symbols + target_symbols) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - controls, target = quregs.get('controls', ()), quregs['target'] - # Find K and L as per https://arxiv.org/abs/1805.03662 Fig 12. - n, k = self.n, 0 - while n > 1 and n % 2 == 0: - k += 1 - n = n // 2 - l, logL = int(n), self.signature.get_left('target').total_bits() - k - logL_qubits = target[:logL] - - yield [ - op.controlled_by(*controls, control_values=self.cv) for op in cirq.H.on_each(*target) - ] - if not len(logL_qubits): - return - - ancilla = context.qubit_manager.qalloc(1) - theta = np.arccos(1 - (2 ** np.floor(np.log2(l))) / l) - yield arithmetic_gates.LessThanGate(logL, l).on(*logL_qubits, *ancilla) - yield cirq.Rz(rads=theta)(*ancilla) - yield arithmetic_gates.LessThanGate(logL, l).on(*logL_qubits, *ancilla) - - yield cirq.H.on_each(*logL_qubits) - - and_ancilla = context.qubit_manager.qalloc(len(self.cv) + logL - 2) - and_op = and_gate.And((0,) * logL + self.cv).on_registers( - ctrl=np.asarray([*logL_qubits, *controls])[:, np.newaxis], - junk=np.asarray(and_ancilla)[:, np.newaxis], - target=ancilla, - ) - yield and_op - yield cirq.Rz(rads=theta)(*ancilla) - yield cirq.inverse(and_op) - - yield cirq.H.on_each(*logL_qubits) - context.qubit_manager.qfree([*ancilla, *and_ancilla]) diff --git a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py b/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py deleted file mode 100644 index 5cdb25b8875..00000000000 --- a/cirq-ft/cirq_ft/algos/prepare_uniform_superposition_test.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -from cirq_ft import infra -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize("n", [*range(3, 20), 25, 41]) -@pytest.mark.parametrize("num_controls", [0, 1]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition(n, num_controls): - gate = cirq_ft.PrepareUniformSuperposition(n, cv=[1] * num_controls) - all_qubits = cirq.LineQubit.range(cirq.num_qubits(gate)) - control, target = (all_qubits[:num_controls], all_qubits[num_controls:]) - turn_on_controls = [cirq.X(c) for c in control] - prepare_uniform_op = gate.on(*control, *target) - circuit = cirq.Circuit(turn_on_controls, prepare_uniform_op) - result = cirq.Simulator(dtype=np.complex128).simulate(circuit, qubit_order=all_qubits) - final_target_state = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=list(range(num_controls, num_controls + len(target))), - ) - expected_target_state = np.asarray([np.sqrt(1.0 / n)] * n + [0] * (2 ** len(target) - n)) - cirq.testing.assert_allclose_up_to_global_phase( - expected_target_state, final_target_state, atol=1e-6 - ) - - -@pytest.mark.parametrize("n", [*range(3, 41, 3)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition_t_complexity(n: int): - gate = cirq_ft.PrepareUniformSuperposition(n) - result = cirq_ft.t_complexity(gate) - assert result.rotations <= 2 - # TODO(#235): Uncomputing `LessThanGate` should take 0 T-gates instead of 4 * n - # and therefore the total complexity should come down to `8 * logN` - assert result.t <= 12 * (n - 1).bit_length() - - gate = cirq_ft.PrepareUniformSuperposition(n, cv=(1,)) - result = cirq_ft.t_complexity(gate) - # TODO(#233): Controlled-H is currently counted as a separate rotation, but it can be - # implemented using 2 T-gates. - assert result.rotations <= 2 + 2 * infra.total_bits(gate.signature) - assert result.t <= 12 * (n - 1).bit_length() - - -@allow_deprecated_cirq_ft_use_in_tests -def test_prepare_uniform_superposition_consistent_protocols(): - gate = cirq_ft.PrepareUniformSuperposition(5, cv=(1, 0)) - # Repr - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - # Diagrams - expected_symbols = ('@', '@(0)', 'UNIFORM(5)', 'target', 'target') - assert cirq.circuit_diagram_info(gate).wire_symbols == expected_symbols - # Equality - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5, cv=(1, 0)), - cirq_ft.PrepareUniformSuperposition(5, cv=[1, 0]), - ) - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5, cv=(0, 1)), - cirq_ft.PrepareUniformSuperposition(5, cv=[0, 1]), - ) - equals_tester.add_equality_group( - cirq_ft.PrepareUniformSuperposition(5), - cirq_ft.PrepareUniformSuperposition(5, cv=()), - cirq_ft.PrepareUniformSuperposition(5, cv=[]), - ) diff --git a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py deleted file mode 100644 index 977111221c7..00000000000 --- a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 -from functools import cached_property -from typing import Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np -from cirq._compat import cached_method -from cirq_ft import infra -from cirq_ft.algos import qrom -from cirq_ft.infra.bit_tools import iter_bits - - -class ProgrammableRotationGateArrayBase(infra.GateWithRegisters): - """Base class for a composite gate to apply multiplexed rotations on target register. - - Programmable rotation gate array is used to apply `M` rotations on a target register, - potentially interleaved with `M - 1` arbitrary unitaries, via the following steps: - - - Represent each rotation angle `theta` as a an integer approximation of `B` bits. - - Use an ancilla register of size `kappa` (a configurable parameter) and load multiplexed - bits of rotations angles, in batches of size at-most `kappa`, using `QROM` reads. - - Thus, a total of `⌈M * B / kappa⌉ + 1` QROM reads are required. - - After every QROM read, apply `kappa` (singly) controlled rotations on the target - register, each controlled on a different qubit from the `kappa` ancilla register. - - Thus, a total of `M * B` controlled rotations are applied on the target register. - - Note that naively applying multiplexed controlled rotations on target register using a unary - iteration loop would require us to apply `O(iteration_length)` controlled rotations for a - single multiplexed rotations array. On the contrary, this approach requires us to apply only - `B` controlled rotations on the target register; which is usually much smaller than the - iteration length. - - Users should derive from this base class and override the `interleaved_unitary` and - `interleaved_unitary_target` abstract methods to specify the information regarding - the unitaries that should be interleaved between `M` rotations. - - For more details, see the reference below: - - References: - Page 45; Section VII.B.1 - [Quantum computing enhanced computational catalysis] - (https://arxiv.org/abs/2007.14460). - Burg, Low et. al. 2021. - """ - - def __init__(self, *angles: Sequence[int], kappa: int, rotation_gate: cirq.Gate): - """Initializes ProgrammableRotationGateArrayBase - - Args: - angles: Sequence of integer-approximated rotation angles s.t. - `rotation_gate ** float_from_integer_approximation(angles[i][k])` should be applied - to the target register when the selection register of ith multiplexed rotation array - stores integer `k`. - kappa: Length of temporary data register to use for storing integer approximated bits - of multiplexed rotation angles. - rotation_gate: Exponential of this gate, depending on `angles`, shall be applied on the - target register. - - Raises: - ValueError: If all multiplexed `angles` sequences are not of the same length. - """ - if len(set(len(thetas) for thetas in angles)) != 1: - raise ValueError("All multiplexed angles sequences to apply must be of same length.") - self._angles = tuple(tuple(thetas) for thetas in angles) - self._selection_bitsize = (len(self._angles[0]) - 1).bit_length() - self._target_bitsize = cirq.num_qubits(rotation_gate) - self._kappa = kappa - self._rotation_gate = rotation_gate - - @property - def kappa(self) -> int: - return self._kappa - - @property - def angles(self) -> Tuple[Tuple[int, ...], ...]: - return self._angles - - @cached_method - def rotation_gate(self, exponent: int = -1) -> cirq.Gate: - """Returns `self._rotation_gate` ** 1 / (2 ** (1 + power))`""" - power = 1 / 2 ** (1 + exponent) - return cirq.pow(self._rotation_gate, power) - - @abc.abstractmethod - def interleaved_unitary( - self, index: int, **qubit_regs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.Operation: - pass - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return (infra.SelectionRegister('selection', self._selection_bitsize, len(self.angles[0])),) - - @cached_property - def kappa_load_target(self) -> Tuple[infra.Register, ...]: - return (infra.Register('kappa_load_target', self.kappa),) - - @cached_property - def rotations_target(self) -> Tuple[infra.Register, ...]: - return (infra.Register('rotations_target', self._target_bitsize),) - - @property - @abc.abstractmethod - def interleaved_unitary_target(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [ - *self.selection_registers, - *self.kappa_load_target, - *self.rotations_target, - *self.interleaved_unitary_target, - ] - ) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - selection, kappa_load_target = quregs.pop('selection'), quregs.pop('kappa_load_target') - rotations_target = quregs.pop('rotations_target') - interleaved_unitary_target = quregs - - # 1. Find a convenient way to process batches of size kappa. - num_bits = sum(max(thetas).bit_length() for thetas in self.angles) - iteration_length = self.selection_registers[0].iteration_length - selection_bitsizes = [s.total_bits() for s in self.selection_registers] - angles_bits = np.zeros(shape=(iteration_length, num_bits), dtype=int) - angles_bit_pow = np.zeros(shape=(num_bits,), dtype=int) - angles_idx = np.zeros(shape=(num_bits,), dtype=int) - st, en = 0, 0 - for i, thetas in enumerate(self.angles): - bit_width = max(thetas).bit_length() - st, en = en, en + bit_width - angles_bits[:, st:en] = [[*iter_bits(t, bit_width)] for t in thetas] - angles_bit_pow[st:en] = [*range(bit_width)][::-1] - angles_idx[st:en] = i - assert en == num_bits - # 2. Process batches of size kappa. - power_of_2s = 2 ** np.arange(self.kappa)[::-1] - last_id = 0 - data = np.zeros(iteration_length, dtype=int) - for st in range(0, num_bits, self.kappa): - en = min(st + self.kappa, num_bits) - data ^= angles_bits[:, st:en].dot(power_of_2s[: en - st]) - yield qrom.QROM( - [data], selection_bitsizes=tuple(selection_bitsizes), target_bitsizes=(self.kappa,) - ).on_registers(selection=selection, target0=kappa_load_target) - data = angles_bits[:, st:en].dot(power_of_2s[: en - st]) - for cqid, bpow, idx in zip(kappa_load_target, angles_bit_pow[st:en], angles_idx[st:en]): - if idx != last_id: - yield self.interleaved_unitary( - last_id, rotations_target=rotations_target, **interleaved_unitary_target - ) - last_id = idx - yield self.rotation_gate(bpow).on(*rotations_target).controlled_by(cqid) - yield qrom.QROM( - [data], selection_bitsizes=tuple(selection_bitsizes), target_bitsizes=(self.kappa,) - ).on_registers(selection=selection, target0=kappa_load_target) - - -class ProgrammableRotationGateArray(ProgrammableRotationGateArrayBase): - """An implementation of `ProgrammableRotationGateArrayBase` base class - - - This implementation of the `ProgrammableRotationGateArray` base class expects - all interleaved_unitaries to act on the `rotations_target` register. - - See docstring of `ProgrammableRotationGateArrayBase` for more details. - """ - - def __init__( - self, - *angles: Sequence[int], - kappa: int, - rotation_gate: cirq.Gate, - interleaved_unitaries: Sequence[cirq.Gate] = (), - ): - super().__init__(*angles, kappa=kappa, rotation_gate=rotation_gate) - if not interleaved_unitaries: - identity_gate = cirq.IdentityGate(infra.total_bits(self.rotations_target)) - interleaved_unitaries = (identity_gate,) * (len(angles) - 1) - assert len(interleaved_unitaries) == len(angles) - 1 - assert all(cirq.num_qubits(u) == self._target_bitsize for u in interleaved_unitaries) - self._interleaved_unitaries = tuple(interleaved_unitaries) - - def interleaved_unitary(self, index: int, **qubit_regs: NDArray[cirq.Qid]) -> cirq.Operation: - return self._interleaved_unitaries[index].on(*qubit_regs['rotations_target']) - - @cached_property - def interleaved_unitary_target(self) -> Tuple[infra.Register, ...]: - return () diff --git a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py b/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py deleted file mode 100644 index 267dabfbe5e..00000000000 --- a/cirq-ft/cirq_ft/algos/programmable_rotation_gate_array_test.py +++ /dev/null @@ -1,149 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Tuple -from numpy.typing import NDArray - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class CustomProgrammableRotationGateArray(cirq_ft.ProgrammableRotationGateArrayBase): - def interleaved_unitary( - self, index: int, **qubit_regs: NDArray[cirq.Qid] # type:ignore[type-var] - ) -> cirq.Operation: - two_qubit_ops_factory = [ - cirq.X(*qubit_regs['unrelated_target']).controlled_by(*qubit_regs['rotations_target']), - cirq.Z(*qubit_regs['unrelated_target']).controlled_by(*qubit_regs['rotations_target']), - ] - return two_qubit_ops_factory[index % 2] - - @cached_property - def interleaved_unitary_target(self) -> Tuple[cirq_ft.Register, ...]: - return tuple(cirq_ft.Signature.build(unrelated_target=1)) - - -def construct_custom_prga(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return CustomProgrammableRotationGateArray(*args, **kwargs) - - -def construct_prga_with_phase(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return cirq_ft.ProgrammableRotationGateArray( - *args, **kwargs, interleaved_unitaries=[cirq.Z] * (len(args) - 1) - ) - - -def construct_prga_with_identity(*args, **kwargs) -> cirq_ft.ProgrammableRotationGateArrayBase: - return cirq_ft.ProgrammableRotationGateArray(*args, **kwargs) - - -@pytest.mark.parametrize( - "angles", [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]], [[3, 4, 5], [10, 11, 12]]] -) -@pytest.mark.parametrize("kappa", [*range(1, 12)]) -@pytest.mark.parametrize( - "constructor", [construct_custom_prga, construct_prga_with_phase, construct_prga_with_identity] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_programmable_rotation_gate_array(angles, kappa, constructor): - rotation_gate = cirq.X - programmable_rotation_gate = constructor(*angles, kappa=kappa, rotation_gate=rotation_gate) - greedy_mm = cirq.GreedyQubitManager(prefix="_a") - g = cirq_ft.testing.GateHelper( - programmable_rotation_gate, context=cirq.DecompositionContext(greedy_mm) - ) - decomposed_circuit = cirq.Circuit(cirq.I.on_each(*g.all_qubits)) + g.decomposed_circuit - # Get interleaved unitaries. - interleaved_unitaries = [ - programmable_rotation_gate.interleaved_unitary(i, **g.quregs) - for i in range(len(angles) - 1) - ] - # Get qubits on which rotations + unitaries act. - rotations_and_unitary_registers = cirq_ft.Signature( - [ - *programmable_rotation_gate.rotations_target, - *programmable_rotation_gate.interleaved_unitary_target, - ] - ) - rotations_and_unitary_qubits = infra.merge_qubits(rotations_and_unitary_registers, **g.quregs) - - # Build circuit. - simulator = cirq.Simulator(dtype=np.complex128) - - def rotation_ops(theta: int) -> cirq.OP_TREE: - # OP-TREE to apply rotation, by integer-approximated angle `theta`, on the target register. - for i, b in enumerate(bin(theta)[2:][::-1]): - if b == '1': - yield cirq.pow(rotation_gate.on(*g.quregs['rotations_target']), (1 / 2 ** (1 + i))) - - for selection_integer in range(len(angles[0])): - # Set bits in initial_state s.t. selection register stores `selection_integer`. - qubit_vals = {x: 0 for x in g.all_qubits} - qubit_vals.update( - zip( - g.quregs['selection'], - iter_bits(selection_integer, g.r.get_left('selection').total_bits()), - ) - ) - initial_state = [qubit_vals[x] for x in g.all_qubits] - # Actual circuit simulation. - result = simulator.simulate( - decomposed_circuit, initial_state=initial_state, qubit_order=g.all_qubits - ) - ru_state_vector = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=[g.all_qubits.index(q) for q in rotations_and_unitary_qubits], - ) - # Expected circuit simulation by applying rotations directly. - expected_circuit = cirq.Circuit( - [ - [rotation_ops(angles[i][selection_integer]), u] - for i, u in enumerate(interleaved_unitaries) - ], - rotation_ops(angles[-1][selection_integer]), - ) - expected_ru_state_vector = simulator.simulate( - expected_circuit, qubit_order=rotations_and_unitary_qubits - ).final_state_vector - # Assert that actual and expected match. - cirq.testing.assert_allclose_up_to_global_phase( - ru_state_vector, expected_ru_state_vector, atol=1e-8 - ) - # Assert that all other qubits are returned to their original state. - ancilla_indices = [ - g.all_qubits.index(q) for q in g.all_qubits if q not in rotations_and_unitary_qubits - ] - ancilla_state_vector = cirq.sub_state_vector( - result.final_state_vector, keep_indices=ancilla_indices - ) - expected_ancilla_state_vector = cirq.quantum_state( - [initial_state[x] for x in ancilla_indices], - qid_shape=(2,) * len(ancilla_indices), - dtype=np.complex128, - ).state_vector() - cirq.testing.assert_allclose_up_to_global_phase( - ancilla_state_vector, expected_ancilla_state_vector, atol=1e-8 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_programmable_rotation_gate_array_consistent(): - with pytest.raises(ValueError, match='must be of same length'): - _ = CustomProgrammableRotationGateArray([1, 2], [1], kappa=1, rotation_gate=cirq.X) diff --git a/cirq-ft/cirq_ft/algos/qrom.ipynb b/cirq-ft/cirq_ft/algos/qrom.ipynb deleted file mode 100644 index 2f37c079d54..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom.ipynb +++ /dev/null @@ -1,110 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "773cf7e2", - "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": "ffe91e97", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# QROM" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2737c79f", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "5d6a18ce", - "metadata": { - "cq.autogen": "_make_QROM.md" - }, - "source": [ - "## `QROM`\n", - "Gate to load data[l] in the target register when the selection stores an index l.\n", - "\n", - "In the case of multi-dimensional data[p,q,r,...] we use of multple name\n", - "selection signature [p, q, r, ...] to index and load the data.\n", - "\n", - "#### Parameters\n", - " - `data`: List of numpy ndarrays specifying the data to load. If the length of this list is greater than one then we use the same selection indices to load each dataset (for example, to load alt and keep data for state preparation). Each data set is required to have the same shape and to be of integer type.\n", - " - `selection_bitsizes`: The number of bits used to represent each selection register corresponding to the size of each dimension of the array. Should be the same length as the shape of each of the datasets.\n", - " - `target_bitsizes`: The number of bits used to represent the data signature. This can be deduced from the maximum element of each of the datasets. Should be of length len(data), i.e. the number of datasets.\n", - " - `num_controls`: The number of control signature.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "7a91a2db", - "metadata": { - "cq.autogen": "_make_QROM.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.QROM([np.array([1, 2, 3, 4, 5])], selection_bitsizes=(3,), target_bitsizes=(3,))\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "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 -} diff --git a/cirq-ft/cirq_ft/algos/qrom.py b/cirq-ft/cirq_ft/algos/qrom.py deleted file mode 100644 index 86f04b44b9d..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom.py +++ /dev/null @@ -1,190 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Callable, Sequence, Tuple - -import attr -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import and_gate, unary_iteration_gate -from numpy.typing import ArrayLike, NDArray - - -@cirq.value_equality() -@attr.frozen -class QROM(unary_iteration_gate.UnaryIterationGate): - """Gate to load data[l] in the target register when the selection stores an index l. - - In the case of multi-dimensional data[p,q,r,...] we use multiple named - selection signature [p, q, r, ...] to index and load the data. Here `p, q, r, ...` - correspond to signature named `selection0`, `selection1`, `selection2`, ... etc. - - When the input data elements contain consecutive entries of identical data elements to - load, the QROM also implements the "variable-spaced" QROM optimization described in Ref[2]. - - Args: - data: List of numpy ndarrays specifying the data to load. If the length - of this list is greater than one then we use the same selection indices - to load each dataset (for example, to load alt and keep data for - state preparation). Each data set is required to have the same - shape and to be of integer type. - selection_bitsizes: The number of bits used to represent each selection register - corresponding to the size of each dimension of the array. Should be - the same length as the shape of each of the datasets. - target_bitsizes: The number of bits used to represent the data - signature. This can be deduced from the maximum element of each of the - datasets. Should be of length len(data), i.e. the number of datasets. - num_controls: The number of control signature. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - - [Compilation of Fault-Tolerant Quantum Heuristics for Combinatorial Optimization] - (https://arxiv.org/abs/2007.07391). - Babbush et. al. (2020). Figure 3. - """ - - data: Sequence[NDArray] - selection_bitsizes: Tuple[int, ...] - target_bitsizes: Tuple[int, ...] - num_controls: int = 0 - - @classmethod - def build(cls, *data: ArrayLike, num_controls: int = 0) -> 'QROM': - _data = [np.array(d, dtype=int) for d in data] - selection_bitsizes = tuple((s - 1).bit_length() for s in _data[0].shape) - target_bitsizes = tuple(max(int(np.max(d)).bit_length(), 1) for d in data) - return QROM( - data=_data, - selection_bitsizes=selection_bitsizes, - target_bitsizes=target_bitsizes, - num_controls=num_controls, - ) - - def __attrs_post_init__(self): - shapes = [d.shape for d in self.data] - assert all([isinstance(s, int) for s in self.selection_bitsizes]) - assert all([isinstance(t, int) for t in self.target_bitsizes]) - assert len(set(shapes)) == 1, f"Data must all have the same size: {shapes}" - assert len(self.target_bitsizes) == len(self.data), ( - f"len(self.target_bitsizes)={len(self.target_bitsizes)} should be same as " - f"len(self.data)={len(self.data)}" - ) - assert all( - t >= int(np.max(d)).bit_length() for t, d in zip(self.target_bitsizes, self.data) - ) - assert isinstance(self.selection_bitsizes, tuple) - assert isinstance(self.target_bitsizes, tuple) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if not self.num_controls else (infra.Register('control', self.num_controls),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - if len(self.data[0].shape) == 1: - return ( - infra.SelectionRegister( - 'selection', self.selection_bitsizes[0], self.data[0].shape[0] - ), - ) - else: - return tuple( - infra.SelectionRegister(f'selection{i}', sb, l) - for i, (l, sb) in enumerate(zip(self.data[0].shape, self.selection_bitsizes)) - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Register(f'target{i}', l) for i, l in enumerate(self.target_bitsizes) if l - ) - - def __repr__(self) -> str: - data_repr = f"({','.join(cirq._compat.proper_repr(d) for d in self.data)})" - selection_repr = repr(self.selection_bitsizes) - target_repr = repr(self.target_bitsizes) - return ( - f"cirq_ft.QROM({data_repr}, selection_bitsizes={selection_repr}, " - f"target_bitsizes={target_repr}, num_controls={self.num_controls})" - ) - - def _load_nth_data( - self, - selection_idx: Tuple[int, ...], - gate: Callable[[cirq.Qid], cirq.Operation], - **target_regs: NDArray[cirq.Qid], # type: ignore[type-var] - ) -> cirq.OP_TREE: - for i, d in enumerate(self.data): - target = target_regs.get(f'target{i}', ()) - for q, bit in zip(target, f'{int(d[selection_idx]):0{len(target)}b}'): - if int(bit): - yield gate(q) - - def decompose_zero_selection( - self, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - controls = infra.merge_qubits(self.control_registers, **quregs) - target_regs = {reg.name: quregs[reg.name] for reg in self.target_registers} - zero_indx = (0,) * len(self.data[0].shape) - if self.num_controls == 0: - yield self._load_nth_data(zero_indx, cirq.X, **target_regs) - elif self.num_controls == 1: - yield self._load_nth_data(zero_indx, lambda q: cirq.CNOT(controls[0], q), **target_regs) - else: - and_ancilla = context.qubit_manager.qalloc(len(controls) - 2) - and_target = context.qubit_manager.qalloc(1)[0] - multi_controlled_and = and_gate.And((1,) * len(controls)).on_registers( - ctrl=np.array(controls)[:, np.newaxis], - junk=np.array(and_ancilla)[:, np.newaxis], - target=and_target, - ) - yield multi_controlled_and - yield self._load_nth_data(zero_indx, lambda q: cirq.CNOT(and_target, q), **target_regs) - yield cirq.inverse(multi_controlled_and) - context.qubit_manager.qfree(and_ancilla + [and_target]) - - def _break_early(self, selection_index_prefix: Tuple[int, ...], l: int, r: int): - for data in self.data: - unique_element = np.unique(data[selection_index_prefix][l:r]) - if len(unique_element) > 1: - return False - return True - - def nth_operation( - self, context: cirq.DecompositionContext, control: cirq.Qid, **kwargs - ) -> cirq.OP_TREE: - selection_idx = tuple(kwargs[reg.name] for reg in self.selection_registers) - target_regs = {reg.name: kwargs[reg.name] for reg in self.target_registers} - yield self._load_nth_data(selection_idx, lambda q: cirq.CNOT(control, q), **target_regs) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * self.num_controls - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - for i, target in enumerate(self.target_registers): - wire_symbols += [f"QROM_{i}"] * target.total_bits() - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int): - if power in [1, -1]: - return self - return NotImplemented # pragma: no cover - - def _value_equality_values_(self): - data_tuple = tuple(tuple(d.flatten()) for d in self.data) - return (self.selection_registers, self.target_registers, self.control_registers, data_tuple) diff --git a/cirq-ft/cirq_ft/algos/qrom_test.py b/cirq-ft/cirq_ft/algos/qrom_test.py deleted file mode 100644 index d0a960370e2..00000000000 --- a/cirq-ft/cirq_ft/algos/qrom_test.py +++ /dev/null @@ -1,292 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 itertools - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "data,num_controls", - [ - pytest.param( - data, - num_controls, - id=f"{num_controls}-data{idx}", - marks=pytest.mark.slow if num_controls == 2 and idx == 2 else (), - ) - for idx, data in enumerate( - [[[1, 2, 3, 4, 5]], [[1, 2, 3], [4, 5, 10]], [[1], [2], [3], [4], [5], [6]]] - ) - for num_controls in [0, 1, 2] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_1d(data, num_controls): - qrom = cirq_ft.QROM.build(*data, num_controls=num_controls) - greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) - g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) - decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) - inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) - - assert ( - len(inverse.all_qubits()) - <= infra.total_bits(g.r) + g.r.get_left('selection').total_bits() + num_controls - ) - assert inverse.all_qubits() == decomposed_circuit.all_qubits() - - for selection_integer in range(len(data[0])): - for cval in range(2): - qubit_vals = {x: 0 for x in g.all_qubits} - qubit_vals.update( - zip( - g.quregs['selection'], - iter_bits(selection_integer, g.r.get_left('selection').total_bits()), - ) - ) - if num_controls: - qubit_vals.update(zip(g.quregs['control'], [cval] * num_controls)) - - initial_state = [qubit_vals[x] for x in g.all_qubits] - if cval or not num_controls: - for ti, d in enumerate(data): - target = g.quregs[f"target{ti}"] - qubit_vals.update(zip(target, iter_bits(d[selection_integer], len(target)))) - final_state = [qubit_vals[x] for x in g.all_qubits] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit, g.all_qubits, initial_state, final_state - ) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit + inverse, g.all_qubits, initial_state, initial_state - ) - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit + inverse, g.all_qubits, final_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_diagram(): - d0 = np.array([1, 2, 3]) - d1 = np.array([4, 5, 6]) - qrom = cirq_ft.QROM.build(d0, d1) - q = cirq.LineQubit.range(cirq.num_qubits(qrom)) - circuit = cirq.Circuit(qrom.on_registers(**infra.split_qubits(qrom.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───In─────── - │ -1: ───In─────── - │ -2: ───QROM_0─── - │ -3: ───QROM_0─── - │ -4: ───QROM_1─── - │ -5: ───QROM_1─── - │ -6: ───QROM_1───""", - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_repr(): - data = [np.array([1, 2]), np.array([3, 5])] - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM(data, selection_bitsizes, target_bitsizes) - cirq.testing.assert_equivalent_repr(qrom, setup_code="import cirq_ft\nimport numpy as np") - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('qrom') - - -@pytest.mark.parametrize( - "data", [[[1, 2, 3, 4, 5]], [[1, 2, 3], [4, 5, 10]], [[1], [2], [3], [4], [5], [6]]] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(data): - qrom = cirq_ft.QROM.build(*data) - g = cirq_ft.testing.GateHelper(qrom) - n = np.prod(qrom.data[0].shape) - assert cirq_ft.t_complexity(g.gate) == cirq_ft.t_complexity(g.operation) - assert cirq_ft.t_complexity(g.gate).t == max(0, 4 * n - 8), n - - -def _assert_qrom_has_diagram(qrom: cirq_ft.QROM, expected_diagram: str): - gh = cirq_ft.testing.GateHelper(qrom) - op = gh.operation - context = cirq.DecompositionContext(qubit_manager=cirq.GreedyQubitManager(prefix="anc")) - circuit = cirq.Circuit(cirq.decompose_once(op, context=context)) - selection = [ - *itertools.chain.from_iterable(gh.quregs[reg.name] for reg in qrom.selection_registers) - ] - selection = [q for q in selection if q in circuit.all_qubits()] - anc = sorted(set(circuit.all_qubits()) - set(op.qubits)) - selection_and_anc = (selection[0],) + sum(zip(selection[1:], anc), ()) - qubit_order = cirq.QubitOrder.explicit(selection_and_anc, fallback=cirq.QubitOrder.DEFAULT) - cirq.testing.assert_has_diagram(circuit, expected_diagram, qubit_order=qubit_order) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_variable_spacing(): - # Tests for variable spacing optimization applied from https://arxiv.org/abs/2007.07391 - data = [1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 7, 7, 8, 8, 8, 8] # Figure 3a. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (8 - 2) * 4 - data = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5] # Figure 3b. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (5 - 2) * 4 - data = [1, 2, 3, 4, 4, 4, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7] # Negative test: t count is not (g-2)*4 - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data)).t == (8 - 2) * 4 - # Works as expected when multiple data arrays are to be loaded. - data = [1, 2, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5] - # (a) Both data sequences are identical - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data, data)).t == (5 - 2) * 4 - # (b) Both data sequences have identical structure, even though the elements are not same. - assert cirq_ft.t_complexity(cirq_ft.QROM.build(data, 2 * np.array(data))).t == (5 - 2) * 4 - # Works as expected when multidimensional input data is to be loaded - qrom = cirq_ft.QROM.build( - np.array( - [ - [1, 1, 1, 1, 1, 1, 1, 1], - [1, 1, 1, 1, 1, 1, 1, 1], - [2, 2, 2, 2, 2, 2, 2, 2], - [2, 2, 2, 2, 2, 2, 2, 2], - ] - ) - ) - # Value to be loaded depends only the on the first bit of outer loop. - _assert_qrom_has_diagram( - qrom, - r''' -selection00: ───X───@───X───@─── - │ │ -target00: ──────────┼───────X─── - │ -target01: ──────────X─────────── - ''', - ) - # When inner loop range is not a power of 2, the inner segment tree cannot be skipped. - qrom = cirq_ft.QROM.build( - np.array( - [[1, 1, 1, 1, 1, 1], [1, 1, 1, 1, 1, 1], [2, 2, 2, 2, 2, 2], [2, 2, 2, 2, 2, 2]], - dtype=int, - ) - ) - _assert_qrom_has_diagram( - qrom, - r''' -selection00: ───X───@─────────@───────@──────X───@─────────@───────@────── - │ │ │ │ │ │ -selection10: ───────(0)───────┼───────@──────────(0)───────┼───────@────── - │ │ │ │ │ │ -anc_1: ─────────────And───@───X───@───And†───────And───@───X───@───And†─── - │ │ │ │ -target00: ────────────────┼───────┼────────────────────X───────X────────── - │ │ -target01: ────────────────X───────X─────────────────────────────────────── - ''', - ) - # No T-gates needed if all elements to load are identical. - assert cirq_ft.t_complexity(cirq_ft.QROM.build([3, 3, 3, 3])).t == 0 - - -@pytest.mark.parametrize( - "data,num_controls", - [ - pytest.param( - data, - num_controls, - id=f"{num_controls}-data{idx}", - marks=pytest.mark.slow if num_controls == 2 and idx == 0 else (), - ) - for idx, data in enumerate( - [ - [np.arange(6).reshape(2, 3), 4 * np.arange(6).reshape(2, 3)], - [np.arange(8).reshape(2, 2, 2)], - ] - ) - for num_controls in [0, 1, 2] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_multi_dim(data, num_controls): - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM( - data, - selection_bitsizes=selection_bitsizes, - target_bitsizes=target_bitsizes, - num_controls=num_controls, - ) - greedy_mm = cirq.GreedyQubitManager('a', maximize_reuse=True) - g = cirq_ft.testing.GateHelper(qrom, context=cirq.DecompositionContext(greedy_mm)) - decomposed_circuit = cirq.Circuit(cirq.decompose(g.operation, context=g.context)) - inverse = cirq.Circuit(cirq.decompose(g.operation**-1, context=g.context)) - - assert ( - len(inverse.all_qubits()) - <= infra.total_bits(g.r) + infra.total_bits(qrom.selection_registers) + num_controls - ) - assert inverse.all_qubits() == decomposed_circuit.all_qubits() - - lens = tuple(reg.total_bits() for reg in qrom.selection_registers) - for idxs in itertools.product(*[range(dim) for dim in data[0].shape]): - qubit_vals = {x: 0 for x in g.all_qubits} - for cval in range(2): - if num_controls: - qubit_vals.update(zip(g.quregs['control'], [cval] * num_controls)) - for isel in range(len(idxs)): - qubit_vals.update( - zip(g.quregs[f'selection{isel}'], iter_bits(idxs[isel], lens[isel])) - ) - initial_state = [qubit_vals[x] for x in g.all_qubits] - if cval or not num_controls: - for ti, d in enumerate(data): - target = g.quregs[f"target{ti}"] - qubit_vals.update(zip(target, iter_bits(int(d[idxs]), len(target)))) - final_state = [qubit_vals[x] for x in g.all_qubits] - qubit_vals = {x: 0 for x in g.all_qubits} - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - decomposed_circuit, g.all_qubits, initial_state, final_state - ) - - -@pytest.mark.parametrize( - "data", - [ - [np.arange(6, dtype=int).reshape(2, 3), 4 * np.arange(6, dtype=int).reshape(2, 3)], - [np.arange(8, dtype=int).reshape(2, 2, 2)], - ], -) -@pytest.mark.parametrize("num_controls", [0, 1, 2]) -@allow_deprecated_cirq_ft_use_in_tests -def test_ndim_t_complexity(data, num_controls): - selection_bitsizes = tuple((s - 1).bit_length() for s in data[0].shape) - target_bitsizes = tuple(int(np.max(d)).bit_length() for d in data) - qrom = cirq_ft.QROM(data, selection_bitsizes, target_bitsizes, num_controls=num_controls) - g = cirq_ft.testing.GateHelper(qrom) - n = data[0].size - assert cirq_ft.t_complexity(g.gate) == cirq_ft.t_complexity(g.operation) - assert cirq_ft.t_complexity(g.gate).t == max(0, 4 * n - 8 + 4 * num_controls) diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb b/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb deleted file mode 100644 index f76d30aa8ff..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.ipynb +++ /dev/null @@ -1,124 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "13836333", - "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": "79886234", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "14d0f193", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "b6fe1c8e", - "metadata": { - "cq.autogen": "_make_QubitizationWalkOperator.md" - }, - "source": [ - "## `QubitizationWalkOperator`\n", - "Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE.\n", - "\n", - "Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of\n", - "two reflections $R_{L} = (2|L>$ of $H$ with eigenvalue $E_k$, $|\\ell>|k>$ and\n", - "an orthogonal state $\\phi_{k}$ span the irreducible two-dimensional space that $|\\ell>|k>$ is\n", - "in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of\n", - "$-2arccos(E_{k} / \\lambda)$ s.t. $W = e^{i arccos(E_k / \\lambda) Y}$.\n", - "\n", - "Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$\n", - "s.t. $spectrum(H) = \\lambda cos(arg(spectrum(W)))$ where $arg(e^{i\\phi}) = \\phi$.\n", - "\n", - "#### Parameters\n", - " - `select`: The SELECT lcu gate implementing $SELECT=\\sum_{l}|l> = \\sum_{l=0}^{L - 1}\\sqrt{\\frac{w_{l}}{\\lambda}} |l> = |\\ell>$\n", - " - `control_val`: If 0/1, a controlled version of the walk operator is constructed. Defaults to None, in which case the resulting walk operator is not controlled.\n", - " - `power`: Constructs $W^{power}$ by repeatedly decomposing into `power` copies of $W$. Defaults to 1. \n", - "\n", - "#### References\n", - "[Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Figure 1.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1847c98d", - "metadata": { - "cq.autogen": "_make_QubitizationWalkOperator.py" - }, - "outputs": [], - "source": [ - "from cirq_ft.algos.qubitization_walk_operator_test import get_walk_operator_for_1d_Ising_model\n", - "\n", - "g = cq_testing.GateHelper(\n", - " get_walk_operator_for_1d_Ising_model(4, 2e-1)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "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/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py b/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py deleted file mode 100644 index 2c59b238c2d..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator.py +++ /dev/null @@ -1,159 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import reflection_using_prepare, select_and_prepare - - -@attr.frozen(cache_hash=True) -class QubitizationWalkOperator(infra.GateWithRegisters): - r"""Constructs a Szegedy Quantum Walk operator using LCU oracles SELECT and PREPARE. - - Constructs a Szegedy quantum walk operator $W = R_{L} . SELECT$, which is a product of - two reflections $R_{L} = (2|L>$ of $H$ with eigenvalue $E_k$, $|\ell>|k>$ and - an orthogonal state $\phi_{k}$ span the irreducible two-dimensional space that $|\ell>|k>$ is - in under the action of $W$. In this space, $W$ implements a Pauli-Y rotation by an angle of - $-2arccos(E_{k} / \lambda)$ s.t. $W = e^{i arccos(E_k / \lambda) Y}$. - - Thus, the walk operator $W$ encodes the spectrum of $H$ as a function of eigenphases of $W$ - s.t. $spectrum(H) = \lambda cos(arg(spectrum(W)))$ where $arg(e^{i\phi}) = \phi$. - - Args: - select: The SELECT lcu gate implementing $SELECT=\sum_{l}|l> = \sum_{l=0}^{L - 1}\sqrt{\frac{w_{l}}{\lambda}} |l> = |\ell>$ - control_val: If 0/1, a controlled version of the walk operator is constructed. Defaults to - None, in which case the resulting walk operator is not controlled. - power: Constructs $W^{power}$ by repeatedly decomposing into `power` copies of $W$. - Defaults to 1. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - """ - - select: select_and_prepare.SelectOracle - prepare: select_and_prepare.PrepareOracle - control_val: Optional[int] = None - power: int = 1 - - def __attrs_post_init__(self): - assert self.select.control_registers == self.reflect.control_registers - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.select.control_registers - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.prepare.selection_registers - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return self.select.target_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - @cached_property - def reflect(self) -> reflection_using_prepare.ReflectionUsingPrepare: - return reflection_using_prepare.ReflectionUsingPrepare( - self.prepare, control_val=self.control_val - ) - - def decompose_from_registers( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - select_reg = {reg.name: quregs[reg.name] for reg in self.select.signature} - select_op = self.select.on_registers(**select_reg) - - reflect_reg = {reg.name: quregs[reg.name] for reg in self.reflect.signature} - reflect_op = self.reflect.on_registers(**reflect_reg) - for _ in range(self.power): - yield select_op - yield reflect_op - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if self.control_val else '@(0)'] * infra.total_bits( - self.control_registers - ) - wire_symbols += ['W'] * ( - infra.total_bits(self.signature) - infra.total_bits(self.control_registers) - ) - wire_symbols[-1] = f'W^{self.power}' if self.power != 1 else 'W' - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'QubitizationWalkOperator': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - c_select = self.select.controlled(control_values=control_values) - assert isinstance(c_select, select_and_prepare.SelectOracle) - return QubitizationWalkOperator( - c_select, self.prepare, control_val=control_values[0], power=self.power - ) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) - - def with_power(self, new_power: int) -> 'QubitizationWalkOperator': - return QubitizationWalkOperator( - self.select, self.prepare, control_val=self.control_val, power=new_power - ) - - def __repr__(self) -> str: - return ( - f'cirq_ft.QubitizationWalkOperator(' - f'{self.select}, ' - f'{self.prepare}, ' - f'{self.control_val}, ' - f'{self.power})' - ) - - def __pow__(self, power: int): - return self.with_power(self.power * power) - - def _t_complexity_(self): - if self.power > 1: - return self.power * infra.t_complexity(self.with_power(1)) - return NotImplemented diff --git a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py b/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py deleted file mode 100644 index 5506029ea62..00000000000 --- a/cirq-ft/cirq_ft/algos/qubitization_walk_operator_test.py +++ /dev/null @@ -1,250 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.algos.generic_select_test import get_1d_Ising_hamiltonian -from cirq_ft.algos.reflection_using_prepare_test import greedily_allocate_ancilla, keep -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -def walk_operator_for_pauli_hamiltonian( - ham: cirq.PauliSum, eps: float -) -> cirq_ft.QubitizationWalkOperator: - q = sorted(ham.qubits) - ham_dps = [ps.dense(q) for ps in ham] - ham_coeff = [abs(ps.coefficient.real) for ps in ham] - prepare = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - ham_coeff, probability_epsilon=eps - ) - select = cirq_ft.GenericSelect( - infra.total_bits(prepare.selection_registers), - select_unitaries=ham_dps, - target_bitsize=len(q), - ) - return cirq_ft.QubitizationWalkOperator(select=select, prepare=prepare) - - -def get_walk_operator_for_1d_Ising_model( - num_sites: int, eps: float -) -> cirq_ft.QubitizationWalkOperator: - ham = get_1d_Ising_hamiltonian(cirq.LineQubit.range(num_sites)) - return walk_operator_for_pauli_hamiltonian(ham, eps) - - -@pytest.mark.slow -@pytest.mark.parametrize('num_sites,eps', [(4, 2e-1), (3, 1e-1)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator(num_sites: int, eps: float): - ham = get_1d_Ising_hamiltonian(cirq.LineQubit.range(num_sites)) - ham_coeff = [abs(ps.coefficient.real) for ps in ham] - qubitization_lambda = np.sum(ham_coeff) - - walk = walk_operator_for_pauli_hamiltonian(ham, eps) - - g = cirq_ft.testing.GateHelper(walk) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - walk_circuit = cirq.Circuit( - cirq.decompose(g.operation, keep=keep, on_stuck_raise=None, context=context) - ) - - L_state = np.zeros(2 ** len(g.quregs['selection'])) - L_state[: len(ham_coeff)] = np.sqrt(ham_coeff / qubitization_lambda) - - greedy_mm = cirq.GreedyQubitManager('ancilla', maximize_reuse=True) - walk_circuit = cirq.map_clean_and_borrowable_qubits(walk_circuit, qm=greedy_mm) - assert len(walk_circuit.all_qubits()) < 23 - qubit_order = cirq.QubitOrder.explicit( - [*g.quregs['selection'], *g.quregs['target']], fallback=cirq.QubitOrder.DEFAULT - ) - - sim = cirq.Simulator(dtype=np.complex128) - - eigen_values, eigen_vectors = np.linalg.eigh(ham.matrix()) - for eig_idx, eig_val in enumerate(eigen_values): - # Applying the walk operator W on an initial state |L>|k> - K_state = eigen_vectors[:, eig_idx].flatten() - prep_L_K = cirq.Circuit( - cirq.StatePreparationChannel(L_state, name="PREP_L").on(*g.quregs['selection']), - cirq.StatePreparationChannel(K_state, name="PREP_K").on(*g.quregs['target']), - ) - # Initial state: |L>|k> - L_K = sim.simulate(prep_L_K, qubit_order=qubit_order).final_state_vector - - prep_walk_circuit = prep_L_K + walk_circuit - # Final state: W|L>|k>|temp> with |temp> register traced out. - final_state = sim.simulate(prep_walk_circuit, qubit_order=qubit_order).final_state_vector - final_state = final_state.reshape(len(L_K), -1).sum(axis=1) - - # Overlap: = E_{k} / lambda - overlap = np.vdot(L_K, final_state) - cirq.testing.assert_allclose_up_to_global_phase( - overlap, eig_val / qubitization_lambda, atol=1e-6 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator_diagrams(): - num_sites, eps = 4, 1e-1 - walk = get_walk_operator_for_1d_Ising_model(num_sites, eps) - # 1. Diagram for $W = SELECT.R_{L}$ - qu_regs = infra.get_named_qubits(walk.signature) - walk_op = walk.on_registers(**qu_regs) - circuit = cirq.Circuit(cirq.decompose_once(walk_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ───In──────────────R_L─── - │ │ -selection1: ───In──────────────R_L─── - │ │ -selection2: ───In──────────────R_L─── - │ -target0: ──────GenericSelect───────── - │ -target1: ──────GenericSelect───────── - │ -target2: ──────GenericSelect───────── - │ -target3: ──────GenericSelect───────── -''', - ) - # 2. Diagram for $W^{2} = SELECT.R_{L}.SELCT.R_{L}$ - walk_squared_op = walk.with_power(2).on_registers(**qu_regs) - circuit = cirq.Circuit(cirq.decompose_once(walk_squared_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ───In──────────────R_L───In──────────────R_L─── - │ │ │ │ -selection1: ───In──────────────R_L───In──────────────R_L─── - │ │ │ │ -selection2: ───In──────────────R_L───In──────────────R_L─── - │ │ -target0: ──────GenericSelect─────────GenericSelect───────── - │ │ -target1: ──────GenericSelect─────────GenericSelect───────── - │ │ -target2: ──────GenericSelect─────────GenericSelect───────── - │ │ -target3: ──────GenericSelect─────────GenericSelect───────── -''', - ) - # 3. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ - controlled_walk_op = walk.controlled().on_registers(**qu_regs, control=cirq.q('control')) - circuit = cirq.Circuit(cirq.decompose_once(controlled_walk_op)) - cirq.testing.assert_has_diagram( - circuit, - ''' -control: ──────@───────────────@───── - │ │ -selection0: ───In──────────────R_L─── - │ │ -selection1: ───In──────────────R_L─── - │ │ -selection2: ───In──────────────R_L─── - │ -target0: ──────GenericSelect───────── - │ -target1: ──────GenericSelect───────── - │ -target2: ──────GenericSelect───────── - │ -target3: ──────GenericSelect───────── -''', - ) - # 4. Diagram for $Ctrl-W = Ctrl-SELECT.Ctrl-R_{L}$ in terms of $Ctrl-SELECT$ and $PREPARE$. - gateset_to_keep = cirq.Gateset( - cirq_ft.GenericSelect, - cirq_ft.StatePreparationAliasSampling, - cirq_ft.MultiControlPauli, - cirq.X, - ) - - def keep(op): - ret = op in gateset_to_keep - if op.gate is not None and isinstance(op.gate, cirq.ops.raw_types._InverseCompositeGate): - ret |= op.gate._original in gateset_to_keep - return ret - - circuit = cirq.Circuit(cirq.decompose(controlled_walk_op, keep=keep, on_stuck_raise=None)) - circuit = greedily_allocate_ancilla(circuit) - # pylint: disable=line-too-long - cirq.testing.assert_has_diagram( - circuit, - ''' -ancilla_0: ────────────────────sigma_mu───────────────────────────────sigma_mu──────────────────────── - │ │ -ancilla_1: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_2: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_3: ────────────────────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_4: ────────────────────keep───────────────────────────────────keep──────────────────────────── - │ │ -ancilla_5: ────────────────────less_than_equal────────────────────────less_than_equal───────────────── - │ │ -control: ──────@───────────────┼───────────────────────────────Z──────┼─────────────────────────────── - │ │ │ │ -selection0: ───In──────────────StatePreparationAliasSampling───@(0)───StatePreparationAliasSampling─── - │ │ │ │ -selection1: ───In──────────────selection───────────────────────@(0)───selection─────────────────────── - │ │ │ │ -selection2: ───In──────────────selection^-1────────────────────@(0)───selection─────────────────────── - │ -target0: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target1: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target2: ──────GenericSelect────────────────────────────────────────────────────────────────────────── - │ -target3: ──────GenericSelect──────────────────────────────────────────────────────────────────────────''', - ) - # pylint: enable=line-too-long - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qubitization_walk_operator_consistent_protocols_and_controlled(): - gate = get_walk_operator_for_1d_Ising_model(4, 1e-1) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - # Test consistent repr - cirq.testing.assert_equivalent_repr( - gate, setup_code='import cirq\nimport cirq_ft\nimport numpy as np' - ) - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('qubitization_walk_operator') - execute_notebook('phase_estimation_of_quantum_walk') diff --git a/cirq-ft/cirq_ft/algos/reflection_using_prepare.py b/cirq-ft/cirq_ft/algos/reflection_using_prepare.py deleted file mode 100644 index 4a084cee4a6..00000000000 --- a/cirq-ft/cirq_ft/algos/reflection_using_prepare.py +++ /dev/null @@ -1,134 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Collection, Optional, Sequence, Tuple, Union -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import multi_control_multi_target_pauli as mcmt -from cirq_ft.algos import select_and_prepare - - -@attr.frozen(cache_hash=True) -class ReflectionUsingPrepare(infra.GateWithRegisters): - """Applies reflection around a state prepared by `prepare_gate` - - Applies $R_{s} = I - 2|s><0|)P$ s.t. $P|0> = |s>$. - Here - $|s>$: The state along which we want to reflect. - $P$: Unitary that prepares that state $|s>$ from the zero state $|0>$ - $R_{s}$: Reflection operator that adds a `-1` phase to all states in the subspace - spanned by $|s>$. - - The composite gate corresponds to implementing the following circuit: - - |control> ------------------ Z ------------------- - | - |L> ---- PREPARE^† --- o --- PREPARE ------- - - - Args: - prepare_gate: An instance of `cq.StatePreparationAliasSampling` gate the corresponds to - `PREPARE`. - control_val: If 0/1, a controlled version of the reflection operator is constructed. - Defaults to None, in which case the resulting reflection operator is not controlled. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Figure 1. - """ - - prepare_gate: select_and_prepare.PrepareOracle - control_val: Optional[int] = None - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return () if self.control_val is None else (infra.Register('control', 1),) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.prepare_gate.selection_registers - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.control_registers, *self.selection_registers]) - - def decompose_from_registers( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - qm = context.qubit_manager - # 0. Allocate new ancillas, if needed. - phase_target = qm.qalloc(1)[0] if self.control_val is None else quregs.pop('control')[0] - state_prep_ancilla = { - reg.name: qm.qalloc(reg.total_bits()) for reg in self.prepare_gate.junk_registers - } - state_prep_selection_regs = quregs - prepare_op = self.prepare_gate.on_registers( - **state_prep_selection_regs, **state_prep_ancilla - ) - # 1. PREPARE† - yield cirq.inverse(prepare_op) - # 2. MultiControlled Z, controlled on |000..00> state. - phase_control = infra.merge_qubits(self.selection_registers, **state_prep_selection_regs) - yield cirq.X(phase_target) if not self.control_val else [] - yield mcmt.MultiControlPauli([0] * len(phase_control), target_gate=cirq.Z).on_registers( - controls=phase_control, target=phase_target - ) - yield cirq.X(phase_target) if not self.control_val else [] - # 3. PREPARE - yield prepare_op - - # 4. Deallocate ancilla. - qm.qfree([q for anc in state_prep_ancilla.values() for q in anc]) - if self.control_val is None: - qm.qfree([phase_target]) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if self.control_val else '@(0)'] * infra.total_bits( - self.control_registers - ) - wire_symbols += ['R_L'] * infra.total_bits(self.selection_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __repr__(self): - return f'cirq_ft.ReflectionUsingPrepare({self.prepare_gate}, {self.control_val})' - - def controlled( - self, - num_controls: Optional[int] = None, - control_values: Optional[ - Union[cirq.ops.AbstractControlValues, Sequence[Union[int, Collection[int]]]] - ] = None, - control_qid_shape: Optional[Tuple[int, ...]] = None, - ) -> 'ReflectionUsingPrepare': - if num_controls is None: - num_controls = 1 - if control_values is None: - control_values = [1] * num_controls - if ( - isinstance(control_values, Sequence) - and isinstance(control_values[0], int) - and len(control_values) == 1 - and self.control_val is None - ): - return ReflectionUsingPrepare(self.prepare_gate, control_val=control_values[0]) - raise NotImplementedError( - f'Cannot create a controlled version of {self} with control_values={control_values}.' - ) diff --git a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py b/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py deleted file mode 100644 index 58618728563..00000000000 --- a/cirq-ft/cirq_ft/algos/reflection_using_prepare_test.py +++ /dev/null @@ -1,231 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 itertools - -import cirq -import cirq_ft -from cirq_ft import infra -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -gateset_to_keep = cirq.Gateset( - cirq_ft.And, - cirq_ft.LessThanGate, - cirq_ft.LessThanEqualGate, - cirq_ft.MultiTargetCSwap, - cirq_ft.MultiTargetCNOT, - cirq_ft.MultiControlPauli, - cirq.H, - cirq.CCNOT, - cirq.CNOT, - cirq.FREDKIN, - cirq.ControlledGate, - cirq.AnyUnitaryGateFamily(1), -) - - -def keep(op: cirq.Operation): - ret = op in gateset_to_keep - if op.gate is not None and isinstance(op.gate, cirq.ops.raw_types._InverseCompositeGate): - ret |= op.gate._original in gateset_to_keep - return ret - - -def greedily_allocate_ancilla(circuit: cirq.AbstractCircuit) -> cirq.Circuit: - greedy_mm = cirq.GreedyQubitManager(prefix="ancilla", maximize_reuse=True) - circuit = cirq.map_clean_and_borrowable_qubits(circuit, qm=greedy_mm) - assert len(circuit.all_qubits()) < 30 - return circuit.unfreeze() - - -def construct_gate_helper_and_qubit_order(gate): - g = cirq_ft.testing.GateHelper(gate) - context = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - circuit = cirq.Circuit( - cirq.decompose(g.operation, keep=keep, on_stuck_raise=None, context=context) - ) - ordered_input = list(itertools.chain(*g.quregs.values())) - qubit_order = cirq.QubitOrder.explicit(ordered_input, fallback=cirq.QubitOrder.DEFAULT) - return g, qubit_order, circuit - - -def get_3q_uniform_dirac_notation(signs): - terms = [ - '0.35|000⟩', - '0.35|001⟩', - '0.35|010⟩', - '0.35|011⟩', - '0.35|100⟩', - '0.35|101⟩', - '0.35|110⟩', - '0.35|111⟩', - ] - ret = terms[0] if signs[0] == '+' else f'-{terms[0]}' - for c, term in zip(signs[1:], terms[1:]): - ret = ret + f' {c} {term}' - return ret - - -@pytest.mark.slow -@pytest.mark.parametrize('num_ones', [*range(5, 9)]) -@pytest.mark.parametrize('eps', [0.01]) -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare(num_ones, eps): - data = [1] * num_ones - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - data, probability_epsilon=eps - ) - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate) - g, qubit_order, decomposed_circuit = construct_gate_helper_and_qubit_order(gate) - decomposed_circuit = greedily_allocate_ancilla(decomposed_circuit) - - initial_state_prep = cirq.Circuit(cirq.H.on_each(*g.quregs['selection'])) - initial_state = cirq.dirac_notation(initial_state_prep.final_state_vector()) - assert initial_state == get_3q_uniform_dirac_notation('++++++++') - result = cirq.Simulator(dtype=np.complex128).simulate( - initial_state_prep + decomposed_circuit, qubit_order=qubit_order - ) - selection = g.quregs['selection'] - prepared_state = result.final_state_vector.reshape(2 ** len(selection), -1).sum(axis=1) - signs = '-' * num_ones + '+' * (9 - num_ones) - assert cirq.dirac_notation(prepared_state) == get_3q_uniform_dirac_notation(signs) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare_diagram(): - data = [1, 2, 3, 4, 5, 6] - eps = 0.1 - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - data, probability_epsilon=eps - ) - # No control - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=None) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' - ┌──────────────────────────────┐ ┌──────────────────────────────┐ -ancilla_0: ─────sigma_mu───────────────────────────────────sigma_mu───────────────────────── - │ │ -ancilla_1: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_2: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_3: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_4: ─────keep───────────────────────────────────────keep───────────────────────────── - │ │ -ancilla_5: ─────less_than_equal────────────────────────────less_than_equal────────────────── - │ │ -ancilla_6: ─────┼────────────────────────────X────Z───────X┼──────────────────────────────── - │ │ │ -selection0: ────StatePreparationAliasSampling─────@(0)─────StatePreparationAliasSampling──── - │ │ │ -selection1: ────selection─────────────────────────@(0)─────selection──────────────────────── - │ │ │ -selection2: ────selection^-1──────────────────────@(0)─────selection──────────────────────── - └──────────────────────────────┘ └──────────────────────────────┘''', - ) - - # Control on `|1>` state - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=1) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' -ancilla_0: ────sigma_mu───────────────────────────────sigma_mu──────────────────────── - │ │ -ancilla_1: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_2: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_3: ────alt────────────────────────────────────alt───────────────────────────── - │ │ -ancilla_4: ────keep───────────────────────────────────keep──────────────────────────── - │ │ -ancilla_5: ────less_than_equal────────────────────────less_than_equal───────────────── - │ │ -control: ──────┼───────────────────────────────Z──────┼─────────────────────────────── - │ │ │ -selection0: ───StatePreparationAliasSampling───@(0)───StatePreparationAliasSampling─── - │ │ │ -selection1: ───selection───────────────────────@(0)───selection─────────────────────── - │ │ │ -selection2: ───selection^-1────────────────────@(0)───selection─────────────────────── -''', - ) - - # Control on `|0>` state - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=0) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - circuit = greedily_allocate_ancilla(cirq.Circuit(cirq.decompose_once(op))) - cirq.testing.assert_has_diagram( - circuit, - ''' - ┌──────────────────────────────┐ ┌──────────────────────────────┐ -ancilla_0: ─────sigma_mu───────────────────────────────────sigma_mu───────────────────────── - │ │ -ancilla_1: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_2: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_3: ─────alt────────────────────────────────────────alt────────────────────────────── - │ │ -ancilla_4: ─────keep───────────────────────────────────────keep───────────────────────────── - │ │ -ancilla_5: ─────less_than_equal────────────────────────────less_than_equal────────────────── - │ │ -control: ───────┼────────────────────────────X────Z───────X┼──────────────────────────────── - │ │ │ -selection0: ────StatePreparationAliasSampling─────@(0)─────StatePreparationAliasSampling──── - │ │ │ -selection1: ────selection─────────────────────────@(0)─────selection──────────────────────── - │ │ │ -selection2: ────selection^-1──────────────────────@(0)─────selection──────────────────────── - └──────────────────────────────┘ └──────────────────────────────┘ -''', - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_reflection_using_prepare_consistent_protocols_and_controlled(): - prepare_gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - [1, 2, 3, 4], probability_epsilon=0.1 - ) - # No control - gate = cirq_ft.ReflectionUsingPrepare(prepare_gate, control_val=None) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - # Test consistent repr - cirq.testing.assert_equivalent_repr( - gate, setup_code='import cirq\nimport cirq_ft\nimport numpy as np' - ) - # Build controlled gate - equals_tester = cirq.testing.EqualsTester() - equals_tester.add_equality_group( - gate.controlled(), - gate.controlled(num_controls=1), - gate.controlled(control_values=(1,)), - op.controlled_by(cirq.q("control")).gate, - ) - equals_tester.add_equality_group( - gate.controlled(control_values=(0,)), - gate.controlled(num_controls=1, control_values=(0,)), - op.controlled_by(cirq.q("control"), control_values=(0,)).gate, - ) - with pytest.raises(NotImplementedError, match="Cannot create a controlled version"): - _ = gate.controlled(num_controls=2) diff --git a/cirq-ft/cirq_ft/algos/select_and_prepare.py b/cirq-ft/cirq_ft/algos/select_and_prepare.py deleted file mode 100644 index 3338aef2750..00000000000 --- a/cirq-ft/cirq_ft/algos/select_and_prepare.py +++ /dev/null @@ -1,84 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 -from functools import cached_property -from typing import Tuple - -from cirq_ft import infra - - -class SelectOracle(infra.GateWithRegisters): - r"""Abstract base class that defines the API for a SELECT Oracle. - - The action of a SELECT oracle on a selection register $|l\rangle$ and target register - $|\Psi\rangle$ can be defined as: - - $$ - \mathrm{SELECT} = \sum_{l}|l \rangle \langle l| \otimes U_l - $$ - - In other words, the `SELECT` oracle applies $l$'th unitary $U_{l}$ on the target register - $|\Psi\rangle$ when the selection register stores integer $l$. - - $$ - \mathrm{SELECT}|l\rangle |\Psi\rangle = |l\rangle U_{l}|\Psi\rangle - $$ - """ - - @property - @abc.abstractmethod - def control_registers(self) -> Tuple[infra.Register, ...]: ... - - @property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: ... - - @property - @abc.abstractmethod - def target_registers(self) -> Tuple[infra.Register, ...]: ... - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - -class PrepareOracle(infra.GateWithRegisters): - r"""Abstract base class that defines the API for a PREPARE Oracle. - - Given a set of coefficients $\{c_0, c_1, ..., c_{N - 1}\}, the PREPARE oracle is used to encode - the coefficients as amplitudes of a state $|\Psi\rangle = \sum_{i=0}^{N-1}c_{i}|i\rangle$ using - a selection register $|i\rangle$. In order to prepare such a state, the PREPARE circuit is also - allowed to use a junk register that is entangled with selection register. - - Thus, the action of a PREPARE circuit on an input state $|0\rangle$ can be defined as: - - $$ - PREPARE |0\rangle = \sum_{i=0}^{N-1}c_{i}|i\rangle |junk_{i}\rangle - $$ - """ - - @property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: ... - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return () - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.junk_registers]) diff --git a/cirq-ft/cirq_ft/algos/select_swap_qrom.py b/cirq-ft/cirq_ft/algos/select_swap_qrom.py deleted file mode 100644 index 169be003e8c..00000000000 --- a/cirq-ft/cirq_ft/algos/select_swap_qrom.py +++ /dev/null @@ -1,244 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import List, Optional, Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np -from cirq_ft import infra -from cirq_ft.algos import qrom, swap_network - - -def find_optimal_log_block_size(iteration_length: int, target_bitsize: int) -> int: - """Find optimal block size, which is a power of 2, for SelectSwapQROM. - - This functions returns the optimal `k` s.t. - * k is in an integer and k >= 0. - * iteration_length/2^k + target_bitsize*(2^k - 1) is minimized. - The corresponding block size for SelectSwapQROM would be 2^k. - """ - k = 0.5 * np.log2(iteration_length / target_bitsize) - if k < 0: - return 1 - - def value(kk: List[int]): - return iteration_length / np.power(2, kk) + target_bitsize * (np.power(2, kk) - 1) - - k_int = [np.floor(k), np.ceil(k)] # restrict optimal k to integers - return int(k_int[np.argmin(value(k_int))]) # obtain optimal k - - -@cirq.value_equality() -class SelectSwapQROM(infra.GateWithRegisters): - """Gate to load data[l] in the target register when the selection register stores integer l. - - Let - N:= Number of data elements to load. - b:= Bit-length of the target register in which data elements should be loaded. - - The `SelectSwapQROM` is a hybrid of the following two existing primitives: - - * Unary Iteration based `cirq_ft.QROM` requires O(N) T-gates to load `N` data - elements into a b-bit target register. Note that the T-complexity is independent of `b`. - * `cirq_ft.SwapWithZeroGate` can swap a `b` bit register indexed `x` with a `b` - bit register at index `0` using O(b) T-gates, if the selection register stores integer `x`. - Note that the swap complexity is independent of the iteration length `N`. - - The `SelectSwapQROM` uses square root decomposition by combining the above two approaches to - further optimize the T-gate complexity of loading `N` data elements, each into a `b` bit - target register as follows: - - * Divide the `N` data elements into batches of size `B` (a variable) and - load each batch simultaneously into `B` distinct target signature using the conventional - QROM. This has T-complexity `O(N / B)`. - * Use `SwapWithZeroGate` to swap the `i % B`'th target register in batch number `i / B` - to load `data[i]` in the 0'th target register. This has T-complexity `O(B * b)`. - - This, the final T-complexity of `SelectSwapQROM` is `O(B * b + N / B)` T-gates; where `B` is - the block-size with an optimal value of `O(sqrt(N / b))`. - - This improvement in T-complexity is achieved at the cost of using an additional `O(B * b)` - ancilla qubits, with a nice property that these additional ancillas can be `dirty`; i.e. - they don't need to start in the |0> state and thus can be borrowed from other parts of the - algorithm. The state of these dirty ancillas would be unaffected after the operation has - finished. - - For more details, see the reference below: - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low, Kliuchnikov, Schaeffer. 2018. - """ - - def __init__( - self, - *data: Sequence[int], - target_bitsizes: Optional[Sequence[int]] = None, - block_size: Optional[int] = None, - ): - """Initializes SelectSwapQROM - - For a single data sequence of length `N`, maximum target bitsize `b` and block size `B`; - SelectSwapQROM requires: - - Selection register & ancilla of size `logN` for QROM data load. - - 1 clean target register of size `b`. - - `B` dirty target signature, each of size `b`. - - Similarly, to load `M` such data sequences, `SelectSwapQROM` requires: - - Selection register & ancilla of size `logN` for QROM data load. - - 1 clean target register of size `sum(target_bitsizes)`. - - `B` dirty target signature, each of size `sum(target_bitsizes)`. - - Args: - data: Sequence of integers to load in the target register. If more than one sequence - is provided, each sequence must be of the same length. - target_bitsizes: Sequence of integers describing the size of target register for each - data sequence to load. Defaults to `max(data[i]).bit_length()` for each i. - block_size(B): Load batches of `B` data elements in each iteration of traditional QROM - (N/B iterations required). Complexity of SelectSwap QROAM scales as - `O(B * b + N / B)`, where `B` is the block_size. Defaults to optimal value of - `O(sqrt(N / b))`. - - Raises: - ValueError: If all target data sequences to load do not have the same length. - """ - # Validate input. - if len(set(len(d) for d in data)) != 1: - raise ValueError("All data sequences to load must be of equal length.") - if target_bitsizes is None: - target_bitsizes = [max(d).bit_length() for d in data] - assert len(target_bitsizes) == len(data) - assert all(t >= max(d).bit_length() for t, d in zip(target_bitsizes, data)) - self._num_sequences = len(data) - self._target_bitsizes = tuple(target_bitsizes) - self._iteration_length = len(data[0]) - if block_size is None: - # Figure out optimal value of block_size - block_size = 2 ** find_optimal_log_block_size(len(data[0]), sum(target_bitsizes)) - assert 0 < block_size <= self._iteration_length - self._block_size = block_size - self._num_blocks = int(np.ceil(self._iteration_length / self.block_size)) - self.selection_q, self.selection_r = tuple( - (L - 1).bit_length() for L in [self.num_blocks, self.block_size] - ) - self._data = tuple(tuple(d) for d in data) - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister( - 'selection', self.selection_q + self.selection_r, self._iteration_length - ), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Register(f'target{sequence_id}', self._target_bitsizes[sequence_id]) - for sequence_id in range(self._num_sequences) - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.target_registers]) - - @property - def data(self) -> Tuple[Tuple[int, ...], ...]: - return self._data - - @property - def block_size(self) -> int: - return self._block_size - - @property - def num_blocks(self) -> int: - return self._num_blocks - - def __repr__(self) -> str: - data_repr = ','.join(repr(d) for d in self.data) - target_repr = repr(self._target_bitsizes) - return ( - f"cirq_ft.SelectSwapQROM(" - f"{data_repr}, " - f"target_bitsizes={target_repr}, " - f"block_size={self.block_size})" - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - # Divide each data sequence and corresponding target registers into - # `self.num_blocks` batches of size `self.block_size`. - selection, targets = quregs.pop('selection'), quregs - qrom_data: List[NDArray] = [] - qrom_target_bitsizes: List[int] = [] - ordered_target_qubits: List[cirq.Qid] = [] - for block_id in range(self.block_size): - for sequence_id in range(self._num_sequences): - data = self.data[sequence_id] - target_bitsize = self._target_bitsizes[sequence_id] - ordered_target_qubits.extend(context.qubit_manager.qborrow(target_bitsize)) - data_for_current_block = data[block_id :: self.block_size] - if len(data_for_current_block) < self.num_blocks: - zero_pad = (0,) * (self.num_blocks - len(data_for_current_block)) - data_for_current_block = data_for_current_block + zero_pad - qrom_data.append(np.array(data_for_current_block)) - qrom_target_bitsizes.append(target_bitsize) - # Construct QROM, SwapWithZero and CX operations using the batched data and qubits. - k = (self.block_size - 1).bit_length() - q, r = selection[: self.selection_q], selection[self.selection_q :] - qrom_gate = qrom.QROM( - qrom_data, - selection_bitsizes=(self.selection_q,), - target_bitsizes=tuple(qrom_target_bitsizes), - ) - qrom_op = qrom_gate.on_registers( - selection=q, **infra.split_qubits(qrom_gate.target_registers, ordered_target_qubits) - ) - swap_with_zero_gate = swap_network.SwapWithZeroGate( - k, infra.total_bits(self.target_registers), self.block_size - ) - swap_with_zero_op = swap_with_zero_gate.on_registers( - selection=r, - **infra.split_qubits(swap_with_zero_gate.target_registers, ordered_target_qubits), - ) - clean_targets = infra.merge_qubits(self.target_registers, **targets) - cnot_op = cirq.Moment(cirq.CNOT(s, t) for s, t in zip(ordered_target_qubits, clean_targets)) - # Yield the operations in correct order. - yield qrom_op - yield swap_with_zero_op - yield cnot_op - yield cirq.inverse(swap_with_zero_op) - yield cirq.inverse(qrom_op) - yield swap_with_zero_op - yield cnot_op - yield cirq.inverse(swap_with_zero_op) - - context.qubit_manager.qfree(ordered_target_qubits) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["In_q"] * self.selection_q - wire_symbols += ["In_r"] * self.selection_r - for i, target in enumerate(self.target_registers): - wire_symbols += [f"QROAM_{i}"] * target.total_bits() - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def _value_equality_values_(self): - return self.block_size, self._target_bitsizes, self.data diff --git a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py b/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py deleted file mode 100644 index 5982cd350b5..00000000000 --- a/cirq-ft/cirq_ft/algos/select_swap_qrom_test.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "data,block_size", - [ - pytest.param( - data, - block_size, - id=f"{block_size}-data{didx}", - marks=pytest.mark.slow if block_size == 3 and didx == 1 else (), - ) - for didx, data in enumerate([[[1, 2, 3, 4, 5]], [[1, 2, 3], [3, 2, 1]]]) - for block_size in [None, 1, 2, 3] - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_select_swap_qrom(data, block_size): - qrom = cirq_ft.SelectSwapQROM(*data, block_size=block_size) - qubit_regs = infra.get_named_qubits(qrom.signature) - selection = qubit_regs["selection"] - selection_q, selection_r = selection[: qrom.selection_q], selection[qrom.selection_q :] - targets = [qubit_regs[f"target{i}"] for i in range(len(data))] - - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - context = cirq.DecompositionContext(greedy_mm) - qrom_circuit = cirq.Circuit(cirq.decompose(qrom.on_registers(**qubit_regs), context=context)) - - dirty_target_ancilla = [ - q for q in qrom_circuit.all_qubits() if isinstance(q, cirq.ops.BorrowableQubit) - ] - - circuit = cirq.Circuit( - # Prepare dirty ancillas in an arbitrary state. - cirq.H.on_each(*dirty_target_ancilla), - cirq.T.on_each(*dirty_target_ancilla), - # The dirty ancillas should remain unaffected by qroam. - *qrom_circuit, - # Bring back the dirty ancillas to their original state. - (cirq.T**-1).on_each(*dirty_target_ancilla), - cirq.H.on_each(*dirty_target_ancilla), - ) - all_qubits = sorted(circuit.all_qubits()) - for selection_integer in range(qrom.selection_registers[0].iteration_length): - svals_q = list(iter_bits(selection_integer // qrom.block_size, len(selection_q))) - svals_r = list(iter_bits(selection_integer % qrom.block_size, len(selection_r))) - qubit_vals = {x: 0 for x in all_qubits} - qubit_vals.update({s: sval for s, sval in zip(selection_q, svals_q)}) - qubit_vals.update({s: sval for s, sval in zip(selection_r, svals_r)}) - - dvals = np.random.randint(2, size=len(dirty_target_ancilla)) - qubit_vals.update({d: dval for d, dval in zip(dirty_target_ancilla, dvals)}) - - initial_state = [qubit_vals[x] for x in all_qubits] - for target, d in zip(targets, data): - for q, b in zip(target, iter_bits(d[selection_integer], len(target))): - qubit_vals[q] = b - final_state = [qubit_vals[x] for x in all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qrom_repr(): - qrom = cirq_ft.SelectSwapQROM([1, 2], [3, 5]) - cirq.testing.assert_equivalent_repr(qrom, setup_code="import cirq_ft\n") - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_diagram(): - data = [[1, 2, 3], [4, 5, 6]] - blocksize = 2 - qrom = cirq_ft.SelectSwapQROM(*data, block_size=blocksize) - q = cirq.LineQubit.range(cirq.num_qubits(qrom)) - circuit = cirq.Circuit(qrom.on_registers(**infra.split_qubits(qrom.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───In_q────── - │ -1: ───In_r────── - │ -2: ───QROAM_0─── - │ -3: ───QROAM_0─── - │ -4: ───QROAM_1─── - │ -5: ───QROAM_1─── - │ -6: ───QROAM_1─── -""", - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_raises(): - with pytest.raises(ValueError, match="must be of equal length"): - _ = cirq_ft.SelectSwapQROM([1, 2], [1, 2, 3]) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_qroam_hashable(): - qrom = cirq_ft.SelectSwapQROM([1, 2, 5, 6, 7, 8]) - assert hash(qrom) is not None - assert cirq_ft.t_complexity(qrom) == cirq_ft.TComplexity(32, 160, 0) diff --git a/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py b/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py deleted file mode 100644 index 057b194d7db..00000000000 --- a/cirq-ft/cirq_ft/algos/selected_majorana_fermion.py +++ /dev/null @@ -1,124 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Sequence, Union, Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np - -from cirq_ft import infra -from cirq_ft.algos import unary_iteration_gate - - -@attr.frozen -class SelectedMajoranaFermionGate(unary_iteration_gate.UnaryIterationGate): - """Implements U s.t. U|l>|Psi> -> |l> T_{l} . Z_{l - 1} ... Z_{0} |Psi> - - where T is a single qubit target gate that defaults to pauli Y. The gate is - implemented using an accumulator bit in the unary iteration circuit as explained - in the reference below. - - - Args: - selection_regs: Indexing `select` signature of type `SelectionRegister`. It also contains - information about the iteration length of each selection register. - control_regs: Control signature for constructing a controlled version of the gate. - target_gate: Single qubit gate to be applied to the target qubits. - - References: - See Fig 9 of https://arxiv.org/abs/1805.03662 for more details. - """ - - selection_regs: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - control_regs: Tuple[infra.Register, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.Register) else tuple(v) - ) - target_gate: cirq.Gate = cirq.Y - - @control_regs.default - def control_regs_default(self): - return infra.Register('control', 1) - - @classmethod - def make_on( - cls, - *, - target_gate=cirq.Y, - **quregs: Union[Sequence[cirq.Qid], NDArray[cirq.Qid]], # type: ignore[type-var] - ) -> cirq.Operation: - """Helper constructor to automatically deduce selection_regs attribute.""" - return SelectedMajoranaFermionGate( - selection_regs=infra.SelectionRegister( - 'selection', len(quregs['selection']), len(quregs['target']) - ), - target_gate=target_gate, - ).on_registers(**quregs) - - @cached_property - def control_registers(self) -> Tuple[infra.Register, ...]: - return self.control_regs - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return self.selection_regs - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - total_iteration_size = np.prod( - tuple(reg.iteration_length for reg in self.selection_registers) - ) - return (infra.Register('target', int(total_iteration_size)),) - - @cached_property - def extra_registers(self) -> Tuple[infra.Register, ...]: - return (infra.Register('accumulator', 1),) - - def decompose_from_registers( - self, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - quregs['accumulator'] = np.array(context.qubit_manager.qalloc(1)) - control = ( - quregs[self.control_regs[0].name] if infra.total_bits(self.control_registers) else [] - ) - yield cirq.X(*quregs['accumulator']).controlled_by(*control) - yield super(SelectedMajoranaFermionGate, self).decompose_from_registers( - context=context, **quregs - ) - context.qubit_manager.qfree(quregs['accumulator']) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - wire_symbols += [f"Z{self.target_gate}"] * infra.total_bits(self.target_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - target: Sequence[cirq.Qid], - accumulator: Sequence[cirq.Qid], - **selection_indices: int, - ) -> cirq.OP_TREE: - selection_shape = tuple(reg.iteration_length for reg in self.selection_regs) - selection_idx = tuple(selection_indices[reg.name] for reg in self.selection_regs) - target_idx = int(np.ravel_multi_index(selection_idx, selection_shape)) - yield cirq.CNOT(control, *accumulator) - yield self.target_gate(target[target_idx]).controlled_by(control) - yield cirq.CZ(*accumulator, target[target_idx]) diff --git a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py b/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py deleted file mode 100644 index eae2999ff3d..00000000000 --- a/cirq-ft/cirq_ft/algos/selected_majorana_fermion_test.py +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize", - [ - (2, 4), - pytest.param(3, 8, marks=pytest.mark.slow), - pytest.param(4, 9, marks=pytest.mark.slow), - ], -) -@pytest.mark.parametrize("target_gate", [cirq.X, cirq.Y]) -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate(selection_bitsize, target_bitsize, target_gate): - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=target_gate, - ) - g = cirq_ft.testing.GateHelper(gate) - assert len(g.all_qubits) <= infra.total_bits(gate.signature) + selection_bitsize + 1 - - sim = cirq.Simulator(dtype=np.complex128) - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.operation.qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.operation.qubits] - - result = sim.simulate( - g.circuit, initial_state=initial_state, qubit_order=g.operation.qubits - ) - - final_target_state = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=[g.operation.qubits.index(q) for q in g.quregs['target']], - ) - - expected_target_state = cirq.Circuit( - [cirq.Z(q) for q in g.quregs['target'][:n]], - target_gate(g.quregs['target'][n]), - [cirq.I(q) for q in g.quregs['target'][n + 1 :]], - ).final_state_vector(qubit_order=g.quregs['target']) - - cirq.testing.assert_allclose_up_to_global_phase( - expected_target_state, final_target_state, atol=1e-6 - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_diagram(): - selection_bitsize, target_bitsize = 3, 5 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - circuit = cirq.Circuit(gate.on_registers(**infra.get_named_qubits(gate.signature))) - qubits = list(q for v in infra.get_named_qubits(gate.signature).values() for q in v) - cirq.testing.assert_has_diagram( - circuit, - """ -control: ──────@──── - │ -selection0: ───In─── - │ -selection1: ───In─── - │ -selection2: ───In─── - │ -target0: ──────ZX─── - │ -target1: ──────ZX─── - │ -target2: ──────ZX─── - │ -target3: ──────ZX─── - │ -target4: ──────ZX─── -""", - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_decomposed_diagram(): - selection_bitsize, target_bitsize = 2, 3 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - g = cirq_ft.testing.GateHelper(gate) - context = cirq.DecompositionContext(greedy_mm) - circuit = cirq.Circuit(cirq.decompose_once(g.operation, context=context)) - ancillas = sorted(set(circuit.all_qubits()) - set(g.operation.qubits)) - qubits = np.concatenate( - [ - g.quregs['control'], - [q for qs in zip(g.quregs['selection'], ancillas[1:]) for q in qs], - ancillas[0:1], - g.quregs['target'], - ] - ) - cirq.testing.assert_has_diagram( - circuit, - """ -control: ──────@───@──────────────────────────────────────@───────────@────── - │ │ │ │ -selection0: ───┼───(0)────────────────────────────────────┼───────────@────── - │ │ │ │ -_a_1: ─────────┼───And───@─────────────@───────────@──────X───@───@───And†─── - │ │ │ │ │ │ -selection1: ───┼─────────(0)───────────┼───────────@──────────┼───┼────────── - │ │ │ │ │ │ -_a_2: ─────────┼─────────And───@───@───X───@───@───And†───────┼───┼────────── - │ │ │ │ │ │ │ -_a_0: ─────────X───────────────X───┼───@───X───┼───@──────────X───┼───@────── - │ │ │ │ │ │ -target0: ──────────────────────────X───@───────┼───┼──────────────┼───┼────── - │ │ │ │ -target1: ──────────────────────────────────────X───@──────────────┼───┼────── - │ │ -target2: ─────────────────────────────────────────────────────────X───@────── """, - qubit_order=qubits, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selected_majorana_fermion_gate_make_on(): - selection_bitsize, target_bitsize = 3, 5 - gate = cirq_ft.SelectedMajoranaFermionGate( - cirq_ft.SelectionRegister('selection', selection_bitsize, target_bitsize), - target_gate=cirq.X, - ) - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - op2 = cirq_ft.SelectedMajoranaFermionGate.make_on( - target_gate=cirq.X, **infra.get_named_qubits(gate.signature) - ) - assert op == op2 diff --git a/cirq-ft/cirq_ft/algos/state_preparation.ipynb b/cirq-ft/cirq_ft/algos/state_preparation.ipynb deleted file mode 100644 index e9cef3f0f42..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation.ipynb +++ /dev/null @@ -1,164 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "ce48b2ae", - "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": "e2dca938", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# State Preparation using Coherent Alias Sampling\n", - "\n", - "Gates for preparing coefficient states.\n", - "\n", - "In section III.D. of the [Linear T paper](https://arxiv.org/abs/1805.03662) the authors introduce\n", - "a technique for initializing a state with $L$ unique coefficients (provided by a classical\n", - "database) with a number of T gates scaling as 4L + O(log(1/eps)) where eps is the\n", - "largest absolute error that one can tolerate in the prepared amplitudes." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1e7c27f6", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "bf85ebed", - "metadata": { - "cq.autogen": "_make_StatePreparationAliasSampling.md" - }, - "source": [ - "## `StatePreparationAliasSampling`\n", - "Initialize a state with $L$ unique coefficients using coherent alias sampling.\n", - "\n", - "In particular, we take the zero state to:\n", - "\n", - "$$\n", - "\\sum_{\\ell=0}^{L-1} \\sqrt{p_\\ell} |\\ell\\rangle |\\mathrm{temp}_\\ell\\rangle\n", - "$$\n", - "\n", - "where the probabilities $p_\\ell$ are $\\mu$-bit binary approximations to the true values and\n", - "where the temporary register must be treated with care, see the details in Section III.D. of\n", - "the reference.\n", - "\n", - "The preparation is equivalent to [classical alias sampling](https://en.wikipedia.org/wiki/Alias_method):\n", - "we sample `l` with probability `p[l]` by first selecting `l` uniformly at random and then\n", - "returning it with probability `keep[l] / 2**mu`; otherwise returning `alt[l]`.\n", - "\n", - "Registers:\n", - " selection: The input/output register $|\\ell\\rangle$ of size lg(L) where the desired\n", - " coefficient state is prepared.\n", - " temp: Work space comprised of sub signature:\n", - " - sigma: A mu-sized register containing uniform probabilities for comparison against\n", - " `keep`.\n", - " - alt: A lg(L)-sized register of alternate indices\n", - " - keep: a mu-sized register of probabilities of keeping the initially sampled index.\n", - " - one bit for the result of the comparison.\n", - "\n", - "This gate corresponds to the following operations:\n", - " - UNIFORM_L on the selection register\n", - " - H^mu on the sigma register\n", - " - QROM addressed by the selection register into the alt and keep signature.\n", - " - LessThanEqualGate comparing the keep and sigma signature.\n", - " - Coherent swap between the selection register and alt register if the comparison\n", - " returns True.\n", - "\n", - "Total space will be (2 * log(L) + 2 mu + 1) work qubits + log(L) ancillas for QROM.\n", - "The 1 ancilla in work qubits is for the `LessThanEqualGate` followed by coherent swap.\n", - "\n", - "#### Parameters\n", - " - `lcu_probabilities`: The LCU coefficients.\n", - " - `probability_epsilon`: The desired accuracy to represent each probability (which sets mu size and keep/alt integers). See `openfermion.circuits.lcu_util.preprocess_lcu_coefficients_for_reversible_sampling` for more information. \n", - "\n", - "#### References\n", - "[Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity](https://arxiv.org/abs/1805.03662). Babbush et. al. (2018). Section III.D. and Figure 11.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4f9f2b46", - "metadata": { - "cq.autogen": "_make_StatePreparationAliasSampling.py" - }, - "outputs": [], - "source": [ - "coeffs = np.array([1.0, 1, 3, 2])\n", - "mu = 3\n", - "\n", - "state_prep = cirq_ft.StatePreparationAliasSampling.from_lcu_probs(\n", - " coeffs, probability_epsilon=2**-mu / len(coeffs)\n", - ")\n", - "g = cq_testing.GateHelper(\n", - " state_prep\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "26c46f4b", - "metadata": {}, - "outputs": [], - "source": [] - } - ], - "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 -} diff --git a/cirq-ft/cirq_ft/algos/state_preparation.py b/cirq-ft/cirq_ft/algos/state_preparation.py deleted file mode 100644 index 6a0f5cec994..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation.py +++ /dev/null @@ -1,186 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -"""Gates for preparing coefficient states. - -In section III.D. of the [Linear T paper](https://arxiv.org/abs/1805.03662) the authors introduce -a technique for initializing a state with $L$ unique coefficients (provided by a classical -database) with a number of T gates scaling as 4L + O(log(1/eps)) where eps is the -largest absolute error that one can tolerate in the prepared amplitudes. -""" - -from functools import cached_property -from typing import List, Tuple -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft import infra, linalg -from cirq_ft.algos import ( - arithmetic_gates, - prepare_uniform_superposition, - qrom, - select_and_prepare, - swap_network, -) - - -@cirq.value_equality() -@attr.frozen -class StatePreparationAliasSampling(select_and_prepare.PrepareOracle): - r"""Initialize a state with $L$ unique coefficients using coherent alias sampling. - - In particular, we take the zero state to: - - $$ - \sum_{\ell=0}^{L-1} \sqrt{p_\ell} |\ell\rangle |\mathrm{temp}_\ell\rangle - $$ - - where the probabilities $p_\ell$ are $\mu$-bit binary approximations to the true values and - where the temporary register must be treated with care, see the details in Section III.D. of - the reference. - - The preparation is equivalent to [classical alias sampling] - (https://en.wikipedia.org/wiki/Alias_method): we sample `l` with probability `p[l]` by first - selecting `l` uniformly at random and then returning it with probability `keep[l] / 2**mu`; - otherwise returning `alt[l]`. - - Signature: - selection: The input/output register $|\ell\rangle$ of size lg(L) where the desired - coefficient state is prepared. - temp: Work space comprised of sub signature: - - sigma: A mu-sized register containing uniform probabilities for comparison against - `keep`. - - alt: A lg(L)-sized register of alternate indices - - keep: a mu-sized register of probabilities of keeping the initially sampled index. - - one bit for the result of the comparison. - - This gate corresponds to the following operations: - - UNIFORM_L on the selection register - - H^mu on the sigma register - - QROM addressed by the selection register into the alt and keep signature. - - LessThanEqualGate comparing the keep and sigma signature. - - Coherent swap between the selection register and alt register if the comparison - returns True. - - Total space will be (2 * log(L) + 2 mu + 1) work qubits + log(L) ancillas for QROM. - The 1 ancilla in work qubits is for the `LessThanEqualGate` followed by coherent swap. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.D. and Figure 11. - """ - - selection_registers: Tuple[infra.SelectionRegister, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, infra.SelectionRegister) else tuple(v) - ) - alt: NDArray[np.int_] - keep: NDArray[np.int_] - mu: int - - @classmethod - def from_lcu_probs( - cls, lcu_probabilities: List[float], *, probability_epsilon: float = 1.0e-5 - ) -> 'StatePreparationAliasSampling': - """Factory to construct the state preparation gate for a given set of LCU coefficients. - - Args: - lcu_probabilities: The LCU coefficients. - probability_epsilon: The desired accuracy to represent each probability - (which sets mu size and keep/alt integers). - See `cirq_ft.linalg.lcu_util.preprocess_lcu_coefficients_for_reversible_sampling` - for more information. - """ - alt, keep, mu = linalg.preprocess_lcu_coefficients_for_reversible_sampling( - lcu_coefficients=lcu_probabilities, epsilon=probability_epsilon - ) - N = len(lcu_probabilities) - return StatePreparationAliasSampling( - selection_registers=infra.SelectionRegister('selection', (N - 1).bit_length(), N), - alt=np.array(alt), - keep=np.array(keep), - mu=mu, - ) - - @cached_property - def sigma_mu_bitsize(self) -> int: - return self.mu - - @cached_property - def alternates_bitsize(self) -> int: - return infra.total_bits(self.selection_registers) - - @cached_property - def keep_bitsize(self) -> int: - return self.mu - - @cached_property - def selection_bitsize(self) -> int: - return infra.total_bits(self.selection_registers) - - @cached_property - def junk_registers(self) -> Tuple[infra.Register, ...]: - return tuple( - infra.Signature.build( - sigma_mu=self.sigma_mu_bitsize, - alt=self.alternates_bitsize, - keep=self.keep_bitsize, - less_than_equal=1, - ) - ) - - def _value_equality_values_(self): - return ( - self.selection_registers, - tuple(self.alt.ravel()), - tuple(self.keep.ravel()), - self.mu, - ) - - def __repr__(self) -> str: - alt_repr = cirq._compat.proper_repr(self.alt) - keep_repr = cirq._compat.proper_repr(self.keep) - return ( - f'cirq_ft.StatePreparationAliasSampling(' - f'{self.selection_registers}, ' - f'{alt_repr}, ' - f'{keep_repr}, ' - f'{self.mu})' - ) - - def decompose_from_registers( - self, - *, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type:ignore[type-var] - ) -> cirq.OP_TREE: - selection, less_than_equal = quregs['selection'], quregs['less_than_equal'] - sigma_mu, alt, keep = quregs.get('sigma_mu', ()), quregs['alt'], quregs.get('keep', ()) - N = self.selection_registers[0].iteration_length - yield prepare_uniform_superposition.PrepareUniformSuperposition(N).on(*selection) - yield cirq.H.on_each(*sigma_mu) - qrom_gate = qrom.QROM( - [self.alt, self.keep], - (self.selection_bitsize,), - (self.alternates_bitsize, self.keep_bitsize), - ) - yield qrom_gate.on_registers(selection=selection, target0=alt, target1=keep) - yield arithmetic_gates.LessThanEqualGate(self.mu, self.mu).on( - *keep, *sigma_mu, *less_than_equal - ) - yield swap_network.MultiTargetCSwap.make_on( - control=less_than_equal, target_x=alt, target_y=selection - ) diff --git a/cirq-ft/cirq_ft/algos/state_preparation_test.py b/cirq-ft/cirq_ft/algos/state_preparation_test.py deleted file mode 100644 index dba8159de19..00000000000 --- a/cirq-ft/cirq_ft/algos/state_preparation_test.py +++ /dev/null @@ -1,101 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft.algos.generic_select_test import get_1d_Ising_lcu_coeffs -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@pytest.mark.parametrize( - "num_sites, epsilon", - [ - (2, 3e-3), - pytest.param(3, 3.0e-3, marks=pytest.mark.slow), - pytest.param(4, 5.0e-3, marks=pytest.mark.slow), - pytest.param(7, 8.0e-3, marks=pytest.mark.slow), - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_state_preparation_via_coherent_alias_sampling(num_sites, epsilon): - lcu_coefficients = get_1d_Ising_lcu_coeffs(num_sites) - gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - lcu_probabilities=lcu_coefficients.tolist(), probability_epsilon=epsilon - ) - g = cirq_ft.testing.GateHelper(gate) - qubit_order = g.operation.qubits - - # Assertion to ensure that simulating the `decomposed_circuit` doesn't run out of memory. - assert len(g.circuit.all_qubits()) < 20 - result = cirq.Simulator(dtype=np.complex128).simulate(g.circuit, qubit_order=qubit_order) - state_vector = result.final_state_vector - # State vector is of the form |l>|temp_{l}>. We trace out the |temp_{l}> part to - # get the coefficients corresponding to |l>. - L, logL = len(lcu_coefficients), len(g.quregs['selection']) - state_vector = state_vector.reshape(2**logL, len(state_vector) // 2**logL) - num_non_zero = (abs(state_vector) > 1e-6).sum(axis=1) - prepared_state = state_vector.sum(axis=1) - assert all(num_non_zero[:L] > 0) and all(num_non_zero[L:] == 0) - assert all(np.abs(prepared_state[:L]) > 1e-6) and all(np.abs(prepared_state[L:]) <= 1e-6) - prepared_state = prepared_state[:L] / np.sqrt(num_non_zero[:L]) - # Assert that the absolute square of prepared state (probabilities instead of amplitudes) is - # same as `lcu_coefficients` up to `epsilon`. - np.testing.assert_allclose(lcu_coefficients, abs(prepared_state) ** 2, atol=epsilon) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_state_preparation_via_coherent_alias_sampling_diagram(): - data = np.asarray(range(1, 5)) / np.sum(range(1, 5)) - gate = cirq_ft.StatePreparationAliasSampling.from_lcu_probs( - lcu_probabilities=data.tolist(), probability_epsilon=0.05 - ) - g = cirq_ft.testing.GateHelper(gate) - qubit_order = g.operation.qubits - - circuit = cirq.Circuit(cirq.decompose_once(g.operation)) - cirq.testing.assert_has_diagram( - circuit, - ''' -selection0: ────────UNIFORM(4)───In───────────────────×(y)─── - │ │ │ -selection1: ────────target───────In───────────────────×(y)─── - │ │ -sigma_mu0: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -sigma_mu1: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -sigma_mu2: ─────────H────────────┼────────In(y)───────┼────── - │ │ │ -alt0: ───────────────────────────QROM_0───┼───────────×(x)─── - │ │ │ -alt1: ───────────────────────────QROM_0───┼───────────×(x)─── - │ │ │ -keep0: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ │ -keep1: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ │ -keep2: ──────────────────────────QROM_1───In(x)───────┼────── - │ │ -less_than_equal: ─────────────────────────+(x <= y)───@────── -''', - qubit_order=qubit_order, - ) - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('state_preparation') diff --git a/cirq-ft/cirq_ft/algos/swap_network.ipynb b/cirq-ft/cirq_ft/algos/swap_network.ipynb deleted file mode 100644 index cf87b7c640f..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network.ipynb +++ /dev/null @@ -1,176 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "aaebd62d", - "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": "f31db18a", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Swap Network" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9354e0b6", - "metadata": { - "cq.autogen": "top_imports" - }, - "outputs": [], - "source": [ - "import cirq\n", - "import numpy as np\n", - "import cirq_ft\n", - "import cirq_ft.infra.testing as cq_testing\n", - "from cirq_ft.infra.jupyter_tools import display_gate_and_compilation\n", - "from typing import *" - ] - }, - { - "cell_type": "markdown", - "id": "f0727311", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwap.md" - }, - "source": [ - "## `MultiTargetCSwap`\n", - "Implements a multi-target controlled swap unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$.\n", - "\n", - "This decomposes into a qubitwise SWAP on the two target signature, and takes 14*n T-gates.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et. al. 2018. See Appendix B.2.c.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "253b1d6a", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwap.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.MultiTargetCSwap(3)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "markdown", - "id": "4a5975fd", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwapApprox.md" - }, - "source": [ - "## `MultiTargetCSwapApprox`\n", - "Approximately implements a multi-target controlled swap unitary using only 4 * n T-gates.\n", - "\n", - "Implements the unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$ such that the output state is\n", - "correct up to a global phase factor of +1 / -1.\n", - "\n", - "This is useful when the incorrect phase can be absorbed in a garbage state of an algorithm; and\n", - "thus ignored, see the reference for more details.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis](https://arxiv.org/abs/1812.00954). Low et. al. 2018. See Appendix B.2.c.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "972e5895", - "metadata": { - "cq.autogen": "_make_MultiTargetCSwapApprox.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.MultiTargetCSwapApprox(2)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - }, - { - "cell_type": "markdown", - "id": "5fae3dd8", - "metadata": { - "cq.autogen": "_make_SwapWithZeroGate.md" - }, - "source": [ - "## `SwapWithZeroGate`\n", - "Swaps |Psi_0> with |Psi_x> if selection register stores index `x`.\n", - "\n", - "Implements the unitary U |x> |Psi_0> |Psi_1> ... |Psi_{n-1}> --> |x> |Psi_x> |Rest of Psi>.\n", - "Note that the state of `|Rest of Psi>` is allowed to be anything and should not be depended\n", - "upon.\n", - "\n", - "#### References\n", - "[Trading T-gates for dirty qubits in state preparation and unitary synthesis] (https://arxiv.org/abs/1812.00954). Low, Kliuchnikov, Schaeffer. 2018.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad3849f0", - "metadata": { - "cq.autogen": "_make_SwapWithZeroGate.py" - }, - "outputs": [], - "source": [ - "g = cq_testing.GateHelper(\n", - " cirq_ft.SwapWithZeroGate(selection_bitsize=2, target_bitsize=3, n_target_registers=4)\n", - ")\n", - "\n", - "display_gate_and_compilation(g)" - ] - } - ], - "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 -} diff --git a/cirq-ft/cirq_ft/algos/swap_network.py b/cirq-ft/cirq_ft/algos/swap_network.py deleted file mode 100644 index 62512de2486..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network.py +++ /dev/null @@ -1,202 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Sequence, Union, Tuple -from numpy.typing import NDArray - -import attr -import cirq -from cirq_ft import infra -from cirq_ft.algos import multi_control_multi_target_pauli as mcmtp - - -@attr.frozen -class MultiTargetCSwap(infra.GateWithRegisters): - """Implements a multi-target controlled swap unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$. - - This decomposes into a qubitwise SWAP on the two target signature, and takes 14*n T-gates. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low et. al. 2018. See Appendix B.2.c. - """ - - bitsize: int - - @classmethod - def make_on( - cls, **quregs: Union[Sequence[cirq.Qid], NDArray[cirq.Qid]] # type: ignore[type-var] - ) -> cirq.Operation: - """Helper constructor to automatically deduce bitsize attributes.""" - return cls(bitsize=len(quregs['target_x'])).on_registers(**quregs) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature.build(control=1, target_x=self.bitsize, target_y=self.bitsize) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - control, target_x, target_y = quregs['control'], quregs['target_x'], quregs['target_y'] - yield [cirq.CSWAP(*control, t_x, t_y) for t_x, t_y in zip(target_x, target_y)] - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - if not args.use_unicode_characters: - return cirq.CircuitDiagramInfo( - ("@",) + ("swap_x",) * self.bitsize + ("swap_y",) * self.bitsize - ) - return cirq.CircuitDiagramInfo(("@",) + ("×(x)",) * self.bitsize + ("×(y)",) * self.bitsize) - - def __repr__(self) -> str: - return f"cirq_ft.MultiTargetCSwap({self.bitsize})" - - def _t_complexity_(self) -> infra.TComplexity: - return infra.TComplexity(t=7 * self.bitsize, clifford=10 * self.bitsize) - - -@attr.frozen -class MultiTargetCSwapApprox(MultiTargetCSwap): - """Approximately implements a multi-target controlled swap unitary using only 4 * n T-gates. - - Implements the unitary $CSWAP_n = |0><0| I + |1><1| SWAP_n$ such that the output state is - correct up to a global phase factor of +1 / -1. - - This is useful when the incorrect phase can be absorbed in a garbage state of an algorithm; and - thus ignored, see the reference for more details. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low et. al. 2018. See Appendix B.2.c. - """ - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - control, target_x, target_y = quregs['control'], quregs['target_x'], quregs['target_y'] - - def g(q: cirq.Qid, adjoint=False) -> cirq.ops.op_tree.OpTree: - yield [cirq.S(q), cirq.H(q)] - yield cirq.T(q) ** (1 - 2 * adjoint) - yield [cirq.H(q), cirq.S(q) ** -1] - - cnot_x_to_y = [cirq.CNOT(x, y) for x, y in zip(target_x, target_y)] - cnot_y_to_x = [cirq.CNOT(y, x) for x, y in zip(target_x, target_y)] - g_inv_on_y = [list(g(q, True)) for q in target_y] # Uses len(target_y) T-gates - g_on_y = [list(g(q)) for q in target_y] # Uses len(target_y) T-gates - - yield [cnot_y_to_x, g_inv_on_y, cnot_x_to_y, g_inv_on_y] - yield mcmtp.MultiTargetCNOT(len(target_y)).on(*control, *target_y) - yield [g_on_y, cnot_x_to_y, g_on_y, cnot_y_to_x] - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - if not args.use_unicode_characters: - return cirq.CircuitDiagramInfo( - ("@(approx)",) + ("swap_x",) * self.bitsize + ("swap_y",) * self.bitsize - ) - return cirq.CircuitDiagramInfo( - ("@(approx)",) + ("×(x)",) * self.bitsize + ("×(y)",) * self.bitsize - ) - - def __repr__(self) -> str: - return f"cirq_ft.MultiTargetCSwapApprox({self.bitsize})" - - def _t_complexity_(self) -> infra.TComplexity: - """TComplexity as explained in Appendix B.2.c of https://arxiv.org/abs/1812.00954""" - n = self.bitsize - # 4 * n: G gates, each wth 1 T and 4 cliffords - # 4 * n: CNOTs - # 2 * n - 1: CNOTs from 1 MultiTargetCNOT - return infra.TComplexity(t=4 * n, clifford=22 * n - 1) - - -@attr.frozen -class SwapWithZeroGate(infra.GateWithRegisters): - """Swaps |Psi_0> with |Psi_x> if selection register stores index `x`. - - Implements the unitary U |x> |Psi_0> |Psi_1> ... |Psi_{n-1}> --> |x> |Psi_x> |Rest of Psi>. - Note that the state of `|Rest of Psi>` is allowed to be anything and should not be depended - upon. - - References: - [Trading T-gates for dirty qubits in state preparation and unitary synthesis] - (https://arxiv.org/abs/1812.00954). - Low, Kliuchnikov, Schaeffer. 2018. - """ - - selection_bitsize: int - target_bitsize: int - n_target_registers: int - - def __attrs_post_init__(self): - assert self.n_target_registers <= 2**self.selection_bitsize - - @cached_property - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - return ( - infra.SelectionRegister('selection', self.selection_bitsize, self.n_target_registers), - ) - - @cached_property - def target_registers(self) -> Tuple[infra.Register, ...]: - return ( - infra.Register('target', bitsize=self.target_bitsize, shape=self.n_target_registers), - ) - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature([*self.selection_registers, *self.target_registers]) - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - selection, target = quregs['selection'], quregs['target'] - assert target.shape == (self.n_target_registers, self.target_bitsize) - cswap_n = MultiTargetCSwapApprox(self.target_bitsize) - # Imagine a complete binary tree of depth `logN` with `N` leaves, each denoting a target - # register. If the selection register stores index `r`, we want to bring the value stored - # in leaf indexed `r` to the leaf indexed `0`. At each node of the binary tree, the left - # subtree contains node with current bit 0 and right subtree contains nodes with current - # bit 1. Thus, leaf indexed `0` is the leftmost node in the tree. - # Start iterating from the root of the tree. If the j'th bit is set in the selection - # register (i.e. the control would be activated); we know that the value we are searching - # for is in the right subtree. In order to (eventually) bring the desired value to node - # 0; we swap all values in the right subtree with all values in the left subtree. This - # takes (N / (2 ** (j + 1)) swaps at level `j`. - # Therefore, in total, we need $\sum_{j=0}^{logN-1} \frac{N}{2 ^ {j + 1}}$ controlled swaps. - for j in range(len(selection)): - for i in range(0, self.n_target_registers - 2**j, 2 ** (j + 1)): - # The inner loop is executed at-most `N - 1` times, where `N:= len(target_regs)`. - yield cswap_n.on_registers( - control=selection[len(selection) - j - 1], - target_x=target[i], - target_y=target[i + 2**j], - ) - - def __repr__(self) -> str: - return ( - "cirq_ft.SwapWithZeroGate(" - f"{self.selection_bitsize}," - f"{self.target_bitsize}," - f"{self.n_target_registers}" - f")" - ) - - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ["@(r⇋0)"] * self.selection_bitsize - for i in range(self.n_target_registers): - wire_symbols += [f"swap_{i}"] * self.target_bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/swap_network_test.py b/cirq-ft/cirq_ft/algos/swap_network_test.py deleted file mode 100644 index 8b388c9dde8..00000000000 --- a/cirq-ft/cirq_ft/algos/swap_network_test.py +++ /dev/null @@ -1,183 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 random - -import cirq -import cirq_ft -import numpy as np -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - -random.seed(12345) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, n_target_registers", - [[3, 5, 1], [2, 2, 3], [2, 3, 4], [3, 2, 5], [4, 1, 10]], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_gate(selection_bitsize, target_bitsize, n_target_registers): - # Construct the gate. - gate = cirq_ft.SwapWithZeroGate(selection_bitsize, target_bitsize, n_target_registers) - # Allocate selection and target qubits. - all_qubits = cirq.LineQubit.range(cirq.num_qubits(gate)) - selection = all_qubits[:selection_bitsize] - target = np.array(all_qubits[selection_bitsize:]).reshape((n_target_registers, target_bitsize)) - # Create a circuit. - circuit = cirq.Circuit(gate.on_registers(selection=selection, target=target)) - - # Load data[i] in i'th target register; where each register is of size target_bitsize - data = [random.randint(0, 2**target_bitsize - 1) for _ in range(n_target_registers)] - target_state = [int(x) for d in data for x in format(d, f"0{target_bitsize}b")] - - sim = cirq.Simulator(dtype=np.complex128) - expected_state_vector = np.zeros(2**target_bitsize) - # Iterate on every selection integer. - for selection_integer in range(len(data)): - # Load `selection_integer` in the selection register and construct initial state. - selection_state = [int(x) for x in format(selection_integer, f"0{selection_bitsize}b")] - initial_state = selection_state + target_state - # Simulate the circuit with the initial state. - result = sim.simulate(circuit, initial_state=initial_state) - # Get the sub_state_vector corresponding to qubit register `target[0]`. - result_state_vector = cirq.sub_state_vector( - result.final_state_vector, - keep_indices=list(range(selection_bitsize, selection_bitsize + target_bitsize)), - ) - # Expected state vector should correspond to data[selection_integer] due to the swap. - expected_state_vector[data[selection_integer]] = 1 - # Assert that result and expected state vectors are equal; reset and continue. - assert cirq.equal_up_to_global_phase(result_state_vector, expected_state_vector) - expected_state_vector[data[selection_integer]] = 0 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_gate_diagram(): - gate = cirq_ft.SwapWithZeroGate(3, 2, 4) - q = cirq.LineQubit.range(cirq.num_qubits(gate)) - circuit = cirq.Circuit(gate.on_registers(**infra.split_qubits(gate.signature, q))) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ────@(r⇋0)─── - │ -1: ────@(r⇋0)─── - │ -2: ────@(r⇋0)─── - │ -3: ────swap_0─── - │ -4: ────swap_0─── - │ -5: ────swap_1─── - │ -6: ────swap_1─── - │ -7: ────swap_2─── - │ -8: ────swap_2─── - │ -9: ────swap_3─── - │ -10: ───swap_3─── -""", - ) - cirq.testing.assert_equivalent_repr(gate, setup_code='import cirq_ft') - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cswap(): - qubits = cirq.LineQubit.range(5) - c, q_x, q_y = qubits[0], qubits[1:3], qubits[3:] - cswap = cirq_ft.MultiTargetCSwap(2).on_registers(control=c, target_x=q_x, target_y=q_y) - cswap_approx = cirq_ft.MultiTargetCSwapApprox(2).on_registers( - control=c, target_x=q_x, target_y=q_y - ) - setup_code = "import cirq\nimport cirq_ft" - cirq.testing.assert_implements_consistent_protocols(cswap, setup_code=setup_code) - cirq.testing.assert_implements_consistent_protocols(cswap_approx, setup_code=setup_code) - circuit = cirq.Circuit(cswap, cswap_approx) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ───@──────@(approx)─── - │ │ -1: ───×(x)───×(x)──────── - │ │ -2: ───×(x)───×(x)──────── - │ │ -3: ───×(y)───×(y)──────── - │ │ -4: ───×(y)───×(y)──────── - """, - ) - cirq.testing.assert_has_diagram( - circuit, - """ -0: ---@--------@(approx)--- - | | -1: ---swap_x---swap_x------ - | | -2: ---swap_x---swap_x------ - | | -3: ---swap_y---swap_y------ - | | -4: ---swap_y---swap_y------ - """, - use_unicode_characters=False, - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_target_cswap_make_on(): - qubits = cirq.LineQubit.range(5) - c, q_x, q_y = qubits[:1], qubits[1:3], qubits[3:] - cswap1 = cirq_ft.MultiTargetCSwap(2).on_registers(control=c, target_x=q_x, target_y=q_y) - cswap2 = cirq_ft.MultiTargetCSwap.make_on(control=c, target_x=q_x, target_y=q_y) - assert cswap1 == cswap2 - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('swap_network') - - -@pytest.mark.parametrize("n", [*range(1, 6)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(n): - g = cirq_ft.MultiTargetCSwap(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - g = cirq_ft.MultiTargetCSwapApprox(n) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(g) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, n_target_registers, want", - [ - [3, 5, 1, (0, 0)], - [2, 2, 3, (16, 86)], - [2, 3, 4, (36, 195)], - [3, 2, 5, (32, 172)], - [4, 1, 10, (36, 189)], - ], -) -@allow_deprecated_cirq_ft_use_in_tests -def test_swap_with_zero_t_complexity(selection_bitsize, target_bitsize, n_target_registers, want): - t_complexity = cirq_ft.TComplexity(t=want[0], clifford=want[1]) - gate = cirq_ft.SwapWithZeroGate(selection_bitsize, target_bitsize, n_target_registers) - assert t_complexity == cirq_ft.t_complexity(gate) diff --git a/cirq-ft/cirq_ft/algos/unary_iteration.ipynb b/cirq-ft/cirq_ft/algos/unary_iteration.ipynb deleted file mode 100644 index aa3bda1e117..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration.ipynb +++ /dev/null @@ -1,575 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "e2fa907b", - "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": "49b5e1e6", - "metadata": {}, - "source": [ - "# Unary Iteration" - ] - }, - { - "cell_type": "markdown", - "id": "fcdb39f2", - "metadata": {}, - "source": [ - "Given an array of potential operations, for example:\n", - "\n", - " ops = [X(i) for i in range(5)]\n", - " \n", - "we would like to select an operation to apply:\n", - "\n", - " n = 4 --> apply ops[4]\n", - " \n", - "If $n$ is a quantum integer, we need to apply the transformation\n", - "\n", - "$$\n", - " |n \\rangle |\\psi\\rangle \\rightarrow |n\\rangle \\, \\mathrm{ops}_n \\cdot |\\psi\\rangle\n", - "$$\n", - "\n", - "The simplest conceptual way to do this is to use a \"total control\" quantum circuit where you introduce a multi-controlled operation for each of the `len(ops)` possible `n` values." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "0148f529", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq\n", - "from cirq.contrib.svg import SVGCircuit\n", - "import numpy as np\n", - "from typing import *" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32e90969", - "metadata": {}, - "outputs": [], - "source": [ - "import operator\n", - "import cirq._compat\n", - "import itertools" - ] - }, - { - "cell_type": "markdown", - "id": "a6d947da", - "metadata": {}, - "source": [ - "## Total Control\n", - "\n", - "Here, we'll use Sympy's boolean logic to show how total control works. We perform an `And( ... )` for each possible bit pattern. We use an `Xnor` on each selection bit to toggle whether it's a positive or negative control (filled or open circle in quantum circuit diagrams).\n", - "\n", - "In this example, we indeed consider $X_n$ as our potential operations and toggle bits in the `target` register according to the total control." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8e61bf03", - "metadata": { - "scrolled": false - }, - "outputs": [], - "source": [ - "import sympy as S\n", - "import sympy.logic.boolalg as slb\n", - "\n", - "def total_control(selection, target):\n", - " \"\"\"Toggle bits in `target` depending on `selection`.\"\"\"\n", - " print(f\"Selection is {selection}\")\n", - " \n", - " for n, trial in enumerate(itertools.product((0, 1), repeat=len(selection))):\n", - " print(f\"Step {n}, apply total control: {trial}\")\n", - " target[n] ^= slb.And(*[slb.Xnor(s, t) for s, t in zip(selection, trial)])\n", - " \n", - " if target[n] == S.true:\n", - " print(f\" -> At this stage, {n}= and our output bit is set\")\n", - "\n", - " \n", - "selection = [0, 0, 0]\n", - "target = [False]*8\n", - "total_control(selection, target) \n", - "print()\n", - "print(\"Target:\")\n", - "print(target)" - ] - }, - { - "cell_type": "markdown", - "id": "e572a31d", - "metadata": {}, - "source": [ - "Note that our target register shows we have indeed applied $X_\\mathrm{0b010}$. Try changing `selection` to other bit patterns and notice how it changes." - ] - }, - { - "cell_type": "markdown", - "id": "a4a75f61", - "metadata": {}, - "source": [ - "Of course, we don't know what state the selection register will be in. We can use sympy's support for symbolic boolean logic to verify our gadget for all possible selection inputs." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5df67d45", - "metadata": {}, - "outputs": [], - "source": [ - "selection = [S.Symbol(f's{i}') for i in range(3)]\n", - "target = [S.false for i in range(2**len(selection)) ]\n", - "total_control(selection, target)\n", - "\n", - "print()\n", - "print(\"Target:\")\n", - "for n, t in enumerate(target):\n", - " print(f'{n}= {t}')\n", - " \n", - "tc_target = target.copy()" - ] - }, - { - "cell_type": "markdown", - "id": "deab0553", - "metadata": {}, - "source": [ - "As expected, the \"not pattern\" (where `~` is boolean not) matches the binary representations of `n`." - ] - }, - { - "cell_type": "markdown", - "id": "81b69e70", - "metadata": {}, - "source": [ - "## Unary Iteration with segment trees\n", - "\n", - "A [segment tree](https://en.wikipedia.org/wiki/Segment_tree) is a data structure that allows logrithmic-time querying of intervals. We use a segment tree where each interval is length 1 and comprises all the `n` integers we may select.\n", - "\n", - "It is defined recursively by dividing the input interval into two half-size intervals until the left limit meets the right limit." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ab998aa4", - "metadata": {}, - "outputs": [], - "source": [ - "def segtree(ctrl, selection, target, depth, left, right):\n", - " \"\"\"Toggle bits in `target` depending on `selection` using a recursive segment tree.\"\"\"\n", - " print(f'depth={depth} left={left} right={right}', end=' ')\n", - " \n", - " if left == (right - 1):\n", - " # Leaf of the recusion.\n", - " print(f'n={n} ctrl={ctrl}')\n", - " target[left] ^= ctrl\n", - " return \n", - " print()\n", - " \n", - " assert depth < len(selection)\n", - " mid = (left + right) >> 1\n", - " \n", - " # Recurse left interval\n", - " new_ctrl = slb.And(ctrl, slb.Not(selection[depth]))\n", - " segtree(ctrl=new_ctrl, selection=selection, target=target, depth=depth+1, left=left, right=mid)\n", - " \n", - " # Recurse right interval\n", - " new_ctrl = slb.And(ctrl, selection[depth])\n", - " segtree(ctrl=new_ctrl, selection=selection, target=target, depth=depth+1, left=mid, right=right)\n", - " \n", - " # Quantum note:\n", - " # instead of throwing away the first value of `new_ctrl` and re-anding\n", - " # with selection, we can just invert the first one (but only if `ctrl` is active)\n", - " # new_ctrl ^= ctrl" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6a514ee6", - "metadata": {}, - "outputs": [], - "source": [ - "selection = [S.Symbol(f's{i}') for i in range(3)]\n", - "target = [S.false for i in range(2**len(selection)) ]\n", - "segtree(S.true, selection, target, 0, 0, 2**len(selection))\n", - "\n", - "print()\n", - "print(\"Target:\")\n", - "for n, t in enumerate(target):\n", - " print(f'n={n} {slb.simplify_logic(t)}')" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "23d91438", - "metadata": {}, - "outputs": [], - "source": [ - "print(f\"{'n':3s} | {'segtree':18s} | {'total control':18s} | same?\")\n", - "for n, (t1, t2) in enumerate(zip(target, tc_target)):\n", - " t1 = slb.simplify_logic(t1)\n", - " print(f'{n:3d} | {str(t1):18s} | {str(t2):18s} | {str(t1==t2)}')" - ] - }, - { - "cell_type": "markdown", - "id": "e39448e6", - "metadata": {}, - "source": [ - "## Quantum Circuit\n", - "\n", - "We can translate the boolean logic to reversible, quantum logic. It is instructive to start from the suboptimal total control quantum circuit for comparison purposes. We can build this as in the sympy boolean-logic case by adding controlled X operations to the target signature, with the controls on the selection signature toggled on or off according to the binary representation of the selection index.\n", - "\n", - "Let us first build a GateWithRegisters object to implement the circuit" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6b37d717", - "metadata": {}, - "outputs": [], - "source": [ - "from functools import cached_property\n", - "import cirq\n", - "from cirq_ft import Signature, GateWithRegisters\n", - "from cirq_ft.infra.bit_tools import iter_bits\n", - "\n", - "class TotallyControlledNot(GateWithRegisters):\n", - " \n", - " def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1):\n", - " self._selection_bitsize = selection_bitsize\n", - " self._target_bitsize = target_bitsize\n", - " self._control_bitsize = control_bitsize\n", - "\n", - " @cached_property\n", - " def signature(self) -> Signature:\n", - " return Signature(\n", - " [\n", - " *Signature.build(control=self._control_bitsize),\n", - " *Signature.build(selection=self._selection_bitsize),\n", - " *Signature.build(target=self._target_bitsize)\n", - " ]\n", - " )\n", - "\n", - " def decompose_from_registers(self, **qubit_regs: Sequence[cirq.Qid]) -> cirq.OP_TREE:\n", - " num_controls = self._control_bitsize + self._selection_bitsize\n", - " for target_bit in range(self._target_bitsize):\n", - " bit_pattern = iter_bits(target_bit, self._selection_bitsize)\n", - " control_values = [1]*self._control_bitsize + list(bit_pattern)\n", - " yield cirq.X.controlled(\n", - " num_controls=num_controls,\n", - " control_values=control_values\n", - " ).on(\n", - " *qubit_regs[\"control\"], \n", - " *qubit_regs[\"selection\"],\n", - " qubit_regs[\"target\"][-(target_bit+1)])\n", - " " - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1f7b6758", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq_ft.infra.testing as cq_testing\n", - "tc_not = TotallyControlledNot(3, 5)\n", - "tc = cq_testing.GateHelper(tc_not)\n", - "cirq.Circuit((cirq.decompose_once(tc.operation)))\n", - "SVGCircuit(cirq.Circuit(cirq.decompose_once(tc.operation)))" - ] - }, - { - "cell_type": "markdown", - "id": "7b28663a", - "metadata": {}, - "source": [ - "## Tests for Correctness\n", - "\n", - "We can use a full statevector simulation to compare the desired statevector to the one generated by the unary iteration circuit for each basis state." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "574c5058", - "metadata": {}, - "outputs": [], - "source": [ - "selection_bitsize = 3\n", - "target_bitsize = 5\n", - "for n in range(target_bitsize):\n", - " # Initial qubit values\n", - " qubit_vals = {q: 0 for q in tc.all_qubits}\n", - " # All controls 'on' to activate circuit\n", - " qubit_vals.update({c: 1 for c in tc.quregs['control']})\n", - " # Set selection according to `n`\n", - " qubit_vals.update(zip(tc.quregs['selection'], iter_bits(n, selection_bitsize)))\n", - "\n", - " initial_state = [qubit_vals[x] for x in tc.all_qubits]\n", - " final_state = [qubit_vals[x] for x in tc.all_qubits]\n", - " final_state[-(n+1)] = 1\n", - " cq_testing.assert_circuit_inp_out_cirqsim(\n", - " tc.circuit, tc.all_qubits, initial_state, final_state\n", - " )\n", - " print(f'n={n} checked!')" - ] - }, - { - "cell_type": "markdown", - "id": "d76fcf8f", - "metadata": {}, - "source": [ - "## Towards a segment tree \n", - "\n", - "Next let's see how we can reduce the circuit to the observe the tree structure.\n", - "First let's recall what we are trying to do with the controlled not. Given a\n", - "selection integer (say 3 = 011), we want to toggle the bit in the target\n", - "register to on if the qubit 1 and 2 are set to on in the selection register." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3aca2666", - "metadata": {}, - "outputs": [], - "source": [ - "# The selection bits [1-3] are set according to binary representation of the number 3 (011)\n", - "initial_state = [1, 0, 1, 1, 0, 0, 0, 0, 0]\n", - "final_state = [1, 0, 1, 1, 0, 1, 0, 0, 0]\n", - "actual, should_be = cq_testing.get_circuit_inp_out_cirqsim(\n", - " tc.circuit, tc.all_qubits, initial_state, final_state\n", - " )\n", - "print(\"simulated: \", actual)\n", - "print(\"expected : \", should_be)\n" - ] - }, - { - "cell_type": "markdown", - "id": "4640eeed", - "metadata": {}, - "source": [ - "Now what is important to note is that we can remove many repeated controlled operations by using ancilla qubits to flag what part of the circuit we need to apply, this works because we know the bit pattern of nearby integers is very similar. \n", - "\n", - "A circuit demonstrating this for our example is given below." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ef853ae7", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq_ft.algos.and_gate import And\n", - "\n", - "selection_bitsize = 2\n", - "target_bitsize = 4\n", - "qubits = cirq.LineQubit(0).range(1 + selection_bitsize * 2 + target_bitsize)\n", - "circuit = cirq.Circuit()\n", - "circuit.append(\n", - " [\n", - " And((1, 0)).on(qubits[0], qubits[1], qubits[2]),\n", - " And((1, 0)).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[8]),\n", - " cirq.CNOT(qubits[2], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[7]),\n", - " And(adjoint=True).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CNOT(qubits[0], qubits[2]),\n", - " And((1, 0)).on(qubits[2], qubits[3], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[6]),\n", - " cirq.CNOT(qubits[2], qubits[4]),\n", - " cirq.CX(qubits[4], qubits[5]),\n", - " And(adjoint=True).on(qubits[2], qubits[3], qubits[4]),\n", - " And(adjoint=True).on(qubits[0], qubits[1], qubits[2]),\n", - " ]\n", - ")\n", - "\n", - "SVGCircuit(circuit)" - ] - }, - { - "cell_type": "markdown", - "id": "b9d45d52", - "metadata": {}, - "source": [ - "Reading from left to right we first check the control is set to on and the selection qubit is off, if both these conditions are met then the ancilla qubit is now set to 1. The next control checks if the previous condition was met and likewise the second selection index is also off. At this point if both these conditions are met we must be indexing 0 as the first two qubits are set to off (00), otherwise we know that we want to apply X to qubit 1 so we perform a CNOT operation to flip the bit value in the second ancilla qubit, before returning back up the circuit. Now if the left half of the circuit was not applied (i.e. the first selection register was set to 1) then the CNOT between the control qubit and the first ancilla qubit causes the ancilla qubit to toggle on. This triggers the right side of the circuit, which now performs the previously described operations to figure out if the lowest bit is set. Combining these two then yields the expected controlled X operation. \n", - "\n", - "Below we check the circuit is giving the expected behaviour." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "83d1287d", - "metadata": {}, - "outputs": [], - "source": [ - "initial_state = [1, 0, 0, 0, 0, 0, 0, 0, 0]\n", - "target_indx = 3\n", - "sel_bits = list(iter_bits(target_indx, selection_bitsize))\n", - "sel_indices = [i for i in range(1, 2*selection_bitsize+1, 2)]\n", - "initial_state[sel_indices[0]] = sel_bits[0]\n", - "initial_state[sel_indices[1]] = sel_bits[1]\n", - "result = cirq.Simulator(dtype=np.complex128).simulate(\n", - " circuit, initial_state=initial_state\n", - ")\n", - "actual = result.dirac_notation(decimals=2)[1:-1]\n", - "print(\"simulated: {}, index set in string {}\".format(actual, len(qubits)-1-target_indx))" - ] - }, - { - "cell_type": "markdown", - "id": "a86e0d42", - "metadata": {}, - "source": [ - "Extending the above idea to larger ranges of integers is relatively straightforward. For example consider the next simplest case of $L=8 = 2^3$. The circuit above takes care of the last two bits and can be duplicated. For the extra bit we just need to add a additional `AND` operations, and a CNOT to switch between the original range `[0,3]` or the new range `[4,7]` depending on whether the new selection register is off or on respectively. This procedure can be repeated and we can begin to notice the recursive tree-like structure. \n", - "\n", - "This structure is just the segtree described previously for boolean logic and this gives is the basic idea of unary iteration, \n", - "which uses `L-1` `AND` operations. Below the `ApplyXToLthQubit` builds the controlled Not operation using the `UnaryIterationGate` as a base class which defines the `decompose_from_registers` method appropriately to recursively construct the unary iteration circuit.\n", - "\n", - "Note below a different ordering of ancilla and selection qubits is taken to what was used in the simpler `L=4` example." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9cba52b1", - "metadata": {}, - "outputs": [], - "source": [ - "from functools import cached_property\n", - "from cirq_ft import Register, SelectionRegister, UnaryIterationGate\n", - "\n", - "class ApplyXToLthQubit(UnaryIterationGate):\n", - " def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1):\n", - " self._selection_bitsize = selection_bitsize\n", - " self._target_bitsize = target_bitsize\n", - " self._control_bitsize = control_bitsize\n", - "\n", - " @cached_property\n", - " def control_registers(self) -> Tuple[Register, ...]:\n", - " return Register('control', self._control_bitsize),\n", - "\n", - " @cached_property\n", - " def selection_registers(self) -> Tuple[SelectionRegister, ...]:\n", - " return SelectionRegister('selection', self._selection_bitsize, self._target_bitsize),\n", - "\n", - " @cached_property\n", - " def target_registers(self) -> Tuple[Register, ...]:\n", - " return Register('target', self._target_bitsize),\n", - "\n", - " def nth_operation(\n", - " self, context, control: cirq.Qid, selection: int, target: Sequence[cirq.Qid]\n", - " ) -> cirq.OP_TREE:\n", - " return cirq.CNOT(control, target[-(selection + 1)])" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a1e4bafa", - "metadata": {}, - "outputs": [], - "source": [ - "import cirq_ft.infra.testing as cq_testing\n", - "selection_bitsize = 3\n", - "target_bitsize = 5\n", - "\n", - "g = cq_testing.GateHelper(\n", - " ApplyXToLthQubit(selection_bitsize, target_bitsize))\n", - "SVGCircuit(cirq.Circuit(cirq.decompose_once(g.operation)))" - ] - }, - { - "cell_type": "markdown", - "id": "13773620", - "metadata": {}, - "source": [ - "## Tests for Correctness\n", - "\n", - "We can use a full statevector simulation to check again that the optimized circuit produces the expected result." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "32ae469b", - "metadata": {}, - "outputs": [], - "source": [ - "from cirq_ft.infra.bit_tools import iter_bits\n", - "\n", - "for n in range(target_bitsize):\n", - " # Initial qubit values\n", - " qubit_vals = {q: 0 for q in g.all_qubits}\n", - " # All controls 'on' to activate circuit\n", - " qubit_vals.update({c: 1 for c in g.quregs['control']})\n", - " # Set selection according to `n`\n", - " qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize)))\n", - "\n", - " initial_state = [qubit_vals[x] for x in g.all_qubits]\n", - " qubit_vals[g.quregs['target'][-(n + 1)]] = 1\n", - " final_state = [qubit_vals[x] for x in g.all_qubits]\n", - " cq_testing.assert_circuit_inp_out_cirqsim(\n", - " g.decomposed_circuit, g.all_qubits, initial_state, final_state\n", - " )\n", - " print(f'n={n} checked!')" - ] - } - ], - "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 -} diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate.py deleted file mode 100644 index 6cd5d3413cb..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate.py +++ /dev/null @@ -1,448 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 -from functools import cached_property -from typing import Callable, Dict, Iterator, List, Sequence, Tuple -from numpy.typing import NDArray - -import cirq -import numpy as np - -from cirq_ft import infra -from cirq_ft.algos import and_gate -from cirq_ft.deprecation import deprecated_cirq_ft_function - - -def _unary_iteration_segtree( - ops: List[cirq.Operation], - control: cirq.Qid, - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - sl: int, - l: int, - r: int, - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - """Constructs a unary iteration circuit by iterating over nodes of an implicit Segment Tree. - - Args: - ops: Operations accumulated so far while traversing the implicit segment tree. The - accumulated ops are yielded and cleared when we reach a leaf node. - control: The control qubit that controls the execution of the entire unary iteration - circuit represented by the current node of the segment tree. - selection: Sequence of selection qubits. The i'th qubit in the list corresponds to the i'th - level in the segment tree.Thus, a total of O(logN) selection qubits are required for a - tree on range `N = (r_iter - l_iter)`. - ancilla: Pre-allocated ancilla qubits to be used for constructing the unary iteration - circuit. - sl: Current depth of the tree. `selection[sl]` gives the selection qubit corresponding to - the current depth. - l: Left index of the range represented by current node of the segment tree. - r: Right index of the range represented by current node of the segment tree. - l_iter: Left index of iteration range over which the segment tree should be constructed. - r_iter: Right index of iteration range over which the segment tree should be constructed. - break_early: For each internal node of the segment tree, `break_early(l, r)` is called to - evaluate whether the unary iteration should terminate early and not recurse in the - subtree of the node representing range `[l, r)`. If True, the internal node is - considered equivalent to a leaf node and the method yields only one tuple - `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`. - - Yields: - One `Tuple[cirq.OP_TREE, cirq.Qid, int]` for each leaf node in the segment tree. The i'th - yielded element corresponds to the i'th leaf node which represents the `l_iter + i`'th - integer. The tuple corresponds to: - - cirq.OP_TREE: Operations to be inserted in the circuit in between the last leaf node - (or beginning of iteration) to the current leaf node. - - cirq.Qid: The control qubit which can be controlled upon to execute the $U_{l}$ on a - target register when the selection register stores integer $l$. - - int: Integer $l$ which would be stored in the selection register if the control qubit - is set. - """ - if l >= r_iter or l_iter >= r: - # Range corresponding to this node is completely outside of iteration range. - return - if l_iter <= l < r <= r_iter and (l == (r - 1) or break_early(l, r)): - # Reached a leaf node or a "special" internal node; yield the operations. - yield tuple(ops), control, l - ops.clear() - return - assert sl < len(selection) - m = (l + r) >> 1 - if r_iter <= m: - # Yield only left sub-tree. - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl + 1, l, m, l_iter, r_iter, break_early - ) - return - if l_iter >= m: - # Yield only right sub-tree - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl + 1, m, r, l_iter, r_iter, break_early - ) - return - anc, sq = ancilla[sl], selection[sl] - ops.append(and_gate.And((1, 0)).on(control, sq, anc)) - yield from _unary_iteration_segtree( - ops, anc, selection, ancilla, sl + 1, l, m, l_iter, r_iter, break_early - ) - ops.append(cirq.CNOT(control, anc)) - yield from _unary_iteration_segtree( - ops, anc, selection, ancilla, sl + 1, m, r, l_iter, r_iter, break_early - ) - ops.append(and_gate.And(adjoint=True).on(control, sq, anc)) - - -def _unary_iteration_zero_control( - ops: List[cirq.Operation], - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - sl, l, r = 0, 0, 2 ** len(selection) - m = (l + r) >> 1 - ops.append(cirq.X(selection[0])) - yield from _unary_iteration_segtree( - ops, selection[0], selection[1:], ancilla, sl, l, m, l_iter, r_iter, break_early - ) - ops.append(cirq.X(selection[0])) - yield from _unary_iteration_segtree( - ops, selection[0], selection[1:], ancilla, sl, m, r, l_iter, r_iter, break_early - ) - - -def _unary_iteration_single_control( - ops: List[cirq.Operation], - control: cirq.Qid, - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - sl, l, r = 0, 0, 2 ** len(selection) - yield from _unary_iteration_segtree( - ops, control, selection, ancilla, sl, l, r, l_iter, r_iter, break_early - ) - - -def _unary_iteration_multi_controls( - ops: List[cirq.Operation], - controls: Sequence[cirq.Qid], - selection: Sequence[cirq.Qid], - ancilla: Sequence[cirq.Qid], - l_iter: int, - r_iter: int, - break_early: Callable[[int, int], bool], -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - num_controls = len(controls) - and_ancilla = ancilla[: num_controls - 2] - and_target = ancilla[num_controls - 2] - multi_controlled_and = and_gate.And((1,) * len(controls)).on_registers( - ctrl=np.array(controls).reshape(len(controls), 1), - junk=np.array(and_ancilla).reshape(len(and_ancilla), 1), - target=and_target, - ) - ops.append(multi_controlled_and) - yield from _unary_iteration_single_control( - ops, and_target, selection, ancilla[num_controls - 1 :], l_iter, r_iter, break_early - ) - ops.append(cirq.inverse(multi_controlled_and)) - - -@deprecated_cirq_ft_function() -def unary_iteration( - l_iter: int, - r_iter: int, - flanking_ops: List[cirq.Operation], - controls: Sequence[cirq.Qid], - selection: Sequence[cirq.Qid], - qubit_manager: cirq.QubitManager, - break_early: Callable[[int, int], bool] = lambda l, r: False, -) -> Iterator[Tuple[cirq.OP_TREE, cirq.Qid, int]]: - """The method performs unary iteration on `selection` integer in `range(l_iter, r_iter)`. - - Unary iteration is a coherent for loop that can be used to conditionally perform a different - operation on a target register for every integer in the `range(l_iter, r_iter)` stored in the - selection register. - - Users can write multi-dimensional coherent for loops as follows: - - >>> import cirq - >>> from cirq_ft import unary_iteration - >>> N, M = 5, 7 - >>> target = [[cirq.q(f't({i}, {j})') for j in range(M)] for i in range(N)] - >>> selection = [[cirq.q(f's({i}, {j})') for j in range(3)] for i in range(3)] - >>> circuit = cirq.Circuit() - >>> i_ops = [] - >>> qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) - >>> for i_optree, i_ctrl, i in unary_iteration(0, N, i_ops, [], selection[0], qm): - ... circuit.append(i_optree) - ... j_ops = [] - ... for j_optree, j_ctrl, j in unary_iteration(0, M, j_ops, [i_ctrl], selection[1], qm): - ... circuit.append(j_optree) - ... # Conditionally perform operations on target register using `j_ctrl`, `i` & `j`. - ... circuit.append(cirq.CNOT(j_ctrl, target[i][j])) - ... circuit.append(j_ops) - >>> circuit.append(i_ops) - - Note: Unary iteration circuits assume that the selection register stores integers only in the - range `[l, r)` for which the corresponding unary iteration circuit should be built. - - Args: - l_iter: Starting index of the iteration range. - r_iter: Ending index of the iteration range. - flanking_ops: A list of `cirq.Operation`s that represents operations to be inserted in the - circuit before/after the first/last iteration of the unary iteration for loop. Note that - the list is mutated by the function, such that before calling the function, the list - represents operations to be inserted before the first iteration and after the last call - to the function, list represents operations to be inserted at the end of last iteration. - controls: Control register of qubits. - selection: Selection register of qubits. - qubit_manager: A `cirq.QubitManager` to allocate new qubits. - break_early: For each internal node of the segment tree, `break_early(l, r)` is called to - evaluate whether the unary iteration should terminate early and not recurse in the - subtree of the node representing range `[l, r)`. If True, the internal node is - considered equivalent to a leaf node and the method yields only one tuple - `(OP_TREE, control_qubit, l)` for all integers in the range `[l, r)`. - - Yields: - (r_iter - l_iter) different tuples, each corresponding to an integer in range - [l_iter, r_iter). - Each returned tuple also corresponds to a unique leaf in the unary iteration tree. - The values of yielded `Tuple[cirq.OP_TREE, cirq.Qid, int]` correspond to: - - cirq.OP_TREE: The op-tree to be inserted in the circuit to get to the current leaf. - - cirq.Qid: Control qubit used to conditionally apply operations on the target conditioned - on the returned integer. - - int: The current integer in the iteration `range(l_iter, r_iter)`. - """ - assert 2 ** len(selection) >= r_iter - l_iter - assert len(selection) > 0 - ancilla = qubit_manager.qalloc(max(0, len(controls) + len(selection) - 1)) - if len(controls) == 0: - yield from _unary_iteration_zero_control( - flanking_ops, selection, ancilla, l_iter, r_iter, break_early - ) - elif len(controls) == 1: - yield from _unary_iteration_single_control( - flanking_ops, controls[0], selection, ancilla, l_iter, r_iter, break_early - ) - else: - yield from _unary_iteration_multi_controls( - flanking_ops, controls, selection, ancilla, l_iter, r_iter, break_early - ) - qubit_manager.qfree(ancilla) - - -class UnaryIterationGate(infra.GateWithRegisters): - """Base class for defining multiplexed gates that can execute a coherent for-loop. - - Unary iteration is a coherent for loop that can be used to conditionally perform a different - operation on a target register for every integer in the `range(l_iter, r_iter)` stored in the - selection register. - - `cirq_ft.UnaryIterationGate` leverages the utility method `cirq_ft.unary_iteration` to provide - a convenient API for users to define a multi-dimensional multiplexed gate that can execute - indexed operations on a target register depending on the index value stored in a selection - register. - - Note: Unary iteration circuits assume that the selection register stores integers only in the - range `[l, r)` for which the corresponding unary iteration circuit should be built. - - References: - [Encoding Electronic Spectra in Quantum Circuits with Linear T Complexity] - (https://arxiv.org/abs/1805.03662). - Babbush et. al. (2018). Section III.A. - """ - - @cached_property - @abc.abstractmethod - def control_registers(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - @abc.abstractmethod - def selection_registers(self) -> Tuple[infra.SelectionRegister, ...]: - pass - - @cached_property - @abc.abstractmethod - def target_registers(self) -> Tuple[infra.Register, ...]: - pass - - @cached_property - def signature(self) -> infra.Signature: - return infra.Signature( - [*self.control_registers, *self.selection_registers, *self.target_registers] - ) - - @cached_property - def extra_registers(self) -> Tuple[infra.Register, ...]: - return () - - @abc.abstractmethod - def nth_operation( - self, context: cirq.DecompositionContext, control: cirq.Qid, **kwargs - ) -> cirq.OP_TREE: - """Apply nth operation on the target signature when selection signature store `n`. - - The `UnaryIterationGate` class is a mixin that represents a coherent for-loop over - different indices (i.e. selection signature). This method denotes the "body" of the - for-loop, which is executed `self.selection_registers.total_iteration_size` times and each - iteration represents a unique combination of values stored in selection signature. For each - call, the method should return the operations that should be applied to the target - signature, given the values stored in selection signature. - - The derived classes should specify the following arguments as `**kwargs`: - 1) `control: cirq.Qid`: A qubit which can be used as a control to selectively - apply operations when selection register stores specific value. - 2) Register names in `self.selection_registers`: Each argument corresponds to - a selection register and represents the integer value stored in the register. - 3) Register names in `self.target_registers`: Each argument corresponds to a target - register and represents the sequence of qubits that represent the target register. - 4) Register names in `self.extra_regs`: Each argument corresponds to an extra - register and represents the sequence of qubits that represent the extra register. - """ - - def decompose_zero_selection( - self, - context: cirq.DecompositionContext, - **quregs: NDArray[cirq.Qid], # type: ignore[type-var] - ) -> cirq.OP_TREE: - """Specify decomposition of the gate when selection register is empty - - By default, if the selection register is empty, the decomposition will raise a - `NotImplementedError`. The derived classes can override this method and specify - a custom decomposition that should be used if the selection register is empty, - i.e. `infra.total_bits(self.selection_registers) == 0`. - - The derived classes should specify the following arguments as `**kwargs`: - 1) Register names in `self.control_registers`: Each argument corresponds to a - control register and represents sequence of qubits that represent the control register. - 2) Register names in `self.target_registers`: Each argument corresponds to a target - register and represents the sequence of qubits that represent the target register. - 3) Register names in `self.extra_regs`: Each argument corresponds to an extra - register and represents the sequence of qubits that represent the extra register. - """ - raise NotImplementedError("Selection register must not be empty.") - - def _break_early(self, selection_index_prefix: Tuple[int, ...], l: int, r: int) -> bool: - """Derived classes should override this method to specify an early termination condition. - - For each internal node of the unary iteration segment tree, `break_early(l, r)` is called - to evaluate whether the unary iteration should not recurse in the subtree of the node - representing range `[l, r)`. If True, the internal node is considered equivalent to a leaf - node and thus, `self.nth_operation` will be called for only integer `l` in the range [l, r). - - When the `UnaryIteration` class is constructed using multiple selection signature, i.e. we - wish to perform nested coherent for-loops, a unary iteration segment tree is constructed - corresponding to each nested coherent for-loop. For every such unary iteration segment tree, - the `_break_early` condition is checked by passing the `selection_index_prefix` tuple. - - Args: - selection_index_prefix: To evaluate the early breaking condition for the i'th nested - for-loop, the `selection_index_prefix` contains `i-1` integers corresponding to - the loop variable values for the first `i-1` nested loops. - l: Beginning of range `[l, r)` for internal node of unary iteration segment tree. - r: End (exclusive) of range `[l, r)` for internal node of unary iteration segment tree. - - Returns: - True of the `len(selection_index_prefix)`'th unary iteration should terminate early for - the given parameters. - """ - return False - - def decompose_from_registers( - self, *, context: cirq.DecompositionContext, **quregs: NDArray[cirq.Qid] - ) -> cirq.OP_TREE: - if infra.total_bits(self.selection_registers) == 0 or self._break_early( - (), 0, self.selection_registers[0].iteration_length - ): - return self.decompose_zero_selection(context=context, **quregs) - - num_loops = len(self.selection_registers) - target_regs = {reg.name: quregs[reg.name] for reg in self.target_registers} - extra_regs = {reg.name: quregs[reg.name] for reg in self.extra_registers} - - def unary_iteration_loops( - nested_depth: int, - selection_reg_name_to_val: Dict[str, int], - controls: Sequence[cirq.Qid], - ) -> Iterator[cirq.OP_TREE]: - """Recursively write any number of nested coherent for-loops using unary iteration. - - This helper method is useful to write `num_loops` number of nested coherent for-loops by - recursively calling this method `num_loops` times. The ith recursive call of this method - has `nested_depth=i` and represents the body of ith nested for-loop. - - Args: - nested_depth: Integer between `[0, num_loops]` representing the nest-level of - for-loop for which this method implements the body. - selection_reg_name_to_val: A dictionary containing `nested_depth` elements mapping - the selection integer names (i.e. loop variables) to corresponding values; - for each of the `nested_depth` parent for-loops written before. - controls: Control qubits that should be used to conditionally activate the body of - this for-loop. - - Returns: - `cirq.OP_TREE` implementing `num_loops` nested coherent for-loops, with operations - returned by `self.nth_operation` applied conditionally to the target register based - on values of selection signature. - """ - if nested_depth == num_loops: - yield self.nth_operation( - context=context, - control=controls[0], - **selection_reg_name_to_val, - **target_regs, - **extra_regs, - ) - return - # Use recursion to write `num_loops` nested loops using unary_iteration(). - ops: List[cirq.Operation] = [] - selection_index_prefix = tuple(selection_reg_name_to_val.values()) - ith_for_loop = unary_iteration( - l_iter=0, - r_iter=self.selection_registers[nested_depth].iteration_length, - flanking_ops=ops, - controls=controls, - selection=[*quregs[self.selection_registers[nested_depth].name]], - qubit_manager=context.qubit_manager, - break_early=lambda l, r: self._break_early(selection_index_prefix, l, r), - ) - for op_tree, control_qid, n in ith_for_loop: - yield op_tree - selection_reg_name_to_val[self.selection_registers[nested_depth].name] = n - yield from unary_iteration_loops( - nested_depth + 1, selection_reg_name_to_val, (control_qid,) - ) - selection_reg_name_to_val.pop(self.selection_registers[nested_depth].name) - yield ops - - return unary_iteration_loops(0, {}, infra.merge_qubits(self.control_registers, **quregs)) - - def _circuit_diagram_info_(self, args: cirq.CircuitDiagramInfoArgs) -> cirq.CircuitDiagramInfo: - """Basic circuit diagram. - - Descendants are encouraged to override this with more descriptive - circuit diagram information. - """ - wire_symbols = ["@"] * infra.total_bits(self.control_registers) - wire_symbols += ["In"] * infra.total_bits(self.selection_registers) - wire_symbols += [self.__class__.__name__] * infra.total_bits(self.target_registers) - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) diff --git a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py b/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py deleted file mode 100644 index d3aa68b93ee..00000000000 --- a/cirq-ft/cirq_ft/algos/unary_iteration_gate_test.py +++ /dev/null @@ -1,208 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -import itertools -from typing import Sequence, Tuple - -import cirq -import cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.bit_tools import iter_bits -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class ApplyXToLthQubit(cirq_ft.UnaryIterationGate): - def __init__(self, selection_bitsize: int, target_bitsize: int, control_bitsize: int = 1): - self._selection_bitsize = selection_bitsize - self._target_bitsize = target_bitsize - self._control_bitsize = control_bitsize - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('control', self._control_bitsize),) - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return ( - cirq_ft.SelectionRegister('selection', self._selection_bitsize, self._target_bitsize), - ) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return (cirq_ft.Register('target', self._target_bitsize),) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - selection: int, - target: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - return cirq.CNOT(control, target[-(selection + 1)]) - - -@pytest.mark.parametrize( - "selection_bitsize, target_bitsize, control_bitsize", [(3, 5, 1), (2, 4, 2), (1, 2, 3)] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_gate(selection_bitsize, target_bitsize, control_bitsize): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = ApplyXToLthQubit(selection_bitsize, target_bitsize, control_bitsize) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - assert len(g.all_qubits) <= 2 * (selection_bitsize + control_bitsize) + target_bitsize - 1 - - for n in range(target_bitsize): - # Initial qubit values - qubit_vals = {q: 0 for q in g.operation.qubits} - # All controls 'on' to activate circuit - qubit_vals.update({c: 1 for c in g.quregs['control']}) - # Set selection according to `n` - qubit_vals.update(zip(g.quregs['selection'], iter_bits(n, selection_bitsize))) - - initial_state = [qubit_vals[x] for x in g.operation.qubits] - qubit_vals[g.quregs['target'][-(n + 1)]] = 1 - final_state = [qubit_vals[x] for x in g.operation.qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.circuit, g.operation.qubits, initial_state, final_state - ) - - -class ApplyXToIJKthQubit(cirq_ft.UnaryIterationGate): - def __init__(self, target_shape: Tuple[int, int, int]): - self._target_shape = target_shape - - @cached_property - def control_registers(self) -> Tuple[cirq_ft.Register, ...]: - return () - - @cached_property - def selection_registers(self) -> Tuple[cirq_ft.SelectionRegister, ...]: - return tuple( - cirq_ft.SelectionRegister( - 'ijk'[i], (self._target_shape[i] - 1).bit_length(), self._target_shape[i] - ) - for i in range(3) - ) - - @cached_property - def target_registers(self) -> Tuple[cirq_ft.Register, ...]: - return tuple( - cirq_ft.Signature.build( - t1=self._target_shape[0], t2=self._target_shape[1], t3=self._target_shape[2] - ) - ) - - def nth_operation( # type: ignore[override] - self, - context: cirq.DecompositionContext, - control: cirq.Qid, - i: int, - j: int, - k: int, - t1: Sequence[cirq.Qid], - t2: Sequence[cirq.Qid], - t3: Sequence[cirq.Qid], - ) -> cirq.OP_TREE: - yield [cirq.CNOT(control, t1[i]), cirq.CNOT(control, t2[j]), cirq.CNOT(control, t3[k])] - - -@pytest.mark.parametrize( - "target_shape", [pytest.param((2, 3, 2), marks=pytest.mark.slow), (2, 2, 2)] -) -@allow_deprecated_cirq_ft_use_in_tests -def test_multi_dimensional_unary_iteration_gate(target_shape: Tuple[int, int, int]): - greedy_mm = cirq.GreedyQubitManager(prefix="_a", maximize_reuse=True) - gate = ApplyXToIJKthQubit(target_shape) - g = cirq_ft.testing.GateHelper(gate, context=cirq.DecompositionContext(greedy_mm)) - assert ( - len(g.all_qubits) - <= infra.total_bits(gate.signature) + infra.total_bits(gate.selection_registers) - 1 - ) - - max_i, max_j, max_k = target_shape - i_len, j_len, k_len = tuple(reg.total_bits() for reg in gate.selection_registers) - for i, j, k in itertools.product(range(max_i), range(max_j), range(max_k)): - qubit_vals = {x: 0 for x in g.operation.qubits} - # Initialize selection bits appropriately: - qubit_vals.update(zip(g.quregs['i'], iter_bits(i, i_len))) - qubit_vals.update(zip(g.quregs['j'], iter_bits(j, j_len))) - qubit_vals.update(zip(g.quregs['k'], iter_bits(k, k_len))) - # Construct initial state - initial_state = [qubit_vals[x] for x in g.operation.qubits] - # Build correct statevector with selection_integer bit flipped in the target register: - for reg_name, idx in zip(['t1', 't2', 't3'], [i, j, k]): - qubit_vals[g.quregs[reg_name][idx]] = 1 - final_state = [qubit_vals[x] for x in g.operation.qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - g.circuit, g.operation.qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_loop(): - n_range, m_range = (3, 5), (6, 8) - selection_registers = [ - cirq_ft.SelectionRegister('n', 3, 5), - cirq_ft.SelectionRegister('m', 3, 8), - ] - selection = infra.get_named_qubits(selection_registers) - target = {(n, m): cirq.q(f't({n}, {m})') for n in range(*n_range) for m in range(*m_range)} - qm = cirq.GreedyQubitManager("ancilla", maximize_reuse=True) - circuit = cirq.Circuit() - i_ops = [] - # Build the unary iteration circuit - for i_optree, i_ctrl, i in cirq_ft.unary_iteration( - n_range[0], n_range[1], i_ops, [], selection['n'], qm - ): - circuit.append(i_optree) - j_ops = [] - for j_optree, j_ctrl, j in cirq_ft.unary_iteration( - m_range[0], m_range[1], j_ops, [i_ctrl], selection['m'], qm - ): - circuit.append(j_optree) - # Conditionally perform operations on target register using `j_ctrl`, `i` & `j`. - circuit.append(cirq.CNOT(j_ctrl, target[(i, j)])) - circuit.append(j_ops) - circuit.append(i_ops) - all_qubits = sorted(circuit.all_qubits()) - - i_len, j_len = 3, 3 - for i, j in itertools.product(range(*n_range), range(*m_range)): - qubit_vals = {x: 0 for x in all_qubits} - # Initialize selection bits appropriately: - qubit_vals.update(zip(selection['n'], iter_bits(i, i_len))) - qubit_vals.update(zip(selection['m'], iter_bits(j, j_len))) - # Construct initial state - initial_state = [qubit_vals[x] for x in all_qubits] - # Build correct statevector with selection_integer bit flipped in the target register: - qubit_vals[target[(i, j)]] = 1 - final_state = [qubit_vals[x] for x in all_qubits] - cirq_ft.testing.assert_circuit_inp_out_cirqsim( - circuit, all_qubits, initial_state, final_state - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_unary_iteration_loop_empty_range(): - qm = cirq.ops.SimpleQubitManager() - assert list(cirq_ft.unary_iteration(4, 4, [], [], [cirq.q('s')], qm)) == [] - assert list(cirq_ft.unary_iteration(4, 3, [], [], [cirq.q('s')], qm)) == [] - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('unary_iteration') diff --git a/cirq-ft/cirq_ft/deprecation.py b/cirq-ft/cirq_ft/deprecation.py deleted file mode 100644 index 7dbf1ed584d..00000000000 --- a/cirq-ft/cirq_ft/deprecation.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools -import unittest.mock -import os -from typing import Callable, Type -from cirq._compat import deprecated, deprecated_class - -_DEPRECATION_DEADLINE = 'v1.4' -_DEPRECATION_FIX_MSG = "Cirq-FT is deprecated in favour of Qualtran. pip install qualtran instead." - - -def deprecated_cirq_ft_class() -> Callable[[Type], Type]: # coverage: ignore - """Decorator to mark a class in Cirq-FT deprecated.""" - return deprecated_class(deadline=_DEPRECATION_DEADLINE, fix=_DEPRECATION_FIX_MSG) - - -def deprecated_cirq_ft_function() -> Callable[[Callable], Callable]: # coverage: ignore - """Decorator to mark a function in Cirq-FT deprecated.""" - return deprecated(deadline=_DEPRECATION_DEADLINE, fix=_DEPRECATION_FIX_MSG) - - -def allow_deprecated_cirq_ft_use_in_tests(func): # coverage: ignore - """Decorator to allow using deprecated classes and functions in Tests and suppress warnings.""" - - @functools.wraps(func) - @unittest.mock.patch.dict(os.environ, ALLOW_DEPRECATION_IN_TEST="True") - def wrapper(*args, **kwargs): - from cirq.testing import assert_logs - import logging - - with assert_logs(min_level=logging.WARNING, max_level=logging.WARNING, count=None) as logs: - ret_val = func(*args, **kwargs) - for log in logs: - msg = log.getMessage() - if _DEPRECATION_FIX_MSG in msg: - assert _DEPRECATION_DEADLINE in msg - return ret_val - - return wrapper diff --git a/cirq-ft/cirq_ft/infra/__init__.py b/cirq-ft/cirq_ft/infra/__init__.py deleted file mode 100644 index 3159271f131..00000000000 --- a/cirq-ft/cirq_ft/infra/__init__.py +++ /dev/null @@ -1,27 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft.infra.gate_with_registers import ( - GateWithRegisters, - Register, - Signature, - Side, - SelectionRegister, - total_bits, - split_qubits, - merge_qubits, - get_named_qubits, -) -from cirq_ft.infra.qubit_management_transformers import map_clean_and_borrowable_qubits -from cirq_ft.infra.t_complexity_protocol import TComplexity, t_complexity diff --git a/cirq-ft/cirq_ft/infra/bit_tools.py b/cirq-ft/cirq_ft/infra/bit_tools.py deleted file mode 100644 index ce54cdd5996..00000000000 --- a/cirq-ft/cirq_ft/infra/bit_tools.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 Iterator, Tuple - -import numpy as np - - -def iter_bits(val: int, width: int, *, signed: bool = False) -> Iterator[int]: - """Iterate over the bits in a binary representation of `val`. - - This uses a big-endian convention where the most significant bit - is yielded first. - - Args: - val: The integer value. Its bitsize must fit within `width` - width: The number of output bits. - signed: If True, the most significant bit represents the sign of - the number (ones complement) which is 1 if val < 0 else 0. - Raises: - ValueError: If `val` is negative or if `val.bit_length()` exceeds `width`. - """ - if val.bit_length() + int(val < 0) > width: - raise ValueError(f"{val} exceeds width {width}.") - if val < 0 and not signed: - raise ValueError(f"{val} is negative.") - if signed: - yield 1 if val < 0 else 0 - width -= 1 - for b in f'{abs(val):0{width}b}': - yield int(b) - - -def iter_bits_twos_complement(val: int, width: int) -> Iterator[int]: - """Iterate over the bits in a binary representation of `val`. - - This uses a big-endian convention where the most significant bit - is yielded first. Allows for negative values and represents these using twos - complement. - - Args: - val: The integer value. Its bitsize must fit within `width` - width: The number of output bits. - - Raises: - ValueError: If `val.bit_length()` exceeds `2 * width + 1`. - """ - if (val.bit_length() - 1) // 2 > width: - raise ValueError(f"{val} exceeds width {width}.") - mask = (1 << width) - 1 - for b in f'{val&mask:0{width}b}': - yield int(b) - - -def iter_bits_fixed_point(val: float, width: int, *, signed: bool = False) -> Iterator[int]: - r"""Represent the floating point number -1 <= val <= 1 using `width` bits. - - $$ - val = \sum_{b=0}^{width - 1} val[b] / 2^{1+b} - $$ - - Args: - val: Floating point number in [-1, 1] - width: The number of output bits in fixed point binary representation of `val`. - signed: If True, the most significant bit represents the sign of - the number (ones complement) which is 1 if val < 0 else 0. - - Raises: - ValueError: If val is not between [0, 1] (signed=False) / [-1, 1] (signed=True). - """ - lb = -1 if signed else 0 - assert lb <= val <= 1, f"{val} must be between [{lb}, 1]" - if signed: - yield 1 if val < 0 else 0 - width -= 1 - val = abs(val) - for _ in range(width): - val = val * 2 - out_bit = np.floor(val) - val = val - out_bit - yield int(out_bit) - - -def float_as_fixed_width_int(val: float, width: int) -> Tuple[int, int]: - """Returns a `width` length fixed point binary representation of `val` where -1 <= val <= 1.""" - bits = [*iter_bits_fixed_point(val, width, signed=True)] - return bits[0], int(''.join(str(b) for b in bits[1:]), 2) diff --git a/cirq-ft/cirq_ft/infra/bit_tools_test.py b/cirq-ft/cirq_ft/infra/bit_tools_test.py deleted file mode 100644 index 7836549d159..00000000000 --- a/cirq-ft/cirq_ft/infra/bit_tools_test.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 math -import random - -import pytest -from cirq_ft.infra.bit_tools import ( - float_as_fixed_width_int, - iter_bits, - iter_bits_fixed_point, - iter_bits_twos_complement, -) - - -def test_iter_bits(): - assert list(iter_bits(0, 2)) == [0, 0] - assert list(iter_bits(0, 3, signed=True)) == [0, 0, 0] - assert list(iter_bits(1, 2)) == [0, 1] - assert list(iter_bits(1, 2, signed=True)) == [0, 1] - assert list(iter_bits(-1, 2, signed=True)) == [1, 1] - assert list(iter_bits(2, 2)) == [1, 0] - assert list(iter_bits(2, 3, signed=True)) == [0, 1, 0] - assert list(iter_bits(-2, 3, signed=True)) == [1, 1, 0] - assert list(iter_bits(3, 2)) == [1, 1] - with pytest.raises(ValueError): - assert list(iter_bits(4, 2)) == [1, 0, 0] - with pytest.raises(ValueError): - _ = list(iter_bits(-3, 4)) - - -def test_iter_bits_twos(): - assert list(iter_bits_twos_complement(0, 4)) == [0, 0, 0, 0] - assert list(iter_bits_twos_complement(1, 4)) == [0, 0, 0, 1] - assert list(iter_bits_twos_complement(-2, 4)) == [1, 1, 1, 0] - assert list(iter_bits_twos_complement(-3, 4)) == [1, 1, 0, 1] - with pytest.raises(ValueError): - _ = list(iter_bits_twos_complement(100, 2)) - - -random.seed(1234) - - -@pytest.mark.parametrize('val', [random.uniform(-1, 1) for _ in range(10)]) -@pytest.mark.parametrize('width', [*range(2, 20, 2)]) -@pytest.mark.parametrize('signed', [True, False]) -def test_iter_bits_fixed_point(val, width, signed): - if (val < 0) and not signed: - with pytest.raises(AssertionError): - _ = [*iter_bits_fixed_point(val, width, signed=signed)] - else: - bits = [*iter_bits_fixed_point(val, width, signed=signed)] - if signed: - sign, bits = bits[0], bits[1:] - assert sign == (1 if val < 0 else 0) - val = abs(val) - approx_val = math.fsum([b * (1 / 2 ** (1 + i)) for i, b in enumerate(bits)]) - unsigned_width = width - 1 if signed else width - assert math.isclose( - val, approx_val, abs_tol=1 / 2**unsigned_width - ), f'{val}:{approx_val}:{width}' - bits_from_int = [ - *iter_bits(float_as_fixed_width_int(val, unsigned_width + 1)[1], unsigned_width) - ] - assert bits == bits_from_int diff --git a/cirq-ft/cirq_ft/infra/decompose_protocol.py b/cirq-ft/cirq_ft/infra/decompose_protocol.py deleted file mode 100644 index e7b6b6615d0..00000000000 --- a/cirq-ft/cirq_ft/infra/decompose_protocol.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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/cirq-ft/cirq_ft/infra/decompose_protocol_test.py b/cirq-ft/cirq_ft/infra/decompose_protocol_test.py deleted file mode 100644 index ac753faf107..00000000000 --- a/cirq-ft/cirq_ft/infra/decompose_protocol_test.py +++ /dev/null @@ -1,58 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 ( - _fredkin, - _try_decompose_from_known_decompositions, - _decompose_once_considering_known_decomposition, -) - - -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/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb b/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb deleted file mode 100644 index 8ccc674942c..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers.ipynb +++ /dev/null @@ -1,242 +0,0 @@ -{ - "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 cirq_ft import Register, Signature, infra\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 cirq_ft 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": [ - "r = gate.signature\n", - "quregs = infra.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 cirq_ft.infra.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/cirq-ft/cirq_ft/infra/gate_with_registers.py b/cirq-ft/cirq_ft/infra/gate_with_registers.py deleted file mode 100644 index 1e9129bdc6a..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers.py +++ /dev/null @@ -1,377 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 enum -import abc -import itertools -from typing import Dict, Iterable, List, Optional, Sequence, Tuple, Union, overload, Iterator -from numpy.typing import NDArray - -import attr -import cirq -import numpy as np -from cirq_ft.deprecation import deprecated_cirq_ft_class - - -class Side(enum.Flag): - """Denote LEFT, RIGHT, or THRU signature. - - LEFT signature serve as input lines (only) to the Gate. RIGHT signature are output - lines (only) from the Gate. THRU signature are both input and output. - - Traditional unitary operations will have THRU signature that operate on a collection of - qubits which are then made available to following operations. RIGHT and LEFT signature - imply allocation, deallocation, or reshaping of the signature. - """ - - LEFT = enum.auto() - RIGHT = enum.auto() - THRU = LEFT | RIGHT - - -@deprecated_cirq_ft_class() -@attr.frozen -class Register: - """A quantum register used to define the input/output API of a `cirq_ft.GateWithRegister` - - Attributes: - name: The string name of the register - bitsize: The number of (qu)bits in the register. - shape: A tuple of integer dimensions to declare a multidimensional register. The - total number of bits is the product of entries in this tuple times `bitsize`. - side: Whether this is a left, right, or thru register. See the documentation for `Side` - for more information. - """ - - name: str - bitsize: int = attr.field() - shape: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - side: Side = Side.THRU - - @bitsize.validator - def bitsize_validator(self, attribute, value): - if value <= 0: - raise ValueError(f"Bitsize for {self=} must be a positive integer. Found {value}.") - - def all_idxs(self) -> Iterable[Tuple[int, ...]]: - """Iterate over all possible indices of a multidimensional register.""" - yield from itertools.product(*[range(sh) for sh in self.shape]) - - def total_bits(self) -> int: - """The total number of bits in this register. - - This is the product of each of the dimensions in `shape`. - """ - return self.bitsize * int(np.prod(self.shape)) - - def __repr__(self): - return ( - f'cirq_ft.Register(' - f'name="{self.name}", ' - f'bitsize={self.bitsize}, ' - f'shape={self.shape}, ' - f'side=cirq_ft.infra.{self.side})' - ) - - -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} - - -@deprecated_cirq_ft_class() -class Signature: - """An ordered collection of `cirq_ft.Register`. - - Args: - registers: an iterable of the contained `cirq_ft.Register`. - """ - - def __init__(self, registers: Iterable[Register]): - self._registers = tuple(registers) - self._lefts = {r.name: r for r in self._registers if r.side & Side.LEFT} - self._rights = {r.name: r for r in self._registers if r.side & Side.RIGHT} - if len(set(self._lefts) | set(self._rights)) != len(self._registers): - raise ValueError("Please provide unique register names.") - - def __repr__(self): - return f'cirq_ft.Signature({self._registers})' - - @classmethod - def build(cls, **registers: int) -> 'Signature': - return cls(Register(name=k, bitsize=v) for k, v in registers.items() if v > 0) - - @overload - def __getitem__(self, key: int) -> Register: - pass - - @overload - def __getitem__(self, key: slice) -> Tuple[Register, ...]: - pass - - def __getitem__(self, key): - return self._registers[key] - - def get_left(self, name: str) -> Register: - """Get a left register by name.""" - return self._lefts[name] - - def get_right(self, name: str) -> Register: - """Get a right register by name.""" - return self._rights[name] - - def __contains__(self, item: Register) -> bool: - return item in self._registers - - def __iter__(self) -> Iterator[Register]: - yield from self._registers - - def __len__(self) -> int: - return len(self._registers) - - def __eq__(self, other) -> bool: - return self._registers == other._registers - - def __hash__(self): - return hash(self._registers) - - -@attr.frozen -class SelectionRegister(Register): - """Register used to represent SELECT register for various LCU methods. - - `SelectionRegister` extends the `Register` class to store the iteration length - corresponding to that register along with its size. - - LCU methods often make use of coherent for-loops via UnaryIteration, iterating over a range - of values stored as a superposition over the `SELECT` register. Such (nested) coherent - for-loops can be represented using a `Tuple[SelectionRegister, ...]` where the i'th entry - stores the bitsize and iteration length of i'th nested for-loop. - - One useful feature when processing such nested for-loops is to flatten out a composite index, - represented by a tuple of indices (i, j, ...), one for each selection register into a single - integer that can be used to index a flat target register. An example of such a mapping - function is described in Eq.45 of https://arxiv.org/abs/1805.03662. A general version of this - mapping function can be implemented using `numpy.ravel_multi_index` and `numpy.unravel_index`. - - For example: - 1) We can flatten a 2D for-loop as follows - >>> import numpy as np - >>> N, M = 10, 20 - >>> flat_indices = set() - >>> for x in range(N): - ... for y in range(M): - ... flat_idx = x * M + y - ... assert np.ravel_multi_index((x, y), (N, M)) == flat_idx - ... assert np.unravel_index(flat_idx, (N, M)) == (x, y) - ... flat_indices.add(flat_idx) - >>> assert len(flat_indices) == N * M - - 2) Similarly, we can flatten a 3D for-loop as follows - >>> import numpy as np - >>> N, M, L = 10, 20, 30 - >>> flat_indices = set() - >>> for x in range(N): - ... for y in range(M): - ... for z in range(L): - ... flat_idx = x * M * L + y * L + z - ... assert np.ravel_multi_index((x, y, z), (N, M, L)) == flat_idx - ... assert np.unravel_index(flat_idx, (N, M, L)) == (x, y, z) - ... flat_indices.add(flat_idx) - >>> assert len(flat_indices) == N * M * L - """ - - name: str - bitsize: int - iteration_length: int = attr.field() - shape: Tuple[int, ...] = attr.field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) - side: Side = Side.THRU - - @iteration_length.default - def _default_iteration_length(self): - return 2**self.bitsize - - @iteration_length.validator - def validate_iteration_length(self, attribute, value): - if len(self.shape) != 0: - raise ValueError(f'Selection register {self.name} should be flat. Found {self.shape=}') - if not (0 <= value <= 2**self.bitsize): - raise ValueError(f'iteration length must be in range [0, 2^{self.bitsize}]') - - def __repr__(self) -> str: - return ( - f'cirq_ft.SelectionRegister(' - f'name="{self.name}", ' - f'bitsize={self.bitsize}, ' - f'shape={self.shape}, ' - f'iteration_length={self.iteration_length})' - ) - - -@deprecated_cirq_ft_class() -class GateWithRegisters(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 cirq_ft - >>> - >>> @attr.frozen - ... class MultiTargetCSwap(cirq_ft.GateWithRegisters): - ... bitsize: int - ... - ... @property - ... def signature(self) -> cirq_ft.Signature: - ... return cirq_ft.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──────────────────""" - - @property - @abc.abstractmethod - def signature(self) -> Signature: ... - - 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/cirq-ft/cirq_ft/infra/gate_with_registers_test.py b/cirq-ft/cirq_ft/infra/gate_with_registers_test.py deleted file mode 100644 index 437d226521b..00000000000 --- a/cirq-ft/cirq_ft/infra/gate_with_registers_test.py +++ /dev/null @@ -1,182 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft.infra.jupyter_tools import execute_notebook -from cirq_ft.infra import split_qubits, merge_qubits, get_named_qubits -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@allow_deprecated_cirq_ft_use_in_tests -def test_register(): - r = cirq_ft.Register("my_reg", 5, (1, 2)) - assert r.bitsize == 5 - assert r.shape == (1, 2) - - with pytest.raises(ValueError, match="must be a positive integer"): - _ = cirq_ft.Register("zero bitsize register", bitsize=0) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers(): - r1 = cirq_ft.Register("r1", 5, side=cirq_ft.infra.Side.LEFT) - r2 = cirq_ft.Register("r2", 2, side=cirq_ft.infra.Side.RIGHT) - r3 = cirq_ft.Register("r3", 1) - regs = cirq_ft.Signature([r1, r2, r3]) - assert len(regs) == 3 - cirq.testing.assert_equivalent_repr(regs, setup_code='import cirq_ft') - - with pytest.raises(ValueError, match="unique"): - _ = cirq_ft.Signature([r1, r1]) - - assert regs[0] == r1 - assert regs[1] == r2 - assert regs[2] == r3 - - assert regs[0:1] == tuple([r1]) - assert regs[0:2] == tuple([r1, r2]) - assert regs[1:3] == tuple([r2, r3]) - - assert regs.get_left("r1") == r1 - assert regs.get_right("r2") == r2 - assert regs.get_left("r3") == r3 - - assert r1 in regs - assert r2 in regs - assert r3 in regs - - assert list(regs) == [r1, r2, r3] - - qubits = cirq.LineQubit.range(8) - qregs = split_qubits(regs, qubits) - assert qregs["r1"].tolist() == cirq.LineQubit.range(5) - assert qregs["r2"].tolist() == cirq.LineQubit.range(5, 5 + 2) - assert qregs["r3"].tolist() == [cirq.LineQubit(7)] - - qubits = qubits[::-1] - - with pytest.raises(ValueError, match="qubit registers must be present"): - _ = merge_qubits(regs, r1=qubits[:5], r2=qubits[5:7], r4=qubits[-1]) - - with pytest.raises(ValueError, match="register must of shape"): - _ = merge_qubits(regs, r1=qubits[:4], r2=qubits[5:7], r3=qubits[-1]) - - merged_qregs = merge_qubits(regs, r1=qubits[:5], r2=qubits[5:7], r3=qubits[-1]) - assert merged_qregs == qubits - - expected_named_qubits = { - "r1": cirq.NamedQubit.range(5, prefix="r1"), - "r2": cirq.NamedQubit.range(2, prefix="r2"), - "r3": [cirq.NamedQubit("r3")], - } - - named_qregs = get_named_qubits(regs) - for reg_name in expected_named_qubits: - assert np.array_equal(named_qregs[reg_name], expected_named_qubits[reg_name]) - - # Python dictionaries preserve insertion order, which should be same as insertion order of - # initial registers. - for reg_order in [[r1, r2, r3], [r2, r3, r1]]: - flat_named_qubits = [ - q for v in get_named_qubits(cirq_ft.Signature(reg_order)).values() for q in v - ] - expected_qubits = [q for r in reg_order for q in expected_named_qubits[r.name]] - assert flat_named_qubits == expected_qubits - - -@pytest.mark.parametrize('n, N, m, M', [(4, 10, 5, 19), (4, 16, 5, 32)]) -@allow_deprecated_cirq_ft_use_in_tests -def test_selection_registers_indexing(n, N, m, M): - regs = [cirq_ft.SelectionRegister('x', n, N), cirq_ft.SelectionRegister('y', m, M)] - for x in range(regs[0].iteration_length): - for y in range(regs[1].iteration_length): - assert np.ravel_multi_index((x, y), (N, M)) == x * M + y - assert np.unravel_index(x * M + y, (N, M)) == (x, y) - - assert np.prod(tuple(reg.iteration_length for reg in regs)) == N * M - - -@allow_deprecated_cirq_ft_use_in_tests -def test_selection_registers_consistent(): - with pytest.raises(ValueError, match="iteration length must be in "): - _ = cirq_ft.SelectionRegister('a', 3, 10) - - with pytest.raises(ValueError, match="should be flat"): - _ = cirq_ft.SelectionRegister('a', bitsize=1, shape=(3, 5), iteration_length=5) - - selection_reg = cirq_ft.Signature( - [ - cirq_ft.SelectionRegister('n', bitsize=3, iteration_length=5), - cirq_ft.SelectionRegister('m', bitsize=4, iteration_length=12), - ] - ) - assert selection_reg[0] == cirq_ft.SelectionRegister('n', 3, 5) - assert selection_reg[1] == cirq_ft.SelectionRegister('m', 4, 12) - assert selection_reg[:1] == tuple([cirq_ft.SelectionRegister('n', 3, 5)]) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers_getitem_raises(): - g = cirq_ft.Signature.build(a=4, b=3, c=2) - with pytest.raises(TypeError, match="indices must be integers or slices"): - _ = g[2.5] - - selection_reg = cirq_ft.Signature( - [cirq_ft.SelectionRegister('n', bitsize=3, iteration_length=5)] - ) - with pytest.raises(TypeError, match='indices must be integers or slices'): - _ = selection_reg[2.5] - - -@allow_deprecated_cirq_ft_use_in_tests -def test_registers_build(): - regs1 = cirq_ft.Signature([cirq_ft.Register("r1", 5), cirq_ft.Register("r2", 2)]) - regs2 = cirq_ft.Signature.build(r1=5, r2=2) - assert regs1 == regs2 - - -class _TestGate(cirq_ft.GateWithRegisters): - @property - def signature(self) -> cirq_ft.Signature: - r1 = cirq_ft.Register("r1", 5) - r2 = cirq_ft.Register("r2", 2) - r3 = cirq_ft.Register("r3", 1) - regs = cirq_ft.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']) - - -@allow_deprecated_cirq_ft_use_in_tests -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 - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('gate_with_registers') diff --git a/cirq-ft/cirq_ft/infra/jupyter_tools.py b/cirq-ft/cirq_ft/infra/jupyter_tools.py deleted file mode 100644 index b6b98cc9b14..00000000000 --- a/cirq-ft/cirq_ft/infra/jupyter_tools.py +++ /dev/null @@ -1,114 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 pathlib import Path -from typing import Iterable - -import cirq -import cirq.contrib.svg.svg as ccsvg -import cirq_ft.infra.testing as cq_testing -import IPython.display -import ipywidgets -import nbformat -from cirq_ft.infra import gate_with_registers, t_complexity_protocol, get_named_qubits, merge_qubits -from nbconvert.preprocessors import ExecutePreprocessor - - -def display_gate_and_compilation(g: cq_testing.GateHelper, vertical=False, include_costs=True): - """Use ipywidgets to display SVG circuits for a `GateHelper` next to each other. - - Args: - g: The `GateHelper` to draw - vertical: If true, lay-out the original gate and its decomposition vertically - rather than side-by-side. - include_costs: If true, each operation is annotated with it's T-complexity cost. - """ - out1 = ipywidgets.Output() - out2 = ipywidgets.Output() - if vertical: - box = ipywidgets.VBox([out1, out2]) - else: - box = ipywidgets.HBox([out1, out2]) - - out1.append_display_data(svg_circuit(g.circuit, registers=g.r, include_costs=include_costs)) - out2.append_display_data( - svg_circuit( - cirq.Circuit(cirq.decompose_once(g.operation)), - registers=g.r, - include_costs=include_costs, - ) - ) - - IPython.display.display(box) - - -def circuit_with_costs(circuit: 'cirq.AbstractCircuit') -> 'cirq.AbstractCircuit': - """Annotates each operation in the circuit with its T-complexity cost.""" - - def _map_func(op: cirq.Operation, _): - t_cost = t_complexity_protocol.t_complexity(op) - return op.with_tags(f't:{t_cost.t:g},r:{t_cost.rotations:g}') - - return cirq.map_operations(circuit, map_func=_map_func) - - -def svg_circuit( - circuit: 'cirq.AbstractCircuit', - registers: Iterable[gate_with_registers.Register] = (), - include_costs: bool = False, -): - """Return an SVG object representing a circuit. - - Args: - circuit: The circuit to draw. - registers: Optional `Signature` object to order the qubits. - include_costs: If true, each operation is annotated with it's T-complexity cost. - - Raises: - ValueError: If `circuit` is empty. - """ - if len(circuit) == 0: - raise ValueError("Circuit is empty.") - - if registers: - qubit_order = cirq.QubitOrder.explicit( - merge_qubits(registers, **get_named_qubits(registers)), fallback=cirq.QubitOrder.DEFAULT - ) - else: - qubit_order = cirq.QubitOrder.DEFAULT - - if include_costs: - circuit = circuit_with_costs(circuit) - - tdd = circuit.to_text_diagram_drawer(transpose=False, qubit_order=qubit_order) - if len(tdd.horizontal_lines) == 0: - raise ValueError("No non-empty moments.") - return IPython.display.SVG(ccsvg.tdd_to_svg(tdd)) - - -def execute_notebook(name: str): - """Execute a jupyter notebook in the caller's directory. - - Args: - name: The name of the notebook without extension. - """ - import traceback - - # Assumes that the notebook is in the same path from where the function was called, - # which may be different from `__file__`. - notebook_path = Path(traceback.extract_stack()[-2].filename).parent / f"{name}.ipynb" - with notebook_path.open() as f: - nb = nbformat.read(f, as_version=4) - ep = ExecutePreprocessor(timeout=600, kernel_name="python3") - ep.preprocess(nb) diff --git a/cirq-ft/cirq_ft/infra/jupyter_tools_test.py b/cirq-ft/cirq_ft/infra/jupyter_tools_test.py deleted file mode 100644 index a60f5fb8dd3..00000000000 --- a/cirq-ft/cirq_ft/infra/jupyter_tools_test.py +++ /dev/null @@ -1,62 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import cirq_ft.infra.testing as cq_testing -import IPython.display -import ipywidgets -import pytest -from cirq_ft.infra.jupyter_tools import display_gate_and_compilation, svg_circuit -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -@allow_deprecated_cirq_ft_use_in_tests -def test_svg_circuit(): - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - svg = svg_circuit(g.circuit, g.r) - svg_str = svg.data - - # check that the order is respected in the svg data. - assert svg_str.find('ctrl') < svg_str.find('junk') < svg_str.find('target') - - # Check svg_circuit raises. - with pytest.raises(ValueError): - svg_circuit(cirq.Circuit()) - with pytest.raises(ValueError): - svg_circuit(cirq.Circuit(cirq.Moment())) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_display_gate_and_compilation(monkeypatch): - call_args = [] - - def _mock_display(stuff): - call_args.append(stuff) - - monkeypatch.setattr(IPython.display, "display", _mock_display) - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - display_gate_and_compilation(g) - - (display_arg,) = call_args - assert isinstance(display_arg, ipywidgets.HBox) - assert len(display_arg.children) == 2 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_circuit_with_costs(): - g = cq_testing.GateHelper(cirq_ft.And(cv=(1, 1, 1))) - circuit = cirq_ft.infra.jupyter_tools.circuit_with_costs(g.circuit) - expected_circuit = cirq.Circuit(g.operation.with_tags('t:8,r:0')) - cirq.testing.assert_same_circuits(circuit, expected_circuit) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers.py deleted file mode 100644 index 517cadd3ccd..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers.py +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 Optional - -import cirq - - -@cirq._compat.deprecated(deadline="v1.4", fix="Use cirq.map_clean_and_borrowable_qubits instead.") -def map_clean_and_borrowable_qubits( - circuit: cirq.AbstractCircuit, *, qm: Optional[cirq.QubitManager] = None -) -> cirq.Circuit: - """This method is deprecated. See docstring of `cirq.map_clean_and_borrowable_qubits`""" - return cirq.map_clean_and_borrowable_qubits(circuit, qm=qm) diff --git a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py b/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py deleted file mode 100644 index 51557be9453..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_management_transformers_test.py +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft - - -def test_deprecated(): - with cirq.testing.assert_deprecated( - "Use cirq.map_clean_and_borrowable_qubits instead", deadline="v1.4" - ): - _ = cirq_ft.map_clean_and_borrowable_qubits(cirq.Circuit(), qm=cirq.SimpleQubitManager()) diff --git a/cirq-ft/cirq_ft/infra/qubit_manager.py b/cirq-ft/cirq_ft/infra/qubit_manager.py deleted file mode 100644 index 5b94b7a056d..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_manager.py +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 - - -@cirq._compat.deprecated_class(deadline="v1.4", fix="Use cirq.GreedyQubitManager instead.") -class GreedyQubitManager(cirq.GreedyQubitManager): - pass diff --git a/cirq-ft/cirq_ft/infra/qubit_manager_test.py b/cirq-ft/cirq_ft/infra/qubit_manager_test.py deleted file mode 100644 index e4e88290ff3..00000000000 --- a/cirq-ft/cirq_ft/infra/qubit_manager_test.py +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft.infra.qubit_manager import GreedyQubitManager - - -def test_deprecated(): - with cirq.testing.assert_deprecated("Use cirq.GreedyQubitManager instead", deadline="v1.4"): - _ = GreedyQubitManager('ancilla') diff --git a/cirq-ft/cirq_ft/infra/t_complexity.ipynb b/cirq-ft/cirq_ft/infra/t_complexity.ipynb deleted file mode 100644 index 3697188b98e..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity.ipynb +++ /dev/null @@ -1,174 +0,0 @@ -{ - "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/cirq-ft/cirq_ft/infra/t_complexity_protocol.py b/cirq-ft/cirq_ft/infra/t_complexity_protocol.py deleted file mode 100644 index 52ed7137d59..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity_protocol.py +++ /dev/null @@ -1,195 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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, Union, overload - -import attr -import cachetools -import cirq -from cirq_ft.infra.decompose_protocol import _decompose_once_considering_known_decomposition -from typing_extensions import Literal, Protocol -from cirq_ft.deprecation import deprecated_cirq_ft_class, deprecated_cirq_ft_function - -_T_GATESET = cirq.Gateset(cirq.T, cirq.T**-1, unroll_circuit_op=False) - - -@deprecated_cirq_ft_class() -@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.num_qubits(stc) <= 2 and 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]: ... - - -@deprecated_cirq_ft_function() -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/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py b/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py deleted file mode 100644 index 6512d6e5e27..00000000000 --- a/cirq-ft/cirq_ft/infra/t_complexity_protocol_test.py +++ /dev/null @@ -1,216 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import pytest -from cirq_ft import infra -from cirq_ft.infra.jupyter_tools import execute_notebook - -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -class SupportTComplexity: - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1) - - -class DoesNotSupportTComplexity: ... - - -class SupportsTComplexityGateWithRegisters(cirq_ft.GateWithRegisters): - @property - def signature(self) -> cirq_ft.Signature: - return cirq_ft.Signature.build(s=1, t=2) - - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1, clifford=2) - - -class SupportTComplexityGate(cirq.Gate): - def _num_qubits_(self) -> int: - return 1 - - def _t_complexity_(self) -> cirq_ft.TComplexity: - return cirq_ft.TComplexity(t=1) - - -class DoesNotSupportTComplexityGate(cirq.Gate): - def _num_qubits_(self): - return 1 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_t_complexity(): - with pytest.raises(TypeError): - _ = cirq_ft.t_complexity(DoesNotSupportTComplexity()) - - with pytest.raises(TypeError): - _ = cirq_ft.t_complexity(DoesNotSupportTComplexityGate()) - assert cirq_ft.t_complexity(DoesNotSupportTComplexity(), fail_quietly=True) is None - assert cirq_ft.t_complexity([DoesNotSupportTComplexity()], fail_quietly=True) is None - assert cirq_ft.t_complexity(DoesNotSupportTComplexityGate(), fail_quietly=True) is None - - assert cirq_ft.t_complexity(SupportTComplexity()) == cirq_ft.TComplexity(t=1) - - g = cirq_ft.testing.GateHelper(SupportsTComplexityGateWithRegisters()) - assert g.gate._decompose_with_context_(g.operation.qubits) is NotImplemented - assert cirq_ft.t_complexity(g.gate) == cirq_ft.TComplexity(t=1, clifford=2) - assert cirq_ft.t_complexity(g.operation) == cirq_ft.TComplexity(t=1, clifford=2) - - assert cirq_ft.t_complexity([cirq.T, cirq.X]) == cirq_ft.TComplexity(t=1, clifford=1) - - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity([cirq.T(q), cirq.X(q)]) == cirq_ft.TComplexity(t=1, clifford=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_gates(): - # T gate and its adjoint - assert cirq_ft.t_complexity(cirq.T) == cirq_ft.TComplexity(t=1) - assert cirq_ft.t_complexity(cirq.T**-1) == cirq_ft.TComplexity(t=1) - - assert cirq_ft.t_complexity(cirq.H) == cirq_ft.TComplexity(clifford=1) # Hadamard - assert cirq_ft.t_complexity(cirq.CNOT) == cirq_ft.TComplexity(clifford=1) # CNOT - assert cirq_ft.t_complexity(cirq.S) == cirq_ft.TComplexity(clifford=1) # S - assert cirq_ft.t_complexity(cirq.S**-1) == cirq_ft.TComplexity(clifford=1) # S† - - # Pauli operators are clifford - assert cirq_ft.t_complexity(cirq.X) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.Y) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.Z) == cirq_ft.TComplexity(clifford=1) - - # Rotation about X, Y, and Z axes - assert cirq_ft.t_complexity(cirq.Rx(rads=2)) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.Ry(rads=2)) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.Rz(rads=2)) == cirq_ft.TComplexity(rotations=1) - - # clifford+T - assert cirq_ft.t_complexity(cirq_ft.And()) == cirq_ft.TComplexity(t=4, clifford=9) - assert cirq_ft.t_complexity(cirq_ft.And() ** -1) == cirq_ft.TComplexity(clifford=4) - - assert cirq_ft.t_complexity(cirq.FREDKIN) == cirq_ft.TComplexity(t=7, clifford=10) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.T(q)) == cirq_ft.TComplexity(t=1) - - gate = cirq_ft.And() - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - assert cirq_ft.t_complexity(op) == cirq_ft.TComplexity(t=4, clifford=9) - - gate = cirq_ft.And() ** -1 - op = gate.on_registers(**infra.get_named_qubits(gate.signature)) - assert cirq_ft.t_complexity(op) == cirq_ft.TComplexity(clifford=4) - - -@allow_deprecated_cirq_ft_use_in_tests -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 cirq_ft.t_complexity(circuit) == cirq_ft.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 cirq_ft.t_complexity(circuit) == cirq_ft.TComplexity(clifford=1, rotations=1, t=1) - - -@allow_deprecated_cirq_ft_use_in_tests -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 cirq_ft.t_complexity(cirq.CircuitOperation(circuit)) == cirq_ft.TComplexity( - clifford=2, rotations=1, t=1 - ) - assert cirq_ft.t_complexity( - cirq.CircuitOperation(circuit, repetitions=10) - ) == cirq_ft.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 cirq_ft.t_complexity(cirq.CircuitOperation(circuit)) == cirq_ft.TComplexity( - clifford=1, rotations=1, t=1 - ) - assert cirq_ft.t_complexity( - cirq.CircuitOperation(circuit, repetitions=3) - ) == cirq_ft.TComplexity(clifford=3, rotations=3, t=3) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_classically_controlled_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.X(q).with_classical_controls('c')) == cirq_ft.TComplexity( - clifford=1 - ) - assert cirq_ft.t_complexity( - cirq.Rx(rads=0.1)(q).with_classical_controls('c') - ) == cirq_ft.TComplexity(rotations=1) - assert cirq_ft.t_complexity(cirq.T(q).with_classical_controls('c')) == cirq_ft.TComplexity(t=1) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_tagged_operations(): - q = cirq.NamedQubit('q') - assert cirq_ft.t_complexity(cirq.X(q).with_tags('tag1')) == cirq_ft.TComplexity(clifford=1) - assert cirq_ft.t_complexity(cirq.T(q).with_tags('tage1')) == cirq_ft.TComplexity(t=1) - assert cirq_ft.t_complexity( - cirq.Ry(rads=0.1)(q).with_tags('tag1', 'tag2') - ) == cirq_ft.TComplexity(rotations=1) - - -@allow_deprecated_cirq_ft_use_in_tests -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) -> cirq_ft.TComplexity: - self.num_calls += 1 - return cirq_ft.TComplexity() - - @property - def qubits(self): - return [cirq.LineQubit(3)] # pragma: no cover - - def with_qubits(self, _): ... - - @property - def gate(self): - return self._gate - - assert cirq_ft.t_complexity(cirq.X) == cirq_ft.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 `cirq_ft.TComplexity()` for IsCachable - # operation; for which the hash would be equivalent to the hash of it's subgate i.e. `cirq.X`. - cirq_ft.t_complexity.cache_clear() - op = IsCachable() - assert cirq_ft.t_complexity([op, op]) == cirq_ft.TComplexity() - assert op.num_calls == 1 - cirq_ft.t_complexity.cache_clear() - - -@pytest.mark.skip(reason="Cirq-FT is deprecated, use Qualtran instead.") -def test_notebook(): - execute_notebook('t_complexity') diff --git a/cirq-ft/cirq_ft/infra/testing.py b/cirq-ft/cirq_ft/infra/testing.py deleted file mode 100644 index a10b0877586..00000000000 --- a/cirq-ft/cirq_ft/infra/testing.py +++ /dev/null @@ -1,133 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 functools import cached_property -from typing import Any, Dict, List, Sequence, Tuple -from numpy.typing import NDArray -import cirq -import numpy as np -from cirq_ft.infra import gate_with_registers, t_complexity_protocol, merge_qubits, get_named_qubits -from cirq_ft.infra.decompose_protocol import _decompose_once_considering_known_decomposition - - -@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: gate_with_registers.GateWithRegisters - context: cirq.DecompositionContext = cirq.DecompositionContext(cirq.ops.SimpleQubitManager()) - - @cached_property - def r(self) -> gate_with_registers.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_protocol.t_complexity(decomposition, fail_quietly=False) - assert expected == from_decomposition, f'{expected} != {from_decomposition}' diff --git a/cirq-ft/cirq_ft/infra/testing_test.py b/cirq-ft/cirq_ft/infra/testing_test.py deleted file mode 100644 index 265642b195e..00000000000 --- a/cirq-ft/cirq_ft/infra/testing_test.py +++ /dev/null @@ -1,96 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft -import numpy as np -import pytest -from cirq_ft.deprecation import allow_deprecated_cirq_ft_use_in_tests - - -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] - - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state) - - final_state = [0, 0, 0, 1] - with pytest.raises(AssertionError): - cirq_ft.testing.assert_circuit_inp_out_cirqsim(circuit, qubits, initial_state, final_state) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_gate_helper(): - g = cirq_ft.testing.GateHelper(cirq_ft.And(cv=(1, 0, 1, 0))) - assert g.gate == cirq_ft.And(cv=(1, 0, 1, 0)) - assert g.r == cirq_ft.Signature( - [ - cirq_ft.Register('ctrl', bitsize=1, shape=4), - cirq_ft.Register('junk', bitsize=1, shape=2, side=cirq_ft.infra.Side.RIGHT), - cirq_ft.Register('target', bitsize=1, side=cirq_ft.infra.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) -> cirq_ft.TComplexity: - return cirq_ft.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) -> cirq_ft.TComplexity: - return cirq_ft.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 - - -@allow_deprecated_cirq_ft_use_in_tests -def test_assert_decompose_is_consistent_with_t_complexity(): - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(cirq.T) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(DoesNotDecompose()) - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity( - cirq_ft.testing.GateHelper(cirq_ft.And()).operation - ) - - -@allow_deprecated_cirq_ft_use_in_tests -def test_assert_decompose_is_consistent_with_t_complexity_raises(): - with pytest.raises(AssertionError): - cirq_ft.testing.assert_decompose_is_consistent_with_t_complexity(InconsistentDecompostion()) diff --git a/cirq-ft/cirq_ft/linalg/__init__.py b/cirq-ft/cirq_ft/linalg/__init__.py deleted file mode 100644 index 45cd99c1114..00000000000 --- a/cirq-ft/cirq_ft/linalg/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 cirq_ft.linalg.lcu_util import preprocess_lcu_coefficients_for_reversible_sampling diff --git a/cirq-ft/cirq_ft/linalg/lcu_util.py b/cirq-ft/cirq_ft/linalg/lcu_util.py deleted file mode 100644 index 6581c6cb35d..00000000000 --- a/cirq-ft/cirq_ft/linalg/lcu_util.py +++ /dev/null @@ -1,187 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -"""Utility methods for LCU circuits as implemented in https://github.com/quantumlib/OpenFermion""" - -import math - - -def _partial_sums(vals): - """Adds up the items in the input, yielding partial sums along the way.""" - total = 0 - for v in vals: - yield total - total += v - yield total - - -def _differences(weights): - """Iterates over the input yielding differences between adjacent items.""" - previous_weight = None - have_previous_weight = False - for w in weights: - if have_previous_weight: - yield w - previous_weight - previous_weight = w - have_previous_weight = True - - -def _discretize_probability_distribution(unnormalized_probabilities, epsilon): - """Approximates probabilities with integers over a common denominator. - - Args: - unnormalized_probabilities: A list of non-negative floats proportional - to probabilities from a probability distribution. The numbers may - not be normalized (they do not have to add up to 1). - epsilon: The absolute error tolerance. - - Returns: - numerators (list[int]): A list of numerators for each probability. - denominator (int): The common denominator to divide numerators by to - get probabilities. - sub_bit_precision (int): The exponent mu such that - denominator = n * 2**mu - where n = len(unnormalized_probabilities). - - It is guaranteed that numerators[i] / denominator is within epsilon of - the i'th input probability (after normalization). - """ - n = len(unnormalized_probabilities) - sub_bit_precision = max(0, int(math.ceil(-math.log(epsilon * n, 2)))) - bin_count = 2**sub_bit_precision * n - - cumulative = list(_partial_sums(unnormalized_probabilities)) - total = cumulative[-1] - discretized_cumulative = [int(math.floor(c / total * bin_count + 0.5)) for c in cumulative] - discretized = list(_differences(discretized_cumulative)) - return discretized, bin_count, sub_bit_precision - - -def _preprocess_for_efficient_roulette_selection(discretized_probabilities): - """Prepares data for performing efficient roulette selection. - - The output is a tuple (alternates, keep_weights). The output is guaranteed - to satisfy a sampling-equivalence property. Specifically, the following - sampling process is guaranteed to be equivalent to simply picking index i - with probability weights[i] / sum(weights): - - 1. Pick a number i in [0, len(weights) - 1] uniformly at random. - 2. Return i With probability keep_weights[i]*len(weights)/sum(weights). - 3. Otherwise return alternates[i]. - - In other words, the output makes it possible to perform roulette selection - while generating only two random numbers, doing a single lookup of the - relevant (keep_chance, alternate) pair, and doing one comparison. This is - not so useful classically, but in the context of a quantum computation - where all those things are expensive the second sampling process is far - superior. - - Args: - discretized_probabilities: A list of probabilities approximated by - integer numerators (with an implied common denominator). In order - to operate without floating point error, it is required that the - sum of this list is a multiple of the number of items in the list. - - Returns: - alternates (list[int]): An alternate index for each index from 0 to - len(weights) - 1 - keep_weight (list[int]): Indicates how often one should stay at index i - instead of switching to alternates[i]. To get the actual keep - probability of the i'th element, multiply keep_weight[i] by - len(discretized_probabilities) then divide by - sum(discretized_probabilities). - Raises: - ValueError: if `discretized_probabilities` input is empty or if the sum of elements - in the list is not a multiple of the number of items in the list. - """ - weights = list(discretized_probabilities) # Need a copy we can mutate. - if not weights: - raise ValueError('Empty input.') - - n = len(weights) - target_weight = sum(weights) // n - if sum(weights) != n * target_weight: - raise ValueError('sum(weights) must be a multiple of len(weights).') - - # Initially, every item's alternative is itself. - alternates = list(range(n)) - keep_weights = [0] * n - - # Scan for needy items and donors. First pass will handle all - # initially-needy items. Second pass will handle any remaining items that - # started as donors but become needy due to over-donation (though some may - # also be handled during the first pass). - donor_position = 0 - for _ in range(2): - for i in range(n): - # Is this a needy item? - if weights[i] >= target_weight: - continue # Nope. - - # Find a donor. - while weights[donor_position] <= target_weight: - donor_position += 1 - - # Donate. - donated = target_weight - weights[i] - weights[donor_position] -= donated - alternates[i] = donor_position - keep_weights[i] = weights[i] - - # Needy item has been paired. Remove it from consideration. - weights[i] = target_weight - - return alternates, keep_weights - - -def preprocess_lcu_coefficients_for_reversible_sampling(lcu_coefficients, epsilon): - """Prepares data used to perform efficient reversible roulette selection. - - Treats the coefficients of unitaries in the linear combination of - unitaries decomposition of the Hamiltonian as probabilities in order to - decompose them into a list of alternate and keep numerators allowing for - an efficient preparation method of a state where the computational basis - state :math. `|k>` has an amplitude proportional to the coefficient. - - It is guaranteed that following the following sampling process will - sample each index k with a probability within epsilon of - lcu_coefficients[k] / sum(lcu_coefficients) and also, - 1. Uniformly sample an index i from [0, len(lcu_coefficients) - 1]. - 2. With probability keep_numers[i] / by keep_denom, return i. - 3. Otherwise return alternates[i]. - - Args: - lcu_coefficients: A list of non-negative floats, with the i'th float - corresponding to the i'th coefficient of an LCU decomposition - of the Hamiltonian (in an ordering determined by the caller). - epsilon: Absolute error tolerance. - - Returns: - alternates (list[int]): A python list of ints indicating alternative - indices that may be switched to after generating a uniform index. - The int at offset k is the alternate to use when the initial index - is k. - keep_numers (list[int]): A python list of ints indicating the - numerators of the probability that the alternative index should be - used instead of the initial index. - sub_bit_precision (int): A python int indicating the exponent of the - denominator to divide the items in keep_numers by in order to get - a probability. The actual denominator is 2**sub_bit_precision. - """ - numers, denom, sub_bit_precision = _discretize_probability_distribution( - lcu_coefficients, epsilon - ) - assert denom == 2**sub_bit_precision * len(numers) - alternates, keep_numers = _preprocess_for_efficient_roulette_selection(numers) - return alternates, keep_numers, sub_bit_precision diff --git a/cirq-ft/cirq_ft/linalg/lcu_util_test.py b/cirq-ft/cirq_ft/linalg/lcu_util_test.py deleted file mode 100644 index bb724d30626..00000000000 --- a/cirq-ft/cirq_ft/linalg/lcu_util_test.py +++ /dev/null @@ -1,163 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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. - -"""Tests for lcu_util.py.""" - -import random -import unittest - -from cirq_ft.linalg.lcu_util import ( - _discretize_probability_distribution, - _preprocess_for_efficient_roulette_selection, - preprocess_lcu_coefficients_for_reversible_sampling, -) - - -class DiscretizeDistributionTest(unittest.TestCase): - def assertGetDiscretizedDistribution(self, probabilities, epsilon): - total_probability = sum(probabilities) - numers, denom, mu = _discretize_probability_distribution(probabilities, epsilon) - self.assertEqual(sum(numers), denom) - self.assertEqual(len(numers), len(probabilities)) - self.assertEqual(len(probabilities) * 2**mu, denom) - for i in range(len(numers)): - self.assertAlmostEqual( - numers[i] / denom, probabilities[i] / total_probability, delta=epsilon - ) - return numers, denom - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.random() for _ in range(n)] - self.assertGetDiscretizedDistribution(weights, 2 ** -random.randint(1, 20)) - - def test_known_discretizations(self): - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.25), ([4], 4)) - - self.assertEqual(self.assertGetDiscretizedDistribution([1], 0.125), ([8], 8)) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.1, 0.1, 0.1], 0.25), ([2, 2, 2], 6) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.25), ([2, 2, 2], 6) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.1), ([4, 4, 4], 12) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.05), ([7, 9, 8], 24) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.01), ([58, 70, 64], 192) - ) - - self.assertEqual( - self.assertGetDiscretizedDistribution([0.09, 0.11, 0.1], 0.00335), - ([115, 141, 128], 384), - ) - - -class PreprocessForEfficientRouletteSelectionTest(unittest.TestCase): - def assertPreprocess(self, weights): - alternates, keep_chances = _preprocess_for_efficient_roulette_selection(weights) - - self.assertEqual(len(alternates), len(keep_chances)) - - target_weight = sum(weights) // len(alternates) - distribution = list(keep_chances) - for i in range(len(alternates)): - distribution[alternates[i]] += target_weight - keep_chances[i] - self.assertEqual(weights, distribution) - - return alternates, keep_chances - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.randint(0, 100) for _ in range(n)] - weights[-1] += n - sum(weights) % n # Ensure multiple of length. - self.assertPreprocess(weights) - - def test_validation(self): - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[]) - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[1, 2]) - with self.assertRaises(ValueError): - _ = self.assertPreprocess(weights=[3, 3, 2]) - - def test_already_uniform(self): - self.assertEqual(self.assertPreprocess(weights=[1]), ([0], [0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1]), ([0, 1], [0, 0])) - self.assertEqual(self.assertPreprocess(weights=[1, 1, 1]), ([0, 1, 2], [0, 0, 0])) - self.assertEqual(self.assertPreprocess(weights=[2, 2, 2]), ([0, 1, 2], [0, 0, 0])) - - def test_donation(self): - # v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[1, 2, 3]), ([2, 1, 2], [1, 0, 0])) - # v0 donates 1 to v1. - self.assertEqual(self.assertPreprocess(weights=[3, 1, 2]), ([0, 0, 2], [0, 1, 0])) - # v0 donates 1 to v1, then 2 to v2. - self.assertEqual(self.assertPreprocess(weights=[5, 1, 0]), ([0, 0, 0], [0, 1, 0])) - - def test_over_donation(self): - # v0 donates 2 to v1, leaving v0 needy, then v2 donates 1 to v0. - self.assertEqual(self.assertPreprocess(weights=[3, 0, 3]), ([2, 0, 2], [1, 0, 0])) - - -class PreprocessLCUCoefficientsForReversibleSamplingTest(unittest.TestCase): - def assertPreprocess(self, lcu_coefs, epsilon): - alternates, keep_numers, mu = preprocess_lcu_coefficients_for_reversible_sampling( - lcu_coefs, epsilon - ) - - n = len(lcu_coefs) - keep_denom = 2**mu - self.assertEqual(len(alternates), n) - self.assertEqual(len(keep_numers), n) - self.assertTrue(all(0 <= e < keep_denom for e in keep_numers)) - - out_distribution = [1 / n * numer / keep_denom for numer in keep_numers] - for i in range(n): - switch_probability = 1 - keep_numers[i] / keep_denom - out_distribution[alternates[i]] += 1 / n * switch_probability - - total = sum(lcu_coefs) - for i in range(n): - self.assertAlmostEqual(out_distribution[i], lcu_coefs[i] / total, delta=epsilon) - - return alternates, keep_numers, keep_denom - - def test_fuzz(self): - random.seed(8) - for _ in range(100): - n = random.randint(1, 50) - weights = [random.randint(0, 100) for _ in range(n)] - weights[-1] += n - sum(weights) % n # Ensure multiple of length. - self.assertPreprocess(weights, 2 ** -random.randint(1, 20)) - - def test_known(self): - self.assertEqual(self.assertPreprocess([1, 2], epsilon=0.01), ([1, 1], [43, 0], 64)) - - self.assertEqual( - self.assertPreprocess([1, 2, 3], epsilon=0.01), ([2, 1, 2], [32, 0, 0], 64) - ) diff --git a/cirq-ft/requirements.txt b/cirq-ft/requirements.txt deleted file mode 100644 index e0eb53a8fb3..00000000000 --- a/cirq-ft/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -attrs -cachetools>=5.3 -ipywidgets -nbconvert -nbformat diff --git a/cirq-ft/setup.py b/cirq-ft/setup.py deleted file mode 100644 index f02d227043a..00000000000 --- a/cirq-ft/setup.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright 2023 The Cirq Developers -# -# 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 pathlib -import os - -from setuptools import find_packages, setup - -# This reads the __version__ variable from cirq/_version.py -__version__ = '' -exec(pathlib.Path('cirq_ft/_version.py').read_text(encoding='utf-8')) - -name = 'cirq-ft' - -description = 'A Cirq package for fault-tolerant algorithms' - -# README file as long_description. -long_description = pathlib.Path('README.rst').read_text(encoding='utf-8') - -# If CIRQ_PRE_RELEASE_VERSION is set then we update the version to this value. -# It is assumed that it ends with one of `.devN`, `.aN`, `.bN`, `.rcN` and hence -# it will be a pre-release version on PyPi. See -# https://packaging.python.org/guides/distributing-packages-using-setuptools/#pre-release-versioning -# for more details. -if 'CIRQ_PRE_RELEASE_VERSION' in os.environ: - __version__ = os.environ['CIRQ_PRE_RELEASE_VERSION'] - long_description = ( - "**This is a development version of Cirq-ft and may be " - "unstable.**\n\n**For the latest stable release of Cirq-ft " - "see**\n`here `__.\n\n" + long_description - ) - -# Read in requirements -requirements = pathlib.Path('requirements.txt').read_text(encoding='utf-8').split('\n') -requirements = [r.strip() for r in requirements] - -# Sanity check -assert __version__, 'Version string cannot be empty' - -requirements += [f'cirq-core=={__version__}'] - -cirq_packages = ['cirq_ft'] + [f'cirq_ft.{package}' for package in find_packages(where='cirq_ft')] - - -setup( - name=name, - version=__version__, - url='http://github.com/quantumlib/cirq', - author='The Cirq Developers', - author_email='cirq-dev@googlegroups.com', - python_requires='>=3.10.0', - install_requires=requirements, - license='Apache 2', - description=description, - long_description=long_description, - packages=cirq_packages, - package_data={'cirq_ft': ['py.typed']}, -) diff --git a/dev_tools/notebooks/isolated_notebook_test.py b/dev_tools/notebooks/isolated_notebook_test.py index 10f299139a9..ee006d4ffba 100644 --- a/dev_tools/notebooks/isolated_notebook_test.py +++ b/dev_tools/notebooks/isolated_notebook_test.py @@ -71,8 +71,6 @@ "**/google/*.ipynb", "**/ionq/*.ipynb", "**/pasqal/*.ipynb", - # skipp cirq-ft notebooks since they are included in individual tests - 'cirq-ft/**', # Rigetti uses local simulation with docker, so should work # if you run into issues locally, run # `docker compose -f cirq-rigetti/docker-compose.test.yaml up` diff --git a/dev_tools/notebooks/notebook_test.py b/dev_tools/notebooks/notebook_test.py index 5c8c74a0396..8c588fc1c5d 100644 --- a/dev_tools/notebooks/notebook_test.py +++ b/dev_tools/notebooks/notebook_test.py @@ -38,8 +38,6 @@ '**/rigetti/*.ipynb', # disabled to unblock Python 3.12. TODO(#6590) - fix and enable. 'cirq-core/cirq/contrib/quimb/Contract-a-Grid-Circuit.ipynb', - # skipp cirq-ft notebooks since they are included in individual tests - 'cirq-ft/**', # skipping fidelity estimation due to # https://github.com/quantumlib/Cirq/issues/3502 'examples/*fidelity*', diff --git a/dev_tools/requirements/deps/cirq-all-no-contrib.txt b/dev_tools/requirements/deps/cirq-all-no-contrib.txt index dce8de17989..244ae247cce 100644 --- a/dev_tools/requirements/deps/cirq-all-no-contrib.txt +++ b/dev_tools/requirements/deps/cirq-all-no-contrib.txt @@ -1,6 +1,5 @@ # captures all the modules -r ../../../cirq-aqt/requirements.txt -r ../../../cirq-core/requirements.txt --r ../../../cirq-ft/requirements.txt -r ../../../cirq-google/requirements.txt -r ../../../cirq-rigetti/requirements.txt