diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 8ae0b129e..8d7bb9620 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_add_k import qualtran.bloqs.gf_arithmetic.gf2_addition import qualtran.bloqs.gf_arithmetic.gf2_inverse import qualtran.bloqs.gf_arithmetic.gf2_multiplication @@ -569,6 +570,11 @@ module=qualtran.bloqs.gf_arithmetic.gf2_addition, bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_addition._GF2_ADDITION_DOC], ), + NotebookSpecV2( + title='GF($2^m$) Add Constant', + module=qualtran.bloqs.gf_arithmetic.gf2_add_k, + bloq_specs=[qualtran.bloqs.gf_arithmetic.gf2_add_k._GF2_ADD_K_DOC], + ), NotebookSpecV2( title='GF($2^m$) Square', module=qualtran.bloqs.gf_arithmetic.gf2_square, diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 208f6968b..0906dd71b 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_add_k.ipynb gf_arithmetic/gf2_square.ipynb gf_arithmetic/gf2_inverse.ipynb diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb b/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb new file mode 100644 index 000000000..4d9e029c2 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k.ipynb @@ -0,0 +1,168 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b02daa35", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# GF($2^m$) Add Constant" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c57ce930", + "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": "e4620511", + "metadata": { + "cq.autogen": "GF2AddK.bloq_doc.md" + }, + "source": [ + "## `GF2AddK`\n", + "In place addition of a constant $k$ for elements in GF($2^m$).\n", + "\n", + "The bloq implements in place addition of a classical constant $k$ and a quantum register\n", + "$|x\\rangle$ storing elements from GF($2^m$). Addition in GF($2^m$) simply reduces to a component\n", + "wise XOR, which can be implemented via X gates.\n", + "\n", + " $$\n", + " |x\\rangle \\rightarrow |x + k\\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 the input register x.\n", + " - `k`: Integer representation of constant over GF($2^m$) that should be added to the input register x. \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": "04dc8f2b", + "metadata": { + "cq.autogen": "GF2AddK.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.gf_arithmetic import GF2AddK" + ] + }, + { + "cell_type": "markdown", + "id": "a0d774d4", + "metadata": { + "cq.autogen": "GF2AddK.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "0588ca5b", + "metadata": { + "cq.autogen": "GF2AddK.gf16_add_k" + }, + "outputs": [], + "source": [ + "gf16_add_k = GF2AddK(4, 1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8250af55", + "metadata": { + "cq.autogen": "GF2AddK.gf2_add_k_symbolic" + }, + "outputs": [], + "source": [ + "import sympy\n", + "\n", + "m, k = sympy.symbols('m, k', positive=True, integers=True)\n", + "gf2_add_k_symbolic = GF2AddK(m, k)" + ] + }, + { + "cell_type": "markdown", + "id": "fe4c4550", + "metadata": { + "cq.autogen": "GF2AddK.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2374ad42", + "metadata": { + "cq.autogen": "GF2AddK.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([gf16_add_k, gf2_add_k_symbolic],\n", + " ['`gf16_add_k`', '`gf2_add_k_symbolic`'])" + ] + }, + { + "cell_type": "markdown", + "id": "f64d8798", + "metadata": { + "cq.autogen": "GF2AddK.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "578f0f39", + "metadata": { + "cq.autogen": "GF2AddK.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "gf16_add_k_g, gf16_add_k_sigma = gf16_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(gf16_add_k_g)\n", + "show_counts_sigma(gf16_add_k_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_add_k.py b/qualtran/bloqs/gf_arithmetic/gf2_add_k.py new file mode 100644 index 000000000..e85f373d5 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k.py @@ -0,0 +1,106 @@ +# 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, Sequence, TYPE_CHECKING + +import attrs + +from qualtran import Bloq, bloq_example, BloqDocSpec, DecomposeTypeError, QGF, Register, Signature +from qualtran.bloqs.basic_gates import XGate +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 GF2AddK(Bloq): + r"""In place addition of a constant $k$ for elements in GF($2^m$). + + The bloq implements in place addition of a classical constant $k$ and a quantum register + $|x\rangle$ storing elements from GF($2^m$). Addition in GF($2^m$) simply reduces to a component + wise XOR, which can be implemented via X gates. + + $$ + |x\rangle \rightarrow |x + k\rangle + $$ + + Args: + bitsize: The degree $m$ of the galois field GF($2^m$). Also corresponds to the number of + qubits in the input register x. + k: Integer representation of constant over GF($2^m$) that should be added to the input + register x. + + Registers: + x: Input THRU register of size $m$ that stores elements from $GF(2^m)$. + """ + + bitsize: SymbolicInt + k: 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 _bits_k(self) -> Sequence[int]: + return self.qgf.to_bits(self.qgf.gf_type(self.k)) + + def is_symbolic(self): + return is_symbolic(self.k, self.bitsize) + + def build_composite_bloq(self, bb: 'BloqBuilder', *, x: 'Soquet') -> Dict[str, 'Soquet']: + if self.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}") + xs = bb.split(x) + + for i, bit in enumerate(self._bits_k): + if bit == 1: + xs[i] = bb.add(XGate(), q=xs[i]) + + x = bb.join(xs, dtype=self.qgf) + + return {'x': x} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + num_flips = self.bitsize if self.is_symbolic() else sum(self._bits_k) + return {XGate(): num_flips} + + def on_classical_vals(self, *, x) -> Dict[str, 'ClassicalValT']: + assert isinstance(x, self.qgf.gf_type) + return {'x': x + self.qgf.gf_type(self.k)} + + +@bloq_example +def _gf16_add_k() -> GF2AddK: + gf16_add_k = GF2AddK(4, 1) + return gf16_add_k + + +@bloq_example +def _gf2_add_k_symbolic() -> GF2AddK: + import sympy + + m, k = sympy.symbols('m, k', positive=True, integers=True) + gf2_add_k_symbolic = GF2AddK(m, k) + return gf2_add_k_symbolic + + +_GF2_ADD_K_DOC = BloqDocSpec(bloq_cls=GF2AddK, examples=(_gf16_add_k, _gf2_add_k_symbolic)) diff --git a/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py new file mode 100644 index 000000000..2f92a4979 --- /dev/null +++ b/qualtran/bloqs/gf_arithmetic/gf2_add_k_test.py @@ -0,0 +1,44 @@ +# 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_add_k import _gf2_add_k_symbolic, _gf16_add_k, GF2AddK +from qualtran.testing import assert_consistent_classical_action + + +def test_gf16_multiplication(bloq_autotester): + bloq_autotester(_gf16_add_k) + + +def test_gf2_multiplication_symbolic(bloq_autotester): + bloq_autotester(_gf2_add_k_symbolic) + + +def test_gf2_multiplication_classical_sim_quick(): + m = 2 + GFM = GF(2**m) + for k in GFM.elements: + bloq = GF2AddK(m, int(k)) + 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): + GFM = GF(2**m) + for k in GFM.elements: + bloq = GF2AddK(m, int(k)) + assert_consistent_classical_action(bloq, x=GFM.elements) diff --git a/qualtran/conftest.py b/qualtran/conftest.py index f3eeec1b3..e229f0a2a 100644 --- a/qualtran/conftest.py +++ b/qualtran/conftest.py @@ -92,6 +92,8 @@ def assert_bloq_example_serializes_for_pytest(bloq_ex: BloqExample): 'symbolic_hamsim_by_gqsp', 'gf16_addition', # cannot serialize QGF 'gf2_addition_symbolic', # cannot serialize QGF + 'gf16_add_k', # cannot serialize QGF + 'gf2_add_k_symbolic', # cannot serialize QGF 'gf16_multiplication', # cannot serialize QGF 'gf2_multiplication_symbolic', # cannot serialize QGF 'gf16_square', # cannot serialize QGF