From 81f33c3bc6333afea62f2dfced474ee0d0c8ff0f Mon Sep 17 00:00:00 2001 From: Anurudh Peduri Date: Wed, 10 Jul 2024 23:48:21 -0700 Subject: [PATCH] cleanup redundant code in `MultiControlledX`, delegate to `MultiControlledPauli` --- qualtran/bloqs/arithmetic/addition.py | 4 +- qualtran/bloqs/arithmetic/comparison.py | 2 +- qualtran/bloqs/factoring/mod_sub.py | 4 +- .../mcmt/multi_control_multi_target_pauli.py | 151 ++---------------- .../multi_control_multi_target_pauli_test.py | 4 +- 5 files changed, 16 insertions(+), 149 deletions(-) diff --git a/qualtran/bloqs/arithmetic/addition.py b/qualtran/bloqs/arithmetic/addition.py index 882722ec78..1f44a0b3e6 100644 --- a/qualtran/bloqs/arithmetic/addition.py +++ b/qualtran/bloqs/arithmetic/addition.py @@ -444,7 +444,7 @@ def build_composite_bloq( if binary_rep[i] == 1: if len(self.cvs) > 0 and ctrls is not None: ctrls, k_split[i] = bb.add( - MultiControlX(cvs=self.cvs), ctrls=ctrls, x=k_split[i] + MultiControlX(cvs=self.cvs), controls=ctrls, target=k_split[i] ) else: k_split[i] = bb.add(XGate(), q=k_split[i]) @@ -464,7 +464,7 @@ def build_composite_bloq( if binary_rep[i] == 1: if len(self.cvs) > 0 and ctrls is not None: ctrls, k_split[i] = bb.add( - MultiControlX(cvs=self.cvs), ctrls=ctrls, x=k_split[i] + MultiControlX(cvs=self.cvs), controls=ctrls, target=k_split[i] ) else: k_split[i] = bb.add(XGate(), q=k_split[i]) diff --git a/qualtran/bloqs/arithmetic/comparison.py b/qualtran/bloqs/arithmetic/comparison.py index 5cd7599e5e..76ee5edd16 100644 --- a/qualtran/bloqs/arithmetic/comparison.py +++ b/qualtran/bloqs/arithmetic/comparison.py @@ -754,7 +754,7 @@ def build_composite_bloq( # We use a specially controlled Toffolli gate to implement GreaterThan. # If a is 1 and b is 0 then a > b and we can flip the target bit. ctrls = np.asarray([a, b]) - ctrls, target = bb.add(MultiControlX(cvs=(1, 0)), ctrls=ctrls, x=target) + ctrls, target = bb.add(MultiControlX(cvs=(1, 0)), controls=ctrls, target=target) a, b = ctrls # Return the output registers. return {'a': a, 'b': b, 'target': target} diff --git a/qualtran/bloqs/factoring/mod_sub.py b/qualtran/bloqs/factoring/mod_sub.py index 653e730ad2..1d305cb7d6 100644 --- a/qualtran/bloqs/factoring/mod_sub.py +++ b/qualtran/bloqs/factoring/mod_sub.py @@ -133,7 +133,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque # representing 0. cvs = tuple([0] * self.bitsize) x_split = bb.split(x) - x_split, ctrl = bb.add(MultiControlX(cvs=cvs), ctrls=x_split, x=ctrl) + x_split, ctrl = bb.add(MultiControlX(cvs=cvs), controls=x_split, target=ctrl) x = bb.join(x_split) # Bitflips all qubits if the ctrl bit is set to 1 (the input x register is not in the 0 @@ -153,7 +153,7 @@ def build_composite_bloq(self, bb: 'BloqBuilder', x: Soquet) -> Dict[str, 'Soque # Perform a multi-controlled bitflip on the ancilla bit if the state of x is the bitstring # representing 0. x_split = bb.split(x) - x_split, ctrl = bb.add(MultiControlX(cvs=cvs), ctrls=x_split, x=ctrl) + x_split, ctrl = bb.add(MultiControlX(cvs=cvs), controls=x_split, target=ctrl) x = bb.join(x_split) # Return the ancilla qubit to the 0 state and free it. diff --git a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py index f2d13e9d0c..b886b013f0 100644 --- a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py +++ b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py @@ -21,19 +21,8 @@ from attrs import field, frozen from numpy.typing import NDArray -from qualtran import ( - Bloq, - bloq_example, - BloqBuilder, - BloqDocSpec, - GateWithRegisters, - QBit, - Register, - Signature, - Soquet, - SoquetT, -) -from qualtran.bloqs.basic_gates import CNOT, Toffoli, XGate +from qualtran import bloq_example, BloqDocSpec, GateWithRegisters, QBit, Register, Signature +from qualtran.bloqs.basic_gates import XGate from qualtran.bloqs.mcmt.and_bloq import _to_tuple_or_has_length, And, is_symbolic, MultiAnd from qualtran.symbolics import HasLength, SymbolicInt @@ -240,135 +229,13 @@ def _ccpauli_symb() -> MultiControlPauli: @frozen -class MultiControlX(Bloq): - r"""Implements multi-control, single-target X gate as a bloq using $n-2$ clean ancillas. - - Args: - cvs: A tuple of control values. Each entry specifies whether that control line is a - "positive" control (`cv[i]=1`) or a "negative" control (`cv[i]=0`). - - Registers: - ctrls: An input register with n 1-bit controls corresponding to the size of the control - variable settings above. - x: A 1-bit input register bit-flipped based on the values in the ctrls register. +class MultiControlX(MultiControlPauli): + r"""Implements multi-control, single-target X gate. - References: - [Constructing Large CNOTS](https://algassert.com/circuits/2015/06/05/Constructing-Large-Controlled-Nots.html). - Section title "$n−2$ Ancilla Bits", Figure titled $C^n$NOT from $n-2$ zeroed bits. + See :class:`MultiControlPauli` for implementation and costs. """ + target_gate: cirq.Pauli = field(init=False) - cvs: Tuple[int, ...] = field(converter=lambda v: (v,) if isinstance(v, int) else tuple(v)) - - @cached_property - def signature(self) -> 'Signature': - assert len(self.cvs) > 0 - return Signature([Register('ctrls', QBit(), shape=(len(self.cvs),)), Register('x', QBit())]) - - def on_classical_vals( - self, ctrls: 'ClassicalValT', x: 'ClassicalValT' - ) -> Dict[str, 'ClassicalValT']: - if np.all(self.cvs == ctrls): - x = (x + 1) % 2 - - return {'ctrls': ctrls, 'x': x} - - def build_composite_bloq( - self, bb: 'BloqBuilder', ctrls: NDArray[Soquet], x: SoquetT # type: ignore[type-var] - ) -> Dict[str, 'SoquetT']: - # n = number of controls in the bloq. - n = len(self.cvs) - - # Base case 1: CNOT() - if n == 1: - # Allows for 0-controlled implementations. - if self.cvs[0] == 0: - ctrls[0] = bb.add(XGate(), q=ctrls[0]) - ctrls[0], x = bb.add(CNOT(), ctrl=ctrls[0], target=x) - if self.cvs[0] == 0: - ctrls[0] = bb.add(XGate(), q=ctrls[0]) - return {'ctrls': ctrls, 'x': x} - - # Base case 2: Toffoli() - if n == 2: - # Allows for 0-controlled implementations. - for i in range(len(self.cvs)): - if self.cvs[i] == 0: - ctrls[i] = bb.add(XGate(), q=ctrls[i]) - - ctrls, x = bb.add(Toffoli(), ctrl=ctrls, target=x) - - for i in range(len(self.cvs)): - if self.cvs[i] == 0: - ctrls[i] = bb.add(XGate(), q=ctrls[i]) - - return {'ctrls': ctrls, 'x': x} - - # Iterative case: MultiControlledX - # Allocate necessary ancilla bits. - ancillas = bb.allocate(n=(n - 2)) - - # Split the ancilla bits for bloq decomposition connections. - ancillas_split = bb.split(ancillas) - - # Initialize a list to store the grouped Toffoli gate controls. - toffoli_ctrls = [] - - # Allows for 0-controlled implementations. - for i in range(len(self.cvs)): - if self.cvs[i] == 0: - ctrls[i] = bb.add(XGate(), q=ctrls[i]) - - # Iterative case 0: The first Toffoli gate is controlled by the first two controls. - toffoli_ctrl = [ctrls[0], ctrls[1]] - toffoli_ctrl, ancillas_split[0] = bb.add( - Toffoli(), ctrl=toffoli_ctrl, target=ancillas_split[0] - ) - # Save the Toffoli controls for later uncomputation. - toffoli_ctrls.append(toffoli_ctrl) - - # Iterative case i: The middle Toffoli gates with controls ancilla and control. - for i in range(n - 3): - toffoli_ctrl = [ancillas_split[i], ctrls[i + 2]] - toffoli_ctrl, ancillas_split[i + 1] = bb.add( - Toffoli(), ctrl=toffoli_ctrl, target=ancillas_split[i + 1] - ) - toffoli_ctrls.append(toffoli_ctrl) - - # Iteritave case n - 1: The final Toffoli gate which is not uncomputed. - toffoli_ctrl = [ancillas_split[n - 3], ctrls[n - 1]] - toffoli_ctrl, x = bb.add(Toffoli(), ctrl=toffoli_ctrl, target=x) - - # Start storing end states back into ancilla and control qubits. - ancillas_split[n - 3] = toffoli_ctrl[0] - ctrls[n - 1] = toffoli_ctrl[1] - - # Iterative case i: Uncomputation of middle Toffoli gates. - for i in range(n - 3): - toffoli_ctrl = toffoli_ctrls.pop() - toffoli_ctrl, ancillas_split[n - 3 - i] = bb.add( - Toffoli(), ctrl=toffoli_ctrl, target=ancillas_split[n - 3 - i] - ) - ancillas_split[n - 4 - i] = toffoli_ctrl[0] - ctrls[n - 2 - i] = toffoli_ctrl[1] - - # Iterative case 0: Uncomputation of first Toffoli gate. - toffoli_ctrl = toffoli_ctrls.pop() - toffoli_ctrl, ancillas_split[0] = bb.add( - Toffoli(), ctrl=toffoli_ctrl, target=ancillas_split[0] - ) - ctrls[0:2] = toffoli_ctrl - - # Uncompute 0-controlled qubits. - for i in range(len(self.cvs)): - if self.cvs[i] == 0: - ctrls[i] = bb.add(XGate(), q=ctrls[i]) - - # Join and free ancilla qubits. - ancillas = bb.join(ancillas_split) - bb.free(ancillas) - - # Return the output registers. - return {'ctrls': ctrls, 'x': x} - - def pretty_name(self) -> str: - return f'C^{len(self.cvs)}-NOT' + @target_gate.default + def _X(self): + return cirq.X diff --git a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli_test.py b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli_test.py index 39fb4f781d..bf35162883 100644 --- a/qualtran/bloqs/mcmt/multi_control_multi_target_pauli_test.py +++ b/qualtran/bloqs/mcmt/multi_control_multi_target_pauli_test.py @@ -112,8 +112,8 @@ def test_classical_multi_control_pauli_target_x(cvs, x, ctrls, result): def test_classical_multi_control_x(cvs, x, ctrls, result): bloq = MultiControlX(cvs=cvs) cbloq = bloq.decompose_bloq() - bloq_classical = bloq.call_classically(x=x, ctrls=ctrls) - cbloq_classical = cbloq.call_classically(x=x, ctrls=ctrls) + bloq_classical = bloq.call_classically(target=x, controls=ctrls) + cbloq_classical = cbloq.call_classically(target=x, controls=ctrls) assert len(bloq_classical) == len(cbloq_classical) for i in range(len(bloq_classical)):