Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cleanup redundant code in MultiControlledX, use MultiControlledPauli #1134

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions qualtran/bloqs/arithmetic/addition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand All @@ -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])
Expand Down
2 changes: 1 addition & 1 deletion qualtran/bloqs/arithmetic/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
4 changes: 2 additions & 2 deletions qualtran/bloqs/factoring/mod_sub.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down
151 changes: 9 additions & 142 deletions qualtran/bloqs/mcmt/multi_control_multi_target_pauli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
4 changes: 2 additions & 2 deletions qualtran/bloqs/mcmt/multi_control_multi_target_pauli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)):
Expand Down
Loading