Skip to content

Commit

Permalink
Create controlled greater than bloq (#1283)
Browse files Browse the repository at this point in the history
  • Loading branch information
NoureldinYosri authored Aug 16, 2024
1 parent dacaf45 commit 7143031
Show file tree
Hide file tree
Showing 10 changed files with 356 additions and 7 deletions.
1 change: 1 addition & 0 deletions dev_tools/autogenerate-bloqs-notebooks-v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@
qualtran.bloqs.arithmetic.comparison._BI_QUBITS_MIXER_DOC,
qualtran.bloqs.arithmetic.comparison._SQ_CMP_DOC,
qualtran.bloqs.arithmetic.comparison._LEQ_DOC,
qualtran.bloqs.arithmetic.comparison._CLinearDepthGreaterThan_DOC,
],
),
NotebookSpecV2(
Expand Down
1 change: 1 addition & 0 deletions qualtran/bloqs/arithmetic/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from qualtran.bloqs.arithmetic.bitwise import BitwiseNot, Xor, XorK
from qualtran.bloqs.arithmetic.comparison import (
BiQubitsMixer,
CLinearDepthGreaterThan,
EqualsAConstant,
GreaterThan,
GreaterThanConstant,
Expand Down
6 changes: 5 additions & 1 deletion qualtran/bloqs/arithmetic/addition.py
Original file line number Diff line number Diff line change
Expand Up @@ -291,10 +291,14 @@ def adjoint(self) -> 'OutOfPlaceAdder':
return evolve(self, is_adjoint=not self.is_adjoint)

def on_classical_vals(
self, *, a: 'ClassicalValT', b: 'ClassicalValT'
self, *, a: 'ClassicalValT', b: 'ClassicalValT', c: Optional['ClassicalValT'] = None
) -> Dict[str, 'ClassicalValT']:
if isinstance(self.bitsize, sympy.Expr):
raise ValueError(f'Classical simulation is not support for symbolic bloq {self}')
if self.is_adjoint:
assert c is not None
return {'a': a, 'b': b}
assert c is None
return {
'a': a,
'b': b,
Expand Down
13 changes: 13 additions & 0 deletions qualtran/bloqs/arithmetic/addition_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,3 +377,16 @@ def test_classical_add_k_signed(bitsize, k, x, cvs, ctrls, result):
@pytest.mark.notebook
def test_notebook():
qlt_testing.execute_notebook('addition')


@pytest.mark.parametrize('bitsize', range(1, 5))
def test_outofplaceadder_classical_action(bitsize):
b = OutOfPlaceAdder(bitsize)
cb = b.decompose_bloq()
for x, y in itertools.product(range(2**bitsize), repeat=2):
assert b.call_classically(a=x, b=y) == cb.call_classically(a=x, b=y)

b = OutOfPlaceAdder(bitsize).adjoint()
cb = b.decompose_bloq()
for x, y in itertools.product(range(2**bitsize), repeat=2):
assert b.call_classically(a=x, b=y, c=x + y) == cb.call_classically(a=x, b=y, c=x + y)
2 changes: 1 addition & 1 deletion qualtran/bloqs/arithmetic/bitwise.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ def adjoint(self) -> 'BitwiseNot':
return self

def build_composite_bloq(self, bb: 'BloqBuilder', x: 'Soquet') -> dict[str, 'SoquetT']:
x = bb.add(OnEach(self.dtype.num_qubits, XGate()), q=x)
x = bb.add(OnEach(self.dtype.num_qubits, XGate(), self.dtype), q=x)
return {'x': x}

def wire_symbol(
Expand Down
120 changes: 119 additions & 1 deletion qualtran/bloqs/arithmetic/comparison.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -800,6 +800,124 @@
"show_call_graph(leq_g)\n",
"show_counts_sigma(leq_sigma)"
]
},
{
"cell_type": "markdown",
"id": "27543be2",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.bloq_doc.md"
},
"source": [
"## `CLinearDepthGreaterThan`\n",
"Controlled greater than between two integers.\n",
"\n",
"Implements $\\ket{c}\\ket{a}\\ket{b}\\ket{t} \\xrightarrow[]{} \\ket{c}\\ket{a}\\ket{b}\\ket{t ⨁ ((a > b)c)}>$\n",
"using $n+2$ Toffoli gates.\n",
"\n",
"Note: the true cost is $n+1$ but an extra Toffoli comes from OutOfPlaceAdder which operates\n",
"on $n+1$ qubits rather than $n$. Changing the definition of OutOfPlaceAdder will remove this\n",
"extra Toffoli.\n",
"\n",
"This comparator relies on the fact that ~(~b + a) = b - a. If a > b, then b - a < 0. We\n",
"implement it by flipping all the bits in b, computing the first half of the addition circuit,\n",
"copying out the carry, and uncomputing the addition circuit.\n",
"\n",
"#### Parameters\n",
" - `dtype`: type of the integer registers.\n",
" - `cv`: ctrl value at which the bloq is active. \n",
"\n",
"#### Registers\n",
" - `a`: dtype input registers.\n",
" - `b`: dtype input registers.\n",
" - `target`: A single bit output register to store the result of a > b. \n",
"\n",
"#### References\n",
" - [Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648). \n",
" - [Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2306.08585). page 7.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d4533e31",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.bloq_doc.py"
},
"outputs": [],
"source": [
"from qualtran.bloqs.arithmetic import CLinearDepthGreaterThan"
]
},
{
"cell_type": "markdown",
"id": "9dcfa76a",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.example_instances.md"
},
"source": [
"### Example Instances"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "6b7ec12f",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.clineardepthgreaterthan_example"
},
"outputs": [],
"source": [
"clineardepthgreaterthan_example = CLinearDepthGreaterThan(QInt(5))"
]
},
{
"cell_type": "markdown",
"id": "70bc70c6",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.graphical_signature.md"
},
"source": [
"#### Graphical Signature"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "b6bf0dce",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.graphical_signature.py"
},
"outputs": [],
"source": [
"from qualtran.drawing import show_bloqs\n",
"show_bloqs([clineardepthgreaterthan_example],\n",
" ['`clineardepthgreaterthan_example`'])"
]
},
{
"cell_type": "markdown",
"id": "9a2d7925",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.call_graph.md"
},
"source": [
"### Call Graph"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "30e9a77b",
"metadata": {
"cq.autogen": "CLinearDepthGreaterThan.call_graph.py"
},
"outputs": [],
"source": [
"from qualtran.resource_counting.generalizers import ignore_split_join\n",
"clineardepthgreaterthan_example_g, clineardepthgreaterthan_example_sigma = clineardepthgreaterthan_example.call_graph(max_depth=1, generalizer=ignore_split_join)\n",
"show_call_graph(clineardepthgreaterthan_example_g)\n",
"show_counts_sigma(clineardepthgreaterthan_example_sigma)"
]
}
],
"metadata": {
Expand All @@ -818,7 +936,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.7"
"version": "3.11.9"
}
},
"nbformat": 4,
Expand Down
145 changes: 144 additions & 1 deletion qualtran/bloqs/arithmetic/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,25 @@
GateWithRegisters,
QAny,
QBit,
QInt,
QMontgomeryUInt,
QUInt,
Register,
Side,
Signature,
Soquet,
SoquetT,
)
from qualtran.bloqs.arithmetic.addition import OutOfPlaceAdder
from qualtran.bloqs.arithmetic.bitwise import BitwiseNot
from qualtran.bloqs.arithmetic.conversions.sign_extension import SignExtend
from qualtran.bloqs.basic_gates import CNOT, XGate
from qualtran.bloqs.bookkeeping import Cast
from qualtran.bloqs.mcmt import MultiControlX
from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd
from qualtran.drawing import WireSymbol
from qualtran.drawing.musical_score import Text, TextBox
from qualtran.drawing.musical_score import Circle, Text, TextBox
from qualtran.resource_counting.generalizers import ignore_split_join
from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt

if TYPE_CHECKING:
Expand Down Expand Up @@ -1017,3 +1024,139 @@ def _eq_k() -> EqualsAConstant:


_EQUALS_K_DOC = BloqDocSpec(bloq_cls=EqualsAConstant, examples=[_eq_k])


@frozen
class CLinearDepthGreaterThan(Bloq):
r"""Controlled greater than between two integers.
Implements $\ket{c}\ket{a}\ket{b}\ket{t} \xrightarrow[]{} \ket{c}\ket{a}\ket{b}\ket{t ⨁ ((a > b)c)}>$
using $n+2$ Toffoli gates.
Note: the true cost is $n+1$ but an extra Toffoli comes from OutOfPlaceAdder which operates
on $n+1$ qubits rather than $n$. Changing the definition of OutOfPlaceAdder will remove this
extra Toffoli.
This comparator relies on the fact that ~(~b + a) = b - a. If a > b, then b - a < 0. We
implement it by flipping all the bits in b, computing the first half of the addition circuit,
copying out the carry, and uncomputing the addition circuit.
Args:
dtype: type of the integer registers.
cv: ctrl value at which the bloq is active.
Registers:
a: dtype input registers.
b: dtype input registers.
target: A single bit output register to store the result of a > b.
References:
[Halving the cost of quantum addition](https://arxiv.org/abs/1709.06648).
[Improved quantum circuits for elliptic curve discrete logarithms](https://arxiv.org/abs/2306.08585)
page 7.
"""

dtype: Union[QInt, QUInt, QMontgomeryUInt]
cv: int = 1

@cached_property
def signature(self) -> Signature:
return Signature.build_from_dtypes(ctrl=QBit(), a=self.dtype, b=self.dtype, target=QBit())

def wire_symbol(
self, reg: Optional['Register'], idx: Tuple[int, ...] = tuple()
) -> 'WireSymbol':
if reg is None:
return Text('')
if reg.name == 'ctrl':
return Circle(filled=self.cv == 1)
if reg.name == "a":
return TextBox('a')
if reg.name == "b":
return TextBox('b')
if reg.name == "target":
return TextBox('t⨁((a>b)c)')
raise ValueError(f'Unknown register name {reg.name}')

def build_composite_bloq(
self, bb: 'BloqBuilder', ctrl: 'Soquet', a: 'Soquet', b: 'Soquet', target: 'Soquet'
) -> Dict[str, 'SoquetT']:

if isinstance(self.dtype, QInt):
a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=a)
b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), x=b)
else:
a = bb.join(np.concatenate([[bb.allocate(1)], bb.split(a)]))
b = bb.join(np.concatenate([[bb.allocate(1)], bb.split(b)]))

dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1)
b = bb.add(BitwiseNot(dtype), x=b) # b := -b-1
a = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=a)
b = bb.add(Cast(dtype, QUInt(dtype.bitsize)), reg=b)
a, b, c = bb.add(OutOfPlaceAdder(self.dtype.bitsize + 1), a=a, b=b) # c := a - b - 1
c = bb.add(BitwiseNot(QUInt(dtype.bitsize + 1)), x=c) # c := b - a

# Update `target`
c_arr = bb.split(c)
# The sign bit is usually the 0th bit however since we already appended an extra bit
# to the input registers and OutOfPlaceAdder is unsigned and stores the result in
# number bits + 1 (i.e. we are adding two extra bits), the sign bit becomes the 1st bit
# with the 0th bit indicating whether an overflow happened or not.
(ctrl, c_arr[1]), target = bb.add(
MultiControlX((self.cv, 1)), controls=np.array([ctrl, c_arr[1]]), target=target
)
c = bb.join(c_arr)

# Uncompute
c = bb.add(BitwiseNot(QUInt(dtype.bitsize + 1)), x=c)
a, b = bb.add(OutOfPlaceAdder(self.dtype.bitsize + 1).adjoint(), a=a, b=b, c=c)
a = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=a)
b = bb.add(Cast(dtype, QUInt(dtype.bitsize)).adjoint(), reg=b)
b = bb.add(BitwiseNot(dtype), x=b)

if isinstance(self.dtype, QInt):
a = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=a)
b = bb.add(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), x=b)
else:
a_arr = bb.split(a)
a = bb.join(a_arr[1:])
b_arr = bb.split(b)
b = bb.join(b_arr[1:])
bb.free(a_arr[0])
bb.free(b_arr[0])
return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target}

def on_classical_vals(
self, ctrl: int, a: int, b: int, target: int
) -> Dict[str, 'ClassicalValT']:
if ctrl == self.cv:
return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target ^ (a > b)}
return {'ctrl': ctrl, 'a': a, 'b': b, 'target': target}

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
signed_ops = []
if isinstance(self.dtype, QInt):
signed_ops = [
(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)), 2),
(SignExtend(self.dtype, QInt(self.dtype.bitsize + 1)).adjoint(), 2),
]
dtype = attrs.evolve(self.dtype, bitsize=self.dtype.bitsize + 1)
return {
(BitwiseNot(dtype), 2),
(BitwiseNot(QUInt(dtype.bitsize + 1)), 2),
(OutOfPlaceAdder(self.dtype.bitsize + 1).adjoint(), 1),
(OutOfPlaceAdder(self.dtype.bitsize + 1), 1),
(MultiControlX((self.cv, 1)), 1),
}.union(signed_ops)


@bloq_example(generalizer=ignore_split_join)
def _clineardepthgreaterthan_example() -> CLinearDepthGreaterThan:
clineardepthgreaterthan_example = CLinearDepthGreaterThan(QInt(5))
return clineardepthgreaterthan_example


_CLinearDepthGreaterThan_DOC = BloqDocSpec(
bloq_cls=CLinearDepthGreaterThan, examples=[_clineardepthgreaterthan_example]
)
Loading

0 comments on commit 7143031

Please sign in to comment.