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

Permutation Bloq #1110

Merged
merged 18 commits into from
Jul 17, 2024
Merged
9 changes: 9 additions & 0 deletions dev_tools/autogenerate-bloqs-notebooks-v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
from qualtran_dev_tools.jupyter_autogen import NotebookSpecV2, render_notebook

import qualtran.bloqs.arithmetic.addition
import qualtran.bloqs.arithmetic.permutation
import qualtran.bloqs.arithmetic.sorting
import qualtran.bloqs.arithmetic.subtraction
import qualtran.bloqs.basic_gates.swap
Expand Down Expand Up @@ -380,6 +381,14 @@
qualtran.bloqs.arithmetic.conversions._TO_CONTG_INDX,
],
),
NotebookSpecV2(
title='Permutations',
module=qualtran.bloqs.arithmetic.permutation,
bloq_specs=[
qualtran.bloqs.arithmetic.permutation._PERMUTATION_DOC,
qualtran.bloqs.arithmetic.permutation._PERMUTATION_CYCLE_DOC,
],
),
]

MOD_ARITHMETIC = [
Expand Down
1 change: 1 addition & 0 deletions docs/bloqs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Bloqs Library
arithmetic/comparison.ipynb
arithmetic/sorting.ipynb
arithmetic/conversions.ipynb
arithmetic/permutation.ipynb

.. toctree::
:maxdepth: 2
Expand Down
120 changes: 120 additions & 0 deletions qualtran/bloqs/arithmetic/bitwise.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Copyright 2024 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 functools import cached_property
from typing import cast, Dict, Optional, Set, Tuple, TYPE_CHECKING

import numpy as np
from attrs import frozen

from qualtran import (
bloq_example,
BloqBuilder,
DecomposeTypeError,
QBit,
QDType,
QUInt,
Register,
Signature,
Soquet,
SoquetT,
)
from qualtran._infra.gate_with_registers import SpecializedSingleQubitControlledGate
from qualtran.bloqs.basic_gates import CNOT, XGate
from qualtran.resource_counting.generalizers import ignore_split_join
from qualtran.symbolics import is_symbolic, SymbolicInt

if TYPE_CHECKING:
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator


def _cvs_converter(vv):
if isinstance(vv, (int, np.integer)):
return (int(vv),)
return tuple(int(v) for v in vv)


@frozen
class XorK(SpecializedSingleQubitControlledGate):
r"""Maps |x> to |x \oplus k> for a constant k.

Args:
dtype: Data type of the input register `x`.
k: The classical integer value to be XOR-ed to x.
control_val: an optional single bit control, apply the operation when
the control qubit equals the `control_val`.

Registers:
x: A quantum register of type `self.dtype` (see above).
ctrl: A sequence of control qubits (only when `control_val` is not None).
"""
dtype: QDType
k: SymbolicInt
control_val: Optional[int] = None

@cached_property
def signature(self) -> 'Signature':
return Signature([*self.control_registers, Register('x', self.dtype)])

@cached_property
def control_registers(self) -> Tuple[Register, ...]:
if self.control_val is not None:
return (Register('ctrl', QBit()),)
return ()

@cached_property
def bitsize(self) -> SymbolicInt:
return self.dtype.num_qubits

def is_symbolic(self):
return is_symbolic(self.k, self.dtype)

def build_composite_bloq(self, bb: 'BloqBuilder', **soqs: 'SoquetT') -> Dict[str, 'SoquetT']:
if self.is_symbolic():
raise DecomposeTypeError(f"cannot decompose symbolic {self}")

# TODO clean this up once https://github.com/quantumlib/Qualtran/pull/1137 is merged
ctrl = soqs.pop('ctrl', None)

xs = bb.split(cast(Soquet, soqs.pop('x')))

for i, bit in enumerate(self.dtype.to_bits(self.k)):
if bit == 1:
if ctrl is not None:
ctrl, xs[i] = bb.add(CNOT(), ctrl=ctrl, target=xs[i])
else:
xs[i] = bb.add(XGate(), q=xs[i])

soqs['x'] = bb.join(xs)

if ctrl is not None:
soqs['ctrl'] = ctrl
return soqs

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
bit_flip_bloq = CNOT() if self.control_val is not None else XGate()
num_flips = self.bitsize if self.is_symbolic() else sum(self.dtype.to_bits(self.k))
return {(bit_flip_bloq, num_flips)}


@bloq_example(generalizer=ignore_split_join)
def _xork() -> XorK:
xork = XorK(QUInt(8), 0b01010111)
return xork


@bloq_example(generalizer=ignore_split_join)
def _cxork() -> XorK:
cxork = XorK(QUInt(8), 0b01010111).controlled()
assert isinstance(cxork, XorK)
return cxork
38 changes: 38 additions & 0 deletions qualtran/bloqs/arithmetic/bitwise_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2024 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 qualtran import QUInt
from qualtran.bloqs.arithmetic.bitwise import _cxork, _xork, XorK


def test_examples(bloq_autotester):
bloq_autotester(_cxork)
bloq_autotester(_xork)


def test_xork_classical_sim():
k = 0b01101010
bloq = XorK(QUInt(9), k)
cbloq = bloq.controlled()

for x in bloq.dtype.get_classical_domain():
(x_out,) = bloq.call_classically(x=x)
assert x_out == x ^ k

ctrl_out, x_out = cbloq.call_classically(ctrl=0, x=x)
assert ctrl_out == 0
assert x_out == x

ctrl_out, x_out = cbloq.call_classically(ctrl=1, x=x)
assert ctrl_out == 1
assert x_out == x ^ k
2 changes: 1 addition & 1 deletion qualtran/bloqs/arithmetic/comparison.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,7 @@
},
"source": [
"## `EqualsAConstant`\n",
"Implements $U_a|x\\rangle = U_a|x\\rangle|z\\rangle = |x\\rangle |z \\land (x = a)\\rangle$\n",
"Implements $U_a|x\\rangle|z\\rangle = |x\\rangle |z \\oplus (x = a)\\rangle$\n",
"\n",
"The bloq_counts and t_complexity are derived from:\n",
"https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html#equality-as-a-special-case\n",
Expand Down
42 changes: 33 additions & 9 deletions qualtran/bloqs/arithmetic/comparison.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
Bloq,
bloq_example,
BloqDocSpec,
DecomposeTypeError,
GateWithRegisters,
QAny,
QBit,
Expand All @@ -48,12 +49,12 @@
Soquet,
SoquetT,
)
from qualtran.bloqs.basic_gates import CNOT, TGate, XGate
from qualtran.bloqs.basic_gates import CNOT, XGate
from qualtran.bloqs.mcmt.and_bloq import And, MultiAnd
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlX
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlPauli, MultiControlX
from qualtran.drawing import WireSymbol
from qualtran.drawing.musical_score import Text, TextBox
from qualtran.symbolics import is_symbolic, SymbolicInt
from qualtran.symbolics import HasLength, is_symbolic, SymbolicInt

if TYPE_CHECKING:
from qualtran import BloqBuilder
Expand Down Expand Up @@ -926,7 +927,7 @@ def _gt_k() -> GreaterThanConstant:

@frozen
class EqualsAConstant(Bloq):
r"""Implements $U_a|x\rangle = U_a|x\rangle|z\rangle = |x\rangle |z \land (x = a)\rangle$
r"""Implements $U_a|x\rangle|z\rangle = |x\rangle |z \oplus (x = a)\rangle$

The bloq_counts and t_complexity are derived from:
https://qualtran.readthedocs.io/en/latest/bloqs/comparison_gates.html#equality-as-a-special-case
Expand All @@ -940,8 +941,8 @@ class EqualsAConstant(Bloq):
target: Register to hold result of comparison.
"""

bitsize: int
val: int
bitsize: SymbolicInt
val: SymbolicInt

@cached_property
def signature(self) -> Signature:
Expand All @@ -956,10 +957,33 @@ def wire_symbol(self, reg: Optional[Register], idx: Tuple[int, ...] = tuple()) -
return TextBox(f"⨁(x = {self.val})")
raise ValueError(f'Unknown register symbol {reg.name}')

def is_symbolic(self):
return is_symbolic(self.bitsize, self.val)

@property
def bits_k(self) -> Union[tuple[int, ...], HasLength]:
if self.is_symbolic():
return HasLength(self.bitsize)

assert not isinstance(self.bitsize, sympy.Expr)
assert not isinstance(self.val, sympy.Expr)
return tuple(QUInt(self.bitsize).to_bits(self.val))

def build_composite_bloq(
self, bb: 'BloqBuilder', x: 'Soquet', target: 'Soquet'
) -> Dict[str, 'SoquetT']:
if is_symbolic(self.bitsize):
raise DecomposeTypeError(f"Cannot decompose {self} with symbolic {self.bitsize=}")

xs = bb.split(x)
xs, target = bb.add(
MultiControlPauli(self.bits_k, target_gate=cirq.X), controls=xs, target=target
)
x = bb.join(xs)
return {'x': x, 'target': target}

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
# See: https://github.com/quantumlib/Qualtran/issues/219
# See: https://github.com/quantumlib/Qualtran/issues/217
return {(TGate(), 4 * (self.bitsize - 1))}
return {(MultiControlPauli(self.bits_k, target_gate=cirq.X), 1)}


def _make_equals_a_constant():
Expand Down
4 changes: 3 additions & 1 deletion qualtran/bloqs/arithmetic/comparison_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,9 @@ def test_equals_a_constant():
qlt_testing.assert_wire_symbols_match_expected(
EqualsAConstant(bitsize, 17), ['In(x)', '⨁(x = 17)']
)
assert t_complexity(EqualsAConstant(bitsize, 17)) == TComplexity(t=4 * (bitsize - 1))
assert t_complexity(EqualsAConstant(bitsize, 17)) == TComplexity(
t=4 * (bitsize - 1), clifford=65
)


@pytest.mark.notebook
Expand Down
Loading
Loading