From 343002cd345ed8400578c955ab4bfbd56c93cfdc Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Wed, 11 Sep 2024 16:30:36 -0700 Subject: [PATCH] ECC --- qualtran/bloqs/arithmetic/_shims.py | 18 ++++++-- qualtran/bloqs/arithmetic/addition.ipynb | 5 ++- qualtran/bloqs/arithmetic/addition.py | 12 +++--- qualtran/bloqs/arithmetic/comparison.py | 6 ++- .../bloqs/arithmetic/controlled_addition.py | 3 ++ qualtran/bloqs/factoring/ecc/_ecc_shims.py | 29 +++++++++++-- qualtran/bloqs/factoring/ecc/ec_add.ipynb | 20 +++++++-- qualtran/bloqs/factoring/ecc/ec_add.py | 13 +++++- qualtran/bloqs/factoring/ecc/ec_add_r.py | 43 +++++++++++++++++-- .../factoring/ecc/ec_phase_estimate_r.py | 25 ++++++++--- qualtran/bloqs/factoring/ecc/ecc.ipynb | 29 +++++++++++-- .../factoring/ecc/find_ecc_private_key.py | 14 ++++-- qualtran/bloqs/mod_arithmetic/_shims.py | 27 +++++++++--- qualtran/bloqs/mod_arithmetic/mod_addition.py | 11 +++-- .../mod_arithmetic/mod_multiplication.py | 4 ++ qualtran/resource_counting/classify_bloqs.py | 4 ++ 16 files changed, 214 insertions(+), 49 deletions(-) diff --git a/qualtran/bloqs/arithmetic/_shims.py b/qualtran/bloqs/arithmetic/_shims.py index 0d40daba7..f5da122cf 100644 --- a/qualtran/bloqs/arithmetic/_shims.py +++ b/qualtran/bloqs/arithmetic/_shims.py @@ -24,7 +24,7 @@ from qualtran import Bloq, QBit, QUInt, Register, Signature from qualtran.bloqs.basic_gates import Toffoli -from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator +from qualtran.resource_counting import BloqCountDictT, CostKey, QubitCount, SympySymbolAllocator @frozen @@ -36,7 +36,13 @@ def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit(), shape=(self.n,)), Register('target', QBit())]) def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: - return {Toffoli(): self.n - 2} + return {Toffoli(): self.n - 1} + + def my_static_costs(self, cost_key: 'CostKey'): + # TODO https://github.com/quantumlib/Qualtran/issues/1261 + if cost_key == QubitCount(): + return self.n + 1 + return NotImplemented @frozen @@ -51,7 +57,7 @@ def signature(self) -> 'Signature': ) def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: - # litinski + # Litinski 2023. Figure/Table 3. return {Toffoli(): self.n} @@ -62,3 +68,9 @@ class CHalf(Bloq): @cached_property def signature(self) -> 'Signature': return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.n))]) + + def build_call_graph(self, ssa: SympySymbolAllocator) -> BloqCountDictT: + # It's unclear what this operation is (as part of the ModInv circuit). + # If we assume it's a modular halving, then we can just run `ModDbl` + # backwards, and the cost is the same. + return {(Toffoli(), 2 * self.n)} diff --git a/qualtran/bloqs/arithmetic/addition.ipynb b/qualtran/bloqs/arithmetic/addition.ipynb index ab22770e1..d44730614 100644 --- a/qualtran/bloqs/arithmetic/addition.ipynb +++ b/qualtran/bloqs/arithmetic/addition.ipynb @@ -38,7 +38,7 @@ "## `Add`\n", "An n-bit addition gate.\n", "\n", - "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a+b\\rangle$ using $4n - 4 T$ gates.\n", + "Implements $U|a\\rangle|b\\rangle \\rightarrow |a\\rangle|a+b\\rangle$ using $n-1$ AND gates.\n", "\n", "#### Parameters\n", " - `a_dtype`: Quantum datatype used to represent the integer a.\n", @@ -49,7 +49,8 @@ " - `b`: A b_dtype.bitsize-sized input/output register (register b above). \n", "\n", "#### References\n", - " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n" + " - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). Gidney 2018. The construction used in this bloq, evolved from [2].\n", + " - [A new quantum ripple-carry addition circuit](https://arxiv.org/abs/quant-ph/0410184). Cuccaro et. al. 2004.\n" ] }, { diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 17ca331a3..c1013bed9 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -76,7 +76,7 @@ class Add(Bloq): r"""An n-bit addition gate. - Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a+b\rangle$ using $4n - 4 T$ gates. + Implements $U|a\rangle|b\rangle \rightarrow |a\rangle|a+b\rangle$ using $n-1$ AND gates. Args: a_dtype: Quantum datatype used to represent the integer a. @@ -89,7 +89,11 @@ class Add(Bloq): b: A b_dtype.bitsize-sized input/output register (register b above). References: - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648) + [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). + Gidney 2018. The construction used in this bloq, evolved from [2]. + + [A new quantum ripple-carry addition circuit](https://arxiv.org/abs/quant-ph/0410184). + Cuccaro et. al. 2004. """ a_dtype: Union[QInt, QUInt, QMontgomeryUInt] = field() @@ -479,10 +483,6 @@ def build_composite_bloq( # Rejoin the qubits representing k for in-place addition. k = bb.join(k_split, dtype=x.reg.dtype) - if not isinstance(x.reg.dtype, (QInt, QUInt, QMontgomeryUInt)): - raise ValueError( - "Only QInt, QUInt and QMontgomerUInt types are supported for composite addition." - ) k, x = bb.add(Add(x.reg.dtype, x.reg.dtype), a=k, b=x) # Resplit the k qubits in order to undo the original bit flips to go from the binary diff --git a/qualtran/bloqs/arithmetic/comparison.py b/qualtran/bloqs/arithmetic/comparison.py index ff4986262..d8ccbd8e2 100644 --- a/qualtran/bloqs/arithmetic/comparison.py +++ b/qualtran/bloqs/arithmetic/comparison.py @@ -749,8 +749,8 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', a: Soquet, b: Soquet, target: SoquetT ) -> Dict[str, 'SoquetT']: - if isinstance(self.bitsize, sympy.Expr): - raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") + if is_symbolic(self.bitsize): + raise DecomposeTypeError(f'Symbolic decomposition is not supported for {self}') # Base Case: Comparing two qubits. # Signed doesn't matter because we can't represent signed integers with 1 qubit. @@ -1080,6 +1080,8 @@ def build_composite_bloq( a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a) b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=b) else: + if self.dtype.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symoblic {self}.") a = bb.join(np.concatenate([[bb.allocate(1)], bb.split(a)])) b = bb.join(np.concatenate([[bb.allocate(1)], bb.split(b)])) diff --git a/qualtran/bloqs/arithmetic/controlled_addition.py b/qualtran/bloqs/arithmetic/controlled_addition.py index cfe4bcc5d..d5aa44380 100644 --- a/qualtran/bloqs/arithmetic/controlled_addition.py +++ b/qualtran/bloqs/arithmetic/controlled_addition.py @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeTypeError, QBit, QInt, QUInt, @@ -134,6 +135,8 @@ def wire_symbol(self, soq: 'Soquet') -> 'WireSymbol': def build_composite_bloq( self, bb: 'BloqBuilder', ctrl: 'Soquet', a: 'Soquet', b: 'Soquet' ) -> Dict[str, 'SoquetT']: + if self.a_dtype.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") a_arr = bb.split(a) ctrl_q = bb.split(ctrl)[0] ancilla_arr = [] diff --git a/qualtran/bloqs/factoring/ecc/_ecc_shims.py b/qualtran/bloqs/factoring/ecc/_ecc_shims.py index 4b602e73f..f6c267738 100644 --- a/qualtran/bloqs/factoring/ecc/_ecc_shims.py +++ b/qualtran/bloqs/factoring/ecc/_ecc_shims.py @@ -13,12 +13,14 @@ # limitations under the License. from functools import cached_property -from typing import Optional, Tuple +from typing import Optional, Sequence, Tuple +import attrs from attrs import frozen -from qualtran import Bloq, CompositeBloq, DecomposeTypeError, QBit, Register, Side, Signature +from qualtran import Bloq, DecomposeTypeError, QBit, QUInt, Register, Side, Signature from qualtran.drawing import RarrowTextBox, Text, WireSymbol +from qualtran.resource_counting import CostKey, QubitCount @frozen @@ -41,5 +43,24 @@ def wire_symbol( return RarrowTextBox('MeasQFT') raise ValueError(f'Unrecognized register name {reg.name}') - def cost_attrs(self): - return [('n', self.n)] + def my_static_costs(self, cost_key: 'CostKey'): + # TODO https://github.com/quantumlib/Qualtran/issues/1261 + if cost_key == QubitCount(): + return self.n + return NotImplemented + + +@frozen +class SimpleQROM(Bloq): + selection_bitsize: int + targets: Sequence[Tuple[str, int]] = attrs.field(converter=tuple) + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [Register('selection', QUInt(self.selection_bitsize))] + + [Register(tname, QUInt(tsize)) for tname, tsize in self.targets] + ) + + def __str__(self): + return 'QROM' diff --git a/qualtran/bloqs/factoring/ecc/ec_add.ipynb b/qualtran/bloqs/factoring/ecc/ec_add.ipynb index 543458c8c..dd4e5a7b5 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add.ipynb +++ b/qualtran/bloqs/factoring/ecc/ec_add.ipynb @@ -91,6 +91,20 @@ "ec_add = ECAdd(n, mod=p)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "cdbe32a7", + "metadata": { + "cq.autogen": "ECAdd.ec_add_256" + }, + "outputs": [], + "source": [ + "ec_add_256 = ECAdd(\n", + " n=256, mod=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF\n", + ")" + ] + }, { "cell_type": "markdown", "id": "39210af4", @@ -111,8 +125,8 @@ "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([ec_add],\n", - " ['`ec_add`'])" + "show_bloqs([ec_add, ec_add_256],\n", + " ['`ec_add`', '`ec_add_256`'])" ] }, { @@ -157,7 +171,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/bloqs/factoring/ecc/ec_add.py b/qualtran/bloqs/factoring/ecc/ec_add.py index e0ff64e6a..da2ea5f8e 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add.py +++ b/qualtran/bloqs/factoring/ecc/ec_add.py @@ -64,7 +64,8 @@ def signature(self) -> 'Signature': ) def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # litinksi + # Litinski 2023. + # These counts are transcribed from the table in Figure 5. return { MultiCToffoli(n=self.n): 18, ModAdd(bitsize=self.n, mod=self.mod): 3, @@ -86,4 +87,12 @@ def _ec_add() -> ECAdd: return ec_add -_EC_ADD_DOC = BloqDocSpec(bloq_cls=ECAdd, examples=[_ec_add]) +@bloq_example +def _ec_add_256() -> ECAdd: + ec_add_256 = ECAdd( + n=256, mod=0xFFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF + ) + return ec_add_256 + + +_EC_ADD_DOC = BloqDocSpec(bloq_cls=ECAdd, examples=[_ec_add, _ec_add_256]) diff --git a/qualtran/bloqs/factoring/ecc/ec_add_r.py b/qualtran/bloqs/factoring/ecc/ec_add_r.py index 7596e120d..602a169ad 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_add_r.py @@ -18,10 +18,23 @@ import sympy from attrs import frozen -from qualtran import Bloq, bloq_example, BloqDocSpec, QBit, QUInt, Register, Signature +from qualtran import ( + Bloq, + bloq_example, + BloqBuilder, + BloqDocSpec, + QBit, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.simulation.classical_sim import ClassicalValT +from ._ecc_shims import SimpleQROM +from .ec_add import ECAdd from .ec_point import ECPoint @@ -140,6 +153,31 @@ def signature(self) -> 'Signature': ] ) + @cached_property + def lookup_bloq(self) -> SimpleQROM: + return SimpleQROM( + selection_bitsize=self.window_size, + targets=[('a', self.n), ('b', self.n), ('lam', self.n)], + ) + + def build_composite_bloq( + self, bb: 'BloqBuilder', ctrl: 'SoquetT', x: Soquet, y: Soquet + ) -> Dict[str, 'SoquetT']: + ctrl = bb.join(ctrl) + a = bb.allocate(dtype=QUInt(self.n)) + b = bb.allocate(dtype=QUInt(self.n)) + lam = bb.allocate(dtype=QUInt(self.n)) + + mod = self.R.mod + ctrl, a, b, lam = bb.add(self.lookup_bloq, selection=ctrl, a=a, b=b, lam=lam) + a, b, x, y, lam = bb.add(ECAdd(n=self.n, mod=mod), a=a, b=b, x=x, y=y, lam=lam) + ctrl, a, b, lam = bb.add(self.lookup_bloq.adjoint(), selection=ctrl, a=a, b=b, lam=lam) + bb.free(a) + bb.free(b) + bb.free(lam) + + return {'ctrl': bb.split(ctrl), 'x': x, 'y': y} + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': @@ -153,9 +191,6 @@ def wire_symbol( return TextBox(f'$+{self.R.y}$') raise ValueError(f'Unrecognized register name {reg.name}') - def __str__(self): - return f'ECWindowAddR({self.n=})' - @bloq_example def _ec_window_add() -> ECWindowAddR: diff --git a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py index f4ddb9d19..63fa6ada8 100644 --- a/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py +++ b/qualtran/bloqs/factoring/ecc/ec_phase_estimate_r.py @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import functools from functools import cached_property -from typing import Dict +from typing import Dict, Set, Union import sympy from attrs import frozen @@ -23,6 +23,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeNotImplementedError, DecomposeTypeError, QUInt, Register, @@ -34,7 +35,7 @@ from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator from ._ecc_shims import MeasureQFT -from .ec_add_r import ECAddR +from .ec_add_r import ECAddR, ECWindowAddR from .ec_point import ECPoint @@ -48,18 +49,29 @@ class ECPhaseEstimateR(Bloq): Args: n: The bitsize of the elliptic curve points' x and y registers. point: The elliptic curve point to phase estimate against. + window_size: If non-zero, use windowed elliptic curve point addition. """ n: int point: ECPoint + window_size: int = 0 @cached_property def signature(self) -> 'Signature': return Signature([Register('x', QUInt(self.n)), Register('y', QUInt(self.n))]) + @property + def ec_add(self) -> Union[ECAddR, ECWindowAddR]: + if self.window_size == 0: + return functools.partial(ECAddR, n=self.n) + return functools.partial(ECWindowAddR, n=self.n, window_size=self.window_size) + 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`.") + if self.window_size != 0: + raise DecomposeNotImplementedError("We don't support a windowed addition circuit yet.") + 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) @@ -67,8 +79,11 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ 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 build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + return { + (self.ec_add(R=self.point), self.n / (2**self.window_size)), + (MeasureQFT(n=self.n), 1), + } def __str__(self) -> str: return f'PE${self.point}$' diff --git a/qualtran/bloqs/factoring/ecc/ecc.ipynb b/qualtran/bloqs/factoring/ecc/ecc.ipynb index a9196721b..f614c4f91 100644 --- a/qualtran/bloqs/factoring/ecc/ecc.ipynb +++ b/qualtran/bloqs/factoring/ecc/ecc.ipynb @@ -89,7 +89,8 @@ "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", " - `base_point`: The base point $P$ with unknown order $r$ such that $P = [r] P$.\n", - " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$. \n", + " - `public_key`: The public key $Q$ such that $Q = [k] P$ for private key $k$.\n", + " - `window_size`: If non-zero, use windowed elliptic curve point addition. \n", "\n", "#### References\n", " - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). Litinski. 2023. Figure 4 (a).\n" @@ -217,7 +218,8 @@ "\n", "#### Parameters\n", " - `n`: The bitsize of the elliptic curve points' x and y registers.\n", - " - `point`: The elliptic curve point to phase estimate against.\n" + " - `point`: The elliptic curve point to phase estimate against.\n", + " - `window_size`: If non-zero, use windowed elliptic curve point addition.\n" ] }, { @@ -443,7 +445,8 @@ "metadata": {}, "outputs": [], "source": [ - "for j in range(1, 20+1):\n", + "P = ECPoint(15, 13, mod=17, curve_a=0)\n", + "for j in range(1, 21):\n", " bloq = ECAddR(n=5, R=j*P)\n", " ctrl, x, y = bloq.call_classically(ctrl=1, x=P.x, y=P.y)\n", " print(f'+[{j:2d}] P -> ({x:2d}, {y:2d})')" @@ -559,6 +562,24 @@ "show_call_graph(ec_window_add_g)\n", "show_counts_sigma(ec_window_add_sigma)" ] + }, + { + "cell_type": "markdown", + "id": "4c931d44-b612-4a6a-ae0a-65a194026d46", + "metadata": {}, + "source": [ + "### Decomposition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "42ae4927-ed07-48cc-8513-0d19ae17d6c1", + "metadata": {}, + "outputs": [], + "source": [ + "show_bloq(ec_window_add.decompose_bloq(), 'musical_score')" + ] } ], "metadata": { @@ -577,7 +598,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.7" + "version": "3.11.8" } }, "nbformat": 4, diff --git a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py index efaa42ed3..356a53ee3 100644 --- a/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py +++ b/qualtran/bloqs/factoring/ecc/find_ecc_private_key.py @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import functools from functools import cached_property from typing import Dict @@ -66,6 +66,7 @@ class FindECCPrivateKey(Bloq): n: The bitsize of the elliptic curve points' x and y registers. base_point: The base point $P$ with unknown order $r$ such that $P = [r] P$. public_key: The public key $Q$ such that $Q = [k] P$ for private key $k$. + window_size: If non-zero, use windowed elliptic curve point addition. References: [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). @@ -75,6 +76,7 @@ class FindECCPrivateKey(Bloq): n: int base_point: ECPoint public_key: ECPoint + window_size: int = 0 @cached_property def signature(self) -> 'Signature': @@ -92,12 +94,16 @@ def curve_a(self) -> SymbolicInt: raise ValueError("Inconsistent curve parameters in the two points.") return self.base_point.curve_a + @property + def ec_pe_r(self) -> ECPhaseEstimateR: + return functools.partial(ECPhaseEstimateR, n=self.n, window_size=self.window_size) + def build_composite_bloq(self, bb: 'BloqBuilder') -> Dict[str, 'SoquetT']: x = bb.add(IntState(bitsize=self.n, val=self.base_point.x)) y = bb.add(IntState(bitsize=self.n, val=self.base_point.y)) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.base_point), x=x, y=y) - x, y = bb.add(ECPhaseEstimateR(n=self.n, point=self.public_key), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.base_point), x=x, y=y) + x, y = bb.add(self.ec_pe_r(point=self.public_key), x=x, y=y) bb.add(Free(QUInt(self.n)), reg=x) bb.add(Free(QUInt(self.n)), reg=y) @@ -108,7 +114,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': Ry = ssa.new_symbol('Ry') generic_point = ECPoint(Rx, Ry, mod=self.mod, curve_a=self.curve_a) - return {ECPhaseEstimateR(n=self.n, point=generic_point): 2} + return {self.ec_pe_r(point=generic_point): 2} def cost_attrs(self): return [('n', self.n)] diff --git a/qualtran/bloqs/mod_arithmetic/_shims.py b/qualtran/bloqs/mod_arithmetic/_shims.py index c6e630235..3b6bd4ff1 100644 --- a/qualtran/bloqs/mod_arithmetic/_shims.py +++ b/qualtran/bloqs/mod_arithmetic/_shims.py @@ -32,7 +32,6 @@ from qualtran.bloqs.basic_gates import CNOT, CSwap, Swap, Toffoli from qualtran.bloqs.mod_arithmetic.mod_multiplication import ModDbl from qualtran.drawing import Text, TextBox, WireSymbol -from qualtran.symbolics import ceil, log2 if TYPE_CHECKING: from qualtran.resource_counting import BloqCountDictT, SympySymbolAllocator @@ -48,8 +47,12 @@ def signature(self) -> 'Signature': return Signature([Register('x', QUInt(self.n)), Register('out', QUInt(self.n))]) def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # This listing is based off of Haner 2023, fig 15. The order of operations - # matches the order in the figure + # This listing is based off of Haner 2023, fig 15 and Litinski 2023, fig 7 circuit. + # The latter cites Gouzien 2023 for its circuit. + # The order of operations matches the order in the figures + + # The leading-order cost might be off by a factor of n, see the comment in + # `ModInv.build_call_graph`. listing = [ (MultiCToffoli(self.n + 1), 1), (CNOT(), 1), @@ -95,8 +98,15 @@ def signature(self) -> 'Signature': return Signature([Register('x', QUInt(self.n)), Register('out', QUInt(self.n))]) def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # Roetteler - # return {(Toffoli(), 32 * self.n**2 * log2(self.n))} + # Roetteler 2017. + # {(Toffoli(), 32 * self.n**2 * log2(self.n))} + + # Litinski 2023 Table/Figure 8 lists the cost as 26n^2 + 2n. + # We can subtract the 2n from `Negate` and `AddK`, and divide by + # 2n to find that they expect `_ModInvInner` to cost 13n; but we only + # find 12 factors of n. Maybe they counted the 3-bit toffoli as an n-bit toffoli(?) + + # The callees are derived from Litinski 2023, Figure 7 circuit. return { _ModInvInner(n=self.n, mod=self.mod): 2 * self.n, Negate(QUInt(self.n)): 1, @@ -132,8 +142,11 @@ def signature(self) -> 'Signature': ) def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT': - # Roetteler montgomery - return {Toffoli(): ceil(16 * self.n**2 * log2(self.n) - 26.3 * self.n**2)} + # Roetteler 2017 montgomery multiplier + # {(Toffoli(), ceil(16 * self.n**2 * log2(self.n) - 26.3 * self.n**2))} + + # Litinski 2023. Figure/Table 8 + return {(Toffoli(), 2.25 * self.n**2 + 9 * self.n)} def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py index 31f66a847..7977593dd 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, @@ -89,7 +90,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 NotImplementedError(f'symbolic decomposition is not supported for {self}') + 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) @@ -115,7 +116,8 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ x = bb.join(x_split[1:], dtype=QMontgomeryUInt(bitsize=self.bitsize)) # Add constant -p to the y register. - y = bb.add(AddK(bitsize=self.bitsize + 1, k=-1 * self.mod, signed=True, cvs=()), x=y) + # FIXME: signed addition not large enough to fit `self.bitsize`-ed number. + y = bb.add(AddK(bitsize=self.bitsize + 1, k=-1 * self.mod, signed=False, cvs=()), x=y) # Controlled addition of classical constant p if the sign of y after the last addition is # negative. @@ -125,7 +127,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ sign_split = bb.split(sign) sign_split, y = bb.add( - AddK(bitsize=self.bitsize, k=self.mod, signed=True, cvs=(1,)), x=y, ctrls=sign_split + AddK(bitsize=self.bitsize, k=self.mod, signed=False, cvs=(1,)), x=y, ctrls=sign_split ) sign = bb.join(sign_split) @@ -390,6 +392,9 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', ctrl, x: Soquet, y: Soquet ) -> Dict[str, 'SoquetT']: + if self.dtype.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") + 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_multiplication.py b/qualtran/bloqs/mod_arithmetic/mod_multiplication.py index 536cfae6b..47445b757 100644 --- a/qualtran/bloqs/mod_arithmetic/mod_multiplication.py +++ b/qualtran/bloqs/mod_arithmetic/mod_multiplication.py @@ -27,6 +27,7 @@ bloq_example, BloqBuilder, BloqDocSpec, + DecomposeTypeError, QBit, QMontgomeryUInt, QUInt, @@ -82,6 +83,9 @@ def on_classical_vals(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: return {'x': x} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'SoquetT']: + if self.dtype.is_symbolic(): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") + # Allocate ancilla bits for sign and double. lower_bit = bb.allocate(n=1) sign = bb.allocate(n=1) diff --git a/qualtran/resource_counting/classify_bloqs.py b/qualtran/resource_counting/classify_bloqs.py index eae56e52e..d84dd9fa8 100644 --- a/qualtran/resource_counting/classify_bloqs.py +++ b/qualtran/resource_counting/classify_bloqs.py @@ -174,6 +174,7 @@ def bloq_is_clifford(b: Bloq) -> bool: CYGate, CZ, Hadamard, + OnEach, SGate, TwoBitSwap, XGate, @@ -186,6 +187,9 @@ def bloq_is_clifford(b: Bloq) -> bool: if isinstance(b, Adjoint): b = b.subbloq + if isinstance(b, OnEach): + b = b.gate + if isinstance( b, (TwoBitSwap, Hadamard, XGate, ZGate, YGate, ArbitraryClifford, CNOT, CYGate, CZ, SGate) ):