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/__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 diff --git a/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_addition.ipynb new file mode 100644 index 000000000..befc2c5fe --- /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..e316e0c58 --- /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), bb.split(y) + 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, dtype=self.qgf), bb.join(y, 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..ed1ed3434 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_addition_test.py @@ -0,0 +1,53 @@ +# 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.resource_counting import get_cost_value, QECGatesCost +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_addition(bloq_autotester): + bloq_autotester(_gf16_addition) + + +def test_gf2_addition_symbolic(bloq_autotester): + bloq_autotester(_gf2_addition_symbolic) + + +def test_gf2_addition_classical_sim_quick(): + m = 2 + bloq = GF2Addition(m) + GFM = GF(2**m) + assert_consistent_classical_action(bloq, x=GFM.elements, y=GFM.elements) + + +def test_gf2_addition_resource(): + bloq = _gf2_addition_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_addition_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',