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
100 changes: 32 additions & 68 deletions qualtran/bloqs/arithmetic/addition.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import math
from functools import cached_property
from typing import (
cast,
Dict,
Iterable,
Iterator,
Expand Down Expand Up @@ -45,7 +44,6 @@
DecomposeTypeError,
GateWithRegisters,
QBit,
QDType,
QInt,
QMontgomeryUInt,
QUInt,
Expand All @@ -61,12 +59,12 @@
from qualtran.bloqs.mcmt.multi_control_multi_target_pauli import MultiControlX
from qualtran.cirq_interop import decompose_from_cirq_style_method
from qualtran.drawing import directional_text_box, Text
from qualtran.symbolics import is_symbolic, SymbolicInt

if TYPE_CHECKING:
from qualtran.drawing import WireSymbol
from qualtran.resource_counting import BloqCountT, SympySymbolAllocator
from qualtran.simulation.classical_sim import ClassicalValT
from qualtran.symbolics import SymbolicInt


@frozen
Expand Down Expand Up @@ -361,63 +359,6 @@ def _cvs_converter(vv):
return tuple(int(v) for v in vv)


@frozen
class XorK(Bloq):
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.
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:
x: A quantum register of type `self.dtype` (see above).
ctrls: A sequence of control qubits (only when `cvs` is non-empty).
"""
dtype: QDType
k: SymbolicInt
cvs: Tuple[int, ...] = field(converter=_cvs_converter, default=())

@cached_property
def signature(self) -> 'Signature':
return Signature(
((Register('ctrls', QBit(), shape=(len(self.cvs),)),) if len(self.cvs) > 0 else ())
+ (Register('x', self.dtype),)
)

@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}")

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

for i, bit in enumerate(self.dtype.to_bits(self.k)):
if bit == 1:
if len(self.cvs) > 0 and ctrls is not None:
ctrls, xs[i] = bb.add(MultiControlX(cvs=self.cvs), ctrls=ctrls, x=xs[i])
else:
xs[i] = bb.add(XGate(), q=xs[i])

soqs['x'] = bb.join(xs)
if ctrls is not None:
soqs['ctrls'] = ctrls
return soqs

def build_call_graph(self, ssa: 'SympySymbolAllocator') -> Set['BloqCountT']:
bit_flip_bloq = MultiControlX(cvs=self.cvs) if len(self.cvs) > 0 else XGate()
num_flips = self.bitsize if self.is_symbolic() else sum(self.dtype.to_bits(self.k))
return {(bit_flip_bloq, num_flips)}


@frozen
class AddK(Bloq):
r"""Takes |x> to |x + k> for a classical integer `k`.
Expand Down Expand Up @@ -490,23 +431,46 @@ def build_composite_bloq(
ctrls = None
k = bb.allocate(dtype=x.reg.dtype)

xor_k_bloq = XorK(x.reg.dtype, self.k, self.cvs)
if ctrls is not None:
ctrls, k = bb.add(xor_k_bloq, ctrls=ctrls, x=k)
# Get binary representation of k and split k into separate wires.
k_split = bb.split(k)
if self.signed:
binary_rep = QInt(self.bitsize).to_bits(self.k)
else:
k = bb.add(xor_k_bloq, x=k)
binary_rep = QUInt(self.bitsize).to_bits(self.k)

# Apply XGates to qubits in k where the bitstring has value 1. Apply CNOTs when the gate is
# controlled.
for i in range(self.bitsize):
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]
)
else:
k_split[i] = bb.add(XGate(), q=k_split[i])
tanujkhattar marked this conversation as resolved.
Show resolved Hide resolved

# Rejoin the qubits representing k for in-place addition.
k = bb.join(k_split, dtype=x.reg.dtype)
if not isinstance(x.reg.dtype, (QInt, QUInt, QMontgomeryUInt)):
raise ValueError(
"Only QInt, QUInt and QMontgomerUInt types are supported for composite addition."
)
k, x = bb.add(Add(x.reg.dtype, x.reg.dtype), a=k, b=x)

if ctrls is not None:
ctrls, k = bb.add(xor_k_bloq, ctrls=ctrls, x=k)
else:
k = bb.add(xor_k_bloq, x=k)
# Resplit the k qubits in order to undo the original bit flips to go from the binary
# representation back to the zero state.
k_split = bb.split(k)
for i in range(self.bitsize):
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]
)
else:
k_split[i] = bb.add(XGate(), q=k_split[i])

# Free the ancilla qubits.
k = bb.join(k_split, dtype=x.reg.dtype)
bb.free(k)

# Return the output registers.
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
Loading
Loading