From d70debec741034c610936c856f2f939b034d5846 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Tue, 24 Sep 2024 20:20:51 -0700 Subject: [PATCH 01/21] Add rsa files - needs a lot of work just stashing it for now --- .../factoring/rsa/find_rsa_private_key.py | 102 ++++++++++++++++++ .../rsa/find_rsa_private_key_test.py | 24 +++++ .../bloqs/factoring/rsa/rsa_phase_estimate.py | 93 ++++++++++++++++ .../factoring/rsa/rsa_phase_estimate_test.py | 23 ++++ 4 files changed, 242 insertions(+) create mode 100644 qualtran/bloqs/factoring/rsa/find_rsa_private_key.py create mode 100644 qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py create mode 100644 qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py create mode 100644 qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py diff --git a/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py b/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py new file mode 100644 index 000000000..cc280a8ab --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py @@ -0,0 +1,102 @@ +# 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 + +import sympy +from attrs import frozen + +from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, QUInt, Signature, SoquetT +from qualtran.bloqs.basic_gates import IntState +from qualtran.bloqs.bookkeeping import Free +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics import SymbolicInt + +from .rsa_phase_estimate import RSAPhaseEstimate + + +@frozen +class FindRSAPrivateKey(Bloq): + r"""Perform one phase estimation to break rsa cryptography. + + This follows the strategy in Litinski 2023. We perform two phase estimations corresponding + to `ECCAddR(R=P)` and `ECCAddR(R=Q)` for base point $P$ and public key $Q$. + + The first phase estimation projects us into a random eigenstate of the ECCAddR(R=P) operator + which we index by the integer $c$. Per eq. 5 in the reference, these eigenstates take the form + $$ + |\psi_c \rangle = \sum_j^{r-1} \omega^{cj}\ | [j]P \rangle \\ + \omega = e^{2\pi i / r} \\ + [r] P = P + $$ + + This state is a simultaneous eigenstate of the second operator, `ECCAddR(R=Q)`. By + the definition of the operator, acting it upon $|\psi_c\rangle$ gives: + $$ + |\psi_c \rangle \rightarrow \sum_j w^{cj} | [j]P + Q \rangle\rangle + $$ + + The private key $k$ that we wish to recover relates the public key to the base point + $$ + Q = [k] P + $$ + so our simultaneous eigenstate can be equivalently written as + $$ + \sum_j^{r-1} \omega^{cj} | [j+k] P \rangle \\ + = \omega^{-ck} |\psi_c \rangle + $$ + + Therefore, the measured result of the second phase estimation is $ck$. Since we have + already measured the random index $c$, we can divide it out to recover the private key $k$. + + Args: + n: The bitsize of the integer we want to factor. + mod: The integer modulus. + + References: + [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). + Figure 1. + """ + + n: 'SymbolicInt' + mod: 'SymbolicInt' + + @cached_property + def signature(self) -> 'Signature': + return Signature([]) + + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: + x = bb.add(IntState(bitsize=self.n, val=1)) + + x = bb.add(RSAPhaseEstimate(n=self.n, mod=self.mod), x=x) + + bb.add(Free(QUInt(self.n)), reg=x) + return {} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return {RSAPhaseEstimate(n=self.n): 1} + + def cost_attrs(self): + return [('n', self.n)] + + +@bloq_example +def _rsa() -> FindRSAPrivateKey: + n, p = sympy.symbols('n p') + rsa = FindRSAPrivateKey(n=n, mod=p) + return rsa + + +_ECC_BLOQ_DOC = BloqDocSpec(bloq_cls=FindRSAPrivateKey, examples=[_rsa]) diff --git a/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py b/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py new file mode 100644 index 000000000..8b27f3e5e --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py @@ -0,0 +1,24 @@ +# 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 qualtran.testing as qlt_testing +from qualtran.bloqs.factoring.rsa.find_rsa_private_key import _rsa + + +def test_rsa(bloq_autotester): + bloq_autotester(_rsa) + + +def test_notebook(): + qlt_testing.execute_notebook('rsa') diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py new file mode 100644 index 000000000..637951f4f --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -0,0 +1,93 @@ +# 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 + +import sympy +from attrs import frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + DecomposeTypeError, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.basic_gates import PlusState +from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator + +from ._ecc_shims import MeasureQFT +from .ec_add_r import ECAddR +from .ec_point import ECPoint + + +@frozen +class RSAPhaseEstimate(Bloq): + """Perform a single phase estimation of ECAddR for a given point. + + This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the + addition of the base point $P$, then of the public key $Q$. + + Args: + n: The bitsize of the elliptic curve points' x and y registers. + point: The elliptic curve point to phase estimate against. + """ + + n: int + point: ECPoint + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]) + + def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: + if isinstance(self.n, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `n`.") + ctrl = [bb.add(PlusState()) for _ in range(self.n)] + for i in range(self.n): + ctrl[i], x, y = bb.add(ECAddR(n=self.n, R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + + bb.add(MeasureQFT(n=self.n), x=ctrl) + return {'x': x, 'y': y} + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': + return {ECAddR(n=self.n, R=self.point): self.n, MeasureQFT(n=self.n): 1} + + def __str__(self) -> str: + return f'PE${self.point}$' + + +@bloq_example +def _ec_pe() -> ECPhaseEstimateR: + n, p = sympy.symbols('n p ') + Rx, Ry = sympy.symbols('R_x R_y') + ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) + return ec_pe + + +@bloq_example +def _ec_pe_small() -> ECPhaseEstimateR: + n = 3 + Rx, Ry, p = sympy.symbols('R_x R_y p') + ec_pe_small = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) + return ec_pe_small + + +_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe]) diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py new file mode 100644 index 000000000..3b520c826 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py @@ -0,0 +1,23 @@ +# 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 qualtran.bloqs.factoring.rsa.rsa_phase_estimate import _rsa_pe, _rsa_pe_small + + +def test_rsa_pe(bloq_autotester): + bloq_autotester(_rsa_pe) + + +def test_rsa_pe_small(bloq_autotester): + bloq_autotester(_rsa_pe_small) From e5daf750c7dce4a3b17d0ed56d025bf93ddd5a9e Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sat, 28 Sep 2024 16:41:14 -0700 Subject: [PATCH 02/21] Made some structure changes RSA --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 9 + .../_ecc_shims.py => _factoring_shims.py} | 0 .../factoring/ecc/ec_phase_estimate_r.py | 2 +- qualtran/bloqs/factoring/mod_add_test.py | 0 qualtran/bloqs/factoring/mod_exp.html | 23 -- qualtran/bloqs/factoring/mod_exp.ipynb | 209 ------------------ .../factoring/rsa/find_rsa_private_key.py | 4 +- .../{mod_exp.py => rsa/rsa_mod_exp.py} | 0 .../rsa_mod_exp_test.py} | 0 .../bloqs/factoring/rsa/rsa_phase_estimate.py | 36 ++- 10 files changed, 27 insertions(+), 256 deletions(-) rename qualtran/bloqs/factoring/{ecc/_ecc_shims.py => _factoring_shims.py} (100%) delete mode 100644 qualtran/bloqs/factoring/mod_add_test.py delete mode 100644 qualtran/bloqs/factoring/mod_exp.html delete mode 100644 qualtran/bloqs/factoring/mod_exp.ipynb rename qualtran/bloqs/factoring/{mod_exp.py => rsa/rsa_mod_exp.py} (100%) rename qualtran/bloqs/factoring/{mod_exp_test.py => rsa/rsa_mod_exp_test.py} (100%) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 54e26e140..407a0e8c7 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.factoring.rsa import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import qualtran.bloqs.mcmt.and_bloq import qualtran.bloqs.mcmt.controlled_via_and @@ -534,6 +535,14 @@ bloq_specs=[qualtran.bloqs.factoring.mod_exp._MODEXP_DOC], directory=f'{SOURCE_DIR}/bloqs/factoring', ), + NotebookSpecV2( + title='Factoring RSA', + module=qualtran.bloqs.factoring.rsa, + bloq_specs=[ + qualtran.bloqs.factoring.rsa.find_rsa_private_key._RSA_BLOQ_DOC, + qualtran.bloqs.factoring.rsa.rsa_phase_estimate._RSA_PE_BLOQ_DOC, + ], + ), NotebookSpecV2( title='Elliptic Curve Addition', module=qualtran.bloqs.factoring.ecc.ec_add, diff --git a/qualtran/bloqs/factoring/ecc/_ecc_shims.py b/qualtran/bloqs/factoring/_factoring_shims.py similarity index 100% rename from qualtran/bloqs/factoring/ecc/_ecc_shims.py rename to qualtran/bloqs/factoring/_factoring_shims.py diff --git a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py index f4ddb9d19..ffe03f2f9 100644 --- a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py @@ -33,7 +33,7 @@ from qualtran.bloqs.basic_gates import PlusState from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator -from ._ecc_shims import MeasureQFT +from .._factoring_shims import MeasureQFT from .ec_add_r import ECAddR from .ec_point import ECPoint diff --git a/qualtran/bloqs/factoring/mod_add_test.py b/qualtran/bloqs/factoring/mod_add_test.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/qualtran/bloqs/factoring/mod_exp.html b/qualtran/bloqs/factoring/mod_exp.html deleted file mode 100644 index 9d015ba4c..000000000 --- a/qualtran/bloqs/factoring/mod_exp.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - shor - - - - - - -
- -
- - - - \ No newline at end of file diff --git a/qualtran/bloqs/factoring/mod_exp.ipynb b/qualtran/bloqs/factoring/mod_exp.ipynb deleted file mode 100644 index 77c87aa15..000000000 --- a/qualtran/bloqs/factoring/mod_exp.ipynb +++ /dev/null @@ -1,209 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "5947b041", - "metadata": { - "cq.autogen": "title_cell" - }, - "source": [ - "# Modular Exponentiation" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce6b0d51", - "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": "2f68374c", - "metadata": { - "cq.autogen": "ModExp.bloq_doc.md" - }, - "source": [ - "## `ModExp`\n", - "Perform $b^e \\mod{m}$ for constant `base` $b$, `mod` $m$, and quantum `exponent` $e$.\n", - "\n", - "Modular exponentiation is the main computational primitive for quantum factoring algorithms.\n", - "We follow [GE2019]'s \"reference implementation\" for factoring. See `ModExp.make_for_shor`\n", - "to set the class attributes for a factoring run.\n", - "\n", - "This bloq decomposes into controlled modular exponentiation for each exponent bit.\n", - "\n", - "#### Parameters\n", - " - `base`: The integer base of the exponentiation\n", - " - `mod`: The integer modulus\n", - " - `exp_bitsize`: The size of the `exponent` thru-register\n", - " - `x_bitsize`: The size of the `x` right-register \n", - "\n", - "#### Registers\n", - " - `exponent`: The exponent\n", - " - `x [right]`: The output register containing the result of the exponentiation \n", - "\n", - "#### References\n", - " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4a1b8a2a", - "metadata": { - "cq.autogen": "ModExp.bloq_doc.py" - }, - "outputs": [], - "source": [ - "from qualtran.bloqs.factoring import ModExp" - ] - }, - { - "cell_type": "markdown", - "id": "902ec939", - "metadata": { - "cq.autogen": "ModExp.example_instances.md" - }, - "source": [ - "### Example Instances" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "4c5aac47", - "metadata": { - "cq.autogen": "ModExp.modexp_small" - }, - "outputs": [], - "source": [ - "modexp_small = ModExp(base=4, mod=15, exp_bitsize=3, x_bitsize=2048)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "41d94c81", - "metadata": { - "cq.autogen": "ModExp.modexp" - }, - "outputs": [], - "source": [ - "modexp = ModExp.make_for_shor(big_n=13 * 17, g=9)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5c0c6c06", - "metadata": { - "cq.autogen": "ModExp.modexp_symb" - }, - "outputs": [], - "source": [ - "g, N, n_e, n_x = sympy.symbols('g N n_e, n_x')\n", - "modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x)" - ] - }, - { - "cell_type": "markdown", - "id": "a55a51df", - "metadata": { - "cq.autogen": "ModExp.graphical_signature.md" - }, - "source": [ - "#### Graphical Signature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce953a6d", - "metadata": { - "cq.autogen": "ModExp.graphical_signature.py" - }, - "outputs": [], - "source": [ - "from qualtran.drawing import show_bloqs\n", - "show_bloqs([modexp_symb, modexp_small, modexp],\n", - " ['`modexp_symb`', '`modexp_small`', '`modexp`'])" - ] - }, - { - "cell_type": "markdown", - "id": "83ba4b54", - "metadata": {}, - "source": [ - "### Decomposition" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a0e53334", - "metadata": {}, - "outputs": [], - "source": [ - "show_bloq(modexp_small.decompose_bloq())" - ] - }, - { - "cell_type": "markdown", - "id": "8662fa01", - "metadata": { - "cq.autogen": "ModExp.call_graph.md" - }, - "source": [ - "### Call Graph" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9a7d9f5a", - "metadata": { - "cq.autogen": "ModExp.call_graph.py" - }, - "outputs": [], - "source": [ - "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "modexp_symb_g, modexp_symb_sigma = modexp_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(modexp_symb_g)\n", - "show_counts_sigma(modexp_symb_sigma)" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.10.9" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py b/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py index cc280a8ab..0d8378c24 100644 --- a/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py +++ b/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py @@ -82,7 +82,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: x = bb.add(RSAPhaseEstimate(n=self.n, mod=self.mod), x=x) - bb.add(Free(QUInt(self.n)), reg=x) + bb.add(Free(QUInt(self.n), dirty=True), reg=x) return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': @@ -99,4 +99,4 @@ def _rsa() -> FindRSAPrivateKey: return rsa -_ECC_BLOQ_DOC = BloqDocSpec(bloq_cls=FindRSAPrivateKey, examples=[_rsa]) +_RSA_BLOQ_DOC = BloqDocSpec(bloq_cls=FindRSAPrivateKey, examples=[_rsa]) diff --git a/qualtran/bloqs/factoring/mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py similarity index 100% rename from qualtran/bloqs/factoring/mod_exp.py rename to qualtran/bloqs/factoring/rsa/rsa_mod_exp.py diff --git a/qualtran/bloqs/factoring/mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py similarity index 100% rename from qualtran/bloqs/factoring/mod_exp_test.py rename to qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py index 637951f4f..70d68694b 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -32,10 +32,9 @@ ) from qualtran.bloqs.basic_gates import PlusState from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.symbolics.types import SymbolicInt -from ._ecc_shims import MeasureQFT -from .ec_add_r import ECAddR -from .ec_point import ECPoint +from .._factoring_shims import MeasureQFT @frozen @@ -50,44 +49,39 @@ class RSAPhaseEstimate(Bloq): point: The elliptic curve point to phase estimate against. """ - n: int - point: ECPoint + n: 'SymbolicInt' + mod: 'SymbolicInt' @cached_property def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]) + return Signature([Register('x', QUInt(self.n))]) def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if isinstance(self.n, sympy.Expr): raise DecomposeTypeError("Cannot decompose symbolic `n`.") - ctrl = [bb.add(PlusState()) for _ in range(self.n)] + ctrl = [bb.add(PlusState()) for _ in range(2 * self.n)] for i in range(self.n): ctrl[i], x, y = bb.add(ECAddR(n=self.n, R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) - bb.add(MeasureQFT(n=self.n), x=ctrl) - return {'x': x, 'y': y} + bb.add(MeasureQFT(n=2 * self.n), x=ctrl) + return {'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': return {ECAddR(n=self.n, R=self.point): self.n, MeasureQFT(n=self.n): 1} - def __str__(self) -> str: - return f'PE${self.point}$' - @bloq_example -def _ec_pe() -> ECPhaseEstimateR: - n, p = sympy.symbols('n p ') - Rx, Ry = sympy.symbols('R_x R_y') - ec_pe = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) +def _rsa_pe() -> RSAPhaseEstimate: + n, p = sympy.symbols('n p') + ec_pe = RSAPhaseEstimate(n=n, p=p) return ec_pe @bloq_example -def _ec_pe_small() -> ECPhaseEstimateR: - n = 3 - Rx, Ry, p = sympy.symbols('R_x R_y p') - ec_pe_small = ECPhaseEstimateR(n=n, point=ECPoint(Rx, Ry, mod=p)) +def _rsa_pe_small() -> RSAPhaseEstimate: + n, p = 3, 7 + ec_pe_small = RSAPhaseEstimate(n=n, p=p) return ec_pe_small -_EC_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=ECPhaseEstimateR, examples=[_ec_pe]) +_RSA_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=RSAPhaseEstimate, examples=[_rsa_pe]) From dcf12848bf0e4adc6a65b95c5ae9d2f661cf003a Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 29 Sep 2024 14:53:09 -0700 Subject: [PATCH 03/21] Rework rsa mod exp bloqs to work in a rsa phase estimation circuit --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 9 +- docs/bloqs/index.rst | 2 +- qualtran/bloqs/factoring/__init__.py | 4 +- qualtran/bloqs/factoring/rsa/__init__.py | 34 ++ .../{ => rsa}/factoring-via-modexp.ipynb | 9 +- .../factoring/rsa/find_rsa_private_key.py | 102 ------ .../rsa/find_rsa_private_key_test.py | 24 -- qualtran/bloqs/factoring/rsa/rsa.ipynb | 315 ++++++++++++++++++ qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 43 ++- .../bloqs/factoring/rsa/rsa_mod_exp_test.py | 35 +- .../bloqs/factoring/rsa/rsa_phase_estimate.py | 63 ++-- .../factoring/rsa/rsa_phase_estimate_test.py | 5 + qualtran/serialization/resolver_dict.py | 8 +- 13 files changed, 437 insertions(+), 216 deletions(-) create mode 100644 qualtran/bloqs/factoring/rsa/__init__.py rename qualtran/bloqs/factoring/{ => rsa}/factoring-via-modexp.ipynb (96%) delete mode 100644 qualtran/bloqs/factoring/rsa/find_rsa_private_key.py delete mode 100644 qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py create mode 100644 qualtran/bloqs/factoring/rsa/rsa.ipynb diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 407a0e8c7..f67a37e0d 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -104,7 +104,6 @@ import qualtran.bloqs.data_loading.qrom_base import qualtran.bloqs.data_loading.select_swap_qrom import qualtran.bloqs.factoring.ecc -import qualtran.bloqs.factoring.mod_exp import qualtran.bloqs.factoring.rsa import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import qualtran.bloqs.mcmt.and_bloq @@ -529,18 +528,12 @@ qualtran.bloqs.mod_arithmetic.mod_multiplication._DIRTY_OUT_OF_PLACE_MONTGOMERY_MOD_MUL_DOC, ], ), - NotebookSpecV2( - title='Modular Exponentiation', - module=qualtran.bloqs.factoring.mod_exp, - bloq_specs=[qualtran.bloqs.factoring.mod_exp._MODEXP_DOC], - directory=f'{SOURCE_DIR}/bloqs/factoring', - ), NotebookSpecV2( title='Factoring RSA', module=qualtran.bloqs.factoring.rsa, bloq_specs=[ - qualtran.bloqs.factoring.rsa.find_rsa_private_key._RSA_BLOQ_DOC, qualtran.bloqs.factoring.rsa.rsa_phase_estimate._RSA_PE_BLOQ_DOC, + qualtran.bloqs.factoring.rsa.rsa_mod_exp._RSA_MODEXP_DOC, ], ), NotebookSpecV2( diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 34df92ddb..f06e141a0 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -83,7 +83,7 @@ Bloqs Library mod_arithmetic/mod_addition.ipynb mod_arithmetic/mod_subtraction.ipynb mod_arithmetic/mod_multiplication.ipynb - factoring/mod_exp.ipynb + factoring/rsa/rsa.ipynb factoring/ecc/ec_add.ipynb factoring/ecc/ecc.ipynb diff --git a/qualtran/bloqs/factoring/__init__.py b/qualtran/bloqs/factoring/__init__.py index 59a92dad8..15780de77 100644 --- a/qualtran/bloqs/factoring/__init__.py +++ b/qualtran/bloqs/factoring/__init__.py @@ -1,4 +1,4 @@ -# Copyright 2023 Google LLC +# 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. @@ -11,5 +11,3 @@ # 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 .mod_exp import ModExp diff --git a/qualtran/bloqs/factoring/rsa/__init__.py b/qualtran/bloqs/factoring/rsa/__init__.py new file mode 100644 index 000000000..f27a3a726 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/__init__.py @@ -0,0 +1,34 @@ +# 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. +# +# isort:skip_file + +r"""Bloqs for breaking RSA cryptography systems via integer factorization. + +RSA cryptography is a form of public key cryptography based on the difficulty of +factoring the product of two large prime numbers. + +Using RSA, the cryptographic scheme chooses two large prime numbers p, q, the product +N, λ(n) = lcm(p - 1, q - 1), an integer e such that 1 < e < λ(n), +and finally d as d ≡ e^-1 (mod λ(n)). The public key consists of the modulus N and the +public (or encryption) exponent e. The private key consists of the private (or decryption) +exponent d, which must be kept secret. p, q, and λ(n) must also be kept secret because +they can be used to calculate d. + +Using Shor's algorithm for factoring, we can find p and q (the factors of N) in polynomial time +with a quantum algorithm. +""" + +from .rsa_phase_estimate import RSAPhaseEstimate +from .rsa_mod_exp import ModExp diff --git a/qualtran/bloqs/factoring/factoring-via-modexp.ipynb b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb similarity index 96% rename from qualtran/bloqs/factoring/factoring-via-modexp.ipynb rename to qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb index dfea45b9e..a12933049 100644 --- a/qualtran/bloqs/factoring/factoring-via-modexp.ipynb +++ b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb @@ -180,7 +180,7 @@ "metadata": {}, "outputs": [], "source": [ - "from qualtran.bloqs.factoring.mod_exp import ModExp\n", + "from qualtran.bloqs.factoring.rsa.rsa_mod_exp import ModExp\n", "from qualtran.drawing import show_bloq\n", "\n", "mod_exp = ModExp(base=g, mod=N, exp_bitsize=32, x_bitsize=32)\n", @@ -205,10 +205,11 @@ "metadata": {}, "outputs": [], "source": [ + "from qualtran import QUInt\n", "for e in range(20):\n", " ref = (g ** e) % N\n", - " _, bloq_eval = mod_exp.call_classically(exponent=e)\n", - " _, decomp_eval = mod_exp_decomp.call_classically(exponent=e)\n", + " _, bloq_eval = mod_exp.call_classically(exponent=QUInt(32).to_bits(e), x=1)\n", + " _, decomp_eval = mod_exp_decomp.call_classically(exponent=QUInt(32).to_bits(e), x=1)\n", " \n", " star = ' *' if ref == 1 else ''\n", " print(f'{e:5d} {ref:5d} {bloq_eval:5d} {decomp_eval:5d} {star}')" @@ -231,7 +232,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.10.13" } }, "nbformat": 4, diff --git a/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py b/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py deleted file mode 100644 index 0d8378c24..000000000 --- a/qualtran/bloqs/factoring/rsa/find_rsa_private_key.py +++ /dev/null @@ -1,102 +0,0 @@ -# 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 - -import sympy -from attrs import frozen - -from qualtran import Bloq, bloq_example, BloqBuilder, BloqDocSpec, QUInt, Signature, SoquetT -from qualtran.bloqs.basic_gates import IntState -from qualtran.bloqs.bookkeeping import Free -from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator -from qualtran.symbolics import SymbolicInt - -from .rsa_phase_estimate import RSAPhaseEstimate - - -@frozen -class FindRSAPrivateKey(Bloq): - r"""Perform one phase estimation to break rsa cryptography. - - This follows the strategy in Litinski 2023. We perform two phase estimations corresponding - to `ECCAddR(R=P)` and `ECCAddR(R=Q)` for base point $P$ and public key $Q$. - - The first phase estimation projects us into a random eigenstate of the ECCAddR(R=P) operator - which we index by the integer $c$. Per eq. 5 in the reference, these eigenstates take the form - $$ - |\psi_c \rangle = \sum_j^{r-1} \omega^{cj}\ | [j]P \rangle \\ - \omega = e^{2\pi i / r} \\ - [r] P = P - $$ - - This state is a simultaneous eigenstate of the second operator, `ECCAddR(R=Q)`. By - the definition of the operator, acting it upon $|\psi_c\rangle$ gives: - $$ - |\psi_c \rangle \rightarrow \sum_j w^{cj} | [j]P + Q \rangle\rangle - $$ - - The private key $k$ that we wish to recover relates the public key to the base point - $$ - Q = [k] P - $$ - so our simultaneous eigenstate can be equivalently written as - $$ - \sum_j^{r-1} \omega^{cj} | [j+k] P \rangle \\ - = \omega^{-ck} |\psi_c \rangle - $$ - - Therefore, the measured result of the second phase estimation is $ck$. Since we have - already measured the random index $c$, we can divide it out to recover the private key $k$. - - Args: - n: The bitsize of the integer we want to factor. - mod: The integer modulus. - - References: - [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). - Figure 1. - """ - - n: 'SymbolicInt' - mod: 'SymbolicInt' - - @cached_property - def signature(self) -> 'Signature': - return Signature([]) - - def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: - x = bb.add(IntState(bitsize=self.n, val=1)) - - x = bb.add(RSAPhaseEstimate(n=self.n, mod=self.mod), x=x) - - bb.add(Free(QUInt(self.n), dirty=True), reg=x) - return {} - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {RSAPhaseEstimate(n=self.n): 1} - - def cost_attrs(self): - return [('n', self.n)] - - -@bloq_example -def _rsa() -> FindRSAPrivateKey: - n, p = sympy.symbols('n p') - rsa = FindRSAPrivateKey(n=n, mod=p) - return rsa - - -_RSA_BLOQ_DOC = BloqDocSpec(bloq_cls=FindRSAPrivateKey, examples=[_rsa]) diff --git a/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py b/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py deleted file mode 100644 index 8b27f3e5e..000000000 --- a/qualtran/bloqs/factoring/rsa/find_rsa_private_key_test.py +++ /dev/null @@ -1,24 +0,0 @@ -# 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 qualtran.testing as qlt_testing -from qualtran.bloqs.factoring.rsa.find_rsa_private_key import _rsa - - -def test_rsa(bloq_autotester): - bloq_autotester(_rsa) - - -def test_notebook(): - qlt_testing.execute_notebook('rsa') diff --git a/qualtran/bloqs/factoring/rsa/rsa.ipynb b/qualtran/bloqs/factoring/rsa/rsa.ipynb new file mode 100644 index 000000000..11b2d2ec4 --- /dev/null +++ b/qualtran/bloqs/factoring/rsa/rsa.ipynb @@ -0,0 +1,315 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "48ce60bb", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Factoring RSA\n", + "\n", + "Bloqs for breaking RSA cryptography systems via integer factorization.\n", + "\n", + "RSA cryptography is a form of public key cryptography based on the difficulty of\n", + "factoring the product of two large prime numbers.\n", + "\n", + "Using RSA, the cryptographic scheme chooses two large prime numbers p, q, the product\n", + "N, λ(n) = lcm(p - 1, q - 1), an integer e such that 1 < e < λ(n),\n", + "and finally d as d ≡ e^-1 (mod λ(n)). The public key consists of the modulus N and the\n", + "public (or encryption) exponent e. The private key consists of the private (or decryption)\n", + "exponent d, which must be kept secret. p, q, and λ(n) must also be kept secret because\n", + "they can be used to calculate d.\n", + "\n", + "Using Shor's algorithm for factoring, we can find p and q (the factors of N) in polynomial time\n", + "with a quantum algorithm." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d12766dd", + "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": "881834c3", + "metadata": { + "cq.autogen": "ModExp.bloq_doc.md" + }, + "source": [ + "## `ModExp`\n", + "Perform $b^e \\mod{m}$ for constant `base` $b$, `mod` $m$, and quantum `exponent` $e$.\n", + "\n", + "Modular exponentiation is the main computational primitive for quantum factoring algorithms.\n", + "We follow [GE2019]'s \"reference implementation\" for factoring. See `ModExp.make_for_shor`\n", + "to set the class attributes for a factoring run.\n", + "\n", + "This bloq decomposes into controlled modular exponentiation for each exponent bit.\n", + "\n", + "#### Parameters\n", + " - `base`: The integer base of the exponentiation\n", + " - `mod`: The integer modulus\n", + " - `exp_bitsize`: The size of the `exponent` thru-register\n", + " - `x_bitsize`: The size of the `x` right-register \n", + "\n", + "#### Registers\n", + " - `exponent`: The exponent\n", + " - `x`: The output register containing the result of the exponentiation \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61062a3d", + "metadata": { + "cq.autogen": "ModExp.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.factoring.rsa import ModExp" + ] + }, + { + "cell_type": "markdown", + "id": "6963ad94", + "metadata": { + "cq.autogen": "ModExp.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56d35af9", + "metadata": { + "cq.autogen": "ModExp.modexp_symb" + }, + "outputs": [], + "source": [ + "g, N, n_e, n_x = sympy.symbols('g N n_e, n_x')\n", + "modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7d3d90b4", + "metadata": { + "cq.autogen": "ModExp.modexp_small" + }, + "outputs": [], + "source": [ + "modexp_small = ModExp(base=4, mod=15, exp_bitsize=3, x_bitsize=2048)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e3a004d3", + "metadata": { + "cq.autogen": "ModExp.modexp" + }, + "outputs": [], + "source": [ + "modexp = ModExp.make_for_shor(big_n=13 * 17, g=9)" + ] + }, + { + "cell_type": "markdown", + "id": "39422b45", + "metadata": { + "cq.autogen": "ModExp.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "83a891e2", + "metadata": { + "cq.autogen": "ModExp.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([modexp_small, modexp],\n", + " ['`modexp_small`', '`modexp`'])" + ] + }, + { + "cell_type": "markdown", + "id": "9c271392", + "metadata": { + "cq.autogen": "ModExp.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6d55278", + "metadata": { + "cq.autogen": "ModExp.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "modexp_small_g, modexp_small_sigma = modexp_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(modexp_small_g)\n", + "show_counts_sigma(modexp_small_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "2603abbd", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.bloq_doc.md" + }, + "source": [ + "## `RSAPhaseEstimate`\n", + "Perform a single phase estimation of ModExp for the given base.\n", + "\n", + "Computes the phase estimation of a single run of Modular Exponentiation with\n", + "a random, valid base.\n", + "\n", + "#### Parameters\n", + " - `n`: The bitsize of the modulus N.\n", + " - `mod`: The modulus N; a part of the public key for RSA. \n", + "\n", + "#### References\n", + " - [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). Stephane Beauregard. 2003.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5b838c20", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.factoring.rsa import RSAPhaseEstimate" + ] + }, + { + "cell_type": "markdown", + "id": "20426b03", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f696c5fd", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe" + }, + "outputs": [], + "source": [ + "n, p = sympy.symbols('n p')\n", + "rsa_pe = RSAPhaseEstimate(n=n, mod=p)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b16e84a5", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe_small" + }, + "outputs": [], + "source": [ + "n, p = 6, 5*7\n", + "rsa_pe_small = RSAPhaseEstimate(n=n, mod=p)" + ] + }, + { + "cell_type": "markdown", + "id": "0c0078d5", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b493a30", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([rsa_pe_small, rsa_pe],\n", + " ['`rsa_pe_small`', '`rsa_pe`'])" + ] + }, + { + "cell_type": "markdown", + "id": "441028fa", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7f30cb55", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "rsa_pe_small_g, rsa_pe_small_sigma = rsa_pe_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(rsa_pe_small_g)\n", + "show_counts_sigma(rsa_pe_small_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/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 129711797..6f797b35a 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -26,19 +26,20 @@ BloqBuilder, BloqDocSpec, DecomposeTypeError, + QBit, QUInt, Register, - Side, Signature, Soquet, SoquetT, ) -from qualtran.bloqs.basic_gates import IntState from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join +from qualtran.simulation.classical_sim import ClassicalValT from qualtran.symbolics import is_symbolic +from qualtran.symbolics.types import HasLength @frozen @@ -59,7 +60,7 @@ class ModExp(Bloq): Registers: exponent: The exponent - x [right]: The output register containing the result of the exponentiation + x: The output register containing the result of the exponentiation References: [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). @@ -79,8 +80,8 @@ def __attrs_post_init__(self): def signature(self) -> 'Signature': return Signature( [ - Register('exponent', QUInt(self.exp_bitsize)), - Register('x', QUInt(self.x_bitsize), side=Side.RIGHT), + Register('exponent', QBit(), shape=(self.exp_bitsize,)), + Register('x', QUInt(self.x_bitsize)), ] ) @@ -98,33 +99,38 @@ def make_for_shor(cls, big_n: int, g: Optional[int] = None): else: little_n = int(math.ceil(math.log2(big_n))) if g is None: - g = random.randint(2, big_n) + while True: + g = random.randint(2, big_n) + if math.gcd(g, big_n) == 1: + break return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n) def _CtrlModMul(self, k: Union[int, sympy.Expr]): """Helper method to return a `CModMulK` with attributes forwarded.""" return CModMulK(QUInt(self.x_bitsize), k=k, mod=self.mod) - def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[str, 'SoquetT']: + def build_composite_bloq( + self, bb: 'BloqBuilder', exponent: 'Soquet', x: 'Soquet' + ) -> Dict[str, 'SoquetT']: if isinstance(self.exp_bitsize, sympy.Expr): raise DecomposeTypeError("`exp_bitsize` must be a concrete value.") - x = bb.add(IntState(val=1, bitsize=self.x_bitsize)) - exponent = bb.split(exponent) - # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method base = self.base % self.mod for j in range(self.exp_bitsize - 1, 0 - 1, -1): exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) base = (base * base) % self.mod - return {'exponent': bb.join(exponent, dtype=QUInt(self.exp_bitsize)), 'x': x} + return {'exponent': exponent, 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') - return {IntState(val=1, bitsize=self.x_bitsize): 1, self._CtrlModMul(k=k): self.exp_bitsize} + return {self._CtrlModMul(k=k): self.exp_bitsize} - def on_classical_vals(self, exponent: int): - return {'exponent': exponent, 'x': (self.base**exponent) % self.mod} + def on_classical_vals(self, exponent, x) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: + return { + 'exponent': exponent, + 'x': (self.base ** QUInt(self.exp_bitsize).from_bits(exponent)) % self.mod, + } def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -156,11 +162,4 @@ def _modexp() -> ModExp: return modexp -@bloq_example -def _modexp_symb() -> ModExp: - g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') - modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x) - return modexp_symb - - -_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_symb, _modexp_small, _modexp)) +_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp)) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index d0b48c4af..772fb4f77 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -19,12 +19,13 @@ import pytest import sympy -from qualtran import Bloq +from qualtran import Bloq, QUInt from qualtran.bloqs.bookkeeping import Join, Split -from qualtran.bloqs.factoring.mod_exp import _modexp, _modexp_symb, ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, ModExp from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text from qualtran.resource_counting import SympySymbolAllocator +from qualtran.symbolics.types import HasLength from qualtran.testing import execute_notebook @@ -48,22 +49,11 @@ def test_mod_exp_consistent_classical(): base = rs.randint(1, mod) bloq = ModExp(base=base, exp_bitsize=ne, x_bitsize=n, mod=mod) - ret1 = bloq.call_classically(exponent=exponent) - ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) - assert ret1 == ret2 - - -def test_modexp_symb_manual(): - g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') - modexp = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x) - assert cast(Text, modexp.wire_symbol(reg=None)).text == 'g^e % N' - counts = modexp.bloq_counts() - counts_by_bloq = {str(bloq): n for bloq, n in counts.items()} - assert counts_by_bloq['|1>'] == 1 - assert counts_by_bloq['CModMulK'] == n_e - - b, x = modexp.call_classically(exponent=sympy.Symbol('b')) - assert str(x) == 'Mod(g**b, N)' + ret1 = bloq.call_classically(exponent=QUInt(ne).to_bits(exponent), x=1) + ret2 = bloq.decompose_bloq().call_classically(exponent=QUInt(ne).to_bits(exponent), x=1) + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) def test_mod_exp_consistent_counts(): @@ -97,15 +87,6 @@ def test_modexp(bloq_autotester): bloq_autotester(_modexp) -def test_modexp_symb(bloq_autotester): - bloq_autotester(_modexp_symb) - - @pytest.mark.notebook def test_intro_notebook(): execute_notebook('factoring-via-modexp') - - -@pytest.mark.notebook -def test_notebook(): - execute_notebook('mod_exp') diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py index 70d68694b..64f473747 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -12,8 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +import math from functools import cached_property -from typing import Dict +from typing import Dict, Optional import sympy from attrs import frozen @@ -30,58 +31,74 @@ Soquet, SoquetT, ) -from qualtran.bloqs.basic_gates import PlusState +from qualtran.bloqs.basic_gates import IntState, PlusState +from qualtran.bloqs.bookkeeping import Free +from qualtran.bloqs.factoring._factoring_shims import MeasureQFT from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator -from qualtran.symbolics.types import SymbolicInt +from qualtran.symbolics.types import is_symbolic, SymbolicInt -from .._factoring_shims import MeasureQFT +from .rsa_mod_exp import ModExp @frozen class RSAPhaseEstimate(Bloq): - """Perform a single phase estimation of ECAddR for a given point. + """Perform a single phase estimation of ModExp for the given base. - This is used as a subroutine in `FindECCPrivateKey`. First, we phase-estimate the - addition of the base point $P$, then of the public key $Q$. + Computes the phase estimation of a single run of Modular Exponentiation with + an optional, pre-set base or a random, valid base. Args: - n: The bitsize of the elliptic curve points' x and y registers. - point: The elliptic curve point to phase estimate against. + n: The bitsize of the modulus N. + mod: The modulus N; a part of the public key for RSA. + base: An optional base for modular exponentiation. + + References: + [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). + Stephane Beauregard. 2003. """ n: 'SymbolicInt' mod: 'SymbolicInt' + base: Optional['SymbolicInt'] = None @cached_property def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n))]) + return Signature([]) + + def __attrs_post_init__(self): + if not is_symbolic(self.n, self.mod): + assert self.n == int(math.ceil(math.log2(self.mod))) - def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: if isinstance(self.n, sympy.Expr): raise DecomposeTypeError("Cannot decompose symbolic `n`.") - ctrl = [bb.add(PlusState()) for _ in range(2 * self.n)] - for i in range(self.n): - ctrl[i], x, y = bb.add(ECAddR(n=self.n, R=2**i * self.point), ctrl=ctrl[i], x=x, y=y) + exponent = [bb.add(PlusState()) for _ in range(2 * self.n)] + x = bb.add(IntState(val=1, bitsize=self.n)) + + exponent, x = bb.add( + ModExp.make_for_shor(big_n=self.mod, g=self.base), exponent=exponent, x=x + ) - bb.add(MeasureQFT(n=2 * self.n), x=ctrl) - return {'x': x} + bb.add(MeasureQFT(n=2 * self.n), x=exponent) + bb.add(Free(QUInt(self.n), dirty=True), reg=x) + return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {ECAddR(n=self.n, R=self.point): self.n, MeasureQFT(n=self.n): 1} + return {MeasureQFT(n=self.n): 1} @bloq_example def _rsa_pe() -> RSAPhaseEstimate: n, p = sympy.symbols('n p') - ec_pe = RSAPhaseEstimate(n=n, p=p) - return ec_pe + rsa_pe = RSAPhaseEstimate(n=n, mod=p) + return rsa_pe @bloq_example def _rsa_pe_small() -> RSAPhaseEstimate: - n, p = 3, 7 - ec_pe_small = RSAPhaseEstimate(n=n, p=p) - return ec_pe_small + n, p = 6, 5 * 7 + rsa_pe_small = RSAPhaseEstimate(n=n, mod=p) + return rsa_pe_small -_RSA_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=RSAPhaseEstimate, examples=[_rsa_pe]) +_RSA_PE_BLOQ_DOC = BloqDocSpec(bloq_cls=RSAPhaseEstimate, examples=[_rsa_pe_small, _rsa_pe]) diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py index 3b520c826..ccf846836 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +import qualtran.testing as qlt_testing from qualtran.bloqs.factoring.rsa.rsa_phase_estimate import _rsa_pe, _rsa_pe_small @@ -21,3 +22,7 @@ def test_rsa_pe(bloq_autotester): def test_rsa_pe_small(bloq_autotester): bloq_autotester(_rsa_pe_small) + + +def test_notebook(): + qlt_testing.execute_notebook('rsa') diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index dad2907ea..362adeb65 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -97,7 +97,9 @@ import qualtran.bloqs.data_loading.qroam_clean import qualtran.bloqs.data_loading.qrom import qualtran.bloqs.data_loading.select_swap_qrom -import qualtran.bloqs.factoring.mod_exp + +qualtran.bloqs.factoring._factoring_shims +import qualtran.bloqs.factoring.rsa import qualtran.bloqs.for_testing.atom import qualtran.bloqs.for_testing.casting import qualtran.bloqs.for_testing.interior_alloc @@ -340,7 +342,9 @@ "qualtran.bloqs.mod_arithmetic.mod_multiplication.CModMulK": qualtran.bloqs.mod_arithmetic.mod_multiplication.CModMulK, "qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.DirtyOutOfPlaceMontgomeryModMul, "qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul": qualtran.bloqs.mod_arithmetic.mod_multiplication.SingleWindowModMul, - "qualtran.bloqs.factoring.mod_exp.ModExp": qualtran.bloqs.factoring.mod_exp.ModExp, + "qualtran.bloqs.factoring._factoring_shims.MeasureQFT": qualtran.bloqs.factoring._factoring_shims.MeasureQFT, + "qualtran.bloqs.factoring.rsa.rsa_phase_estimate.RSAPhaseEstimate": qualtran.bloqs.factoring.rsa.rsa_phase_estimate.RSAPhaseEstimate, + "qualtran.bloqs.factoring.rsa.rsa_mod_exp.ModExp": qualtran.bloqs.factoring.rsa.rsa_mod_exp.ModExp, "qualtran.bloqs.for_testing.atom.TestAtom": qualtran.bloqs.for_testing.atom.TestAtom, "qualtran.bloqs.for_testing.atom.TestGWRAtom": qualtran.bloqs.for_testing.atom.TestGWRAtom, "qualtran.bloqs.for_testing.atom.TestTwoBitOp": qualtran.bloqs.for_testing.atom.TestTwoBitOp, From dd9d2512a107aa510cd0c8caa7429dac71f524a4 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 29 Sep 2024 15:24:48 -0700 Subject: [PATCH 04/21] Fix mypy issues --- qualtran/bloqs/factoring/_factoring_shims.py | 5 +++-- qualtran/bloqs/factoring/rsa/rsa.ipynb | 7 ++++--- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 15 ++++++++++----- qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py | 6 ++++-- qualtran/serialization/bloq_test.py | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/qualtran/bloqs/factoring/_factoring_shims.py b/qualtran/bloqs/factoring/_factoring_shims.py index 4b602e73f..2fdeb18ab 100644 --- a/qualtran/bloqs/factoring/_factoring_shims.py +++ b/qualtran/bloqs/factoring/_factoring_shims.py @@ -13,8 +13,9 @@ # limitations under the License. from functools import cached_property -from typing import Optional, Tuple +from typing import Optional, Tuple, Union +import sympy from attrs import frozen from qualtran import Bloq, CompositeBloq, DecomposeTypeError, QBit, Register, Side, Signature @@ -23,7 +24,7 @@ @frozen class MeasureQFT(Bloq): - n: int + n: Union[int, sympy.Expr] @cached_property def signature(self) -> 'Signature': diff --git a/qualtran/bloqs/factoring/rsa/rsa.ipynb b/qualtran/bloqs/factoring/rsa/rsa.ipynb index 11b2d2ec4..4daf77005 100644 --- a/qualtran/bloqs/factoring/rsa/rsa.ipynb +++ b/qualtran/bloqs/factoring/rsa/rsa.ipynb @@ -192,11 +192,12 @@ "Perform a single phase estimation of ModExp for the given base.\n", "\n", "Computes the phase estimation of a single run of Modular Exponentiation with\n", - "a random, valid base.\n", + "an optional, pre-set base or a random, valid base.\n", "\n", "#### Parameters\n", " - `n`: The bitsize of the modulus N.\n", - " - `mod`: The modulus N; a part of the public key for RSA. \n", + " - `mod`: The modulus N; a part of the public key for RSA.\n", + " - `base`: An optional base for modular exponentiation. \n", "\n", "#### References\n", " - [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). Stephane Beauregard. 2003.\n" @@ -246,7 +247,7 @@ }, "outputs": [], "source": [ - "n, p = 6, 5*7\n", + "n, p = 6, 5 * 7\n", "rsa_pe_small = RSAPhaseEstimate(n=n, mod=p)" ] }, diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 6f797b35a..2c419effd 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -86,7 +86,9 @@ def signature(self) -> 'Signature': ) @classmethod - def make_for_shor(cls, big_n: int, g: Optional[int] = None): + def make_for_shor( + cls, big_n: Union[int, sympy.Expr], g: Optional[Union[int, sympy.Expr]] = None + ): """Factory method that sets up the modular exponentiation for a factoring run. Args: @@ -99,10 +101,13 @@ def make_for_shor(cls, big_n: int, g: Optional[int] = None): else: little_n = int(math.ceil(math.log2(big_n))) if g is None: - while True: - g = random.randint(2, big_n) - if math.gcd(g, big_n) == 1: - break + if isinstance(big_n, sympy.Expr): + g = sympy.symbols('g') + else: + while True: + g = random.randint(2, int(big_n)) + if math.gcd(g, int(big_n)) == 1: + break return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n) def _CtrlModMul(self, k: Union[int, sympy.Expr]): diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index 772fb4f77..96cb56bc1 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -49,8 +49,10 @@ def test_mod_exp_consistent_classical(): base = rs.randint(1, mod) bloq = ModExp(base=base, exp_bitsize=ne, x_bitsize=n, mod=mod) - ret1 = bloq.call_classically(exponent=QUInt(ne).to_bits(exponent), x=1) - ret2 = bloq.decompose_bloq().call_classically(exponent=QUInt(ne).to_bits(exponent), x=1) + ret1 = bloq.call_classically(exponent=QUInt(ne).to_bits_array(exponent)[0], x=1) + ret2 = bloq.decompose_bloq().call_classically( + exponent=QUInt(ne).to_bits_array(exponent)[0], x=1 + ) assert len(ret1) == len(ret2) for i in range(len(ret1)): np.testing.assert_array_equal(ret1[i], ret2[i]) diff --git a/qualtran/serialization/bloq_test.py b/qualtran/serialization/bloq_test.py index 5964908ee..aecb74c82 100644 --- a/qualtran/serialization/bloq_test.py +++ b/qualtran/serialization/bloq_test.py @@ -23,7 +23,7 @@ from qualtran import Bloq, Signature from qualtran._infra.composite_bloq_test import TestTwoCNOT -from qualtran.bloqs.factoring.mod_exp import ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import ModExp from qualtran.cirq_interop import CirqGateAsBloq from qualtran.cirq_interop._cirq_to_bloq_test import TestCNOT as TestCNOTCirq from qualtran.cirq_interop.t_complexity_protocol import TComplexity From cb82d398a23eb5be7244c01227a8a6c4d70b3f7a Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Tue, 1 Oct 2024 12:35:56 -0700 Subject: [PATCH 05/21] Add some classical simulation --- qualtran/bloqs/mod_arithmetic/mod_addition.py | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index efe224946..8610869f9 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -278,6 +278,21 @@ class CModAddK(Bloq): @cached_property def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'SoquetT', + ) -> Dict[str, 'SoquetT']: + return {'ctrl': ctrl, 'x': x} + + def on_classical_vals( + self, ctrl: 'ClassicalValT', x: 'ClassicalValT', + ) -> Dict[str, 'ClassicalValT']: + if ctrl == 0: + return {'ctrl': 0, 'x': x} + + assert ctrl == 1, 'Bad ctrl value.' + x = (x + self.k) % self.mod + return {'ctrl': ctrl, 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') @@ -315,6 +330,16 @@ def signature(self) -> 'Signature': Register('y', QUInt(self.bitsize)), ] ) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'SoquetT', y: 'SoquetT' + ) -> Dict[str, 'SoquetT']: + x_split = bb.split(x) + for i in range(self.bitsize): + x_split[i], y = bb.add(CModAddK(k=(self.k * 2**i), bitsize=self.bitsize, mod=self.mod), ctrl=x_split[i], x=y) + x = bb.join(x_split, dtype=QUInt(self.bitsize)) + + return {'ctrl': ctrl, 'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') From 14927c0c1534710db43d1bf8a66df3ad5d255b21 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Tue, 1 Oct 2024 19:18:51 -0700 Subject: [PATCH 06/21] Implement primitives for ModExp --- dev_tools/autogenerate-bloqs-notebooks-v2.py | 2 + qualtran/bloqs/basic_gates/z_basis.py | 2 +- .../bloqs/mod_arithmetic/mod_addition.ipynb | 233 ++++++++++++++++++ qualtran/bloqs/mod_arithmetic/mod_addition.py | 93 ++++++- .../bloqs/mod_arithmetic/mod_addition_test.py | 61 +++-- qualtran/serialization/resolver_dict.py | 1 + 6 files changed, 361 insertions(+), 31 deletions(-) diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index 54e26e140..c042e3e11 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -507,6 +507,8 @@ qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC, qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC, qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_K_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._CTRL_SCALE_MOD_ADD_DOC, ], ), NotebookSpecV2( diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index e7b017eeb..f30825fe6 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -482,7 +482,7 @@ class IntEffect(_IntVector): val: The register of size `bitsize` which de-allocates the value `val`. """ - def __init__(self, val: int, bitsize: int): + def __init__(self, val: Union[int, sympy.Expr], bitsize: Union[int, sympy.Expr]): self.__attrs_init__(val=val, bitsize=bitsize, state=False) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb index dab6f57c5..ab6b4c994 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb @@ -394,6 +394,239 @@ "show_call_graph(cmodadd_example_g)\n", "show_counts_sigma(cmodadd_example_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "0523961e", + "metadata": { + "cq.autogen": "CModAddK.bloq_doc.md" + }, + "source": [ + "## `CModAddK`\n", + "Perform x += k mod m for constant k, m and quantum x.\n", + "\n", + "#### Parameters\n", + " - `k`: The integer to add to `x`.\n", + " - `mod`: The modulus for the addition.\n", + " - `bitsize`: The bitsize of the `x` register. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The control bit\n", + " - `x`: The register to perform the in-place modular addition.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "341211fa", + "metadata": { + "cq.autogen": "CModAddK.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import CModAddK" + ] + }, + { + "cell_type": "markdown", + "id": "6b00aef7", + "metadata": { + "cq.autogen": "CModAddK.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b8877f17", + "metadata": { + "cq.autogen": "CModAddK.cmod_add_k" + }, + "outputs": [], + "source": [ + "n, m, k = sympy.symbols('n m k')\n", + "cmod_add_k = CModAddK(bitsize=n, mod=m, k=k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87fe8b8f", + "metadata": { + "cq.autogen": "CModAddK.cmod_add_k_small" + }, + "outputs": [], + "source": [ + "cmod_add_k_small = CModAddK(bitsize=4, mod=7, k=1)" + ] + }, + { + "cell_type": "markdown", + "id": "f44f0268", + "metadata": { + "cq.autogen": "CModAddK.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7a02d6e", + "metadata": { + "cq.autogen": "CModAddK.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([cmod_add_k, cmod_add_k_small],\n", + " ['`cmod_add_k`', '`cmod_add_k_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "48b87ff2", + "metadata": { + "cq.autogen": "CModAddK.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f6e6770b", + "metadata": { + "cq.autogen": "CModAddK.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "cmod_add_k_g, cmod_add_k_sigma = cmod_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(cmod_add_k_g)\n", + "show_counts_sigma(cmod_add_k_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "21f93349", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.bloq_doc.md" + }, + "source": [ + "## `CtrlScaleModAdd`\n", + "Perform y += x*k mod m for constant k, m and quantum x, y.\n", + "\n", + "#### Parameters\n", + " - `k`: The constant integer to scale `x` before adding into `y`.\n", + " - `mod`: The modulus of the addition\n", + " - `bitsize`: The size of the two registers. \n", + "\n", + "#### Registers\n", + " - `ctrl`: The control bit\n", + " - `x`: The 'source' quantum register containing the integer to be scaled and added to `y`.\n", + " - `y`: The 'destination' quantum register to which the addition will apply.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4ac170e5", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import CtrlScaleModAdd" + ] + }, + { + "cell_type": "markdown", + "id": "58ee7de2", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73c2c6f7", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.ctrl_scale_mod_add" + }, + "outputs": [], + "source": [ + "n, m, k = sympy.symbols('n m k')\n", + "ctrl_scale_mod_add = CtrlScaleModAdd(bitsize=n, mod=m, k=k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e822d5eb", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.ctrl_scale_mod_add_small" + }, + "outputs": [], + "source": [ + "ctrl_scale_mod_add_small = CtrlScaleModAdd(bitsize=4, mod=7, k=1)" + ] + }, + { + "cell_type": "markdown", + "id": "fe4c8957", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e4dc8923", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([ctrl_scale_mod_add, ctrl_scale_mod_add_small],\n", + " ['`ctrl_scale_mod_add`', '`ctrl_scale_mod_add_small`'])" + ] + }, + { + "cell_type": "markdown", + "id": "97d6888d", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "bd734f6b", + "metadata": { + "cq.autogen": "CtrlScaleModAdd.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "ctrl_scale_mod_add_g, ctrl_scale_mod_add_sigma = ctrl_scale_mod_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(ctrl_scale_mod_add_g)\n", + "show_counts_sigma(ctrl_scale_mod_add_sigma)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index 8610869f9..e8dd38e87 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -32,11 +32,13 @@ Soquet, SoquetT, ) +from qualtran._infra.bloq import DecomposeTypeError from qualtran.bloqs.arithmetic.addition import Add, AddK from qualtran.bloqs.arithmetic.comparison import CLinearDepthGreaterThan, LinearDepthGreaterThan from qualtran.bloqs.arithmetic.controlled_addition import CAdd -from qualtran.bloqs.basic_gates import XGate +from qualtran.bloqs.basic_gates import IntEffect, IntState, XGate from qualtran.bloqs.bookkeeping import Cast +from qualtran.bloqs.mcmt.and_bloq import And from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join @@ -278,14 +280,19 @@ class CModAddK(Bloq): @cached_property def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) - + def build_composite_bloq( - self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'SoquetT', + self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet' ) -> Dict[str, 'SoquetT']: + if isinstance(self.bitsize, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") + k = bb.add(IntState(bitsize=self.bitsize, val=self.k)) + ctrl, k, x = bb.add(CModAdd(QUInt(self.bitsize), mod=self.mod), ctrl=ctrl, x=k, y=x) + bb.add(IntEffect(bitsize=self.bitsize, val=self.k), val=k) return {'ctrl': ctrl, 'x': x} - + def on_classical_vals( - self, ctrl: 'ClassicalValT', x: 'ClassicalValT', + self, ctrl: 'ClassicalValT', x: 'ClassicalValT' ) -> Dict[str, 'ClassicalValT']: if ctrl == 0: return {'ctrl': 0, 'x': x} @@ -295,11 +302,34 @@ def on_classical_vals( return {'ctrl': ctrl, 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - k = ssa.new_symbol('k') - return {AddK(k=k, bitsize=self.bitsize).controlled(): 5} + return {CModAdd(QUInt(self.bitsize), mod=self.mod): 1} + + def wire_symbol( + self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() + ) -> 'WireSymbol': + if reg is None: + return Text(f"mod {self.mod}") + if reg.name == 'ctrl': + return Circle() + if reg.name == 'x': + return TextBox(f'x += {self.k} mod {self.mod}') + raise ValueError(f"Unknown register {reg}") + + +@bloq_example(generalizer=ignore_split_join) +def _cmod_add_k() -> CModAddK: + n, m, k = sympy.symbols('n m k') + cmod_add_k = CModAddK(bitsize=n, mod=m, k=k) + return cmod_add_k - def short_name(self) -> str: - return f'x += {self.k} % {self.mod}' + +@bloq_example +def _cmod_add_k_small() -> CModAddK: + cmod_add_k_small = CModAddK(bitsize=4, mod=7, k=1) + return cmod_add_k_small + + +_C_MOD_ADD_K_DOC = BloqDocSpec(bloq_cls=CModAddK, examples=[_cmod_add_k, _cmod_add_k_small]) @frozen @@ -330,20 +360,39 @@ def signature(self) -> 'Signature': Register('y', QUInt(self.bitsize)), ] ) - + def build_composite_bloq( - self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: 'SoquetT', y: 'SoquetT' + self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet', y: 'Soquet' ) -> Dict[str, 'SoquetT']: + if isinstance(self.bitsize, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") x_split = bb.split(x) for i in range(self.bitsize): - x_split[i], y = bb.add(CModAddK(k=(self.k * 2**i), bitsize=self.bitsize, mod=self.mod), ctrl=x_split[i], x=y) + and_ctrl = [ctrl, x_split[i]] + and_ctrl, ancilla = bb.add(And(), ctrl=and_ctrl) + ancilla, y = bb.add( + CModAddK( + k=((self.k * 2 ** (self.bitsize - 1 - i)) % self.mod), + bitsize=self.bitsize, + mod=self.mod, + ), + ctrl=ancilla, + x=y, + ) + and_ctrl = bb.add(And().adjoint(), ctrl=and_ctrl, target=ancilla) + ctrl = and_ctrl[0] + x_split[i] = and_ctrl[1] x = bb.join(x_split, dtype=QUInt(self.bitsize)) return {'ctrl': ctrl, 'x': x, 'y': y} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') - return {CModAddK(k=k, bitsize=self.bitsize, mod=self.mod): self.bitsize} + return { + CModAddK(k=k, bitsize=self.bitsize, mod=self.mod): self.bitsize, + And(): self.bitsize, + And().adjoint(): self.bitsize, + } def on_classical_vals( self, ctrl: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' @@ -369,6 +418,24 @@ def wire_symbol( raise ValueError(f"Unknown register {reg}") +@bloq_example(generalizer=ignore_split_join) +def _ctrl_scale_mod_add() -> CtrlScaleModAdd: + n, m, k = sympy.symbols('n m k') + ctrl_scale_mod_add = CtrlScaleModAdd(bitsize=n, mod=m, k=k) + return ctrl_scale_mod_add + + +@bloq_example +def _ctrl_scale_mod_add_small() -> CtrlScaleModAdd: + ctrl_scale_mod_add_small = CtrlScaleModAdd(bitsize=4, mod=7, k=1) + return ctrl_scale_mod_add_small + + +_CTRL_SCALE_MOD_ADD_DOC = BloqDocSpec( + bloq_cls=CtrlScaleModAdd, examples=[_ctrl_scale_mod_add, _ctrl_scale_mod_add_small] +) + + @frozen class CModAdd(Bloq): r"""Controlled Modular Addition. diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py index 948001b38..b8391a3c8 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py @@ -18,10 +18,17 @@ import pytest import sympy +import qualtran.testing as qlt_testing from qualtran import QMontgomeryUInt, QUInt from qualtran.bloqs.arithmetic import Add from qualtran.bloqs.mod_arithmetic import CModAdd, CModAddK, CtrlScaleModAdd, ModAdd, ModAddK -from qualtran.bloqs.mod_arithmetic.mod_addition import _cmodadd_example +from qualtran.bloqs.mod_arithmetic.mod_addition import ( + _cmod_add_k, + _cmod_add_k_small, + _cmodadd_example, + _ctrl_scale_mod_add, + _ctrl_scale_mod_add_small, +) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join @@ -60,22 +67,6 @@ def test_add_mod_n_gate_counts(bitsize): assert bloq.t_complexity() == add_constant_mod_n_ref_t_complexity_(bloq) -def test_ctrl_scale_mod_add(): - bloq = CtrlScaleModAdd(k=123, mod=13 * 17, bitsize=8) - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 8 - - -def test_ctrl_mod_add_k(): - bloq = CModAddK(k=123, mod=13 * 17, bitsize=8) - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 5 - - @pytest.mark.parametrize('bitsize,p', [(1, 1), (2, 3), (5, 8)]) def test_mod_add_valid_decomp(bitsize, p): bloq = ModAdd(bitsize=bitsize, mod=p) @@ -130,6 +121,26 @@ def test_classical_action_cmodadd_fast(control, bitsize): assert b.call_classically(ctrl=c, x=x, y=y) == cb.call_classically(ctrl=c, x=x, y=y) +@pytest.mark.slow +@pytest.mark.parametrize( + ['prime', 'bitsize', 'k'], + [(p, n, k) for p in (13, 17, 23) for n in range(p.bit_length(), 8) for k in range(1, p)], +) +def test_cscalemodadd_classical_action(bitsize, prime, k): + b = CtrlScaleModAdd(bitsize=bitsize, mod=prime, k=k) + qlt_testing.assert_consistent_classical_action(b, ctrl=(0, 1), x=range(prime), y=range(prime)) + + +@pytest.mark.slow +@pytest.mark.parametrize( + ['prime', 'bitsize', 'k'], + [(p, n, k) for p in (13, 17, 23) for n in range(p.bit_length(), 8) for k in range(1, p)], +) +def test_cmodaddk_classical_action(bitsize, prime, k): + b = CModAddK(bitsize=bitsize, mod=prime, k=k) + qlt_testing.assert_consistent_classical_action(b, ctrl=(0, 1), x=range(prime)) + + @pytest.mark.parametrize('control', range(2)) @pytest.mark.parametrize( ['prime', 'bitsize'], @@ -157,6 +168,22 @@ def test_cmodadd_example(bloq_autotester): bloq_autotester(_cmodadd_example) +def test_cmod_add_k(bloq_autotester): + bloq_autotester(_cmod_add_k) + + +def test_cmod_add_k_small(bloq_autotester): + bloq_autotester(_cmod_add_k_small) + + +def test_ctrl_scale_mod_add(bloq_autotester): + bloq_autotester(_ctrl_scale_mod_add) + + +def test_ctrl_scale_mod_add_small(bloq_autotester): + bloq_autotester(_ctrl_scale_mod_add_small) + + @pytest.mark.notebook def test_notebook(): execute_notebook('mod_addition') diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index dad2907ea..15d671560 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -330,6 +330,7 @@ "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd": qualtran.bloqs.mod_arithmetic.CModAdd, "qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK, + "qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CtrlScaleModAdd": qualtran.bloqs.mod_arithmetic.CtrlScaleModAdd, "qualtran.bloqs.mod_arithmetic.ModAdd": qualtran.bloqs.mod_arithmetic.ModAdd, "qualtran.bloqs.mod_arithmetic.ModSub": qualtran.bloqs.mod_arithmetic.ModSub, From fbdd7e11e32cf60d81b1c7b020da9ad9d342d1ff Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 2 Oct 2024 11:09:15 -0700 Subject: [PATCH 07/21] Fix serialization test error --- qualtran/bloqs/mod_arithmetic/mod_addition.py | 7 +-- .../bloqs/mod_arithmetic/mod_addition_test.py | 52 ++++++++++--------- qualtran/serialization/resolver_dict.py | 3 +- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index e8dd38e87..5708dc6d7 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -43,7 +43,6 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT -from qualtran.symbolics import is_symbolic if TYPE_CHECKING: from qualtran import BloqBuilder @@ -90,8 +89,8 @@ def on_classical_vals( return {'x': x, 'y': (x + y) % self.mod} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: - if is_symbolic(self.bitsize): - raise NotImplementedError(f'symbolic decomposition is not supported for {self}') + if isinstance(self.bitsize, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") # Allocate ancilla bits for use in addition. junk_bit = bb.allocate(n=1) sign = bb.allocate(n=1) @@ -482,6 +481,8 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', ctrl, x: Soquet, y: Soquet ) -> Dict[str, 'SoquetT']: + if isinstance(self.dtype.bitsize, sympy.Expr): + raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") y_arr = bb.split(y) ancilla = bb.allocate(1) x = bb.add(Cast(self.dtype, QUInt(self.dtype.bitsize)), reg=x) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py index b8391a3c8..a716052b2 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py @@ -28,6 +28,10 @@ _cmodadd_example, _ctrl_scale_mod_add, _ctrl_scale_mod_add_small, + _mod_add, + _mod_add_k, + _mod_add_k_large, + _mod_add_k_small, ) from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost @@ -39,6 +43,29 @@ ) +@pytest.mark.parametrize( + "bloq", + [ + _mod_add, + _mod_add_k, + _mod_add_k_small, + _mod_add_k_large, + _cmod_add_k, + _cmod_add_k_small, + _ctrl_scale_mod_add, + _ctrl_scale_mod_add_small, + _cmodadd_example, + ], +) +def test_examples(bloq_autotester, bloq): + bloq_autotester(bloq) + + +@pytest.mark.notebook +def test_notebook(): + execute_notebook('mod_addition') + + 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)} @@ -164,31 +191,6 @@ def test_cmodadd_cost(control, dtype): assert cost.total_t_count() == 4 * n_toffolis -def test_cmodadd_example(bloq_autotester): - bloq_autotester(_cmodadd_example) - - -def test_cmod_add_k(bloq_autotester): - bloq_autotester(_cmod_add_k) - - -def test_cmod_add_k_small(bloq_autotester): - bloq_autotester(_cmod_add_k_small) - - -def test_ctrl_scale_mod_add(bloq_autotester): - bloq_autotester(_ctrl_scale_mod_add) - - -def test_ctrl_scale_mod_add_small(bloq_autotester): - bloq_autotester(_ctrl_scale_mod_add_small) - - -@pytest.mark.notebook -def test_notebook(): - execute_notebook('mod_addition') - - def test_cmod_add_complexity_vs_ref(): n, k = sympy.symbols('n k', integer=True, positive=True) bloq = CModAdd(QUInt(n), mod=k) diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 15d671560..f53ea457b 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -328,7 +328,8 @@ "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint, "qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM": qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM, "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.CModAddK, - "qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd": qualtran.bloqs.mod_arithmetic.CModAdd, + "qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd, + "qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.CModAdd, "qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.ModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CtrlScaleModAdd": qualtran.bloqs.mod_arithmetic.CtrlScaleModAdd, From 5fdc4172103967f8f3b36032d9724b7f1eccbed3 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 2 Oct 2024 11:12:04 -0700 Subject: [PATCH 08/21] Change Union -> SymbolicInt --- qualtran/bloqs/basic_gates/z_basis.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/basic_gates/z_basis.py b/qualtran/bloqs/basic_gates/z_basis.py index f30825fe6..fe9c6b561 100644 --- a/qualtran/bloqs/basic_gates/z_basis.py +++ b/qualtran/bloqs/basic_gates/z_basis.py @@ -43,6 +43,7 @@ from qualtran.bloqs.bookkeeping import ArbitraryClifford from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.drawing import Circle, directional_text_box, Text, TextBox, WireSymbol +from qualtran.symbolics.types import SymbolicInt if TYPE_CHECKING: import cirq @@ -457,7 +458,7 @@ class IntState(_IntVector): val: The register of size `bitsize` which initializes the value `val`. """ - def __init__(self, val: Union[int, sympy.Expr], bitsize: Union[int, sympy.Expr]): + def __init__(self, val: SymbolicInt, bitsize: SymbolicInt): self.__attrs_init__(val=val, bitsize=bitsize, state=True) @@ -482,7 +483,7 @@ class IntEffect(_IntVector): val: The register of size `bitsize` which de-allocates the value `val`. """ - def __init__(self, val: Union[int, sympy.Expr], bitsize: Union[int, sympy.Expr]): + def __init__(self, val: SymbolicInt, bitsize: SymbolicInt): self.__attrs_init__(val=val, bitsize=bitsize, state=False) From 643560056c6ac05373c960964f8a63796f77010f Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 6 Oct 2024 15:38:30 -0700 Subject: [PATCH 09/21] Fix nits --- .../bloqs/mod_arithmetic/mod_addition.ipynb | 10 ++++-- qualtran/bloqs/mod_arithmetic/mod_addition.py | 34 +++++++++++++------ 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb index ab6b4c994..c7a8537a1 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb @@ -412,7 +412,10 @@ "\n", "#### Registers\n", " - `ctrl`: The control bit\n", - " - `x`: The register to perform the in-place modular addition.\n" + " - `x`: The register to perform the in-place modular addition. \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). [Quantum Networks for Elementary Arithmetic Operations](https://arxiv.org/abs/quant-ph/9511018). [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Construction in the Gidney and Ekerå paper from 2019 references the second source's construction in figure 4. Because the construction is simply a ModAdd bloq and not a ModAddK bloq, we choose to use the bloq described in the third paper as it uses 2 fewer Add bloqs.\n" ] }, { @@ -529,7 +532,10 @@ "#### Registers\n", " - `ctrl`: The control bit\n", " - `x`: The 'source' quantum register containing the integer to be scaled and added to `y`.\n", - " - `y`: The 'destination' quantum register to which the addition will apply.\n" + " - `y`: The 'destination' quantum register to which the addition will apply. \n", + "\n", + "#### References\n", + " - [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Construction based on description in section 2.2 paragraph 4. We add n And/And† bloqs because the bloq is controlled, but the construction also involves modular addition controlled on the qubits comprising register x.\n" ] }, { diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index 5708dc6d7..c85340148 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -23,6 +23,7 @@ Bloq, bloq_example, BloqDocSpec, + DecomposeTypeError, GateWithRegisters, QBit, QMontgomeryUInt, @@ -32,7 +33,6 @@ Soquet, SoquetT, ) -from qualtran._infra.bloq import DecomposeTypeError from qualtran.bloqs.arithmetic.addition import Add, AddK from qualtran.bloqs.arithmetic.comparison import CLinearDepthGreaterThan, LinearDepthGreaterThan from qualtran.bloqs.arithmetic.controlled_addition import CAdd @@ -43,6 +43,7 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT +from qualtran.symbolics.types import is_symbolic if TYPE_CHECKING: from qualtran import BloqBuilder @@ -89,8 +90,8 @@ def on_classical_vals( return {'x': x, 'y': (x + y) % self.mod} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: - if isinstance(self.bitsize, sympy.Expr): - raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') # Allocate ancilla bits for use in addition. junk_bit = bb.allocate(n=1) sign = bb.allocate(n=1) @@ -270,6 +271,15 @@ class CModAddK(Bloq): Registers: ctrl: The control bit x: The register to perform the in-place modular addition. + + References: + [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + [Quantum Networks for Elementary Arithmetic Operations](https://arxiv.org/abs/quant-ph/9511018). + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). + Construction in the Gidney and Ekerå paper from 2019 references the second source's + construction in figure 4. Because the construction is simply a ModAdd bloq and not a + ModAddK bloq, we choose to use the bloq described in the third paper as it uses 2 fewer Add + bloqs. """ k: Union[int, sympy.Expr] @@ -283,8 +293,6 @@ def signature(self) -> 'Signature': def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet' ) -> Dict[str, 'SoquetT']: - if isinstance(self.bitsize, sympy.Expr): - raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") k = bb.add(IntState(bitsize=self.bitsize, val=self.k)) ctrl, k, x = bb.add(CModAdd(QUInt(self.bitsize), mod=self.mod), ctrl=ctrl, x=k, y=x) bb.add(IntEffect(bitsize=self.bitsize, val=self.k), val=k) @@ -344,6 +352,12 @@ class CtrlScaleModAdd(Bloq): ctrl: The control bit x: The 'source' quantum register containing the integer to be scaled and added to `y`. y: The 'destination' quantum register to which the addition will apply. + + References: + [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + Construction based on description in section 2.2 paragraph 4. We add n And/And† bloqs + because the bloq is controlled, but the construction also involves modular addition + controlled on the qubits comprising register x. """ k: Union[int, sympy.Expr] @@ -363,10 +377,10 @@ def signature(self) -> 'Signature': def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet', y: 'Soquet' ) -> Dict[str, 'SoquetT']: - if isinstance(self.bitsize, sympy.Expr): - raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') x_split = bb.split(x) - for i in range(self.bitsize): + for i in range(int(self.bitsize)): and_ctrl = [ctrl, x_split[i]] and_ctrl, ancilla = bb.add(And(), ctrl=and_ctrl) ancilla, y = bb.add( @@ -481,8 +495,8 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', ctrl, x: Soquet, y: Soquet ) -> Dict[str, 'SoquetT']: - if isinstance(self.dtype.bitsize, sympy.Expr): - raise DecomposeTypeError("Cannot decompose symbolic `bitsize`.") + if is_symbolic(self.dtype.bitsize): + raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') y_arr = bb.split(y) ancilla = bb.allocate(1) x = bb.add(Cast(self.dtype, QUInt(self.dtype.bitsize)), reg=x) From f06e2f89793b9d3f7fe37e438ab0fa667b061313 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 6 Oct 2024 20:25:47 -0700 Subject: [PATCH 10/21] Better symbolic messages --- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 22 +++++++++---------- .../bloqs/factoring/rsa/rsa_phase_estimate.py | 8 +++---- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 2c419effd..91e8600b1 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -39,7 +39,7 @@ from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT from qualtran.symbolics import is_symbolic -from qualtran.symbolics.types import HasLength +from qualtran.symbolics.types import SymbolicInt @frozen @@ -67,10 +67,10 @@ class ModExp(Bloq): Gidney and Ekerå. 2019. """ - base: Union[int, sympy.Expr] - mod: Union[int, sympy.Expr] - exp_bitsize: Union[int, sympy.Expr] - x_bitsize: Union[int, sympy.Expr] + base: 'SymbolicInt' + mod: 'SymbolicInt' + exp_bitsize: 'SymbolicInt' + x_bitsize: 'SymbolicInt' def __attrs_post_init__(self): if not is_symbolic(self.base, self.mod): @@ -87,7 +87,7 @@ def signature(self) -> 'Signature': @classmethod def make_for_shor( - cls, big_n: Union[int, sympy.Expr], g: Optional[Union[int, sympy.Expr]] = None + cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None ): """Factory method that sets up the modular exponentiation for a factoring run. @@ -96,12 +96,12 @@ def make_for_shor( to set `x_bitsize` and `exp_bitsize`. g: Optional base of the exponentiation. If `None`, we pick a random base. """ - if isinstance(big_n, sympy.Expr): + if is_symbolic(big_n): little_n = sympy.ceiling(sympy.log(big_n, 2)) else: little_n = int(math.ceil(math.log2(big_n))) if g is None: - if isinstance(big_n, sympy.Expr): + if is_symbolic(big_n): g = sympy.symbols('g') else: while True: @@ -110,15 +110,15 @@ def make_for_shor( break return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n) - def _CtrlModMul(self, k: Union[int, sympy.Expr]): + def _CtrlModMul(self, k: 'SymbolicInt'): """Helper method to return a `CModMulK` with attributes forwarded.""" return CModMulK(QUInt(self.x_bitsize), k=k, mod=self.mod) def build_composite_bloq( self, bb: 'BloqBuilder', exponent: 'Soquet', x: 'Soquet' ) -> Dict[str, 'SoquetT']: - if isinstance(self.exp_bitsize, sympy.Expr): - raise DecomposeTypeError("`exp_bitsize` must be a concrete value.") + if is_symbolic(self.exp_bitsize): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `exp_bitsize`.") # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method base = self.base % self.mod for j in range(self.exp_bitsize - 1, 0 - 1, -1): diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py index 64f473747..24041cb0e 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -26,9 +26,7 @@ BloqDocSpec, DecomposeTypeError, QUInt, - Register, Signature, - Soquet, SoquetT, ) from qualtran.bloqs.basic_gates import IntState, PlusState @@ -70,8 +68,8 @@ def __attrs_post_init__(self): assert self.n == int(math.ceil(math.log2(self.mod))) def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: - if isinstance(self.n, sympy.Expr): - raise DecomposeTypeError("Cannot decompose symbolic `n`.") + if is_symbolic(self.n): + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") exponent = [bb.add(PlusState()) for _ in range(2 * self.n)] x = bb.add(IntState(val=1, bitsize=self.n)) @@ -84,7 +82,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {MeasureQFT(n=self.n): 1} + return {MeasureQFT(n=self.n): 1, ModExp.make_for_shor(big_n=self.mod, g=self.base): 1} @bloq_example From dd45a8fef3c4350e4075d8db956c87cc2e70c863 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 6 Oct 2024 20:28:07 -0700 Subject: [PATCH 11/21] Better symbolic decomposition error messages --- qualtran/bloqs/mod_arithmetic/mod_addition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index c85340148..aa0a5e3c1 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -91,7 +91,7 @@ def on_classical_vals( def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: if is_symbolic(self.bitsize): - raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") # Allocate ancilla bits for use in addition. junk_bit = bb.allocate(n=1) sign = bb.allocate(n=1) @@ -378,7 +378,7 @@ def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', x: 'Soquet', y: 'Soquet' ) -> Dict[str, 'SoquetT']: if is_symbolic(self.bitsize): - raise DecomposeTypeError(f'symbolic decomposition is not supported for {self}') + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `bitsize`.") x_split = bb.split(x) for i in range(int(self.bitsize)): and_ctrl = [ctrl, x_split[i]] From a9b9f5629fcfe6c26f4f27b9375f6e7c55c554ea Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 9 Oct 2024 22:09:06 -0700 Subject: [PATCH 12/21] Fix merge conflicts --- dev_tools/qualtran_dev_tools/notebook_specs.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index f96b511e4..1b5f219b4 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -493,6 +493,8 @@ qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC, qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC, qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._C_MOD_ADD_K_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._CTRL_SCALE_MOD_ADD_DOC, ], ), NotebookSpecV2( From 3bf7e63792b37c6192bcac695df001cf1d420a50 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Thu, 10 Oct 2024 17:01:54 -0700 Subject: [PATCH 13/21] Fixed docstring to be more readable (hopefully) --- qualtran/bloqs/mod_arithmetic/mod_addition.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index aa0a5e3c1..7d7f5dafb 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_addition.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -43,7 +43,7 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT -from qualtran.symbolics.types import is_symbolic +from qualtran.symbolics import is_symbolic if TYPE_CHECKING: from qualtran import BloqBuilder @@ -274,12 +274,20 @@ class CModAddK(Bloq): References: [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + Gidney and Ekerå 2019. + Uses CModAddK in the "reference implementation" in section 2.2 paragraph 4 to implement + controlled scaled addition. References circuit from Elementary Arithmetic Operations paper + cited below. + [Quantum Networks for Elementary Arithmetic Operations](https://arxiv.org/abs/quant-ph/9511018). + Vedral et al. 1995. + Implements ModAdd in figure 4; because the referenced circuit is ModAdd instead of ModAddK + we choose to turn k into an IntState and reuse the existing CModAdd bloq. + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). - Construction in the Gidney and Ekerå paper from 2019 references the second source's - construction in figure 4. Because the construction is simply a ModAdd bloq and not a - ModAddK bloq, we choose to use the bloq described in the third paper as it uses 2 fewer Add - bloqs. + Litinski et al. 2023. + This CModAdd circuit uses 2 fewer additions than the implementation referenced above. + Because of this we choose to use this CModAdd bloq instead. """ k: Union[int, sympy.Expr] From 284c55248e95f72feac2d2f8b1ba525775cad40e Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sat, 12 Oct 2024 10:17:42 -0700 Subject: [PATCH 14/21] Refactor RSA to have a phase estimation circuit and a classical simulable modular exponentiation circuit --- .../factoring/rsa/factoring-via-modexp.ipynb | 4 +- qualtran/bloqs/factoring/rsa/rsa.ipynb | 23 +++++-- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 38 ++++++----- .../bloqs/factoring/rsa/rsa_mod_exp_test.py | 29 ++++++--- .../bloqs/factoring/rsa/rsa_phase_estimate.py | 63 +++++++++++++++---- .../factoring/rsa/rsa_phase_estimate_test.py | 11 ++-- 6 files changed, 116 insertions(+), 52 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb index a12933049..08ff940b8 100644 --- a/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb +++ b/qualtran/bloqs/factoring/rsa/factoring-via-modexp.ipynb @@ -208,8 +208,8 @@ "from qualtran import QUInt\n", "for e in range(20):\n", " ref = (g ** e) % N\n", - " _, bloq_eval = mod_exp.call_classically(exponent=QUInt(32).to_bits(e), x=1)\n", - " _, decomp_eval = mod_exp_decomp.call_classically(exponent=QUInt(32).to_bits(e), x=1)\n", + " _, bloq_eval = mod_exp.call_classically(exponent=e)\n", + " _, decomp_eval = mod_exp_decomp.call_classically(exponent=e)\n", " \n", " star = ' *' if ref == 1 else ''\n", " print(f'{e:5d} {ref:5d} {bloq_eval:5d} {decomp_eval:5d} {star}')" diff --git a/qualtran/bloqs/factoring/rsa/rsa.ipynb b/qualtran/bloqs/factoring/rsa/rsa.ipynb index 4daf77005..8c020792b 100644 --- a/qualtran/bloqs/factoring/rsa/rsa.ipynb +++ b/qualtran/bloqs/factoring/rsa/rsa.ipynb @@ -152,8 +152,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([modexp_small, modexp],\n", - " ['`modexp_small`', '`modexp`'])" + "show_bloqs([modexp_small, modexp, modexp_symb],\n", + " ['`modexp_small`', '`modexp`', '`modexp_symb`'])" ] }, { @@ -234,8 +234,8 @@ }, "outputs": [], "source": [ - "n, p = sympy.symbols('n p')\n", - "rsa_pe = RSAPhaseEstimate(n=n, mod=p)" + "n, p, g = sympy.symbols('n p g')\n", + "rsa_pe = RSAPhaseEstimate(n=n, mod=p, base=g)" ] }, { @@ -247,8 +247,7 @@ }, "outputs": [], "source": [ - "n, p = 6, 5 * 7\n", - "rsa_pe_small = RSAPhaseEstimate(n=n, mod=p)" + "rsa_pe_small = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9)" ] }, { @@ -299,6 +298,18 @@ "show_call_graph(rsa_pe_small_g)\n", "show_counts_sigma(rsa_pe_small_sigma)" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d72bc625", + "metadata": { + "cq.autogen": "RSAPhaseEstimate.rsa_pe_shor" + }, + "outputs": [], + "source": [ + "rsa_pe_shor = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9)" + ] } ], "metadata": { diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 91e8600b1..da4d46038 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -26,13 +26,14 @@ BloqBuilder, BloqDocSpec, DecomposeTypeError, - QBit, QUInt, Register, Signature, Soquet, SoquetT, ) +from qualtran._infra.registers import Side +from qualtran.bloqs.basic_gates.z_basis import IntState from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -80,15 +81,13 @@ def __attrs_post_init__(self): def signature(self) -> 'Signature': return Signature( [ - Register('exponent', QBit(), shape=(self.exp_bitsize,)), - Register('x', QUInt(self.x_bitsize)), + Register('exponent', QUInt(self.exp_bitsize)), + Register('x', QUInt(self.x_bitsize), side=Side.RIGHT), ] ) @classmethod - def make_for_shor( - cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None - ): + def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None): """Factory method that sets up the modular exponentiation for a factoring run. Args: @@ -114,28 +113,26 @@ def _CtrlModMul(self, k: 'SymbolicInt'): """Helper method to return a `CModMulK` with attributes forwarded.""" return CModMulK(QUInt(self.x_bitsize), k=k, mod=self.mod) - def build_composite_bloq( - self, bb: 'BloqBuilder', exponent: 'Soquet', x: 'Soquet' - ) -> Dict[str, 'SoquetT']: + def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[str, 'SoquetT']: if is_symbolic(self.exp_bitsize): raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `exp_bitsize`.") # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + x = bb.add(IntState(val=1, bitsize=self.x_bitsize)) + exponent = bb.split(exponent) + base = self.base % self.mod for j in range(self.exp_bitsize - 1, 0 - 1, -1): exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) base = (base * base) % self.mod - return {'exponent': exponent, 'x': x} + return {'exponent': bb.join(exponent, dtype=QUInt(self.exp_bitsize)), 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': k = ssa.new_symbol('k') - return {self._CtrlModMul(k=k): self.exp_bitsize} + return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} - def on_classical_vals(self, exponent, x) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: - return { - 'exponent': exponent, - 'x': (self.base ** QUInt(self.exp_bitsize).from_bits(exponent)) % self.mod, - } + def on_classical_vals(self, exponent) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: + return {'exponent': exponent, 'x': (self.base**exponent) % self.mod} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() @@ -167,4 +164,11 @@ def _modexp() -> ModExp: return modexp -_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp)) +@bloq_example +def _modexp_symb() -> ModExp: + g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') + modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x) + return modexp_symb + + +_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp, _modexp_symb)) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index 96cb56bc1..ce04f7c3d 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -19,13 +19,12 @@ import pytest import sympy -from qualtran import Bloq, QUInt +from qualtran import Bloq from qualtran.bloqs.bookkeeping import Join, Split -from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, _modexp_small, _modexp_symb, ModExp from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text from qualtran.resource_counting import SympySymbolAllocator -from qualtran.symbolics.types import HasLength from qualtran.testing import execute_notebook @@ -49,15 +48,26 @@ def test_mod_exp_consistent_classical(): base = rs.randint(1, mod) bloq = ModExp(base=base, exp_bitsize=ne, x_bitsize=n, mod=mod) - ret1 = bloq.call_classically(exponent=QUInt(ne).to_bits_array(exponent)[0], x=1) - ret2 = bloq.decompose_bloq().call_classically( - exponent=QUInt(ne).to_bits_array(exponent)[0], x=1 - ) + ret1 = bloq.call_classically(exponent=exponent, x=1) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent, x=1) assert len(ret1) == len(ret2) for i in range(len(ret1)): np.testing.assert_array_equal(ret1[i], ret2[i]) +def test_modexp_symb_manual(): + g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') + modexp = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x) + assert cast(Text, modexp.wire_symbol(reg=None)).text == 'g^e % N' + counts = modexp.bloq_counts() + counts_by_bloq = {str(bloq): n for bloq, n in counts.items()} + assert counts_by_bloq['|1>'] == 1 + assert counts_by_bloq['CModMulK'] == n_e + + b, x = modexp.call_classically(exponent=sympy.Symbol('b')) + assert str(x) == 'Mod(g**b, N)' + + def test_mod_exp_consistent_counts(): bloq = ModExp(base=11, exp_bitsize=3, x_bitsize=10, mod=50) @@ -85,8 +95,9 @@ def test_mod_exp_t_complexity(): assert tcomp.t > 0 -def test_modexp(bloq_autotester): - bloq_autotester(_modexp) +@pytest.mark.parametrize('bloq', [_modexp, _modexp_symb, _modexp_small]) +def test_modexp(bloq_autotester, bloq): + bloq_autotester(bloq) @pytest.mark.notebook diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py index 24041cb0e..049214c76 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -13,9 +13,11 @@ # limitations under the License. import math +import random from functools import cached_property from typing import Dict, Optional +import attrs import sympy from attrs import frozen @@ -32,11 +34,10 @@ from qualtran.bloqs.basic_gates import IntState, PlusState from qualtran.bloqs.bookkeeping import Free from qualtran.bloqs.factoring._factoring_shims import MeasureQFT +from qualtran.bloqs.mod_arithmetic.mod_multiplication import CModMulK from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.symbolics.types import is_symbolic, SymbolicInt -from .rsa_mod_exp import ModExp - @frozen class RSAPhaseEstimate(Bloq): @@ -48,7 +49,7 @@ class RSAPhaseEstimate(Bloq): Args: n: The bitsize of the modulus N. mod: The modulus N; a part of the public key for RSA. - base: An optional base for modular exponentiation. + base: A base for modular exponentiation. References: [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). @@ -57,45 +58,83 @@ class RSAPhaseEstimate(Bloq): n: 'SymbolicInt' mod: 'SymbolicInt' - base: Optional['SymbolicInt'] = None + base: 'SymbolicInt' @cached_property def signature(self) -> 'Signature': return Signature([]) + @classmethod + def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None): + """Factory method that sets up the modular exponentiation for a factoring run. + + Args: + big_n: The large composite number N. Used to set `mod`. Its bitsize is used + to set `x_bitsize` and `exp_bitsize`. + g: Optional base of the exponentiation. If `None`, we pick a random base. + """ + if is_symbolic(big_n): + little_n = sympy.ceiling(sympy.log(big_n, 2)) + else: + little_n = int(math.ceil(math.log2(big_n))) + if g is None: + if is_symbolic(big_n): + g = sympy.symbols('g') + else: + while True: + g = random.randint(2, int(big_n)) + if math.gcd(g, int(big_n)) == 1: + break + return cls(base=g, mod=big_n, n=little_n) + def __attrs_post_init__(self): if not is_symbolic(self.n, self.mod): assert self.n == int(math.ceil(math.log2(self.mod))) + def _CtrlModMul(self, k: 'SymbolicInt'): + """Helper method to return a `CModMulK` with attributes forwarded.""" + return CModMulK(QUInt(self.n), k=k, mod=self.mod) + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: if is_symbolic(self.n): raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `n`.") exponent = [bb.add(PlusState()) for _ in range(2 * self.n)] x = bb.add(IntState(val=1, bitsize=self.n)) - exponent, x = bb.add( - ModExp.make_for_shor(big_n=self.mod, g=self.base), exponent=exponent, x=x - ) + base = self.base % self.mod + for j in range((2 * self.n) - 1, 0 - 1, -1): + exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) + base = (base * base) % self.mod bb.add(MeasureQFT(n=2 * self.n), x=exponent) bb.add(Free(QUInt(self.n), dirty=True), reg=x) return {} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - return {MeasureQFT(n=self.n): 1, ModExp.make_for_shor(big_n=self.mod, g=self.base): 1} + k = ssa.new_symbol('k') + return {MeasureQFT(n=self.n): 1, self._CtrlModMul(k=k): 2 * self.n} + + +_K = sympy.Symbol('k_exp') + + +def _generalize_k(b: Bloq) -> Optional[Bloq]: + if isinstance(b, CModMulK): + return attrs.evolve(b, k=_K) + + return b @bloq_example def _rsa_pe() -> RSAPhaseEstimate: - n, p = sympy.symbols('n p') - rsa_pe = RSAPhaseEstimate(n=n, mod=p) + n, p, g = sympy.symbols('n p g') + rsa_pe = RSAPhaseEstimate(n=n, mod=p, base=g) return rsa_pe @bloq_example def _rsa_pe_small() -> RSAPhaseEstimate: - n, p = 6, 5 * 7 - rsa_pe_small = RSAPhaseEstimate(n=n, mod=p) + rsa_pe_small = RSAPhaseEstimate.make_for_shor(big_n=13 * 17, g=9) return rsa_pe_small diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py index ccf846836..4ddedf964 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate_test.py @@ -12,16 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. +import pytest + import qualtran.testing as qlt_testing from qualtran.bloqs.factoring.rsa.rsa_phase_estimate import _rsa_pe, _rsa_pe_small -def test_rsa_pe(bloq_autotester): - bloq_autotester(_rsa_pe) - - -def test_rsa_pe_small(bloq_autotester): - bloq_autotester(_rsa_pe_small) +@pytest.mark.parametrize('bloq', [_rsa_pe_small, _rsa_pe]) +def test_rsa_pe(bloq_autotester, bloq): + bloq_autotester(bloq) def test_notebook(): From bd414203d168e738dd11daf2c6d0d26aa6bac16e Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sat, 12 Oct 2024 10:20:35 -0700 Subject: [PATCH 15/21] Fix notebook specs merge conflict --- dev_tools/qualtran_dev_tools/notebook_specs.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/dev_tools/qualtran_dev_tools/notebook_specs.py b/dev_tools/qualtran_dev_tools/notebook_specs.py index f96b511e4..fe12a5f19 100644 --- a/dev_tools/qualtran_dev_tools/notebook_specs.py +++ b/dev_tools/qualtran_dev_tools/notebook_specs.py @@ -83,7 +83,7 @@ import qualtran.bloqs.data_loading.qrom_base import qualtran.bloqs.data_loading.select_swap_qrom import qualtran.bloqs.factoring.ecc -import qualtran.bloqs.factoring.mod_exp +import qualtran.bloqs.factoring.rsa import qualtran.bloqs.gf_arithmetic.gf2_add_k import qualtran.bloqs.gf_arithmetic.gf2_addition import qualtran.bloqs.gf_arithmetic.gf2_inverse @@ -515,10 +515,12 @@ ], ), NotebookSpecV2( - title='Modular Exponentiation', - module=qualtran.bloqs.factoring.mod_exp, - bloq_specs=[qualtran.bloqs.factoring.mod_exp._MODEXP_DOC], - directory=f'{SOURCE_DIR}/bloqs/factoring', + title='Factoring RSA', + module=qualtran.bloqs.factoring.rsa, + bloq_specs=[ + qualtran.bloqs.factoring.rsa.rsa_phase_estimate._RSA_PE_BLOQ_DOC, + qualtran.bloqs.factoring.rsa.rsa_mod_exp._RSA_MODEXP_DOC, + ], ), NotebookSpecV2( title='Elliptic Curve Addition', From 7d2bcba7430e9f2b50b24e8aaf89fd09836da60e Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 13 Oct 2024 16:38:21 -0700 Subject: [PATCH 16/21] Super WIP windowed mod exp --- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 84 ++++++++++++++++--- .../bloqs/factoring/rsa/rsa_phase_estimate.py | 11 +++ 2 files changed, 82 insertions(+), 13 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index da4d46038..543129e96 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -17,6 +17,7 @@ from typing import cast, Dict, Optional, Tuple, Union import attrs +import numpy as np import sympy from attrs import frozen @@ -33,14 +34,16 @@ SoquetT, ) from qualtran._infra.registers import Side +from qualtran.bloqs.arithmetic import Add from qualtran.bloqs.basic_gates.z_basis import IntState +from qualtran.bloqs.data_loading.qroam_clean import QROAMClean from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join from qualtran.simulation.classical_sim import ClassicalValT from qualtran.symbolics import is_symbolic -from qualtran.symbolics.types import SymbolicInt +from qualtran.symbolics.types import Shaped, SymbolicInt @frozen @@ -54,10 +57,13 @@ class ModExp(Bloq): This bloq decomposes into controlled modular exponentiation for each exponent bit. Args: - base: The integer base of the exponentiation - mod: The integer modulus - exp_bitsize: The size of the `exponent` thru-register - x_bitsize: The size of the `x` right-register + base: The integer base of the exponentiation. + mod: The integer modulus. + exp_bitsize: The size of the `exponent` thru-register. + x_bitsize: The size of the `x` right-register. + exp_window_size: The window size of windowed arithmetic on the controlled modular + multiplications. + mult_window_size: The window size of windowed arithmetic on the modular product additions. Registers: exponent: The exponent @@ -66,12 +72,17 @@ class ModExp(Bloq): References: [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019. + + [Windowed quantum arithmetic](https://arxiv.org/abs/1905.07682). + Craig Gidney. 2019. """ base: 'SymbolicInt' mod: 'SymbolicInt' exp_bitsize: 'SymbolicInt' x_bitsize: 'SymbolicInt' + exp_window_size: 'SymbolicInt' = 1 + mult_window_size: 'SymbolicInt' = 1 def __attrs_post_init__(self): if not is_symbolic(self.base, self.mod): @@ -87,7 +98,7 @@ def signature(self) -> 'Signature': ) @classmethod - def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None): + def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None, exp_window_size: Optional['SymbolicInt'] = 1, mult_window_size: Optional['SymbolicInt'] = 1): """Factory method that sets up the modular exponentiation for a factoring run. Args: @@ -107,7 +118,29 @@ def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None): g = random.randint(2, int(big_n)) if math.gcd(g, int(big_n)) == 1: break - return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n) + return cls(base=g, mod=big_n, exp_bitsize=2 * little_n, x_bitsize=little_n, exp_window_size=exp_window_size, mult_window_size=mult_window_size) + + def qrom(self, data): + if is_symbolic(self.exp_bitsize) or is_symbolic(self.exp_window_size): + log_block_sizes = None + if is_symbolic(self.exp_bitsize) and not is_symbolic(self.exp_window_size): + # We assume that bitsize is much larger than window_size + log_block_sizes = (0,) + return QROAMClean( + [ + Shaped((2**(self.exp_window_size+self.mult_window_size),)), + ], + selection_bitsizes=(self.exp_window_size, self.mult_window_size), + target_bitsizes=(self.x_bitsize,), + log_block_sizes=log_block_sizes, + ) + + return QROAMClean( + [data], + selection_bitsizes=(self.exp_window_size, self.mult_window_size), + target_bitsizes=(self.x_bitsize,), + ) + def _CtrlModMul(self, k: 'SymbolicInt'): """Helper method to return a `CModMulK` with attributes forwarded.""" @@ -115,15 +148,40 @@ def _CtrlModMul(self, k: 'SymbolicInt'): def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[str, 'SoquetT']: if is_symbolic(self.exp_bitsize): - raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `exp_bitsize`.") - # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + raise DecomposeTypeError(f"Cannot decompose {self} with symbolic `exp_bitsize`.") x = bb.add(IntState(val=1, bitsize=self.x_bitsize)) exponent = bb.split(exponent) - base = self.base % self.mod - for j in range(self.exp_bitsize - 1, 0 - 1, -1): - exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) - base = (base * base) % self.mod + if self.exp_window_size > 1 or self.mult_window_size > 1: + k = self.base + k_inv = pow(self.base, -1, self.mod) + + a = bb.split(x) + b = bb.add(IntState(val=0, bitsize=self.x_bitsize)) + + ei = np.split(np.array(exponent), self.exp_bitsize // self.exp_window_size) + for i in range(self.exp_bitsize // self.exp_window_size): + kes = [pow(k, 2**i * x_e, self.mod) for x_e in range(2**self.exp_window_size)] + kes_inv = [pow(x_e, -1, self.mod) for x_e in kes] + + mi = np.split(np.array(a), self.x_bitsize // self.mult_window_size) + for j in range(self.x_bitsize // self.mult_window_size): + data = ([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) + ei[i], mi[j], t = bb.add(self.qrom(data), selection0_=ei[i], selection1_=mi[j]) + t, b = bb.add(Add(QUInt(self.x_bitsize), QUInt(self.x_bitsize), a=t, b=b)) + ei[i], mi[j] = bb.add(self.qrom(data).adjoint(), selection0_=ei[i], selection1_=mi[j], target=t) + + a = np.concatenate(mi, axis=None) + x = bb.join(a, QUInt(self.x_bitsize)) + exponent = np.concatenate(ei, axis=None) + bb.free(b) + else: + # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method + + base = self.base % self.mod + for j in range(self.exp_bitsize - 1, 0 - 1, -1): + exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) + base = (base * base) % self.mod return {'exponent': bb.join(exponent, dtype=QUInt(self.exp_bitsize)), 'x': x} diff --git a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py index 049214c76..ff137d0fc 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py +++ b/qualtran/bloqs/factoring/rsa/rsa_phase_estimate.py @@ -50,15 +50,26 @@ class RSAPhaseEstimate(Bloq): n: The bitsize of the modulus N. mod: The modulus N; a part of the public key for RSA. base: A base for modular exponentiation. + exp_window_size: The window size of windowed arithmetic on the controlled modular + multiplications. + mult_window_size: The window size of windowed arithmetic on the modular product additions. References: + [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). + Gidney and Ekerå. 2019. + [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). Stephane Beauregard. 2003. + + [Windowed quantum arithmetic](https://arxiv.org/abs/1905.07682). + Craig Gidney. 2019. """ n: 'SymbolicInt' mod: 'SymbolicInt' base: 'SymbolicInt' + exp_window_size: 'SymbolicInt' = 1 + mult_window_size: 'SymbolicInt' = 1 @cached_property def signature(self) -> 'Signature': From ba18d5fdd42662c101ac3527638bed10dbda320b Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Sun, 13 Oct 2024 19:41:47 -0700 Subject: [PATCH 17/21] Bloq decomposition complete - needs testing --- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 49 +++++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 543129e96..d9951b4b1 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -35,6 +35,8 @@ ) from qualtran._infra.registers import Side from qualtran.bloqs.arithmetic import Add +from qualtran.bloqs.arithmetic.subtraction import SubtractFrom +from qualtran.bloqs.basic_gates.swap import Swap from qualtran.bloqs.basic_gates.z_basis import IntState from qualtran.bloqs.data_loading.qroam_clean import QROAMClean from qualtran.bloqs.mod_arithmetic import CModMulK @@ -154,7 +156,6 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st if self.exp_window_size > 1 or self.mult_window_size > 1: k = self.base - k_inv = pow(self.base, -1, self.mod) a = bb.split(x) b = bb.add(IntState(val=0, bitsize=self.x_bitsize)) @@ -166,18 +167,43 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st mi = np.split(np.array(a), self.x_bitsize // self.mult_window_size) for j in range(self.x_bitsize // self.mult_window_size): - data = ([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) - ei[i], mi[j], t = bb.add(self.qrom(data), selection0_=ei[i], selection1_=mi[j]) - t, b = bb.add(Add(QUInt(self.x_bitsize), QUInt(self.x_bitsize), a=t, b=b)) - ei[i], mi[j] = bb.add(self.qrom(data).adjoint(), selection0_=ei[i], selection1_=mi[j], target=t) - + data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) + ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) + mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) + ei_i, mi_i, t = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) + t, b = bb.add(Add(QUInt(self.x_bitsize), QUInt(self.x_bitsize)), a=t, b=b) + ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t) + ei[i] = bb.split(ei_i) + mi[j] = bb.split(mi_i) + a = np.concatenate(mi, axis=None) + a = bb.join(a, QUInt(self.x_bitsize)) + + b = bb.split(b) + mi = np.split(np.array(b), self.x_bitsize // self.mult_window_size) + for j in range(self.x_bitsize // self.mult_window_size): + data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) + ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) + mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) + ei_i, mi_i, t = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) + t, a = bb.add(SubtractFrom(QUInt(self.x_bitsize)), a=t, b=a) + ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t) + ei[i] = bb.split(ei_i) + mi[j] = bb.split(mi_i) + + b = np.concatenate(mi, axis=None) + + b = bb.join(b, QUInt(self.x_bitsize)) + + a, b = bb.add(Swap(self.x_bitsize), x=a, y=b) + + a = bb.split(a) + x = bb.join(a, QUInt(self.x_bitsize)) exponent = np.concatenate(ei, axis=None) bb.free(b) else: # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method - base = self.base % self.mod for j in range(self.exp_bitsize - 1, 0 - 1, -1): exponent[j], x = bb.add(self._CtrlModMul(k=base), ctrl=exponent[j], x=x) @@ -186,8 +212,13 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st return {'exponent': bb.join(exponent, dtype=QUInt(self.exp_bitsize)), 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - k = ssa.new_symbol('k') - return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} + if self.exp_window_size > 1 or self.mult_window_size > 1: + bloq_counts = (self.exp_bitsize // self.exp_window_size) * (self.x_bitsize // self.mult_window_size) + return {self.qrom: 2 * bloq_counts, self.qrom.adjoint(): 2 * bloq_counts, Add(): bloq_counts, SubtractFrom(): bloq_counts, + Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + else: + k = ssa.new_symbol('k') + return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} def on_classical_vals(self, exponent) -> Dict[str, Union['ClassicalValT', sympy.Expr]]: return {'exponent': exponent, 'x': (self.base**exponent) % self.mod} From 45b58b4bd8d248694eb2112a25ffb6ab8d2ba5f1 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Tue, 15 Oct 2024 14:47:03 -0700 Subject: [PATCH 18/21] More work on decomposition --- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 46 +++++++++++++------ .../bloqs/factoring/rsa/rsa_mod_exp_test.py | 4 +- 2 files changed, 33 insertions(+), 17 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index d9951b4b1..090d94be4 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -40,6 +40,8 @@ from qualtran.bloqs.basic_gates.z_basis import IntState from qualtran.bloqs.data_loading.qroam_clean import QROAMClean from qualtran.bloqs.mod_arithmetic import CModMulK +from qualtran.bloqs.mod_arithmetic.mod_addition import ModAdd +from qualtran.bloqs.mod_arithmetic.mod_subtraction import ModSub from qualtran.drawing import Text, WireSymbol from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_split_join @@ -83,8 +85,8 @@ class ModExp(Bloq): mod: 'SymbolicInt' exp_bitsize: 'SymbolicInt' x_bitsize: 'SymbolicInt' - exp_window_size: 'SymbolicInt' = 1 - mult_window_size: 'SymbolicInt' = 1 + exp_window_size: Optional['SymbolicInt'] = None + mult_window_size: Optional['SymbolicInt'] = None def __attrs_post_init__(self): if not is_symbolic(self.base, self.mod): @@ -100,7 +102,7 @@ def signature(self) -> 'Signature': ) @classmethod - def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None, exp_window_size: Optional['SymbolicInt'] = 1, mult_window_size: Optional['SymbolicInt'] = 1): + def make_for_shor(cls, big_n: 'SymbolicInt', g: Optional['SymbolicInt'] = None, exp_window_size: Optional['SymbolicInt'] = None, mult_window_size: Optional['SymbolicInt'] = None): """Factory method that sets up the modular exponentiation for a factoring run. Args: @@ -154,7 +156,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st x = bb.add(IntState(val=1, bitsize=self.x_bitsize)) exponent = bb.split(exponent) - if self.exp_window_size > 1 or self.mult_window_size > 1: + if self.exp_window_size is not None and self.mult_window_size is not None: k = self.base a = bb.split(x) @@ -170,9 +172,10 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) - ei_i, mi_i, t = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) - t, b = bb.add(Add(QUInt(self.x_bitsize), QUInt(self.x_bitsize)), a=t, b=b) - ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t) + ei_i, mi_i, t, *junk = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) + t, b = bb.add(ModAdd(self.x_bitsize, self.mod), x=t, y=b) + junk_mapping = {f'junk_target{i}_': junk[i] for i in range(len(junk))} + ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t, **junk_mapping) ei[i] = bb.split(ei_i) mi[j] = bb.split(mi_i) @@ -185,9 +188,10 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) - ei_i, mi_i, t = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) - t, a = bb.add(SubtractFrom(QUInt(self.x_bitsize)), a=t, b=a) - ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t) + ei_i, mi_i, t, *junk = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) + t, a = bb.add(ModSub(QUInt(self.x_bitsize), self.mod), x=t, y=a) + junk_mapping = {f'junk_target{i}_': junk[i] for i in range(len(junk))} + ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t, **junk_mapping) ei[i] = bb.split(ei_i) mi[j] = bb.split(mi_i) @@ -201,7 +205,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st x = bb.join(a, QUInt(self.x_bitsize)) exponent = np.concatenate(ei, axis=None) - bb.free(b) + bb.free(b, dirty=True) else: # https://en.wikipedia.org/wiki/Modular_exponentiation#Right-to-left_binary_method base = self.base % self.mod @@ -212,10 +216,10 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st return {'exponent': bb.join(exponent, dtype=QUInt(self.exp_bitsize)), 'x': x} def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - if self.exp_window_size > 1 or self.mult_window_size > 1: + if self.exp_window_size is not None and self.mult_window_size is not None: bloq_counts = (self.exp_bitsize // self.exp_window_size) * (self.x_bitsize // self.mult_window_size) - return {self.qrom: 2 * bloq_counts, self.qrom.adjoint(): 2 * bloq_counts, Add(): bloq_counts, SubtractFrom(): bloq_counts, - Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + return {}#return {self.qrom: 2 * bloq_counts, self.qrom.adjoint(): 2 * bloq_counts, Add(): bloq_counts, SubtractFrom(): bloq_counts, + #Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} else: k = ssa.new_symbol('k') return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} @@ -253,11 +257,23 @@ def _modexp() -> ModExp: return modexp +@bloq_example(generalizer=(ignore_split_join, _generalize_k)) +def _modexp_window() -> ModExp: + modexp_window = ModExp.make_for_shor(big_n=13 * 17, g=9, exp_window_size=2, mult_window_size=2) + return modexp_window + + @bloq_example def _modexp_symb() -> ModExp: g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') modexp_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x) return modexp_symb +@bloq_example +def _modexp_window_symb() -> ModExp: + g, N, n_e, n_x, w_e, w_m = sympy.symbols('g N n_e, n_x w_e w_m') + modexp_window_symb = ModExp(base=g, mod=N, exp_bitsize=n_e, x_bitsize=n_x, exp_window_size=w_e, mult_window_size=w_m) + return modexp_window_symb + -_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp, _modexp_symb)) +_RSA_MODEXP_DOC = BloqDocSpec(bloq_cls=ModExp, examples=(_modexp_small, _modexp, _modexp_symb, _modexp_window, _modexp_window_symb)) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index ce04f7c3d..2979257f6 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -21,7 +21,7 @@ from qualtran import Bloq from qualtran.bloqs.bookkeeping import Join, Split -from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, _modexp_small, _modexp_symb, ModExp +from qualtran.bloqs.factoring.rsa.rsa_mod_exp import _modexp, _modexp_small, _modexp_symb, _modexp_window, _modexp_window_symb, ModExp from qualtran.bloqs.mod_arithmetic import CModMulK from qualtran.drawing import Text from qualtran.resource_counting import SympySymbolAllocator @@ -95,7 +95,7 @@ def test_mod_exp_t_complexity(): assert tcomp.t > 0 -@pytest.mark.parametrize('bloq', [_modexp, _modexp_symb, _modexp_small]) +@pytest.mark.parametrize('bloq', [_modexp, _modexp_symb, _modexp_small, _modexp_window, _modexp_window_symb]) def test_modexp(bloq_autotester, bloq): bloq_autotester(bloq) From d7460a4bcf14a4ee8268e9e3ba4656556f4c19d7 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Tue, 15 Oct 2024 20:24:54 -0700 Subject: [PATCH 19/21] stash current changes --- .../bloqs/factoring/rsa/rsa_mod_exp_test.py | 33 +++++++++++++++++-- qualtran/serialization/resolver_dict.py | 4 +-- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index 2979257f6..1809ada0f 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -48,12 +48,41 @@ def test_mod_exp_consistent_classical(): base = rs.randint(1, mod) bloq = ModExp(base=base, exp_bitsize=ne, x_bitsize=n, mod=mod) - ret1 = bloq.call_classically(exponent=exponent, x=1) - ret2 = bloq.decompose_bloq().call_classically(exponent=exponent, x=1) + ret1 = bloq.call_classically(exponent=exponent) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) assert len(ret1) == len(ret2) for i in range(len(ret1)): np.testing.assert_array_equal(ret1[i], ret2[i]) +@pytest.mark.parametrize('p', [13, 37, 131]) +def test_mod_exp_window_consistent_classical_fast(p): + bloq = ModExp.make_for_shor(big_n=p, exp_window_size=2, mult_window_size=2) + + rs = np.random.RandomState(52) + n_x = int(np.ceil(np.log2(p))) + exponent = rs.randint(1, 2**n_x) + + ret1 = bloq.call_classically(exponent=exponent) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) + +@pytest.mark.slow +@pytest.mark.parametrize('p, w_e, w_m', [(p, w_e, w_m) for p in (13, 37) for w_e in range(1, (2 * int(np.ceil(np.log2(p)))) + 1) if (2 * int(np.ceil(np.log2(p)))) % w_e == 0 for w_m in range(1, int(np.ceil(np.log2(p))) + 1) if int(np.ceil(np.log2(p))) % w_m == 0]) +def test_mod_exp_window_consistent_classical(p, w_e, w_m): + bloq = ModExp.make_for_shor(big_n=p, exp_window_size=w_e, mult_window_size=w_m) + + rs = np.random.RandomState(52) + n_x = int(np.ceil(np.log2(p))) + exponent = rs.randint(1, 2**n_x) + + ret1 = bloq.call_classically(exponent=exponent) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) + def test_modexp_symb_manual(): g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 69b6e6b97..300803847 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -97,8 +97,7 @@ import qualtran.bloqs.data_loading.qroam_clean import qualtran.bloqs.data_loading.qrom import qualtran.bloqs.data_loading.select_swap_qrom - -qualtran.bloqs.factoring._factoring_shims +import qualtran.bloqs.factoring._factoring_shims import qualtran.bloqs.factoring.rsa import qualtran.bloqs.for_testing.atom import qualtran.bloqs.for_testing.casting @@ -328,6 +327,7 @@ "qualtran.bloqs.data_loading.qrom.QROM": qualtran.bloqs.data_loading.qrom.QROM, "qualtran.bloqs.data_loading.qroam_clean.QROAMClean": qualtran.bloqs.data_loading.qroam_clean.QROAMClean, "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjoint, + "qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper": qualtran.bloqs.data_loading.qroam_clean.QROAMCleanAdjointWrapper, "qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM": qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM, "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd": qualtran.bloqs.mod_arithmetic.mod_addition.ModAdd, From 36e42b3df2d2d2ce3d0709498c2207b253c63a69 Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 23 Oct 2024 14:55:03 -0700 Subject: [PATCH 20/21] Partial bugfix windowed arithmetic --- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 41 +++++++++++++------ .../bloqs/factoring/rsa/rsa_mod_exp_test.py | 35 +++++++++------- qualtran/serialization/resolver_dict.py | 2 +- 3 files changed, 50 insertions(+), 28 deletions(-) diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 090d94be4..0eba14a32 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -170,14 +170,14 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st mi = np.split(np.array(a), self.x_bitsize // self.mult_window_size) for j in range(self.x_bitsize // self.mult_window_size): data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) - ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) - mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) + ei_i = bb.join(ei[(self.exp_bitsize // self.exp_window_size) - i - 1], QUInt((self.exp_window_size))) + mi_i = bb.join(mi[(self.x_bitsize // self.mult_window_size) - j - 1], QUInt((self.mult_window_size))) ei_i, mi_i, t, *junk = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) t, b = bb.add(ModAdd(self.x_bitsize, self.mod), x=t, y=b) junk_mapping = {f'junk_target{i}_': junk[i] for i in range(len(junk))} ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t, **junk_mapping) - ei[i] = bb.split(ei_i) - mi[j] = bb.split(mi_i) + ei[(self.exp_bitsize // self.exp_window_size) - i - 1] = bb.split(ei_i) + mi[(self.x_bitsize // self.mult_window_size) - j - 1] = bb.split(mi_i) a = np.concatenate(mi, axis=None) a = bb.join(a, QUInt(self.x_bitsize)) @@ -186,14 +186,14 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st mi = np.split(np.array(b), self.x_bitsize // self.mult_window_size) for j in range(self.x_bitsize // self.mult_window_size): data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) - ei_i = bb.join(ei[i], QUInt((self.exp_window_size))) - mi_i = bb.join(mi[j], QUInt((self.mult_window_size))) + ei_i = bb.join(ei[(self.exp_bitsize // self.exp_window_size) - i - 1], QUInt((self.exp_window_size))) + mi_i = bb.join(mi[(self.x_bitsize // self.mult_window_size) - j - 1], QUInt((self.mult_window_size))) ei_i, mi_i, t, *junk = bb.add(self.qrom(data), selection0=ei_i, selection1=mi_i) t, a = bb.add(ModSub(QUInt(self.x_bitsize), self.mod), x=t, y=a) junk_mapping = {f'junk_target{i}_': junk[i] for i in range(len(junk))} ei_i, mi_i = bb.add(self.qrom(data).adjoint(), selection0=ei_i, selection1=mi_i, target0_=t, **junk_mapping) - ei[i] = bb.split(ei_i) - mi[j] = bb.split(mi_i) + ei[(self.exp_bitsize // self.exp_window_size) - i - 1] = bb.split(ei_i) + mi[(self.x_bitsize // self.mult_window_size) - j - 1] = bb.split(mi_i) b = np.concatenate(mi, axis=None) @@ -217,9 +217,26 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': if self.exp_window_size is not None and self.mult_window_size is not None: - bloq_counts = (self.exp_bitsize // self.exp_window_size) * (self.x_bitsize // self.mult_window_size) - return {}#return {self.qrom: 2 * bloq_counts, self.qrom.adjoint(): 2 * bloq_counts, Add(): bloq_counts, SubtractFrom(): bloq_counts, - #Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + cg = {IntState(val=1, bitsize=self.x_bitsize): 1, Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + + k = self.base + for i in range(self.exp_bitsize // self.exp_window_size): + kes = [pow(k, 2**i * x_e, self.mod) for x_e in range(2**self.exp_window_size)] + kes_inv = [pow(x_e, -1, self.mod) for x_e in kes] + + for j in range(self.x_bitsize // self.mult_window_size): + data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) + cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 + cg[ModAdd(self.x_bitsize, self.mod)] = cg.get(ModAdd(self.x_bitsize, self.mod), 0) + 1 + cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 + + for j in range(self.x_bitsize // self.mult_window_size): + data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) + cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 + cg[ModSub(QUInt(self.x_bitsize), self.mod)] = cg.get(ModSub(QUInt(self.x_bitsize), self.mod), 0) + 1 + cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 + + return cg else: k = ssa.new_symbol('k') return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1} @@ -259,7 +276,7 @@ def _modexp() -> ModExp: @bloq_example(generalizer=(ignore_split_join, _generalize_k)) def _modexp_window() -> ModExp: - modexp_window = ModExp.make_for_shor(big_n=13 * 17, g=9, exp_window_size=2, mult_window_size=2) + modexp_window = ModExp.make_for_shor(big_n=13 * 17, g=9, exp_window_size=8, mult_window_size=4) return modexp_window diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py index 1809ada0f..83014a0ea 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp_test.py @@ -54,35 +54,40 @@ def test_mod_exp_consistent_classical(): for i in range(len(ret1)): np.testing.assert_array_equal(ret1[i], ret2[i]) -@pytest.mark.parametrize('p', [13, 37, 131]) +@pytest.mark.parametrize('p', [11, 13]) def test_mod_exp_window_consistent_classical_fast(p): bloq = ModExp.make_for_shor(big_n=p, exp_window_size=2, mult_window_size=2) rs = np.random.RandomState(52) n_x = int(np.ceil(np.log2(p))) - exponent = rs.randint(1, 2**n_x) + + for _ in range(10): + exponent = rs.randint(1, 2**n_x) - ret1 = bloq.call_classically(exponent=exponent) - ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) - assert len(ret1) == len(ret2) - for i in range(len(ret1)): - np.testing.assert_array_equal(ret1[i], ret2[i]) + ret1 = bloq.call_classically(exponent=exponent) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) +''' @pytest.mark.slow -@pytest.mark.parametrize('p, w_e, w_m', [(p, w_e, w_m) for p in (13, 37) for w_e in range(1, (2 * int(np.ceil(np.log2(p)))) + 1) if (2 * int(np.ceil(np.log2(p)))) % w_e == 0 for w_m in range(1, int(np.ceil(np.log2(p))) + 1) if int(np.ceil(np.log2(p))) % w_m == 0]) +@pytest.mark.parametrize('p, w_e, w_m', [(p, w_e, w_m) for p in (7, 11, 13) for w_e in range(1, (2 * int(np.ceil(np.log2(p)))) + 1) if (2 * int(np.ceil(np.log2(p)))) % w_e == 0 for w_m in range(1, int(np.ceil(np.log2(p))) + 1) if int(np.ceil(np.log2(p))) % w_m == 0]) def test_mod_exp_window_consistent_classical(p, w_e, w_m): bloq = ModExp.make_for_shor(big_n=p, exp_window_size=w_e, mult_window_size=w_m) rs = np.random.RandomState(52) n_x = int(np.ceil(np.log2(p))) - exponent = rs.randint(1, 2**n_x) - - ret1 = bloq.call_classically(exponent=exponent) - ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) - assert len(ret1) == len(ret2) - for i in range(len(ret1)): - np.testing.assert_array_equal(ret1[i], ret2[i]) + for _ in range(10): + exponent = rs.randint(1, 2**n_x) + + ret1 = bloq.call_classically(exponent=exponent) + ret2 = bloq.decompose_bloq().call_classically(exponent=exponent) + assert len(ret1) == len(ret2) + for i in range(len(ret1)): + np.testing.assert_array_equal(ret1[i], ret2[i]) +''' def test_modexp_symb_manual(): g, N, n_e, n_x = sympy.symbols('g N n_e, n_x') diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index 300803847..b1919da7f 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -336,7 +336,7 @@ "qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK": qualtran.bloqs.mod_arithmetic.mod_addition.CModAddK, "qualtran.bloqs.mod_arithmetic.mod_addition.CtrlScaleModAdd": qualtran.bloqs.mod_arithmetic.CtrlScaleModAdd, "qualtran.bloqs.mod_arithmetic.ModAdd": qualtran.bloqs.mod_arithmetic.ModAdd, - "qualtran.bloqs.mod_arithmetic.ModSub": qualtran.bloqs.mod_arithmetic.ModSub, + "qualtran.bloqs.mod_arithmetic.mod_subtraction.ModSub": qualtran.bloqs.mod_arithmetic.mod_subtraction.ModSub, "qualtran.bloqs.mod_arithmetic.CModSub": qualtran.bloqs.mod_arithmetic.CModSub, "qualtran.bloqs.mod_arithmetic.mod_subtraction.ModNeg": qualtran.bloqs.mod_arithmetic.mod_subtraction.ModNeg, "qualtran.bloqs.mod_arithmetic.mod_subtraction.CModNeg": qualtran.bloqs.mod_arithmetic.mod_subtraction.CModNeg, From edbcc515862ae24c438d9c9265b9f1e481e0558d Mon Sep 17 00:00:00 2001 From: Frankie Papa Date: Wed, 23 Oct 2024 15:43:50 -0700 Subject: [PATCH 21/21] stash changes for now --- qualtran/bloqs/factoring/_factoring_shims.py | 3 +- qualtran/bloqs/factoring/rsa/rsa_mod_exp.py | 53 ++++++++++++-------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/qualtran/bloqs/factoring/_factoring_shims.py b/qualtran/bloqs/factoring/_factoring_shims.py index 40440d7f1..896e103b8 100644 --- a/qualtran/bloqs/factoring/_factoring_shims.py +++ b/qualtran/bloqs/factoring/_factoring_shims.py @@ -13,9 +13,8 @@ # limitations under the License. from functools import cached_property -from typing import Optional, Tuple, Union +from typing import Optional, Tuple -import sympy from attrs import frozen from qualtran import Bloq, CompositeBloq, DecomposeTypeError, QBit, Register, Side, Signature diff --git a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py index 313c3311b..1474b6e4c 100644 --- a/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py +++ b/qualtran/bloqs/factoring/rsa/rsa_mod_exp.py @@ -75,6 +75,9 @@ class ModExp(Bloq): [How to factor 2048 bit RSA integers in 8 hours using 20 million noisy qubits](https://arxiv.org/abs/1905.09749). Gidney and Ekerå. 2019. + [Circuit for Shor's algorithm using 2n+3 qubits](https://arxiv.org/abs/quant-ph/0205095). + Stephane Beauregard. 2003. + [Windowed quantum arithmetic](https://arxiv.org/abs/1905.07682). Craig Gidney. 2019. """ @@ -133,7 +136,7 @@ def qrom(self, data): log_block_sizes = (0,) return QROAMClean( [ - Shaped((2**(self.exp_window_size+self.mult_window_size),)), + data, ], selection_bitsizes=(self.exp_window_size, self.mult_window_size), target_bitsizes=(self.x_bitsize,), @@ -218,26 +221,34 @@ def build_composite_bloq(self, bb: 'BloqBuilder', exponent: 'Soquet') -> Dict[st def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': if self.exp_window_size is not None and self.mult_window_size is not None: - cg = {IntState(val=1, bitsize=self.x_bitsize): 1, Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} - - k = self.base - for i in range(self.exp_bitsize // self.exp_window_size): - kes = [pow(k, 2**i * x_e, self.mod) for x_e in range(2**self.exp_window_size)] - kes_inv = [pow(x_e, -1, self.mod) for x_e in kes] - - for j in range(self.x_bitsize // self.mult_window_size): - data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) - cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 - cg[ModAdd(self.x_bitsize, self.mod)] = cg.get(ModAdd(self.x_bitsize, self.mod), 0) + 1 - cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 - - for j in range(self.x_bitsize // self.mult_window_size): - data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) - cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 - cg[ModSub(QUInt(self.x_bitsize), self.mod)] = cg.get(ModSub(QUInt(self.x_bitsize), self.mod), 0) + 1 - cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 - - return cg + if is_symbolic(self.exp_window_size, self.mult_window_size): + num_iterations = self.exp_bitsize // self.exp_window_size + return {self.qrom(Shaped((2**(self.exp_window_size+self.mult_window_size),))): 1, + self.qrom(Shaped((2**(self.exp_window_size+self.mult_window_size),))).adjoint(): 1, + ModAdd(self.x_bitsize, self.mod): 1, + ModSub(QUInt(self.x_bitsize), self.mod): 1, + IntState(val=1, bitsize=self.x_bitsize): 1, Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + else: + cg = {IntState(val=1, bitsize=self.x_bitsize): 1, Swap(self.x_bitsize): self.exp_bitsize // self.exp_window_size} + + k = self.base + for i in range(self.exp_bitsize // self.exp_window_size): + kes = [pow(k, 2**i * x_e, self.mod) for x_e in range(2**self.exp_window_size)] + kes_inv = [pow(x_e, -1, self.mod) for x_e in kes] + + for j in range(self.x_bitsize // self.mult_window_size): + data = list([(ke * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke in kes) + cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 + cg[ModAdd(self.x_bitsize, self.mod)] = cg.get(ModAdd(self.x_bitsize, self.mod), 0) + 1 + cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 + + for j in range(self.x_bitsize // self.mult_window_size): + data = list([(ke_inv * f * 2**j) % self.mod for f in range(2**self.mult_window_size)] for ke_inv in kes_inv) + cg[self.qrom(data)] = cg.get(self.qrom(data), 0) + 1 + cg[ModSub(QUInt(self.x_bitsize), self.mod)] = cg.get(ModSub(QUInt(self.x_bitsize), self.mod), 0) + 1 + cg[self.qrom(data).adjoint()] = cg.get(self.qrom(data).adjoint(), 0) + 1 + + return cg else: k = ssa.new_symbol('k') return {self._CtrlModMul(k=k): self.exp_bitsize, IntState(val=1, bitsize=self.x_bitsize): 1}