Skip to content

Commit

Permalink
cleanup redundant code in MultiControlledX, delegate to `MultiContr…
Browse files Browse the repository at this point in the history
…olledPauli`
  • Loading branch information
anurudhp committed Jul 12, 2024
1 parent 3391ca6 commit 5c1e62c
Show file tree
Hide file tree
Showing 5 changed files with 16 additions and 149 deletions.
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

0 comments on commit 5c1e62c

Please sign in to comment.