From 05504ce7c11edda407724e71b211e8336a23788a Mon Sep 17 00:00:00 2001 From: Matthew Harrigan Date: Fri, 17 May 2024 19:25:45 +0000 Subject: [PATCH] [mod_arithmetic] Organize modular addition (#907) * [mod_arithmetic] Organize modular addition * C[AddK] -> AddK with cvs and fixes * format and minimize changes * mark notebook test * revert tcompl removal * reconcile with ecc commit * format * Don't test notebooks twice * fix merge conflict: wire symbol * fixes (typing, generally) * format * merge conflicts * lint * more mergy issues --- .github/workflows/ci.yaml | 2 +- dev_tools/autogenerate-bloqs-notebooks-v2.py | 14 +- docs/bloqs/index.rst | 6 + qualtran/bloqs/arithmetic/__init__.py | 10 +- qualtran/bloqs/arithmetic/_shims.py | 10 - qualtran/bloqs/arithmetic/addition.ipynb | 184 ++------- qualtran/bloqs/arithmetic/addition.py | 191 ++++----- qualtran/bloqs/arithmetic/addition_test.py | 89 +---- qualtran/bloqs/arithmetic/comparison.py | 2 +- qualtran/bloqs/factoring/__init__.py | 1 - qualtran/bloqs/factoring/ecc/ec_add.py | 2 +- qualtran/bloqs/factoring/mod_add_test.py | 45 --- qualtran/bloqs/factoring/mod_exp_test.py | 6 + qualtran/bloqs/factoring/mod_mul.py | 12 +- qualtran/bloqs/factoring/mod_mul_test.py | 2 +- qualtran/bloqs/factoring/mod_sub.py | 19 +- qualtran/bloqs/hubbard_model.py | 4 +- qualtran/bloqs/mod_arithmetic/__init__.py | 3 +- .../bloqs/mod_arithmetic/mod_addition.ipynb | 294 ++++++++++++++ .../mod_addition.py} | 376 +++++++++++------- .../bloqs/mod_arithmetic/mod_addition_test.py | 71 ++++ .../state_preparation_via_rotation_test.py | 2 + .../cirq_interop/t_complexity_protocol.py | 3 +- qualtran/serialization/resolver_dict.py | 13 +- .../msft_resource_estimator_interop.ipynb | 8 +- .../msft_resource_estimator_interop_test.py | 21 + 26 files changed, 780 insertions(+), 610 deletions(-) create mode 100644 qualtran/bloqs/mod_arithmetic/mod_addition.ipynb rename qualtran/bloqs/{factoring/mod_add.py => mod_arithmetic/mod_addition.py} (56%) create mode 100644 qualtran/bloqs/mod_arithmetic/mod_addition_test.py create mode 100644 qualtran/surface_code/msft_resource_estimator_interop_test.py diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 51c2cd83e..44c3e7601 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -32,7 +32,7 @@ jobs: pip install -r dev_tools/requirements/envs/pytest.env.txt pip install --no-deps -e . - run: | - check/pytest -m 'not notebooks' + check/pytest -m 'not notebook' pytest-dev-tools: runs-on: ubuntu-latest diff --git a/dev_tools/autogenerate-bloqs-notebooks-v2.py b/dev_tools/autogenerate-bloqs-notebooks-v2.py index b652f2d19..e2fba15a7 100644 --- a/dev_tools/autogenerate-bloqs-notebooks-v2.py +++ b/dev_tools/autogenerate-bloqs-notebooks-v2.py @@ -78,6 +78,7 @@ import qualtran.bloqs.factoring.mod_exp import qualtran.bloqs.hamiltonian_simulation.hamiltonian_simulation_by_gqsp import qualtran.bloqs.mcmt.and_bloq +import qualtran.bloqs.mod_arithmetic.mod_addition import qualtran.bloqs.multiplexers.apply_gate_to_lth_target import qualtran.bloqs.multiplexers.select_pauli_lcu import qualtran.bloqs.phase_estimation.lp_resource_state @@ -291,7 +292,6 @@ bloq_specs=[ qualtran.bloqs.arithmetic.addition._ADD_DOC, qualtran.bloqs.arithmetic.addition._ADD_OOP_DOC, - qualtran.bloqs.arithmetic.addition._SIMPLE_ADD_K_DOC, qualtran.bloqs.arithmetic.addition._ADD_K_DOC, ], ), @@ -367,6 +367,17 @@ ), ] +MOD_ARITHMETIC = [ + NotebookSpecV2( + title='Modular Addition', + module=qualtran.bloqs.mod_arithmetic.mod_addition, + bloq_specs=[ + qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_DOC, + qualtran.bloqs.mod_arithmetic.mod_addition._MOD_ADD_K_DOC, + ], + ) +] + ROT_QFT_PE = [ # -------------------------------------------------------------------------- @@ -544,6 +555,7 @@ ('Basic Gates', BASIC_GATES), ('Chemistry', CHEMISTRY), ('Arithmetic', ARITHMETIC), + ('Modular Arithmetic', MOD_ARITHMETIC), ('Rotations', ROT_QFT_PE), ('Other', OTHER), ] diff --git a/docs/bloqs/index.rst b/docs/bloqs/index.rst index 460600db6..4c05e3fed 100644 --- a/docs/bloqs/index.rst +++ b/docs/bloqs/index.rst @@ -65,6 +65,12 @@ Bloqs Library factoring/ecc/ecc.ipynb factoring/ecc/ec_add.ipynb +.. toctree:: + :maxdepth: 2 + :caption: Modular Arithmetic: + + mod_arithmetic/mod_addition.ipynb + .. toctree:: :maxdepth: 2 :caption: Rotations: diff --git a/qualtran/bloqs/arithmetic/__init__.py b/qualtran/bloqs/arithmetic/__init__.py index b6daf5591..3d53d8e68 100644 --- a/qualtran/bloqs/arithmetic/__init__.py +++ b/qualtran/bloqs/arithmetic/__init__.py @@ -12,13 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -from qualtran.bloqs.arithmetic.addition import ( - Add, - AddConstantMod, - OutOfPlaceAdder, - SimpleAddConstant, - Subtract, -) +from qualtran.bloqs.arithmetic.addition import Add, AddK, OutOfPlaceAdder, Subtract from qualtran.bloqs.arithmetic.comparison import ( BiQubitsMixer, EqualsAConstant, @@ -41,4 +35,4 @@ ) from qualtran.bloqs.arithmetic.sorting import BitonicSort, Comparator -from ._shims import AddK, CHalf, Lt, MultiCToffoli, Negate, Sub +from ._shims import CHalf, Lt, MultiCToffoli, Negate, Sub diff --git a/qualtran/bloqs/arithmetic/_shims.py b/qualtran/bloqs/arithmetic/_shims.py index b2e5ee381..839a1a420 100644 --- a/qualtran/bloqs/arithmetic/_shims.py +++ b/qualtran/bloqs/arithmetic/_shims.py @@ -40,16 +40,6 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: return {(Toffoli(), self.n - 2)} -@frozen -class AddK(Bloq): - n: int - k: int - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('x', QUInt(self.n))]) - - @frozen class Sub(Bloq): n: int diff --git a/qualtran/bloqs/arithmetic/addition.ipynb b/qualtran/bloqs/arithmetic/addition.ipynb index adf7de8a1..ab22770e1 100644 --- a/qualtran/bloqs/arithmetic/addition.ipynb +++ b/qualtran/bloqs/arithmetic/addition.ipynb @@ -307,149 +307,19 @@ }, { "cell_type": "markdown", - "id": "1df2fb29", + "id": "2813f173", "metadata": { - "cq.autogen": "AddConstantMod.bloq_doc.md" + "cq.autogen": "AddK.bloq_doc.md" }, "source": [ - "## `AddConstantMod`\n", - "Applies U(add, M)|x> = |(x + add) % M> if x < M else |x>.\n", - "\n", - "Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t.\n", - " 1. If integer `x` < `mod`: output is `|(x + add) % M>`\n", - " 2. If integer `x` >= `mod`: output is `|x>`.\n", - "\n", - "This condition is needed to ensure that the mapping of all input basis states (i.e. input\n", - "states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus\n", - "the gate is reversible.\n", - "\n", - "Also supports controlled version of the gate by specifying a per qubit control value as a tuple\n", - "of integers passed as `cvs`." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5ef2dab4", - "metadata": { - "cq.autogen": "AddConstantMod.bloq_doc.py" - }, - "outputs": [], - "source": [ - "from qualtran.bloqs.arithmetic import AddConstantMod" - ] - }, - { - "cell_type": "markdown", - "id": "ed3f3c05", - "metadata": { - "cq.autogen": "AddConstantMod.example_instances.md" - }, - "source": [ - "### Example Instances" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "195a8047", - "metadata": { - "cq.autogen": "AddConstantMod.add_k_symb" - }, - "outputs": [], - "source": [ - "n, m, k = sympy.symbols('n m k')\n", - "add_k_symb = AddConstantMod(bitsize=n, mod=m, add_val=k)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "9c3f7fb3", - "metadata": { - "cq.autogen": "AddConstantMod.add_k_small" - }, - "outputs": [], - "source": [ - "add_k_small = AddConstantMod(bitsize=4, mod=7, add_val=1)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "bdc82bc1", - "metadata": { - "cq.autogen": "AddConstantMod.add_k_large" - }, - "outputs": [], - "source": [ - "add_k_large = AddConstantMod(bitsize=64, mod=500, add_val=23)" - ] - }, - { - "cell_type": "markdown", - "id": "b6885dbb", - "metadata": { - "cq.autogen": "AddConstantMod.graphical_signature.md" - }, - "source": [ - "#### Graphical Signature" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "65773e01", - "metadata": { - "cq.autogen": "AddConstantMod.graphical_signature.py" - }, - "outputs": [], - "source": [ - "from qualtran.drawing import show_bloqs\n", - "show_bloqs([add_k_symb, add_k_small, add_k_large],\n", - " ['`add_k_symb`', '`add_k_small`', '`add_k_large`'])" - ] - }, - { - "cell_type": "markdown", - "id": "1a4bf832", - "metadata": { - "cq.autogen": "AddConstantMod.call_graph.md" - }, - "source": [ - "### Call Graph" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61f1568e", - "metadata": { - "cq.autogen": "AddConstantMod.call_graph.py" - }, - "outputs": [], - "source": [ - "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "add_k_symb_g, add_k_symb_sigma = add_k_symb.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(add_k_symb_g)\n", - "show_counts_sigma(add_k_symb_sigma)" - ] - }, - { - "cell_type": "markdown", - "id": "9449e4e9", - "metadata": { - "cq.autogen": "SimpleAddConstant.bloq_doc.md" - }, - "source": [ - "## `SimpleAddConstant`\n", + "## `AddK`\n", "Takes |x> to |x + k> for a classical integer `k`.\n", "\n", - "Applies addition to input register `|x>` given classical integer 'k'.\n", - "\n", - "This is the simple version of constant addition because it involves simply converting the\n", - "classical integer into a quantum parameter and using quantum-quantum addition as opposed to\n", - "designing a bespoke circuit for constant addition based on the classical parameter.\n", + "This construction simply XORs the classical constant into a quantum register and\n", + "applies quantum-quantum addition. This is the lowest T-count algorithm at the expense\n", + "of $n$ auxiliary qubits. This construction also permits an inexpensive controlled version:\n", + "you only need to control the loading of the classical constant which can be done with\n", + "only clifford operations.\n", "\n", "#### Parameters\n", " - `bitsize`: Number of bits used to represent each integer.\n", @@ -461,7 +331,7 @@ " - `x`: A bitsize-sized input register (register x above). \n", "\n", "#### References\n", - " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). Fig 2a\n" + " - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). Haner et. al. 2020. Section 3: Components. \"Integer addition\" and Fig 2a.\n" ] }, { @@ -469,18 +339,18 @@ "execution_count": null, "id": "cd255bf9", "metadata": { - "cq.autogen": "SimpleAddConstant.bloq_doc.py" + "cq.autogen": "AddK.bloq_doc.py" }, "outputs": [], "source": [ - "from qualtran.bloqs.arithmetic import SimpleAddConstant" + "from qualtran.bloqs.arithmetic import AddK" ] }, { "cell_type": "markdown", "id": "7538f9a5", "metadata": { - "cq.autogen": "SimpleAddConstant.example_instances.md" + "cq.autogen": "AddK.example_instances.md" }, "source": [ "### Example Instances" @@ -491,12 +361,12 @@ "execution_count": null, "id": "4305289f", "metadata": { - "cq.autogen": "SimpleAddConstant.simple_add_k_symb" + "cq.autogen": "AddK.add_k" }, "outputs": [], "source": [ "n, k = sympy.symbols('n k')\n", - "simple_add_k_symb = SimpleAddConstant(bitsize=n, k=k)" + "add_k = AddK(bitsize=n, k=k)" ] }, { @@ -504,11 +374,11 @@ "execution_count": null, "id": "f6048819", "metadata": { - "cq.autogen": "SimpleAddConstant.simple_add_k_small" + "cq.autogen": "AddK.add_k_small" }, "outputs": [], "source": [ - "simple_add_k_small = SimpleAddConstant(bitsize=4, k=2, signed=False)" + "add_k_small = AddK(bitsize=4, k=2, signed=False)" ] }, { @@ -516,18 +386,18 @@ "execution_count": null, "id": "b67fd469", "metadata": { - "cq.autogen": "SimpleAddConstant.simple_add_k_large" + "cq.autogen": "AddK.add_k_large" }, "outputs": [], "source": [ - "simple_add_k_large = SimpleAddConstant(bitsize=64, k=-23, signed=True)" + "add_k_large = AddK(bitsize=64, k=-23, signed=True)" ] }, { "cell_type": "markdown", "id": "b8b04228", "metadata": { - "cq.autogen": "SimpleAddConstant.graphical_signature.md" + "cq.autogen": "AddK.graphical_signature.md" }, "source": [ "#### Graphical Signature" @@ -538,20 +408,20 @@ "execution_count": null, "id": "e93e7f2e", "metadata": { - "cq.autogen": "SimpleAddConstant.graphical_signature.py" + "cq.autogen": "AddK.graphical_signature.py" }, "outputs": [], "source": [ "from qualtran.drawing import show_bloqs\n", - "show_bloqs([simple_add_k_small, simple_add_k_large],\n", - " ['`simple_add_k_small`', '`simple_add_k_large`'])" + "show_bloqs([add_k, add_k_small, add_k_large],\n", + " ['`add_k`', '`add_k_small`', '`add_k_large`'])" ] }, { "cell_type": "markdown", "id": "13552795", "metadata": { - "cq.autogen": "SimpleAddConstant.call_graph.md" + "cq.autogen": "AddK.call_graph.md" }, "source": [ "### Call Graph" @@ -562,14 +432,14 @@ "execution_count": null, "id": "d8d6584e", "metadata": { - "cq.autogen": "SimpleAddConstant.call_graph.py" + "cq.autogen": "AddK.call_graph.py" }, "outputs": [], "source": [ "from qualtran.resource_counting.generalizers import ignore_split_join\n", - "simple_add_k_small_g, simple_add_k_small_sigma = simple_add_k_small.call_graph(max_depth=1, generalizer=ignore_split_join)\n", - "show_call_graph(simple_add_k_small_g)\n", - "show_counts_sigma(simple_add_k_small_sigma)" + "add_k_g, add_k_sigma = add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(add_k_g)\n", + "show_counts_sigma(add_k_sigma)" ] }, { diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 7ee299dc6..00bfb5779 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -28,6 +28,7 @@ Union, ) +import attrs import cirq import numpy as np import sympy @@ -35,11 +36,14 @@ from numpy.typing import NDArray from qualtran import ( + AddControlledT, Bloq, bloq_example, BloqBuilder, BloqDocSpec, CompositeBloq, + CtrlSpec, + DecomposeTypeError, GateWithRegisters, QBit, QInt, @@ -383,15 +387,21 @@ def _add_oop_large() -> OutOfPlaceAdder: ) +def _cvs_converter(vv): + if isinstance(vv, (int, np.integer)): + return (int(vv),) + return tuple(int(v) for v in vv) + + @frozen -class SimpleAddConstant(Bloq): +class AddK(Bloq): r"""Takes |x> to |x + k> for a classical integer `k`. - Applies addition to input register `|x>` given classical integer 'k'. - - This is the simple version of constant addition because it involves simply converting the - classical integer into a quantum parameter and using quantum-quantum addition as opposed to - designing a bespoke circuit for constant addition based on the classical parameter. + This construction simply XORs the classical constant into a quantum register and + applies quantum-quantum addition. This is the lowest T-count algorithm at the expense + of $n$ auxiliary qubits. This construction also permits an inexpensive controlled version: + you only need to control the loading of the classical constant which can be done with + only clifford operations. Args: bitsize: Number of bits used to represent each integer. @@ -405,14 +415,13 @@ class SimpleAddConstant(Bloq): x: A bitsize-sized input register (register x above). References: - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580) Fig 2a + [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2001.09580). + Haner et. al. 2020. Section 3: Components. "Integer addition" and Fig 2a. """ - bitsize: Union[int, sympy.Expr] - k: int - cvs: Tuple[int, ...] = field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) + bitsize: 'SymbolicInt' + k: 'SymbolicInt' + cvs: Tuple[int, ...] = field(converter=_cvs_converter, default=()) signed: bool = False @cached_property @@ -445,10 +454,9 @@ def on_classical_vals( def build_composite_bloq( self, bb: 'BloqBuilder', x: Soquet, **regs: SoquetT ) -> Dict[str, 'SoquetT']: - if isinstance(self.bitsize, sympy.Expr): - raise ValueError( - f'Symbolic bitsize {self.bitsize} not supported for SimpleAddConstant.build_composite_bloq' - ) + if isinstance(self.k, sympy.Expr) or isinstance(self.bitsize, sympy.Expr): + raise DecomposeTypeError(f"Cannot decompose symbolic {self}.") + # Assign registers to variables and allocate ancilla bits for classical integer k. if len(self.cvs) > 0: ctrls = regs['ctrls'] @@ -504,134 +512,69 @@ def build_composite_bloq( else: return {'x': x} - def pretty_name(self) -> str: - return f'x += {self.k}' - - -@bloq_example -def _simple_add_k_small() -> SimpleAddConstant: - simple_add_k_small = SimpleAddConstant(bitsize=4, k=2, signed=False) - return simple_add_k_small - - -@bloq_example -def _simple_add_k_large() -> SimpleAddConstant: - simple_add_k_large = SimpleAddConstant(bitsize=64, k=-23, signed=True) - return simple_add_k_large - - -_SIMPLE_ADD_K_DOC = BloqDocSpec( - bloq_cls=SimpleAddConstant, examples=[_simple_add_k_small, _simple_add_k_large] -) - - -@frozen(auto_attribs=True) -class AddConstantMod(GateWithRegisters, cirq.ArithmeticGate): # type: ignore[misc] - """Applies U(add, M)|x> = |(x + add) % M> if x < M else |x>. - - Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t. - 1. If integer `x` < `mod`: output is `|(x + add) % M>` - 2. If integer `x` >= `mod`: output is `|x>`. - - This condition is needed to ensure that the mapping of all input basis states (i.e. input - states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus - the gate is reversible. - - Also supports controlled version of the gate by specifying a per qubit control value as a tuple - of integers passed as `cvs`. - """ + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + loading_cost: Tuple[Bloq, SymbolicInt] + if len(self.cvs) == 0: + loading_cost = (XGate(), self.bitsize) # upper bound; depends on the data. + elif len(self.cvs) == 1: + loading_cost = (CNOT(), self.bitsize) # upper bound; depends on the data. + else: + # Otherwise, use the decomposition + return super().build_call_graph(ssa=ssa) - bitsize: int - mod: int = field() - add_val: int = 1 - cvs: Tuple[int, ...] = field( - converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() - ) + return {loading_cost, (Add(QUInt(self.bitsize)), 1)} - @mod.validator - def _validate_mod(self, attribute, value): - if isinstance(value, sympy.Expr) or isinstance(self.bitsize, sympy.Expr): - return - if not 1 <= value <= 2**self.bitsize: - raise ValueError(f"mod: {value} must be between [1, {2 ** self.bitsize}].") + def get_ctrl_system( + self, ctrl_spec: Optional['CtrlSpec'] = None + ) -> Tuple['Bloq', 'AddControlledT']: + if ctrl_spec is None: + ctrl_spec = CtrlSpec() - @cached_property - def signature(self) -> Signature: if self.cvs: - return Signature( - [Register('ctrl', QUInt(len(self.cvs))), Register('x', QUInt(self.bitsize))] - ) - return Signature([Register('x', QUInt(self.bitsize))]) + # We're already controlled, use default fallback + return super().get_ctrl_system(ctrl_spec) - def registers(self) -> Sequence[Union[int, Sequence[int]]]: - add_reg = (2,) * self.bitsize - control_reg = (2,) * len(self.cvs) - return (control_reg, add_reg) if control_reg else (add_reg,) - - def with_registers(self, *new_registers: Union[int, Sequence[int]]) -> "AddConstantMod": - raise NotImplementedError() - - def _classical_unctrled(self, target_val: int): - if target_val < self.mod: - return (target_val + self.add_val) % self.mod - return target_val - - def apply(self, *args) -> Union[int, Iterable[int]]: - target_val = args[-1] - new_target_val = self._classical_unctrled(target_val) - if self.cvs and args[0] != int(''.join(str(x) for x in self.cvs), 2): - new_target_val = target_val - ret = (args[0], new_target_val) if self.cvs else (new_target_val,) - return ret + if ctrl_spec.num_ctrl_reg != 1: + # Multiple control registers, use default fallback + return super().get_ctrl_system(ctrl_spec) - def on_classical_vals( - self, *, x: int, ctrl: Optional[int] = None - ) -> Dict[str, 'ClassicalValT']: - out = self._classical_unctrled(x) - if self.cvs: - assert ctrl is not None - if ctrl == int(''.join(str(x) for x in self.cvs), 2): - return {'ctrl': ctrl, 'x': out} - else: - return {'ctrl': ctrl, 'x': x} + ((qdtype, cv_shape),) = ctrl_spec.activation_function_dtypes() + if qdtype != QBit(): + # Control values aren't bits, use default fallback + return super().get_ctrl_system(ctrl_spec) - assert ctrl is None - return {'x': out} + # Supported via this class's custom `cvs` attribute. + bloq = attrs.evolve(self, cvs=ctrl_spec.cvs) - def _circuit_diagram_info_(self, _) -> cirq.CircuitDiagramInfo: - wire_symbols = ['@' if b else '@(0)' for b in self.cvs] - wire_symbols += [f"Add_{self.add_val}_Mod_{self.mod}"] * self.bitsize - return cirq.CircuitDiagramInfo(wire_symbols=wire_symbols) - - def __pow__(self, power: int) -> 'AddConstantMod': - return AddConstantMod(self.bitsize, self.mod, add_val=self.add_val * power, cvs=self.cvs) + def _add_ctrled( + bb: 'BloqBuilder', ctrl_soqs: Sequence['SoquetT'], in_soqs: Dict[str, 'SoquetT'] + ) -> Tuple[Iterable['SoquetT'], Iterable['SoquetT']]: + ctrl, x = bb.add_t(bloq, ctrls=ctrl_soqs[0], **in_soqs) + return (ctrl,), (x,) - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return {(Add(QUInt(self.bitsize), QUInt(self.bitsize)), 5)} + return bloq, _add_ctrled @bloq_example -def _add_k_symb() -> AddConstantMod: - n, m, k = sympy.symbols('n m k') - add_k_symb = AddConstantMod(bitsize=n, mod=m, add_val=k) - return add_k_symb +def _add_k() -> AddK: + n, k = sympy.symbols('n k') + add_k = AddK(bitsize=n, k=k) + return add_k @bloq_example -def _add_k_small() -> AddConstantMod: - add_k_small = AddConstantMod(bitsize=4, mod=7, add_val=1) +def _add_k_small() -> AddK: + add_k_small = AddK(bitsize=4, k=2, signed=False) return add_k_small @bloq_example -def _add_k_large() -> AddConstantMod: - add_k_large = AddConstantMod(bitsize=64, mod=500, add_val=23) +def _add_k_large() -> AddK: + add_k_large = AddK(bitsize=64, k=-23, signed=True) return add_k_large -_ADD_K_DOC = BloqDocSpec( - bloq_cls=AddConstantMod, examples=[_add_k_symb, _add_k_small, _add_k_large] -) +_ADD_K_DOC = BloqDocSpec(bloq_cls=AddK, examples=[_add_k, _add_k_small, _add_k_large]) @frozen @@ -723,7 +666,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: ) return { (XGate(), self.b_dtype.bitsize), - (SimpleAddConstant(self.b_dtype.bitsize, k=1), 1), + (AddK(self.b_dtype.bitsize, k=1), 1), (Add(a_dtype, b_dtype), 1), (util_bloqs.Split(self.b_dtype), 1), (util_bloqs.Join(self.b_dtype), 1), @@ -732,7 +675,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: def build_composite_bloq(self, bb: 'BloqBuilder', a: Soquet, b: Soquet) -> Dict[str, 'SoquetT']: b = np.array([bb.add(XGate(), q=q) for q in bb.split(b)]) # 1s complement of b. b = bb.add( - SimpleAddConstant(self.b_dtype.bitsize, k=1), x=bb.join(b, self.b_dtype) + AddK(self.b_dtype.bitsize, k=1), x=bb.join(b, self.b_dtype) ) # 2s complement of b. a_dtype = ( diff --git a/qualtran/bloqs/arithmetic/addition_test.py b/qualtran/bloqs/arithmetic/addition_test.py index d5c404162..6f708d762 100644 --- a/qualtran/bloqs/arithmetic/addition_test.py +++ b/qualtran/bloqs/arithmetic/addition_test.py @@ -17,18 +17,11 @@ import cirq import numpy as np import pytest +import sympy import qualtran.testing as qlt_testing -from qualtran import BloqBuilder, QInt, QUInt -from qualtran._infra.gate_with_registers import get_named_qubits -from qualtran.bloqs.arithmetic.addition import ( - Add, - AddConstantMod, - OutOfPlaceAdder, - SimpleAddConstant, - Subtract, -) -from qualtran.bloqs.arithmetic.comparison_test import identity_map +from qualtran import BloqBuilder, CtrlSpec, QInt, QUInt +from qualtran.bloqs.arithmetic.addition import Add, AddK, OutOfPlaceAdder, Subtract from qualtran.cirq_interop.bit_tools import iter_bits, iter_bits_twos_complement from qualtran.cirq_interop.t_complexity_protocol import TComplexity from qualtran.cirq_interop.testing import ( @@ -249,59 +242,6 @@ def test_add_classical(): assert ret1 == ret2 -@pytest.mark.parametrize('bitsize', [3]) -@pytest.mark.parametrize('mod', [5, 8]) -@pytest.mark.parametrize('add_val', [1, 2]) -@pytest.mark.parametrize('cvs', [[], [0, 1], [1, 0], [1, 1]]) -def test_add_mod_n(bitsize, mod, add_val, cvs): - gate = AddConstantMod(bitsize, mod, add_val=add_val, cvs=cvs) - basis_map = {} - num_cvs = len(cvs) - for x in range(2**bitsize): - y = (x + add_val) % mod if x < mod else x - if not num_cvs: - basis_map[x] = y - continue - for cb in range(2**num_cvs): - inp = f'0b_{cb:0{num_cvs}b}_{x:0{bitsize}b}' - if tuple(int(x) for x in f'{cb:0{num_cvs}b}') == tuple(cvs): - out = f'0b_{cb:0{num_cvs}b}_{y:0{bitsize}b}' - basis_map[int(inp, 2)] = int(out, 2) - else: - basis_map[int(inp, 2)] = int(inp, 2) - - op = gate.on_registers(**get_named_qubits(gate.signature)) - circuit = cirq.Circuit(op) - cirq.testing.assert_equivalent_computational_basis_map(basis_map, circuit) - # Missing cirq stubs - circuit += op**-1 # type: ignore[operator] - cirq.testing.assert_equivalent_computational_basis_map(identity_map(gate.num_qubits()), circuit) - - -def test_add_mod_n_protocols(): - with pytest.raises(ValueError, match="must be between"): - _ = AddConstantMod(3, 10) - add_one = AddConstantMod(3, 5, 1) - add_two = AddConstantMod(3, 5, 2, cvs=[1, 0]) - - assert add_one == AddConstantMod(3, 5, 1) - assert add_one != add_two - assert hash(add_one) != hash(add_two) - assert add_two.cvs == (1, 0) - assert cirq.circuit_diagram_info(add_two).wire_symbols == ('@', '@(0)') + ('Add_2_Mod_5',) * 3 - - -def add_constant_mod_n_ref_t_complexity_(b: AddConstantMod) -> TComplexity: - # Rough cost as given in https://arxiv.org/abs/1905.09749 - return 5 * Add(QUInt(b.bitsize)).t_complexity() - - -@pytest.mark.parametrize('bitsize', [3, 9]) -def test_add_mod_n_gate_counts(bitsize): - bloq = AddConstantMod(bitsize, mod=8, add_val=2, cvs=[0, 1]) - assert bloq.t_complexity() == add_constant_mod_n_ref_t_complexity_(bloq) - - def test_out_of_place_adder(): basis_map = {} gate = OutOfPlaceAdder(bitsize=3) @@ -328,19 +268,26 @@ def test_out_of_place_adder(): qlt_testing.assert_valid_bloq_decomposition(gate**-1) +def test_controlled_add_k(): + n, k = sympy.symbols('n k') + addk = AddK(n, k) + assert addk.controlled() == AddK(n, k, cvs=(1,)) + assert addk.controlled(CtrlSpec(cvs=0)) == AddK(n, k, cvs=(0,)) + + @pytest.mark.parametrize('bitsize', [5]) @pytest.mark.parametrize('k', [5, 8]) @pytest.mark.parametrize('cvs', [[], [0, 1], [1, 0], [1, 1]]) -def test_simple_add_constant_decomp_unsigned(bitsize, k, cvs): - bloq = SimpleAddConstant(bitsize=bitsize, k=k, cvs=cvs, signed=False) +def test_add_k_decomp_unsigned(bitsize, k, cvs): + bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=False) qlt_testing.assert_valid_bloq_decomposition(bloq) @pytest.mark.parametrize('bitsize', [5]) @pytest.mark.parametrize('k', [-5, 8]) @pytest.mark.parametrize('cvs', [[], [0, 1], [1, 0], [1, 1]]) -def test_simple_add_constant_decomp_signed(bitsize, k, cvs): - bloq = SimpleAddConstant(bitsize=bitsize, k=k, cvs=cvs, signed=True) +def test_add_k_decomp_signed(bitsize, k, cvs): + bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=True) qlt_testing.assert_valid_bloq_decomposition(bloq) @@ -353,8 +300,8 @@ def test_simple_add_constant_decomp_signed(bitsize, k, cvs): (5, 1, 2, (1, 0, 1), (0, 0, 0), 2), ], ) -def test_classical_simple_add_constant_unsigned(bitsize, k, x, cvs, ctrls, result): - bloq = SimpleAddConstant(bitsize=bitsize, k=k, cvs=cvs, signed=False) +def test_classical_add_k_unsigned(bitsize, k, x, cvs, ctrls, result): + bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=False) cbloq = bloq.decompose_bloq() bloq_classical = bloq.call_classically(ctrls=ctrls, x=x) cbloq_classical = cbloq.call_classically(ctrls=ctrls, x=x) @@ -369,8 +316,8 @@ def test_classical_simple_add_constant_unsigned(bitsize, k, x, cvs, ctrls, resul # TODO: write tests for signed integer addition (subtraction) # https://github.com/quantumlib/Qualtran/issues/606 @pytest.mark.parametrize('bitsize,k,x,cvs,ctrls,result', [(5, 2, 0, (1, 0), (1, 0), 2)]) -def test_classical_simple_add_constant_signed(bitsize, k, x, cvs, ctrls, result): - bloq = SimpleAddConstant(bitsize=bitsize, k=k, cvs=cvs, signed=True) +def test_classical_add_k_signed(bitsize, k, x, cvs, ctrls, result): + bloq = AddK(bitsize=bitsize, k=k, cvs=cvs, signed=True) cbloq = bloq.decompose_bloq() bloq_classical = bloq.call_classically(ctrls=ctrls, x=x) cbloq_classical = cbloq.call_classically(ctrls=ctrls, x=x) diff --git a/qualtran/bloqs/arithmetic/comparison.py b/qualtran/bloqs/arithmetic/comparison.py index a49c40141..76d35d29c 100644 --- a/qualtran/bloqs/arithmetic/comparison.py +++ b/qualtran/bloqs/arithmetic/comparison.py @@ -692,7 +692,7 @@ class LinearDepthGreaterThan(Bloq): """ bitsize: int - signed: bool + signed: bool = False @property def signature(self): diff --git a/qualtran/bloqs/factoring/__init__.py b/qualtran/bloqs/factoring/__init__.py index ed6c0f56b..94f9d1861 100644 --- a/qualtran/bloqs/factoring/__init__.py +++ b/qualtran/bloqs/factoring/__init__.py @@ -12,6 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .mod_add import CtrlAddK, CtrlModAddK, CtrlScaleModAdd from .mod_exp import ModExp from .mod_mul import CtrlModMul diff --git a/qualtran/bloqs/factoring/ecc/ec_add.py b/qualtran/bloqs/factoring/ecc/ec_add.py index 94c3e138d..c695580cb 100644 --- a/qualtran/bloqs/factoring/ecc/ec_add.py +++ b/qualtran/bloqs/factoring/ecc/ec_add.py @@ -77,7 +77,7 @@ def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: # litinksi return { (MultiCToffoli(n=self.n), 18), - (ModAdd(n=self.n, mod=self.mod), 3), + (ModAdd(bitsize=self.n, mod=self.mod), 3), (CModAdd(n=self.n, mod=self.mod), 2), (ModSub(n=self.n, mod=self.mod), 2), (CModSub(n=self.n, mod=self.mod), 4), diff --git a/qualtran/bloqs/factoring/mod_add_test.py b/qualtran/bloqs/factoring/mod_add_test.py index 7f6e272cb..e69de29bb 100644 --- a/qualtran/bloqs/factoring/mod_add_test.py +++ b/qualtran/bloqs/factoring/mod_add_test.py @@ -1,45 +0,0 @@ -# Copyright 2023 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 typing import cast - -import pytest - -from qualtran.bloqs.factoring.mod_add import CtrlModAddK, CtrlScaleModAdd, MontgomeryModAdd -from qualtran.drawing import Text -from qualtran.testing import assert_valid_bloq_decomposition - - -def test_ctrl_scale_mod_add(): - bloq = CtrlScaleModAdd(k=123, mod=13 * 17, bitsize=8) - assert cast(Text, bloq.wire_symbol(reg=None)).text == 'y += x*123 % 221' - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 8 - - -def test_ctrl_mod_add_k(): - bloq = CtrlModAddK(k=123, mod=13 * 17, bitsize=8) - assert cast(Text, bloq.wire_symbol(reg=None)).text == 'x += 123 % 221' - - counts = bloq.bloq_counts() - ((bloq, n),) = counts.items() - assert n == 5 - - -@pytest.mark.parametrize('bitsize,p', [(1, 1), (2, 3), (5, 8)]) -def test_montgomery_mod_add_decomp(bitsize, p): - bloq = MontgomeryModAdd(bitsize=bitsize, p=p) - assert_valid_bloq_decomposition(bloq) diff --git a/qualtran/bloqs/factoring/mod_exp_test.py b/qualtran/bloqs/factoring/mod_exp_test.py index 32ff8e409..47b554485 100644 --- a/qualtran/bloqs/factoring/mod_exp_test.py +++ b/qualtran/bloqs/factoring/mod_exp_test.py @@ -86,6 +86,12 @@ def generalize(b: Bloq) -> Optional[Bloq]: assert counts1 == counts2 +def test_mod_exp_t_complexity(): + bloq = ModExp(base=8, exp_bitsize=3, x_bitsize=10, mod=50) + tcomp = bloq.t_complexity() + assert tcomp.t > 0 + + def test_modexp(bloq_autotester): bloq_autotester(_modexp) diff --git a/qualtran/bloqs/factoring/mod_mul.py b/qualtran/bloqs/factoring/mod_mul.py index 150276508..129266d39 100644 --- a/qualtran/bloqs/factoring/mod_mul.py +++ b/qualtran/bloqs/factoring/mod_mul.py @@ -31,9 +31,9 @@ Soquet, SoquetT, ) -from qualtran.bloqs.arithmetic.addition import SimpleAddConstant +from qualtran.bloqs.arithmetic.addition import AddK from qualtran.bloqs.basic_gates import CNOT, CSwap, XGate -from qualtran.bloqs.factoring.mod_add import CtrlScaleModAdd +from qualtran.bloqs.mod_arithmetic import CtrlScaleModAdd from qualtran.drawing import Circle, directional_text_box, Text, WireSymbol from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.resource_counting.generalizers import ignore_alloc_free, ignore_split_join @@ -163,9 +163,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque ) # Add constant -p to the x register. - x = bb.add( - SimpleAddConstant(bitsize=self.bitsize + 2, k=-1 * self.p, signed=True, cvs=()), x=x - ) + x = bb.add(AddK(bitsize=self.bitsize + 2, k=-1 * self.p, signed=True, cvs=()), x=x) # Split the three bit pieces again so that we can use the sign to control our constant # addition circuit. @@ -176,9 +174,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque # Add constant p to the x register if the result of the last modular reduction is negative. sign_split = bb.split(sign) sign_split, x = bb.add( - SimpleAddConstant(bitsize=self.bitsize + 1, k=self.p, signed=True, cvs=(1,)), - ctrls=sign_split, - x=x, + AddK(bitsize=self.bitsize + 1, k=self.p, signed=True, cvs=(1,)), ctrls=sign_split, x=x ) sign = bb.join(sign_split) diff --git a/qualtran/bloqs/factoring/mod_mul_test.py b/qualtran/bloqs/factoring/mod_mul_test.py index 5edcb96fd..9b0dd1896 100644 --- a/qualtran/bloqs/factoring/mod_mul_test.py +++ b/qualtran/bloqs/factoring/mod_mul_test.py @@ -21,8 +21,8 @@ import qualtran.testing as qlt_testing from qualtran import Bloq -from qualtran.bloqs.factoring.mod_add import CtrlScaleModAdd from qualtran.bloqs.factoring.mod_mul import _modmul, _modmul_symb, CtrlModMul, MontgomeryModDbl +from qualtran.bloqs.mod_arithmetic import CtrlScaleModAdd from qualtran.bloqs.util_bloqs import Allocate, Free from qualtran.drawing import Text from qualtran.resource_counting import SympySymbolAllocator diff --git a/qualtran/bloqs/factoring/mod_sub.py b/qualtran/bloqs/factoring/mod_sub.py index 813972660..653e730ad 100644 --- a/qualtran/bloqs/factoring/mod_sub.py +++ b/qualtran/bloqs/factoring/mod_sub.py @@ -18,10 +18,10 @@ from attrs import frozen from qualtran import Bloq, QMontgomeryUInt, Register, Signature, Soquet, SoquetT -from qualtran.bloqs.arithmetic.addition import SimpleAddConstant +from qualtran.bloqs.arithmetic.addition import AddK from qualtran.bloqs.basic_gates import CNOT, XGate -from qualtran.bloqs.factoring.mod_add import MontgomeryModAdd from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlX +from qualtran.bloqs.mod_arithmetic import ModAdd if TYPE_CHECKING: from qualtran import BloqBuilder @@ -67,7 +67,6 @@ def on_classical_vals( return {'x': x, 'y': (y - x) % self.p} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: - # Bit flip all qubits in register x. x_split = bb.split(x) for i in range(self.bitsize): @@ -75,16 +74,13 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ x = bb.join(x_split, dtype=QMontgomeryUInt(self.bitsize)) # Add constant p+1 to the x register. - x = bb.add(SimpleAddConstant(bitsize=self.bitsize, k=self.p + 1, signed=False, cvs=()), x=x) + x = bb.add(AddK(bitsize=self.bitsize, k=self.p + 1, signed=False, cvs=()), x=x) # Perform in-place addition on quantum register y. - x, y = bb.add(MontgomeryModAdd(bitsize=self.bitsize, p=self.p), x=x, y=y) + x, y = bb.add(ModAdd(bitsize=self.bitsize, mod=self.p), x=x, y=y) # Add constant -(p+1) to the x register to uncompute the first addition. - x = bb.add( - SimpleAddConstant(bitsize=self.bitsize, k=self.p + 1, signed=False, cvs=()).adjoint(), - x=x, - ) + x = bb.add(AddK(bitsize=self.bitsize, k=self.p + 1, signed=False, cvs=()).adjoint(), x=x) # Bit flip all qubits in register x. x_split = bb.split(x) @@ -129,7 +125,6 @@ def on_classical_vals(self, x: 'ClassicalValT') -> Dict[str, 'ClassicalValT']: return {'x': (-1 * x) % self.p} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'SoquetT']: - # Initialize an ancilla qubit to |1>. ctrl = bb.allocate(n=1) ctrl = bb.add(XGate(), q=ctrl) @@ -151,9 +146,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque # Add constant p+1 to the x register. ctrl_split = bb.split(ctrl) ctrl_split, x = bb.add( - SimpleAddConstant(bitsize=self.bitsize, k=self.p + 1, cvs=(1,), signed=False), - ctrls=ctrl_split, - x=x, + AddK(bitsize=self.bitsize, k=self.p + 1, cvs=(1,), signed=False), ctrls=ctrl_split, x=x ) ctrl = bb.join(ctrl_split) diff --git a/qualtran/bloqs/hubbard_model.py b/qualtran/bloqs/hubbard_model.py index ada823dd2..4c258eb34 100644 --- a/qualtran/bloqs/hubbard_model.py +++ b/qualtran/bloqs/hubbard_model.py @@ -56,9 +56,9 @@ from qualtran import BoundedQUInt, QAny, QBit, Register, Signature from qualtran._infra.gate_with_registers import SpecializedSingleQubitControlledGate, total_bits -from qualtran.bloqs.arithmetic import AddConstantMod from qualtran.bloqs.basic_gates import CSwap from qualtran.bloqs.mcmt.and_bloq import MultiAnd +from qualtran.bloqs.mod_arithmetic import ModAddK from qualtran.bloqs.multiplexers.apply_gate_to_lth_target import ApplyGateToLthQubit from qualtran.bloqs.multiplexers.selected_majorana_fermion import SelectedMajoranaFermion from qualtran.bloqs.qubitization_walk_operator import QubitizationWalkOperator @@ -319,7 +319,7 @@ def decompose_from_registers( yield from [cirq.X(*V), cirq.H(*alpha).controlled_by(*V), cirq.CX(*V, *beta), cirq.X(*V)] yield cirq.Circuit(cirq.CNOT.on_each([*zip([*p_x, *p_y, *alpha], [*q_x, *q_y, *beta])])) yield CSwap.make_on(ctrl=temp[:1], x=q_x, y=q_y) - yield AddConstantMod(len(q_x), self.x_dim, add_val=1, cvs=[0, 0]).on(*U, *V, *q_x) + yield ModAddK(len(q_x), self.x_dim, add_val=1, cvs=[0, 0]).on(*U, *V, *q_x) yield CSwap.make_on(ctrl=temp[:1], x=q_x, y=q_y) and_target = context.qubit_manager.qalloc(1) diff --git a/qualtran/bloqs/mod_arithmetic/__init__.py b/qualtran/bloqs/mod_arithmetic/__init__.py index cd4fce6f6..4c20e61bf 100644 --- a/qualtran/bloqs/mod_arithmetic/__init__.py +++ b/qualtran/bloqs/mod_arithmetic/__init__.py @@ -12,4 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ._shims import CModAdd, CModNeg, CModSub, ModAdd, ModDbl, ModInv, ModMul, ModNeg, ModSub +from ._shims import CModAdd, CModNeg, CModSub, ModDbl, ModInv, ModMul, ModNeg, ModSub +from .mod_addition import CModAddK, CtrlScaleModAdd, ModAdd, ModAddK diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb new file mode 100644 index 000000000..73efeeb8e --- /dev/null +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.ipynb @@ -0,0 +1,294 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "f4f08a50", + "metadata": { + "cq.autogen": "title_cell" + }, + "source": [ + "# Modular Addition" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3d60a8e4", + "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": "9198f9e7", + "metadata": { + "cq.autogen": "ModAdd.bloq_doc.md" + }, + "source": [ + "## `ModAdd`\n", + "An n-bit modular addition gate.\n", + "\n", + "Implements |x>|y> => |x>|y + x % p> using $4n$ Toffoli\n", + "gates.\n", + "\n", + "This gate can also operate on integers in the Montgomery form.\n", + "\n", + "#### Parameters\n", + " - `bitsize`: Number of bits used to represent each integer.\n", + " - `mod`: The modulus for the addition. \n", + "\n", + "#### Registers\n", + " - `x`: A bitsize-sized input register (register x above).\n", + " - `y`: A bitsize-sized input/output register (register y above). \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). Construction from Figure 6a and cost summary in Figure 8.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9205f875", + "metadata": { + "cq.autogen": "ModAdd.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import ModAdd" + ] + }, + { + "cell_type": "markdown", + "id": "58858002", + "metadata": { + "cq.autogen": "ModAdd.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbc5506a", + "metadata": { + "cq.autogen": "ModAdd.mod_add" + }, + "outputs": [], + "source": [ + "n, p = sympy.symbols('n p')\n", + "mod_add = ModAdd(n, mod=p)" + ] + }, + { + "cell_type": "markdown", + "id": "8bc5ef97", + "metadata": { + "cq.autogen": "ModAdd.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "70ba06f7", + "metadata": { + "cq.autogen": "ModAdd.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([mod_add],\n", + " ['`mod_add`'])" + ] + }, + { + "cell_type": "markdown", + "id": "20a1f851", + "metadata": { + "cq.autogen": "ModAdd.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad2fb92f", + "metadata": { + "cq.autogen": "ModAdd.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "mod_add_g, mod_add_sigma = mod_add.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(mod_add_g)\n", + "show_counts_sigma(mod_add_sigma)" + ] + }, + { + "cell_type": "markdown", + "id": "732e7df7", + "metadata": { + "cq.autogen": "ModAddK.bloq_doc.md" + }, + "source": [ + "## `ModAddK`\n", + "Applies U(add, M)|x> = |(x + add) % M> if x < M else |x>.\n", + "\n", + "Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t.\n", + " 1. If integer `x` < `mod`: output is `|(x + add) % M>`\n", + " 2. If integer `x` >= `mod`: output is `|x>`.\n", + "\n", + "This condition is needed to ensure that the mapping of all input basis states (i.e. input\n", + "states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus\n", + "the gate is reversible.\n", + "\n", + "Also supports controlled version of the gate by specifying a per qubit control value as a tuple\n", + "of integers passed as `cvs`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6b978c53", + "metadata": { + "cq.autogen": "ModAddK.bloq_doc.py" + }, + "outputs": [], + "source": [ + "from qualtran.bloqs.mod_arithmetic import ModAddK" + ] + }, + { + "cell_type": "markdown", + "id": "60a3bb0e", + "metadata": { + "cq.autogen": "ModAddK.example_instances.md" + }, + "source": [ + "### Example Instances" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4d032451", + "metadata": { + "cq.autogen": "ModAddK.mod_add_k" + }, + "outputs": [], + "source": [ + "n, m, k = sympy.symbols('n m k')\n", + "mod_add_k = ModAddK(bitsize=n, mod=m, add_val=k)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad89d8b6", + "metadata": { + "cq.autogen": "ModAddK.mod_add_k_small" + }, + "outputs": [], + "source": [ + "mod_add_k_small = ModAddK(bitsize=4, mod=7, add_val=1)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "438ea8db", + "metadata": { + "cq.autogen": "ModAddK.mod_add_k_large" + }, + "outputs": [], + "source": [ + "mod_add_k_large = ModAddK(bitsize=64, mod=500, add_val=23)" + ] + }, + { + "cell_type": "markdown", + "id": "b9054563", + "metadata": { + "cq.autogen": "ModAddK.graphical_signature.md" + }, + "source": [ + "#### Graphical Signature" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5e74b988", + "metadata": { + "cq.autogen": "ModAddK.graphical_signature.py" + }, + "outputs": [], + "source": [ + "from qualtran.drawing import show_bloqs\n", + "show_bloqs([mod_add_k, mod_add_k_small, mod_add_k_large],\n", + " ['`mod_add_k`', '`mod_add_k_small`', '`mod_add_k_large`'])" + ] + }, + { + "cell_type": "markdown", + "id": "d91f3f90", + "metadata": { + "cq.autogen": "ModAddK.call_graph.md" + }, + "source": [ + "### Call Graph" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "494b8b4a", + "metadata": { + "cq.autogen": "ModAddK.call_graph.py" + }, + "outputs": [], + "source": [ + "from qualtran.resource_counting.generalizers import ignore_split_join\n", + "mod_add_k_g, mod_add_k_sigma = mod_add_k.call_graph(max_depth=1, generalizer=ignore_split_join)\n", + "show_call_graph(mod_add_k_g)\n", + "show_counts_sigma(mod_add_k_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.11.7" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/qualtran/bloqs/factoring/mod_add.py b/qualtran/bloqs/mod_arithmetic/mod_addition.py similarity index 56% rename from qualtran/bloqs/factoring/mod_add.py rename to qualtran/bloqs/mod_arithmetic/mod_addition.py index 26e22b261..6a53ad06c 100644 --- a/qualtran/bloqs/factoring/mod_add.py +++ b/qualtran/bloqs/mod_arithmetic/mod_addition.py @@ -17,182 +17,65 @@ import numpy as np import sympy -from attrs import frozen - -from qualtran import Bloq, QBit, QMontgomeryUInt, QUInt, Register, Signature, Soquet, SoquetT -from qualtran.bloqs.arithmetic.addition import Add, SimpleAddConstant +from attrs import field, frozen + +from qualtran import ( + Bloq, + bloq_example, + BloqDocSpec, + GateWithRegisters, + QBit, + QMontgomeryUInt, + QUInt, + Register, + Signature, + Soquet, + SoquetT, +) +from qualtran.bloqs.arithmetic.addition import Add, AddK from qualtran.bloqs.arithmetic.comparison import LinearDepthGreaterThan -from qualtran.bloqs.basic_gates import TGate, XGate +from qualtran.bloqs.basic_gates import XGate from qualtran.drawing import Circle, Text, TextBox, WireSymbol from qualtran.resource_counting import BloqCountT, SympySymbolAllocator from qualtran.simulation.classical_sim import ClassicalValT if TYPE_CHECKING: - from qualtran import BloqBuilder, Soquet - - -@frozen -class CtrlScaleModAdd(Bloq): - """Perform y += x*k mod m for constant k, m and quantum x, y. - - Args: - k: The constant integer to scale `x` before adding into `y`. - mod: The modulus of the addition - bitsize: The size of the two registers. - - Registers: - 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. - """ - - k: Union[int, sympy.Expr] - mod: Union[int, sympy.Expr] - bitsize: Union[int, sympy.Expr] - - @cached_property - def signature(self) -> 'Signature': - return Signature( - [ - Register('ctrl', QBit()), - Register('x', QUInt(self.bitsize)), - Register('y', QUInt(self.bitsize)), - ] - ) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - k = ssa.new_symbol('k') - return {(CtrlModAddK(k=k, bitsize=self.bitsize, mod=self.mod), self.bitsize)} - - def on_classical_vals( - self, ctrl: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' - ) -> Dict[str, 'ClassicalValT']: - if ctrl == 0: - return {'ctrl': 0, 'x': x, 'y': y} - - assert ctrl == 1, 'Bad ctrl value.' - y_out = (y + x * self.k) % self.mod - return {'ctrl': ctrl, 'x': x, 'y': y_out} - - def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -> 'WireSymbol': - if reg is None: - return Text(f'y += x*{self.k} % {self.mod}') - if reg.name == 'ctrl': - return Circle() - if reg.name == 'x': - return TextBox('x') - if reg.name == 'y': - return TextBox(f'y += x*{self.k}') - raise ValueError(f"Unknown register name {reg.name}") - - -@frozen -class CtrlModAddK(Bloq): - """Perform x += k mod m for constant k, m and quantum x. - - Args: - k: The integer to add to `x`. - mod: The modulus for the addition. - bitsize: The bitsize of the `x` register. - - Registers: - ctrl: The control bit - x: The register to perform the in-place modular addition. - """ - - k: Union[int, sympy.Expr] - mod: Union[int, sympy.Expr] - bitsize: Union[int, sympy.Expr] - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - k = ssa.new_symbol('k') - return {(CtrlAddK(k=k, bitsize=self.bitsize), 5)} - - def wire_symbol( - self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() - ) -> 'WireSymbol': - if reg is None: - return Text(f'x += {self.k} % {self.mod}') - return super().wire_symbol(reg, idx) - - -@frozen -class CtrlAddK(Bloq): - """Perform x += k for constant k and quantum x. - - Args: - k: The integer to add to `x`. - bitsize: The bitsize of the `x` register. - - Registers: - ctrl: The control bit - x: The register to perform the addition. - """ - - k: Union[int, sympy.Expr] - bitsize: Union[int, sympy.Expr] - - @cached_property - def signature(self) -> 'Signature': - return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) - - def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: - return {(TGate(), 2 * self.bitsize)} - - def wire_symbol( - self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() - ) -> 'WireSymbol': - if reg is None: - return Text(f'x += {self.k}') - return super().wire_symbol(reg, idx) + from qualtran import BloqBuilder @frozen -class MontgomeryModAdd(Bloq): +class ModAdd(Bloq): r"""An n-bit modular addition gate. - This gate is designed to operate on integers in the Montgomery form. Implements |x>|y> => |x>|y + x % p> using $4n$ Toffoli gates. + This gate can also operate on integers in the Montgomery form. + Args: bitsize: Number of bits used to represent each integer. - p: The modulus for the addition. + mod: The modulus for the addition. Registers: x: A bitsize-sized input register (register x above). y: A bitsize-sized input/output register (register y above). References: - [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585) Fig 6a and 8 + [How to compute a 256-bit elliptic curve private key with only 50 million Toffoli gates](https://arxiv.org/abs/2306.08585). + Construction from Figure 6a and cost summary in Figure 8. """ bitsize: int - p: int + mod: int @cached_property def signature(self) -> 'Signature': - return Signature( - [ - Register('x', QMontgomeryUInt(self.bitsize)), - Register('y', QMontgomeryUInt(self.bitsize)), - ] - ) + return Signature([Register('x', QUInt(self.bitsize)), Register('y', QUInt(self.bitsize))]) def on_classical_vals( self, x: 'ClassicalValT', y: 'ClassicalValT' ) -> Dict[str, 'ClassicalValT']: - y += x - y -= self.p - - if y < 0: - y += self.p - - return {'x': x, 'y': y} + return {'x': x, 'y': (x + y) % self.mod} def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[str, 'SoquetT']: # Allocate ancilla bits for use in addition. @@ -220,9 +103,7 @@ 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( - SimpleAddConstant(bitsize=self.bitsize + 1, k=-1 * self.p, signed=True, cvs=()), x=y - ) + y = bb.add(AddK(bitsize=self.bitsize + 1, k=-1 * self.mod, signed=True, cvs=()), x=y) # Controlled addition of classical constant p if the sign of y after the last addition is # negative. @@ -232,9 +113,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ sign_split = bb.split(sign) sign_split, y = bb.add( - SimpleAddConstant(bitsize=self.bitsize, k=self.p, signed=True, cvs=(1,)), - x=y, - ctrls=sign_split, + AddK(bitsize=self.bitsize, k=self.mod, signed=True, cvs=(1,)), x=y, ctrls=sign_split ) sign = bb.join(sign_split) @@ -252,9 +131,202 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet, y: Soquet) -> Dict[ # Return the output registers. return {'x': x, 'y': y} + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + return { + (Add(QUInt(self.bitsize + 1)), 1), + (AddK(self.bitsize + 1, k=-self.mod), 1), + (AddK(self.bitsize, k=self.mod).controlled(), 1), + (LinearDepthGreaterThan(self.bitsize), 1), + (XGate(), 1), + } + + def short_name(self) -> str: + return f'y = y + x mod {self.mod}' + + +@bloq_example +def _mod_add() -> ModAdd: + n, p = sympy.symbols('n p') + mod_add = ModAdd(n, mod=p) + return mod_add + + +_MOD_ADD_DOC = BloqDocSpec(bloq_cls=ModAdd, examples=[_mod_add]) + + +@frozen(auto_attribs=True) +class ModAddK(GateWithRegisters): + """Applies U(add, M)|x> = |(x + add) % M> if x < M else |x>. + + Applies modular addition to input register `|x>` given parameters `mod` and `add_val` s.t. + 1. If integer `x` < `mod`: output is `|(x + add) % M>` + 2. If integer `x` >= `mod`: output is `|x>`. + + This condition is needed to ensure that the mapping of all input basis states (i.e. input + states |0>, |1>, ..., |2 ** bitsize - 1) to corresponding output states is bijective and thus + the gate is reversible. + + Also supports controlled version of the gate by specifying a per qubit control value as a tuple + of integers passed as `cvs`. + """ + + bitsize: int + mod: int = field() + add_val: int = 1 + cvs: Tuple[int, ...] = field( + converter=lambda v: (v,) if isinstance(v, int) else tuple(v), default=() + ) + + @mod.validator + def _validate_mod(self, attribute, value): + if isinstance(value, sympy.Expr) or isinstance(self.bitsize, sympy.Expr): + return + if not 1 <= value <= 2**self.bitsize: + raise ValueError(f"mod: {value} must be between [1, {2 ** self.bitsize}].") + + @cached_property + def signature(self) -> Signature: + if self.cvs: + return Signature( + [ + Register('ctrl', QBit(), shape=(len(self.cvs),)), + Register('x', QUInt(self.bitsize)), + ] + ) + return Signature([Register('x', QUInt(self.bitsize))]) + + def _classical_unctrled(self, target_val: int): + if target_val < self.mod: + return (target_val + self.add_val) % self.mod + return target_val + + def on_classical_vals( + self, *, x: int, ctrl: Optional[int] = None + ) -> Dict[str, 'ClassicalValT']: + out = self._classical_unctrled(x) + if self.cvs: + assert ctrl is not None + if ctrl == int(''.join(str(x) for x in self.cvs), 2): + return {'ctrl': ctrl, 'x': out} + else: + return {'ctrl': ctrl, 'x': x} + + assert ctrl is None + return {'x': out} + + def __pow__(self, power: int) -> 'ModAddK': + return ModAddK(self.bitsize, self.mod, add_val=self.add_val * power, cvs=self.cvs) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + return {(Add(QUInt(self.bitsize), QUInt(self.bitsize)), 5)} + + +@bloq_example +def _mod_add_k() -> ModAddK: + n, m, k = sympy.symbols('n m k') + mod_add_k = ModAddK(bitsize=n, mod=m, add_val=k) + return mod_add_k + + +@bloq_example +def _mod_add_k_small() -> ModAddK: + mod_add_k_small = ModAddK(bitsize=4, mod=7, add_val=1) + return mod_add_k_small + + +@bloq_example +def _mod_add_k_large() -> ModAddK: + mod_add_k_large = ModAddK(bitsize=64, mod=500, add_val=23) + return mod_add_k_large + + +_MOD_ADD_K_DOC = BloqDocSpec( + bloq_cls=ModAddK, examples=[_mod_add_k, _mod_add_k_small, _mod_add_k_large] +) + + +@frozen +class CModAddK(Bloq): + """Perform x += k mod m for constant k, m and quantum x. + + Args: + k: The integer to add to `x`. + mod: The modulus for the addition. + bitsize: The bitsize of the `x` register. + + Registers: + ctrl: The control bit + x: The register to perform the in-place modular addition. + """ + + k: Union[int, sympy.Expr] + mod: Union[int, sympy.Expr] + bitsize: Union[int, sympy.Expr] + + @cached_property + def signature(self) -> 'Signature': + return Signature([Register('ctrl', QBit()), Register('x', QUInt(self.bitsize))]) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + k = ssa.new_symbol('k') + return {(AddK(k=k, bitsize=self.bitsize).controlled(), 5)} + + def short_name(self) -> str: + return f'x += {self.k} % {self.mod}' + + +@frozen +class CtrlScaleModAdd(Bloq): + """Perform y += x*k mod m for constant k, m and quantum x, y. + + Args: + k: The constant integer to scale `x` before adding into `y`. + mod: The modulus of the addition + bitsize: The size of the two registers. + + Registers: + 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. + """ + + k: Union[int, sympy.Expr] + mod: Union[int, sympy.Expr] + bitsize: Union[int, sympy.Expr] + + @cached_property + def signature(self) -> 'Signature': + return Signature( + [ + Register('ctrl', QBit()), + Register('x', QUInt(self.bitsize)), + Register('y', QUInt(self.bitsize)), + ] + ) + + def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']: + k = ssa.new_symbol('k') + return {(CModAddK(k=k, bitsize=self.bitsize, mod=self.mod), self.bitsize)} + + def on_classical_vals( + self, ctrl: 'ClassicalValT', x: 'ClassicalValT', y: 'ClassicalValT' + ) -> Dict[str, 'ClassicalValT']: + if ctrl == 0: + return {'ctrl': 0, 'x': x, 'y': y} + + assert ctrl == 1, 'Bad ctrl value.' + y_out = (y + x * self.k) % self.mod + return {'ctrl': ctrl, 'x': x, 'y': y_out} + def wire_symbol( self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple() ) -> 'WireSymbol': if reg is None: - return Text(f'y = y + x mod {self.p}') - return super().wire_symbol(reg, idx) + return Text(f"mod {self.mod}") + if reg.name == 'ctrl': + return Circle() + if reg.name == 'x': + return TextBox('x') + if reg.name == 'y': + return TextBox(f'y += x*{self.k}') + raise ValueError(f"Unknown register {reg}") diff --git a/qualtran/bloqs/mod_arithmetic/mod_addition_test.py b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py new file mode 100644 index 000000000..7a227c901 --- /dev/null +++ b/qualtran/bloqs/mod_arithmetic/mod_addition_test.py @@ -0,0 +1,71 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from qualtran import QUInt +from qualtran.bloqs.arithmetic import Add +from qualtran.bloqs.mod_arithmetic import CModAddK, CtrlScaleModAdd, ModAdd, ModAddK +from qualtran.cirq_interop.t_complexity_protocol import TComplexity +from qualtran.testing import assert_valid_bloq_decomposition + + +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)} + + +def test_add_mod_n_protocols(): + with pytest.raises(ValueError, match="must be between"): + _ = ModAddK(3, 10) + add_one = ModAddK(3, 5, 1) + add_two = ModAddK(3, 5, 2, cvs=[1, 0]) + + assert add_one == ModAddK(3, 5, 1) + assert add_one != add_two + assert hash(add_one) != hash(add_two) + assert add_two.cvs == (1, 0) + + +def add_constant_mod_n_ref_t_complexity_(b: ModAddK) -> TComplexity: + # Rough cost as given in https://arxiv.org/abs/1905.09749 + return 5 * Add(QUInt(b.bitsize)).t_complexity() + + +@pytest.mark.parametrize('bitsize', [3, 9]) +def test_add_mod_n_gate_counts(bitsize): + bloq = ModAddK(bitsize, mod=8, add_val=2, cvs=[0, 1]) + 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) + assert_valid_bloq_decomposition(bloq) diff --git a/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py b/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py index 1c1750fe5..72c558615 100644 --- a/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py +++ b/qualtran/bloqs/state_preparation/state_preparation_via_rotation_test.py @@ -251,10 +251,12 @@ def test_state_preparation_via_rotation_multi_qubit_ctrl( assert np.allclose(result, correct) +@pytest.mark.notebook def test_notebook(): execute_notebook("state_preparation_via_rotation") +@pytest.mark.notebook def test_notebook_tutorial(): execute_notebook("state_preparation_via_rotation_tutorial") diff --git a/qualtran/cirq_interop/t_complexity_protocol.py b/qualtran/cirq_interop/t_complexity_protocol.py index 1259f1388..155cc7a01 100644 --- a/qualtran/cirq_interop/t_complexity_protocol.py +++ b/qualtran/cirq_interop/t_complexity_protocol.py @@ -128,7 +128,7 @@ def _from_directly_countable(stc: Any) -> Optional[TComplexity]: quregs = get_named_qubits(stc.signature) qm = cirq.SimpleQubitManager() op, _ = stc.as_cirq_op(qubit_manager=qm, **quregs) - return t_complexity(cirq.decompose_once(op)) + return t_complexity(op) if cirq.num_qubits(stc) == 1 and cirq.has_unitary(stc): # Single qubit rotation operation. @@ -211,7 +211,6 @@ def _t_complexity_from_strategies( def _t_complexity_for_gate_or_op( gate_or_op: Union[cirq.Gate, cirq.Operation, Bloq] ) -> Optional[TComplexity]: - if isinstance(gate_or_op, cirq.Operation) and gate_or_op.gate is not None: gate_or_op = gate_or_op.gate diff --git a/qualtran/serialization/resolver_dict.py b/qualtran/serialization/resolver_dict.py index ade1ce2ce..3b779f46d 100644 --- a/qualtran/serialization/resolver_dict.py +++ b/qualtran/serialization/resolver_dict.py @@ -67,7 +67,6 @@ import qualtran.bloqs.chemistry.trotter.trotterized_unitary import qualtran.bloqs.data_loading.qrom import qualtran.bloqs.data_loading.select_swap_qrom -import qualtran.bloqs.factoring.mod_add import qualtran.bloqs.factoring.mod_exp import qualtran.bloqs.factoring.mod_mul import qualtran.bloqs.factoring.mod_sub @@ -84,6 +83,7 @@ import qualtran.bloqs.mean_estimation.arctan import qualtran.bloqs.mean_estimation.complex_phase_oracle import qualtran.bloqs.mean_estimation.mean_estimation_operator +import qualtran.bloqs.mod_arithmetic import qualtran.bloqs.multiplexers.apply_gate_to_lth_target import qualtran.bloqs.multiplexers.select_pauli_lcu import qualtran.bloqs.multiplexers.selected_majorana_fermion @@ -122,9 +122,9 @@ "qualtran._infra.composite_bloq.CompositeBloq": CompositeBloq, "qualtran.cirq_interop._cirq_to_bloq.CirqGateAsBloq": CirqGateAsBloq, "qualtran.bloqs.arithmetic.addition.Add": qualtran.bloqs.arithmetic.addition.Add, - "qualtran.bloqs.arithmetic.addition.AddConstantMod": qualtran.bloqs.arithmetic.addition.AddConstantMod, + "qualtran.bloqs.arithmetic.mod_addition.ModAddK": qualtran.bloqs.mod_arithmetic.ModAddK, "qualtran.bloqs.arithmetic.addition.OutOfPlaceAdder": qualtran.bloqs.arithmetic.addition.OutOfPlaceAdder, - "qualtran.bloqs.arithmetic.addition.SimpleAddConstant": qualtran.bloqs.arithmetic.addition.SimpleAddConstant, + "qualtran.bloqs.arithmetic.addition.AddK": qualtran.bloqs.arithmetic.AddK, "qualtran.bloqs.arithmetic.comparison.BiQubitsMixer": qualtran.bloqs.arithmetic.comparison.BiQubitsMixer, "qualtran.bloqs.arithmetic.comparison.EqualsAConstant": qualtran.bloqs.arithmetic.comparison.EqualsAConstant, "qualtran.bloqs.arithmetic.comparison.GreaterThan": qualtran.bloqs.arithmetic.comparison.GreaterThan, @@ -247,10 +247,9 @@ "qualtran.bloqs.chemistry.trotter.trotterized_unitary": qualtran.bloqs.chemistry.trotter.trotterized_unitary, "qualtran.bloqs.data_loading.qrom.QROM": qualtran.bloqs.data_loading.qrom.QROM, "qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM": qualtran.bloqs.data_loading.select_swap_qrom.SelectSwapQROM, - "qualtran.bloqs.factoring.mod_add.CtrlAddK": qualtran.bloqs.factoring.mod_add.CtrlAddK, - "qualtran.bloqs.factoring.mod_add.CtrlModAddK": qualtran.bloqs.factoring.mod_add.CtrlModAddK, - "qualtran.bloqs.factoring.mod_add.CtrlScaleModAdd": qualtran.bloqs.factoring.mod_add.CtrlScaleModAdd, - "qualtran.bloqs.factoring.mod_add.MontgomeryModAdd": qualtran.bloqs.factoring.mod_add.MontgomeryModAdd, + "qualtran.bloqs.mod_arithmetic.CModAddK": qualtran.bloqs.mod_arithmetic.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.factoring.mod_exp.ModExp": qualtran.bloqs.factoring.mod_exp.ModExp, "qualtran.bloqs.factoring.mod_mul.CtrlModMul": qualtran.bloqs.factoring.mod_mul.CtrlModMul, "qualtran.bloqs.factoring.mod_mul.MontgomeryModDbl": qualtran.bloqs.factoring.mod_mul.MontgomeryModDbl, diff --git a/qualtran/surface_code/msft_resource_estimator_interop.ipynb b/qualtran/surface_code/msft_resource_estimator_interop.ipynb index d91685a3c..af6337271 100644 --- a/qualtran/surface_code/msft_resource_estimator_interop.ipynb +++ b/qualtran/surface_code/msft_resource_estimator_interop.ipynb @@ -64,9 +64,9 @@ " from qsharp.estimator import LogicalCounts\n", " from qualtran._infra.gate_with_registers import get_named_qubits\n", "\n", - " # This way of calculating the number of qubits could be inaccurate if a sub_bloq\n", - " # allocates new qubits but this should be a good estimate in most cases.\n", - " num_qubits = sum(len(r) for r in get_named_qubits(bloq.signature).values())\n", + " # This way of calculating the number of qubits will be inaccurate if a sub_bloq\n", + " # allocates new qubits.\n", + " num_qubits = bloq.signature.n_qubits()\n", " complexity = bloq.t_complexity()\n", "\n", " return LogicalCounts({\n", @@ -154,7 +154,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.8" + "version": "3.11.7" } }, "nbformat": 4, diff --git a/qualtran/surface_code/msft_resource_estimator_interop_test.py b/qualtran/surface_code/msft_resource_estimator_interop_test.py new file mode 100644 index 000000000..00d043444 --- /dev/null +++ b/qualtran/surface_code/msft_resource_estimator_interop_test.py @@ -0,0 +1,21 @@ +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +import qualtran.testing as qlt_testing + + +@pytest.mark.notebook +def test_msft_resource_estimator_interop_notebook(): + qlt_testing.execute_notebook('msft_resource_estimator_interop')