From 97aae937ad9a91808dca9190a0cdef73426155de Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Fri, 4 Oct 2024 14:34:33 -0700 Subject: [PATCH 1/5] Add GF2Add bloq for addition over GF(2^m) --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 8 +- docs/bloqs/index.rst | 1 + .../bloqs/gf_arithmetic/gf2_addition.ipynb | 168 ++++++++++++++++++ qualtran/bloqs/gf_arithmetic/gf2_addition.py | 99 +++++++++++ .../bloqs/gf_arithmetic/gf2_addition_test.py | 46 +++++ qualtran/conftest.py | 2 + 6 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_addition.py create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_addition_test.py diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 7b38a5db0..69b789f8f 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -105,6 +105,7 @@ import qualtran.bloqs.data_loading.select_swap_qrom import qualtran.bloqs.factoring.ecc import qualtran.bloqs.factoring.mod_exp +import qualtran.bloqs.gf_arithmetic.gf2_addition import qualtran.bloqs.gf_arithmetic.gf2_multiplication import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import qualtran.bloqs.mcmt.and_bloq @@ -560,7 +561,12 @@ title='GF($2^m$) Multiplication', module=qualtran.bloqs.gf_arithmetic.gf2_multiplication, bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_multiplication._GF2_MULTIPLICATION_DOC], - ) + ), + NotebookSpecV2( + title='GF($2^m$) Addition', + module=qualtran.bloqs.gf_arithmetic.gf2_addition, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_addition._GF2_ADDITION_DOC], + ), ] diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 4dc18012a..1d481a8ce 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -92,6 +92,7 @@ Bloqs Library :caption: GF Arithmetic: gf_arithmetic/gf2_multiplication.ipynb + gf_arithmetic/gf2_addition.ipynb .. toctree:: :maxdepth: 2 diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb new file mode 100644 index 000000000..05aa241dd --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "52204197", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Addition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74b61b7c", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "fab3b162", + "metadata": { + "cq.autogen": "GF2Addition.bloq_doc.md" + }, + "source": [ + "## `GF2Addition`\n", + "In place addition over GF($2^m$).\n", + "\n", + "The bloq implements in place addition of two quantum registers storing elements\n", + "from GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which\n", + "can be implemented via CNOT gates. The addition is performed in-place such that\n", + "\n", + " $$\n", + " |x\\rangle |y\\rangle \\rightarrow |x\\rangle |x + y\\rangle\n", + " $$\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in each of the two input registers x and y that should be added. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n", + " - `y`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "507cd9b4", + "metadata": { + "cq.autogen": "GF2Addition.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Addition" + ] + }, + { + "cell_type": "markdown", + "id": "fae463a5", + "metadata": { + "cq.autogen": "GF2Addition.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57a0702b", + "metadata": { + "cq.autogen": "GF2Addition.gf16_addition" + }, + "outputs": [], + "source": [ + "gf16_addition = GF2Addition(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2c2db2b3", + "metadata": { + "cq.autogen": "GF2Addition.gf2_addition_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_addition_symbolic = GF2Addition(m)" + ] + }, + { + "cell_type": "markdown", + "id": "3e416779", + "metadata": { + "cq.autogen": "GF2Addition.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0691c5f3", + "metadata": { + "cq.autogen": "GF2Addition.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_addition, gf2_addition_symbolic],\n", + " ['`gf16_addition`', '`gf2_addition_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "189469f9", + "metadata": { + "cq.autogen": "GF2Addition.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "eae7490a", + "metadata": { + "cq.autogen": "GF2Addition.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_addition_g, gf16_addition_sigma = gf16_addition.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_addition_g)\n", + "show_counts_sigma(gf16_addition_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition.py b/qualtran/bloqs/gf_arithmetic/gf2_addition.py new file mode 100644 index 000000000..268371ae6 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition.py @@ -0,0 +1,99 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.basic_gates import CNOT +from qualtran.symbolics import is_symbolic, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2Addition(Bloq): + r"""In place addition over GF($2^m$). + + The bloq implements in place addition of two quantum registers storing elements + from GF($2^m$). Addition in GF($2^m$) simply reduces to a component wise XOR, which + can be implemented via CNOT gates. The addition is performed in-place such that + + $$ + |x\rangle |y\rangle \rightarrow |x\rangle |x + y\rangle + $$ + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in each of the two input registers x and y that should be added. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + y: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', dtype=self.qgf), Register('y', dtype=self.qgf)]) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + def build_composite_bloq( + self, bb: 'BloqBuilder', *, x: 'Soquet', y: 'Soquet' + ) -> Dict[str, 'Soquet']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + x, y = bb.split(x)[::-1], bb.split(y)[::-1] + m = int(self.bitsize) + for i in range(m): + x[i], y[i] = bb.add(CNOT(), ctrl=x[i], target=y[i]) + x, y = (bb.join(x[::-1], dtype=self.qgf), bb.join(y[::-1], dtype=self.qgf)) + return {'x': x, 'y': y} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return {CNOT(): self.bitsize} + + def on_classical_vals(self, *, x, y) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) and isinstance(y, self.qgf.gf_type) + return {'x': x, 'y': x + y} + + +@bloq_example +def _gf16_addition() -> GF2Addition: + gf16_addition = GF2Addition(4) + return gf16_addition + + +@bloq_example +def _gf2_addition_symbolic() -> GF2Addition: + import sympy + + m = sympy.Symbol('m') + gf2_addition_symbolic = GF2Addition(m) + return gf2_addition_symbolic + + +_GF2_ADDITION_DOC = BloqDocSpec( + bloq_cls=GF2Addition, examples=(_gf16_addition, _gf2_addition_symbolic) +) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py new file mode 100644 index 000000000..71a9abf74 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py @@ -0,0 +1,46 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from galois import GF + +from qualtran.bloqs.gf_arithmetic.gf2_addition import ( + _gf2_addition_symbolic, + _gf16_addition, + GF2Addition, +) +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_multiplication(bloq_autotester): + bloq_autotester(_gf16_addition) + + +def test_gf2_multiplication_symbolic(bloq_autotester): + bloq_autotester(_gf2_addition_symbolic) + + +def test_gf2_multiplication_classical_sim_quick(): + m = 2 + bloq = GF2Addition(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +def test_gf2_multiplication_classical_sim(m): + bloq = GF2Addition(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index f1bee79a2..489dc0bea 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -90,6 +90,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'qubitization_qpe_sparse_chem', # too slow 'trott_unitary', 'symbolic_hamsim_by_gqsp', + 'gf16_addition', # cannot serialize QGF + 'gf2_addition_symbolic', # cannot serialize QGF 'gf16_multiplication', # cannot serialize QGF 'gf2_multiplication_symbolic', # cannot serialize QGF 'gqsp_1d_ising', From 14f14c5ae04009c675e3f6c16cccdad945965bcd Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Fri, 4 Oct 2024 15:10:56 -0700 Subject: [PATCH 2/5] Add import --- qualtran/bloqs/gf_arithmetic/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qualtran/bloqs/gf_arithmetic/__init__.py b/qualtran/bloqs/gf_arithmetic/__init__.py index 6fc27f4c6..c434f695d 100644 --- a/qualtran/bloqs/gf_arithmetic/__init__.py +++ b/qualtran/bloqs/gf_arithmetic/__init__.py @@ -12,4 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. +from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication From 40d18c02fc9cf52268e720522b2d55b280c3fec5 Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Sat, 5 Oct 2024 17:22:05 -0700 Subject: [PATCH 3/5] Add GF2Square bloq for squaring over GF(2^m) --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 6 + docs/bloqs/index.rst | 1 + qualtran/bloqs/gf_arithmetic/__init__.py | 1 + qualtran/bloqs/gf_arithmetic/gf2_square.ipynb | 173 ++++++++++++++++++ qualtran/bloqs/gf_arithmetic/gf2_square.py | 124 +++++++++++++ .../bloqs/gf_arithmetic/gf2_square_test.py | 42 +++++ qualtran/conftest.py | 2 + 7 files changed, 349 insertions(+) create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_square.ipynb create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_square.py create mode 100644 qualtran/bloqs/gf_arithmetic/gf2_square_test.py diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 69b789f8f..0a0dbe453 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -107,6 +107,7 @@ import qualtran.bloqs.factoring.mod_exp import qualtran.bloqs.gf_arithmetic.gf2_addition import qualtran.bloqs.gf_arithmetic.gf2_multiplication +import qualtran.bloqs.gf_arithmetic.gf2_square import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import qualtran.bloqs.mcmt.and_bloq import qualtran.bloqs.mcmt.controlled_via_and @@ -567,6 +568,11 @@ module=qualtran.bloqs.gf_arithmetic.gf2_addition, bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_addition._GF2_ADDITION_DOC], ), + NotebookSpecV2( + title='GF($2^m$) Square', + module=qualtran.bloqs.gf_arithmetic.gf2_square, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_square._GF2_SQUARE_DOC], + ), ] diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 1d481a8ce..6a543d88b 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -93,6 +93,7 @@ Bloqs Library gf_arithmetic/gf2_multiplication.ipynb gf_arithmetic/gf2_addition.ipynb + gf_arithmetic/gf2_square.ipynb .. toctree:: :maxdepth: 2 diff --git a/qualtran/bloqs/gf_arithmetic/__init__.py b/qualtran/bloqs/gf_arithmetic/__init__.py index c434f695d..56084da16 100644 --- a/qualtran/bloqs/gf_arithmetic/__init__.py +++ b/qualtran/bloqs/gf_arithmetic/__init__.py @@ -14,3 +14,4 @@ from qualtran.bloqs.gf_arithmetic.gf2_addition import GF2Addition from qualtran.bloqs.gf_arithmetic.gf2_multiplication import GF2Multiplication +from qualtran.bloqs.gf_arithmetic.gf2_square import GF2Square diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb new file mode 100644 index 000000000..4ddf5e6dd --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square.ipynb @@ -0,0 +1,173 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "cb46f029", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Square" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6054ebf1", + "metadata": { + "cq.autogen": "top_imports" + }, + "outputs": [], + "source": [ + "from qualtran import Bloq, CompositeBloq, BloqBuilder, Signature, Register\n", + "from qualtran import QBit, QInt, QUInt, QAny\n", + "from qualtran.drawing import show_bloq, show_call_graph, show_counts_sigma\n", + "from typing import *\n", + "import numpy as np\n", + "import sympy\n", + "import cirq" + ] + }, + { + "cell_type": "markdown", + "id": "abc9f3d5", + "metadata": { + "cq.autogen": "GF2Square.bloq_doc.md" + }, + "source": [ + "## `GF2Square`\n", + "In place squaring for elements in GF($2^m$)\n", + "\n", + "The bloq implements in-place squaring of a quantum registers storing elements\n", + "from GF($2^m$). Specifically, it implements the transformation\n", + "\n", + "$$\n", + " |a\\rangle \\rightarrow |a^2\\rangle\n", + "$$\n", + "\n", + "The key insight is that for elements in GF($2^m$),\n", + "$$\n", + " a^2 =a_0 + a_1 x^2 + a_2 x^4 + ... + a_{n-1} x^{2(n - 1)}\n", + "$$\n", + "\n", + "Thus, squaring can be implemented via a linear reversible circuit using only CNOT gates.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of qubits in the input register to be squared. \n", + "\n", + "#### Registers\n", + " - `x`: Input THRU register of size $m$ that stores elements from $GF(2^m)$.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c78c541d", + "metadata": { + "cq.autogen": "GF2Square.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2Square" + ] + }, + { + "cell_type": "markdown", + "id": "3867aabe", + "metadata": { + "cq.autogen": "GF2Square.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10c374a4", + "metadata": { + "cq.autogen": "GF2Square.gf16_square" + }, + "outputs": [], + "source": [ + "gf16_square = GF2Square(4)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "34d1aa8e", + "metadata": { + "cq.autogen": "GF2Square.gf2_square_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m = sympy.Symbol('m')\n", + "gf2_square_symbolic = GF2Square(m)" + ] + }, + { + "cell_type": "markdown", + "id": "40f24bac", + "metadata": { + "cq.autogen": "GF2Square.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "218453ac", + "metadata": { + "cq.autogen": "GF2Square.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_square, gf2_square_symbolic],\n", + " ['`gf16_square`', '`gf2_square_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "ffc34750", + "metadata": { + "cq.autogen": "GF2Square.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "05bdb33f", + "metadata": { + "cq.autogen": "GF2Square.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_square_g, gf16_square_sigma = gf16_square.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_square_g)\n", + "show_counts_sigma(gf16_square_sigma)" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square.py b/qualtran/bloqs/gf_arithmetic/gf2_square.py new file mode 100644 index 000000000..c4ee06c06 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square.py @@ -0,0 +1,124 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from functools import cached_property +from typing import Dict, Set, TYPE_CHECKING, Union + +import attrs +import numpy as np +from galois import GF, Poly + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.gf_arithmetic.gf2_multiplication import SynthesizeLRCircuit +from qualtran.symbolics import is_symbolic, Shaped, SymbolicInt + +if TYPE_CHECKING: + from qualtran import BloqBuilder, Soquet + from qualtran.resource_counting import BloqCountDictT, BloqCountT, SympySymbolAllocator + from qualtran.simulation.classical_sim import ClassicalValT + + +@attrs.frozen +class GF2Square(Bloq): + r"""In place squaring for elements in GF($2^m$) + + The bloq implements in-place squaring of a quantum registers storing elements + from GF($2^m$). Specifically, it implements the transformation + + $$ + |a\rangle \rightarrow |a^2\rangle + $$ + + The key insight is that for elements in GF($2^m$), + $$ + a^2 =a_0 + a_1 x^2 + a_2 x^4 + ... + a_{n-1} x^{2(n - 1)} + $$ + + Thus, squaring can be implemented via a linear reversible circuit using only CNOT gates. + + Args: + bitsize: The degree $m$ of the galois field $GF(2^m)$. Also corresponds to the number of + qubits in the input register to be squared. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', dtype=self.qgf)]) + + @cached_property + def qgf(self) -> QGF: + return QGF(characteristic=2, degree=self.bitsize) + + @cached_property + def squaring_matrix(self) -> np.ndarray: + m = int(self.bitsize) + f = self.qgf.gf_type.irreducible_poly + M = np.zeros((m, m)) + alpha = [0] * m + for i in range(m): + # x ** (2 * i) % f + alpha[-i - 1] = 1 + coeffs = ((Poly(alpha, GF(2)) * Poly(alpha, GF(2))) % f).coeffs.tolist()[::-1] + coeffs = coeffs + [0] * (m - len(coeffs)) + M[i] = coeffs + alpha[-i - 1] = 0 + return np.transpose(M) + + @cached_property + def synthesize_squaring_matrix(self) -> SynthesizeLRCircuit: + m = self.bitsize + return ( + SynthesizeLRCircuit(Shaped((m, m))) + if is_symbolic(m) + else SynthesizeLRCircuit(self.squaring_matrix) + ) + + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'Soquet']: + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + x = bb.split(x)[::-1] + x = bb.add(self.synthesize_squaring_matrix, q=x) + x = bb.join(x[::-1], dtype=self.qgf) + return {'x': x} + + def build_call_graph( + self, ssa: 'SympySymbolAllocator' + ) -> Union['BloqCountDictT', Set['BloqCountT']]: + return {self.synthesize_squaring_matrix: 1} + + def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) + return {'x': x**2} + + +@bloq_example +def _gf16_square() -> GF2Square: + gf16_square = GF2Square(4) + return gf16_square + + +@bloq_example +def _gf2_square_symbolic() -> GF2Square: + import sympy + + m = sympy.Symbol('m') + gf2_square_symbolic = GF2Square(m) + return gf2_square_symbolic + + +_GF2_SQUARE_DOC = BloqDocSpec(bloq_cls=GF2Square, examples=(_gf16_square, _gf2_square_symbolic)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py new file mode 100644 index 000000000..7bc0dce79 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py @@ -0,0 +1,42 @@ +# Copyright 2024 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest +from galois import GF + +from qualtran.bloqs.gf_arithmetic.gf2_square import _gf2_square_symbolic, _gf16_square, GF2Square +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_multiplication(bloq_autotester): + bloq_autotester(_gf16_square) + + +def test_gf2_multiplication_symbolic(bloq_autotester): + bloq_autotester(_gf2_square_symbolic) + + +def test_gf2_multiplication_classical_sim_quick(): + m = 2 + bloq = GF2Square(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements) + + +@pytest.mark.slow +@pytest.mark.parametrize('m', [3, 4, 5]) +def test_gf2_multiplication_classical_sim(m): + bloq = GF2Square(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index 489dc0bea..8a4e7a0c1 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -94,6 +94,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'gf2_addition_symbolic', # cannot serialize QGF 'gf16_multiplication', # cannot serialize QGF 'gf2_multiplication_symbolic', # cannot serialize QGF + 'gf16_square', # cannot serialize QGF + 'gf2_square_symbolic', # cannot serialize QGF 'gqsp_1d_ising', 'auto_partition', 'unitary_block_encoding', From 5b50df4315b87eb36a86d00661811094dcb251ee Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Mon, 7 Oct 2024 16:59:20 -0700 Subject: [PATCH 4/5] Docstring and more tests --- qualtran/bloqs/gf_arithmetic/gf2_square.py | 1 + qualtran/bloqs/gf_arithmetic/gf2_square_test.py | 15 +++++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square.py b/qualtran/bloqs/gf_arithmetic/gf2_square.py index c4ee06c06..140895e92 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_square.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_square.py @@ -66,6 +66,7 @@ def qgf(self) -> QGF: @cached_property def squaring_matrix(self) -> np.ndarray: + r"""$m \times m$ matrix that maps the input $x^{i}$ to $x^{2 * i} % P(x)$""" m = int(self.bitsize) f = self.qgf.gf_type.irreducible_poly M = np.zeros((m, m)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py index 7bc0dce79..f272f5447 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py @@ -16,27 +16,34 @@ from galois import GF from qualtran.bloqs.gf_arithmetic.gf2_square import _gf2_square_symbolic, _gf16_square, GF2Square +from qualtran.resource_counting import get_cost_value, QECGatesCost from qualtran.testing import assert_consistent_classical_action -def test_gf16_multiplication(bloq_autotester): +def test_gf16_square(bloq_autotester): bloq_autotester(_gf16_square) -def test_gf2_multiplication_symbolic(bloq_autotester): +def test_gf2_square_symbolic(bloq_autotester): bloq_autotester(_gf2_square_symbolic) -def test_gf2_multiplication_classical_sim_quick(): +def test_gf2_square_classical_sim_quick(): m = 2 bloq = GF2Square(m) GFM = GF(2**m) assert_consistent_classical_action(bloq, x=GFM.elements) +def test_gf2_square_resource(): + bloq = _gf2_square_symbolic.make() + assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0 + assert get_cost_value(bloq, QECGatesCost()).clifford == bloq.bitsize + + @pytest.mark.slow @pytest.mark.parametrize('m', [3, 4, 5]) -def test_gf2_multiplication_classical_sim(m): +def test_gf2_square_classical_sim(m): bloq = GF2Square(m) GFM = GF(2**m) assert_consistent_classical_action(bloq, x=GFM.elements) From 701f0ea9fadb5717b37a5037ec611b9b5b13d4ae Mon Sep 17 00:00:00 2001 From: Tanuj Khattar Date: Mon, 7 Oct 2024 17:16:56 -0700 Subject: [PATCH 5/5] Fix pytest typo --- qualtran/bloqs/gf_arithmetic/gf2_square_test.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py index f272f5447..a6d2ab970 100644 --- a/qualtran/bloqs/gf_arithmetic/gf2_square_test.py +++ b/qualtran/bloqs/gf_arithmetic/gf2_square_test.py @@ -13,10 +13,12 @@ # limitations under the License. import pytest +import sympy from galois import GF from qualtran.bloqs.gf_arithmetic.gf2_square import _gf2_square_symbolic, _gf16_square, GF2Square from qualtran.resource_counting import get_cost_value, QECGatesCost +from qualtran.symbolics import ceil, log2 from qualtran.testing import assert_consistent_classical_action @@ -37,8 +39,9 @@ def test_gf2_square_classical_sim_quick(): def test_gf2_square_resource(): bloq = _gf2_square_symbolic.make() + m = bloq.bitsize assert get_cost_value(bloq, QECGatesCost()).total_t_count() == 0 - assert get_cost_value(bloq, QECGatesCost()).clifford == bloq.bitsize + assert sympy.simplify(get_cost_value(bloq, QECGatesCost()).clifford - ceil(m**2 / log2(m))) == 0 @pytest.mark.slow